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, DateTimeField, Fetch, Function, Identifier,
166    Interval, IntervalUnit, IntervalUnitSpec, JoinKind, Literal, Offset, Over, Select, Subquery,
167    Top, Var, WindowFrame, 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::{is_aggregate, 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_clickhouse_session_semantics(&expr, self.dialect_type, target, opts)?;
3448                Self::reject_postgres_tsql_strict_regex_predicates(
3449                    &expr,
3450                    self.dialect_type,
3451                    target,
3452                    opts,
3453                )?;
3454                Self::reject_tsql_strict_json_constructor_return_types(
3455                    &expr,
3456                    self.dialect_type,
3457                    target,
3458                    opts,
3459                )?;
3460                Self::reject_postgres_tsql_strict_json_aggregate_modifiers(
3461                    &expr,
3462                    self.dialect_type,
3463                    target,
3464                    opts,
3465                )?;
3466
3467                // When source and target differ, first normalize the source dialect's
3468                // AST constructs to standard SQL, so that the target dialect can handle them.
3469                // This handles cases like Snowflake's SQUARE -> POWER, DIV0 -> CASE, etc.
3470                let normalized =
3471                    if self.dialect_type != target && self.dialect_type != DialectType::Generic {
3472                        self.transform_with_guard(expr, opts.complexity_guard)?
3473                    } else {
3474                        expr
3475                    };
3476
3477                // For TSQL source targeting non-TSQL: unwrap ISNULL(JSON_QUERY(...), JSON_VALUE(...))
3478                // to just JSON_QUERY(...) so cross_dialect_normalize can convert it cleanly.
3479                // The TSQL read transform wraps JsonQuery in ISNULL for identity, but for
3480                // cross-dialect transpilation we need the unwrapped JSON_QUERY.
3481                let normalized =
3482                    if matches!(self.dialect_type, DialectType::TSQL | DialectType::Fabric)
3483                        && !matches!(target, DialectType::TSQL | DialectType::Fabric)
3484                    {
3485                        transform_recursive(normalized, &|e| {
3486                            if let Expression::Function(ref f) = e {
3487                                if f.name.eq_ignore_ascii_case("ISNULL") && f.args.len() == 2 {
3488                                    // Check if first arg is JSON_QUERY and second is JSON_VALUE
3489                                    if let (
3490                                        Expression::Function(ref jq),
3491                                        Expression::Function(ref jv),
3492                                    ) = (&f.args[0], &f.args[1])
3493                                    {
3494                                        if jq.name.eq_ignore_ascii_case("JSON_QUERY")
3495                                            && jv.name.eq_ignore_ascii_case("JSON_VALUE")
3496                                        {
3497                                            // Unwrap: return just JSON_QUERY(...)
3498                                            return Ok(f.args[0].clone());
3499                                        }
3500                                    }
3501                                }
3502                            }
3503                            Ok(e)
3504                        })?
3505                    } else {
3506                        normalized
3507                    };
3508
3509                // Snowflake source to non-Snowflake target: CURRENT_TIME -> LOCALTIME.
3510                // Preserve precision for ClickHouse's Time64 lowering; retain the existing
3511                // precision-less LOCALTIME compatibility output for other targets.
3512                let normalized = if matches!(self.dialect_type, DialectType::Snowflake)
3513                    && !matches!(target, DialectType::Snowflake)
3514                {
3515                    transform_recursive(normalized, &|e| match e {
3516                        Expression::Function(ref f)
3517                            if f.name.eq_ignore_ascii_case("CURRENT_TIME") =>
3518                        {
3519                            let precision = if matches!(target, DialectType::ClickHouse) {
3520                                f.args.first().cloned().map(Box::new)
3521                            } else {
3522                                None
3523                            };
3524                            Ok(Expression::Localtime(Box::new(
3525                                crate::expressions::Localtime { this: precision },
3526                            )))
3527                        }
3528                        Expression::Localtime(ref localtime)
3529                            if !matches!(target, DialectType::ClickHouse)
3530                                && localtime.this.is_some() =>
3531                        {
3532                            Ok(Expression::Localtime(Box::new(
3533                                crate::expressions::Localtime { this: None },
3534                            )))
3535                        }
3536                        _ => Ok(e),
3537                    })?
3538                } else {
3539                    normalized
3540                };
3541
3542                // Snowflake source to DuckDB target: REPEAT(' ', n) -> REPEAT(' ', CAST(n AS BIGINT))
3543                // Snowflake's SPACE(n) is converted to REPEAT(' ', n) by the Snowflake source
3544                // transform. DuckDB requires the count argument to be BIGINT.
3545                let normalized = if matches!(self.dialect_type, DialectType::Snowflake)
3546                    && matches!(target, DialectType::DuckDB)
3547                {
3548                    transform_recursive(normalized, &|e| {
3549                        if let Expression::Function(ref f) = e {
3550                            if f.name.eq_ignore_ascii_case("REPEAT") && f.args.len() == 2 {
3551                                // Check if first arg is space string literal
3552                                if let Expression::Literal(ref lit) = f.args[0] {
3553                                    if let crate::expressions::Literal::String(ref s) = lit.as_ref()
3554                                    {
3555                                        if s == " " {
3556                                            // Wrap second arg in CAST(... AS BIGINT) if not already
3557                                            if !matches!(f.args[1], Expression::Cast(_)) {
3558                                                let mut new_args = f.args.clone();
3559                                                new_args[1] = Expression::Cast(Box::new(
3560                                                    crate::expressions::Cast {
3561                                                        this: new_args[1].clone(),
3562                                                        to: crate::expressions::DataType::BigInt {
3563                                                            length: None,
3564                                                        },
3565                                                        trailing_comments: Vec::new(),
3566                                                        double_colon_syntax: false,
3567                                                        format: None,
3568                                                        default: None,
3569                                                        inferred_type: None,
3570                                                    },
3571                                                ));
3572                                                return Ok(Expression::Function(Box::new(
3573                                                    crate::expressions::Function {
3574                                                        name: f.name.clone(),
3575                                                        args: new_args,
3576                                                        distinct: f.distinct,
3577                                                        trailing_comments: f
3578                                                            .trailing_comments
3579                                                            .clone(),
3580                                                        use_bracket_syntax: f.use_bracket_syntax,
3581                                                        no_parens: f.no_parens,
3582                                                        quoted: f.quoted,
3583                                                        span: None,
3584                                                        inferred_type: None,
3585                                                    },
3586                                                )));
3587                                            }
3588                                        }
3589                                    }
3590                                }
3591                            }
3592                        }
3593                        Ok(e)
3594                    })?
3595                } else {
3596                    normalized
3597                };
3598
3599                // Propagate struct field names in arrays (for BigQuery source to non-BigQuery target)
3600                // BigQuery->BigQuery should NOT propagate names (BigQuery handles implicit inheritance)
3601                let normalized = if matches!(self.dialect_type, DialectType::BigQuery)
3602                    && !matches!(target, DialectType::BigQuery)
3603                {
3604                    crate::transforms::propagate_struct_field_names(normalized)?
3605                } else {
3606                    normalized
3607                };
3608
3609                // Snowflake source to DuckDB target: RANDOM()/RANDOM(seed) -> scaled RANDOM()
3610                // Snowflake RANDOM() returns integer in [-2^63, 2^63-1], DuckDB RANDOM() returns float [0, 1)
3611                // Skip RANDOM inside UNIFORM/NORMAL/ZIPF/RANDSTR generator args since those
3612                // functions handle their generator args differently (as float seeds).
3613                let normalized = if matches!(self.dialect_type, DialectType::Snowflake)
3614                    && matches!(target, DialectType::DuckDB)
3615                {
3616                    fn make_scaled_random() -> Expression {
3617                        let lower =
3618                            Expression::Literal(Box::new(crate::expressions::Literal::Number(
3619                                "-9.223372036854776E+18".to_string(),
3620                            )));
3621                        let upper =
3622                            Expression::Literal(Box::new(crate::expressions::Literal::Number(
3623                                "9.223372036854776e+18".to_string(),
3624                            )));
3625                        let random_call = Expression::Random(crate::expressions::Random);
3626                        let range_size = Expression::Paren(Box::new(crate::expressions::Paren {
3627                            this: Expression::Sub(Box::new(crate::expressions::BinaryOp {
3628                                left: upper,
3629                                right: lower.clone(),
3630                                left_comments: vec![],
3631                                operator_comments: vec![],
3632                                trailing_comments: vec![],
3633                                inferred_type: None,
3634                            })),
3635                            trailing_comments: vec![],
3636                        }));
3637                        let scaled = Expression::Mul(Box::new(crate::expressions::BinaryOp {
3638                            left: random_call,
3639                            right: range_size,
3640                            left_comments: vec![],
3641                            operator_comments: vec![],
3642                            trailing_comments: vec![],
3643                            inferred_type: None,
3644                        }));
3645                        let shifted = Expression::Add(Box::new(crate::expressions::BinaryOp {
3646                            left: lower,
3647                            right: scaled,
3648                            left_comments: vec![],
3649                            operator_comments: vec![],
3650                            trailing_comments: vec![],
3651                            inferred_type: None,
3652                        }));
3653                        Expression::Cast(Box::new(crate::expressions::Cast {
3654                            this: shifted,
3655                            to: crate::expressions::DataType::BigInt { length: None },
3656                            trailing_comments: vec![],
3657                            double_colon_syntax: false,
3658                            format: None,
3659                            default: None,
3660                            inferred_type: None,
3661                        }))
3662                    }
3663
3664                    // Pre-process: protect seeded RANDOM(seed) inside UNIFORM/NORMAL/ZIPF/RANDSTR
3665                    // by converting Rand{seed: Some(s)} to Function{name:"RANDOM", args:[s]}.
3666                    // This prevents transform_recursive (which is bottom-up) from expanding
3667                    // seeded RANDOM into make_scaled_random() and losing the seed value.
3668                    // Unseeded RANDOM()/Rand{seed:None} is left as-is so it gets expanded
3669                    // and then un-expanded back to Expression::Random by the code below.
3670                    let normalized = transform_recursive(normalized, &|e| {
3671                        if let Expression::Function(ref f) = e {
3672                            let n = f.name.to_ascii_uppercase();
3673                            if n == "UNIFORM" || n == "NORMAL" || n == "ZIPF" || n == "RANDSTR" {
3674                                if let Expression::Function(mut f) = e {
3675                                    for arg in f.args.iter_mut() {
3676                                        if let Expression::Rand(ref r) = arg {
3677                                            if r.lower.is_none() && r.upper.is_none() {
3678                                                if let Some(ref seed) = r.seed {
3679                                                    // Convert Rand{seed: Some(s)} to Function("RANDOM", [s])
3680                                                    // so it won't be expanded by the RANDOM expansion below
3681                                                    *arg = Expression::Function(Box::new(
3682                                                        crate::expressions::Function::new(
3683                                                            "RANDOM".to_string(),
3684                                                            vec![*seed.clone()],
3685                                                        ),
3686                                                    ));
3687                                                }
3688                                            }
3689                                        }
3690                                    }
3691                                    return Ok(Expression::Function(f));
3692                                }
3693                            }
3694                        }
3695                        Ok(e)
3696                    })?;
3697
3698                    // transform_recursive processes bottom-up, so RANDOM() (unseeded) inside
3699                    // generator functions (UNIFORM, NORMAL, ZIPF) gets expanded before
3700                    // we see the parent. We detect this and undo the expansion by replacing
3701                    // the expanded pattern back with Expression::Random.
3702                    // Seeded RANDOM(seed) was already protected above as Function("RANDOM", [seed]).
3703                    // Note: RANDSTR is NOT included here — it needs the expanded form for unseeded
3704                    // RANDOM() since the DuckDB handler uses the expanded SQL as-is in the hash.
3705                    transform_recursive(normalized, &|e| {
3706                        if let Expression::Function(ref f) = e {
3707                            let n = f.name.to_ascii_uppercase();
3708                            if n == "UNIFORM" || n == "NORMAL" || n == "ZIPF" {
3709                                if let Expression::Function(mut f) = e {
3710                                    for arg in f.args.iter_mut() {
3711                                        // Detect expanded RANDOM pattern: CAST(-9.22... + RANDOM() * (...) AS BIGINT)
3712                                        if let Expression::Cast(ref cast) = arg {
3713                                            if matches!(
3714                                                cast.to,
3715                                                crate::expressions::DataType::BigInt { .. }
3716                                            ) {
3717                                                if let Expression::Add(ref add) = cast.this {
3718                                                    if let Expression::Literal(ref lit) = add.left {
3719                                                        if let crate::expressions::Literal::Number(
3720                                                            ref num,
3721                                                        ) = lit.as_ref()
3722                                                        {
3723                                                            if num == "-9.223372036854776E+18" {
3724                                                                *arg = Expression::Random(
3725                                                                    crate::expressions::Random,
3726                                                                );
3727                                                            }
3728                                                        }
3729                                                    }
3730                                                }
3731                                            }
3732                                        }
3733                                    }
3734                                    return Ok(Expression::Function(f));
3735                                }
3736                                return Ok(e);
3737                            }
3738                        }
3739                        match e {
3740                            Expression::Random(_) => Ok(make_scaled_random()),
3741                            // Rand(seed) with no bounds: drop seed and expand
3742                            // (DuckDB RANDOM doesn't support seeds)
3743                            Expression::Rand(ref r) if r.lower.is_none() && r.upper.is_none() => {
3744                                Ok(make_scaled_random())
3745                            }
3746                            _ => Ok(e),
3747                        }
3748                    })?
3749                } else {
3750                    normalized
3751                };
3752
3753                // Apply cross-dialect semantic normalizations
3754                let normalized = normalization::normalize(
3755                    normalized,
3756                    self.dialect_type,
3757                    target,
3758                    matches!(
3759                        opts.unsupported_level,
3760                        UnsupportedLevel::Raise | UnsupportedLevel::Immediate
3761                    ),
3762                )?;
3763
3764                let normalized = if matches!(target, DialectType::TSQL | DialectType::Fabric) {
3765                    Self::normalize_tsql_fetch_overlaps_date_bin(normalized)?
3766                } else {
3767                    normalized
3768                };
3769
3770                let normalized =
3771                    if matches!(
3772                        self.dialect_type,
3773                        DialectType::PostgreSQL | DialectType::CockroachDB
3774                    ) && !matches!(target, DialectType::PostgreSQL | DialectType::CockroachDB)
3775                    {
3776                        Self::normalize_postgres_type_function_casts(normalized, target)?
3777                    } else {
3778                        normalized
3779                    };
3780
3781                let normalized = if matches!(self.dialect_type, DialectType::SQLite)
3782                    && !matches!(target, DialectType::SQLite)
3783                {
3784                    Self::normalize_sqlite_double_quoted_defaults(normalized)?
3785                } else {
3786                    normalized
3787                };
3788
3789                let normalized = if matches!(self.dialect_type, DialectType::PostgreSQL)
3790                    && matches!(target, DialectType::SQLite)
3791                {
3792                    Self::normalize_postgres_to_sqlite_types(normalized)?
3793                } else {
3794                    normalized
3795                };
3796
3797                let normalized = if matches!(self.dialect_type, DialectType::PostgreSQL)
3798                    && matches!(target, DialectType::Fabric)
3799                {
3800                    Self::normalize_postgres_to_fabric_types(normalized)?
3801                } else {
3802                    normalized
3803                };
3804
3805                // For DuckDB target from BigQuery source: wrap UNNEST of struct arrays in
3806                // (SELECT UNNEST(..., max_depth => 2)) subquery
3807                // Must run BEFORE unnest_alias_to_column_alias since it changes alias structure
3808                let normalized = if matches!(self.dialect_type, DialectType::BigQuery)
3809                    && matches!(target, DialectType::DuckDB)
3810                {
3811                    crate::transforms::wrap_duckdb_unnest_struct(normalized)?
3812                } else {
3813                    normalized
3814                };
3815
3816                // Convert BigQuery UNNEST aliases to column-alias format for DuckDB/Presto/Spark
3817                // UNNEST(arr) AS x -> UNNEST(arr) AS _t0(x)
3818                let normalized = if matches!(self.dialect_type, DialectType::BigQuery)
3819                    && matches!(
3820                        target,
3821                        DialectType::DuckDB
3822                            | DialectType::Presto
3823                            | DialectType::Trino
3824                            | DialectType::Athena
3825                            | DialectType::Spark
3826                            | DialectType::Databricks
3827                    ) {
3828                    crate::transforms::unnest_alias_to_column_alias(normalized)?
3829                } else if matches!(self.dialect_type, DialectType::BigQuery)
3830                    && matches!(target, DialectType::BigQuery | DialectType::Redshift)
3831                {
3832                    // For BigQuery/Redshift targets: move UNNEST FROM items to CROSS JOINs
3833                    // but don't convert alias format (no _t0 wrapper)
3834                    let result = crate::transforms::unnest_from_to_cross_join(normalized)?;
3835                    // For Redshift: strip UNNEST when arg is a column reference path
3836                    if matches!(target, DialectType::Redshift) {
3837                        crate::transforms::strip_unnest_column_refs(result)?
3838                    } else {
3839                        result
3840                    }
3841                } else {
3842                    normalized
3843                };
3844
3845                // For Presto/Trino targets from PostgreSQL/Redshift source:
3846                // Wrap UNNEST aliases from GENERATE_SERIES conversion: AS s -> AS _u(s)
3847                let normalized = if matches!(
3848                    self.dialect_type,
3849                    DialectType::PostgreSQL | DialectType::Redshift
3850                ) && matches!(
3851                    target,
3852                    DialectType::Presto | DialectType::Trino | DialectType::Athena
3853                ) {
3854                    crate::transforms::wrap_unnest_join_aliases(normalized)?
3855                } else {
3856                    normalized
3857                };
3858
3859                // Eliminate DISTINCT ON with target-dialect awareness
3860                // This must happen after source transform (which may produce DISTINCT ON)
3861                // and before target transform, with knowledge of the target dialect's NULL ordering behavior
3862                let normalized = crate::transforms::eliminate_distinct_on_for_dialect(
3863                    normalized,
3864                    Some(target),
3865                    Some(self.dialect_type),
3866                )?;
3867
3868                // GENERATE_DATE_ARRAY in UNNEST -> Snowflake ARRAY_GENERATE_RANGE + DATEADD
3869                let normalized = if matches!(target, DialectType::Snowflake) {
3870                    Self::transform_generate_date_array_snowflake(normalized)?
3871                } else {
3872                    normalized
3873                };
3874
3875                // CROSS JOIN UNNEST -> LATERAL VIEW EXPLODE/INLINE for Spark/Hive/Databricks
3876                let normalized = if matches!(
3877                    target,
3878                    DialectType::Spark | DialectType::Databricks | DialectType::Hive
3879                ) {
3880                    crate::transforms::unnest_to_explode_select(normalized)?
3881                } else {
3882                    normalized
3883                };
3884
3885                // Wrap UNION with ORDER BY/LIMIT in a subquery for dialects that require it
3886                let normalized = if matches!(target, DialectType::ClickHouse | DialectType::TSQL) {
3887                    crate::transforms::no_limit_order_by_union(normalized)?
3888                } else {
3889                    normalized
3890                };
3891
3892                let normalized = if matches!(
3893                    self.dialect_type,
3894                    DialectType::PostgreSQL | DialectType::CockroachDB
3895                ) && matches!(target, DialectType::TSQL | DialectType::Fabric)
3896                {
3897                    Self::normalize_postgres_boolean_semantics_for_tsql(normalized)?
3898                } else {
3899                    normalized
3900                };
3901
3902                let normalized = if self.dialect_type == DialectType::PostgreSQL
3903                    && matches!(target, DialectType::TSQL | DialectType::Fabric)
3904                {
3905                    Self::normalize_postgres_bytea_literals_for_tsql(normalized)?
3906                } else {
3907                    normalized
3908                };
3909
3910                let normalized = if matches!(
3911                    self.dialect_type,
3912                    DialectType::PostgreSQL | DialectType::CockroachDB
3913                ) && matches!(target, DialectType::TSQL | DialectType::Fabric)
3914                {
3915                    Self::normalize_postgres_string_semantics_for_tsql(normalized)?
3916                } else {
3917                    normalized
3918                };
3919
3920                // TSQL: Convert COUNT(*) -> COUNT_BIG(*) when source is not TSQL/Fabric
3921                // Python sqlglot does this in the TSQL generator, but we can't do it there
3922                // because it would break TSQL -> TSQL identity
3923                let normalized = if matches!(target, DialectType::TSQL | DialectType::Fabric)
3924                    && !matches!(self.dialect_type, DialectType::TSQL | DialectType::Fabric)
3925                {
3926                    transform_recursive(normalized, &|e| {
3927                        if let Expression::Count(ref c) = e {
3928                            // Build COUNT_BIG(...) as an AggregateFunction
3929                            let args = if c.star {
3930                                vec![Expression::Star(crate::expressions::Star {
3931                                    table: None,
3932                                    except: None,
3933                                    replace: None,
3934                                    rename: None,
3935                                    trailing_comments: Vec::new(),
3936                                    span: None,
3937                                })]
3938                            } else if let Some(ref this) = c.this {
3939                                vec![this.clone()]
3940                            } else {
3941                                vec![]
3942                            };
3943                            Ok(Expression::AggregateFunction(Box::new(
3944                                crate::expressions::AggregateFunction {
3945                                    name: "COUNT_BIG".to_string(),
3946                                    args,
3947                                    distinct: c.distinct,
3948                                    filter: c.filter.clone(),
3949                                    order_by: Vec::new(),
3950                                    limit: None,
3951                                    ignore_nulls: None,
3952                                    inferred_type: None,
3953                                },
3954                            )))
3955                        } else {
3956                            Ok(e)
3957                        }
3958                    })?
3959                } else {
3960                    normalized
3961                };
3962
3963                // T-SQL/Fabric do not have a scalar boolean type. Keep predicate
3964                // contexts intact, but materialize boolean-valued expressions used
3965                // as values before target transforms add ORDER BY null sort keys.
3966                let normalized = if matches!(target, DialectType::TSQL | DialectType::Fabric)
3967                    && !matches!(self.dialect_type, DialectType::TSQL | DialectType::Fabric)
3968                {
3969                    let normalized = if self.dialect_type == DialectType::PostgreSQL {
3970                        Self::rewrite_postgres_row_value_equality_for_tsql(normalized)?
3971                    } else {
3972                        normalized
3973                    };
3974                    Self::rewrite_boolean_values_for_tsql(normalized)?
3975                } else {
3976                    normalized
3977                };
3978
3979                let normalized = if matches!(
3980                    self.dialect_type,
3981                    DialectType::PostgreSQL | DialectType::CockroachDB
3982                ) && matches!(target, DialectType::TSQL | DialectType::Fabric)
3983                {
3984                    Self::rewrite_postgres_format_for_tsql(normalized, target)?
3985                } else {
3986                    normalized
3987                };
3988
3989                let normalized = if self.dialect_type == DialectType::PostgreSQL
3990                    && matches!(target, DialectType::TSQL | DialectType::Fabric)
3991                {
3992                    Self::normalize_postgres_only_for_tsql(normalized)?
3993                } else {
3994                    normalized
3995                };
3996
3997                let transformed =
3998                    target_dialect.transform_with_guard(normalized, opts.complexity_guard)?;
3999
4000                // T-SQL and Fabric do not support aggregate FILTER clauses. Rewrite any
4001                // remaining filters after target transforms so special aggregate rewrites
4002                // (for example BOOL_OR/BOOL_AND) can consume their filters first.
4003                let transformed = if matches!(target, DialectType::TSQL | DialectType::Fabric) {
4004                    Self::rewrite_aggregate_filters_for_tsql(transformed)?
4005                } else {
4006                    transformed
4007                };
4008
4009                let transformed = if matches!(
4010                    self.dialect_type,
4011                    DialectType::PostgreSQL | DialectType::CockroachDB
4012                ) && matches!(target, DialectType::TSQL | DialectType::Fabric)
4013                {
4014                    crate::transforms::grouped_percentiles_to_tsql_windows(transformed)?
4015                } else {
4016                    transformed
4017                };
4018
4019                let transformed = if matches!(
4020                    self.dialect_type,
4021                    DialectType::PostgreSQL | DialectType::CockroachDB
4022                ) && matches!(target, DialectType::TSQL | DialectType::Fabric)
4023                {
4024                    Self::normalize_postgres_trim_for_tsql(transformed)?
4025                } else {
4026                    transformed
4027                };
4028
4029                let transformed = if matches!(
4030                    self.dialect_type,
4031                    DialectType::PostgreSQL | DialectType::CockroachDB
4032                ) && matches!(target, DialectType::TSQL | DialectType::Fabric)
4033                {
4034                    Self::rewrite_postgres_json_array_elements_select_for_tsql(transformed)?
4035                } else {
4036                    transformed
4037                };
4038
4039                // DuckDB target: when FROM is RANGE(n), replace SEQ's ROW_NUMBER pattern with `range`
4040                let transformed = if matches!(target, DialectType::DuckDB) {
4041                    Self::seq_rownum_to_range(transformed)?
4042                } else {
4043                    transformed
4044                };
4045
4046                if matches!(target, DialectType::TSQL | DialectType::Fabric) {
4047                    Self::reject_tsql_interval_casts(&transformed, target, opts)?;
4048                }
4049
4050                let transformed = if matches!(target, DialectType::TSQL | DialectType::Fabric) {
4051                    Self::rewrite_tsql_interval_casts_to_varchar(transformed)?
4052                } else {
4053                    transformed
4054                };
4055
4056                let transformed = if matches!(target, DialectType::TSQL | DialectType::Fabric) {
4057                    Self::legalize_tsql_nested_order_by(transformed)?
4058                } else {
4059                    transformed
4060                };
4061
4062                Self::reject_strict_unsupported(&transformed, self.dialect_type, target, opts)?;
4063
4064                let mut sql = target_dialect.generate_with_transpile_options(
4065                    &transformed,
4066                    self.dialect_type,
4067                    opts,
4068                )?;
4069
4070                // Align a known Snowflake pretty-print edge case with Python sqlglot output.
4071                if opts.pretty && target == DialectType::Snowflake {
4072                    sql = Self::normalize_snowflake_pretty(sql);
4073                }
4074
4075                Ok(sql)
4076            })
4077            .collect()
4078    }
4079}
4080
4081// Transpile-only methods: cross-dialect normalization and helpers
4082#[cfg(feature = "transpile")]
4083impl Dialect {
4084    fn legalize_tsql_nested_order_by(expr: Expression) -> Result<Expression> {
4085        let preserve_root_order = matches!(&expr, Expression::Select(select) if Self::tsql_select_needs_order_offset(select));
4086
4087        let mut transformed = transform_recursive(expr, &|node| match node {
4088            Expression::Select(mut select) => {
4089                Self::legalize_tsql_select_offset(&mut select);
4090                if Self::tsql_select_needs_order_offset(&select) {
4091                    select.offset = Some(Offset {
4092                        this: Expression::Literal(Box::new(Literal::Number("0".to_string()))),
4093                        rows: Some(true),
4094                    });
4095                }
4096                Ok(Expression::Select(select))
4097            }
4098            Expression::Subquery(mut subquery) => {
4099                Self::legalize_tsql_offset(&mut subquery.order_by, &mut subquery.offset, false);
4100                Ok(Expression::Subquery(subquery))
4101            }
4102            Expression::Union(mut union) => {
4103                Self::legalize_tsql_set_offset(&mut union.order_by, &mut union.offset);
4104                Ok(Expression::Union(union))
4105            }
4106            Expression::Intersect(mut intersect) => {
4107                Self::legalize_tsql_set_offset(&mut intersect.order_by, &mut intersect.offset);
4108                Ok(Expression::Intersect(intersect))
4109            }
4110            Expression::Except(mut except) => {
4111                Self::legalize_tsql_set_offset(&mut except.order_by, &mut except.offset);
4112                Ok(Expression::Except(except))
4113            }
4114            other => Ok(other),
4115        })?;
4116
4117        if preserve_root_order {
4118            if let Expression::Select(select) = &mut transformed {
4119                select.offset = None;
4120            }
4121        }
4122
4123        Self::drop_tsql_unbounded_nested_set_order_by(transformed)
4124    }
4125
4126    fn drop_tsql_unbounded_nested_set_order_by(mut expr: Expression) -> Result<Expression> {
4127        let root_order_by = Self::take_tsql_root_set_order_by(&mut expr);
4128
4129        let mut transformed = transform_recursive(expr, &|node| match node {
4130            Expression::Union(mut union) => {
4131                if union.limit.is_none() && union.offset.is_none() {
4132                    union.order_by = None;
4133                }
4134                Ok(Expression::Union(union))
4135            }
4136            Expression::Intersect(mut intersect) => {
4137                if intersect.limit.is_none() && intersect.offset.is_none() {
4138                    intersect.order_by = None;
4139                }
4140                Ok(Expression::Intersect(intersect))
4141            }
4142            Expression::Except(mut except) => {
4143                if except.limit.is_none() && except.offset.is_none() {
4144                    except.order_by = None;
4145                }
4146                Ok(Expression::Except(except))
4147            }
4148            other => Ok(other),
4149        })?;
4150
4151        if let Some(order_by) = root_order_by {
4152            Self::restore_tsql_root_set_order_by(&mut transformed, order_by);
4153        }
4154
4155        Ok(transformed)
4156    }
4157
4158    fn take_tsql_root_set_order_by(expr: &mut Expression) -> Option<OrderBy> {
4159        match expr {
4160            Expression::Union(union) => union.order_by.take(),
4161            Expression::Intersect(intersect) => intersect.order_by.take(),
4162            Expression::Except(except) => except.order_by.take(),
4163            Expression::Subquery(subquery) if subquery.alias.is_none() => {
4164                Self::take_tsql_root_set_order_by(&mut subquery.this)
4165            }
4166            Expression::Paren(paren) => Self::take_tsql_root_set_order_by(&mut paren.this),
4167            _ => None,
4168        }
4169    }
4170
4171    fn restore_tsql_root_set_order_by(expr: &mut Expression, order_by: OrderBy) {
4172        match expr {
4173            Expression::Union(union) => union.order_by = Some(order_by),
4174            Expression::Intersect(intersect) => intersect.order_by = Some(order_by),
4175            Expression::Except(except) => except.order_by = Some(order_by),
4176            Expression::Subquery(subquery) if subquery.alias.is_none() => {
4177                Self::restore_tsql_root_set_order_by(&mut subquery.this, order_by);
4178            }
4179            Expression::Paren(paren) => {
4180                Self::restore_tsql_root_set_order_by(&mut paren.this, order_by);
4181            }
4182            _ => {}
4183        }
4184    }
4185
4186    fn legalize_tsql_select_offset(select: &mut crate::expressions::Select) {
4187        let has_fetch = select.fetch.is_some();
4188        Self::legalize_tsql_offset(&mut select.order_by, &mut select.offset, has_fetch);
4189    }
4190
4191    fn legalize_tsql_offset(
4192        order_by: &mut Option<OrderBy>,
4193        offset: &mut Option<Offset>,
4194        retain_inert_offset: bool,
4195    ) {
4196        if order_by.is_some() {
4197            return;
4198        }
4199
4200        if offset
4201            .as_ref()
4202            .is_some_and(|offset| Self::tsql_offset_is_inert(&offset.this))
4203            && !retain_inert_offset
4204        {
4205            *offset = None;
4206        } else if offset.is_some() {
4207            *order_by = Some(Generator::dummy_tsql_order_by());
4208        }
4209    }
4210
4211    fn legalize_tsql_set_offset(
4212        order_by: &mut Option<OrderBy>,
4213        offset: &mut Option<Box<Expression>>,
4214    ) {
4215        if order_by.is_some() {
4216            return;
4217        }
4218
4219        if offset.as_deref().is_some_and(Self::tsql_offset_is_inert) {
4220            *offset = None;
4221        } else if offset.is_some() {
4222            *order_by = Some(Generator::dummy_tsql_order_by());
4223        }
4224    }
4225
4226    fn tsql_offset_is_inert(expr: &Expression) -> bool {
4227        match expr {
4228            Expression::Null(_) => true,
4229            Expression::Literal(literal) => match literal.as_ref() {
4230                Literal::Number(value) => value.parse::<i128>().is_ok_and(|value| value == 0),
4231                _ => false,
4232            },
4233            _ => false,
4234        }
4235    }
4236
4237    fn tsql_select_needs_order_offset(select: &crate::expressions::Select) -> bool {
4238        select.order_by.is_some()
4239            && select.top.is_none()
4240            && select.limit.is_none()
4241            && select.offset.is_none()
4242            && select.fetch.is_none()
4243            && select.for_xml.is_empty()
4244            && select.for_json.is_empty()
4245    }
4246
4247    fn reject_clickhouse_session_semantics(
4248        expr: &Expression,
4249        source: DialectType,
4250        target: DialectType,
4251        opts: &TranspileOptions,
4252    ) -> Result<()> {
4253        if !matches!(
4254            opts.unsupported_level,
4255            UnsupportedLevel::Raise | UnsupportedLevel::Immediate
4256        ) || target != DialectType::ClickHouse
4257            || source == DialectType::ClickHouse
4258        {
4259            return Ok(());
4260        }
4261
4262        const JOIN_USE_NULLS_DIAGNOSTIC: &str = "ClickHouse outer joins require the target setting join_use_nulls = 1 to preserve unmatched-row NULL semantics";
4263        const AGGREGATE_NULL_FOR_EMPTY_DIAGNOSTIC: &str = "ClickHouse non-count aggregates require the target setting aggregate_functions_null_for_empty = 1 to preserve empty-input NULL semantics";
4264
4265        fn is_current_session_time(node: &Expression) -> bool {
4266            match node {
4267                Expression::CurrentDate(_)
4268                | Expression::CurrentTime(_)
4269                | Expression::CurrentTimestamp(_)
4270                | Expression::CurrentTimestampLTZ(_)
4271                | Expression::Localtime(_)
4272                | Expression::Localtimestamp(_)
4273                | Expression::Systimestamp(_) => true,
4274                Expression::Function(function) if !function.quoted => matches!(
4275                    function.name.to_ascii_uppercase().as_str(),
4276                    "CURRENT_DATE"
4277                        | "CURRENT_TIME"
4278                        | "LOCALTIME"
4279                        | "CURRENT_TIMESTAMP"
4280                        | "CURRENT_TIMESTAMP_LTZ"
4281                        | "LOCALTIMESTAMP"
4282                        | "NOW"
4283                        | "GETDATE"
4284                        | "SYSTIMESTAMP"
4285                ),
4286                _ => false,
4287            }
4288        }
4289
4290        fn is_session_week_part(expression: &Expression) -> bool {
4291            let part = match expression {
4292                Expression::Literal(literal) => match literal.as_ref() {
4293                    Literal::String(value) => Some(value.as_str()),
4294                    _ => None,
4295                },
4296                Expression::Identifier(identifier) => Some(identifier.name.as_str()),
4297                Expression::Var(var) => Some(var.this.as_str()),
4298                Expression::Column(column) if column.table.is_none() => {
4299                    Some(column.name.name.as_str())
4300                }
4301                _ => None,
4302            };
4303            part.is_some_and(|part| {
4304                matches!(
4305                    part.to_ascii_uppercase().as_str(),
4306                    "WEEK" | "W" | "WK" | "WEEKOFYEAR" | "WOY" | "WY"
4307                )
4308            })
4309        }
4310
4311        fn is_session_week_trunc(node: &Expression) -> bool {
4312            match node {
4313                Expression::DateTrunc(date_trunc) => {
4314                    matches!(date_trunc.unit, DateTimeField::Week)
4315                }
4316                Expression::Function(function)
4317                    if !function.quoted && function.name.eq_ignore_ascii_case("DATE_TRUNC") =>
4318                {
4319                    function.args.first().is_some_and(is_session_week_part)
4320                }
4321                _ => false,
4322            }
4323        }
4324
4325        fn is_null_extending_clickhouse_join(node: &Expression) -> bool {
4326            fn join_is_null_extending(join: &Join) -> bool {
4327                matches!(
4328                    join.kind,
4329                    JoinKind::Left
4330                        | JoinKind::Right
4331                        | JoinKind::Full
4332                        | JoinKind::NaturalLeft
4333                        | JoinKind::NaturalRight
4334                        | JoinKind::NaturalFull
4335                        | JoinKind::AsOfLeft
4336                        | JoinKind::AsOfRight
4337                )
4338            }
4339
4340            fn joins_are_null_extending(joins: &[Join]) -> bool {
4341                joins.iter().any(join_is_null_extending)
4342            }
4343
4344            match node {
4345                Expression::Join(join) => join_is_null_extending(join),
4346                Expression::Select(select) => joins_are_null_extending(&select.joins),
4347                Expression::JoinedTable(joined) => joins_are_null_extending(&joined.joins),
4348                Expression::Update(update) => {
4349                    joins_are_null_extending(&update.table_joins)
4350                        || joins_are_null_extending(&update.from_joins)
4351                }
4352                Expression::Delete(delete) => joins_are_null_extending(&delete.joins),
4353                _ => false,
4354            }
4355        }
4356
4357        fn is_count_like_aggregate(node: &Expression) -> bool {
4358            match node {
4359                Expression::Count(_)
4360                | Expression::CountIf(_)
4361                | Expression::ApproxDistinct(_)
4362                | Expression::ApproxCountDistinct(_) => true,
4363                Expression::AggregateFunction(function) => matches!(
4364                    function.name.to_ascii_uppercase().as_str(),
4365                    "COUNT" | "COUNT_IF" | "COUNTIF" | "APPROX_DISTINCT" | "APPROX_COUNT_DISTINCT"
4366                ),
4367                _ => false,
4368            }
4369        }
4370
4371        fn is_empty_input_sensitive_aggregate(node: &Expression, source: DialectType) -> bool {
4372            Dialect::node_is_aggregate_function(node)
4373                && !is_count_like_aggregate(node)
4374                && !matches!(node, Expression::BitwiseCount(_))
4375                && !matches!(node, Expression::Median(_) if source == DialectType::Snowflake)
4376                && !matches!(
4377                    node,
4378                    Expression::AggregateFunction(function)
4379                        if function.name.to_ascii_lowercase().ends_with("ornull")
4380                )
4381        }
4382
4383        fn setting_is_enabled(setting: &Expression, name: &str) -> bool {
4384            let Expression::Eq(equality) = setting else {
4385                return false;
4386            };
4387            let setting_name = match &equality.left {
4388                Expression::Identifier(identifier) => identifier.name.as_str(),
4389                Expression::Column(column) if column.table.is_none() => column.name.name.as_str(),
4390                Expression::Var(var) => var.this.as_str(),
4391                _ => return false,
4392            };
4393            let enabled = match &equality.right {
4394                Expression::Literal(literal) => {
4395                    matches!(literal.as_ref(), Literal::Number(value) if value.parse::<i128>().is_ok_and(|value| value == 1))
4396                }
4397                Expression::Boolean(boolean) => boolean.value,
4398                _ => false,
4399            };
4400            setting_name.eq_ignore_ascii_case(name) && enabled
4401        }
4402
4403        fn query_has_enabled_setting(expr: &Expression, name: &str) -> bool {
4404            expr.dfs().any(|node| {
4405                matches!(
4406                    node,
4407                    Expression::Select(select)
4408                        if select.settings.as_ref().is_some_and(|settings| {
4409                            settings.iter().any(|setting| setting_is_enabled(setting, name))
4410                        })
4411                )
4412            })
4413        }
4414
4415        let mut diagnostics = Vec::new();
4416        let join_use_nulls_enabled = query_has_enabled_setting(expr, "join_use_nulls");
4417        let aggregate_null_for_empty_enabled =
4418            query_has_enabled_setting(expr, "aggregate_functions_null_for_empty");
4419        for node in expr.dfs() {
4420            if source == DialectType::Snowflake && is_current_session_time(node) {
4421                Self::push_unsupported_diagnostic(
4422                    &mut diagnostics,
4423                    "Snowflake current date/time expressions depend on the session TIMEZONE, which cannot be preserved for ClickHouse",
4424                );
4425            }
4426            if source == DialectType::Snowflake && is_session_week_trunc(node) {
4427                Self::push_unsupported_diagnostic(
4428                    &mut diagnostics,
4429                    "Snowflake DATE_TRUNC with a week unit depends on the session WEEK_START, which cannot be preserved for ClickHouse",
4430                );
4431            }
4432            if !join_use_nulls_enabled && is_null_extending_clickhouse_join(node) {
4433                Self::push_unsupported_diagnostic(&mut diagnostics, JOIN_USE_NULLS_DIAGNOSTIC);
4434            }
4435            if !aggregate_null_for_empty_enabled && is_empty_input_sensitive_aggregate(node, source)
4436            {
4437                Self::push_unsupported_diagnostic(
4438                    &mut diagnostics,
4439                    AGGREGATE_NULL_FOR_EMPTY_DIAGNOSTIC,
4440                );
4441            }
4442            if opts.unsupported_level == UnsupportedLevel::Immediate && !diagnostics.is_empty() {
4443                break;
4444            }
4445        }
4446
4447        if diagnostics.is_empty() {
4448            return Ok(());
4449        }
4450
4451        let limit = if opts.unsupported_level == UnsupportedLevel::Immediate {
4452            1
4453        } else {
4454            opts.max_unsupported.max(1)
4455        };
4456        let mut messages = diagnostics.iter().take(limit).cloned().collect::<Vec<_>>();
4457        if diagnostics.len() > limit {
4458            messages.push(format!("... and {} more", diagnostics.len() - limit));
4459        }
4460
4461        Err(crate::error::Error::unsupported(
4462            messages.join("; "),
4463            target.to_string(),
4464        ))
4465    }
4466
4467    fn reject_strict_unsupported(
4468        expr: &Expression,
4469        source: DialectType,
4470        target: DialectType,
4471        opts: &TranspileOptions,
4472    ) -> Result<()> {
4473        if !matches!(
4474            opts.unsupported_level,
4475            UnsupportedLevel::Raise | UnsupportedLevel::Immediate
4476        ) {
4477            return Ok(());
4478        }
4479
4480        let mut diagnostics = Vec::new();
4481        if matches!(source, DialectType::PostgreSQL | DialectType::CockroachDB)
4482            && matches!(target, DialectType::TSQL | DialectType::Fabric)
4483            && Self::tsql_apply_has_invalid_outer_aggregate(expr)
4484        {
4485            Self::push_unsupported_diagnostic(
4486                &mut diagnostics,
4487                "APPLY aggregate expressions that combine an outer reference with another column reference",
4488            );
4489        }
4490        let structural_grouping_tuples =
4491            if matches!(source, DialectType::PostgreSQL | DialectType::CockroachDB)
4492                && matches!(target, DialectType::TSQL | DialectType::Fabric)
4493            {
4494                Self::collect_tsql_grouping_tuple_nodes(expr)
4495            } else {
4496                HashSet::new()
4497            };
4498
4499        for node in expr.dfs() {
4500            if source == DialectType::Snowflake && target == DialectType::ClickHouse {
4501                if Self::node_is_function_named(node, "TO_CHAR") {
4502                    Self::push_unsupported_diagnostic(
4503                        &mut diagnostics,
4504                        "Snowflake TO_CHAR overload or dynamic format",
4505                    );
4506                }
4507                if Self::node_is_function_named(node, "TRY_TO_DOUBLE")
4508                    || matches!(node, Expression::ToDouble(to_double) if to_double.safe.is_some() && to_double.format.is_some())
4509                {
4510                    Self::push_unsupported_diagnostic(
4511                        &mut diagnostics,
4512                        "Snowflake TRY_TO_DOUBLE with a format model",
4513                    );
4514                }
4515                if Self::node_is_function_named(node, "TRY_TO_NUMBER")
4516                    || Self::node_is_function_named(node, "TRY_TO_NUMERIC")
4517                    || Self::node_is_function_named(node, "TRY_TO_DECIMAL")
4518                    || matches!(node, Expression::ToNumber(to_number) if to_number.safe.is_some())
4519                {
4520                    Self::push_unsupported_diagnostic(
4521                        &mut diagnostics,
4522                        "Snowflake TRY_TO_NUMBER decimal conversion semantics",
4523                    );
4524                }
4525                if Self::node_is_function_named(node, "FLATTEN") {
4526                    Self::push_unsupported_diagnostic(
4527                        &mut diagnostics,
4528                        "Snowflake FLATTEN table-function semantics",
4529                    );
4530                }
4531            }
4532
4533            if matches!(target, DialectType::Fabric | DialectType::Hive)
4534                && Self::node_has_recursive_with(node)
4535            {
4536                Self::push_unsupported_diagnostic(&mut diagnostics, "recursive CTEs");
4537            }
4538
4539            if matches!(target, DialectType::TSQL | DialectType::Fabric)
4540                && Self::node_has_lateral(node)
4541            {
4542                Self::push_unsupported_diagnostic(&mut diagnostics, "LATERAL joins and subqueries");
4543            }
4544
4545            if matches!(target, DialectType::TSQL | DialectType::Fabric) {
4546                if Self::node_has_join_using(node) {
4547                    Self::push_unsupported_diagnostic(&mut diagnostics, "JOIN USING clauses");
4548                }
4549                if Self::node_has_natural_join(node) {
4550                    Self::push_unsupported_diagnostic(&mut diagnostics, "NATURAL JOIN");
4551                }
4552                if Self::node_has_unsupported_relation_column_aliases(node) {
4553                    Self::push_unsupported_diagnostic(
4554                        &mut diagnostics,
4555                        "column alias lists on base or joined table references",
4556                    );
4557                }
4558                if Self::node_has_qualified_whole_row_aggregate_argument(node) {
4559                    Self::push_unsupported_diagnostic(
4560                        &mut diagnostics,
4561                        "qualified whole-row aggregate arguments",
4562                    );
4563                }
4564                if Self::node_has_subquery_in_aggregate_argument(node) {
4565                    Self::push_unsupported_diagnostic(
4566                        &mut diagnostics,
4567                        "aggregate arguments containing subqueries",
4568                    );
4569                }
4570            }
4571
4572            if !Self::target_supports_distinct_on(target) && Self::node_has_distinct_on(node) {
4573                Self::push_unsupported_diagnostic(&mut diagnostics, "DISTINCT ON");
4574            }
4575
4576            if !Self::target_supports_remaining_unnest(target) && Self::node_is_unnest(node) {
4577                Self::push_unsupported_diagnostic(&mut diagnostics, "UNNEST");
4578            }
4579
4580            if !Self::target_supports_remaining_explode(target) && Self::node_is_explode(node) {
4581                Self::push_unsupported_diagnostic(&mut diagnostics, "EXPLODE");
4582            }
4583
4584            if Self::target_lacks_array_agg(target) && Self::node_is_array_agg(node) {
4585                Self::push_unsupported_diagnostic(&mut diagnostics, "ARRAY_AGG");
4586            }
4587
4588            if matches!(target, DialectType::TSQL | DialectType::Fabric)
4589                && Self::node_is_distinct_string_agg(node)
4590            {
4591                Self::push_unsupported_diagnostic(&mut diagnostics, "STRING_AGG with DISTINCT");
4592            }
4593
4594            if matches!(target, DialectType::TSQL | DialectType::Fabric)
4595                && matches!(node, Expression::NthValue(_))
4596            {
4597                Self::push_unsupported_diagnostic(&mut diagnostics, "NTH_VALUE");
4598            }
4599
4600            if matches!(target, DialectType::TSQL | DialectType::Fabric) {
4601                if let Some(frame) = Self::node_window_frame(node) {
4602                    if matches!(frame.kind, WindowFrameKind::Groups) {
4603                        Self::push_unsupported_diagnostic(&mut diagnostics, "GROUPS window frames");
4604                    }
4605                    if matches!(frame.kind, WindowFrameKind::Range)
4606                        && (Self::window_frame_bound_has_value_offset(&frame.start)
4607                            || frame
4608                                .end
4609                                .as_ref()
4610                                .is_some_and(Self::window_frame_bound_has_value_offset))
4611                    {
4612                        Self::push_unsupported_diagnostic(
4613                            &mut diagnostics,
4614                            "value-offset RANGE window frames",
4615                        );
4616                    }
4617                    if frame.exclude.is_some() {
4618                        Self::push_unsupported_diagnostic(
4619                            &mut diagnostics,
4620                            "window frame EXCLUDE clauses",
4621                        );
4622                    }
4623                }
4624            }
4625
4626            if matches!(target, DialectType::TSQL | DialectType::Fabric)
4627                && Self::node_is_regex_predicate(node)
4628            {
4629                Self::push_unsupported_diagnostic(
4630                    &mut diagnostics,
4631                    "regular expression predicates",
4632                );
4633            }
4634
4635            if matches!(target, DialectType::TSQL | DialectType::Fabric)
4636                && Self::node_is_non_subquery_any(node)
4637            {
4638                Self::push_unsupported_diagnostic(
4639                    &mut diagnostics,
4640                    "ANY over non-subquery expressions",
4641                );
4642            }
4643
4644            if matches!(target, DialectType::TSQL | DialectType::Fabric)
4645                && Self::node_is_row_value_subquery_comparison(node)
4646            {
4647                Self::push_unsupported_diagnostic(
4648                    &mut diagnostics,
4649                    "row-value subquery comparisons",
4650                );
4651            }
4652
4653            if matches!(target, DialectType::TSQL | DialectType::Fabric)
4654                && Self::node_is_row_value_values_membership(node)
4655            {
4656                Self::push_unsupported_diagnostic(
4657                    &mut diagnostics,
4658                    "row-value VALUES membership comparisons",
4659                );
4660            }
4661
4662            if matches!(target, DialectType::TSQL | DialectType::Fabric)
4663                && Self::node_has_fetch_with_ties(node)
4664            {
4665                Self::push_unsupported_diagnostic(&mut diagnostics, "FETCH WITH TIES without TOP");
4666            }
4667
4668            if matches!(target, DialectType::TSQL | DialectType::Fabric)
4669                && Self::node_is_overlaps(node)
4670            {
4671                Self::push_unsupported_diagnostic(&mut diagnostics, "OVERLAPS");
4672            }
4673
4674            if matches!(target, DialectType::TSQL | DialectType::Fabric)
4675                && Self::node_is_date_bin(node)
4676            {
4677                Self::push_unsupported_diagnostic(&mut diagnostics, "DATE_BIN");
4678            }
4679
4680            if source == DialectType::PostgreSQL
4681                && matches!(target, DialectType::TSQL | DialectType::Fabric)
4682                && Self::node_is_unresolved_postgres_date_subtraction(node)
4683            {
4684                Self::push_unsupported_diagnostic(
4685                    &mut diagnostics,
4686                    "PostgreSQL date subtraction with an unresolved column type",
4687                );
4688            }
4689
4690            if matches!(source, DialectType::PostgreSQL | DialectType::CockroachDB)
4691                && !matches!(target, DialectType::PostgreSQL | DialectType::CockroachDB)
4692            {
4693                if Self::node_is_postgres_json_build_object(node)
4694                    && !(matches!(target, DialectType::TSQL | DialectType::Fabric)
4695                        && Self::postgres_json_build_object_can_lower_to_json_object(node))
4696                {
4697                    Self::push_unsupported_diagnostic(
4698                        &mut diagnostics,
4699                        "PostgreSQL JSON_BUILD_OBJECT",
4700                    );
4701                }
4702                if Self::node_is_function_named(node, "TO_TSVECTOR") {
4703                    Self::push_unsupported_diagnostic(&mut diagnostics, "PostgreSQL TO_TSVECTOR");
4704                }
4705                if matches!(target, DialectType::TSQL | DialectType::Fabric) {
4706                    if let Some(composite_semantics) =
4707                        Self::postgres_tsql_unsupported_composite_semantics(
4708                            node,
4709                            structural_grouping_tuples.contains(&(node as *const Expression)),
4710                        )
4711                    {
4712                        Self::push_unsupported_diagnostic(
4713                            &mut diagnostics,
4714                            &format!("PostgreSQL {composite_semantics}"),
4715                        );
4716                    }
4717                    if Self::node_is_postgres_unknown_cast(node) {
4718                        Self::push_unsupported_diagnostic(
4719                            &mut diagnostics,
4720                            "PostgreSQL unresolved UNKNOWN casts",
4721                        );
4722                    }
4723                    if let Some(collation_name) =
4724                        Self::postgres_tsql_unsupported_collation_name(node)
4725                    {
4726                        Self::push_unsupported_diagnostic(
4727                            &mut diagnostics,
4728                            &format!("PostgreSQL collation \"{collation_name}\""),
4729                        );
4730                    }
4731                    if let Some(array_semantics) =
4732                        Self::postgres_tsql_unsupported_array_semantics(node)
4733                    {
4734                        Self::push_unsupported_diagnostic(
4735                            &mut diagnostics,
4736                            &format!("PostgreSQL {array_semantics}"),
4737                        );
4738                    }
4739                    if let Some(string_semantics) =
4740                        Self::postgres_tsql_unsupported_string_semantics(node)
4741                    {
4742                        Self::push_unsupported_diagnostic(
4743                            &mut diagnostics,
4744                            &format!("PostgreSQL {string_semantics}"),
4745                        );
4746                    }
4747                    if source == DialectType::PostgreSQL {
4748                        if let Some(binary_semantics) =
4749                            Self::postgres_tsql_unsupported_binary_semantics(node)
4750                        {
4751                            Self::push_unsupported_diagnostic(
4752                                &mut diagnostics,
4753                                &format!("PostgreSQL {binary_semantics}"),
4754                            );
4755                        }
4756                    }
4757                    if let Some(function_name) =
4758                        Self::postgres_tsql_unsupported_function_name(node, target)
4759                    {
4760                        Self::push_unsupported_diagnostic(
4761                            &mut diagnostics,
4762                            &format!("PostgreSQL {function_name}"),
4763                        );
4764                    }
4765                }
4766                if matches!(target, DialectType::TSQL | DialectType::Fabric)
4767                    && Self::node_is_postgres_type_function_cast(node)
4768                {
4769                    Self::push_unsupported_diagnostic(
4770                        &mut diagnostics,
4771                        "PostgreSQL type-name function casts",
4772                    );
4773                }
4774            }
4775
4776            if opts.unsupported_level == UnsupportedLevel::Immediate && !diagnostics.is_empty() {
4777                break;
4778            }
4779        }
4780
4781        if matches!(target, DialectType::TSQL | DialectType::Fabric) {
4782            Self::collect_tsql_unsupported_ordered_sets(expr, &mut diagnostics);
4783            Self::collect_tsql_windows_missing_order(expr, &HashMap::new(), &mut diagnostics);
4784        }
4785
4786        if diagnostics.is_empty() {
4787            return Ok(());
4788        }
4789
4790        let limit = if opts.unsupported_level == UnsupportedLevel::Immediate {
4791            1
4792        } else {
4793            opts.max_unsupported.max(1)
4794        };
4795        let mut messages = diagnostics.iter().take(limit).cloned().collect::<Vec<_>>();
4796        if diagnostics.len() > limit {
4797            messages.push(format!("... and {} more", diagnostics.len() - limit));
4798        }
4799
4800        Err(crate::error::Error::unsupported(
4801            messages.join("; "),
4802            target.to_string(),
4803        ))
4804    }
4805
4806    fn reject_postgres_tsql_strict_regex_predicates(
4807        expr: &Expression,
4808        source: DialectType,
4809        target: DialectType,
4810        opts: &TranspileOptions,
4811    ) -> Result<()> {
4812        if !matches!(
4813            opts.unsupported_level,
4814            UnsupportedLevel::Raise | UnsupportedLevel::Immediate
4815        ) || !matches!(source, DialectType::PostgreSQL | DialectType::CockroachDB)
4816            || !matches!(target, DialectType::TSQL | DialectType::Fabric)
4817        {
4818            return Ok(());
4819        }
4820
4821        if expr.dfs().any(Self::node_is_regex_predicate) {
4822            return Err(crate::error::Error::unsupported(
4823                "regular expression predicates",
4824                target.to_string(),
4825            ));
4826        }
4827
4828        Ok(())
4829    }
4830
4831    fn reject_tsql_strict_json_constructor_return_types(
4832        expr: &Expression,
4833        source: DialectType,
4834        target: DialectType,
4835        opts: &TranspileOptions,
4836    ) -> Result<()> {
4837        if !matches!(
4838            opts.unsupported_level,
4839            UnsupportedLevel::Raise | UnsupportedLevel::Immediate
4840        ) || source == target
4841            || !matches!(target, DialectType::TSQL | DialectType::Fabric)
4842        {
4843            return Ok(());
4844        }
4845
4846        let mut diagnostics = Vec::new();
4847        for node in expr.dfs() {
4848            if let Some(return_type) =
4849                normalization::unsupported_tsql_json_constructor_return_type(node)
4850            {
4851                let message =
4852                    format!("SQL/JSON constructor RETURNING {return_type} cannot be preserved");
4853                Self::push_unsupported_diagnostic(&mut diagnostics, &message);
4854                if opts.unsupported_level == UnsupportedLevel::Immediate {
4855                    break;
4856                }
4857            }
4858        }
4859
4860        if diagnostics.is_empty() {
4861            return Ok(());
4862        }
4863
4864        let limit = if opts.unsupported_level == UnsupportedLevel::Immediate {
4865            1
4866        } else {
4867            opts.max_unsupported.max(1)
4868        };
4869        let mut messages = diagnostics.iter().take(limit).cloned().collect::<Vec<_>>();
4870        if diagnostics.len() > limit {
4871            messages.push(format!("... and {} more", diagnostics.len() - limit));
4872        }
4873
4874        Err(crate::error::Error::unsupported(
4875            messages.join("; "),
4876            target.to_string(),
4877        ))
4878    }
4879
4880    fn reject_postgres_tsql_strict_json_aggregate_modifiers(
4881        expr: &Expression,
4882        source: DialectType,
4883        target: DialectType,
4884        opts: &TranspileOptions,
4885    ) -> Result<()> {
4886        if !matches!(
4887            opts.unsupported_level,
4888            UnsupportedLevel::Raise | UnsupportedLevel::Immediate
4889        ) || !matches!(source, DialectType::PostgreSQL | DialectType::CockroachDB)
4890            || !matches!(target, DialectType::TSQL | DialectType::Fabric)
4891        {
4892            return Ok(());
4893        }
4894
4895        let mut diagnostics = Vec::new();
4896        for node in expr.dfs() {
4897            match node {
4898                Expression::Function(function)
4899                    if !function.quoted
4900                        && matches!(
4901                            function.name.to_ascii_uppercase().as_str(),
4902                            "JSON_AGG" | "JSONB_AGG"
4903                        ) =>
4904                {
4905                    let name = function.name.to_ascii_uppercase();
4906                    if function.args.len() != 1 {
4907                        Self::push_unsupported_diagnostic(
4908                            &mut diagnostics,
4909                            &format!("PostgreSQL {name} with invalid argument count"),
4910                        );
4911                    }
4912                    if function.distinct {
4913                        Self::push_unsupported_diagnostic(
4914                            &mut diagnostics,
4915                            &format!("PostgreSQL {name} with DISTINCT"),
4916                        );
4917                    }
4918                }
4919                Expression::AggregateFunction(function)
4920                    if matches!(
4921                        function.name.to_ascii_uppercase().as_str(),
4922                        "JSON_AGG" | "JSONB_AGG"
4923                    ) =>
4924                {
4925                    let name = function.name.to_ascii_uppercase();
4926                    if function.args.len() != 1 {
4927                        Self::push_unsupported_diagnostic(
4928                            &mut diagnostics,
4929                            &format!("PostgreSQL {name} with invalid argument count"),
4930                        );
4931                    }
4932                    if function.distinct {
4933                        Self::push_unsupported_diagnostic(
4934                            &mut diagnostics,
4935                            &format!("PostgreSQL {name} with DISTINCT"),
4936                        );
4937                    }
4938                    if function.filter.is_some() {
4939                        Self::push_unsupported_diagnostic(
4940                            &mut diagnostics,
4941                            &format!("PostgreSQL {name} with FILTER"),
4942                        );
4943                    }
4944                    if function.limit.is_some() || function.ignore_nulls.is_some() {
4945                        Self::push_unsupported_diagnostic(
4946                            &mut diagnostics,
4947                            &format!("PostgreSQL {name} with unsupported aggregate modifiers"),
4948                        );
4949                    }
4950                }
4951                Expression::Filter(filter) => {
4952                    if let Some(name) = Self::postgres_json_aggregate_name(&filter.this) {
4953                        Self::push_unsupported_diagnostic(
4954                            &mut diagnostics,
4955                            &format!("PostgreSQL {name} with FILTER"),
4956                        );
4957                    }
4958                }
4959                _ => {}
4960            }
4961
4962            if opts.unsupported_level == UnsupportedLevel::Immediate && !diagnostics.is_empty() {
4963                break;
4964            }
4965        }
4966
4967        if diagnostics.is_empty() {
4968            return Ok(());
4969        }
4970
4971        let limit = if opts.unsupported_level == UnsupportedLevel::Immediate {
4972            1
4973        } else {
4974            opts.max_unsupported.max(1)
4975        };
4976        let mut messages = diagnostics.iter().take(limit).cloned().collect::<Vec<_>>();
4977        if diagnostics.len() > limit {
4978            messages.push(format!("... and {} more", diagnostics.len() - limit));
4979        }
4980
4981        Err(crate::error::Error::unsupported(
4982            messages.join("; "),
4983            target.to_string(),
4984        ))
4985    }
4986
4987    fn postgres_json_aggregate_name(expr: &Expression) -> Option<String> {
4988        let name = match expr {
4989            Expression::Function(function) if !function.quoted => &function.name,
4990            Expression::AggregateFunction(function) => &function.name,
4991            _ => return None,
4992        };
4993        let name = name.to_ascii_uppercase();
4994        matches!(name.as_str(), "JSON_AGG" | "JSONB_AGG").then_some(name)
4995    }
4996
4997    fn push_unsupported_diagnostic(diagnostics: &mut Vec<String>, message: &str) {
4998        if !diagnostics.iter().any(|existing| existing == message) {
4999            diagnostics.push(message.to_string());
5000        }
5001    }
5002
5003    fn node_is_unresolved_postgres_date_subtraction(expr: &Expression) -> bool {
5004        let Expression::Sub(op) = expr else {
5005            return false;
5006        };
5007
5008        (Self::is_explicit_date_expr(&op.left) && Self::is_column_expr(&op.right))
5009            || (Self::is_column_expr(&op.left) && Self::is_explicit_date_expr(&op.right))
5010    }
5011
5012    fn is_column_expr(expr: &Expression) -> bool {
5013        match expr {
5014            Expression::Column(_) => true,
5015            Expression::Paren(paren) => Self::is_column_expr(&paren.this),
5016            _ => false,
5017        }
5018    }
5019
5020    fn node_window_frame(expr: &Expression) -> Option<&WindowFrame> {
5021        match expr {
5022            Expression::WindowFunction(window) => window.over.frame.as_ref(),
5023            Expression::Window(window) | Expression::WindowSpec(window) => window.frame.as_ref(),
5024            _ => None,
5025        }
5026    }
5027
5028    fn window_frame_bound_has_value_offset(bound: &WindowFrameBound) -> bool {
5029        matches!(
5030            bound,
5031            WindowFrameBound::Preceding(_)
5032                | WindowFrameBound::Following(_)
5033                | WindowFrameBound::Value(_)
5034                | WindowFrameBound::BarePreceding
5035                | WindowFrameBound::BareFollowing
5036        )
5037    }
5038
5039    fn collect_tsql_windows_missing_order(
5040        expr: &Expression,
5041        active_windows: &HashMap<String, Over>,
5042        diagnostics: &mut Vec<String>,
5043    ) {
5044        if let Expression::Select(select) = expr {
5045            let local_windows = select
5046                .windows
5047                .as_ref()
5048                .map(|windows| {
5049                    windows
5050                        .iter()
5051                        .map(|window| (window.name.name.to_ascii_lowercase(), window.spec.clone()))
5052                        .collect()
5053                })
5054                .unwrap_or_default();
5055
5056            for child in expr.children() {
5057                Self::collect_tsql_windows_missing_order(child, &local_windows, diagnostics);
5058            }
5059            return;
5060        }
5061
5062        if let Expression::WindowFunction(window) = expr {
5063            let (has_order, has_frame) = Self::effective_window_order_and_frame(
5064                &window.over,
5065                active_windows,
5066                &mut Vec::new(),
5067            );
5068
5069            if !has_order {
5070                if has_frame {
5071                    Self::push_unsupported_diagnostic(
5072                        diagnostics,
5073                        "window frames without ORDER BY",
5074                    );
5075                }
5076                if let Some(function_name) =
5077                    Self::tsql_window_function_requiring_order(&window.this)
5078                {
5079                    Self::push_unsupported_diagnostic(
5080                        diagnostics,
5081                        &format!("{function_name} without ORDER BY"),
5082                    );
5083                }
5084            }
5085        }
5086
5087        for child in expr.children() {
5088            Self::collect_tsql_windows_missing_order(child, active_windows, diagnostics);
5089        }
5090    }
5091
5092    fn effective_window_order_and_frame(
5093        over: &Over,
5094        active_windows: &HashMap<String, Over>,
5095        seen: &mut Vec<String>,
5096    ) -> (bool, bool) {
5097        let inherited = over
5098            .window_name
5099            .as_ref()
5100            .and_then(|name| {
5101                let key = name.name.to_ascii_lowercase();
5102                if seen.iter().any(|seen_name| seen_name == &key) {
5103                    return None;
5104                }
5105                let named = active_windows.get(&key)?;
5106                seen.push(key);
5107                let properties =
5108                    Self::effective_window_order_and_frame(named, active_windows, seen);
5109                seen.pop();
5110                Some(properties)
5111            })
5112            .unwrap_or((false, false));
5113
5114        (
5115            !over.order_by.is_empty() || inherited.0,
5116            over.frame.is_some() || inherited.1,
5117        )
5118    }
5119
5120    fn tsql_window_function_requiring_order(expr: &Expression) -> Option<&'static str> {
5121        match expr {
5122            Expression::FirstValue(_) => Some("FIRST_VALUE"),
5123            Expression::LastValue(_) => Some("LAST_VALUE"),
5124            Expression::Function(function) if function.name.eq_ignore_ascii_case("FIRST_VALUE") => {
5125                Some("FIRST_VALUE")
5126            }
5127            Expression::Function(function) if function.name.eq_ignore_ascii_case("LAST_VALUE") => {
5128                Some("LAST_VALUE")
5129            }
5130            _ => None,
5131        }
5132    }
5133
5134    fn collect_tsql_unsupported_ordered_sets(expr: &Expression, diagnostics: &mut Vec<String>) {
5135        match expr {
5136            Expression::WindowFunction(window) => {
5137                if let Expression::WithinGroup(within_group) = &window.this {
5138                    if Self::within_group_is_hypothetical_set(within_group) {
5139                        Self::push_unsupported_diagnostic(
5140                            diagnostics,
5141                            "RANK/DENSE_RANK/CUME_DIST/PERCENT_RANK hypothetical-set aggregates",
5142                        );
5143                        return;
5144                    }
5145
5146                    if Self::within_group_is_mode(within_group) {
5147                        Self::push_unsupported_diagnostic(
5148                            diagnostics,
5149                            "MODE ordered-set aggregates",
5150                        );
5151                        return;
5152                    }
5153
5154                    if Self::within_group_is_percentile(within_group) {
5155                        if !window.over.order_by.is_empty() || window.over.frame.is_some() {
5156                            Self::push_unsupported_diagnostic(
5157                                diagnostics,
5158                                "PERCENTILE_CONT/PERCENTILE_DISC window ORDER BY or frame clauses",
5159                            );
5160                        }
5161                        return;
5162                    }
5163                }
5164            }
5165            Expression::WithinGroup(within_group) => {
5166                if Self::within_group_is_hypothetical_set(within_group) {
5167                    Self::push_unsupported_diagnostic(
5168                        diagnostics,
5169                        "RANK/DENSE_RANK/CUME_DIST/PERCENT_RANK hypothetical-set aggregates",
5170                    );
5171                    return;
5172                }
5173
5174                if Self::within_group_is_mode(within_group) {
5175                    Self::push_unsupported_diagnostic(diagnostics, "MODE ordered-set aggregates");
5176                    return;
5177                }
5178
5179                if Self::within_group_is_percentile(within_group) {
5180                    Self::push_unsupported_diagnostic(
5181                        diagnostics,
5182                        "PERCENTILE_CONT/PERCENTILE_DISC ordered-set aggregates without OVER",
5183                    );
5184                    return;
5185                }
5186            }
5187            _ => {}
5188        }
5189
5190        for child in expr.children() {
5191            Self::collect_tsql_unsupported_ordered_sets(child, diagnostics);
5192        }
5193    }
5194
5195    fn within_group_is_hypothetical_set(within_group: &crate::expressions::WithinGroup) -> bool {
5196        match &within_group.this {
5197            Expression::Function(function) => Self::is_hypothetical_set_name(&function.name),
5198            Expression::AggregateFunction(function) => {
5199                Self::is_hypothetical_set_name(&function.name)
5200            }
5201            Expression::Rank(_)
5202            | Expression::DenseRank(_)
5203            | Expression::CumeDist(_)
5204            | Expression::PercentRank(_) => true,
5205            _ => false,
5206        }
5207    }
5208
5209    fn within_group_is_percentile(within_group: &crate::expressions::WithinGroup) -> bool {
5210        match &within_group.this {
5211            Expression::Function(function) => Self::is_percentile_ordered_set_name(&function.name),
5212            Expression::AggregateFunction(function) => {
5213                Self::is_percentile_ordered_set_name(&function.name)
5214            }
5215            Expression::PercentileCont(_) | Expression::PercentileDisc(_) => true,
5216            _ => false,
5217        }
5218    }
5219
5220    fn within_group_is_mode(within_group: &crate::expressions::WithinGroup) -> bool {
5221        match &within_group.this {
5222            Expression::Function(function) => function.name.eq_ignore_ascii_case("MODE"),
5223            Expression::AggregateFunction(function) => function.name.eq_ignore_ascii_case("MODE"),
5224            Expression::Mode(_) => true,
5225            _ => false,
5226        }
5227    }
5228
5229    fn is_percentile_ordered_set_name(name: &str) -> bool {
5230        name.eq_ignore_ascii_case("PERCENTILE_CONT") || name.eq_ignore_ascii_case("PERCENTILE_DISC")
5231    }
5232
5233    fn is_hypothetical_set_name(name: &str) -> bool {
5234        name.eq_ignore_ascii_case("RANK")
5235            || name.eq_ignore_ascii_case("DENSE_RANK")
5236            || name.eq_ignore_ascii_case("CUME_DIST")
5237            || name.eq_ignore_ascii_case("PERCENT_RANK")
5238    }
5239
5240    fn target_supports_distinct_on(target: DialectType) -> bool {
5241        matches!(target, DialectType::PostgreSQL | DialectType::DuckDB)
5242    }
5243
5244    fn node_has_distinct_on(expr: &Expression) -> bool {
5245        matches!(
5246            expr,
5247            Expression::Select(select)
5248                if select
5249                    .distinct_on
5250                    .as_ref()
5251                    .is_some_and(|distinct_on| !distinct_on.is_empty())
5252        )
5253    }
5254
5255    fn node_has_recursive_with(expr: &Expression) -> bool {
5256        fn recursive(with: &Option<With>) -> bool {
5257            with.as_ref().is_some_and(|with| with.recursive)
5258        }
5259
5260        match expr {
5261            Expression::With(with) => with.recursive,
5262            Expression::Select(select) => recursive(&select.with),
5263            Expression::Union(union) => recursive(&union.with),
5264            Expression::Intersect(intersect) => recursive(&intersect.with),
5265            Expression::Except(except) => recursive(&except.with),
5266            Expression::Pivot(pivot) => recursive(&pivot.with),
5267            Expression::Insert(insert) => recursive(&insert.with),
5268            Expression::Update(update) => recursive(&update.with),
5269            Expression::Delete(delete) => recursive(&delete.with),
5270            _ => false,
5271        }
5272    }
5273
5274    fn node_has_lateral(expr: &Expression) -> bool {
5275        fn join_has_lateral(join: &Join) -> bool {
5276            matches!(
5277                join.kind,
5278                crate::expressions::JoinKind::Lateral | crate::expressions::JoinKind::LeftLateral
5279            ) || Dialect::node_has_lateral(&join.this)
5280                || join.on.as_ref().is_some_and(Dialect::node_has_lateral)
5281                || join
5282                    .match_condition
5283                    .as_ref()
5284                    .is_some_and(Dialect::node_has_lateral)
5285                || join.pivots.iter().any(Dialect::node_has_lateral)
5286        }
5287
5288        fn joins_have_lateral(joins: &[Join]) -> bool {
5289            joins.iter().any(join_has_lateral)
5290        }
5291
5292        match expr {
5293            Expression::Subquery(subquery) => {
5294                subquery.lateral || Dialect::node_has_lateral(&subquery.this)
5295            }
5296            Expression::Lateral(_) | Expression::LateralView(_) => true,
5297            Expression::Join(join) => join_has_lateral(join),
5298            Expression::Select(select) => {
5299                !select.lateral_views.is_empty()
5300                    || joins_have_lateral(&select.joins)
5301                    || select
5302                        .from
5303                        .as_ref()
5304                        .is_some_and(|from| from.expressions.iter().any(Dialect::node_has_lateral))
5305            }
5306            Expression::JoinedTable(joined) => {
5307                !joined.lateral_views.is_empty()
5308                    || Dialect::node_has_lateral(&joined.left)
5309                    || joins_have_lateral(&joined.joins)
5310            }
5311            Expression::Update(update) => {
5312                joins_have_lateral(&update.table_joins) || joins_have_lateral(&update.from_joins)
5313            }
5314            _ => false,
5315        }
5316    }
5317
5318    fn node_has_join_using(expr: &Expression) -> bool {
5319        fn has_using(joins: &[Join]) -> bool {
5320            joins.iter().any(|join| !join.using.is_empty())
5321        }
5322
5323        match expr {
5324            Expression::Join(join) => !join.using.is_empty(),
5325            Expression::Select(select) => has_using(&select.joins),
5326            Expression::JoinedTable(joined) => has_using(&joined.joins),
5327            Expression::Update(update) => {
5328                has_using(&update.table_joins) || has_using(&update.from_joins)
5329            }
5330            Expression::Delete(delete) => has_using(&delete.joins),
5331            _ => false,
5332        }
5333    }
5334
5335    fn node_has_natural_join(expr: &Expression) -> bool {
5336        fn is_natural(join: &Join) -> bool {
5337            matches!(
5338                join.kind,
5339                crate::expressions::JoinKind::Natural
5340                    | crate::expressions::JoinKind::NaturalLeft
5341                    | crate::expressions::JoinKind::NaturalRight
5342                    | crate::expressions::JoinKind::NaturalFull
5343            )
5344        }
5345
5346        fn has_natural(joins: &[Join]) -> bool {
5347            joins.iter().any(is_natural)
5348        }
5349
5350        match expr {
5351            Expression::Join(join) => is_natural(join),
5352            Expression::Select(select) => has_natural(&select.joins),
5353            Expression::JoinedTable(joined) => has_natural(&joined.joins),
5354            Expression::Update(update) => {
5355                has_natural(&update.table_joins) || has_natural(&update.from_joins)
5356            }
5357            Expression::Delete(delete) => has_natural(&delete.joins),
5358            _ => false,
5359        }
5360    }
5361
5362    fn node_has_unsupported_relation_column_aliases(expr: &Expression) -> bool {
5363        match expr {
5364            Expression::Table(table) => !table.column_aliases.is_empty(),
5365            Expression::Alias(alias) => {
5366                !alias.column_aliases.is_empty()
5367                    && matches!(
5368                        alias.this,
5369                        Expression::Table(_) | Expression::JoinedTable(_)
5370                    )
5371            }
5372            _ => false,
5373        }
5374    }
5375
5376    fn node_is_aggregate_function(expr: &Expression) -> bool {
5377        is_aggregate(expr)
5378    }
5379
5380    fn node_has_qualified_whole_row_aggregate_argument(expr: &Expression) -> bool {
5381        fn contains_qualified_star(expr: &Expression) -> bool {
5382            match expr {
5383                Expression::Star(star) => star.table.is_some(),
5384                // A star projected by an embedded query is not an argument of
5385                // the surrounding aggregate (for example, inside EXISTS).
5386                Expression::Select(_)
5387                | Expression::Subquery(_)
5388                | Expression::Union(_)
5389                | Expression::Intersect(_)
5390                | Expression::Except(_) => false,
5391                _ => expr.children().into_iter().any(contains_qualified_star),
5392            }
5393        }
5394
5395        Self::node_is_aggregate_function(expr)
5396            && expr.children().into_iter().any(contains_qualified_star)
5397    }
5398
5399    fn node_has_subquery_in_aggregate_argument(expr: &Expression) -> bool {
5400        fn contains_query(expr: &Expression) -> bool {
5401            matches!(
5402                expr,
5403                Expression::Select(_)
5404                    | Expression::Subquery(_)
5405                    | Expression::Union(_)
5406                    | Expression::Intersect(_)
5407                    | Expression::Except(_)
5408            ) || expr.children().into_iter().any(contains_query)
5409        }
5410
5411        Self::node_is_aggregate_function(expr) && expr.children().into_iter().any(contains_query)
5412    }
5413
5414    fn tsql_apply_has_invalid_outer_aggregate(expr: &Expression) -> bool {
5415        // SQL Server error 8124: if an aggregate expression contains an outer
5416        // reference, that reference must be the only column used by the expression.
5417        fn collect_source_names(expr: &Expression, names: &mut HashSet<String>) {
5418            let mut insert = |name: &Identifier| {
5419                if !name.name.is_empty() {
5420                    names.insert(name.name.to_ascii_lowercase());
5421                }
5422            };
5423
5424            match expr {
5425                Expression::Table(table) => {
5426                    insert(table.alias.as_ref().unwrap_or(&table.name));
5427                }
5428                Expression::Subquery(subquery) => {
5429                    if let Some(alias) = &subquery.alias {
5430                        insert(alias);
5431                    }
5432                }
5433                Expression::Alias(alias) => insert(&alias.alias),
5434                Expression::JoinedTable(joined) => {
5435                    if let Some(alias) = &joined.alias {
5436                        insert(alias);
5437                    } else {
5438                        collect_source_names(&joined.left, names);
5439                        for join in &joined.joins {
5440                            collect_source_names(&join.this, names);
5441                        }
5442                    }
5443                }
5444                Expression::Paren(paren) => collect_source_names(&paren.this, names),
5445                Expression::Pivot(pivot) => {
5446                    if let Some(alias) = &pivot.alias {
5447                        insert(alias);
5448                    } else {
5449                        collect_source_names(&pivot.this, names);
5450                    }
5451                }
5452                Expression::Unpivot(unpivot) => {
5453                    if let Some(alias) = &unpivot.alias {
5454                        insert(alias);
5455                    } else {
5456                        collect_source_names(&unpivot.this, names);
5457                    }
5458                }
5459                _ => {}
5460            }
5461        }
5462
5463        fn collect_columns<'a>(
5464            expr: &'a Expression,
5465            columns: &mut Vec<&'a crate::expressions::Column>,
5466        ) {
5467            match expr {
5468                Expression::Column(column) => columns.push(column),
5469                // Query expressions introduce a new name-resolution scope. Their
5470                // columns are checked when their own SELECT node is visited.
5471                Expression::Select(_)
5472                | Expression::Subquery(_)
5473                | Expression::Union(_)
5474                | Expression::Intersect(_)
5475                | Expression::Except(_) => {}
5476                _ => {
5477                    for child in expr.children() {
5478                        collect_columns(child, columns);
5479                    }
5480                }
5481            }
5482        }
5483
5484        fn same_column(
5485            left: &crate::expressions::Column,
5486            right: &crate::expressions::Column,
5487        ) -> bool {
5488            let same_identifier =
5489                |left: &Identifier, right: &Identifier| left.name.eq_ignore_ascii_case(&right.name);
5490
5491            same_identifier(&left.name, &right.name)
5492                && match (&left.table, &right.table) {
5493                    (Some(left), Some(right)) => same_identifier(left, right),
5494                    (None, None) => true,
5495                    _ => false,
5496                }
5497        }
5498
5499        fn aggregate_is_invalid(expr: &Expression, local_sources: &HashSet<String>) -> bool {
5500            let mut columns = Vec::new();
5501            collect_columns(expr, &mut columns);
5502
5503            let outer_column = columns.iter().copied().find(|column| match &column.table {
5504                Some(table) => !local_sources.contains(&table.name.to_ascii_lowercase()),
5505                // Without a local source, an unqualified reference in a lateral
5506                // query can only resolve against an outer scope.
5507                None => local_sources.is_empty(),
5508            });
5509
5510            outer_column
5511                .is_some_and(|outer| columns.iter().any(|column| !same_column(outer, column)))
5512        }
5513
5514        fn expression_has_invalid_aggregate(
5515            expr: &Expression,
5516            local_sources: &HashSet<String>,
5517        ) -> bool {
5518            if Dialect::node_is_aggregate_function(expr)
5519                && aggregate_is_invalid(expr, local_sources)
5520            {
5521                return true;
5522            }
5523
5524            match expr {
5525                Expression::Select(_)
5526                | Expression::Subquery(_)
5527                | Expression::Union(_)
5528                | Expression::Intersect(_)
5529                | Expression::Except(_) => false,
5530                _ => expr
5531                    .children()
5532                    .into_iter()
5533                    .any(|child| expression_has_invalid_aggregate(child, local_sources)),
5534            }
5535        }
5536
5537        fn select_has_invalid_aggregate(select: &Select) -> bool {
5538            let mut local_sources = HashSet::new();
5539            if let Some(from) = &select.from {
5540                for source in &from.expressions {
5541                    collect_source_names(source, &mut local_sources);
5542                }
5543            }
5544            for join in &select.joins {
5545                collect_source_names(&join.this, &mut local_sources);
5546            }
5547
5548            let invalid =
5549                |expr: &Expression| expression_has_invalid_aggregate(expr, &local_sources);
5550
5551            select.expressions.iter().any(invalid)
5552                || select.prewhere.as_ref().is_some_and(invalid)
5553                || select
5554                    .where_clause
5555                    .as_ref()
5556                    .is_some_and(|where_clause| invalid(&where_clause.this))
5557                || select
5558                    .group_by
5559                    .as_ref()
5560                    .is_some_and(|group_by| group_by.expressions.iter().any(invalid))
5561                || select
5562                    .having
5563                    .as_ref()
5564                    .is_some_and(|having| invalid(&having.this))
5565                || select
5566                    .qualify
5567                    .as_ref()
5568                    .is_some_and(|qualify| invalid(&qualify.this))
5569                || select.order_by.as_ref().is_some_and(|order_by| {
5570                    order_by
5571                        .expressions
5572                        .iter()
5573                        .any(|ordered| invalid(&ordered.this))
5574                })
5575        }
5576
5577        fn apply_rhs_is_invalid(rhs: &Expression) -> bool {
5578            rhs.dfs().any(|node| match node {
5579                Expression::Select(select) => select_has_invalid_aggregate(select),
5580                _ => false,
5581            })
5582        }
5583
5584        fn joins_have_invalid_aggregate(joins: &[Join]) -> bool {
5585            joins.iter().any(|join| {
5586                matches!(
5587                    join.kind,
5588                    crate::expressions::JoinKind::CrossApply
5589                        | crate::expressions::JoinKind::OuterApply
5590                ) && apply_rhs_is_invalid(&join.this)
5591            })
5592        }
5593
5594        expr.dfs().any(|node| match node {
5595            Expression::Select(select) => joins_have_invalid_aggregate(&select.joins),
5596            Expression::JoinedTable(joined) => joins_have_invalid_aggregate(&joined.joins),
5597            Expression::Update(update) => {
5598                joins_have_invalid_aggregate(&update.table_joins)
5599                    || joins_have_invalid_aggregate(&update.from_joins)
5600            }
5601            Expression::Delete(delete) => joins_have_invalid_aggregate(&delete.joins),
5602            _ => false,
5603        })
5604    }
5605
5606    fn target_supports_remaining_unnest(target: DialectType) -> bool {
5607        matches!(
5608            target,
5609            DialectType::PostgreSQL
5610                | DialectType::BigQuery
5611                | DialectType::DuckDB
5612                | DialectType::Presto
5613                | DialectType::Trino
5614                | DialectType::Athena
5615        )
5616    }
5617
5618    fn target_supports_remaining_explode(target: DialectType) -> bool {
5619        matches!(
5620            target,
5621            DialectType::Spark | DialectType::Databricks | DialectType::Hive
5622        )
5623    }
5624
5625    fn target_lacks_array_agg(target: DialectType) -> bool {
5626        matches!(
5627            target,
5628            DialectType::Fabric
5629                | DialectType::TSQL
5630                | DialectType::MySQL
5631                | DialectType::SQLite
5632                | DialectType::Oracle
5633        )
5634    }
5635
5636    fn node_is_unnest(expr: &Expression) -> bool {
5637        matches!(expr, Expression::Unnest(_)) || Self::node_is_function_named(expr, "UNNEST")
5638    }
5639
5640    fn node_is_explode(expr: &Expression) -> bool {
5641        matches!(expr, Expression::Explode(_) | Expression::ExplodeOuter(_))
5642            || Self::node_is_function_named(expr, "EXPLODE")
5643            || Self::node_is_function_named(expr, "EXPLODE_OUTER")
5644    }
5645
5646    fn node_is_array_agg(expr: &Expression) -> bool {
5647        matches!(expr, Expression::ArrayAgg(_)) || Self::node_is_function_named(expr, "ARRAY_AGG")
5648    }
5649
5650    fn node_is_distinct_string_agg(expr: &Expression) -> bool {
5651        match expr {
5652            Expression::StringAgg(agg) => agg.distinct,
5653            Expression::Function(function) => {
5654                function.distinct && function.name.eq_ignore_ascii_case("STRING_AGG")
5655            }
5656            Expression::AggregateFunction(function) => {
5657                function.distinct && function.name.eq_ignore_ascii_case("STRING_AGG")
5658            }
5659            _ => false,
5660        }
5661    }
5662
5663    fn postgres_tsql_unsupported_collation_name(expr: &Expression) -> Option<&'static str> {
5664        let Expression::Collation(collation) = expr else {
5665            return None;
5666        };
5667
5668        if collation.collation.eq_ignore_ascii_case("C") {
5669            Some("C")
5670        } else if collation.collation.eq_ignore_ascii_case("POSIX") {
5671            Some("POSIX")
5672        } else {
5673            None
5674        }
5675    }
5676
5677    fn collect_tsql_grouping_tuple_nodes(expr: &Expression) -> HashSet<*const Expression> {
5678        let mut tuples = HashSet::new();
5679
5680        for node in expr.dfs() {
5681            let Expression::Select(select) = node else {
5682                continue;
5683            };
5684            let Some(group_by) = &select.group_by else {
5685                continue;
5686            };
5687
5688            for expression in &group_by.expressions {
5689                Self::collect_tsql_grouping_element_tuples(expression, &mut tuples);
5690            }
5691        }
5692
5693        tuples
5694    }
5695
5696    fn collect_tsql_grouping_element_tuples(
5697        expr: &Expression,
5698        tuples: &mut HashSet<*const Expression>,
5699    ) {
5700        match expr {
5701            Expression::GroupingSets(grouping_sets) => {
5702                for expression in &grouping_sets.expressions {
5703                    Self::collect_tsql_grouping_unit_tuples(expression, tuples);
5704                }
5705            }
5706            Expression::Rollup(rollup) => {
5707                for expression in &rollup.expressions {
5708                    Self::collect_tsql_grouping_unit_tuples(expression, tuples);
5709                }
5710            }
5711            Expression::Cube(cube) => {
5712                for expression in &cube.expressions {
5713                    Self::collect_tsql_grouping_unit_tuples(expression, tuples);
5714                }
5715            }
5716            Expression::Function(function)
5717                if !function.quoted
5718                    && (function.name.eq_ignore_ascii_case("GROUPING SETS")
5719                        || function.name.eq_ignore_ascii_case("ROLLUP")
5720                        || function.name.eq_ignore_ascii_case("CUBE")) =>
5721            {
5722                for expression in &function.args {
5723                    Self::collect_tsql_grouping_unit_tuples(expression, tuples);
5724                }
5725            }
5726            _ => {}
5727        }
5728    }
5729
5730    fn collect_tsql_grouping_unit_tuples(
5731        expr: &Expression,
5732        tuples: &mut HashSet<*const Expression>,
5733    ) {
5734        match expr {
5735            Expression::Tuple(tuple) => {
5736                tuples.insert(expr as *const Expression);
5737                for expression in &tuple.expressions {
5738                    match expression {
5739                        Expression::Tuple(_) | Expression::Paren(_) => {
5740                            Self::collect_tsql_grouping_unit_tuples(expression, tuples);
5741                        }
5742                        Expression::GroupingSets(_)
5743                        | Expression::Rollup(_)
5744                        | Expression::Cube(_) => {
5745                            Self::collect_tsql_grouping_element_tuples(expression, tuples);
5746                        }
5747                        Expression::Function(function)
5748                            if !function.quoted
5749                                && (function.name.eq_ignore_ascii_case("GROUPING SETS")
5750                                    || function.name.eq_ignore_ascii_case("ROLLUP")
5751                                    || function.name.eq_ignore_ascii_case("CUBE")) =>
5752                        {
5753                            Self::collect_tsql_grouping_element_tuples(expression, tuples);
5754                        }
5755                        _ => {}
5756                    }
5757                }
5758            }
5759            Expression::Paren(paren) => {
5760                Self::collect_tsql_grouping_unit_tuples(&paren.this, tuples);
5761            }
5762            Expression::GroupingSets(_) | Expression::Rollup(_) | Expression::Cube(_) => {
5763                Self::collect_tsql_grouping_element_tuples(expr, tuples);
5764            }
5765            Expression::Function(function)
5766                if !function.quoted
5767                    && (function.name.eq_ignore_ascii_case("GROUPING SETS")
5768                        || function.name.eq_ignore_ascii_case("ROLLUP")
5769                        || function.name.eq_ignore_ascii_case("CUBE")) =>
5770            {
5771                Self::collect_tsql_grouping_element_tuples(expr, tuples);
5772            }
5773            _ => {}
5774        }
5775    }
5776
5777    fn postgres_tsql_unsupported_composite_semantics(
5778        expr: &Expression,
5779        structural_grouping_tuple: bool,
5780    ) -> Option<&'static str> {
5781        match expr {
5782            Expression::Tuple(_) if !structural_grouping_tuple => Some("row/composite values"),
5783            Expression::Struct(_) | Expression::StructFunc(_) => Some("row/composite values"),
5784            Expression::Function(function)
5785                if !function.quoted && function.name.eq_ignore_ascii_case("ROW") =>
5786            {
5787                Some("row/composite values")
5788            }
5789            Expression::StructExtract(_) => Some("row/composite field access"),
5790            Expression::Cast(cast) | Expression::TryCast(cast) | Expression::SafeCast(cast) if matches!(&cast.this, Expression::Star(star) if star.table.is_some()) => {
5791                Some("qualified whole-row casts")
5792            }
5793            _ => None,
5794        }
5795    }
5796
5797    fn node_is_postgres_unknown_cast(expr: &Expression) -> bool {
5798        match expr {
5799            Expression::Cast(cast) | Expression::TryCast(cast) | Expression::SafeCast(cast) => {
5800                normalization::is_postgres_unknown_type(&cast.to)
5801            }
5802            _ => false,
5803        }
5804    }
5805
5806    fn postgres_tsql_unsupported_array_semantics(expr: &Expression) -> Option<&'static str> {
5807        match expr {
5808            Expression::Array(_) | Expression::ArrayFunc(_) => Some("array literals"),
5809            Expression::Subscript(_) => Some("array subscripts"),
5810            Expression::ArraySlice(_) => Some("array slices"),
5811            Expression::DataType(DataType::Array { .. }) => Some("array data types"),
5812            Expression::Cast(cast) | Expression::TryCast(cast) | Expression::SafeCast(cast)
5813                if matches!(&cast.to, DataType::Array { .. }) =>
5814            {
5815                Some("array data types")
5816            }
5817            Expression::ArrayLength(_) | Expression::ArraySize(_) => Some("ARRAY_LENGTH"),
5818            Expression::Cardinality(_) => Some("CARDINALITY"),
5819            Expression::ArrayToString(_) | Expression::ArrayJoin(_) => Some("ARRAY_TO_STRING"),
5820            Expression::StringToArray(_) => Some("STRING_TO_ARRAY"),
5821            Expression::ArrayContains(_)
5822            | Expression::ArrayPosition(_)
5823            | Expression::ArrayAppend(_)
5824            | Expression::ArrayPrepend(_)
5825            | Expression::ArrayConcat(_)
5826            | Expression::ArraySort(_)
5827            | Expression::ArrayReverse(_)
5828            | Expression::ArrayDistinct(_)
5829            | Expression::ArrayFilter(_)
5830            | Expression::ArrayTransform(_)
5831            | Expression::ArrayFlatten(_)
5832            | Expression::ArrayCompact(_)
5833            | Expression::ArrayIntersect(_)
5834            | Expression::ArrayUnion(_)
5835            | Expression::ArrayExcept(_)
5836            | Expression::ArrayRemove(_)
5837            | Expression::ArrayZip(_)
5838            | Expression::ArrayAll(_)
5839            | Expression::ArrayAny(_)
5840            | Expression::ArrayConstructCompact(_)
5841            | Expression::ArraySum(_) => Some("array functions"),
5842            Expression::ArrayContainsAll(_)
5843            | Expression::ArrayContainedBy(_)
5844            | Expression::ArrayOverlaps(_) => Some("array operators"),
5845            Expression::Function(function) => {
5846                Self::postgres_tsql_unsupported_array_function_name_str(&function.name)
5847            }
5848            Expression::AggregateFunction(function) => {
5849                Self::postgres_tsql_unsupported_array_function_name_str(&function.name)
5850            }
5851            _ => None,
5852        }
5853    }
5854
5855    fn postgres_tsql_unsupported_array_function_name_str(name: &str) -> Option<&'static str> {
5856        if name.eq_ignore_ascii_case("ARRAY") {
5857            Some("array literals")
5858        } else if name.eq_ignore_ascii_case("ARRAY_LENGTH")
5859            || name.eq_ignore_ascii_case("ARRAY_SIZE")
5860        {
5861            Some("ARRAY_LENGTH")
5862        } else if name.eq_ignore_ascii_case("CARDINALITY") {
5863            Some("CARDINALITY")
5864        } else if name.eq_ignore_ascii_case("ARRAY_TO_STRING")
5865            || name.eq_ignore_ascii_case("ARRAY_JOIN")
5866        {
5867            Some("ARRAY_TO_STRING")
5868        } else if name.eq_ignore_ascii_case("STRING_TO_ARRAY") {
5869            Some("STRING_TO_ARRAY")
5870        } else {
5871            None
5872        }
5873    }
5874
5875    fn node_is_regex_predicate(expr: &Expression) -> bool {
5876        matches!(
5877            expr,
5878            Expression::SimilarTo(_) | Expression::RegexpLike(_) | Expression::RegexpILike(_)
5879        ) || Self::node_is_function_named(expr, "REGEXP_LIKE")
5880            || Self::node_is_function_named(expr, "REGEXP_I_LIKE")
5881            || Self::node_is_function_named(expr, "REGEXP_ILIKE")
5882    }
5883
5884    fn node_is_non_subquery_any(expr: &Expression) -> bool {
5885        matches!(
5886            expr,
5887            Expression::Any(q) if !Self::quantified_rhs_is_subquery(&q.subquery)
5888        )
5889    }
5890
5891    fn quantified_rhs_is_subquery(expr: &Expression) -> bool {
5892        match expr {
5893            Expression::Select(_) | Expression::Subquery(_) => true,
5894            Expression::Paren(paren) => Self::quantified_rhs_is_subquery(&paren.this),
5895            _ => false,
5896        }
5897    }
5898
5899    fn node_is_row_value_subquery_comparison(expr: &Expression) -> bool {
5900        match expr {
5901            Expression::In(in_expr) => {
5902                Self::in_rhs_is_subquery_like(in_expr) && Self::expr_is_row_value(&in_expr.this)
5903            }
5904            Expression::Eq(op) | Expression::Neq(op) => {
5905                (Self::expr_is_row_value(&op.left) && Self::expr_is_subquery_like(&op.right))
5906                    || (Self::expr_is_row_value(&op.right) && Self::expr_is_subquery_like(&op.left))
5907            }
5908            _ => false,
5909        }
5910    }
5911
5912    fn node_is_row_value_values_membership(expr: &Expression) -> bool {
5913        matches!(
5914            expr,
5915            Expression::In(in_expr)
5916                if Self::expr_is_row_value(&in_expr.this)
5917                    && Self::in_rhs_is_values_like(in_expr)
5918        )
5919    }
5920
5921    fn expr_is_row_value(expr: &Expression) -> bool {
5922        match expr {
5923            Expression::Tuple(tuple) => tuple.expressions.len() > 1,
5924            Expression::Function(function) if function.name.eq_ignore_ascii_case("ROW") => {
5925                function.args.len() > 1
5926            }
5927            Expression::Paren(paren) => Self::expr_is_row_value(&paren.this),
5928            _ => false,
5929        }
5930    }
5931
5932    fn expr_is_subquery_like(expr: &Expression) -> bool {
5933        match expr {
5934            Expression::Select(_) | Expression::Subquery(_) => true,
5935            Expression::Paren(paren) => Self::expr_is_subquery_like(&paren.this),
5936            _ => false,
5937        }
5938    }
5939
5940    fn in_rhs_is_subquery_like(in_expr: &crate::expressions::In) -> bool {
5941        if in_expr
5942            .query
5943            .as_ref()
5944            .is_some_and(Self::expr_is_subquery_like)
5945        {
5946            return true;
5947        }
5948
5949        in_expr.expressions.len() == 1 && Self::expr_is_subquery_like(&in_expr.expressions[0])
5950    }
5951
5952    fn in_rhs_is_values_like(in_expr: &crate::expressions::In) -> bool {
5953        if in_expr
5954            .query
5955            .as_ref()
5956            .is_some_and(Self::expr_is_values_like)
5957        {
5958            return true;
5959        }
5960
5961        (in_expr.expressions.len() == 1
5962            && Self::expr_is_values_like(&in_expr.expressions[0]))
5963            || in_expr.expressions.first().is_some_and(|expr| {
5964                matches!(expr, Expression::Function(function) if function.name.eq_ignore_ascii_case("VALUES"))
5965            })
5966    }
5967
5968    fn expr_is_values_like(expr: &Expression) -> bool {
5969        match expr {
5970            Expression::Values(_) => true,
5971            Expression::Paren(paren) => Self::expr_is_values_like(&paren.this),
5972            Expression::Subquery(subquery) => Self::expr_is_values_like(&subquery.this),
5973            _ => false,
5974        }
5975    }
5976
5977    fn normalize_tsql_fetch_overlaps_date_bin(expr: Expression) -> Result<Expression> {
5978        transform_recursive(expr, &|e| match e {
5979            Expression::Select(mut select) => {
5980                if select.top.is_none() && select.offset.is_none() {
5981                    if let Some(fetch) = select.fetch.take() {
5982                        if let Some(top) = Self::fetch_with_ties_to_top(fetch.clone()) {
5983                            select.top = Some(top);
5984                        } else {
5985                            select.fetch = Some(fetch);
5986                        }
5987                    }
5988                }
5989                Self::rewrite_tsql_overlaps_in_select_predicates(&mut select)?;
5990                Ok(Expression::Select(select))
5991            }
5992            Expression::DateBin(date_bin) => {
5993                let date_bin = *date_bin;
5994                if let Some(rewritten) = Self::date_bin_to_date_bucket(date_bin.clone()) {
5995                    Ok(rewritten)
5996                } else {
5997                    Ok(Expression::DateBin(Box::new(date_bin)))
5998                }
5999            }
6000            Expression::Function(function) => {
6001                let function = *function;
6002                if function.name.eq_ignore_ascii_case("DATE_BIN") {
6003                    if let Some(rewritten) = Self::date_bin_function_to_date_bucket(&function) {
6004                        Ok(rewritten)
6005                    } else {
6006                        Ok(Expression::Function(Box::new(function)))
6007                    }
6008                } else {
6009                    Ok(Expression::Function(Box::new(function)))
6010                }
6011            }
6012            _ => Ok(e),
6013        })
6014    }
6015
6016    fn rewrite_tsql_overlaps_in_select_predicates(
6017        select: &mut crate::expressions::Select,
6018    ) -> Result<()> {
6019        if let Some(where_clause) = &mut select.where_clause {
6020            where_clause.this = Self::rewrite_tsql_overlaps_predicate(where_clause.this.clone())?;
6021        }
6022        if let Some(having) = &mut select.having {
6023            having.this = Self::rewrite_tsql_overlaps_predicate(having.this.clone())?;
6024        }
6025        if let Some(qualify) = &mut select.qualify {
6026            qualify.this = Self::rewrite_tsql_overlaps_predicate(qualify.this.clone())?;
6027        }
6028        for join in &mut select.joins {
6029            if let Some(on) = join.on.take() {
6030                join.on = Some(Self::rewrite_tsql_overlaps_predicate(on)?);
6031            }
6032            if let Some(match_condition) = join.match_condition.take() {
6033                join.match_condition =
6034                    Some(Self::rewrite_tsql_overlaps_predicate(match_condition)?);
6035            }
6036        }
6037        Ok(())
6038    }
6039
6040    fn rewrite_tsql_overlaps_predicate(expr: Expression) -> Result<Expression> {
6041        transform_recursive(expr, &|e| match e {
6042            Expression::Overlaps(overlaps) => {
6043                let overlaps = *overlaps;
6044                if let Some(rewritten) = Self::rewrite_full_overlaps_for_tsql(&overlaps) {
6045                    Ok(rewritten)
6046                } else {
6047                    Ok(Expression::Overlaps(Box::new(overlaps)))
6048                }
6049            }
6050            _ => Ok(e),
6051        })
6052    }
6053
6054    fn fetch_with_ties_to_top(fetch: Fetch) -> Option<Top> {
6055        if !fetch.with_ties {
6056            return None;
6057        }
6058
6059        fetch.count.map(|count| Top {
6060            this: count,
6061            percent: fetch.percent,
6062            with_ties: true,
6063            parenthesized: true,
6064        })
6065    }
6066
6067    fn rewrite_full_overlaps_for_tsql(
6068        overlaps: &crate::expressions::OverlapsExpr,
6069    ) -> Option<Expression> {
6070        let (left_start, left_end, right_start, right_end) =
6071            if let (Some(left_start), Some(left_end), Some(right_start), Some(right_end)) = (
6072                overlaps.left_start.as_ref(),
6073                overlaps.left_end.as_ref(),
6074                overlaps.right_start.as_ref(),
6075                overlaps.right_end.as_ref(),
6076            ) {
6077                (left_start, left_end, right_start, right_end)
6078            } else if let (
6079                Some(Expression::Tuple(left_tuple)),
6080                Some(Expression::Tuple(right_tuple)),
6081            ) = (&overlaps.this, &overlaps.expression)
6082            {
6083                if left_tuple.expressions.len() != 2 || right_tuple.expressions.len() != 2 {
6084                    return None;
6085                }
6086                (
6087                    &left_tuple.expressions[0],
6088                    &left_tuple.expressions[1],
6089                    &right_tuple.expressions[0],
6090                    &right_tuple.expressions[1],
6091                )
6092            } else {
6093                return None;
6094            };
6095
6096        let left_min = Self::case_min(left_start.clone(), left_end.clone());
6097        let left_max = Self::case_max(left_start.clone(), left_end.clone());
6098        let right_min = Self::case_min(right_start.clone(), right_end.clone());
6099        let right_max = Self::case_max(right_start.clone(), right_end.clone());
6100
6101        Some(Expression::And(Box::new(BinaryOp::new(
6102            Expression::Lte(Box::new(BinaryOp::new(left_min, right_max))),
6103            Expression::Lte(Box::new(BinaryOp::new(right_min, left_max))),
6104        ))))
6105    }
6106
6107    fn case_min(left: Expression, right: Expression) -> Expression {
6108        Expression::Case(Box::new(Case {
6109            operand: None,
6110            whens: vec![(
6111                Expression::Lte(Box::new(BinaryOp::new(left.clone(), right.clone()))),
6112                left,
6113            )],
6114            else_: Some(right),
6115            comments: Vec::new(),
6116            inferred_type: None,
6117        }))
6118    }
6119
6120    fn case_max(left: Expression, right: Expression) -> Expression {
6121        Expression::Case(Box::new(Case {
6122            operand: None,
6123            whens: vec![(
6124                Expression::Gte(Box::new(BinaryOp::new(left.clone(), right.clone()))),
6125                left,
6126            )],
6127            else_: Some(right),
6128            comments: Vec::new(),
6129            inferred_type: None,
6130        }))
6131    }
6132
6133    fn date_bin_to_date_bucket(date_bin: DateBin) -> Option<Expression> {
6134        if date_bin.unit.is_some() || date_bin.zone.is_some() {
6135            return None;
6136        }
6137
6138        let (datepart, number) = Self::date_bucket_parts(&date_bin.this)?;
6139        let mut args = vec![
6140            Self::date_bucket_datepart(datepart),
6141            number,
6142            *date_bin.expression,
6143        ];
6144        if let Some(origin) = date_bin.origin {
6145            args.push(*origin);
6146        }
6147
6148        Some(Expression::Function(Box::new(Function::new(
6149            "DATE_BUCKET".to_string(),
6150            args,
6151        ))))
6152    }
6153
6154    fn date_bin_function_to_date_bucket(function: &Function) -> Option<Expression> {
6155        if !(2..=3).contains(&function.args.len()) {
6156            return None;
6157        }
6158
6159        let (datepart, number) = Self::date_bucket_parts(&function.args[0])?;
6160        let mut args = vec![
6161            Self::date_bucket_datepart(datepart),
6162            number,
6163            function.args[1].clone(),
6164        ];
6165        if let Some(origin) = function.args.get(2) {
6166            args.push(origin.clone());
6167        }
6168
6169        Some(Expression::Function(Box::new(Function::new(
6170            "DATE_BUCKET".to_string(),
6171            args,
6172        ))))
6173    }
6174
6175    fn date_bucket_parts(stride: &Expression) -> Option<(&'static str, Expression)> {
6176        match stride {
6177            Expression::Literal(lit) => match lit.as_ref() {
6178                Literal::String(value) => Self::date_bucket_parts_from_string(value),
6179                _ => None,
6180            },
6181            Expression::Interval(interval) => Self::date_bucket_parts_from_interval(interval),
6182            _ => None,
6183        }
6184    }
6185
6186    fn date_bucket_parts_from_interval(interval: &Interval) -> Option<(&'static str, Expression)> {
6187        match &interval.unit {
6188            Some(IntervalUnitSpec::Simple { unit, .. }) => {
6189                let datepart = Self::date_bucket_datepart_from_unit(*unit)?;
6190                let amount = interval
6191                    .this
6192                    .as_ref()
6193                    .and_then(Self::date_bucket_amount_expr)?;
6194                Some((datepart, amount))
6195            }
6196            None => interval.this.as_ref().and_then(|expr| match expr {
6197                Expression::Literal(lit) => match lit.as_ref() {
6198                    Literal::String(value) => Self::date_bucket_parts_from_string(value),
6199                    _ => None,
6200                },
6201                _ => None,
6202            }),
6203            _ => None,
6204        }
6205    }
6206
6207    fn date_bucket_parts_from_string(value: &str) -> Option<(&'static str, Expression)> {
6208        let mut parts = value.split_whitespace();
6209        let amount = parts.next()?;
6210        let unit = parts.next()?;
6211        if parts.next().is_some() {
6212            return None;
6213        }
6214
6215        Some((
6216            Self::date_bucket_datepart_from_name(unit)?,
6217            Self::positive_integer_expr(amount)?,
6218        ))
6219    }
6220
6221    fn date_bucket_amount_expr(expr: &Expression) -> Option<Expression> {
6222        match expr {
6223            Expression::Literal(lit) => match lit.as_ref() {
6224                Literal::Number(value) => Self::positive_integer_expr(value),
6225                Literal::String(value) => Self::positive_integer_expr(value),
6226                _ => None,
6227            },
6228            _ => Some(expr.clone()),
6229        }
6230    }
6231
6232    fn positive_integer_expr(value: &str) -> Option<Expression> {
6233        let parsed = value.trim().parse::<i64>().ok()?;
6234        (parsed > 0).then(|| Expression::number(parsed))
6235    }
6236
6237    fn date_bucket_datepart(datepart: &str) -> Expression {
6238        Expression::Var(Box::new(Var {
6239            this: datepart.to_string(),
6240        }))
6241    }
6242
6243    fn date_bucket_datepart_from_unit(unit: IntervalUnit) -> Option<&'static str> {
6244        match unit {
6245            IntervalUnit::Week => Some("WEEK"),
6246            IntervalUnit::Day => Some("DAY"),
6247            IntervalUnit::Hour => Some("HOUR"),
6248            IntervalUnit::Minute => Some("MINUTE"),
6249            IntervalUnit::Second => Some("SECOND"),
6250            IntervalUnit::Millisecond => Some("MILLISECOND"),
6251            _ => None,
6252        }
6253    }
6254
6255    fn date_bucket_datepart_from_name(unit: &str) -> Option<&'static str> {
6256        match unit.trim().to_ascii_uppercase().as_str() {
6257            "WEEK" | "WEEKS" | "W" | "WK" | "WKS" | "WW" => Some("WEEK"),
6258            "DAY" | "DAYS" | "D" | "DD" => Some("DAY"),
6259            "HOUR" | "HOURS" | "H" | "HH" | "HR" | "HRS" => Some("HOUR"),
6260            "MINUTE" | "MINUTES" | "MI" | "MIN" | "MINS" | "N" => Some("MINUTE"),
6261            "SECOND" | "SECONDS" | "S" | "SEC" | "SECS" | "SS" => Some("SECOND"),
6262            "MILLISECOND" | "MILLISECONDS" | "MS" | "MSEC" | "MSECS" | "MILLISEC" | "MILLISECS" => {
6263                Some("MILLISECOND")
6264            }
6265            _ => None,
6266        }
6267    }
6268
6269    fn node_has_fetch_with_ties(expr: &Expression) -> bool {
6270        matches!(
6271            expr,
6272            Expression::Select(select)
6273                if select
6274                    .fetch
6275                    .as_ref()
6276                    .is_some_and(|fetch| fetch.with_ties)
6277        )
6278    }
6279
6280    fn node_is_overlaps(expr: &Expression) -> bool {
6281        matches!(expr, Expression::Overlaps(_))
6282    }
6283
6284    fn node_is_date_bin(expr: &Expression) -> bool {
6285        matches!(expr, Expression::DateBin(_)) || Self::node_is_function_named(expr, "DATE_BIN")
6286    }
6287
6288    fn node_is_function_named(expr: &Expression, name: &str) -> bool {
6289        match expr {
6290            Expression::Function(function) => function.name.eq_ignore_ascii_case(name),
6291            Expression::AggregateFunction(function) => function.name.eq_ignore_ascii_case(name),
6292            _ => false,
6293        }
6294    }
6295
6296    fn node_is_postgres_json_build_object(expr: &Expression) -> bool {
6297        match expr {
6298            Expression::Function(function) => {
6299                function.name.eq_ignore_ascii_case("JSON_BUILD_OBJECT")
6300                    || function.name.eq_ignore_ascii_case("JSONB_BUILD_OBJECT")
6301            }
6302            _ => false,
6303        }
6304    }
6305
6306    fn postgres_json_build_object_can_lower_to_json_object(expr: &Expression) -> bool {
6307        matches!(
6308            expr,
6309            Expression::Function(function)
6310                if (function.name.eq_ignore_ascii_case("JSON_BUILD_OBJECT")
6311                    || function.name.eq_ignore_ascii_case("JSONB_BUILD_OBJECT"))
6312                    && !function.distinct
6313                    && function.args.len() % 2 == 0
6314        )
6315    }
6316
6317    fn node_is_postgres_json_array_elements(expr: &Expression) -> bool {
6318        matches!(
6319            expr,
6320            Expression::Function(function)
6321                if function.name.eq_ignore_ascii_case("JSON_ARRAY_ELEMENTS")
6322                    || function.name.eq_ignore_ascii_case("JSONB_ARRAY_ELEMENTS")
6323                    || function.name.eq_ignore_ascii_case("JSON_ARRAY_ELEMENTS_TEXT")
6324                    || function.name.eq_ignore_ascii_case("JSONB_ARRAY_ELEMENTS_TEXT")
6325        )
6326    }
6327
6328    fn postgres_tsql_unsupported_function_name(
6329        expr: &Expression,
6330        target: DialectType,
6331    ) -> Option<&'static str> {
6332        match expr {
6333            Expression::Lpad(_) => Some("LPAD"),
6334            Expression::Rpad(_) => Some("RPAD"),
6335            Expression::SplitPart(_) => Some("SPLIT_PART"),
6336            Expression::Initcap(_) => Some("INITCAP"),
6337            Expression::RegexpReplace(_) => Some("REGEXP_REPLACE"),
6338            Expression::RegexpInstr(_) => Some("REGEXP_INSTR"),
6339            Expression::RegexpCount(_) => Some("REGEXP_COUNT"),
6340            Expression::RegexpSplit(_) => Some("REGEXP_SPLIT"),
6341            Expression::DecodeCase(_) => Some("DECODE"),
6342            Expression::ToJson(_) => Some("TO_JSON"),
6343            Expression::JSONBObjectAgg(_) => Some("JSONB_OBJECT_AGG"),
6344            Expression::ToNumber(_) => Some("TO_NUMBER"),
6345            Expression::WidthBucket(_) => Some("WIDTH_BUCKET"),
6346            Expression::BitwiseAndAgg(_) => Some("BIT_AND"),
6347            Expression::BitwiseOrAgg(_) => Some("BIT_OR"),
6348            Expression::BitwiseXorAgg(_) => Some("BIT_XOR"),
6349            Expression::Corr(_) => Some("CORR"),
6350            Expression::CovarPop(_) => Some("COVAR_POP"),
6351            Expression::CovarSamp(_) => Some("COVAR_SAMP"),
6352            Expression::RegrAvgx(_) => Some("REGR_AVGX"),
6353            Expression::RegrAvgy(_) => Some("REGR_AVGY"),
6354            Expression::RegrCount(_) => Some("REGR_COUNT"),
6355            Expression::RegrIntercept(_) => Some("REGR_INTERCEPT"),
6356            Expression::RegrR2(_) => Some("REGR_R2"),
6357            Expression::RegrSlope(_) => Some("REGR_SLOPE"),
6358            Expression::RegrSxx(_) => Some("REGR_SXX"),
6359            Expression::RegrSxy(_) => Some("REGR_SXY"),
6360            Expression::RegrSyy(_) => Some("REGR_SYY"),
6361            Expression::Function(function) => {
6362                Self::postgres_tsql_unsupported_function_name_str(&function.name, target)
6363            }
6364            Expression::AggregateFunction(function) => {
6365                Self::postgres_tsql_unsupported_function_name_str(&function.name, target)
6366            }
6367            _ => None,
6368        }
6369    }
6370
6371    fn postgres_tsql_unsupported_function_name_str(
6372        name: &str,
6373        target: DialectType,
6374    ) -> Option<&'static str> {
6375        if name.eq_ignore_ascii_case("LPAD") {
6376            Some("LPAD")
6377        } else if name.eq_ignore_ascii_case("RPAD") {
6378            Some("RPAD")
6379        } else if name.eq_ignore_ascii_case("SPLIT_PART") {
6380            Some("SPLIT_PART")
6381        } else if name.eq_ignore_ascii_case("INITCAP") {
6382            Some("INITCAP")
6383        } else if name.eq_ignore_ascii_case("TO_JSON") {
6384            Some("TO_JSON")
6385        } else if name.eq_ignore_ascii_case("TO_JSONB") {
6386            Some("TO_JSONB")
6387        } else if name.eq_ignore_ascii_case("JSONB_OBJECT_AGG") {
6388            Some("JSONB_OBJECT_AGG")
6389        } else if name.eq_ignore_ascii_case("ROW_TO_JSON") {
6390            Some("ROW_TO_JSON")
6391        } else if name.eq_ignore_ascii_case("JSON_ARRAY_ELEMENTS") {
6392            Some("JSON_ARRAY_ELEMENTS")
6393        } else if name.eq_ignore_ascii_case("JSONB_ARRAY_ELEMENTS") {
6394            Some("JSONB_ARRAY_ELEMENTS")
6395        } else if name.eq_ignore_ascii_case("JSON_ARRAY_ELEMENTS_TEXT") {
6396            Some("JSON_ARRAY_ELEMENTS_TEXT")
6397        } else if name.eq_ignore_ascii_case("JSONB_ARRAY_ELEMENTS_TEXT") {
6398            Some("JSONB_ARRAY_ELEMENTS_TEXT")
6399        } else if name.eq_ignore_ascii_case("ENCODE") {
6400            Some("ENCODE")
6401        } else if name.eq_ignore_ascii_case("DECODE") {
6402            Some("DECODE")
6403        } else if name.eq_ignore_ascii_case("REGEXP_REPLACE") {
6404            Some("REGEXP_REPLACE")
6405        } else if name.eq_ignore_ascii_case("REGEXP_COUNT") {
6406            Some("REGEXP_COUNT")
6407        } else if name.eq_ignore_ascii_case("REGEXP_INSTR") {
6408            Some("REGEXP_INSTR")
6409        } else if name.eq_ignore_ascii_case("REGEXP_SUBSTR") {
6410            Some("REGEXP_SUBSTR")
6411        } else if name.eq_ignore_ascii_case("REGEXP_SPLIT") {
6412            Some("REGEXP_SPLIT")
6413        } else if name.eq_ignore_ascii_case("REGEXP_SPLIT_TO_ARRAY") {
6414            Some("REGEXP_SPLIT_TO_ARRAY")
6415        } else if name.eq_ignore_ascii_case("REGEXP_SPLIT_TO_TABLE") {
6416            Some("REGEXP_SPLIT_TO_TABLE")
6417        } else if name.eq_ignore_ascii_case("SHA224") {
6418            Some("SHA224")
6419        } else if name.eq_ignore_ascii_case("SHA384") {
6420            Some("SHA384")
6421        } else if name.eq_ignore_ascii_case("TO_BIN") {
6422            Some("TO_BIN")
6423        } else if name.eq_ignore_ascii_case("TO_OCT") {
6424            Some("TO_OCT")
6425        } else if target == DialectType::TSQL && name.eq_ignore_ascii_case("UNISTR") {
6426            Some("UNISTR")
6427        } else if name.eq_ignore_ascii_case("AGE") {
6428            Some("AGE")
6429        } else if name.eq_ignore_ascii_case("ERF") {
6430            Some("ERF")
6431        } else if name.eq_ignore_ascii_case("SINH") {
6432            Some("SINH")
6433        } else if name.eq_ignore_ascii_case("COSH") {
6434            Some("COSH")
6435        } else if name.eq_ignore_ascii_case("TANH") {
6436            Some("TANH")
6437        } else if name.eq_ignore_ascii_case("ASINH") {
6438            Some("ASINH")
6439        } else if name.eq_ignore_ascii_case("ACOSH") {
6440            Some("ACOSH")
6441        } else if name.eq_ignore_ascii_case("ATANH") {
6442            Some("ATANH")
6443        } else if name.eq_ignore_ascii_case("GCD") {
6444            Some("GCD")
6445        } else if name.eq_ignore_ascii_case("LCM") {
6446            Some("LCM")
6447        } else if name.eq_ignore_ascii_case("QUOTE_LITERAL") {
6448            Some("QUOTE_LITERAL")
6449        } else if name.eq_ignore_ascii_case("WIDTH_BUCKET") {
6450            Some("WIDTH_BUCKET")
6451        } else if name.eq_ignore_ascii_case("SCALE") {
6452            Some("SCALE")
6453        } else if name.eq_ignore_ascii_case("TRIM_SCALE") {
6454            Some("TRIM_SCALE")
6455        } else if name.eq_ignore_ascii_case("MIN_SCALE") {
6456            Some("MIN_SCALE")
6457        } else if name.eq_ignore_ascii_case("FACTORIAL") {
6458            Some("FACTORIAL")
6459        } else if name.eq_ignore_ascii_case("PG_LSN") {
6460            Some("PG_LSN")
6461        } else if name.eq_ignore_ascii_case("TO_CHAR") {
6462            Some("TO_CHAR")
6463        } else if name.eq_ignore_ascii_case("PG_TYPEOF") {
6464            Some("PG_TYPEOF")
6465        } else if name.eq_ignore_ascii_case("BIT_AND") {
6466            Some("BIT_AND")
6467        } else if name.eq_ignore_ascii_case("BIT_OR") {
6468            Some("BIT_OR")
6469        } else if name.eq_ignore_ascii_case("BIT_XOR") {
6470            Some("BIT_XOR")
6471        } else if name.eq_ignore_ascii_case("CORR") {
6472            Some("CORR")
6473        } else if name.eq_ignore_ascii_case("COVAR_POP") {
6474            Some("COVAR_POP")
6475        } else if name.eq_ignore_ascii_case("COVAR_SAMP") {
6476            Some("COVAR_SAMP")
6477        } else if name.eq_ignore_ascii_case("REGR_AVGX") {
6478            Some("REGR_AVGX")
6479        } else if name.eq_ignore_ascii_case("REGR_AVGY") {
6480            Some("REGR_AVGY")
6481        } else if name.eq_ignore_ascii_case("REGR_COUNT") {
6482            Some("REGR_COUNT")
6483        } else if name.eq_ignore_ascii_case("REGR_INTERCEPT") {
6484            Some("REGR_INTERCEPT")
6485        } else if name.eq_ignore_ascii_case("REGR_R2") {
6486            Some("REGR_R2")
6487        } else if name.eq_ignore_ascii_case("REGR_SLOPE") {
6488            Some("REGR_SLOPE")
6489        } else if name.eq_ignore_ascii_case("REGR_SXX") {
6490            Some("REGR_SXX")
6491        } else if name.eq_ignore_ascii_case("REGR_SXY") {
6492            Some("REGR_SXY")
6493        } else if name.eq_ignore_ascii_case("REGR_SYY") {
6494            Some("REGR_SYY")
6495        } else if name.eq_ignore_ascii_case("FLOAT8_ACCUM") {
6496            Some("FLOAT8_ACCUM")
6497        } else if name.eq_ignore_ascii_case("FLOAT8_REGR_ACCUM") {
6498            Some("FLOAT8_REGR_ACCUM")
6499        } else if name.eq_ignore_ascii_case("FLOAT8_COMBINE") {
6500            Some("FLOAT8_COMBINE")
6501        } else if name.eq_ignore_ascii_case("FLOAT8_REGR_COMBINE") {
6502            Some("FLOAT8_REGR_COMBINE")
6503        } else if name.eq_ignore_ascii_case("BOOLAND_STATEFUNC") {
6504            Some("BOOLAND_STATEFUNC")
6505        } else if name.eq_ignore_ascii_case("BOOLOR_STATEFUNC") {
6506            Some("BOOLOR_STATEFUNC")
6507        } else {
6508            None
6509        }
6510    }
6511
6512    fn normalize_postgres_trim_for_tsql(expr: Expression) -> Result<Expression> {
6513        transform_recursive(expr, &|e| match e {
6514            Expression::Trim(trim) => {
6515                let mut trim = *trim;
6516                trim.characters = trim.characters.map(Self::strip_postgres_text_literal_cast);
6517                match trim.position {
6518                    crate::expressions::TrimPosition::Both
6519                        if trim.position_explicit && trim.characters.is_some() =>
6520                    {
6521                        trim.position_explicit = false;
6522                        trim.sql_standard_syntax = true;
6523                        Ok(Expression::Trim(Box::new(trim)))
6524                    }
6525                    crate::expressions::TrimPosition::Leading if trim.characters.is_some() => {
6526                        let characters = trim.characters.take().expect("checked above");
6527                        Ok(Expression::Function(Box::new(Function::new(
6528                            "LTRIM",
6529                            vec![trim.this, characters],
6530                        ))))
6531                    }
6532                    crate::expressions::TrimPosition::Trailing if trim.characters.is_some() => {
6533                        let characters = trim.characters.take().expect("checked above");
6534                        Ok(Expression::Function(Box::new(Function::new(
6535                            "RTRIM",
6536                            vec![trim.this, characters],
6537                        ))))
6538                    }
6539                    _ => Ok(Expression::Trim(Box::new(trim))),
6540                }
6541            }
6542            other => Ok(other),
6543        })
6544    }
6545
6546    fn normalize_postgres_string_semantics_for_tsql(expr: Expression) -> Result<Expression> {
6547        transform_recursive(expr, &|e| match e {
6548            Expression::Like(mut op) => {
6549                Self::recover_postgres_like_escape(&mut op);
6550                Ok(Expression::Like(op))
6551            }
6552            Expression::ILike(mut op) => {
6553                Self::recover_postgres_like_escape(&mut op);
6554                Ok(Expression::ILike(op))
6555            }
6556            Expression::Substring(mut substring)
6557                if substring.length.is_none()
6558                    && Self::is_explicitly_numeric_expression(&substring.start) =>
6559            {
6560                substring.length = Some(Expression::number(i32::MAX as i64));
6561                Ok(Expression::Substring(substring))
6562            }
6563            Expression::Trim(mut trim) => {
6564                trim.characters = trim.characters.map(Self::strip_postgres_text_literal_cast);
6565                Ok(Expression::Trim(trim))
6566            }
6567            Expression::Function(mut function)
6568                if !function.quoted
6569                    && matches!(
6570                        function.name.to_ascii_uppercase().as_str(),
6571                        "BTRIM" | "LTRIM" | "RTRIM"
6572                    )
6573                    && function.args.len() == 2 =>
6574            {
6575                function.args[1] = Self::strip_postgres_text_literal_cast(function.args[1].clone());
6576                Ok(Expression::Function(function))
6577            }
6578            Expression::Translate(translate) => {
6579                Ok(Self::normalize_postgres_translate_for_tsql(*translate))
6580            }
6581            Expression::Function(function)
6582                if !function.quoted
6583                    && function.name.eq_ignore_ascii_case("TRANSLATE")
6584                    && function.args.len() == 3 =>
6585            {
6586                Ok(Self::normalize_postgres_translate_function_for_tsql(
6587                    *function,
6588                ))
6589            }
6590            other => Ok(other),
6591        })
6592    }
6593
6594    fn normalize_postgres_bytea_literals_for_tsql(expr: Expression) -> Result<Expression> {
6595        transform_recursive(expr, &|e| match e {
6596            Expression::Cast(cast) if Self::is_postgres_bytea_data_type(&cast.to) => {
6597                let Some(value) = Self::postgres_plain_string_literal_value(&cast.this) else {
6598                    return Ok(Expression::Cast(cast));
6599                };
6600                let Some(hex) = Self::postgres_bytea_hex_payload(value) else {
6601                    return Ok(Expression::Cast(cast));
6602                };
6603
6604                // Replace the complete BYTEA cast. Keeping a bare T-SQL
6605                // CAST(... AS VARBINARY) would apply SQL Server's default length
6606                // and could truncate payloads longer than 30 bytes.
6607                Ok(Expression::Literal(Box::new(Literal::HexString(hex))))
6608            }
6609            other => Ok(other),
6610        })
6611    }
6612
6613    fn postgres_plain_string_literal_value(expr: &Expression) -> Option<&str> {
6614        match expr {
6615            Expression::Literal(literal) => match literal.as_ref() {
6616                Literal::String(value) => Some(value),
6617                _ => None,
6618            },
6619            Expression::Paren(paren) => Self::postgres_plain_string_literal_value(&paren.this),
6620            _ => None,
6621        }
6622    }
6623
6624    fn postgres_bytea_hex_payload(value: &str) -> Option<String> {
6625        let payload = value.strip_prefix("\\x")?;
6626        if payload.is_empty() {
6627            return Some(String::new());
6628        }
6629
6630        let mut chars = payload.chars().peekable();
6631        let mut hex = String::with_capacity(payload.len());
6632        loop {
6633            let high = chars.next()?;
6634            let low = chars.next()?;
6635            if !high.is_ascii_hexdigit() || !low.is_ascii_hexdigit() {
6636                return None;
6637            }
6638            hex.push(high);
6639            hex.push(low);
6640
6641            let Some(next) = chars.peek().copied() else {
6642                return Some(hex);
6643            };
6644            if next.is_ascii_whitespace() {
6645                while chars
6646                    .peek()
6647                    .is_some_and(|character| character.is_ascii_whitespace())
6648                {
6649                    chars.next();
6650                }
6651                // PostgreSQL permits whitespace between byte pairs, not after
6652                // the prefix or after the final pair.
6653                chars.peek()?;
6654            }
6655        }
6656    }
6657
6658    fn is_postgres_bytea_data_type(data_type: &DataType) -> bool {
6659        match data_type {
6660            DataType::VarBinary { length: None } => true,
6661            DataType::Custom { name } => name.trim().eq_ignore_ascii_case("BYTEA"),
6662            _ => false,
6663        }
6664    }
6665
6666    fn postgres_tsql_unsupported_binary_semantics(expr: &Expression) -> Option<&'static str> {
6667        let cast = match expr {
6668            Expression::Cast(cast) | Expression::TryCast(cast) | Expression::SafeCast(cast)
6669                if Self::is_postgres_bytea_data_type(&cast.to) =>
6670            {
6671                cast
6672            }
6673            _ => return None,
6674        };
6675
6676        let literal = match &cast.this {
6677            Expression::Literal(literal) => literal.as_ref(),
6678            Expression::Paren(paren) => match &paren.this {
6679                Expression::Literal(literal) => literal.as_ref(),
6680                _ => return None,
6681            },
6682            _ => return None,
6683        };
6684        let value = match literal {
6685            Literal::String(value) | Literal::EscapeString(value) => value,
6686            _ => return None,
6687        };
6688
6689        if value.starts_with("\\x") {
6690            Some("bytea hex literals with invalid or unsupported formatting")
6691        } else if value.contains('\\') {
6692            Some("bytea escape-format literals")
6693        } else {
6694            None
6695        }
6696    }
6697
6698    fn recover_postgres_like_escape(op: &mut crate::expressions::LikeOp) {
6699        if op.escape.is_some() {
6700            return;
6701        }
6702
6703        let Expression::Function(function) = &op.right else {
6704            return;
6705        };
6706        if function.quoted
6707            || function.distinct
6708            || !function.name.eq_ignore_ascii_case("LIKE_ESCAPE")
6709            || function.args.len() != 2
6710        {
6711            return;
6712        }
6713
6714        let pattern = function.args[0].clone();
6715        let escape = function.args[1].clone();
6716        op.right = Self::strip_postgres_text_literal_cast(pattern);
6717        op.escape = Some(Self::strip_postgres_text_literal_cast(escape));
6718    }
6719
6720    fn normalize_postgres_translate_for_tsql(
6721        mut translate: crate::expressions::Translate,
6722    ) -> Expression {
6723        let (Some(from), Some(to)) = (&translate.from_, &translate.to) else {
6724            return Expression::Translate(Box::new(translate));
6725        };
6726
6727        let (Some(from_value), Some(to_value)) = (
6728            Self::postgres_text_literal_value(from),
6729            Self::postgres_text_literal_value(to),
6730        ) else {
6731            return Expression::Translate(Box::new(translate));
6732        };
6733        let from_value = from_value.to_string();
6734        let to_value = to_value.to_string();
6735
6736        if from_value.chars().count() > to_value.chars().count() {
6737            if let Some(input) = Self::postgres_text_literal_value(&translate.this) {
6738                return Expression::string(Self::translate_postgres_literal(
6739                    input,
6740                    &from_value,
6741                    &to_value,
6742                ));
6743            }
6744            return Expression::Translate(Box::new(translate));
6745        }
6746
6747        translate.from_ = Some(Box::new(Self::strip_postgres_text_literal_cast(
6748            *translate.from_.expect("checked above"),
6749        )));
6750        let normalized_to = if from_value.chars().count() < to_value.chars().count() {
6751            Expression::string(
6752                to_value
6753                    .chars()
6754                    .take(from_value.chars().count())
6755                    .collect::<String>(),
6756            )
6757        } else {
6758            Self::strip_postgres_text_literal_cast(*translate.to.expect("checked above"))
6759        };
6760        translate.to = Some(Box::new(normalized_to));
6761        Expression::Translate(Box::new(translate))
6762    }
6763
6764    fn normalize_postgres_translate_function_for_tsql(mut function: Function) -> Expression {
6765        let from = Self::postgres_text_literal_value(&function.args[1]);
6766        let to = Self::postgres_text_literal_value(&function.args[2]);
6767        let (Some(from), Some(to)) = (from, to) else {
6768            return Expression::Function(Box::new(function));
6769        };
6770        let from = from.to_string();
6771        let to = to.to_string();
6772
6773        if from.chars().count() > to.chars().count() {
6774            if let Some(input) = Self::postgres_text_literal_value(&function.args[0]) {
6775                return Expression::string(Self::translate_postgres_literal(input, &from, &to));
6776            }
6777            return Expression::Function(Box::new(function));
6778        }
6779
6780        function.args[1] = Self::strip_postgres_text_literal_cast(function.args[1].clone());
6781        function.args[2] = if from.chars().count() < to.chars().count() {
6782            Expression::string(to.chars().take(from.chars().count()).collect::<String>())
6783        } else {
6784            Self::strip_postgres_text_literal_cast(function.args[2].clone())
6785        };
6786        Expression::Function(Box::new(function))
6787    }
6788
6789    fn translate_postgres_literal(input: &str, from: &str, to: &str) -> String {
6790        let from = from.chars().collect::<Vec<_>>();
6791        let to = to.chars().collect::<Vec<_>>();
6792        let mut output = String::with_capacity(input.len());
6793
6794        for ch in input.chars() {
6795            match from.iter().position(|candidate| *candidate == ch) {
6796                Some(index) if index < to.len() => output.push(to[index]),
6797                Some(_) => {}
6798                None => output.push(ch),
6799            }
6800        }
6801
6802        output
6803    }
6804
6805    fn postgres_tsql_unsupported_string_semantics(expr: &Expression) -> Option<&'static str> {
6806        match expr {
6807            Expression::Substring(substring) if substring.length.is_none() => {
6808                if Self::postgres_text_literal_value(&substring.start).is_some() {
6809                    Some("regular-expression SUBSTRING")
6810                } else {
6811                    Some("SUBSTRING without a statically numeric start position")
6812                }
6813            }
6814            Expression::Translate(translate) => {
6815                let from = translate
6816                    .from_
6817                    .as_deref()
6818                    .and_then(Self::postgres_text_literal_value);
6819                let to = translate
6820                    .to
6821                    .as_deref()
6822                    .and_then(Self::postgres_text_literal_value);
6823                match (from, to) {
6824                    (Some(from), Some(to)) if from.chars().count() == to.chars().count() => None,
6825                    _ => Some("TRANSLATE with source and replacement lengths that differ or cannot be proven equal"),
6826                }
6827            }
6828            Expression::Function(function)
6829                if !function.quoted && function.name.eq_ignore_ascii_case("LIKE_ESCAPE") =>
6830            {
6831                Some("LIKE_ESCAPE helper outside a LIKE predicate")
6832            }
6833            Expression::Function(function)
6834                if !function.quoted
6835                    && function.name.eq_ignore_ascii_case("TRANSLATE")
6836                    && function.args.len() == 3 =>
6837            {
6838                let from = Self::postgres_text_literal_value(&function.args[1]);
6839                let to = Self::postgres_text_literal_value(&function.args[2]);
6840                match (from, to) {
6841                    (Some(from), Some(to)) if from.chars().count() == to.chars().count() => None,
6842                    _ => Some("TRANSLATE with source and replacement lengths that differ or cannot be proven equal"),
6843                }
6844            }
6845            Expression::Trim(trim)
6846                if trim
6847                    .characters
6848                    .as_ref()
6849                    .is_some_and(Self::is_unbounded_text_cast) =>
6850            {
6851                Some("TRIM character set cast to an unbounded text type")
6852            }
6853            Expression::Function(function)
6854                if !function.quoted
6855                    && matches!(
6856                        function.name.to_ascii_uppercase().as_str(),
6857                        "LTRIM" | "RTRIM"
6858                    )
6859                    && function.args.len() == 2
6860                    && Self::is_unbounded_text_cast(&function.args[1]) =>
6861            {
6862                Some("TRIM character set cast to an unbounded text type")
6863            }
6864            _ => None,
6865        }
6866    }
6867
6868    fn strip_postgres_text_literal_cast(expr: Expression) -> Expression {
6869        match expr {
6870            Expression::Cast(cast)
6871                if Self::is_text_data_type(&cast.to)
6872                    && Self::postgres_text_literal_value(&cast.this).is_some() =>
6873            {
6874                Self::strip_postgres_text_literal_cast(cast.this)
6875            }
6876            Expression::TryCast(cast)
6877                if Self::is_text_data_type(&cast.to)
6878                    && Self::postgres_text_literal_value(&cast.this).is_some() =>
6879            {
6880                Self::strip_postgres_text_literal_cast(cast.this)
6881            }
6882            Expression::SafeCast(cast)
6883                if Self::is_text_data_type(&cast.to)
6884                    && Self::postgres_text_literal_value(&cast.this).is_some() =>
6885            {
6886                Self::strip_postgres_text_literal_cast(cast.this)
6887            }
6888            Expression::Paren(mut paren)
6889                if Self::postgres_text_literal_value(&paren.this).is_some() =>
6890            {
6891                paren.this = Self::strip_postgres_text_literal_cast(paren.this);
6892                Expression::Paren(paren)
6893            }
6894            other => other,
6895        }
6896    }
6897
6898    fn postgres_text_literal_value(expr: &Expression) -> Option<&str> {
6899        match expr {
6900            Expression::Literal(literal) if literal.is_string() => Some(literal.value_str()),
6901            Expression::Cast(cast) | Expression::TryCast(cast) | Expression::SafeCast(cast)
6902                if Self::is_text_data_type(&cast.to) =>
6903            {
6904                Self::postgres_text_literal_value(&cast.this)
6905            }
6906            Expression::Alias(alias) => Self::postgres_text_literal_value(&alias.this),
6907            Expression::Paren(paren) => Self::postgres_text_literal_value(&paren.this),
6908            _ => None,
6909        }
6910    }
6911
6912    fn is_text_data_type(data_type: &DataType) -> bool {
6913        match data_type {
6914            DataType::Char { .. }
6915            | DataType::VarChar { .. }
6916            | DataType::String { .. }
6917            | DataType::Text
6918            | DataType::TextWithLength { .. } => true,
6919            DataType::Custom { name } => {
6920                let base = name
6921                    .split_once('(')
6922                    .map_or(name.as_str(), |(base, _)| base)
6923                    .trim();
6924                matches!(
6925                    base.to_ascii_uppercase().as_str(),
6926                    "CHAR"
6927                        | "NCHAR"
6928                        | "VARCHAR"
6929                        | "NVARCHAR"
6930                        | "TEXT"
6931                        | "NTEXT"
6932                        | "STRING"
6933                        | "CHARACTER VARYING"
6934                )
6935            }
6936            _ => false,
6937        }
6938    }
6939
6940    fn is_unbounded_text_cast(expr: &Expression) -> bool {
6941        let data_type = match expr {
6942            Expression::Cast(cast) | Expression::TryCast(cast) | Expression::SafeCast(cast) => {
6943                &cast.to
6944            }
6945            Expression::Paren(paren) => return Self::is_unbounded_text_cast(&paren.this),
6946            _ => return false,
6947        };
6948
6949        match data_type {
6950            DataType::Text => true,
6951            DataType::VarChar { length: None, .. } | DataType::String { length: None } => true,
6952            DataType::Custom { name } => name.to_ascii_uppercase().contains("(MAX)"),
6953            _ => false,
6954        }
6955    }
6956
6957    fn is_explicitly_numeric_expression(expr: &Expression) -> bool {
6958        if expr.inferred_type().is_some_and(Self::is_numeric_data_type) {
6959            return true;
6960        }
6961
6962        match expr {
6963            Expression::Literal(literal) => literal.is_number(),
6964            Expression::Cast(cast) | Expression::TryCast(cast) | Expression::SafeCast(cast) => {
6965                Self::is_numeric_data_type(&cast.to)
6966            }
6967            Expression::Alias(alias) => Self::is_explicitly_numeric_expression(&alias.this),
6968            Expression::Paren(paren) => Self::is_explicitly_numeric_expression(&paren.this),
6969            Expression::Neg(unary) => Self::is_explicitly_numeric_expression(&unary.this),
6970            _ => false,
6971        }
6972    }
6973
6974    fn is_numeric_data_type(data_type: &DataType) -> bool {
6975        match data_type {
6976            DataType::TinyInt { .. }
6977            | DataType::SmallInt { .. }
6978            | DataType::Int { .. }
6979            | DataType::BigInt { .. }
6980            | DataType::Float { .. }
6981            | DataType::Double { .. }
6982            | DataType::Decimal { .. } => true,
6983            DataType::Custom { name } => {
6984                let base = name
6985                    .split_once('(')
6986                    .map_or(name.as_str(), |(base, _)| base)
6987                    .trim();
6988                matches!(
6989                    base.to_ascii_uppercase().as_str(),
6990                    "TINYINT"
6991                        | "SMALLINT"
6992                        | "INT"
6993                        | "INTEGER"
6994                        | "BIGINT"
6995                        | "DECIMAL"
6996                        | "NUMERIC"
6997                        | "REAL"
6998                        | "FLOAT"
6999                        | "MONEY"
7000                        | "SMALLMONEY"
7001                )
7002            }
7003            _ => false,
7004        }
7005    }
7006
7007    fn normalize_postgres_only_for_tsql(expr: Expression) -> Result<Expression> {
7008        transform_recursive(expr, &|e| match e {
7009            Expression::Table(mut table) if table.only => {
7010                table.only = false;
7011                Ok(Expression::Table(table))
7012            }
7013            other => Ok(other),
7014        })
7015    }
7016
7017    fn rewrite_postgres_json_array_elements_select_for_tsql(
7018        expr: Expression,
7019    ) -> Result<Expression> {
7020        let Expression::Select(select) = expr else {
7021            return Ok(expr);
7022        };
7023        let mut select = *select;
7024        if !Self::is_plain_single_projection_select(&select) {
7025            return Ok(Expression::Select(Box::new(select)));
7026        }
7027
7028        let Some(json_arg) =
7029            Self::postgres_json_array_elements_projection_arg(&select.expressions[0])
7030        else {
7031            return Ok(Expression::Select(Box::new(select)));
7032        };
7033
7034        select.expressions = vec![Expression::column("value")];
7035        select.from = Some(From {
7036            expressions: vec![Expression::OpenJSON(Box::new(
7037                crate::expressions::OpenJSON {
7038                    this: Box::new(json_arg),
7039                    path: None,
7040                    expressions: Vec::new(),
7041                },
7042            ))],
7043        });
7044
7045        Ok(Expression::Select(Box::new(select)))
7046    }
7047
7048    fn is_plain_single_projection_select(select: &crate::expressions::Select) -> bool {
7049        select.expressions.len() == 1
7050            && select.from.is_none()
7051            && select.joins.is_empty()
7052            && select.lateral_views.is_empty()
7053            && select.prewhere.is_none()
7054            && select.where_clause.is_none()
7055            && select.group_by.is_none()
7056            && select.having.is_none()
7057            && select.qualify.is_none()
7058            && select.order_by.is_none()
7059            && select.distribute_by.is_none()
7060            && select.cluster_by.is_none()
7061            && select.sort_by.is_none()
7062            && select.limit.is_none()
7063            && select.offset.is_none()
7064            && select.limit_by.is_none()
7065            && select.fetch.is_none()
7066            && !select.distinct
7067            && select.distinct_on.is_none()
7068            && select.top.is_none()
7069            && select.with.is_none()
7070            && select.sample.is_none()
7071            && select.into.is_none()
7072            && select.locks.is_empty()
7073            && select.for_xml.is_empty()
7074            && select.for_json.is_empty()
7075            && select.exclude.is_none()
7076    }
7077
7078    fn postgres_json_array_elements_projection_arg(expr: &Expression) -> Option<Expression> {
7079        match expr {
7080            Expression::Function(function)
7081                if Self::node_is_postgres_json_array_elements(expr) && function.args.len() == 1 =>
7082            {
7083                Some(function.args[0].clone())
7084            }
7085            Expression::Alias(alias) => {
7086                Self::postgres_json_array_elements_projection_arg(&alias.this)
7087            }
7088            _ => None,
7089        }
7090    }
7091
7092    fn normalize_postgres_type_function_casts(
7093        expr: Expression,
7094        target: DialectType,
7095    ) -> Result<Expression> {
7096        transform_recursive(expr, &|e| match e {
7097            Expression::Function(function) => {
7098                let mut function = *function;
7099                if function.args.len() == 1
7100                    && !function.distinct
7101                    && !function.quoted
7102                    && !function.use_bracket_syntax
7103                    && !function.name.contains('.')
7104                {
7105                    if let Some(to) = Self::postgres_type_function_data_type(&function.name) {
7106                        let this = function.args.remove(0);
7107                        let cast = Cast {
7108                            this,
7109                            to,
7110                            trailing_comments: function.trailing_comments,
7111                            double_colon_syntax: false,
7112                            format: None,
7113                            default: None,
7114                            inferred_type: function.inferred_type,
7115                        };
7116                        return Ok(
7117                            if matches!(target, DialectType::TSQL | DialectType::Fabric) {
7118                                normalization::rewrite_postgres_float_to_integer_cast(cast)
7119                            } else {
7120                                Expression::Cast(Box::new(cast))
7121                            },
7122                        );
7123                    }
7124                }
7125                Ok(Expression::Function(Box::new(function)))
7126            }
7127            _ => Ok(e),
7128        })
7129    }
7130
7131    fn node_is_postgres_type_function_cast(expr: &Expression) -> bool {
7132        matches!(
7133            expr,
7134            Expression::Function(function)
7135                if !function.quoted
7136                    && !function.use_bracket_syntax
7137                    && !function.name.contains('.')
7138                    && Self::postgres_type_function_data_type(&function.name).is_some()
7139        )
7140    }
7141
7142    fn postgres_type_function_data_type(name: &str) -> Option<DataType> {
7143        match name.to_ascii_uppercase().as_str() {
7144            "NUMERIC" | "DECIMAL" | "DEC" => Some(DataType::Decimal {
7145                precision: None,
7146                scale: None,
7147            }),
7148            "INT2" | "SMALLINT" => Some(DataType::SmallInt { length: None }),
7149            "INT4" | "INT" => Some(DataType::Int {
7150                length: None,
7151                integer_spelling: false,
7152            }),
7153            "INTEGER" => Some(DataType::Int {
7154                length: None,
7155                integer_spelling: true,
7156            }),
7157            "INT8" | "BIGINT" => Some(DataType::BigInt { length: None }),
7158            "FLOAT4" | "REAL" => Some(DataType::Float {
7159                precision: None,
7160                scale: None,
7161                real_spelling: true,
7162            }),
7163            "FLOAT8" => Some(DataType::Double {
7164                precision: None,
7165                scale: None,
7166            }),
7167            "BOOL" | "BOOLEAN" => Some(DataType::Boolean),
7168            "TEXT" => Some(DataType::Text),
7169            "VARCHAR" => Some(DataType::VarChar {
7170                length: None,
7171                parenthesized_length: false,
7172            }),
7173            "UUID" => Some(DataType::Uuid),
7174            _ => None,
7175        }
7176    }
7177
7178    fn rewrite_boolean_values_for_tsql(expr: Expression) -> Result<Expression> {
7179        match expr {
7180            Expression::Select(select) => Self::rewrite_boolean_values_in_tsql_select(select),
7181            Expression::Subquery(mut subquery) => {
7182                subquery.this = Self::rewrite_boolean_values_for_tsql(subquery.this)?;
7183                Ok(Expression::Subquery(subquery))
7184            }
7185            Expression::Union(mut union) => {
7186                let left = std::mem::replace(&mut union.left, Expression::null());
7187                let right = std::mem::replace(&mut union.right, Expression::null());
7188                union.left = Self::rewrite_boolean_values_for_tsql(left)?;
7189                union.right = Self::rewrite_boolean_values_for_tsql(right)?;
7190                if let Some(mut with) = union.with.take() {
7191                    with.ctes = with
7192                        .ctes
7193                        .into_iter()
7194                        .map(|mut cte| {
7195                            cte.this = Self::rewrite_boolean_values_for_tsql(cte.this)?;
7196                            Ok(cte)
7197                        })
7198                        .collect::<Result<Vec<_>>>()?;
7199                    union.with = Some(with);
7200                }
7201                Ok(Expression::Union(union))
7202            }
7203            Expression::Intersect(mut intersect) => {
7204                let left = std::mem::replace(&mut intersect.left, Expression::null());
7205                let right = std::mem::replace(&mut intersect.right, Expression::null());
7206                intersect.left = Self::rewrite_boolean_values_for_tsql(left)?;
7207                intersect.right = Self::rewrite_boolean_values_for_tsql(right)?;
7208                Ok(Expression::Intersect(intersect))
7209            }
7210            Expression::Except(mut except) => {
7211                let left = std::mem::replace(&mut except.left, Expression::null());
7212                let right = std::mem::replace(&mut except.right, Expression::null());
7213                except.left = Self::rewrite_boolean_values_for_tsql(left)?;
7214                except.right = Self::rewrite_boolean_values_for_tsql(right)?;
7215                Ok(Expression::Except(except))
7216            }
7217            other => Self::rewrite_tsql_boolean_nested_contexts(other),
7218        }
7219    }
7220
7221    fn rewrite_postgres_row_value_equality_for_tsql(expr: Expression) -> Result<Expression> {
7222        transform_recursive(expr, &|e| match e {
7223            Expression::Eq(op) => {
7224                let op = *op;
7225                Ok(Self::postgres_row_value_equality_to_tsql_scalar(&op)
7226                    .unwrap_or_else(|| Expression::Eq(Box::new(op))))
7227            }
7228            other => Ok(other),
7229        })
7230    }
7231
7232    fn postgres_row_value_equality_to_tsql_scalar(op: &BinaryOp) -> Option<Expression> {
7233        let (row, query) =
7234            if Self::expr_is_row_value(&op.left) && Self::expr_is_subquery_like(&op.right) {
7235                (&op.left, &op.right)
7236            } else if Self::expr_is_row_value(&op.right) && Self::expr_is_subquery_like(&op.left) {
7237                (&op.right, &op.left)
7238            } else {
7239                return None;
7240            };
7241
7242        let row_values = Self::row_value_expressions(row)?;
7243        let projection_count = Self::subquery_projection_count(query)?;
7244        if row_values.is_empty() || row_values.len() != projection_count {
7245            return None;
7246        }
7247
7248        // Keep the complete original query behind a derived table. The outer scalar
7249        // SELECT therefore returns the same number of rows as the PostgreSQL
7250        // single-row subquery: zero rows stay NULL and multiple rows still raise a
7251        // scalar-subquery cardinality error in T-SQL/Fabric.
7252        let mut taken_names = HashSet::new();
7253        Self::collect_generated_alias_conflicts(row, &mut taken_names);
7254        Self::collect_generated_alias_conflicts(query, &mut taken_names);
7255
7256        let source_alias = find_new_name(&taken_names, "_polyglot_row");
7257        taken_names.insert(source_alias.to_ascii_lowercase());
7258        let column_aliases = (1..=row_values.len())
7259            .map(|index| {
7260                let name = find_new_name(&taken_names, &format!("_polyglot_row_value_{index}"));
7261                taken_names.insert(name.to_ascii_lowercase());
7262                Identifier::new(name)
7263            })
7264            .collect::<Vec<_>>();
7265        let source = Self::subquery_as_derived_table(
7266            query,
7267            Identifier::new(&source_alias),
7268            column_aliases.clone(),
7269        )?;
7270
7271        let mut equal_components = Vec::with_capacity(row_values.len());
7272        let mut unequal_components = Vec::with_capacity(row_values.len());
7273        for (column, row_value) in column_aliases.into_iter().zip(row_values) {
7274            let projected = Expression::qualified_column(source_alias.clone(), column.name);
7275            equal_components.push(Expression::Eq(Box::new(BinaryOp::new(
7276                projected.clone(),
7277                row_value.clone(),
7278            ))));
7279            unequal_components.push(Expression::Neq(Box::new(BinaryOp::new(
7280                projected, row_value,
7281            ))));
7282        }
7283
7284        let all_equal = equal_components
7285            .into_iter()
7286            .reduce(|left, right| Expression::And(Box::new(BinaryOp::new(left, right))))?;
7287        let any_unequal = unequal_components
7288            .into_iter()
7289            .reduce(|left, right| Expression::Or(Box::new(BinaryOp::new(left, right))))?;
7290        let comparison = Expression::Case(Box::new(Case {
7291            operand: None,
7292            whens: vec![
7293                (all_equal, Expression::number(1)),
7294                (any_unequal, Expression::number(0)),
7295            ],
7296            else_: Some(Expression::null()),
7297            comments: Vec::new(),
7298            inferred_type: None,
7299        }));
7300
7301        let scalar_select = Select::new().column(comparison).from(source);
7302        let scalar_subquery = Expression::Subquery(Box::new(Subquery {
7303            this: Expression::Select(Box::new(scalar_select)),
7304            alias: None,
7305            column_aliases: Vec::new(),
7306            alias_explicit_as: false,
7307            alias_keyword: None,
7308            order_by: None,
7309            limit: None,
7310            offset: None,
7311            distribute_by: None,
7312            sort_by: None,
7313            cluster_by: None,
7314            lateral: false,
7315            modifiers_inside: false,
7316            trailing_comments: Vec::new(),
7317            inferred_type: Some(DataType::Boolean),
7318        }));
7319
7320        Some(Expression::Cast(Box::new(Cast {
7321            this: scalar_subquery,
7322            to: DataType::Boolean,
7323            trailing_comments: Vec::new(),
7324            double_colon_syntax: false,
7325            format: None,
7326            default: None,
7327            inferred_type: Some(DataType::Boolean),
7328        })))
7329    }
7330
7331    fn row_value_expressions(expr: &Expression) -> Option<Vec<Expression>> {
7332        match expr {
7333            Expression::Tuple(tuple) => Some(tuple.expressions.clone()),
7334            Expression::Function(function) if function.name.eq_ignore_ascii_case("ROW") => {
7335                Some(function.args.clone())
7336            }
7337            Expression::Paren(paren) => Self::row_value_expressions(&paren.this),
7338            _ => None,
7339        }
7340    }
7341
7342    fn subquery_projection_count(expr: &Expression) -> Option<usize> {
7343        match expr {
7344            Expression::Select(select) => Some(select.expressions.len()),
7345            Expression::Subquery(subquery) => Self::subquery_projection_count(&subquery.this),
7346            Expression::Paren(paren) => Self::subquery_projection_count(&paren.this),
7347            _ => None,
7348        }
7349    }
7350
7351    fn subquery_as_derived_table(
7352        expr: &Expression,
7353        alias: Identifier,
7354        column_aliases: Vec<Identifier>,
7355    ) -> Option<Expression> {
7356        match expr.clone() {
7357            Expression::Subquery(mut subquery) => {
7358                subquery.alias = Some(alias);
7359                subquery.column_aliases = column_aliases;
7360                subquery.alias_explicit_as = true;
7361                subquery.alias_keyword = None;
7362                Some(Expression::Subquery(subquery))
7363            }
7364            Expression::Select(_) | Expression::Paren(_) => {
7365                Some(Expression::Subquery(Box::new(Subquery {
7366                    this: expr.clone(),
7367                    alias: Some(alias),
7368                    column_aliases,
7369                    alias_explicit_as: true,
7370                    alias_keyword: None,
7371                    order_by: None,
7372                    limit: None,
7373                    offset: None,
7374                    distribute_by: None,
7375                    sort_by: None,
7376                    cluster_by: None,
7377                    lateral: false,
7378                    modifiers_inside: false,
7379                    trailing_comments: Vec::new(),
7380                    inferred_type: None,
7381                })))
7382            }
7383            _ => None,
7384        }
7385    }
7386
7387    fn collect_generated_alias_conflicts(expr: &Expression, names: &mut HashSet<String>) {
7388        fn insert(names: &mut HashSet<String>, identifier: &Identifier) {
7389            if !identifier.name.is_empty() {
7390                names.insert(identifier.name.to_ascii_lowercase());
7391            }
7392        }
7393
7394        for node in expr.dfs() {
7395            match node {
7396                Expression::Identifier(identifier) => insert(names, identifier),
7397                Expression::Column(column) => {
7398                    insert(names, &column.name);
7399                    if let Some(table) = &column.table {
7400                        insert(names, table);
7401                    }
7402                }
7403                Expression::Table(table) => {
7404                    insert(names, &table.name);
7405                    if let Some(schema) = &table.schema {
7406                        insert(names, schema);
7407                    }
7408                    if let Some(catalog) = &table.catalog {
7409                        insert(names, catalog);
7410                    }
7411                    if let Some(alias) = &table.alias {
7412                        insert(names, alias);
7413                    }
7414                    for alias in &table.column_aliases {
7415                        insert(names, alias);
7416                    }
7417                }
7418                Expression::Alias(alias) => {
7419                    insert(names, &alias.alias);
7420                    for column_alias in &alias.column_aliases {
7421                        insert(names, column_alias);
7422                    }
7423                }
7424                Expression::Subquery(subquery) => {
7425                    if let Some(alias) = &subquery.alias {
7426                        insert(names, alias);
7427                    }
7428                    for column_alias in &subquery.column_aliases {
7429                        insert(names, column_alias);
7430                    }
7431                }
7432                Expression::Cte(cte) => {
7433                    insert(names, &cte.alias);
7434                    for column in &cte.columns {
7435                        insert(names, column);
7436                    }
7437                    for key in &cte.key_expressions {
7438                        insert(names, key);
7439                    }
7440                }
7441                Expression::Values(values) => {
7442                    if let Some(alias) = &values.alias {
7443                        insert(names, alias);
7444                    }
7445                    for column_alias in &values.column_aliases {
7446                        insert(names, column_alias);
7447                    }
7448                }
7449                Expression::Unnest(unnest) => {
7450                    if let Some(alias) = &unnest.alias {
7451                        insert(names, alias);
7452                    }
7453                    if let Some(offset_alias) = &unnest.offset_alias {
7454                        insert(names, offset_alias);
7455                    }
7456                }
7457                _ => {}
7458            }
7459        }
7460    }
7461
7462    fn rewrite_postgres_format_for_tsql(
7463        expr: Expression,
7464        target: DialectType,
7465    ) -> Result<Expression> {
7466        transform_recursive(expr, &|e| match e {
7467            Expression::Function(f) if f.name.eq_ignore_ascii_case("FORMAT") => {
7468                Self::postgres_format_function_to_tsql(*f, target)
7469            }
7470            other => Ok(other),
7471        })
7472    }
7473
7474    fn postgres_format_function_to_tsql(f: Function, target: DialectType) -> Result<Expression> {
7475        let Some(format_expr) = f.args.first() else {
7476            return Err(Self::unsupported_postgres_format_for_tsql(
7477                target,
7478                "missing format string",
7479            ));
7480        };
7481
7482        let format = match format_expr {
7483            Expression::Literal(lit) if lit.is_string() => lit.value_str(),
7484            _ => {
7485                return Err(Self::unsupported_postgres_format_for_tsql(
7486                    target,
7487                    "dynamic format strings",
7488                ))
7489            }
7490        };
7491
7492        let value_args = &f.args[1..];
7493        let mut arg_index = 0usize;
7494        let mut literal = String::new();
7495        let mut segments = Vec::new();
7496        let mut chars = format.chars();
7497
7498        while let Some(ch) = chars.next() {
7499            if ch != '%' {
7500                literal.push(ch);
7501                continue;
7502            }
7503
7504            let Some(specifier) = chars.next() else {
7505                return Err(Self::unsupported_postgres_format_for_tsql(
7506                    target,
7507                    "unterminated format specifier",
7508                ));
7509            };
7510
7511            match specifier {
7512                '%' => literal.push('%'),
7513                's' => {
7514                    if !literal.is_empty() {
7515                        segments.push(Expression::string(std::mem::take(&mut literal)));
7516                    }
7517                    let Some(arg) = value_args.get(arg_index) else {
7518                        return Err(Self::unsupported_postgres_format_for_tsql(
7519                            target,
7520                            "not enough arguments",
7521                        ));
7522                    };
7523                    segments.push(arg.clone());
7524                    arg_index += 1;
7525                }
7526                other => {
7527                    return Err(Self::unsupported_postgres_format_for_tsql(
7528                        target,
7529                        format!("unsupported format specifier %{other}"),
7530                    ))
7531                }
7532            }
7533        }
7534
7535        if !literal.is_empty() {
7536            segments.push(Expression::string(literal));
7537        }
7538
7539        if arg_index != value_args.len() {
7540            return Err(Self::unsupported_postgres_format_for_tsql(
7541                target,
7542                "unused format arguments",
7543            ));
7544        }
7545
7546        Ok(Self::postgres_format_segments_to_tsql_concat(segments))
7547    }
7548
7549    fn postgres_format_segments_to_tsql_concat(mut segments: Vec<Expression>) -> Expression {
7550        if segments.is_empty() {
7551            return Expression::string("");
7552        }
7553
7554        if segments.len() == 1 {
7555            let only = segments.pop().expect("one segment");
7556            if matches!(&only, Expression::Literal(lit) if lit.is_string()) {
7557                return only;
7558            }
7559
7560            return Expression::Function(Box::new(Function::new(
7561                "CONCAT".to_string(),
7562                vec![only, Expression::string("")],
7563            )));
7564        }
7565
7566        Expression::Function(Box::new(Function::new("CONCAT".to_string(), segments)))
7567    }
7568
7569    fn unsupported_postgres_format_for_tsql(
7570        target: DialectType,
7571        reason: impl Into<String>,
7572    ) -> crate::error::Error {
7573        crate::error::Error::unsupported(
7574            format!("PostgreSQL format() ({})", reason.into()),
7575            target.to_string(),
7576        )
7577    }
7578
7579    fn rewrite_boolean_values_in_tsql_select(
7580        mut select: Box<crate::expressions::Select>,
7581    ) -> Result<Expression> {
7582        if let Some(mut with) = select.with.take() {
7583            with.ctes = with
7584                .ctes
7585                .into_iter()
7586                .map(|mut cte| {
7587                    cte.this = Self::rewrite_boolean_values_for_tsql(cte.this)?;
7588                    Ok(cte)
7589                })
7590                .collect::<Result<Vec<_>>>()?;
7591            select.with = Some(with);
7592        }
7593
7594        select.expressions = select
7595            .expressions
7596            .into_iter()
7597            .map(Self::rewrite_tsql_boolean_scalar_value)
7598            .collect::<Result<Vec<_>>>()?;
7599
7600        if let Some(mut from) = select.from.take() {
7601            from.expressions = from
7602                .expressions
7603                .into_iter()
7604                .map(Self::rewrite_tsql_boolean_nested_contexts)
7605                .collect::<Result<Vec<_>>>()?;
7606            select.from = Some(from);
7607        }
7608
7609        select.joins = select
7610            .joins
7611            .into_iter()
7612            .map(|mut join| {
7613                join.this = Self::rewrite_tsql_boolean_nested_contexts(join.this)?;
7614                if let Some(on) = join.on.take() {
7615                    join.on = Some(Self::rewrite_tsql_boolean_predicate_context(on)?);
7616                }
7617                if let Some(match_condition) = join.match_condition.take() {
7618                    join.match_condition = Some(Self::rewrite_tsql_boolean_predicate_context(
7619                        match_condition,
7620                    )?);
7621                }
7622                join.pivots = join
7623                    .pivots
7624                    .into_iter()
7625                    .map(Self::rewrite_tsql_boolean_nested_contexts)
7626                    .collect::<Result<Vec<_>>>()?;
7627                Ok(join)
7628            })
7629            .collect::<Result<Vec<_>>>()?;
7630
7631        select.lateral_views = select
7632            .lateral_views
7633            .into_iter()
7634            .map(|mut lateral_view| {
7635                lateral_view.this = Self::rewrite_tsql_boolean_nested_contexts(lateral_view.this)?;
7636                Ok(lateral_view)
7637            })
7638            .collect::<Result<Vec<_>>>()?;
7639
7640        if let Some(prewhere) = select.prewhere.take() {
7641            select.prewhere = Some(Self::rewrite_tsql_boolean_predicate_context(prewhere)?);
7642        }
7643
7644        if let Some(mut where_clause) = select.where_clause.take() {
7645            where_clause.this = Self::rewrite_tsql_boolean_predicate_context(where_clause.this)?;
7646            select.where_clause = Some(where_clause);
7647        }
7648
7649        if let Some(mut group_by) = select.group_by.take() {
7650            group_by.expressions = group_by
7651                .expressions
7652                .into_iter()
7653                .map(Self::rewrite_tsql_boolean_scalar_value)
7654                .collect::<Result<Vec<_>>>()?;
7655            select.group_by = Some(group_by);
7656        }
7657
7658        if let Some(mut having) = select.having.take() {
7659            having.this = Self::rewrite_tsql_boolean_predicate_context(having.this)?;
7660            select.having = Some(having);
7661        }
7662
7663        if let Some(mut qualify) = select.qualify.take() {
7664            qualify.this = Self::rewrite_tsql_boolean_predicate_context(qualify.this)?;
7665            select.qualify = Some(qualify);
7666        }
7667
7668        if let Some(mut order_by) = select.order_by.take() {
7669            order_by.expressions = Self::rewrite_tsql_boolean_ordered_values(order_by.expressions)?;
7670            select.order_by = Some(order_by);
7671        }
7672
7673        if let Some(mut distribute_by) = select.distribute_by.take() {
7674            distribute_by.expressions = distribute_by
7675                .expressions
7676                .into_iter()
7677                .map(Self::rewrite_tsql_boolean_scalar_value)
7678                .collect::<Result<Vec<_>>>()?;
7679            select.distribute_by = Some(distribute_by);
7680        }
7681
7682        if let Some(mut cluster_by) = select.cluster_by.take() {
7683            cluster_by.expressions =
7684                Self::rewrite_tsql_boolean_ordered_values(cluster_by.expressions)?;
7685            select.cluster_by = Some(cluster_by);
7686        }
7687
7688        if let Some(mut sort_by) = select.sort_by.take() {
7689            sort_by.expressions = Self::rewrite_tsql_boolean_ordered_values(sort_by.expressions)?;
7690            select.sort_by = Some(sort_by);
7691        }
7692
7693        if let Some(limit_by) = select.limit_by.take() {
7694            select.limit_by = Some(
7695                limit_by
7696                    .into_iter()
7697                    .map(Self::rewrite_tsql_boolean_scalar_value)
7698                    .collect::<Result<Vec<_>>>()?,
7699            );
7700        }
7701
7702        if let Some(distinct_on) = select.distinct_on.take() {
7703            select.distinct_on = Some(
7704                distinct_on
7705                    .into_iter()
7706                    .map(Self::rewrite_tsql_boolean_scalar_value)
7707                    .collect::<Result<Vec<_>>>()?,
7708            );
7709        }
7710
7711        if let Some(mut sample) = select.sample.take() {
7712            sample.size = Self::rewrite_tsql_boolean_nested_contexts(sample.size)?;
7713            if let Some(offset) = sample.offset.take() {
7714                sample.offset = Some(Self::rewrite_tsql_boolean_nested_contexts(offset)?);
7715            }
7716            if let Some(bucket_numerator) = sample.bucket_numerator.take() {
7717                sample.bucket_numerator = Some(Box::new(
7718                    Self::rewrite_tsql_boolean_nested_contexts(*bucket_numerator)?,
7719                ));
7720            }
7721            if let Some(bucket_denominator) = sample.bucket_denominator.take() {
7722                sample.bucket_denominator = Some(Box::new(
7723                    Self::rewrite_tsql_boolean_nested_contexts(*bucket_denominator)?,
7724                ));
7725            }
7726            if let Some(bucket_field) = sample.bucket_field.take() {
7727                sample.bucket_field = Some(Box::new(Self::rewrite_tsql_boolean_nested_contexts(
7728                    *bucket_field,
7729                )?));
7730            }
7731            select.sample = Some(sample);
7732        }
7733
7734        if let Some(settings) = select.settings.take() {
7735            select.settings = Some(
7736                settings
7737                    .into_iter()
7738                    .map(Self::rewrite_tsql_boolean_nested_contexts)
7739                    .collect::<Result<Vec<_>>>()?,
7740            );
7741        }
7742
7743        if let Some(format) = select.format.take() {
7744            select.format = Some(Self::rewrite_tsql_boolean_nested_contexts(format)?);
7745        }
7746
7747        if let Some(mut windows) = select.windows.take() {
7748            for window in windows.iter_mut() {
7749                Self::rewrite_tsql_boolean_over_values(&mut window.spec)?;
7750            }
7751            select.windows = Some(windows);
7752        }
7753
7754        Ok(Expression::Select(select))
7755    }
7756
7757    fn normalize_postgres_boolean_semantics_for_tsql(expr: Expression) -> Result<Expression> {
7758        transform_recursive(expr, &|e| match e {
7759            Expression::Function(function)
7760                if function.args.len() == 2
7761                    && (function.name.eq_ignore_ascii_case("BOOLEQ")
7762                        || function.name.eq_ignore_ascii_case("BOOLNE")) =>
7763            {
7764                let is_equal = function.name.eq_ignore_ascii_case("BOOLEQ");
7765                let mut args = function.args.into_iter();
7766                let op = BinaryOp {
7767                    left: args.next().expect("checked boolean operator arity"),
7768                    right: args.next().expect("checked boolean operator arity"),
7769                    left_comments: Vec::new(),
7770                    operator_comments: Vec::new(),
7771                    trailing_comments: function.trailing_comments,
7772                    inferred_type: None,
7773                };
7774                if is_equal {
7775                    Ok(Expression::Eq(Box::new(op)))
7776                } else {
7777                    Ok(Expression::Neq(Box::new(op)))
7778                }
7779            }
7780            Expression::Cast(cast)
7781                if matches!(cast.to, DataType::Text)
7782                    && Self::is_known_postgres_boolean_expression(&cast.this) =>
7783            {
7784                Ok(Self::postgres_boolean_text_value(cast.this))
7785            }
7786            other => Ok(other),
7787        })
7788    }
7789
7790    fn is_known_postgres_boolean_expression(expr: &Expression) -> bool {
7791        match expr {
7792            Expression::Boolean(_) => true,
7793            Expression::Cast(cast) => matches!(cast.to, DataType::Boolean),
7794            Expression::Paren(paren) => Self::is_known_postgres_boolean_expression(&paren.this),
7795            other => Self::is_tsql_boolean_value_expression(other),
7796        }
7797    }
7798
7799    fn postgres_boolean_text_value(predicate: Expression) -> Expression {
7800        if let Expression::Boolean(boolean) = predicate {
7801            return Expression::string(if boolean.value { "true" } else { "false" });
7802        }
7803
7804        Self::three_valued_boolean_case(
7805            predicate,
7806            Expression::string("true"),
7807            Expression::string("false"),
7808        )
7809    }
7810
7811    fn rewrite_tsql_boolean_scalar_value(expr: Expression) -> Result<Expression> {
7812        if let Expression::Boolean(boolean) = expr {
7813            return Ok(Expression::Cast(Box::new(Cast {
7814                this: Expression::Boolean(boolean),
7815                to: DataType::Boolean,
7816                trailing_comments: Vec::new(),
7817                double_colon_syntax: false,
7818                format: None,
7819                default: None,
7820                inferred_type: None,
7821            })));
7822        }
7823
7824        if Self::is_tsql_boolean_value_expression(&expr) {
7825            // Tuple/subquery equality currently lowers only its positive branch to EXISTS.
7826            // Keep its established two-way scalar fallback until that rewrite models UNKNOWN.
7827            let can_be_unknown = Self::tsql_boolean_expression_can_be_unknown(&expr)
7828                && !Self::node_is_row_value_subquery_comparison(&expr);
7829            let predicate = Self::rewrite_tsql_boolean_predicate_context(expr)?;
7830            return Ok(Self::tsql_boolean_value_case(predicate, can_be_unknown));
7831        }
7832
7833        match expr {
7834            Expression::Alias(mut alias) => {
7835                alias.this = Self::rewrite_tsql_boolean_scalar_value(alias.this)?;
7836                Ok(Expression::Alias(alias))
7837            }
7838            Expression::Paren(mut paren) => {
7839                paren.this = Self::rewrite_tsql_boolean_scalar_value(paren.this)?;
7840                Ok(Expression::Paren(paren))
7841            }
7842            Expression::Cast(mut cast) => {
7843                cast.this = Self::rewrite_tsql_boolean_scalar_value(cast.this)?;
7844                if let Some(format) = cast.format.take() {
7845                    cast.format = Some(Box::new(Self::rewrite_tsql_boolean_nested_contexts(
7846                        *format,
7847                    )?));
7848                }
7849                if let Some(default) = cast.default.take() {
7850                    cast.default =
7851                        Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(*default)?));
7852                }
7853                Ok(Expression::Cast(cast))
7854            }
7855            Expression::TryCast(mut cast) => {
7856                cast.this = Self::rewrite_tsql_boolean_scalar_value(cast.this)?;
7857                if let Some(format) = cast.format.take() {
7858                    cast.format = Some(Box::new(Self::rewrite_tsql_boolean_nested_contexts(
7859                        *format,
7860                    )?));
7861                }
7862                if let Some(default) = cast.default.take() {
7863                    cast.default =
7864                        Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(*default)?));
7865                }
7866                Ok(Expression::TryCast(cast))
7867            }
7868            Expression::SafeCast(mut cast) => {
7869                cast.this = Self::rewrite_tsql_boolean_scalar_value(cast.this)?;
7870                if let Some(format) = cast.format.take() {
7871                    cast.format = Some(Box::new(Self::rewrite_tsql_boolean_nested_contexts(
7872                        *format,
7873                    )?));
7874                }
7875                if let Some(default) = cast.default.take() {
7876                    cast.default =
7877                        Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(*default)?));
7878                }
7879                Ok(Expression::SafeCast(cast))
7880            }
7881            Expression::Case(mut case) => {
7882                let is_simple_case = case.operand.is_some();
7883                if let Some(operand) = case.operand.take() {
7884                    case.operand = Some(Self::rewrite_tsql_boolean_scalar_value(operand)?);
7885                }
7886                case.whens = case
7887                    .whens
7888                    .into_iter()
7889                    .map(|(condition, result)| {
7890                        let condition = if is_simple_case {
7891                            Self::rewrite_tsql_boolean_scalar_value(condition)?
7892                        } else {
7893                            Self::rewrite_tsql_boolean_predicate_context(condition)?
7894                        };
7895                        Ok((condition, Self::rewrite_tsql_boolean_scalar_value(result)?))
7896                    })
7897                    .collect::<Result<Vec<_>>>()?;
7898                if let Some(else_) = case.else_.take() {
7899                    case.else_ = Some(Self::rewrite_tsql_boolean_scalar_value(else_)?);
7900                }
7901                Ok(Expression::Case(case))
7902            }
7903            Expression::IfFunc(mut if_func) => {
7904                if_func.condition =
7905                    Self::rewrite_tsql_boolean_predicate_context(if_func.condition)?;
7906                if_func.true_value = Self::rewrite_tsql_boolean_scalar_value(if_func.true_value)?;
7907                if let Some(false_value) = if_func.false_value.take() {
7908                    if_func.false_value =
7909                        Some(Self::rewrite_tsql_boolean_scalar_value(false_value)?);
7910                }
7911                Ok(Expression::IfFunc(if_func))
7912            }
7913            Expression::WindowFunction(mut window_function) => {
7914                window_function.this =
7915                    Self::rewrite_tsql_boolean_nested_contexts(window_function.this)?;
7916                Self::rewrite_tsql_boolean_over_values(&mut window_function.over)?;
7917                if let Some(mut keep) = window_function.keep.take() {
7918                    keep.order_by = Self::rewrite_tsql_boolean_ordered_values(keep.order_by)?;
7919                    window_function.keep = Some(keep);
7920                }
7921                Ok(Expression::WindowFunction(window_function))
7922            }
7923            Expression::WithinGroup(mut within_group) => {
7924                within_group.this = Self::rewrite_tsql_boolean_nested_contexts(within_group.this)?;
7925                within_group.order_by =
7926                    Self::rewrite_tsql_boolean_ordered_values(within_group.order_by)?;
7927                Ok(Expression::WithinGroup(within_group))
7928            }
7929            Expression::Subquery(mut subquery) => {
7930                subquery.this = Self::rewrite_boolean_values_for_tsql(subquery.this)?;
7931                Ok(Expression::Subquery(subquery))
7932            }
7933            Expression::Select(select) => Self::rewrite_boolean_values_in_tsql_select(select),
7934            other => Self::rewrite_tsql_boolean_nested_contexts(other),
7935        }
7936    }
7937
7938    fn rewrite_tsql_boolean_predicate_context(expr: Expression) -> Result<Expression> {
7939        let expr = Self::rewrite_tsql_boolean_nested_contexts(expr)?;
7940        Ok(crate::transforms::ensure_bool_condition(expr))
7941    }
7942
7943    fn rewrite_tsql_boolean_nested_contexts(expr: Expression) -> Result<Expression> {
7944        transform_recursive(expr, &|e| match e {
7945            Expression::Select(select) => Self::rewrite_boolean_values_in_tsql_select(select),
7946            Expression::Subquery(mut subquery) => {
7947                subquery.this = Self::rewrite_boolean_values_for_tsql(subquery.this)?;
7948                Ok(Expression::Subquery(subquery))
7949            }
7950            Expression::Union(_) | Expression::Intersect(_) | Expression::Except(_) => {
7951                Self::rewrite_boolean_values_for_tsql(e)
7952            }
7953            other => Self::rewrite_tsql_boolean_cast_operand(other),
7954        })
7955    }
7956
7957    fn rewrite_tsql_boolean_cast_operand(expr: Expression) -> Result<Expression> {
7958        macro_rules! rewrite_cast_operand {
7959            ($variant:ident, $cast:expr) => {{
7960                let mut cast = $cast;
7961                if Self::is_tsql_boolean_value_expression(&cast.this) {
7962                    cast.this = Self::rewrite_tsql_boolean_scalar_value(cast.this)?;
7963                }
7964                Ok(Expression::$variant(cast))
7965            }};
7966        }
7967
7968        match expr {
7969            Expression::Cast(cast) => rewrite_cast_operand!(Cast, cast),
7970            Expression::TryCast(cast) => rewrite_cast_operand!(TryCast, cast),
7971            Expression::SafeCast(cast) => rewrite_cast_operand!(SafeCast, cast),
7972            other => Ok(other),
7973        }
7974    }
7975
7976    fn rewrite_tsql_boolean_ordered_values(
7977        ordered: Vec<crate::expressions::Ordered>,
7978    ) -> Result<Vec<crate::expressions::Ordered>> {
7979        ordered
7980            .into_iter()
7981            .map(|mut ordered| {
7982                ordered.this = Self::rewrite_tsql_boolean_scalar_value(ordered.this)?;
7983                if let Some(with_fill) = ordered.with_fill.take() {
7984                    ordered.with_fill = Some(Box::new(
7985                        Self::rewrite_tsql_boolean_with_fill_values(*with_fill)?,
7986                    ));
7987                }
7988                Ok(ordered)
7989            })
7990            .collect()
7991    }
7992
7993    fn rewrite_tsql_boolean_with_fill_values(
7994        mut with_fill: crate::expressions::WithFill,
7995    ) -> Result<crate::expressions::WithFill> {
7996        if let Some(from) = with_fill.from_.take() {
7997            with_fill.from_ = Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(*from)?));
7998        }
7999        if let Some(to) = with_fill.to.take() {
8000            with_fill.to = Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(*to)?));
8001        }
8002        if let Some(step) = with_fill.step.take() {
8003            with_fill.step = Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(*step)?));
8004        }
8005        if let Some(staleness) = with_fill.staleness.take() {
8006            with_fill.staleness = Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(
8007                *staleness,
8008            )?));
8009        }
8010        if let Some(interpolate) = with_fill.interpolate.take() {
8011            with_fill.interpolate = Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(
8012                *interpolate,
8013            )?));
8014        }
8015        Ok(with_fill)
8016    }
8017
8018    fn rewrite_tsql_boolean_over_values(over: &mut crate::expressions::Over) -> Result<()> {
8019        over.partition_by = std::mem::take(&mut over.partition_by)
8020            .into_iter()
8021            .map(Self::rewrite_tsql_boolean_scalar_value)
8022            .collect::<Result<Vec<_>>>()?;
8023        over.order_by =
8024            Self::rewrite_tsql_boolean_ordered_values(std::mem::take(&mut over.order_by))?;
8025        Ok(())
8026    }
8027
8028    fn is_tsql_boolean_value_expression(expr: &Expression) -> bool {
8029        match expr {
8030            Expression::Paren(paren) => Self::is_tsql_boolean_value_expression(&paren.this),
8031            Expression::Eq(_)
8032            | Expression::Neq(_)
8033            | Expression::Lt(_)
8034            | Expression::Lte(_)
8035            | Expression::Gt(_)
8036            | Expression::Gte(_)
8037            | Expression::Is(_)
8038            | Expression::IsNull(_)
8039            | Expression::IsTrue(_)
8040            | Expression::IsFalse(_)
8041            | Expression::Like(_)
8042            | Expression::ILike(_)
8043            | Expression::StartsWith(_)
8044            | Expression::SimilarTo(_)
8045            | Expression::Glob(_)
8046            | Expression::RegexpLike(_)
8047            | Expression::In(_)
8048            | Expression::Between(_)
8049            | Expression::Exists(_)
8050            | Expression::And(_)
8051            | Expression::Or(_)
8052            | Expression::Not(_)
8053            | Expression::Any(_)
8054            | Expression::All(_)
8055            | Expression::NullSafeEq(_)
8056            | Expression::NullSafeNeq(_)
8057            | Expression::EqualNull(_) => true,
8058            _ => false,
8059        }
8060    }
8061
8062    fn tsql_boolean_expression_can_be_unknown(expr: &Expression) -> bool {
8063        match expr {
8064            Expression::Boolean(_)
8065            | Expression::IsNull(_)
8066            | Expression::IsTrue(_)
8067            | Expression::IsFalse(_)
8068            | Expression::Exists(_)
8069            | Expression::NullSafeEq(_)
8070            | Expression::NullSafeNeq(_)
8071            | Expression::EqualNull(_) => false,
8072            Expression::Paren(paren) => Self::tsql_boolean_expression_can_be_unknown(&paren.this),
8073            Expression::Not(op) => Self::tsql_boolean_expression_can_be_unknown(&op.this),
8074            Expression::And(op) | Expression::Or(op) => {
8075                Self::tsql_boolean_expression_can_be_unknown(&op.left)
8076                    || Self::tsql_boolean_expression_can_be_unknown(&op.right)
8077            }
8078            _ => true,
8079        }
8080    }
8081
8082    fn tsql_boolean_value_case(predicate: Expression, can_be_unknown: bool) -> Expression {
8083        let case = if can_be_unknown {
8084            Self::three_valued_boolean_case(predicate, Expression::number(1), Expression::number(0))
8085        } else {
8086            Expression::Case(Box::new(crate::expressions::Case {
8087                operand: None,
8088                whens: vec![(predicate, Expression::number(1))],
8089                else_: Some(Expression::number(0)),
8090                comments: Vec::new(),
8091                inferred_type: None,
8092            }))
8093        };
8094
8095        Expression::Cast(Box::new(Cast {
8096            this: case,
8097            to: DataType::Boolean,
8098            trailing_comments: Vec::new(),
8099            double_colon_syntax: false,
8100            format: None,
8101            default: None,
8102            inferred_type: None,
8103        }))
8104    }
8105
8106    fn three_valued_boolean_case(
8107        predicate: Expression,
8108        true_value: Expression,
8109        false_value: Expression,
8110    ) -> Expression {
8111        let false_operand = if matches!(predicate, Expression::And(_) | Expression::Or(_)) {
8112            Expression::Paren(Box::new(crate::expressions::Paren {
8113                this: predicate.clone(),
8114                trailing_comments: Vec::new(),
8115            }))
8116        } else {
8117            predicate.clone()
8118        };
8119        let false_predicate = Expression::Not(Box::new(crate::expressions::UnaryOp {
8120            this: false_operand,
8121            inferred_type: None,
8122        }));
8123
8124        Expression::Case(Box::new(crate::expressions::Case {
8125            operand: None,
8126            whens: vec![(predicate, true_value), (false_predicate, false_value)],
8127            else_: Some(Expression::null()),
8128            comments: Vec::new(),
8129            inferred_type: None,
8130        }))
8131    }
8132
8133    fn rewrite_aggregate_filters_for_tsql(expr: Expression) -> Result<Expression> {
8134        transform_recursive(expr, &|e| Self::rewrite_aggregate_filter_for_tsql(e))
8135    }
8136
8137    fn rewrite_aggregate_filter_for_tsql(expr: Expression) -> Result<Expression> {
8138        macro_rules! rewrite_agg_filter {
8139            ($variant:ident, $agg:expr) => {{
8140                let mut agg = $agg;
8141                if let Some(filter) = agg.filter.take() {
8142                    let this = std::mem::replace(&mut agg.this, Expression::null());
8143                    agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
8144                }
8145                Ok(Expression::$variant(agg))
8146            }};
8147        }
8148
8149        match expr {
8150            Expression::Filter(filter) => {
8151                let condition = match *filter.expression {
8152                    Expression::Where(where_) => where_.this,
8153                    other => other,
8154                };
8155                Ok(Self::push_filter_into_tsql_aggregate(
8156                    *filter.this,
8157                    condition,
8158                ))
8159            }
8160            Expression::AggregateFunction(mut agg) => {
8161                if let Some(filter) = agg.filter.take() {
8162                    Self::rewrite_generic_aggregate_filter_for_tsql(&mut agg, filter);
8163                }
8164                Ok(Expression::AggregateFunction(agg))
8165            }
8166            Expression::Count(mut count) => {
8167                if let Some(filter) = count.filter.take() {
8168                    let value = if count.star {
8169                        Expression::number(1)
8170                    } else {
8171                        count.this.take().unwrap_or_else(|| Expression::number(1))
8172                    };
8173                    count.star = false;
8174                    count.this = Some(Self::conditional_aggregate_value_for_tsql(filter, value));
8175                }
8176                Ok(Expression::Count(count))
8177            }
8178            Expression::Sum(agg) => rewrite_agg_filter!(Sum, agg),
8179            Expression::Avg(agg) => rewrite_agg_filter!(Avg, agg),
8180            Expression::Min(agg) => rewrite_agg_filter!(Min, agg),
8181            Expression::Max(agg) => rewrite_agg_filter!(Max, agg),
8182            Expression::ArrayAgg(agg) => rewrite_agg_filter!(ArrayAgg, agg),
8183            Expression::CountIf(agg) => Ok(Expression::CountIf(agg)),
8184            Expression::Stddev(agg) => rewrite_agg_filter!(Stddev, agg),
8185            Expression::StddevPop(agg) => rewrite_agg_filter!(StddevPop, agg),
8186            Expression::StddevSamp(agg) => rewrite_agg_filter!(StddevSamp, agg),
8187            Expression::Variance(agg) => rewrite_agg_filter!(Variance, agg),
8188            Expression::VarPop(agg) => rewrite_agg_filter!(VarPop, agg),
8189            Expression::VarSamp(agg) => rewrite_agg_filter!(VarSamp, agg),
8190            Expression::Median(agg) => rewrite_agg_filter!(Median, agg),
8191            Expression::Mode(agg) => rewrite_agg_filter!(Mode, agg),
8192            Expression::First(agg) => rewrite_agg_filter!(First, agg),
8193            Expression::Last(agg) => rewrite_agg_filter!(Last, agg),
8194            Expression::AnyValue(agg) => rewrite_agg_filter!(AnyValue, agg),
8195            Expression::ApproxDistinct(agg) => rewrite_agg_filter!(ApproxDistinct, agg),
8196            Expression::ApproxCountDistinct(agg) => {
8197                rewrite_agg_filter!(ApproxCountDistinct, agg)
8198            }
8199            Expression::LogicalAnd(agg) => rewrite_agg_filter!(LogicalAnd, agg),
8200            Expression::LogicalOr(agg) => rewrite_agg_filter!(LogicalOr, agg),
8201            Expression::Skewness(agg) => rewrite_agg_filter!(Skewness, agg),
8202            Expression::ArrayConcatAgg(agg) => rewrite_agg_filter!(ArrayConcatAgg, agg),
8203            Expression::ArrayUniqueAgg(agg) => rewrite_agg_filter!(ArrayUniqueAgg, agg),
8204            Expression::BoolXorAgg(agg) => rewrite_agg_filter!(BoolXorAgg, agg),
8205            Expression::BitwiseAndAgg(agg) => rewrite_agg_filter!(BitwiseAndAgg, agg),
8206            Expression::BitwiseOrAgg(agg) => rewrite_agg_filter!(BitwiseOrAgg, agg),
8207            Expression::BitwiseXorAgg(agg) => rewrite_agg_filter!(BitwiseXorAgg, agg),
8208            Expression::StringAgg(mut agg) => {
8209                if let Some(filter) = agg.filter.take() {
8210                    let this = std::mem::replace(&mut agg.this, Expression::null());
8211                    agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
8212                }
8213                Ok(Expression::StringAgg(agg))
8214            }
8215            Expression::GroupConcat(mut agg) => {
8216                if let Some(filter) = agg.filter.take() {
8217                    let this = std::mem::replace(&mut agg.this, Expression::null());
8218                    agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
8219                }
8220                Ok(Expression::GroupConcat(agg))
8221            }
8222            Expression::ListAgg(mut agg) => {
8223                if let Some(filter) = agg.filter.take() {
8224                    let this = std::mem::replace(&mut agg.this, Expression::null());
8225                    agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
8226                }
8227                Ok(Expression::ListAgg(agg))
8228            }
8229            Expression::WithinGroup(mut within_group) => {
8230                within_group.this = Self::rewrite_aggregate_filters_for_tsql(within_group.this)?;
8231                Ok(Expression::WithinGroup(within_group))
8232            }
8233            other => Ok(other),
8234        }
8235    }
8236
8237    fn push_filter_into_tsql_aggregate(expr: Expression, filter: Expression) -> Expression {
8238        macro_rules! push_agg_filter {
8239            ($variant:ident, $agg:expr) => {{
8240                let mut agg = $agg;
8241                let this = std::mem::replace(&mut agg.this, Expression::null());
8242                agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
8243                agg.filter = None;
8244                Expression::$variant(agg)
8245            }};
8246        }
8247
8248        match expr {
8249            Expression::AggregateFunction(mut agg) => {
8250                Self::rewrite_generic_aggregate_filter_for_tsql(&mut agg, filter);
8251                Expression::AggregateFunction(agg)
8252            }
8253            Expression::Count(mut count) => {
8254                let value = if count.star {
8255                    Expression::number(1)
8256                } else {
8257                    count.this.take().unwrap_or_else(|| Expression::number(1))
8258                };
8259                count.star = false;
8260                count.filter = None;
8261                count.this = Some(Self::conditional_aggregate_value_for_tsql(filter, value));
8262                Expression::Count(count)
8263            }
8264            Expression::Sum(agg) => push_agg_filter!(Sum, agg),
8265            Expression::Avg(agg) => push_agg_filter!(Avg, agg),
8266            Expression::Min(agg) => push_agg_filter!(Min, agg),
8267            Expression::Max(agg) => push_agg_filter!(Max, agg),
8268            Expression::ArrayAgg(agg) => push_agg_filter!(ArrayAgg, agg),
8269            Expression::CountIf(mut agg) => {
8270                agg.filter = Some(filter);
8271                Expression::CountIf(agg)
8272            }
8273            Expression::Stddev(agg) => push_agg_filter!(Stddev, agg),
8274            Expression::StddevPop(agg) => push_agg_filter!(StddevPop, agg),
8275            Expression::StddevSamp(agg) => push_agg_filter!(StddevSamp, agg),
8276            Expression::Variance(agg) => push_agg_filter!(Variance, agg),
8277            Expression::VarPop(agg) => push_agg_filter!(VarPop, agg),
8278            Expression::VarSamp(agg) => push_agg_filter!(VarSamp, agg),
8279            Expression::Median(agg) => push_agg_filter!(Median, agg),
8280            Expression::Mode(agg) => push_agg_filter!(Mode, agg),
8281            Expression::First(agg) => push_agg_filter!(First, agg),
8282            Expression::Last(agg) => push_agg_filter!(Last, agg),
8283            Expression::AnyValue(agg) => push_agg_filter!(AnyValue, agg),
8284            Expression::ApproxDistinct(agg) => push_agg_filter!(ApproxDistinct, agg),
8285            Expression::ApproxCountDistinct(agg) => {
8286                push_agg_filter!(ApproxCountDistinct, agg)
8287            }
8288            Expression::LogicalAnd(agg) => push_agg_filter!(LogicalAnd, agg),
8289            Expression::LogicalOr(agg) => push_agg_filter!(LogicalOr, agg),
8290            Expression::Skewness(agg) => push_agg_filter!(Skewness, agg),
8291            Expression::ArrayConcatAgg(agg) => push_agg_filter!(ArrayConcatAgg, agg),
8292            Expression::ArrayUniqueAgg(agg) => push_agg_filter!(ArrayUniqueAgg, agg),
8293            Expression::BoolXorAgg(agg) => push_agg_filter!(BoolXorAgg, agg),
8294            Expression::BitwiseAndAgg(agg) => push_agg_filter!(BitwiseAndAgg, agg),
8295            Expression::BitwiseOrAgg(agg) => push_agg_filter!(BitwiseOrAgg, agg),
8296            Expression::BitwiseXorAgg(agg) => push_agg_filter!(BitwiseXorAgg, agg),
8297            Expression::StringAgg(mut agg) => {
8298                let this = std::mem::replace(&mut agg.this, Expression::null());
8299                agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
8300                agg.filter = None;
8301                Expression::StringAgg(agg)
8302            }
8303            Expression::GroupConcat(mut agg) => {
8304                let this = std::mem::replace(&mut agg.this, Expression::null());
8305                agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
8306                agg.filter = None;
8307                Expression::GroupConcat(agg)
8308            }
8309            Expression::ListAgg(mut agg) => {
8310                let this = std::mem::replace(&mut agg.this, Expression::null());
8311                agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
8312                agg.filter = None;
8313                Expression::ListAgg(agg)
8314            }
8315            Expression::WithinGroup(mut within_group) => {
8316                within_group.this =
8317                    Self::push_filter_into_tsql_aggregate(within_group.this, filter);
8318                Expression::WithinGroup(within_group)
8319            }
8320            other => Expression::Filter(Box::new(crate::expressions::Filter {
8321                this: Box::new(other),
8322                expression: Box::new(filter),
8323            })),
8324        }
8325    }
8326
8327    fn rewrite_generic_aggregate_filter_for_tsql(
8328        agg: &mut crate::expressions::AggregateFunction,
8329        filter: Expression,
8330    ) {
8331        let is_count =
8332            agg.name.eq_ignore_ascii_case("COUNT") || agg.name.eq_ignore_ascii_case("COUNT_BIG");
8333        let is_count_star = is_count
8334            && (agg.args.is_empty()
8335                || (agg.args.len() == 1 && matches!(agg.args[0], Expression::Star(_))));
8336
8337        if is_count_star {
8338            agg.args = vec![Self::conditional_aggregate_value_for_tsql(
8339                filter,
8340                Expression::number(1),
8341            )];
8342        } else if !agg.args.is_empty() {
8343            agg.args = agg
8344                .args
8345                .drain(..)
8346                .map(|arg| Self::conditional_aggregate_value_for_tsql(filter.clone(), arg))
8347                .collect();
8348        } else {
8349            agg.filter = Some(filter);
8350        }
8351    }
8352
8353    fn conditional_aggregate_value_for_tsql(filter: Expression, value: Expression) -> Expression {
8354        let filter = crate::transforms::ensure_bool_condition(filter);
8355        Expression::Case(Box::new(crate::expressions::Case {
8356            operand: None,
8357            whens: vec![(filter, value)],
8358            else_: None,
8359            comments: Vec::new(),
8360            inferred_type: None,
8361        }))
8362    }
8363
8364    fn reject_pgvector_distance_operators_for_sqlite(&self, sql: &str) -> Result<()> {
8365        let tokens = self.tokenize(sql)?;
8366        for (i, token) in tokens.iter().enumerate() {
8367            if token.token_type == TokenType::NullsafeEq {
8368                return Err(crate::error::Error::unsupported(
8369                    "PostgreSQL pgvector cosine distance operator <=>",
8370                    "SQLite",
8371                ));
8372            }
8373            if token.token_type == TokenType::Lt
8374                && tokens
8375                    .get(i + 1)
8376                    .is_some_and(|token| token.token_type == TokenType::Tilde)
8377                && tokens
8378                    .get(i + 2)
8379                    .is_some_and(|token| token.token_type == TokenType::Gt)
8380            {
8381                return Err(crate::error::Error::unsupported(
8382                    "PostgreSQL pgvector Hamming distance operator <~>",
8383                    "SQLite",
8384                ));
8385            }
8386        }
8387        Ok(())
8388    }
8389
8390    fn normalize_sqlite_double_quoted_defaults(expr: Expression) -> Result<Expression> {
8391        fn normalize_default_expr(expr: Expression) -> Result<Expression> {
8392            transform_recursive(expr, &|e| match e {
8393                Expression::Column(col)
8394                    if col.table.is_none() && col.name.quoted && !col.join_mark =>
8395                {
8396                    Ok(Expression::Literal(Box::new(Literal::String(
8397                        col.name.name,
8398                    ))))
8399                }
8400                Expression::Identifier(id) if id.quoted => {
8401                    Ok(Expression::Literal(Box::new(Literal::String(id.name))))
8402                }
8403                _ => Ok(e),
8404            })
8405        }
8406
8407        fn normalize_column_default(col: &mut crate::expressions::ColumnDef) -> Result<()> {
8408            if let Some(default) = col.default.take() {
8409                col.default = Some(normalize_default_expr(default)?);
8410            }
8411
8412            for constraint in &mut col.constraints {
8413                if let ColumnConstraint::Default(default) = constraint {
8414                    *default = normalize_default_expr(default.clone())?;
8415                }
8416            }
8417
8418            Ok(())
8419        }
8420
8421        transform_recursive(expr, &|e| match e {
8422            Expression::CreateTable(mut ct) => {
8423                for column in &mut ct.columns {
8424                    normalize_column_default(column)?;
8425                }
8426                Ok(Expression::CreateTable(ct))
8427            }
8428            Expression::ColumnDef(mut col) => {
8429                normalize_column_default(&mut col)?;
8430                Ok(Expression::ColumnDef(col))
8431            }
8432            _ => Ok(e),
8433        })
8434    }
8435
8436    fn normalize_postgres_to_sqlite_types(expr: Expression) -> Result<Expression> {
8437        fn sqlite_type(dt: crate::expressions::DataType) -> crate::expressions::DataType {
8438            use crate::expressions::DataType;
8439
8440            match dt {
8441                DataType::Bit { .. } => DataType::Int {
8442                    length: None,
8443                    integer_spelling: true,
8444                },
8445                DataType::TextWithLength { .. } => DataType::Text,
8446                DataType::VarChar { .. } => DataType::Text,
8447                DataType::Char { .. } => DataType::Text,
8448                DataType::Timestamp { timezone: true, .. } => DataType::Text,
8449                DataType::Custom { name } => {
8450                    let base = name
8451                        .split_once('(')
8452                        .map_or(name.as_str(), |(base, _)| base)
8453                        .trim();
8454                    if base.eq_ignore_ascii_case("TSVECTOR")
8455                        || base.eq_ignore_ascii_case("TIMESTAMPTZ")
8456                        || base.eq_ignore_ascii_case("TIMESTAMP WITH TIME ZONE")
8457                        || base.eq_ignore_ascii_case("NVARCHAR")
8458                        || base.eq_ignore_ascii_case("NCHAR")
8459                    {
8460                        DataType::Text
8461                    } else {
8462                        DataType::Custom { name }
8463                    }
8464                }
8465                _ => dt,
8466            }
8467        }
8468
8469        transform_recursive(expr, &|e| match e {
8470            Expression::DataType(dt) => Ok(Expression::DataType(sqlite_type(dt))),
8471            Expression::CreateTable(mut ct) => {
8472                for column in &mut ct.columns {
8473                    column.data_type = sqlite_type(column.data_type.clone());
8474                }
8475                Ok(Expression::CreateTable(ct))
8476            }
8477            _ => Ok(e),
8478        })
8479    }
8480
8481    fn normalize_postgres_to_fabric_types(expr: Expression) -> Result<Expression> {
8482        fn fabric_type(dt: crate::expressions::DataType) -> crate::expressions::DataType {
8483            use crate::expressions::DataType;
8484
8485            match dt {
8486                DataType::Decimal {
8487                    precision: None,
8488                    scale: None,
8489                } => DataType::Decimal {
8490                    precision: Some(38),
8491                    scale: Some(10),
8492                },
8493                DataType::Json | DataType::JsonB => DataType::Custom {
8494                    name: "VARCHAR(MAX)".to_string(),
8495                },
8496                _ => dt,
8497            }
8498        }
8499
8500        transform_recursive(expr, &|e| match e {
8501            Expression::DataType(dt) => Ok(Expression::DataType(fabric_type(dt))),
8502            Expression::CreateTable(mut ct) => {
8503                for column in &mut ct.columns {
8504                    column.data_type = fabric_type(column.data_type.clone());
8505                }
8506                Ok(Expression::CreateTable(ct))
8507            }
8508            Expression::ColumnDef(mut col) => {
8509                col.data_type = fabric_type(col.data_type);
8510                Ok(Expression::ColumnDef(col))
8511            }
8512            _ => Ok(e),
8513        })
8514    }
8515
8516    /// For DuckDB target: when FROM clause contains RANGE(n), replace
8517    /// `(ROW_NUMBER() OVER (ORDER BY 1 NULLS FIRST) - 1)` with `range` in select expressions.
8518    /// This handles SEQ1/2/4/8 → RANGE transpilation from Snowflake.
8519    fn seq_rownum_to_range(expr: Expression) -> Result<Expression> {
8520        if let Expression::Select(mut select) = expr {
8521            // Check if FROM contains a RANGE function
8522            let has_range_from = if let Some(ref from) = select.from {
8523                from.expressions.iter().any(|e| {
8524                    // Check for direct RANGE(...) or aliased RANGE(...)
8525                    match e {
8526                        Expression::Function(f) => f.name.eq_ignore_ascii_case("RANGE"),
8527                        Expression::Alias(a) => {
8528                            matches!(&a.this, Expression::Function(f) if f.name.eq_ignore_ascii_case("RANGE"))
8529                        }
8530                        _ => false,
8531                    }
8532                })
8533            } else {
8534                false
8535            };
8536
8537            if has_range_from {
8538                // Replace the ROW_NUMBER pattern in select expressions
8539                select.expressions = select
8540                    .expressions
8541                    .into_iter()
8542                    .map(|e| Self::replace_rownum_with_range(e))
8543                    .collect();
8544            }
8545
8546            Ok(Expression::Select(select))
8547        } else {
8548            Ok(expr)
8549        }
8550    }
8551
8552    /// Replace `(ROW_NUMBER() OVER (...) - 1)` with `range` column reference
8553    fn replace_rownum_with_range(expr: Expression) -> Expression {
8554        match expr {
8555            // Match: (ROW_NUMBER() OVER (...) - 1) % N → range % N
8556            Expression::Mod(op) => {
8557                let new_left = Self::try_replace_rownum_paren(&op.left);
8558                Expression::Mod(Box::new(crate::expressions::BinaryOp {
8559                    left: new_left,
8560                    right: op.right,
8561                    left_comments: op.left_comments,
8562                    operator_comments: op.operator_comments,
8563                    trailing_comments: op.trailing_comments,
8564                    inferred_type: op.inferred_type,
8565                }))
8566            }
8567            // Match: (CASE WHEN (ROW...) % N >= ... THEN ... ELSE ... END)
8568            Expression::Paren(p) => {
8569                let inner = Self::replace_rownum_with_range(p.this);
8570                Expression::Paren(Box::new(crate::expressions::Paren {
8571                    this: inner,
8572                    trailing_comments: p.trailing_comments,
8573                }))
8574            }
8575            Expression::Case(mut c) => {
8576                // Replace ROW_NUMBER in WHEN conditions and THEN expressions
8577                c.whens = c
8578                    .whens
8579                    .into_iter()
8580                    .map(|(cond, then)| {
8581                        (
8582                            Self::replace_rownum_with_range(cond),
8583                            Self::replace_rownum_with_range(then),
8584                        )
8585                    })
8586                    .collect();
8587                if let Some(else_) = c.else_ {
8588                    c.else_ = Some(Self::replace_rownum_with_range(else_));
8589                }
8590                Expression::Case(c)
8591            }
8592            Expression::Gte(op) => Expression::Gte(Box::new(crate::expressions::BinaryOp {
8593                left: Self::replace_rownum_with_range(op.left),
8594                right: op.right,
8595                left_comments: op.left_comments,
8596                operator_comments: op.operator_comments,
8597                trailing_comments: op.trailing_comments,
8598                inferred_type: op.inferred_type,
8599            })),
8600            Expression::Sub(op) => Expression::Sub(Box::new(crate::expressions::BinaryOp {
8601                left: Self::replace_rownum_with_range(op.left),
8602                right: op.right,
8603                left_comments: op.left_comments,
8604                operator_comments: op.operator_comments,
8605                trailing_comments: op.trailing_comments,
8606                inferred_type: op.inferred_type,
8607            })),
8608            Expression::Alias(mut a) => {
8609                a.this = Self::replace_rownum_with_range(a.this);
8610                Expression::Alias(a)
8611            }
8612            other => other,
8613        }
8614    }
8615
8616    /// Check if an expression is `(ROW_NUMBER() OVER (...) - 1)` and replace with `range`
8617    fn try_replace_rownum_paren(expr: &Expression) -> Expression {
8618        if let Expression::Paren(ref p) = expr {
8619            if let Expression::Sub(ref sub) = p.this {
8620                if let Expression::WindowFunction(ref wf) = sub.left {
8621                    if let Expression::Function(ref f) = wf.this {
8622                        if f.name.eq_ignore_ascii_case("ROW_NUMBER") {
8623                            if let Expression::Literal(ref lit) = sub.right {
8624                                if let crate::expressions::Literal::Number(ref n) = lit.as_ref() {
8625                                    if n == "1" {
8626                                        return Expression::column("range");
8627                                    }
8628                                }
8629                            }
8630                        }
8631                    }
8632                }
8633            }
8634        }
8635        expr.clone()
8636    }
8637
8638    /// Transform BigQuery GENERATE_DATE_ARRAY in UNNEST for Snowflake target.
8639    /// Converts:
8640    ///   SELECT ..., alias, ... FROM t CROSS JOIN UNNEST(GENERATE_DATE_ARRAY(start, end, INTERVAL '1' unit)) AS alias
8641    /// To:
8642    ///   SELECT ..., DATEADD(unit, CAST(alias AS INT), CAST(start AS DATE)) AS alias, ...
8643    ///   FROM t, LATERAL FLATTEN(INPUT => ARRAY_GENERATE_RANGE(0, DATEDIFF(unit, start, end) + 1)) AS _t0(seq, key, path, index, alias, this)
8644    fn transform_generate_date_array_snowflake(expr: Expression) -> Result<Expression> {
8645        use crate::expressions::*;
8646        transform_recursive(expr, &|e| {
8647            // Handle ARRAY_SIZE(GENERATE_DATE_ARRAY(...)) -> ARRAY_SIZE((SELECT ARRAY_AGG(*) FROM subquery))
8648            if let Expression::ArraySize(ref af) = e {
8649                if let Expression::Function(ref f) = af.this {
8650                    if f.name.eq_ignore_ascii_case("GENERATE_DATE_ARRAY") && f.args.len() >= 2 {
8651                        let result = Self::convert_array_size_gda_snowflake(f)?;
8652                        return Ok(result);
8653                    }
8654                }
8655            }
8656
8657            let Expression::Select(mut sel) = e else {
8658                return Ok(e);
8659            };
8660
8661            // Find joins with UNNEST containing GenerateSeries (from GENERATE_DATE_ARRAY conversion)
8662            let mut gda_info: Option<(String, Expression, Expression, String)> = None; // (alias_name, start_expr, end_expr, unit)
8663            let mut gda_join_idx: Option<usize> = None;
8664
8665            for (idx, join) in sel.joins.iter().enumerate() {
8666                // The join.this may be:
8667                // 1. Unnest(UnnestFunc { alias: Some("mnth"), ... })
8668                // 2. Alias(Alias { this: Unnest(UnnestFunc { alias: None, ... }), alias: "mnth", ... })
8669                let (unnest_ref, alias_name) = match &join.this {
8670                    Expression::Unnest(ref unnest) => {
8671                        let alias = unnest.alias.as_ref().map(|id| id.name.clone());
8672                        (Some(unnest.as_ref()), alias)
8673                    }
8674                    Expression::Alias(ref a) => {
8675                        if let Expression::Unnest(ref unnest) = a.this {
8676                            (Some(unnest.as_ref()), Some(a.alias.name.clone()))
8677                        } else {
8678                            (None, None)
8679                        }
8680                    }
8681                    _ => (None, None),
8682                };
8683
8684                if let (Some(unnest), Some(alias)) = (unnest_ref, alias_name) {
8685                    // Check the main expression (this) of the UNNEST for GENERATE_DATE_ARRAY function
8686                    if let Expression::Function(ref f) = unnest.this {
8687                        if f.name.eq_ignore_ascii_case("GENERATE_DATE_ARRAY") && f.args.len() >= 2 {
8688                            let start_expr = f.args[0].clone();
8689                            let end_expr = f.args[1].clone();
8690                            let step = f.args.get(2).cloned();
8691
8692                            // Extract unit from step interval
8693                            let unit = if let Some(Expression::Interval(ref iv)) = step {
8694                                if let Some(IntervalUnitSpec::Simple { ref unit, .. }) = iv.unit {
8695                                    Some(format!("{:?}", unit).to_ascii_uppercase())
8696                                } else if let Some(ref this) = iv.this {
8697                                    // The interval may be stored as a string like "1 MONTH"
8698                                    if let Expression::Literal(lit) = this {
8699                                        if let Literal::String(ref s) = lit.as_ref() {
8700                                            let parts: Vec<&str> = s.split_whitespace().collect();
8701                                            if parts.len() == 2 {
8702                                                Some(parts[1].to_ascii_uppercase())
8703                                            } else if parts.len() == 1 {
8704                                                // Single word like "MONTH" or just "1"
8705                                                let upper = parts[0].to_ascii_uppercase();
8706                                                if matches!(
8707                                                    upper.as_str(),
8708                                                    "YEAR"
8709                                                        | "QUARTER"
8710                                                        | "MONTH"
8711                                                        | "WEEK"
8712                                                        | "DAY"
8713                                                        | "HOUR"
8714                                                        | "MINUTE"
8715                                                        | "SECOND"
8716                                                ) {
8717                                                    Some(upper)
8718                                                } else {
8719                                                    None
8720                                                }
8721                                            } else {
8722                                                None
8723                                            }
8724                                        } else {
8725                                            None
8726                                        }
8727                                    } else {
8728                                        None
8729                                    }
8730                                } else {
8731                                    None
8732                                }
8733                            } else {
8734                                None
8735                            };
8736
8737                            if let Some(unit_str) = unit {
8738                                gda_info = Some((alias, start_expr, end_expr, unit_str));
8739                                gda_join_idx = Some(idx);
8740                            }
8741                        }
8742                    }
8743                }
8744                if gda_info.is_some() {
8745                    break;
8746                }
8747            }
8748
8749            let Some((alias_name, start_expr, end_expr, unit_str)) = gda_info else {
8750                // Also check FROM clause for UNNEST(GENERATE_DATE_ARRAY(...)) patterns
8751                // This handles Generic->Snowflake where GENERATE_DATE_ARRAY is in FROM, not in JOIN
8752                let result = Self::try_transform_from_gda_snowflake(sel);
8753                return result;
8754            };
8755            let join_idx = gda_join_idx.unwrap();
8756
8757            // Build ARRAY_GENERATE_RANGE(0, DATEDIFF(unit, start, end) + 1)
8758            // ARRAY_GENERATE_RANGE uses exclusive end, and we need DATEDIFF + 1 values
8759            // (inclusive date range), so the exclusive end is DATEDIFF + 1.
8760            let datediff = Expression::Function(Box::new(Function::new(
8761                "DATEDIFF".to_string(),
8762                vec![
8763                    Expression::boxed_column(Column {
8764                        name: Identifier::new(&unit_str),
8765                        table: None,
8766                        join_mark: false,
8767                        trailing_comments: vec![],
8768                        span: None,
8769                        inferred_type: None,
8770                    }),
8771                    start_expr.clone(),
8772                    end_expr.clone(),
8773                ],
8774            )));
8775            let datediff_plus_one = Expression::Add(Box::new(BinaryOp {
8776                left: datediff,
8777                right: Expression::Literal(Box::new(Literal::Number("1".to_string()))),
8778                left_comments: vec![],
8779                operator_comments: vec![],
8780                trailing_comments: vec![],
8781                inferred_type: None,
8782            }));
8783
8784            let array_gen_range = Expression::Function(Box::new(Function::new(
8785                "ARRAY_GENERATE_RANGE".to_string(),
8786                vec![
8787                    Expression::Literal(Box::new(Literal::Number("0".to_string()))),
8788                    datediff_plus_one,
8789                ],
8790            )));
8791
8792            // Build FLATTEN(INPUT => ARRAY_GENERATE_RANGE(...))
8793            let flatten_input = Expression::NamedArgument(Box::new(NamedArgument {
8794                name: Identifier::new("INPUT"),
8795                value: array_gen_range,
8796                separator: crate::expressions::NamedArgSeparator::DArrow,
8797            }));
8798            let flatten = Expression::Function(Box::new(Function::new(
8799                "FLATTEN".to_string(),
8800                vec![flatten_input],
8801            )));
8802
8803            // Build LATERAL FLATTEN(...) AS _t0(seq, key, path, index, alias, this)
8804            let alias_table = Alias {
8805                this: flatten,
8806                alias: Identifier::new("_t0"),
8807                column_aliases: vec![
8808                    Identifier::new("seq"),
8809                    Identifier::new("key"),
8810                    Identifier::new("path"),
8811                    Identifier::new("index"),
8812                    Identifier::new(&alias_name),
8813                    Identifier::new("this"),
8814                ],
8815                alias_explicit_as: false,
8816                alias_keyword: None,
8817                pre_alias_comments: vec![],
8818                trailing_comments: vec![],
8819                inferred_type: None,
8820            };
8821            let lateral_expr = Expression::Lateral(Box::new(Lateral {
8822                this: Box::new(Expression::Alias(Box::new(alias_table))),
8823                view: None,
8824                outer: None,
8825                alias: None,
8826                alias_quoted: false,
8827                cross_apply: None,
8828                ordinality: None,
8829                column_aliases: vec![],
8830            }));
8831
8832            // Remove the original join and add to FROM expressions
8833            sel.joins.remove(join_idx);
8834            if let Some(ref mut from) = sel.from {
8835                from.expressions.push(lateral_expr);
8836            }
8837
8838            // Build DATEADD(unit, CAST(alias AS INT), CAST(start AS DATE))
8839            let dateadd_expr = Expression::Function(Box::new(Function::new(
8840                "DATEADD".to_string(),
8841                vec![
8842                    Expression::boxed_column(Column {
8843                        name: Identifier::new(&unit_str),
8844                        table: None,
8845                        join_mark: false,
8846                        trailing_comments: vec![],
8847                        span: None,
8848                        inferred_type: None,
8849                    }),
8850                    Expression::Cast(Box::new(Cast {
8851                        this: Expression::boxed_column(Column {
8852                            name: Identifier::new(&alias_name),
8853                            table: None,
8854                            join_mark: false,
8855                            trailing_comments: vec![],
8856                            span: None,
8857                            inferred_type: None,
8858                        }),
8859                        to: DataType::Int {
8860                            length: None,
8861                            integer_spelling: false,
8862                        },
8863                        trailing_comments: vec![],
8864                        double_colon_syntax: false,
8865                        format: None,
8866                        default: None,
8867                        inferred_type: None,
8868                    })),
8869                    Expression::Cast(Box::new(Cast {
8870                        this: start_expr.clone(),
8871                        to: DataType::Date,
8872                        trailing_comments: vec![],
8873                        double_colon_syntax: false,
8874                        format: None,
8875                        default: None,
8876                        inferred_type: None,
8877                    })),
8878                ],
8879            )));
8880
8881            // Replace references to the alias in the SELECT list
8882            let new_exprs: Vec<Expression> = sel
8883                .expressions
8884                .iter()
8885                .map(|expr| Self::replace_column_ref_with_dateadd(expr, &alias_name, &dateadd_expr))
8886                .collect();
8887            sel.expressions = new_exprs;
8888
8889            Ok(Expression::Select(sel))
8890        })
8891    }
8892
8893    /// Helper: replace column references to `alias_name` with dateadd expression
8894    fn replace_column_ref_with_dateadd(
8895        expr: &Expression,
8896        alias_name: &str,
8897        dateadd: &Expression,
8898    ) -> Expression {
8899        use crate::expressions::*;
8900        match expr {
8901            Expression::Column(c) if c.name.name == alias_name && c.table.is_none() => {
8902                // Plain column reference -> DATEADD(...) AS alias_name
8903                Expression::Alias(Box::new(Alias {
8904                    this: dateadd.clone(),
8905                    alias: Identifier::new(alias_name),
8906                    column_aliases: vec![],
8907                    alias_explicit_as: false,
8908                    alias_keyword: None,
8909                    pre_alias_comments: vec![],
8910                    trailing_comments: vec![],
8911                    inferred_type: None,
8912                }))
8913            }
8914            Expression::Alias(a) => {
8915                // Check if the inner expression references the alias
8916                let new_this = Self::replace_column_ref_inner(&a.this, alias_name, dateadd);
8917                Expression::Alias(Box::new(Alias {
8918                    this: new_this,
8919                    alias: a.alias.clone(),
8920                    column_aliases: a.column_aliases.clone(),
8921                    alias_explicit_as: false,
8922                    alias_keyword: None,
8923                    pre_alias_comments: a.pre_alias_comments.clone(),
8924                    trailing_comments: a.trailing_comments.clone(),
8925                    inferred_type: None,
8926                }))
8927            }
8928            _ => expr.clone(),
8929        }
8930    }
8931
8932    /// Helper: replace column references in inner expression (not top-level)
8933    fn replace_column_ref_inner(
8934        expr: &Expression,
8935        alias_name: &str,
8936        dateadd: &Expression,
8937    ) -> Expression {
8938        use crate::expressions::*;
8939        match expr {
8940            Expression::Column(c) if c.name.name == alias_name && c.table.is_none() => {
8941                dateadd.clone()
8942            }
8943            Expression::Add(op) => {
8944                let left = Self::replace_column_ref_inner(&op.left, alias_name, dateadd);
8945                let right = Self::replace_column_ref_inner(&op.right, alias_name, dateadd);
8946                Expression::Add(Box::new(BinaryOp {
8947                    left,
8948                    right,
8949                    left_comments: op.left_comments.clone(),
8950                    operator_comments: op.operator_comments.clone(),
8951                    trailing_comments: op.trailing_comments.clone(),
8952                    inferred_type: None,
8953                }))
8954            }
8955            Expression::Sub(op) => {
8956                let left = Self::replace_column_ref_inner(&op.left, alias_name, dateadd);
8957                let right = Self::replace_column_ref_inner(&op.right, alias_name, dateadd);
8958                Expression::Sub(Box::new(BinaryOp {
8959                    left,
8960                    right,
8961                    left_comments: op.left_comments.clone(),
8962                    operator_comments: op.operator_comments.clone(),
8963                    trailing_comments: op.trailing_comments.clone(),
8964                    inferred_type: None,
8965                }))
8966            }
8967            Expression::Mul(op) => {
8968                let left = Self::replace_column_ref_inner(&op.left, alias_name, dateadd);
8969                let right = Self::replace_column_ref_inner(&op.right, alias_name, dateadd);
8970                Expression::Mul(Box::new(BinaryOp {
8971                    left,
8972                    right,
8973                    left_comments: op.left_comments.clone(),
8974                    operator_comments: op.operator_comments.clone(),
8975                    trailing_comments: op.trailing_comments.clone(),
8976                    inferred_type: None,
8977                }))
8978            }
8979            _ => expr.clone(),
8980        }
8981    }
8982
8983    /// Handle UNNEST(GENERATE_DATE_ARRAY(...)) in FROM clause for Snowflake target.
8984    /// Converts to a subquery with DATEADD + TABLE(FLATTEN(ARRAY_GENERATE_RANGE(...))).
8985    fn try_transform_from_gda_snowflake(
8986        mut sel: Box<crate::expressions::Select>,
8987    ) -> Result<Expression> {
8988        use crate::expressions::*;
8989
8990        // Extract GDA info from FROM clause
8991        let mut gda_info: Option<(
8992            usize,
8993            String,
8994            Expression,
8995            Expression,
8996            String,
8997            Option<(String, Vec<Identifier>)>,
8998        )> = None; // (from_idx, col_name, start, end, unit, outer_alias)
8999
9000        if let Some(ref from) = sel.from {
9001            for (idx, table_expr) in from.expressions.iter().enumerate() {
9002                // Pattern 1: UNNEST(GENERATE_DATE_ARRAY(...))
9003                // Pattern 2: Alias(UNNEST(GENERATE_DATE_ARRAY(...))) AS _q(date_week)
9004                let (unnest_opt, outer_alias_info) = match table_expr {
9005                    Expression::Unnest(ref unnest) => (Some(unnest.as_ref()), None),
9006                    Expression::Alias(ref a) => {
9007                        if let Expression::Unnest(ref unnest) = a.this {
9008                            let alias_info = (a.alias.name.clone(), a.column_aliases.clone());
9009                            (Some(unnest.as_ref()), Some(alias_info))
9010                        } else {
9011                            (None, None)
9012                        }
9013                    }
9014                    _ => (None, None),
9015                };
9016
9017                if let Some(unnest) = unnest_opt {
9018                    // Check for GENERATE_DATE_ARRAY function
9019                    let func_opt = match &unnest.this {
9020                        Expression::Function(ref f)
9021                            if f.name.eq_ignore_ascii_case("GENERATE_DATE_ARRAY")
9022                                && f.args.len() >= 2 =>
9023                        {
9024                            Some(f)
9025                        }
9026                        // Also check for GenerateSeries (from earlier normalization)
9027                        _ => None,
9028                    };
9029
9030                    if let Some(f) = func_opt {
9031                        let start_expr = f.args[0].clone();
9032                        let end_expr = f.args[1].clone();
9033                        let step = f.args.get(2).cloned();
9034
9035                        // Extract unit and column name
9036                        let unit = Self::extract_interval_unit_str(&step);
9037                        let col_name = outer_alias_info
9038                            .as_ref()
9039                            .and_then(|(_, cols)| cols.first().map(|id| id.name.clone()))
9040                            .unwrap_or_else(|| "value".to_string());
9041
9042                        if let Some(unit_str) = unit {
9043                            gda_info = Some((
9044                                idx,
9045                                col_name,
9046                                start_expr,
9047                                end_expr,
9048                                unit_str,
9049                                outer_alias_info,
9050                            ));
9051                            break;
9052                        }
9053                    }
9054                }
9055            }
9056        }
9057
9058        let Some((from_idx, col_name, start_expr, end_expr, unit_str, outer_alias_info)) = gda_info
9059        else {
9060            return Ok(Expression::Select(sel));
9061        };
9062
9063        // Build the Snowflake subquery:
9064        // (SELECT DATEADD(unit, CAST(col_name AS INT), CAST(start AS DATE)) AS col_name
9065        //  FROM TABLE(FLATTEN(INPUT => ARRAY_GENERATE_RANGE(0, DATEDIFF(unit, start, end) + 1))) AS _t0(seq, key, path, index, col_name, this))
9066
9067        // DATEDIFF(unit, start, end)
9068        let datediff = Expression::Function(Box::new(Function::new(
9069            "DATEDIFF".to_string(),
9070            vec![
9071                Expression::boxed_column(Column {
9072                    name: Identifier::new(&unit_str),
9073                    table: None,
9074                    join_mark: false,
9075                    trailing_comments: vec![],
9076                    span: None,
9077                    inferred_type: None,
9078                }),
9079                start_expr.clone(),
9080                end_expr.clone(),
9081            ],
9082        )));
9083        // DATEDIFF(...) + 1
9084        let datediff_plus_one = Expression::Add(Box::new(BinaryOp {
9085            left: datediff,
9086            right: Expression::Literal(Box::new(Literal::Number("1".to_string()))),
9087            left_comments: vec![],
9088            operator_comments: vec![],
9089            trailing_comments: vec![],
9090            inferred_type: None,
9091        }));
9092
9093        let array_gen_range = Expression::Function(Box::new(Function::new(
9094            "ARRAY_GENERATE_RANGE".to_string(),
9095            vec![
9096                Expression::Literal(Box::new(Literal::Number("0".to_string()))),
9097                datediff_plus_one,
9098            ],
9099        )));
9100
9101        // TABLE(FLATTEN(INPUT => ...))
9102        let flatten_input = Expression::NamedArgument(Box::new(NamedArgument {
9103            name: Identifier::new("INPUT"),
9104            value: array_gen_range,
9105            separator: crate::expressions::NamedArgSeparator::DArrow,
9106        }));
9107        let flatten = Expression::Function(Box::new(Function::new(
9108            "FLATTEN".to_string(),
9109            vec![flatten_input],
9110        )));
9111
9112        // Determine alias name for the table: use outer alias or _t0
9113        let table_alias_name = outer_alias_info
9114            .as_ref()
9115            .map(|(name, _)| name.clone())
9116            .unwrap_or_else(|| "_t0".to_string());
9117
9118        // TABLE(FLATTEN(...)) AS _t0(seq, key, path, index, col_name, this)
9119        let table_func =
9120            Expression::Function(Box::new(Function::new("TABLE".to_string(), vec![flatten])));
9121        let flatten_aliased = Expression::Alias(Box::new(Alias {
9122            this: table_func,
9123            alias: Identifier::new(&table_alias_name),
9124            column_aliases: vec![
9125                Identifier::new("seq"),
9126                Identifier::new("key"),
9127                Identifier::new("path"),
9128                Identifier::new("index"),
9129                Identifier::new(&col_name),
9130                Identifier::new("this"),
9131            ],
9132            alias_explicit_as: false,
9133            alias_keyword: None,
9134            pre_alias_comments: vec![],
9135            trailing_comments: vec![],
9136            inferred_type: None,
9137        }));
9138
9139        // SELECT DATEADD(unit, CAST(col_name AS INT), CAST(start AS DATE)) AS col_name
9140        let dateadd_expr = Expression::Function(Box::new(Function::new(
9141            "DATEADD".to_string(),
9142            vec![
9143                Expression::boxed_column(Column {
9144                    name: Identifier::new(&unit_str),
9145                    table: None,
9146                    join_mark: false,
9147                    trailing_comments: vec![],
9148                    span: None,
9149                    inferred_type: None,
9150                }),
9151                Expression::Cast(Box::new(Cast {
9152                    this: Expression::boxed_column(Column {
9153                        name: Identifier::new(&col_name),
9154                        table: None,
9155                        join_mark: false,
9156                        trailing_comments: vec![],
9157                        span: None,
9158                        inferred_type: None,
9159                    }),
9160                    to: DataType::Int {
9161                        length: None,
9162                        integer_spelling: false,
9163                    },
9164                    trailing_comments: vec![],
9165                    double_colon_syntax: false,
9166                    format: None,
9167                    default: None,
9168                    inferred_type: None,
9169                })),
9170                // Use start_expr directly - it's already been normalized (DATE literal -> CAST)
9171                start_expr.clone(),
9172            ],
9173        )));
9174        let dateadd_aliased = Expression::Alias(Box::new(Alias {
9175            this: dateadd_expr,
9176            alias: Identifier::new(&col_name),
9177            column_aliases: vec![],
9178            alias_explicit_as: false,
9179            alias_keyword: None,
9180            pre_alias_comments: vec![],
9181            trailing_comments: vec![],
9182            inferred_type: None,
9183        }));
9184
9185        // Build inner SELECT
9186        let mut inner_select = Select::new();
9187        inner_select.expressions = vec![dateadd_aliased];
9188        inner_select.from = Some(From {
9189            expressions: vec![flatten_aliased],
9190        });
9191
9192        let inner_select_expr = Expression::Select(Box::new(inner_select));
9193        let subquery = Expression::Subquery(Box::new(Subquery {
9194            this: inner_select_expr,
9195            alias: None,
9196            column_aliases: vec![],
9197            alias_explicit_as: false,
9198            alias_keyword: None,
9199            order_by: None,
9200            limit: None,
9201            offset: None,
9202            distribute_by: None,
9203            sort_by: None,
9204            cluster_by: None,
9205            lateral: false,
9206            modifiers_inside: false,
9207            trailing_comments: vec![],
9208            inferred_type: None,
9209        }));
9210
9211        // If there was an outer alias (e.g., AS _q(date_week)), wrap with alias
9212        let replacement = if let Some((alias_name, col_aliases)) = outer_alias_info {
9213            Expression::Alias(Box::new(Alias {
9214                this: subquery,
9215                alias: Identifier::new(&alias_name),
9216                column_aliases: col_aliases,
9217                alias_explicit_as: false,
9218                alias_keyword: None,
9219                pre_alias_comments: vec![],
9220                trailing_comments: vec![],
9221                inferred_type: None,
9222            }))
9223        } else {
9224            subquery
9225        };
9226
9227        // Replace the FROM expression
9228        if let Some(ref mut from) = sel.from {
9229            from.expressions[from_idx] = replacement;
9230        }
9231
9232        Ok(Expression::Select(sel))
9233    }
9234
9235    /// Convert ARRAY_SIZE(GENERATE_DATE_ARRAY(start, end, step)) for Snowflake.
9236    /// Produces: ARRAY_SIZE((SELECT ARRAY_AGG(*) FROM (SELECT DATEADD(unit, CAST(value AS INT), start) AS value
9237    ///   FROM TABLE(FLATTEN(INPUT => ARRAY_GENERATE_RANGE(0, DATEDIFF(unit, start, end) + 1))) AS _t0(...))))
9238    fn convert_array_size_gda_snowflake(f: &crate::expressions::Function) -> Result<Expression> {
9239        use crate::expressions::*;
9240
9241        let start_expr = f.args[0].clone();
9242        let end_expr = f.args[1].clone();
9243        let step = f.args.get(2).cloned();
9244        let unit_str = Self::extract_interval_unit_str(&step).unwrap_or_else(|| "DAY".to_string());
9245        let col_name = "value";
9246
9247        // Build the inner subquery: same as try_transform_from_gda_snowflake
9248        let datediff = Expression::Function(Box::new(Function::new(
9249            "DATEDIFF".to_string(),
9250            vec![
9251                Expression::boxed_column(Column {
9252                    name: Identifier::new(&unit_str),
9253                    table: None,
9254                    join_mark: false,
9255                    trailing_comments: vec![],
9256                    span: None,
9257                    inferred_type: None,
9258                }),
9259                start_expr.clone(),
9260                end_expr.clone(),
9261            ],
9262        )));
9263        // DATEDIFF(...) + 1
9264        let datediff_plus_one = Expression::Add(Box::new(BinaryOp {
9265            left: datediff,
9266            right: Expression::Literal(Box::new(Literal::Number("1".to_string()))),
9267            left_comments: vec![],
9268            operator_comments: vec![],
9269            trailing_comments: vec![],
9270            inferred_type: None,
9271        }));
9272
9273        let array_gen_range = Expression::Function(Box::new(Function::new(
9274            "ARRAY_GENERATE_RANGE".to_string(),
9275            vec![
9276                Expression::Literal(Box::new(Literal::Number("0".to_string()))),
9277                datediff_plus_one,
9278            ],
9279        )));
9280
9281        let flatten_input = Expression::NamedArgument(Box::new(NamedArgument {
9282            name: Identifier::new("INPUT"),
9283            value: array_gen_range,
9284            separator: crate::expressions::NamedArgSeparator::DArrow,
9285        }));
9286        let flatten = Expression::Function(Box::new(Function::new(
9287            "FLATTEN".to_string(),
9288            vec![flatten_input],
9289        )));
9290
9291        let table_func =
9292            Expression::Function(Box::new(Function::new("TABLE".to_string(), vec![flatten])));
9293        let flatten_aliased = Expression::Alias(Box::new(Alias {
9294            this: table_func,
9295            alias: Identifier::new("_t0"),
9296            column_aliases: vec![
9297                Identifier::new("seq"),
9298                Identifier::new("key"),
9299                Identifier::new("path"),
9300                Identifier::new("index"),
9301                Identifier::new(col_name),
9302                Identifier::new("this"),
9303            ],
9304            alias_explicit_as: false,
9305            alias_keyword: None,
9306            pre_alias_comments: vec![],
9307            trailing_comments: vec![],
9308            inferred_type: None,
9309        }));
9310
9311        let dateadd_expr = Expression::Function(Box::new(Function::new(
9312            "DATEADD".to_string(),
9313            vec![
9314                Expression::boxed_column(Column {
9315                    name: Identifier::new(&unit_str),
9316                    table: None,
9317                    join_mark: false,
9318                    trailing_comments: vec![],
9319                    span: None,
9320                    inferred_type: None,
9321                }),
9322                Expression::Cast(Box::new(Cast {
9323                    this: Expression::boxed_column(Column {
9324                        name: Identifier::new(col_name),
9325                        table: None,
9326                        join_mark: false,
9327                        trailing_comments: vec![],
9328                        span: None,
9329                        inferred_type: None,
9330                    }),
9331                    to: DataType::Int {
9332                        length: None,
9333                        integer_spelling: false,
9334                    },
9335                    trailing_comments: vec![],
9336                    double_colon_syntax: false,
9337                    format: None,
9338                    default: None,
9339                    inferred_type: None,
9340                })),
9341                start_expr.clone(),
9342            ],
9343        )));
9344        let dateadd_aliased = Expression::Alias(Box::new(Alias {
9345            this: dateadd_expr,
9346            alias: Identifier::new(col_name),
9347            column_aliases: vec![],
9348            alias_explicit_as: false,
9349            alias_keyword: None,
9350            pre_alias_comments: vec![],
9351            trailing_comments: vec![],
9352            inferred_type: None,
9353        }));
9354
9355        // Inner SELECT: SELECT DATEADD(...) AS value FROM TABLE(FLATTEN(...)) AS _t0(...)
9356        let mut inner_select = Select::new();
9357        inner_select.expressions = vec![dateadd_aliased];
9358        inner_select.from = Some(From {
9359            expressions: vec![flatten_aliased],
9360        });
9361
9362        // Wrap in subquery for the inner part
9363        let inner_subquery = Expression::Subquery(Box::new(Subquery {
9364            this: Expression::Select(Box::new(inner_select)),
9365            alias: None,
9366            column_aliases: vec![],
9367            alias_explicit_as: false,
9368            alias_keyword: None,
9369            order_by: None,
9370            limit: None,
9371            offset: None,
9372            distribute_by: None,
9373            sort_by: None,
9374            cluster_by: None,
9375            lateral: false,
9376            modifiers_inside: false,
9377            trailing_comments: vec![],
9378            inferred_type: None,
9379        }));
9380
9381        // Outer: SELECT ARRAY_AGG(*) FROM (inner_subquery)
9382        let star = Expression::Star(Star {
9383            table: None,
9384            except: None,
9385            replace: None,
9386            rename: None,
9387            trailing_comments: vec![],
9388            span: None,
9389        });
9390        let array_agg = Expression::ArrayAgg(Box::new(AggFunc {
9391            this: star,
9392            distinct: false,
9393            filter: None,
9394            order_by: vec![],
9395            name: Some("ARRAY_AGG".to_string()),
9396            ignore_nulls: None,
9397            having_max: None,
9398            limit: None,
9399            inferred_type: None,
9400        }));
9401
9402        let mut outer_select = Select::new();
9403        outer_select.expressions = vec![array_agg];
9404        outer_select.from = Some(From {
9405            expressions: vec![inner_subquery],
9406        });
9407
9408        // Wrap in a subquery
9409        let outer_subquery = Expression::Subquery(Box::new(Subquery {
9410            this: Expression::Select(Box::new(outer_select)),
9411            alias: None,
9412            column_aliases: vec![],
9413            alias_explicit_as: false,
9414            alias_keyword: None,
9415            order_by: None,
9416            limit: None,
9417            offset: None,
9418            distribute_by: None,
9419            sort_by: None,
9420            cluster_by: None,
9421            lateral: false,
9422            modifiers_inside: false,
9423            trailing_comments: vec![],
9424            inferred_type: None,
9425        }));
9426
9427        // ARRAY_SIZE(subquery)
9428        Ok(Expression::ArraySize(Box::new(UnaryFunc::new(
9429            outer_subquery,
9430        ))))
9431    }
9432
9433    /// Extract interval unit string from an optional step expression.
9434    fn extract_interval_unit_str(step: &Option<Expression>) -> Option<String> {
9435        use crate::expressions::*;
9436        if let Some(Expression::Interval(ref iv)) = step {
9437            if let Some(IntervalUnitSpec::Simple { ref unit, .. }) = iv.unit {
9438                return Some(format!("{:?}", unit).to_ascii_uppercase());
9439            }
9440            if let Some(ref this) = iv.this {
9441                if let Expression::Literal(lit) = this {
9442                    if let Literal::String(ref s) = lit.as_ref() {
9443                        let parts: Vec<&str> = s.split_whitespace().collect();
9444                        if parts.len() == 2 {
9445                            return Some(parts[1].to_ascii_uppercase());
9446                        } else if parts.len() == 1 {
9447                            let upper = parts[0].to_ascii_uppercase();
9448                            if matches!(
9449                                upper.as_str(),
9450                                "YEAR"
9451                                    | "QUARTER"
9452                                    | "MONTH"
9453                                    | "WEEK"
9454                                    | "DAY"
9455                                    | "HOUR"
9456                                    | "MINUTE"
9457                                    | "SECOND"
9458                            ) {
9459                                return Some(upper);
9460                            }
9461                        }
9462                    }
9463                }
9464            }
9465        }
9466        // Default to DAY if no step or no interval
9467        if step.is_none() {
9468            return Some("DAY".to_string());
9469        }
9470        None
9471    }
9472
9473    fn normalize_snowflake_pretty(mut sql: String) -> String {
9474        if sql.contains("LATERAL IFF(_u.pos = _u_2.pos_2, _u_2.entity, NULL) AS datasource(SEQ, KEY, PATH, INDEX, VALUE, THIS)")
9475            && sql.contains("ARRAY_GENERATE_RANGE(0, (GREATEST(ARRAY_SIZE(INPUT => PARSE_JSON(flags))) - 1) + 1)")
9476        {
9477            sql = sql.replace(
9478                "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')",
9479                "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    )",
9480            );
9481
9482            sql = sql.replace(
9483                "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)",
9484                "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)",
9485            );
9486
9487            sql = sql.replace(
9488                "OR (_u.pos > (ARRAY_SIZE(INPUT => PARSE_JSON(flags)) - 1)\n  AND _u_2.pos_2 = (ARRAY_SIZE(INPUT => PARSE_JSON(flags)) - 1))",
9489                "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  )",
9490            );
9491        }
9492
9493        sql
9494    }
9495
9496    #[cfg(feature = "transpile")]
9497    fn wrap_tsql_top_level_values(expr: Expression) -> Expression {
9498        match expr {
9499            Expression::Values(values) => Self::tsql_values_as_select(*values),
9500            Expression::Union(mut union) => {
9501                let left = std::mem::replace(&mut union.left, Expression::Null(Null));
9502                let right = std::mem::replace(&mut union.right, Expression::Null(Null));
9503                union.left = Self::wrap_tsql_values_set_operand(left);
9504                union.right = Self::wrap_tsql_values_set_operand(right);
9505                Expression::Union(union)
9506            }
9507            Expression::Intersect(mut intersect) => {
9508                let left = std::mem::replace(&mut intersect.left, Expression::Null(Null));
9509                let right = std::mem::replace(&mut intersect.right, Expression::Null(Null));
9510                intersect.left = Self::wrap_tsql_values_set_operand(left);
9511                intersect.right = Self::wrap_tsql_values_set_operand(right);
9512                Expression::Intersect(intersect)
9513            }
9514            Expression::Except(mut except) => {
9515                let left = std::mem::replace(&mut except.left, Expression::Null(Null));
9516                let right = std::mem::replace(&mut except.right, Expression::Null(Null));
9517                except.left = Self::wrap_tsql_values_set_operand(left);
9518                except.right = Self::wrap_tsql_values_set_operand(right);
9519                Expression::Except(except)
9520            }
9521            other => other,
9522        }
9523    }
9524
9525    #[cfg(feature = "transpile")]
9526    fn wrap_tsql_values_set_operand(expr: Expression) -> Expression {
9527        match expr {
9528            Expression::Values(values) => Self::tsql_values_as_select(*values),
9529            Expression::Select(mut select)
9530                if Self::is_parser_wrapped_values_set_operand(&select) =>
9531            {
9532                let mut from = select.from.take().expect("checked as present");
9533                let Expression::Values(values) = from
9534                    .expressions
9535                    .pop()
9536                    .expect("checked as a single VALUES source")
9537                else {
9538                    unreachable!("checked as a VALUES source");
9539                };
9540                Self::tsql_values_as_select(*values)
9541            }
9542            Expression::Annotated(mut annotated) => {
9543                annotated.this = Self::wrap_tsql_values_set_operand(annotated.this);
9544                Expression::Annotated(annotated)
9545            }
9546            Expression::Union(mut union) => {
9547                let left = std::mem::replace(&mut union.left, Expression::Null(Null));
9548                let right = std::mem::replace(&mut union.right, Expression::Null(Null));
9549                union.left = Self::wrap_tsql_values_set_operand(left);
9550                union.right = Self::wrap_tsql_values_set_operand(right);
9551                Expression::Union(union)
9552            }
9553            Expression::Intersect(mut intersect) => {
9554                let left = std::mem::replace(&mut intersect.left, Expression::Null(Null));
9555                let right = std::mem::replace(&mut intersect.right, Expression::Null(Null));
9556                intersect.left = Self::wrap_tsql_values_set_operand(left);
9557                intersect.right = Self::wrap_tsql_values_set_operand(right);
9558                Expression::Intersect(intersect)
9559            }
9560            Expression::Except(mut except) => {
9561                let left = std::mem::replace(&mut except.left, Expression::Null(Null));
9562                let right = std::mem::replace(&mut except.right, Expression::Null(Null));
9563                except.left = Self::wrap_tsql_values_set_operand(left);
9564                except.right = Self::wrap_tsql_values_set_operand(right);
9565                Expression::Except(except)
9566            }
9567            other => other,
9568        }
9569    }
9570
9571    #[cfg(feature = "transpile")]
9572    fn is_parser_wrapped_values_set_operand(select: &Select) -> bool {
9573        let Some(from) = &select.from else {
9574            return false;
9575        };
9576        let [Expression::Values(values)] = from.expressions.as_slice() else {
9577            return false;
9578        };
9579        if !values
9580            .alias
9581            .as_ref()
9582            .is_some_and(|alias| alias.name == "_values")
9583        {
9584            return false;
9585        }
9586
9587        let mut parser_wrapper = Select::new();
9588        parser_wrapper.expressions = vec![Expression::star()];
9589        parser_wrapper.from = Some(from.clone());
9590        select == &parser_wrapper
9591    }
9592
9593    #[cfg(feature = "transpile")]
9594    fn tsql_values_as_select(mut values: crate::expressions::Values) -> Expression {
9595        let column_aliases = if values.column_aliases.is_empty() {
9596            let column_count = values
9597                .expressions
9598                .first()
9599                .map(|row| row.expressions.len())
9600                .unwrap_or(0);
9601            (1..=column_count)
9602                .map(|index| Identifier::new(format!("column{index}")))
9603                .collect()
9604        } else {
9605            std::mem::take(&mut values.column_aliases)
9606        };
9607
9608        values.alias = None;
9609
9610        let values_subquery = Expression::Subquery(Box::new(crate::expressions::Subquery {
9611            this: Expression::Values(Box::new(values)),
9612            alias: Some(Identifier::new("_v")),
9613            column_aliases,
9614            alias_explicit_as: false,
9615            alias_keyword: None,
9616            order_by: None,
9617            limit: None,
9618            offset: None,
9619            distribute_by: None,
9620            sort_by: None,
9621            cluster_by: None,
9622            lateral: false,
9623            modifiers_inside: false,
9624            trailing_comments: Vec::new(),
9625            inferred_type: None,
9626        }));
9627
9628        let mut select = crate::expressions::Select::new();
9629        select.expressions = vec![Expression::star()];
9630        select.from = Some(From {
9631            expressions: vec![values_subquery],
9632        });
9633
9634        Expression::Select(Box::new(select))
9635    }
9636
9637    fn extract_interval_parts(
9638        interval_expr: &Expression,
9639    ) -> Option<(Expression, crate::expressions::IntervalUnit)> {
9640        use crate::expressions::{DataType, IntervalUnit, IntervalUnitSpec, Literal};
9641
9642        fn unit_from_str(unit: &str) -> Option<IntervalUnit> {
9643            match unit.trim().to_ascii_uppercase().as_str() {
9644                "YEAR" | "YEARS" | "Y" | "YR" | "YRS" | "YY" | "YYYY" => Some(IntervalUnit::Year),
9645                "QUARTER" | "QUARTERS" | "Q" | "QTR" | "QTRS" | "QQ" => Some(IntervalUnit::Quarter),
9646                "MONTH" | "MONTHS" | "MON" | "MONS" | "MM" => Some(IntervalUnit::Month),
9647                "WEEK" | "WEEKS" | "W" | "WK" | "WKS" | "WW" | "ISOWEEK" => {
9648                    Some(IntervalUnit::Week)
9649                }
9650                "DAY" | "DAYS" | "D" | "DD" => Some(IntervalUnit::Day),
9651                "HOUR" | "HOURS" | "H" | "HH" | "HR" | "HRS" => Some(IntervalUnit::Hour),
9652                "MINUTE" | "MINUTES" | "MI" | "MIN" | "MINS" | "N" => Some(IntervalUnit::Minute),
9653                "SECOND" | "SECONDS" | "S" | "SEC" | "SECS" | "SS" => Some(IntervalUnit::Second),
9654                "MILLISECOND" | "MILLISECONDS" | "MS" | "MSEC" | "MSECS" | "MSECOND"
9655                | "MSECONDS" | "MILLISEC" | "MILLISECS" | "MILLISECON" => {
9656                    Some(IntervalUnit::Millisecond)
9657                }
9658                "MICROSECOND" | "MICROSECONDS" | "US" | "USEC" | "USECS" | "USECOND"
9659                | "USECONDS" | "MICROSEC" | "MICROSECS" | "MCS" => Some(IntervalUnit::Microsecond),
9660                "NANOSECOND" | "NANOSECONDS" | "NS" | "NSEC" | "NSECS" | "NSECOND" | "NSECONDS"
9661                | "NANOSEC" | "NANOSECS" => Some(IntervalUnit::Nanosecond),
9662                _ => None,
9663            }
9664        }
9665
9666        fn parts_from_literal_string(s: &str) -> Option<(Expression, IntervalUnit)> {
9667            let mut parts = s.split_whitespace();
9668            let value = parts.next()?;
9669            let unit = unit_from_str(parts.next()?)?;
9670            Some((
9671                Expression::Literal(Box::new(Literal::String(value.to_string()))),
9672                unit,
9673            ))
9674        }
9675
9676        fn unit_from_spec(unit: &IntervalUnitSpec) -> Option<IntervalUnit> {
9677            match unit {
9678                IntervalUnitSpec::Simple { unit, .. } => Some(*unit),
9679                IntervalUnitSpec::Expr(expr) => match expr.as_ref() {
9680                    Expression::Day(_) => Some(IntervalUnit::Day),
9681                    Expression::Month(_) => Some(IntervalUnit::Month),
9682                    Expression::Year(_) => Some(IntervalUnit::Year),
9683                    Expression::Identifier(id) => unit_from_str(&id.name),
9684                    Expression::Var(v) => unit_from_str(&v.this),
9685                    Expression::Column(col) => unit_from_str(&col.name.name),
9686                    _ => None,
9687                },
9688                _ => None,
9689            }
9690        }
9691
9692        match interval_expr {
9693            Expression::Interval(iv) => {
9694                let val = iv.this.clone().unwrap_or(Expression::number(0));
9695                if let Expression::Literal(lit) = &val {
9696                    if let Literal::String(s) = lit.as_ref() {
9697                        if let Some(parts) = parts_from_literal_string(s) {
9698                            return Some(parts);
9699                        }
9700                    }
9701                }
9702                let unit = iv
9703                    .unit
9704                    .as_ref()
9705                    .and_then(unit_from_spec)
9706                    .unwrap_or(IntervalUnit::Day);
9707                Some((val, unit))
9708            }
9709            Expression::Cast(cast) if matches!(cast.to, DataType::Interval { .. }) => {
9710                if let Expression::Literal(lit) = &cast.this {
9711                    if let Literal::String(s) = lit.as_ref() {
9712                        if let Some(parts) = parts_from_literal_string(s) {
9713                            return Some(parts);
9714                        }
9715                    }
9716                }
9717                let unit = match &cast.to {
9718                    DataType::Interval {
9719                        unit: Some(unit), ..
9720                    } => unit_from_str(unit).unwrap_or(IntervalUnit::Day),
9721                    _ => IntervalUnit::Day,
9722                };
9723                Some((cast.this.clone(), unit))
9724            }
9725            _ => None,
9726        }
9727    }
9728
9729    fn data_type_is_interval(dt: &DataType) -> bool {
9730        match dt {
9731            DataType::Interval { .. } => true,
9732            DataType::Custom { name } => name.trim().eq_ignore_ascii_case("INTERVAL"),
9733            _ => false,
9734        }
9735    }
9736
9737    fn node_is_interval_cast(node: &Expression) -> bool {
9738        match node {
9739            Expression::Cast(c) | Expression::TryCast(c) | Expression::SafeCast(c) => {
9740                Self::data_type_is_interval(&c.to)
9741            }
9742            _ => false,
9743        }
9744    }
9745
9746    fn reject_tsql_interval_casts(
9747        expr: &Expression,
9748        target: DialectType,
9749        opts: &TranspileOptions,
9750    ) -> Result<()> {
9751        if !matches!(
9752            opts.unsupported_level,
9753            UnsupportedLevel::Raise | UnsupportedLevel::Immediate
9754        ) {
9755            return Ok(());
9756        }
9757
9758        if expr.dfs().any(Self::node_is_interval_cast) {
9759            return Err(crate::error::Error::unsupported(
9760                "INTERVAL casts",
9761                target.to_string(),
9762            ));
9763        }
9764
9765        Ok(())
9766    }
9767
9768    fn tsql_varchar_max_type() -> DataType {
9769        DataType::Custom {
9770            name: "VARCHAR(MAX)".to_string(),
9771        }
9772    }
9773
9774    fn rewrite_tsql_interval_casts_to_varchar(expr: Expression) -> Result<Expression> {
9775        transform_recursive(expr, &|e| match e {
9776            Expression::Cast(mut cast) if Self::data_type_is_interval(&cast.to) => {
9777                cast.to = Self::tsql_varchar_max_type();
9778                cast.double_colon_syntax = false;
9779                Ok(Expression::Cast(cast))
9780            }
9781            Expression::TryCast(mut cast) if Self::data_type_is_interval(&cast.to) => {
9782                cast.to = Self::tsql_varchar_max_type();
9783                cast.double_colon_syntax = false;
9784                Ok(Expression::TryCast(cast))
9785            }
9786            Expression::SafeCast(mut cast) if Self::data_type_is_interval(&cast.to) => {
9787                cast.to = Self::tsql_varchar_max_type();
9788                cast.double_colon_syntax = false;
9789                Ok(Expression::SafeCast(cast))
9790            }
9791            _ => Ok(e),
9792        })
9793    }
9794
9795    fn rewrite_tsql_interval_arithmetic_legacy(
9796        expr: &Expression,
9797        source: DialectType,
9798    ) -> Option<Expression> {
9799        match expr {
9800            Expression::Add(op) => {
9801                if Self::extract_interval_parts(&op.right).is_some() {
9802                    return Some(Self::build_tsql_dateadd_from_interval(
9803                        op.left.clone(),
9804                        &op.right,
9805                        false,
9806                    ));
9807                }
9808
9809                if Self::is_postgres_family_source(source) {
9810                    if Self::is_explicit_date_expr(&op.left)
9811                        && Self::is_integer_day_offset_expr(&op.right)
9812                    {
9813                        return Some(Self::build_tsql_dateadd_days(
9814                            op.left.clone(),
9815                            op.right.clone(),
9816                            false,
9817                        ));
9818                    }
9819
9820                    if Self::is_integer_day_offset_expr(&op.left)
9821                        && Self::is_explicit_date_expr(&op.right)
9822                    {
9823                        return Some(Self::build_tsql_dateadd_days(
9824                            op.right.clone(),
9825                            op.left.clone(),
9826                            false,
9827                        ));
9828                    }
9829                }
9830
9831                None
9832            }
9833            Expression::Sub(op) => {
9834                if Self::extract_interval_parts(&op.right).is_some() {
9835                    return Some(Self::build_tsql_dateadd_from_interval(
9836                        op.left.clone(),
9837                        &op.right,
9838                        true,
9839                    ));
9840                }
9841
9842                if Self::is_postgres_family_source(source) {
9843                    if Self::is_explicit_date_expr(&op.left)
9844                        && Self::is_explicit_date_expr(&op.right)
9845                    {
9846                        return Some(Self::build_tsql_datediff_days(
9847                            op.right.clone(),
9848                            op.left.clone(),
9849                        ));
9850                    }
9851
9852                    if Self::is_explicit_date_expr(&op.left)
9853                        && Self::is_integer_day_offset_expr(&op.right)
9854                    {
9855                        return Some(Self::build_tsql_dateadd_days(
9856                            op.left.clone(),
9857                            op.right.clone(),
9858                            true,
9859                        ));
9860                    }
9861                }
9862
9863                None
9864            }
9865            _ => None,
9866        }
9867    }
9868
9869    fn is_postgres_family_source(source: DialectType) -> bool {
9870        matches!(
9871            source,
9872            DialectType::PostgreSQL
9873                | DialectType::Redshift
9874                | DialectType::Materialize
9875                | DialectType::RisingWave
9876                | DialectType::CockroachDB
9877        )
9878    }
9879
9880    fn is_explicit_date_expr(expr: &Expression) -> bool {
9881        use crate::expressions::Literal;
9882
9883        match expr {
9884            Expression::Literal(lit) => matches!(lit.as_ref(), Literal::Date(_)),
9885            Expression::Cast(c) | Expression::TryCast(c) | Expression::SafeCast(c) => {
9886                matches!(c.to, crate::expressions::DataType::Date)
9887            }
9888            Expression::Paren(p) => Self::is_explicit_date_expr(&p.this),
9889            Expression::CurrentDate(_)
9890            | Expression::Date(_)
9891            | Expression::MakeDate(_)
9892            | Expression::ToDate(_)
9893            | Expression::DateStrToDate(_) => true,
9894            _ => false,
9895        }
9896    }
9897
9898    fn is_integer_day_offset_expr(expr: &Expression) -> bool {
9899        use crate::expressions::Literal;
9900
9901        match expr {
9902            Expression::Literal(lit) => match lit.as_ref() {
9903                Literal::Number(n) => n.parse::<i64>().is_ok(),
9904                _ => false,
9905            },
9906            Expression::Parameter(_) | Expression::Placeholder(_) => true,
9907            Expression::Neg(op) => Self::is_integer_day_offset_expr(&op.this),
9908            Expression::Paren(p) => Self::is_integer_day_offset_expr(&p.this),
9909            _ => false,
9910        }
9911    }
9912
9913    fn build_tsql_datediff_days(start: Expression, end: Expression) -> Expression {
9914        Expression::Function(Box::new(Function::new(
9915            "DATEDIFF".to_string(),
9916            vec![Expression::Identifier(Identifier::new("DAY")), start, end],
9917        )))
9918    }
9919
9920    fn build_tsql_dateadd_days(date: Expression, amount: Expression, subtract: bool) -> Expression {
9921        Expression::Function(Box::new(Function::new(
9922            "DATEADD".to_string(),
9923            vec![
9924                Expression::Identifier(Identifier::new("DAY")),
9925                Self::tsql_dateadd_amount(amount, subtract),
9926                date,
9927            ],
9928        )))
9929    }
9930
9931    fn build_tsql_dateadd_from_interval(
9932        date: Expression,
9933        interval: &Expression,
9934        subtract: bool,
9935    ) -> Expression {
9936        let (value, unit) = Self::extract_interval_parts(interval)
9937            .unwrap_or_else(|| (interval.clone(), crate::expressions::IntervalUnit::Day));
9938        let unit = normalization::temporal::interval_unit_to_string(&unit);
9939        let amount = Self::tsql_dateadd_amount(value, subtract);
9940
9941        Expression::Function(Box::new(Function::new(
9942            "DATEADD".to_string(),
9943            vec![Expression::Identifier(Identifier::new(unit)), amount, date],
9944        )))
9945    }
9946
9947    fn tsql_dateadd_amount(value: Expression, negate: bool) -> Expression {
9948        use crate::expressions::{Parameter, ParameterStyle, UnaryOp};
9949
9950        fn numeric_literal_value(value: &Expression) -> Option<&str> {
9951            match value {
9952                Expression::Literal(lit) => match lit.as_ref() {
9953                    crate::expressions::Literal::Number(n)
9954                    | crate::expressions::Literal::String(n) => Some(n.as_str()),
9955                    _ => None,
9956                },
9957                _ => None,
9958            }
9959        }
9960
9961        fn colon_parameter(value: &Expression) -> Option<Expression> {
9962            let Expression::Literal(lit) = value else {
9963                return None;
9964            };
9965            let crate::expressions::Literal::String(s) = lit.as_ref() else {
9966                return None;
9967            };
9968            let name = s.strip_prefix(':')?;
9969            if name.is_empty()
9970                || !name
9971                    .chars()
9972                    .all(|ch| ch.is_ascii_alphanumeric() || ch == '_')
9973            {
9974                return None;
9975            }
9976
9977            Some(Expression::Parameter(Box::new(Parameter {
9978                name: if name.chars().all(|ch| ch.is_ascii_digit()) {
9979                    None
9980                } else {
9981                    Some(name.to_string())
9982                },
9983                index: name.parse::<u32>().ok(),
9984                style: ParameterStyle::Colon,
9985                quoted: false,
9986                string_quoted: false,
9987                expression: None,
9988            })))
9989        }
9990
9991        let value = colon_parameter(&value).unwrap_or(value);
9992
9993        if let Some(n) = numeric_literal_value(&value) {
9994            if let Ok(parsed) = n.parse::<f64>() {
9995                let normalized = if negate { -parsed } else { parsed };
9996                let rendered = if normalized.fract() == 0.0 {
9997                    format!("{}", normalized as i64)
9998                } else {
9999                    normalized.to_string()
10000                };
10001                return Expression::Literal(Box::new(crate::expressions::Literal::Number(
10002                    rendered,
10003                )));
10004            }
10005        }
10006
10007        if !negate {
10008            return value;
10009        }
10010
10011        match value {
10012            Expression::Neg(op) => op.this,
10013            other => Expression::Neg(Box::new(UnaryOp {
10014                this: other,
10015                inferred_type: None,
10016            })),
10017        }
10018    }
10019
10020    /// Internal TO_DATE function that won't be converted to CAST by the Snowflake handler.
10021    /// Uses the name `_POLYGLOT_TO_DATE` which is not recognized by the TO_DATE -> CAST logic.
10022    /// The Snowflake DATEDIFF handler converts these back to TO_DATE.
10023    const PRESERVED_TO_DATE: &'static str = "_POLYGLOT_TO_DATE";
10024}
10025
10026#[cfg(test)]
10027mod tests {
10028    use super::*;
10029
10030    #[test]
10031    fn built_in_dialect_instances_share_tokenizer_config() {
10032        let first = Dialect::get(DialectType::PostgreSQL);
10033        let second = Dialect::get(DialectType::PostgreSQL);
10034
10035        assert!(first.tokenizer.shares_config_with(&second.tokenizer));
10036    }
10037
10038    #[test]
10039    fn test_dialect_type_from_str() {
10040        assert_eq!(
10041            "postgres".parse::<DialectType>().unwrap(),
10042            DialectType::PostgreSQL
10043        );
10044        assert_eq!(
10045            "postgresql".parse::<DialectType>().unwrap(),
10046            DialectType::PostgreSQL
10047        );
10048        assert_eq!("mysql".parse::<DialectType>().unwrap(), DialectType::MySQL);
10049        assert_eq!(
10050            "bigquery".parse::<DialectType>().unwrap(),
10051            DialectType::BigQuery
10052        );
10053    }
10054
10055    #[test]
10056    fn test_basic_transpile() {
10057        let dialect = Dialect::get(DialectType::Generic);
10058        let result = dialect
10059            .transpile("SELECT 1", DialectType::PostgreSQL)
10060            .unwrap();
10061        assert_eq!(result.len(), 1);
10062        assert_eq!(result[0], "SELECT 1");
10063    }
10064
10065    #[test]
10066    fn test_sqlite_double_quoted_column_defaults_to_postgres_strings() {
10067        let sqlite = Dialect::get(DialectType::SQLite);
10068        let result = sqlite
10069            .transpile(
10070                r#"CREATE TABLE "_collections" (
10071                    "type" TEXT DEFAULT "base" NOT NULL,
10072                    "fields" JSON DEFAULT "[]" NOT NULL,
10073                    "options" JSON DEFAULT "{}" NOT NULL
10074                )"#,
10075                DialectType::PostgreSQL,
10076            )
10077            .unwrap();
10078
10079        assert!(result[0].contains(r#""type" TEXT DEFAULT 'base' NOT NULL"#));
10080        assert!(result[0].contains(r#""fields" JSON DEFAULT '[]' NOT NULL"#));
10081        assert!(result[0].contains(r#""options" JSON DEFAULT '{}' NOT NULL"#));
10082    }
10083
10084    #[test]
10085    fn test_sqlite_identity_preserves_double_quoted_column_defaults() {
10086        let sqlite = Dialect::get(DialectType::SQLite);
10087        let result = sqlite
10088            .transpile(
10089                r#"CREATE TABLE "_collections" ("type" TEXT DEFAULT "base" NOT NULL)"#,
10090                DialectType::SQLite,
10091            )
10092            .unwrap();
10093
10094        assert_eq!(
10095            result[0],
10096            r#"CREATE TABLE "_collections" ("type" TEXT DEFAULT "base" NOT NULL)"#
10097        );
10098    }
10099
10100    #[test]
10101    fn test_function_transformation_mysql() {
10102        // NVL should be transformed to IFNULL in MySQL
10103        let dialect = Dialect::get(DialectType::Generic);
10104        let result = dialect
10105            .transpile("SELECT NVL(a, b)", DialectType::MySQL)
10106            .unwrap();
10107        assert_eq!(result[0], "SELECT IFNULL(a, b)");
10108    }
10109
10110    #[test]
10111    fn test_get_path_duckdb() {
10112        // Test: step by step
10113        let snowflake = Dialect::get(DialectType::Snowflake);
10114
10115        // Step 1: Parse and check what Snowflake produces as intermediate
10116        let result_sf_sf = snowflake
10117            .transpile(
10118                "SELECT PARSE_JSON('{\"fruit\":\"banana\"}'):fruit",
10119                DialectType::Snowflake,
10120            )
10121            .unwrap();
10122        eprintln!("Snowflake->Snowflake colon: {}", result_sf_sf[0]);
10123
10124        // Step 2: DuckDB target
10125        let result_sf_dk = snowflake
10126            .transpile(
10127                "SELECT PARSE_JSON('{\"fruit\":\"banana\"}'):fruit",
10128                DialectType::DuckDB,
10129            )
10130            .unwrap();
10131        eprintln!("Snowflake->DuckDB colon: {}", result_sf_dk[0]);
10132
10133        // Step 3: GET_PATH directly
10134        let result_gp = snowflake
10135            .transpile(
10136                "SELECT GET_PATH(PARSE_JSON('{\"fruit\":\"banana\"}'), 'fruit')",
10137                DialectType::DuckDB,
10138            )
10139            .unwrap();
10140        eprintln!("Snowflake->DuckDB explicit GET_PATH: {}", result_gp[0]);
10141    }
10142
10143    #[test]
10144    fn test_function_transformation_postgres() {
10145        // IFNULL should be transformed to COALESCE in PostgreSQL
10146        let dialect = Dialect::get(DialectType::Generic);
10147        let result = dialect
10148            .transpile("SELECT IFNULL(a, b)", DialectType::PostgreSQL)
10149            .unwrap();
10150        assert_eq!(result[0], "SELECT COALESCE(a, b)");
10151
10152        // NVL should also be transformed to COALESCE
10153        let result = dialect
10154            .transpile("SELECT NVL(a, b)", DialectType::PostgreSQL)
10155            .unwrap();
10156        assert_eq!(result[0], "SELECT COALESCE(a, b)");
10157    }
10158
10159    #[test]
10160    fn test_hive_cast_to_trycast() {
10161        // Hive CAST should become TRY_CAST for targets that support it
10162        let hive = Dialect::get(DialectType::Hive);
10163        let result = hive
10164            .transpile("CAST(1 AS INT)", DialectType::DuckDB)
10165            .unwrap();
10166        assert_eq!(result[0], "TRY_CAST(1 AS INT)");
10167
10168        let result = hive
10169            .transpile("CAST(1 AS INT)", DialectType::Presto)
10170            .unwrap();
10171        assert_eq!(result[0], "TRY_CAST(1 AS INTEGER)");
10172    }
10173
10174    #[test]
10175    fn test_hive_array_identity() {
10176        // Hive ARRAY<DATE> should preserve angle bracket syntax
10177        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')";
10178        let hive = Dialect::get(DialectType::Hive);
10179
10180        // Test via transpile (this works)
10181        let result = hive.transpile(sql, DialectType::Hive).unwrap();
10182        eprintln!("Hive ARRAY via transpile: {}", result[0]);
10183        assert!(
10184            result[0].contains("ARRAY<DATE>"),
10185            "transpile: Expected ARRAY<DATE>, got: {}",
10186            result[0]
10187        );
10188
10189        // Test via parse -> transform -> generate (identity test path)
10190        let ast = hive.parse(sql).unwrap();
10191        let transformed = hive.transform(ast[0].clone()).unwrap();
10192        let output = hive.generate(&transformed).unwrap();
10193        eprintln!("Hive ARRAY via identity path: {}", output);
10194        assert!(
10195            output.contains("ARRAY<DATE>"),
10196            "identity path: Expected ARRAY<DATE>, got: {}",
10197            output
10198        );
10199    }
10200
10201    #[test]
10202    fn test_starrocks_delete_between_expansion() {
10203        // StarRocks doesn't support BETWEEN in DELETE statements
10204        let dialect = Dialect::get(DialectType::Generic);
10205
10206        // BETWEEN should be expanded to >= AND <= in DELETE
10207        let result = dialect
10208            .transpile(
10209                "DELETE FROM t WHERE a BETWEEN b AND c",
10210                DialectType::StarRocks,
10211            )
10212            .unwrap();
10213        assert_eq!(result[0], "DELETE FROM t WHERE a >= b AND a <= c");
10214
10215        // NOT BETWEEN should be expanded to < OR > in DELETE
10216        let result = dialect
10217            .transpile(
10218                "DELETE FROM t WHERE a NOT BETWEEN b AND c",
10219                DialectType::StarRocks,
10220            )
10221            .unwrap();
10222        assert_eq!(result[0], "DELETE FROM t WHERE a < b OR a > c");
10223
10224        // BETWEEN in SELECT should NOT be expanded (StarRocks supports it there)
10225        let result = dialect
10226            .transpile(
10227                "SELECT * FROM t WHERE a BETWEEN b AND c",
10228                DialectType::StarRocks,
10229            )
10230            .unwrap();
10231        assert!(
10232            result[0].contains("BETWEEN"),
10233            "BETWEEN should be preserved in SELECT"
10234        );
10235    }
10236
10237    #[test]
10238    fn test_snowflake_ltrim_rtrim_parse() {
10239        let sf = Dialect::get(DialectType::Snowflake);
10240        let sql = "SELECT LTRIM(RTRIM(col)) FROM t1";
10241        let result = sf.transpile(sql, DialectType::DuckDB);
10242        match &result {
10243            Ok(r) => eprintln!("LTRIM/RTRIM result: {}", r[0]),
10244            Err(e) => eprintln!("LTRIM/RTRIM error: {}", e),
10245        }
10246        assert!(
10247            result.is_ok(),
10248            "Expected successful parse of LTRIM(RTRIM(col)), got error: {:?}",
10249            result.err()
10250        );
10251    }
10252
10253    #[test]
10254    fn test_duckdb_count_if_parse() {
10255        let duck = Dialect::get(DialectType::DuckDB);
10256        let sql = "COUNT_IF(x)";
10257        let result = duck.transpile(sql, DialectType::DuckDB);
10258        match &result {
10259            Ok(r) => eprintln!("COUNT_IF result: {}", r[0]),
10260            Err(e) => eprintln!("COUNT_IF error: {}", e),
10261        }
10262        assert!(
10263            result.is_ok(),
10264            "Expected successful parse of COUNT_IF(x), got error: {:?}",
10265            result.err()
10266        );
10267    }
10268
10269    #[test]
10270    fn test_tsql_cast_tinyint_parse() {
10271        let tsql = Dialect::get(DialectType::TSQL);
10272        let sql = "CAST(X AS TINYINT)";
10273        let result = tsql.transpile(sql, DialectType::DuckDB);
10274        match &result {
10275            Ok(r) => eprintln!("TSQL CAST TINYINT result: {}", r[0]),
10276            Err(e) => eprintln!("TSQL CAST TINYINT error: {}", e),
10277        }
10278        assert!(
10279            result.is_ok(),
10280            "Expected successful transpile, got error: {:?}",
10281            result.err()
10282        );
10283    }
10284
10285    #[test]
10286    fn test_pg_hash_bitwise_xor() {
10287        let dialect = Dialect::get(DialectType::PostgreSQL);
10288        let result = dialect.transpile("x # y", DialectType::PostgreSQL).unwrap();
10289        assert_eq!(result[0], "x # y");
10290    }
10291
10292    #[test]
10293    fn test_pg_array_to_duckdb() {
10294        let dialect = Dialect::get(DialectType::PostgreSQL);
10295        let result = dialect
10296            .transpile("SELECT ARRAY[1, 2, 3] @> ARRAY[1, 2]", DialectType::DuckDB)
10297            .unwrap();
10298        assert_eq!(result[0], "SELECT [1, 2, 3] @> [1, 2]");
10299    }
10300
10301    #[test]
10302    fn test_array_remove_bigquery() {
10303        let dialect = Dialect::get(DialectType::Generic);
10304        let result = dialect
10305            .transpile("ARRAY_REMOVE(the_array, target)", DialectType::BigQuery)
10306            .unwrap();
10307        assert_eq!(
10308            result[0],
10309            "ARRAY(SELECT _u FROM UNNEST(the_array) AS _u WHERE _u <> target)"
10310        );
10311    }
10312
10313    #[test]
10314    fn test_map_clickhouse_case() {
10315        let dialect = Dialect::get(DialectType::Generic);
10316        let parsed = dialect
10317            .parse("CAST(MAP('a', '1') AS MAP(TEXT, TEXT))")
10318            .unwrap();
10319        eprintln!("MAP parsed: {:?}", parsed);
10320        let result = dialect
10321            .transpile(
10322                "CAST(MAP('a', '1') AS MAP(TEXT, TEXT))",
10323                DialectType::ClickHouse,
10324            )
10325            .unwrap();
10326        eprintln!("MAP result: {}", result[0]);
10327    }
10328
10329    #[test]
10330    fn test_generate_date_array_presto() {
10331        let dialect = Dialect::get(DialectType::Generic);
10332        let result = dialect.transpile(
10333            "SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
10334            DialectType::Presto,
10335        ).unwrap();
10336        eprintln!("GDA -> Presto: {}", result[0]);
10337        assert_eq!(result[0], "SELECT * FROM UNNEST(SEQUENCE(CAST('2020-01-01' AS DATE), CAST('2020-02-01' AS DATE), (1 * INTERVAL '7' DAY)))");
10338    }
10339
10340    #[test]
10341    fn test_generate_date_array_postgres() {
10342        let dialect = Dialect::get(DialectType::Generic);
10343        let result = dialect.transpile(
10344            "SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
10345            DialectType::PostgreSQL,
10346        ).unwrap();
10347        eprintln!("GDA -> PostgreSQL: {}", result[0]);
10348    }
10349
10350    #[test]
10351    fn test_generate_date_array_snowflake() {
10352        let dialect = Dialect::get(DialectType::Generic);
10353        let result = dialect
10354            .transpile(
10355                "SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
10356                DialectType::Snowflake,
10357            )
10358            .unwrap();
10359        eprintln!("GDA -> Snowflake: {}", result[0]);
10360    }
10361
10362    #[test]
10363    fn test_array_length_generate_date_array_snowflake() {
10364        let dialect = Dialect::get(DialectType::Generic);
10365        let result = dialect.transpile(
10366            "SELECT ARRAY_LENGTH(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
10367            DialectType::Snowflake,
10368        ).unwrap();
10369        eprintln!("ARRAY_LENGTH(GDA) -> Snowflake: {}", result[0]);
10370    }
10371
10372    #[test]
10373    fn test_generate_date_array_mysql() {
10374        let dialect = Dialect::get(DialectType::Generic);
10375        let result = dialect.transpile(
10376            "SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
10377            DialectType::MySQL,
10378        ).unwrap();
10379        eprintln!("GDA -> MySQL: {}", result[0]);
10380    }
10381
10382    #[test]
10383    fn test_generate_date_array_redshift() {
10384        let dialect = Dialect::get(DialectType::Generic);
10385        let result = dialect.transpile(
10386            "SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
10387            DialectType::Redshift,
10388        ).unwrap();
10389        eprintln!("GDA -> Redshift: {}", result[0]);
10390    }
10391
10392    #[test]
10393    fn test_generate_date_array_tsql() {
10394        let dialect = Dialect::get(DialectType::Generic);
10395        let result = dialect.transpile(
10396            "SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
10397            DialectType::TSQL,
10398        ).unwrap();
10399        eprintln!("GDA -> TSQL: {}", result[0]);
10400    }
10401
10402    #[test]
10403    fn test_struct_colon_syntax() {
10404        let dialect = Dialect::get(DialectType::Generic);
10405        // Test without colon first
10406        let result = dialect.transpile(
10407            "CAST((1, 2, 3, 4) AS STRUCT<a TINYINT, b SMALLINT, c INT, d BIGINT>)",
10408            DialectType::ClickHouse,
10409        );
10410        match result {
10411            Ok(r) => eprintln!("STRUCT no colon -> ClickHouse: {}", r[0]),
10412            Err(e) => eprintln!("STRUCT no colon error: {}", e),
10413        }
10414        // Now test with colon
10415        let result = dialect.transpile(
10416            "CAST((1, 2, 3, 4) AS STRUCT<a: TINYINT, b: SMALLINT, c: INT, d: BIGINT>)",
10417            DialectType::ClickHouse,
10418        );
10419        match result {
10420            Ok(r) => eprintln!("STRUCT colon -> ClickHouse: {}", r[0]),
10421            Err(e) => eprintln!("STRUCT colon error: {}", e),
10422        }
10423    }
10424
10425    #[test]
10426    fn test_generate_date_array_cte_wrapped_mysql() {
10427        let dialect = Dialect::get(DialectType::Generic);
10428        let result = dialect.transpile(
10429            "WITH dates AS (SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))) SELECT * FROM dates",
10430            DialectType::MySQL,
10431        ).unwrap();
10432        eprintln!("GDA CTE -> MySQL: {}", result[0]);
10433    }
10434
10435    #[test]
10436    fn test_generate_date_array_cte_wrapped_tsql() {
10437        let dialect = Dialect::get(DialectType::Generic);
10438        let result = dialect.transpile(
10439            "WITH dates AS (SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))) SELECT * FROM dates",
10440            DialectType::TSQL,
10441        ).unwrap();
10442        eprintln!("GDA CTE -> TSQL: {}", result[0]);
10443    }
10444
10445    #[test]
10446    fn test_decode_literal_no_null_check() {
10447        // Oracle DECODE with all literals should produce simple equality, no IS NULL
10448        let dialect = Dialect::get(DialectType::Oracle);
10449        let result = dialect
10450            .transpile("SELECT decode(1,2,3,4)", DialectType::DuckDB)
10451            .unwrap();
10452        assert_eq!(
10453            result[0], "SELECT CASE WHEN 1 = 2 THEN 3 ELSE 4 END",
10454            "Literal DECODE should not have IS NULL checks"
10455        );
10456    }
10457
10458    #[test]
10459    fn test_decode_column_vs_literal_no_null_check() {
10460        // Oracle DECODE with column vs literal should use simple equality (like sqlglot)
10461        let dialect = Dialect::get(DialectType::Oracle);
10462        let result = dialect
10463            .transpile("SELECT decode(col, 2, 3, 4) FROM t", DialectType::DuckDB)
10464            .unwrap();
10465        assert_eq!(
10466            result[0], "SELECT CASE WHEN col = 2 THEN 3 ELSE 4 END FROM t",
10467            "Column vs literal DECODE should not have IS NULL checks"
10468        );
10469    }
10470
10471    #[test]
10472    fn test_decode_column_vs_column_keeps_null_check() {
10473        // Oracle DECODE with column vs column should keep null-safe comparison
10474        let dialect = Dialect::get(DialectType::Oracle);
10475        let result = dialect
10476            .transpile("SELECT decode(col, col2, 3, 4) FROM t", DialectType::DuckDB)
10477            .unwrap();
10478        assert!(
10479            result[0].contains("IS NULL"),
10480            "Column vs column DECODE should have IS NULL checks, got: {}",
10481            result[0]
10482        );
10483    }
10484
10485    #[test]
10486    fn test_decode_null_search() {
10487        // Oracle DECODE with NULL search should use IS NULL
10488        let dialect = Dialect::get(DialectType::Oracle);
10489        let result = dialect
10490            .transpile("SELECT decode(col, NULL, 3, 4) FROM t", DialectType::DuckDB)
10491            .unwrap();
10492        assert_eq!(
10493            result[0],
10494            "SELECT CASE WHEN col IS NULL THEN 3 ELSE 4 END FROM t",
10495        );
10496    }
10497
10498    // =========================================================================
10499    // REGEXP function transpilation tests
10500    // =========================================================================
10501
10502    #[test]
10503    fn test_regexp_substr_snowflake_to_duckdb_2arg() {
10504        let dialect = Dialect::get(DialectType::Snowflake);
10505        let result = dialect
10506            .transpile("SELECT REGEXP_SUBSTR(s, 'pattern')", DialectType::DuckDB)
10507            .unwrap();
10508        assert_eq!(result[0], "SELECT REGEXP_EXTRACT(s, 'pattern')");
10509    }
10510
10511    #[test]
10512    fn test_regexp_substr_snowflake_to_duckdb_3arg_pos1() {
10513        let dialect = Dialect::get(DialectType::Snowflake);
10514        let result = dialect
10515            .transpile("SELECT REGEXP_SUBSTR(s, 'pattern', 1)", DialectType::DuckDB)
10516            .unwrap();
10517        assert_eq!(result[0], "SELECT REGEXP_EXTRACT(s, 'pattern')");
10518    }
10519
10520    #[test]
10521    fn test_regexp_substr_snowflake_to_duckdb_3arg_pos_gt1() {
10522        let dialect = Dialect::get(DialectType::Snowflake);
10523        let result = dialect
10524            .transpile("SELECT REGEXP_SUBSTR(s, 'pattern', 3)", DialectType::DuckDB)
10525            .unwrap();
10526        assert_eq!(
10527            result[0],
10528            "SELECT REGEXP_EXTRACT(NULLIF(SUBSTRING(s, 3), ''), 'pattern')"
10529        );
10530    }
10531
10532    #[test]
10533    fn test_regexp_substr_snowflake_to_duckdb_4arg_occ_gt1() {
10534        let dialect = Dialect::get(DialectType::Snowflake);
10535        let result = dialect
10536            .transpile(
10537                "SELECT REGEXP_SUBSTR(s, 'pattern', 1, 3)",
10538                DialectType::DuckDB,
10539            )
10540            .unwrap();
10541        assert_eq!(
10542            result[0],
10543            "SELECT ARRAY_EXTRACT(REGEXP_EXTRACT_ALL(s, 'pattern'), 3)"
10544        );
10545    }
10546
10547    #[test]
10548    fn test_regexp_substr_snowflake_to_duckdb_5arg_e_flag() {
10549        let dialect = Dialect::get(DialectType::Snowflake);
10550        let result = dialect
10551            .transpile(
10552                "SELECT REGEXP_SUBSTR(s, 'pattern', 1, 1, 'e')",
10553                DialectType::DuckDB,
10554            )
10555            .unwrap();
10556        assert_eq!(result[0], "SELECT REGEXP_EXTRACT(s, 'pattern')");
10557    }
10558
10559    #[test]
10560    fn test_regexp_substr_snowflake_to_duckdb_6arg_group0() {
10561        let dialect = Dialect::get(DialectType::Snowflake);
10562        let result = dialect
10563            .transpile(
10564                "SELECT REGEXP_SUBSTR(s, 'pattern', 1, 1, 'e', 0)",
10565                DialectType::DuckDB,
10566            )
10567            .unwrap();
10568        assert_eq!(result[0], "SELECT REGEXP_EXTRACT(s, 'pattern')");
10569    }
10570
10571    #[test]
10572    fn test_regexp_substr_snowflake_identity_strip_group0() {
10573        let dialect = Dialect::get(DialectType::Snowflake);
10574        let result = dialect
10575            .transpile(
10576                "SELECT REGEXP_SUBSTR(s, 'pattern', 1, 1, 'e', 0)",
10577                DialectType::Snowflake,
10578            )
10579            .unwrap();
10580        assert_eq!(result[0], "SELECT REGEXP_SUBSTR(s, 'pattern', 1, 1, 'e')");
10581    }
10582
10583    #[test]
10584    fn test_regexp_substr_all_snowflake_to_duckdb_2arg() {
10585        let dialect = Dialect::get(DialectType::Snowflake);
10586        let result = dialect
10587            .transpile(
10588                "SELECT REGEXP_SUBSTR_ALL(s, 'pattern')",
10589                DialectType::DuckDB,
10590            )
10591            .unwrap();
10592        assert_eq!(result[0], "SELECT REGEXP_EXTRACT_ALL(s, 'pattern')");
10593    }
10594
10595    #[test]
10596    fn test_regexp_substr_all_snowflake_to_duckdb_3arg_pos_gt1() {
10597        let dialect = Dialect::get(DialectType::Snowflake);
10598        let result = dialect
10599            .transpile(
10600                "SELECT REGEXP_SUBSTR_ALL(s, 'pattern', 3)",
10601                DialectType::DuckDB,
10602            )
10603            .unwrap();
10604        assert_eq!(
10605            result[0],
10606            "SELECT REGEXP_EXTRACT_ALL(SUBSTRING(s, 3), 'pattern')"
10607        );
10608    }
10609
10610    #[test]
10611    fn test_regexp_substr_all_snowflake_to_duckdb_5arg_e_flag() {
10612        let dialect = Dialect::get(DialectType::Snowflake);
10613        let result = dialect
10614            .transpile(
10615                "SELECT REGEXP_SUBSTR_ALL(s, 'pattern', 1, 1, 'e')",
10616                DialectType::DuckDB,
10617            )
10618            .unwrap();
10619        assert_eq!(result[0], "SELECT REGEXP_EXTRACT_ALL(s, 'pattern')");
10620    }
10621
10622    #[test]
10623    fn test_regexp_substr_all_snowflake_to_duckdb_6arg_group0() {
10624        let dialect = Dialect::get(DialectType::Snowflake);
10625        let result = dialect
10626            .transpile(
10627                "SELECT REGEXP_SUBSTR_ALL(s, 'pattern', 1, 1, 'e', 0)",
10628                DialectType::DuckDB,
10629            )
10630            .unwrap();
10631        assert_eq!(result[0], "SELECT REGEXP_EXTRACT_ALL(s, 'pattern')");
10632    }
10633
10634    #[test]
10635    fn test_regexp_substr_all_snowflake_identity_strip_group0() {
10636        let dialect = Dialect::get(DialectType::Snowflake);
10637        let result = dialect
10638            .transpile(
10639                "SELECT REGEXP_SUBSTR_ALL(s, 'pattern', 1, 1, 'e', 0)",
10640                DialectType::Snowflake,
10641            )
10642            .unwrap();
10643        assert_eq!(
10644            result[0],
10645            "SELECT REGEXP_SUBSTR_ALL(s, 'pattern', 1, 1, 'e')"
10646        );
10647    }
10648
10649    #[test]
10650    fn test_regexp_count_snowflake_to_duckdb_2arg() {
10651        let dialect = Dialect::get(DialectType::Snowflake);
10652        let result = dialect
10653            .transpile("SELECT REGEXP_COUNT(s, 'pattern')", DialectType::DuckDB)
10654            .unwrap();
10655        assert_eq!(
10656            result[0],
10657            "SELECT CASE WHEN 'pattern' = '' THEN 0 ELSE LENGTH(REGEXP_EXTRACT_ALL(s, 'pattern')) END"
10658        );
10659    }
10660
10661    #[test]
10662    fn test_regexp_count_snowflake_to_duckdb_3arg() {
10663        let dialect = Dialect::get(DialectType::Snowflake);
10664        let result = dialect
10665            .transpile("SELECT REGEXP_COUNT(s, 'pattern', 3)", DialectType::DuckDB)
10666            .unwrap();
10667        assert_eq!(
10668            result[0],
10669            "SELECT CASE WHEN 'pattern' = '' THEN 0 ELSE LENGTH(REGEXP_EXTRACT_ALL(SUBSTRING(s, 3), 'pattern')) END"
10670        );
10671    }
10672
10673    #[test]
10674    fn test_regexp_count_snowflake_to_duckdb_4arg_flags() {
10675        let dialect = Dialect::get(DialectType::Snowflake);
10676        let result = dialect
10677            .transpile(
10678                "SELECT REGEXP_COUNT(s, 'pattern', 1, 'i')",
10679                DialectType::DuckDB,
10680            )
10681            .unwrap();
10682        assert_eq!(
10683            result[0],
10684            "SELECT CASE WHEN '(?i)' || 'pattern' = '' THEN 0 ELSE LENGTH(REGEXP_EXTRACT_ALL(SUBSTRING(s, 1), '(?i)' || 'pattern')) END"
10685        );
10686    }
10687
10688    #[test]
10689    fn test_regexp_count_snowflake_to_duckdb_4arg_flags_literal_string() {
10690        let dialect = Dialect::get(DialectType::Snowflake);
10691        let result = dialect
10692            .transpile(
10693                "SELECT REGEXP_COUNT('Hello World', 'L', 1, 'im')",
10694                DialectType::DuckDB,
10695            )
10696            .unwrap();
10697        assert_eq!(
10698            result[0],
10699            "SELECT CASE WHEN '(?im)' || 'L' = '' THEN 0 ELSE LENGTH(REGEXP_EXTRACT_ALL(SUBSTRING('Hello World', 1), '(?im)' || 'L')) END"
10700        );
10701    }
10702
10703    #[test]
10704    fn test_regexp_replace_snowflake_to_duckdb_5arg_pos1_occ1() {
10705        let dialect = Dialect::get(DialectType::Snowflake);
10706        let result = dialect
10707            .transpile(
10708                "SELECT REGEXP_REPLACE(s, 'pattern', 'repl', 1, 1)",
10709                DialectType::DuckDB,
10710            )
10711            .unwrap();
10712        assert_eq!(result[0], "SELECT REGEXP_REPLACE(s, 'pattern', 'repl')");
10713    }
10714
10715    #[test]
10716    fn test_regexp_replace_snowflake_to_duckdb_5arg_pos_gt1_occ0() {
10717        let dialect = Dialect::get(DialectType::Snowflake);
10718        let result = dialect
10719            .transpile(
10720                "SELECT REGEXP_REPLACE(s, 'pattern', 'repl', 3, 0)",
10721                DialectType::DuckDB,
10722            )
10723            .unwrap();
10724        assert_eq!(
10725            result[0],
10726            "SELECT SUBSTRING(s, 1, 2) || REGEXP_REPLACE(SUBSTRING(s, 3), 'pattern', 'repl', 'g')"
10727        );
10728    }
10729
10730    #[test]
10731    fn test_regexp_replace_snowflake_to_duckdb_5arg_pos_gt1_occ1() {
10732        let dialect = Dialect::get(DialectType::Snowflake);
10733        let result = dialect
10734            .transpile(
10735                "SELECT REGEXP_REPLACE(s, 'pattern', 'repl', 3, 1)",
10736                DialectType::DuckDB,
10737            )
10738            .unwrap();
10739        assert_eq!(
10740            result[0],
10741            "SELECT SUBSTRING(s, 1, 2) || REGEXP_REPLACE(SUBSTRING(s, 3), 'pattern', 'repl')"
10742        );
10743    }
10744
10745    #[test]
10746    fn test_rlike_snowflake_to_duckdb_2arg() {
10747        let dialect = Dialect::get(DialectType::Snowflake);
10748        let result = dialect
10749            .transpile("SELECT RLIKE(a, b)", DialectType::DuckDB)
10750            .unwrap();
10751        assert_eq!(result[0], "SELECT REGEXP_FULL_MATCH(a, b)");
10752    }
10753
10754    #[test]
10755    fn test_rlike_snowflake_to_duckdb_3arg_flags() {
10756        let dialect = Dialect::get(DialectType::Snowflake);
10757        let result = dialect
10758            .transpile("SELECT RLIKE(a, b, 'i')", DialectType::DuckDB)
10759            .unwrap();
10760        assert_eq!(result[0], "SELECT REGEXP_FULL_MATCH(a, b, 'i')");
10761    }
10762
10763    #[test]
10764    fn test_regexp_extract_all_bigquery_to_snowflake_no_capture() {
10765        let dialect = Dialect::get(DialectType::BigQuery);
10766        let result = dialect
10767            .transpile(
10768                "SELECT REGEXP_EXTRACT_ALL(s, 'pattern')",
10769                DialectType::Snowflake,
10770            )
10771            .unwrap();
10772        assert_eq!(result[0], "SELECT REGEXP_SUBSTR_ALL(s, 'pattern')");
10773    }
10774
10775    #[test]
10776    fn test_regexp_extract_all_bigquery_to_snowflake_with_capture() {
10777        let dialect = Dialect::get(DialectType::BigQuery);
10778        let result = dialect
10779            .transpile(
10780                "SELECT REGEXP_EXTRACT_ALL(s, '(a)[0-9]')",
10781                DialectType::Snowflake,
10782            )
10783            .unwrap();
10784        assert_eq!(
10785            result[0],
10786            "SELECT REGEXP_SUBSTR_ALL(s, '(a)[0-9]', 1, 1, 'c', 1)"
10787        );
10788    }
10789
10790    #[test]
10791    fn test_regexp_instr_snowflake_to_duckdb_2arg() {
10792        let dialect = Dialect::get(DialectType::Snowflake);
10793        let result = dialect
10794            .transpile("SELECT REGEXP_INSTR(s, 'pattern')", DialectType::DuckDB)
10795            .unwrap();
10796        assert!(
10797            result[0].contains("CASE WHEN"),
10798            "Expected CASE WHEN in result: {}",
10799            result[0]
10800        );
10801        assert!(
10802            result[0].contains("LIST_SUM"),
10803            "Expected LIST_SUM in result: {}",
10804            result[0]
10805        );
10806    }
10807
10808    #[test]
10809    fn test_array_except_generic_to_duckdb() {
10810        let dialect = Dialect::get(DialectType::Generic);
10811        let result = dialect
10812            .transpile(
10813                "SELECT ARRAY_EXCEPT(ARRAY(1, 2, 3), ARRAY(2))",
10814                DialectType::DuckDB,
10815            )
10816            .unwrap();
10817        eprintln!("ARRAY_EXCEPT Generic->DuckDB: {}", result[0]);
10818        assert!(
10819            result[0].contains("CASE WHEN"),
10820            "Expected CASE WHEN: {}",
10821            result[0]
10822        );
10823        assert!(
10824            result[0].contains("LIST_FILTER"),
10825            "Expected LIST_FILTER: {}",
10826            result[0]
10827        );
10828        assert!(
10829            result[0].contains("LIST_DISTINCT"),
10830            "Expected LIST_DISTINCT: {}",
10831            result[0]
10832        );
10833        assert!(
10834            result[0].contains("IS NOT DISTINCT FROM"),
10835            "Expected IS NOT DISTINCT FROM: {}",
10836            result[0]
10837        );
10838        assert!(
10839            result[0].contains("= 0"),
10840            "Expected = 0 filter: {}",
10841            result[0]
10842        );
10843    }
10844
10845    #[test]
10846    fn test_array_except_generic_to_snowflake() {
10847        let dialect = Dialect::get(DialectType::Generic);
10848        let result = dialect
10849            .transpile(
10850                "SELECT ARRAY_EXCEPT(ARRAY(1, 2, 3), ARRAY(2))",
10851                DialectType::Snowflake,
10852            )
10853            .unwrap();
10854        eprintln!("ARRAY_EXCEPT Generic->Snowflake: {}", result[0]);
10855        assert_eq!(result[0], "SELECT ARRAY_EXCEPT([1, 2, 3], [2])");
10856    }
10857
10858    #[test]
10859    fn test_array_except_generic_to_presto() {
10860        let dialect = Dialect::get(DialectType::Generic);
10861        let result = dialect
10862            .transpile(
10863                "SELECT ARRAY_EXCEPT(ARRAY(1, 2, 3), ARRAY(2))",
10864                DialectType::Presto,
10865            )
10866            .unwrap();
10867        eprintln!("ARRAY_EXCEPT Generic->Presto: {}", result[0]);
10868        assert_eq!(result[0], "SELECT ARRAY_EXCEPT(ARRAY[1, 2, 3], ARRAY[2])");
10869    }
10870
10871    #[test]
10872    fn test_array_except_snowflake_to_duckdb() {
10873        let dialect = Dialect::get(DialectType::Snowflake);
10874        let result = dialect
10875            .transpile("SELECT ARRAY_EXCEPT([1, 2, 3], [2])", DialectType::DuckDB)
10876            .unwrap();
10877        eprintln!("ARRAY_EXCEPT Snowflake->DuckDB: {}", result[0]);
10878        assert!(
10879            result[0].contains("CASE WHEN"),
10880            "Expected CASE WHEN: {}",
10881            result[0]
10882        );
10883        assert!(
10884            result[0].contains("LIST_TRANSFORM"),
10885            "Expected LIST_TRANSFORM: {}",
10886            result[0]
10887        );
10888    }
10889
10890    #[test]
10891    fn test_array_contains_snowflake_to_snowflake() {
10892        let dialect = Dialect::get(DialectType::Snowflake);
10893        let result = dialect
10894            .transpile(
10895                "SELECT ARRAY_CONTAINS(x, [1, NULL, 3])",
10896                DialectType::Snowflake,
10897            )
10898            .unwrap();
10899        eprintln!("ARRAY_CONTAINS Snowflake->Snowflake: {}", result[0]);
10900        assert_eq!(result[0], "SELECT ARRAY_CONTAINS(x, [1, NULL, 3])");
10901    }
10902
10903    #[test]
10904    fn test_array_contains_snowflake_to_duckdb() {
10905        let dialect = Dialect::get(DialectType::Snowflake);
10906        let result = dialect
10907            .transpile(
10908                "SELECT ARRAY_CONTAINS(x, [1, NULL, 3])",
10909                DialectType::DuckDB,
10910            )
10911            .unwrap();
10912        eprintln!("ARRAY_CONTAINS Snowflake->DuckDB: {}", result[0]);
10913        assert!(
10914            result[0].contains("CASE WHEN"),
10915            "Expected CASE WHEN: {}",
10916            result[0]
10917        );
10918        assert!(
10919            result[0].contains("NULLIF"),
10920            "Expected NULLIF: {}",
10921            result[0]
10922        );
10923        assert!(
10924            result[0].contains("ARRAY_CONTAINS"),
10925            "Expected ARRAY_CONTAINS: {}",
10926            result[0]
10927        );
10928    }
10929
10930    #[test]
10931    fn test_array_distinct_snowflake_to_duckdb() {
10932        let dialect = Dialect::get(DialectType::Snowflake);
10933        let result = dialect
10934            .transpile(
10935                "SELECT ARRAY_DISTINCT([1, 2, 2, 3, 1])",
10936                DialectType::DuckDB,
10937            )
10938            .unwrap();
10939        eprintln!("ARRAY_DISTINCT Snowflake->DuckDB: {}", result[0]);
10940        assert!(
10941            result[0].contains("CASE WHEN"),
10942            "Expected CASE WHEN: {}",
10943            result[0]
10944        );
10945        assert!(
10946            result[0].contains("LIST_DISTINCT"),
10947            "Expected LIST_DISTINCT: {}",
10948            result[0]
10949        );
10950        assert!(
10951            result[0].contains("LIST_APPEND"),
10952            "Expected LIST_APPEND: {}",
10953            result[0]
10954        );
10955        assert!(
10956            result[0].contains("LIST_FILTER"),
10957            "Expected LIST_FILTER: {}",
10958            result[0]
10959        );
10960    }
10961}