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