Skip to main content

polyglot_sql/dialects/
mod.rs

1//! SQL Dialect System
2//!
3//! This module implements the dialect abstraction layer that enables SQL transpilation
4//! between more than 30 SQL dialects. Each dialect encapsulates three concerns:
5//!
6//! - **Tokenization**: Dialect-specific lexing rules (e.g., BigQuery uses backtick quoting,
7//!   MySQL uses backtick for identifiers, TSQL uses square brackets).
8//! - **Generation**: How AST nodes are rendered back to SQL text, including identifier quoting
9//!   style, function name casing, and syntax variations.
10//! - **Transformation**: AST-level rewrites that convert dialect-specific constructs to/from
11//!   a normalized form (e.g., Snowflake `SQUARE(x)` becomes `POWER(x, 2)`).
12//!
13//! The primary entry point is [`Dialect::get`], which returns a configured [`Dialect`] instance
14//! for a given [`DialectType`]. From there, callers can [`parse`](Dialect::parse),
15//! [`generate`](Dialect::generate), [`transform`](Dialect::transform), or
16//! [`transpile`](Dialect::transpile) to another dialect in a single call.
17//!
18//! Each concrete dialect (e.g., `PostgresDialect`, `BigQueryDialect`) implements the
19//! [`DialectImpl`] trait, which provides configuration hooks and expression-level transforms.
20//! Dialect modules live in submodules of this module and are re-exported here.
21
22mod generic; // Always compiled
23#[cfg(feature = "transpile")]
24mod normalization;
25
26#[cfg(feature = "dialect-athena")]
27mod athena;
28#[cfg(feature = "dialect-bigquery")]
29mod bigquery;
30#[cfg(feature = "dialect-clickhouse")]
31mod clickhouse;
32#[cfg(feature = "dialect-cockroachdb")]
33mod cockroachdb;
34#[cfg(feature = "dialect-databricks")]
35mod databricks;
36#[cfg(feature = "dialect-datafusion")]
37mod datafusion;
38#[cfg(feature = "dialect-doris")]
39mod doris;
40#[cfg(feature = "dialect-dremio")]
41mod dremio;
42#[cfg(feature = "dialect-drill")]
43mod drill;
44#[cfg(feature = "dialect-druid")]
45mod druid;
46#[cfg(feature = "dialect-duckdb")]
47mod duckdb;
48#[cfg(feature = "dialect-dune")]
49mod dune;
50#[cfg(feature = "dialect-exasol")]
51mod exasol;
52#[cfg(feature = "dialect-fabric")]
53mod fabric;
54#[cfg(feature = "dialect-hana")]
55mod hana;
56#[cfg(feature = "dialect-hive")]
57mod hive;
58#[cfg(feature = "dialect-materialize")]
59mod materialize;
60#[cfg(feature = "dialect-mysql")]
61mod mysql;
62#[cfg(feature = "dialect-oracle")]
63mod oracle;
64#[cfg(feature = "dialect-postgresql")]
65mod postgres;
66#[cfg(feature = "dialect-presto")]
67mod presto;
68#[cfg(feature = "dialect-redshift")]
69mod redshift;
70#[cfg(feature = "dialect-risingwave")]
71mod risingwave;
72#[cfg(feature = "dialect-singlestore")]
73mod singlestore;
74#[cfg(feature = "dialect-snowflake")]
75mod snowflake;
76#[cfg(feature = "dialect-solr")]
77mod solr;
78#[cfg(feature = "dialect-spark")]
79mod spark;
80#[cfg(feature = "dialect-sqlite")]
81mod sqlite;
82#[cfg(feature = "dialect-starrocks")]
83mod starrocks;
84#[cfg(feature = "dialect-tableau")]
85mod tableau;
86#[cfg(feature = "dialect-teradata")]
87mod teradata;
88#[cfg(feature = "dialect-tidb")]
89mod tidb;
90#[cfg(feature = "dialect-trino")]
91mod trino;
92#[cfg(any(feature = "dialect-tsql", feature = "dialect-fabric"))]
93mod tsql;
94#[cfg(feature = "dialect-vertica")]
95mod vertica;
96
97pub use generic::GenericDialect; // Always available
98
99#[cfg(feature = "dialect-athena")]
100pub use athena::AthenaDialect;
101#[cfg(feature = "dialect-bigquery")]
102pub use bigquery::BigQueryDialect;
103#[cfg(feature = "dialect-clickhouse")]
104pub use clickhouse::ClickHouseDialect;
105#[cfg(feature = "dialect-cockroachdb")]
106pub use cockroachdb::CockroachDBDialect;
107#[cfg(feature = "dialect-databricks")]
108pub use databricks::DatabricksDialect;
109#[cfg(feature = "dialect-datafusion")]
110pub use datafusion::DataFusionDialect;
111#[cfg(feature = "dialect-doris")]
112pub use doris::DorisDialect;
113#[cfg(feature = "dialect-dremio")]
114pub use dremio::DremioDialect;
115#[cfg(feature = "dialect-drill")]
116pub use drill::DrillDialect;
117#[cfg(feature = "dialect-druid")]
118pub use druid::DruidDialect;
119#[cfg(feature = "dialect-duckdb")]
120pub use duckdb::DuckDBDialect;
121#[cfg(feature = "dialect-dune")]
122pub use dune::DuneDialect;
123#[cfg(feature = "dialect-exasol")]
124pub use exasol::ExasolDialect;
125#[cfg(feature = "dialect-fabric")]
126pub use fabric::FabricDialect;
127#[cfg(feature = "dialect-hana")]
128pub use hana::HanaDialect;
129#[cfg(feature = "dialect-hive")]
130pub use hive::HiveDialect;
131#[cfg(feature = "dialect-materialize")]
132pub use materialize::MaterializeDialect;
133#[cfg(feature = "dialect-mysql")]
134pub use mysql::MySQLDialect;
135#[cfg(feature = "dialect-oracle")]
136pub use oracle::OracleDialect;
137#[cfg(feature = "dialect-postgresql")]
138pub use postgres::PostgresDialect;
139#[cfg(feature = "dialect-presto")]
140pub use presto::PrestoDialect;
141#[cfg(feature = "dialect-redshift")]
142pub use redshift::RedshiftDialect;
143#[cfg(feature = "dialect-risingwave")]
144pub use risingwave::RisingWaveDialect;
145#[cfg(feature = "dialect-singlestore")]
146pub use singlestore::SingleStoreDialect;
147#[cfg(feature = "dialect-snowflake")]
148pub use snowflake::SnowflakeDialect;
149#[cfg(feature = "dialect-solr")]
150pub use solr::SolrDialect;
151#[cfg(feature = "dialect-spark")]
152pub use spark::SparkDialect;
153#[cfg(feature = "dialect-sqlite")]
154pub use sqlite::SQLiteDialect;
155#[cfg(feature = "dialect-starrocks")]
156pub use starrocks::StarRocksDialect;
157#[cfg(feature = "dialect-tableau")]
158pub use tableau::TableauDialect;
159#[cfg(feature = "dialect-teradata")]
160pub use teradata::TeradataDialect;
161#[cfg(feature = "dialect-tidb")]
162pub use tidb::TiDBDialect;
163#[cfg(feature = "dialect-trino")]
164pub use trino::TrinoDialect;
165#[cfg(feature = "dialect-tsql")]
166pub use tsql::TSQLDialect;
167#[cfg(feature = "dialect-vertica")]
168pub use vertica::VerticaDialect;
169
170use crate::error::Result;
171#[cfg(feature = "transpile")]
172use crate::expressions::{
173    BinaryOp, Case, Cast, ColumnConstraint, DateBin, DateTimeField, Fetch, Function, Identifier,
174    Interval, IntervalUnit, IntervalUnitSpec, JoinKind, Literal, Offset, Over, Select, Subquery,
175    Top, Var, WindowFrame, WindowFrameBound, WindowFrameKind,
176};
177use crate::expressions::{DataType, Expression};
178#[cfg(any(
179    feature = "transpile",
180    feature = "ast-tools",
181    feature = "generate",
182    feature = "semantic"
183))]
184use crate::expressions::{From, FunctionBody, Join, Null, OrderBy, OutputClause, TableRef, With};
185#[cfg(feature = "transpile")]
186use crate::generator::UnsupportedLevel;
187#[cfg(feature = "generate")]
188use crate::generator::{Generator, GeneratorConfig};
189#[cfg(feature = "transpile")]
190use crate::guard::enforce_generate_ast;
191use crate::guard::{enforce_input, ComplexityGuardOptions, ParseOptions};
192#[cfg(feature = "transpile")]
193use crate::helper::find_new_name;
194use crate::parser::Parser;
195#[cfg(feature = "transpile")]
196use crate::tokens::TokenType;
197use crate::tokens::{Token, Tokenizer, TokenizerConfig};
198#[cfg(feature = "transpile")]
199use crate::traversal::{is_aggregate, ExpressionWalk};
200use serde::{Deserialize, Serialize};
201use std::collections::HashMap;
202#[cfg(feature = "transpile")]
203use std::collections::HashSet;
204use std::sync::{Arc, LazyLock, RwLock};
205
206/// Enumeration of all supported SQL dialects.
207///
208/// Each variant corresponds to a specific SQL database engine or query language.
209/// The `Generic` variant represents standard SQL with no dialect-specific behavior,
210/// and is used as the default when no dialect is specified.
211///
212/// Dialect names are case-insensitive when parsed from strings via [`FromStr`].
213/// Some dialects accept aliases (e.g., "mssql" and "sqlserver" both resolve to [`TSQL`](DialectType::TSQL)).
214#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
215#[cfg_attr(feature = "bindings", derive(ts_rs::TS))]
216#[serde(rename_all = "lowercase")]
217pub enum DialectType {
218    /// Standard SQL with no dialect-specific behavior (default).
219    Generic,
220    /// PostgreSQL -- advanced open-source relational database.
221    PostgreSQL,
222    /// MySQL -- widely-used open-source relational database (also accepts "mysql").
223    MySQL,
224    /// Google BigQuery -- serverless cloud data warehouse with unique syntax (backtick quoting, STRUCT types, QUALIFY).
225    BigQuery,
226    /// Snowflake -- cloud data platform with QUALIFY clause, FLATTEN, and variant types.
227    Snowflake,
228    /// DuckDB -- in-process analytical database with modern SQL extensions.
229    DuckDB,
230    /// SQLite -- lightweight embedded relational database.
231    SQLite,
232    /// Apache Hive -- data warehouse on Hadoop with HiveQL syntax.
233    Hive,
234    /// Apache Spark SQL -- distributed query engine (also accepts "spark2").
235    Spark,
236    /// Trino -- distributed SQL query engine (formerly PrestoSQL).
237    Trino,
238    /// PrestoDB -- distributed SQL query engine for big data.
239    Presto,
240    /// Amazon Redshift -- cloud data warehouse based on PostgreSQL.
241    Redshift,
242    /// Transact-SQL (T-SQL) -- Microsoft SQL Server and Azure SQL (also accepts "mssql", "sqlserver").
243    TSQL,
244    /// Oracle Database -- commercial relational database with PL/SQL extensions.
245    Oracle,
246    /// ClickHouse -- column-oriented OLAP database for real-time analytics.
247    ClickHouse,
248    /// Databricks SQL -- Spark-based lakehouse platform with QUALIFY support.
249    Databricks,
250    /// Amazon Athena -- serverless query service (hybrid Trino/Hive engine).
251    Athena,
252    /// Teradata -- enterprise data warehouse with proprietary SQL extensions.
253    Teradata,
254    /// Apache Doris -- real-time analytical database (MySQL-compatible).
255    Doris,
256    /// StarRocks -- sub-second OLAP database (MySQL-compatible).
257    StarRocks,
258    /// Materialize -- streaming SQL database built on differential dataflow.
259    Materialize,
260    /// RisingWave -- distributed streaming database with PostgreSQL compatibility.
261    RisingWave,
262    /// SingleStore (formerly MemSQL) -- distributed SQL database (also accepts "memsql").
263    SingleStore,
264    /// CockroachDB -- distributed SQL database with PostgreSQL compatibility (also accepts "cockroach").
265    CockroachDB,
266    /// TiDB -- distributed HTAP database with MySQL compatibility.
267    TiDB,
268    /// Apache Druid -- real-time analytics database.
269    Druid,
270    /// Apache Solr -- search platform with SQL interface.
271    Solr,
272    /// Tableau -- data visualization platform with its own SQL dialect.
273    Tableau,
274    /// Dune Analytics -- blockchain analytics SQL engine.
275    Dune,
276    /// Microsoft Fabric -- unified analytics platform (T-SQL based).
277    Fabric,
278    /// Apache Drill -- schema-free SQL query engine for big data.
279    Drill,
280    /// Dremio -- data lakehouse platform with Arrow-based query engine.
281    Dremio,
282    /// Exasol -- in-memory analytic database.
283    Exasol,
284    /// Apache DataFusion -- Arrow-based query engine with modern SQL extensions.
285    DataFusion,
286    /// SAP HANA Cloud and SAP HANA Platform SQL.
287    HANA,
288    /// Vertica (OpenText Analytics Database) -- columnar MPP analytic database.
289    Vertica,
290}
291
292impl DialectType {
293    /// Whether SELECT projections may use string literals as column aliases.
294    /// DuckDB permits this only when the alias has an explicit AS keyword.
295    pub(crate) const fn supports_string_aliases(self, explicit_as: bool) -> bool {
296        matches!(
297            self,
298            DialectType::TSQL | DialectType::Fabric | DialectType::MySQL | DialectType::SQLite
299        ) || (matches!(self, DialectType::DuckDB) && explicit_as)
300    }
301
302    /// Whether this dialect natively accepts DuckDB-style `ORDER BY ALL`
303    /// (a bare `ALL` keyword meaning "every projected column, in order").
304    /// Other targets need it rewritten to positional `ORDER BY 1, 2, ...`.
305    pub(crate) const fn supports_order_by_all(self) -> bool {
306        matches!(
307            self,
308            DialectType::DuckDB
309                | DialectType::Snowflake
310                | DialectType::ClickHouse
311                | DialectType::Databricks
312                | DialectType::Spark
313        )
314    }
315}
316
317impl Default for DialectType {
318    fn default() -> Self {
319        DialectType::Generic
320    }
321}
322
323impl std::fmt::Display for DialectType {
324    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
325        match self {
326            DialectType::Generic => write!(f, "generic"),
327            DialectType::PostgreSQL => write!(f, "postgresql"),
328            DialectType::MySQL => write!(f, "mysql"),
329            DialectType::BigQuery => write!(f, "bigquery"),
330            DialectType::Snowflake => write!(f, "snowflake"),
331            DialectType::DuckDB => write!(f, "duckdb"),
332            DialectType::SQLite => write!(f, "sqlite"),
333            DialectType::Hive => write!(f, "hive"),
334            DialectType::Spark => write!(f, "spark"),
335            DialectType::Trino => write!(f, "trino"),
336            DialectType::Presto => write!(f, "presto"),
337            DialectType::Redshift => write!(f, "redshift"),
338            DialectType::TSQL => write!(f, "tsql"),
339            DialectType::Oracle => write!(f, "oracle"),
340            DialectType::ClickHouse => write!(f, "clickhouse"),
341            DialectType::Databricks => write!(f, "databricks"),
342            DialectType::Athena => write!(f, "athena"),
343            DialectType::Teradata => write!(f, "teradata"),
344            DialectType::Doris => write!(f, "doris"),
345            DialectType::StarRocks => write!(f, "starrocks"),
346            DialectType::Materialize => write!(f, "materialize"),
347            DialectType::RisingWave => write!(f, "risingwave"),
348            DialectType::SingleStore => write!(f, "singlestore"),
349            DialectType::CockroachDB => write!(f, "cockroachdb"),
350            DialectType::TiDB => write!(f, "tidb"),
351            DialectType::Druid => write!(f, "druid"),
352            DialectType::Solr => write!(f, "solr"),
353            DialectType::Tableau => write!(f, "tableau"),
354            DialectType::Dune => write!(f, "dune"),
355            DialectType::Fabric => write!(f, "fabric"),
356            DialectType::Drill => write!(f, "drill"),
357            DialectType::Dremio => write!(f, "dremio"),
358            DialectType::Exasol => write!(f, "exasol"),
359            DialectType::DataFusion => write!(f, "datafusion"),
360            DialectType::HANA => write!(f, "hana"),
361            DialectType::Vertica => write!(f, "vertica"),
362        }
363    }
364}
365
366impl std::str::FromStr for DialectType {
367    type Err = crate::error::Error;
368
369    fn from_str(s: &str) -> Result<Self> {
370        match s.to_ascii_lowercase().as_str() {
371            "generic" | "" => Ok(DialectType::Generic),
372            "postgres" | "postgresql" => Ok(DialectType::PostgreSQL),
373            "mysql" => Ok(DialectType::MySQL),
374            "bigquery" => Ok(DialectType::BigQuery),
375            "snowflake" => Ok(DialectType::Snowflake),
376            "duckdb" => Ok(DialectType::DuckDB),
377            "sqlite" => Ok(DialectType::SQLite),
378            "hive" => Ok(DialectType::Hive),
379            "spark" | "spark2" => Ok(DialectType::Spark),
380            "trino" => Ok(DialectType::Trino),
381            "presto" => Ok(DialectType::Presto),
382            "redshift" => Ok(DialectType::Redshift),
383            "tsql" | "mssql" | "sqlserver" => Ok(DialectType::TSQL),
384            "oracle" => Ok(DialectType::Oracle),
385            "clickhouse" => Ok(DialectType::ClickHouse),
386            "databricks" => Ok(DialectType::Databricks),
387            "athena" => Ok(DialectType::Athena),
388            "teradata" => Ok(DialectType::Teradata),
389            "doris" => Ok(DialectType::Doris),
390            "starrocks" => Ok(DialectType::StarRocks),
391            "materialize" => Ok(DialectType::Materialize),
392            "risingwave" => Ok(DialectType::RisingWave),
393            "singlestore" | "memsql" => Ok(DialectType::SingleStore),
394            "cockroachdb" | "cockroach" => Ok(DialectType::CockroachDB),
395            "tidb" => Ok(DialectType::TiDB),
396            "druid" => Ok(DialectType::Druid),
397            "solr" => Ok(DialectType::Solr),
398            "tableau" => Ok(DialectType::Tableau),
399            "dune" => Ok(DialectType::Dune),
400            "fabric" => Ok(DialectType::Fabric),
401            "drill" => Ok(DialectType::Drill),
402            "dremio" => Ok(DialectType::Dremio),
403            "exasol" => Ok(DialectType::Exasol),
404            "datafusion" | "arrow-datafusion" | "arrow_datafusion" => Ok(DialectType::DataFusion),
405            "hana" | "saphana" | "sap_hana" => Ok(DialectType::HANA),
406            "vertica" => Ok(DialectType::Vertica),
407            _ => Err(crate::error::Error::parse(
408                format!("Unknown dialect: {}", s),
409                0,
410                0,
411                0,
412                0,
413            )),
414        }
415    }
416}
417
418/// Trait that each concrete SQL dialect must implement.
419///
420/// `DialectImpl` provides the configuration hooks and per-expression transform logic
421/// that distinguish one dialect from another. Implementors supply:
422///
423/// - A [`DialectType`] identifier.
424/// - Optional overrides for tokenizer and generator configuration (defaults to generic SQL).
425/// - An expression-level transform function ([`transform_expr`](DialectImpl::transform_expr))
426///   that rewrites individual AST nodes for this dialect (e.g., converting `NVL` to `COALESCE`).
427/// - An optional preprocessing step ([`preprocess`](DialectImpl::preprocess)) for whole-tree
428///   rewrites that must run before the recursive per-node transform (e.g., eliminating QUALIFY).
429///
430/// The default implementations are no-ops, so a minimal dialect only needs to provide
431/// [`dialect_type`](DialectImpl::dialect_type) and override the methods that differ from
432/// standard SQL.
433pub trait DialectImpl {
434    /// Returns the [`DialectType`] that identifies this dialect.
435    fn dialect_type(&self) -> DialectType;
436
437    /// Returns the tokenizer configuration for this dialect.
438    ///
439    /// Override to customize identifier quoting characters, string escape rules,
440    /// comment styles, and other lexing behavior.
441    fn tokenizer_config(&self) -> TokenizerConfig {
442        TokenizerConfig::default()
443    }
444
445    /// Returns the generator configuration for this dialect.
446    ///
447    /// Override to customize identifier quoting style, function name casing,
448    /// keyword casing, and other SQL generation behavior.
449    #[cfg(feature = "generate")]
450    fn generator_config(&self) -> GeneratorConfig {
451        GeneratorConfig::default()
452    }
453
454    /// Returns a generator configuration tailored to a specific expression.
455    ///
456    /// Override this for hybrid dialects like Athena that route to different SQL engines
457    /// based on expression type (e.g., Hive-style generation for DDL, Trino-style for DML).
458    /// The default delegates to [`generator_config`](DialectImpl::generator_config).
459    #[cfg(feature = "generate")]
460    fn generator_config_for_expr(&self, _expr: &Expression) -> GeneratorConfig {
461        self.generator_config()
462    }
463
464    /// Transforms a single expression node for this dialect, without recursing into children.
465    ///
466    /// This is the per-node rewrite hook invoked by [`transform_recursive`]. Return the
467    /// expression unchanged if no dialect-specific rewrite is needed. Transformations
468    /// typically include function renaming, operator substitution, and type mapping.
469    #[cfg(feature = "transpile")]
470    fn transform_expr(&self, expr: Expression) -> Result<Expression> {
471        Ok(expr)
472    }
473
474    /// Applies whole-tree preprocessing transforms before the recursive per-node pass.
475    ///
476    /// Override this to apply structural rewrites that must see the entire tree at once,
477    /// such as `eliminate_qualify`, `eliminate_distinct_on`, `ensure_bools`, or
478    /// `explode_projection_to_unnest`. The default is a no-op pass-through.
479    #[cfg(feature = "transpile")]
480    fn preprocess(&self, expr: Expression) -> Result<Expression> {
481        Ok(expr)
482    }
483}
484
485/// Recursively transforms a [`DataType`](crate::expressions::DataType), handling nested
486/// parametric types such as `ARRAY<INT>`, `STRUCT<a INT, b TEXT>`, and `MAP<STRING, INT>`.
487///
488/// The outer type is first passed through `transform_fn` as an `Expression::DataType`,
489/// and then nested element/field types are recursed into. This ensures that dialect-level
490/// type mappings (e.g., `INT` to `INTEGER`) propagate into complex nested types.
491#[cfg(any(
492    feature = "transpile",
493    feature = "ast-tools",
494    feature = "generate",
495    feature = "semantic"
496))]
497pub(crate) fn transform_data_type_recursive<F>(
498    dt: crate::expressions::DataType,
499    transform_fn: &F,
500) -> Result<crate::expressions::DataType>
501where
502    F: Fn(Expression) -> Result<Expression>,
503{
504    use crate::expressions::DataType;
505    // First, transform the outermost type through the expression system
506    let dt_expr = transform_fn(Expression::DataType(dt))?;
507    let dt = match dt_expr {
508        Expression::DataType(d) => d,
509        _ => {
510            return Ok(match dt_expr {
511                _ => DataType::Custom {
512                    name: "UNKNOWN".to_string(),
513                },
514            })
515        }
516    };
517    // Then recurse into nested types
518    match dt {
519        DataType::Array {
520            element_type,
521            dimension,
522        } => {
523            let inner = transform_data_type_recursive(*element_type, transform_fn)?;
524            Ok(DataType::Array {
525                element_type: Box::new(inner),
526                dimension,
527            })
528        }
529        DataType::List { element_type } => {
530            let inner = transform_data_type_recursive(*element_type, transform_fn)?;
531            Ok(DataType::List {
532                element_type: Box::new(inner),
533            })
534        }
535        DataType::Struct { fields, nested } => {
536            let mut new_fields = Vec::new();
537            for mut field in fields {
538                field.data_type = transform_data_type_recursive(field.data_type, transform_fn)?;
539                new_fields.push(field);
540            }
541            Ok(DataType::Struct {
542                fields: new_fields,
543                nested,
544            })
545        }
546        DataType::Map {
547            key_type,
548            value_type,
549        } => {
550            let k = transform_data_type_recursive(*key_type, transform_fn)?;
551            let v = transform_data_type_recursive(*value_type, transform_fn)?;
552            Ok(DataType::Map {
553                key_type: Box::new(k),
554                value_type: Box::new(v),
555            })
556        }
557        other => Ok(other),
558    }
559}
560
561/// Convert DuckDB C-style format strings to Presto C-style format strings.
562/// DuckDB and Presto both use C-style % directives but with different specifiers for some cases.
563#[cfg(feature = "transpile")]
564fn duckdb_to_presto_format(fmt: &str) -> String {
565    // Order matters: handle longer patterns first to avoid partial replacements
566    let mut result = fmt.to_string();
567    // First pass: mark multi-char patterns with placeholders
568    result = result.replace("%-m", "\x01NOPADM\x01");
569    result = result.replace("%-d", "\x01NOPADD\x01");
570    result = result.replace("%-I", "\x01NOPADI\x01");
571    result = result.replace("%-H", "\x01NOPADH\x01");
572    result = result.replace("%H:%M:%S", "\x01HMS\x01");
573    result = result.replace("%Y-%m-%d", "\x01YMD\x01");
574    // Now convert individual specifiers
575    result = result.replace("%M", "%i");
576    result = result.replace("%S", "%s");
577    // Restore multi-char patterns with Presto equivalents
578    result = result.replace("\x01NOPADM\x01", "%c");
579    result = result.replace("\x01NOPADD\x01", "%e");
580    result = result.replace("\x01NOPADI\x01", "%l");
581    result = result.replace("\x01NOPADH\x01", "%k");
582    result = result.replace("\x01HMS\x01", "%T");
583    result = result.replace("\x01YMD\x01", "%Y-%m-%d");
584    result
585}
586
587/// Convert DuckDB C-style format strings to BigQuery format strings.
588/// BigQuery uses a mix of strftime-like directives.
589#[cfg(feature = "transpile")]
590fn duckdb_to_bigquery_format(fmt: &str) -> String {
591    let mut result = fmt.to_string();
592    // Handle longer patterns first
593    result = result.replace("%-d", "%e");
594    result = result.replace("%Y-%m-%d %H:%M:%S", "%F %T");
595    result = result.replace("%Y-%m-%d", "%F");
596    result = result.replace("%H:%M:%S", "%T");
597    result
598}
599
600#[cfg(feature = "transpile")]
601fn presto_to_java_format(fmt: &str) -> String {
602    fmt.replace("%Y", "yyyy")
603        .replace("%m", "MM")
604        .replace("%d", "dd")
605        .replace("%H", "HH")
606        .replace("%i", "mm")
607        .replace("%S", "ss")
608        .replace("%s", "ss")
609        .replace("%y", "yy")
610        .replace("%T", "HH:mm:ss")
611        .replace("%F", "yyyy-MM-dd")
612        .replace("%M", "MMMM")
613}
614
615#[cfg(feature = "transpile")]
616fn presto_to_java_parse_format(fmt: &str) -> String {
617    fmt.replace("%Y", "yyyy")
618        .replace("%m", "M")
619        .replace("%d", "d")
620        .replace("%H", "H")
621        .replace("%i", "m")
622        .replace("%S", "s")
623        .replace("%s", "s")
624        .replace("%y", "yy")
625        .replace("%T", "H:m:s")
626        .replace("%F", "yyyy-M-d")
627        .replace("%M", "MMMM")
628}
629
630#[cfg(feature = "transpile")]
631fn normalize_presto_format(fmt: &str) -> String {
632    fmt.replace("%H:%i:%S", "%T").replace("%H:%i:%s", "%T")
633}
634
635#[cfg(feature = "transpile")]
636fn presto_to_duckdb_format(fmt: &str) -> String {
637    fmt.replace("%i", "%M")
638        .replace("%s", "%S")
639        .replace("%T", "%H:%M:%S")
640}
641
642#[cfg(feature = "transpile")]
643fn presto_to_bigquery_format(fmt: &str) -> String {
644    fmt.replace("%Y-%m-%d", "%F")
645        .replace("%H:%i:%S", "%T")
646        .replace("%H:%i:%s", "%T")
647        .replace("%i", "%M")
648        .replace("%s", "%S")
649}
650
651#[cfg(feature = "transpile")]
652fn is_default_presto_timestamp_format(fmt: &str) -> bool {
653    let normalized = normalize_presto_format(fmt);
654    normalized == "%Y-%m-%d %T"
655        || normalized == "%Y-%m-%d %H:%i:%S"
656        || fmt == "%Y-%m-%d %H:%i:%S"
657        || fmt == "%Y-%m-%d %T"
658}
659
660#[cfg(feature = "transpile")]
661fn is_default_presto_date_format(fmt: &str) -> bool {
662    fmt == "%Y-%m-%d" || fmt == "%F"
663}
664
665/// The parser marks the ORDER BY ALL keyword as a Var, never an identifier.
666#[cfg(feature = "generate")]
667pub(crate) fn is_order_by_all_marker(e: &Expression) -> bool {
668    matches!(e, Expression::Var(v) if v.this.eq_ignore_ascii_case("all"))
669}
670
671#[cfg(feature = "transpile")]
672fn is_order_by_all(order_by: &Option<crate::expressions::OrderBy>) -> bool {
673    order_by.as_ref().is_some_and(|ob| {
674        ob.expressions.len() == 1 && is_order_by_all_marker(&ob.expressions[0].this)
675    })
676}
677
678/// Whether `e` is a projection item that can produce more or fewer than one
679/// output column (`*`, or DuckDB's `COLUMNS(...)` macro), making the output
680/// column count unknowable from the AST alone.
681#[cfg(feature = "transpile")]
682fn has_unknown_column_count(e: &Expression) -> bool {
683    use crate::expressions::Expression as E;
684    let mut inner = e;
685    loop {
686        inner = match inner {
687            E::Alias(a) => &a.this,
688            E::Paren(p) => &p.this,
689            _ => break,
690        };
691    }
692    if matches!(inner, E::Star(_)) {
693        return true;
694    }
695    // COLUMNS still expands inside arithmetic, casts and aggregate/function
696    // arguments. COUNT(*) and scalar subqueries, however, produce one column.
697    contains_in_projection(inner, |node| match node {
698        E::Columns(_) => true,
699        E::Function(f) => {
700            !f.quoted && f.qualified_name.is_empty() && f.name.eq_ignore_ascii_case("COLUMNS")
701        }
702        E::MethodCall(m) => m.method.name.eq_ignore_ascii_case("COLUMNS"),
703        _ => false,
704    })
705}
706
707#[cfg(feature = "transpile")]
708pub(crate) fn contains_in_projection(
709    e: &Expression,
710    predicate: impl Fn(&Expression) -> bool,
711) -> bool {
712    let mut pending = vec![e];
713    while let Some(node) = pending.pop() {
714        if matches!(
715            node,
716            Expression::Subquery(_) | Expression::Select(_) | Expression::Exists(_)
717        ) {
718            continue;
719        }
720        if predicate(node) {
721            return true;
722        }
723        crate::ast_children::for_each_child_untracked(node, |child| pending.push(child));
724    }
725    false
726}
727
728/// The number of columns a query block projects, when that count is known
729/// statically from the AST. Compound queries (UNION/INTERSECT/EXCEPT) take
730/// their width from the left-hand operand, per standard SQL set-operation
731/// rules (both sides must already agree on column count).
732#[cfg(feature = "transpile")]
733fn query_output_width(e: &Expression) -> Option<usize> {
734    use crate::expressions::Expression as E;
735    match e {
736        E::Select(sel) => {
737            if sel.expressions.is_empty() || sel.expressions.iter().any(has_unknown_column_count) {
738                None
739            } else {
740                Some(sel.expressions.len())
741            }
742        }
743        E::Paren(p) => query_output_width(&p.this),
744        E::Subquery(s) => query_output_width(&s.this),
745        E::Union(u) if !u.by_name => query_output_width(&u.left),
746        E::Intersect(i) => query_output_width(&i.left),
747        E::Except(ex) => query_output_width(&ex.left),
748        _ => None,
749    }
750}
751
752/// Rewrite an `ORDER BY ALL` clause into positional `ORDER BY 1..n` over
753/// `width` columns, in place. No-op if `order_by` isn't the ALL marker.
754/// Errors if the marker is present but `width` is unknown (e.g. a `*` or
755/// `COLUMNS(...)` projection, where positional ordinals can't be derived).
756///
757/// The source's explicit direction and NULL order (if any) carry over to each
758/// positional entry unchanged; the later cross-dialect NULL-ordering pass
759/// fills in the source dialect's implied default where the target's differs.
760/// MySQL is the exception: it has no NULLS FIRST/LAST syntax and is skipped by
761/// that pass, so compare every supported source's default against MySQL here.
762#[cfg(feature = "transpile")]
763fn expand_order_by_all(
764    order_by: &mut Option<crate::expressions::OrderBy>,
765    width: Option<usize>,
766    source: DialectType,
767    target: DialectType,
768) -> Result<()> {
769    use crate::expressions::Expression as E;
770    if !is_order_by_all(order_by) {
771        return Ok(());
772    }
773    let Some(n) = width else {
774        return Err(crate::error::Error::unsupported(
775            "ORDER BY ALL over a projection with an unknown column count (*, COLUMNS(...), or UNION BY NAME) cannot be expanded to positional ORDER BY",
776            target.to_string(),
777        ));
778    };
779    let ob = order_by.as_mut().unwrap();
780    let (desc, nulls_first) = (ob.expressions[0].desc, ob.expressions[0].nulls_first);
781
782    // An ordinal cannot be used inside a CASE expression to emulate NULL
783    // placement: it would become a constant instead of a column reference.
784    if matches!(target, DialectType::MySQL) {
785        let required_nulls_first = nulls_first.unwrap_or(match source {
786            DialectType::DuckDB | DialectType::ClickHouse => false,
787            DialectType::Snowflake => desc,
788            DialectType::Spark | DialectType::Databricks => !desc,
789            _ => unreachable!("ORDER BY ALL source capability must be checked"),
790        });
791        if required_nulls_first != !desc {
792            return Err(crate::error::Error::unsupported(
793                "ORDER BY ALL's NULL ordering (MySQL's default NULL placement does not match and cannot be emulated with positional ordering)",
794                target.to_string(),
795            ));
796        }
797    }
798
799    ob.expressions = (1..=n as i64)
800        .map(|i| {
801            let mut o = ob.expressions[0].clone();
802            o.this = E::number(i);
803            if target == DialectType::MySQL {
804                // Matching explicit NULL placement is redundant and MySQL
805                // cannot print the NULLS FIRST/LAST syntax.
806                o.nulls_first = None;
807            }
808            o
809        })
810        .collect();
811    Ok(())
812}
813
814/// Expand when the target lacks ALL, or when a native target cannot use it
815/// for this query (aggregate projections in Snowflake, DISTINCT ON emulation).
816#[cfg(feature = "transpile")]
817fn expand_select_order_by_all(
818    mut sel: Box<crate::expressions::Select>,
819    source: DialectType,
820    target: DialectType,
821) -> Result<Box<crate::expressions::Select>> {
822    if !is_order_by_all(&sel.order_by) {
823        return Ok(sel);
824    }
825    let emulate_distinct = sel
826        .distinct_on
827        .as_ref()
828        .is_some_and(|cols| !cols.is_empty())
829        && !Dialect::target_supports_distinct_on(target);
830    let snowflake_aggregate = target == DialectType::Snowflake
831        && sel
832            .expressions
833            .iter()
834            .any(|e| contains_in_projection(e, crate::traversal::is_aggregate));
835    if target.supports_order_by_all() && !emulate_distinct && !snowflake_aggregate {
836        return Ok(sel);
837    }
838    let width =
839        if sel.expressions.is_empty() || sel.expressions.iter().any(has_unknown_column_count) {
840            None
841        } else {
842            Some(sel.expressions.len())
843        };
844    expand_order_by_all(&mut sel.order_by, width, source, target)?;
845    Ok(sel)
846}
847
848/// Applies a dialect transform bottom-up through selected syntax children.
849///
850/// The public entrypoint uses an explicit task stack for the recursion-heavy shapes
851/// that dominate deeply nested SQL (nested SELECT/FROM/SUBQUERY chains, set-operation
852/// trees, and common binary/unary expression chains). Less common shapes currently
853/// reuse the reference recursive implementation so semantics stay identical while
854/// the hot path avoids stack growth.
855#[cfg(any(
856    feature = "transpile",
857    feature = "ast-tools",
858    feature = "generate",
859    feature = "semantic"
860))]
861pub fn transform_recursive<F>(expr: Expression, transform_fn: &F) -> Result<Expression>
862where
863    F: Fn(Expression) -> Result<Expression>,
864{
865    #[cfg(feature = "stacker")]
866    {
867        let red_zone = if cfg!(debug_assertions) {
868            4 * 1024 * 1024
869        } else {
870            1024 * 1024
871        };
872        stacker::maybe_grow(red_zone, 8 * 1024 * 1024, move || {
873            transform_recursive_inner(expr, transform_fn)
874        })
875    }
876    #[cfg(not(feature = "stacker"))]
877    {
878        transform_recursive_inner(expr, transform_fn)
879    }
880}
881
882#[cfg(any(
883    feature = "transpile",
884    feature = "ast-tools",
885    feature = "generate",
886    feature = "semantic"
887))]
888fn transform_recursive_inner<F>(expr: Expression, transform_fn: &F) -> Result<Expression>
889where
890    F: Fn(Expression) -> Result<Expression>,
891{
892    // These are the shapes handled by the former explicit-stack fast path. Other
893    // nodes retain the reference transformer's selective and wrapper-aware child
894    // semantics even though all physical children are visible to traversal APIs.
895    fn uses_generated_dispatch(expression: &Expression) -> bool {
896        match expression {
897            Expression::CreateTable(_) => true,
898            Expression::Select(select) => {
899                select.joins.is_empty()
900                    && select.with.is_none()
901                    && select.order_by.is_none()
902                    && select.windows.is_none()
903                    && select.settings.is_none()
904            }
905            Expression::Union(set_op) => set_op.with.is_none() && set_op.order_by.is_none(),
906            Expression::Intersect(set_op) => set_op.with.is_none() && set_op.order_by.is_none(),
907            Expression::Except(set_op) => set_op.with.is_none() && set_op.order_by.is_none(),
908            Expression::Subquery(subquery) => subquery.order_by.is_none(),
909            Expression::Vertica(_)
910            | Expression::Literal(_)
911            | Expression::Boolean(_)
912            | Expression::Null(_)
913            | Expression::Identifier(_)
914            | Expression::Star(_)
915            | Expression::Parameter(_)
916            | Expression::Placeholder(_)
917            | Expression::SessionParameter(_)
918            | Expression::Interval(_)
919            | Expression::Alias(_)
920            | Expression::Paren(_)
921            | Expression::Not(_)
922            | Expression::Neg(_)
923            | Expression::IsNull(_)
924            | Expression::IsTrue(_)
925            | Expression::IsFalse(_)
926            | Expression::Exists(_)
927            | Expression::Any(_)
928            | Expression::All(_)
929            | Expression::TableArgument(_)
930            | Expression::And(_)
931            | Expression::Or(_)
932            | Expression::Add(_)
933            | Expression::Sub(_)
934            | Expression::Mul(_)
935            | Expression::Div(_)
936            | Expression::IntDiv(_)
937            | Expression::Eq(_)
938            | Expression::NullSafeEq(_)
939            | Expression::NullSafeNeq(_)
940            | Expression::Lt(_)
941            | Expression::Gt(_)
942            | Expression::Neq(_)
943            | Expression::Lte(_)
944            | Expression::Gte(_)
945            | Expression::Mod(_)
946            | Expression::Concat(_)
947            | Expression::BitwiseAnd(_)
948            | Expression::BitwiseOr(_)
949            | Expression::BitwiseXor(_)
950            | Expression::BitwiseLeftShift(_)
951            | Expression::BitwiseRightShift(_)
952            | Expression::Is(_)
953            | Expression::MemberOf(_)
954            | Expression::ArrayContainsAll(_)
955            | Expression::ArrayContainedBy(_)
956            | Expression::ArrayOverlaps(_)
957            | Expression::TsMatch(_)
958            | Expression::Adjacent(_)
959            | Expression::Like(_)
960            | Expression::ILike(_)
961            | Expression::Function(_)
962            | Expression::AggregateFunction(_)
963            | Expression::JSONValue(_)
964            | Expression::JSONTable(_)
965            | Expression::JSONColumnDef(_)
966            | Expression::JsonQuery(_)
967            | Expression::RegexpLike(_)
968            | Expression::RegexpReplace(_)
969            | Expression::RegexpExtract(_)
970            | Expression::RegexpInstr(_)
971            | Expression::RegexpCount(_)
972            | Expression::Cube(_)
973            | Expression::Rollup(_)
974            | Expression::GroupingSets(_)
975            | Expression::Hint(_)
976            | Expression::PartitionByProperty(_)
977            | Expression::StorageProperty(_)
978            | Expression::Hierarchy(_)
979            | Expression::ViewParameter(_)
980            | Expression::Call(_)
981            | Expression::Lead(_)
982            | Expression::Lag(_)
983            | Expression::Array(_)
984            | Expression::Tuple(_)
985            | Expression::ArrayFunc(_)
986            | Expression::Coalesce(_)
987            | Expression::Greatest(_)
988            | Expression::Least(_)
989            | Expression::ArrayConcat(_)
990            | Expression::ArrayIntersect(_)
991            | Expression::ArrayZip(_)
992            | Expression::MapConcat(_)
993            | Expression::JsonArray(_)
994            | Expression::From(_) => true,
995            _ => false,
996        }
997    }
998
999    crate::traversal::transform_with_dispatch(
1000        expr,
1001        transform_fn,
1002        &uses_generated_dispatch,
1003        &transform_recursive_reference,
1004    )
1005}
1006
1007#[cfg(any(
1008    feature = "transpile",
1009    feature = "ast-tools",
1010    feature = "generate",
1011    feature = "semantic"
1012))]
1013fn transform_table_ref_recursive<F>(table: TableRef, transform_fn: &F) -> Result<TableRef>
1014where
1015    F: Fn(Expression) -> Result<Expression>,
1016{
1017    match transform_recursive(Expression::Table(Box::new(table)), transform_fn)? {
1018        Expression::Table(table) => Ok(*table),
1019        _ => Err(crate::error::Error::parse(
1020            "TableRef transformation returned non-table expression",
1021            0,
1022            0,
1023            0,
1024            0,
1025        )),
1026    }
1027}
1028
1029#[cfg(any(
1030    feature = "transpile",
1031    feature = "ast-tools",
1032    feature = "generate",
1033    feature = "semantic"
1034))]
1035fn transform_from_recursive<F>(from: From, transform_fn: &F) -> Result<From>
1036where
1037    F: Fn(Expression) -> Result<Expression>,
1038{
1039    match transform_recursive(Expression::From(Box::new(from)), transform_fn)? {
1040        Expression::From(from) => Ok(*from),
1041        _ => Err(crate::error::Error::parse(
1042            "FROM transformation returned non-FROM expression",
1043            0,
1044            0,
1045            0,
1046            0,
1047        )),
1048    }
1049}
1050
1051#[cfg(any(
1052    feature = "transpile",
1053    feature = "ast-tools",
1054    feature = "generate",
1055    feature = "semantic"
1056))]
1057fn transform_join_recursive<F>(mut join: Join, transform_fn: &F) -> Result<Join>
1058where
1059    F: Fn(Expression) -> Result<Expression>,
1060{
1061    join.this = transform_recursive(join.this, transform_fn)?;
1062    if let Some(on) = join.on.take() {
1063        join.on = Some(transform_recursive(on, transform_fn)?);
1064    }
1065    if let Some(match_condition) = join.match_condition.take() {
1066        join.match_condition = Some(transform_recursive(match_condition, transform_fn)?);
1067    }
1068    join.pivots = join
1069        .pivots
1070        .into_iter()
1071        .map(|pivot| transform_recursive(pivot, transform_fn))
1072        .collect::<Result<Vec<_>>>()?;
1073
1074    match transform_fn(Expression::Join(Box::new(join)))? {
1075        Expression::Join(join) => Ok(*join),
1076        _ => Err(crate::error::Error::parse(
1077            "Join transformation returned non-join expression",
1078            0,
1079            0,
1080            0,
1081            0,
1082        )),
1083    }
1084}
1085
1086#[cfg(any(
1087    feature = "transpile",
1088    feature = "ast-tools",
1089    feature = "generate",
1090    feature = "semantic"
1091))]
1092fn transform_output_clause_recursive<F>(
1093    mut output: OutputClause,
1094    transform_fn: &F,
1095) -> Result<OutputClause>
1096where
1097    F: Fn(Expression) -> Result<Expression>,
1098{
1099    output.columns = output
1100        .columns
1101        .into_iter()
1102        .map(|column| transform_recursive(column, transform_fn))
1103        .collect::<Result<Vec<_>>>()?;
1104    if let Some(into_table) = output.into_table.take() {
1105        output.into_table = Some(transform_recursive(into_table, transform_fn)?);
1106    }
1107    Ok(output)
1108}
1109
1110#[cfg(any(
1111    feature = "transpile",
1112    feature = "ast-tools",
1113    feature = "generate",
1114    feature = "semantic"
1115))]
1116fn transform_with_recursive<F>(mut with: With, transform_fn: &F) -> Result<With>
1117where
1118    F: Fn(Expression) -> Result<Expression>,
1119{
1120    with.ctes = with
1121        .ctes
1122        .into_iter()
1123        .map(|mut cte| {
1124            cte.this = transform_recursive(cte.this, transform_fn)?;
1125            Ok(cte)
1126        })
1127        .collect::<Result<Vec<_>>>()?;
1128    if let Some(search) = with.search.take() {
1129        with.search = Some(Box::new(transform_recursive(*search, transform_fn)?));
1130    }
1131    Ok(with)
1132}
1133
1134#[cfg(any(
1135    feature = "transpile",
1136    feature = "ast-tools",
1137    feature = "generate",
1138    feature = "semantic"
1139))]
1140fn transform_order_by_recursive<F>(mut order: OrderBy, transform_fn: &F) -> Result<OrderBy>
1141where
1142    F: Fn(Expression) -> Result<Expression>,
1143{
1144    order.expressions = order
1145        .expressions
1146        .into_iter()
1147        .map(|mut ordered| {
1148            let original = ordered.this.clone();
1149            ordered.this = transform_recursive(ordered.this, transform_fn).unwrap_or(original);
1150            match transform_fn(Expression::Ordered(Box::new(ordered.clone()))) {
1151                Ok(Expression::Ordered(transformed)) => Ok(*transformed),
1152                Ok(_) | Err(_) => Ok(ordered),
1153            }
1154        })
1155        .collect::<Result<Vec<_>>>()?;
1156    Ok(order)
1157}
1158
1159#[cfg(any(
1160    feature = "transpile",
1161    feature = "ast-tools",
1162    feature = "generate",
1163    feature = "semantic"
1164))]
1165fn transform_recursive_reference<F>(expr: Expression, transform_fn: &F) -> Result<Expression>
1166where
1167    F: Fn(Expression) -> Result<Expression>,
1168{
1169    use crate::expressions::BinaryOp;
1170
1171    // Helper macro to recurse into AggFunc-based expressions (this, filter, order_by, having_max, limit).
1172    macro_rules! recurse_agg {
1173        ($variant:ident, $f:expr) => {{
1174            let mut f = $f;
1175            f.this = transform_recursive(f.this, transform_fn)?;
1176            if let Some(filter) = f.filter.take() {
1177                f.filter = Some(transform_recursive(filter, transform_fn)?);
1178            }
1179            for ord in &mut f.order_by {
1180                ord.this = transform_recursive(
1181                    std::mem::replace(&mut ord.this, Expression::Null(crate::expressions::Null)),
1182                    transform_fn,
1183                )?;
1184            }
1185            if let Some((ref mut expr, _)) = f.having_max {
1186                *expr = Box::new(transform_recursive(
1187                    std::mem::replace(expr.as_mut(), Expression::Null(crate::expressions::Null)),
1188                    transform_fn,
1189                )?);
1190            }
1191            if let Some(limit) = f.limit.take() {
1192                f.limit = Some(Box::new(transform_recursive(*limit, transform_fn)?));
1193            }
1194            Expression::$variant(f)
1195        }};
1196    }
1197
1198    // Helper macro to transform binary ops with Box<BinaryOp>
1199    macro_rules! transform_binary {
1200        ($variant:ident, $op:expr) => {{
1201            let left = transform_recursive($op.left, transform_fn)?;
1202            let right = transform_recursive($op.right, transform_fn)?;
1203            Expression::$variant(Box::new(BinaryOp {
1204                left,
1205                right,
1206                left_comments: $op.left_comments,
1207                operator_comments: $op.operator_comments,
1208                trailing_comments: $op.trailing_comments,
1209                inferred_type: $op.inferred_type,
1210            }))
1211        }};
1212    }
1213
1214    // Fast path: leaf nodes never need child traversal, apply transform directly
1215    if matches!(
1216        &expr,
1217        Expression::Literal(_)
1218            | Expression::Boolean(_)
1219            | Expression::Null(_)
1220            | Expression::Identifier(_)
1221            | Expression::Star(_)
1222            | Expression::Parameter(_)
1223            | Expression::Placeholder(_)
1224            | Expression::SessionParameter(_)
1225    ) {
1226        return transform_fn(expr);
1227    }
1228
1229    // First recursively transform children, then apply the transform function
1230    let expr = match expr {
1231        Expression::Select(mut select) => {
1232            select.query_hints = std::mem::take(&mut select.query_hints)
1233                .into_iter()
1234                .map(|hint| transform_recursive(hint, transform_fn))
1235                .collect::<Result<Vec<_>>>()?;
1236            if let Some(extension) = &mut select.vertica {
1237                use crate::ast_children::AstNode;
1238                let mut failure = None;
1239                extension.visit_expressions_mut(&mut |child| {
1240                    if failure.is_none() {
1241                        let old =
1242                            std::mem::replace(child, Expression::Null(crate::expressions::Null));
1243                        match transform_recursive(old, transform_fn) {
1244                            Ok(new) => *child = new,
1245                            Err(error) => failure = Some(error),
1246                        }
1247                    }
1248                });
1249                if let Some(error) = failure {
1250                    return Err(error);
1251                }
1252            }
1253            select.expressions = select
1254                .expressions
1255                .into_iter()
1256                .map(|e| transform_recursive(e, transform_fn))
1257                .collect::<Result<Vec<_>>>()?;
1258
1259            // Transform FROM clause
1260            if let Some(mut from) = select.from.take() {
1261                from.expressions = from
1262                    .expressions
1263                    .into_iter()
1264                    .map(|e| transform_recursive(e, transform_fn))
1265                    .collect::<Result<Vec<_>>>()?;
1266                select.from = Some(from);
1267            }
1268
1269            // Transform JOINs - important for CROSS APPLY / LATERAL transformations
1270            select.joins = select
1271                .joins
1272                .into_iter()
1273                .map(|mut join| {
1274                    join.this = transform_recursive(join.this, transform_fn)?;
1275                    if let Some(on) = join.on.take() {
1276                        join.on = Some(transform_recursive(on, transform_fn)?);
1277                    }
1278                    // Wrap join in Expression::Join to allow transform_fn to transform it
1279                    match transform_fn(Expression::Join(Box::new(join)))? {
1280                        Expression::Join(j) => Ok(*j),
1281                        _ => Err(crate::error::Error::parse(
1282                            "Join transformation returned non-join expression",
1283                            0,
1284                            0,
1285                            0,
1286                            0,
1287                        )),
1288                    }
1289                })
1290                .collect::<Result<Vec<_>>>()?;
1291
1292            // Transform LATERAL VIEW expressions (Hive/Spark)
1293            select.lateral_views = select
1294                .lateral_views
1295                .into_iter()
1296                .map(|mut lv| {
1297                    lv.this = transform_recursive(lv.this, transform_fn)?;
1298                    Ok(lv)
1299                })
1300                .collect::<Result<Vec<_>>>()?;
1301
1302            // Transform WHERE clause
1303            if let Some(mut where_clause) = select.where_clause.take() {
1304                where_clause.this = transform_recursive(where_clause.this, transform_fn)?;
1305                select.where_clause = Some(where_clause);
1306            }
1307
1308            // Transform GROUP BY
1309            if let Some(mut group_by) = select.group_by.take() {
1310                group_by.expressions = group_by
1311                    .expressions
1312                    .into_iter()
1313                    .map(|e| transform_recursive(e, transform_fn))
1314                    .collect::<Result<Vec<_>>>()?;
1315                select.group_by = Some(group_by);
1316            }
1317
1318            // Transform HAVING
1319            if let Some(mut having) = select.having.take() {
1320                having.this = transform_recursive(having.this, transform_fn)?;
1321                select.having = Some(having);
1322            }
1323
1324            // Transform WITH (CTEs)
1325            if let Some(mut with) = select.with.take() {
1326                with.ctes = with
1327                    .ctes
1328                    .into_iter()
1329                    .map(|mut cte| {
1330                        let original = cte.this.clone();
1331                        cte.this = transform_recursive(cte.this, transform_fn).unwrap_or(original);
1332                        cte
1333                    })
1334                    .collect();
1335                select.with = Some(with);
1336            }
1337
1338            // Transform ORDER BY
1339            if let Some(mut order) = select.order_by.take() {
1340                order.expressions = order
1341                    .expressions
1342                    .into_iter()
1343                    .map(|o| {
1344                        let mut o = o;
1345                        let original = o.this.clone();
1346                        o.this = transform_recursive(o.this, transform_fn).unwrap_or(original);
1347                        // Also apply transform to the Ordered wrapper itself (for NULLS FIRST etc.)
1348                        match transform_fn(Expression::Ordered(Box::new(o.clone()))) {
1349                            Ok(Expression::Ordered(transformed)) => *transformed,
1350                            Ok(_) | Err(_) => o,
1351                        }
1352                    })
1353                    .collect();
1354                select.order_by = Some(order);
1355            }
1356
1357            // Transform WINDOW clause order_by
1358            if let Some(ref mut windows) = select.windows {
1359                for nw in windows.iter_mut() {
1360                    nw.spec.order_by = std::mem::take(&mut nw.spec.order_by)
1361                        .into_iter()
1362                        .map(|o| {
1363                            let mut o = o;
1364                            let original = o.this.clone();
1365                            o.this = transform_recursive(o.this, transform_fn).unwrap_or(original);
1366                            match transform_fn(Expression::Ordered(Box::new(o.clone()))) {
1367                                Ok(Expression::Ordered(transformed)) => *transformed,
1368                                Ok(_) | Err(_) => o,
1369                            }
1370                        })
1371                        .collect();
1372                }
1373            }
1374
1375            // Transform QUALIFY
1376            if let Some(mut qual) = select.qualify.take() {
1377                qual.this = transform_recursive(qual.this, transform_fn)?;
1378                select.qualify = Some(qual);
1379            }
1380
1381            Expression::Select(select)
1382        }
1383        Expression::Function(mut f) => {
1384            f.args = f
1385                .args
1386                .into_iter()
1387                .map(|e| transform_recursive(e, transform_fn))
1388                .collect::<Result<Vec<_>>>()?;
1389            Expression::Function(f)
1390        }
1391        Expression::AggregateFunction(mut f) => {
1392            f.args = f
1393                .args
1394                .into_iter()
1395                .map(|e| transform_recursive(e, transform_fn))
1396                .collect::<Result<Vec<_>>>()?;
1397            if let Some(filter) = f.filter {
1398                f.filter = Some(transform_recursive(filter, transform_fn)?);
1399            }
1400            Expression::AggregateFunction(f)
1401        }
1402        Expression::WindowFunction(mut wf) => {
1403            wf.this = transform_recursive(wf.this, transform_fn)?;
1404            wf.over.partition_by = wf
1405                .over
1406                .partition_by
1407                .into_iter()
1408                .map(|e| transform_recursive(e, transform_fn))
1409                .collect::<Result<Vec<_>>>()?;
1410            // Transform order_by items through Expression::Ordered wrapper
1411            wf.over.order_by = wf
1412                .over
1413                .order_by
1414                .into_iter()
1415                .map(|o| {
1416                    let mut o = o;
1417                    o.this = transform_recursive(o.this, transform_fn)?;
1418                    match transform_fn(Expression::Ordered(Box::new(o)))? {
1419                        Expression::Ordered(transformed) => Ok(*transformed),
1420                        _ => Err(crate::error::Error::parse(
1421                            "Ordered transformation returned non-Ordered expression",
1422                            0,
1423                            0,
1424                            0,
1425                            0,
1426                        )),
1427                    }
1428                })
1429                .collect::<Result<Vec<_>>>()?;
1430            Expression::WindowFunction(wf)
1431        }
1432        Expression::Alias(mut a) => {
1433            a.this = transform_recursive(a.this, transform_fn)?;
1434            Expression::Alias(a)
1435        }
1436        Expression::Cast(mut c) => {
1437            c.this = transform_recursive(c.this, transform_fn)?;
1438            // Also transform the target data type (recursively for nested types like ARRAY<INT>, STRUCT<a INT>)
1439            c.to = transform_data_type_recursive(c.to, transform_fn)?;
1440            Expression::Cast(c)
1441        }
1442        Expression::And(op) => transform_binary!(And, *op),
1443        Expression::Or(op) => transform_binary!(Or, *op),
1444        Expression::Add(op) => transform_binary!(Add, *op),
1445        Expression::Sub(op) => transform_binary!(Sub, *op),
1446        Expression::Mul(op) => transform_binary!(Mul, *op),
1447        Expression::Div(op) => transform_binary!(Div, *op),
1448        Expression::Eq(op) => transform_binary!(Eq, *op),
1449        Expression::Lt(op) => transform_binary!(Lt, *op),
1450        Expression::Gt(op) => transform_binary!(Gt, *op),
1451        Expression::Paren(mut p) => {
1452            p.this = transform_recursive(p.this, transform_fn)?;
1453            Expression::Paren(p)
1454        }
1455        Expression::Coalesce(mut f) => {
1456            f.expressions = f
1457                .expressions
1458                .into_iter()
1459                .map(|e| transform_recursive(e, transform_fn))
1460                .collect::<Result<Vec<_>>>()?;
1461            Expression::Coalesce(f)
1462        }
1463        Expression::IfNull(mut f) => {
1464            f.this = transform_recursive(f.this, transform_fn)?;
1465            f.expression = transform_recursive(f.expression, transform_fn)?;
1466            Expression::IfNull(f)
1467        }
1468        Expression::Nvl(mut f) => {
1469            f.this = transform_recursive(f.this, transform_fn)?;
1470            f.expression = transform_recursive(f.expression, transform_fn)?;
1471            Expression::Nvl(f)
1472        }
1473        Expression::In(mut i) => {
1474            i.this = transform_recursive(i.this, transform_fn)?;
1475            i.expressions = i
1476                .expressions
1477                .into_iter()
1478                .map(|e| transform_recursive(e, transform_fn))
1479                .collect::<Result<Vec<_>>>()?;
1480            if let Some(query) = i.query {
1481                i.query = Some(transform_recursive(query, transform_fn)?);
1482            }
1483            Expression::In(i)
1484        }
1485        Expression::Not(mut n) => {
1486            n.this = transform_recursive(n.this, transform_fn)?;
1487            Expression::Not(n)
1488        }
1489        Expression::ArraySlice(mut s) => {
1490            s.this = transform_recursive(s.this, transform_fn)?;
1491            if let Some(start) = s.start {
1492                s.start = Some(transform_recursive(start, transform_fn)?);
1493            }
1494            if let Some(end) = s.end {
1495                s.end = Some(transform_recursive(end, transform_fn)?);
1496            }
1497            Expression::ArraySlice(s)
1498        }
1499        Expression::Subscript(mut s) => {
1500            s.this = transform_recursive(s.this, transform_fn)?;
1501            s.index = transform_recursive(s.index, transform_fn)?;
1502            Expression::Subscript(s)
1503        }
1504        Expression::Array(mut a) => {
1505            a.expressions = a
1506                .expressions
1507                .into_iter()
1508                .map(|e| transform_recursive(e, transform_fn))
1509                .collect::<Result<Vec<_>>>()?;
1510            Expression::Array(a)
1511        }
1512        Expression::Struct(mut s) => {
1513            let mut new_fields = Vec::new();
1514            for (name, expr) in s.fields {
1515                let transformed = transform_recursive(expr, transform_fn)?;
1516                new_fields.push((name, transformed));
1517            }
1518            s.fields = new_fields;
1519            Expression::Struct(s)
1520        }
1521        Expression::NamedArgument(mut na) => {
1522            na.value = transform_recursive(na.value, transform_fn)?;
1523            Expression::NamedArgument(na)
1524        }
1525        Expression::MapFunc(mut m) => {
1526            m.keys = m
1527                .keys
1528                .into_iter()
1529                .map(|e| transform_recursive(e, transform_fn))
1530                .collect::<Result<Vec<_>>>()?;
1531            m.values = m
1532                .values
1533                .into_iter()
1534                .map(|e| transform_recursive(e, transform_fn))
1535                .collect::<Result<Vec<_>>>()?;
1536            Expression::MapFunc(m)
1537        }
1538        Expression::ArrayFunc(mut a) => {
1539            a.expressions = a
1540                .expressions
1541                .into_iter()
1542                .map(|e| transform_recursive(e, transform_fn))
1543                .collect::<Result<Vec<_>>>()?;
1544            Expression::ArrayFunc(a)
1545        }
1546        Expression::Lambda(mut l) => {
1547            l.body = transform_recursive(l.body, transform_fn)?;
1548            Expression::Lambda(l)
1549        }
1550        Expression::JsonExtract(mut f) => {
1551            f.this = transform_recursive(f.this, transform_fn)?;
1552            f.path = transform_recursive(f.path, transform_fn)?;
1553            Expression::JsonExtract(f)
1554        }
1555        Expression::JsonExtractScalar(mut f) => {
1556            f.this = transform_recursive(f.this, transform_fn)?;
1557            f.path = transform_recursive(f.path, transform_fn)?;
1558            Expression::JsonExtractScalar(f)
1559        }
1560
1561        // ===== UnaryFunc-based expressions =====
1562        // These all have a single `this: Expression` child
1563        Expression::Length(mut f) => {
1564            f.this = transform_recursive(f.this, transform_fn)?;
1565            Expression::Length(f)
1566        }
1567        Expression::Upper(mut f) => {
1568            f.this = transform_recursive(f.this, transform_fn)?;
1569            Expression::Upper(f)
1570        }
1571        Expression::Lower(mut f) => {
1572            f.this = transform_recursive(f.this, transform_fn)?;
1573            Expression::Lower(f)
1574        }
1575        Expression::LTrim(mut f) => {
1576            f.this = transform_recursive(f.this, transform_fn)?;
1577            Expression::LTrim(f)
1578        }
1579        Expression::RTrim(mut f) => {
1580            f.this = transform_recursive(f.this, transform_fn)?;
1581            Expression::RTrim(f)
1582        }
1583        Expression::Reverse(mut f) => {
1584            f.this = transform_recursive(f.this, transform_fn)?;
1585            Expression::Reverse(f)
1586        }
1587        Expression::Abs(mut f) => {
1588            f.this = transform_recursive(f.this, transform_fn)?;
1589            Expression::Abs(f)
1590        }
1591        Expression::Ceil(mut f) => {
1592            f.this = transform_recursive(f.this, transform_fn)?;
1593            Expression::Ceil(f)
1594        }
1595        Expression::Floor(mut f) => {
1596            f.this = transform_recursive(f.this, transform_fn)?;
1597            Expression::Floor(f)
1598        }
1599        Expression::Sign(mut f) => {
1600            f.this = transform_recursive(f.this, transform_fn)?;
1601            Expression::Sign(f)
1602        }
1603        Expression::Sqrt(mut f) => {
1604            f.this = transform_recursive(f.this, transform_fn)?;
1605            Expression::Sqrt(f)
1606        }
1607        Expression::Cbrt(mut f) => {
1608            f.this = transform_recursive(f.this, transform_fn)?;
1609            Expression::Cbrt(f)
1610        }
1611        Expression::Ln(mut f) => {
1612            f.this = transform_recursive(f.this, transform_fn)?;
1613            Expression::Ln(f)
1614        }
1615        Expression::Log(mut f) => {
1616            f.this = transform_recursive(f.this, transform_fn)?;
1617            if let Some(base) = f.base {
1618                f.base = Some(transform_recursive(base, transform_fn)?);
1619            }
1620            Expression::Log(f)
1621        }
1622        Expression::Exp(mut f) => {
1623            f.this = transform_recursive(f.this, transform_fn)?;
1624            Expression::Exp(f)
1625        }
1626        Expression::Date(mut f) => {
1627            f.this = transform_recursive(f.this, transform_fn)?;
1628            Expression::Date(f)
1629        }
1630        Expression::Stddev(f) => recurse_agg!(Stddev, f),
1631        Expression::StddevSamp(f) => recurse_agg!(StddevSamp, f),
1632        Expression::Variance(f) => recurse_agg!(Variance, f),
1633
1634        // ===== BinaryFunc-based expressions =====
1635        Expression::ModFunc(mut f) => {
1636            f.this = transform_recursive(f.this, transform_fn)?;
1637            f.expression = transform_recursive(f.expression, transform_fn)?;
1638            Expression::ModFunc(f)
1639        }
1640        Expression::Power(mut f) => {
1641            f.this = transform_recursive(f.this, transform_fn)?;
1642            f.expression = transform_recursive(f.expression, transform_fn)?;
1643            Expression::Power(f)
1644        }
1645        Expression::MapFromArrays(mut f) => {
1646            f.this = transform_recursive(f.this, transform_fn)?;
1647            f.expression = transform_recursive(f.expression, transform_fn)?;
1648            Expression::MapFromArrays(f)
1649        }
1650        Expression::ElementAt(mut f) => {
1651            f.this = transform_recursive(f.this, transform_fn)?;
1652            f.expression = transform_recursive(f.expression, transform_fn)?;
1653            Expression::ElementAt(f)
1654        }
1655        Expression::MapContainsKey(mut f) => {
1656            f.this = transform_recursive(f.this, transform_fn)?;
1657            f.expression = transform_recursive(f.expression, transform_fn)?;
1658            Expression::MapContainsKey(f)
1659        }
1660        Expression::Left(mut f) => {
1661            f.this = transform_recursive(f.this, transform_fn)?;
1662            f.length = transform_recursive(f.length, transform_fn)?;
1663            Expression::Left(f)
1664        }
1665        Expression::Right(mut f) => {
1666            f.this = transform_recursive(f.this, transform_fn)?;
1667            f.length = transform_recursive(f.length, transform_fn)?;
1668            Expression::Right(f)
1669        }
1670        Expression::Repeat(mut f) => {
1671            f.this = transform_recursive(f.this, transform_fn)?;
1672            f.times = transform_recursive(f.times, transform_fn)?;
1673            Expression::Repeat(f)
1674        }
1675
1676        // ===== Complex function expressions =====
1677        Expression::Substring(mut f) => {
1678            f.this = transform_recursive(f.this, transform_fn)?;
1679            f.start = transform_recursive(f.start, transform_fn)?;
1680            if let Some(len) = f.length {
1681                f.length = Some(transform_recursive(len, transform_fn)?);
1682            }
1683            Expression::Substring(f)
1684        }
1685        Expression::Replace(mut f) => {
1686            f.this = transform_recursive(f.this, transform_fn)?;
1687            f.old = transform_recursive(f.old, transform_fn)?;
1688            f.new = transform_recursive(f.new, transform_fn)?;
1689            Expression::Replace(f)
1690        }
1691        Expression::ConcatWs(mut f) => {
1692            f.separator = transform_recursive(f.separator, transform_fn)?;
1693            f.expressions = f
1694                .expressions
1695                .into_iter()
1696                .map(|e| transform_recursive(e, transform_fn))
1697                .collect::<Result<Vec<_>>>()?;
1698            Expression::ConcatWs(f)
1699        }
1700        Expression::Trim(mut f) => {
1701            f.this = transform_recursive(f.this, transform_fn)?;
1702            if let Some(chars) = f.characters {
1703                f.characters = Some(transform_recursive(chars, transform_fn)?);
1704            }
1705            Expression::Trim(f)
1706        }
1707        Expression::Split(mut f) => {
1708            f.this = transform_recursive(f.this, transform_fn)?;
1709            f.delimiter = transform_recursive(f.delimiter, transform_fn)?;
1710            Expression::Split(f)
1711        }
1712        Expression::Lpad(mut f) => {
1713            f.this = transform_recursive(f.this, transform_fn)?;
1714            f.length = transform_recursive(f.length, transform_fn)?;
1715            if let Some(fill) = f.fill {
1716                f.fill = Some(transform_recursive(fill, transform_fn)?);
1717            }
1718            Expression::Lpad(f)
1719        }
1720        Expression::Rpad(mut f) => {
1721            f.this = transform_recursive(f.this, transform_fn)?;
1722            f.length = transform_recursive(f.length, transform_fn)?;
1723            if let Some(fill) = f.fill {
1724                f.fill = Some(transform_recursive(fill, transform_fn)?);
1725            }
1726            Expression::Rpad(f)
1727        }
1728
1729        // ===== Conditional expressions =====
1730        Expression::Case(mut c) => {
1731            if let Some(operand) = c.operand {
1732                c.operand = Some(transform_recursive(operand, transform_fn)?);
1733            }
1734            c.whens = c
1735                .whens
1736                .into_iter()
1737                .map(|(cond, then)| {
1738                    let new_cond = transform_recursive(cond.clone(), transform_fn).unwrap_or(cond);
1739                    let new_then = transform_recursive(then.clone(), transform_fn).unwrap_or(then);
1740                    (new_cond, new_then)
1741                })
1742                .collect();
1743            if let Some(else_expr) = c.else_ {
1744                c.else_ = Some(transform_recursive(else_expr, transform_fn)?);
1745            }
1746            Expression::Case(c)
1747        }
1748        Expression::IfFunc(mut f) => {
1749            f.condition = transform_recursive(f.condition, transform_fn)?;
1750            f.true_value = transform_recursive(f.true_value, transform_fn)?;
1751            if let Some(false_val) = f.false_value {
1752                f.false_value = Some(transform_recursive(false_val, transform_fn)?);
1753            }
1754            Expression::IfFunc(f)
1755        }
1756
1757        // ===== Date/Time expressions =====
1758        Expression::DateAdd(mut f) => {
1759            f.this = transform_recursive(f.this, transform_fn)?;
1760            f.interval = transform_recursive(f.interval, transform_fn)?;
1761            Expression::DateAdd(f)
1762        }
1763        Expression::DateSub(mut f) => {
1764            f.this = transform_recursive(f.this, transform_fn)?;
1765            f.interval = transform_recursive(f.interval, transform_fn)?;
1766            Expression::DateSub(f)
1767        }
1768        Expression::DateDiff(mut f) => {
1769            f.this = transform_recursive(f.this, transform_fn)?;
1770            f.expression = transform_recursive(f.expression, transform_fn)?;
1771            Expression::DateDiff(f)
1772        }
1773        Expression::DateTrunc(mut f) => {
1774            f.this = transform_recursive(f.this, transform_fn)?;
1775            Expression::DateTrunc(f)
1776        }
1777        Expression::Extract(mut f) => {
1778            f.this = transform_recursive(f.this, transform_fn)?;
1779            Expression::Extract(f)
1780        }
1781
1782        // ===== JSON expressions =====
1783        Expression::JsonObject(mut f) => {
1784            f.pairs = f
1785                .pairs
1786                .into_iter()
1787                .map(|(k, v)| {
1788                    let new_k = transform_recursive(k, transform_fn)?;
1789                    let new_v = transform_recursive(v, transform_fn)?;
1790                    Ok((new_k, new_v))
1791                })
1792                .collect::<Result<Vec<_>>>()?;
1793            Expression::JsonObject(f)
1794        }
1795
1796        // ===== Subquery expressions =====
1797        Expression::Subquery(mut s) => {
1798            s.this = transform_recursive(s.this, transform_fn)?;
1799            if let Some(order_by) = s.order_by.take() {
1800                s.order_by = Some(transform_order_by_recursive(order_by, transform_fn)?);
1801            }
1802            Expression::Subquery(s)
1803        }
1804        Expression::Exists(mut e) => {
1805            e.this = transform_recursive(e.this, transform_fn)?;
1806            Expression::Exists(e)
1807        }
1808        Expression::Describe(mut d) => {
1809            d.target = transform_recursive(d.target, transform_fn)?;
1810            Expression::Describe(d)
1811        }
1812
1813        // ===== Set operations =====
1814        Expression::Union(mut u) => {
1815            let left = std::mem::replace(&mut u.left, Expression::Null(Null));
1816            u.left = transform_recursive(left, transform_fn)?;
1817            let right = std::mem::replace(&mut u.right, Expression::Null(Null));
1818            u.right = transform_recursive(right, transform_fn)?;
1819            if let Some(mut order) = u.order_by.take() {
1820                order.expressions = order
1821                    .expressions
1822                    .into_iter()
1823                    .map(|o| {
1824                        let mut o = o;
1825                        let original = o.this.clone();
1826                        o.this = transform_recursive(o.this, transform_fn).unwrap_or(original);
1827                        match transform_fn(Expression::Ordered(Box::new(o.clone()))) {
1828                            Ok(Expression::Ordered(transformed)) => *transformed,
1829                            Ok(_) | Err(_) => o,
1830                        }
1831                    })
1832                    .collect();
1833                u.order_by = Some(order);
1834            }
1835            if let Some(mut with) = u.with.take() {
1836                with.ctes = with
1837                    .ctes
1838                    .into_iter()
1839                    .map(|mut cte| {
1840                        let original = cte.this.clone();
1841                        cte.this = transform_recursive(cte.this, transform_fn).unwrap_or(original);
1842                        cte
1843                    })
1844                    .collect();
1845                u.with = Some(with);
1846            }
1847            Expression::Union(u)
1848        }
1849        Expression::Intersect(mut i) => {
1850            let left = std::mem::replace(&mut i.left, Expression::Null(Null));
1851            i.left = transform_recursive(left, transform_fn)?;
1852            let right = std::mem::replace(&mut i.right, Expression::Null(Null));
1853            i.right = transform_recursive(right, transform_fn)?;
1854            if let Some(mut order) = i.order_by.take() {
1855                order.expressions = order
1856                    .expressions
1857                    .into_iter()
1858                    .map(|o| {
1859                        let mut o = o;
1860                        let original = o.this.clone();
1861                        o.this = transform_recursive(o.this, transform_fn).unwrap_or(original);
1862                        match transform_fn(Expression::Ordered(Box::new(o.clone()))) {
1863                            Ok(Expression::Ordered(transformed)) => *transformed,
1864                            Ok(_) | Err(_) => o,
1865                        }
1866                    })
1867                    .collect();
1868                i.order_by = Some(order);
1869            }
1870            if let Some(mut with) = i.with.take() {
1871                with.ctes = with
1872                    .ctes
1873                    .into_iter()
1874                    .map(|mut cte| {
1875                        let original = cte.this.clone();
1876                        cte.this = transform_recursive(cte.this, transform_fn).unwrap_or(original);
1877                        cte
1878                    })
1879                    .collect();
1880                i.with = Some(with);
1881            }
1882            Expression::Intersect(i)
1883        }
1884        Expression::Except(mut e) => {
1885            let left = std::mem::replace(&mut e.left, Expression::Null(Null));
1886            e.left = transform_recursive(left, transform_fn)?;
1887            let right = std::mem::replace(&mut e.right, Expression::Null(Null));
1888            e.right = transform_recursive(right, transform_fn)?;
1889            if let Some(mut order) = e.order_by.take() {
1890                order.expressions = order
1891                    .expressions
1892                    .into_iter()
1893                    .map(|o| {
1894                        let mut o = o;
1895                        let original = o.this.clone();
1896                        o.this = transform_recursive(o.this, transform_fn).unwrap_or(original);
1897                        match transform_fn(Expression::Ordered(Box::new(o.clone()))) {
1898                            Ok(Expression::Ordered(transformed)) => *transformed,
1899                            Ok(_) | Err(_) => o,
1900                        }
1901                    })
1902                    .collect();
1903                e.order_by = Some(order);
1904            }
1905            if let Some(mut with) = e.with.take() {
1906                with.ctes = with
1907                    .ctes
1908                    .into_iter()
1909                    .map(|mut cte| {
1910                        let original = cte.this.clone();
1911                        cte.this = transform_recursive(cte.this, transform_fn).unwrap_or(original);
1912                        cte
1913                    })
1914                    .collect();
1915                e.with = Some(with);
1916            }
1917            Expression::Except(e)
1918        }
1919
1920        // ===== DML expressions =====
1921        Expression::Insert(mut ins) => {
1922            // Transform VALUES clause expressions
1923            let mut new_values = Vec::new();
1924            for row in ins.values {
1925                let mut new_row = Vec::new();
1926                for e in row {
1927                    new_row.push(transform_recursive(e, transform_fn)?);
1928                }
1929                new_values.push(new_row);
1930            }
1931            ins.values = new_values;
1932
1933            // Transform query (for INSERT ... SELECT)
1934            if let Some(query) = ins.query {
1935                ins.query = Some(transform_recursive(query, transform_fn)?);
1936            }
1937
1938            // Transform RETURNING clause
1939            let mut new_returning = Vec::new();
1940            for e in ins.returning {
1941                new_returning.push(transform_recursive(e, transform_fn)?);
1942            }
1943            ins.returning = new_returning;
1944
1945            // Transform ON CONFLICT clause
1946            if let Some(on_conflict) = ins.on_conflict {
1947                ins.on_conflict = Some(Box::new(transform_recursive(*on_conflict, transform_fn)?));
1948            }
1949
1950            Expression::Insert(ins)
1951        }
1952        Expression::Update(mut upd) => {
1953            upd.table = transform_table_ref_recursive(upd.table, transform_fn)?;
1954            upd.extra_tables = upd
1955                .extra_tables
1956                .into_iter()
1957                .map(|table| transform_table_ref_recursive(table, transform_fn))
1958                .collect::<Result<Vec<_>>>()?;
1959            upd.table_joins = upd
1960                .table_joins
1961                .into_iter()
1962                .map(|join| transform_join_recursive(join, transform_fn))
1963                .collect::<Result<Vec<_>>>()?;
1964            upd.set = upd
1965                .set
1966                .into_iter()
1967                .map(|(id, val)| {
1968                    let new_val = transform_recursive(val.clone(), transform_fn).unwrap_or(val);
1969                    (id, new_val)
1970                })
1971                .collect();
1972            if let Some(from_clause) = upd.from_clause.take() {
1973                upd.from_clause = Some(transform_from_recursive(from_clause, transform_fn)?);
1974            }
1975            upd.from_joins = upd
1976                .from_joins
1977                .into_iter()
1978                .map(|join| transform_join_recursive(join, transform_fn))
1979                .collect::<Result<Vec<_>>>()?;
1980            if let Some(mut where_clause) = upd.where_clause.take() {
1981                where_clause.this = transform_recursive(where_clause.this, transform_fn)?;
1982                upd.where_clause = Some(where_clause);
1983            }
1984            upd.returning = upd
1985                .returning
1986                .into_iter()
1987                .map(|expr| transform_recursive(expr, transform_fn))
1988                .collect::<Result<Vec<_>>>()?;
1989            if let Some(output) = upd.output.take() {
1990                upd.output = Some(transform_output_clause_recursive(output, transform_fn)?);
1991            }
1992            if let Some(with) = upd.with.take() {
1993                upd.with = Some(transform_with_recursive(with, transform_fn)?);
1994            }
1995            if let Some(limit) = upd.limit.take() {
1996                upd.limit = Some(transform_recursive(limit, transform_fn)?);
1997            }
1998            if let Some(order_by) = upd.order_by.take() {
1999                upd.order_by = Some(transform_order_by_recursive(order_by, transform_fn)?);
2000            }
2001            Expression::Update(upd)
2002        }
2003        Expression::Delete(mut del) => {
2004            del.table = transform_table_ref_recursive(del.table, transform_fn)?;
2005            del.using = del
2006                .using
2007                .into_iter()
2008                .map(|table| transform_table_ref_recursive(table, transform_fn))
2009                .collect::<Result<Vec<_>>>()?;
2010            if let Some(mut where_clause) = del.where_clause.take() {
2011                where_clause.this = transform_recursive(where_clause.this, transform_fn)?;
2012                del.where_clause = Some(where_clause);
2013            }
2014            if let Some(output) = del.output.take() {
2015                del.output = Some(transform_output_clause_recursive(output, transform_fn)?);
2016            }
2017            if let Some(with) = del.with.take() {
2018                del.with = Some(transform_with_recursive(with, transform_fn)?);
2019            }
2020            if let Some(limit) = del.limit.take() {
2021                del.limit = Some(transform_recursive(limit, transform_fn)?);
2022            }
2023            if let Some(order_by) = del.order_by.take() {
2024                del.order_by = Some(transform_order_by_recursive(order_by, transform_fn)?);
2025            }
2026            del.returning = del
2027                .returning
2028                .into_iter()
2029                .map(|expr| transform_recursive(expr, transform_fn))
2030                .collect::<Result<Vec<_>>>()?;
2031            del.tables = del
2032                .tables
2033                .into_iter()
2034                .map(|table| transform_table_ref_recursive(table, transform_fn))
2035                .collect::<Result<Vec<_>>>()?;
2036            del.joins = del
2037                .joins
2038                .into_iter()
2039                .map(|join| transform_join_recursive(join, transform_fn))
2040                .collect::<Result<Vec<_>>>()?;
2041            Expression::Delete(del)
2042        }
2043
2044        // ===== CTE expressions =====
2045        Expression::With(mut w) => {
2046            w.ctes = w
2047                .ctes
2048                .into_iter()
2049                .map(|mut cte| {
2050                    let original = cte.this.clone();
2051                    cte.this = transform_recursive(cte.this, transform_fn).unwrap_or(original);
2052                    cte
2053                })
2054                .collect();
2055            Expression::With(w)
2056        }
2057        Expression::Cte(mut c) => {
2058            c.this = transform_recursive(c.this, transform_fn)?;
2059            Expression::Cte(c)
2060        }
2061
2062        // ===== Order expressions =====
2063        Expression::Ordered(mut o) => {
2064            o.this = transform_recursive(o.this, transform_fn)?;
2065            Expression::Ordered(o)
2066        }
2067
2068        // ===== Negation =====
2069        Expression::Neg(mut n) => {
2070            n.this = transform_recursive(n.this, transform_fn)?;
2071            Expression::Neg(n)
2072        }
2073
2074        // ===== Between =====
2075        Expression::Between(mut b) => {
2076            b.this = transform_recursive(b.this, transform_fn)?;
2077            b.low = transform_recursive(b.low, transform_fn)?;
2078            b.high = transform_recursive(b.high, transform_fn)?;
2079            Expression::Between(b)
2080        }
2081        Expression::IsNull(mut i) => {
2082            i.this = transform_recursive(i.this, transform_fn)?;
2083            Expression::IsNull(i)
2084        }
2085        Expression::IsTrue(mut i) => {
2086            i.this = transform_recursive(i.this, transform_fn)?;
2087            Expression::IsTrue(i)
2088        }
2089        Expression::IsFalse(mut i) => {
2090            i.this = transform_recursive(i.this, transform_fn)?;
2091            Expression::IsFalse(i)
2092        }
2093
2094        // ===== Like expressions =====
2095        Expression::Like(mut l) => {
2096            l.left = transform_recursive(l.left, transform_fn)?;
2097            l.right = transform_recursive(l.right, transform_fn)?;
2098            Expression::Like(l)
2099        }
2100        Expression::ILike(mut l) => {
2101            l.left = transform_recursive(l.left, transform_fn)?;
2102            l.right = transform_recursive(l.right, transform_fn)?;
2103            Expression::ILike(l)
2104        }
2105
2106        // ===== Additional binary ops not covered by macro =====
2107        Expression::Neq(op) => transform_binary!(Neq, *op),
2108        Expression::Lte(op) => transform_binary!(Lte, *op),
2109        Expression::Gte(op) => transform_binary!(Gte, *op),
2110        Expression::Mod(op) => transform_binary!(Mod, *op),
2111        Expression::Concat(op) => transform_binary!(Concat, *op),
2112        Expression::BitwiseAnd(op) => transform_binary!(BitwiseAnd, *op),
2113        Expression::BitwiseOr(op) => transform_binary!(BitwiseOr, *op),
2114        Expression::BitwiseXor(op) => transform_binary!(BitwiseXor, *op),
2115        Expression::BitwiseLeftShift(op) => transform_binary!(BitwiseLeftShift, *op),
2116        Expression::BitwiseRightShift(op) => transform_binary!(BitwiseRightShift, *op),
2117        Expression::Is(op) => transform_binary!(Is, *op),
2118
2119        // ===== TryCast / SafeCast =====
2120        Expression::TryCast(mut c) => {
2121            c.this = transform_recursive(c.this, transform_fn)?;
2122            c.to = transform_data_type_recursive(c.to, transform_fn)?;
2123            Expression::TryCast(c)
2124        }
2125        Expression::SafeCast(mut c) => {
2126            c.this = transform_recursive(c.this, transform_fn)?;
2127            c.to = transform_data_type_recursive(c.to, transform_fn)?;
2128            Expression::SafeCast(c)
2129        }
2130
2131        // ===== Misc =====
2132        Expression::Unnest(mut f) => {
2133            f.this = transform_recursive(f.this, transform_fn)?;
2134            f.expressions = f
2135                .expressions
2136                .into_iter()
2137                .map(|e| transform_recursive(e, transform_fn))
2138                .collect::<Result<Vec<_>>>()?;
2139            Expression::Unnest(f)
2140        }
2141        Expression::Explode(mut f) => {
2142            f.this = transform_recursive(f.this, transform_fn)?;
2143            Expression::Explode(f)
2144        }
2145        Expression::GroupConcat(mut f) => {
2146            f.this = transform_recursive(f.this, transform_fn)?;
2147            Expression::GroupConcat(f)
2148        }
2149        Expression::StringAgg(mut f) => {
2150            f.this = transform_recursive(f.this, transform_fn)?;
2151            if let Some(order_by) = f.order_by.take() {
2152                f.order_by = Some(
2153                    order_by
2154                        .into_iter()
2155                        .map(|mut ordered| {
2156                            let original = ordered.this.clone();
2157                            ordered.this =
2158                                transform_recursive(ordered.this, transform_fn).unwrap_or(original);
2159                            match transform_fn(Expression::Ordered(Box::new(ordered.clone()))) {
2160                                Ok(Expression::Ordered(transformed)) => Ok(*transformed),
2161                                Ok(_) | Err(_) => Ok(ordered),
2162                            }
2163                        })
2164                        .collect::<Result<Vec<_>>>()?,
2165                );
2166            }
2167            Expression::StringAgg(f)
2168        }
2169        Expression::ListAgg(mut f) => {
2170            f.this = transform_recursive(f.this, transform_fn)?;
2171            Expression::ListAgg(f)
2172        }
2173        Expression::ArrayAgg(mut f) => {
2174            f.this = transform_recursive(f.this, transform_fn)?;
2175            Expression::ArrayAgg(f)
2176        }
2177        Expression::ParseJson(mut f) => {
2178            f.this = transform_recursive(f.this, transform_fn)?;
2179            Expression::ParseJson(f)
2180        }
2181        Expression::ToJson(mut f) => {
2182            f.this = transform_recursive(f.this, transform_fn)?;
2183            Expression::ToJson(f)
2184        }
2185        Expression::JSONExtract(mut e) => {
2186            e.this = Box::new(transform_recursive(*e.this, transform_fn)?);
2187            e.expression = Box::new(transform_recursive(*e.expression, transform_fn)?);
2188            Expression::JSONExtract(e)
2189        }
2190        Expression::JSONExtractScalar(mut e) => {
2191            e.this = Box::new(transform_recursive(*e.this, transform_fn)?);
2192            e.expression = Box::new(transform_recursive(*e.expression, transform_fn)?);
2193            Expression::JSONExtractScalar(e)
2194        }
2195
2196        // StrToTime: recurse into this
2197        Expression::StrToTime(mut e) => {
2198            e.this = Box::new(transform_recursive(*e.this, transform_fn)?);
2199            Expression::StrToTime(e)
2200        }
2201
2202        // UnixToTime: recurse into this
2203        Expression::UnixToTime(mut e) => {
2204            e.this = Box::new(transform_recursive(*e.this, transform_fn)?);
2205            Expression::UnixToTime(e)
2206        }
2207
2208        Expression::Upsert(mut upsert) => {
2209            upsert.table = transform_table_ref_recursive(upsert.table, transform_fn)?;
2210            upsert.source = transform_recursive(upsert.source, transform_fn)?;
2211            if let Some(partition) = upsert.partition.take() {
2212                upsert.partition = Some(transform_recursive(partition, transform_fn)?);
2213            }
2214            if let Some(condition) = upsert.condition.take() {
2215                upsert.condition = Some(transform_recursive(condition, transform_fn)?);
2216            }
2217            Expression::Upsert(upsert)
2218        }
2219
2220        // CreateTable: recurse into column defaults, on_update expressions, and data types
2221        Expression::CreateTable(mut ct) => {
2222            for col in &mut ct.columns {
2223                if let Some(default_expr) = col.default.take() {
2224                    col.default = Some(transform_recursive(default_expr, transform_fn)?);
2225                }
2226                if let Some(on_update_expr) = col.on_update.take() {
2227                    col.on_update = Some(transform_recursive(on_update_expr, transform_fn)?);
2228                }
2229                // Note: Column data type transformations (INT -> INT64 for BigQuery, etc.)
2230                // are NOT applied here because per-dialect transforms are designed for CAST/expression
2231                // contexts and may not produce correct results for DDL column definitions.
2232                // The DDL type mappings would need dedicated handling per source/target pair.
2233            }
2234            if let Some(as_select) = ct.as_select.take() {
2235                ct.as_select = Some(transform_recursive(as_select, transform_fn)?);
2236            }
2237            Expression::CreateTable(ct)
2238        }
2239
2240        // CreateView: recurse into the view body query
2241        Expression::CreateView(mut cv) => {
2242            cv.query = transform_recursive(cv.query, transform_fn)?;
2243            Expression::CreateView(cv)
2244        }
2245
2246        // CreateTask: recurse into the task body
2247        Expression::CreateTask(mut ct) => {
2248            ct.body = transform_recursive(ct.body, transform_fn)?;
2249            Expression::CreateTask(ct)
2250        }
2251
2252        // Prepare: recurse into the prepared statement body
2253        Expression::Prepare(mut prepare) => {
2254            prepare.statement = transform_recursive(prepare.statement, transform_fn)?;
2255            Expression::Prepare(prepare)
2256        }
2257
2258        // Execute: recurse into procedure/prepared name and argument values
2259        Expression::Execute(mut execute) => {
2260            execute.this = transform_recursive(execute.this, transform_fn)?;
2261            execute.arguments = execute
2262                .arguments
2263                .into_iter()
2264                .map(|argument| transform_recursive(argument, transform_fn))
2265                .collect::<Result<Vec<_>>>()?;
2266            execute.parameters = execute
2267                .parameters
2268                .into_iter()
2269                .map(|mut parameter| {
2270                    parameter.value = transform_recursive(parameter.value, transform_fn)?;
2271                    Ok(parameter)
2272                })
2273                .collect::<Result<Vec<_>>>()?;
2274            Expression::Execute(execute)
2275        }
2276
2277        // CreateProcedure: recurse into body expressions
2278        Expression::CreateProcedure(mut cp) => {
2279            if let Some(body) = cp.body.take() {
2280                cp.body = Some(match body {
2281                    FunctionBody::Expression(expr) => {
2282                        FunctionBody::Expression(transform_recursive(expr, transform_fn)?)
2283                    }
2284                    FunctionBody::Return(expr) => {
2285                        FunctionBody::Return(transform_recursive(expr, transform_fn)?)
2286                    }
2287                    FunctionBody::Statements(stmts) => {
2288                        let transformed_stmts = stmts
2289                            .into_iter()
2290                            .map(|s| transform_recursive(s, transform_fn))
2291                            .collect::<Result<Vec<_>>>()?;
2292                        FunctionBody::Statements(transformed_stmts)
2293                    }
2294                    other => other,
2295                });
2296            }
2297            Expression::CreateProcedure(cp)
2298        }
2299
2300        // CreateFunction: recurse into body expressions
2301        Expression::CreateFunction(mut cf) => {
2302            if let Some(body) = cf.body.take() {
2303                cf.body = Some(match body {
2304                    FunctionBody::Expression(expr) => {
2305                        FunctionBody::Expression(transform_recursive(expr, transform_fn)?)
2306                    }
2307                    FunctionBody::Return(expr) => {
2308                        FunctionBody::Return(transform_recursive(expr, transform_fn)?)
2309                    }
2310                    FunctionBody::Statements(stmts) => {
2311                        let transformed_stmts = stmts
2312                            .into_iter()
2313                            .map(|s| transform_recursive(s, transform_fn))
2314                            .collect::<Result<Vec<_>>>()?;
2315                        FunctionBody::Statements(transformed_stmts)
2316                    }
2317                    other => other,
2318                });
2319            }
2320            Expression::CreateFunction(cf)
2321        }
2322
2323        // MemberOf: recurse into left and right operands
2324        Expression::MemberOf(op) => transform_binary!(MemberOf, *op),
2325        // ArrayContainsAll (@>): recurse into left and right operands
2326        Expression::ArrayContainsAll(op) => transform_binary!(ArrayContainsAll, *op),
2327        // ArrayContainedBy (<@): recurse into left and right operands
2328        Expression::ArrayContainedBy(op) => transform_binary!(ArrayContainedBy, *op),
2329        // ArrayOverlaps (&&): recurse into left and right operands
2330        Expression::ArrayOverlaps(op) => transform_binary!(ArrayOverlaps, *op),
2331        // TsMatch (@@): recurse into left and right operands
2332        Expression::TsMatch(op) => transform_binary!(TsMatch, *op),
2333        // Adjacent (-|-): recurse into left and right operands
2334        Expression::Adjacent(op) => transform_binary!(Adjacent, *op),
2335
2336        // Table: recurse into when (HistoricalData) and changes fields
2337        Expression::Table(mut t) => {
2338            if let Some(when) = t.when.take() {
2339                let transformed =
2340                    transform_recursive(Expression::HistoricalData(when), transform_fn)?;
2341                if let Expression::HistoricalData(hd) = transformed {
2342                    t.when = Some(hd);
2343                }
2344            }
2345            if let Some(changes) = t.changes.take() {
2346                let transformed = transform_recursive(Expression::Changes(changes), transform_fn)?;
2347                if let Expression::Changes(c) = transformed {
2348                    t.changes = Some(c);
2349                }
2350            }
2351            Expression::Table(t)
2352        }
2353
2354        // HistoricalData (Snowflake time travel): recurse into expression
2355        Expression::HistoricalData(mut hd) => {
2356            *hd.expression = transform_recursive(*hd.expression, transform_fn)?;
2357            Expression::HistoricalData(hd)
2358        }
2359
2360        // Changes (Snowflake CHANGES clause): recurse into at_before and end
2361        Expression::Changes(mut c) => {
2362            if let Some(at_before) = c.at_before.take() {
2363                c.at_before = Some(Box::new(transform_recursive(*at_before, transform_fn)?));
2364            }
2365            if let Some(end) = c.end.take() {
2366                c.end = Some(Box::new(transform_recursive(*end, transform_fn)?));
2367            }
2368            Expression::Changes(c)
2369        }
2370
2371        // TableArgument: TABLE(expr) or MODEL(expr)
2372        Expression::TableArgument(mut ta) => {
2373            ta.this = transform_recursive(ta.this, transform_fn)?;
2374            Expression::TableArgument(ta)
2375        }
2376
2377        // JoinedTable: (tbl1 JOIN tbl2 ON ...) - recurse into left and join tables
2378        Expression::JoinedTable(mut jt) => {
2379            jt.left = transform_recursive(jt.left, transform_fn)?;
2380            jt.joins = jt
2381                .joins
2382                .into_iter()
2383                .map(|mut join| {
2384                    join.this = transform_recursive(join.this, transform_fn)?;
2385                    if let Some(on) = join.on.take() {
2386                        join.on = Some(transform_recursive(on, transform_fn)?);
2387                    }
2388                    match transform_fn(Expression::Join(Box::new(join)))? {
2389                        Expression::Join(j) => Ok(*j),
2390                        _ => Err(crate::error::Error::parse(
2391                            "Join transformation returned non-join expression",
2392                            0,
2393                            0,
2394                            0,
2395                            0,
2396                        )),
2397                    }
2398                })
2399                .collect::<Result<Vec<_>>>()?;
2400            jt.lateral_views = jt
2401                .lateral_views
2402                .into_iter()
2403                .map(|mut lv| {
2404                    lv.this = transform_recursive(lv.this, transform_fn)?;
2405                    Ok(lv)
2406                })
2407                .collect::<Result<Vec<_>>>()?;
2408            Expression::JoinedTable(jt)
2409        }
2410
2411        // Lateral: LATERAL func() - recurse into the function expression
2412        Expression::Lateral(mut lat) => {
2413            *lat.this = transform_recursive(*lat.this, transform_fn)?;
2414            Expression::Lateral(lat)
2415        }
2416
2417        // WithinGroup: recurse into order_by items (for NULLS FIRST/LAST etc.)
2418        // but NOT into wg.this - the inner function is handled by StringAggConvert/GroupConcatConvert
2419        // as a unit together with the WithinGroup wrapper
2420        Expression::WithinGroup(mut wg) => {
2421            wg.order_by = wg
2422                .order_by
2423                .into_iter()
2424                .map(|mut o| {
2425                    let original = o.this.clone();
2426                    o.this = transform_recursive(o.this, transform_fn).unwrap_or(original);
2427                    match transform_fn(Expression::Ordered(Box::new(o.clone()))) {
2428                        Ok(Expression::Ordered(transformed)) => *transformed,
2429                        Ok(_) | Err(_) => o,
2430                    }
2431                })
2432                .collect();
2433            Expression::WithinGroup(wg)
2434        }
2435
2436        // Filter: recurse into both the aggregate and the filter condition
2437        Expression::Filter(mut f) => {
2438            f.this = Box::new(transform_recursive(*f.this, transform_fn)?);
2439            f.expression = Box::new(transform_recursive(*f.expression, transform_fn)?);
2440            Expression::Filter(f)
2441        }
2442
2443        // Aggregate functions (AggFunc-based): recurse into the aggregate argument,
2444        // filter, order_by, having_max, and limit.
2445        // Stddev, StddevSamp, Variance, and ArrayAgg are handled earlier in this match.
2446        Expression::Sum(f) => recurse_agg!(Sum, f),
2447        Expression::Avg(f) => recurse_agg!(Avg, f),
2448        Expression::Min(f) => recurse_agg!(Min, f),
2449        Expression::Max(f) => recurse_agg!(Max, f),
2450        Expression::CountIf(f) => recurse_agg!(CountIf, f),
2451        Expression::StddevPop(f) => recurse_agg!(StddevPop, f),
2452        Expression::VarPop(f) => recurse_agg!(VarPop, f),
2453        Expression::VarSamp(f) => recurse_agg!(VarSamp, f),
2454        Expression::Median(f) => recurse_agg!(Median, f),
2455        Expression::Mode(f) => recurse_agg!(Mode, f),
2456        Expression::First(f) => recurse_agg!(First, f),
2457        Expression::Last(f) => recurse_agg!(Last, f),
2458        Expression::AnyValue(f) => recurse_agg!(AnyValue, f),
2459        Expression::ApproxDistinct(f) => recurse_agg!(ApproxDistinct, f),
2460        Expression::ApproxCountDistinct(f) => recurse_agg!(ApproxCountDistinct, f),
2461        Expression::LogicalAnd(f) => recurse_agg!(LogicalAnd, f),
2462        Expression::LogicalOr(f) => recurse_agg!(LogicalOr, f),
2463        Expression::Skewness(f) => recurse_agg!(Skewness, f),
2464        Expression::ArrayConcatAgg(f) => recurse_agg!(ArrayConcatAgg, f),
2465        Expression::ArrayUniqueAgg(f) => recurse_agg!(ArrayUniqueAgg, f),
2466        Expression::BoolXorAgg(f) => recurse_agg!(BoolXorAgg, f),
2467        Expression::BitwiseOrAgg(f) => recurse_agg!(BitwiseOrAgg, f),
2468        Expression::BitwiseAndAgg(f) => recurse_agg!(BitwiseAndAgg, f),
2469        Expression::BitwiseXorAgg(f) => recurse_agg!(BitwiseXorAgg, f),
2470
2471        // Count has its own struct with an Option<Expression> `this` field
2472        Expression::Count(mut c) => {
2473            if let Some(this) = c.this.take() {
2474                c.this = Some(transform_recursive(this, transform_fn)?);
2475            }
2476            if let Some(filter) = c.filter.take() {
2477                c.filter = Some(transform_recursive(filter, transform_fn)?);
2478            }
2479            Expression::Count(c)
2480        }
2481
2482        Expression::PipeOperator(mut pipe) => {
2483            pipe.this = transform_recursive(pipe.this, transform_fn)?;
2484            pipe.expression = transform_recursive(pipe.expression, transform_fn)?;
2485            Expression::PipeOperator(pipe)
2486        }
2487
2488        // ArrayExcept/ArrayContains/ArrayDistinct: recurse into children
2489        Expression::ArrayExcept(mut f) => {
2490            f.this = transform_recursive(f.this, transform_fn)?;
2491            f.expression = transform_recursive(f.expression, transform_fn)?;
2492            Expression::ArrayExcept(f)
2493        }
2494        Expression::ArrayContains(mut f) => {
2495            f.this = transform_recursive(f.this, transform_fn)?;
2496            f.expression = transform_recursive(f.expression, transform_fn)?;
2497            Expression::ArrayContains(f)
2498        }
2499        Expression::ArrayDistinct(mut f) => {
2500            f.this = transform_recursive(f.this, transform_fn)?;
2501            Expression::ArrayDistinct(f)
2502        }
2503        Expression::ArrayPosition(mut f) => {
2504            f.this = transform_recursive(f.this, transform_fn)?;
2505            f.expression = transform_recursive(f.expression, transform_fn)?;
2506            Expression::ArrayPosition(f)
2507        }
2508
2509        // Pass through leaf nodes unchanged
2510        other => other,
2511    };
2512
2513    // Then apply the transform function
2514    transform_fn(expr)
2515}
2516
2517/// Returns the tokenizer config, generator config, and expression transform closure
2518/// for a built-in dialect type. This is the shared implementation used by both
2519/// `Dialect::get()` and custom dialect construction.
2520// ---------------------------------------------------------------------------
2521// Cached dialect configurations
2522// ---------------------------------------------------------------------------
2523
2524/// Pre-computed tokenizer + generator configs for a dialect, cached via `LazyLock`.
2525/// Transform closures are cheap (unit-struct method calls) and created fresh each time.
2526struct CachedDialectConfig {
2527    tokenizer_config: Arc<TokenizerConfig>,
2528    #[cfg(feature = "generate")]
2529    generator_config: Arc<GeneratorConfig>,
2530}
2531
2532struct DialectConfigs {
2533    tokenizer_config: Arc<TokenizerConfig>,
2534    #[cfg(feature = "generate")]
2535    generator_config: Arc<GeneratorConfig>,
2536    #[cfg(feature = "transpile")]
2537    transformer: Box<dyn Fn(Expression) -> Result<Expression> + Send + Sync>,
2538}
2539
2540/// Declare a per-dialect `LazyLock<CachedDialectConfig>` static.
2541macro_rules! cached_dialect {
2542    ($static_name:ident, $dialect_struct:expr, $feature:literal) => {
2543        #[cfg(feature = $feature)]
2544        static $static_name: LazyLock<CachedDialectConfig> = LazyLock::new(|| {
2545            let d = $dialect_struct;
2546            CachedDialectConfig {
2547                tokenizer_config: Arc::new(d.tokenizer_config()),
2548                #[cfg(feature = "generate")]
2549                generator_config: Arc::new(d.generator_config()),
2550            }
2551        });
2552    };
2553}
2554
2555static CACHED_GENERIC: LazyLock<CachedDialectConfig> = LazyLock::new(|| {
2556    let d = GenericDialect;
2557    CachedDialectConfig {
2558        tokenizer_config: Arc::new(d.tokenizer_config()),
2559        #[cfg(feature = "generate")]
2560        generator_config: Arc::new(d.generator_config()),
2561    }
2562});
2563
2564cached_dialect!(CACHED_POSTGRESQL, PostgresDialect, "dialect-postgresql");
2565cached_dialect!(CACHED_MYSQL, MySQLDialect, "dialect-mysql");
2566cached_dialect!(CACHED_BIGQUERY, BigQueryDialect, "dialect-bigquery");
2567cached_dialect!(CACHED_SNOWFLAKE, SnowflakeDialect, "dialect-snowflake");
2568cached_dialect!(CACHED_DUCKDB, DuckDBDialect, "dialect-duckdb");
2569cached_dialect!(CACHED_TSQL, TSQLDialect, "dialect-tsql");
2570cached_dialect!(CACHED_ORACLE, OracleDialect, "dialect-oracle");
2571cached_dialect!(CACHED_HIVE, HiveDialect, "dialect-hive");
2572cached_dialect!(CACHED_SPARK, SparkDialect, "dialect-spark");
2573cached_dialect!(CACHED_SQLITE, SQLiteDialect, "dialect-sqlite");
2574cached_dialect!(CACHED_PRESTO, PrestoDialect, "dialect-presto");
2575cached_dialect!(CACHED_TRINO, TrinoDialect, "dialect-trino");
2576cached_dialect!(CACHED_REDSHIFT, RedshiftDialect, "dialect-redshift");
2577cached_dialect!(CACHED_CLICKHOUSE, ClickHouseDialect, "dialect-clickhouse");
2578cached_dialect!(CACHED_DATABRICKS, DatabricksDialect, "dialect-databricks");
2579cached_dialect!(CACHED_ATHENA, AthenaDialect, "dialect-athena");
2580cached_dialect!(CACHED_TERADATA, TeradataDialect, "dialect-teradata");
2581cached_dialect!(CACHED_DORIS, DorisDialect, "dialect-doris");
2582cached_dialect!(CACHED_STARROCKS, StarRocksDialect, "dialect-starrocks");
2583cached_dialect!(
2584    CACHED_MATERIALIZE,
2585    MaterializeDialect,
2586    "dialect-materialize"
2587);
2588cached_dialect!(CACHED_RISINGWAVE, RisingWaveDialect, "dialect-risingwave");
2589cached_dialect!(
2590    CACHED_SINGLESTORE,
2591    SingleStoreDialect,
2592    "dialect-singlestore"
2593);
2594cached_dialect!(
2595    CACHED_COCKROACHDB,
2596    CockroachDBDialect,
2597    "dialect-cockroachdb"
2598);
2599cached_dialect!(CACHED_TIDB, TiDBDialect, "dialect-tidb");
2600cached_dialect!(CACHED_DRUID, DruidDialect, "dialect-druid");
2601cached_dialect!(CACHED_SOLR, SolrDialect, "dialect-solr");
2602cached_dialect!(CACHED_TABLEAU, TableauDialect, "dialect-tableau");
2603cached_dialect!(CACHED_DUNE, DuneDialect, "dialect-dune");
2604cached_dialect!(CACHED_FABRIC, FabricDialect, "dialect-fabric");
2605cached_dialect!(CACHED_DRILL, DrillDialect, "dialect-drill");
2606cached_dialect!(CACHED_DREMIO, DremioDialect, "dialect-dremio");
2607cached_dialect!(CACHED_EXASOL, ExasolDialect, "dialect-exasol");
2608cached_dialect!(CACHED_DATAFUSION, DataFusionDialect, "dialect-datafusion");
2609cached_dialect!(CACHED_HANA, HanaDialect, "dialect-hana");
2610cached_dialect!(CACHED_VERTICA, VerticaDialect, "dialect-vertica");
2611
2612fn configs_for_dialect_type(dt: DialectType) -> DialectConfigs {
2613    /// Clone configs from a cached static and pair with a fresh transform closure.
2614    macro_rules! from_cache {
2615        ($cache:expr, $dialect_struct:expr) => {{
2616            let c = &*$cache;
2617            DialectConfigs {
2618                tokenizer_config: c.tokenizer_config.clone(),
2619                #[cfg(feature = "generate")]
2620                generator_config: c.generator_config.clone(),
2621                #[cfg(feature = "transpile")]
2622                transformer: Box::new(move |e| $dialect_struct.transform_expr(e)),
2623            }
2624        }};
2625    }
2626    match dt {
2627        #[cfg(feature = "dialect-postgresql")]
2628        DialectType::PostgreSQL => from_cache!(CACHED_POSTGRESQL, PostgresDialect),
2629        #[cfg(feature = "dialect-mysql")]
2630        DialectType::MySQL => from_cache!(CACHED_MYSQL, MySQLDialect),
2631        #[cfg(feature = "dialect-bigquery")]
2632        DialectType::BigQuery => from_cache!(CACHED_BIGQUERY, BigQueryDialect),
2633        #[cfg(feature = "dialect-snowflake")]
2634        DialectType::Snowflake => from_cache!(CACHED_SNOWFLAKE, SnowflakeDialect),
2635        #[cfg(feature = "dialect-duckdb")]
2636        DialectType::DuckDB => from_cache!(CACHED_DUCKDB, DuckDBDialect),
2637        #[cfg(feature = "dialect-tsql")]
2638        DialectType::TSQL => from_cache!(CACHED_TSQL, TSQLDialect),
2639        #[cfg(feature = "dialect-oracle")]
2640        DialectType::Oracle => from_cache!(CACHED_ORACLE, OracleDialect),
2641        #[cfg(feature = "dialect-hive")]
2642        DialectType::Hive => from_cache!(CACHED_HIVE, HiveDialect),
2643        #[cfg(feature = "dialect-spark")]
2644        DialectType::Spark => from_cache!(CACHED_SPARK, SparkDialect),
2645        #[cfg(feature = "dialect-sqlite")]
2646        DialectType::SQLite => from_cache!(CACHED_SQLITE, SQLiteDialect),
2647        #[cfg(feature = "dialect-presto")]
2648        DialectType::Presto => from_cache!(CACHED_PRESTO, PrestoDialect),
2649        #[cfg(feature = "dialect-trino")]
2650        DialectType::Trino => from_cache!(CACHED_TRINO, TrinoDialect),
2651        #[cfg(feature = "dialect-redshift")]
2652        DialectType::Redshift => from_cache!(CACHED_REDSHIFT, RedshiftDialect),
2653        #[cfg(feature = "dialect-clickhouse")]
2654        DialectType::ClickHouse => from_cache!(CACHED_CLICKHOUSE, ClickHouseDialect),
2655        #[cfg(feature = "dialect-databricks")]
2656        DialectType::Databricks => from_cache!(CACHED_DATABRICKS, DatabricksDialect),
2657        #[cfg(feature = "dialect-athena")]
2658        DialectType::Athena => from_cache!(CACHED_ATHENA, AthenaDialect),
2659        #[cfg(feature = "dialect-teradata")]
2660        DialectType::Teradata => from_cache!(CACHED_TERADATA, TeradataDialect),
2661        #[cfg(feature = "dialect-doris")]
2662        DialectType::Doris => from_cache!(CACHED_DORIS, DorisDialect),
2663        #[cfg(feature = "dialect-starrocks")]
2664        DialectType::StarRocks => from_cache!(CACHED_STARROCKS, StarRocksDialect),
2665        #[cfg(feature = "dialect-materialize")]
2666        DialectType::Materialize => from_cache!(CACHED_MATERIALIZE, MaterializeDialect),
2667        #[cfg(feature = "dialect-risingwave")]
2668        DialectType::RisingWave => from_cache!(CACHED_RISINGWAVE, RisingWaveDialect),
2669        #[cfg(feature = "dialect-singlestore")]
2670        DialectType::SingleStore => from_cache!(CACHED_SINGLESTORE, SingleStoreDialect),
2671        #[cfg(feature = "dialect-cockroachdb")]
2672        DialectType::CockroachDB => from_cache!(CACHED_COCKROACHDB, CockroachDBDialect),
2673        #[cfg(feature = "dialect-tidb")]
2674        DialectType::TiDB => from_cache!(CACHED_TIDB, TiDBDialect),
2675        #[cfg(feature = "dialect-druid")]
2676        DialectType::Druid => from_cache!(CACHED_DRUID, DruidDialect),
2677        #[cfg(feature = "dialect-solr")]
2678        DialectType::Solr => from_cache!(CACHED_SOLR, SolrDialect),
2679        #[cfg(feature = "dialect-tableau")]
2680        DialectType::Tableau => from_cache!(CACHED_TABLEAU, TableauDialect),
2681        #[cfg(feature = "dialect-dune")]
2682        DialectType::Dune => from_cache!(CACHED_DUNE, DuneDialect),
2683        #[cfg(feature = "dialect-fabric")]
2684        DialectType::Fabric => from_cache!(CACHED_FABRIC, FabricDialect),
2685        #[cfg(feature = "dialect-drill")]
2686        DialectType::Drill => from_cache!(CACHED_DRILL, DrillDialect),
2687        #[cfg(feature = "dialect-dremio")]
2688        DialectType::Dremio => from_cache!(CACHED_DREMIO, DremioDialect),
2689        #[cfg(feature = "dialect-exasol")]
2690        DialectType::Exasol => from_cache!(CACHED_EXASOL, ExasolDialect),
2691        #[cfg(feature = "dialect-datafusion")]
2692        DialectType::DataFusion => from_cache!(CACHED_DATAFUSION, DataFusionDialect),
2693        #[cfg(feature = "dialect-hana")]
2694        DialectType::HANA => from_cache!(CACHED_HANA, HanaDialect),
2695        #[cfg(feature = "dialect-vertica")]
2696        DialectType::Vertica => from_cache!(CACHED_VERTICA, VerticaDialect),
2697        _ => from_cache!(CACHED_GENERIC, GenericDialect),
2698    }
2699}
2700
2701// ---------------------------------------------------------------------------
2702// Custom dialect registry
2703// ---------------------------------------------------------------------------
2704
2705static CUSTOM_DIALECT_REGISTRY: LazyLock<RwLock<HashMap<String, Arc<CustomDialectConfig>>>> =
2706    LazyLock::new(|| RwLock::new(HashMap::new()));
2707
2708struct CustomDialectConfig {
2709    name: String,
2710    base_dialect: DialectType,
2711    tokenizer_config: Arc<TokenizerConfig>,
2712    #[cfg(feature = "generate")]
2713    generator_config: GeneratorConfig,
2714    #[cfg(feature = "transpile")]
2715    transform: Option<Arc<dyn Fn(Expression) -> Result<Expression> + Send + Sync>>,
2716    #[cfg(feature = "transpile")]
2717    preprocess: Option<Arc<dyn Fn(Expression) -> Result<Expression> + Send + Sync>>,
2718}
2719
2720/// Fluent builder for creating and registering custom SQL dialects.
2721///
2722/// A custom dialect is based on an existing built-in dialect and allows selective
2723/// overrides of tokenizer configuration, generator configuration, and expression
2724/// transforms.
2725///
2726/// # Example
2727///
2728/// ```rust,ignore
2729/// use polyglot_sql::dialects::{CustomDialectBuilder, DialectType, Dialect};
2730/// use polyglot_sql::generator::NormalizeFunctions;
2731///
2732/// CustomDialectBuilder::new("my_postgres")
2733///     .based_on(DialectType::PostgreSQL)
2734///     .generator_config_modifier(|gc| {
2735///         gc.normalize_functions = NormalizeFunctions::Lower;
2736///     })
2737///     .register()
2738///     .unwrap();
2739///
2740/// let d = Dialect::get_by_name("my_postgres").unwrap();
2741/// let exprs = d.parse("SELECT COUNT(*)").unwrap();
2742/// let sql = d.generate(&exprs[0]).unwrap();
2743/// assert_eq!(sql, "select count(*)");
2744///
2745/// polyglot_sql::unregister_custom_dialect("my_postgres");
2746/// ```
2747pub struct CustomDialectBuilder {
2748    name: String,
2749    base_dialect: DialectType,
2750    tokenizer_modifier: Option<Box<dyn FnOnce(&mut TokenizerConfig)>>,
2751    #[cfg(feature = "generate")]
2752    generator_modifier: Option<Box<dyn FnOnce(&mut GeneratorConfig)>>,
2753    #[cfg(feature = "transpile")]
2754    transform: Option<Arc<dyn Fn(Expression) -> Result<Expression> + Send + Sync>>,
2755    #[cfg(feature = "transpile")]
2756    preprocess: Option<Arc<dyn Fn(Expression) -> Result<Expression> + Send + Sync>>,
2757}
2758
2759impl CustomDialectBuilder {
2760    /// Create a new builder with the given name. Defaults to `Generic` as the base dialect.
2761    pub fn new(name: impl Into<String>) -> Self {
2762        Self {
2763            name: name.into(),
2764            base_dialect: DialectType::Generic,
2765            tokenizer_modifier: None,
2766            #[cfg(feature = "generate")]
2767            generator_modifier: None,
2768            #[cfg(feature = "transpile")]
2769            transform: None,
2770            #[cfg(feature = "transpile")]
2771            preprocess: None,
2772        }
2773    }
2774
2775    /// Set the base built-in dialect to inherit configuration from.
2776    pub fn based_on(mut self, dialect: DialectType) -> Self {
2777        self.base_dialect = dialect;
2778        self
2779    }
2780
2781    /// Provide a closure that modifies the tokenizer configuration inherited from the base dialect.
2782    pub fn tokenizer_config_modifier<F>(mut self, f: F) -> Self
2783    where
2784        F: FnOnce(&mut TokenizerConfig) + 'static,
2785    {
2786        self.tokenizer_modifier = Some(Box::new(f));
2787        self
2788    }
2789
2790    /// Provide a closure that modifies the generator configuration inherited from the base dialect.
2791    #[cfg(feature = "generate")]
2792    pub fn generator_config_modifier<F>(mut self, f: F) -> Self
2793    where
2794        F: FnOnce(&mut GeneratorConfig) + 'static,
2795    {
2796        self.generator_modifier = Some(Box::new(f));
2797        self
2798    }
2799
2800    /// Set a custom per-node expression transform function.
2801    ///
2802    /// This replaces the base dialect's transform. It is called on every expression
2803    /// node during the recursive transform pass.
2804    #[cfg(feature = "transpile")]
2805    pub fn transform_fn<F>(mut self, f: F) -> Self
2806    where
2807        F: Fn(Expression) -> Result<Expression> + Send + Sync + 'static,
2808    {
2809        self.transform = Some(Arc::new(f));
2810        self
2811    }
2812
2813    /// Set a custom whole-tree preprocessing function.
2814    ///
2815    /// This replaces the base dialect's built-in preprocessing. It is called once
2816    /// on the entire expression tree before the recursive per-node transform.
2817    #[cfg(feature = "transpile")]
2818    pub fn preprocess_fn<F>(mut self, f: F) -> Self
2819    where
2820        F: Fn(Expression) -> Result<Expression> + Send + Sync + 'static,
2821    {
2822        self.preprocess = Some(Arc::new(f));
2823        self
2824    }
2825
2826    /// Build the custom dialect configuration and register it in the global registry.
2827    ///
2828    /// Returns an error if:
2829    /// - The name collides with a built-in dialect name
2830    /// - A custom dialect with the same name is already registered
2831    pub fn register(self) -> Result<()> {
2832        // Reject names that collide with built-in dialects
2833        if DialectType::from_str(&self.name).is_ok() {
2834            return Err(crate::error::Error::parse(
2835                format!(
2836                    "Cannot register custom dialect '{}': name collides with built-in dialect",
2837                    self.name
2838                ),
2839                0,
2840                0,
2841                0,
2842                0,
2843            ));
2844        }
2845
2846        // Get base configs
2847        let base_configs = configs_for_dialect_type(self.base_dialect);
2848        let mut tok_config = (*base_configs.tokenizer_config).clone();
2849        #[cfg(feature = "generate")]
2850        let mut gen_config = (*base_configs.generator_config).clone();
2851
2852        // Apply modifiers
2853        if let Some(tok_mod) = self.tokenizer_modifier {
2854            tok_mod(&mut tok_config);
2855        }
2856        #[cfg(feature = "generate")]
2857        if let Some(gen_mod) = self.generator_modifier {
2858            gen_mod(&mut gen_config);
2859        }
2860
2861        let config = CustomDialectConfig {
2862            name: self.name.clone(),
2863            base_dialect: self.base_dialect,
2864            tokenizer_config: Arc::new(tok_config),
2865            #[cfg(feature = "generate")]
2866            generator_config: gen_config,
2867            #[cfg(feature = "transpile")]
2868            transform: self.transform,
2869            #[cfg(feature = "transpile")]
2870            preprocess: self.preprocess,
2871        };
2872
2873        register_custom_dialect(config)
2874    }
2875}
2876
2877use std::str::FromStr;
2878
2879fn register_custom_dialect(config: CustomDialectConfig) -> Result<()> {
2880    let mut registry = CUSTOM_DIALECT_REGISTRY.write().map_err(|e| {
2881        crate::error::Error::parse(format!("Registry lock poisoned: {}", e), 0, 0, 0, 0)
2882    })?;
2883
2884    if registry.contains_key(&config.name) {
2885        return Err(crate::error::Error::parse(
2886            format!("Custom dialect '{}' is already registered", config.name),
2887            0,
2888            0,
2889            0,
2890            0,
2891        ));
2892    }
2893
2894    registry.insert(config.name.clone(), Arc::new(config));
2895    Ok(())
2896}
2897
2898/// Remove a custom dialect from the global registry.
2899///
2900/// Returns `true` if a dialect with that name was found and removed,
2901/// `false` if no such custom dialect existed.
2902pub fn unregister_custom_dialect(name: &str) -> bool {
2903    if let Ok(mut registry) = CUSTOM_DIALECT_REGISTRY.write() {
2904        registry.remove(name).is_some()
2905    } else {
2906        false
2907    }
2908}
2909
2910fn get_custom_dialect_config(name: &str) -> Option<Arc<CustomDialectConfig>> {
2911    CUSTOM_DIALECT_REGISTRY
2912        .read()
2913        .ok()
2914        .and_then(|registry| registry.get(name).cloned())
2915}
2916
2917/// Main entry point for dialect-specific SQL operations.
2918///
2919/// A `Dialect` bundles together a tokenizer, generator configuration, and expression
2920/// transformer for a specific SQL database engine. It is the high-level API through
2921/// which callers parse, generate, transform, and transpile SQL.
2922///
2923/// # Usage
2924///
2925/// ```rust,ignore
2926/// use polyglot_sql::dialects::{Dialect, DialectType};
2927///
2928/// // Parse PostgreSQL SQL into an AST
2929/// let pg = Dialect::get(DialectType::PostgreSQL);
2930/// let exprs = pg.parse("SELECT id, name FROM users WHERE active")?;
2931///
2932/// // Transpile from PostgreSQL to BigQuery
2933/// let results = pg.transpile("SELECT NOW()", DialectType::BigQuery)?;
2934/// assert_eq!(results[0], "SELECT CURRENT_TIMESTAMP()");
2935/// ```
2936///
2937/// Obtain an instance via [`Dialect::get`] or [`Dialect::get_by_name`].
2938/// The struct is `Send + Sync` safe so it can be shared across threads.
2939pub struct Dialect {
2940    dialect_type: DialectType,
2941    tokenizer: Tokenizer,
2942    #[cfg(feature = "generate")]
2943    generator_config: Arc<GeneratorConfig>,
2944    #[cfg(feature = "transpile")]
2945    transformer: Box<dyn Fn(Expression) -> Result<Expression> + Send + Sync>,
2946    /// Optional function to get expression-specific generator config (for hybrid dialects like Athena).
2947    #[cfg(feature = "generate")]
2948    generator_config_for_expr: Option<Box<dyn Fn(&Expression) -> GeneratorConfig + Send + Sync>>,
2949    /// Optional custom preprocessing function (overrides built-in preprocess for custom dialects).
2950    #[cfg(feature = "transpile")]
2951    custom_preprocess: Option<Box<dyn Fn(Expression) -> Result<Expression> + Send + Sync>>,
2952}
2953
2954/// Options for [`Dialect::transpile_with`].
2955///
2956/// Use [`TranspileOptions::default`] for defaults, then tweak the fields you need.
2957/// The struct is marked `#[non_exhaustive]` so new fields can be added without
2958/// breaking the API.
2959///
2960/// The struct derives `Serialize`/`Deserialize` using camelCase field names so
2961/// it can be round-tripped over JSON bridges (C FFI, WASM) without mapping.
2962#[cfg(feature = "transpile")]
2963#[derive(Debug, Clone, Serialize, Deserialize)]
2964#[serde(rename_all = "camelCase", default)]
2965#[non_exhaustive]
2966pub struct TranspileOptions {
2967    /// Whether to pretty-print the output SQL.
2968    pub pretty: bool,
2969    /// How unsupported target-dialect constructs should be handled.
2970    ///
2971    /// The default is [`UnsupportedLevel::Warn`], which preserves the current
2972    /// compatibility behavior and continues transpilation.
2973    pub unsupported_level: UnsupportedLevel,
2974    /// Maximum number of unsupported diagnostics to include in raised errors.
2975    pub max_unsupported: usize,
2976    /// Complexity guard limits used while parsing, transforming, and generating.
2977    pub complexity_guard: ComplexityGuardOptions,
2978}
2979
2980#[cfg(feature = "transpile")]
2981impl Default for TranspileOptions {
2982    fn default() -> Self {
2983        Self {
2984            pretty: false,
2985            unsupported_level: UnsupportedLevel::Warn,
2986            max_unsupported: 3,
2987            complexity_guard: ComplexityGuardOptions::default(),
2988        }
2989    }
2990}
2991
2992#[cfg(feature = "transpile")]
2993impl TranspileOptions {
2994    /// Construct options with pretty-printing enabled.
2995    pub fn pretty() -> Self {
2996        Self {
2997            pretty: true,
2998            ..Default::default()
2999        }
3000    }
3001
3002    /// Construct options that raise when known unsupported constructs remain.
3003    pub fn strict() -> Self {
3004        Self {
3005            unsupported_level: UnsupportedLevel::Raise,
3006            ..Default::default()
3007        }
3008    }
3009
3010    /// Set how unsupported target-dialect constructs should be handled.
3011    pub fn with_unsupported_level(mut self, level: UnsupportedLevel) -> Self {
3012        self.unsupported_level = level;
3013        self
3014    }
3015
3016    /// Set the maximum number of unsupported diagnostics to include in raised errors.
3017    pub fn with_max_unsupported(mut self, max: usize) -> Self {
3018        self.max_unsupported = max;
3019        self
3020    }
3021
3022    /// Set complexity guard limits for parse/transpile/generate recursion-heavy paths.
3023    pub fn with_complexity_guard(mut self, guard: ComplexityGuardOptions) -> Self {
3024        self.complexity_guard = guard;
3025        self
3026    }
3027}
3028
3029/// A value that can be used as the target dialect in [`Dialect::transpile`] /
3030/// [`Dialect::transpile_with`].
3031///
3032/// Implemented for [`DialectType`] (built-in dialect enum) and `&Dialect` (any
3033/// dialect handle, including custom ones). End users do not normally need to
3034/// implement this trait themselves.
3035#[cfg(feature = "transpile")]
3036pub trait TranspileTarget {
3037    /// Invoke `f` with a reference to the resolved target dialect.
3038    fn with_dialect<R>(self, f: impl FnOnce(&Dialect) -> R) -> R;
3039}
3040
3041#[cfg(feature = "transpile")]
3042impl TranspileTarget for DialectType {
3043    fn with_dialect<R>(self, f: impl FnOnce(&Dialect) -> R) -> R {
3044        f(&Dialect::get(self))
3045    }
3046}
3047
3048#[cfg(feature = "transpile")]
3049impl TranspileTarget for &Dialect {
3050    fn with_dialect<R>(self, f: impl FnOnce(&Dialect) -> R) -> R {
3051        f(self)
3052    }
3053}
3054
3055impl Dialect {
3056    /// Creates a fully configured [`Dialect`] instance for the given [`DialectType`].
3057    ///
3058    /// This is the primary constructor. It initializes the tokenizer, generator config,
3059    /// and expression transformer based on the dialect's [`DialectImpl`] implementation.
3060    /// For hybrid dialects like Athena, it also sets up expression-specific generator
3061    /// config routing.
3062    pub fn get(dialect_type: DialectType) -> Self {
3063        let configs = configs_for_dialect_type(dialect_type);
3064        let tokenizer_config = configs.tokenizer_config;
3065        #[cfg(feature = "generate")]
3066        let generator_config = configs.generator_config;
3067        #[cfg(feature = "transpile")]
3068        let transformer = configs.transformer;
3069
3070        // Set up expression-specific generator config for hybrid dialects
3071        #[cfg(feature = "generate")]
3072        let generator_config_for_expr: Option<
3073            Box<dyn Fn(&Expression) -> GeneratorConfig + Send + Sync>,
3074        > = match dialect_type {
3075            #[cfg(feature = "dialect-athena")]
3076            DialectType::Athena => Some(Box::new(|expr| {
3077                AthenaDialect.generator_config_for_expr(expr)
3078            })),
3079            _ => None,
3080        };
3081
3082        Self {
3083            dialect_type,
3084            tokenizer: Tokenizer::from_shared_config(tokenizer_config),
3085            #[cfg(feature = "generate")]
3086            generator_config,
3087            #[cfg(feature = "transpile")]
3088            transformer,
3089            #[cfg(feature = "generate")]
3090            generator_config_for_expr,
3091            #[cfg(feature = "transpile")]
3092            custom_preprocess: None,
3093        }
3094    }
3095
3096    /// Look up a dialect by string name.
3097    ///
3098    /// Checks built-in dialect names first (via [`DialectType::from_str`]), then
3099    /// falls back to the custom dialect registry. Returns `None` if no dialect
3100    /// with the given name exists.
3101    pub fn get_by_name(name: &str) -> Option<Self> {
3102        // Try built-in first
3103        if let Ok(dt) = DialectType::from_str(name) {
3104            return Some(Self::get(dt));
3105        }
3106
3107        // Try custom registry
3108        let config = get_custom_dialect_config(name)?;
3109        Some(Self::from_custom_config(&config))
3110    }
3111
3112    /// Construct a `Dialect` from a custom dialect configuration.
3113    fn from_custom_config(config: &CustomDialectConfig) -> Self {
3114        // Build the transformer: use custom if provided, else use base dialect's
3115        #[cfg(feature = "transpile")]
3116        let transformer: Box<dyn Fn(Expression) -> Result<Expression> + Send + Sync> =
3117            if let Some(ref custom_transform) = config.transform {
3118                let t = Arc::clone(custom_transform);
3119                Box::new(move |e| t(e))
3120            } else {
3121                configs_for_dialect_type(config.base_dialect).transformer
3122            };
3123
3124        // Build the custom preprocess: use custom if provided
3125        #[cfg(feature = "transpile")]
3126        let custom_preprocess: Option<
3127            Box<dyn Fn(Expression) -> Result<Expression> + Send + Sync>,
3128        > = config.preprocess.as_ref().map(|p| {
3129            let p = Arc::clone(p);
3130            Box::new(move |e: Expression| p(e))
3131                as Box<dyn Fn(Expression) -> Result<Expression> + Send + Sync>
3132        });
3133
3134        Self {
3135            dialect_type: config.base_dialect,
3136            tokenizer: Tokenizer::from_shared_config(config.tokenizer_config.clone()),
3137            #[cfg(feature = "generate")]
3138            generator_config: Arc::new(config.generator_config.clone()),
3139            #[cfg(feature = "transpile")]
3140            transformer,
3141            #[cfg(feature = "generate")]
3142            generator_config_for_expr: None,
3143            #[cfg(feature = "transpile")]
3144            custom_preprocess,
3145        }
3146    }
3147
3148    /// Get the dialect type
3149    pub fn dialect_type(&self) -> DialectType {
3150        self.dialect_type
3151    }
3152
3153    /// Get the generator configuration
3154    #[cfg(feature = "generate")]
3155    pub fn generator_config(&self) -> &GeneratorConfig {
3156        &self.generator_config
3157    }
3158
3159    /// Parses a SQL string into a list of [`Expression`] AST nodes.
3160    ///
3161    /// The input may contain multiple semicolon-separated statements; each one
3162    /// produces a separate element in the returned vector. Tokenization uses
3163    /// this dialect's configured tokenizer, and parsing uses the dialect-aware parser.
3164    pub fn parse(&self, sql: &str) -> Result<Vec<Expression>> {
3165        self.parse_with_guard(sql, self.default_complexity_guard())
3166    }
3167
3168    /// Parse using per-call limits without changing this dialect's defaults.
3169    pub fn parse_with_options(&self, sql: &str, options: &ParseOptions) -> Result<Vec<Expression>> {
3170        self.parse_with_guard(
3171            sql,
3172            options
3173                .complexity_guard
3174                .unwrap_or_else(|| self.default_complexity_guard()),
3175        )
3176    }
3177
3178    fn parse_with_guard(
3179        &self,
3180        sql: &str,
3181        complexity_guard: ComplexityGuardOptions,
3182    ) -> Result<Vec<Expression>> {
3183        enforce_input(sql, &complexity_guard)?;
3184        let source: Arc<str> = Arc::from(sql);
3185        let (tokens, token_guard_stats) = self.tokenizer.tokenize_for_parser(&source)?;
3186        let config = crate::parser::ParserConfig {
3187            dialect: Some(self.dialect_type),
3188            complexity_guard,
3189            ..Default::default()
3190        };
3191        let mut parser = Parser::with_parser_tokens(tokens, token_guard_stats, config, source);
3192        parser.parse()
3193    }
3194
3195    fn default_complexity_guard(&self) -> ComplexityGuardOptions {
3196        let mut guard = ComplexityGuardOptions::default();
3197        if matches!(self.dialect_type, DialectType::ClickHouse) {
3198            guard.max_ast_depth = Some(4_096);
3199            guard.max_function_call_depth = Some(512);
3200        }
3201        guard
3202    }
3203
3204    #[cfg(feature = "transpile")]
3205    fn default_transpile_complexity_guard(
3206        &self,
3207        target_dialect: &Dialect,
3208        guard: ComplexityGuardOptions,
3209    ) -> ComplexityGuardOptions {
3210        if guard != ComplexityGuardOptions::default() {
3211            return guard;
3212        }
3213
3214        if matches!(self.dialect_type, DialectType::ClickHouse)
3215            || matches!(target_dialect.dialect_type, DialectType::ClickHouse)
3216        {
3217            let mut guard = guard;
3218            guard.max_ast_depth = Some(4_096);
3219            guard.max_function_call_depth = Some(512);
3220            guard
3221        } else {
3222            guard
3223        }
3224    }
3225
3226    /// Parse a standalone SQL data type using this dialect's tokenizer and parser.
3227    ///
3228    /// This accepts type strings such as `DECIMAL(10, 2)`, `INT[]`, or
3229    /// `STRUCT(a INT, b VARCHAR)` without requiring a surrounding statement.
3230    pub fn parse_data_type(&self, sql: &str) -> Result<DataType> {
3231        self.parse_data_type_with_options(sql, &ParseOptions::default())
3232    }
3233
3234    /// Parse a standalone data type using per-call complexity limits.
3235    pub fn parse_data_type_with_options(
3236        &self,
3237        sql: &str,
3238        options: &ParseOptions,
3239    ) -> Result<DataType> {
3240        let complexity_guard = options
3241            .complexity_guard
3242            .unwrap_or_else(|| self.default_complexity_guard());
3243        enforce_input(sql, &complexity_guard)?;
3244        let source: Arc<str> = Arc::from(sql);
3245        let (tokens, token_guard_stats) = self.tokenizer.tokenize_for_parser(&source)?;
3246        let config = crate::parser::ParserConfig {
3247            dialect: Some(self.dialect_type),
3248            complexity_guard,
3249            ..Default::default()
3250        };
3251        let mut parser = Parser::with_parser_tokens(tokens, token_guard_stats, config, source);
3252        parser.parse_standalone_data_type()
3253    }
3254
3255    /// Tokenize SQL using this dialect's tokenizer configuration.
3256    pub fn tokenize(&self, sql: &str) -> Result<Vec<Token>> {
3257        self.tokenizer.tokenize(sql)
3258    }
3259
3260    /// Get the generator config for a specific expression (supports hybrid dialects).
3261    /// Returns an owned `GeneratorConfig` suitable for mutation before generation.
3262    #[cfg(feature = "generate")]
3263    fn get_config_for_expr(&self, expr: &Expression) -> GeneratorConfig {
3264        if let Some(ref config_fn) = self.generator_config_for_expr {
3265            config_fn(expr)
3266        } else {
3267            (*self.generator_config).clone()
3268        }
3269    }
3270
3271    /// Generates a SQL string from an [`Expression`] AST node.
3272    ///
3273    /// The output uses this dialect's generator configuration for identifier quoting,
3274    /// keyword casing, function name normalization, and syntax style. The result is
3275    /// a single-line (non-pretty) SQL string.
3276    #[cfg(feature = "generate")]
3277    pub fn generate(&self, expr: &Expression) -> Result<String> {
3278        // Fast path: when no per-expression config override, share the Arc cheaply.
3279        if self.generator_config_for_expr.is_none() {
3280            let mut generator = Generator::with_arc_config(self.generator_config.clone());
3281            return generator.generate(expr);
3282        }
3283        let config = self.get_config_for_expr(expr);
3284        let mut generator = Generator::with_config(config);
3285        generator.generate(expr)
3286    }
3287
3288    /// Generate SQL with pretty printing, retaining explicit null-ordering clauses.
3289    #[cfg(feature = "generate")]
3290    pub fn generate_pretty(&self, expr: &Expression) -> Result<String> {
3291        let mut config = self.get_config_for_expr(expr);
3292        config.pretty = true;
3293        let mut generator = Generator::with_config(config).with_preserved_null_ordering();
3294        generator.generate(expr)
3295    }
3296
3297    /// Render analysis facts with the same AST limits used to accept the input.
3298    #[cfg(all(feature = "semantic", feature = "generate"))]
3299    pub(crate) fn generate_with_guard(
3300        &self,
3301        expr: &Expression,
3302        guard: Option<ComplexityGuardOptions>,
3303    ) -> Result<String> {
3304        let Some(guard) = guard else {
3305            return self.generate(expr);
3306        };
3307        let mut config = self.get_config_for_expr(expr);
3308        config.complexity_guard = guard;
3309        Generator::with_config(config).generate(expr)
3310    }
3311
3312    /// Generate SQL from an expression with source dialect info (for transpilation)
3313    #[cfg(feature = "generate")]
3314    pub fn generate_with_source(&self, expr: &Expression, source: DialectType) -> Result<String> {
3315        let mut config = self.get_config_for_expr(expr);
3316        config.source_dialect = Some(source);
3317        let mut generator = Generator::with_config(config);
3318        generator.generate(expr)
3319    }
3320
3321    /// Generate SQL from an expression with pretty printing and source dialect info
3322    #[cfg(feature = "generate")]
3323    pub fn generate_pretty_with_source(
3324        &self,
3325        expr: &Expression,
3326        source: DialectType,
3327    ) -> Result<String> {
3328        let mut config = self.get_config_for_expr(expr);
3329        config.pretty = true;
3330        config.source_dialect = Some(source);
3331        let mut generator = Generator::with_config(config);
3332        generator.generate(expr)
3333    }
3334
3335    /// Generate SQL from an expression with source dialect and transpile options.
3336    #[cfg(all(feature = "generate", feature = "transpile"))]
3337    fn generate_with_transpile_options(
3338        &self,
3339        expr: &Expression,
3340        source: DialectType,
3341        opts: &TranspileOptions,
3342    ) -> Result<String> {
3343        let mut config = self.get_config_for_expr(expr);
3344        config.source_dialect = Some(source);
3345        config.pretty = opts.pretty;
3346        config.unsupported_level = opts.unsupported_level;
3347        config.max_unsupported = opts.max_unsupported.max(1);
3348        config.complexity_guard = opts.complexity_guard;
3349        let mut generator = Generator::with_config(config);
3350        generator.generate(expr)
3351    }
3352
3353    /// Generate SQL from an expression with forced identifier quoting (identify=True)
3354    #[cfg(feature = "generate")]
3355    pub fn generate_with_identify(&self, expr: &Expression) -> Result<String> {
3356        let mut config = self.get_config_for_expr(expr);
3357        config.always_quote_identifiers = true;
3358        let mut generator = Generator::with_config(config);
3359        generator.generate(expr)
3360    }
3361
3362    /// Generate SQL from an expression with pretty printing and forced identifier quoting
3363    #[cfg(feature = "generate")]
3364    pub fn generate_pretty_with_identify(&self, expr: &Expression) -> Result<String> {
3365        let mut config = (*self.generator_config).clone();
3366        config.pretty = true;
3367        config.always_quote_identifiers = true;
3368        let mut generator = Generator::with_config(config);
3369        generator.generate(expr)
3370    }
3371
3372    /// Generate SQL from an expression with caller-specified config overrides
3373    #[cfg(feature = "generate")]
3374    pub fn generate_with_overrides(
3375        &self,
3376        expr: &Expression,
3377        overrides: impl FnOnce(&mut GeneratorConfig),
3378    ) -> Result<String> {
3379        let mut config = self.get_config_for_expr(expr);
3380        overrides(&mut config);
3381        let mut generator = Generator::with_config(config);
3382        generator.generate(expr)
3383    }
3384
3385    /// Transforms an expression tree to conform to this dialect's syntax and semantics.
3386    ///
3387    /// The transformation proceeds in two phases:
3388    /// 1. **Preprocessing** -- whole-tree structural rewrites such as eliminating QUALIFY,
3389    ///    ensuring boolean predicates, or converting DISTINCT ON to a window-function pattern.
3390    /// 2. **Recursive per-node transform** -- a bottom-up pass via [`transform_recursive`]
3391    ///    that applies this dialect's [`DialectImpl::transform_expr`] to every node.
3392    ///
3393    /// This method is used both during transpilation (to rewrite an AST for a target dialect)
3394    /// and for identity transforms (normalizing SQL within the same dialect).
3395    #[cfg(feature = "transpile")]
3396    pub fn transform(&self, expr: Expression) -> Result<Expression> {
3397        self.transform_with_guard(expr, self.default_complexity_guard())
3398    }
3399
3400    #[cfg(feature = "transpile")]
3401    fn transform_with_guard(
3402        &self,
3403        expr: Expression,
3404        complexity_guard: ComplexityGuardOptions,
3405    ) -> Result<Expression> {
3406        enforce_generate_ast(&expr, &complexity_guard)?;
3407        // Apply preprocessing transforms based on dialect
3408        let preprocessed = self.preprocess(expr)?;
3409        // Then apply recursive transformation
3410        transform_recursive(preprocessed, &|node| {
3411            // Calls with retained source semantics are lowered by the generator;
3412            // ordinary AST visitors and optimizer passes still see their shared node kind.
3413            if node.source_dialect().is_some()
3414                || matches!(&node, Expression::Function(f) if !f.qualified_name.is_empty())
3415            {
3416                Ok(node)
3417            } else {
3418                (self.transformer)(node)
3419            }
3420        })
3421    }
3422
3423    /// Apply dialect-specific preprocessing transforms
3424    #[cfg(feature = "transpile")]
3425    fn preprocess(&self, expr: Expression) -> Result<Expression> {
3426        // If a custom preprocess function is set, use it instead of the built-in logic
3427        if let Some(ref custom_preprocess) = self.custom_preprocess {
3428            return custom_preprocess(expr);
3429        }
3430
3431        #[cfg(any(
3432            feature = "dialect-mysql",
3433            feature = "dialect-postgresql",
3434            feature = "dialect-bigquery",
3435            feature = "dialect-snowflake",
3436            feature = "dialect-tsql",
3437            feature = "dialect-spark",
3438            feature = "dialect-databricks",
3439            feature = "dialect-hive",
3440            feature = "dialect-sqlite",
3441            feature = "dialect-trino",
3442            feature = "dialect-presto",
3443            feature = "dialect-duckdb",
3444            feature = "dialect-redshift",
3445            feature = "dialect-starrocks",
3446            feature = "dialect-oracle",
3447            feature = "dialect-clickhouse",
3448            feature = "dialect-fabric",
3449            feature = "dialect-vertica",
3450        ))]
3451        use crate::transforms;
3452
3453        match self.dialect_type {
3454            // MySQL doesn't support QUALIFY, DISTINCT ON, FULL OUTER JOIN
3455            // MySQL doesn't natively support GENERATE_DATE_ARRAY (expand to recursive CTE)
3456            #[cfg(feature = "dialect-mysql")]
3457            DialectType::MySQL => {
3458                let expr = transforms::eliminate_qualify(expr)?;
3459                let expr = transforms::eliminate_full_outer_join(expr)?;
3460                let expr = transforms::eliminate_semi_and_anti_joins(expr)?;
3461                let expr = transforms::unnest_generate_date_array_using_recursive_cte(expr)?;
3462                Ok(expr)
3463            }
3464            // PostgreSQL doesn't support QUALIFY
3465            // PostgreSQL: UNNEST(GENERATE_SERIES) -> subquery wrapping
3466            // PostgreSQL: Normalize SET ... TO to SET ... = in CREATE FUNCTION
3467            #[cfg(feature = "dialect-postgresql")]
3468            DialectType::PostgreSQL => {
3469                let expr = transforms::eliminate_qualify(expr)?;
3470                let expr = transforms::eliminate_semi_and_anti_joins(expr)?;
3471                let expr = transforms::unwrap_unnest_generate_series_for_postgres(expr)?;
3472                // Normalize SET ... TO to SET ... = in CREATE FUNCTION
3473                // Only normalize when sqlglot would fully parse (no body) —
3474                // sqlglot falls back to Command for complex function bodies,
3475                // preserving the original text including TO.
3476                let expr = if let Expression::CreateFunction(mut cf) = expr {
3477                    if cf.body.is_none() {
3478                        for opt in &mut cf.set_options {
3479                            if let crate::expressions::FunctionSetValue::Value { use_to, .. } =
3480                                &mut opt.value
3481                            {
3482                                *use_to = false;
3483                            }
3484                        }
3485                    }
3486                    Expression::CreateFunction(cf)
3487                } else {
3488                    expr
3489                };
3490                Ok(expr)
3491            }
3492            // BigQuery doesn't support DISTINCT ON or CTE column aliases
3493            #[cfg(feature = "dialect-bigquery")]
3494            DialectType::BigQuery => {
3495                let expr = transforms::eliminate_semi_and_anti_joins(expr)?;
3496                let expr = transforms::pushdown_cte_column_names(expr)?;
3497                let expr = transforms::explode_projection_to_unnest(expr, DialectType::BigQuery)?;
3498                Ok(expr)
3499            }
3500            // Snowflake
3501            #[cfg(feature = "dialect-snowflake")]
3502            DialectType::Snowflake => {
3503                let expr = transforms::eliminate_semi_and_anti_joins(expr)?;
3504                let expr = transforms::eliminate_window_clause(expr)?;
3505                let expr = transforms::snowflake_flatten_projection_to_unnest(expr)?;
3506                Ok(expr)
3507            }
3508            // TSQL doesn't support QUALIFY
3509            // TSQL requires boolean expressions in WHERE/HAVING (no implicit truthiness)
3510            // TSQL doesn't support CTEs in subqueries (hoist to top level)
3511            // NOTE: no_limit_order_by_union is handled in cross_dialect_normalize (not preprocess)
3512            // to avoid breaking TSQL identity tests where ORDER BY on UNION is valid
3513            #[cfg(feature = "dialect-tsql")]
3514            DialectType::TSQL => {
3515                let expr = transforms::eliminate_qualify(expr)?;
3516                let expr = transforms::eliminate_semi_and_anti_joins(expr)?;
3517                let expr = transforms::normalize_grouping_sets_for_tsql(expr)?;
3518                let expr =
3519                    transforms::expand_distinct_grouping_sets_for_tsql(expr, DialectType::TSQL)?;
3520                let expr = transforms::ensure_bools(expr)?;
3521                let expr = transforms::unnest_generate_date_array_using_recursive_cte(expr)?;
3522                let expr = transforms::strip_cte_materialization(expr)?;
3523                let expr = transforms::move_ctes_to_top_level(expr)?;
3524                let expr = transforms::qualify_derived_table_outputs(expr)?;
3525                Ok(expr)
3526            }
3527            // Fabric shares T-SQL predicate rules and CTE placement restrictions,
3528            // but keeps Fabric-specific APPLY and derived-table behavior separate.
3529            #[cfg(feature = "dialect-fabric")]
3530            DialectType::Fabric => {
3531                let expr = transforms::normalize_grouping_sets_for_tsql(expr)?;
3532                let expr =
3533                    transforms::expand_distinct_grouping_sets_for_tsql(expr, DialectType::Fabric)?;
3534                let expr = transforms::ensure_bools(expr)?;
3535                let expr = transforms::strip_cte_materialization(expr)?;
3536                let expr = transforms::move_ctes_to_top_level(expr)?;
3537                Ok(expr)
3538            }
3539            // Spark doesn't support QUALIFY (but Databricks does)
3540            // Spark doesn't support CTEs in subqueries (hoist to top level)
3541            #[cfg(feature = "dialect-spark")]
3542            DialectType::Spark => {
3543                let expr = transforms::eliminate_qualify(expr)?;
3544                let expr = transforms::add_auto_table_alias(expr)?;
3545                let expr = transforms::simplify_nested_paren_values(expr)?;
3546                let expr = transforms::move_ctes_to_top_level(expr)?;
3547                Ok(expr)
3548            }
3549            // Databricks supports QUALIFY natively
3550            // Databricks doesn't support CTEs in subqueries (hoist to top level)
3551            #[cfg(feature = "dialect-databricks")]
3552            DialectType::Databricks => {
3553                let expr = transforms::add_auto_table_alias(expr)?;
3554                let expr = transforms::simplify_nested_paren_values(expr)?;
3555                let expr = transforms::move_ctes_to_top_level(expr)?;
3556                Ok(expr)
3557            }
3558            // Hive doesn't support QUALIFY or CTEs in subqueries
3559            #[cfg(feature = "dialect-hive")]
3560            DialectType::Hive => {
3561                let expr = transforms::eliminate_qualify(expr)?;
3562                let expr = transforms::move_ctes_to_top_level(expr)?;
3563                Ok(expr)
3564            }
3565            // SQLite doesn't support QUALIFY
3566            #[cfg(feature = "dialect-sqlite")]
3567            DialectType::SQLite => {
3568                let expr = transforms::eliminate_qualify(expr)?;
3569                Ok(expr)
3570            }
3571            // Trino doesn't support QUALIFY
3572            #[cfg(feature = "dialect-trino")]
3573            DialectType::Trino => {
3574                let expr = transforms::eliminate_qualify(expr)?;
3575                let expr = transforms::explode_projection_to_unnest(expr, DialectType::Trino)?;
3576                Ok(expr)
3577            }
3578            // Presto doesn't support QUALIFY or WINDOW clause
3579            #[cfg(feature = "dialect-presto")]
3580            DialectType::Presto => {
3581                let expr = transforms::eliminate_qualify(expr)?;
3582                let expr = transforms::eliminate_window_clause(expr)?;
3583                let expr = transforms::explode_projection_to_unnest(expr, DialectType::Presto)?;
3584                Ok(expr)
3585            }
3586            // DuckDB supports QUALIFY - no elimination needed
3587            // Expand POSEXPLODE to GENERATE_SUBSCRIPTS + UNNEST
3588            // Expand LIKE ANY / ILIKE ANY to OR chains (DuckDB doesn't support quantifiers)
3589            #[cfg(feature = "dialect-duckdb")]
3590            DialectType::DuckDB => {
3591                let expr = transforms::expand_posexplode_duckdb(expr)?;
3592                let expr = transforms::expand_like_any(expr)?;
3593                Ok(expr)
3594            }
3595            // Redshift doesn't support QUALIFY, WINDOW clause, or GENERATE_DATE_ARRAY
3596            #[cfg(feature = "dialect-redshift")]
3597            DialectType::Redshift => {
3598                let expr = transforms::eliminate_qualify(expr)?;
3599                let expr = transforms::eliminate_window_clause(expr)?;
3600                let expr = transforms::unnest_generate_date_array_using_recursive_cte(expr)?;
3601                Ok(expr)
3602            }
3603            // StarRocks doesn't support BETWEEN in DELETE statements or QUALIFY
3604            #[cfg(feature = "dialect-starrocks")]
3605            DialectType::StarRocks => {
3606                let expr = transforms::eliminate_qualify(expr)?;
3607                let expr = transforms::expand_between_in_delete(expr)?;
3608                let expr = transforms::eliminate_distinct_on_for_dialect(
3609                    expr,
3610                    Some(DialectType::StarRocks),
3611                    Some(DialectType::StarRocks),
3612                )?;
3613                let expr = transforms::unnest_generate_date_array_using_recursive_cte(expr)?;
3614                Ok(expr)
3615            }
3616            // DataFusion supports QUALIFY and semi/anti joins natively
3617            #[cfg(feature = "dialect-datafusion")]
3618            DialectType::DataFusion => Ok(expr),
3619            // Vertica doesn't support QUALIFY or semi/anti join syntax
3620            #[cfg(feature = "dialect-vertica")]
3621            DialectType::Vertica => {
3622                let expr = transforms::eliminate_qualify(expr)?;
3623                let expr = transforms::eliminate_semi_and_anti_joins(expr)?;
3624                Ok(expr)
3625            }
3626            // Oracle doesn't support QUALIFY
3627            #[cfg(feature = "dialect-oracle")]
3628            DialectType::Oracle => {
3629                let expr = transforms::eliminate_qualify(expr)?;
3630                Ok(expr)
3631            }
3632            // Drill - no special preprocessing needed
3633            #[cfg(feature = "dialect-drill")]
3634            DialectType::Drill => Ok(expr),
3635            // Teradata - no special preprocessing needed
3636            #[cfg(feature = "dialect-teradata")]
3637            DialectType::Teradata => Ok(expr),
3638            // ClickHouse doesn't support ORDER BY/LIMIT directly on UNION
3639            #[cfg(feature = "dialect-clickhouse")]
3640            DialectType::ClickHouse => {
3641                let expr = transforms::no_limit_order_by_union(expr)?;
3642                Ok(expr)
3643            }
3644            // Other dialects - no preprocessing
3645            _ => Ok(expr),
3646        }
3647    }
3648
3649    /// Transpile SQL from this dialect to the given target dialect.
3650    ///
3651    /// The target may be specified as either a built-in [`DialectType`] enum variant
3652    /// or as a reference to a [`Dialect`] handle (built-in or custom). Both work:
3653    ///
3654    /// ```rust,ignore
3655    /// let pg = Dialect::get(DialectType::PostgreSQL);
3656    /// pg.transpile("SELECT NOW()", DialectType::BigQuery)?;   // enum
3657    /// pg.transpile("SELECT NOW()", &custom_dialect)?;         // handle
3658    /// ```
3659    ///
3660    /// For pretty-printing or other options, use [`transpile_with`](Self::transpile_with).
3661    #[cfg(feature = "transpile")]
3662    pub fn transpile<T: TranspileTarget>(&self, sql: &str, target: T) -> Result<Vec<String>> {
3663        self.transpile_with(sql, target, TranspileOptions::default())
3664    }
3665
3666    /// Transpile SQL with configurable [`TranspileOptions`] (e.g. pretty-printing).
3667    #[cfg(feature = "transpile")]
3668    pub fn transpile_with<T: TranspileTarget>(
3669        &self,
3670        sql: &str,
3671        target: T,
3672        opts: TranspileOptions,
3673    ) -> Result<Vec<String>> {
3674        target.with_dialect(|td| self.transpile_inner(sql, td, &opts))
3675    }
3676
3677    #[cfg(feature = "transpile")]
3678    fn transpile_inner(
3679        &self,
3680        sql: &str,
3681        target_dialect: &Dialect,
3682        opts: &TranspileOptions,
3683    ) -> Result<Vec<String>> {
3684        let mut effective_opts = opts.clone();
3685        effective_opts.complexity_guard =
3686            self.default_transpile_complexity_guard(target_dialect, opts.complexity_guard);
3687        let opts = &effective_opts;
3688        let target = target_dialect.dialect_type;
3689        if matches!(self.dialect_type, DialectType::PostgreSQL)
3690            && matches!(target, DialectType::SQLite)
3691        {
3692            self.reject_pgvector_distance_operators_for_sqlite(sql)?;
3693        }
3694        let expressions = self.parse_with_guard(sql, opts.complexity_guard)?;
3695        let generic_identity =
3696            self.dialect_type == DialectType::Generic && target == DialectType::Generic;
3697
3698        if generic_identity {
3699            return expressions
3700                .into_iter()
3701                .map(|expr| {
3702                    Self::reject_strict_unsupported(&expr, self.dialect_type, target, opts)?;
3703                    target_dialect.generate_with_transpile_options(&expr, self.dialect_type, opts)
3704                })
3705                .collect();
3706        }
3707
3708        expressions
3709            .into_iter()
3710            .map(|expr| {
3711                // Reject unsupported source semantics before normalization can
3712                // erase them. This AST-only pass shares conversion decisions with
3713                // the generator and never renders throwaway SQL.
3714                Self::reject_hana_source_semantics(
3715                    &expr,
3716                    self.dialect_type,
3717                    target_dialect.generator_config.dialect.unwrap_or_default(),
3718                )?;
3719
3720                let expr =
3721                    normalization::vertica::prepare_conversion(expr, self.dialect_type, target)?;
3722                normalization::vertica::validate_conversion(&expr, self.dialect_type, target)?;
3723                let expr = normalization::duckdb::prepare_integer_division(
3724                    expr,
3725                    self.dialect_type,
3726                    target,
3727                )?;
3728                // DuckDB source: normalize VARCHAR/CHAR to TEXT (DuckDB doesn't support
3729                // VARCHAR length constraints). This emulates Python sqlglot's DuckDB parser
3730                // where VARCHAR_LENGTH = None and VARCHAR maps to TEXT.
3731                //
3732                // Any source with native `ORDER BY ALL`: expand it to positional
3733                // `ORDER BY 1..n` for targets without it, on both plain SELECTs and
3734                // compound queries (whose ORDER BY lives on the set-op node).
3735                let source = self.dialect_type;
3736                let is_duckdb = matches!(source, DialectType::DuckDB);
3737                let expand_all = source.supports_order_by_all() && !target.supports_order_by_all();
3738                let expr = if source.supports_order_by_all() {
3739                    use crate::expressions::DataType as DT;
3740                    transform_recursive(expr, &|e| match e {
3741                        Expression::DataType(DT::VarChar { .. }) if is_duckdb => {
3742                            Ok(Expression::DataType(DT::Text))
3743                        }
3744                        Expression::DataType(DT::Char { .. }) if is_duckdb => {
3745                            Ok(Expression::DataType(DT::Text))
3746                        }
3747                        Expression::Select(sel) => {
3748                            expand_select_order_by_all(sel, source, target).map(Expression::Select)
3749                        }
3750                        Expression::Subquery(mut subquery) if expand_all => {
3751                            let width = query_output_width(&subquery.this);
3752                            expand_order_by_all(&mut subquery.order_by, width, source, target)?;
3753                            Ok(Expression::Subquery(subquery))
3754                        }
3755                        Expression::Union(mut u) if expand_all => {
3756                            let width = if u.by_name {
3757                                None
3758                            } else {
3759                                query_output_width(&u.left)
3760                            };
3761                            expand_order_by_all(&mut u.order_by, width, source, target)?;
3762                            Ok(Expression::Union(u))
3763                        }
3764                        Expression::Intersect(mut i) if expand_all => {
3765                            let width = query_output_width(&i.left);
3766                            expand_order_by_all(&mut i.order_by, width, source, target)?;
3767                            Ok(Expression::Intersect(i))
3768                        }
3769                        Expression::Except(mut ex) if expand_all => {
3770                            let width = query_output_width(&ex.left);
3771                            expand_order_by_all(&mut ex.order_by, width, source, target)?;
3772                            Ok(Expression::Except(ex))
3773                        }
3774                        _ => Ok(e),
3775                    })?
3776                } else {
3777                    expr
3778                };
3779
3780                Self::reject_clickhouse_session_semantics(&expr, self.dialect_type, target, opts)?;
3781                Self::reject_postgres_tsql_strict_regex_predicates(
3782                    &expr,
3783                    self.dialect_type,
3784                    target,
3785                    opts,
3786                )?;
3787                Self::reject_tsql_strict_json_constructor_return_types(
3788                    &expr,
3789                    self.dialect_type,
3790                    target,
3791                    opts,
3792                )?;
3793                Self::reject_postgres_tsql_strict_json_aggregate_modifiers(
3794                    &expr,
3795                    self.dialect_type,
3796                    target,
3797                    opts,
3798                )?;
3799
3800                // When source and target differ, first normalize the source dialect's
3801                // AST constructs to standard SQL, so that the target dialect can handle them.
3802                // This handles cases like Snowflake's SQUARE -> POWER, DIV0 -> CASE, etc.
3803                let normalized =
3804                    if self.dialect_type != target && self.dialect_type != DialectType::Generic {
3805                        self.transform_with_guard(expr, opts.complexity_guard)?
3806                    } else {
3807                        expr
3808                    };
3809
3810                // For TSQL source targeting non-TSQL: unwrap ISNULL(JSON_QUERY(...), JSON_VALUE(...))
3811                // to just JSON_QUERY(...) so cross_dialect_normalize can convert it cleanly.
3812                // The TSQL read transform wraps JsonQuery in ISNULL for identity, but for
3813                // cross-dialect transpilation we need the unwrapped JSON_QUERY.
3814                let normalized =
3815                    if matches!(self.dialect_type, DialectType::TSQL | DialectType::Fabric)
3816                        && !matches!(target, DialectType::TSQL | DialectType::Fabric)
3817                    {
3818                        transform_recursive(normalized, &|e| {
3819                            if let Expression::Function(ref f) = e {
3820                                if f.name.eq_ignore_ascii_case("ISNULL") && f.args.len() == 2 {
3821                                    // Check if first arg is JSON_QUERY and second is JSON_VALUE
3822                                    if let (
3823                                        Expression::Function(ref jq),
3824                                        Expression::Function(ref jv),
3825                                    ) = (&f.args[0], &f.args[1])
3826                                    {
3827                                        if jq.name.eq_ignore_ascii_case("JSON_QUERY")
3828                                            && jv.name.eq_ignore_ascii_case("JSON_VALUE")
3829                                        {
3830                                            // Unwrap: return just JSON_QUERY(...)
3831                                            return Ok(f.args[0].clone());
3832                                        }
3833                                    }
3834                                }
3835                            }
3836                            Ok(e)
3837                        })?
3838                    } else {
3839                        normalized
3840                    };
3841
3842                // Snowflake source to non-Snowflake target: CURRENT_TIME -> LOCALTIME.
3843                // Preserve precision for ClickHouse's Time64 lowering; retain the existing
3844                // precision-less LOCALTIME compatibility output for other targets.
3845                let normalized = if matches!(self.dialect_type, DialectType::Snowflake)
3846                    && !matches!(target, DialectType::Snowflake)
3847                {
3848                    transform_recursive(normalized, &|e| match e {
3849                        Expression::Function(ref f)
3850                            if f.name.eq_ignore_ascii_case("CURRENT_TIME") =>
3851                        {
3852                            let precision = if matches!(target, DialectType::ClickHouse) {
3853                                f.args.first().cloned().map(Box::new)
3854                            } else {
3855                                None
3856                            };
3857                            Ok(Expression::Localtime(Box::new(
3858                                crate::expressions::Localtime { this: precision },
3859                            )))
3860                        }
3861                        Expression::Localtime(ref localtime)
3862                            if !matches!(target, DialectType::ClickHouse)
3863                                && localtime.this.is_some() =>
3864                        {
3865                            Ok(Expression::Localtime(Box::new(
3866                                crate::expressions::Localtime { this: None },
3867                            )))
3868                        }
3869                        _ => Ok(e),
3870                    })?
3871                } else {
3872                    normalized
3873                };
3874
3875                // Snowflake source to DuckDB target: REPEAT(' ', n) -> REPEAT(' ', CAST(n AS BIGINT))
3876                // Snowflake's SPACE(n) is converted to REPEAT(' ', n) by the Snowflake source
3877                // transform. DuckDB requires the count argument to be BIGINT.
3878                let normalized = if matches!(self.dialect_type, DialectType::Snowflake)
3879                    && matches!(target, DialectType::DuckDB)
3880                {
3881                    transform_recursive(normalized, &|e| {
3882                        if let Expression::Function(ref f) = e {
3883                            if f.name.eq_ignore_ascii_case("REPEAT") && f.args.len() == 2 {
3884                                // Check if first arg is space string literal
3885                                if let Expression::Literal(ref lit) = f.args[0] {
3886                                    if let crate::expressions::Literal::String(ref s) = lit.as_ref()
3887                                    {
3888                                        if s == " " {
3889                                            // Wrap second arg in CAST(... AS BIGINT) if not already
3890                                            if !matches!(f.args[1], Expression::Cast(_)) {
3891                                                let mut new_args = f.args.clone();
3892                                                new_args[1] = Expression::Cast(Box::new(
3893                                                    crate::expressions::Cast {
3894                                                        this: new_args[1].clone(),
3895                                                        to: crate::expressions::DataType::BigInt {
3896                                                            length: None,
3897                                                        },
3898                                                        trailing_comments: Vec::new(),
3899                                                        double_colon_syntax: false,
3900                                                        format: None,
3901                                                        default: None,
3902                                                        inferred_type: None,
3903                                                    },
3904                                                ));
3905                                                return Ok(Expression::Function(Box::new(
3906                                                    crate::expressions::Function {
3907                                                        on_error: None,
3908                                                        qualified_name: Vec::new(),
3909                                                        source_dialect: None,
3910
3911                                                        name: f.name.clone(),
3912                                                        args: new_args,
3913                                                        distinct: f.distinct,
3914                                                        trailing_comments: f
3915                                                            .trailing_comments
3916                                                            .clone(),
3917                                                        use_bracket_syntax: f.use_bracket_syntax,
3918                                                        no_parens: f.no_parens,
3919                                                        quoted: f.quoted,
3920                                                        span: None,
3921                                                        inferred_type: None,
3922                                                    },
3923                                                )));
3924                                            }
3925                                        }
3926                                    }
3927                                }
3928                            }
3929                        }
3930                        Ok(e)
3931                    })?
3932                } else {
3933                    normalized
3934                };
3935
3936                // Propagate struct field names in arrays (for BigQuery source to non-BigQuery target)
3937                // BigQuery->BigQuery should NOT propagate names (BigQuery handles implicit inheritance)
3938                let normalized = if matches!(self.dialect_type, DialectType::BigQuery)
3939                    && !matches!(target, DialectType::BigQuery)
3940                {
3941                    crate::transforms::propagate_struct_field_names(normalized)?
3942                } else {
3943                    normalized
3944                };
3945
3946                // Snowflake source to DuckDB target: RANDOM()/RANDOM(seed) -> scaled RANDOM()
3947                // Snowflake RANDOM() returns integer in [-2^63, 2^63-1], DuckDB RANDOM() returns float [0, 1)
3948                // Skip RANDOM inside UNIFORM/NORMAL/ZIPF/RANDSTR generator args since those
3949                // functions handle their generator args differently (as float seeds).
3950                let normalized = if matches!(self.dialect_type, DialectType::Snowflake)
3951                    && matches!(target, DialectType::DuckDB)
3952                {
3953                    fn make_scaled_random() -> Expression {
3954                        let lower =
3955                            Expression::Literal(Box::new(crate::expressions::Literal::Number(
3956                                "-9.223372036854776E+18".to_string(),
3957                            )));
3958                        let upper =
3959                            Expression::Literal(Box::new(crate::expressions::Literal::Number(
3960                                "9.223372036854776e+18".to_string(),
3961                            )));
3962                        let random_call = Expression::Random(crate::expressions::Random);
3963                        let range_size = Expression::Paren(Box::new(crate::expressions::Paren {
3964                            this: Expression::Sub(Box::new(crate::expressions::BinaryOp {
3965                                left: upper,
3966                                right: lower.clone(),
3967                                left_comments: vec![],
3968                                operator_comments: vec![],
3969                                trailing_comments: vec![],
3970                                inferred_type: None,
3971                            })),
3972                            trailing_comments: vec![],
3973                        }));
3974                        let scaled = Expression::Mul(Box::new(crate::expressions::BinaryOp {
3975                            left: random_call,
3976                            right: range_size,
3977                            left_comments: vec![],
3978                            operator_comments: vec![],
3979                            trailing_comments: vec![],
3980                            inferred_type: None,
3981                        }));
3982                        let shifted = Expression::Add(Box::new(crate::expressions::BinaryOp {
3983                            left: lower,
3984                            right: scaled,
3985                            left_comments: vec![],
3986                            operator_comments: vec![],
3987                            trailing_comments: vec![],
3988                            inferred_type: None,
3989                        }));
3990                        Expression::Cast(Box::new(crate::expressions::Cast {
3991                            this: shifted,
3992                            to: crate::expressions::DataType::BigInt { length: None },
3993                            trailing_comments: vec![],
3994                            double_colon_syntax: false,
3995                            format: None,
3996                            default: None,
3997                            inferred_type: None,
3998                        }))
3999                    }
4000
4001                    // Pre-process: protect seeded RANDOM(seed) inside UNIFORM/NORMAL/ZIPF/RANDSTR
4002                    // by converting Rand{seed: Some(s)} to Function{name:"RANDOM", args:[s]}.
4003                    // This prevents transform_recursive (which is bottom-up) from expanding
4004                    // seeded RANDOM into make_scaled_random() and losing the seed value.
4005                    // Unseeded RANDOM()/Rand{seed:None} is left as-is so it gets expanded
4006                    // and then un-expanded back to Expression::Random by the code below.
4007                    let normalized = transform_recursive(normalized, &|e| {
4008                        if let Expression::Function(ref f) = e {
4009                            let n = f.name.to_ascii_uppercase();
4010                            if n == "UNIFORM" || n == "NORMAL" || n == "ZIPF" || n == "RANDSTR" {
4011                                if let Expression::Function(mut f) = e {
4012                                    for arg in f.args.iter_mut() {
4013                                        if let Expression::Rand(ref r) = arg {
4014                                            if r.lower.is_none() && r.upper.is_none() {
4015                                                if let Some(ref seed) = r.seed {
4016                                                    // Convert Rand{seed: Some(s)} to Function("RANDOM", [s])
4017                                                    // so it won't be expanded by the RANDOM expansion below
4018                                                    *arg = Expression::Function(Box::new(
4019                                                        crate::expressions::Function::new(
4020                                                            "RANDOM".to_string(),
4021                                                            vec![*seed.clone()],
4022                                                        ),
4023                                                    ));
4024                                                }
4025                                            }
4026                                        }
4027                                    }
4028                                    return Ok(Expression::Function(f));
4029                                }
4030                            }
4031                        }
4032                        Ok(e)
4033                    })?;
4034
4035                    // transform_recursive processes bottom-up, so RANDOM() (unseeded) inside
4036                    // generator functions (UNIFORM, NORMAL, ZIPF) gets expanded before
4037                    // we see the parent. We detect this and undo the expansion by replacing
4038                    // the expanded pattern back with Expression::Random.
4039                    // Seeded RANDOM(seed) was already protected above as Function("RANDOM", [seed]).
4040                    // Note: RANDSTR is NOT included here — it needs the expanded form for unseeded
4041                    // RANDOM() since the DuckDB handler uses the expanded SQL as-is in the hash.
4042                    transform_recursive(normalized, &|e| {
4043                        if let Expression::Function(ref f) = e {
4044                            let n = f.name.to_ascii_uppercase();
4045                            if n == "UNIFORM" || n == "NORMAL" || n == "ZIPF" {
4046                                if let Expression::Function(mut f) = e {
4047                                    for arg in f.args.iter_mut() {
4048                                        // Detect expanded RANDOM pattern: CAST(-9.22... + RANDOM() * (...) AS BIGINT)
4049                                        if let Expression::Cast(ref cast) = arg {
4050                                            if matches!(
4051                                                cast.to,
4052                                                crate::expressions::DataType::BigInt { .. }
4053                                            ) {
4054                                                if let Expression::Add(ref add) = cast.this {
4055                                                    if let Expression::Literal(ref lit) = add.left {
4056                                                        if let crate::expressions::Literal::Number(
4057                                                            ref num,
4058                                                        ) = lit.as_ref()
4059                                                        {
4060                                                            if num == "-9.223372036854776E+18" {
4061                                                                *arg = Expression::Random(
4062                                                                    crate::expressions::Random,
4063                                                                );
4064                                                            }
4065                                                        }
4066                                                    }
4067                                                }
4068                                            }
4069                                        }
4070                                    }
4071                                    return Ok(Expression::Function(f));
4072                                }
4073                                return Ok(e);
4074                            }
4075                        }
4076                        match e {
4077                            Expression::Random(_) => Ok(make_scaled_random()),
4078                            // Rand(seed) with no bounds: drop seed and expand
4079                            // (DuckDB RANDOM doesn't support seeds)
4080                            Expression::Rand(ref r) if r.lower.is_none() && r.upper.is_none() => {
4081                                Ok(make_scaled_random())
4082                            }
4083                            _ => Ok(e),
4084                        }
4085                    })?
4086                } else {
4087                    normalized
4088                };
4089
4090                // Apply cross-dialect semantic normalizations
4091                let normalized = normalization::normalize(
4092                    normalized,
4093                    self.dialect_type,
4094                    target,
4095                    matches!(
4096                        opts.unsupported_level,
4097                        UnsupportedLevel::Raise | UnsupportedLevel::Immediate
4098                    ),
4099                )?;
4100
4101                let normalized = if matches!(target, DialectType::TSQL | DialectType::Fabric) {
4102                    Self::normalize_tsql_fetch_overlaps_date_bin(normalized)?
4103                } else {
4104                    normalized
4105                };
4106
4107                let normalized =
4108                    if matches!(
4109                        self.dialect_type,
4110                        DialectType::PostgreSQL | DialectType::CockroachDB
4111                    ) && !matches!(target, DialectType::PostgreSQL | DialectType::CockroachDB)
4112                    {
4113                        Self::normalize_postgres_type_function_casts(normalized, target)?
4114                    } else {
4115                        normalized
4116                    };
4117
4118                let normalized = if matches!(self.dialect_type, DialectType::SQLite)
4119                    && !matches!(target, DialectType::SQLite)
4120                {
4121                    Self::normalize_sqlite_double_quoted_defaults(normalized)?
4122                } else {
4123                    normalized
4124                };
4125
4126                let normalized = if matches!(self.dialect_type, DialectType::PostgreSQL)
4127                    && matches!(target, DialectType::SQLite)
4128                {
4129                    Self::normalize_postgres_to_sqlite_types(normalized)?
4130                } else {
4131                    normalized
4132                };
4133
4134                let normalized = if matches!(self.dialect_type, DialectType::PostgreSQL)
4135                    && matches!(target, DialectType::Fabric)
4136                {
4137                    Self::normalize_postgres_to_fabric_types(normalized)?
4138                } else {
4139                    normalized
4140                };
4141
4142                // For DuckDB target from BigQuery source: wrap UNNEST of struct arrays in
4143                // (SELECT UNNEST(..., max_depth => 2)) subquery
4144                // Must run BEFORE unnest_alias_to_column_alias since it changes alias structure
4145                let normalized = if matches!(self.dialect_type, DialectType::BigQuery)
4146                    && matches!(target, DialectType::DuckDB)
4147                {
4148                    crate::transforms::wrap_duckdb_unnest_struct(normalized)?
4149                } else {
4150                    normalized
4151                };
4152
4153                // Convert BigQuery UNNEST aliases to column-alias format for DuckDB/Presto/Spark
4154                // UNNEST(arr) AS x -> UNNEST(arr) AS _t0(x)
4155                let normalized = if matches!(self.dialect_type, DialectType::BigQuery)
4156                    && matches!(
4157                        target,
4158                        DialectType::DuckDB
4159                            | DialectType::Presto
4160                            | DialectType::Trino
4161                            | DialectType::Athena
4162                            | DialectType::Spark
4163                            | DialectType::Databricks
4164                    ) {
4165                    crate::transforms::unnest_alias_to_column_alias(normalized)?
4166                } else if matches!(self.dialect_type, DialectType::BigQuery)
4167                    && matches!(target, DialectType::BigQuery | DialectType::Redshift)
4168                {
4169                    // For BigQuery/Redshift targets: move UNNEST FROM items to CROSS JOINs
4170                    // but don't convert alias format (no _t0 wrapper)
4171                    let result = crate::transforms::unnest_from_to_cross_join(normalized)?;
4172                    // For Redshift: strip UNNEST when arg is a column reference path
4173                    if matches!(target, DialectType::Redshift) {
4174                        crate::transforms::strip_unnest_column_refs(result)?
4175                    } else {
4176                        result
4177                    }
4178                } else {
4179                    normalized
4180                };
4181
4182                // For Presto/Trino targets from PostgreSQL/Redshift source:
4183                // Wrap UNNEST aliases from GENERATE_SERIES conversion: AS s -> AS _u(s)
4184                let normalized = if matches!(
4185                    self.dialect_type,
4186                    DialectType::PostgreSQL | DialectType::Redshift
4187                ) && matches!(
4188                    target,
4189                    DialectType::Presto | DialectType::Trino | DialectType::Athena
4190                ) {
4191                    crate::transforms::wrap_unnest_join_aliases(normalized)?
4192                } else {
4193                    normalized
4194                };
4195
4196                // Eliminate DISTINCT ON with target-dialect awareness
4197                // This must happen after source transform (which may produce DISTINCT ON)
4198                // and before target transform, with knowledge of the target dialect's NULL ordering behavior
4199                let normalized = crate::transforms::eliminate_distinct_on_for_dialect(
4200                    normalized,
4201                    Some(target),
4202                    Some(self.dialect_type),
4203                )?;
4204
4205                // GENERATE_DATE_ARRAY in UNNEST -> Snowflake ARRAY_GENERATE_RANGE + DATEADD
4206                let normalized = if matches!(target, DialectType::Snowflake) {
4207                    Self::transform_generate_date_array_snowflake(normalized)?
4208                } else {
4209                    normalized
4210                };
4211
4212                // CROSS JOIN UNNEST -> LATERAL VIEW EXPLODE/INLINE for Spark/Hive/Databricks
4213                let normalized = if matches!(
4214                    target,
4215                    DialectType::Spark | DialectType::Databricks | DialectType::Hive
4216                ) {
4217                    crate::transforms::unnest_to_explode_select(normalized)?
4218                } else {
4219                    normalized
4220                };
4221
4222                // Wrap UNION with ORDER BY/LIMIT in a subquery for dialects that require it
4223                let normalized = if matches!(target, DialectType::ClickHouse | DialectType::TSQL) {
4224                    crate::transforms::no_limit_order_by_union(normalized)?
4225                } else {
4226                    normalized
4227                };
4228
4229                let normalized = if matches!(
4230                    self.dialect_type,
4231                    DialectType::PostgreSQL | DialectType::CockroachDB
4232                ) && matches!(target, DialectType::TSQL | DialectType::Fabric)
4233                {
4234                    Self::normalize_postgres_boolean_semantics_for_tsql(normalized)?
4235                } else {
4236                    normalized
4237                };
4238
4239                let normalized = if self.dialect_type == DialectType::PostgreSQL
4240                    && matches!(target, DialectType::TSQL | DialectType::Fabric)
4241                {
4242                    Self::normalize_postgres_bytea_literals_for_tsql(normalized)?
4243                } else {
4244                    normalized
4245                };
4246
4247                let normalized = if matches!(
4248                    self.dialect_type,
4249                    DialectType::PostgreSQL | DialectType::CockroachDB
4250                ) && matches!(target, DialectType::TSQL | DialectType::Fabric)
4251                {
4252                    Self::normalize_postgres_string_semantics_for_tsql(normalized)?
4253                } else {
4254                    normalized
4255                };
4256
4257                // TSQL: Convert COUNT(*) -> COUNT_BIG(*) when source is not TSQL/Fabric
4258                // Python sqlglot does this in the TSQL generator, but we can't do it there
4259                // because it would break TSQL -> TSQL identity
4260                let normalized = if matches!(target, DialectType::TSQL | DialectType::Fabric)
4261                    && !matches!(self.dialect_type, DialectType::TSQL | DialectType::Fabric)
4262                {
4263                    transform_recursive(normalized, &|e| {
4264                        if let Expression::Count(ref c) = e {
4265                            // Build COUNT_BIG(...) as an AggregateFunction
4266                            let args = if c.star {
4267                                vec![Expression::Star(crate::expressions::Star {
4268                                    table: None,
4269                                    except: None,
4270                                    replace: None,
4271                                    rename: None,
4272                                    trailing_comments: Vec::new(),
4273                                    span: None,
4274                                })]
4275                            } else if let Some(ref this) = c.this {
4276                                vec![this.clone()]
4277                            } else {
4278                                vec![]
4279                            };
4280                            Ok(Expression::AggregateFunction(Box::new(
4281                                crate::expressions::AggregateFunction {
4282                                    source_dialect: None,
4283
4284                                    name: "COUNT_BIG".to_string(),
4285                                    args,
4286                                    distinct: c.distinct,
4287                                    filter: c.filter.clone(),
4288                                    order_by: Vec::new(),
4289                                    limit: None,
4290                                    ignore_nulls: None,
4291                                    inferred_type: None,
4292                                },
4293                            )))
4294                        } else {
4295                            Ok(e)
4296                        }
4297                    })?
4298                } else {
4299                    normalized
4300                };
4301
4302                // T-SQL/Fabric do not have a scalar boolean type. Keep predicate
4303                // contexts intact, but materialize boolean-valued expressions used
4304                // as values before target transforms add ORDER BY null sort keys.
4305                let normalized = if matches!(target, DialectType::TSQL | DialectType::Fabric)
4306                    && !matches!(self.dialect_type, DialectType::TSQL | DialectType::Fabric)
4307                {
4308                    let normalized = if self.dialect_type == DialectType::PostgreSQL {
4309                        Self::rewrite_postgres_row_value_equality_for_tsql(normalized)?
4310                    } else {
4311                        normalized
4312                    };
4313                    Self::rewrite_boolean_values_for_tsql(normalized)?
4314                } else {
4315                    normalized
4316                };
4317
4318                let normalized = if matches!(
4319                    self.dialect_type,
4320                    DialectType::PostgreSQL | DialectType::CockroachDB
4321                ) && matches!(target, DialectType::TSQL | DialectType::Fabric)
4322                {
4323                    Self::rewrite_postgres_format_for_tsql(normalized, target)?
4324                } else {
4325                    normalized
4326                };
4327
4328                let normalized = if self.dialect_type == DialectType::PostgreSQL
4329                    && matches!(target, DialectType::TSQL | DialectType::Fabric)
4330                {
4331                    Self::normalize_postgres_only_for_tsql(normalized)?
4332                } else {
4333                    normalized
4334                };
4335
4336                let transformed =
4337                    target_dialect.transform_with_guard(normalized, opts.complexity_guard)?;
4338
4339                // T-SQL and Fabric do not support aggregate FILTER clauses. Rewrite any
4340                // remaining filters after target transforms so special aggregate rewrites
4341                // (for example BOOL_OR/BOOL_AND) can consume their filters first.
4342                let transformed = if matches!(target, DialectType::TSQL | DialectType::Fabric) {
4343                    Self::rewrite_aggregate_filters_for_tsql(transformed)?
4344                } else {
4345                    transformed
4346                };
4347
4348                let transformed = if matches!(
4349                    self.dialect_type,
4350                    DialectType::PostgreSQL | DialectType::CockroachDB
4351                ) && matches!(target, DialectType::TSQL | DialectType::Fabric)
4352                {
4353                    crate::transforms::grouped_percentiles_to_tsql_windows(transformed)?
4354                } else {
4355                    transformed
4356                };
4357
4358                let transformed = if matches!(
4359                    self.dialect_type,
4360                    DialectType::PostgreSQL | DialectType::CockroachDB
4361                ) && matches!(target, DialectType::TSQL | DialectType::Fabric)
4362                {
4363                    Self::normalize_postgres_trim_for_tsql(transformed)?
4364                } else {
4365                    transformed
4366                };
4367
4368                let transformed = if matches!(
4369                    self.dialect_type,
4370                    DialectType::PostgreSQL | DialectType::CockroachDB
4371                ) && matches!(target, DialectType::TSQL | DialectType::Fabric)
4372                {
4373                    Self::rewrite_postgres_json_array_elements_select_for_tsql(transformed)?
4374                } else {
4375                    transformed
4376                };
4377
4378                // DuckDB target: when FROM is RANGE(n), replace SEQ's ROW_NUMBER pattern with `range`
4379                let transformed = if matches!(target, DialectType::DuckDB) {
4380                    Self::seq_rownum_to_range(transformed)?
4381                } else {
4382                    transformed
4383                };
4384
4385                if matches!(target, DialectType::TSQL | DialectType::Fabric) {
4386                    Self::reject_tsql_interval_casts(&transformed, target, opts)?;
4387                }
4388
4389                let transformed = if matches!(target, DialectType::TSQL | DialectType::Fabric) {
4390                    Self::rewrite_tsql_interval_casts_to_varchar(transformed)?
4391                } else {
4392                    transformed
4393                };
4394
4395                let transformed = if matches!(target, DialectType::TSQL | DialectType::Fabric) {
4396                    Self::legalize_tsql_nested_order_by(transformed)?
4397                } else {
4398                    transformed
4399                };
4400
4401                Self::reject_strict_unsupported(&transformed, self.dialect_type, target, opts)?;
4402
4403                let mut sql = target_dialect.generate_with_transpile_options(
4404                    &transformed,
4405                    self.dialect_type,
4406                    opts,
4407                )?;
4408
4409                // Align a known Snowflake pretty-print edge case with Python sqlglot output.
4410                if opts.pretty && target == DialectType::Snowflake {
4411                    sql = Self::normalize_snowflake_pretty(sql);
4412                }
4413
4414                Ok(sql)
4415            })
4416            .collect()
4417    }
4418}
4419
4420// Transpile-only methods: cross-dialect normalization and helpers
4421#[cfg(feature = "transpile")]
4422impl Dialect {
4423    fn legalize_tsql_nested_order_by(expr: Expression) -> Result<Expression> {
4424        let preserve_root_order = matches!(&expr, Expression::Select(select) if Self::tsql_select_needs_order_offset(select));
4425
4426        let mut transformed = transform_recursive(expr, &|node| match node {
4427            Expression::Select(mut select) => {
4428                Self::legalize_tsql_select_offset(&mut select);
4429                if Self::tsql_select_needs_order_offset(&select) {
4430                    select.offset = Some(Offset {
4431                        this: Expression::Literal(Box::new(Literal::Number("0".to_string()))),
4432                        rows: Some(true),
4433                    });
4434                }
4435                Ok(Expression::Select(select))
4436            }
4437            Expression::Subquery(mut subquery) => {
4438                Self::legalize_tsql_offset(&mut subquery.order_by, &mut subquery.offset, false);
4439                Ok(Expression::Subquery(subquery))
4440            }
4441            Expression::Union(mut union) => {
4442                Self::legalize_tsql_set_offset(&mut union.order_by, &mut union.offset);
4443                Ok(Expression::Union(union))
4444            }
4445            Expression::Intersect(mut intersect) => {
4446                Self::legalize_tsql_set_offset(&mut intersect.order_by, &mut intersect.offset);
4447                Ok(Expression::Intersect(intersect))
4448            }
4449            Expression::Except(mut except) => {
4450                Self::legalize_tsql_set_offset(&mut except.order_by, &mut except.offset);
4451                Ok(Expression::Except(except))
4452            }
4453            other => Ok(other),
4454        })?;
4455
4456        if preserve_root_order {
4457            if let Expression::Select(select) = &mut transformed {
4458                select.offset = None;
4459            }
4460        }
4461
4462        Self::drop_tsql_unbounded_nested_set_order_by(transformed)
4463    }
4464
4465    fn drop_tsql_unbounded_nested_set_order_by(mut expr: Expression) -> Result<Expression> {
4466        let root_order_by = Self::take_tsql_root_set_order_by(&mut expr);
4467
4468        let mut transformed = transform_recursive(expr, &|node| match node {
4469            Expression::Union(mut union) => {
4470                if union.limit.is_none() && union.offset.is_none() {
4471                    union.order_by = None;
4472                }
4473                Ok(Expression::Union(union))
4474            }
4475            Expression::Intersect(mut intersect) => {
4476                if intersect.limit.is_none() && intersect.offset.is_none() {
4477                    intersect.order_by = None;
4478                }
4479                Ok(Expression::Intersect(intersect))
4480            }
4481            Expression::Except(mut except) => {
4482                if except.limit.is_none() && except.offset.is_none() {
4483                    except.order_by = None;
4484                }
4485                Ok(Expression::Except(except))
4486            }
4487            other => Ok(other),
4488        })?;
4489
4490        if let Some(order_by) = root_order_by {
4491            Self::restore_tsql_root_set_order_by(&mut transformed, order_by);
4492        }
4493
4494        Ok(transformed)
4495    }
4496
4497    fn take_tsql_root_set_order_by(expr: &mut Expression) -> Option<OrderBy> {
4498        match expr {
4499            Expression::Union(union) => union.order_by.take(),
4500            Expression::Intersect(intersect) => intersect.order_by.take(),
4501            Expression::Except(except) => except.order_by.take(),
4502            Expression::Subquery(subquery) if subquery.alias.is_none() => {
4503                Self::take_tsql_root_set_order_by(&mut subquery.this)
4504            }
4505            Expression::Paren(paren) => Self::take_tsql_root_set_order_by(&mut paren.this),
4506            _ => None,
4507        }
4508    }
4509
4510    fn restore_tsql_root_set_order_by(expr: &mut Expression, order_by: OrderBy) {
4511        match expr {
4512            Expression::Union(union) => union.order_by = Some(order_by),
4513            Expression::Intersect(intersect) => intersect.order_by = Some(order_by),
4514            Expression::Except(except) => except.order_by = Some(order_by),
4515            Expression::Subquery(subquery) if subquery.alias.is_none() => {
4516                Self::restore_tsql_root_set_order_by(&mut subquery.this, order_by);
4517            }
4518            Expression::Paren(paren) => {
4519                Self::restore_tsql_root_set_order_by(&mut paren.this, order_by);
4520            }
4521            _ => {}
4522        }
4523    }
4524
4525    fn legalize_tsql_select_offset(select: &mut crate::expressions::Select) {
4526        let has_fetch = select.fetch.is_some();
4527        Self::legalize_tsql_offset(&mut select.order_by, &mut select.offset, has_fetch);
4528    }
4529
4530    fn legalize_tsql_offset(
4531        order_by: &mut Option<OrderBy>,
4532        offset: &mut Option<Offset>,
4533        retain_inert_offset: bool,
4534    ) {
4535        if order_by.is_some() {
4536            return;
4537        }
4538
4539        if offset
4540            .as_ref()
4541            .is_some_and(|offset| Self::tsql_offset_is_inert(&offset.this))
4542            && !retain_inert_offset
4543        {
4544            *offset = None;
4545        } else if offset.is_some() {
4546            *order_by = Some(Generator::dummy_tsql_order_by());
4547        }
4548    }
4549
4550    fn legalize_tsql_set_offset(
4551        order_by: &mut Option<OrderBy>,
4552        offset: &mut Option<Box<Expression>>,
4553    ) {
4554        if order_by.is_some() {
4555            return;
4556        }
4557
4558        if offset.as_deref().is_some_and(Self::tsql_offset_is_inert) {
4559            *offset = None;
4560        } else if offset.is_some() {
4561            *order_by = Some(Generator::dummy_tsql_order_by());
4562        }
4563    }
4564
4565    fn tsql_offset_is_inert(expr: &Expression) -> bool {
4566        match expr {
4567            Expression::Null(_) => true,
4568            Expression::Literal(literal) => match literal.as_ref() {
4569                Literal::Number(value) => value.parse::<i128>().is_ok_and(|value| value == 0),
4570                _ => false,
4571            },
4572            _ => false,
4573        }
4574    }
4575
4576    fn tsql_select_needs_order_offset(select: &crate::expressions::Select) -> bool {
4577        select.order_by.is_some()
4578            && select.top.is_none()
4579            && select.limit.is_none()
4580            && select.offset.is_none()
4581            && select.fetch.is_none()
4582            && select.for_xml.is_empty()
4583            && select.for_json.is_empty()
4584    }
4585
4586    fn reject_hana_source_semantics(
4587        expression: &Expression,
4588        source: DialectType,
4589        target: DialectType,
4590    ) -> Result<()> {
4591        if source != DialectType::HANA || target == DialectType::HANA {
4592            return Ok(());
4593        }
4594        let mut pending = vec![expression];
4595        while let Some(node) = pending.pop() {
4596            if node.source_dialect() == Some(DialectType::HANA) {
4597                Generator::validate_hana_source_node(node, target)?;
4598            }
4599            // A supported outer call does not establish support for its arguments.
4600            // Types in column definitions, casts, routine signatures, etc. are
4601            // embedded syntax, not Expression children. Validate each type once,
4602            // before normalization can replace its enclosing ARRAY/STRUCT type.
4603            // A scalar native CAST already uses a value-aware conversion check
4604            // (e.g. a HANA TINYINT literal can fit a target signed integer).
4605            let cast_type_validated = matches!(node, Expression::Cast(c)
4606                if matches!(c.to, crate::expressions::DataType::Hana { .. }));
4607            let mut type_result = Ok(());
4608            crate::ast_children::for_each_child_and_type_untracked(
4609                node,
4610                |child| pending.push(child),
4611                |data_type| {
4612                    if !cast_type_validated && type_result.is_ok() {
4613                        type_result = Generator::validate_hana_source_type(data_type, target);
4614                    }
4615                },
4616            );
4617            type_result?;
4618        }
4619        Ok(())
4620    }
4621
4622    fn reject_clickhouse_session_semantics(
4623        expr: &Expression,
4624        source: DialectType,
4625        target: DialectType,
4626        opts: &TranspileOptions,
4627    ) -> Result<()> {
4628        if !matches!(
4629            opts.unsupported_level,
4630            UnsupportedLevel::Raise | UnsupportedLevel::Immediate
4631        ) || target != DialectType::ClickHouse
4632            || source == DialectType::ClickHouse
4633        {
4634            return Ok(());
4635        }
4636
4637        const JOIN_USE_NULLS_DIAGNOSTIC: &str = "ClickHouse outer joins require the target setting join_use_nulls = 1 to preserve unmatched-row NULL semantics";
4638        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";
4639
4640        fn is_current_session_time(node: &Expression) -> bool {
4641            match node {
4642                Expression::CurrentDate(_)
4643                | Expression::CurrentTime(_)
4644                | Expression::CurrentTimestamp(_)
4645                | Expression::CurrentTimestampLTZ(_)
4646                | Expression::Localtime(_)
4647                | Expression::Localtimestamp(_)
4648                | Expression::Systimestamp(_) => true,
4649                Expression::Function(function) if !function.quoted => matches!(
4650                    function.name.to_ascii_uppercase().as_str(),
4651                    "CURRENT_DATE"
4652                        | "CURRENT_TIME"
4653                        | "LOCALTIME"
4654                        | "CURRENT_TIMESTAMP"
4655                        | "CURRENT_TIMESTAMP_LTZ"
4656                        | "LOCALTIMESTAMP"
4657                        | "NOW"
4658                        | "GETDATE"
4659                        | "SYSTIMESTAMP"
4660                ),
4661                _ => false,
4662            }
4663        }
4664
4665        fn is_session_week_part(expression: &Expression) -> bool {
4666            let part = match expression {
4667                Expression::Literal(literal) => match literal.as_ref() {
4668                    Literal::String(value) => Some(value.as_str()),
4669                    _ => None,
4670                },
4671                Expression::Identifier(identifier) => Some(identifier.name.as_str()),
4672                Expression::Var(var) => Some(var.this.as_str()),
4673                Expression::Column(column) if column.table.is_none() => {
4674                    Some(column.name.name.as_str())
4675                }
4676                _ => None,
4677            };
4678            part.is_some_and(|part| {
4679                matches!(
4680                    part.to_ascii_uppercase().as_str(),
4681                    "WEEK" | "W" | "WK" | "WEEKOFYEAR" | "WOY" | "WY"
4682                )
4683            })
4684        }
4685
4686        fn is_session_week_trunc(node: &Expression) -> bool {
4687            match node {
4688                Expression::DateTrunc(date_trunc) => {
4689                    matches!(date_trunc.unit, DateTimeField::Week)
4690                }
4691                Expression::Function(function)
4692                    if !function.quoted && function.name.eq_ignore_ascii_case("DATE_TRUNC") =>
4693                {
4694                    function.args.first().is_some_and(is_session_week_part)
4695                }
4696                _ => false,
4697            }
4698        }
4699
4700        fn is_null_extending_clickhouse_join(node: &Expression) -> bool {
4701            fn join_is_null_extending(join: &Join) -> bool {
4702                matches!(
4703                    join.kind,
4704                    JoinKind::Left
4705                        | JoinKind::Right
4706                        | JoinKind::Full
4707                        | JoinKind::NaturalLeft
4708                        | JoinKind::NaturalRight
4709                        | JoinKind::NaturalFull
4710                        | JoinKind::AsOfLeft
4711                        | JoinKind::AsOfRight
4712                )
4713            }
4714
4715            fn joins_are_null_extending(joins: &[Join]) -> bool {
4716                joins.iter().any(join_is_null_extending)
4717            }
4718
4719            match node {
4720                Expression::Join(join) => join_is_null_extending(join),
4721                Expression::Select(select) => joins_are_null_extending(&select.joins),
4722                Expression::JoinedTable(joined) => joins_are_null_extending(&joined.joins),
4723                Expression::Update(update) => {
4724                    joins_are_null_extending(&update.table_joins)
4725                        || joins_are_null_extending(&update.from_joins)
4726                }
4727                Expression::Delete(delete) => joins_are_null_extending(&delete.joins),
4728                _ => false,
4729            }
4730        }
4731
4732        fn is_count_like_aggregate(node: &Expression) -> bool {
4733            match node {
4734                Expression::Count(_)
4735                | Expression::CountIf(_)
4736                | Expression::ApproxDistinct(_)
4737                | Expression::ApproxCountDistinct(_) => true,
4738                Expression::AggregateFunction(function) => matches!(
4739                    function.name.to_ascii_uppercase().as_str(),
4740                    "COUNT" | "COUNT_IF" | "COUNTIF" | "APPROX_DISTINCT" | "APPROX_COUNT_DISTINCT"
4741                ),
4742                _ => false,
4743            }
4744        }
4745
4746        fn is_empty_input_sensitive_aggregate(node: &Expression, source: DialectType) -> bool {
4747            Dialect::node_is_aggregate_function(node)
4748                && !is_count_like_aggregate(node)
4749                && !matches!(node, Expression::BitwiseCount(_))
4750                && !matches!(node, Expression::Median(_) if source == DialectType::Snowflake)
4751                && !matches!(
4752                    node,
4753                    Expression::AggregateFunction(function)
4754                        if function.name.to_ascii_lowercase().ends_with("ornull")
4755                )
4756        }
4757
4758        fn setting_is_enabled(setting: &Expression, name: &str) -> bool {
4759            let Expression::Eq(equality) = setting else {
4760                return false;
4761            };
4762            let setting_name = match &equality.left {
4763                Expression::Identifier(identifier) => identifier.name.as_str(),
4764                Expression::Column(column) if column.table.is_none() => column.name.name.as_str(),
4765                Expression::Var(var) => var.this.as_str(),
4766                _ => return false,
4767            };
4768            let enabled = match &equality.right {
4769                Expression::Literal(literal) => {
4770                    matches!(literal.as_ref(), Literal::Number(value) if value.parse::<i128>().is_ok_and(|value| value == 1))
4771                }
4772                Expression::Boolean(boolean) => boolean.value,
4773                _ => false,
4774            };
4775            setting_name.eq_ignore_ascii_case(name) && enabled
4776        }
4777
4778        fn query_has_enabled_setting(expr: &Expression, name: &str) -> bool {
4779            expr.dfs().any(|node| {
4780                matches!(
4781                    node,
4782                    Expression::Select(select)
4783                        if select.settings.as_ref().is_some_and(|settings| {
4784                            settings.iter().any(|setting| setting_is_enabled(setting, name))
4785                        })
4786                )
4787            })
4788        }
4789
4790        let mut diagnostics = Vec::new();
4791        let join_use_nulls_enabled = query_has_enabled_setting(expr, "join_use_nulls");
4792        let aggregate_null_for_empty_enabled =
4793            query_has_enabled_setting(expr, "aggregate_functions_null_for_empty");
4794        for node in expr.dfs() {
4795            if source == DialectType::Snowflake && is_current_session_time(node) {
4796                Self::push_unsupported_diagnostic(
4797                    &mut diagnostics,
4798                    "Snowflake current date/time expressions depend on the session TIMEZONE, which cannot be preserved for ClickHouse",
4799                );
4800            }
4801            if source == DialectType::Snowflake && is_session_week_trunc(node) {
4802                Self::push_unsupported_diagnostic(
4803                    &mut diagnostics,
4804                    "Snowflake DATE_TRUNC with a week unit depends on the session WEEK_START, which cannot be preserved for ClickHouse",
4805                );
4806            }
4807            if !join_use_nulls_enabled && is_null_extending_clickhouse_join(node) {
4808                Self::push_unsupported_diagnostic(&mut diagnostics, JOIN_USE_NULLS_DIAGNOSTIC);
4809            }
4810            if !aggregate_null_for_empty_enabled && is_empty_input_sensitive_aggregate(node, source)
4811            {
4812                Self::push_unsupported_diagnostic(
4813                    &mut diagnostics,
4814                    AGGREGATE_NULL_FOR_EMPTY_DIAGNOSTIC,
4815                );
4816            }
4817            if opts.unsupported_level == UnsupportedLevel::Immediate && !diagnostics.is_empty() {
4818                break;
4819            }
4820        }
4821
4822        if diagnostics.is_empty() {
4823            return Ok(());
4824        }
4825
4826        let limit = if opts.unsupported_level == UnsupportedLevel::Immediate {
4827            1
4828        } else {
4829            opts.max_unsupported.max(1)
4830        };
4831        let mut messages = diagnostics.iter().take(limit).cloned().collect::<Vec<_>>();
4832        if diagnostics.len() > limit {
4833            messages.push(format!("... and {} more", diagnostics.len() - limit));
4834        }
4835
4836        Err(crate::error::Error::unsupported(
4837            messages.join("; "),
4838            target.to_string(),
4839        ))
4840    }
4841
4842    fn reject_strict_unsupported(
4843        expr: &Expression,
4844        source: DialectType,
4845        target: DialectType,
4846        opts: &TranspileOptions,
4847    ) -> Result<()> {
4848        if !matches!(
4849            opts.unsupported_level,
4850            UnsupportedLevel::Raise | UnsupportedLevel::Immediate
4851        ) {
4852            return Ok(());
4853        }
4854
4855        let mut diagnostics = Vec::new();
4856        if matches!(source, DialectType::PostgreSQL | DialectType::CockroachDB)
4857            && matches!(target, DialectType::TSQL | DialectType::Fabric)
4858            && Self::tsql_apply_has_invalid_outer_aggregate(expr)
4859        {
4860            Self::push_unsupported_diagnostic(
4861                &mut diagnostics,
4862                "APPLY aggregate expressions that combine an outer reference with another column reference",
4863            );
4864        }
4865        let structural_grouping_tuples =
4866            if matches!(source, DialectType::PostgreSQL | DialectType::CockroachDB)
4867                && matches!(target, DialectType::TSQL | DialectType::Fabric)
4868            {
4869                Self::collect_tsql_grouping_tuple_nodes(expr)
4870            } else {
4871                HashSet::new()
4872            };
4873
4874        for node in expr.dfs() {
4875            if source == DialectType::Snowflake && target == DialectType::ClickHouse {
4876                if Self::node_is_function_named(node, "TO_CHAR") {
4877                    Self::push_unsupported_diagnostic(
4878                        &mut diagnostics,
4879                        "Snowflake TO_CHAR overload or dynamic format",
4880                    );
4881                }
4882                if Self::node_is_function_named(node, "TRY_TO_DOUBLE")
4883                    || matches!(node, Expression::ToDouble(to_double) if to_double.safe.is_some() && to_double.format.is_some())
4884                {
4885                    Self::push_unsupported_diagnostic(
4886                        &mut diagnostics,
4887                        "Snowflake TRY_TO_DOUBLE with a format model",
4888                    );
4889                }
4890                if Self::node_is_function_named(node, "TRY_TO_NUMBER")
4891                    || Self::node_is_function_named(node, "TRY_TO_NUMERIC")
4892                    || Self::node_is_function_named(node, "TRY_TO_DECIMAL")
4893                    || matches!(node, Expression::ToNumber(to_number) if to_number.safe.is_some())
4894                {
4895                    Self::push_unsupported_diagnostic(
4896                        &mut diagnostics,
4897                        "Snowflake TRY_TO_NUMBER decimal conversion semantics",
4898                    );
4899                }
4900                if Self::node_is_function_named(node, "FLATTEN") {
4901                    Self::push_unsupported_diagnostic(
4902                        &mut diagnostics,
4903                        "Snowflake FLATTEN table-function semantics",
4904                    );
4905                }
4906            }
4907
4908            if matches!(target, DialectType::Fabric | DialectType::Hive)
4909                && Self::node_has_recursive_with(node)
4910            {
4911                Self::push_unsupported_diagnostic(&mut diagnostics, "recursive CTEs");
4912            }
4913
4914            if matches!(target, DialectType::TSQL | DialectType::Fabric)
4915                && Self::node_has_lateral(node)
4916            {
4917                Self::push_unsupported_diagnostic(&mut diagnostics, "LATERAL joins and subqueries");
4918            }
4919
4920            if matches!(target, DialectType::TSQL | DialectType::Fabric) {
4921                if Self::node_has_join_using(node) {
4922                    Self::push_unsupported_diagnostic(&mut diagnostics, "JOIN USING clauses");
4923                }
4924                if Self::node_has_natural_join(node) {
4925                    Self::push_unsupported_diagnostic(&mut diagnostics, "NATURAL JOIN");
4926                }
4927                if Self::node_has_unsupported_relation_column_aliases(node) {
4928                    Self::push_unsupported_diagnostic(
4929                        &mut diagnostics,
4930                        "column alias lists on base or joined table references",
4931                    );
4932                }
4933                if Self::node_has_qualified_whole_row_aggregate_argument(node) {
4934                    Self::push_unsupported_diagnostic(
4935                        &mut diagnostics,
4936                        "qualified whole-row aggregate arguments",
4937                    );
4938                }
4939                if Self::node_has_subquery_in_aggregate_argument(node) {
4940                    Self::push_unsupported_diagnostic(
4941                        &mut diagnostics,
4942                        "aggregate arguments containing subqueries",
4943                    );
4944                }
4945            }
4946
4947            if !Self::target_supports_distinct_on(target) && Self::node_has_distinct_on(node) {
4948                Self::push_unsupported_diagnostic(&mut diagnostics, "DISTINCT ON");
4949            }
4950
4951            if !Self::target_supports_remaining_unnest(target) && Self::node_is_unnest(node) {
4952                Self::push_unsupported_diagnostic(&mut diagnostics, "UNNEST");
4953            }
4954
4955            if !Self::target_supports_remaining_explode(target)
4956                && Self::node_is_explode(node)
4957                && !(source == DialectType::Vertica && target == DialectType::Vertica)
4958            {
4959                Self::push_unsupported_diagnostic(&mut diagnostics, "EXPLODE");
4960            }
4961
4962            if Self::target_lacks_array_agg(target) && Self::node_is_array_agg(node) {
4963                Self::push_unsupported_diagnostic(&mut diagnostics, "ARRAY_AGG");
4964            }
4965
4966            if matches!(target, DialectType::TSQL | DialectType::Fabric)
4967                && Self::node_is_distinct_string_agg(node)
4968            {
4969                Self::push_unsupported_diagnostic(&mut diagnostics, "STRING_AGG with DISTINCT");
4970            }
4971
4972            if matches!(target, DialectType::TSQL | DialectType::Fabric)
4973                && matches!(node, Expression::NthValue(_))
4974            {
4975                Self::push_unsupported_diagnostic(&mut diagnostics, "NTH_VALUE");
4976            }
4977
4978            if matches!(target, DialectType::TSQL | DialectType::Fabric) {
4979                if let Some(frame) = Self::node_window_frame(node) {
4980                    if matches!(frame.kind, WindowFrameKind::Groups) {
4981                        Self::push_unsupported_diagnostic(&mut diagnostics, "GROUPS window frames");
4982                    }
4983                    if matches!(frame.kind, WindowFrameKind::Range)
4984                        && (Self::window_frame_bound_has_value_offset(&frame.start)
4985                            || frame
4986                                .end
4987                                .as_ref()
4988                                .is_some_and(Self::window_frame_bound_has_value_offset))
4989                    {
4990                        Self::push_unsupported_diagnostic(
4991                            &mut diagnostics,
4992                            "value-offset RANGE window frames",
4993                        );
4994                    }
4995                    if frame.exclude.is_some() {
4996                        Self::push_unsupported_diagnostic(
4997                            &mut diagnostics,
4998                            "window frame EXCLUDE clauses",
4999                        );
5000                    }
5001                }
5002            }
5003
5004            if matches!(target, DialectType::TSQL | DialectType::Fabric)
5005                && Self::node_is_regex_predicate(node)
5006            {
5007                Self::push_unsupported_diagnostic(
5008                    &mut diagnostics,
5009                    "regular expression predicates",
5010                );
5011            }
5012
5013            if matches!(target, DialectType::TSQL | DialectType::Fabric)
5014                && Self::node_is_non_subquery_any(node)
5015            {
5016                Self::push_unsupported_diagnostic(
5017                    &mut diagnostics,
5018                    "ANY over non-subquery expressions",
5019                );
5020            }
5021
5022            if matches!(target, DialectType::TSQL | DialectType::Fabric)
5023                && Self::node_is_row_value_subquery_comparison(node)
5024            {
5025                Self::push_unsupported_diagnostic(
5026                    &mut diagnostics,
5027                    "row-value subquery comparisons",
5028                );
5029            }
5030
5031            if matches!(target, DialectType::TSQL | DialectType::Fabric)
5032                && Self::node_is_row_value_values_membership(node)
5033            {
5034                Self::push_unsupported_diagnostic(
5035                    &mut diagnostics,
5036                    "row-value VALUES membership comparisons",
5037                );
5038            }
5039
5040            if matches!(target, DialectType::TSQL | DialectType::Fabric)
5041                && Self::node_has_fetch_with_ties(node)
5042            {
5043                Self::push_unsupported_diagnostic(&mut diagnostics, "FETCH WITH TIES without TOP");
5044            }
5045
5046            if matches!(target, DialectType::TSQL | DialectType::Fabric)
5047                && Self::node_is_overlaps(node)
5048            {
5049                Self::push_unsupported_diagnostic(&mut diagnostics, "OVERLAPS");
5050            }
5051
5052            if matches!(target, DialectType::TSQL | DialectType::Fabric)
5053                && Self::node_is_date_bin(node)
5054            {
5055                Self::push_unsupported_diagnostic(&mut diagnostics, "DATE_BIN");
5056            }
5057
5058            if source == DialectType::PostgreSQL
5059                && matches!(target, DialectType::TSQL | DialectType::Fabric)
5060                && Self::node_is_unresolved_postgres_date_subtraction(node)
5061            {
5062                Self::push_unsupported_diagnostic(
5063                    &mut diagnostics,
5064                    "PostgreSQL date subtraction with an unresolved column type",
5065                );
5066            }
5067
5068            if matches!(source, DialectType::PostgreSQL | DialectType::CockroachDB)
5069                && !matches!(target, DialectType::PostgreSQL | DialectType::CockroachDB)
5070            {
5071                if Self::node_is_postgres_json_build_object(node)
5072                    && !(matches!(target, DialectType::TSQL | DialectType::Fabric)
5073                        && Self::postgres_json_build_object_can_lower_to_json_object(node))
5074                {
5075                    Self::push_unsupported_diagnostic(
5076                        &mut diagnostics,
5077                        "PostgreSQL JSON_BUILD_OBJECT",
5078                    );
5079                }
5080                if Self::node_is_function_named(node, "TO_TSVECTOR") {
5081                    Self::push_unsupported_diagnostic(&mut diagnostics, "PostgreSQL TO_TSVECTOR");
5082                }
5083                if matches!(target, DialectType::TSQL | DialectType::Fabric) {
5084                    if let Some(composite_semantics) =
5085                        Self::postgres_tsql_unsupported_composite_semantics(
5086                            node,
5087                            structural_grouping_tuples.contains(&(node as *const Expression)),
5088                        )
5089                    {
5090                        Self::push_unsupported_diagnostic(
5091                            &mut diagnostics,
5092                            &format!("PostgreSQL {composite_semantics}"),
5093                        );
5094                    }
5095                    if Self::node_is_postgres_unknown_cast(node) {
5096                        Self::push_unsupported_diagnostic(
5097                            &mut diagnostics,
5098                            "PostgreSQL unresolved UNKNOWN casts",
5099                        );
5100                    }
5101                    if let Some(collation_name) =
5102                        Self::postgres_tsql_unsupported_collation_name(node)
5103                    {
5104                        Self::push_unsupported_diagnostic(
5105                            &mut diagnostics,
5106                            &format!("PostgreSQL collation \"{collation_name}\""),
5107                        );
5108                    }
5109                    if let Some(array_semantics) =
5110                        Self::postgres_tsql_unsupported_array_semantics(node)
5111                    {
5112                        Self::push_unsupported_diagnostic(
5113                            &mut diagnostics,
5114                            &format!("PostgreSQL {array_semantics}"),
5115                        );
5116                    }
5117                    if let Some(string_semantics) =
5118                        Self::postgres_tsql_unsupported_string_semantics(node)
5119                    {
5120                        Self::push_unsupported_diagnostic(
5121                            &mut diagnostics,
5122                            &format!("PostgreSQL {string_semantics}"),
5123                        );
5124                    }
5125                    if source == DialectType::PostgreSQL {
5126                        if let Some(binary_semantics) =
5127                            Self::postgres_tsql_unsupported_binary_semantics(node)
5128                        {
5129                            Self::push_unsupported_diagnostic(
5130                                &mut diagnostics,
5131                                &format!("PostgreSQL {binary_semantics}"),
5132                            );
5133                        }
5134                    }
5135                    if let Some(function_name) =
5136                        Self::postgres_tsql_unsupported_function_name(node, target)
5137                    {
5138                        Self::push_unsupported_diagnostic(
5139                            &mut diagnostics,
5140                            &format!("PostgreSQL {function_name}"),
5141                        );
5142                    }
5143                }
5144                if matches!(target, DialectType::TSQL | DialectType::Fabric)
5145                    && Self::node_is_postgres_type_function_cast(node)
5146                {
5147                    Self::push_unsupported_diagnostic(
5148                        &mut diagnostics,
5149                        "PostgreSQL type-name function casts",
5150                    );
5151                }
5152            }
5153
5154            if opts.unsupported_level == UnsupportedLevel::Immediate && !diagnostics.is_empty() {
5155                break;
5156            }
5157        }
5158
5159        if matches!(target, DialectType::TSQL | DialectType::Fabric) {
5160            Self::collect_tsql_unsupported_ordered_sets(expr, &mut diagnostics);
5161            Self::collect_tsql_windows_missing_order(expr, &HashMap::new(), &mut diagnostics);
5162        }
5163
5164        if diagnostics.is_empty() {
5165            return Ok(());
5166        }
5167
5168        let limit = if opts.unsupported_level == UnsupportedLevel::Immediate {
5169            1
5170        } else {
5171            opts.max_unsupported.max(1)
5172        };
5173        let mut messages = diagnostics.iter().take(limit).cloned().collect::<Vec<_>>();
5174        if diagnostics.len() > limit {
5175            messages.push(format!("... and {} more", diagnostics.len() - limit));
5176        }
5177
5178        Err(crate::error::Error::unsupported(
5179            messages.join("; "),
5180            target.to_string(),
5181        ))
5182    }
5183
5184    fn reject_postgres_tsql_strict_regex_predicates(
5185        expr: &Expression,
5186        source: DialectType,
5187        target: DialectType,
5188        opts: &TranspileOptions,
5189    ) -> Result<()> {
5190        if !matches!(
5191            opts.unsupported_level,
5192            UnsupportedLevel::Raise | UnsupportedLevel::Immediate
5193        ) || !matches!(source, DialectType::PostgreSQL | DialectType::CockroachDB)
5194            || !matches!(target, DialectType::TSQL | DialectType::Fabric)
5195        {
5196            return Ok(());
5197        }
5198
5199        if expr.dfs().any(Self::node_is_regex_predicate) {
5200            return Err(crate::error::Error::unsupported(
5201                "regular expression predicates",
5202                target.to_string(),
5203            ));
5204        }
5205
5206        Ok(())
5207    }
5208
5209    fn reject_tsql_strict_json_constructor_return_types(
5210        expr: &Expression,
5211        source: DialectType,
5212        target: DialectType,
5213        opts: &TranspileOptions,
5214    ) -> Result<()> {
5215        if !matches!(
5216            opts.unsupported_level,
5217            UnsupportedLevel::Raise | UnsupportedLevel::Immediate
5218        ) || source == target
5219            || !matches!(target, DialectType::TSQL | DialectType::Fabric)
5220        {
5221            return Ok(());
5222        }
5223
5224        let mut diagnostics = Vec::new();
5225        for node in expr.dfs() {
5226            if let Some(return_type) =
5227                normalization::unsupported_tsql_json_constructor_return_type(node)
5228            {
5229                let message =
5230                    format!("SQL/JSON constructor RETURNING {return_type} cannot be preserved");
5231                Self::push_unsupported_diagnostic(&mut diagnostics, &message);
5232                if opts.unsupported_level == UnsupportedLevel::Immediate {
5233                    break;
5234                }
5235            }
5236        }
5237
5238        if diagnostics.is_empty() {
5239            return Ok(());
5240        }
5241
5242        let limit = if opts.unsupported_level == UnsupportedLevel::Immediate {
5243            1
5244        } else {
5245            opts.max_unsupported.max(1)
5246        };
5247        let mut messages = diagnostics.iter().take(limit).cloned().collect::<Vec<_>>();
5248        if diagnostics.len() > limit {
5249            messages.push(format!("... and {} more", diagnostics.len() - limit));
5250        }
5251
5252        Err(crate::error::Error::unsupported(
5253            messages.join("; "),
5254            target.to_string(),
5255        ))
5256    }
5257
5258    fn reject_postgres_tsql_strict_json_aggregate_modifiers(
5259        expr: &Expression,
5260        source: DialectType,
5261        target: DialectType,
5262        opts: &TranspileOptions,
5263    ) -> Result<()> {
5264        if !matches!(
5265            opts.unsupported_level,
5266            UnsupportedLevel::Raise | UnsupportedLevel::Immediate
5267        ) || !matches!(source, DialectType::PostgreSQL | DialectType::CockroachDB)
5268            || !matches!(target, DialectType::TSQL | DialectType::Fabric)
5269        {
5270            return Ok(());
5271        }
5272
5273        let mut diagnostics = Vec::new();
5274        for node in expr.dfs() {
5275            match node {
5276                Expression::Function(function)
5277                    if !function.quoted
5278                        && matches!(
5279                            function.name.to_ascii_uppercase().as_str(),
5280                            "JSON_AGG" | "JSONB_AGG"
5281                        ) =>
5282                {
5283                    let name = function.name.to_ascii_uppercase();
5284                    if function.args.len() != 1 {
5285                        Self::push_unsupported_diagnostic(
5286                            &mut diagnostics,
5287                            &format!("PostgreSQL {name} with invalid argument count"),
5288                        );
5289                    }
5290                    if function.distinct {
5291                        Self::push_unsupported_diagnostic(
5292                            &mut diagnostics,
5293                            &format!("PostgreSQL {name} with DISTINCT"),
5294                        );
5295                    }
5296                }
5297                Expression::AggregateFunction(function)
5298                    if matches!(
5299                        function.name.to_ascii_uppercase().as_str(),
5300                        "JSON_AGG" | "JSONB_AGG"
5301                    ) =>
5302                {
5303                    let name = function.name.to_ascii_uppercase();
5304                    if function.args.len() != 1 {
5305                        Self::push_unsupported_diagnostic(
5306                            &mut diagnostics,
5307                            &format!("PostgreSQL {name} with invalid argument count"),
5308                        );
5309                    }
5310                    if function.distinct {
5311                        Self::push_unsupported_diagnostic(
5312                            &mut diagnostics,
5313                            &format!("PostgreSQL {name} with DISTINCT"),
5314                        );
5315                    }
5316                    if function.filter.is_some() {
5317                        Self::push_unsupported_diagnostic(
5318                            &mut diagnostics,
5319                            &format!("PostgreSQL {name} with FILTER"),
5320                        );
5321                    }
5322                    if function.limit.is_some() || function.ignore_nulls.is_some() {
5323                        Self::push_unsupported_diagnostic(
5324                            &mut diagnostics,
5325                            &format!("PostgreSQL {name} with unsupported aggregate modifiers"),
5326                        );
5327                    }
5328                }
5329                Expression::Filter(filter) => {
5330                    if let Some(name) = Self::postgres_json_aggregate_name(&filter.this) {
5331                        Self::push_unsupported_diagnostic(
5332                            &mut diagnostics,
5333                            &format!("PostgreSQL {name} with FILTER"),
5334                        );
5335                    }
5336                }
5337                _ => {}
5338            }
5339
5340            if opts.unsupported_level == UnsupportedLevel::Immediate && !diagnostics.is_empty() {
5341                break;
5342            }
5343        }
5344
5345        if diagnostics.is_empty() {
5346            return Ok(());
5347        }
5348
5349        let limit = if opts.unsupported_level == UnsupportedLevel::Immediate {
5350            1
5351        } else {
5352            opts.max_unsupported.max(1)
5353        };
5354        let mut messages = diagnostics.iter().take(limit).cloned().collect::<Vec<_>>();
5355        if diagnostics.len() > limit {
5356            messages.push(format!("... and {} more", diagnostics.len() - limit));
5357        }
5358
5359        Err(crate::error::Error::unsupported(
5360            messages.join("; "),
5361            target.to_string(),
5362        ))
5363    }
5364
5365    fn postgres_json_aggregate_name(expr: &Expression) -> Option<String> {
5366        let name = match expr {
5367            Expression::Function(function) if !function.quoted => &function.name,
5368            Expression::AggregateFunction(function) => &function.name,
5369            _ => return None,
5370        };
5371        let name = name.to_ascii_uppercase();
5372        matches!(name.as_str(), "JSON_AGG" | "JSONB_AGG").then_some(name)
5373    }
5374
5375    fn push_unsupported_diagnostic(diagnostics: &mut Vec<String>, message: &str) {
5376        if !diagnostics.iter().any(|existing| existing == message) {
5377            diagnostics.push(message.to_string());
5378        }
5379    }
5380
5381    fn node_is_unresolved_postgres_date_subtraction(expr: &Expression) -> bool {
5382        let Expression::Sub(op) = expr else {
5383            return false;
5384        };
5385
5386        (Self::is_explicit_date_expr(&op.left) && Self::is_column_expr(&op.right))
5387            || (Self::is_column_expr(&op.left) && Self::is_explicit_date_expr(&op.right))
5388    }
5389
5390    fn is_column_expr(expr: &Expression) -> bool {
5391        match expr {
5392            Expression::Column(_) => true,
5393            Expression::Paren(paren) => Self::is_column_expr(&paren.this),
5394            _ => false,
5395        }
5396    }
5397
5398    fn node_window_frame(expr: &Expression) -> Option<&WindowFrame> {
5399        match expr {
5400            Expression::WindowFunction(window) => window.over.frame.as_ref(),
5401            Expression::Window(window) | Expression::WindowSpec(window) => window.frame.as_ref(),
5402            _ => None,
5403        }
5404    }
5405
5406    fn window_frame_bound_has_value_offset(bound: &WindowFrameBound) -> bool {
5407        matches!(
5408            bound,
5409            WindowFrameBound::Preceding(_)
5410                | WindowFrameBound::Following(_)
5411                | WindowFrameBound::Value(_)
5412                | WindowFrameBound::BarePreceding
5413                | WindowFrameBound::BareFollowing
5414        )
5415    }
5416
5417    fn collect_tsql_windows_missing_order(
5418        expr: &Expression,
5419        active_windows: &HashMap<String, Over>,
5420        diagnostics: &mut Vec<String>,
5421    ) {
5422        if let Expression::Select(select) = expr {
5423            let local_windows = select
5424                .windows
5425                .as_ref()
5426                .map(|windows| {
5427                    windows
5428                        .iter()
5429                        .map(|window| (window.name.name.to_ascii_lowercase(), window.spec.clone()))
5430                        .collect()
5431                })
5432                .unwrap_or_default();
5433
5434            for child in expr.children() {
5435                Self::collect_tsql_windows_missing_order(child, &local_windows, diagnostics);
5436            }
5437            return;
5438        }
5439
5440        if let Expression::WindowFunction(window) = expr {
5441            let (has_order, has_frame) = Self::effective_window_order_and_frame(
5442                &window.over,
5443                active_windows,
5444                &mut Vec::new(),
5445            );
5446
5447            if !has_order {
5448                if has_frame {
5449                    Self::push_unsupported_diagnostic(
5450                        diagnostics,
5451                        "window frames without ORDER BY",
5452                    );
5453                }
5454                if let Some(function_name) =
5455                    Self::tsql_window_function_requiring_order(&window.this)
5456                {
5457                    Self::push_unsupported_diagnostic(
5458                        diagnostics,
5459                        &format!("{function_name} without ORDER BY"),
5460                    );
5461                }
5462            }
5463        }
5464
5465        for child in expr.children() {
5466            Self::collect_tsql_windows_missing_order(child, active_windows, diagnostics);
5467        }
5468    }
5469
5470    fn effective_window_order_and_frame(
5471        over: &Over,
5472        active_windows: &HashMap<String, Over>,
5473        seen: &mut Vec<String>,
5474    ) -> (bool, bool) {
5475        let inherited = over
5476            .window_name
5477            .as_ref()
5478            .and_then(|name| {
5479                let key = name.name.to_ascii_lowercase();
5480                if seen.iter().any(|seen_name| seen_name == &key) {
5481                    return None;
5482                }
5483                let named = active_windows.get(&key)?;
5484                seen.push(key);
5485                let properties =
5486                    Self::effective_window_order_and_frame(named, active_windows, seen);
5487                seen.pop();
5488                Some(properties)
5489            })
5490            .unwrap_or((false, false));
5491
5492        (
5493            !over.order_by.is_empty() || inherited.0,
5494            over.frame.is_some() || inherited.1,
5495        )
5496    }
5497
5498    fn tsql_window_function_requiring_order(expr: &Expression) -> Option<&'static str> {
5499        match expr {
5500            Expression::FirstValue(_) => Some("FIRST_VALUE"),
5501            Expression::LastValue(_) => Some("LAST_VALUE"),
5502            Expression::Function(function) if function.name.eq_ignore_ascii_case("FIRST_VALUE") => {
5503                Some("FIRST_VALUE")
5504            }
5505            Expression::Function(function) if function.name.eq_ignore_ascii_case("LAST_VALUE") => {
5506                Some("LAST_VALUE")
5507            }
5508            _ => None,
5509        }
5510    }
5511
5512    fn collect_tsql_unsupported_ordered_sets(expr: &Expression, diagnostics: &mut Vec<String>) {
5513        match expr {
5514            Expression::WindowFunction(window) => {
5515                if let Expression::WithinGroup(within_group) = &window.this {
5516                    if Self::within_group_is_hypothetical_set(within_group) {
5517                        Self::push_unsupported_diagnostic(
5518                            diagnostics,
5519                            "RANK/DENSE_RANK/CUME_DIST/PERCENT_RANK hypothetical-set aggregates",
5520                        );
5521                        return;
5522                    }
5523
5524                    if Self::within_group_is_mode(within_group) {
5525                        Self::push_unsupported_diagnostic(
5526                            diagnostics,
5527                            "MODE ordered-set aggregates",
5528                        );
5529                        return;
5530                    }
5531
5532                    if Self::within_group_is_percentile(within_group) {
5533                        if !window.over.order_by.is_empty() || window.over.frame.is_some() {
5534                            Self::push_unsupported_diagnostic(
5535                                diagnostics,
5536                                "PERCENTILE_CONT/PERCENTILE_DISC window ORDER BY or frame clauses",
5537                            );
5538                        }
5539                        return;
5540                    }
5541                }
5542            }
5543            Expression::WithinGroup(within_group) => {
5544                if Self::within_group_is_hypothetical_set(within_group) {
5545                    Self::push_unsupported_diagnostic(
5546                        diagnostics,
5547                        "RANK/DENSE_RANK/CUME_DIST/PERCENT_RANK hypothetical-set aggregates",
5548                    );
5549                    return;
5550                }
5551
5552                if Self::within_group_is_mode(within_group) {
5553                    Self::push_unsupported_diagnostic(diagnostics, "MODE ordered-set aggregates");
5554                    return;
5555                }
5556
5557                if Self::within_group_is_percentile(within_group) {
5558                    Self::push_unsupported_diagnostic(
5559                        diagnostics,
5560                        "PERCENTILE_CONT/PERCENTILE_DISC ordered-set aggregates without OVER",
5561                    );
5562                    return;
5563                }
5564            }
5565            _ => {}
5566        }
5567
5568        for child in expr.children() {
5569            Self::collect_tsql_unsupported_ordered_sets(child, diagnostics);
5570        }
5571    }
5572
5573    fn within_group_is_hypothetical_set(within_group: &crate::expressions::WithinGroup) -> bool {
5574        match &within_group.this {
5575            Expression::Function(function) => Self::is_hypothetical_set_name(&function.name),
5576            Expression::AggregateFunction(function) => {
5577                Self::is_hypothetical_set_name(&function.name)
5578            }
5579            Expression::Rank(_)
5580            | Expression::DenseRank(_)
5581            | Expression::CumeDist(_)
5582            | Expression::PercentRank(_) => true,
5583            _ => false,
5584        }
5585    }
5586
5587    fn within_group_is_percentile(within_group: &crate::expressions::WithinGroup) -> bool {
5588        match &within_group.this {
5589            Expression::Function(function) => Self::is_percentile_ordered_set_name(&function.name),
5590            Expression::AggregateFunction(function) => {
5591                Self::is_percentile_ordered_set_name(&function.name)
5592            }
5593            Expression::PercentileCont(_) | Expression::PercentileDisc(_) => true,
5594            _ => false,
5595        }
5596    }
5597
5598    fn within_group_is_mode(within_group: &crate::expressions::WithinGroup) -> bool {
5599        match &within_group.this {
5600            Expression::Function(function) => function.name.eq_ignore_ascii_case("MODE"),
5601            Expression::AggregateFunction(function) => function.name.eq_ignore_ascii_case("MODE"),
5602            Expression::Mode(_) => true,
5603            _ => false,
5604        }
5605    }
5606
5607    fn is_percentile_ordered_set_name(name: &str) -> bool {
5608        name.eq_ignore_ascii_case("PERCENTILE_CONT") || name.eq_ignore_ascii_case("PERCENTILE_DISC")
5609    }
5610
5611    fn is_hypothetical_set_name(name: &str) -> bool {
5612        name.eq_ignore_ascii_case("RANK")
5613            || name.eq_ignore_ascii_case("DENSE_RANK")
5614            || name.eq_ignore_ascii_case("CUME_DIST")
5615            || name.eq_ignore_ascii_case("PERCENT_RANK")
5616    }
5617
5618    fn target_supports_distinct_on(target: DialectType) -> bool {
5619        matches!(target, DialectType::PostgreSQL | DialectType::DuckDB)
5620    }
5621
5622    fn node_has_distinct_on(expr: &Expression) -> bool {
5623        matches!(
5624            expr,
5625            Expression::Select(select)
5626                if select
5627                    .distinct_on
5628                    .as_ref()
5629                    .is_some_and(|distinct_on| !distinct_on.is_empty())
5630        )
5631    }
5632
5633    fn node_has_recursive_with(expr: &Expression) -> bool {
5634        fn recursive(with: &Option<With>) -> bool {
5635            with.as_ref().is_some_and(|with| with.recursive)
5636        }
5637
5638        match expr {
5639            Expression::With(with) => with.recursive,
5640            Expression::Select(select) => recursive(&select.with),
5641            Expression::Union(union) => recursive(&union.with),
5642            Expression::Intersect(intersect) => recursive(&intersect.with),
5643            Expression::Except(except) => recursive(&except.with),
5644            Expression::Pivot(pivot) => recursive(&pivot.with),
5645            Expression::Insert(insert) => recursive(&insert.with),
5646            Expression::Update(update) => recursive(&update.with),
5647            Expression::Delete(delete) => recursive(&delete.with),
5648            _ => false,
5649        }
5650    }
5651
5652    fn node_has_lateral(expr: &Expression) -> bool {
5653        fn join_has_lateral(join: &Join) -> bool {
5654            matches!(
5655                join.kind,
5656                crate::expressions::JoinKind::Lateral | crate::expressions::JoinKind::LeftLateral
5657            ) || Dialect::node_has_lateral(&join.this)
5658                || join.on.as_ref().is_some_and(Dialect::node_has_lateral)
5659                || join
5660                    .match_condition
5661                    .as_ref()
5662                    .is_some_and(Dialect::node_has_lateral)
5663                || join.pivots.iter().any(Dialect::node_has_lateral)
5664        }
5665
5666        fn joins_have_lateral(joins: &[Join]) -> bool {
5667            joins.iter().any(join_has_lateral)
5668        }
5669
5670        match expr {
5671            Expression::Subquery(subquery) => {
5672                subquery.lateral || Dialect::node_has_lateral(&subquery.this)
5673            }
5674            Expression::Lateral(_) | Expression::LateralView(_) => true,
5675            Expression::Join(join) => join_has_lateral(join),
5676            Expression::Select(select) => {
5677                !select.lateral_views.is_empty()
5678                    || joins_have_lateral(&select.joins)
5679                    || select
5680                        .from
5681                        .as_ref()
5682                        .is_some_and(|from| from.expressions.iter().any(Dialect::node_has_lateral))
5683            }
5684            Expression::JoinedTable(joined) => {
5685                !joined.lateral_views.is_empty()
5686                    || Dialect::node_has_lateral(&joined.left)
5687                    || joins_have_lateral(&joined.joins)
5688            }
5689            Expression::Update(update) => {
5690                joins_have_lateral(&update.table_joins) || joins_have_lateral(&update.from_joins)
5691            }
5692            _ => false,
5693        }
5694    }
5695
5696    fn node_has_join_using(expr: &Expression) -> bool {
5697        fn has_using(joins: &[Join]) -> bool {
5698            joins.iter().any(|join| !join.using.is_empty())
5699        }
5700
5701        match expr {
5702            Expression::Join(join) => !join.using.is_empty(),
5703            Expression::Select(select) => has_using(&select.joins),
5704            Expression::JoinedTable(joined) => has_using(&joined.joins),
5705            Expression::Update(update) => {
5706                has_using(&update.table_joins) || has_using(&update.from_joins)
5707            }
5708            Expression::Delete(delete) => has_using(&delete.joins),
5709            _ => false,
5710        }
5711    }
5712
5713    fn node_has_natural_join(expr: &Expression) -> bool {
5714        fn is_natural(join: &Join) -> bool {
5715            matches!(
5716                join.kind,
5717                crate::expressions::JoinKind::Natural
5718                    | crate::expressions::JoinKind::NaturalLeft
5719                    | crate::expressions::JoinKind::NaturalRight
5720                    | crate::expressions::JoinKind::NaturalFull
5721            )
5722        }
5723
5724        fn has_natural(joins: &[Join]) -> bool {
5725            joins.iter().any(is_natural)
5726        }
5727
5728        match expr {
5729            Expression::Join(join) => is_natural(join),
5730            Expression::Select(select) => has_natural(&select.joins),
5731            Expression::JoinedTable(joined) => has_natural(&joined.joins),
5732            Expression::Update(update) => {
5733                has_natural(&update.table_joins) || has_natural(&update.from_joins)
5734            }
5735            Expression::Delete(delete) => has_natural(&delete.joins),
5736            _ => false,
5737        }
5738    }
5739
5740    fn node_has_unsupported_relation_column_aliases(expr: &Expression) -> bool {
5741        match expr {
5742            Expression::Table(table) => !table.column_aliases.is_empty(),
5743            Expression::Alias(alias) => {
5744                !alias.column_aliases.is_empty()
5745                    && matches!(
5746                        alias.this,
5747                        Expression::Table(_) | Expression::JoinedTable(_)
5748                    )
5749            }
5750            _ => false,
5751        }
5752    }
5753
5754    fn node_is_aggregate_function(expr: &Expression) -> bool {
5755        is_aggregate(expr)
5756    }
5757
5758    fn node_has_qualified_whole_row_aggregate_argument(expr: &Expression) -> bool {
5759        fn contains_qualified_star(expr: &Expression) -> bool {
5760            match expr {
5761                Expression::Star(star) => star.table.is_some(),
5762                // A star projected by an embedded query is not an argument of
5763                // the surrounding aggregate (for example, inside EXISTS).
5764                Expression::Select(_)
5765                | Expression::Subquery(_)
5766                | Expression::Union(_)
5767                | Expression::Intersect(_)
5768                | Expression::Except(_) => false,
5769                _ => expr.children().into_iter().any(contains_qualified_star),
5770            }
5771        }
5772
5773        Self::node_is_aggregate_function(expr)
5774            && expr.children().into_iter().any(contains_qualified_star)
5775    }
5776
5777    fn node_has_subquery_in_aggregate_argument(expr: &Expression) -> bool {
5778        fn contains_query(expr: &Expression) -> bool {
5779            matches!(
5780                expr,
5781                Expression::Select(_)
5782                    | Expression::Subquery(_)
5783                    | Expression::Union(_)
5784                    | Expression::Intersect(_)
5785                    | Expression::Except(_)
5786            ) || expr.children().into_iter().any(contains_query)
5787        }
5788
5789        Self::node_is_aggregate_function(expr) && expr.children().into_iter().any(contains_query)
5790    }
5791
5792    fn tsql_apply_has_invalid_outer_aggregate(expr: &Expression) -> bool {
5793        // SQL Server error 8124: if an aggregate expression contains an outer
5794        // reference, that reference must be the only column used by the expression.
5795        fn collect_source_names(expr: &Expression, names: &mut HashSet<String>) {
5796            let mut insert = |name: &Identifier| {
5797                if !name.name.is_empty() {
5798                    names.insert(name.name.to_ascii_lowercase());
5799                }
5800            };
5801
5802            match expr {
5803                Expression::Table(table) => {
5804                    insert(table.alias.as_ref().unwrap_or(&table.name));
5805                }
5806                Expression::Subquery(subquery) => {
5807                    if let Some(alias) = &subquery.alias {
5808                        insert(alias);
5809                    }
5810                }
5811                Expression::Alias(alias) => insert(&alias.alias),
5812                Expression::JoinedTable(joined) => {
5813                    if let Some(alias) = &joined.alias {
5814                        insert(alias);
5815                    } else {
5816                        collect_source_names(&joined.left, names);
5817                        for join in &joined.joins {
5818                            collect_source_names(&join.this, names);
5819                        }
5820                    }
5821                }
5822                Expression::Paren(paren) => collect_source_names(&paren.this, names),
5823                Expression::Pivot(pivot) => {
5824                    if let Some(alias) = &pivot.alias {
5825                        insert(alias);
5826                    } else {
5827                        collect_source_names(&pivot.this, names);
5828                    }
5829                }
5830                Expression::Unpivot(unpivot) => {
5831                    if let Some(alias) = &unpivot.alias {
5832                        insert(alias);
5833                    } else {
5834                        collect_source_names(&unpivot.this, names);
5835                    }
5836                }
5837                _ => {}
5838            }
5839        }
5840
5841        fn collect_columns<'a>(
5842            expr: &'a Expression,
5843            columns: &mut Vec<&'a crate::expressions::Column>,
5844        ) {
5845            match expr {
5846                Expression::Column(column) => columns.push(column),
5847                // Query expressions introduce a new name-resolution scope. Their
5848                // columns are checked when their own SELECT node is visited.
5849                Expression::Select(_)
5850                | Expression::Subquery(_)
5851                | Expression::Union(_)
5852                | Expression::Intersect(_)
5853                | Expression::Except(_) => {}
5854                _ => {
5855                    for child in expr.children() {
5856                        collect_columns(child, columns);
5857                    }
5858                }
5859            }
5860        }
5861
5862        fn same_column(
5863            left: &crate::expressions::Column,
5864            right: &crate::expressions::Column,
5865        ) -> bool {
5866            let same_identifier =
5867                |left: &Identifier, right: &Identifier| left.name.eq_ignore_ascii_case(&right.name);
5868
5869            same_identifier(&left.name, &right.name)
5870                && match (&left.table, &right.table) {
5871                    (Some(left), Some(right)) => same_identifier(left, right),
5872                    (None, None) => true,
5873                    _ => false,
5874                }
5875        }
5876
5877        fn aggregate_is_invalid(expr: &Expression, local_sources: &HashSet<String>) -> bool {
5878            let mut columns = Vec::new();
5879            collect_columns(expr, &mut columns);
5880
5881            let outer_column = columns.iter().copied().find(|column| match &column.table {
5882                Some(table) => !local_sources.contains(&table.name.to_ascii_lowercase()),
5883                // Without a local source, an unqualified reference in a lateral
5884                // query can only resolve against an outer scope.
5885                None => local_sources.is_empty(),
5886            });
5887
5888            outer_column
5889                .is_some_and(|outer| columns.iter().any(|column| !same_column(outer, column)))
5890        }
5891
5892        fn expression_has_invalid_aggregate(
5893            expr: &Expression,
5894            local_sources: &HashSet<String>,
5895        ) -> bool {
5896            if Dialect::node_is_aggregate_function(expr)
5897                && aggregate_is_invalid(expr, local_sources)
5898            {
5899                return true;
5900            }
5901
5902            match expr {
5903                Expression::Select(_)
5904                | Expression::Subquery(_)
5905                | Expression::Union(_)
5906                | Expression::Intersect(_)
5907                | Expression::Except(_) => false,
5908                _ => expr
5909                    .children()
5910                    .into_iter()
5911                    .any(|child| expression_has_invalid_aggregate(child, local_sources)),
5912            }
5913        }
5914
5915        fn select_has_invalid_aggregate(select: &Select) -> bool {
5916            let mut local_sources = HashSet::new();
5917            if let Some(from) = &select.from {
5918                for source in &from.expressions {
5919                    collect_source_names(source, &mut local_sources);
5920                }
5921            }
5922            for join in &select.joins {
5923                collect_source_names(&join.this, &mut local_sources);
5924            }
5925
5926            let invalid =
5927                |expr: &Expression| expression_has_invalid_aggregate(expr, &local_sources);
5928
5929            select.expressions.iter().any(invalid)
5930                || select.prewhere.as_ref().is_some_and(invalid)
5931                || select
5932                    .where_clause
5933                    .as_ref()
5934                    .is_some_and(|where_clause| invalid(&where_clause.this))
5935                || select
5936                    .group_by
5937                    .as_ref()
5938                    .is_some_and(|group_by| group_by.expressions.iter().any(invalid))
5939                || select
5940                    .having
5941                    .as_ref()
5942                    .is_some_and(|having| invalid(&having.this))
5943                || select
5944                    .qualify
5945                    .as_ref()
5946                    .is_some_and(|qualify| invalid(&qualify.this))
5947                || select.order_by.as_ref().is_some_and(|order_by| {
5948                    order_by
5949                        .expressions
5950                        .iter()
5951                        .any(|ordered| invalid(&ordered.this))
5952                })
5953        }
5954
5955        fn apply_rhs_is_invalid(rhs: &Expression) -> bool {
5956            rhs.dfs().any(|node| match node {
5957                Expression::Select(select) => select_has_invalid_aggregate(select),
5958                _ => false,
5959            })
5960        }
5961
5962        fn joins_have_invalid_aggregate(joins: &[Join]) -> bool {
5963            joins.iter().any(|join| {
5964                matches!(
5965                    join.kind,
5966                    crate::expressions::JoinKind::CrossApply
5967                        | crate::expressions::JoinKind::OuterApply
5968                ) && apply_rhs_is_invalid(&join.this)
5969            })
5970        }
5971
5972        expr.dfs().any(|node| match node {
5973            Expression::Select(select) => joins_have_invalid_aggregate(&select.joins),
5974            Expression::JoinedTable(joined) => joins_have_invalid_aggregate(&joined.joins),
5975            Expression::Update(update) => {
5976                joins_have_invalid_aggregate(&update.table_joins)
5977                    || joins_have_invalid_aggregate(&update.from_joins)
5978            }
5979            Expression::Delete(delete) => joins_have_invalid_aggregate(&delete.joins),
5980            _ => false,
5981        })
5982    }
5983
5984    fn target_supports_remaining_unnest(target: DialectType) -> bool {
5985        matches!(
5986            target,
5987            DialectType::PostgreSQL
5988                | DialectType::BigQuery
5989                | DialectType::DuckDB
5990                | DialectType::Presto
5991                | DialectType::Trino
5992                | DialectType::Athena
5993        )
5994    }
5995
5996    fn target_supports_remaining_explode(target: DialectType) -> bool {
5997        matches!(
5998            target,
5999            DialectType::Spark | DialectType::Databricks | DialectType::Hive
6000        )
6001    }
6002
6003    fn target_lacks_array_agg(target: DialectType) -> bool {
6004        matches!(
6005            target,
6006            DialectType::Fabric
6007                | DialectType::TSQL
6008                | DialectType::MySQL
6009                | DialectType::SQLite
6010                | DialectType::Oracle
6011        )
6012    }
6013
6014    fn node_is_unnest(expr: &Expression) -> bool {
6015        matches!(expr, Expression::Unnest(_)) || Self::node_is_function_named(expr, "UNNEST")
6016    }
6017
6018    fn node_is_explode(expr: &Expression) -> bool {
6019        matches!(expr, Expression::Explode(_) | Expression::ExplodeOuter(_))
6020            || Self::node_is_function_named(expr, "EXPLODE")
6021            || Self::node_is_function_named(expr, "EXPLODE_OUTER")
6022    }
6023
6024    fn node_is_array_agg(expr: &Expression) -> bool {
6025        matches!(expr, Expression::ArrayAgg(_)) || Self::node_is_function_named(expr, "ARRAY_AGG")
6026    }
6027
6028    fn node_is_distinct_string_agg(expr: &Expression) -> bool {
6029        match expr {
6030            Expression::StringAgg(agg) => agg.distinct,
6031            Expression::Function(function) => {
6032                function.distinct && function.name.eq_ignore_ascii_case("STRING_AGG")
6033            }
6034            Expression::AggregateFunction(function) => {
6035                function.distinct && function.name.eq_ignore_ascii_case("STRING_AGG")
6036            }
6037            _ => false,
6038        }
6039    }
6040
6041    fn postgres_tsql_unsupported_collation_name(expr: &Expression) -> Option<&'static str> {
6042        let Expression::Collation(collation) = expr else {
6043            return None;
6044        };
6045
6046        if collation.collation.eq_ignore_ascii_case("C") {
6047            Some("C")
6048        } else if collation.collation.eq_ignore_ascii_case("POSIX") {
6049            Some("POSIX")
6050        } else {
6051            None
6052        }
6053    }
6054
6055    fn collect_tsql_grouping_tuple_nodes(expr: &Expression) -> HashSet<*const Expression> {
6056        let mut tuples = HashSet::new();
6057
6058        for node in expr.dfs() {
6059            let Expression::Select(select) = node else {
6060                continue;
6061            };
6062            let Some(group_by) = &select.group_by else {
6063                continue;
6064            };
6065
6066            for expression in &group_by.expressions {
6067                Self::collect_tsql_grouping_element_tuples(expression, &mut tuples);
6068            }
6069        }
6070
6071        tuples
6072    }
6073
6074    fn collect_tsql_grouping_element_tuples(
6075        expr: &Expression,
6076        tuples: &mut HashSet<*const Expression>,
6077    ) {
6078        match expr {
6079            Expression::GroupingSets(grouping_sets) => {
6080                for expression in &grouping_sets.expressions {
6081                    Self::collect_tsql_grouping_unit_tuples(expression, tuples);
6082                }
6083            }
6084            Expression::Rollup(rollup) => {
6085                for expression in &rollup.expressions {
6086                    Self::collect_tsql_grouping_unit_tuples(expression, tuples);
6087                }
6088            }
6089            Expression::Cube(cube) => {
6090                for expression in &cube.expressions {
6091                    Self::collect_tsql_grouping_unit_tuples(expression, tuples);
6092                }
6093            }
6094            Expression::Function(function)
6095                if !function.quoted
6096                    && (function.name.eq_ignore_ascii_case("GROUPING SETS")
6097                        || function.name.eq_ignore_ascii_case("ROLLUP")
6098                        || function.name.eq_ignore_ascii_case("CUBE")) =>
6099            {
6100                for expression in &function.args {
6101                    Self::collect_tsql_grouping_unit_tuples(expression, tuples);
6102                }
6103            }
6104            _ => {}
6105        }
6106    }
6107
6108    fn collect_tsql_grouping_unit_tuples(
6109        expr: &Expression,
6110        tuples: &mut HashSet<*const Expression>,
6111    ) {
6112        match expr {
6113            Expression::Tuple(tuple) => {
6114                tuples.insert(expr as *const Expression);
6115                for expression in &tuple.expressions {
6116                    match expression {
6117                        Expression::Tuple(_) | Expression::Paren(_) => {
6118                            Self::collect_tsql_grouping_unit_tuples(expression, tuples);
6119                        }
6120                        Expression::GroupingSets(_)
6121                        | Expression::Rollup(_)
6122                        | Expression::Cube(_) => {
6123                            Self::collect_tsql_grouping_element_tuples(expression, tuples);
6124                        }
6125                        Expression::Function(function)
6126                            if !function.quoted
6127                                && (function.name.eq_ignore_ascii_case("GROUPING SETS")
6128                                    || function.name.eq_ignore_ascii_case("ROLLUP")
6129                                    || function.name.eq_ignore_ascii_case("CUBE")) =>
6130                        {
6131                            Self::collect_tsql_grouping_element_tuples(expression, tuples);
6132                        }
6133                        _ => {}
6134                    }
6135                }
6136            }
6137            Expression::Paren(paren) => {
6138                Self::collect_tsql_grouping_unit_tuples(&paren.this, tuples);
6139            }
6140            Expression::GroupingSets(_) | Expression::Rollup(_) | Expression::Cube(_) => {
6141                Self::collect_tsql_grouping_element_tuples(expr, tuples);
6142            }
6143            Expression::Function(function)
6144                if !function.quoted
6145                    && (function.name.eq_ignore_ascii_case("GROUPING SETS")
6146                        || function.name.eq_ignore_ascii_case("ROLLUP")
6147                        || function.name.eq_ignore_ascii_case("CUBE")) =>
6148            {
6149                Self::collect_tsql_grouping_element_tuples(expr, tuples);
6150            }
6151            _ => {}
6152        }
6153    }
6154
6155    fn postgres_tsql_unsupported_composite_semantics(
6156        expr: &Expression,
6157        structural_grouping_tuple: bool,
6158    ) -> Option<&'static str> {
6159        match expr {
6160            Expression::Tuple(_) if !structural_grouping_tuple => Some("row/composite values"),
6161            Expression::Struct(_) | Expression::StructFunc(_) => Some("row/composite values"),
6162            Expression::Function(function)
6163                if !function.quoted && function.name.eq_ignore_ascii_case("ROW") =>
6164            {
6165                Some("row/composite values")
6166            }
6167            Expression::StructExtract(_) => Some("row/composite field access"),
6168            Expression::Cast(cast) | Expression::TryCast(cast) | Expression::SafeCast(cast) if matches!(&cast.this, Expression::Star(star) if star.table.is_some()) => {
6169                Some("qualified whole-row casts")
6170            }
6171            _ => None,
6172        }
6173    }
6174
6175    fn node_is_postgres_unknown_cast(expr: &Expression) -> bool {
6176        match expr {
6177            Expression::Cast(cast) | Expression::TryCast(cast) | Expression::SafeCast(cast) => {
6178                normalization::is_postgres_unknown_type(&cast.to)
6179            }
6180            _ => false,
6181        }
6182    }
6183
6184    fn postgres_tsql_unsupported_array_semantics(expr: &Expression) -> Option<&'static str> {
6185        match expr {
6186            Expression::Array(_) | Expression::ArrayFunc(_) => Some("array literals"),
6187            Expression::Subscript(_) => Some("array subscripts"),
6188            Expression::ArraySlice(_) => Some("array slices"),
6189            Expression::DataType(DataType::Array { .. }) => Some("array data types"),
6190            Expression::Cast(cast) | Expression::TryCast(cast) | Expression::SafeCast(cast)
6191                if matches!(&cast.to, DataType::Array { .. }) =>
6192            {
6193                Some("array data types")
6194            }
6195            Expression::ArrayLength(_) | Expression::ArraySize(_) => Some("ARRAY_LENGTH"),
6196            Expression::Cardinality(_) => Some("CARDINALITY"),
6197            Expression::ArrayToString(_) | Expression::ArrayJoin(_) => Some("ARRAY_TO_STRING"),
6198            Expression::StringToArray(_) => Some("STRING_TO_ARRAY"),
6199            Expression::ArrayContains(_)
6200            | Expression::ArrayPosition(_)
6201            | Expression::ArrayAppend(_)
6202            | Expression::ArrayPrepend(_)
6203            | Expression::ArrayConcat(_)
6204            | Expression::ArraySort(_)
6205            | Expression::ArrayReverse(_)
6206            | Expression::ArrayDistinct(_)
6207            | Expression::ArrayFilter(_)
6208            | Expression::ArrayTransform(_)
6209            | Expression::ArrayFlatten(_)
6210            | Expression::ArrayCompact(_)
6211            | Expression::ArrayIntersect(_)
6212            | Expression::ArrayUnion(_)
6213            | Expression::ArrayExcept(_)
6214            | Expression::ArrayRemove(_)
6215            | Expression::ArrayZip(_)
6216            | Expression::ArrayAll(_)
6217            | Expression::ArrayAny(_)
6218            | Expression::ArrayConstructCompact(_)
6219            | Expression::ArraySum(_) => Some("array functions"),
6220            Expression::ArrayContainsAll(_)
6221            | Expression::ArrayContainedBy(_)
6222            | Expression::ArrayOverlaps(_) => Some("array operators"),
6223            Expression::Function(function) => {
6224                Self::postgres_tsql_unsupported_array_function_name_str(&function.name)
6225            }
6226            Expression::AggregateFunction(function) => {
6227                Self::postgres_tsql_unsupported_array_function_name_str(&function.name)
6228            }
6229            _ => None,
6230        }
6231    }
6232
6233    fn postgres_tsql_unsupported_array_function_name_str(name: &str) -> Option<&'static str> {
6234        if name.eq_ignore_ascii_case("ARRAY") {
6235            Some("array literals")
6236        } else if name.eq_ignore_ascii_case("ARRAY_LENGTH")
6237            || name.eq_ignore_ascii_case("ARRAY_SIZE")
6238        {
6239            Some("ARRAY_LENGTH")
6240        } else if name.eq_ignore_ascii_case("CARDINALITY") {
6241            Some("CARDINALITY")
6242        } else if name.eq_ignore_ascii_case("ARRAY_TO_STRING")
6243            || name.eq_ignore_ascii_case("ARRAY_JOIN")
6244        {
6245            Some("ARRAY_TO_STRING")
6246        } else if name.eq_ignore_ascii_case("STRING_TO_ARRAY") {
6247            Some("STRING_TO_ARRAY")
6248        } else {
6249            None
6250        }
6251    }
6252
6253    fn node_is_regex_predicate(expr: &Expression) -> bool {
6254        matches!(
6255            expr,
6256            Expression::SimilarTo(_) | Expression::RegexpLike(_) | Expression::RegexpILike(_)
6257        ) || Self::node_is_function_named(expr, "REGEXP_LIKE")
6258            || Self::node_is_function_named(expr, "REGEXP_I_LIKE")
6259            || Self::node_is_function_named(expr, "REGEXP_ILIKE")
6260    }
6261
6262    fn node_is_non_subquery_any(expr: &Expression) -> bool {
6263        matches!(
6264            expr,
6265            Expression::Any(q) if !Self::quantified_rhs_is_subquery(&q.subquery)
6266        )
6267    }
6268
6269    fn quantified_rhs_is_subquery(expr: &Expression) -> bool {
6270        match expr {
6271            Expression::Select(_) | Expression::Subquery(_) => true,
6272            Expression::Paren(paren) => Self::quantified_rhs_is_subquery(&paren.this),
6273            _ => false,
6274        }
6275    }
6276
6277    fn node_is_row_value_subquery_comparison(expr: &Expression) -> bool {
6278        match expr {
6279            Expression::In(in_expr) => {
6280                Self::in_rhs_is_subquery_like(in_expr) && Self::expr_is_row_value(&in_expr.this)
6281            }
6282            Expression::Eq(op) | Expression::Neq(op) => {
6283                (Self::expr_is_row_value(&op.left) && Self::expr_is_subquery_like(&op.right))
6284                    || (Self::expr_is_row_value(&op.right) && Self::expr_is_subquery_like(&op.left))
6285            }
6286            _ => false,
6287        }
6288    }
6289
6290    fn node_is_row_value_values_membership(expr: &Expression) -> bool {
6291        matches!(
6292            expr,
6293            Expression::In(in_expr)
6294                if Self::expr_is_row_value(&in_expr.this)
6295                    && Self::in_rhs_is_values_like(in_expr)
6296        )
6297    }
6298
6299    fn expr_is_row_value(expr: &Expression) -> bool {
6300        match expr {
6301            Expression::Tuple(tuple) => tuple.expressions.len() > 1,
6302            Expression::Function(function) if function.name.eq_ignore_ascii_case("ROW") => {
6303                function.args.len() > 1
6304            }
6305            Expression::Paren(paren) => Self::expr_is_row_value(&paren.this),
6306            _ => false,
6307        }
6308    }
6309
6310    fn expr_is_subquery_like(expr: &Expression) -> bool {
6311        match expr {
6312            Expression::Select(_) | Expression::Subquery(_) => true,
6313            Expression::Paren(paren) => Self::expr_is_subquery_like(&paren.this),
6314            _ => false,
6315        }
6316    }
6317
6318    fn in_rhs_is_subquery_like(in_expr: &crate::expressions::In) -> bool {
6319        if in_expr
6320            .query
6321            .as_ref()
6322            .is_some_and(Self::expr_is_subquery_like)
6323        {
6324            return true;
6325        }
6326
6327        in_expr.expressions.len() == 1 && Self::expr_is_subquery_like(&in_expr.expressions[0])
6328    }
6329
6330    fn in_rhs_is_values_like(in_expr: &crate::expressions::In) -> bool {
6331        if in_expr
6332            .query
6333            .as_ref()
6334            .is_some_and(Self::expr_is_values_like)
6335        {
6336            return true;
6337        }
6338
6339        (in_expr.expressions.len() == 1
6340            && Self::expr_is_values_like(&in_expr.expressions[0]))
6341            || in_expr.expressions.first().is_some_and(|expr| {
6342                matches!(expr, Expression::Function(function) if function.name.eq_ignore_ascii_case("VALUES"))
6343            })
6344    }
6345
6346    fn expr_is_values_like(expr: &Expression) -> bool {
6347        match expr {
6348            Expression::Values(_) => true,
6349            Expression::Paren(paren) => Self::expr_is_values_like(&paren.this),
6350            Expression::Subquery(subquery) => Self::expr_is_values_like(&subquery.this),
6351            _ => false,
6352        }
6353    }
6354
6355    fn normalize_tsql_fetch_overlaps_date_bin(expr: Expression) -> Result<Expression> {
6356        transform_recursive(expr, &|e| match e {
6357            Expression::Select(mut select) => {
6358                if select.top.is_none() && select.offset.is_none() {
6359                    if let Some(fetch) = select.fetch.take() {
6360                        if let Some(top) = Self::fetch_with_ties_to_top(fetch.clone()) {
6361                            select.top = Some(top);
6362                        } else {
6363                            select.fetch = Some(fetch);
6364                        }
6365                    }
6366                }
6367                Self::rewrite_tsql_overlaps_in_select_predicates(&mut select)?;
6368                Ok(Expression::Select(select))
6369            }
6370            Expression::DateBin(date_bin) => {
6371                let date_bin = *date_bin;
6372                if let Some(rewritten) = Self::date_bin_to_date_bucket(date_bin.clone()) {
6373                    Ok(rewritten)
6374                } else {
6375                    Ok(Expression::DateBin(Box::new(date_bin)))
6376                }
6377            }
6378            Expression::Function(function) => {
6379                let function = *function;
6380                if function.name.eq_ignore_ascii_case("DATE_BIN") {
6381                    if let Some(rewritten) = Self::date_bin_function_to_date_bucket(&function) {
6382                        Ok(rewritten)
6383                    } else {
6384                        Ok(Expression::Function(Box::new(function)))
6385                    }
6386                } else {
6387                    Ok(Expression::Function(Box::new(function)))
6388                }
6389            }
6390            _ => Ok(e),
6391        })
6392    }
6393
6394    fn rewrite_tsql_overlaps_in_select_predicates(
6395        select: &mut crate::expressions::Select,
6396    ) -> Result<()> {
6397        if let Some(where_clause) = &mut select.where_clause {
6398            where_clause.this = Self::rewrite_tsql_overlaps_predicate(where_clause.this.clone())?;
6399        }
6400        if let Some(having) = &mut select.having {
6401            having.this = Self::rewrite_tsql_overlaps_predicate(having.this.clone())?;
6402        }
6403        if let Some(qualify) = &mut select.qualify {
6404            qualify.this = Self::rewrite_tsql_overlaps_predicate(qualify.this.clone())?;
6405        }
6406        for join in &mut select.joins {
6407            if let Some(on) = join.on.take() {
6408                join.on = Some(Self::rewrite_tsql_overlaps_predicate(on)?);
6409            }
6410            if let Some(match_condition) = join.match_condition.take() {
6411                join.match_condition =
6412                    Some(Self::rewrite_tsql_overlaps_predicate(match_condition)?);
6413            }
6414        }
6415        Ok(())
6416    }
6417
6418    fn rewrite_tsql_overlaps_predicate(expr: Expression) -> Result<Expression> {
6419        transform_recursive(expr, &|e| match e {
6420            Expression::Overlaps(overlaps) => {
6421                let overlaps = *overlaps;
6422                if let Some(rewritten) = Self::rewrite_full_overlaps_for_tsql(&overlaps) {
6423                    Ok(rewritten)
6424                } else {
6425                    Ok(Expression::Overlaps(Box::new(overlaps)))
6426                }
6427            }
6428            _ => Ok(e),
6429        })
6430    }
6431
6432    fn fetch_with_ties_to_top(fetch: Fetch) -> Option<Top> {
6433        if !fetch.with_ties {
6434            return None;
6435        }
6436
6437        fetch.count.map(|count| Top {
6438            this: count,
6439            percent: fetch.percent,
6440            with_ties: true,
6441            parenthesized: true,
6442        })
6443    }
6444
6445    fn rewrite_full_overlaps_for_tsql(
6446        overlaps: &crate::expressions::OverlapsExpr,
6447    ) -> Option<Expression> {
6448        let (left_start, left_end, right_start, right_end) =
6449            if let (Some(left_start), Some(left_end), Some(right_start), Some(right_end)) = (
6450                overlaps.left_start.as_ref(),
6451                overlaps.left_end.as_ref(),
6452                overlaps.right_start.as_ref(),
6453                overlaps.right_end.as_ref(),
6454            ) {
6455                (left_start, left_end, right_start, right_end)
6456            } else if let (
6457                Some(Expression::Tuple(left_tuple)),
6458                Some(Expression::Tuple(right_tuple)),
6459            ) = (&overlaps.this, &overlaps.expression)
6460            {
6461                if left_tuple.expressions.len() != 2 || right_tuple.expressions.len() != 2 {
6462                    return None;
6463                }
6464                (
6465                    &left_tuple.expressions[0],
6466                    &left_tuple.expressions[1],
6467                    &right_tuple.expressions[0],
6468                    &right_tuple.expressions[1],
6469                )
6470            } else {
6471                return None;
6472            };
6473
6474        let left_min = Self::case_min(left_start.clone(), left_end.clone());
6475        let left_max = Self::case_max(left_start.clone(), left_end.clone());
6476        let right_min = Self::case_min(right_start.clone(), right_end.clone());
6477        let right_max = Self::case_max(right_start.clone(), right_end.clone());
6478
6479        Some(Expression::And(Box::new(BinaryOp::new(
6480            Expression::Lte(Box::new(BinaryOp::new(left_min, right_max))),
6481            Expression::Lte(Box::new(BinaryOp::new(right_min, left_max))),
6482        ))))
6483    }
6484
6485    fn case_min(left: Expression, right: Expression) -> Expression {
6486        Expression::Case(Box::new(Case {
6487            operand: None,
6488            whens: vec![(
6489                Expression::Lte(Box::new(BinaryOp::new(left.clone(), right.clone()))),
6490                left,
6491            )],
6492            else_: Some(right),
6493            comments: Vec::new(),
6494            inferred_type: None,
6495        }))
6496    }
6497
6498    fn case_max(left: Expression, right: Expression) -> Expression {
6499        Expression::Case(Box::new(Case {
6500            operand: None,
6501            whens: vec![(
6502                Expression::Gte(Box::new(BinaryOp::new(left.clone(), right.clone()))),
6503                left,
6504            )],
6505            else_: Some(right),
6506            comments: Vec::new(),
6507            inferred_type: None,
6508        }))
6509    }
6510
6511    fn date_bin_to_date_bucket(date_bin: DateBin) -> Option<Expression> {
6512        if date_bin.unit.is_some() || date_bin.zone.is_some() {
6513            return None;
6514        }
6515
6516        let (datepart, number) = Self::date_bucket_parts(&date_bin.this)?;
6517        let mut args = vec![
6518            Self::date_bucket_datepart(datepart),
6519            number,
6520            *date_bin.expression,
6521        ];
6522        if let Some(origin) = date_bin.origin {
6523            args.push(*origin);
6524        }
6525
6526        Some(Expression::Function(Box::new(Function::new(
6527            "DATE_BUCKET".to_string(),
6528            args,
6529        ))))
6530    }
6531
6532    fn date_bin_function_to_date_bucket(function: &Function) -> Option<Expression> {
6533        if !(2..=3).contains(&function.args.len()) {
6534            return None;
6535        }
6536
6537        let (datepart, number) = Self::date_bucket_parts(&function.args[0])?;
6538        let mut args = vec![
6539            Self::date_bucket_datepart(datepart),
6540            number,
6541            function.args[1].clone(),
6542        ];
6543        if let Some(origin) = function.args.get(2) {
6544            args.push(origin.clone());
6545        }
6546
6547        Some(Expression::Function(Box::new(Function::new(
6548            "DATE_BUCKET".to_string(),
6549            args,
6550        ))))
6551    }
6552
6553    fn date_bucket_parts(stride: &Expression) -> Option<(&'static str, Expression)> {
6554        match stride {
6555            Expression::Literal(lit) => match lit.as_ref() {
6556                Literal::String(value) => Self::date_bucket_parts_from_string(value),
6557                _ => None,
6558            },
6559            Expression::Interval(interval) => Self::date_bucket_parts_from_interval(interval),
6560            _ => None,
6561        }
6562    }
6563
6564    fn date_bucket_parts_from_interval(interval: &Interval) -> Option<(&'static str, Expression)> {
6565        match &interval.unit {
6566            Some(IntervalUnitSpec::Simple { unit, .. }) => {
6567                let datepart = Self::date_bucket_datepart_from_unit(*unit)?;
6568                let amount = interval
6569                    .this
6570                    .as_ref()
6571                    .and_then(Self::date_bucket_amount_expr)?;
6572                Some((datepart, amount))
6573            }
6574            None => interval.this.as_ref().and_then(|expr| match expr {
6575                Expression::Literal(lit) => match lit.as_ref() {
6576                    Literal::String(value) => Self::date_bucket_parts_from_string(value),
6577                    _ => None,
6578                },
6579                _ => None,
6580            }),
6581            _ => None,
6582        }
6583    }
6584
6585    fn date_bucket_parts_from_string(value: &str) -> Option<(&'static str, Expression)> {
6586        let mut parts = value.split_whitespace();
6587        let amount = parts.next()?;
6588        let unit = parts.next()?;
6589        if parts.next().is_some() {
6590            return None;
6591        }
6592
6593        Some((
6594            Self::date_bucket_datepart_from_name(unit)?,
6595            Self::positive_integer_expr(amount)?,
6596        ))
6597    }
6598
6599    fn date_bucket_amount_expr(expr: &Expression) -> Option<Expression> {
6600        match expr {
6601            Expression::Literal(lit) => match lit.as_ref() {
6602                Literal::Number(value) => Self::positive_integer_expr(value),
6603                Literal::String(value) => Self::positive_integer_expr(value),
6604                _ => None,
6605            },
6606            _ => Some(expr.clone()),
6607        }
6608    }
6609
6610    fn positive_integer_expr(value: &str) -> Option<Expression> {
6611        let parsed = value.trim().parse::<i64>().ok()?;
6612        (parsed > 0).then(|| Expression::number(parsed))
6613    }
6614
6615    fn date_bucket_datepart(datepart: &str) -> Expression {
6616        Expression::Var(Box::new(Var {
6617            this: datepart.to_string(),
6618        }))
6619    }
6620
6621    fn date_bucket_datepart_from_unit(unit: IntervalUnit) -> Option<&'static str> {
6622        match unit {
6623            IntervalUnit::Week => Some("WEEK"),
6624            IntervalUnit::Day => Some("DAY"),
6625            IntervalUnit::Hour => Some("HOUR"),
6626            IntervalUnit::Minute => Some("MINUTE"),
6627            IntervalUnit::Second => Some("SECOND"),
6628            IntervalUnit::Millisecond => Some("MILLISECOND"),
6629            _ => None,
6630        }
6631    }
6632
6633    fn date_bucket_datepart_from_name(unit: &str) -> Option<&'static str> {
6634        match unit.trim().to_ascii_uppercase().as_str() {
6635            "WEEK" | "WEEKS" | "W" | "WK" | "WKS" | "WW" => Some("WEEK"),
6636            "DAY" | "DAYS" | "D" | "DD" => Some("DAY"),
6637            "HOUR" | "HOURS" | "H" | "HH" | "HR" | "HRS" => Some("HOUR"),
6638            "MINUTE" | "MINUTES" | "MI" | "MIN" | "MINS" | "N" => Some("MINUTE"),
6639            "SECOND" | "SECONDS" | "S" | "SEC" | "SECS" | "SS" => Some("SECOND"),
6640            "MILLISECOND" | "MILLISECONDS" | "MS" | "MSEC" | "MSECS" | "MILLISEC" | "MILLISECS" => {
6641                Some("MILLISECOND")
6642            }
6643            _ => None,
6644        }
6645    }
6646
6647    fn node_has_fetch_with_ties(expr: &Expression) -> bool {
6648        matches!(
6649            expr,
6650            Expression::Select(select)
6651                if select
6652                    .fetch
6653                    .as_ref()
6654                    .is_some_and(|fetch| fetch.with_ties)
6655        )
6656    }
6657
6658    fn node_is_overlaps(expr: &Expression) -> bool {
6659        matches!(expr, Expression::Overlaps(_))
6660    }
6661
6662    fn node_is_date_bin(expr: &Expression) -> bool {
6663        matches!(expr, Expression::DateBin(_)) || Self::node_is_function_named(expr, "DATE_BIN")
6664    }
6665
6666    fn node_is_function_named(expr: &Expression, name: &str) -> bool {
6667        match expr {
6668            Expression::Function(function) => function.name.eq_ignore_ascii_case(name),
6669            Expression::AggregateFunction(function) => function.name.eq_ignore_ascii_case(name),
6670            _ => false,
6671        }
6672    }
6673
6674    fn node_is_postgres_json_build_object(expr: &Expression) -> bool {
6675        match expr {
6676            Expression::Function(function) => {
6677                function.name.eq_ignore_ascii_case("JSON_BUILD_OBJECT")
6678                    || function.name.eq_ignore_ascii_case("JSONB_BUILD_OBJECT")
6679            }
6680            _ => false,
6681        }
6682    }
6683
6684    fn postgres_json_build_object_can_lower_to_json_object(expr: &Expression) -> bool {
6685        matches!(
6686            expr,
6687            Expression::Function(function)
6688                if (function.name.eq_ignore_ascii_case("JSON_BUILD_OBJECT")
6689                    || function.name.eq_ignore_ascii_case("JSONB_BUILD_OBJECT"))
6690                    && !function.distinct
6691                    && function.args.len() % 2 == 0
6692        )
6693    }
6694
6695    fn node_is_postgres_json_array_elements(expr: &Expression) -> bool {
6696        matches!(
6697            expr,
6698            Expression::Function(function)
6699                if function.name.eq_ignore_ascii_case("JSON_ARRAY_ELEMENTS")
6700                    || function.name.eq_ignore_ascii_case("JSONB_ARRAY_ELEMENTS")
6701                    || function.name.eq_ignore_ascii_case("JSON_ARRAY_ELEMENTS_TEXT")
6702                    || function.name.eq_ignore_ascii_case("JSONB_ARRAY_ELEMENTS_TEXT")
6703        )
6704    }
6705
6706    fn postgres_tsql_unsupported_function_name(
6707        expr: &Expression,
6708        target: DialectType,
6709    ) -> Option<&'static str> {
6710        match expr {
6711            Expression::Lpad(_) => Some("LPAD"),
6712            Expression::Rpad(_) => Some("RPAD"),
6713            Expression::SplitPart(_) => Some("SPLIT_PART"),
6714            Expression::Initcap(_) => Some("INITCAP"),
6715            Expression::RegexpReplace(_) => Some("REGEXP_REPLACE"),
6716            Expression::RegexpInstr(_) => Some("REGEXP_INSTR"),
6717            Expression::RegexpCount(_) => Some("REGEXP_COUNT"),
6718            Expression::RegexpSplit(_) => Some("REGEXP_SPLIT"),
6719            Expression::DecodeCase(_) => Some("DECODE"),
6720            Expression::ToJson(_) => Some("TO_JSON"),
6721            Expression::JSONBObjectAgg(_) => Some("JSONB_OBJECT_AGG"),
6722            Expression::ToNumber(_) => Some("TO_NUMBER"),
6723            Expression::WidthBucket(_) => Some("WIDTH_BUCKET"),
6724            Expression::BitwiseAndAgg(_) => Some("BIT_AND"),
6725            Expression::BitwiseOrAgg(_) => Some("BIT_OR"),
6726            Expression::BitwiseXorAgg(_) => Some("BIT_XOR"),
6727            Expression::Corr(_) => Some("CORR"),
6728            Expression::CovarPop(_) => Some("COVAR_POP"),
6729            Expression::CovarSamp(_) => Some("COVAR_SAMP"),
6730            Expression::RegrAvgx(_) => Some("REGR_AVGX"),
6731            Expression::RegrAvgy(_) => Some("REGR_AVGY"),
6732            Expression::RegrCount(_) => Some("REGR_COUNT"),
6733            Expression::RegrIntercept(_) => Some("REGR_INTERCEPT"),
6734            Expression::RegrR2(_) => Some("REGR_R2"),
6735            Expression::RegrSlope(_) => Some("REGR_SLOPE"),
6736            Expression::RegrSxx(_) => Some("REGR_SXX"),
6737            Expression::RegrSxy(_) => Some("REGR_SXY"),
6738            Expression::RegrSyy(_) => Some("REGR_SYY"),
6739            Expression::Function(function) => {
6740                Self::postgres_tsql_unsupported_function_name_str(&function.name, target)
6741            }
6742            Expression::AggregateFunction(function) => {
6743                Self::postgres_tsql_unsupported_function_name_str(&function.name, target)
6744            }
6745            _ => None,
6746        }
6747    }
6748
6749    fn postgres_tsql_unsupported_function_name_str(
6750        name: &str,
6751        target: DialectType,
6752    ) -> Option<&'static str> {
6753        if name.eq_ignore_ascii_case("LPAD") {
6754            Some("LPAD")
6755        } else if name.eq_ignore_ascii_case("RPAD") {
6756            Some("RPAD")
6757        } else if name.eq_ignore_ascii_case("SPLIT_PART") {
6758            Some("SPLIT_PART")
6759        } else if name.eq_ignore_ascii_case("INITCAP") {
6760            Some("INITCAP")
6761        } else if name.eq_ignore_ascii_case("TO_JSON") {
6762            Some("TO_JSON")
6763        } else if name.eq_ignore_ascii_case("TO_JSONB") {
6764            Some("TO_JSONB")
6765        } else if name.eq_ignore_ascii_case("JSONB_OBJECT_AGG") {
6766            Some("JSONB_OBJECT_AGG")
6767        } else if name.eq_ignore_ascii_case("ROW_TO_JSON") {
6768            Some("ROW_TO_JSON")
6769        } else if name.eq_ignore_ascii_case("JSON_ARRAY_ELEMENTS") {
6770            Some("JSON_ARRAY_ELEMENTS")
6771        } else if name.eq_ignore_ascii_case("JSONB_ARRAY_ELEMENTS") {
6772            Some("JSONB_ARRAY_ELEMENTS")
6773        } else if name.eq_ignore_ascii_case("JSON_ARRAY_ELEMENTS_TEXT") {
6774            Some("JSON_ARRAY_ELEMENTS_TEXT")
6775        } else if name.eq_ignore_ascii_case("JSONB_ARRAY_ELEMENTS_TEXT") {
6776            Some("JSONB_ARRAY_ELEMENTS_TEXT")
6777        } else if name.eq_ignore_ascii_case("ENCODE") {
6778            Some("ENCODE")
6779        } else if name.eq_ignore_ascii_case("DECODE") {
6780            Some("DECODE")
6781        } else if name.eq_ignore_ascii_case("REGEXP_REPLACE") {
6782            Some("REGEXP_REPLACE")
6783        } else if name.eq_ignore_ascii_case("REGEXP_COUNT") {
6784            Some("REGEXP_COUNT")
6785        } else if name.eq_ignore_ascii_case("REGEXP_INSTR") {
6786            Some("REGEXP_INSTR")
6787        } else if name.eq_ignore_ascii_case("REGEXP_SUBSTR") {
6788            Some("REGEXP_SUBSTR")
6789        } else if name.eq_ignore_ascii_case("REGEXP_SPLIT") {
6790            Some("REGEXP_SPLIT")
6791        } else if name.eq_ignore_ascii_case("REGEXP_SPLIT_TO_ARRAY") {
6792            Some("REGEXP_SPLIT_TO_ARRAY")
6793        } else if name.eq_ignore_ascii_case("REGEXP_SPLIT_TO_TABLE") {
6794            Some("REGEXP_SPLIT_TO_TABLE")
6795        } else if name.eq_ignore_ascii_case("SHA224") {
6796            Some("SHA224")
6797        } else if name.eq_ignore_ascii_case("SHA384") {
6798            Some("SHA384")
6799        } else if name.eq_ignore_ascii_case("TO_BIN") {
6800            Some("TO_BIN")
6801        } else if name.eq_ignore_ascii_case("TO_OCT") {
6802            Some("TO_OCT")
6803        } else if target == DialectType::TSQL && name.eq_ignore_ascii_case("UNISTR") {
6804            Some("UNISTR")
6805        } else if name.eq_ignore_ascii_case("AGE") {
6806            Some("AGE")
6807        } else if name.eq_ignore_ascii_case("ERF") {
6808            Some("ERF")
6809        } else if name.eq_ignore_ascii_case("SINH") {
6810            Some("SINH")
6811        } else if name.eq_ignore_ascii_case("COSH") {
6812            Some("COSH")
6813        } else if name.eq_ignore_ascii_case("TANH") {
6814            Some("TANH")
6815        } else if name.eq_ignore_ascii_case("ASINH") {
6816            Some("ASINH")
6817        } else if name.eq_ignore_ascii_case("ACOSH") {
6818            Some("ACOSH")
6819        } else if name.eq_ignore_ascii_case("ATANH") {
6820            Some("ATANH")
6821        } else if name.eq_ignore_ascii_case("GCD") {
6822            Some("GCD")
6823        } else if name.eq_ignore_ascii_case("LCM") {
6824            Some("LCM")
6825        } else if name.eq_ignore_ascii_case("QUOTE_LITERAL") {
6826            Some("QUOTE_LITERAL")
6827        } else if name.eq_ignore_ascii_case("WIDTH_BUCKET") {
6828            Some("WIDTH_BUCKET")
6829        } else if name.eq_ignore_ascii_case("SCALE") {
6830            Some("SCALE")
6831        } else if name.eq_ignore_ascii_case("TRIM_SCALE") {
6832            Some("TRIM_SCALE")
6833        } else if name.eq_ignore_ascii_case("MIN_SCALE") {
6834            Some("MIN_SCALE")
6835        } else if name.eq_ignore_ascii_case("FACTORIAL") {
6836            Some("FACTORIAL")
6837        } else if name.eq_ignore_ascii_case("PG_LSN") {
6838            Some("PG_LSN")
6839        } else if name.eq_ignore_ascii_case("TO_CHAR") {
6840            Some("TO_CHAR")
6841        } else if name.eq_ignore_ascii_case("PG_TYPEOF") {
6842            Some("PG_TYPEOF")
6843        } else if name.eq_ignore_ascii_case("BIT_AND") {
6844            Some("BIT_AND")
6845        } else if name.eq_ignore_ascii_case("BIT_OR") {
6846            Some("BIT_OR")
6847        } else if name.eq_ignore_ascii_case("BIT_XOR") {
6848            Some("BIT_XOR")
6849        } else if name.eq_ignore_ascii_case("CORR") {
6850            Some("CORR")
6851        } else if name.eq_ignore_ascii_case("COVAR_POP") {
6852            Some("COVAR_POP")
6853        } else if name.eq_ignore_ascii_case("COVAR_SAMP") {
6854            Some("COVAR_SAMP")
6855        } else if name.eq_ignore_ascii_case("REGR_AVGX") {
6856            Some("REGR_AVGX")
6857        } else if name.eq_ignore_ascii_case("REGR_AVGY") {
6858            Some("REGR_AVGY")
6859        } else if name.eq_ignore_ascii_case("REGR_COUNT") {
6860            Some("REGR_COUNT")
6861        } else if name.eq_ignore_ascii_case("REGR_INTERCEPT") {
6862            Some("REGR_INTERCEPT")
6863        } else if name.eq_ignore_ascii_case("REGR_R2") {
6864            Some("REGR_R2")
6865        } else if name.eq_ignore_ascii_case("REGR_SLOPE") {
6866            Some("REGR_SLOPE")
6867        } else if name.eq_ignore_ascii_case("REGR_SXX") {
6868            Some("REGR_SXX")
6869        } else if name.eq_ignore_ascii_case("REGR_SXY") {
6870            Some("REGR_SXY")
6871        } else if name.eq_ignore_ascii_case("REGR_SYY") {
6872            Some("REGR_SYY")
6873        } else if name.eq_ignore_ascii_case("FLOAT8_ACCUM") {
6874            Some("FLOAT8_ACCUM")
6875        } else if name.eq_ignore_ascii_case("FLOAT8_REGR_ACCUM") {
6876            Some("FLOAT8_REGR_ACCUM")
6877        } else if name.eq_ignore_ascii_case("FLOAT8_COMBINE") {
6878            Some("FLOAT8_COMBINE")
6879        } else if name.eq_ignore_ascii_case("FLOAT8_REGR_COMBINE") {
6880            Some("FLOAT8_REGR_COMBINE")
6881        } else if name.eq_ignore_ascii_case("BOOLAND_STATEFUNC") {
6882            Some("BOOLAND_STATEFUNC")
6883        } else if name.eq_ignore_ascii_case("BOOLOR_STATEFUNC") {
6884            Some("BOOLOR_STATEFUNC")
6885        } else {
6886            None
6887        }
6888    }
6889
6890    fn normalize_postgres_trim_for_tsql(expr: Expression) -> Result<Expression> {
6891        transform_recursive(expr, &|e| match e {
6892            Expression::Trim(trim) => {
6893                let mut trim = *trim;
6894                trim.characters = trim.characters.map(Self::strip_postgres_text_literal_cast);
6895                match trim.position {
6896                    crate::expressions::TrimPosition::Both
6897                        if trim.position_explicit && trim.characters.is_some() =>
6898                    {
6899                        trim.position_explicit = false;
6900                        trim.sql_standard_syntax = true;
6901                        Ok(Expression::Trim(Box::new(trim)))
6902                    }
6903                    crate::expressions::TrimPosition::Leading if trim.characters.is_some() => {
6904                        let characters = trim.characters.take().expect("checked above");
6905                        Ok(Expression::Function(Box::new(Function::new(
6906                            "LTRIM",
6907                            vec![trim.this, characters],
6908                        ))))
6909                    }
6910                    crate::expressions::TrimPosition::Trailing if trim.characters.is_some() => {
6911                        let characters = trim.characters.take().expect("checked above");
6912                        Ok(Expression::Function(Box::new(Function::new(
6913                            "RTRIM",
6914                            vec![trim.this, characters],
6915                        ))))
6916                    }
6917                    _ => Ok(Expression::Trim(Box::new(trim))),
6918                }
6919            }
6920            other => Ok(other),
6921        })
6922    }
6923
6924    fn normalize_postgres_string_semantics_for_tsql(expr: Expression) -> Result<Expression> {
6925        transform_recursive(expr, &|e| match e {
6926            Expression::Like(mut op) => {
6927                Self::recover_postgres_like_escape(&mut op);
6928                Ok(Expression::Like(op))
6929            }
6930            Expression::ILike(mut op) => {
6931                Self::recover_postgres_like_escape(&mut op);
6932                Ok(Expression::ILike(op))
6933            }
6934            Expression::Substring(mut substring)
6935                if substring.length.is_none()
6936                    && Self::is_explicitly_numeric_expression(&substring.start) =>
6937            {
6938                substring.length = Some(Expression::number(i32::MAX as i64));
6939                Ok(Expression::Substring(substring))
6940            }
6941            Expression::Trim(mut trim) => {
6942                trim.characters = trim.characters.map(Self::strip_postgres_text_literal_cast);
6943                Ok(Expression::Trim(trim))
6944            }
6945            Expression::Function(mut function)
6946                if !function.quoted
6947                    && matches!(
6948                        function.name.to_ascii_uppercase().as_str(),
6949                        "BTRIM" | "LTRIM" | "RTRIM"
6950                    )
6951                    && function.args.len() == 2 =>
6952            {
6953                function.args[1] = Self::strip_postgres_text_literal_cast(function.args[1].clone());
6954                Ok(Expression::Function(function))
6955            }
6956            Expression::Translate(translate) => {
6957                Ok(Self::normalize_postgres_translate_for_tsql(*translate))
6958            }
6959            Expression::Function(function)
6960                if !function.quoted
6961                    && function.name.eq_ignore_ascii_case("TRANSLATE")
6962                    && function.args.len() == 3 =>
6963            {
6964                Ok(Self::normalize_postgres_translate_function_for_tsql(
6965                    *function,
6966                ))
6967            }
6968            other => Ok(other),
6969        })
6970    }
6971
6972    fn normalize_postgres_bytea_literals_for_tsql(expr: Expression) -> Result<Expression> {
6973        transform_recursive(expr, &|e| match e {
6974            Expression::Cast(cast) if Self::is_postgres_bytea_data_type(&cast.to) => {
6975                let Some(value) = Self::postgres_plain_string_literal_value(&cast.this) else {
6976                    return Ok(Expression::Cast(cast));
6977                };
6978                let Some(hex) = Self::postgres_bytea_hex_payload(value) else {
6979                    return Ok(Expression::Cast(cast));
6980                };
6981
6982                // Replace the complete BYTEA cast. Keeping a bare T-SQL
6983                // CAST(... AS VARBINARY) would apply SQL Server's default length
6984                // and could truncate payloads longer than 30 bytes.
6985                Ok(Expression::Literal(Box::new(Literal::HexString(hex))))
6986            }
6987            other => Ok(other),
6988        })
6989    }
6990
6991    fn postgres_plain_string_literal_value(expr: &Expression) -> Option<&str> {
6992        match expr {
6993            Expression::Literal(literal) => match literal.as_ref() {
6994                Literal::String(value) => Some(value),
6995                _ => None,
6996            },
6997            Expression::Paren(paren) => Self::postgres_plain_string_literal_value(&paren.this),
6998            _ => None,
6999        }
7000    }
7001
7002    fn postgres_bytea_hex_payload(value: &str) -> Option<String> {
7003        let payload = value.strip_prefix("\\x")?;
7004        if payload.is_empty() {
7005            return Some(String::new());
7006        }
7007
7008        let mut chars = payload.chars().peekable();
7009        let mut hex = String::with_capacity(payload.len());
7010        loop {
7011            let high = chars.next()?;
7012            let low = chars.next()?;
7013            if !high.is_ascii_hexdigit() || !low.is_ascii_hexdigit() {
7014                return None;
7015            }
7016            hex.push(high);
7017            hex.push(low);
7018
7019            let Some(next) = chars.peek().copied() else {
7020                return Some(hex);
7021            };
7022            if next.is_ascii_whitespace() {
7023                while chars
7024                    .peek()
7025                    .is_some_and(|character| character.is_ascii_whitespace())
7026                {
7027                    chars.next();
7028                }
7029                // PostgreSQL permits whitespace between byte pairs, not after
7030                // the prefix or after the final pair.
7031                chars.peek()?;
7032            }
7033        }
7034    }
7035
7036    fn is_postgres_bytea_data_type(data_type: &DataType) -> bool {
7037        match data_type {
7038            DataType::VarBinary { length: None } => true,
7039            DataType::Custom { name } => name.trim().eq_ignore_ascii_case("BYTEA"),
7040            _ => false,
7041        }
7042    }
7043
7044    fn postgres_tsql_unsupported_binary_semantics(expr: &Expression) -> Option<&'static str> {
7045        let cast = match expr {
7046            Expression::Cast(cast) | Expression::TryCast(cast) | Expression::SafeCast(cast)
7047                if Self::is_postgres_bytea_data_type(&cast.to) =>
7048            {
7049                cast
7050            }
7051            _ => return None,
7052        };
7053
7054        let literal = match &cast.this {
7055            Expression::Literal(literal) => literal.as_ref(),
7056            Expression::Paren(paren) => match &paren.this {
7057                Expression::Literal(literal) => literal.as_ref(),
7058                _ => return None,
7059            },
7060            _ => return None,
7061        };
7062        let value = match literal {
7063            Literal::String(value) | Literal::EscapeString(value) => value,
7064            _ => return None,
7065        };
7066
7067        if value.starts_with("\\x") {
7068            Some("bytea hex literals with invalid or unsupported formatting")
7069        } else if value.contains('\\') {
7070            Some("bytea escape-format literals")
7071        } else {
7072            None
7073        }
7074    }
7075
7076    fn recover_postgres_like_escape(op: &mut crate::expressions::LikeOp) {
7077        if op.escape.is_some() {
7078            return;
7079        }
7080
7081        let Expression::Function(function) = &op.right else {
7082            return;
7083        };
7084        if function.quoted
7085            || function.distinct
7086            || !function.name.eq_ignore_ascii_case("LIKE_ESCAPE")
7087            || function.args.len() != 2
7088        {
7089            return;
7090        }
7091
7092        let pattern = function.args[0].clone();
7093        let escape = function.args[1].clone();
7094        op.right = Self::strip_postgres_text_literal_cast(pattern);
7095        op.escape = Some(Self::strip_postgres_text_literal_cast(escape));
7096    }
7097
7098    fn normalize_postgres_translate_for_tsql(
7099        mut translate: crate::expressions::Translate,
7100    ) -> Expression {
7101        let (Some(from), Some(to)) = (&translate.from_, &translate.to) else {
7102            return Expression::Translate(Box::new(translate));
7103        };
7104
7105        let (Some(from_value), Some(to_value)) = (
7106            Self::postgres_text_literal_value(from),
7107            Self::postgres_text_literal_value(to),
7108        ) else {
7109            return Expression::Translate(Box::new(translate));
7110        };
7111        let from_value = from_value.to_string();
7112        let to_value = to_value.to_string();
7113
7114        if from_value.chars().count() > to_value.chars().count() {
7115            if let Some(input) = Self::postgres_text_literal_value(&translate.this) {
7116                return Expression::string(Self::translate_postgres_literal(
7117                    input,
7118                    &from_value,
7119                    &to_value,
7120                ));
7121            }
7122            return Expression::Translate(Box::new(translate));
7123        }
7124
7125        translate.from_ = Some(Box::new(Self::strip_postgres_text_literal_cast(
7126            *translate.from_.expect("checked above"),
7127        )));
7128        let normalized_to = if from_value.chars().count() < to_value.chars().count() {
7129            Expression::string(
7130                to_value
7131                    .chars()
7132                    .take(from_value.chars().count())
7133                    .collect::<String>(),
7134            )
7135        } else {
7136            Self::strip_postgres_text_literal_cast(*translate.to.expect("checked above"))
7137        };
7138        translate.to = Some(Box::new(normalized_to));
7139        Expression::Translate(Box::new(translate))
7140    }
7141
7142    fn normalize_postgres_translate_function_for_tsql(mut function: Function) -> Expression {
7143        let from = Self::postgres_text_literal_value(&function.args[1]);
7144        let to = Self::postgres_text_literal_value(&function.args[2]);
7145        let (Some(from), Some(to)) = (from, to) else {
7146            return Expression::Function(Box::new(function));
7147        };
7148        let from = from.to_string();
7149        let to = to.to_string();
7150
7151        if from.chars().count() > to.chars().count() {
7152            if let Some(input) = Self::postgres_text_literal_value(&function.args[0]) {
7153                return Expression::string(Self::translate_postgres_literal(input, &from, &to));
7154            }
7155            return Expression::Function(Box::new(function));
7156        }
7157
7158        function.args[1] = Self::strip_postgres_text_literal_cast(function.args[1].clone());
7159        function.args[2] = if from.chars().count() < to.chars().count() {
7160            Expression::string(to.chars().take(from.chars().count()).collect::<String>())
7161        } else {
7162            Self::strip_postgres_text_literal_cast(function.args[2].clone())
7163        };
7164        Expression::Function(Box::new(function))
7165    }
7166
7167    fn translate_postgres_literal(input: &str, from: &str, to: &str) -> String {
7168        let from = from.chars().collect::<Vec<_>>();
7169        let to = to.chars().collect::<Vec<_>>();
7170        let mut output = String::with_capacity(input.len());
7171
7172        for ch in input.chars() {
7173            match from.iter().position(|candidate| *candidate == ch) {
7174                Some(index) if index < to.len() => output.push(to[index]),
7175                Some(_) => {}
7176                None => output.push(ch),
7177            }
7178        }
7179
7180        output
7181    }
7182
7183    fn postgres_tsql_unsupported_string_semantics(expr: &Expression) -> Option<&'static str> {
7184        match expr {
7185            Expression::Substring(substring) if substring.length.is_none() => {
7186                if Self::postgres_text_literal_value(&substring.start).is_some() {
7187                    Some("regular-expression SUBSTRING")
7188                } else {
7189                    Some("SUBSTRING without a statically numeric start position")
7190                }
7191            }
7192            Expression::Translate(translate) => {
7193                let from = translate
7194                    .from_
7195                    .as_deref()
7196                    .and_then(Self::postgres_text_literal_value);
7197                let to = translate
7198                    .to
7199                    .as_deref()
7200                    .and_then(Self::postgres_text_literal_value);
7201                match (from, to) {
7202                    (Some(from), Some(to)) if from.chars().count() == to.chars().count() => None,
7203                    _ => Some("TRANSLATE with source and replacement lengths that differ or cannot be proven equal"),
7204                }
7205            }
7206            Expression::Function(function)
7207                if !function.quoted && function.name.eq_ignore_ascii_case("LIKE_ESCAPE") =>
7208            {
7209                Some("LIKE_ESCAPE helper outside a LIKE predicate")
7210            }
7211            Expression::Function(function)
7212                if !function.quoted
7213                    && function.name.eq_ignore_ascii_case("TRANSLATE")
7214                    && function.args.len() == 3 =>
7215            {
7216                let from = Self::postgres_text_literal_value(&function.args[1]);
7217                let to = Self::postgres_text_literal_value(&function.args[2]);
7218                match (from, to) {
7219                    (Some(from), Some(to)) if from.chars().count() == to.chars().count() => None,
7220                    _ => Some("TRANSLATE with source and replacement lengths that differ or cannot be proven equal"),
7221                }
7222            }
7223            Expression::Trim(trim)
7224                if trim
7225                    .characters
7226                    .as_ref()
7227                    .is_some_and(Self::is_unbounded_text_cast) =>
7228            {
7229                Some("TRIM character set cast to an unbounded text type")
7230            }
7231            Expression::Function(function)
7232                if !function.quoted
7233                    && matches!(
7234                        function.name.to_ascii_uppercase().as_str(),
7235                        "LTRIM" | "RTRIM"
7236                    )
7237                    && function.args.len() == 2
7238                    && Self::is_unbounded_text_cast(&function.args[1]) =>
7239            {
7240                Some("TRIM character set cast to an unbounded text type")
7241            }
7242            _ => None,
7243        }
7244    }
7245
7246    fn strip_postgres_text_literal_cast(expr: Expression) -> Expression {
7247        match expr {
7248            Expression::Cast(cast)
7249                if Self::is_text_data_type(&cast.to)
7250                    && Self::postgres_text_literal_value(&cast.this).is_some() =>
7251            {
7252                Self::strip_postgres_text_literal_cast(cast.this)
7253            }
7254            Expression::TryCast(cast)
7255                if Self::is_text_data_type(&cast.to)
7256                    && Self::postgres_text_literal_value(&cast.this).is_some() =>
7257            {
7258                Self::strip_postgres_text_literal_cast(cast.this)
7259            }
7260            Expression::SafeCast(cast)
7261                if Self::is_text_data_type(&cast.to)
7262                    && Self::postgres_text_literal_value(&cast.this).is_some() =>
7263            {
7264                Self::strip_postgres_text_literal_cast(cast.this)
7265            }
7266            Expression::Paren(mut paren)
7267                if Self::postgres_text_literal_value(&paren.this).is_some() =>
7268            {
7269                paren.this = Self::strip_postgres_text_literal_cast(paren.this);
7270                Expression::Paren(paren)
7271            }
7272            other => other,
7273        }
7274    }
7275
7276    fn postgres_text_literal_value(expr: &Expression) -> Option<&str> {
7277        match expr {
7278            Expression::Literal(literal) if literal.is_string() => Some(literal.value_str()),
7279            Expression::Cast(cast) | Expression::TryCast(cast) | Expression::SafeCast(cast)
7280                if Self::is_text_data_type(&cast.to) =>
7281            {
7282                Self::postgres_text_literal_value(&cast.this)
7283            }
7284            Expression::Alias(alias) => Self::postgres_text_literal_value(&alias.this),
7285            Expression::Paren(paren) => Self::postgres_text_literal_value(&paren.this),
7286            _ => None,
7287        }
7288    }
7289
7290    fn is_text_data_type(data_type: &DataType) -> bool {
7291        match data_type {
7292            DataType::Char { .. }
7293            | DataType::VarChar { .. }
7294            | DataType::String { .. }
7295            | DataType::Text
7296            | DataType::TextWithLength { .. } => true,
7297            DataType::Custom { name } => {
7298                let base = name
7299                    .split_once('(')
7300                    .map_or(name.as_str(), |(base, _)| base)
7301                    .trim();
7302                matches!(
7303                    base.to_ascii_uppercase().as_str(),
7304                    "CHAR"
7305                        | "NCHAR"
7306                        | "VARCHAR"
7307                        | "NVARCHAR"
7308                        | "TEXT"
7309                        | "NTEXT"
7310                        | "STRING"
7311                        | "CHARACTER VARYING"
7312                )
7313            }
7314            _ => false,
7315        }
7316    }
7317
7318    fn is_unbounded_text_cast(expr: &Expression) -> bool {
7319        let data_type = match expr {
7320            Expression::Cast(cast) | Expression::TryCast(cast) | Expression::SafeCast(cast) => {
7321                &cast.to
7322            }
7323            Expression::Paren(paren) => return Self::is_unbounded_text_cast(&paren.this),
7324            _ => return false,
7325        };
7326
7327        match data_type {
7328            DataType::Text => true,
7329            DataType::VarChar { length: None, .. } | DataType::String { length: None } => true,
7330            DataType::Custom { name } => name.to_ascii_uppercase().contains("(MAX)"),
7331            _ => false,
7332        }
7333    }
7334
7335    fn is_explicitly_numeric_expression(expr: &Expression) -> bool {
7336        if expr.inferred_type().is_some_and(Self::is_numeric_data_type) {
7337            return true;
7338        }
7339
7340        match expr {
7341            Expression::Literal(literal) => literal.is_number(),
7342            Expression::Cast(cast) | Expression::TryCast(cast) | Expression::SafeCast(cast) => {
7343                Self::is_numeric_data_type(&cast.to)
7344            }
7345            Expression::Alias(alias) => Self::is_explicitly_numeric_expression(&alias.this),
7346            Expression::Paren(paren) => Self::is_explicitly_numeric_expression(&paren.this),
7347            Expression::Neg(unary) => Self::is_explicitly_numeric_expression(&unary.this),
7348            _ => false,
7349        }
7350    }
7351
7352    fn is_numeric_data_type(data_type: &DataType) -> bool {
7353        match data_type {
7354            DataType::TinyInt { .. }
7355            | DataType::SmallInt { .. }
7356            | DataType::Int { .. }
7357            | DataType::BigInt { .. }
7358            | DataType::Float { .. }
7359            | DataType::Double { .. }
7360            | DataType::Decimal { .. } => true,
7361            DataType::Custom { name } => {
7362                let base = name
7363                    .split_once('(')
7364                    .map_or(name.as_str(), |(base, _)| base)
7365                    .trim();
7366                matches!(
7367                    base.to_ascii_uppercase().as_str(),
7368                    "TINYINT"
7369                        | "SMALLINT"
7370                        | "INT"
7371                        | "INTEGER"
7372                        | "BIGINT"
7373                        | "DECIMAL"
7374                        | "NUMERIC"
7375                        | "REAL"
7376                        | "FLOAT"
7377                        | "MONEY"
7378                        | "SMALLMONEY"
7379                )
7380            }
7381            _ => false,
7382        }
7383    }
7384
7385    fn normalize_postgres_only_for_tsql(expr: Expression) -> Result<Expression> {
7386        transform_recursive(expr, &|e| match e {
7387            Expression::Table(mut table) if table.only => {
7388                table.only = false;
7389                Ok(Expression::Table(table))
7390            }
7391            other => Ok(other),
7392        })
7393    }
7394
7395    fn rewrite_postgres_json_array_elements_select_for_tsql(
7396        expr: Expression,
7397    ) -> Result<Expression> {
7398        let Expression::Select(select) = expr else {
7399            return Ok(expr);
7400        };
7401        let mut select = *select;
7402        if !Self::is_plain_single_projection_select(&select) {
7403            return Ok(Expression::Select(Box::new(select)));
7404        }
7405
7406        let Some(json_arg) =
7407            Self::postgres_json_array_elements_projection_arg(&select.expressions[0])
7408        else {
7409            return Ok(Expression::Select(Box::new(select)));
7410        };
7411
7412        select.expressions = vec![Expression::column("value")];
7413        select.from = Some(From {
7414            expressions: vec![Expression::OpenJSON(Box::new(
7415                crate::expressions::OpenJSON {
7416                    this: Box::new(json_arg),
7417                    path: None,
7418                    expressions: Vec::new(),
7419                },
7420            ))],
7421        });
7422
7423        Ok(Expression::Select(Box::new(select)))
7424    }
7425
7426    fn is_plain_single_projection_select(select: &crate::expressions::Select) -> bool {
7427        select.expressions.len() == 1
7428            && select.from.is_none()
7429            && select.joins.is_empty()
7430            && select.lateral_views.is_empty()
7431            && select.prewhere.is_none()
7432            && select.where_clause.is_none()
7433            && select.group_by.is_none()
7434            && select.having.is_none()
7435            && select.qualify.is_none()
7436            && select.order_by.is_none()
7437            && select.distribute_by.is_none()
7438            && select.cluster_by.is_none()
7439            && select.sort_by.is_none()
7440            && select.limit.is_none()
7441            && select.offset.is_none()
7442            && select.limit_by.is_none()
7443            && select.fetch.is_none()
7444            && !select.distinct
7445            && select.distinct_on.is_none()
7446            && select.top.is_none()
7447            && select.with.is_none()
7448            && select.sample.is_none()
7449            && select.into.is_none()
7450            && select.locks.is_empty()
7451            && select.for_xml.is_empty()
7452            && select.for_json.is_empty()
7453            && select.exclude.is_none()
7454    }
7455
7456    fn postgres_json_array_elements_projection_arg(expr: &Expression) -> Option<Expression> {
7457        match expr {
7458            Expression::Function(function)
7459                if Self::node_is_postgres_json_array_elements(expr) && function.args.len() == 1 =>
7460            {
7461                Some(function.args[0].clone())
7462            }
7463            Expression::Alias(alias) => {
7464                Self::postgres_json_array_elements_projection_arg(&alias.this)
7465            }
7466            _ => None,
7467        }
7468    }
7469
7470    fn normalize_postgres_type_function_casts(
7471        expr: Expression,
7472        target: DialectType,
7473    ) -> Result<Expression> {
7474        transform_recursive(expr, &|e| match e {
7475            Expression::Function(function) => {
7476                let mut function = *function;
7477                if function.args.len() == 1
7478                    && !function.distinct
7479                    && !function.quoted
7480                    && !function.use_bracket_syntax
7481                    && !function.name.contains('.')
7482                {
7483                    if let Some(to) = Self::postgres_type_function_data_type(&function.name) {
7484                        let this = function.args.remove(0);
7485                        let cast = Cast {
7486                            this,
7487                            to,
7488                            trailing_comments: function.trailing_comments,
7489                            double_colon_syntax: false,
7490                            format: None,
7491                            default: None,
7492                            inferred_type: function.inferred_type,
7493                        };
7494                        return Ok(
7495                            if matches!(target, DialectType::TSQL | DialectType::Fabric) {
7496                                normalization::rewrite_postgres_float_to_integer_cast(cast)
7497                            } else {
7498                                Expression::Cast(Box::new(cast))
7499                            },
7500                        );
7501                    }
7502                }
7503                Ok(Expression::Function(Box::new(function)))
7504            }
7505            _ => Ok(e),
7506        })
7507    }
7508
7509    fn node_is_postgres_type_function_cast(expr: &Expression) -> bool {
7510        matches!(
7511            expr,
7512            Expression::Function(function)
7513                if !function.quoted
7514                    && !function.use_bracket_syntax
7515                    && !function.name.contains('.')
7516                    && Self::postgres_type_function_data_type(&function.name).is_some()
7517        )
7518    }
7519
7520    fn postgres_type_function_data_type(name: &str) -> Option<DataType> {
7521        match name.to_ascii_uppercase().as_str() {
7522            "NUMERIC" | "DECIMAL" | "DEC" => Some(DataType::Decimal {
7523                precision: None,
7524                scale: None,
7525            }),
7526            "INT2" | "SMALLINT" => Some(DataType::SmallInt { length: None }),
7527            "INT4" | "INT" => Some(DataType::Int {
7528                length: None,
7529                integer_spelling: false,
7530            }),
7531            "INTEGER" => Some(DataType::Int {
7532                length: None,
7533                integer_spelling: true,
7534            }),
7535            "INT8" | "BIGINT" => Some(DataType::BigInt { length: None }),
7536            "FLOAT4" | "REAL" => Some(DataType::Float {
7537                precision: None,
7538                scale: None,
7539                real_spelling: true,
7540            }),
7541            "FLOAT8" => Some(DataType::Double {
7542                precision: None,
7543                scale: None,
7544            }),
7545            "BOOL" | "BOOLEAN" => Some(DataType::Boolean),
7546            "TEXT" => Some(DataType::Text),
7547            "VARCHAR" => Some(DataType::VarChar {
7548                length: None,
7549                parenthesized_length: false,
7550            }),
7551            "UUID" => Some(DataType::Uuid),
7552            _ => None,
7553        }
7554    }
7555
7556    fn rewrite_boolean_values_for_tsql(expr: Expression) -> Result<Expression> {
7557        match expr {
7558            Expression::Select(select) => Self::rewrite_boolean_values_in_tsql_select(select),
7559            Expression::Subquery(mut subquery) => {
7560                subquery.this = Self::rewrite_boolean_values_for_tsql(subquery.this)?;
7561                Ok(Expression::Subquery(subquery))
7562            }
7563            Expression::Union(mut union) => {
7564                let left = std::mem::replace(&mut union.left, Expression::null());
7565                let right = std::mem::replace(&mut union.right, Expression::null());
7566                union.left = Self::rewrite_boolean_values_for_tsql(left)?;
7567                union.right = Self::rewrite_boolean_values_for_tsql(right)?;
7568                if let Some(mut with) = union.with.take() {
7569                    with.ctes = with
7570                        .ctes
7571                        .into_iter()
7572                        .map(|mut cte| {
7573                            cte.this = Self::rewrite_boolean_values_for_tsql(cte.this)?;
7574                            Ok(cte)
7575                        })
7576                        .collect::<Result<Vec<_>>>()?;
7577                    union.with = Some(with);
7578                }
7579                Ok(Expression::Union(union))
7580            }
7581            Expression::Intersect(mut intersect) => {
7582                let left = std::mem::replace(&mut intersect.left, Expression::null());
7583                let right = std::mem::replace(&mut intersect.right, Expression::null());
7584                intersect.left = Self::rewrite_boolean_values_for_tsql(left)?;
7585                intersect.right = Self::rewrite_boolean_values_for_tsql(right)?;
7586                Ok(Expression::Intersect(intersect))
7587            }
7588            Expression::Except(mut except) => {
7589                let left = std::mem::replace(&mut except.left, Expression::null());
7590                let right = std::mem::replace(&mut except.right, Expression::null());
7591                except.left = Self::rewrite_boolean_values_for_tsql(left)?;
7592                except.right = Self::rewrite_boolean_values_for_tsql(right)?;
7593                Ok(Expression::Except(except))
7594            }
7595            other => Self::rewrite_tsql_boolean_nested_contexts(other),
7596        }
7597    }
7598
7599    fn rewrite_postgres_row_value_equality_for_tsql(expr: Expression) -> Result<Expression> {
7600        transform_recursive(expr, &|e| match e {
7601            Expression::Eq(op) => {
7602                let op = *op;
7603                Ok(Self::postgres_row_value_equality_to_tsql_scalar(&op)
7604                    .unwrap_or_else(|| Expression::Eq(Box::new(op))))
7605            }
7606            other => Ok(other),
7607        })
7608    }
7609
7610    fn postgres_row_value_equality_to_tsql_scalar(op: &BinaryOp) -> Option<Expression> {
7611        let (row, query) =
7612            if Self::expr_is_row_value(&op.left) && Self::expr_is_subquery_like(&op.right) {
7613                (&op.left, &op.right)
7614            } else if Self::expr_is_row_value(&op.right) && Self::expr_is_subquery_like(&op.left) {
7615                (&op.right, &op.left)
7616            } else {
7617                return None;
7618            };
7619
7620        let row_values = Self::row_value_expressions(row)?;
7621        let projection_count = Self::subquery_projection_count(query)?;
7622        if row_values.is_empty() || row_values.len() != projection_count {
7623            return None;
7624        }
7625
7626        // Keep the complete original query behind a derived table. The outer scalar
7627        // SELECT therefore returns the same number of rows as the PostgreSQL
7628        // single-row subquery: zero rows stay NULL and multiple rows still raise a
7629        // scalar-subquery cardinality error in T-SQL/Fabric.
7630        let mut taken_names = HashSet::new();
7631        Self::collect_generated_alias_conflicts(row, &mut taken_names);
7632        Self::collect_generated_alias_conflicts(query, &mut taken_names);
7633
7634        let source_alias = find_new_name(&taken_names, "_polyglot_row");
7635        taken_names.insert(source_alias.to_ascii_lowercase());
7636        let column_aliases = (1..=row_values.len())
7637            .map(|index| {
7638                let name = find_new_name(&taken_names, &format!("_polyglot_row_value_{index}"));
7639                taken_names.insert(name.to_ascii_lowercase());
7640                Identifier::new(name)
7641            })
7642            .collect::<Vec<_>>();
7643        let source = Self::subquery_as_derived_table(
7644            query,
7645            Identifier::new(&source_alias),
7646            column_aliases.clone(),
7647        )?;
7648
7649        let mut equal_components = Vec::with_capacity(row_values.len());
7650        let mut unequal_components = Vec::with_capacity(row_values.len());
7651        for (column, row_value) in column_aliases.into_iter().zip(row_values) {
7652            let projected = Expression::qualified_column(source_alias.clone(), column.name);
7653            equal_components.push(Expression::Eq(Box::new(BinaryOp::new(
7654                projected.clone(),
7655                row_value.clone(),
7656            ))));
7657            unequal_components.push(Expression::Neq(Box::new(BinaryOp::new(
7658                projected, row_value,
7659            ))));
7660        }
7661
7662        let all_equal = equal_components
7663            .into_iter()
7664            .reduce(|left, right| Expression::And(Box::new(BinaryOp::new(left, right))))?;
7665        let any_unequal = unequal_components
7666            .into_iter()
7667            .reduce(|left, right| Expression::Or(Box::new(BinaryOp::new(left, right))))?;
7668        let comparison = Expression::Case(Box::new(Case {
7669            operand: None,
7670            whens: vec![
7671                (all_equal, Expression::number(1)),
7672                (any_unequal, Expression::number(0)),
7673            ],
7674            else_: Some(Expression::null()),
7675            comments: Vec::new(),
7676            inferred_type: None,
7677        }));
7678
7679        let scalar_select = Select::new().column(comparison).from(source);
7680        let scalar_subquery = Expression::Subquery(Box::new(Subquery {
7681            this: Expression::Select(Box::new(scalar_select)),
7682            alias: None,
7683            column_aliases: Vec::new(),
7684            alias_explicit_as: false,
7685            alias_keyword: None,
7686            order_by: None,
7687            limit: None,
7688            offset: None,
7689            distribute_by: None,
7690            sort_by: None,
7691            cluster_by: None,
7692            lateral: false,
7693            modifiers_inside: false,
7694            trailing_comments: Vec::new(),
7695            inferred_type: Some(DataType::Boolean),
7696        }));
7697
7698        Some(Expression::Cast(Box::new(Cast {
7699            this: scalar_subquery,
7700            to: DataType::Boolean,
7701            trailing_comments: Vec::new(),
7702            double_colon_syntax: false,
7703            format: None,
7704            default: None,
7705            inferred_type: Some(DataType::Boolean),
7706        })))
7707    }
7708
7709    fn row_value_expressions(expr: &Expression) -> Option<Vec<Expression>> {
7710        match expr {
7711            Expression::Tuple(tuple) => Some(tuple.expressions.clone()),
7712            Expression::Function(function) if function.name.eq_ignore_ascii_case("ROW") => {
7713                Some(function.args.clone())
7714            }
7715            Expression::Paren(paren) => Self::row_value_expressions(&paren.this),
7716            _ => None,
7717        }
7718    }
7719
7720    fn subquery_projection_count(expr: &Expression) -> Option<usize> {
7721        match expr {
7722            Expression::Select(select) => Some(select.expressions.len()),
7723            Expression::Subquery(subquery) => Self::subquery_projection_count(&subquery.this),
7724            Expression::Paren(paren) => Self::subquery_projection_count(&paren.this),
7725            _ => None,
7726        }
7727    }
7728
7729    fn subquery_as_derived_table(
7730        expr: &Expression,
7731        alias: Identifier,
7732        column_aliases: Vec<Identifier>,
7733    ) -> Option<Expression> {
7734        match expr.clone() {
7735            Expression::Subquery(mut subquery) => {
7736                subquery.alias = Some(alias);
7737                subquery.column_aliases = column_aliases;
7738                subquery.alias_explicit_as = true;
7739                subquery.alias_keyword = None;
7740                Some(Expression::Subquery(subquery))
7741            }
7742            Expression::Select(_) | Expression::Paren(_) => {
7743                Some(Expression::Subquery(Box::new(Subquery {
7744                    this: expr.clone(),
7745                    alias: Some(alias),
7746                    column_aliases,
7747                    alias_explicit_as: true,
7748                    alias_keyword: None,
7749                    order_by: None,
7750                    limit: None,
7751                    offset: None,
7752                    distribute_by: None,
7753                    sort_by: None,
7754                    cluster_by: None,
7755                    lateral: false,
7756                    modifiers_inside: false,
7757                    trailing_comments: Vec::new(),
7758                    inferred_type: None,
7759                })))
7760            }
7761            _ => None,
7762        }
7763    }
7764
7765    fn collect_generated_alias_conflicts(expr: &Expression, names: &mut HashSet<String>) {
7766        fn insert(names: &mut HashSet<String>, identifier: &Identifier) {
7767            if !identifier.name.is_empty() {
7768                names.insert(identifier.name.to_ascii_lowercase());
7769            }
7770        }
7771
7772        for node in expr.dfs() {
7773            match node {
7774                Expression::Identifier(identifier) => insert(names, identifier),
7775                Expression::Column(column) => {
7776                    insert(names, &column.name);
7777                    if let Some(table) = &column.table {
7778                        insert(names, table);
7779                    }
7780                }
7781                Expression::Table(table) => {
7782                    insert(names, &table.name);
7783                    if let Some(schema) = &table.schema {
7784                        insert(names, schema);
7785                    }
7786                    if let Some(catalog) = &table.catalog {
7787                        insert(names, catalog);
7788                    }
7789                    if let Some(alias) = &table.alias {
7790                        insert(names, alias);
7791                    }
7792                    for alias in &table.column_aliases {
7793                        insert(names, alias);
7794                    }
7795                }
7796                Expression::Alias(alias) => {
7797                    insert(names, &alias.alias);
7798                    for column_alias in &alias.column_aliases {
7799                        insert(names, column_alias);
7800                    }
7801                }
7802                Expression::Subquery(subquery) => {
7803                    if let Some(alias) = &subquery.alias {
7804                        insert(names, alias);
7805                    }
7806                    for column_alias in &subquery.column_aliases {
7807                        insert(names, column_alias);
7808                    }
7809                }
7810                Expression::Cte(cte) => {
7811                    insert(names, &cte.alias);
7812                    for column in &cte.columns {
7813                        insert(names, column);
7814                    }
7815                    for key in &cte.key_expressions {
7816                        insert(names, key);
7817                    }
7818                }
7819                Expression::Values(values) => {
7820                    if let Some(alias) = &values.alias {
7821                        insert(names, alias);
7822                    }
7823                    for column_alias in &values.column_aliases {
7824                        insert(names, column_alias);
7825                    }
7826                }
7827                Expression::Unnest(unnest) => {
7828                    if let Some(alias) = &unnest.alias {
7829                        insert(names, alias);
7830                    }
7831                    if let Some(offset_alias) = &unnest.offset_alias {
7832                        insert(names, offset_alias);
7833                    }
7834                }
7835                _ => {}
7836            }
7837        }
7838    }
7839
7840    fn rewrite_postgres_format_for_tsql(
7841        expr: Expression,
7842        target: DialectType,
7843    ) -> Result<Expression> {
7844        transform_recursive(expr, &|e| match e {
7845            Expression::Function(f) if f.name.eq_ignore_ascii_case("FORMAT") => {
7846                Self::postgres_format_function_to_tsql(*f, target)
7847            }
7848            other => Ok(other),
7849        })
7850    }
7851
7852    fn postgres_format_function_to_tsql(f: Function, target: DialectType) -> Result<Expression> {
7853        let Some(format_expr) = f.args.first() else {
7854            return Err(Self::unsupported_postgres_format_for_tsql(
7855                target,
7856                "missing format string",
7857            ));
7858        };
7859
7860        let format = match format_expr {
7861            Expression::Literal(lit) if lit.is_string() => lit.value_str(),
7862            _ => {
7863                return Err(Self::unsupported_postgres_format_for_tsql(
7864                    target,
7865                    "dynamic format strings",
7866                ))
7867            }
7868        };
7869
7870        let value_args = &f.args[1..];
7871        let mut arg_index = 0usize;
7872        let mut literal = String::new();
7873        let mut segments = Vec::new();
7874        let mut chars = format.chars();
7875
7876        while let Some(ch) = chars.next() {
7877            if ch != '%' {
7878                literal.push(ch);
7879                continue;
7880            }
7881
7882            let Some(specifier) = chars.next() else {
7883                return Err(Self::unsupported_postgres_format_for_tsql(
7884                    target,
7885                    "unterminated format specifier",
7886                ));
7887            };
7888
7889            match specifier {
7890                '%' => literal.push('%'),
7891                's' => {
7892                    if !literal.is_empty() {
7893                        segments.push(Expression::string(std::mem::take(&mut literal)));
7894                    }
7895                    let Some(arg) = value_args.get(arg_index) else {
7896                        return Err(Self::unsupported_postgres_format_for_tsql(
7897                            target,
7898                            "not enough arguments",
7899                        ));
7900                    };
7901                    segments.push(arg.clone());
7902                    arg_index += 1;
7903                }
7904                other => {
7905                    return Err(Self::unsupported_postgres_format_for_tsql(
7906                        target,
7907                        format!("unsupported format specifier %{other}"),
7908                    ))
7909                }
7910            }
7911        }
7912
7913        if !literal.is_empty() {
7914            segments.push(Expression::string(literal));
7915        }
7916
7917        if arg_index != value_args.len() {
7918            return Err(Self::unsupported_postgres_format_for_tsql(
7919                target,
7920                "unused format arguments",
7921            ));
7922        }
7923
7924        Ok(Self::postgres_format_segments_to_tsql_concat(segments))
7925    }
7926
7927    fn postgres_format_segments_to_tsql_concat(mut segments: Vec<Expression>) -> Expression {
7928        if segments.is_empty() {
7929            return Expression::string("");
7930        }
7931
7932        if segments.len() == 1 {
7933            let only = segments.pop().expect("one segment");
7934            if matches!(&only, Expression::Literal(lit) if lit.is_string()) {
7935                return only;
7936            }
7937
7938            return Expression::Function(Box::new(Function::new(
7939                "CONCAT".to_string(),
7940                vec![only, Expression::string("")],
7941            )));
7942        }
7943
7944        Expression::Function(Box::new(Function::new("CONCAT".to_string(), segments)))
7945    }
7946
7947    fn unsupported_postgres_format_for_tsql(
7948        target: DialectType,
7949        reason: impl Into<String>,
7950    ) -> crate::error::Error {
7951        crate::error::Error::unsupported(
7952            format!("PostgreSQL format() ({})", reason.into()),
7953            target.to_string(),
7954        )
7955    }
7956
7957    fn rewrite_boolean_values_in_tsql_select(
7958        mut select: Box<crate::expressions::Select>,
7959    ) -> Result<Expression> {
7960        if let Some(mut with) = select.with.take() {
7961            with.ctes = with
7962                .ctes
7963                .into_iter()
7964                .map(|mut cte| {
7965                    cte.this = Self::rewrite_boolean_values_for_tsql(cte.this)?;
7966                    Ok(cte)
7967                })
7968                .collect::<Result<Vec<_>>>()?;
7969            select.with = Some(with);
7970        }
7971
7972        select.expressions = select
7973            .expressions
7974            .into_iter()
7975            .map(Self::rewrite_tsql_boolean_scalar_value)
7976            .collect::<Result<Vec<_>>>()?;
7977
7978        if let Some(mut from) = select.from.take() {
7979            from.expressions = from
7980                .expressions
7981                .into_iter()
7982                .map(Self::rewrite_tsql_boolean_nested_contexts)
7983                .collect::<Result<Vec<_>>>()?;
7984            select.from = Some(from);
7985        }
7986
7987        select.joins = select
7988            .joins
7989            .into_iter()
7990            .map(|mut join| {
7991                join.this = Self::rewrite_tsql_boolean_nested_contexts(join.this)?;
7992                if let Some(on) = join.on.take() {
7993                    join.on = Some(Self::rewrite_tsql_boolean_predicate_context(on)?);
7994                }
7995                if let Some(match_condition) = join.match_condition.take() {
7996                    join.match_condition = Some(Self::rewrite_tsql_boolean_predicate_context(
7997                        match_condition,
7998                    )?);
7999                }
8000                join.pivots = join
8001                    .pivots
8002                    .into_iter()
8003                    .map(Self::rewrite_tsql_boolean_nested_contexts)
8004                    .collect::<Result<Vec<_>>>()?;
8005                Ok(join)
8006            })
8007            .collect::<Result<Vec<_>>>()?;
8008
8009        select.lateral_views = select
8010            .lateral_views
8011            .into_iter()
8012            .map(|mut lateral_view| {
8013                lateral_view.this = Self::rewrite_tsql_boolean_nested_contexts(lateral_view.this)?;
8014                Ok(lateral_view)
8015            })
8016            .collect::<Result<Vec<_>>>()?;
8017
8018        if let Some(prewhere) = select.prewhere.take() {
8019            select.prewhere = Some(Self::rewrite_tsql_boolean_predicate_context(prewhere)?);
8020        }
8021
8022        if let Some(mut where_clause) = select.where_clause.take() {
8023            where_clause.this = Self::rewrite_tsql_boolean_predicate_context(where_clause.this)?;
8024            select.where_clause = Some(where_clause);
8025        }
8026
8027        if let Some(mut group_by) = select.group_by.take() {
8028            group_by.expressions = group_by
8029                .expressions
8030                .into_iter()
8031                .map(Self::rewrite_tsql_boolean_scalar_value)
8032                .collect::<Result<Vec<_>>>()?;
8033            select.group_by = Some(group_by);
8034        }
8035
8036        if let Some(mut having) = select.having.take() {
8037            having.this = Self::rewrite_tsql_boolean_predicate_context(having.this)?;
8038            select.having = Some(having);
8039        }
8040
8041        if let Some(mut qualify) = select.qualify.take() {
8042            qualify.this = Self::rewrite_tsql_boolean_predicate_context(qualify.this)?;
8043            select.qualify = Some(qualify);
8044        }
8045
8046        if let Some(mut order_by) = select.order_by.take() {
8047            order_by.expressions = Self::rewrite_tsql_boolean_ordered_values(order_by.expressions)?;
8048            select.order_by = Some(order_by);
8049        }
8050
8051        if let Some(mut distribute_by) = select.distribute_by.take() {
8052            distribute_by.expressions = distribute_by
8053                .expressions
8054                .into_iter()
8055                .map(Self::rewrite_tsql_boolean_scalar_value)
8056                .collect::<Result<Vec<_>>>()?;
8057            select.distribute_by = Some(distribute_by);
8058        }
8059
8060        if let Some(mut cluster_by) = select.cluster_by.take() {
8061            cluster_by.expressions =
8062                Self::rewrite_tsql_boolean_ordered_values(cluster_by.expressions)?;
8063            select.cluster_by = Some(cluster_by);
8064        }
8065
8066        if let Some(mut sort_by) = select.sort_by.take() {
8067            sort_by.expressions = Self::rewrite_tsql_boolean_ordered_values(sort_by.expressions)?;
8068            select.sort_by = Some(sort_by);
8069        }
8070
8071        if let Some(limit_by) = select.limit_by.take() {
8072            select.limit_by = Some(
8073                limit_by
8074                    .into_iter()
8075                    .map(Self::rewrite_tsql_boolean_scalar_value)
8076                    .collect::<Result<Vec<_>>>()?,
8077            );
8078        }
8079
8080        if let Some(distinct_on) = select.distinct_on.take() {
8081            select.distinct_on = Some(
8082                distinct_on
8083                    .into_iter()
8084                    .map(Self::rewrite_tsql_boolean_scalar_value)
8085                    .collect::<Result<Vec<_>>>()?,
8086            );
8087        }
8088
8089        if let Some(mut sample) = select.sample.take() {
8090            sample.size = Self::rewrite_tsql_boolean_nested_contexts(sample.size)?;
8091            if let Some(offset) = sample.offset.take() {
8092                sample.offset = Some(Self::rewrite_tsql_boolean_nested_contexts(offset)?);
8093            }
8094            if let Some(bucket_numerator) = sample.bucket_numerator.take() {
8095                sample.bucket_numerator = Some(Box::new(
8096                    Self::rewrite_tsql_boolean_nested_contexts(*bucket_numerator)?,
8097                ));
8098            }
8099            if let Some(bucket_denominator) = sample.bucket_denominator.take() {
8100                sample.bucket_denominator = Some(Box::new(
8101                    Self::rewrite_tsql_boolean_nested_contexts(*bucket_denominator)?,
8102                ));
8103            }
8104            if let Some(bucket_field) = sample.bucket_field.take() {
8105                sample.bucket_field = Some(Box::new(Self::rewrite_tsql_boolean_nested_contexts(
8106                    *bucket_field,
8107                )?));
8108            }
8109            select.sample = Some(sample);
8110        }
8111
8112        if let Some(settings) = select.settings.take() {
8113            select.settings = Some(
8114                settings
8115                    .into_iter()
8116                    .map(Self::rewrite_tsql_boolean_nested_contexts)
8117                    .collect::<Result<Vec<_>>>()?,
8118            );
8119        }
8120
8121        if let Some(format) = select.format.take() {
8122            select.format = Some(Self::rewrite_tsql_boolean_nested_contexts(format)?);
8123        }
8124
8125        if let Some(mut windows) = select.windows.take() {
8126            for window in windows.iter_mut() {
8127                Self::rewrite_tsql_boolean_over_values(&mut window.spec)?;
8128            }
8129            select.windows = Some(windows);
8130        }
8131
8132        Ok(Expression::Select(select))
8133    }
8134
8135    fn normalize_postgres_boolean_semantics_for_tsql(expr: Expression) -> Result<Expression> {
8136        transform_recursive(expr, &|e| match e {
8137            Expression::Function(function)
8138                if function.args.len() == 2
8139                    && (function.name.eq_ignore_ascii_case("BOOLEQ")
8140                        || function.name.eq_ignore_ascii_case("BOOLNE")) =>
8141            {
8142                let is_equal = function.name.eq_ignore_ascii_case("BOOLEQ");
8143                let mut args = function.args.into_iter();
8144                let op = BinaryOp {
8145                    left: args.next().expect("checked boolean operator arity"),
8146                    right: args.next().expect("checked boolean operator arity"),
8147                    left_comments: Vec::new(),
8148                    operator_comments: Vec::new(),
8149                    trailing_comments: function.trailing_comments,
8150                    inferred_type: None,
8151                };
8152                if is_equal {
8153                    Ok(Expression::Eq(Box::new(op)))
8154                } else {
8155                    Ok(Expression::Neq(Box::new(op)))
8156                }
8157            }
8158            Expression::Cast(cast)
8159                if matches!(cast.to, DataType::Text)
8160                    && Self::is_known_postgres_boolean_expression(&cast.this) =>
8161            {
8162                Ok(Self::postgres_boolean_text_value(cast.this))
8163            }
8164            other => Ok(other),
8165        })
8166    }
8167
8168    fn is_known_postgres_boolean_expression(expr: &Expression) -> bool {
8169        match expr {
8170            Expression::Boolean(_) => true,
8171            Expression::Cast(cast) => matches!(cast.to, DataType::Boolean),
8172            Expression::Paren(paren) => Self::is_known_postgres_boolean_expression(&paren.this),
8173            other => Self::is_tsql_boolean_value_expression(other),
8174        }
8175    }
8176
8177    fn postgres_boolean_text_value(predicate: Expression) -> Expression {
8178        if let Expression::Boolean(boolean) = predicate {
8179            return Expression::string(if boolean.value { "true" } else { "false" });
8180        }
8181
8182        Self::three_valued_boolean_case(
8183            predicate,
8184            Expression::string("true"),
8185            Expression::string("false"),
8186        )
8187    }
8188
8189    fn rewrite_tsql_boolean_scalar_value(expr: Expression) -> Result<Expression> {
8190        if let Expression::Boolean(boolean) = expr {
8191            return Ok(Expression::Cast(Box::new(Cast {
8192                this: Expression::Boolean(boolean),
8193                to: DataType::Boolean,
8194                trailing_comments: Vec::new(),
8195                double_colon_syntax: false,
8196                format: None,
8197                default: None,
8198                inferred_type: None,
8199            })));
8200        }
8201
8202        if Self::is_tsql_boolean_value_expression(&expr) {
8203            // Tuple/subquery equality currently lowers only its positive branch to EXISTS.
8204            // Keep its established two-way scalar fallback until that rewrite models UNKNOWN.
8205            let can_be_unknown = Self::tsql_boolean_expression_can_be_unknown(&expr)
8206                && !Self::node_is_row_value_subquery_comparison(&expr);
8207            let predicate = Self::rewrite_tsql_boolean_predicate_context(expr)?;
8208            return Ok(Self::tsql_boolean_value_case(predicate, can_be_unknown));
8209        }
8210
8211        match expr {
8212            Expression::Alias(mut alias) => {
8213                alias.this = Self::rewrite_tsql_boolean_scalar_value(alias.this)?;
8214                Ok(Expression::Alias(alias))
8215            }
8216            Expression::Paren(mut paren) => {
8217                paren.this = Self::rewrite_tsql_boolean_scalar_value(paren.this)?;
8218                Ok(Expression::Paren(paren))
8219            }
8220            Expression::Cast(mut cast) => {
8221                cast.this = Self::rewrite_tsql_boolean_scalar_value(cast.this)?;
8222                if let Some(format) = cast.format.take() {
8223                    cast.format = Some(Box::new(Self::rewrite_tsql_boolean_nested_contexts(
8224                        *format,
8225                    )?));
8226                }
8227                if let Some(default) = cast.default.take() {
8228                    cast.default =
8229                        Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(*default)?));
8230                }
8231                Ok(Expression::Cast(cast))
8232            }
8233            Expression::TryCast(mut cast) => {
8234                cast.this = Self::rewrite_tsql_boolean_scalar_value(cast.this)?;
8235                if let Some(format) = cast.format.take() {
8236                    cast.format = Some(Box::new(Self::rewrite_tsql_boolean_nested_contexts(
8237                        *format,
8238                    )?));
8239                }
8240                if let Some(default) = cast.default.take() {
8241                    cast.default =
8242                        Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(*default)?));
8243                }
8244                Ok(Expression::TryCast(cast))
8245            }
8246            Expression::SafeCast(mut cast) => {
8247                cast.this = Self::rewrite_tsql_boolean_scalar_value(cast.this)?;
8248                if let Some(format) = cast.format.take() {
8249                    cast.format = Some(Box::new(Self::rewrite_tsql_boolean_nested_contexts(
8250                        *format,
8251                    )?));
8252                }
8253                if let Some(default) = cast.default.take() {
8254                    cast.default =
8255                        Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(*default)?));
8256                }
8257                Ok(Expression::SafeCast(cast))
8258            }
8259            Expression::Case(mut case) => {
8260                let is_simple_case = case.operand.is_some();
8261                if let Some(operand) = case.operand.take() {
8262                    case.operand = Some(Self::rewrite_tsql_boolean_scalar_value(operand)?);
8263                }
8264                case.whens = case
8265                    .whens
8266                    .into_iter()
8267                    .map(|(condition, result)| {
8268                        let condition = if is_simple_case {
8269                            Self::rewrite_tsql_boolean_scalar_value(condition)?
8270                        } else {
8271                            Self::rewrite_tsql_boolean_predicate_context(condition)?
8272                        };
8273                        Ok((condition, Self::rewrite_tsql_boolean_scalar_value(result)?))
8274                    })
8275                    .collect::<Result<Vec<_>>>()?;
8276                if let Some(else_) = case.else_.take() {
8277                    case.else_ = Some(Self::rewrite_tsql_boolean_scalar_value(else_)?);
8278                }
8279                Ok(Expression::Case(case))
8280            }
8281            Expression::IfFunc(mut if_func) => {
8282                if_func.condition =
8283                    Self::rewrite_tsql_boolean_predicate_context(if_func.condition)?;
8284                if_func.true_value = Self::rewrite_tsql_boolean_scalar_value(if_func.true_value)?;
8285                if let Some(false_value) = if_func.false_value.take() {
8286                    if_func.false_value =
8287                        Some(Self::rewrite_tsql_boolean_scalar_value(false_value)?);
8288                }
8289                Ok(Expression::IfFunc(if_func))
8290            }
8291            Expression::WindowFunction(mut window_function) => {
8292                window_function.this =
8293                    Self::rewrite_tsql_boolean_nested_contexts(window_function.this)?;
8294                Self::rewrite_tsql_boolean_over_values(&mut window_function.over)?;
8295                if let Some(mut keep) = window_function.keep.take() {
8296                    keep.order_by = Self::rewrite_tsql_boolean_ordered_values(keep.order_by)?;
8297                    window_function.keep = Some(keep);
8298                }
8299                Ok(Expression::WindowFunction(window_function))
8300            }
8301            Expression::WithinGroup(mut within_group) => {
8302                within_group.this = Self::rewrite_tsql_boolean_nested_contexts(within_group.this)?;
8303                within_group.order_by =
8304                    Self::rewrite_tsql_boolean_ordered_values(within_group.order_by)?;
8305                Ok(Expression::WithinGroup(within_group))
8306            }
8307            Expression::Subquery(mut subquery) => {
8308                subquery.this = Self::rewrite_boolean_values_for_tsql(subquery.this)?;
8309                Ok(Expression::Subquery(subquery))
8310            }
8311            Expression::Select(select) => Self::rewrite_boolean_values_in_tsql_select(select),
8312            other => Self::rewrite_tsql_boolean_nested_contexts(other),
8313        }
8314    }
8315
8316    fn rewrite_tsql_boolean_predicate_context(expr: Expression) -> Result<Expression> {
8317        let expr = Self::rewrite_tsql_boolean_nested_contexts(expr)?;
8318        Ok(crate::transforms::ensure_bool_condition(expr))
8319    }
8320
8321    fn rewrite_tsql_boolean_nested_contexts(expr: Expression) -> Result<Expression> {
8322        transform_recursive(expr, &|e| match e {
8323            Expression::Select(select) => Self::rewrite_boolean_values_in_tsql_select(select),
8324            Expression::Subquery(mut subquery) => {
8325                subquery.this = Self::rewrite_boolean_values_for_tsql(subquery.this)?;
8326                Ok(Expression::Subquery(subquery))
8327            }
8328            Expression::Union(_) | Expression::Intersect(_) | Expression::Except(_) => {
8329                Self::rewrite_boolean_values_for_tsql(e)
8330            }
8331            other => Self::rewrite_tsql_boolean_cast_operand(other),
8332        })
8333    }
8334
8335    fn rewrite_tsql_boolean_cast_operand(expr: Expression) -> Result<Expression> {
8336        macro_rules! rewrite_cast_operand {
8337            ($variant:ident, $cast:expr) => {{
8338                let mut cast = $cast;
8339                if Self::is_tsql_boolean_value_expression(&cast.this) {
8340                    cast.this = Self::rewrite_tsql_boolean_scalar_value(cast.this)?;
8341                }
8342                Ok(Expression::$variant(cast))
8343            }};
8344        }
8345
8346        match expr {
8347            Expression::Cast(cast) => rewrite_cast_operand!(Cast, cast),
8348            Expression::TryCast(cast) => rewrite_cast_operand!(TryCast, cast),
8349            Expression::SafeCast(cast) => rewrite_cast_operand!(SafeCast, cast),
8350            other => Ok(other),
8351        }
8352    }
8353
8354    fn rewrite_tsql_boolean_ordered_values(
8355        ordered: Vec<crate::expressions::Ordered>,
8356    ) -> Result<Vec<crate::expressions::Ordered>> {
8357        ordered
8358            .into_iter()
8359            .map(|mut ordered| {
8360                ordered.this = Self::rewrite_tsql_boolean_scalar_value(ordered.this)?;
8361                if let Some(with_fill) = ordered.with_fill.take() {
8362                    ordered.with_fill = Some(Box::new(
8363                        Self::rewrite_tsql_boolean_with_fill_values(*with_fill)?,
8364                    ));
8365                }
8366                Ok(ordered)
8367            })
8368            .collect()
8369    }
8370
8371    fn rewrite_tsql_boolean_with_fill_values(
8372        mut with_fill: crate::expressions::WithFill,
8373    ) -> Result<crate::expressions::WithFill> {
8374        if let Some(from) = with_fill.from_.take() {
8375            with_fill.from_ = Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(*from)?));
8376        }
8377        if let Some(to) = with_fill.to.take() {
8378            with_fill.to = Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(*to)?));
8379        }
8380        if let Some(step) = with_fill.step.take() {
8381            with_fill.step = Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(*step)?));
8382        }
8383        if let Some(staleness) = with_fill.staleness.take() {
8384            with_fill.staleness = Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(
8385                *staleness,
8386            )?));
8387        }
8388        if let Some(interpolate) = with_fill.interpolate.take() {
8389            with_fill.interpolate = Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(
8390                *interpolate,
8391            )?));
8392        }
8393        Ok(with_fill)
8394    }
8395
8396    fn rewrite_tsql_boolean_over_values(over: &mut crate::expressions::Over) -> Result<()> {
8397        over.partition_by = std::mem::take(&mut over.partition_by)
8398            .into_iter()
8399            .map(Self::rewrite_tsql_boolean_scalar_value)
8400            .collect::<Result<Vec<_>>>()?;
8401        over.order_by =
8402            Self::rewrite_tsql_boolean_ordered_values(std::mem::take(&mut over.order_by))?;
8403        Ok(())
8404    }
8405
8406    fn is_tsql_boolean_value_expression(expr: &Expression) -> bool {
8407        match expr {
8408            Expression::Paren(paren) => Self::is_tsql_boolean_value_expression(&paren.this),
8409            Expression::Eq(_)
8410            | Expression::Neq(_)
8411            | Expression::Lt(_)
8412            | Expression::Lte(_)
8413            | Expression::Gt(_)
8414            | Expression::Gte(_)
8415            | Expression::Is(_)
8416            | Expression::IsNull(_)
8417            | Expression::IsTrue(_)
8418            | Expression::IsFalse(_)
8419            | Expression::Like(_)
8420            | Expression::ILike(_)
8421            | Expression::StartsWith(_)
8422            | Expression::SimilarTo(_)
8423            | Expression::Glob(_)
8424            | Expression::RegexpLike(_)
8425            | Expression::In(_)
8426            | Expression::Between(_)
8427            | Expression::Exists(_)
8428            | Expression::And(_)
8429            | Expression::Or(_)
8430            | Expression::Not(_)
8431            | Expression::Any(_)
8432            | Expression::All(_)
8433            | Expression::NullSafeEq(_)
8434            | Expression::NullSafeNeq(_)
8435            | Expression::EqualNull(_) => true,
8436            _ => false,
8437        }
8438    }
8439
8440    fn tsql_boolean_expression_can_be_unknown(expr: &Expression) -> bool {
8441        match expr {
8442            Expression::Boolean(_)
8443            | Expression::IsNull(_)
8444            | Expression::IsTrue(_)
8445            | Expression::IsFalse(_)
8446            | Expression::Exists(_)
8447            | Expression::NullSafeEq(_)
8448            | Expression::NullSafeNeq(_)
8449            | Expression::EqualNull(_) => false,
8450            Expression::Paren(paren) => Self::tsql_boolean_expression_can_be_unknown(&paren.this),
8451            Expression::Not(op) => Self::tsql_boolean_expression_can_be_unknown(&op.this),
8452            Expression::And(op) | Expression::Or(op) => {
8453                Self::tsql_boolean_expression_can_be_unknown(&op.left)
8454                    || Self::tsql_boolean_expression_can_be_unknown(&op.right)
8455            }
8456            _ => true,
8457        }
8458    }
8459
8460    fn tsql_boolean_value_case(predicate: Expression, can_be_unknown: bool) -> Expression {
8461        let case = if can_be_unknown {
8462            Self::three_valued_boolean_case(predicate, Expression::number(1), Expression::number(0))
8463        } else {
8464            Expression::Case(Box::new(crate::expressions::Case {
8465                operand: None,
8466                whens: vec![(predicate, Expression::number(1))],
8467                else_: Some(Expression::number(0)),
8468                comments: Vec::new(),
8469                inferred_type: None,
8470            }))
8471        };
8472
8473        Expression::Cast(Box::new(Cast {
8474            this: case,
8475            to: DataType::Boolean,
8476            trailing_comments: Vec::new(),
8477            double_colon_syntax: false,
8478            format: None,
8479            default: None,
8480            inferred_type: None,
8481        }))
8482    }
8483
8484    fn three_valued_boolean_case(
8485        predicate: Expression,
8486        true_value: Expression,
8487        false_value: Expression,
8488    ) -> Expression {
8489        let false_operand = if matches!(predicate, Expression::And(_) | Expression::Or(_)) {
8490            Expression::Paren(Box::new(crate::expressions::Paren {
8491                this: predicate.clone(),
8492                trailing_comments: Vec::new(),
8493            }))
8494        } else {
8495            predicate.clone()
8496        };
8497        let false_predicate = Expression::Not(Box::new(crate::expressions::UnaryOp {
8498            this: false_operand,
8499            inferred_type: None,
8500        }));
8501
8502        Expression::Case(Box::new(crate::expressions::Case {
8503            operand: None,
8504            whens: vec![(predicate, true_value), (false_predicate, false_value)],
8505            else_: Some(Expression::null()),
8506            comments: Vec::new(),
8507            inferred_type: None,
8508        }))
8509    }
8510
8511    fn rewrite_aggregate_filters_for_tsql(expr: Expression) -> Result<Expression> {
8512        transform_recursive(expr, &|e| Self::rewrite_aggregate_filter_for_tsql(e))
8513    }
8514
8515    fn rewrite_aggregate_filter_for_tsql(expr: Expression) -> Result<Expression> {
8516        macro_rules! rewrite_agg_filter {
8517            ($variant:ident, $agg:expr) => {{
8518                let mut agg = $agg;
8519                if let Some(filter) = agg.filter.take() {
8520                    let this = std::mem::replace(&mut agg.this, Expression::null());
8521                    agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
8522                }
8523                Ok(Expression::$variant(agg))
8524            }};
8525        }
8526
8527        match expr {
8528            Expression::Filter(filter) => {
8529                let condition = match *filter.expression {
8530                    Expression::Where(where_) => where_.this,
8531                    other => other,
8532                };
8533                Ok(Self::push_filter_into_tsql_aggregate(
8534                    *filter.this,
8535                    condition,
8536                ))
8537            }
8538            Expression::AggregateFunction(mut agg) => {
8539                if let Some(filter) = agg.filter.take() {
8540                    Self::rewrite_generic_aggregate_filter_for_tsql(&mut agg, filter);
8541                }
8542                Ok(Expression::AggregateFunction(agg))
8543            }
8544            Expression::Count(mut count) => {
8545                if let Some(filter) = count.filter.take() {
8546                    let value = if count.star {
8547                        Expression::number(1)
8548                    } else {
8549                        count.this.take().unwrap_or_else(|| Expression::number(1))
8550                    };
8551                    count.star = false;
8552                    count.this = Some(Self::conditional_aggregate_value_for_tsql(filter, value));
8553                }
8554                Ok(Expression::Count(count))
8555            }
8556            Expression::Sum(agg) => rewrite_agg_filter!(Sum, agg),
8557            Expression::Avg(agg) => rewrite_agg_filter!(Avg, agg),
8558            Expression::Min(agg) => rewrite_agg_filter!(Min, agg),
8559            Expression::Max(agg) => rewrite_agg_filter!(Max, agg),
8560            Expression::ArrayAgg(agg) => rewrite_agg_filter!(ArrayAgg, agg),
8561            Expression::CountIf(agg) => Ok(Expression::CountIf(agg)),
8562            Expression::Stddev(agg) => rewrite_agg_filter!(Stddev, agg),
8563            Expression::StddevPop(agg) => rewrite_agg_filter!(StddevPop, agg),
8564            Expression::StddevSamp(agg) => rewrite_agg_filter!(StddevSamp, agg),
8565            Expression::Variance(agg) => rewrite_agg_filter!(Variance, agg),
8566            Expression::VarPop(agg) => rewrite_agg_filter!(VarPop, agg),
8567            Expression::VarSamp(agg) => rewrite_agg_filter!(VarSamp, agg),
8568            Expression::Median(agg) => rewrite_agg_filter!(Median, agg),
8569            Expression::Mode(agg) => rewrite_agg_filter!(Mode, agg),
8570            Expression::First(agg) => rewrite_agg_filter!(First, agg),
8571            Expression::Last(agg) => rewrite_agg_filter!(Last, agg),
8572            Expression::AnyValue(agg) => rewrite_agg_filter!(AnyValue, agg),
8573            Expression::ApproxDistinct(agg) => rewrite_agg_filter!(ApproxDistinct, agg),
8574            Expression::ApproxCountDistinct(agg) => {
8575                rewrite_agg_filter!(ApproxCountDistinct, agg)
8576            }
8577            Expression::LogicalAnd(agg) => rewrite_agg_filter!(LogicalAnd, agg),
8578            Expression::LogicalOr(agg) => rewrite_agg_filter!(LogicalOr, agg),
8579            Expression::Skewness(agg) => rewrite_agg_filter!(Skewness, agg),
8580            Expression::ArrayConcatAgg(agg) => rewrite_agg_filter!(ArrayConcatAgg, agg),
8581            Expression::ArrayUniqueAgg(agg) => rewrite_agg_filter!(ArrayUniqueAgg, agg),
8582            Expression::BoolXorAgg(agg) => rewrite_agg_filter!(BoolXorAgg, agg),
8583            Expression::BitwiseAndAgg(agg) => rewrite_agg_filter!(BitwiseAndAgg, agg),
8584            Expression::BitwiseOrAgg(agg) => rewrite_agg_filter!(BitwiseOrAgg, agg),
8585            Expression::BitwiseXorAgg(agg) => rewrite_agg_filter!(BitwiseXorAgg, agg),
8586            Expression::StringAgg(mut agg) => {
8587                if let Some(filter) = agg.filter.take() {
8588                    let this = std::mem::replace(&mut agg.this, Expression::null());
8589                    agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
8590                }
8591                Ok(Expression::StringAgg(agg))
8592            }
8593            Expression::GroupConcat(mut agg) => {
8594                if let Some(filter) = agg.filter.take() {
8595                    let this = std::mem::replace(&mut agg.this, Expression::null());
8596                    agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
8597                }
8598                Ok(Expression::GroupConcat(agg))
8599            }
8600            Expression::ListAgg(mut agg) => {
8601                if let Some(filter) = agg.filter.take() {
8602                    let this = std::mem::replace(&mut agg.this, Expression::null());
8603                    agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
8604                }
8605                Ok(Expression::ListAgg(agg))
8606            }
8607            Expression::WithinGroup(mut within_group) => {
8608                within_group.this = Self::rewrite_aggregate_filters_for_tsql(within_group.this)?;
8609                Ok(Expression::WithinGroup(within_group))
8610            }
8611            other => Ok(other),
8612        }
8613    }
8614
8615    fn push_filter_into_tsql_aggregate(expr: Expression, filter: Expression) -> Expression {
8616        macro_rules! push_agg_filter {
8617            ($variant:ident, $agg:expr) => {{
8618                let mut agg = $agg;
8619                let this = std::mem::replace(&mut agg.this, Expression::null());
8620                agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
8621                agg.filter = None;
8622                Expression::$variant(agg)
8623            }};
8624        }
8625
8626        match expr {
8627            Expression::AggregateFunction(mut agg) => {
8628                Self::rewrite_generic_aggregate_filter_for_tsql(&mut agg, filter);
8629                Expression::AggregateFunction(agg)
8630            }
8631            Expression::Count(mut count) => {
8632                let value = if count.star {
8633                    Expression::number(1)
8634                } else {
8635                    count.this.take().unwrap_or_else(|| Expression::number(1))
8636                };
8637                count.star = false;
8638                count.filter = None;
8639                count.this = Some(Self::conditional_aggregate_value_for_tsql(filter, value));
8640                Expression::Count(count)
8641            }
8642            Expression::Sum(agg) => push_agg_filter!(Sum, agg),
8643            Expression::Avg(agg) => push_agg_filter!(Avg, agg),
8644            Expression::Min(agg) => push_agg_filter!(Min, agg),
8645            Expression::Max(agg) => push_agg_filter!(Max, agg),
8646            Expression::ArrayAgg(agg) => push_agg_filter!(ArrayAgg, agg),
8647            Expression::CountIf(mut agg) => {
8648                agg.filter = Some(filter);
8649                Expression::CountIf(agg)
8650            }
8651            Expression::Stddev(agg) => push_agg_filter!(Stddev, agg),
8652            Expression::StddevPop(agg) => push_agg_filter!(StddevPop, agg),
8653            Expression::StddevSamp(agg) => push_agg_filter!(StddevSamp, agg),
8654            Expression::Variance(agg) => push_agg_filter!(Variance, agg),
8655            Expression::VarPop(agg) => push_agg_filter!(VarPop, agg),
8656            Expression::VarSamp(agg) => push_agg_filter!(VarSamp, agg),
8657            Expression::Median(agg) => push_agg_filter!(Median, agg),
8658            Expression::Mode(agg) => push_agg_filter!(Mode, agg),
8659            Expression::First(agg) => push_agg_filter!(First, agg),
8660            Expression::Last(agg) => push_agg_filter!(Last, agg),
8661            Expression::AnyValue(agg) => push_agg_filter!(AnyValue, agg),
8662            Expression::ApproxDistinct(agg) => push_agg_filter!(ApproxDistinct, agg),
8663            Expression::ApproxCountDistinct(agg) => {
8664                push_agg_filter!(ApproxCountDistinct, agg)
8665            }
8666            Expression::LogicalAnd(agg) => push_agg_filter!(LogicalAnd, agg),
8667            Expression::LogicalOr(agg) => push_agg_filter!(LogicalOr, agg),
8668            Expression::Skewness(agg) => push_agg_filter!(Skewness, agg),
8669            Expression::ArrayConcatAgg(agg) => push_agg_filter!(ArrayConcatAgg, agg),
8670            Expression::ArrayUniqueAgg(agg) => push_agg_filter!(ArrayUniqueAgg, agg),
8671            Expression::BoolXorAgg(agg) => push_agg_filter!(BoolXorAgg, agg),
8672            Expression::BitwiseAndAgg(agg) => push_agg_filter!(BitwiseAndAgg, agg),
8673            Expression::BitwiseOrAgg(agg) => push_agg_filter!(BitwiseOrAgg, agg),
8674            Expression::BitwiseXorAgg(agg) => push_agg_filter!(BitwiseXorAgg, agg),
8675            Expression::StringAgg(mut agg) => {
8676                let this = std::mem::replace(&mut agg.this, Expression::null());
8677                agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
8678                agg.filter = None;
8679                Expression::StringAgg(agg)
8680            }
8681            Expression::GroupConcat(mut agg) => {
8682                let this = std::mem::replace(&mut agg.this, Expression::null());
8683                agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
8684                agg.filter = None;
8685                Expression::GroupConcat(agg)
8686            }
8687            Expression::ListAgg(mut agg) => {
8688                let this = std::mem::replace(&mut agg.this, Expression::null());
8689                agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
8690                agg.filter = None;
8691                Expression::ListAgg(agg)
8692            }
8693            Expression::WithinGroup(mut within_group) => {
8694                within_group.this =
8695                    Self::push_filter_into_tsql_aggregate(within_group.this, filter);
8696                Expression::WithinGroup(within_group)
8697            }
8698            other => Expression::Filter(Box::new(crate::expressions::Filter {
8699                this: Box::new(other),
8700                expression: Box::new(filter),
8701            })),
8702        }
8703    }
8704
8705    fn rewrite_generic_aggregate_filter_for_tsql(
8706        agg: &mut crate::expressions::AggregateFunction,
8707        filter: Expression,
8708    ) {
8709        let is_count =
8710            agg.name.eq_ignore_ascii_case("COUNT") || agg.name.eq_ignore_ascii_case("COUNT_BIG");
8711        let is_count_star = is_count
8712            && (agg.args.is_empty()
8713                || (agg.args.len() == 1 && matches!(agg.args[0], Expression::Star(_))));
8714
8715        if is_count_star {
8716            agg.args = vec![Self::conditional_aggregate_value_for_tsql(
8717                filter,
8718                Expression::number(1),
8719            )];
8720        } else if !agg.args.is_empty() {
8721            agg.args = agg
8722                .args
8723                .drain(..)
8724                .map(|arg| Self::conditional_aggregate_value_for_tsql(filter.clone(), arg))
8725                .collect();
8726        } else {
8727            agg.filter = Some(filter);
8728        }
8729    }
8730
8731    fn conditional_aggregate_value_for_tsql(filter: Expression, value: Expression) -> Expression {
8732        let filter = crate::transforms::ensure_bool_condition(filter);
8733        Expression::Case(Box::new(crate::expressions::Case {
8734            operand: None,
8735            whens: vec![(filter, value)],
8736            else_: None,
8737            comments: Vec::new(),
8738            inferred_type: None,
8739        }))
8740    }
8741
8742    fn reject_pgvector_distance_operators_for_sqlite(&self, sql: &str) -> Result<()> {
8743        let tokens = self.tokenize(sql)?;
8744        for (i, token) in tokens.iter().enumerate() {
8745            if token.token_type == TokenType::NullsafeEq {
8746                return Err(crate::error::Error::unsupported(
8747                    "PostgreSQL pgvector cosine distance operator <=>",
8748                    "SQLite",
8749                ));
8750            }
8751            if token.token_type == TokenType::Lt
8752                && tokens
8753                    .get(i + 1)
8754                    .is_some_and(|token| token.token_type == TokenType::Tilde)
8755                && tokens
8756                    .get(i + 2)
8757                    .is_some_and(|token| token.token_type == TokenType::Gt)
8758            {
8759                return Err(crate::error::Error::unsupported(
8760                    "PostgreSQL pgvector Hamming distance operator <~>",
8761                    "SQLite",
8762                ));
8763            }
8764        }
8765        Ok(())
8766    }
8767
8768    fn normalize_sqlite_double_quoted_defaults(expr: Expression) -> Result<Expression> {
8769        fn normalize_default_expr(expr: Expression) -> Result<Expression> {
8770            transform_recursive(expr, &|e| match e {
8771                Expression::Column(col)
8772                    if col.table.is_none() && col.name.quoted && !col.join_mark =>
8773                {
8774                    Ok(Expression::Literal(Box::new(Literal::String(
8775                        col.name.name,
8776                    ))))
8777                }
8778                Expression::Identifier(id) if id.quoted => {
8779                    Ok(Expression::Literal(Box::new(Literal::String(id.name))))
8780                }
8781                _ => Ok(e),
8782            })
8783        }
8784
8785        fn normalize_column_default(col: &mut crate::expressions::ColumnDef) -> Result<()> {
8786            if let Some(default) = col.default.take() {
8787                col.default = Some(normalize_default_expr(default)?);
8788            }
8789
8790            for constraint in &mut col.constraints {
8791                if let ColumnConstraint::Default(default) = constraint {
8792                    *default = normalize_default_expr(default.clone())?;
8793                }
8794            }
8795
8796            Ok(())
8797        }
8798
8799        transform_recursive(expr, &|e| match e {
8800            Expression::CreateTable(mut ct) => {
8801                for column in &mut ct.columns {
8802                    normalize_column_default(column)?;
8803                }
8804                Ok(Expression::CreateTable(ct))
8805            }
8806            Expression::ColumnDef(mut col) => {
8807                normalize_column_default(&mut col)?;
8808                Ok(Expression::ColumnDef(col))
8809            }
8810            _ => Ok(e),
8811        })
8812    }
8813
8814    fn normalize_postgres_to_sqlite_types(expr: Expression) -> Result<Expression> {
8815        fn sqlite_type(dt: crate::expressions::DataType) -> crate::expressions::DataType {
8816            use crate::expressions::DataType;
8817
8818            match dt {
8819                DataType::Bit { .. } => DataType::Int {
8820                    length: None,
8821                    integer_spelling: true,
8822                },
8823                DataType::TextWithLength { .. } => DataType::Text,
8824                DataType::VarChar { .. } => DataType::Text,
8825                DataType::Char { .. } => DataType::Text,
8826                DataType::Timestamp { timezone: true, .. } => DataType::Text,
8827                DataType::Custom { name } => {
8828                    let base = name
8829                        .split_once('(')
8830                        .map_or(name.as_str(), |(base, _)| base)
8831                        .trim();
8832                    if base.eq_ignore_ascii_case("TSVECTOR")
8833                        || base.eq_ignore_ascii_case("TIMESTAMPTZ")
8834                        || base.eq_ignore_ascii_case("TIMESTAMP WITH TIME ZONE")
8835                        || base.eq_ignore_ascii_case("NVARCHAR")
8836                        || base.eq_ignore_ascii_case("NCHAR")
8837                    {
8838                        DataType::Text
8839                    } else {
8840                        DataType::Custom { name }
8841                    }
8842                }
8843                _ => dt,
8844            }
8845        }
8846
8847        transform_recursive(expr, &|e| match e {
8848            Expression::DataType(dt) => Ok(Expression::DataType(sqlite_type(dt))),
8849            Expression::CreateTable(mut ct) => {
8850                for column in &mut ct.columns {
8851                    column.data_type = sqlite_type(column.data_type.clone());
8852                }
8853                Ok(Expression::CreateTable(ct))
8854            }
8855            _ => Ok(e),
8856        })
8857    }
8858
8859    fn normalize_postgres_to_fabric_types(expr: Expression) -> Result<Expression> {
8860        fn fabric_type(dt: crate::expressions::DataType) -> crate::expressions::DataType {
8861            use crate::expressions::DataType;
8862
8863            match dt {
8864                DataType::Decimal {
8865                    precision: None,
8866                    scale: None,
8867                } => DataType::Decimal {
8868                    precision: Some(38),
8869                    scale: Some(10),
8870                },
8871                DataType::Json | DataType::JsonB => DataType::Custom {
8872                    name: "VARCHAR(MAX)".to_string(),
8873                },
8874                _ => dt,
8875            }
8876        }
8877
8878        transform_recursive(expr, &|e| match e {
8879            Expression::DataType(dt) => Ok(Expression::DataType(fabric_type(dt))),
8880            Expression::CreateTable(mut ct) => {
8881                for column in &mut ct.columns {
8882                    column.data_type = fabric_type(column.data_type.clone());
8883                }
8884                Ok(Expression::CreateTable(ct))
8885            }
8886            Expression::ColumnDef(mut col) => {
8887                col.data_type = fabric_type(col.data_type);
8888                Ok(Expression::ColumnDef(col))
8889            }
8890            _ => Ok(e),
8891        })
8892    }
8893
8894    /// For DuckDB target: when FROM clause contains RANGE(n), replace
8895    /// `(ROW_NUMBER() OVER (ORDER BY 1 NULLS FIRST) - 1)` with `range` in select expressions.
8896    /// This handles SEQ1/2/4/8 → RANGE transpilation from Snowflake.
8897    fn seq_rownum_to_range(expr: Expression) -> Result<Expression> {
8898        if let Expression::Select(mut select) = expr {
8899            // Check if FROM contains a RANGE function
8900            let has_range_from = if let Some(ref from) = select.from {
8901                from.expressions.iter().any(|e| {
8902                    // Check for direct RANGE(...) or aliased RANGE(...)
8903                    match e {
8904                        Expression::Function(f) => f.name.eq_ignore_ascii_case("RANGE"),
8905                        Expression::Alias(a) => {
8906                            matches!(&a.this, Expression::Function(f) if f.name.eq_ignore_ascii_case("RANGE"))
8907                        }
8908                        _ => false,
8909                    }
8910                })
8911            } else {
8912                false
8913            };
8914
8915            if has_range_from {
8916                // Replace the ROW_NUMBER pattern in select expressions
8917                select.expressions = select
8918                    .expressions
8919                    .into_iter()
8920                    .map(|e| Self::replace_rownum_with_range(e))
8921                    .collect();
8922            }
8923
8924            Ok(Expression::Select(select))
8925        } else {
8926            Ok(expr)
8927        }
8928    }
8929
8930    /// Replace `(ROW_NUMBER() OVER (...) - 1)` with `range` column reference
8931    fn replace_rownum_with_range(expr: Expression) -> Expression {
8932        match expr {
8933            // Match: (ROW_NUMBER() OVER (...) - 1) % N → range % N
8934            Expression::Mod(op) => {
8935                let new_left = Self::try_replace_rownum_paren(&op.left);
8936                Expression::Mod(Box::new(crate::expressions::BinaryOp {
8937                    left: new_left,
8938                    right: op.right,
8939                    left_comments: op.left_comments,
8940                    operator_comments: op.operator_comments,
8941                    trailing_comments: op.trailing_comments,
8942                    inferred_type: op.inferred_type,
8943                }))
8944            }
8945            // Match: (CASE WHEN (ROW...) % N >= ... THEN ... ELSE ... END)
8946            Expression::Paren(p) => {
8947                let inner = Self::replace_rownum_with_range(p.this);
8948                Expression::Paren(Box::new(crate::expressions::Paren {
8949                    this: inner,
8950                    trailing_comments: p.trailing_comments,
8951                }))
8952            }
8953            Expression::Case(mut c) => {
8954                // Replace ROW_NUMBER in WHEN conditions and THEN expressions
8955                c.whens = c
8956                    .whens
8957                    .into_iter()
8958                    .map(|(cond, then)| {
8959                        (
8960                            Self::replace_rownum_with_range(cond),
8961                            Self::replace_rownum_with_range(then),
8962                        )
8963                    })
8964                    .collect();
8965                if let Some(else_) = c.else_ {
8966                    c.else_ = Some(Self::replace_rownum_with_range(else_));
8967                }
8968                Expression::Case(c)
8969            }
8970            Expression::Gte(op) => Expression::Gte(Box::new(crate::expressions::BinaryOp {
8971                left: Self::replace_rownum_with_range(op.left),
8972                right: op.right,
8973                left_comments: op.left_comments,
8974                operator_comments: op.operator_comments,
8975                trailing_comments: op.trailing_comments,
8976                inferred_type: op.inferred_type,
8977            })),
8978            Expression::Sub(op) => Expression::Sub(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::Alias(mut a) => {
8987                a.this = Self::replace_rownum_with_range(a.this);
8988                Expression::Alias(a)
8989            }
8990            other => other,
8991        }
8992    }
8993
8994    /// Check if an expression is `(ROW_NUMBER() OVER (...) - 1)` and replace with `range`
8995    fn try_replace_rownum_paren(expr: &Expression) -> Expression {
8996        if let Expression::Paren(ref p) = expr {
8997            if let Expression::Sub(ref sub) = p.this {
8998                if let Expression::WindowFunction(ref wf) = sub.left {
8999                    if let Expression::Function(ref f) = wf.this {
9000                        if f.name.eq_ignore_ascii_case("ROW_NUMBER") {
9001                            if let Expression::Literal(ref lit) = sub.right {
9002                                if let crate::expressions::Literal::Number(ref n) = lit.as_ref() {
9003                                    if n == "1" {
9004                                        return Expression::column("range");
9005                                    }
9006                                }
9007                            }
9008                        }
9009                    }
9010                }
9011            }
9012        }
9013        expr.clone()
9014    }
9015
9016    /// Transform BigQuery GENERATE_DATE_ARRAY in UNNEST for Snowflake target.
9017    /// Converts:
9018    ///   SELECT ..., alias, ... FROM t CROSS JOIN UNNEST(GENERATE_DATE_ARRAY(start, end, INTERVAL '1' unit)) AS alias
9019    /// To:
9020    ///   SELECT ..., DATEADD(unit, CAST(alias AS INT), CAST(start AS DATE)) AS alias, ...
9021    ///   FROM t, LATERAL FLATTEN(INPUT => ARRAY_GENERATE_RANGE(0, DATEDIFF(unit, start, end) + 1)) AS _t0(seq, key, path, index, alias, this)
9022    fn transform_generate_date_array_snowflake(expr: Expression) -> Result<Expression> {
9023        use crate::expressions::*;
9024        transform_recursive(expr, &|e| {
9025            // Handle ARRAY_SIZE(GENERATE_DATE_ARRAY(...)) -> ARRAY_SIZE((SELECT ARRAY_AGG(*) FROM subquery))
9026            if let Expression::ArraySize(ref af) = e {
9027                if let Expression::Function(ref f) = af.this {
9028                    if f.name.eq_ignore_ascii_case("GENERATE_DATE_ARRAY") && f.args.len() >= 2 {
9029                        let result = Self::convert_array_size_gda_snowflake(f)?;
9030                        return Ok(result);
9031                    }
9032                }
9033            }
9034
9035            let Expression::Select(mut sel) = e else {
9036                return Ok(e);
9037            };
9038
9039            // Find joins with UNNEST containing GenerateSeries (from GENERATE_DATE_ARRAY conversion)
9040            let mut gda_info: Option<(String, Expression, Expression, String)> = None; // (alias_name, start_expr, end_expr, unit)
9041            let mut gda_join_idx: Option<usize> = None;
9042
9043            for (idx, join) in sel.joins.iter().enumerate() {
9044                // The join.this may be:
9045                // 1. Unnest(UnnestFunc { alias: Some("mnth"), ... })
9046                // 2. Alias(Alias { this: Unnest(UnnestFunc { alias: None, ... }), alias: "mnth", ... })
9047                let (unnest_ref, alias_name) = match &join.this {
9048                    Expression::Unnest(ref unnest) => {
9049                        let alias = unnest.alias.as_ref().map(|id| id.name.clone());
9050                        (Some(unnest.as_ref()), alias)
9051                    }
9052                    Expression::Alias(ref a) => {
9053                        if let Expression::Unnest(ref unnest) = a.this {
9054                            (Some(unnest.as_ref()), Some(a.alias.name.clone()))
9055                        } else {
9056                            (None, None)
9057                        }
9058                    }
9059                    _ => (None, None),
9060                };
9061
9062                if let (Some(unnest), Some(alias)) = (unnest_ref, alias_name) {
9063                    // Check the main expression (this) of the UNNEST for GENERATE_DATE_ARRAY function
9064                    if let Expression::Function(ref f) = unnest.this {
9065                        if f.name.eq_ignore_ascii_case("GENERATE_DATE_ARRAY") && f.args.len() >= 2 {
9066                            let start_expr = f.args[0].clone();
9067                            let end_expr = f.args[1].clone();
9068                            let step = f.args.get(2).cloned();
9069
9070                            // Extract unit from step interval
9071                            let unit = if let Some(Expression::Interval(ref iv)) = step {
9072                                if let Some(IntervalUnitSpec::Simple { ref unit, .. }) = iv.unit {
9073                                    Some(format!("{:?}", unit).to_ascii_uppercase())
9074                                } else if let Some(ref this) = iv.this {
9075                                    // The interval may be stored as a string like "1 MONTH"
9076                                    if let Expression::Literal(lit) = this {
9077                                        if let Literal::String(ref s) = lit.as_ref() {
9078                                            let parts: Vec<&str> = s.split_whitespace().collect();
9079                                            if parts.len() == 2 {
9080                                                Some(parts[1].to_ascii_uppercase())
9081                                            } else if parts.len() == 1 {
9082                                                // Single word like "MONTH" or just "1"
9083                                                let upper = parts[0].to_ascii_uppercase();
9084                                                if matches!(
9085                                                    upper.as_str(),
9086                                                    "YEAR"
9087                                                        | "QUARTER"
9088                                                        | "MONTH"
9089                                                        | "WEEK"
9090                                                        | "DAY"
9091                                                        | "HOUR"
9092                                                        | "MINUTE"
9093                                                        | "SECOND"
9094                                                ) {
9095                                                    Some(upper)
9096                                                } else {
9097                                                    None
9098                                                }
9099                                            } else {
9100                                                None
9101                                            }
9102                                        } else {
9103                                            None
9104                                        }
9105                                    } else {
9106                                        None
9107                                    }
9108                                } else {
9109                                    None
9110                                }
9111                            } else {
9112                                None
9113                            };
9114
9115                            if let Some(unit_str) = unit {
9116                                gda_info = Some((alias, start_expr, end_expr, unit_str));
9117                                gda_join_idx = Some(idx);
9118                            }
9119                        }
9120                    }
9121                }
9122                if gda_info.is_some() {
9123                    break;
9124                }
9125            }
9126
9127            let Some((alias_name, start_expr, end_expr, unit_str)) = gda_info else {
9128                // Also check FROM clause for UNNEST(GENERATE_DATE_ARRAY(...)) patterns
9129                // This handles Generic->Snowflake where GENERATE_DATE_ARRAY is in FROM, not in JOIN
9130                let result = Self::try_transform_from_gda_snowflake(sel);
9131                return result;
9132            };
9133            let join_idx = gda_join_idx.unwrap();
9134
9135            // Build ARRAY_GENERATE_RANGE(0, DATEDIFF(unit, start, end) + 1)
9136            // ARRAY_GENERATE_RANGE uses exclusive end, and we need DATEDIFF + 1 values
9137            // (inclusive date range), so the exclusive end is DATEDIFF + 1.
9138            let datediff = Expression::Function(Box::new(Function::new(
9139                "DATEDIFF".to_string(),
9140                vec![
9141                    Expression::boxed_column(Column {
9142                        name: Identifier::new(&unit_str),
9143                        table: None,
9144                        join_mark: false,
9145                        trailing_comments: vec![],
9146                        span: None,
9147                        inferred_type: None,
9148                    }),
9149                    start_expr.clone(),
9150                    end_expr.clone(),
9151                ],
9152            )));
9153            let datediff_plus_one = Expression::Add(Box::new(BinaryOp {
9154                left: datediff,
9155                right: Expression::Literal(Box::new(Literal::Number("1".to_string()))),
9156                left_comments: vec![],
9157                operator_comments: vec![],
9158                trailing_comments: vec![],
9159                inferred_type: None,
9160            }));
9161
9162            let array_gen_range = Expression::Function(Box::new(Function::new(
9163                "ARRAY_GENERATE_RANGE".to_string(),
9164                vec![
9165                    Expression::Literal(Box::new(Literal::Number("0".to_string()))),
9166                    datediff_plus_one,
9167                ],
9168            )));
9169
9170            // Build FLATTEN(INPUT => ARRAY_GENERATE_RANGE(...))
9171            let flatten_input = Expression::NamedArgument(Box::new(NamedArgument {
9172                name: Identifier::new("INPUT"),
9173                value: array_gen_range,
9174                separator: crate::expressions::NamedArgSeparator::DArrow,
9175            }));
9176            let flatten = Expression::Function(Box::new(Function::new(
9177                "FLATTEN".to_string(),
9178                vec![flatten_input],
9179            )));
9180
9181            // Build LATERAL FLATTEN(...) AS _t0(seq, key, path, index, alias, this)
9182            let alias_table = Alias {
9183                this: flatten,
9184                alias: Identifier::new("_t0"),
9185                column_aliases: vec![
9186                    Identifier::new("seq"),
9187                    Identifier::new("key"),
9188                    Identifier::new("path"),
9189                    Identifier::new("index"),
9190                    Identifier::new(&alias_name),
9191                    Identifier::new("this"),
9192                ],
9193                alias_explicit_as: false,
9194                alias_keyword: None,
9195                pre_alias_comments: vec![],
9196                trailing_comments: vec![],
9197                inferred_type: None,
9198            };
9199            let lateral_expr = Expression::Lateral(Box::new(Lateral {
9200                this: Box::new(Expression::Alias(Box::new(alias_table))),
9201                view: None,
9202                outer: None,
9203                alias: None,
9204                alias_quoted: false,
9205                cross_apply: None,
9206                ordinality: None,
9207                column_aliases: vec![],
9208                column_aliases_quoted: vec![],
9209            }));
9210
9211            // Remove the original join and add to FROM expressions
9212            sel.joins.remove(join_idx);
9213            if let Some(ref mut from) = sel.from {
9214                from.expressions.push(lateral_expr);
9215            }
9216
9217            // Build DATEADD(unit, CAST(alias AS INT), CAST(start AS DATE))
9218            let dateadd_expr = Expression::Function(Box::new(Function::new(
9219                "DATEADD".to_string(),
9220                vec![
9221                    Expression::boxed_column(Column {
9222                        name: Identifier::new(&unit_str),
9223                        table: None,
9224                        join_mark: false,
9225                        trailing_comments: vec![],
9226                        span: None,
9227                        inferred_type: None,
9228                    }),
9229                    Expression::Cast(Box::new(Cast {
9230                        this: Expression::boxed_column(Column {
9231                            name: Identifier::new(&alias_name),
9232                            table: None,
9233                            join_mark: false,
9234                            trailing_comments: vec![],
9235                            span: None,
9236                            inferred_type: None,
9237                        }),
9238                        to: DataType::Int {
9239                            length: None,
9240                            integer_spelling: false,
9241                        },
9242                        trailing_comments: vec![],
9243                        double_colon_syntax: false,
9244                        format: None,
9245                        default: None,
9246                        inferred_type: None,
9247                    })),
9248                    Expression::Cast(Box::new(Cast {
9249                        this: start_expr.clone(),
9250                        to: DataType::Date,
9251                        trailing_comments: vec![],
9252                        double_colon_syntax: false,
9253                        format: None,
9254                        default: None,
9255                        inferred_type: None,
9256                    })),
9257                ],
9258            )));
9259
9260            // Replace references to the alias in the SELECT list
9261            let new_exprs: Vec<Expression> = sel
9262                .expressions
9263                .iter()
9264                .map(|expr| Self::replace_column_ref_with_dateadd(expr, &alias_name, &dateadd_expr))
9265                .collect();
9266            sel.expressions = new_exprs;
9267
9268            Ok(Expression::Select(sel))
9269        })
9270    }
9271
9272    /// Helper: replace column references to `alias_name` with dateadd expression
9273    fn replace_column_ref_with_dateadd(
9274        expr: &Expression,
9275        alias_name: &str,
9276        dateadd: &Expression,
9277    ) -> Expression {
9278        use crate::expressions::*;
9279        match expr {
9280            Expression::Column(c) if c.name.name == alias_name && c.table.is_none() => {
9281                // Plain column reference -> DATEADD(...) AS alias_name
9282                Expression::Alias(Box::new(Alias {
9283                    this: dateadd.clone(),
9284                    alias: Identifier::new(alias_name),
9285                    column_aliases: vec![],
9286                    alias_explicit_as: false,
9287                    alias_keyword: None,
9288                    pre_alias_comments: vec![],
9289                    trailing_comments: vec![],
9290                    inferred_type: None,
9291                }))
9292            }
9293            Expression::Alias(a) => {
9294                // Check if the inner expression references the alias
9295                let new_this = Self::replace_column_ref_inner(&a.this, alias_name, dateadd);
9296                Expression::Alias(Box::new(Alias {
9297                    this: new_this,
9298                    alias: a.alias.clone(),
9299                    column_aliases: a.column_aliases.clone(),
9300                    alias_explicit_as: false,
9301                    alias_keyword: None,
9302                    pre_alias_comments: a.pre_alias_comments.clone(),
9303                    trailing_comments: a.trailing_comments.clone(),
9304                    inferred_type: None,
9305                }))
9306            }
9307            _ => expr.clone(),
9308        }
9309    }
9310
9311    /// Helper: replace column references in inner expression (not top-level)
9312    fn replace_column_ref_inner(
9313        expr: &Expression,
9314        alias_name: &str,
9315        dateadd: &Expression,
9316    ) -> Expression {
9317        use crate::expressions::*;
9318        match expr {
9319            Expression::Column(c) if c.name.name == alias_name && c.table.is_none() => {
9320                dateadd.clone()
9321            }
9322            Expression::Add(op) => {
9323                let left = Self::replace_column_ref_inner(&op.left, alias_name, dateadd);
9324                let right = Self::replace_column_ref_inner(&op.right, alias_name, dateadd);
9325                Expression::Add(Box::new(BinaryOp {
9326                    left,
9327                    right,
9328                    left_comments: op.left_comments.clone(),
9329                    operator_comments: op.operator_comments.clone(),
9330                    trailing_comments: op.trailing_comments.clone(),
9331                    inferred_type: None,
9332                }))
9333            }
9334            Expression::Sub(op) => {
9335                let left = Self::replace_column_ref_inner(&op.left, alias_name, dateadd);
9336                let right = Self::replace_column_ref_inner(&op.right, alias_name, dateadd);
9337                Expression::Sub(Box::new(BinaryOp {
9338                    left,
9339                    right,
9340                    left_comments: op.left_comments.clone(),
9341                    operator_comments: op.operator_comments.clone(),
9342                    trailing_comments: op.trailing_comments.clone(),
9343                    inferred_type: None,
9344                }))
9345            }
9346            Expression::Mul(op) => {
9347                let left = Self::replace_column_ref_inner(&op.left, alias_name, dateadd);
9348                let right = Self::replace_column_ref_inner(&op.right, alias_name, dateadd);
9349                Expression::Mul(Box::new(BinaryOp {
9350                    left,
9351                    right,
9352                    left_comments: op.left_comments.clone(),
9353                    operator_comments: op.operator_comments.clone(),
9354                    trailing_comments: op.trailing_comments.clone(),
9355                    inferred_type: None,
9356                }))
9357            }
9358            _ => expr.clone(),
9359        }
9360    }
9361
9362    /// Handle UNNEST(GENERATE_DATE_ARRAY(...)) in FROM clause for Snowflake target.
9363    /// Converts to a subquery with DATEADD + TABLE(FLATTEN(ARRAY_GENERATE_RANGE(...))).
9364    fn try_transform_from_gda_snowflake(
9365        mut sel: Box<crate::expressions::Select>,
9366    ) -> Result<Expression> {
9367        use crate::expressions::*;
9368
9369        // Extract GDA info from FROM clause
9370        let mut gda_info: Option<(
9371            usize,
9372            String,
9373            Expression,
9374            Expression,
9375            String,
9376            Option<(String, Vec<Identifier>)>,
9377        )> = None; // (from_idx, col_name, start, end, unit, outer_alias)
9378
9379        if let Some(ref from) = sel.from {
9380            for (idx, table_expr) in from.expressions.iter().enumerate() {
9381                // Pattern 1: UNNEST(GENERATE_DATE_ARRAY(...))
9382                // Pattern 2: Alias(UNNEST(GENERATE_DATE_ARRAY(...))) AS _q(date_week)
9383                let (unnest_opt, outer_alias_info) = match table_expr {
9384                    Expression::Unnest(ref unnest) => (Some(unnest.as_ref()), None),
9385                    Expression::Alias(ref a) => {
9386                        if let Expression::Unnest(ref unnest) = a.this {
9387                            let alias_info = (a.alias.name.clone(), a.column_aliases.clone());
9388                            (Some(unnest.as_ref()), Some(alias_info))
9389                        } else {
9390                            (None, None)
9391                        }
9392                    }
9393                    _ => (None, None),
9394                };
9395
9396                if let Some(unnest) = unnest_opt {
9397                    // Check for GENERATE_DATE_ARRAY function
9398                    let func_opt = match &unnest.this {
9399                        Expression::Function(ref f)
9400                            if f.name.eq_ignore_ascii_case("GENERATE_DATE_ARRAY")
9401                                && f.args.len() >= 2 =>
9402                        {
9403                            Some(f)
9404                        }
9405                        // Also check for GenerateSeries (from earlier normalization)
9406                        _ => None,
9407                    };
9408
9409                    if let Some(f) = func_opt {
9410                        let start_expr = f.args[0].clone();
9411                        let end_expr = f.args[1].clone();
9412                        let step = f.args.get(2).cloned();
9413
9414                        // Extract unit and column name
9415                        let unit = Self::extract_interval_unit_str(&step);
9416                        let col_name = outer_alias_info
9417                            .as_ref()
9418                            .and_then(|(_, cols)| cols.first().map(|id| id.name.clone()))
9419                            .unwrap_or_else(|| "value".to_string());
9420
9421                        if let Some(unit_str) = unit {
9422                            gda_info = Some((
9423                                idx,
9424                                col_name,
9425                                start_expr,
9426                                end_expr,
9427                                unit_str,
9428                                outer_alias_info,
9429                            ));
9430                            break;
9431                        }
9432                    }
9433                }
9434            }
9435        }
9436
9437        let Some((from_idx, col_name, start_expr, end_expr, unit_str, outer_alias_info)) = gda_info
9438        else {
9439            return Ok(Expression::Select(sel));
9440        };
9441
9442        // Build the Snowflake subquery:
9443        // (SELECT DATEADD(unit, CAST(col_name AS INT), CAST(start AS DATE)) AS col_name
9444        //  FROM TABLE(FLATTEN(INPUT => ARRAY_GENERATE_RANGE(0, DATEDIFF(unit, start, end) + 1))) AS _t0(seq, key, path, index, col_name, this))
9445
9446        // DATEDIFF(unit, start, end)
9447        let datediff = Expression::Function(Box::new(Function::new(
9448            "DATEDIFF".to_string(),
9449            vec![
9450                Expression::boxed_column(Column {
9451                    name: Identifier::new(&unit_str),
9452                    table: None,
9453                    join_mark: false,
9454                    trailing_comments: vec![],
9455                    span: None,
9456                    inferred_type: None,
9457                }),
9458                start_expr.clone(),
9459                end_expr.clone(),
9460            ],
9461        )));
9462        // DATEDIFF(...) + 1
9463        let datediff_plus_one = Expression::Add(Box::new(BinaryOp {
9464            left: datediff,
9465            right: Expression::Literal(Box::new(Literal::Number("1".to_string()))),
9466            left_comments: vec![],
9467            operator_comments: vec![],
9468            trailing_comments: vec![],
9469            inferred_type: None,
9470        }));
9471
9472        let array_gen_range = Expression::Function(Box::new(Function::new(
9473            "ARRAY_GENERATE_RANGE".to_string(),
9474            vec![
9475                Expression::Literal(Box::new(Literal::Number("0".to_string()))),
9476                datediff_plus_one,
9477            ],
9478        )));
9479
9480        // TABLE(FLATTEN(INPUT => ...))
9481        let flatten_input = Expression::NamedArgument(Box::new(NamedArgument {
9482            name: Identifier::new("INPUT"),
9483            value: array_gen_range,
9484            separator: crate::expressions::NamedArgSeparator::DArrow,
9485        }));
9486        let flatten = Expression::Function(Box::new(Function::new(
9487            "FLATTEN".to_string(),
9488            vec![flatten_input],
9489        )));
9490
9491        // Determine alias name for the table: use outer alias or _t0
9492        let table_alias_name = outer_alias_info
9493            .as_ref()
9494            .map(|(name, _)| name.clone())
9495            .unwrap_or_else(|| "_t0".to_string());
9496
9497        // TABLE(FLATTEN(...)) AS _t0(seq, key, path, index, col_name, this)
9498        let table_func =
9499            Expression::Function(Box::new(Function::new("TABLE".to_string(), vec![flatten])));
9500        let flatten_aliased = Expression::Alias(Box::new(Alias {
9501            this: table_func,
9502            alias: Identifier::new(&table_alias_name),
9503            column_aliases: vec![
9504                Identifier::new("seq"),
9505                Identifier::new("key"),
9506                Identifier::new("path"),
9507                Identifier::new("index"),
9508                Identifier::new(&col_name),
9509                Identifier::new("this"),
9510            ],
9511            alias_explicit_as: false,
9512            alias_keyword: None,
9513            pre_alias_comments: vec![],
9514            trailing_comments: vec![],
9515            inferred_type: None,
9516        }));
9517
9518        // SELECT DATEADD(unit, CAST(col_name AS INT), CAST(start AS DATE)) AS col_name
9519        let dateadd_expr = Expression::Function(Box::new(Function::new(
9520            "DATEADD".to_string(),
9521            vec![
9522                Expression::boxed_column(Column {
9523                    name: Identifier::new(&unit_str),
9524                    table: None,
9525                    join_mark: false,
9526                    trailing_comments: vec![],
9527                    span: None,
9528                    inferred_type: None,
9529                }),
9530                Expression::Cast(Box::new(Cast {
9531                    this: Expression::boxed_column(Column {
9532                        name: Identifier::new(&col_name),
9533                        table: None,
9534                        join_mark: false,
9535                        trailing_comments: vec![],
9536                        span: None,
9537                        inferred_type: None,
9538                    }),
9539                    to: DataType::Int {
9540                        length: None,
9541                        integer_spelling: false,
9542                    },
9543                    trailing_comments: vec![],
9544                    double_colon_syntax: false,
9545                    format: None,
9546                    default: None,
9547                    inferred_type: None,
9548                })),
9549                // Use start_expr directly - it's already been normalized (DATE literal -> CAST)
9550                start_expr.clone(),
9551            ],
9552        )));
9553        let dateadd_aliased = Expression::Alias(Box::new(Alias {
9554            this: dateadd_expr,
9555            alias: Identifier::new(&col_name),
9556            column_aliases: vec![],
9557            alias_explicit_as: false,
9558            alias_keyword: None,
9559            pre_alias_comments: vec![],
9560            trailing_comments: vec![],
9561            inferred_type: None,
9562        }));
9563
9564        // Build inner SELECT
9565        let mut inner_select = Select::new();
9566        inner_select.expressions = vec![dateadd_aliased];
9567        inner_select.from = Some(From {
9568            expressions: vec![flatten_aliased],
9569        });
9570
9571        let inner_select_expr = Expression::Select(Box::new(inner_select));
9572        let subquery = Expression::Subquery(Box::new(Subquery {
9573            this: inner_select_expr,
9574            alias: None,
9575            column_aliases: vec![],
9576            alias_explicit_as: false,
9577            alias_keyword: None,
9578            order_by: None,
9579            limit: None,
9580            offset: None,
9581            distribute_by: None,
9582            sort_by: None,
9583            cluster_by: None,
9584            lateral: false,
9585            modifiers_inside: false,
9586            trailing_comments: vec![],
9587            inferred_type: None,
9588        }));
9589
9590        // If there was an outer alias (e.g., AS _q(date_week)), wrap with alias
9591        let replacement = if let Some((alias_name, col_aliases)) = outer_alias_info {
9592            Expression::Alias(Box::new(Alias {
9593                this: subquery,
9594                alias: Identifier::new(&alias_name),
9595                column_aliases: col_aliases,
9596                alias_explicit_as: false,
9597                alias_keyword: None,
9598                pre_alias_comments: vec![],
9599                trailing_comments: vec![],
9600                inferred_type: None,
9601            }))
9602        } else {
9603            subquery
9604        };
9605
9606        // Replace the FROM expression
9607        if let Some(ref mut from) = sel.from {
9608            from.expressions[from_idx] = replacement;
9609        }
9610
9611        Ok(Expression::Select(sel))
9612    }
9613
9614    /// Convert ARRAY_SIZE(GENERATE_DATE_ARRAY(start, end, step)) for Snowflake.
9615    /// Produces: ARRAY_SIZE((SELECT ARRAY_AGG(*) FROM (SELECT DATEADD(unit, CAST(value AS INT), start) AS value
9616    ///   FROM TABLE(FLATTEN(INPUT => ARRAY_GENERATE_RANGE(0, DATEDIFF(unit, start, end) + 1))) AS _t0(...))))
9617    fn convert_array_size_gda_snowflake(f: &crate::expressions::Function) -> Result<Expression> {
9618        use crate::expressions::*;
9619
9620        let start_expr = f.args[0].clone();
9621        let end_expr = f.args[1].clone();
9622        let step = f.args.get(2).cloned();
9623        let unit_str = Self::extract_interval_unit_str(&step).unwrap_or_else(|| "DAY".to_string());
9624        let col_name = "value";
9625
9626        // Build the inner subquery: same as try_transform_from_gda_snowflake
9627        let datediff = Expression::Function(Box::new(Function::new(
9628            "DATEDIFF".to_string(),
9629            vec![
9630                Expression::boxed_column(Column {
9631                    name: Identifier::new(&unit_str),
9632                    table: None,
9633                    join_mark: false,
9634                    trailing_comments: vec![],
9635                    span: None,
9636                    inferred_type: None,
9637                }),
9638                start_expr.clone(),
9639                end_expr.clone(),
9640            ],
9641        )));
9642        // DATEDIFF(...) + 1
9643        let datediff_plus_one = Expression::Add(Box::new(BinaryOp {
9644            left: datediff,
9645            right: Expression::Literal(Box::new(Literal::Number("1".to_string()))),
9646            left_comments: vec![],
9647            operator_comments: vec![],
9648            trailing_comments: vec![],
9649            inferred_type: None,
9650        }));
9651
9652        let array_gen_range = Expression::Function(Box::new(Function::new(
9653            "ARRAY_GENERATE_RANGE".to_string(),
9654            vec![
9655                Expression::Literal(Box::new(Literal::Number("0".to_string()))),
9656                datediff_plus_one,
9657            ],
9658        )));
9659
9660        let flatten_input = Expression::NamedArgument(Box::new(NamedArgument {
9661            name: Identifier::new("INPUT"),
9662            value: array_gen_range,
9663            separator: crate::expressions::NamedArgSeparator::DArrow,
9664        }));
9665        let flatten = Expression::Function(Box::new(Function::new(
9666            "FLATTEN".to_string(),
9667            vec![flatten_input],
9668        )));
9669
9670        let table_func =
9671            Expression::Function(Box::new(Function::new("TABLE".to_string(), vec![flatten])));
9672        let flatten_aliased = Expression::Alias(Box::new(Alias {
9673            this: table_func,
9674            alias: Identifier::new("_t0"),
9675            column_aliases: vec![
9676                Identifier::new("seq"),
9677                Identifier::new("key"),
9678                Identifier::new("path"),
9679                Identifier::new("index"),
9680                Identifier::new(col_name),
9681                Identifier::new("this"),
9682            ],
9683            alias_explicit_as: false,
9684            alias_keyword: None,
9685            pre_alias_comments: vec![],
9686            trailing_comments: vec![],
9687            inferred_type: None,
9688        }));
9689
9690        let dateadd_expr = Expression::Function(Box::new(Function::new(
9691            "DATEADD".to_string(),
9692            vec![
9693                Expression::boxed_column(Column {
9694                    name: Identifier::new(&unit_str),
9695                    table: None,
9696                    join_mark: false,
9697                    trailing_comments: vec![],
9698                    span: None,
9699                    inferred_type: None,
9700                }),
9701                Expression::Cast(Box::new(Cast {
9702                    this: Expression::boxed_column(Column {
9703                        name: Identifier::new(col_name),
9704                        table: None,
9705                        join_mark: false,
9706                        trailing_comments: vec![],
9707                        span: None,
9708                        inferred_type: None,
9709                    }),
9710                    to: DataType::Int {
9711                        length: None,
9712                        integer_spelling: false,
9713                    },
9714                    trailing_comments: vec![],
9715                    double_colon_syntax: false,
9716                    format: None,
9717                    default: None,
9718                    inferred_type: None,
9719                })),
9720                start_expr.clone(),
9721            ],
9722        )));
9723        let dateadd_aliased = Expression::Alias(Box::new(Alias {
9724            this: dateadd_expr,
9725            alias: Identifier::new(col_name),
9726            column_aliases: vec![],
9727            alias_explicit_as: false,
9728            alias_keyword: None,
9729            pre_alias_comments: vec![],
9730            trailing_comments: vec![],
9731            inferred_type: None,
9732        }));
9733
9734        // Inner SELECT: SELECT DATEADD(...) AS value FROM TABLE(FLATTEN(...)) AS _t0(...)
9735        let mut inner_select = Select::new();
9736        inner_select.expressions = vec![dateadd_aliased];
9737        inner_select.from = Some(From {
9738            expressions: vec![flatten_aliased],
9739        });
9740
9741        // Wrap in subquery for the inner part
9742        let inner_subquery = Expression::Subquery(Box::new(Subquery {
9743            this: Expression::Select(Box::new(inner_select)),
9744            alias: None,
9745            column_aliases: vec![],
9746            alias_explicit_as: false,
9747            alias_keyword: None,
9748            order_by: None,
9749            limit: None,
9750            offset: None,
9751            distribute_by: None,
9752            sort_by: None,
9753            cluster_by: None,
9754            lateral: false,
9755            modifiers_inside: false,
9756            trailing_comments: vec![],
9757            inferred_type: None,
9758        }));
9759
9760        // Outer: SELECT ARRAY_AGG(*) FROM (inner_subquery)
9761        let star = Expression::Star(Star {
9762            table: None,
9763            except: None,
9764            replace: None,
9765            rename: None,
9766            trailing_comments: vec![],
9767            span: None,
9768        });
9769        let array_agg = Expression::ArrayAgg(Box::new(AggFunc {
9770            this: star,
9771            distinct: false,
9772            filter: None,
9773            order_by: vec![],
9774            name: Some("ARRAY_AGG".to_string()),
9775            ignore_nulls: None,
9776            having_max: None,
9777            limit: None,
9778            inferred_type: None,
9779        }));
9780
9781        let mut outer_select = Select::new();
9782        outer_select.expressions = vec![array_agg];
9783        outer_select.from = Some(From {
9784            expressions: vec![inner_subquery],
9785        });
9786
9787        // Wrap in a subquery
9788        let outer_subquery = Expression::Subquery(Box::new(Subquery {
9789            this: Expression::Select(Box::new(outer_select)),
9790            alias: None,
9791            column_aliases: vec![],
9792            alias_explicit_as: false,
9793            alias_keyword: None,
9794            order_by: None,
9795            limit: None,
9796            offset: None,
9797            distribute_by: None,
9798            sort_by: None,
9799            cluster_by: None,
9800            lateral: false,
9801            modifiers_inside: false,
9802            trailing_comments: vec![],
9803            inferred_type: None,
9804        }));
9805
9806        // ARRAY_SIZE(subquery)
9807        Ok(Expression::ArraySize(Box::new(UnaryFunc::new(
9808            outer_subquery,
9809        ))))
9810    }
9811
9812    /// Extract interval unit string from an optional step expression.
9813    fn extract_interval_unit_str(step: &Option<Expression>) -> Option<String> {
9814        use crate::expressions::*;
9815        if let Some(Expression::Interval(ref iv)) = step {
9816            if let Some(IntervalUnitSpec::Simple { ref unit, .. }) = iv.unit {
9817                return Some(format!("{:?}", unit).to_ascii_uppercase());
9818            }
9819            if let Some(ref this) = iv.this {
9820                if let Expression::Literal(lit) = this {
9821                    if let Literal::String(ref s) = lit.as_ref() {
9822                        let parts: Vec<&str> = s.split_whitespace().collect();
9823                        if parts.len() == 2 {
9824                            return Some(parts[1].to_ascii_uppercase());
9825                        } else if parts.len() == 1 {
9826                            let upper = parts[0].to_ascii_uppercase();
9827                            if matches!(
9828                                upper.as_str(),
9829                                "YEAR"
9830                                    | "QUARTER"
9831                                    | "MONTH"
9832                                    | "WEEK"
9833                                    | "DAY"
9834                                    | "HOUR"
9835                                    | "MINUTE"
9836                                    | "SECOND"
9837                            ) {
9838                                return Some(upper);
9839                            }
9840                        }
9841                    }
9842                }
9843            }
9844        }
9845        // Default to DAY if no step or no interval
9846        if step.is_none() {
9847            return Some("DAY".to_string());
9848        }
9849        None
9850    }
9851
9852    fn normalize_snowflake_pretty(mut sql: String) -> String {
9853        if sql.contains("LATERAL IFF(_u.pos = _u_2.pos_2, _u_2.entity, NULL) AS datasource(SEQ, KEY, PATH, INDEX, VALUE, THIS)")
9854            && sql.contains("ARRAY_GENERATE_RANGE(0, (GREATEST(ARRAY_SIZE(INPUT => PARSE_JSON(flags))) - 1) + 1)")
9855        {
9856            sql = sql.replace(
9857                "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')",
9858                "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    )",
9859            );
9860
9861            sql = sql.replace(
9862                "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)",
9863                "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)",
9864            );
9865
9866            sql = sql.replace(
9867                "OR (_u.pos > (ARRAY_SIZE(INPUT => PARSE_JSON(flags)) - 1)\n  AND _u_2.pos_2 = (ARRAY_SIZE(INPUT => PARSE_JSON(flags)) - 1))",
9868                "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  )",
9869            );
9870        }
9871
9872        sql
9873    }
9874
9875    #[cfg(feature = "transpile")]
9876    fn wrap_tsql_top_level_values(expr: Expression) -> Expression {
9877        match expr {
9878            Expression::Values(values) => Self::tsql_values_as_select(*values),
9879            Expression::Union(mut union) => {
9880                let left = std::mem::replace(&mut union.left, Expression::Null(Null));
9881                let right = std::mem::replace(&mut union.right, Expression::Null(Null));
9882                union.left = Self::wrap_tsql_values_set_operand(left);
9883                union.right = Self::wrap_tsql_values_set_operand(right);
9884                Expression::Union(union)
9885            }
9886            Expression::Intersect(mut intersect) => {
9887                let left = std::mem::replace(&mut intersect.left, Expression::Null(Null));
9888                let right = std::mem::replace(&mut intersect.right, Expression::Null(Null));
9889                intersect.left = Self::wrap_tsql_values_set_operand(left);
9890                intersect.right = Self::wrap_tsql_values_set_operand(right);
9891                Expression::Intersect(intersect)
9892            }
9893            Expression::Except(mut except) => {
9894                let left = std::mem::replace(&mut except.left, Expression::Null(Null));
9895                let right = std::mem::replace(&mut except.right, Expression::Null(Null));
9896                except.left = Self::wrap_tsql_values_set_operand(left);
9897                except.right = Self::wrap_tsql_values_set_operand(right);
9898                Expression::Except(except)
9899            }
9900            other => other,
9901        }
9902    }
9903
9904    #[cfg(feature = "transpile")]
9905    fn wrap_tsql_values_set_operand(expr: Expression) -> Expression {
9906        match expr {
9907            Expression::Values(values) => Self::tsql_values_as_select(*values),
9908            Expression::Select(mut select)
9909                if Self::is_parser_wrapped_values_set_operand(&select) =>
9910            {
9911                let mut from = select.from.take().expect("checked as present");
9912                let Expression::Values(values) = from
9913                    .expressions
9914                    .pop()
9915                    .expect("checked as a single VALUES source")
9916                else {
9917                    unreachable!("checked as a VALUES source");
9918                };
9919                Self::tsql_values_as_select(*values)
9920            }
9921            Expression::Annotated(mut annotated) => {
9922                annotated.this = Self::wrap_tsql_values_set_operand(annotated.this);
9923                Expression::Annotated(annotated)
9924            }
9925            Expression::Union(mut union) => {
9926                let left = std::mem::replace(&mut union.left, Expression::Null(Null));
9927                let right = std::mem::replace(&mut union.right, Expression::Null(Null));
9928                union.left = Self::wrap_tsql_values_set_operand(left);
9929                union.right = Self::wrap_tsql_values_set_operand(right);
9930                Expression::Union(union)
9931            }
9932            Expression::Intersect(mut intersect) => {
9933                let left = std::mem::replace(&mut intersect.left, Expression::Null(Null));
9934                let right = std::mem::replace(&mut intersect.right, Expression::Null(Null));
9935                intersect.left = Self::wrap_tsql_values_set_operand(left);
9936                intersect.right = Self::wrap_tsql_values_set_operand(right);
9937                Expression::Intersect(intersect)
9938            }
9939            Expression::Except(mut except) => {
9940                let left = std::mem::replace(&mut except.left, Expression::Null(Null));
9941                let right = std::mem::replace(&mut except.right, Expression::Null(Null));
9942                except.left = Self::wrap_tsql_values_set_operand(left);
9943                except.right = Self::wrap_tsql_values_set_operand(right);
9944                Expression::Except(except)
9945            }
9946            other => other,
9947        }
9948    }
9949
9950    #[cfg(feature = "transpile")]
9951    fn is_parser_wrapped_values_set_operand(select: &Select) -> bool {
9952        let Some(from) = &select.from else {
9953            return false;
9954        };
9955        let [Expression::Values(values)] = from.expressions.as_slice() else {
9956            return false;
9957        };
9958        if !values
9959            .alias
9960            .as_ref()
9961            .is_some_and(|alias| alias.name == "_values")
9962        {
9963            return false;
9964        }
9965
9966        let mut parser_wrapper = Select::new();
9967        parser_wrapper.expressions = vec![Expression::star()];
9968        parser_wrapper.from = Some(from.clone());
9969        select == &parser_wrapper
9970    }
9971
9972    #[cfg(feature = "transpile")]
9973    fn tsql_values_as_select(mut values: crate::expressions::Values) -> Expression {
9974        let column_aliases = if values.column_aliases.is_empty() {
9975            let column_count = values
9976                .expressions
9977                .first()
9978                .map(|row| row.expressions.len())
9979                .unwrap_or(0);
9980            (1..=column_count)
9981                .map(|index| Identifier::new(format!("column{index}")))
9982                .collect()
9983        } else {
9984            std::mem::take(&mut values.column_aliases)
9985        };
9986
9987        values.alias = None;
9988
9989        let values_subquery = Expression::Subquery(Box::new(crate::expressions::Subquery {
9990            this: Expression::Values(Box::new(values)),
9991            alias: Some(Identifier::new("_v")),
9992            column_aliases,
9993            alias_explicit_as: false,
9994            alias_keyword: None,
9995            order_by: None,
9996            limit: None,
9997            offset: None,
9998            distribute_by: None,
9999            sort_by: None,
10000            cluster_by: None,
10001            lateral: false,
10002            modifiers_inside: false,
10003            trailing_comments: Vec::new(),
10004            inferred_type: None,
10005        }));
10006
10007        let mut select = crate::expressions::Select::new();
10008        select.expressions = vec![Expression::star()];
10009        select.from = Some(From {
10010            expressions: vec![values_subquery],
10011        });
10012
10013        Expression::Select(Box::new(select))
10014    }
10015
10016    fn extract_interval_parts(
10017        interval_expr: &Expression,
10018    ) -> Option<(Expression, crate::expressions::IntervalUnit)> {
10019        use crate::expressions::{DataType, IntervalUnit, IntervalUnitSpec, Literal};
10020
10021        fn unit_from_str(unit: &str) -> Option<IntervalUnit> {
10022            match unit.trim().to_ascii_uppercase().as_str() {
10023                "YEAR" | "YEARS" | "Y" | "YR" | "YRS" | "YY" | "YYYY" => Some(IntervalUnit::Year),
10024                "QUARTER" | "QUARTERS" | "Q" | "QTR" | "QTRS" | "QQ" => Some(IntervalUnit::Quarter),
10025                "MONTH" | "MONTHS" | "MON" | "MONS" | "MM" => Some(IntervalUnit::Month),
10026                "WEEK" | "WEEKS" | "W" | "WK" | "WKS" | "WW" | "ISOWEEK" => {
10027                    Some(IntervalUnit::Week)
10028                }
10029                "DAY" | "DAYS" | "D" | "DD" => Some(IntervalUnit::Day),
10030                "HOUR" | "HOURS" | "H" | "HH" | "HR" | "HRS" => Some(IntervalUnit::Hour),
10031                "MINUTE" | "MINUTES" | "MI" | "MIN" | "MINS" | "N" => Some(IntervalUnit::Minute),
10032                "SECOND" | "SECONDS" | "S" | "SEC" | "SECS" | "SS" => Some(IntervalUnit::Second),
10033                "MILLISECOND" | "MILLISECONDS" | "MS" | "MSEC" | "MSECS" | "MSECOND"
10034                | "MSECONDS" | "MILLISEC" | "MILLISECS" | "MILLISECON" => {
10035                    Some(IntervalUnit::Millisecond)
10036                }
10037                "MICROSECOND" | "MICROSECONDS" | "US" | "USEC" | "USECS" | "USECOND"
10038                | "USECONDS" | "MICROSEC" | "MICROSECS" | "MCS" => Some(IntervalUnit::Microsecond),
10039                "NANOSECOND" | "NANOSECONDS" | "NS" | "NSEC" | "NSECS" | "NSECOND" | "NSECONDS"
10040                | "NANOSEC" | "NANOSECS" => Some(IntervalUnit::Nanosecond),
10041                _ => None,
10042            }
10043        }
10044
10045        fn parts_from_literal_string(s: &str) -> Option<(Expression, IntervalUnit)> {
10046            let mut parts = s.split_whitespace();
10047            let value = parts.next()?;
10048            let unit = unit_from_str(parts.next()?)?;
10049            Some((
10050                Expression::Literal(Box::new(Literal::String(value.to_string()))),
10051                unit,
10052            ))
10053        }
10054
10055        fn unit_from_spec(unit: &IntervalUnitSpec) -> Option<IntervalUnit> {
10056            match unit {
10057                IntervalUnitSpec::Simple { unit, .. } => Some(*unit),
10058                IntervalUnitSpec::Expr(expr) => match expr.as_ref() {
10059                    Expression::Day(_) => Some(IntervalUnit::Day),
10060                    Expression::Month(_) => Some(IntervalUnit::Month),
10061                    Expression::Year(_) => Some(IntervalUnit::Year),
10062                    Expression::Identifier(id) => unit_from_str(&id.name),
10063                    Expression::Var(v) => unit_from_str(&v.this),
10064                    Expression::Column(col) => unit_from_str(&col.name.name),
10065                    _ => None,
10066                },
10067                _ => None,
10068            }
10069        }
10070
10071        match interval_expr {
10072            Expression::Interval(iv) => {
10073                let val = iv.this.clone().unwrap_or(Expression::number(0));
10074                if let Expression::Literal(lit) = &val {
10075                    if let Literal::String(s) = lit.as_ref() {
10076                        if let Some(parts) = parts_from_literal_string(s) {
10077                            return Some(parts);
10078                        }
10079                    }
10080                }
10081                let unit = iv
10082                    .unit
10083                    .as_ref()
10084                    .and_then(unit_from_spec)
10085                    .unwrap_or(IntervalUnit::Day);
10086                Some((val, unit))
10087            }
10088            Expression::Cast(cast) if matches!(cast.to, DataType::Interval { .. }) => {
10089                if let Expression::Literal(lit) = &cast.this {
10090                    if let Literal::String(s) = lit.as_ref() {
10091                        if let Some(parts) = parts_from_literal_string(s) {
10092                            return Some(parts);
10093                        }
10094                    }
10095                }
10096                let unit = match &cast.to {
10097                    DataType::Interval {
10098                        unit: Some(unit), ..
10099                    } => unit_from_str(unit).unwrap_or(IntervalUnit::Day),
10100                    _ => IntervalUnit::Day,
10101                };
10102                Some((cast.this.clone(), unit))
10103            }
10104            _ => None,
10105        }
10106    }
10107
10108    fn data_type_is_interval(dt: &DataType) -> bool {
10109        match dt {
10110            DataType::Interval { .. } => true,
10111            DataType::Custom { name } => name.trim().eq_ignore_ascii_case("INTERVAL"),
10112            _ => false,
10113        }
10114    }
10115
10116    fn node_is_interval_cast(node: &Expression) -> bool {
10117        match node {
10118            Expression::Cast(c) | Expression::TryCast(c) | Expression::SafeCast(c) => {
10119                Self::data_type_is_interval(&c.to)
10120            }
10121            _ => false,
10122        }
10123    }
10124
10125    fn reject_tsql_interval_casts(
10126        expr: &Expression,
10127        target: DialectType,
10128        opts: &TranspileOptions,
10129    ) -> Result<()> {
10130        if !matches!(
10131            opts.unsupported_level,
10132            UnsupportedLevel::Raise | UnsupportedLevel::Immediate
10133        ) {
10134            return Ok(());
10135        }
10136
10137        if expr.dfs().any(Self::node_is_interval_cast) {
10138            return Err(crate::error::Error::unsupported(
10139                "INTERVAL casts",
10140                target.to_string(),
10141            ));
10142        }
10143
10144        Ok(())
10145    }
10146
10147    fn tsql_varchar_max_type() -> DataType {
10148        DataType::Custom {
10149            name: "VARCHAR(MAX)".to_string(),
10150        }
10151    }
10152
10153    fn rewrite_tsql_interval_casts_to_varchar(expr: Expression) -> Result<Expression> {
10154        transform_recursive(expr, &|e| match e {
10155            Expression::Cast(mut cast) if Self::data_type_is_interval(&cast.to) => {
10156                cast.to = Self::tsql_varchar_max_type();
10157                cast.double_colon_syntax = false;
10158                Ok(Expression::Cast(cast))
10159            }
10160            Expression::TryCast(mut cast) if Self::data_type_is_interval(&cast.to) => {
10161                cast.to = Self::tsql_varchar_max_type();
10162                cast.double_colon_syntax = false;
10163                Ok(Expression::TryCast(cast))
10164            }
10165            Expression::SafeCast(mut cast) if Self::data_type_is_interval(&cast.to) => {
10166                cast.to = Self::tsql_varchar_max_type();
10167                cast.double_colon_syntax = false;
10168                Ok(Expression::SafeCast(cast))
10169            }
10170            _ => Ok(e),
10171        })
10172    }
10173
10174    fn rewrite_tsql_interval_arithmetic_legacy(
10175        expr: &Expression,
10176        source: DialectType,
10177    ) -> Option<Expression> {
10178        match expr {
10179            Expression::Add(op) => {
10180                if Self::extract_interval_parts(&op.right).is_some() {
10181                    return Some(Self::build_tsql_dateadd_from_interval(
10182                        op.left.clone(),
10183                        &op.right,
10184                        false,
10185                    ));
10186                }
10187
10188                if Self::is_postgres_family_source(source) {
10189                    if Self::is_explicit_date_expr(&op.left)
10190                        && Self::is_integer_day_offset_expr(&op.right)
10191                    {
10192                        return Some(Self::build_tsql_dateadd_days(
10193                            op.left.clone(),
10194                            op.right.clone(),
10195                            false,
10196                        ));
10197                    }
10198
10199                    if Self::is_integer_day_offset_expr(&op.left)
10200                        && Self::is_explicit_date_expr(&op.right)
10201                    {
10202                        return Some(Self::build_tsql_dateadd_days(
10203                            op.right.clone(),
10204                            op.left.clone(),
10205                            false,
10206                        ));
10207                    }
10208                }
10209
10210                None
10211            }
10212            Expression::Sub(op) => {
10213                if Self::extract_interval_parts(&op.right).is_some() {
10214                    return Some(Self::build_tsql_dateadd_from_interval(
10215                        op.left.clone(),
10216                        &op.right,
10217                        true,
10218                    ));
10219                }
10220
10221                if Self::is_postgres_family_source(source) {
10222                    if Self::is_explicit_date_expr(&op.left)
10223                        && Self::is_explicit_date_expr(&op.right)
10224                    {
10225                        return Some(Self::build_tsql_datediff_days(
10226                            op.right.clone(),
10227                            op.left.clone(),
10228                        ));
10229                    }
10230
10231                    if Self::is_explicit_date_expr(&op.left)
10232                        && Self::is_integer_day_offset_expr(&op.right)
10233                    {
10234                        return Some(Self::build_tsql_dateadd_days(
10235                            op.left.clone(),
10236                            op.right.clone(),
10237                            true,
10238                        ));
10239                    }
10240                }
10241
10242                None
10243            }
10244            _ => None,
10245        }
10246    }
10247
10248    fn is_postgres_family_source(source: DialectType) -> bool {
10249        matches!(
10250            source,
10251            DialectType::PostgreSQL
10252                | DialectType::Redshift
10253                | DialectType::Materialize
10254                | DialectType::RisingWave
10255                | DialectType::CockroachDB
10256        )
10257    }
10258
10259    fn is_explicit_date_expr(expr: &Expression) -> bool {
10260        use crate::expressions::Literal;
10261
10262        match expr {
10263            Expression::Literal(lit) => matches!(lit.as_ref(), Literal::Date(_)),
10264            Expression::Cast(c) | Expression::TryCast(c) | Expression::SafeCast(c) => {
10265                matches!(c.to, crate::expressions::DataType::Date)
10266            }
10267            Expression::Paren(p) => Self::is_explicit_date_expr(&p.this),
10268            Expression::CurrentDate(_)
10269            | Expression::Date(_)
10270            | Expression::MakeDate(_)
10271            | Expression::ToDate(_)
10272            | Expression::DateStrToDate(_) => true,
10273            _ => false,
10274        }
10275    }
10276
10277    fn is_integer_day_offset_expr(expr: &Expression) -> bool {
10278        use crate::expressions::Literal;
10279
10280        match expr {
10281            Expression::Literal(lit) => match lit.as_ref() {
10282                Literal::Number(n) => n.parse::<i64>().is_ok(),
10283                _ => false,
10284            },
10285            Expression::Parameter(_) | Expression::Placeholder(_) => true,
10286            Expression::Neg(op) => Self::is_integer_day_offset_expr(&op.this),
10287            Expression::Paren(p) => Self::is_integer_day_offset_expr(&p.this),
10288            _ => false,
10289        }
10290    }
10291
10292    fn build_tsql_datediff_days(start: Expression, end: Expression) -> Expression {
10293        Expression::Function(Box::new(Function::new(
10294            "DATEDIFF".to_string(),
10295            vec![Expression::Identifier(Identifier::new("DAY")), start, end],
10296        )))
10297    }
10298
10299    fn build_tsql_dateadd_days(date: Expression, amount: Expression, subtract: bool) -> Expression {
10300        Expression::Function(Box::new(Function::new(
10301            "DATEADD".to_string(),
10302            vec![
10303                Expression::Identifier(Identifier::new("DAY")),
10304                Self::tsql_dateadd_amount(amount, subtract),
10305                date,
10306            ],
10307        )))
10308    }
10309
10310    fn build_tsql_dateadd_from_interval(
10311        date: Expression,
10312        interval: &Expression,
10313        subtract: bool,
10314    ) -> Expression {
10315        let (value, unit) = Self::extract_interval_parts(interval)
10316            .unwrap_or_else(|| (interval.clone(), crate::expressions::IntervalUnit::Day));
10317        let unit = normalization::temporal::interval_unit_to_string(&unit);
10318        let amount = Self::tsql_dateadd_amount(value, subtract);
10319
10320        Expression::Function(Box::new(Function::new(
10321            "DATEADD".to_string(),
10322            vec![Expression::Identifier(Identifier::new(unit)), amount, date],
10323        )))
10324    }
10325
10326    fn tsql_dateadd_amount(value: Expression, negate: bool) -> Expression {
10327        use crate::expressions::{Parameter, ParameterStyle, UnaryOp};
10328
10329        fn numeric_literal_value(value: &Expression) -> Option<&str> {
10330            match value {
10331                Expression::Literal(lit) => match lit.as_ref() {
10332                    crate::expressions::Literal::Number(n)
10333                    | crate::expressions::Literal::String(n) => Some(n.as_str()),
10334                    _ => None,
10335                },
10336                _ => None,
10337            }
10338        }
10339
10340        fn colon_parameter(value: &Expression) -> Option<Expression> {
10341            let Expression::Literal(lit) = value else {
10342                return None;
10343            };
10344            let crate::expressions::Literal::String(s) = lit.as_ref() else {
10345                return None;
10346            };
10347            let name = s.strip_prefix(':')?;
10348            if name.is_empty()
10349                || !name
10350                    .chars()
10351                    .all(|ch| ch.is_ascii_alphanumeric() || ch == '_')
10352            {
10353                return None;
10354            }
10355
10356            Some(Expression::Parameter(Box::new(Parameter {
10357                name: if name.chars().all(|ch| ch.is_ascii_digit()) {
10358                    None
10359                } else {
10360                    Some(name.to_string())
10361                },
10362                index: name.parse::<u32>().ok(),
10363                style: ParameterStyle::Colon,
10364                quoted: false,
10365                string_quoted: false,
10366                expression: None,
10367            })))
10368        }
10369
10370        let value = colon_parameter(&value).unwrap_or(value);
10371
10372        if let Some(n) = numeric_literal_value(&value) {
10373            if let Ok(parsed) = n.parse::<f64>() {
10374                let normalized = if negate { -parsed } else { parsed };
10375                let rendered = if normalized.fract() == 0.0 {
10376                    format!("{}", normalized as i64)
10377                } else {
10378                    normalized.to_string()
10379                };
10380                return Expression::Literal(Box::new(crate::expressions::Literal::Number(
10381                    rendered,
10382                )));
10383            }
10384        }
10385
10386        if !negate {
10387            return value;
10388        }
10389
10390        match value {
10391            Expression::Neg(op) => op.this,
10392            other => Expression::Neg(Box::new(UnaryOp {
10393                this: other,
10394                inferred_type: None,
10395            })),
10396        }
10397    }
10398
10399    /// Internal TO_DATE function that won't be converted to CAST by the Snowflake handler.
10400    /// Uses the name `_POLYGLOT_TO_DATE` which is not recognized by the TO_DATE -> CAST logic.
10401    /// The Snowflake DATEDIFF handler converts these back to TO_DATE.
10402    const PRESERVED_TO_DATE: &'static str = "_POLYGLOT_TO_DATE";
10403}
10404
10405#[cfg(test)]
10406mod tests {
10407    use super::*;
10408
10409    #[cfg(all(
10410        feature = "transpile",
10411        feature = "dialect-hana",
10412        feature = "dialect-duckdb",
10413        feature = "dialect-trino"
10414    ))]
10415    #[test]
10416    fn hana_validation_and_generation_agree_after_ast_serialization() {
10417        let hana = Dialect::get(DialectType::HANA);
10418        for (sql, duckdb, trino) in [
10419            ("SELECT COALESCE(IFNULL(x, 0), 1) FROM t", true, true),
10420            ("SELECT LOCATE('abcabc', 'bc', 2)", true, true),
10421            ("SELECT LOCATE('abcabc', 'bc', 1, 2)", false, true),
10422            ("SELECT SUBSTRING('abc', -1, -2)", true, true),
10423            ("SELECT TO_DECIMAL(12.345, 5, 2)", true, true),
10424            (
10425                "SELECT CAST(12.345 AS DECIMAL(5)), CAST(-12.9 AS INT)",
10426                true,
10427                true,
10428            ),
10429            ("SELECT TO_DATE('2024-01-15', 'YYYY-MM-DD')", true, true),
10430            (
10431                "SELECT TO_TIMESTAMP('2024-01-15', 'YYYY-MM-DD')",
10432                false,
10433                true,
10434            ),
10435            (
10436                "SELECT TO_VARCHAR(DATE '2024-01-15', 'YYYY-MM-DD')",
10437                true,
10438                true,
10439            ),
10440            ("SELECT CURRENT_UTCDATE", true, true),
10441            (
10442                "SELECT CURRENT_UTCTIMESTAMP(7), CURRENT_UTCTIME",
10443                false,
10444                true,
10445            ),
10446            ("CREATE TABLE t (n INT, xs INT ARRAY)", true, true),
10447            ("CREATE TABLE t (n SMALLDECIMAL ARRAY)", false, false),
10448            ("ALTER TABLE t ADD (n SMALLDECIMAL ARRAY)", false, false),
10449            ("ALTER TABLE t ADD (n INT ARRAY)", true, true),
10450            ("SELECT COALESCE(ADD_DAYS(d, 1), d) FROM t", false, false),
10451            ("SELECT COALESCE(CAST(x AS INT), 0) FROM t", false, false),
10452            (
10453                "SELECT COALESCE(JSON_VALUE(j, '$.a'), 'x') FROM t",
10454                false,
10455                false,
10456            ),
10457            (
10458                "SELECT COALESCE(SUBSTR_REGEXPR('a' IN s), 'x') FROM t",
10459                false,
10460                false,
10461            ),
10462            (
10463                "SELECT COALESCE(STRING_AGG(x, ','), '') FROM t",
10464                false,
10465                false,
10466            ),
10467            (
10468                "CREATE TABLE t (n INT DEFAULT ADD_DAYS(d, 1))",
10469                false,
10470                false,
10471            ),
10472            ("SELECT * FROM t FOR JSON", false, false),
10473            ("CREATE COLUMN TABLE t (n INT)", false, false),
10474            ("SELECT * FROM t WITH HINT (NO_INLINE)", false, false),
10475        ] {
10476            let ast = hana.parse(sql).unwrap().remove(0);
10477            let json = serde_json::to_string(&ast).unwrap();
10478            let ast: Expression = serde_json::from_str(&json).unwrap();
10479            for (target, supported) in [(DialectType::DuckDB, duckdb), (DialectType::Trino, trino)]
10480            {
10481                assert_eq!(
10482                    Dialect::reject_hana_source_semantics(&ast, DialectType::HANA, target).is_ok(),
10483                    supported,
10484                    "validation: {sql} -> {target}"
10485                );
10486                assert_eq!(
10487                    Dialect::get(target).generate(&ast).is_ok(),
10488                    supported,
10489                    "generation: {sql} -> {target}"
10490                );
10491            }
10492            assert!(Dialect::reject_hana_source_semantics(
10493                &ast,
10494                DialectType::HANA,
10495                DialectType::HANA
10496            )
10497            .is_ok());
10498        }
10499    }
10500
10501    #[cfg(all(
10502        feature = "transpile",
10503        feature = "dialect-hana",
10504        feature = "dialect-duckdb"
10505    ))]
10506    #[test]
10507    fn hana_rejects_source_semantics_before_preprocessing_can_erase_them() {
10508        use std::sync::atomic::{AtomicBool, Ordering};
10509        let called = Arc::new(AtomicBool::new(false));
10510        let seen = Arc::clone(&called);
10511        let mut hana = Dialect::get(DialectType::HANA);
10512        hana.custom_preprocess = Some(Box::new(move |_| {
10513            seen.store(true, Ordering::Relaxed);
10514            Ok(Expression::number(1))
10515        }));
10516        for level in [
10517            UnsupportedLevel::Ignore,
10518            UnsupportedLevel::Warn,
10519            UnsupportedLevel::Raise,
10520            UnsupportedLevel::Immediate,
10521        ] {
10522            for sql in [
10523                "SELECT COALESCE(ADD_DAYS(d, 1), d) FROM t",
10524                "SELECT COALESCE(CAST(x AS INT), 0) FROM t",
10525                "SELECT COALESCE(JSON_VALUE(j, '$.a'), 'x') FROM t",
10526                "CREATE TABLE t (x SMALLDECIMAL ARRAY)",
10527                "ALTER TABLE t ADD (x SMALLDECIMAL ARRAY)",
10528                "SELECT CAST(xs AS SMALLDECIMAL ARRAY) FROM t",
10529                "CREATE FUNCTION f(x SMALLDECIMAL ARRAY) RETURNS INT AS 1",
10530                "SELECT * FROM t FOR JSON",
10531                "SELECT * FROM t WITH HINT (NO_INLINE)",
10532            ] {
10533                assert!(
10534                    hana.transpile_with(
10535                        sql,
10536                        DialectType::DuckDB,
10537                        TranspileOptions::default().with_unsupported_level(level)
10538                    )
10539                    .is_err(),
10540                    "{sql}"
10541                );
10542                assert!(
10543                    !called.load(Ordering::Relaxed),
10544                    "preprocessing must not erase unsupported input: {sql}"
10545                );
10546            }
10547        }
10548        let mut target = Dialect::get(DialectType::DuckDB);
10549        Arc::make_mut(&mut target.generator_config).dialect = Some(DialectType::Generic);
10550        assert!(hana
10551            .transpile("SELECT LOCATE('abc', 'b')", &target)
10552            .is_err());
10553        assert!(!called.load(Ordering::Relaxed));
10554        assert!(hana
10555            .transpile("SELECT COALESCE(x, 0) FROM t", DialectType::DuckDB)
10556            .is_ok());
10557        assert!(called.load(Ordering::Relaxed));
10558    }
10559
10560    #[test]
10561    fn built_in_dialect_instances_share_tokenizer_config() {
10562        let first = Dialect::get(DialectType::PostgreSQL);
10563        let second = Dialect::get(DialectType::PostgreSQL);
10564
10565        assert!(first.tokenizer.shares_config_with(&second.tokenizer));
10566    }
10567
10568    #[test]
10569    fn test_dialect_type_from_str() {
10570        assert_eq!(
10571            "postgres".parse::<DialectType>().unwrap(),
10572            DialectType::PostgreSQL
10573        );
10574        assert_eq!(
10575            "postgresql".parse::<DialectType>().unwrap(),
10576            DialectType::PostgreSQL
10577        );
10578        assert_eq!("mysql".parse::<DialectType>().unwrap(), DialectType::MySQL);
10579        assert_eq!(
10580            "bigquery".parse::<DialectType>().unwrap(),
10581            DialectType::BigQuery
10582        );
10583    }
10584
10585    #[test]
10586    fn test_basic_transpile() {
10587        let dialect = Dialect::get(DialectType::Generic);
10588        let result = dialect
10589            .transpile("SELECT 1", DialectType::PostgreSQL)
10590            .unwrap();
10591        assert_eq!(result.len(), 1);
10592        assert_eq!(result[0], "SELECT 1");
10593    }
10594
10595    #[test]
10596    fn test_sqlite_double_quoted_column_defaults_to_postgres_strings() {
10597        let sqlite = Dialect::get(DialectType::SQLite);
10598        let result = sqlite
10599            .transpile(
10600                r#"CREATE TABLE "_collections" (
10601                    "type" TEXT DEFAULT "base" NOT NULL,
10602                    "fields" JSON DEFAULT "[]" NOT NULL,
10603                    "options" JSON DEFAULT "{}" NOT NULL
10604                )"#,
10605                DialectType::PostgreSQL,
10606            )
10607            .unwrap();
10608
10609        assert!(result[0].contains(r#""type" TEXT DEFAULT 'base' NOT NULL"#));
10610        assert!(result[0].contains(r#""fields" JSON DEFAULT '[]' NOT NULL"#));
10611        assert!(result[0].contains(r#""options" JSON DEFAULT '{}' NOT NULL"#));
10612    }
10613
10614    #[test]
10615    fn test_sqlite_identity_preserves_double_quoted_column_defaults() {
10616        let sqlite = Dialect::get(DialectType::SQLite);
10617        let result = sqlite
10618            .transpile(
10619                r#"CREATE TABLE "_collections" ("type" TEXT DEFAULT "base" NOT NULL)"#,
10620                DialectType::SQLite,
10621            )
10622            .unwrap();
10623
10624        assert_eq!(
10625            result[0],
10626            r#"CREATE TABLE "_collections" ("type" TEXT DEFAULT "base" NOT NULL)"#
10627        );
10628    }
10629
10630    #[test]
10631    fn test_function_transformation_mysql() {
10632        // NVL should be transformed to IFNULL in MySQL
10633        let dialect = Dialect::get(DialectType::Generic);
10634        let result = dialect
10635            .transpile("SELECT NVL(a, b)", DialectType::MySQL)
10636            .unwrap();
10637        assert_eq!(result[0], "SELECT IFNULL(a, b)");
10638    }
10639
10640    #[test]
10641    fn test_get_path_duckdb() {
10642        // Test: step by step
10643        let snowflake = Dialect::get(DialectType::Snowflake);
10644
10645        // Step 1: Parse and check what Snowflake produces as intermediate
10646        let result_sf_sf = snowflake
10647            .transpile(
10648                "SELECT PARSE_JSON('{\"fruit\":\"banana\"}'):fruit",
10649                DialectType::Snowflake,
10650            )
10651            .unwrap();
10652        eprintln!("Snowflake->Snowflake colon: {}", result_sf_sf[0]);
10653
10654        // Step 2: DuckDB target
10655        let result_sf_dk = snowflake
10656            .transpile(
10657                "SELECT PARSE_JSON('{\"fruit\":\"banana\"}'):fruit",
10658                DialectType::DuckDB,
10659            )
10660            .unwrap();
10661        eprintln!("Snowflake->DuckDB colon: {}", result_sf_dk[0]);
10662
10663        // Step 3: GET_PATH directly
10664        let result_gp = snowflake
10665            .transpile(
10666                "SELECT GET_PATH(PARSE_JSON('{\"fruit\":\"banana\"}'), 'fruit')",
10667                DialectType::DuckDB,
10668            )
10669            .unwrap();
10670        eprintln!("Snowflake->DuckDB explicit GET_PATH: {}", result_gp[0]);
10671    }
10672
10673    #[test]
10674    fn test_function_transformation_postgres() {
10675        // IFNULL should be transformed to COALESCE in PostgreSQL
10676        let dialect = Dialect::get(DialectType::Generic);
10677        let result = dialect
10678            .transpile("SELECT IFNULL(a, b)", DialectType::PostgreSQL)
10679            .unwrap();
10680        assert_eq!(result[0], "SELECT COALESCE(a, b)");
10681
10682        // NVL should also be transformed to COALESCE
10683        let result = dialect
10684            .transpile("SELECT NVL(a, b)", DialectType::PostgreSQL)
10685            .unwrap();
10686        assert_eq!(result[0], "SELECT COALESCE(a, b)");
10687    }
10688
10689    #[test]
10690    fn test_hive_cast_to_trycast() {
10691        // Hive CAST should become TRY_CAST for targets that support it
10692        let hive = Dialect::get(DialectType::Hive);
10693        let result = hive
10694            .transpile("CAST(1 AS INT)", DialectType::DuckDB)
10695            .unwrap();
10696        assert_eq!(result[0], "TRY_CAST(1 AS INT)");
10697
10698        let result = hive
10699            .transpile("CAST(1 AS INT)", DialectType::Presto)
10700            .unwrap();
10701        assert_eq!(result[0], "TRY_CAST(1 AS INTEGER)");
10702    }
10703
10704    #[test]
10705    fn test_hive_array_identity() {
10706        // Hive ARRAY<DATE> should preserve angle bracket syntax
10707        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')";
10708        let hive = Dialect::get(DialectType::Hive);
10709
10710        // Test via transpile (this works)
10711        let result = hive.transpile(sql, DialectType::Hive).unwrap();
10712        eprintln!("Hive ARRAY via transpile: {}", result[0]);
10713        assert!(
10714            result[0].contains("ARRAY<DATE>"),
10715            "transpile: Expected ARRAY<DATE>, got: {}",
10716            result[0]
10717        );
10718
10719        // Test via parse -> transform -> generate (identity test path)
10720        let ast = hive.parse(sql).unwrap();
10721        let transformed = hive.transform(ast[0].clone()).unwrap();
10722        let output = hive.generate(&transformed).unwrap();
10723        eprintln!("Hive ARRAY via identity path: {}", output);
10724        assert!(
10725            output.contains("ARRAY<DATE>"),
10726            "identity path: Expected ARRAY<DATE>, got: {}",
10727            output
10728        );
10729    }
10730
10731    #[test]
10732    fn test_starrocks_delete_between_expansion() {
10733        // StarRocks doesn't support BETWEEN in DELETE statements
10734        let dialect = Dialect::get(DialectType::Generic);
10735
10736        // BETWEEN should be expanded to >= AND <= in DELETE
10737        let result = dialect
10738            .transpile(
10739                "DELETE FROM t WHERE a BETWEEN b AND c",
10740                DialectType::StarRocks,
10741            )
10742            .unwrap();
10743        assert_eq!(result[0], "DELETE FROM t WHERE a >= b AND a <= c");
10744
10745        // NOT BETWEEN should be expanded to < OR > in DELETE
10746        let result = dialect
10747            .transpile(
10748                "DELETE FROM t WHERE a NOT BETWEEN b AND c",
10749                DialectType::StarRocks,
10750            )
10751            .unwrap();
10752        assert_eq!(result[0], "DELETE FROM t WHERE a < b OR a > c");
10753
10754        // BETWEEN in SELECT should NOT be expanded (StarRocks supports it there)
10755        let result = dialect
10756            .transpile(
10757                "SELECT * FROM t WHERE a BETWEEN b AND c",
10758                DialectType::StarRocks,
10759            )
10760            .unwrap();
10761        assert!(
10762            result[0].contains("BETWEEN"),
10763            "BETWEEN should be preserved in SELECT"
10764        );
10765    }
10766
10767    #[test]
10768    fn test_snowflake_ltrim_rtrim_parse() {
10769        let sf = Dialect::get(DialectType::Snowflake);
10770        let sql = "SELECT LTRIM(RTRIM(col)) FROM t1";
10771        let result = sf.transpile(sql, DialectType::DuckDB);
10772        match &result {
10773            Ok(r) => eprintln!("LTRIM/RTRIM result: {}", r[0]),
10774            Err(e) => eprintln!("LTRIM/RTRIM error: {}", e),
10775        }
10776        assert!(
10777            result.is_ok(),
10778            "Expected successful parse of LTRIM(RTRIM(col)), got error: {:?}",
10779            result.err()
10780        );
10781    }
10782
10783    #[test]
10784    fn test_duckdb_count_if_parse() {
10785        let duck = Dialect::get(DialectType::DuckDB);
10786        let sql = "COUNT_IF(x)";
10787        let result = duck.transpile(sql, DialectType::DuckDB);
10788        match &result {
10789            Ok(r) => eprintln!("COUNT_IF result: {}", r[0]),
10790            Err(e) => eprintln!("COUNT_IF error: {}", e),
10791        }
10792        assert!(
10793            result.is_ok(),
10794            "Expected successful parse of COUNT_IF(x), got error: {:?}",
10795            result.err()
10796        );
10797    }
10798
10799    #[test]
10800    fn test_tsql_cast_tinyint_parse() {
10801        let tsql = Dialect::get(DialectType::TSQL);
10802        let sql = "CAST(X AS TINYINT)";
10803        let result = tsql.transpile(sql, DialectType::DuckDB);
10804        match &result {
10805            Ok(r) => eprintln!("TSQL CAST TINYINT result: {}", r[0]),
10806            Err(e) => eprintln!("TSQL CAST TINYINT error: {}", e),
10807        }
10808        assert!(
10809            result.is_ok(),
10810            "Expected successful transpile, got error: {:?}",
10811            result.err()
10812        );
10813    }
10814
10815    #[test]
10816    fn test_pg_hash_bitwise_xor() {
10817        let dialect = Dialect::get(DialectType::PostgreSQL);
10818        let result = dialect.transpile("x # y", DialectType::PostgreSQL).unwrap();
10819        assert_eq!(result[0], "x # y");
10820    }
10821
10822    #[test]
10823    fn test_pg_array_to_duckdb() {
10824        let dialect = Dialect::get(DialectType::PostgreSQL);
10825        let result = dialect
10826            .transpile("SELECT ARRAY[1, 2, 3] @> ARRAY[1, 2]", DialectType::DuckDB)
10827            .unwrap();
10828        assert_eq!(result[0], "SELECT [1, 2, 3] @> [1, 2]");
10829    }
10830
10831    #[test]
10832    fn test_array_remove_bigquery() {
10833        let dialect = Dialect::get(DialectType::Generic);
10834        let result = dialect
10835            .transpile("ARRAY_REMOVE(the_array, target)", DialectType::BigQuery)
10836            .unwrap();
10837        assert_eq!(
10838            result[0],
10839            "ARRAY(SELECT _u FROM UNNEST(the_array) AS _u WHERE _u <> target)"
10840        );
10841    }
10842
10843    #[test]
10844    fn test_map_clickhouse_case() {
10845        let dialect = Dialect::get(DialectType::Generic);
10846        let parsed = dialect
10847            .parse("CAST(MAP('a', '1') AS MAP(TEXT, TEXT))")
10848            .unwrap();
10849        eprintln!("MAP parsed: {:?}", parsed);
10850        let result = dialect
10851            .transpile(
10852                "CAST(MAP('a', '1') AS MAP(TEXT, TEXT))",
10853                DialectType::ClickHouse,
10854            )
10855            .unwrap();
10856        eprintln!("MAP result: {}", result[0]);
10857    }
10858
10859    #[test]
10860    fn test_generate_date_array_presto() {
10861        let dialect = Dialect::get(DialectType::Generic);
10862        let result = dialect.transpile(
10863            "SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
10864            DialectType::Presto,
10865        ).unwrap();
10866        eprintln!("GDA -> Presto: {}", result[0]);
10867        assert_eq!(result[0], "SELECT * FROM UNNEST(SEQUENCE(CAST('2020-01-01' AS DATE), CAST('2020-02-01' AS DATE), (1 * INTERVAL '7' DAY)))");
10868    }
10869
10870    #[test]
10871    fn test_generate_date_array_postgres() {
10872        let dialect = Dialect::get(DialectType::Generic);
10873        let result = dialect.transpile(
10874            "SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
10875            DialectType::PostgreSQL,
10876        ).unwrap();
10877        eprintln!("GDA -> PostgreSQL: {}", result[0]);
10878    }
10879
10880    #[test]
10881    fn test_generate_date_array_snowflake() {
10882        let dialect = Dialect::get(DialectType::Generic);
10883        let result = dialect
10884            .transpile(
10885                "SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
10886                DialectType::Snowflake,
10887            )
10888            .unwrap();
10889        eprintln!("GDA -> Snowflake: {}", result[0]);
10890    }
10891
10892    #[test]
10893    fn test_array_length_generate_date_array_snowflake() {
10894        let dialect = Dialect::get(DialectType::Generic);
10895        let result = dialect.transpile(
10896            "SELECT ARRAY_LENGTH(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
10897            DialectType::Snowflake,
10898        ).unwrap();
10899        eprintln!("ARRAY_LENGTH(GDA) -> Snowflake: {}", result[0]);
10900    }
10901
10902    #[test]
10903    fn test_generate_date_array_mysql() {
10904        let dialect = Dialect::get(DialectType::Generic);
10905        let result = dialect.transpile(
10906            "SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
10907            DialectType::MySQL,
10908        ).unwrap();
10909        eprintln!("GDA -> MySQL: {}", result[0]);
10910    }
10911
10912    #[test]
10913    fn test_generate_date_array_redshift() {
10914        let dialect = Dialect::get(DialectType::Generic);
10915        let result = dialect.transpile(
10916            "SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
10917            DialectType::Redshift,
10918        ).unwrap();
10919        eprintln!("GDA -> Redshift: {}", result[0]);
10920    }
10921
10922    #[test]
10923    fn test_generate_date_array_tsql() {
10924        let dialect = Dialect::get(DialectType::Generic);
10925        let result = dialect.transpile(
10926            "SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
10927            DialectType::TSQL,
10928        ).unwrap();
10929        eprintln!("GDA -> TSQL: {}", result[0]);
10930    }
10931
10932    #[test]
10933    fn test_struct_colon_syntax() {
10934        let dialect = Dialect::get(DialectType::Generic);
10935        // Test without colon first
10936        let result = dialect.transpile(
10937            "CAST((1, 2, 3, 4) AS STRUCT<a TINYINT, b SMALLINT, c INT, d BIGINT>)",
10938            DialectType::ClickHouse,
10939        );
10940        match result {
10941            Ok(r) => eprintln!("STRUCT no colon -> ClickHouse: {}", r[0]),
10942            Err(e) => eprintln!("STRUCT no colon error: {}", e),
10943        }
10944        // Now test with colon
10945        let result = dialect.transpile(
10946            "CAST((1, 2, 3, 4) AS STRUCT<a: TINYINT, b: SMALLINT, c: INT, d: BIGINT>)",
10947            DialectType::ClickHouse,
10948        );
10949        match result {
10950            Ok(r) => eprintln!("STRUCT colon -> ClickHouse: {}", r[0]),
10951            Err(e) => eprintln!("STRUCT colon error: {}", e),
10952        }
10953    }
10954
10955    #[test]
10956    fn test_generate_date_array_cte_wrapped_mysql() {
10957        let dialect = Dialect::get(DialectType::Generic);
10958        let result = dialect.transpile(
10959            "WITH dates AS (SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))) SELECT * FROM dates",
10960            DialectType::MySQL,
10961        ).unwrap();
10962        eprintln!("GDA CTE -> MySQL: {}", result[0]);
10963    }
10964
10965    #[test]
10966    fn test_generate_date_array_cte_wrapped_tsql() {
10967        let dialect = Dialect::get(DialectType::Generic);
10968        let result = dialect.transpile(
10969            "WITH dates AS (SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))) SELECT * FROM dates",
10970            DialectType::TSQL,
10971        ).unwrap();
10972        eprintln!("GDA CTE -> TSQL: {}", result[0]);
10973    }
10974
10975    #[test]
10976    fn test_decode_literal_no_null_check() {
10977        // Oracle DECODE with all literals should produce simple equality, no IS NULL
10978        let dialect = Dialect::get(DialectType::Oracle);
10979        let result = dialect
10980            .transpile("SELECT decode(1,2,3,4)", DialectType::DuckDB)
10981            .unwrap();
10982        assert_eq!(
10983            result[0], "SELECT CASE WHEN 1 = 2 THEN 3 ELSE 4 END",
10984            "Literal DECODE should not have IS NULL checks"
10985        );
10986    }
10987
10988    #[test]
10989    fn test_decode_column_vs_literal_no_null_check() {
10990        // Oracle DECODE with column vs literal should use simple equality (like sqlglot)
10991        let dialect = Dialect::get(DialectType::Oracle);
10992        let result = dialect
10993            .transpile("SELECT decode(col, 2, 3, 4) FROM t", DialectType::DuckDB)
10994            .unwrap();
10995        assert_eq!(
10996            result[0], "SELECT CASE WHEN col = 2 THEN 3 ELSE 4 END FROM t",
10997            "Column vs literal DECODE should not have IS NULL checks"
10998        );
10999    }
11000
11001    #[test]
11002    fn test_decode_column_vs_column_keeps_null_check() {
11003        // Oracle DECODE with column vs column should keep null-safe comparison
11004        let dialect = Dialect::get(DialectType::Oracle);
11005        let result = dialect
11006            .transpile("SELECT decode(col, col2, 3, 4) FROM t", DialectType::DuckDB)
11007            .unwrap();
11008        assert!(
11009            result[0].contains("IS NULL"),
11010            "Column vs column DECODE should have IS NULL checks, got: {}",
11011            result[0]
11012        );
11013    }
11014
11015    #[test]
11016    fn test_decode_null_search() {
11017        // Oracle DECODE with NULL search should use IS NULL
11018        let dialect = Dialect::get(DialectType::Oracle);
11019        let result = dialect
11020            .transpile("SELECT decode(col, NULL, 3, 4) FROM t", DialectType::DuckDB)
11021            .unwrap();
11022        assert_eq!(
11023            result[0],
11024            "SELECT CASE WHEN col IS NULL THEN 3 ELSE 4 END FROM t",
11025        );
11026    }
11027
11028    // =========================================================================
11029    // REGEXP function transpilation tests
11030    // =========================================================================
11031
11032    #[test]
11033    fn test_regexp_substr_snowflake_to_duckdb_2arg() {
11034        let dialect = Dialect::get(DialectType::Snowflake);
11035        let result = dialect
11036            .transpile("SELECT REGEXP_SUBSTR(s, 'pattern')", DialectType::DuckDB)
11037            .unwrap();
11038        assert_eq!(result[0], "SELECT REGEXP_EXTRACT(s, 'pattern')");
11039    }
11040
11041    #[test]
11042    fn test_regexp_substr_snowflake_to_duckdb_3arg_pos1() {
11043        let dialect = Dialect::get(DialectType::Snowflake);
11044        let result = dialect
11045            .transpile("SELECT REGEXP_SUBSTR(s, 'pattern', 1)", DialectType::DuckDB)
11046            .unwrap();
11047        assert_eq!(result[0], "SELECT REGEXP_EXTRACT(s, 'pattern')");
11048    }
11049
11050    #[test]
11051    fn test_regexp_substr_snowflake_to_duckdb_3arg_pos_gt1() {
11052        let dialect = Dialect::get(DialectType::Snowflake);
11053        let result = dialect
11054            .transpile("SELECT REGEXP_SUBSTR(s, 'pattern', 3)", DialectType::DuckDB)
11055            .unwrap();
11056        assert_eq!(
11057            result[0],
11058            "SELECT REGEXP_EXTRACT(NULLIF(SUBSTRING(s, 3), ''), 'pattern')"
11059        );
11060    }
11061
11062    #[test]
11063    fn test_regexp_substr_snowflake_to_duckdb_4arg_occ_gt1() {
11064        let dialect = Dialect::get(DialectType::Snowflake);
11065        let result = dialect
11066            .transpile(
11067                "SELECT REGEXP_SUBSTR(s, 'pattern', 1, 3)",
11068                DialectType::DuckDB,
11069            )
11070            .unwrap();
11071        assert_eq!(
11072            result[0],
11073            "SELECT ARRAY_EXTRACT(REGEXP_EXTRACT_ALL(s, 'pattern'), 3)"
11074        );
11075    }
11076
11077    #[test]
11078    fn test_regexp_substr_snowflake_to_duckdb_5arg_e_flag() {
11079        let dialect = Dialect::get(DialectType::Snowflake);
11080        let result = dialect
11081            .transpile(
11082                "SELECT REGEXP_SUBSTR(s, 'pattern', 1, 1, 'e')",
11083                DialectType::DuckDB,
11084            )
11085            .unwrap();
11086        assert_eq!(result[0], "SELECT REGEXP_EXTRACT(s, 'pattern')");
11087    }
11088
11089    #[test]
11090    fn test_regexp_substr_snowflake_to_duckdb_6arg_group0() {
11091        let dialect = Dialect::get(DialectType::Snowflake);
11092        let result = dialect
11093            .transpile(
11094                "SELECT REGEXP_SUBSTR(s, 'pattern', 1, 1, 'e', 0)",
11095                DialectType::DuckDB,
11096            )
11097            .unwrap();
11098        assert_eq!(result[0], "SELECT REGEXP_EXTRACT(s, 'pattern')");
11099    }
11100
11101    #[test]
11102    fn test_regexp_substr_snowflake_identity_strip_group0() {
11103        let dialect = Dialect::get(DialectType::Snowflake);
11104        let result = dialect
11105            .transpile(
11106                "SELECT REGEXP_SUBSTR(s, 'pattern', 1, 1, 'e', 0)",
11107                DialectType::Snowflake,
11108            )
11109            .unwrap();
11110        assert_eq!(result[0], "SELECT REGEXP_SUBSTR(s, 'pattern', 1, 1, 'e')");
11111    }
11112
11113    #[test]
11114    fn test_regexp_substr_all_snowflake_to_duckdb_2arg() {
11115        let dialect = Dialect::get(DialectType::Snowflake);
11116        let result = dialect
11117            .transpile(
11118                "SELECT REGEXP_SUBSTR_ALL(s, 'pattern')",
11119                DialectType::DuckDB,
11120            )
11121            .unwrap();
11122        assert_eq!(result[0], "SELECT REGEXP_EXTRACT_ALL(s, 'pattern')");
11123    }
11124
11125    #[test]
11126    fn test_regexp_substr_all_snowflake_to_duckdb_3arg_pos_gt1() {
11127        let dialect = Dialect::get(DialectType::Snowflake);
11128        let result = dialect
11129            .transpile(
11130                "SELECT REGEXP_SUBSTR_ALL(s, 'pattern', 3)",
11131                DialectType::DuckDB,
11132            )
11133            .unwrap();
11134        assert_eq!(
11135            result[0],
11136            "SELECT REGEXP_EXTRACT_ALL(SUBSTRING(s, 3), 'pattern')"
11137        );
11138    }
11139
11140    #[test]
11141    fn test_regexp_substr_all_snowflake_to_duckdb_5arg_e_flag() {
11142        let dialect = Dialect::get(DialectType::Snowflake);
11143        let result = dialect
11144            .transpile(
11145                "SELECT REGEXP_SUBSTR_ALL(s, 'pattern', 1, 1, 'e')",
11146                DialectType::DuckDB,
11147            )
11148            .unwrap();
11149        assert_eq!(result[0], "SELECT REGEXP_EXTRACT_ALL(s, 'pattern')");
11150    }
11151
11152    #[test]
11153    fn test_regexp_substr_all_snowflake_to_duckdb_6arg_group0() {
11154        let dialect = Dialect::get(DialectType::Snowflake);
11155        let result = dialect
11156            .transpile(
11157                "SELECT REGEXP_SUBSTR_ALL(s, 'pattern', 1, 1, 'e', 0)",
11158                DialectType::DuckDB,
11159            )
11160            .unwrap();
11161        assert_eq!(result[0], "SELECT REGEXP_EXTRACT_ALL(s, 'pattern')");
11162    }
11163
11164    #[test]
11165    fn test_regexp_substr_all_snowflake_identity_strip_group0() {
11166        let dialect = Dialect::get(DialectType::Snowflake);
11167        let result = dialect
11168            .transpile(
11169                "SELECT REGEXP_SUBSTR_ALL(s, 'pattern', 1, 1, 'e', 0)",
11170                DialectType::Snowflake,
11171            )
11172            .unwrap();
11173        assert_eq!(
11174            result[0],
11175            "SELECT REGEXP_SUBSTR_ALL(s, 'pattern', 1, 1, 'e')"
11176        );
11177    }
11178
11179    #[test]
11180    fn test_regexp_count_snowflake_to_duckdb_2arg() {
11181        let dialect = Dialect::get(DialectType::Snowflake);
11182        let result = dialect
11183            .transpile("SELECT REGEXP_COUNT(s, 'pattern')", DialectType::DuckDB)
11184            .unwrap();
11185        assert_eq!(
11186            result[0],
11187            "SELECT CASE WHEN 'pattern' = '' THEN 0 ELSE LENGTH(REGEXP_EXTRACT_ALL(s, 'pattern')) END"
11188        );
11189    }
11190
11191    #[test]
11192    fn test_regexp_count_snowflake_to_duckdb_3arg() {
11193        let dialect = Dialect::get(DialectType::Snowflake);
11194        let result = dialect
11195            .transpile("SELECT REGEXP_COUNT(s, 'pattern', 3)", DialectType::DuckDB)
11196            .unwrap();
11197        assert_eq!(
11198            result[0],
11199            "SELECT CASE WHEN 'pattern' = '' THEN 0 ELSE LENGTH(REGEXP_EXTRACT_ALL(SUBSTRING(s, 3), 'pattern')) END"
11200        );
11201    }
11202
11203    #[test]
11204    fn test_regexp_count_snowflake_to_duckdb_4arg_flags() {
11205        let dialect = Dialect::get(DialectType::Snowflake);
11206        let result = dialect
11207            .transpile(
11208                "SELECT REGEXP_COUNT(s, 'pattern', 1, 'i')",
11209                DialectType::DuckDB,
11210            )
11211            .unwrap();
11212        assert_eq!(
11213            result[0],
11214            "SELECT CASE WHEN '(?i)' || 'pattern' = '' THEN 0 ELSE LENGTH(REGEXP_EXTRACT_ALL(SUBSTRING(s, 1), '(?i)' || 'pattern')) END"
11215        );
11216    }
11217
11218    #[test]
11219    fn test_regexp_count_snowflake_to_duckdb_4arg_flags_literal_string() {
11220        let dialect = Dialect::get(DialectType::Snowflake);
11221        let result = dialect
11222            .transpile(
11223                "SELECT REGEXP_COUNT('Hello World', 'L', 1, 'im')",
11224                DialectType::DuckDB,
11225            )
11226            .unwrap();
11227        assert_eq!(
11228            result[0],
11229            "SELECT CASE WHEN '(?im)' || 'L' = '' THEN 0 ELSE LENGTH(REGEXP_EXTRACT_ALL(SUBSTRING('Hello World', 1), '(?im)' || 'L')) END"
11230        );
11231    }
11232
11233    #[test]
11234    fn test_regexp_replace_snowflake_to_duckdb_5arg_pos1_occ1() {
11235        let dialect = Dialect::get(DialectType::Snowflake);
11236        let result = dialect
11237            .transpile(
11238                "SELECT REGEXP_REPLACE(s, 'pattern', 'repl', 1, 1)",
11239                DialectType::DuckDB,
11240            )
11241            .unwrap();
11242        assert_eq!(result[0], "SELECT REGEXP_REPLACE(s, 'pattern', 'repl')");
11243    }
11244
11245    #[test]
11246    fn test_regexp_replace_snowflake_to_duckdb_5arg_pos_gt1_occ0() {
11247        let dialect = Dialect::get(DialectType::Snowflake);
11248        let result = dialect
11249            .transpile(
11250                "SELECT REGEXP_REPLACE(s, 'pattern', 'repl', 3, 0)",
11251                DialectType::DuckDB,
11252            )
11253            .unwrap();
11254        assert_eq!(
11255            result[0],
11256            "SELECT SUBSTRING(s, 1, 2) || REGEXP_REPLACE(SUBSTRING(s, 3), 'pattern', 'repl', 'g')"
11257        );
11258    }
11259
11260    #[test]
11261    fn test_regexp_replace_snowflake_to_duckdb_5arg_pos_gt1_occ1() {
11262        let dialect = Dialect::get(DialectType::Snowflake);
11263        let result = dialect
11264            .transpile(
11265                "SELECT REGEXP_REPLACE(s, 'pattern', 'repl', 3, 1)",
11266                DialectType::DuckDB,
11267            )
11268            .unwrap();
11269        assert_eq!(
11270            result[0],
11271            "SELECT SUBSTRING(s, 1, 2) || REGEXP_REPLACE(SUBSTRING(s, 3), 'pattern', 'repl')"
11272        );
11273    }
11274
11275    #[test]
11276    fn test_rlike_snowflake_to_duckdb_2arg() {
11277        let dialect = Dialect::get(DialectType::Snowflake);
11278        let result = dialect
11279            .transpile("SELECT RLIKE(a, b)", DialectType::DuckDB)
11280            .unwrap();
11281        assert_eq!(result[0], "SELECT REGEXP_FULL_MATCH(a, b)");
11282    }
11283
11284    #[test]
11285    fn test_rlike_snowflake_to_duckdb_3arg_flags() {
11286        let dialect = Dialect::get(DialectType::Snowflake);
11287        let result = dialect
11288            .transpile("SELECT RLIKE(a, b, 'i')", DialectType::DuckDB)
11289            .unwrap();
11290        assert_eq!(result[0], "SELECT REGEXP_FULL_MATCH(a, b, 'i')");
11291    }
11292
11293    #[test]
11294    fn test_regexp_extract_all_bigquery_to_snowflake_no_capture() {
11295        let dialect = Dialect::get(DialectType::BigQuery);
11296        let result = dialect
11297            .transpile(
11298                "SELECT REGEXP_EXTRACT_ALL(s, 'pattern')",
11299                DialectType::Snowflake,
11300            )
11301            .unwrap();
11302        assert_eq!(result[0], "SELECT REGEXP_SUBSTR_ALL(s, 'pattern')");
11303    }
11304
11305    #[test]
11306    fn test_regexp_extract_all_bigquery_to_snowflake_with_capture() {
11307        let dialect = Dialect::get(DialectType::BigQuery);
11308        let result = dialect
11309            .transpile(
11310                "SELECT REGEXP_EXTRACT_ALL(s, '(a)[0-9]')",
11311                DialectType::Snowflake,
11312            )
11313            .unwrap();
11314        assert_eq!(
11315            result[0],
11316            "SELECT REGEXP_SUBSTR_ALL(s, '(a)[0-9]', 1, 1, 'c', 1)"
11317        );
11318    }
11319
11320    #[test]
11321    fn test_regexp_instr_snowflake_to_duckdb_2arg() {
11322        let dialect = Dialect::get(DialectType::Snowflake);
11323        let result = dialect
11324            .transpile("SELECT REGEXP_INSTR(s, 'pattern')", DialectType::DuckDB)
11325            .unwrap();
11326        assert!(
11327            result[0].contains("CASE WHEN"),
11328            "Expected CASE WHEN in result: {}",
11329            result[0]
11330        );
11331        assert!(
11332            result[0].contains("LIST_SUM"),
11333            "Expected LIST_SUM in result: {}",
11334            result[0]
11335        );
11336    }
11337
11338    #[test]
11339    fn test_array_except_generic_to_duckdb() {
11340        let dialect = Dialect::get(DialectType::Generic);
11341        let result = dialect
11342            .transpile(
11343                "SELECT ARRAY_EXCEPT(ARRAY(1, 2, 3), ARRAY(2))",
11344                DialectType::DuckDB,
11345            )
11346            .unwrap();
11347        eprintln!("ARRAY_EXCEPT Generic->DuckDB: {}", result[0]);
11348        assert!(
11349            result[0].contains("CASE WHEN"),
11350            "Expected CASE WHEN: {}",
11351            result[0]
11352        );
11353        assert!(
11354            result[0].contains("LIST_FILTER"),
11355            "Expected LIST_FILTER: {}",
11356            result[0]
11357        );
11358        assert!(
11359            result[0].contains("LIST_DISTINCT"),
11360            "Expected LIST_DISTINCT: {}",
11361            result[0]
11362        );
11363        assert!(
11364            result[0].contains("IS NOT DISTINCT FROM"),
11365            "Expected IS NOT DISTINCT FROM: {}",
11366            result[0]
11367        );
11368        assert!(
11369            result[0].contains("= 0"),
11370            "Expected = 0 filter: {}",
11371            result[0]
11372        );
11373    }
11374
11375    #[test]
11376    fn test_array_except_generic_to_snowflake() {
11377        let dialect = Dialect::get(DialectType::Generic);
11378        let result = dialect
11379            .transpile(
11380                "SELECT ARRAY_EXCEPT(ARRAY(1, 2, 3), ARRAY(2))",
11381                DialectType::Snowflake,
11382            )
11383            .unwrap();
11384        eprintln!("ARRAY_EXCEPT Generic->Snowflake: {}", result[0]);
11385        assert_eq!(result[0], "SELECT ARRAY_EXCEPT([1, 2, 3], [2])");
11386    }
11387
11388    #[test]
11389    fn test_array_except_generic_to_presto() {
11390        let dialect = Dialect::get(DialectType::Generic);
11391        let result = dialect
11392            .transpile(
11393                "SELECT ARRAY_EXCEPT(ARRAY(1, 2, 3), ARRAY(2))",
11394                DialectType::Presto,
11395            )
11396            .unwrap();
11397        eprintln!("ARRAY_EXCEPT Generic->Presto: {}", result[0]);
11398        assert_eq!(result[0], "SELECT ARRAY_EXCEPT(ARRAY[1, 2, 3], ARRAY[2])");
11399    }
11400
11401    #[test]
11402    fn test_array_except_snowflake_to_duckdb() {
11403        let dialect = Dialect::get(DialectType::Snowflake);
11404        let result = dialect
11405            .transpile("SELECT ARRAY_EXCEPT([1, 2, 3], [2])", DialectType::DuckDB)
11406            .unwrap();
11407        eprintln!("ARRAY_EXCEPT Snowflake->DuckDB: {}", result[0]);
11408        assert!(
11409            result[0].contains("CASE WHEN"),
11410            "Expected CASE WHEN: {}",
11411            result[0]
11412        );
11413        assert!(
11414            result[0].contains("LIST_TRANSFORM"),
11415            "Expected LIST_TRANSFORM: {}",
11416            result[0]
11417        );
11418    }
11419
11420    #[test]
11421    fn test_array_contains_snowflake_to_snowflake() {
11422        let dialect = Dialect::get(DialectType::Snowflake);
11423        let result = dialect
11424            .transpile(
11425                "SELECT ARRAY_CONTAINS(x, [1, NULL, 3])",
11426                DialectType::Snowflake,
11427            )
11428            .unwrap();
11429        eprintln!("ARRAY_CONTAINS Snowflake->Snowflake: {}", result[0]);
11430        assert_eq!(result[0], "SELECT ARRAY_CONTAINS(x, [1, NULL, 3])");
11431    }
11432
11433    #[test]
11434    fn test_array_contains_snowflake_to_duckdb() {
11435        let dialect = Dialect::get(DialectType::Snowflake);
11436        let result = dialect
11437            .transpile(
11438                "SELECT ARRAY_CONTAINS(x, [1, NULL, 3])",
11439                DialectType::DuckDB,
11440            )
11441            .unwrap();
11442        eprintln!("ARRAY_CONTAINS Snowflake->DuckDB: {}", result[0]);
11443        assert!(
11444            result[0].contains("CASE WHEN"),
11445            "Expected CASE WHEN: {}",
11446            result[0]
11447        );
11448        assert!(
11449            result[0].contains("NULLIF"),
11450            "Expected NULLIF: {}",
11451            result[0]
11452        );
11453        assert!(
11454            result[0].contains("ARRAY_CONTAINS"),
11455            "Expected ARRAY_CONTAINS: {}",
11456            result[0]
11457        );
11458    }
11459
11460    #[test]
11461    fn test_array_distinct_snowflake_to_duckdb() {
11462        let dialect = Dialect::get(DialectType::Snowflake);
11463        let result = dialect
11464            .transpile(
11465                "SELECT ARRAY_DISTINCT([1, 2, 2, 3, 1])",
11466                DialectType::DuckDB,
11467            )
11468            .unwrap();
11469        eprintln!("ARRAY_DISTINCT Snowflake->DuckDB: {}", result[0]);
11470        assert!(
11471            result[0].contains("CASE WHEN"),
11472            "Expected CASE WHEN: {}",
11473            result[0]
11474        );
11475        assert!(
11476            result[0].contains("LIST_DISTINCT"),
11477            "Expected LIST_DISTINCT: {}",
11478            result[0]
11479        );
11480        assert!(
11481            result[0].contains("LIST_APPEND"),
11482            "Expected LIST_APPEND: {}",
11483            result[0]
11484        );
11485        assert!(
11486            result[0].contains("LIST_FILTER"),
11487            "Expected LIST_FILTER: {}",
11488            result[0]
11489        );
11490    }
11491}