Skip to main content

polyglot_sql/dialects/
mod.rs

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