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