polyglot_sql/dialects/mod.rs
1//! SQL Dialect System
2//!
3//! This module implements the dialect abstraction layer that enables SQL transpilation
4//! between 30+ database engines. 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
24#[cfg(feature = "dialect-athena")]
25mod athena;
26#[cfg(feature = "dialect-bigquery")]
27mod bigquery;
28#[cfg(feature = "dialect-clickhouse")]
29mod clickhouse;
30#[cfg(feature = "dialect-cockroachdb")]
31mod cockroachdb;
32#[cfg(feature = "dialect-databricks")]
33mod databricks;
34#[cfg(feature = "dialect-datafusion")]
35mod datafusion;
36#[cfg(feature = "dialect-doris")]
37mod doris;
38#[cfg(feature = "dialect-dremio")]
39mod dremio;
40#[cfg(feature = "dialect-drill")]
41mod drill;
42#[cfg(feature = "dialect-druid")]
43mod druid;
44#[cfg(feature = "dialect-duckdb")]
45mod duckdb;
46#[cfg(feature = "dialect-dune")]
47mod dune;
48#[cfg(feature = "dialect-exasol")]
49mod exasol;
50#[cfg(feature = "dialect-fabric")]
51mod fabric;
52#[cfg(feature = "dialect-hive")]
53mod hive;
54#[cfg(feature = "dialect-materialize")]
55mod materialize;
56#[cfg(feature = "dialect-mysql")]
57mod mysql;
58#[cfg(feature = "dialect-oracle")]
59mod oracle;
60#[cfg(feature = "dialect-postgresql")]
61mod postgres;
62#[cfg(feature = "dialect-presto")]
63mod presto;
64#[cfg(feature = "dialect-redshift")]
65mod redshift;
66#[cfg(feature = "dialect-risingwave")]
67mod risingwave;
68#[cfg(feature = "dialect-singlestore")]
69mod singlestore;
70#[cfg(feature = "dialect-snowflake")]
71mod snowflake;
72#[cfg(feature = "dialect-solr")]
73mod solr;
74#[cfg(feature = "dialect-spark")]
75mod spark;
76#[cfg(feature = "dialect-sqlite")]
77mod sqlite;
78#[cfg(feature = "dialect-starrocks")]
79mod starrocks;
80#[cfg(feature = "dialect-tableau")]
81mod tableau;
82#[cfg(feature = "dialect-teradata")]
83mod teradata;
84#[cfg(feature = "dialect-tidb")]
85mod tidb;
86#[cfg(feature = "dialect-trino")]
87mod trino;
88#[cfg(feature = "dialect-tsql")]
89mod tsql;
90
91pub use generic::GenericDialect; // Always available
92
93#[cfg(feature = "dialect-athena")]
94pub use athena::AthenaDialect;
95#[cfg(feature = "dialect-bigquery")]
96pub use bigquery::BigQueryDialect;
97#[cfg(feature = "dialect-clickhouse")]
98pub use clickhouse::ClickHouseDialect;
99#[cfg(feature = "dialect-cockroachdb")]
100pub use cockroachdb::CockroachDBDialect;
101#[cfg(feature = "dialect-databricks")]
102pub use databricks::DatabricksDialect;
103#[cfg(feature = "dialect-datafusion")]
104pub use datafusion::DataFusionDialect;
105#[cfg(feature = "dialect-doris")]
106pub use doris::DorisDialect;
107#[cfg(feature = "dialect-dremio")]
108pub use dremio::DremioDialect;
109#[cfg(feature = "dialect-drill")]
110pub use drill::DrillDialect;
111#[cfg(feature = "dialect-druid")]
112pub use druid::DruidDialect;
113#[cfg(feature = "dialect-duckdb")]
114pub use duckdb::DuckDBDialect;
115#[cfg(feature = "dialect-dune")]
116pub use dune::DuneDialect;
117#[cfg(feature = "dialect-exasol")]
118pub use exasol::ExasolDialect;
119#[cfg(feature = "dialect-fabric")]
120pub use fabric::FabricDialect;
121#[cfg(feature = "dialect-hive")]
122pub use hive::HiveDialect;
123#[cfg(feature = "dialect-materialize")]
124pub use materialize::MaterializeDialect;
125#[cfg(feature = "dialect-mysql")]
126pub use mysql::MySQLDialect;
127#[cfg(feature = "dialect-oracle")]
128pub use oracle::OracleDialect;
129#[cfg(feature = "dialect-postgresql")]
130pub use postgres::PostgresDialect;
131#[cfg(feature = "dialect-presto")]
132pub use presto::PrestoDialect;
133#[cfg(feature = "dialect-redshift")]
134pub use redshift::RedshiftDialect;
135#[cfg(feature = "dialect-risingwave")]
136pub use risingwave::RisingWaveDialect;
137#[cfg(feature = "dialect-singlestore")]
138pub use singlestore::SingleStoreDialect;
139#[cfg(feature = "dialect-snowflake")]
140pub use snowflake::SnowflakeDialect;
141#[cfg(feature = "dialect-solr")]
142pub use solr::SolrDialect;
143#[cfg(feature = "dialect-spark")]
144pub use spark::SparkDialect;
145#[cfg(feature = "dialect-sqlite")]
146pub use sqlite::SQLiteDialect;
147#[cfg(feature = "dialect-starrocks")]
148pub use starrocks::StarRocksDialect;
149#[cfg(feature = "dialect-tableau")]
150pub use tableau::TableauDialect;
151#[cfg(feature = "dialect-teradata")]
152pub use teradata::TeradataDialect;
153#[cfg(feature = "dialect-tidb")]
154pub use tidb::TiDBDialect;
155#[cfg(feature = "dialect-trino")]
156pub use trino::TrinoDialect;
157#[cfg(feature = "dialect-tsql")]
158pub use tsql::TSQLDialect;
159
160use crate::error::Result;
161#[cfg(feature = "transpile")]
162use crate::expressions::{
163 BinaryOp, Case, Cast, ColumnConstraint, DateBin, Fetch, Function, Identifier, Interval,
164 IntervalUnit, IntervalUnitSpec, LikeOp, Literal, Top, Var,
165};
166use crate::expressions::{DataType, Expression};
167#[cfg(any(
168 feature = "transpile",
169 feature = "ast-tools",
170 feature = "generate",
171 feature = "semantic"
172))]
173use crate::expressions::{From, FunctionBody, Join, Null, OrderBy, OutputClause, TableRef, With};
174#[cfg(feature = "transpile")]
175use crate::generator::UnsupportedLevel;
176#[cfg(feature = "generate")]
177use crate::generator::{Generator, GeneratorConfig};
178#[cfg(feature = "transpile")]
179use crate::guard::enforce_generate_ast;
180use crate::guard::{enforce_input, ComplexityGuardOptions};
181use crate::parser::Parser;
182#[cfg(feature = "transpile")]
183use crate::tokens::TokenType;
184use crate::tokens::{Token, Tokenizer, TokenizerConfig};
185#[cfg(feature = "transpile")]
186use crate::traversal::ExpressionWalk;
187use serde::{Deserialize, Serialize};
188use std::collections::HashMap;
189use std::sync::{Arc, LazyLock, RwLock};
190
191/// Enumeration of all supported SQL dialects.
192///
193/// Each variant corresponds to a specific SQL database engine or query language.
194/// The `Generic` variant represents standard SQL with no dialect-specific behavior,
195/// and is used as the default when no dialect is specified.
196///
197/// Dialect names are case-insensitive when parsed from strings via [`FromStr`].
198/// Some dialects accept aliases (e.g., "mssql" and "sqlserver" both resolve to [`TSQL`](DialectType::TSQL)).
199#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
200#[serde(rename_all = "lowercase")]
201pub enum DialectType {
202 /// Standard SQL with no dialect-specific behavior (default).
203 Generic,
204 /// PostgreSQL -- advanced open-source relational database.
205 PostgreSQL,
206 /// MySQL -- widely-used open-source relational database (also accepts "mysql").
207 MySQL,
208 /// Google BigQuery -- serverless cloud data warehouse with unique syntax (backtick quoting, STRUCT types, QUALIFY).
209 BigQuery,
210 /// Snowflake -- cloud data platform with QUALIFY clause, FLATTEN, and variant types.
211 Snowflake,
212 /// DuckDB -- in-process analytical database with modern SQL extensions.
213 DuckDB,
214 /// SQLite -- lightweight embedded relational database.
215 SQLite,
216 /// Apache Hive -- data warehouse on Hadoop with HiveQL syntax.
217 Hive,
218 /// Apache Spark SQL -- distributed query engine (also accepts "spark2").
219 Spark,
220 /// Trino -- distributed SQL query engine (formerly PrestoSQL).
221 Trino,
222 /// PrestoDB -- distributed SQL query engine for big data.
223 Presto,
224 /// Amazon Redshift -- cloud data warehouse based on PostgreSQL.
225 Redshift,
226 /// Transact-SQL (T-SQL) -- Microsoft SQL Server and Azure SQL (also accepts "mssql", "sqlserver").
227 TSQL,
228 /// Oracle Database -- commercial relational database with PL/SQL extensions.
229 Oracle,
230 /// ClickHouse -- column-oriented OLAP database for real-time analytics.
231 ClickHouse,
232 /// Databricks SQL -- Spark-based lakehouse platform with QUALIFY support.
233 Databricks,
234 /// Amazon Athena -- serverless query service (hybrid Trino/Hive engine).
235 Athena,
236 /// Teradata -- enterprise data warehouse with proprietary SQL extensions.
237 Teradata,
238 /// Apache Doris -- real-time analytical database (MySQL-compatible).
239 Doris,
240 /// StarRocks -- sub-second OLAP database (MySQL-compatible).
241 StarRocks,
242 /// Materialize -- streaming SQL database built on differential dataflow.
243 Materialize,
244 /// RisingWave -- distributed streaming database with PostgreSQL compatibility.
245 RisingWave,
246 /// SingleStore (formerly MemSQL) -- distributed SQL database (also accepts "memsql").
247 SingleStore,
248 /// CockroachDB -- distributed SQL database with PostgreSQL compatibility (also accepts "cockroach").
249 CockroachDB,
250 /// TiDB -- distributed HTAP database with MySQL compatibility.
251 TiDB,
252 /// Apache Druid -- real-time analytics database.
253 Druid,
254 /// Apache Solr -- search platform with SQL interface.
255 Solr,
256 /// Tableau -- data visualization platform with its own SQL dialect.
257 Tableau,
258 /// Dune Analytics -- blockchain analytics SQL engine.
259 Dune,
260 /// Microsoft Fabric -- unified analytics platform (T-SQL based).
261 Fabric,
262 /// Apache Drill -- schema-free SQL query engine for big data.
263 Drill,
264 /// Dremio -- data lakehouse platform with Arrow-based query engine.
265 Dremio,
266 /// Exasol -- in-memory analytic database.
267 Exasol,
268 /// Apache DataFusion -- Arrow-based query engine with modern SQL extensions.
269 DataFusion,
270}
271
272impl Default for DialectType {
273 fn default() -> Self {
274 DialectType::Generic
275 }
276}
277
278impl std::fmt::Display for DialectType {
279 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
280 match self {
281 DialectType::Generic => write!(f, "generic"),
282 DialectType::PostgreSQL => write!(f, "postgresql"),
283 DialectType::MySQL => write!(f, "mysql"),
284 DialectType::BigQuery => write!(f, "bigquery"),
285 DialectType::Snowflake => write!(f, "snowflake"),
286 DialectType::DuckDB => write!(f, "duckdb"),
287 DialectType::SQLite => write!(f, "sqlite"),
288 DialectType::Hive => write!(f, "hive"),
289 DialectType::Spark => write!(f, "spark"),
290 DialectType::Trino => write!(f, "trino"),
291 DialectType::Presto => write!(f, "presto"),
292 DialectType::Redshift => write!(f, "redshift"),
293 DialectType::TSQL => write!(f, "tsql"),
294 DialectType::Oracle => write!(f, "oracle"),
295 DialectType::ClickHouse => write!(f, "clickhouse"),
296 DialectType::Databricks => write!(f, "databricks"),
297 DialectType::Athena => write!(f, "athena"),
298 DialectType::Teradata => write!(f, "teradata"),
299 DialectType::Doris => write!(f, "doris"),
300 DialectType::StarRocks => write!(f, "starrocks"),
301 DialectType::Materialize => write!(f, "materialize"),
302 DialectType::RisingWave => write!(f, "risingwave"),
303 DialectType::SingleStore => write!(f, "singlestore"),
304 DialectType::CockroachDB => write!(f, "cockroachdb"),
305 DialectType::TiDB => write!(f, "tidb"),
306 DialectType::Druid => write!(f, "druid"),
307 DialectType::Solr => write!(f, "solr"),
308 DialectType::Tableau => write!(f, "tableau"),
309 DialectType::Dune => write!(f, "dune"),
310 DialectType::Fabric => write!(f, "fabric"),
311 DialectType::Drill => write!(f, "drill"),
312 DialectType::Dremio => write!(f, "dremio"),
313 DialectType::Exasol => write!(f, "exasol"),
314 DialectType::DataFusion => write!(f, "datafusion"),
315 }
316 }
317}
318
319impl std::str::FromStr for DialectType {
320 type Err = crate::error::Error;
321
322 fn from_str(s: &str) -> Result<Self> {
323 match s.to_ascii_lowercase().as_str() {
324 "generic" | "" => Ok(DialectType::Generic),
325 "postgres" | "postgresql" => Ok(DialectType::PostgreSQL),
326 "mysql" => Ok(DialectType::MySQL),
327 "bigquery" => Ok(DialectType::BigQuery),
328 "snowflake" => Ok(DialectType::Snowflake),
329 "duckdb" => Ok(DialectType::DuckDB),
330 "sqlite" => Ok(DialectType::SQLite),
331 "hive" => Ok(DialectType::Hive),
332 "spark" | "spark2" => Ok(DialectType::Spark),
333 "trino" => Ok(DialectType::Trino),
334 "presto" => Ok(DialectType::Presto),
335 "redshift" => Ok(DialectType::Redshift),
336 "tsql" | "mssql" | "sqlserver" => Ok(DialectType::TSQL),
337 "oracle" => Ok(DialectType::Oracle),
338 "clickhouse" => Ok(DialectType::ClickHouse),
339 "databricks" => Ok(DialectType::Databricks),
340 "athena" => Ok(DialectType::Athena),
341 "teradata" => Ok(DialectType::Teradata),
342 "doris" => Ok(DialectType::Doris),
343 "starrocks" => Ok(DialectType::StarRocks),
344 "materialize" => Ok(DialectType::Materialize),
345 "risingwave" => Ok(DialectType::RisingWave),
346 "singlestore" | "memsql" => Ok(DialectType::SingleStore),
347 "cockroachdb" | "cockroach" => Ok(DialectType::CockroachDB),
348 "tidb" => Ok(DialectType::TiDB),
349 "druid" => Ok(DialectType::Druid),
350 "solr" => Ok(DialectType::Solr),
351 "tableau" => Ok(DialectType::Tableau),
352 "dune" => Ok(DialectType::Dune),
353 "fabric" => Ok(DialectType::Fabric),
354 "drill" => Ok(DialectType::Drill),
355 "dremio" => Ok(DialectType::Dremio),
356 "exasol" => Ok(DialectType::Exasol),
357 "datafusion" | "arrow-datafusion" | "arrow_datafusion" => Ok(DialectType::DataFusion),
358 _ => Err(crate::error::Error::parse(
359 format!("Unknown dialect: {}", s),
360 0,
361 0,
362 0,
363 0,
364 )),
365 }
366 }
367}
368
369/// Trait that each concrete SQL dialect must implement.
370///
371/// `DialectImpl` provides the configuration hooks and per-expression transform logic
372/// that distinguish one dialect from another. Implementors supply:
373///
374/// - A [`DialectType`] identifier.
375/// - Optional overrides for tokenizer and generator configuration (defaults to generic SQL).
376/// - An expression-level transform function ([`transform_expr`](DialectImpl::transform_expr))
377/// that rewrites individual AST nodes for this dialect (e.g., converting `NVL` to `COALESCE`).
378/// - An optional preprocessing step ([`preprocess`](DialectImpl::preprocess)) for whole-tree
379/// rewrites that must run before the recursive per-node transform (e.g., eliminating QUALIFY).
380///
381/// The default implementations are no-ops, so a minimal dialect only needs to provide
382/// [`dialect_type`](DialectImpl::dialect_type) and override the methods that differ from
383/// standard SQL.
384pub trait DialectImpl {
385 /// Returns the [`DialectType`] that identifies this dialect.
386 fn dialect_type(&self) -> DialectType;
387
388 /// Returns the tokenizer configuration for this dialect.
389 ///
390 /// Override to customize identifier quoting characters, string escape rules,
391 /// comment styles, and other lexing behavior.
392 fn tokenizer_config(&self) -> TokenizerConfig {
393 TokenizerConfig::default()
394 }
395
396 /// Returns the generator configuration for this dialect.
397 ///
398 /// Override to customize identifier quoting style, function name casing,
399 /// keyword casing, and other SQL generation behavior.
400 #[cfg(feature = "generate")]
401 fn generator_config(&self) -> GeneratorConfig {
402 GeneratorConfig::default()
403 }
404
405 /// Returns a generator configuration tailored to a specific expression.
406 ///
407 /// Override this for hybrid dialects like Athena that route to different SQL engines
408 /// based on expression type (e.g., Hive-style generation for DDL, Trino-style for DML).
409 /// The default delegates to [`generator_config`](DialectImpl::generator_config).
410 #[cfg(feature = "generate")]
411 fn generator_config_for_expr(&self, _expr: &Expression) -> GeneratorConfig {
412 self.generator_config()
413 }
414
415 /// Transforms a single expression node for this dialect, without recursing into children.
416 ///
417 /// This is the per-node rewrite hook invoked by [`transform_recursive`]. Return the
418 /// expression unchanged if no dialect-specific rewrite is needed. Transformations
419 /// typically include function renaming, operator substitution, and type mapping.
420 #[cfg(feature = "transpile")]
421 fn transform_expr(&self, expr: Expression) -> Result<Expression> {
422 Ok(expr)
423 }
424
425 /// Applies whole-tree preprocessing transforms before the recursive per-node pass.
426 ///
427 /// Override this to apply structural rewrites that must see the entire tree at once,
428 /// such as `eliminate_qualify`, `eliminate_distinct_on`, `ensure_bools`, or
429 /// `explode_projection_to_unnest`. The default is a no-op pass-through.
430 #[cfg(feature = "transpile")]
431 fn preprocess(&self, expr: Expression) -> Result<Expression> {
432 Ok(expr)
433 }
434}
435
436/// Recursively transforms a [`DataType`](crate::expressions::DataType), handling nested
437/// parametric types such as `ARRAY<INT>`, `STRUCT<a INT, b TEXT>`, and `MAP<STRING, INT>`.
438///
439/// The outer type is first passed through `transform_fn` as an `Expression::DataType`,
440/// and then nested element/field types are recursed into. This ensures that dialect-level
441/// type mappings (e.g., `INT` to `INTEGER`) propagate into complex nested types.
442#[cfg(any(
443 feature = "transpile",
444 feature = "ast-tools",
445 feature = "generate",
446 feature = "semantic"
447))]
448fn transform_data_type_recursive<F>(
449 dt: crate::expressions::DataType,
450 transform_fn: &F,
451) -> Result<crate::expressions::DataType>
452where
453 F: Fn(Expression) -> Result<Expression>,
454{
455 use crate::expressions::DataType;
456 // First, transform the outermost type through the expression system
457 let dt_expr = transform_fn(Expression::DataType(dt))?;
458 let dt = match dt_expr {
459 Expression::DataType(d) => d,
460 _ => {
461 return Ok(match dt_expr {
462 _ => DataType::Custom {
463 name: "UNKNOWN".to_string(),
464 },
465 })
466 }
467 };
468 // Then recurse into nested types
469 match dt {
470 DataType::Array {
471 element_type,
472 dimension,
473 } => {
474 let inner = transform_data_type_recursive(*element_type, transform_fn)?;
475 Ok(DataType::Array {
476 element_type: Box::new(inner),
477 dimension,
478 })
479 }
480 DataType::List { element_type } => {
481 let inner = transform_data_type_recursive(*element_type, transform_fn)?;
482 Ok(DataType::List {
483 element_type: Box::new(inner),
484 })
485 }
486 DataType::Struct { fields, nested } => {
487 let mut new_fields = Vec::new();
488 for mut field in fields {
489 field.data_type = transform_data_type_recursive(field.data_type, transform_fn)?;
490 new_fields.push(field);
491 }
492 Ok(DataType::Struct {
493 fields: new_fields,
494 nested,
495 })
496 }
497 DataType::Map {
498 key_type,
499 value_type,
500 } => {
501 let k = transform_data_type_recursive(*key_type, transform_fn)?;
502 let v = transform_data_type_recursive(*value_type, transform_fn)?;
503 Ok(DataType::Map {
504 key_type: Box::new(k),
505 value_type: Box::new(v),
506 })
507 }
508 other => Ok(other),
509 }
510}
511
512/// Convert DuckDB C-style format strings to Presto C-style format strings.
513/// DuckDB and Presto both use C-style % directives but with different specifiers for some cases.
514#[cfg(feature = "transpile")]
515fn duckdb_to_presto_format(fmt: &str) -> String {
516 // Order matters: handle longer patterns first to avoid partial replacements
517 let mut result = fmt.to_string();
518 // First pass: mark multi-char patterns with placeholders
519 result = result.replace("%-m", "\x01NOPADM\x01");
520 result = result.replace("%-d", "\x01NOPADD\x01");
521 result = result.replace("%-I", "\x01NOPADI\x01");
522 result = result.replace("%-H", "\x01NOPADH\x01");
523 result = result.replace("%H:%M:%S", "\x01HMS\x01");
524 result = result.replace("%Y-%m-%d", "\x01YMD\x01");
525 // Now convert individual specifiers
526 result = result.replace("%M", "%i");
527 result = result.replace("%S", "%s");
528 // Restore multi-char patterns with Presto equivalents
529 result = result.replace("\x01NOPADM\x01", "%c");
530 result = result.replace("\x01NOPADD\x01", "%e");
531 result = result.replace("\x01NOPADI\x01", "%l");
532 result = result.replace("\x01NOPADH\x01", "%k");
533 result = result.replace("\x01HMS\x01", "%T");
534 result = result.replace("\x01YMD\x01", "%Y-%m-%d");
535 result
536}
537
538/// Convert DuckDB C-style format strings to BigQuery format strings.
539/// BigQuery uses a mix of strftime-like directives.
540#[cfg(feature = "transpile")]
541fn duckdb_to_bigquery_format(fmt: &str) -> String {
542 let mut result = fmt.to_string();
543 // Handle longer patterns first
544 result = result.replace("%-d", "%e");
545 result = result.replace("%Y-%m-%d %H:%M:%S", "%F %T");
546 result = result.replace("%Y-%m-%d", "%F");
547 result = result.replace("%H:%M:%S", "%T");
548 result
549}
550
551#[cfg(feature = "transpile")]
552fn presto_to_java_format(fmt: &str) -> String {
553 fmt.replace("%Y", "yyyy")
554 .replace("%m", "MM")
555 .replace("%d", "dd")
556 .replace("%H", "HH")
557 .replace("%i", "mm")
558 .replace("%S", "ss")
559 .replace("%s", "ss")
560 .replace("%y", "yy")
561 .replace("%T", "HH:mm:ss")
562 .replace("%F", "yyyy-MM-dd")
563 .replace("%M", "MMMM")
564}
565
566#[cfg(feature = "transpile")]
567fn normalize_presto_format(fmt: &str) -> String {
568 fmt.replace("%H:%i:%S", "%T").replace("%H:%i:%s", "%T")
569}
570
571#[cfg(feature = "transpile")]
572fn presto_to_duckdb_format(fmt: &str) -> String {
573 fmt.replace("%i", "%M")
574 .replace("%s", "%S")
575 .replace("%T", "%H:%M:%S")
576}
577
578#[cfg(feature = "transpile")]
579fn presto_to_bigquery_format(fmt: &str) -> String {
580 fmt.replace("%Y-%m-%d", "%F")
581 .replace("%H:%i:%S", "%T")
582 .replace("%H:%i:%s", "%T")
583 .replace("%i", "%M")
584 .replace("%s", "%S")
585}
586
587#[cfg(feature = "transpile")]
588fn is_default_presto_timestamp_format(fmt: &str) -> bool {
589 let normalized = normalize_presto_format(fmt);
590 normalized == "%Y-%m-%d %T"
591 || normalized == "%Y-%m-%d %H:%i:%S"
592 || fmt == "%Y-%m-%d %H:%i:%S"
593 || fmt == "%Y-%m-%d %T"
594}
595
596#[cfg(feature = "transpile")]
597fn is_default_presto_date_format(fmt: &str) -> bool {
598 fmt == "%Y-%m-%d" || fmt == "%F"
599}
600
601#[cfg(any(
602 feature = "transpile",
603 feature = "ast-tools",
604 feature = "generate",
605 feature = "semantic"
606))]
607#[derive(Debug)]
608enum TransformTask {
609 Visit(Expression),
610 Finish(FinishTask),
611}
612
613#[cfg(any(
614 feature = "transpile",
615 feature = "ast-tools",
616 feature = "generate",
617 feature = "semantic"
618))]
619#[derive(Debug)]
620enum FinishTask {
621 Unary(Expression),
622 Binary(Expression),
623 CastLike(Expression),
624 List(Expression, usize),
625 From(crate::expressions::From, usize),
626 Select(SelectFrame),
627 SetOp(Expression),
628}
629
630#[cfg(any(
631 feature = "transpile",
632 feature = "ast-tools",
633 feature = "generate",
634 feature = "semantic"
635))]
636#[derive(Debug)]
637struct SelectFrame {
638 select: Box<crate::expressions::Select>,
639 expr_count: usize,
640 from_present: bool,
641 where_present: bool,
642 group_by_count: usize,
643 having_present: bool,
644 qualify_present: bool,
645}
646
647#[cfg(any(
648 feature = "transpile",
649 feature = "ast-tools",
650 feature = "generate",
651 feature = "semantic"
652))]
653fn transform_pop_result(results: &mut Vec<Expression>) -> Result<Expression> {
654 results
655 .pop()
656 .ok_or_else(|| crate::error::Error::Internal("transform stack underflow".to_string()))
657}
658
659#[cfg(any(
660 feature = "transpile",
661 feature = "ast-tools",
662 feature = "generate",
663 feature = "semantic"
664))]
665fn transform_pop_results(results: &mut Vec<Expression>, count: usize) -> Result<Vec<Expression>> {
666 if results.len() < count {
667 return Err(crate::error::Error::Internal(
668 "transform result stack underflow".to_string(),
669 ));
670 }
671 Ok(results.split_off(results.len() - count))
672}
673
674/// Applies a transform function bottom-up through an entire expression tree.
675///
676/// The public entrypoint uses an explicit task stack for the recursion-heavy shapes
677/// that dominate deeply nested SQL (nested SELECT/FROM/SUBQUERY chains, set-operation
678/// trees, and common binary/unary expression chains). Less common shapes currently
679/// reuse the reference recursive implementation so semantics stay identical while
680/// the hot path avoids stack growth.
681#[cfg(any(
682 feature = "transpile",
683 feature = "ast-tools",
684 feature = "generate",
685 feature = "semantic"
686))]
687pub fn transform_recursive<F>(expr: Expression, transform_fn: &F) -> Result<Expression>
688where
689 F: Fn(Expression) -> Result<Expression>,
690{
691 #[cfg(feature = "stacker")]
692 {
693 let red_zone = if cfg!(debug_assertions) {
694 4 * 1024 * 1024
695 } else {
696 1024 * 1024
697 };
698 stacker::maybe_grow(red_zone, 8 * 1024 * 1024, move || {
699 transform_recursive_inner(expr, transform_fn)
700 })
701 }
702 #[cfg(not(feature = "stacker"))]
703 {
704 transform_recursive_inner(expr, transform_fn)
705 }
706}
707
708#[cfg(any(
709 feature = "transpile",
710 feature = "ast-tools",
711 feature = "generate",
712 feature = "semantic"
713))]
714fn transform_recursive_inner<F>(expr: Expression, transform_fn: &F) -> Result<Expression>
715where
716 F: Fn(Expression) -> Result<Expression>,
717{
718 let mut tasks = vec![TransformTask::Visit(expr)];
719 let mut results = Vec::new();
720
721 while let Some(task) = tasks.pop() {
722 match task {
723 TransformTask::Visit(expr) => {
724 if matches!(
725 &expr,
726 Expression::Literal(_)
727 | Expression::Boolean(_)
728 | Expression::Null(_)
729 | Expression::Identifier(_)
730 | Expression::Star(_)
731 | Expression::Parameter(_)
732 | Expression::Placeholder(_)
733 | Expression::SessionParameter(_)
734 ) {
735 results.push(transform_fn(expr)?);
736 continue;
737 }
738
739 match expr {
740 Expression::Alias(mut alias) => {
741 let child = std::mem::replace(&mut alias.this, Expression::Null(Null));
742 tasks.push(TransformTask::Finish(FinishTask::Unary(Expression::Alias(
743 alias,
744 ))));
745 tasks.push(TransformTask::Visit(child));
746 }
747 Expression::Paren(mut paren) => {
748 let child = std::mem::replace(&mut paren.this, Expression::Null(Null));
749 tasks.push(TransformTask::Finish(FinishTask::Unary(Expression::Paren(
750 paren,
751 ))));
752 tasks.push(TransformTask::Visit(child));
753 }
754 Expression::Not(mut not) => {
755 let child = std::mem::replace(&mut not.this, Expression::Null(Null));
756 tasks.push(TransformTask::Finish(FinishTask::Unary(Expression::Not(
757 not,
758 ))));
759 tasks.push(TransformTask::Visit(child));
760 }
761 Expression::Neg(mut neg) => {
762 let child = std::mem::replace(&mut neg.this, Expression::Null(Null));
763 tasks.push(TransformTask::Finish(FinishTask::Unary(Expression::Neg(
764 neg,
765 ))));
766 tasks.push(TransformTask::Visit(child));
767 }
768 Expression::IsNull(mut expr) => {
769 let child = std::mem::replace(&mut expr.this, Expression::Null(Null));
770 tasks.push(TransformTask::Finish(FinishTask::Unary(
771 Expression::IsNull(expr),
772 )));
773 tasks.push(TransformTask::Visit(child));
774 }
775 Expression::IsTrue(mut expr) => {
776 let child = std::mem::replace(&mut expr.this, Expression::Null(Null));
777 tasks.push(TransformTask::Finish(FinishTask::Unary(
778 Expression::IsTrue(expr),
779 )));
780 tasks.push(TransformTask::Visit(child));
781 }
782 Expression::IsFalse(mut expr) => {
783 let child = std::mem::replace(&mut expr.this, Expression::Null(Null));
784 tasks.push(TransformTask::Finish(FinishTask::Unary(
785 Expression::IsFalse(expr),
786 )));
787 tasks.push(TransformTask::Visit(child));
788 }
789 Expression::Subquery(mut subquery) => {
790 let child = std::mem::replace(&mut subquery.this, Expression::Null(Null));
791 tasks.push(TransformTask::Finish(FinishTask::Unary(
792 Expression::Subquery(subquery),
793 )));
794 tasks.push(TransformTask::Visit(child));
795 }
796 Expression::Exists(mut exists) => {
797 let child = std::mem::replace(&mut exists.this, Expression::Null(Null));
798 tasks.push(TransformTask::Finish(FinishTask::Unary(
799 Expression::Exists(exists),
800 )));
801 tasks.push(TransformTask::Visit(child));
802 }
803 Expression::TableArgument(mut arg) => {
804 let child = std::mem::replace(&mut arg.this, Expression::Null(Null));
805 tasks.push(TransformTask::Finish(FinishTask::Unary(
806 Expression::TableArgument(arg),
807 )));
808 tasks.push(TransformTask::Visit(child));
809 }
810 Expression::And(mut op) => {
811 let right = std::mem::replace(&mut op.right, Expression::Null(Null));
812 let left = std::mem::replace(&mut op.left, Expression::Null(Null));
813 tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::And(
814 op,
815 ))));
816 tasks.push(TransformTask::Visit(right));
817 tasks.push(TransformTask::Visit(left));
818 }
819 Expression::Or(mut op) => {
820 let right = std::mem::replace(&mut op.right, Expression::Null(Null));
821 let left = std::mem::replace(&mut op.left, Expression::Null(Null));
822 tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::Or(
823 op,
824 ))));
825 tasks.push(TransformTask::Visit(right));
826 tasks.push(TransformTask::Visit(left));
827 }
828 Expression::Add(mut op) => {
829 let right = std::mem::replace(&mut op.right, Expression::Null(Null));
830 let left = std::mem::replace(&mut op.left, Expression::Null(Null));
831 tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::Add(
832 op,
833 ))));
834 tasks.push(TransformTask::Visit(right));
835 tasks.push(TransformTask::Visit(left));
836 }
837 Expression::Sub(mut op) => {
838 let right = std::mem::replace(&mut op.right, Expression::Null(Null));
839 let left = std::mem::replace(&mut op.left, Expression::Null(Null));
840 tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::Sub(
841 op,
842 ))));
843 tasks.push(TransformTask::Visit(right));
844 tasks.push(TransformTask::Visit(left));
845 }
846 Expression::Mul(mut op) => {
847 let right = std::mem::replace(&mut op.right, Expression::Null(Null));
848 let left = std::mem::replace(&mut op.left, Expression::Null(Null));
849 tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::Mul(
850 op,
851 ))));
852 tasks.push(TransformTask::Visit(right));
853 tasks.push(TransformTask::Visit(left));
854 }
855 Expression::Div(mut op) => {
856 let right = std::mem::replace(&mut op.right, Expression::Null(Null));
857 let left = std::mem::replace(&mut op.left, Expression::Null(Null));
858 tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::Div(
859 op,
860 ))));
861 tasks.push(TransformTask::Visit(right));
862 tasks.push(TransformTask::Visit(left));
863 }
864 Expression::Eq(mut op) => {
865 let right = std::mem::replace(&mut op.right, Expression::Null(Null));
866 let left = std::mem::replace(&mut op.left, Expression::Null(Null));
867 tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::Eq(
868 op,
869 ))));
870 tasks.push(TransformTask::Visit(right));
871 tasks.push(TransformTask::Visit(left));
872 }
873 Expression::Lt(mut op) => {
874 let right = std::mem::replace(&mut op.right, Expression::Null(Null));
875 let left = std::mem::replace(&mut op.left, Expression::Null(Null));
876 tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::Lt(
877 op,
878 ))));
879 tasks.push(TransformTask::Visit(right));
880 tasks.push(TransformTask::Visit(left));
881 }
882 Expression::Gt(mut op) => {
883 let right = std::mem::replace(&mut op.right, Expression::Null(Null));
884 let left = std::mem::replace(&mut op.left, Expression::Null(Null));
885 tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::Gt(
886 op,
887 ))));
888 tasks.push(TransformTask::Visit(right));
889 tasks.push(TransformTask::Visit(left));
890 }
891 Expression::Neq(mut op) => {
892 let right = std::mem::replace(&mut op.right, Expression::Null(Null));
893 let left = std::mem::replace(&mut op.left, Expression::Null(Null));
894 tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::Neq(
895 op,
896 ))));
897 tasks.push(TransformTask::Visit(right));
898 tasks.push(TransformTask::Visit(left));
899 }
900 Expression::Lte(mut op) => {
901 let right = std::mem::replace(&mut op.right, Expression::Null(Null));
902 let left = std::mem::replace(&mut op.left, Expression::Null(Null));
903 tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::Lte(
904 op,
905 ))));
906 tasks.push(TransformTask::Visit(right));
907 tasks.push(TransformTask::Visit(left));
908 }
909 Expression::Gte(mut op) => {
910 let right = std::mem::replace(&mut op.right, Expression::Null(Null));
911 let left = std::mem::replace(&mut op.left, Expression::Null(Null));
912 tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::Gte(
913 op,
914 ))));
915 tasks.push(TransformTask::Visit(right));
916 tasks.push(TransformTask::Visit(left));
917 }
918 Expression::Mod(mut op) => {
919 let right = std::mem::replace(&mut op.right, Expression::Null(Null));
920 let left = std::mem::replace(&mut op.left, Expression::Null(Null));
921 tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::Mod(
922 op,
923 ))));
924 tasks.push(TransformTask::Visit(right));
925 tasks.push(TransformTask::Visit(left));
926 }
927 Expression::Concat(mut op) => {
928 let right = std::mem::replace(&mut op.right, Expression::Null(Null));
929 let left = std::mem::replace(&mut op.left, Expression::Null(Null));
930 tasks.push(TransformTask::Finish(FinishTask::Binary(
931 Expression::Concat(op),
932 )));
933 tasks.push(TransformTask::Visit(right));
934 tasks.push(TransformTask::Visit(left));
935 }
936 Expression::BitwiseAnd(mut op) => {
937 let right = std::mem::replace(&mut op.right, Expression::Null(Null));
938 let left = std::mem::replace(&mut op.left, Expression::Null(Null));
939 tasks.push(TransformTask::Finish(FinishTask::Binary(
940 Expression::BitwiseAnd(op),
941 )));
942 tasks.push(TransformTask::Visit(right));
943 tasks.push(TransformTask::Visit(left));
944 }
945 Expression::BitwiseOr(mut op) => {
946 let right = std::mem::replace(&mut op.right, Expression::Null(Null));
947 let left = std::mem::replace(&mut op.left, Expression::Null(Null));
948 tasks.push(TransformTask::Finish(FinishTask::Binary(
949 Expression::BitwiseOr(op),
950 )));
951 tasks.push(TransformTask::Visit(right));
952 tasks.push(TransformTask::Visit(left));
953 }
954 Expression::BitwiseXor(mut op) => {
955 let right = std::mem::replace(&mut op.right, Expression::Null(Null));
956 let left = std::mem::replace(&mut op.left, Expression::Null(Null));
957 tasks.push(TransformTask::Finish(FinishTask::Binary(
958 Expression::BitwiseXor(op),
959 )));
960 tasks.push(TransformTask::Visit(right));
961 tasks.push(TransformTask::Visit(left));
962 }
963 Expression::Is(mut op) => {
964 let right = std::mem::replace(&mut op.right, Expression::Null(Null));
965 let left = std::mem::replace(&mut op.left, Expression::Null(Null));
966 tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::Is(
967 op,
968 ))));
969 tasks.push(TransformTask::Visit(right));
970 tasks.push(TransformTask::Visit(left));
971 }
972 Expression::MemberOf(mut op) => {
973 let right = std::mem::replace(&mut op.right, Expression::Null(Null));
974 let left = std::mem::replace(&mut op.left, Expression::Null(Null));
975 tasks.push(TransformTask::Finish(FinishTask::Binary(
976 Expression::MemberOf(op),
977 )));
978 tasks.push(TransformTask::Visit(right));
979 tasks.push(TransformTask::Visit(left));
980 }
981 Expression::ArrayContainsAll(mut op) => {
982 let right = std::mem::replace(&mut op.right, Expression::Null(Null));
983 let left = std::mem::replace(&mut op.left, Expression::Null(Null));
984 tasks.push(TransformTask::Finish(FinishTask::Binary(
985 Expression::ArrayContainsAll(op),
986 )));
987 tasks.push(TransformTask::Visit(right));
988 tasks.push(TransformTask::Visit(left));
989 }
990 Expression::ArrayContainedBy(mut op) => {
991 let right = std::mem::replace(&mut op.right, Expression::Null(Null));
992 let left = std::mem::replace(&mut op.left, Expression::Null(Null));
993 tasks.push(TransformTask::Finish(FinishTask::Binary(
994 Expression::ArrayContainedBy(op),
995 )));
996 tasks.push(TransformTask::Visit(right));
997 tasks.push(TransformTask::Visit(left));
998 }
999 Expression::ArrayOverlaps(mut op) => {
1000 let right = std::mem::replace(&mut op.right, Expression::Null(Null));
1001 let left = std::mem::replace(&mut op.left, Expression::Null(Null));
1002 tasks.push(TransformTask::Finish(FinishTask::Binary(
1003 Expression::ArrayOverlaps(op),
1004 )));
1005 tasks.push(TransformTask::Visit(right));
1006 tasks.push(TransformTask::Visit(left));
1007 }
1008 Expression::TsMatch(mut op) => {
1009 let right = std::mem::replace(&mut op.right, Expression::Null(Null));
1010 let left = std::mem::replace(&mut op.left, Expression::Null(Null));
1011 tasks.push(TransformTask::Finish(FinishTask::Binary(
1012 Expression::TsMatch(op),
1013 )));
1014 tasks.push(TransformTask::Visit(right));
1015 tasks.push(TransformTask::Visit(left));
1016 }
1017 Expression::Adjacent(mut op) => {
1018 let right = std::mem::replace(&mut op.right, Expression::Null(Null));
1019 let left = std::mem::replace(&mut op.left, Expression::Null(Null));
1020 tasks.push(TransformTask::Finish(FinishTask::Binary(
1021 Expression::Adjacent(op),
1022 )));
1023 tasks.push(TransformTask::Visit(right));
1024 tasks.push(TransformTask::Visit(left));
1025 }
1026 Expression::Like(mut like) => {
1027 let right = std::mem::replace(&mut like.right, Expression::Null(Null));
1028 let left = std::mem::replace(&mut like.left, Expression::Null(Null));
1029 tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::Like(
1030 like,
1031 ))));
1032 tasks.push(TransformTask::Visit(right));
1033 tasks.push(TransformTask::Visit(left));
1034 }
1035 Expression::ILike(mut like) => {
1036 let right = std::mem::replace(&mut like.right, Expression::Null(Null));
1037 let left = std::mem::replace(&mut like.left, Expression::Null(Null));
1038 tasks.push(TransformTask::Finish(FinishTask::Binary(
1039 Expression::ILike(like),
1040 )));
1041 tasks.push(TransformTask::Visit(right));
1042 tasks.push(TransformTask::Visit(left));
1043 }
1044 Expression::Cast(mut cast) => {
1045 let child = std::mem::replace(&mut cast.this, Expression::Null(Null));
1046 tasks.push(TransformTask::Finish(FinishTask::CastLike(
1047 Expression::Cast(cast),
1048 )));
1049 tasks.push(TransformTask::Visit(child));
1050 }
1051 Expression::TryCast(mut cast) => {
1052 let child = std::mem::replace(&mut cast.this, Expression::Null(Null));
1053 tasks.push(TransformTask::Finish(FinishTask::CastLike(
1054 Expression::TryCast(cast),
1055 )));
1056 tasks.push(TransformTask::Visit(child));
1057 }
1058 Expression::SafeCast(mut cast) => {
1059 let child = std::mem::replace(&mut cast.this, Expression::Null(Null));
1060 tasks.push(TransformTask::Finish(FinishTask::CastLike(
1061 Expression::SafeCast(cast),
1062 )));
1063 tasks.push(TransformTask::Visit(child));
1064 }
1065 Expression::Function(mut function) => {
1066 let args = std::mem::take(&mut function.args);
1067 let count = args.len();
1068 tasks.push(TransformTask::Finish(FinishTask::List(
1069 Expression::Function(function),
1070 count,
1071 )));
1072 for child in args.into_iter().rev() {
1073 tasks.push(TransformTask::Visit(child));
1074 }
1075 }
1076 Expression::Array(mut array) => {
1077 let expressions = std::mem::take(&mut array.expressions);
1078 let count = expressions.len();
1079 tasks.push(TransformTask::Finish(FinishTask::List(
1080 Expression::Array(array),
1081 count,
1082 )));
1083 for child in expressions.into_iter().rev() {
1084 tasks.push(TransformTask::Visit(child));
1085 }
1086 }
1087 Expression::Tuple(mut tuple) => {
1088 let expressions = std::mem::take(&mut tuple.expressions);
1089 let count = expressions.len();
1090 tasks.push(TransformTask::Finish(FinishTask::List(
1091 Expression::Tuple(tuple),
1092 count,
1093 )));
1094 for child in expressions.into_iter().rev() {
1095 tasks.push(TransformTask::Visit(child));
1096 }
1097 }
1098 Expression::ArrayFunc(mut array) => {
1099 let expressions = std::mem::take(&mut array.expressions);
1100 let count = expressions.len();
1101 tasks.push(TransformTask::Finish(FinishTask::List(
1102 Expression::ArrayFunc(array),
1103 count,
1104 )));
1105 for child in expressions.into_iter().rev() {
1106 tasks.push(TransformTask::Visit(child));
1107 }
1108 }
1109 Expression::Coalesce(mut func) => {
1110 let expressions = std::mem::take(&mut func.expressions);
1111 let count = expressions.len();
1112 tasks.push(TransformTask::Finish(FinishTask::List(
1113 Expression::Coalesce(func),
1114 count,
1115 )));
1116 for child in expressions.into_iter().rev() {
1117 tasks.push(TransformTask::Visit(child));
1118 }
1119 }
1120 Expression::Greatest(mut func) => {
1121 let expressions = std::mem::take(&mut func.expressions);
1122 let count = expressions.len();
1123 tasks.push(TransformTask::Finish(FinishTask::List(
1124 Expression::Greatest(func),
1125 count,
1126 )));
1127 for child in expressions.into_iter().rev() {
1128 tasks.push(TransformTask::Visit(child));
1129 }
1130 }
1131 Expression::Least(mut func) => {
1132 let expressions = std::mem::take(&mut func.expressions);
1133 let count = expressions.len();
1134 tasks.push(TransformTask::Finish(FinishTask::List(
1135 Expression::Least(func),
1136 count,
1137 )));
1138 for child in expressions.into_iter().rev() {
1139 tasks.push(TransformTask::Visit(child));
1140 }
1141 }
1142 Expression::ArrayConcat(mut func) => {
1143 let expressions = std::mem::take(&mut func.expressions);
1144 let count = expressions.len();
1145 tasks.push(TransformTask::Finish(FinishTask::List(
1146 Expression::ArrayConcat(func),
1147 count,
1148 )));
1149 for child in expressions.into_iter().rev() {
1150 tasks.push(TransformTask::Visit(child));
1151 }
1152 }
1153 Expression::ArrayIntersect(mut func) => {
1154 let expressions = std::mem::take(&mut func.expressions);
1155 let count = expressions.len();
1156 tasks.push(TransformTask::Finish(FinishTask::List(
1157 Expression::ArrayIntersect(func),
1158 count,
1159 )));
1160 for child in expressions.into_iter().rev() {
1161 tasks.push(TransformTask::Visit(child));
1162 }
1163 }
1164 Expression::ArrayZip(mut func) => {
1165 let expressions = std::mem::take(&mut func.expressions);
1166 let count = expressions.len();
1167 tasks.push(TransformTask::Finish(FinishTask::List(
1168 Expression::ArrayZip(func),
1169 count,
1170 )));
1171 for child in expressions.into_iter().rev() {
1172 tasks.push(TransformTask::Visit(child));
1173 }
1174 }
1175 Expression::MapConcat(mut func) => {
1176 let expressions = std::mem::take(&mut func.expressions);
1177 let count = expressions.len();
1178 tasks.push(TransformTask::Finish(FinishTask::List(
1179 Expression::MapConcat(func),
1180 count,
1181 )));
1182 for child in expressions.into_iter().rev() {
1183 tasks.push(TransformTask::Visit(child));
1184 }
1185 }
1186 Expression::JsonArray(mut func) => {
1187 let expressions = std::mem::take(&mut func.expressions);
1188 let count = expressions.len();
1189 tasks.push(TransformTask::Finish(FinishTask::List(
1190 Expression::JsonArray(func),
1191 count,
1192 )));
1193 for child in expressions.into_iter().rev() {
1194 tasks.push(TransformTask::Visit(child));
1195 }
1196 }
1197 Expression::From(mut from) => {
1198 let expressions = std::mem::take(&mut from.expressions);
1199 let count = expressions.len();
1200 tasks.push(TransformTask::Finish(FinishTask::From(*from, count)));
1201 for child in expressions.into_iter().rev() {
1202 tasks.push(TransformTask::Visit(child));
1203 }
1204 }
1205 Expression::Select(mut select) => {
1206 let expressions = std::mem::take(&mut select.expressions);
1207 let expr_count = expressions.len();
1208
1209 let from_info = select.from.take().map(|mut from| {
1210 let children = std::mem::take(&mut from.expressions);
1211 (from, children)
1212 });
1213 let from_present = from_info.is_some();
1214
1215 let where_child = select.where_clause.as_mut().map(|where_clause| {
1216 std::mem::replace(&mut where_clause.this, Expression::Null(Null))
1217 });
1218 let where_present = where_child.is_some();
1219
1220 let group_expressions = select
1221 .group_by
1222 .as_mut()
1223 .map(|group_by| std::mem::take(&mut group_by.expressions))
1224 .unwrap_or_default();
1225 let group_by_count = group_expressions.len();
1226
1227 let having_child = select.having.as_mut().map(|having| {
1228 std::mem::replace(&mut having.this, Expression::Null(Null))
1229 });
1230 let having_present = having_child.is_some();
1231
1232 let qualify_child = select.qualify.as_mut().map(|qualify| {
1233 std::mem::replace(&mut qualify.this, Expression::Null(Null))
1234 });
1235 let qualify_present = qualify_child.is_some();
1236
1237 tasks.push(TransformTask::Finish(FinishTask::Select(SelectFrame {
1238 select,
1239 expr_count,
1240 from_present,
1241 where_present,
1242 group_by_count,
1243 having_present,
1244 qualify_present,
1245 })));
1246
1247 if let Some(child) = qualify_child {
1248 tasks.push(TransformTask::Visit(child));
1249 }
1250 if let Some(child) = having_child {
1251 tasks.push(TransformTask::Visit(child));
1252 }
1253 for child in group_expressions.into_iter().rev() {
1254 tasks.push(TransformTask::Visit(child));
1255 }
1256 if let Some(child) = where_child {
1257 tasks.push(TransformTask::Visit(child));
1258 }
1259 if let Some((from, children)) = from_info {
1260 tasks.push(TransformTask::Finish(FinishTask::From(
1261 from,
1262 children.len(),
1263 )));
1264 for child in children.into_iter().rev() {
1265 tasks.push(TransformTask::Visit(child));
1266 }
1267 }
1268 for child in expressions.into_iter().rev() {
1269 tasks.push(TransformTask::Visit(child));
1270 }
1271 }
1272 Expression::Union(mut union) => {
1273 let right = std::mem::replace(&mut union.right, Expression::Null(Null));
1274 let left = std::mem::replace(&mut union.left, Expression::Null(Null));
1275 tasks.push(TransformTask::Finish(FinishTask::SetOp(Expression::Union(
1276 union,
1277 ))));
1278 tasks.push(TransformTask::Visit(right));
1279 tasks.push(TransformTask::Visit(left));
1280 }
1281 Expression::Intersect(mut intersect) => {
1282 let right = std::mem::replace(&mut intersect.right, Expression::Null(Null));
1283 let left = std::mem::replace(&mut intersect.left, Expression::Null(Null));
1284 tasks.push(TransformTask::Finish(FinishTask::SetOp(
1285 Expression::Intersect(intersect),
1286 )));
1287 tasks.push(TransformTask::Visit(right));
1288 tasks.push(TransformTask::Visit(left));
1289 }
1290 Expression::Except(mut except) => {
1291 let right = std::mem::replace(&mut except.right, Expression::Null(Null));
1292 let left = std::mem::replace(&mut except.left, Expression::Null(Null));
1293 tasks.push(TransformTask::Finish(FinishTask::SetOp(
1294 Expression::Except(except),
1295 )));
1296 tasks.push(TransformTask::Visit(right));
1297 tasks.push(TransformTask::Visit(left));
1298 }
1299 other => {
1300 results.push(transform_recursive_reference(other, transform_fn)?);
1301 }
1302 }
1303 }
1304 TransformTask::Finish(finish) => match finish {
1305 FinishTask::Unary(expr) => {
1306 let child = transform_pop_result(&mut results)?;
1307 let rebuilt = match expr {
1308 Expression::Alias(mut alias) => {
1309 alias.this = child;
1310 Expression::Alias(alias)
1311 }
1312 Expression::Paren(mut paren) => {
1313 paren.this = child;
1314 Expression::Paren(paren)
1315 }
1316 Expression::Not(mut not) => {
1317 not.this = child;
1318 Expression::Not(not)
1319 }
1320 Expression::Neg(mut neg) => {
1321 neg.this = child;
1322 Expression::Neg(neg)
1323 }
1324 Expression::IsNull(mut expr) => {
1325 expr.this = child;
1326 Expression::IsNull(expr)
1327 }
1328 Expression::IsTrue(mut expr) => {
1329 expr.this = child;
1330 Expression::IsTrue(expr)
1331 }
1332 Expression::IsFalse(mut expr) => {
1333 expr.this = child;
1334 Expression::IsFalse(expr)
1335 }
1336 Expression::Subquery(mut subquery) => {
1337 subquery.this = child;
1338 Expression::Subquery(subquery)
1339 }
1340 Expression::Exists(mut exists) => {
1341 exists.this = child;
1342 Expression::Exists(exists)
1343 }
1344 Expression::TableArgument(mut arg) => {
1345 arg.this = child;
1346 Expression::TableArgument(arg)
1347 }
1348 _ => {
1349 return Err(crate::error::Error::Internal(
1350 "unexpected unary transform task".to_string(),
1351 ));
1352 }
1353 };
1354 results.push(transform_fn(rebuilt)?);
1355 }
1356 FinishTask::Binary(expr) => {
1357 let mut children = transform_pop_results(&mut results, 2)?.into_iter();
1358 let left = children.next().expect("left child");
1359 let right = children.next().expect("right child");
1360 let rebuilt = match expr {
1361 Expression::And(mut op) => {
1362 op.left = left;
1363 op.right = right;
1364 Expression::And(op)
1365 }
1366 Expression::Or(mut op) => {
1367 op.left = left;
1368 op.right = right;
1369 Expression::Or(op)
1370 }
1371 Expression::Add(mut op) => {
1372 op.left = left;
1373 op.right = right;
1374 Expression::Add(op)
1375 }
1376 Expression::Sub(mut op) => {
1377 op.left = left;
1378 op.right = right;
1379 Expression::Sub(op)
1380 }
1381 Expression::Mul(mut op) => {
1382 op.left = left;
1383 op.right = right;
1384 Expression::Mul(op)
1385 }
1386 Expression::Div(mut op) => {
1387 op.left = left;
1388 op.right = right;
1389 Expression::Div(op)
1390 }
1391 Expression::Eq(mut op) => {
1392 op.left = left;
1393 op.right = right;
1394 Expression::Eq(op)
1395 }
1396 Expression::Lt(mut op) => {
1397 op.left = left;
1398 op.right = right;
1399 Expression::Lt(op)
1400 }
1401 Expression::Gt(mut op) => {
1402 op.left = left;
1403 op.right = right;
1404 Expression::Gt(op)
1405 }
1406 Expression::Neq(mut op) => {
1407 op.left = left;
1408 op.right = right;
1409 Expression::Neq(op)
1410 }
1411 Expression::Lte(mut op) => {
1412 op.left = left;
1413 op.right = right;
1414 Expression::Lte(op)
1415 }
1416 Expression::Gte(mut op) => {
1417 op.left = left;
1418 op.right = right;
1419 Expression::Gte(op)
1420 }
1421 Expression::Mod(mut op) => {
1422 op.left = left;
1423 op.right = right;
1424 Expression::Mod(op)
1425 }
1426 Expression::Concat(mut op) => {
1427 op.left = left;
1428 op.right = right;
1429 Expression::Concat(op)
1430 }
1431 Expression::BitwiseAnd(mut op) => {
1432 op.left = left;
1433 op.right = right;
1434 Expression::BitwiseAnd(op)
1435 }
1436 Expression::BitwiseOr(mut op) => {
1437 op.left = left;
1438 op.right = right;
1439 Expression::BitwiseOr(op)
1440 }
1441 Expression::BitwiseXor(mut op) => {
1442 op.left = left;
1443 op.right = right;
1444 Expression::BitwiseXor(op)
1445 }
1446 Expression::Is(mut op) => {
1447 op.left = left;
1448 op.right = right;
1449 Expression::Is(op)
1450 }
1451 Expression::MemberOf(mut op) => {
1452 op.left = left;
1453 op.right = right;
1454 Expression::MemberOf(op)
1455 }
1456 Expression::ArrayContainsAll(mut op) => {
1457 op.left = left;
1458 op.right = right;
1459 Expression::ArrayContainsAll(op)
1460 }
1461 Expression::ArrayContainedBy(mut op) => {
1462 op.left = left;
1463 op.right = right;
1464 Expression::ArrayContainedBy(op)
1465 }
1466 Expression::ArrayOverlaps(mut op) => {
1467 op.left = left;
1468 op.right = right;
1469 Expression::ArrayOverlaps(op)
1470 }
1471 Expression::TsMatch(mut op) => {
1472 op.left = left;
1473 op.right = right;
1474 Expression::TsMatch(op)
1475 }
1476 Expression::Adjacent(mut op) => {
1477 op.left = left;
1478 op.right = right;
1479 Expression::Adjacent(op)
1480 }
1481 Expression::Like(mut like) => {
1482 like.left = left;
1483 like.right = right;
1484 Expression::Like(like)
1485 }
1486 Expression::ILike(mut like) => {
1487 like.left = left;
1488 like.right = right;
1489 Expression::ILike(like)
1490 }
1491 _ => {
1492 return Err(crate::error::Error::Internal(
1493 "unexpected binary transform task".to_string(),
1494 ));
1495 }
1496 };
1497 results.push(transform_fn(rebuilt)?);
1498 }
1499 FinishTask::CastLike(expr) => {
1500 let child = transform_pop_result(&mut results)?;
1501 let rebuilt = match expr {
1502 Expression::Cast(mut cast) => {
1503 cast.this = child;
1504 cast.to = transform_data_type_recursive(cast.to, transform_fn)?;
1505 Expression::Cast(cast)
1506 }
1507 Expression::TryCast(mut cast) => {
1508 cast.this = child;
1509 cast.to = transform_data_type_recursive(cast.to, transform_fn)?;
1510 Expression::TryCast(cast)
1511 }
1512 Expression::SafeCast(mut cast) => {
1513 cast.this = child;
1514 cast.to = transform_data_type_recursive(cast.to, transform_fn)?;
1515 Expression::SafeCast(cast)
1516 }
1517 _ => {
1518 return Err(crate::error::Error::Internal(
1519 "unexpected cast transform task".to_string(),
1520 ));
1521 }
1522 };
1523 results.push(transform_fn(rebuilt)?);
1524 }
1525 FinishTask::List(expr, count) => {
1526 let children = transform_pop_results(&mut results, count)?;
1527 let rebuilt = match expr {
1528 Expression::Function(mut function) => {
1529 function.args = children;
1530 Expression::Function(function)
1531 }
1532 Expression::Array(mut array) => {
1533 array.expressions = children;
1534 Expression::Array(array)
1535 }
1536 Expression::Tuple(mut tuple) => {
1537 tuple.expressions = children;
1538 Expression::Tuple(tuple)
1539 }
1540 Expression::ArrayFunc(mut array) => {
1541 array.expressions = children;
1542 Expression::ArrayFunc(array)
1543 }
1544 Expression::Coalesce(mut func) => {
1545 func.expressions = children;
1546 Expression::Coalesce(func)
1547 }
1548 Expression::Greatest(mut func) => {
1549 func.expressions = children;
1550 Expression::Greatest(func)
1551 }
1552 Expression::Least(mut func) => {
1553 func.expressions = children;
1554 Expression::Least(func)
1555 }
1556 Expression::ArrayConcat(mut func) => {
1557 func.expressions = children;
1558 Expression::ArrayConcat(func)
1559 }
1560 Expression::ArrayIntersect(mut func) => {
1561 func.expressions = children;
1562 Expression::ArrayIntersect(func)
1563 }
1564 Expression::ArrayZip(mut func) => {
1565 func.expressions = children;
1566 Expression::ArrayZip(func)
1567 }
1568 Expression::MapConcat(mut func) => {
1569 func.expressions = children;
1570 Expression::MapConcat(func)
1571 }
1572 Expression::JsonArray(mut func) => {
1573 func.expressions = children;
1574 Expression::JsonArray(func)
1575 }
1576 _ => {
1577 return Err(crate::error::Error::Internal(
1578 "unexpected list transform task".to_string(),
1579 ));
1580 }
1581 };
1582 results.push(transform_fn(rebuilt)?);
1583 }
1584 FinishTask::From(mut from, count) => {
1585 from.expressions = transform_pop_results(&mut results, count)?;
1586 results.push(transform_fn(Expression::From(Box::new(from)))?);
1587 }
1588 FinishTask::Select(frame) => {
1589 let mut select = *frame.select;
1590
1591 if frame.qualify_present {
1592 if let Some(ref mut qualify) = select.qualify {
1593 qualify.this = transform_pop_result(&mut results)?;
1594 }
1595 }
1596 if frame.having_present {
1597 if let Some(ref mut having) = select.having {
1598 having.this = transform_pop_result(&mut results)?;
1599 }
1600 }
1601 if frame.group_by_count > 0 {
1602 if let Some(ref mut group_by) = select.group_by {
1603 group_by.expressions =
1604 transform_pop_results(&mut results, frame.group_by_count)?;
1605 }
1606 }
1607 if frame.where_present {
1608 if let Some(ref mut where_clause) = select.where_clause {
1609 where_clause.this = transform_pop_result(&mut results)?;
1610 }
1611 }
1612 if frame.from_present {
1613 match transform_pop_result(&mut results)? {
1614 Expression::From(from) => {
1615 select.from = Some(*from);
1616 }
1617 _ => {
1618 return Err(crate::error::Error::Internal(
1619 "expected FROM expression result".to_string(),
1620 ));
1621 }
1622 }
1623 }
1624 select.expressions = transform_pop_results(&mut results, frame.expr_count)?;
1625
1626 select.joins = select
1627 .joins
1628 .into_iter()
1629 .map(|mut join| {
1630 join.this = transform_recursive(join.this, transform_fn)?;
1631 if let Some(on) = join.on.take() {
1632 join.on = Some(transform_recursive(on, transform_fn)?);
1633 }
1634 match transform_fn(Expression::Join(Box::new(join)))? {
1635 Expression::Join(j) => Ok(*j),
1636 _ => Err(crate::error::Error::parse(
1637 "Join transformation returned non-join expression",
1638 0,
1639 0,
1640 0,
1641 0,
1642 )),
1643 }
1644 })
1645 .collect::<Result<Vec<_>>>()?;
1646
1647 select.lateral_views = select
1648 .lateral_views
1649 .into_iter()
1650 .map(|mut lv| {
1651 lv.this = transform_recursive(lv.this, transform_fn)?;
1652 Ok(lv)
1653 })
1654 .collect::<Result<Vec<_>>>()?;
1655
1656 if let Some(mut with) = select.with.take() {
1657 with.ctes = with
1658 .ctes
1659 .into_iter()
1660 .map(|mut cte| {
1661 let original = cte.this.clone();
1662 cte.this =
1663 transform_recursive(cte.this, transform_fn).unwrap_or(original);
1664 cte
1665 })
1666 .collect();
1667 select.with = Some(with);
1668 }
1669
1670 if let Some(mut order) = select.order_by.take() {
1671 order.expressions = order
1672 .expressions
1673 .into_iter()
1674 .map(|o| {
1675 let mut o = o;
1676 let original = o.this.clone();
1677 o.this =
1678 transform_recursive(o.this, transform_fn).unwrap_or(original);
1679 match transform_fn(Expression::Ordered(Box::new(o.clone()))) {
1680 Ok(Expression::Ordered(transformed)) => *transformed,
1681 Ok(_) | Err(_) => o,
1682 }
1683 })
1684 .collect();
1685 select.order_by = Some(order);
1686 }
1687
1688 if let Some(ref mut windows) = select.windows {
1689 for nw in windows.iter_mut() {
1690 nw.spec.order_by = std::mem::take(&mut nw.spec.order_by)
1691 .into_iter()
1692 .map(|o| {
1693 let mut o = o;
1694 let original = o.this.clone();
1695 o.this = transform_recursive(o.this, transform_fn)
1696 .unwrap_or(original);
1697 match transform_fn(Expression::Ordered(Box::new(o.clone()))) {
1698 Ok(Expression::Ordered(transformed)) => *transformed,
1699 Ok(_) | Err(_) => o,
1700 }
1701 })
1702 .collect();
1703 }
1704 }
1705
1706 results.push(transform_fn(Expression::Select(Box::new(select)))?);
1707 }
1708 FinishTask::SetOp(expr) => {
1709 let mut children = transform_pop_results(&mut results, 2)?.into_iter();
1710 let left = children.next().expect("left child");
1711 let right = children.next().expect("right child");
1712
1713 let rebuilt = match expr {
1714 Expression::Union(mut union) => {
1715 union.left = left;
1716 union.right = right;
1717 if let Some(mut order) = union.order_by.take() {
1718 order.expressions = order
1719 .expressions
1720 .into_iter()
1721 .map(|o| {
1722 let mut o = o;
1723 let original = o.this.clone();
1724 o.this = transform_recursive(o.this, transform_fn)
1725 .unwrap_or(original);
1726 match transform_fn(Expression::Ordered(Box::new(o.clone())))
1727 {
1728 Ok(Expression::Ordered(transformed)) => *transformed,
1729 Ok(_) | Err(_) => o,
1730 }
1731 })
1732 .collect();
1733 union.order_by = Some(order);
1734 }
1735 if let Some(mut with) = union.with.take() {
1736 with.ctes = with
1737 .ctes
1738 .into_iter()
1739 .map(|mut cte| {
1740 let original = cte.this.clone();
1741 cte.this = transform_recursive(cte.this, transform_fn)
1742 .unwrap_or(original);
1743 cte
1744 })
1745 .collect();
1746 union.with = Some(with);
1747 }
1748 Expression::Union(union)
1749 }
1750 Expression::Intersect(mut intersect) => {
1751 intersect.left = left;
1752 intersect.right = right;
1753 if let Some(mut order) = intersect.order_by.take() {
1754 order.expressions = order
1755 .expressions
1756 .into_iter()
1757 .map(|o| {
1758 let mut o = o;
1759 let original = o.this.clone();
1760 o.this = transform_recursive(o.this, transform_fn)
1761 .unwrap_or(original);
1762 match transform_fn(Expression::Ordered(Box::new(o.clone())))
1763 {
1764 Ok(Expression::Ordered(transformed)) => *transformed,
1765 Ok(_) | Err(_) => o,
1766 }
1767 })
1768 .collect();
1769 intersect.order_by = Some(order);
1770 }
1771 if let Some(mut with) = intersect.with.take() {
1772 with.ctes = with
1773 .ctes
1774 .into_iter()
1775 .map(|mut cte| {
1776 let original = cte.this.clone();
1777 cte.this = transform_recursive(cte.this, transform_fn)
1778 .unwrap_or(original);
1779 cte
1780 })
1781 .collect();
1782 intersect.with = Some(with);
1783 }
1784 Expression::Intersect(intersect)
1785 }
1786 Expression::Except(mut except) => {
1787 except.left = left;
1788 except.right = right;
1789 if let Some(mut order) = except.order_by.take() {
1790 order.expressions = order
1791 .expressions
1792 .into_iter()
1793 .map(|o| {
1794 let mut o = o;
1795 let original = o.this.clone();
1796 o.this = transform_recursive(o.this, transform_fn)
1797 .unwrap_or(original);
1798 match transform_fn(Expression::Ordered(Box::new(o.clone())))
1799 {
1800 Ok(Expression::Ordered(transformed)) => *transformed,
1801 Ok(_) | Err(_) => o,
1802 }
1803 })
1804 .collect();
1805 except.order_by = Some(order);
1806 }
1807 if let Some(mut with) = except.with.take() {
1808 with.ctes = with
1809 .ctes
1810 .into_iter()
1811 .map(|mut cte| {
1812 let original = cte.this.clone();
1813 cte.this = transform_recursive(cte.this, transform_fn)
1814 .unwrap_or(original);
1815 cte
1816 })
1817 .collect();
1818 except.with = Some(with);
1819 }
1820 Expression::Except(except)
1821 }
1822 _ => {
1823 return Err(crate::error::Error::Internal(
1824 "unexpected set-op transform task".to_string(),
1825 ));
1826 }
1827 };
1828 results.push(transform_fn(rebuilt)?);
1829 }
1830 },
1831 }
1832 }
1833
1834 match results.len() {
1835 1 => Ok(results.pop().expect("single transform result")),
1836 _ => Err(crate::error::Error::Internal(
1837 "unexpected transform result stack size".to_string(),
1838 )),
1839 }
1840}
1841
1842#[cfg(any(
1843 feature = "transpile",
1844 feature = "ast-tools",
1845 feature = "generate",
1846 feature = "semantic"
1847))]
1848fn transform_table_ref_recursive<F>(table: TableRef, transform_fn: &F) -> Result<TableRef>
1849where
1850 F: Fn(Expression) -> Result<Expression>,
1851{
1852 match transform_recursive(Expression::Table(Box::new(table)), transform_fn)? {
1853 Expression::Table(table) => Ok(*table),
1854 _ => Err(crate::error::Error::parse(
1855 "TableRef transformation returned non-table expression",
1856 0,
1857 0,
1858 0,
1859 0,
1860 )),
1861 }
1862}
1863
1864#[cfg(any(
1865 feature = "transpile",
1866 feature = "ast-tools",
1867 feature = "generate",
1868 feature = "semantic"
1869))]
1870fn transform_from_recursive<F>(from: From, transform_fn: &F) -> Result<From>
1871where
1872 F: Fn(Expression) -> Result<Expression>,
1873{
1874 match transform_recursive(Expression::From(Box::new(from)), transform_fn)? {
1875 Expression::From(from) => Ok(*from),
1876 _ => Err(crate::error::Error::parse(
1877 "FROM transformation returned non-FROM expression",
1878 0,
1879 0,
1880 0,
1881 0,
1882 )),
1883 }
1884}
1885
1886#[cfg(any(
1887 feature = "transpile",
1888 feature = "ast-tools",
1889 feature = "generate",
1890 feature = "semantic"
1891))]
1892fn transform_join_recursive<F>(mut join: Join, transform_fn: &F) -> Result<Join>
1893where
1894 F: Fn(Expression) -> Result<Expression>,
1895{
1896 join.this = transform_recursive(join.this, transform_fn)?;
1897 if let Some(on) = join.on.take() {
1898 join.on = Some(transform_recursive(on, transform_fn)?);
1899 }
1900 if let Some(match_condition) = join.match_condition.take() {
1901 join.match_condition = Some(transform_recursive(match_condition, transform_fn)?);
1902 }
1903 join.pivots = join
1904 .pivots
1905 .into_iter()
1906 .map(|pivot| transform_recursive(pivot, transform_fn))
1907 .collect::<Result<Vec<_>>>()?;
1908
1909 match transform_fn(Expression::Join(Box::new(join)))? {
1910 Expression::Join(join) => Ok(*join),
1911 _ => Err(crate::error::Error::parse(
1912 "Join transformation returned non-join expression",
1913 0,
1914 0,
1915 0,
1916 0,
1917 )),
1918 }
1919}
1920
1921#[cfg(any(
1922 feature = "transpile",
1923 feature = "ast-tools",
1924 feature = "generate",
1925 feature = "semantic"
1926))]
1927fn transform_output_clause_recursive<F>(
1928 mut output: OutputClause,
1929 transform_fn: &F,
1930) -> Result<OutputClause>
1931where
1932 F: Fn(Expression) -> Result<Expression>,
1933{
1934 output.columns = output
1935 .columns
1936 .into_iter()
1937 .map(|column| transform_recursive(column, transform_fn))
1938 .collect::<Result<Vec<_>>>()?;
1939 if let Some(into_table) = output.into_table.take() {
1940 output.into_table = Some(transform_recursive(into_table, transform_fn)?);
1941 }
1942 Ok(output)
1943}
1944
1945#[cfg(any(
1946 feature = "transpile",
1947 feature = "ast-tools",
1948 feature = "generate",
1949 feature = "semantic"
1950))]
1951fn transform_with_recursive<F>(mut with: With, transform_fn: &F) -> Result<With>
1952where
1953 F: Fn(Expression) -> Result<Expression>,
1954{
1955 with.ctes = with
1956 .ctes
1957 .into_iter()
1958 .map(|mut cte| {
1959 cte.this = transform_recursive(cte.this, transform_fn)?;
1960 Ok(cte)
1961 })
1962 .collect::<Result<Vec<_>>>()?;
1963 if let Some(search) = with.search.take() {
1964 with.search = Some(Box::new(transform_recursive(*search, transform_fn)?));
1965 }
1966 Ok(with)
1967}
1968
1969#[cfg(any(
1970 feature = "transpile",
1971 feature = "ast-tools",
1972 feature = "generate",
1973 feature = "semantic"
1974))]
1975fn transform_order_by_recursive<F>(mut order: OrderBy, transform_fn: &F) -> Result<OrderBy>
1976where
1977 F: Fn(Expression) -> Result<Expression>,
1978{
1979 order.expressions = order
1980 .expressions
1981 .into_iter()
1982 .map(|mut ordered| {
1983 let original = ordered.this.clone();
1984 ordered.this = transform_recursive(ordered.this, transform_fn).unwrap_or(original);
1985 match transform_fn(Expression::Ordered(Box::new(ordered.clone()))) {
1986 Ok(Expression::Ordered(transformed)) => Ok(*transformed),
1987 Ok(_) | Err(_) => Ok(ordered),
1988 }
1989 })
1990 .collect::<Result<Vec<_>>>()?;
1991 Ok(order)
1992}
1993
1994#[cfg(any(
1995 feature = "transpile",
1996 feature = "ast-tools",
1997 feature = "generate",
1998 feature = "semantic"
1999))]
2000fn transform_recursive_reference<F>(expr: Expression, transform_fn: &F) -> Result<Expression>
2001where
2002 F: Fn(Expression) -> Result<Expression>,
2003{
2004 use crate::expressions::BinaryOp;
2005
2006 // Helper macro to recurse into AggFunc-based expressions (this, filter, order_by, having_max, limit).
2007 macro_rules! recurse_agg {
2008 ($variant:ident, $f:expr) => {{
2009 let mut f = $f;
2010 f.this = transform_recursive(f.this, transform_fn)?;
2011 if let Some(filter) = f.filter.take() {
2012 f.filter = Some(transform_recursive(filter, transform_fn)?);
2013 }
2014 for ord in &mut f.order_by {
2015 ord.this = transform_recursive(
2016 std::mem::replace(&mut ord.this, Expression::Null(crate::expressions::Null)),
2017 transform_fn,
2018 )?;
2019 }
2020 if let Some((ref mut expr, _)) = f.having_max {
2021 *expr = Box::new(transform_recursive(
2022 std::mem::replace(expr.as_mut(), Expression::Null(crate::expressions::Null)),
2023 transform_fn,
2024 )?);
2025 }
2026 if let Some(limit) = f.limit.take() {
2027 f.limit = Some(Box::new(transform_recursive(*limit, transform_fn)?));
2028 }
2029 Expression::$variant(f)
2030 }};
2031 }
2032
2033 // Helper macro to transform binary ops with Box<BinaryOp>
2034 macro_rules! transform_binary {
2035 ($variant:ident, $op:expr) => {{
2036 let left = transform_recursive($op.left, transform_fn)?;
2037 let right = transform_recursive($op.right, transform_fn)?;
2038 Expression::$variant(Box::new(BinaryOp {
2039 left,
2040 right,
2041 left_comments: $op.left_comments,
2042 operator_comments: $op.operator_comments,
2043 trailing_comments: $op.trailing_comments,
2044 inferred_type: $op.inferred_type,
2045 }))
2046 }};
2047 }
2048
2049 // Fast path: leaf nodes never need child traversal, apply transform directly
2050 if matches!(
2051 &expr,
2052 Expression::Literal(_)
2053 | Expression::Boolean(_)
2054 | Expression::Null(_)
2055 | Expression::Identifier(_)
2056 | Expression::Star(_)
2057 | Expression::Parameter(_)
2058 | Expression::Placeholder(_)
2059 | Expression::SessionParameter(_)
2060 ) {
2061 return transform_fn(expr);
2062 }
2063
2064 // First recursively transform children, then apply the transform function
2065 let expr = match expr {
2066 Expression::Select(mut select) => {
2067 select.expressions = select
2068 .expressions
2069 .into_iter()
2070 .map(|e| transform_recursive(e, transform_fn))
2071 .collect::<Result<Vec<_>>>()?;
2072
2073 // Transform FROM clause
2074 if let Some(mut from) = select.from.take() {
2075 from.expressions = from
2076 .expressions
2077 .into_iter()
2078 .map(|e| transform_recursive(e, transform_fn))
2079 .collect::<Result<Vec<_>>>()?;
2080 select.from = Some(from);
2081 }
2082
2083 // Transform JOINs - important for CROSS APPLY / LATERAL transformations
2084 select.joins = select
2085 .joins
2086 .into_iter()
2087 .map(|mut join| {
2088 join.this = transform_recursive(join.this, transform_fn)?;
2089 if let Some(on) = join.on.take() {
2090 join.on = Some(transform_recursive(on, transform_fn)?);
2091 }
2092 // Wrap join in Expression::Join to allow transform_fn to transform it
2093 match transform_fn(Expression::Join(Box::new(join)))? {
2094 Expression::Join(j) => Ok(*j),
2095 _ => Err(crate::error::Error::parse(
2096 "Join transformation returned non-join expression",
2097 0,
2098 0,
2099 0,
2100 0,
2101 )),
2102 }
2103 })
2104 .collect::<Result<Vec<_>>>()?;
2105
2106 // Transform LATERAL VIEW expressions (Hive/Spark)
2107 select.lateral_views = select
2108 .lateral_views
2109 .into_iter()
2110 .map(|mut lv| {
2111 lv.this = transform_recursive(lv.this, transform_fn)?;
2112 Ok(lv)
2113 })
2114 .collect::<Result<Vec<_>>>()?;
2115
2116 // Transform WHERE clause
2117 if let Some(mut where_clause) = select.where_clause.take() {
2118 where_clause.this = transform_recursive(where_clause.this, transform_fn)?;
2119 select.where_clause = Some(where_clause);
2120 }
2121
2122 // Transform GROUP BY
2123 if let Some(mut group_by) = select.group_by.take() {
2124 group_by.expressions = group_by
2125 .expressions
2126 .into_iter()
2127 .map(|e| transform_recursive(e, transform_fn))
2128 .collect::<Result<Vec<_>>>()?;
2129 select.group_by = Some(group_by);
2130 }
2131
2132 // Transform HAVING
2133 if let Some(mut having) = select.having.take() {
2134 having.this = transform_recursive(having.this, transform_fn)?;
2135 select.having = Some(having);
2136 }
2137
2138 // Transform WITH (CTEs)
2139 if let Some(mut with) = select.with.take() {
2140 with.ctes = with
2141 .ctes
2142 .into_iter()
2143 .map(|mut cte| {
2144 let original = cte.this.clone();
2145 cte.this = transform_recursive(cte.this, transform_fn).unwrap_or(original);
2146 cte
2147 })
2148 .collect();
2149 select.with = Some(with);
2150 }
2151
2152 // Transform ORDER BY
2153 if let Some(mut order) = select.order_by.take() {
2154 order.expressions = order
2155 .expressions
2156 .into_iter()
2157 .map(|o| {
2158 let mut o = o;
2159 let original = o.this.clone();
2160 o.this = transform_recursive(o.this, transform_fn).unwrap_or(original);
2161 // Also apply transform to the Ordered wrapper itself (for NULLS FIRST etc.)
2162 match transform_fn(Expression::Ordered(Box::new(o.clone()))) {
2163 Ok(Expression::Ordered(transformed)) => *transformed,
2164 Ok(_) | Err(_) => o,
2165 }
2166 })
2167 .collect();
2168 select.order_by = Some(order);
2169 }
2170
2171 // Transform WINDOW clause order_by
2172 if let Some(ref mut windows) = select.windows {
2173 for nw in windows.iter_mut() {
2174 nw.spec.order_by = std::mem::take(&mut nw.spec.order_by)
2175 .into_iter()
2176 .map(|o| {
2177 let mut o = o;
2178 let original = o.this.clone();
2179 o.this = transform_recursive(o.this, transform_fn).unwrap_or(original);
2180 match transform_fn(Expression::Ordered(Box::new(o.clone()))) {
2181 Ok(Expression::Ordered(transformed)) => *transformed,
2182 Ok(_) | Err(_) => o,
2183 }
2184 })
2185 .collect();
2186 }
2187 }
2188
2189 // Transform QUALIFY
2190 if let Some(mut qual) = select.qualify.take() {
2191 qual.this = transform_recursive(qual.this, transform_fn)?;
2192 select.qualify = Some(qual);
2193 }
2194
2195 Expression::Select(select)
2196 }
2197 Expression::Function(mut f) => {
2198 f.args = f
2199 .args
2200 .into_iter()
2201 .map(|e| transform_recursive(e, transform_fn))
2202 .collect::<Result<Vec<_>>>()?;
2203 Expression::Function(f)
2204 }
2205 Expression::AggregateFunction(mut f) => {
2206 f.args = f
2207 .args
2208 .into_iter()
2209 .map(|e| transform_recursive(e, transform_fn))
2210 .collect::<Result<Vec<_>>>()?;
2211 if let Some(filter) = f.filter {
2212 f.filter = Some(transform_recursive(filter, transform_fn)?);
2213 }
2214 Expression::AggregateFunction(f)
2215 }
2216 Expression::WindowFunction(mut wf) => {
2217 wf.this = transform_recursive(wf.this, transform_fn)?;
2218 wf.over.partition_by = wf
2219 .over
2220 .partition_by
2221 .into_iter()
2222 .map(|e| transform_recursive(e, transform_fn))
2223 .collect::<Result<Vec<_>>>()?;
2224 // Transform order_by items through Expression::Ordered wrapper
2225 wf.over.order_by = wf
2226 .over
2227 .order_by
2228 .into_iter()
2229 .map(|o| {
2230 let mut o = o;
2231 o.this = transform_recursive(o.this, transform_fn)?;
2232 match transform_fn(Expression::Ordered(Box::new(o)))? {
2233 Expression::Ordered(transformed) => Ok(*transformed),
2234 _ => Err(crate::error::Error::parse(
2235 "Ordered transformation returned non-Ordered expression",
2236 0,
2237 0,
2238 0,
2239 0,
2240 )),
2241 }
2242 })
2243 .collect::<Result<Vec<_>>>()?;
2244 Expression::WindowFunction(wf)
2245 }
2246 Expression::Alias(mut a) => {
2247 a.this = transform_recursive(a.this, transform_fn)?;
2248 Expression::Alias(a)
2249 }
2250 Expression::Cast(mut c) => {
2251 c.this = transform_recursive(c.this, transform_fn)?;
2252 // Also transform the target data type (recursively for nested types like ARRAY<INT>, STRUCT<a INT>)
2253 c.to = transform_data_type_recursive(c.to, transform_fn)?;
2254 Expression::Cast(c)
2255 }
2256 Expression::And(op) => transform_binary!(And, *op),
2257 Expression::Or(op) => transform_binary!(Or, *op),
2258 Expression::Add(op) => transform_binary!(Add, *op),
2259 Expression::Sub(op) => transform_binary!(Sub, *op),
2260 Expression::Mul(op) => transform_binary!(Mul, *op),
2261 Expression::Div(op) => transform_binary!(Div, *op),
2262 Expression::Eq(op) => transform_binary!(Eq, *op),
2263 Expression::Lt(op) => transform_binary!(Lt, *op),
2264 Expression::Gt(op) => transform_binary!(Gt, *op),
2265 Expression::Paren(mut p) => {
2266 p.this = transform_recursive(p.this, transform_fn)?;
2267 Expression::Paren(p)
2268 }
2269 Expression::Coalesce(mut f) => {
2270 f.expressions = f
2271 .expressions
2272 .into_iter()
2273 .map(|e| transform_recursive(e, transform_fn))
2274 .collect::<Result<Vec<_>>>()?;
2275 Expression::Coalesce(f)
2276 }
2277 Expression::IfNull(mut f) => {
2278 f.this = transform_recursive(f.this, transform_fn)?;
2279 f.expression = transform_recursive(f.expression, transform_fn)?;
2280 Expression::IfNull(f)
2281 }
2282 Expression::Nvl(mut f) => {
2283 f.this = transform_recursive(f.this, transform_fn)?;
2284 f.expression = transform_recursive(f.expression, transform_fn)?;
2285 Expression::Nvl(f)
2286 }
2287 Expression::In(mut i) => {
2288 i.this = transform_recursive(i.this, transform_fn)?;
2289 i.expressions = i
2290 .expressions
2291 .into_iter()
2292 .map(|e| transform_recursive(e, transform_fn))
2293 .collect::<Result<Vec<_>>>()?;
2294 if let Some(query) = i.query {
2295 i.query = Some(transform_recursive(query, transform_fn)?);
2296 }
2297 Expression::In(i)
2298 }
2299 Expression::Not(mut n) => {
2300 n.this = transform_recursive(n.this, transform_fn)?;
2301 Expression::Not(n)
2302 }
2303 Expression::ArraySlice(mut s) => {
2304 s.this = transform_recursive(s.this, transform_fn)?;
2305 if let Some(start) = s.start {
2306 s.start = Some(transform_recursive(start, transform_fn)?);
2307 }
2308 if let Some(end) = s.end {
2309 s.end = Some(transform_recursive(end, transform_fn)?);
2310 }
2311 Expression::ArraySlice(s)
2312 }
2313 Expression::Subscript(mut s) => {
2314 s.this = transform_recursive(s.this, transform_fn)?;
2315 s.index = transform_recursive(s.index, transform_fn)?;
2316 Expression::Subscript(s)
2317 }
2318 Expression::Array(mut a) => {
2319 a.expressions = a
2320 .expressions
2321 .into_iter()
2322 .map(|e| transform_recursive(e, transform_fn))
2323 .collect::<Result<Vec<_>>>()?;
2324 Expression::Array(a)
2325 }
2326 Expression::Struct(mut s) => {
2327 let mut new_fields = Vec::new();
2328 for (name, expr) in s.fields {
2329 let transformed = transform_recursive(expr, transform_fn)?;
2330 new_fields.push((name, transformed));
2331 }
2332 s.fields = new_fields;
2333 Expression::Struct(s)
2334 }
2335 Expression::NamedArgument(mut na) => {
2336 na.value = transform_recursive(na.value, transform_fn)?;
2337 Expression::NamedArgument(na)
2338 }
2339 Expression::MapFunc(mut m) => {
2340 m.keys = m
2341 .keys
2342 .into_iter()
2343 .map(|e| transform_recursive(e, transform_fn))
2344 .collect::<Result<Vec<_>>>()?;
2345 m.values = m
2346 .values
2347 .into_iter()
2348 .map(|e| transform_recursive(e, transform_fn))
2349 .collect::<Result<Vec<_>>>()?;
2350 Expression::MapFunc(m)
2351 }
2352 Expression::ArrayFunc(mut a) => {
2353 a.expressions = a
2354 .expressions
2355 .into_iter()
2356 .map(|e| transform_recursive(e, transform_fn))
2357 .collect::<Result<Vec<_>>>()?;
2358 Expression::ArrayFunc(a)
2359 }
2360 Expression::Lambda(mut l) => {
2361 l.body = transform_recursive(l.body, transform_fn)?;
2362 Expression::Lambda(l)
2363 }
2364 Expression::JsonExtract(mut f) => {
2365 f.this = transform_recursive(f.this, transform_fn)?;
2366 f.path = transform_recursive(f.path, transform_fn)?;
2367 Expression::JsonExtract(f)
2368 }
2369 Expression::JsonExtractScalar(mut f) => {
2370 f.this = transform_recursive(f.this, transform_fn)?;
2371 f.path = transform_recursive(f.path, transform_fn)?;
2372 Expression::JsonExtractScalar(f)
2373 }
2374
2375 // ===== UnaryFunc-based expressions =====
2376 // These all have a single `this: Expression` child
2377 Expression::Length(mut f) => {
2378 f.this = transform_recursive(f.this, transform_fn)?;
2379 Expression::Length(f)
2380 }
2381 Expression::Upper(mut f) => {
2382 f.this = transform_recursive(f.this, transform_fn)?;
2383 Expression::Upper(f)
2384 }
2385 Expression::Lower(mut f) => {
2386 f.this = transform_recursive(f.this, transform_fn)?;
2387 Expression::Lower(f)
2388 }
2389 Expression::LTrim(mut f) => {
2390 f.this = transform_recursive(f.this, transform_fn)?;
2391 Expression::LTrim(f)
2392 }
2393 Expression::RTrim(mut f) => {
2394 f.this = transform_recursive(f.this, transform_fn)?;
2395 Expression::RTrim(f)
2396 }
2397 Expression::Reverse(mut f) => {
2398 f.this = transform_recursive(f.this, transform_fn)?;
2399 Expression::Reverse(f)
2400 }
2401 Expression::Abs(mut f) => {
2402 f.this = transform_recursive(f.this, transform_fn)?;
2403 Expression::Abs(f)
2404 }
2405 Expression::Ceil(mut f) => {
2406 f.this = transform_recursive(f.this, transform_fn)?;
2407 Expression::Ceil(f)
2408 }
2409 Expression::Floor(mut f) => {
2410 f.this = transform_recursive(f.this, transform_fn)?;
2411 Expression::Floor(f)
2412 }
2413 Expression::Sign(mut f) => {
2414 f.this = transform_recursive(f.this, transform_fn)?;
2415 Expression::Sign(f)
2416 }
2417 Expression::Sqrt(mut f) => {
2418 f.this = transform_recursive(f.this, transform_fn)?;
2419 Expression::Sqrt(f)
2420 }
2421 Expression::Cbrt(mut f) => {
2422 f.this = transform_recursive(f.this, transform_fn)?;
2423 Expression::Cbrt(f)
2424 }
2425 Expression::Ln(mut f) => {
2426 f.this = transform_recursive(f.this, transform_fn)?;
2427 Expression::Ln(f)
2428 }
2429 Expression::Log(mut f) => {
2430 f.this = transform_recursive(f.this, transform_fn)?;
2431 if let Some(base) = f.base {
2432 f.base = Some(transform_recursive(base, transform_fn)?);
2433 }
2434 Expression::Log(f)
2435 }
2436 Expression::Exp(mut f) => {
2437 f.this = transform_recursive(f.this, transform_fn)?;
2438 Expression::Exp(f)
2439 }
2440 Expression::Date(mut f) => {
2441 f.this = transform_recursive(f.this, transform_fn)?;
2442 Expression::Date(f)
2443 }
2444 Expression::Stddev(f) => recurse_agg!(Stddev, f),
2445 Expression::StddevSamp(f) => recurse_agg!(StddevSamp, f),
2446 Expression::Variance(f) => recurse_agg!(Variance, f),
2447
2448 // ===== BinaryFunc-based expressions =====
2449 Expression::ModFunc(mut f) => {
2450 f.this = transform_recursive(f.this, transform_fn)?;
2451 f.expression = transform_recursive(f.expression, transform_fn)?;
2452 Expression::ModFunc(f)
2453 }
2454 Expression::Power(mut f) => {
2455 f.this = transform_recursive(f.this, transform_fn)?;
2456 f.expression = transform_recursive(f.expression, transform_fn)?;
2457 Expression::Power(f)
2458 }
2459 Expression::MapFromArrays(mut f) => {
2460 f.this = transform_recursive(f.this, transform_fn)?;
2461 f.expression = transform_recursive(f.expression, transform_fn)?;
2462 Expression::MapFromArrays(f)
2463 }
2464 Expression::ElementAt(mut f) => {
2465 f.this = transform_recursive(f.this, transform_fn)?;
2466 f.expression = transform_recursive(f.expression, transform_fn)?;
2467 Expression::ElementAt(f)
2468 }
2469 Expression::MapContainsKey(mut f) => {
2470 f.this = transform_recursive(f.this, transform_fn)?;
2471 f.expression = transform_recursive(f.expression, transform_fn)?;
2472 Expression::MapContainsKey(f)
2473 }
2474 Expression::Left(mut f) => {
2475 f.this = transform_recursive(f.this, transform_fn)?;
2476 f.length = transform_recursive(f.length, transform_fn)?;
2477 Expression::Left(f)
2478 }
2479 Expression::Right(mut f) => {
2480 f.this = transform_recursive(f.this, transform_fn)?;
2481 f.length = transform_recursive(f.length, transform_fn)?;
2482 Expression::Right(f)
2483 }
2484 Expression::Repeat(mut f) => {
2485 f.this = transform_recursive(f.this, transform_fn)?;
2486 f.times = transform_recursive(f.times, transform_fn)?;
2487 Expression::Repeat(f)
2488 }
2489
2490 // ===== Complex function expressions =====
2491 Expression::Substring(mut f) => {
2492 f.this = transform_recursive(f.this, transform_fn)?;
2493 f.start = transform_recursive(f.start, transform_fn)?;
2494 if let Some(len) = f.length {
2495 f.length = Some(transform_recursive(len, transform_fn)?);
2496 }
2497 Expression::Substring(f)
2498 }
2499 Expression::Replace(mut f) => {
2500 f.this = transform_recursive(f.this, transform_fn)?;
2501 f.old = transform_recursive(f.old, transform_fn)?;
2502 f.new = transform_recursive(f.new, transform_fn)?;
2503 Expression::Replace(f)
2504 }
2505 Expression::ConcatWs(mut f) => {
2506 f.separator = transform_recursive(f.separator, transform_fn)?;
2507 f.expressions = f
2508 .expressions
2509 .into_iter()
2510 .map(|e| transform_recursive(e, transform_fn))
2511 .collect::<Result<Vec<_>>>()?;
2512 Expression::ConcatWs(f)
2513 }
2514 Expression::Trim(mut f) => {
2515 f.this = transform_recursive(f.this, transform_fn)?;
2516 if let Some(chars) = f.characters {
2517 f.characters = Some(transform_recursive(chars, transform_fn)?);
2518 }
2519 Expression::Trim(f)
2520 }
2521 Expression::Split(mut f) => {
2522 f.this = transform_recursive(f.this, transform_fn)?;
2523 f.delimiter = transform_recursive(f.delimiter, transform_fn)?;
2524 Expression::Split(f)
2525 }
2526 Expression::Lpad(mut f) => {
2527 f.this = transform_recursive(f.this, transform_fn)?;
2528 f.length = transform_recursive(f.length, transform_fn)?;
2529 if let Some(fill) = f.fill {
2530 f.fill = Some(transform_recursive(fill, transform_fn)?);
2531 }
2532 Expression::Lpad(f)
2533 }
2534 Expression::Rpad(mut f) => {
2535 f.this = transform_recursive(f.this, transform_fn)?;
2536 f.length = transform_recursive(f.length, transform_fn)?;
2537 if let Some(fill) = f.fill {
2538 f.fill = Some(transform_recursive(fill, transform_fn)?);
2539 }
2540 Expression::Rpad(f)
2541 }
2542
2543 // ===== Conditional expressions =====
2544 Expression::Case(mut c) => {
2545 if let Some(operand) = c.operand {
2546 c.operand = Some(transform_recursive(operand, transform_fn)?);
2547 }
2548 c.whens = c
2549 .whens
2550 .into_iter()
2551 .map(|(cond, then)| {
2552 let new_cond = transform_recursive(cond.clone(), transform_fn).unwrap_or(cond);
2553 let new_then = transform_recursive(then.clone(), transform_fn).unwrap_or(then);
2554 (new_cond, new_then)
2555 })
2556 .collect();
2557 if let Some(else_expr) = c.else_ {
2558 c.else_ = Some(transform_recursive(else_expr, transform_fn)?);
2559 }
2560 Expression::Case(c)
2561 }
2562 Expression::IfFunc(mut f) => {
2563 f.condition = transform_recursive(f.condition, transform_fn)?;
2564 f.true_value = transform_recursive(f.true_value, transform_fn)?;
2565 if let Some(false_val) = f.false_value {
2566 f.false_value = Some(transform_recursive(false_val, transform_fn)?);
2567 }
2568 Expression::IfFunc(f)
2569 }
2570
2571 // ===== Date/Time expressions =====
2572 Expression::DateAdd(mut f) => {
2573 f.this = transform_recursive(f.this, transform_fn)?;
2574 f.interval = transform_recursive(f.interval, transform_fn)?;
2575 Expression::DateAdd(f)
2576 }
2577 Expression::DateSub(mut f) => {
2578 f.this = transform_recursive(f.this, transform_fn)?;
2579 f.interval = transform_recursive(f.interval, transform_fn)?;
2580 Expression::DateSub(f)
2581 }
2582 Expression::DateDiff(mut f) => {
2583 f.this = transform_recursive(f.this, transform_fn)?;
2584 f.expression = transform_recursive(f.expression, transform_fn)?;
2585 Expression::DateDiff(f)
2586 }
2587 Expression::DateTrunc(mut f) => {
2588 f.this = transform_recursive(f.this, transform_fn)?;
2589 Expression::DateTrunc(f)
2590 }
2591 Expression::Extract(mut f) => {
2592 f.this = transform_recursive(f.this, transform_fn)?;
2593 Expression::Extract(f)
2594 }
2595
2596 // ===== JSON expressions =====
2597 Expression::JsonObject(mut f) => {
2598 f.pairs = f
2599 .pairs
2600 .into_iter()
2601 .map(|(k, v)| {
2602 let new_k = transform_recursive(k, transform_fn)?;
2603 let new_v = transform_recursive(v, transform_fn)?;
2604 Ok((new_k, new_v))
2605 })
2606 .collect::<Result<Vec<_>>>()?;
2607 Expression::JsonObject(f)
2608 }
2609
2610 // ===== Subquery expressions =====
2611 Expression::Subquery(mut s) => {
2612 s.this = transform_recursive(s.this, transform_fn)?;
2613 Expression::Subquery(s)
2614 }
2615 Expression::Exists(mut e) => {
2616 e.this = transform_recursive(e.this, transform_fn)?;
2617 Expression::Exists(e)
2618 }
2619 Expression::Describe(mut d) => {
2620 d.target = transform_recursive(d.target, transform_fn)?;
2621 Expression::Describe(d)
2622 }
2623
2624 // ===== Set operations =====
2625 Expression::Union(mut u) => {
2626 let left = std::mem::replace(&mut u.left, Expression::Null(Null));
2627 u.left = transform_recursive(left, transform_fn)?;
2628 let right = std::mem::replace(&mut u.right, Expression::Null(Null));
2629 u.right = transform_recursive(right, transform_fn)?;
2630 if let Some(mut order) = u.order_by.take() {
2631 order.expressions = order
2632 .expressions
2633 .into_iter()
2634 .map(|o| {
2635 let mut o = o;
2636 let original = o.this.clone();
2637 o.this = transform_recursive(o.this, transform_fn).unwrap_or(original);
2638 match transform_fn(Expression::Ordered(Box::new(o.clone()))) {
2639 Ok(Expression::Ordered(transformed)) => *transformed,
2640 Ok(_) | Err(_) => o,
2641 }
2642 })
2643 .collect();
2644 u.order_by = Some(order);
2645 }
2646 if let Some(mut with) = u.with.take() {
2647 with.ctes = with
2648 .ctes
2649 .into_iter()
2650 .map(|mut cte| {
2651 let original = cte.this.clone();
2652 cte.this = transform_recursive(cte.this, transform_fn).unwrap_or(original);
2653 cte
2654 })
2655 .collect();
2656 u.with = Some(with);
2657 }
2658 Expression::Union(u)
2659 }
2660 Expression::Intersect(mut i) => {
2661 let left = std::mem::replace(&mut i.left, Expression::Null(Null));
2662 i.left = transform_recursive(left, transform_fn)?;
2663 let right = std::mem::replace(&mut i.right, Expression::Null(Null));
2664 i.right = transform_recursive(right, transform_fn)?;
2665 if let Some(mut order) = i.order_by.take() {
2666 order.expressions = order
2667 .expressions
2668 .into_iter()
2669 .map(|o| {
2670 let mut o = o;
2671 let original = o.this.clone();
2672 o.this = transform_recursive(o.this, transform_fn).unwrap_or(original);
2673 match transform_fn(Expression::Ordered(Box::new(o.clone()))) {
2674 Ok(Expression::Ordered(transformed)) => *transformed,
2675 Ok(_) | Err(_) => o,
2676 }
2677 })
2678 .collect();
2679 i.order_by = Some(order);
2680 }
2681 if let Some(mut with) = i.with.take() {
2682 with.ctes = with
2683 .ctes
2684 .into_iter()
2685 .map(|mut cte| {
2686 let original = cte.this.clone();
2687 cte.this = transform_recursive(cte.this, transform_fn).unwrap_or(original);
2688 cte
2689 })
2690 .collect();
2691 i.with = Some(with);
2692 }
2693 Expression::Intersect(i)
2694 }
2695 Expression::Except(mut e) => {
2696 let left = std::mem::replace(&mut e.left, Expression::Null(Null));
2697 e.left = transform_recursive(left, transform_fn)?;
2698 let right = std::mem::replace(&mut e.right, Expression::Null(Null));
2699 e.right = transform_recursive(right, transform_fn)?;
2700 if let Some(mut order) = e.order_by.take() {
2701 order.expressions = order
2702 .expressions
2703 .into_iter()
2704 .map(|o| {
2705 let mut o = o;
2706 let original = o.this.clone();
2707 o.this = transform_recursive(o.this, transform_fn).unwrap_or(original);
2708 match transform_fn(Expression::Ordered(Box::new(o.clone()))) {
2709 Ok(Expression::Ordered(transformed)) => *transformed,
2710 Ok(_) | Err(_) => o,
2711 }
2712 })
2713 .collect();
2714 e.order_by = Some(order);
2715 }
2716 if let Some(mut with) = e.with.take() {
2717 with.ctes = with
2718 .ctes
2719 .into_iter()
2720 .map(|mut cte| {
2721 let original = cte.this.clone();
2722 cte.this = transform_recursive(cte.this, transform_fn).unwrap_or(original);
2723 cte
2724 })
2725 .collect();
2726 e.with = Some(with);
2727 }
2728 Expression::Except(e)
2729 }
2730
2731 // ===== DML expressions =====
2732 Expression::Insert(mut ins) => {
2733 // Transform VALUES clause expressions
2734 let mut new_values = Vec::new();
2735 for row in ins.values {
2736 let mut new_row = Vec::new();
2737 for e in row {
2738 new_row.push(transform_recursive(e, transform_fn)?);
2739 }
2740 new_values.push(new_row);
2741 }
2742 ins.values = new_values;
2743
2744 // Transform query (for INSERT ... SELECT)
2745 if let Some(query) = ins.query {
2746 ins.query = Some(transform_recursive(query, transform_fn)?);
2747 }
2748
2749 // Transform RETURNING clause
2750 let mut new_returning = Vec::new();
2751 for e in ins.returning {
2752 new_returning.push(transform_recursive(e, transform_fn)?);
2753 }
2754 ins.returning = new_returning;
2755
2756 // Transform ON CONFLICT clause
2757 if let Some(on_conflict) = ins.on_conflict {
2758 ins.on_conflict = Some(Box::new(transform_recursive(*on_conflict, transform_fn)?));
2759 }
2760
2761 Expression::Insert(ins)
2762 }
2763 Expression::Update(mut upd) => {
2764 upd.table = transform_table_ref_recursive(upd.table, transform_fn)?;
2765 upd.extra_tables = upd
2766 .extra_tables
2767 .into_iter()
2768 .map(|table| transform_table_ref_recursive(table, transform_fn))
2769 .collect::<Result<Vec<_>>>()?;
2770 upd.table_joins = upd
2771 .table_joins
2772 .into_iter()
2773 .map(|join| transform_join_recursive(join, transform_fn))
2774 .collect::<Result<Vec<_>>>()?;
2775 upd.set = upd
2776 .set
2777 .into_iter()
2778 .map(|(id, val)| {
2779 let new_val = transform_recursive(val.clone(), transform_fn).unwrap_or(val);
2780 (id, new_val)
2781 })
2782 .collect();
2783 if let Some(from_clause) = upd.from_clause.take() {
2784 upd.from_clause = Some(transform_from_recursive(from_clause, transform_fn)?);
2785 }
2786 upd.from_joins = upd
2787 .from_joins
2788 .into_iter()
2789 .map(|join| transform_join_recursive(join, transform_fn))
2790 .collect::<Result<Vec<_>>>()?;
2791 if let Some(mut where_clause) = upd.where_clause.take() {
2792 where_clause.this = transform_recursive(where_clause.this, transform_fn)?;
2793 upd.where_clause = Some(where_clause);
2794 }
2795 upd.returning = upd
2796 .returning
2797 .into_iter()
2798 .map(|expr| transform_recursive(expr, transform_fn))
2799 .collect::<Result<Vec<_>>>()?;
2800 if let Some(output) = upd.output.take() {
2801 upd.output = Some(transform_output_clause_recursive(output, transform_fn)?);
2802 }
2803 if let Some(with) = upd.with.take() {
2804 upd.with = Some(transform_with_recursive(with, transform_fn)?);
2805 }
2806 if let Some(limit) = upd.limit.take() {
2807 upd.limit = Some(transform_recursive(limit, transform_fn)?);
2808 }
2809 if let Some(order_by) = upd.order_by.take() {
2810 upd.order_by = Some(transform_order_by_recursive(order_by, transform_fn)?);
2811 }
2812 Expression::Update(upd)
2813 }
2814 Expression::Delete(mut del) => {
2815 del.table = transform_table_ref_recursive(del.table, transform_fn)?;
2816 del.using = del
2817 .using
2818 .into_iter()
2819 .map(|table| transform_table_ref_recursive(table, transform_fn))
2820 .collect::<Result<Vec<_>>>()?;
2821 if let Some(mut where_clause) = del.where_clause.take() {
2822 where_clause.this = transform_recursive(where_clause.this, transform_fn)?;
2823 del.where_clause = Some(where_clause);
2824 }
2825 if let Some(output) = del.output.take() {
2826 del.output = Some(transform_output_clause_recursive(output, transform_fn)?);
2827 }
2828 if let Some(with) = del.with.take() {
2829 del.with = Some(transform_with_recursive(with, transform_fn)?);
2830 }
2831 if let Some(limit) = del.limit.take() {
2832 del.limit = Some(transform_recursive(limit, transform_fn)?);
2833 }
2834 if let Some(order_by) = del.order_by.take() {
2835 del.order_by = Some(transform_order_by_recursive(order_by, transform_fn)?);
2836 }
2837 del.returning = del
2838 .returning
2839 .into_iter()
2840 .map(|expr| transform_recursive(expr, transform_fn))
2841 .collect::<Result<Vec<_>>>()?;
2842 del.tables = del
2843 .tables
2844 .into_iter()
2845 .map(|table| transform_table_ref_recursive(table, transform_fn))
2846 .collect::<Result<Vec<_>>>()?;
2847 del.joins = del
2848 .joins
2849 .into_iter()
2850 .map(|join| transform_join_recursive(join, transform_fn))
2851 .collect::<Result<Vec<_>>>()?;
2852 Expression::Delete(del)
2853 }
2854
2855 // ===== CTE expressions =====
2856 Expression::With(mut w) => {
2857 w.ctes = w
2858 .ctes
2859 .into_iter()
2860 .map(|mut cte| {
2861 let original = cte.this.clone();
2862 cte.this = transform_recursive(cte.this, transform_fn).unwrap_or(original);
2863 cte
2864 })
2865 .collect();
2866 Expression::With(w)
2867 }
2868 Expression::Cte(mut c) => {
2869 c.this = transform_recursive(c.this, transform_fn)?;
2870 Expression::Cte(c)
2871 }
2872
2873 // ===== Order expressions =====
2874 Expression::Ordered(mut o) => {
2875 o.this = transform_recursive(o.this, transform_fn)?;
2876 Expression::Ordered(o)
2877 }
2878
2879 // ===== Negation =====
2880 Expression::Neg(mut n) => {
2881 n.this = transform_recursive(n.this, transform_fn)?;
2882 Expression::Neg(n)
2883 }
2884
2885 // ===== Between =====
2886 Expression::Between(mut b) => {
2887 b.this = transform_recursive(b.this, transform_fn)?;
2888 b.low = transform_recursive(b.low, transform_fn)?;
2889 b.high = transform_recursive(b.high, transform_fn)?;
2890 Expression::Between(b)
2891 }
2892 Expression::IsNull(mut i) => {
2893 i.this = transform_recursive(i.this, transform_fn)?;
2894 Expression::IsNull(i)
2895 }
2896 Expression::IsTrue(mut i) => {
2897 i.this = transform_recursive(i.this, transform_fn)?;
2898 Expression::IsTrue(i)
2899 }
2900 Expression::IsFalse(mut i) => {
2901 i.this = transform_recursive(i.this, transform_fn)?;
2902 Expression::IsFalse(i)
2903 }
2904
2905 // ===== Like expressions =====
2906 Expression::Like(mut l) => {
2907 l.left = transform_recursive(l.left, transform_fn)?;
2908 l.right = transform_recursive(l.right, transform_fn)?;
2909 Expression::Like(l)
2910 }
2911 Expression::ILike(mut l) => {
2912 l.left = transform_recursive(l.left, transform_fn)?;
2913 l.right = transform_recursive(l.right, transform_fn)?;
2914 Expression::ILike(l)
2915 }
2916
2917 // ===== Additional binary ops not covered by macro =====
2918 Expression::Neq(op) => transform_binary!(Neq, *op),
2919 Expression::Lte(op) => transform_binary!(Lte, *op),
2920 Expression::Gte(op) => transform_binary!(Gte, *op),
2921 Expression::Mod(op) => transform_binary!(Mod, *op),
2922 Expression::Concat(op) => transform_binary!(Concat, *op),
2923 Expression::BitwiseAnd(op) => transform_binary!(BitwiseAnd, *op),
2924 Expression::BitwiseOr(op) => transform_binary!(BitwiseOr, *op),
2925 Expression::BitwiseXor(op) => transform_binary!(BitwiseXor, *op),
2926 Expression::Is(op) => transform_binary!(Is, *op),
2927
2928 // ===== TryCast / SafeCast =====
2929 Expression::TryCast(mut c) => {
2930 c.this = transform_recursive(c.this, transform_fn)?;
2931 c.to = transform_data_type_recursive(c.to, transform_fn)?;
2932 Expression::TryCast(c)
2933 }
2934 Expression::SafeCast(mut c) => {
2935 c.this = transform_recursive(c.this, transform_fn)?;
2936 c.to = transform_data_type_recursive(c.to, transform_fn)?;
2937 Expression::SafeCast(c)
2938 }
2939
2940 // ===== Misc =====
2941 Expression::Unnest(mut f) => {
2942 f.this = transform_recursive(f.this, transform_fn)?;
2943 f.expressions = f
2944 .expressions
2945 .into_iter()
2946 .map(|e| transform_recursive(e, transform_fn))
2947 .collect::<Result<Vec<_>>>()?;
2948 Expression::Unnest(f)
2949 }
2950 Expression::Explode(mut f) => {
2951 f.this = transform_recursive(f.this, transform_fn)?;
2952 Expression::Explode(f)
2953 }
2954 Expression::GroupConcat(mut f) => {
2955 f.this = transform_recursive(f.this, transform_fn)?;
2956 Expression::GroupConcat(f)
2957 }
2958 Expression::StringAgg(mut f) => {
2959 f.this = transform_recursive(f.this, transform_fn)?;
2960 if let Some(order_by) = f.order_by.take() {
2961 f.order_by = Some(
2962 order_by
2963 .into_iter()
2964 .map(|mut ordered| {
2965 let original = ordered.this.clone();
2966 ordered.this =
2967 transform_recursive(ordered.this, transform_fn).unwrap_or(original);
2968 match transform_fn(Expression::Ordered(Box::new(ordered.clone()))) {
2969 Ok(Expression::Ordered(transformed)) => Ok(*transformed),
2970 Ok(_) | Err(_) => Ok(ordered),
2971 }
2972 })
2973 .collect::<Result<Vec<_>>>()?,
2974 );
2975 }
2976 Expression::StringAgg(f)
2977 }
2978 Expression::ListAgg(mut f) => {
2979 f.this = transform_recursive(f.this, transform_fn)?;
2980 Expression::ListAgg(f)
2981 }
2982 Expression::ArrayAgg(mut f) => {
2983 f.this = transform_recursive(f.this, transform_fn)?;
2984 Expression::ArrayAgg(f)
2985 }
2986 Expression::ParseJson(mut f) => {
2987 f.this = transform_recursive(f.this, transform_fn)?;
2988 Expression::ParseJson(f)
2989 }
2990 Expression::ToJson(mut f) => {
2991 f.this = transform_recursive(f.this, transform_fn)?;
2992 Expression::ToJson(f)
2993 }
2994 Expression::JSONExtract(mut e) => {
2995 e.this = Box::new(transform_recursive(*e.this, transform_fn)?);
2996 e.expression = Box::new(transform_recursive(*e.expression, transform_fn)?);
2997 Expression::JSONExtract(e)
2998 }
2999 Expression::JSONExtractScalar(mut e) => {
3000 e.this = Box::new(transform_recursive(*e.this, transform_fn)?);
3001 e.expression = Box::new(transform_recursive(*e.expression, transform_fn)?);
3002 Expression::JSONExtractScalar(e)
3003 }
3004
3005 // StrToTime: recurse into this
3006 Expression::StrToTime(mut e) => {
3007 e.this = Box::new(transform_recursive(*e.this, transform_fn)?);
3008 Expression::StrToTime(e)
3009 }
3010
3011 // UnixToTime: recurse into this
3012 Expression::UnixToTime(mut e) => {
3013 e.this = Box::new(transform_recursive(*e.this, transform_fn)?);
3014 Expression::UnixToTime(e)
3015 }
3016
3017 // CreateTable: recurse into column defaults, on_update expressions, and data types
3018 Expression::CreateTable(mut ct) => {
3019 for col in &mut ct.columns {
3020 if let Some(default_expr) = col.default.take() {
3021 col.default = Some(transform_recursive(default_expr, transform_fn)?);
3022 }
3023 if let Some(on_update_expr) = col.on_update.take() {
3024 col.on_update = Some(transform_recursive(on_update_expr, transform_fn)?);
3025 }
3026 // Note: Column data type transformations (INT -> INT64 for BigQuery, etc.)
3027 // are NOT applied here because per-dialect transforms are designed for CAST/expression
3028 // contexts and may not produce correct results for DDL column definitions.
3029 // The DDL type mappings would need dedicated handling per source/target pair.
3030 }
3031 if let Some(as_select) = ct.as_select.take() {
3032 ct.as_select = Some(transform_recursive(as_select, transform_fn)?);
3033 }
3034 Expression::CreateTable(ct)
3035 }
3036
3037 // CreateView: recurse into the view body query
3038 Expression::CreateView(mut cv) => {
3039 cv.query = transform_recursive(cv.query, transform_fn)?;
3040 Expression::CreateView(cv)
3041 }
3042
3043 // CreateTask: recurse into the task body
3044 Expression::CreateTask(mut ct) => {
3045 ct.body = transform_recursive(ct.body, transform_fn)?;
3046 Expression::CreateTask(ct)
3047 }
3048
3049 // Prepare: recurse into the prepared statement body
3050 Expression::Prepare(mut prepare) => {
3051 prepare.statement = transform_recursive(prepare.statement, transform_fn)?;
3052 Expression::Prepare(prepare)
3053 }
3054
3055 // Execute: recurse into procedure/prepared name and argument values
3056 Expression::Execute(mut execute) => {
3057 execute.this = transform_recursive(execute.this, transform_fn)?;
3058 execute.arguments = execute
3059 .arguments
3060 .into_iter()
3061 .map(|argument| transform_recursive(argument, transform_fn))
3062 .collect::<Result<Vec<_>>>()?;
3063 execute.parameters = execute
3064 .parameters
3065 .into_iter()
3066 .map(|mut parameter| {
3067 parameter.value = transform_recursive(parameter.value, transform_fn)?;
3068 Ok(parameter)
3069 })
3070 .collect::<Result<Vec<_>>>()?;
3071 Expression::Execute(execute)
3072 }
3073
3074 // CreateProcedure: recurse into body expressions
3075 Expression::CreateProcedure(mut cp) => {
3076 if let Some(body) = cp.body.take() {
3077 cp.body = Some(match body {
3078 FunctionBody::Expression(expr) => {
3079 FunctionBody::Expression(transform_recursive(expr, transform_fn)?)
3080 }
3081 FunctionBody::Return(expr) => {
3082 FunctionBody::Return(transform_recursive(expr, transform_fn)?)
3083 }
3084 FunctionBody::Statements(stmts) => {
3085 let transformed_stmts = stmts
3086 .into_iter()
3087 .map(|s| transform_recursive(s, transform_fn))
3088 .collect::<Result<Vec<_>>>()?;
3089 FunctionBody::Statements(transformed_stmts)
3090 }
3091 other => other,
3092 });
3093 }
3094 Expression::CreateProcedure(cp)
3095 }
3096
3097 // CreateFunction: recurse into body expressions
3098 Expression::CreateFunction(mut cf) => {
3099 if let Some(body) = cf.body.take() {
3100 cf.body = Some(match body {
3101 FunctionBody::Expression(expr) => {
3102 FunctionBody::Expression(transform_recursive(expr, transform_fn)?)
3103 }
3104 FunctionBody::Return(expr) => {
3105 FunctionBody::Return(transform_recursive(expr, transform_fn)?)
3106 }
3107 FunctionBody::Statements(stmts) => {
3108 let transformed_stmts = stmts
3109 .into_iter()
3110 .map(|s| transform_recursive(s, transform_fn))
3111 .collect::<Result<Vec<_>>>()?;
3112 FunctionBody::Statements(transformed_stmts)
3113 }
3114 other => other,
3115 });
3116 }
3117 Expression::CreateFunction(cf)
3118 }
3119
3120 // MemberOf: recurse into left and right operands
3121 Expression::MemberOf(op) => transform_binary!(MemberOf, *op),
3122 // ArrayContainsAll (@>): recurse into left and right operands
3123 Expression::ArrayContainsAll(op) => transform_binary!(ArrayContainsAll, *op),
3124 // ArrayContainedBy (<@): recurse into left and right operands
3125 Expression::ArrayContainedBy(op) => transform_binary!(ArrayContainedBy, *op),
3126 // ArrayOverlaps (&&): recurse into left and right operands
3127 Expression::ArrayOverlaps(op) => transform_binary!(ArrayOverlaps, *op),
3128 // TsMatch (@@): recurse into left and right operands
3129 Expression::TsMatch(op) => transform_binary!(TsMatch, *op),
3130 // Adjacent (-|-): recurse into left and right operands
3131 Expression::Adjacent(op) => transform_binary!(Adjacent, *op),
3132
3133 // Table: recurse into when (HistoricalData) and changes fields
3134 Expression::Table(mut t) => {
3135 if let Some(when) = t.when.take() {
3136 let transformed =
3137 transform_recursive(Expression::HistoricalData(when), transform_fn)?;
3138 if let Expression::HistoricalData(hd) = transformed {
3139 t.when = Some(hd);
3140 }
3141 }
3142 if let Some(changes) = t.changes.take() {
3143 let transformed = transform_recursive(Expression::Changes(changes), transform_fn)?;
3144 if let Expression::Changes(c) = transformed {
3145 t.changes = Some(c);
3146 }
3147 }
3148 Expression::Table(t)
3149 }
3150
3151 // HistoricalData (Snowflake time travel): recurse into expression
3152 Expression::HistoricalData(mut hd) => {
3153 *hd.expression = transform_recursive(*hd.expression, transform_fn)?;
3154 Expression::HistoricalData(hd)
3155 }
3156
3157 // Changes (Snowflake CHANGES clause): recurse into at_before and end
3158 Expression::Changes(mut c) => {
3159 if let Some(at_before) = c.at_before.take() {
3160 c.at_before = Some(Box::new(transform_recursive(*at_before, transform_fn)?));
3161 }
3162 if let Some(end) = c.end.take() {
3163 c.end = Some(Box::new(transform_recursive(*end, transform_fn)?));
3164 }
3165 Expression::Changes(c)
3166 }
3167
3168 // TableArgument: TABLE(expr) or MODEL(expr)
3169 Expression::TableArgument(mut ta) => {
3170 ta.this = transform_recursive(ta.this, transform_fn)?;
3171 Expression::TableArgument(ta)
3172 }
3173
3174 // JoinedTable: (tbl1 JOIN tbl2 ON ...) - recurse into left and join tables
3175 Expression::JoinedTable(mut jt) => {
3176 jt.left = transform_recursive(jt.left, transform_fn)?;
3177 jt.joins = jt
3178 .joins
3179 .into_iter()
3180 .map(|mut join| {
3181 join.this = transform_recursive(join.this, transform_fn)?;
3182 if let Some(on) = join.on.take() {
3183 join.on = Some(transform_recursive(on, transform_fn)?);
3184 }
3185 match transform_fn(Expression::Join(Box::new(join)))? {
3186 Expression::Join(j) => Ok(*j),
3187 _ => Err(crate::error::Error::parse(
3188 "Join transformation returned non-join expression",
3189 0,
3190 0,
3191 0,
3192 0,
3193 )),
3194 }
3195 })
3196 .collect::<Result<Vec<_>>>()?;
3197 jt.lateral_views = jt
3198 .lateral_views
3199 .into_iter()
3200 .map(|mut lv| {
3201 lv.this = transform_recursive(lv.this, transform_fn)?;
3202 Ok(lv)
3203 })
3204 .collect::<Result<Vec<_>>>()?;
3205 Expression::JoinedTable(jt)
3206 }
3207
3208 // Lateral: LATERAL func() - recurse into the function expression
3209 Expression::Lateral(mut lat) => {
3210 *lat.this = transform_recursive(*lat.this, transform_fn)?;
3211 Expression::Lateral(lat)
3212 }
3213
3214 // WithinGroup: recurse into order_by items (for NULLS FIRST/LAST etc.)
3215 // but NOT into wg.this - the inner function is handled by StringAggConvert/GroupConcatConvert
3216 // as a unit together with the WithinGroup wrapper
3217 Expression::WithinGroup(mut wg) => {
3218 wg.order_by = wg
3219 .order_by
3220 .into_iter()
3221 .map(|mut o| {
3222 let original = o.this.clone();
3223 o.this = transform_recursive(o.this, transform_fn).unwrap_or(original);
3224 match transform_fn(Expression::Ordered(Box::new(o.clone()))) {
3225 Ok(Expression::Ordered(transformed)) => *transformed,
3226 Ok(_) | Err(_) => o,
3227 }
3228 })
3229 .collect();
3230 Expression::WithinGroup(wg)
3231 }
3232
3233 // Filter: recurse into both the aggregate and the filter condition
3234 Expression::Filter(mut f) => {
3235 f.this = Box::new(transform_recursive(*f.this, transform_fn)?);
3236 f.expression = Box::new(transform_recursive(*f.expression, transform_fn)?);
3237 Expression::Filter(f)
3238 }
3239
3240 // Aggregate functions (AggFunc-based): recurse into the aggregate argument,
3241 // filter, order_by, having_max, and limit.
3242 // Stddev, StddevSamp, Variance, and ArrayAgg are handled earlier in this match.
3243 Expression::Sum(f) => recurse_agg!(Sum, f),
3244 Expression::Avg(f) => recurse_agg!(Avg, f),
3245 Expression::Min(f) => recurse_agg!(Min, f),
3246 Expression::Max(f) => recurse_agg!(Max, f),
3247 Expression::CountIf(f) => recurse_agg!(CountIf, f),
3248 Expression::StddevPop(f) => recurse_agg!(StddevPop, f),
3249 Expression::VarPop(f) => recurse_agg!(VarPop, f),
3250 Expression::VarSamp(f) => recurse_agg!(VarSamp, f),
3251 Expression::Median(f) => recurse_agg!(Median, f),
3252 Expression::Mode(f) => recurse_agg!(Mode, f),
3253 Expression::First(f) => recurse_agg!(First, f),
3254 Expression::Last(f) => recurse_agg!(Last, f),
3255 Expression::AnyValue(f) => recurse_agg!(AnyValue, f),
3256 Expression::ApproxDistinct(f) => recurse_agg!(ApproxDistinct, f),
3257 Expression::ApproxCountDistinct(f) => recurse_agg!(ApproxCountDistinct, f),
3258 Expression::LogicalAnd(f) => recurse_agg!(LogicalAnd, f),
3259 Expression::LogicalOr(f) => recurse_agg!(LogicalOr, f),
3260 Expression::Skewness(f) => recurse_agg!(Skewness, f),
3261 Expression::ArrayConcatAgg(f) => recurse_agg!(ArrayConcatAgg, f),
3262 Expression::ArrayUniqueAgg(f) => recurse_agg!(ArrayUniqueAgg, f),
3263 Expression::BoolXorAgg(f) => recurse_agg!(BoolXorAgg, f),
3264 Expression::BitwiseOrAgg(f) => recurse_agg!(BitwiseOrAgg, f),
3265 Expression::BitwiseAndAgg(f) => recurse_agg!(BitwiseAndAgg, f),
3266 Expression::BitwiseXorAgg(f) => recurse_agg!(BitwiseXorAgg, f),
3267
3268 // Count has its own struct with an Option<Expression> `this` field
3269 Expression::Count(mut c) => {
3270 if let Some(this) = c.this.take() {
3271 c.this = Some(transform_recursive(this, transform_fn)?);
3272 }
3273 if let Some(filter) = c.filter.take() {
3274 c.filter = Some(transform_recursive(filter, transform_fn)?);
3275 }
3276 Expression::Count(c)
3277 }
3278
3279 Expression::PipeOperator(mut pipe) => {
3280 pipe.this = transform_recursive(pipe.this, transform_fn)?;
3281 pipe.expression = transform_recursive(pipe.expression, transform_fn)?;
3282 Expression::PipeOperator(pipe)
3283 }
3284
3285 // ArrayExcept/ArrayContains/ArrayDistinct: recurse into children
3286 Expression::ArrayExcept(mut f) => {
3287 f.this = transform_recursive(f.this, transform_fn)?;
3288 f.expression = transform_recursive(f.expression, transform_fn)?;
3289 Expression::ArrayExcept(f)
3290 }
3291 Expression::ArrayContains(mut f) => {
3292 f.this = transform_recursive(f.this, transform_fn)?;
3293 f.expression = transform_recursive(f.expression, transform_fn)?;
3294 Expression::ArrayContains(f)
3295 }
3296 Expression::ArrayDistinct(mut f) => {
3297 f.this = transform_recursive(f.this, transform_fn)?;
3298 Expression::ArrayDistinct(f)
3299 }
3300 Expression::ArrayPosition(mut f) => {
3301 f.this = transform_recursive(f.this, transform_fn)?;
3302 f.expression = transform_recursive(f.expression, transform_fn)?;
3303 Expression::ArrayPosition(f)
3304 }
3305
3306 // Pass through leaf nodes unchanged
3307 other => other,
3308 };
3309
3310 // Then apply the transform function
3311 transform_fn(expr)
3312}
3313
3314/// Returns the tokenizer config, generator config, and expression transform closure
3315/// for a built-in dialect type. This is the shared implementation used by both
3316/// `Dialect::get()` and custom dialect construction.
3317// ---------------------------------------------------------------------------
3318// Cached dialect configurations
3319// ---------------------------------------------------------------------------
3320
3321/// Pre-computed tokenizer + generator configs for a dialect, cached via `LazyLock`.
3322/// Transform closures are cheap (unit-struct method calls) and created fresh each time.
3323struct CachedDialectConfig {
3324 tokenizer_config: TokenizerConfig,
3325 #[cfg(feature = "generate")]
3326 generator_config: Arc<GeneratorConfig>,
3327}
3328
3329struct DialectConfigs {
3330 tokenizer_config: TokenizerConfig,
3331 #[cfg(feature = "generate")]
3332 generator_config: Arc<GeneratorConfig>,
3333 #[cfg(feature = "transpile")]
3334 transformer: Box<dyn Fn(Expression) -> Result<Expression> + Send + Sync>,
3335}
3336
3337/// Declare a per-dialect `LazyLock<CachedDialectConfig>` static.
3338macro_rules! cached_dialect {
3339 ($static_name:ident, $dialect_struct:expr, $feature:literal) => {
3340 #[cfg(feature = $feature)]
3341 static $static_name: LazyLock<CachedDialectConfig> = LazyLock::new(|| {
3342 let d = $dialect_struct;
3343 CachedDialectConfig {
3344 tokenizer_config: d.tokenizer_config(),
3345 #[cfg(feature = "generate")]
3346 generator_config: Arc::new(d.generator_config()),
3347 }
3348 });
3349 };
3350}
3351
3352static CACHED_GENERIC: LazyLock<CachedDialectConfig> = LazyLock::new(|| {
3353 let d = GenericDialect;
3354 CachedDialectConfig {
3355 tokenizer_config: d.tokenizer_config(),
3356 #[cfg(feature = "generate")]
3357 generator_config: Arc::new(d.generator_config()),
3358 }
3359});
3360
3361cached_dialect!(CACHED_POSTGRESQL, PostgresDialect, "dialect-postgresql");
3362cached_dialect!(CACHED_MYSQL, MySQLDialect, "dialect-mysql");
3363cached_dialect!(CACHED_BIGQUERY, BigQueryDialect, "dialect-bigquery");
3364cached_dialect!(CACHED_SNOWFLAKE, SnowflakeDialect, "dialect-snowflake");
3365cached_dialect!(CACHED_DUCKDB, DuckDBDialect, "dialect-duckdb");
3366cached_dialect!(CACHED_TSQL, TSQLDialect, "dialect-tsql");
3367cached_dialect!(CACHED_ORACLE, OracleDialect, "dialect-oracle");
3368cached_dialect!(CACHED_HIVE, HiveDialect, "dialect-hive");
3369cached_dialect!(CACHED_SPARK, SparkDialect, "dialect-spark");
3370cached_dialect!(CACHED_SQLITE, SQLiteDialect, "dialect-sqlite");
3371cached_dialect!(CACHED_PRESTO, PrestoDialect, "dialect-presto");
3372cached_dialect!(CACHED_TRINO, TrinoDialect, "dialect-trino");
3373cached_dialect!(CACHED_REDSHIFT, RedshiftDialect, "dialect-redshift");
3374cached_dialect!(CACHED_CLICKHOUSE, ClickHouseDialect, "dialect-clickhouse");
3375cached_dialect!(CACHED_DATABRICKS, DatabricksDialect, "dialect-databricks");
3376cached_dialect!(CACHED_ATHENA, AthenaDialect, "dialect-athena");
3377cached_dialect!(CACHED_TERADATA, TeradataDialect, "dialect-teradata");
3378cached_dialect!(CACHED_DORIS, DorisDialect, "dialect-doris");
3379cached_dialect!(CACHED_STARROCKS, StarRocksDialect, "dialect-starrocks");
3380cached_dialect!(
3381 CACHED_MATERIALIZE,
3382 MaterializeDialect,
3383 "dialect-materialize"
3384);
3385cached_dialect!(CACHED_RISINGWAVE, RisingWaveDialect, "dialect-risingwave");
3386cached_dialect!(
3387 CACHED_SINGLESTORE,
3388 SingleStoreDialect,
3389 "dialect-singlestore"
3390);
3391cached_dialect!(
3392 CACHED_COCKROACHDB,
3393 CockroachDBDialect,
3394 "dialect-cockroachdb"
3395);
3396cached_dialect!(CACHED_TIDB, TiDBDialect, "dialect-tidb");
3397cached_dialect!(CACHED_DRUID, DruidDialect, "dialect-druid");
3398cached_dialect!(CACHED_SOLR, SolrDialect, "dialect-solr");
3399cached_dialect!(CACHED_TABLEAU, TableauDialect, "dialect-tableau");
3400cached_dialect!(CACHED_DUNE, DuneDialect, "dialect-dune");
3401cached_dialect!(CACHED_FABRIC, FabricDialect, "dialect-fabric");
3402cached_dialect!(CACHED_DRILL, DrillDialect, "dialect-drill");
3403cached_dialect!(CACHED_DREMIO, DremioDialect, "dialect-dremio");
3404cached_dialect!(CACHED_EXASOL, ExasolDialect, "dialect-exasol");
3405cached_dialect!(CACHED_DATAFUSION, DataFusionDialect, "dialect-datafusion");
3406
3407fn configs_for_dialect_type(dt: DialectType) -> DialectConfigs {
3408 /// Clone configs from a cached static and pair with a fresh transform closure.
3409 macro_rules! from_cache {
3410 ($cache:expr, $dialect_struct:expr) => {{
3411 let c = &*$cache;
3412 DialectConfigs {
3413 tokenizer_config: c.tokenizer_config.clone(),
3414 #[cfg(feature = "generate")]
3415 generator_config: c.generator_config.clone(),
3416 #[cfg(feature = "transpile")]
3417 transformer: Box::new(move |e| $dialect_struct.transform_expr(e)),
3418 }
3419 }};
3420 }
3421 match dt {
3422 #[cfg(feature = "dialect-postgresql")]
3423 DialectType::PostgreSQL => from_cache!(CACHED_POSTGRESQL, PostgresDialect),
3424 #[cfg(feature = "dialect-mysql")]
3425 DialectType::MySQL => from_cache!(CACHED_MYSQL, MySQLDialect),
3426 #[cfg(feature = "dialect-bigquery")]
3427 DialectType::BigQuery => from_cache!(CACHED_BIGQUERY, BigQueryDialect),
3428 #[cfg(feature = "dialect-snowflake")]
3429 DialectType::Snowflake => from_cache!(CACHED_SNOWFLAKE, SnowflakeDialect),
3430 #[cfg(feature = "dialect-duckdb")]
3431 DialectType::DuckDB => from_cache!(CACHED_DUCKDB, DuckDBDialect),
3432 #[cfg(feature = "dialect-tsql")]
3433 DialectType::TSQL => from_cache!(CACHED_TSQL, TSQLDialect),
3434 #[cfg(feature = "dialect-oracle")]
3435 DialectType::Oracle => from_cache!(CACHED_ORACLE, OracleDialect),
3436 #[cfg(feature = "dialect-hive")]
3437 DialectType::Hive => from_cache!(CACHED_HIVE, HiveDialect),
3438 #[cfg(feature = "dialect-spark")]
3439 DialectType::Spark => from_cache!(CACHED_SPARK, SparkDialect),
3440 #[cfg(feature = "dialect-sqlite")]
3441 DialectType::SQLite => from_cache!(CACHED_SQLITE, SQLiteDialect),
3442 #[cfg(feature = "dialect-presto")]
3443 DialectType::Presto => from_cache!(CACHED_PRESTO, PrestoDialect),
3444 #[cfg(feature = "dialect-trino")]
3445 DialectType::Trino => from_cache!(CACHED_TRINO, TrinoDialect),
3446 #[cfg(feature = "dialect-redshift")]
3447 DialectType::Redshift => from_cache!(CACHED_REDSHIFT, RedshiftDialect),
3448 #[cfg(feature = "dialect-clickhouse")]
3449 DialectType::ClickHouse => from_cache!(CACHED_CLICKHOUSE, ClickHouseDialect),
3450 #[cfg(feature = "dialect-databricks")]
3451 DialectType::Databricks => from_cache!(CACHED_DATABRICKS, DatabricksDialect),
3452 #[cfg(feature = "dialect-athena")]
3453 DialectType::Athena => from_cache!(CACHED_ATHENA, AthenaDialect),
3454 #[cfg(feature = "dialect-teradata")]
3455 DialectType::Teradata => from_cache!(CACHED_TERADATA, TeradataDialect),
3456 #[cfg(feature = "dialect-doris")]
3457 DialectType::Doris => from_cache!(CACHED_DORIS, DorisDialect),
3458 #[cfg(feature = "dialect-starrocks")]
3459 DialectType::StarRocks => from_cache!(CACHED_STARROCKS, StarRocksDialect),
3460 #[cfg(feature = "dialect-materialize")]
3461 DialectType::Materialize => from_cache!(CACHED_MATERIALIZE, MaterializeDialect),
3462 #[cfg(feature = "dialect-risingwave")]
3463 DialectType::RisingWave => from_cache!(CACHED_RISINGWAVE, RisingWaveDialect),
3464 #[cfg(feature = "dialect-singlestore")]
3465 DialectType::SingleStore => from_cache!(CACHED_SINGLESTORE, SingleStoreDialect),
3466 #[cfg(feature = "dialect-cockroachdb")]
3467 DialectType::CockroachDB => from_cache!(CACHED_COCKROACHDB, CockroachDBDialect),
3468 #[cfg(feature = "dialect-tidb")]
3469 DialectType::TiDB => from_cache!(CACHED_TIDB, TiDBDialect),
3470 #[cfg(feature = "dialect-druid")]
3471 DialectType::Druid => from_cache!(CACHED_DRUID, DruidDialect),
3472 #[cfg(feature = "dialect-solr")]
3473 DialectType::Solr => from_cache!(CACHED_SOLR, SolrDialect),
3474 #[cfg(feature = "dialect-tableau")]
3475 DialectType::Tableau => from_cache!(CACHED_TABLEAU, TableauDialect),
3476 #[cfg(feature = "dialect-dune")]
3477 DialectType::Dune => from_cache!(CACHED_DUNE, DuneDialect),
3478 #[cfg(feature = "dialect-fabric")]
3479 DialectType::Fabric => from_cache!(CACHED_FABRIC, FabricDialect),
3480 #[cfg(feature = "dialect-drill")]
3481 DialectType::Drill => from_cache!(CACHED_DRILL, DrillDialect),
3482 #[cfg(feature = "dialect-dremio")]
3483 DialectType::Dremio => from_cache!(CACHED_DREMIO, DremioDialect),
3484 #[cfg(feature = "dialect-exasol")]
3485 DialectType::Exasol => from_cache!(CACHED_EXASOL, ExasolDialect),
3486 #[cfg(feature = "dialect-datafusion")]
3487 DialectType::DataFusion => from_cache!(CACHED_DATAFUSION, DataFusionDialect),
3488 _ => from_cache!(CACHED_GENERIC, GenericDialect),
3489 }
3490}
3491
3492// ---------------------------------------------------------------------------
3493// Custom dialect registry
3494// ---------------------------------------------------------------------------
3495
3496static CUSTOM_DIALECT_REGISTRY: LazyLock<RwLock<HashMap<String, Arc<CustomDialectConfig>>>> =
3497 LazyLock::new(|| RwLock::new(HashMap::new()));
3498
3499struct CustomDialectConfig {
3500 name: String,
3501 base_dialect: DialectType,
3502 tokenizer_config: TokenizerConfig,
3503 #[cfg(feature = "generate")]
3504 generator_config: GeneratorConfig,
3505 #[cfg(feature = "transpile")]
3506 transform: Option<Arc<dyn Fn(Expression) -> Result<Expression> + Send + Sync>>,
3507 #[cfg(feature = "transpile")]
3508 preprocess: Option<Arc<dyn Fn(Expression) -> Result<Expression> + Send + Sync>>,
3509}
3510
3511/// Fluent builder for creating and registering custom SQL dialects.
3512///
3513/// A custom dialect is based on an existing built-in dialect and allows selective
3514/// overrides of tokenizer configuration, generator configuration, and expression
3515/// transforms.
3516///
3517/// # Example
3518///
3519/// ```rust,ignore
3520/// use polyglot_sql::dialects::{CustomDialectBuilder, DialectType, Dialect};
3521/// use polyglot_sql::generator::NormalizeFunctions;
3522///
3523/// CustomDialectBuilder::new("my_postgres")
3524/// .based_on(DialectType::PostgreSQL)
3525/// .generator_config_modifier(|gc| {
3526/// gc.normalize_functions = NormalizeFunctions::Lower;
3527/// })
3528/// .register()
3529/// .unwrap();
3530///
3531/// let d = Dialect::get_by_name("my_postgres").unwrap();
3532/// let exprs = d.parse("SELECT COUNT(*)").unwrap();
3533/// let sql = d.generate(&exprs[0]).unwrap();
3534/// assert_eq!(sql, "select count(*)");
3535///
3536/// polyglot_sql::unregister_custom_dialect("my_postgres");
3537/// ```
3538pub struct CustomDialectBuilder {
3539 name: String,
3540 base_dialect: DialectType,
3541 tokenizer_modifier: Option<Box<dyn FnOnce(&mut TokenizerConfig)>>,
3542 #[cfg(feature = "generate")]
3543 generator_modifier: Option<Box<dyn FnOnce(&mut GeneratorConfig)>>,
3544 #[cfg(feature = "transpile")]
3545 transform: Option<Arc<dyn Fn(Expression) -> Result<Expression> + Send + Sync>>,
3546 #[cfg(feature = "transpile")]
3547 preprocess: Option<Arc<dyn Fn(Expression) -> Result<Expression> + Send + Sync>>,
3548}
3549
3550impl CustomDialectBuilder {
3551 /// Create a new builder with the given name. Defaults to `Generic` as the base dialect.
3552 pub fn new(name: impl Into<String>) -> Self {
3553 Self {
3554 name: name.into(),
3555 base_dialect: DialectType::Generic,
3556 tokenizer_modifier: None,
3557 #[cfg(feature = "generate")]
3558 generator_modifier: None,
3559 #[cfg(feature = "transpile")]
3560 transform: None,
3561 #[cfg(feature = "transpile")]
3562 preprocess: None,
3563 }
3564 }
3565
3566 /// Set the base built-in dialect to inherit configuration from.
3567 pub fn based_on(mut self, dialect: DialectType) -> Self {
3568 self.base_dialect = dialect;
3569 self
3570 }
3571
3572 /// Provide a closure that modifies the tokenizer configuration inherited from the base dialect.
3573 pub fn tokenizer_config_modifier<F>(mut self, f: F) -> Self
3574 where
3575 F: FnOnce(&mut TokenizerConfig) + 'static,
3576 {
3577 self.tokenizer_modifier = Some(Box::new(f));
3578 self
3579 }
3580
3581 /// Provide a closure that modifies the generator configuration inherited from the base dialect.
3582 #[cfg(feature = "generate")]
3583 pub fn generator_config_modifier<F>(mut self, f: F) -> Self
3584 where
3585 F: FnOnce(&mut GeneratorConfig) + 'static,
3586 {
3587 self.generator_modifier = Some(Box::new(f));
3588 self
3589 }
3590
3591 /// Set a custom per-node expression transform function.
3592 ///
3593 /// This replaces the base dialect's transform. It is called on every expression
3594 /// node during the recursive transform pass.
3595 #[cfg(feature = "transpile")]
3596 pub fn transform_fn<F>(mut self, f: F) -> Self
3597 where
3598 F: Fn(Expression) -> Result<Expression> + Send + Sync + 'static,
3599 {
3600 self.transform = Some(Arc::new(f));
3601 self
3602 }
3603
3604 /// Set a custom whole-tree preprocessing function.
3605 ///
3606 /// This replaces the base dialect's built-in preprocessing. It is called once
3607 /// on the entire expression tree before the recursive per-node transform.
3608 #[cfg(feature = "transpile")]
3609 pub fn preprocess_fn<F>(mut self, f: F) -> Self
3610 where
3611 F: Fn(Expression) -> Result<Expression> + Send + Sync + 'static,
3612 {
3613 self.preprocess = Some(Arc::new(f));
3614 self
3615 }
3616
3617 /// Build the custom dialect configuration and register it in the global registry.
3618 ///
3619 /// Returns an error if:
3620 /// - The name collides with a built-in dialect name
3621 /// - A custom dialect with the same name is already registered
3622 pub fn register(self) -> Result<()> {
3623 // Reject names that collide with built-in dialects
3624 if DialectType::from_str(&self.name).is_ok() {
3625 return Err(crate::error::Error::parse(
3626 format!(
3627 "Cannot register custom dialect '{}': name collides with built-in dialect",
3628 self.name
3629 ),
3630 0,
3631 0,
3632 0,
3633 0,
3634 ));
3635 }
3636
3637 // Get base configs
3638 let base_configs = configs_for_dialect_type(self.base_dialect);
3639 let mut tok_config = base_configs.tokenizer_config;
3640 #[cfg(feature = "generate")]
3641 let mut gen_config = (*base_configs.generator_config).clone();
3642
3643 // Apply modifiers
3644 if let Some(tok_mod) = self.tokenizer_modifier {
3645 tok_mod(&mut tok_config);
3646 }
3647 #[cfg(feature = "generate")]
3648 if let Some(gen_mod) = self.generator_modifier {
3649 gen_mod(&mut gen_config);
3650 }
3651
3652 let config = CustomDialectConfig {
3653 name: self.name.clone(),
3654 base_dialect: self.base_dialect,
3655 tokenizer_config: tok_config,
3656 #[cfg(feature = "generate")]
3657 generator_config: gen_config,
3658 #[cfg(feature = "transpile")]
3659 transform: self.transform,
3660 #[cfg(feature = "transpile")]
3661 preprocess: self.preprocess,
3662 };
3663
3664 register_custom_dialect(config)
3665 }
3666}
3667
3668use std::str::FromStr;
3669
3670fn register_custom_dialect(config: CustomDialectConfig) -> Result<()> {
3671 let mut registry = CUSTOM_DIALECT_REGISTRY.write().map_err(|e| {
3672 crate::error::Error::parse(format!("Registry lock poisoned: {}", e), 0, 0, 0, 0)
3673 })?;
3674
3675 if registry.contains_key(&config.name) {
3676 return Err(crate::error::Error::parse(
3677 format!("Custom dialect '{}' is already registered", config.name),
3678 0,
3679 0,
3680 0,
3681 0,
3682 ));
3683 }
3684
3685 registry.insert(config.name.clone(), Arc::new(config));
3686 Ok(())
3687}
3688
3689/// Remove a custom dialect from the global registry.
3690///
3691/// Returns `true` if a dialect with that name was found and removed,
3692/// `false` if no such custom dialect existed.
3693pub fn unregister_custom_dialect(name: &str) -> bool {
3694 if let Ok(mut registry) = CUSTOM_DIALECT_REGISTRY.write() {
3695 registry.remove(name).is_some()
3696 } else {
3697 false
3698 }
3699}
3700
3701fn get_custom_dialect_config(name: &str) -> Option<Arc<CustomDialectConfig>> {
3702 CUSTOM_DIALECT_REGISTRY
3703 .read()
3704 .ok()
3705 .and_then(|registry| registry.get(name).cloned())
3706}
3707
3708/// Main entry point for dialect-specific SQL operations.
3709///
3710/// A `Dialect` bundles together a tokenizer, generator configuration, and expression
3711/// transformer for a specific SQL database engine. It is the high-level API through
3712/// which callers parse, generate, transform, and transpile SQL.
3713///
3714/// # Usage
3715///
3716/// ```rust,ignore
3717/// use polyglot_sql::dialects::{Dialect, DialectType};
3718///
3719/// // Parse PostgreSQL SQL into an AST
3720/// let pg = Dialect::get(DialectType::PostgreSQL);
3721/// let exprs = pg.parse("SELECT id, name FROM users WHERE active")?;
3722///
3723/// // Transpile from PostgreSQL to BigQuery
3724/// let results = pg.transpile("SELECT NOW()", DialectType::BigQuery)?;
3725/// assert_eq!(results[0], "SELECT CURRENT_TIMESTAMP()");
3726/// ```
3727///
3728/// Obtain an instance via [`Dialect::get`] or [`Dialect::get_by_name`].
3729/// The struct is `Send + Sync` safe so it can be shared across threads.
3730pub struct Dialect {
3731 dialect_type: DialectType,
3732 tokenizer: Tokenizer,
3733 #[cfg(feature = "generate")]
3734 generator_config: Arc<GeneratorConfig>,
3735 #[cfg(feature = "transpile")]
3736 transformer: Box<dyn Fn(Expression) -> Result<Expression> + Send + Sync>,
3737 /// Optional function to get expression-specific generator config (for hybrid dialects like Athena).
3738 #[cfg(feature = "generate")]
3739 generator_config_for_expr: Option<Box<dyn Fn(&Expression) -> GeneratorConfig + Send + Sync>>,
3740 /// Optional custom preprocessing function (overrides built-in preprocess for custom dialects).
3741 #[cfg(feature = "transpile")]
3742 custom_preprocess: Option<Box<dyn Fn(Expression) -> Result<Expression> + Send + Sync>>,
3743}
3744
3745/// Options for [`Dialect::transpile_with`].
3746///
3747/// Use [`TranspileOptions::default`] for defaults, then tweak the fields you need.
3748/// The struct is marked `#[non_exhaustive]` so new fields can be added without
3749/// breaking the API.
3750///
3751/// The struct derives `Serialize`/`Deserialize` using camelCase field names so
3752/// it can be round-tripped over JSON bridges (C FFI, WASM) without mapping.
3753#[cfg(feature = "transpile")]
3754#[derive(Debug, Clone, Serialize, Deserialize)]
3755#[serde(rename_all = "camelCase", default)]
3756#[non_exhaustive]
3757pub struct TranspileOptions {
3758 /// Whether to pretty-print the output SQL.
3759 pub pretty: bool,
3760 /// How unsupported target-dialect constructs should be handled.
3761 ///
3762 /// The default is [`UnsupportedLevel::Warn`], which preserves the current
3763 /// compatibility behavior and continues transpilation.
3764 pub unsupported_level: UnsupportedLevel,
3765 /// Maximum number of unsupported diagnostics to include in raised errors.
3766 pub max_unsupported: usize,
3767 /// Complexity guard limits used while parsing, transforming, and generating.
3768 pub complexity_guard: ComplexityGuardOptions,
3769}
3770
3771#[cfg(feature = "transpile")]
3772impl Default for TranspileOptions {
3773 fn default() -> Self {
3774 Self {
3775 pretty: false,
3776 unsupported_level: UnsupportedLevel::Warn,
3777 max_unsupported: 3,
3778 complexity_guard: ComplexityGuardOptions::default(),
3779 }
3780 }
3781}
3782
3783#[cfg(feature = "transpile")]
3784impl TranspileOptions {
3785 /// Construct options with pretty-printing enabled.
3786 pub fn pretty() -> Self {
3787 Self {
3788 pretty: true,
3789 ..Default::default()
3790 }
3791 }
3792
3793 /// Construct options that raise when known unsupported constructs remain.
3794 pub fn strict() -> Self {
3795 Self {
3796 unsupported_level: UnsupportedLevel::Raise,
3797 ..Default::default()
3798 }
3799 }
3800
3801 /// Set how unsupported target-dialect constructs should be handled.
3802 pub fn with_unsupported_level(mut self, level: UnsupportedLevel) -> Self {
3803 self.unsupported_level = level;
3804 self
3805 }
3806
3807 /// Set the maximum number of unsupported diagnostics to include in raised errors.
3808 pub fn with_max_unsupported(mut self, max: usize) -> Self {
3809 self.max_unsupported = max;
3810 self
3811 }
3812
3813 /// Set complexity guard limits for parse/transpile/generate recursion-heavy paths.
3814 pub fn with_complexity_guard(mut self, guard: ComplexityGuardOptions) -> Self {
3815 self.complexity_guard = guard;
3816 self
3817 }
3818}
3819
3820/// A value that can be used as the target dialect in [`Dialect::transpile`] /
3821/// [`Dialect::transpile_with`].
3822///
3823/// Implemented for [`DialectType`] (built-in dialect enum) and `&Dialect` (any
3824/// dialect handle, including custom ones). End users do not normally need to
3825/// implement this trait themselves.
3826#[cfg(feature = "transpile")]
3827pub trait TranspileTarget {
3828 /// Invoke `f` with a reference to the resolved target dialect.
3829 fn with_dialect<R>(self, f: impl FnOnce(&Dialect) -> R) -> R;
3830}
3831
3832#[cfg(feature = "transpile")]
3833impl TranspileTarget for DialectType {
3834 fn with_dialect<R>(self, f: impl FnOnce(&Dialect) -> R) -> R {
3835 f(&Dialect::get(self))
3836 }
3837}
3838
3839#[cfg(feature = "transpile")]
3840impl TranspileTarget for &Dialect {
3841 fn with_dialect<R>(self, f: impl FnOnce(&Dialect) -> R) -> R {
3842 f(self)
3843 }
3844}
3845
3846impl Dialect {
3847 /// Creates a fully configured [`Dialect`] instance for the given [`DialectType`].
3848 ///
3849 /// This is the primary constructor. It initializes the tokenizer, generator config,
3850 /// and expression transformer based on the dialect's [`DialectImpl`] implementation.
3851 /// For hybrid dialects like Athena, it also sets up expression-specific generator
3852 /// config routing.
3853 pub fn get(dialect_type: DialectType) -> Self {
3854 let configs = configs_for_dialect_type(dialect_type);
3855 let tokenizer_config = configs.tokenizer_config;
3856 #[cfg(feature = "generate")]
3857 let generator_config = configs.generator_config;
3858 #[cfg(feature = "transpile")]
3859 let transformer = configs.transformer;
3860
3861 // Set up expression-specific generator config for hybrid dialects
3862 #[cfg(feature = "generate")]
3863 let generator_config_for_expr: Option<
3864 Box<dyn Fn(&Expression) -> GeneratorConfig + Send + Sync>,
3865 > = match dialect_type {
3866 #[cfg(feature = "dialect-athena")]
3867 DialectType::Athena => Some(Box::new(|expr| {
3868 AthenaDialect.generator_config_for_expr(expr)
3869 })),
3870 _ => None,
3871 };
3872
3873 Self {
3874 dialect_type,
3875 tokenizer: Tokenizer::new(tokenizer_config),
3876 #[cfg(feature = "generate")]
3877 generator_config,
3878 #[cfg(feature = "transpile")]
3879 transformer,
3880 #[cfg(feature = "generate")]
3881 generator_config_for_expr,
3882 #[cfg(feature = "transpile")]
3883 custom_preprocess: None,
3884 }
3885 }
3886
3887 /// Look up a dialect by string name.
3888 ///
3889 /// Checks built-in dialect names first (via [`DialectType::from_str`]), then
3890 /// falls back to the custom dialect registry. Returns `None` if no dialect
3891 /// with the given name exists.
3892 pub fn get_by_name(name: &str) -> Option<Self> {
3893 // Try built-in first
3894 if let Ok(dt) = DialectType::from_str(name) {
3895 return Some(Self::get(dt));
3896 }
3897
3898 // Try custom registry
3899 let config = get_custom_dialect_config(name)?;
3900 Some(Self::from_custom_config(&config))
3901 }
3902
3903 /// Construct a `Dialect` from a custom dialect configuration.
3904 fn from_custom_config(config: &CustomDialectConfig) -> Self {
3905 // Build the transformer: use custom if provided, else use base dialect's
3906 #[cfg(feature = "transpile")]
3907 let transformer: Box<dyn Fn(Expression) -> Result<Expression> + Send + Sync> =
3908 if let Some(ref custom_transform) = config.transform {
3909 let t = Arc::clone(custom_transform);
3910 Box::new(move |e| t(e))
3911 } else {
3912 configs_for_dialect_type(config.base_dialect).transformer
3913 };
3914
3915 // Build the custom preprocess: use custom if provided
3916 #[cfg(feature = "transpile")]
3917 let custom_preprocess: Option<
3918 Box<dyn Fn(Expression) -> Result<Expression> + Send + Sync>,
3919 > = config.preprocess.as_ref().map(|p| {
3920 let p = Arc::clone(p);
3921 Box::new(move |e: Expression| p(e))
3922 as Box<dyn Fn(Expression) -> Result<Expression> + Send + Sync>
3923 });
3924
3925 Self {
3926 dialect_type: config.base_dialect,
3927 tokenizer: Tokenizer::new(config.tokenizer_config.clone()),
3928 #[cfg(feature = "generate")]
3929 generator_config: Arc::new(config.generator_config.clone()),
3930 #[cfg(feature = "transpile")]
3931 transformer,
3932 #[cfg(feature = "generate")]
3933 generator_config_for_expr: None,
3934 #[cfg(feature = "transpile")]
3935 custom_preprocess,
3936 }
3937 }
3938
3939 /// Get the dialect type
3940 pub fn dialect_type(&self) -> DialectType {
3941 self.dialect_type
3942 }
3943
3944 /// Get the generator configuration
3945 #[cfg(feature = "generate")]
3946 pub fn generator_config(&self) -> &GeneratorConfig {
3947 &self.generator_config
3948 }
3949
3950 /// Parses a SQL string into a list of [`Expression`] AST nodes.
3951 ///
3952 /// The input may contain multiple semicolon-separated statements; each one
3953 /// produces a separate element in the returned vector. Tokenization uses
3954 /// this dialect's configured tokenizer, and parsing uses the dialect-aware parser.
3955 pub fn parse(&self, sql: &str) -> Result<Vec<Expression>> {
3956 self.parse_with_guard(sql, self.default_complexity_guard())
3957 }
3958
3959 fn parse_with_guard(
3960 &self,
3961 sql: &str,
3962 complexity_guard: ComplexityGuardOptions,
3963 ) -> Result<Vec<Expression>> {
3964 enforce_input(sql, &complexity_guard)?;
3965 let tokens = self.tokenizer.tokenize(sql)?;
3966 let config = crate::parser::ParserConfig {
3967 dialect: Some(self.dialect_type),
3968 complexity_guard,
3969 ..Default::default()
3970 };
3971 let mut parser = Parser::with_source(tokens, config, sql.to_string());
3972 parser.parse()
3973 }
3974
3975 fn default_complexity_guard(&self) -> ComplexityGuardOptions {
3976 let mut guard = ComplexityGuardOptions::default();
3977 if matches!(self.dialect_type, DialectType::ClickHouse) {
3978 guard.max_ast_depth = Some(4_096);
3979 guard.max_function_call_depth = Some(512);
3980 }
3981 guard
3982 }
3983
3984 #[cfg(feature = "transpile")]
3985 fn default_transpile_complexity_guard(
3986 &self,
3987 target_dialect: &Dialect,
3988 guard: ComplexityGuardOptions,
3989 ) -> ComplexityGuardOptions {
3990 if guard != ComplexityGuardOptions::default() {
3991 return guard;
3992 }
3993
3994 if matches!(self.dialect_type, DialectType::ClickHouse)
3995 || matches!(target_dialect.dialect_type, DialectType::ClickHouse)
3996 {
3997 let mut guard = guard;
3998 guard.max_ast_depth = Some(4_096);
3999 guard.max_function_call_depth = Some(512);
4000 guard
4001 } else {
4002 guard
4003 }
4004 }
4005
4006 /// Parse a standalone SQL data type using this dialect's tokenizer and parser.
4007 ///
4008 /// This accepts type strings such as `DECIMAL(10, 2)`, `INT[]`, or
4009 /// `STRUCT(a INT, b VARCHAR)` without requiring a surrounding statement.
4010 pub fn parse_data_type(&self, sql: &str) -> Result<DataType> {
4011 let complexity_guard = self.default_complexity_guard();
4012 enforce_input(sql, &complexity_guard)?;
4013 let tokens = self.tokenizer.tokenize(sql)?;
4014 let config = crate::parser::ParserConfig {
4015 dialect: Some(self.dialect_type),
4016 complexity_guard,
4017 ..Default::default()
4018 };
4019 let mut parser = Parser::with_source(tokens, config, sql.to_string());
4020 parser.parse_standalone_data_type()
4021 }
4022
4023 /// Tokenize SQL using this dialect's tokenizer configuration.
4024 pub fn tokenize(&self, sql: &str) -> Result<Vec<Token>> {
4025 self.tokenizer.tokenize(sql)
4026 }
4027
4028 /// Get the generator config for a specific expression (supports hybrid dialects).
4029 /// Returns an owned `GeneratorConfig` suitable for mutation before generation.
4030 #[cfg(feature = "generate")]
4031 fn get_config_for_expr(&self, expr: &Expression) -> GeneratorConfig {
4032 if let Some(ref config_fn) = self.generator_config_for_expr {
4033 config_fn(expr)
4034 } else {
4035 (*self.generator_config).clone()
4036 }
4037 }
4038
4039 /// Generates a SQL string from an [`Expression`] AST node.
4040 ///
4041 /// The output uses this dialect's generator configuration for identifier quoting,
4042 /// keyword casing, function name normalization, and syntax style. The result is
4043 /// a single-line (non-pretty) SQL string.
4044 #[cfg(feature = "generate")]
4045 pub fn generate(&self, expr: &Expression) -> Result<String> {
4046 // Fast path: when no per-expression config override, share the Arc cheaply.
4047 if self.generator_config_for_expr.is_none() {
4048 let mut generator = Generator::with_arc_config(self.generator_config.clone());
4049 return generator.generate(expr);
4050 }
4051 let config = self.get_config_for_expr(expr);
4052 let mut generator = Generator::with_config(config);
4053 generator.generate(expr)
4054 }
4055
4056 /// Generate SQL from an expression with pretty printing enabled
4057 #[cfg(feature = "generate")]
4058 pub fn generate_pretty(&self, expr: &Expression) -> Result<String> {
4059 let mut config = self.get_config_for_expr(expr);
4060 config.pretty = true;
4061 let mut generator = Generator::with_config(config);
4062 generator.generate(expr)
4063 }
4064
4065 /// Generate SQL from an expression with source dialect info (for transpilation)
4066 #[cfg(feature = "generate")]
4067 pub fn generate_with_source(&self, expr: &Expression, source: DialectType) -> Result<String> {
4068 let mut config = self.get_config_for_expr(expr);
4069 config.source_dialect = Some(source);
4070 let mut generator = Generator::with_config(config);
4071 generator.generate(expr)
4072 }
4073
4074 /// Generate SQL from an expression with pretty printing and source dialect info
4075 #[cfg(feature = "generate")]
4076 pub fn generate_pretty_with_source(
4077 &self,
4078 expr: &Expression,
4079 source: DialectType,
4080 ) -> Result<String> {
4081 let mut config = self.get_config_for_expr(expr);
4082 config.pretty = true;
4083 config.source_dialect = Some(source);
4084 let mut generator = Generator::with_config(config);
4085 generator.generate(expr)
4086 }
4087
4088 /// Generate SQL from an expression with source dialect and transpile options.
4089 #[cfg(all(feature = "generate", feature = "transpile"))]
4090 fn generate_with_transpile_options(
4091 &self,
4092 expr: &Expression,
4093 source: DialectType,
4094 opts: &TranspileOptions,
4095 ) -> Result<String> {
4096 let mut config = self.get_config_for_expr(expr);
4097 config.source_dialect = Some(source);
4098 config.pretty = opts.pretty;
4099 config.unsupported_level = opts.unsupported_level;
4100 config.max_unsupported = opts.max_unsupported.max(1);
4101 config.complexity_guard = opts.complexity_guard;
4102 let mut generator = Generator::with_config(config);
4103 generator.generate(expr)
4104 }
4105
4106 /// Generate SQL from an expression with forced identifier quoting (identify=True)
4107 #[cfg(feature = "generate")]
4108 pub fn generate_with_identify(&self, expr: &Expression) -> Result<String> {
4109 let mut config = self.get_config_for_expr(expr);
4110 config.always_quote_identifiers = true;
4111 let mut generator = Generator::with_config(config);
4112 generator.generate(expr)
4113 }
4114
4115 /// Generate SQL from an expression with pretty printing and forced identifier quoting
4116 #[cfg(feature = "generate")]
4117 pub fn generate_pretty_with_identify(&self, expr: &Expression) -> Result<String> {
4118 let mut config = (*self.generator_config).clone();
4119 config.pretty = true;
4120 config.always_quote_identifiers = true;
4121 let mut generator = Generator::with_config(config);
4122 generator.generate(expr)
4123 }
4124
4125 /// Generate SQL from an expression with caller-specified config overrides
4126 #[cfg(feature = "generate")]
4127 pub fn generate_with_overrides(
4128 &self,
4129 expr: &Expression,
4130 overrides: impl FnOnce(&mut GeneratorConfig),
4131 ) -> Result<String> {
4132 let mut config = self.get_config_for_expr(expr);
4133 overrides(&mut config);
4134 let mut generator = Generator::with_config(config);
4135 generator.generate(expr)
4136 }
4137
4138 /// Transforms an expression tree to conform to this dialect's syntax and semantics.
4139 ///
4140 /// The transformation proceeds in two phases:
4141 /// 1. **Preprocessing** -- whole-tree structural rewrites such as eliminating QUALIFY,
4142 /// ensuring boolean predicates, or converting DISTINCT ON to a window-function pattern.
4143 /// 2. **Recursive per-node transform** -- a bottom-up pass via [`transform_recursive`]
4144 /// that applies this dialect's [`DialectImpl::transform_expr`] to every node.
4145 ///
4146 /// This method is used both during transpilation (to rewrite an AST for a target dialect)
4147 /// and for identity transforms (normalizing SQL within the same dialect).
4148 #[cfg(feature = "transpile")]
4149 pub fn transform(&self, expr: Expression) -> Result<Expression> {
4150 self.transform_with_guard(expr, self.default_complexity_guard())
4151 }
4152
4153 #[cfg(feature = "transpile")]
4154 fn transform_with_guard(
4155 &self,
4156 expr: Expression,
4157 complexity_guard: ComplexityGuardOptions,
4158 ) -> Result<Expression> {
4159 enforce_generate_ast(&expr, &complexity_guard)?;
4160 // Apply preprocessing transforms based on dialect
4161 let preprocessed = self.preprocess(expr)?;
4162 // Then apply recursive transformation
4163 transform_recursive(preprocessed, &self.transformer)
4164 }
4165
4166 /// Apply dialect-specific preprocessing transforms
4167 #[cfg(feature = "transpile")]
4168 fn preprocess(&self, expr: Expression) -> Result<Expression> {
4169 // If a custom preprocess function is set, use it instead of the built-in logic
4170 if let Some(ref custom_preprocess) = self.custom_preprocess {
4171 return custom_preprocess(expr);
4172 }
4173
4174 #[cfg(any(
4175 feature = "dialect-mysql",
4176 feature = "dialect-postgresql",
4177 feature = "dialect-bigquery",
4178 feature = "dialect-snowflake",
4179 feature = "dialect-tsql",
4180 feature = "dialect-spark",
4181 feature = "dialect-databricks",
4182 feature = "dialect-hive",
4183 feature = "dialect-sqlite",
4184 feature = "dialect-trino",
4185 feature = "dialect-presto",
4186 feature = "dialect-duckdb",
4187 feature = "dialect-redshift",
4188 feature = "dialect-starrocks",
4189 feature = "dialect-oracle",
4190 feature = "dialect-clickhouse",
4191 feature = "dialect-fabric",
4192 ))]
4193 use crate::transforms;
4194
4195 match self.dialect_type {
4196 // MySQL doesn't support QUALIFY, DISTINCT ON, FULL OUTER JOIN
4197 // MySQL doesn't natively support GENERATE_DATE_ARRAY (expand to recursive CTE)
4198 #[cfg(feature = "dialect-mysql")]
4199 DialectType::MySQL => {
4200 let expr = transforms::eliminate_qualify(expr)?;
4201 let expr = transforms::eliminate_full_outer_join(expr)?;
4202 let expr = transforms::eliminate_semi_and_anti_joins(expr)?;
4203 let expr = transforms::unnest_generate_date_array_using_recursive_cte(expr)?;
4204 Ok(expr)
4205 }
4206 // PostgreSQL doesn't support QUALIFY
4207 // PostgreSQL: UNNEST(GENERATE_SERIES) -> subquery wrapping
4208 // PostgreSQL: Normalize SET ... TO to SET ... = in CREATE FUNCTION
4209 #[cfg(feature = "dialect-postgresql")]
4210 DialectType::PostgreSQL => {
4211 let expr = transforms::eliminate_qualify(expr)?;
4212 let expr = transforms::eliminate_semi_and_anti_joins(expr)?;
4213 let expr = transforms::unwrap_unnest_generate_series_for_postgres(expr)?;
4214 // Normalize SET ... TO to SET ... = in CREATE FUNCTION
4215 // Only normalize when sqlglot would fully parse (no body) —
4216 // sqlglot falls back to Command for complex function bodies,
4217 // preserving the original text including TO.
4218 let expr = if let Expression::CreateFunction(mut cf) = expr {
4219 if cf.body.is_none() {
4220 for opt in &mut cf.set_options {
4221 if let crate::expressions::FunctionSetValue::Value { use_to, .. } =
4222 &mut opt.value
4223 {
4224 *use_to = false;
4225 }
4226 }
4227 }
4228 Expression::CreateFunction(cf)
4229 } else {
4230 expr
4231 };
4232 Ok(expr)
4233 }
4234 // BigQuery doesn't support DISTINCT ON or CTE column aliases
4235 #[cfg(feature = "dialect-bigquery")]
4236 DialectType::BigQuery => {
4237 let expr = transforms::eliminate_semi_and_anti_joins(expr)?;
4238 let expr = transforms::pushdown_cte_column_names(expr)?;
4239 let expr = transforms::explode_projection_to_unnest(expr, DialectType::BigQuery)?;
4240 Ok(expr)
4241 }
4242 // Snowflake
4243 #[cfg(feature = "dialect-snowflake")]
4244 DialectType::Snowflake => {
4245 let expr = transforms::eliminate_semi_and_anti_joins(expr)?;
4246 let expr = transforms::eliminate_window_clause(expr)?;
4247 let expr = transforms::snowflake_flatten_projection_to_unnest(expr)?;
4248 Ok(expr)
4249 }
4250 // TSQL doesn't support QUALIFY
4251 // TSQL requires boolean expressions in WHERE/HAVING (no implicit truthiness)
4252 // TSQL doesn't support CTEs in subqueries (hoist to top level)
4253 // NOTE: no_limit_order_by_union is handled in cross_dialect_normalize (not preprocess)
4254 // to avoid breaking TSQL identity tests where ORDER BY on UNION is valid
4255 #[cfg(feature = "dialect-tsql")]
4256 DialectType::TSQL => {
4257 let expr = transforms::eliminate_qualify(expr)?;
4258 let expr = transforms::eliminate_semi_and_anti_joins(expr)?;
4259 let expr = transforms::ensure_bools(expr)?;
4260 let expr = transforms::unnest_generate_date_array_using_recursive_cte(expr)?;
4261 let expr = transforms::strip_cte_materialization(expr)?;
4262 let expr = transforms::move_ctes_to_top_level(expr)?;
4263 let expr = transforms::qualify_derived_table_outputs(expr)?;
4264 Ok(expr)
4265 }
4266 // Fabric shares T-SQL predicate rules and CTE placement restrictions,
4267 // but keeps Fabric-specific APPLY and derived-table behavior separate.
4268 #[cfg(feature = "dialect-fabric")]
4269 DialectType::Fabric => {
4270 let expr = transforms::ensure_bools(expr)?;
4271 let expr = transforms::strip_cte_materialization(expr)?;
4272 let expr = transforms::move_ctes_to_top_level(expr)?;
4273 Ok(expr)
4274 }
4275 // Spark doesn't support QUALIFY (but Databricks does)
4276 // Spark doesn't support CTEs in subqueries (hoist to top level)
4277 #[cfg(feature = "dialect-spark")]
4278 DialectType::Spark => {
4279 let expr = transforms::eliminate_qualify(expr)?;
4280 let expr = transforms::add_auto_table_alias(expr)?;
4281 let expr = transforms::simplify_nested_paren_values(expr)?;
4282 let expr = transforms::move_ctes_to_top_level(expr)?;
4283 Ok(expr)
4284 }
4285 // Databricks supports QUALIFY natively
4286 // Databricks doesn't support CTEs in subqueries (hoist to top level)
4287 #[cfg(feature = "dialect-databricks")]
4288 DialectType::Databricks => {
4289 let expr = transforms::add_auto_table_alias(expr)?;
4290 let expr = transforms::simplify_nested_paren_values(expr)?;
4291 let expr = transforms::move_ctes_to_top_level(expr)?;
4292 Ok(expr)
4293 }
4294 // Hive doesn't support QUALIFY or CTEs in subqueries
4295 #[cfg(feature = "dialect-hive")]
4296 DialectType::Hive => {
4297 let expr = transforms::eliminate_qualify(expr)?;
4298 let expr = transforms::move_ctes_to_top_level(expr)?;
4299 Ok(expr)
4300 }
4301 // SQLite doesn't support QUALIFY
4302 #[cfg(feature = "dialect-sqlite")]
4303 DialectType::SQLite => {
4304 let expr = transforms::eliminate_qualify(expr)?;
4305 Ok(expr)
4306 }
4307 // Trino doesn't support QUALIFY
4308 #[cfg(feature = "dialect-trino")]
4309 DialectType::Trino => {
4310 let expr = transforms::eliminate_qualify(expr)?;
4311 let expr = transforms::explode_projection_to_unnest(expr, DialectType::Trino)?;
4312 Ok(expr)
4313 }
4314 // Presto doesn't support QUALIFY or WINDOW clause
4315 #[cfg(feature = "dialect-presto")]
4316 DialectType::Presto => {
4317 let expr = transforms::eliminate_qualify(expr)?;
4318 let expr = transforms::eliminate_window_clause(expr)?;
4319 let expr = transforms::explode_projection_to_unnest(expr, DialectType::Presto)?;
4320 Ok(expr)
4321 }
4322 // DuckDB supports QUALIFY - no elimination needed
4323 // Expand POSEXPLODE to GENERATE_SUBSCRIPTS + UNNEST
4324 // Expand LIKE ANY / ILIKE ANY to OR chains (DuckDB doesn't support quantifiers)
4325 #[cfg(feature = "dialect-duckdb")]
4326 DialectType::DuckDB => {
4327 let expr = transforms::expand_posexplode_duckdb(expr)?;
4328 let expr = transforms::expand_like_any(expr)?;
4329 Ok(expr)
4330 }
4331 // Redshift doesn't support QUALIFY, WINDOW clause, or GENERATE_DATE_ARRAY
4332 #[cfg(feature = "dialect-redshift")]
4333 DialectType::Redshift => {
4334 let expr = transforms::eliminate_qualify(expr)?;
4335 let expr = transforms::eliminate_window_clause(expr)?;
4336 let expr = transforms::unnest_generate_date_array_using_recursive_cte(expr)?;
4337 Ok(expr)
4338 }
4339 // StarRocks doesn't support BETWEEN in DELETE statements or QUALIFY
4340 #[cfg(feature = "dialect-starrocks")]
4341 DialectType::StarRocks => {
4342 let expr = transforms::eliminate_qualify(expr)?;
4343 let expr = transforms::expand_between_in_delete(expr)?;
4344 let expr = transforms::eliminate_distinct_on_for_dialect(
4345 expr,
4346 Some(DialectType::StarRocks),
4347 Some(DialectType::StarRocks),
4348 )?;
4349 let expr = transforms::unnest_generate_date_array_using_recursive_cte(expr)?;
4350 Ok(expr)
4351 }
4352 // DataFusion supports QUALIFY and semi/anti joins natively
4353 #[cfg(feature = "dialect-datafusion")]
4354 DialectType::DataFusion => Ok(expr),
4355 // Oracle doesn't support QUALIFY
4356 #[cfg(feature = "dialect-oracle")]
4357 DialectType::Oracle => {
4358 let expr = transforms::eliminate_qualify(expr)?;
4359 Ok(expr)
4360 }
4361 // Drill - no special preprocessing needed
4362 #[cfg(feature = "dialect-drill")]
4363 DialectType::Drill => Ok(expr),
4364 // Teradata - no special preprocessing needed
4365 #[cfg(feature = "dialect-teradata")]
4366 DialectType::Teradata => Ok(expr),
4367 // ClickHouse doesn't support ORDER BY/LIMIT directly on UNION
4368 #[cfg(feature = "dialect-clickhouse")]
4369 DialectType::ClickHouse => {
4370 let expr = transforms::no_limit_order_by_union(expr)?;
4371 Ok(expr)
4372 }
4373 // Other dialects - no preprocessing
4374 _ => Ok(expr),
4375 }
4376 }
4377
4378 /// Transpile SQL from this dialect to the given target dialect.
4379 ///
4380 /// The target may be specified as either a built-in [`DialectType`] enum variant
4381 /// or as a reference to a [`Dialect`] handle (built-in or custom). Both work:
4382 ///
4383 /// ```rust,ignore
4384 /// let pg = Dialect::get(DialectType::PostgreSQL);
4385 /// pg.transpile("SELECT NOW()", DialectType::BigQuery)?; // enum
4386 /// pg.transpile("SELECT NOW()", &custom_dialect)?; // handle
4387 /// ```
4388 ///
4389 /// For pretty-printing or other options, use [`transpile_with`](Self::transpile_with).
4390 #[cfg(feature = "transpile")]
4391 pub fn transpile<T: TranspileTarget>(&self, sql: &str, target: T) -> Result<Vec<String>> {
4392 self.transpile_with(sql, target, TranspileOptions::default())
4393 }
4394
4395 /// Transpile SQL with configurable [`TranspileOptions`] (e.g. pretty-printing).
4396 #[cfg(feature = "transpile")]
4397 pub fn transpile_with<T: TranspileTarget>(
4398 &self,
4399 sql: &str,
4400 target: T,
4401 opts: TranspileOptions,
4402 ) -> Result<Vec<String>> {
4403 target.with_dialect(|td| self.transpile_inner(sql, td, &opts))
4404 }
4405
4406 #[cfg(feature = "transpile")]
4407 fn transpile_inner(
4408 &self,
4409 sql: &str,
4410 target_dialect: &Dialect,
4411 opts: &TranspileOptions,
4412 ) -> Result<Vec<String>> {
4413 let mut effective_opts = opts.clone();
4414 effective_opts.complexity_guard =
4415 self.default_transpile_complexity_guard(target_dialect, opts.complexity_guard);
4416 let opts = &effective_opts;
4417 let target = target_dialect.dialect_type;
4418 if matches!(self.dialect_type, DialectType::PostgreSQL)
4419 && matches!(target, DialectType::SQLite)
4420 {
4421 self.reject_pgvector_distance_operators_for_sqlite(sql)?;
4422 }
4423 let expressions = self.parse_with_guard(sql, opts.complexity_guard)?;
4424 let generic_identity =
4425 self.dialect_type == DialectType::Generic && target == DialectType::Generic;
4426
4427 if generic_identity {
4428 return expressions
4429 .into_iter()
4430 .map(|expr| {
4431 Self::reject_strict_unsupported(&expr, self.dialect_type, target, opts)?;
4432 target_dialect.generate_with_transpile_options(&expr, self.dialect_type, opts)
4433 })
4434 .collect();
4435 }
4436
4437 expressions
4438 .into_iter()
4439 .map(|expr| {
4440 // DuckDB source: normalize VARCHAR/CHAR to TEXT (DuckDB doesn't support
4441 // VARCHAR length constraints). This emulates Python sqlglot's DuckDB parser
4442 // where VARCHAR_LENGTH = None and VARCHAR maps to TEXT.
4443 let expr = if matches!(self.dialect_type, DialectType::DuckDB) {
4444 use crate::expressions::DataType as DT;
4445 transform_recursive(expr, &|e| match e {
4446 Expression::DataType(DT::VarChar { .. }) => {
4447 Ok(Expression::DataType(DT::Text))
4448 }
4449 Expression::DataType(DT::Char { .. }) => Ok(Expression::DataType(DT::Text)),
4450 _ => Ok(e),
4451 })?
4452 } else {
4453 expr
4454 };
4455
4456 Self::reject_postgres_tsql_strict_regex_predicates(
4457 &expr,
4458 self.dialect_type,
4459 target,
4460 opts,
4461 )?;
4462
4463 // When source and target differ, first normalize the source dialect's
4464 // AST constructs to standard SQL, so that the target dialect can handle them.
4465 // This handles cases like Snowflake's SQUARE -> POWER, DIV0 -> CASE, etc.
4466 let normalized =
4467 if self.dialect_type != target && self.dialect_type != DialectType::Generic {
4468 self.transform_with_guard(expr, opts.complexity_guard)?
4469 } else {
4470 expr
4471 };
4472
4473 // For TSQL source targeting non-TSQL: unwrap ISNULL(JSON_QUERY(...), JSON_VALUE(...))
4474 // to just JSON_QUERY(...) so cross_dialect_normalize can convert it cleanly.
4475 // The TSQL read transform wraps JsonQuery in ISNULL for identity, but for
4476 // cross-dialect transpilation we need the unwrapped JSON_QUERY.
4477 let normalized =
4478 if matches!(self.dialect_type, DialectType::TSQL | DialectType::Fabric)
4479 && !matches!(target, DialectType::TSQL | DialectType::Fabric)
4480 {
4481 transform_recursive(normalized, &|e| {
4482 if let Expression::Function(ref f) = e {
4483 if f.name.eq_ignore_ascii_case("ISNULL") && f.args.len() == 2 {
4484 // Check if first arg is JSON_QUERY and second is JSON_VALUE
4485 if let (
4486 Expression::Function(ref jq),
4487 Expression::Function(ref jv),
4488 ) = (&f.args[0], &f.args[1])
4489 {
4490 if jq.name.eq_ignore_ascii_case("JSON_QUERY")
4491 && jv.name.eq_ignore_ascii_case("JSON_VALUE")
4492 {
4493 // Unwrap: return just JSON_QUERY(...)
4494 return Ok(f.args[0].clone());
4495 }
4496 }
4497 }
4498 }
4499 Ok(e)
4500 })?
4501 } else {
4502 normalized
4503 };
4504
4505 // Snowflake source to non-Snowflake target: CURRENT_TIME -> LOCALTIME
4506 // Snowflake's CURRENT_TIME is equivalent to LOCALTIME in other dialects.
4507 // Python sqlglot parses Snowflake's CURRENT_TIME as Localtime expression.
4508 let normalized = if matches!(self.dialect_type, DialectType::Snowflake)
4509 && !matches!(target, DialectType::Snowflake)
4510 {
4511 transform_recursive(normalized, &|e| {
4512 if let Expression::Function(ref f) = e {
4513 if f.name.eq_ignore_ascii_case("CURRENT_TIME") {
4514 return Ok(Expression::Localtime(Box::new(
4515 crate::expressions::Localtime { this: None },
4516 )));
4517 }
4518 }
4519 Ok(e)
4520 })?
4521 } else {
4522 normalized
4523 };
4524
4525 // Snowflake source to DuckDB target: REPEAT(' ', n) -> REPEAT(' ', CAST(n AS BIGINT))
4526 // Snowflake's SPACE(n) is converted to REPEAT(' ', n) by the Snowflake source
4527 // transform. DuckDB requires the count argument to be BIGINT.
4528 let normalized = if matches!(self.dialect_type, DialectType::Snowflake)
4529 && matches!(target, DialectType::DuckDB)
4530 {
4531 transform_recursive(normalized, &|e| {
4532 if let Expression::Function(ref f) = e {
4533 if f.name.eq_ignore_ascii_case("REPEAT") && f.args.len() == 2 {
4534 // Check if first arg is space string literal
4535 if let Expression::Literal(ref lit) = f.args[0] {
4536 if let crate::expressions::Literal::String(ref s) = lit.as_ref()
4537 {
4538 if s == " " {
4539 // Wrap second arg in CAST(... AS BIGINT) if not already
4540 if !matches!(f.args[1], Expression::Cast(_)) {
4541 let mut new_args = f.args.clone();
4542 new_args[1] = Expression::Cast(Box::new(
4543 crate::expressions::Cast {
4544 this: new_args[1].clone(),
4545 to: crate::expressions::DataType::BigInt {
4546 length: None,
4547 },
4548 trailing_comments: Vec::new(),
4549 double_colon_syntax: false,
4550 format: None,
4551 default: None,
4552 inferred_type: None,
4553 },
4554 ));
4555 return Ok(Expression::Function(Box::new(
4556 crate::expressions::Function {
4557 name: f.name.clone(),
4558 args: new_args,
4559 distinct: f.distinct,
4560 trailing_comments: f
4561 .trailing_comments
4562 .clone(),
4563 use_bracket_syntax: f.use_bracket_syntax,
4564 no_parens: f.no_parens,
4565 quoted: f.quoted,
4566 span: None,
4567 inferred_type: None,
4568 },
4569 )));
4570 }
4571 }
4572 }
4573 }
4574 }
4575 }
4576 Ok(e)
4577 })?
4578 } else {
4579 normalized
4580 };
4581
4582 // Propagate struct field names in arrays (for BigQuery source to non-BigQuery target)
4583 // BigQuery->BigQuery should NOT propagate names (BigQuery handles implicit inheritance)
4584 let normalized = if matches!(self.dialect_type, DialectType::BigQuery)
4585 && !matches!(target, DialectType::BigQuery)
4586 {
4587 crate::transforms::propagate_struct_field_names(normalized)?
4588 } else {
4589 normalized
4590 };
4591
4592 // Snowflake source to DuckDB target: RANDOM()/RANDOM(seed) -> scaled RANDOM()
4593 // Snowflake RANDOM() returns integer in [-2^63, 2^63-1], DuckDB RANDOM() returns float [0, 1)
4594 // Skip RANDOM inside UNIFORM/NORMAL/ZIPF/RANDSTR generator args since those
4595 // functions handle their generator args differently (as float seeds).
4596 let normalized = if matches!(self.dialect_type, DialectType::Snowflake)
4597 && matches!(target, DialectType::DuckDB)
4598 {
4599 fn make_scaled_random() -> Expression {
4600 let lower =
4601 Expression::Literal(Box::new(crate::expressions::Literal::Number(
4602 "-9.223372036854776E+18".to_string(),
4603 )));
4604 let upper =
4605 Expression::Literal(Box::new(crate::expressions::Literal::Number(
4606 "9.223372036854776e+18".to_string(),
4607 )));
4608 let random_call = Expression::Random(crate::expressions::Random);
4609 let range_size = Expression::Paren(Box::new(crate::expressions::Paren {
4610 this: Expression::Sub(Box::new(crate::expressions::BinaryOp {
4611 left: upper,
4612 right: lower.clone(),
4613 left_comments: vec![],
4614 operator_comments: vec![],
4615 trailing_comments: vec![],
4616 inferred_type: None,
4617 })),
4618 trailing_comments: vec![],
4619 }));
4620 let scaled = Expression::Mul(Box::new(crate::expressions::BinaryOp {
4621 left: random_call,
4622 right: range_size,
4623 left_comments: vec![],
4624 operator_comments: vec![],
4625 trailing_comments: vec![],
4626 inferred_type: None,
4627 }));
4628 let shifted = Expression::Add(Box::new(crate::expressions::BinaryOp {
4629 left: lower,
4630 right: scaled,
4631 left_comments: vec![],
4632 operator_comments: vec![],
4633 trailing_comments: vec![],
4634 inferred_type: None,
4635 }));
4636 Expression::Cast(Box::new(crate::expressions::Cast {
4637 this: shifted,
4638 to: crate::expressions::DataType::BigInt { length: None },
4639 trailing_comments: vec![],
4640 double_colon_syntax: false,
4641 format: None,
4642 default: None,
4643 inferred_type: None,
4644 }))
4645 }
4646
4647 // Pre-process: protect seeded RANDOM(seed) inside UNIFORM/NORMAL/ZIPF/RANDSTR
4648 // by converting Rand{seed: Some(s)} to Function{name:"RANDOM", args:[s]}.
4649 // This prevents transform_recursive (which is bottom-up) from expanding
4650 // seeded RANDOM into make_scaled_random() and losing the seed value.
4651 // Unseeded RANDOM()/Rand{seed:None} is left as-is so it gets expanded
4652 // and then un-expanded back to Expression::Random by the code below.
4653 let normalized = transform_recursive(normalized, &|e| {
4654 if let Expression::Function(ref f) = e {
4655 let n = f.name.to_ascii_uppercase();
4656 if n == "UNIFORM" || n == "NORMAL" || n == "ZIPF" || n == "RANDSTR" {
4657 if let Expression::Function(mut f) = e {
4658 for arg in f.args.iter_mut() {
4659 if let Expression::Rand(ref r) = arg {
4660 if r.lower.is_none() && r.upper.is_none() {
4661 if let Some(ref seed) = r.seed {
4662 // Convert Rand{seed: Some(s)} to Function("RANDOM", [s])
4663 // so it won't be expanded by the RANDOM expansion below
4664 *arg = Expression::Function(Box::new(
4665 crate::expressions::Function::new(
4666 "RANDOM".to_string(),
4667 vec![*seed.clone()],
4668 ),
4669 ));
4670 }
4671 }
4672 }
4673 }
4674 return Ok(Expression::Function(f));
4675 }
4676 }
4677 }
4678 Ok(e)
4679 })?;
4680
4681 // transform_recursive processes bottom-up, so RANDOM() (unseeded) inside
4682 // generator functions (UNIFORM, NORMAL, ZIPF) gets expanded before
4683 // we see the parent. We detect this and undo the expansion by replacing
4684 // the expanded pattern back with Expression::Random.
4685 // Seeded RANDOM(seed) was already protected above as Function("RANDOM", [seed]).
4686 // Note: RANDSTR is NOT included here — it needs the expanded form for unseeded
4687 // RANDOM() since the DuckDB handler uses the expanded SQL as-is in the hash.
4688 transform_recursive(normalized, &|e| {
4689 if let Expression::Function(ref f) = e {
4690 let n = f.name.to_ascii_uppercase();
4691 if n == "UNIFORM" || n == "NORMAL" || n == "ZIPF" {
4692 if let Expression::Function(mut f) = e {
4693 for arg in f.args.iter_mut() {
4694 // Detect expanded RANDOM pattern: CAST(-9.22... + RANDOM() * (...) AS BIGINT)
4695 if let Expression::Cast(ref cast) = arg {
4696 if matches!(
4697 cast.to,
4698 crate::expressions::DataType::BigInt { .. }
4699 ) {
4700 if let Expression::Add(ref add) = cast.this {
4701 if let Expression::Literal(ref lit) = add.left {
4702 if let crate::expressions::Literal::Number(
4703 ref num,
4704 ) = lit.as_ref()
4705 {
4706 if num == "-9.223372036854776E+18" {
4707 *arg = Expression::Random(
4708 crate::expressions::Random,
4709 );
4710 }
4711 }
4712 }
4713 }
4714 }
4715 }
4716 }
4717 return Ok(Expression::Function(f));
4718 }
4719 return Ok(e);
4720 }
4721 }
4722 match e {
4723 Expression::Random(_) => Ok(make_scaled_random()),
4724 // Rand(seed) with no bounds: drop seed and expand
4725 // (DuckDB RANDOM doesn't support seeds)
4726 Expression::Rand(ref r) if r.lower.is_none() && r.upper.is_none() => {
4727 Ok(make_scaled_random())
4728 }
4729 _ => Ok(e),
4730 }
4731 })?
4732 } else {
4733 normalized
4734 };
4735
4736 // Apply cross-dialect semantic normalizations
4737 let normalized =
4738 Self::cross_dialect_normalize(normalized, self.dialect_type, target)?;
4739
4740 let normalized = if matches!(target, DialectType::TSQL | DialectType::Fabric) {
4741 Self::normalize_tsql_fetch_overlaps_date_bin(normalized)?
4742 } else {
4743 normalized
4744 };
4745
4746 let normalized =
4747 if matches!(
4748 self.dialect_type,
4749 DialectType::PostgreSQL | DialectType::CockroachDB
4750 ) && !matches!(target, DialectType::PostgreSQL | DialectType::CockroachDB)
4751 {
4752 Self::normalize_postgres_type_function_casts(normalized)?
4753 } else {
4754 normalized
4755 };
4756
4757 let normalized = if matches!(self.dialect_type, DialectType::SQLite)
4758 && !matches!(target, DialectType::SQLite)
4759 {
4760 Self::normalize_sqlite_double_quoted_defaults(normalized)?
4761 } else {
4762 normalized
4763 };
4764
4765 let normalized = if matches!(self.dialect_type, DialectType::PostgreSQL)
4766 && matches!(target, DialectType::SQLite)
4767 {
4768 Self::normalize_postgres_to_sqlite_types(normalized)?
4769 } else {
4770 normalized
4771 };
4772
4773 let normalized = if matches!(self.dialect_type, DialectType::PostgreSQL)
4774 && matches!(target, DialectType::Fabric)
4775 {
4776 Self::normalize_postgres_to_fabric_types(normalized)?
4777 } else {
4778 normalized
4779 };
4780
4781 // For DuckDB target from BigQuery source: wrap UNNEST of struct arrays in
4782 // (SELECT UNNEST(..., max_depth => 2)) subquery
4783 // Must run BEFORE unnest_alias_to_column_alias since it changes alias structure
4784 let normalized = if matches!(self.dialect_type, DialectType::BigQuery)
4785 && matches!(target, DialectType::DuckDB)
4786 {
4787 crate::transforms::wrap_duckdb_unnest_struct(normalized)?
4788 } else {
4789 normalized
4790 };
4791
4792 // Convert BigQuery UNNEST aliases to column-alias format for DuckDB/Presto/Spark
4793 // UNNEST(arr) AS x -> UNNEST(arr) AS _t0(x)
4794 let normalized = if matches!(self.dialect_type, DialectType::BigQuery)
4795 && matches!(
4796 target,
4797 DialectType::DuckDB
4798 | DialectType::Presto
4799 | DialectType::Trino
4800 | DialectType::Athena
4801 | DialectType::Spark
4802 | DialectType::Databricks
4803 ) {
4804 crate::transforms::unnest_alias_to_column_alias(normalized)?
4805 } else if matches!(self.dialect_type, DialectType::BigQuery)
4806 && matches!(target, DialectType::BigQuery | DialectType::Redshift)
4807 {
4808 // For BigQuery/Redshift targets: move UNNEST FROM items to CROSS JOINs
4809 // but don't convert alias format (no _t0 wrapper)
4810 let result = crate::transforms::unnest_from_to_cross_join(normalized)?;
4811 // For Redshift: strip UNNEST when arg is a column reference path
4812 if matches!(target, DialectType::Redshift) {
4813 crate::transforms::strip_unnest_column_refs(result)?
4814 } else {
4815 result
4816 }
4817 } else {
4818 normalized
4819 };
4820
4821 // For Presto/Trino targets from PostgreSQL/Redshift source:
4822 // Wrap UNNEST aliases from GENERATE_SERIES conversion: AS s -> AS _u(s)
4823 let normalized = if matches!(
4824 self.dialect_type,
4825 DialectType::PostgreSQL | DialectType::Redshift
4826 ) && matches!(
4827 target,
4828 DialectType::Presto | DialectType::Trino | DialectType::Athena
4829 ) {
4830 crate::transforms::wrap_unnest_join_aliases(normalized)?
4831 } else {
4832 normalized
4833 };
4834
4835 // Eliminate DISTINCT ON with target-dialect awareness
4836 // This must happen after source transform (which may produce DISTINCT ON)
4837 // and before target transform, with knowledge of the target dialect's NULL ordering behavior
4838 let normalized = crate::transforms::eliminate_distinct_on_for_dialect(
4839 normalized,
4840 Some(target),
4841 Some(self.dialect_type),
4842 )?;
4843
4844 // GENERATE_DATE_ARRAY in UNNEST -> Snowflake ARRAY_GENERATE_RANGE + DATEADD
4845 let normalized = if matches!(target, DialectType::Snowflake) {
4846 Self::transform_generate_date_array_snowflake(normalized)?
4847 } else {
4848 normalized
4849 };
4850
4851 // CROSS JOIN UNNEST -> LATERAL VIEW EXPLODE/INLINE for Spark/Hive/Databricks
4852 let normalized = if matches!(
4853 target,
4854 DialectType::Spark | DialectType::Databricks | DialectType::Hive
4855 ) {
4856 crate::transforms::unnest_to_explode_select(normalized)?
4857 } else {
4858 normalized
4859 };
4860
4861 // Wrap UNION with ORDER BY/LIMIT in a subquery for dialects that require it
4862 let normalized = if matches!(target, DialectType::ClickHouse | DialectType::TSQL) {
4863 crate::transforms::no_limit_order_by_union(normalized)?
4864 } else {
4865 normalized
4866 };
4867
4868 // TSQL: Convert COUNT(*) -> COUNT_BIG(*) when source is not TSQL/Fabric
4869 // Python sqlglot does this in the TSQL generator, but we can't do it there
4870 // because it would break TSQL -> TSQL identity
4871 let normalized = if matches!(target, DialectType::TSQL | DialectType::Fabric)
4872 && !matches!(self.dialect_type, DialectType::TSQL | DialectType::Fabric)
4873 {
4874 transform_recursive(normalized, &|e| {
4875 if let Expression::Count(ref c) = e {
4876 // Build COUNT_BIG(...) as an AggregateFunction
4877 let args = if c.star {
4878 vec![Expression::Star(crate::expressions::Star {
4879 table: None,
4880 except: None,
4881 replace: None,
4882 rename: None,
4883 trailing_comments: Vec::new(),
4884 span: None,
4885 })]
4886 } else if let Some(ref this) = c.this {
4887 vec![this.clone()]
4888 } else {
4889 vec![]
4890 };
4891 Ok(Expression::AggregateFunction(Box::new(
4892 crate::expressions::AggregateFunction {
4893 name: "COUNT_BIG".to_string(),
4894 args,
4895 distinct: c.distinct,
4896 filter: c.filter.clone(),
4897 order_by: Vec::new(),
4898 limit: None,
4899 ignore_nulls: None,
4900 inferred_type: None,
4901 },
4902 )))
4903 } else {
4904 Ok(e)
4905 }
4906 })?
4907 } else {
4908 normalized
4909 };
4910
4911 // T-SQL/Fabric do not have a scalar boolean type. Keep predicate
4912 // contexts intact, but materialize boolean-valued expressions used
4913 // as values before target transforms add ORDER BY null sort keys.
4914 let normalized = if matches!(target, DialectType::TSQL | DialectType::Fabric)
4915 && !matches!(self.dialect_type, DialectType::TSQL | DialectType::Fabric)
4916 {
4917 Self::rewrite_boolean_values_for_tsql(normalized)?
4918 } else {
4919 normalized
4920 };
4921
4922 let normalized = if matches!(
4923 self.dialect_type,
4924 DialectType::PostgreSQL | DialectType::CockroachDB
4925 ) && matches!(target, DialectType::TSQL | DialectType::Fabric)
4926 {
4927 Self::rewrite_postgres_format_for_tsql(normalized, target)?
4928 } else {
4929 normalized
4930 };
4931
4932 let transformed =
4933 target_dialect.transform_with_guard(normalized, opts.complexity_guard)?;
4934
4935 // T-SQL and Fabric do not support aggregate FILTER clauses. Rewrite any
4936 // remaining filters after target transforms so special aggregate rewrites
4937 // (for example BOOL_OR/BOOL_AND) can consume their filters first.
4938 let transformed = if matches!(target, DialectType::TSQL | DialectType::Fabric) {
4939 Self::rewrite_aggregate_filters_for_tsql(transformed)?
4940 } else {
4941 transformed
4942 };
4943
4944 let transformed = if matches!(
4945 self.dialect_type,
4946 DialectType::PostgreSQL | DialectType::CockroachDB
4947 ) && matches!(target, DialectType::TSQL | DialectType::Fabric)
4948 {
4949 crate::transforms::grouped_percentiles_to_tsql_windows(transformed)?
4950 } else {
4951 transformed
4952 };
4953
4954 let transformed = if matches!(
4955 self.dialect_type,
4956 DialectType::PostgreSQL | DialectType::CockroachDB
4957 ) && matches!(target, DialectType::TSQL | DialectType::Fabric)
4958 {
4959 Self::normalize_postgres_trim_for_tsql(transformed)?
4960 } else {
4961 transformed
4962 };
4963
4964 let transformed = if matches!(
4965 self.dialect_type,
4966 DialectType::PostgreSQL | DialectType::CockroachDB
4967 ) && matches!(target, DialectType::TSQL | DialectType::Fabric)
4968 {
4969 Self::rewrite_postgres_json_array_elements_select_for_tsql(transformed)?
4970 } else {
4971 transformed
4972 };
4973
4974 // DuckDB target: when FROM is RANGE(n), replace SEQ's ROW_NUMBER pattern with `range`
4975 let transformed = if matches!(target, DialectType::DuckDB) {
4976 Self::seq_rownum_to_range(transformed)?
4977 } else {
4978 transformed
4979 };
4980
4981 if matches!(target, DialectType::TSQL | DialectType::Fabric) {
4982 Self::reject_tsql_interval_casts(&transformed, target, opts)?;
4983 }
4984
4985 let transformed = if matches!(target, DialectType::TSQL | DialectType::Fabric) {
4986 Self::rewrite_tsql_interval_casts_to_varchar(transformed)?
4987 } else {
4988 transformed
4989 };
4990
4991 Self::reject_strict_unsupported(&transformed, self.dialect_type, target, opts)?;
4992
4993 let mut sql = target_dialect.generate_with_transpile_options(
4994 &transformed,
4995 self.dialect_type,
4996 opts,
4997 )?;
4998
4999 // Align a known Snowflake pretty-print edge case with Python sqlglot output.
5000 if opts.pretty && target == DialectType::Snowflake {
5001 sql = Self::normalize_snowflake_pretty(sql);
5002 }
5003
5004 Ok(sql)
5005 })
5006 .collect()
5007 }
5008}
5009
5010// Transpile-only methods: cross-dialect normalization and helpers
5011#[cfg(feature = "transpile")]
5012impl Dialect {
5013 fn reject_strict_unsupported(
5014 expr: &Expression,
5015 source: DialectType,
5016 target: DialectType,
5017 opts: &TranspileOptions,
5018 ) -> Result<()> {
5019 if !matches!(
5020 opts.unsupported_level,
5021 UnsupportedLevel::Raise | UnsupportedLevel::Immediate
5022 ) {
5023 return Ok(());
5024 }
5025
5026 let mut diagnostics = Vec::new();
5027
5028 for node in expr.dfs() {
5029 if matches!(target, DialectType::Fabric | DialectType::Hive)
5030 && Self::node_has_recursive_with(node)
5031 {
5032 Self::push_unsupported_diagnostic(&mut diagnostics, "recursive CTEs");
5033 }
5034
5035 if matches!(target, DialectType::TSQL | DialectType::Fabric)
5036 && Self::node_has_lateral(node)
5037 {
5038 Self::push_unsupported_diagnostic(&mut diagnostics, "LATERAL joins and subqueries");
5039 }
5040
5041 if !Self::target_supports_distinct_on(target) && Self::node_has_distinct_on(node) {
5042 Self::push_unsupported_diagnostic(&mut diagnostics, "DISTINCT ON");
5043 }
5044
5045 if !Self::target_supports_remaining_unnest(target) && Self::node_is_unnest(node) {
5046 Self::push_unsupported_diagnostic(&mut diagnostics, "UNNEST");
5047 }
5048
5049 if !Self::target_supports_remaining_explode(target) && Self::node_is_explode(node) {
5050 Self::push_unsupported_diagnostic(&mut diagnostics, "EXPLODE");
5051 }
5052
5053 if Self::target_lacks_array_agg(target) && Self::node_is_array_agg(node) {
5054 Self::push_unsupported_diagnostic(&mut diagnostics, "ARRAY_AGG");
5055 }
5056
5057 if matches!(target, DialectType::TSQL | DialectType::Fabric)
5058 && Self::node_is_regex_predicate(node)
5059 {
5060 Self::push_unsupported_diagnostic(
5061 &mut diagnostics,
5062 "regular expression predicates",
5063 );
5064 }
5065
5066 if matches!(target, DialectType::TSQL | DialectType::Fabric)
5067 && Self::node_is_non_subquery_any(node)
5068 {
5069 Self::push_unsupported_diagnostic(
5070 &mut diagnostics,
5071 "ANY over non-subquery expressions",
5072 );
5073 }
5074
5075 if matches!(target, DialectType::TSQL | DialectType::Fabric)
5076 && Self::node_is_row_value_subquery_comparison(node)
5077 {
5078 Self::push_unsupported_diagnostic(
5079 &mut diagnostics,
5080 "row-value subquery comparisons",
5081 );
5082 }
5083
5084 if matches!(target, DialectType::TSQL | DialectType::Fabric)
5085 && Self::node_has_fetch_with_ties(node)
5086 {
5087 Self::push_unsupported_diagnostic(&mut diagnostics, "FETCH WITH TIES without TOP");
5088 }
5089
5090 if matches!(target, DialectType::TSQL | DialectType::Fabric)
5091 && Self::node_is_overlaps(node)
5092 {
5093 Self::push_unsupported_diagnostic(&mut diagnostics, "OVERLAPS");
5094 }
5095
5096 if matches!(target, DialectType::TSQL | DialectType::Fabric)
5097 && Self::node_is_date_bin(node)
5098 {
5099 Self::push_unsupported_diagnostic(&mut diagnostics, "DATE_BIN");
5100 }
5101
5102 if matches!(source, DialectType::PostgreSQL | DialectType::CockroachDB)
5103 && !matches!(target, DialectType::PostgreSQL | DialectType::CockroachDB)
5104 {
5105 if Self::node_is_postgres_json_build_object(node)
5106 && !(matches!(target, DialectType::TSQL | DialectType::Fabric)
5107 && Self::postgres_json_build_object_can_lower_to_json_object(node))
5108 {
5109 Self::push_unsupported_diagnostic(
5110 &mut diagnostics,
5111 "PostgreSQL JSON_BUILD_OBJECT",
5112 );
5113 }
5114 if Self::node_is_function_named(node, "TO_TSVECTOR") {
5115 Self::push_unsupported_diagnostic(&mut diagnostics, "PostgreSQL TO_TSVECTOR");
5116 }
5117 if matches!(target, DialectType::TSQL | DialectType::Fabric) {
5118 if let Some(array_semantics) =
5119 Self::postgres_tsql_unsupported_array_semantics(node)
5120 {
5121 Self::push_unsupported_diagnostic(
5122 &mut diagnostics,
5123 &format!("PostgreSQL {array_semantics}"),
5124 );
5125 }
5126 if let Some(function_name) = Self::postgres_tsql_unsupported_function_name(node)
5127 {
5128 Self::push_unsupported_diagnostic(
5129 &mut diagnostics,
5130 &format!("PostgreSQL {function_name}"),
5131 );
5132 }
5133 }
5134 if matches!(target, DialectType::TSQL | DialectType::Fabric)
5135 && Self::node_is_postgres_type_function_cast(node)
5136 {
5137 Self::push_unsupported_diagnostic(
5138 &mut diagnostics,
5139 "PostgreSQL type-name function casts",
5140 );
5141 }
5142 }
5143
5144 if opts.unsupported_level == UnsupportedLevel::Immediate && !diagnostics.is_empty() {
5145 break;
5146 }
5147 }
5148
5149 if matches!(target, DialectType::TSQL | DialectType::Fabric) {
5150 Self::collect_tsql_unsupported_ordered_sets(expr, &mut diagnostics);
5151 }
5152
5153 if diagnostics.is_empty() {
5154 return Ok(());
5155 }
5156
5157 let limit = if opts.unsupported_level == UnsupportedLevel::Immediate {
5158 1
5159 } else {
5160 opts.max_unsupported.max(1)
5161 };
5162 let mut messages = diagnostics.iter().take(limit).cloned().collect::<Vec<_>>();
5163 if diagnostics.len() > limit {
5164 messages.push(format!("... and {} more", diagnostics.len() - limit));
5165 }
5166
5167 Err(crate::error::Error::unsupported(
5168 messages.join("; "),
5169 target.to_string(),
5170 ))
5171 }
5172
5173 fn reject_postgres_tsql_strict_regex_predicates(
5174 expr: &Expression,
5175 source: DialectType,
5176 target: DialectType,
5177 opts: &TranspileOptions,
5178 ) -> Result<()> {
5179 if !matches!(
5180 opts.unsupported_level,
5181 UnsupportedLevel::Raise | UnsupportedLevel::Immediate
5182 ) || !matches!(source, DialectType::PostgreSQL | DialectType::CockroachDB)
5183 || !matches!(target, DialectType::TSQL | DialectType::Fabric)
5184 {
5185 return Ok(());
5186 }
5187
5188 if expr.dfs().any(Self::node_is_regex_predicate) {
5189 return Err(crate::error::Error::unsupported(
5190 "regular expression predicates",
5191 target.to_string(),
5192 ));
5193 }
5194
5195 Ok(())
5196 }
5197
5198 fn push_unsupported_diagnostic(diagnostics: &mut Vec<String>, message: &str) {
5199 if !diagnostics.iter().any(|existing| existing == message) {
5200 diagnostics.push(message.to_string());
5201 }
5202 }
5203
5204 fn collect_tsql_unsupported_ordered_sets(expr: &Expression, diagnostics: &mut Vec<String>) {
5205 match expr {
5206 Expression::WindowFunction(window) => {
5207 if let Expression::WithinGroup(within_group) = &window.this {
5208 if Self::within_group_is_mode(within_group) {
5209 Self::push_unsupported_diagnostic(
5210 diagnostics,
5211 "MODE ordered-set aggregates",
5212 );
5213 return;
5214 }
5215
5216 if Self::within_group_is_percentile(within_group) {
5217 if !window.over.order_by.is_empty() || window.over.frame.is_some() {
5218 Self::push_unsupported_diagnostic(
5219 diagnostics,
5220 "PERCENTILE_CONT/PERCENTILE_DISC window ORDER BY or frame clauses",
5221 );
5222 }
5223 return;
5224 }
5225 }
5226 }
5227 Expression::WithinGroup(within_group) => {
5228 if Self::within_group_is_mode(within_group) {
5229 Self::push_unsupported_diagnostic(diagnostics, "MODE ordered-set aggregates");
5230 return;
5231 }
5232
5233 if Self::within_group_is_percentile(within_group) {
5234 Self::push_unsupported_diagnostic(
5235 diagnostics,
5236 "PERCENTILE_CONT/PERCENTILE_DISC ordered-set aggregates without OVER",
5237 );
5238 return;
5239 }
5240 }
5241 _ => {}
5242 }
5243
5244 for child in expr.children() {
5245 Self::collect_tsql_unsupported_ordered_sets(child, diagnostics);
5246 }
5247 }
5248
5249 fn within_group_is_percentile(within_group: &crate::expressions::WithinGroup) -> bool {
5250 match &within_group.this {
5251 Expression::Function(function) => Self::is_percentile_ordered_set_name(&function.name),
5252 Expression::AggregateFunction(function) => {
5253 Self::is_percentile_ordered_set_name(&function.name)
5254 }
5255 Expression::PercentileCont(_) | Expression::PercentileDisc(_) => true,
5256 _ => false,
5257 }
5258 }
5259
5260 fn within_group_is_mode(within_group: &crate::expressions::WithinGroup) -> bool {
5261 match &within_group.this {
5262 Expression::Function(function) => function.name.eq_ignore_ascii_case("MODE"),
5263 Expression::AggregateFunction(function) => function.name.eq_ignore_ascii_case("MODE"),
5264 Expression::Mode(_) => true,
5265 _ => false,
5266 }
5267 }
5268
5269 fn is_percentile_ordered_set_name(name: &str) -> bool {
5270 name.eq_ignore_ascii_case("PERCENTILE_CONT") || name.eq_ignore_ascii_case("PERCENTILE_DISC")
5271 }
5272
5273 fn target_supports_distinct_on(target: DialectType) -> bool {
5274 matches!(target, DialectType::PostgreSQL | DialectType::DuckDB)
5275 }
5276
5277 fn node_has_distinct_on(expr: &Expression) -> bool {
5278 matches!(
5279 expr,
5280 Expression::Select(select)
5281 if select
5282 .distinct_on
5283 .as_ref()
5284 .is_some_and(|distinct_on| !distinct_on.is_empty())
5285 )
5286 }
5287
5288 fn node_has_recursive_with(expr: &Expression) -> bool {
5289 fn recursive(with: &Option<With>) -> bool {
5290 with.as_ref().is_some_and(|with| with.recursive)
5291 }
5292
5293 match expr {
5294 Expression::With(with) => with.recursive,
5295 Expression::Select(select) => recursive(&select.with),
5296 Expression::Union(union) => recursive(&union.with),
5297 Expression::Intersect(intersect) => recursive(&intersect.with),
5298 Expression::Except(except) => recursive(&except.with),
5299 Expression::Pivot(pivot) => recursive(&pivot.with),
5300 Expression::Insert(insert) => recursive(&insert.with),
5301 Expression::Update(update) => recursive(&update.with),
5302 Expression::Delete(delete) => recursive(&delete.with),
5303 _ => false,
5304 }
5305 }
5306
5307 fn node_has_lateral(expr: &Expression) -> bool {
5308 fn join_has_lateral(join: &Join) -> bool {
5309 matches!(
5310 join.kind,
5311 crate::expressions::JoinKind::Lateral | crate::expressions::JoinKind::LeftLateral
5312 ) || Dialect::node_has_lateral(&join.this)
5313 || join.on.as_ref().is_some_and(Dialect::node_has_lateral)
5314 || join
5315 .match_condition
5316 .as_ref()
5317 .is_some_and(Dialect::node_has_lateral)
5318 || join.pivots.iter().any(Dialect::node_has_lateral)
5319 }
5320
5321 fn joins_have_lateral(joins: &[Join]) -> bool {
5322 joins.iter().any(join_has_lateral)
5323 }
5324
5325 match expr {
5326 Expression::Subquery(subquery) => {
5327 subquery.lateral || Dialect::node_has_lateral(&subquery.this)
5328 }
5329 Expression::Lateral(_) | Expression::LateralView(_) => true,
5330 Expression::Join(join) => join_has_lateral(join),
5331 Expression::Select(select) => {
5332 !select.lateral_views.is_empty()
5333 || joins_have_lateral(&select.joins)
5334 || select
5335 .from
5336 .as_ref()
5337 .is_some_and(|from| from.expressions.iter().any(Dialect::node_has_lateral))
5338 }
5339 Expression::JoinedTable(joined) => {
5340 !joined.lateral_views.is_empty()
5341 || Dialect::node_has_lateral(&joined.left)
5342 || joins_have_lateral(&joined.joins)
5343 }
5344 Expression::Update(update) => {
5345 joins_have_lateral(&update.table_joins) || joins_have_lateral(&update.from_joins)
5346 }
5347 _ => false,
5348 }
5349 }
5350
5351 fn target_supports_remaining_unnest(target: DialectType) -> bool {
5352 matches!(
5353 target,
5354 DialectType::PostgreSQL
5355 | DialectType::BigQuery
5356 | DialectType::DuckDB
5357 | DialectType::Presto
5358 | DialectType::Trino
5359 | DialectType::Athena
5360 )
5361 }
5362
5363 fn target_supports_remaining_explode(target: DialectType) -> bool {
5364 matches!(
5365 target,
5366 DialectType::Spark | DialectType::Databricks | DialectType::Hive
5367 )
5368 }
5369
5370 fn target_lacks_array_agg(target: DialectType) -> bool {
5371 matches!(
5372 target,
5373 DialectType::Fabric
5374 | DialectType::TSQL
5375 | DialectType::MySQL
5376 | DialectType::SQLite
5377 | DialectType::Oracle
5378 )
5379 }
5380
5381 fn node_is_unnest(expr: &Expression) -> bool {
5382 matches!(expr, Expression::Unnest(_)) || Self::node_is_function_named(expr, "UNNEST")
5383 }
5384
5385 fn node_is_explode(expr: &Expression) -> bool {
5386 matches!(expr, Expression::Explode(_) | Expression::ExplodeOuter(_))
5387 || Self::node_is_function_named(expr, "EXPLODE")
5388 || Self::node_is_function_named(expr, "EXPLODE_OUTER")
5389 }
5390
5391 fn node_is_array_agg(expr: &Expression) -> bool {
5392 matches!(expr, Expression::ArrayAgg(_)) || Self::node_is_function_named(expr, "ARRAY_AGG")
5393 }
5394
5395 fn postgres_tsql_unsupported_array_semantics(expr: &Expression) -> Option<&'static str> {
5396 match expr {
5397 Expression::Array(_) | Expression::ArrayFunc(_) => Some("array literals"),
5398 Expression::Subscript(_) => Some("array subscripts"),
5399 Expression::ArraySlice(_) => Some("array slices"),
5400 Expression::DataType(DataType::Array { .. }) => Some("array data types"),
5401 Expression::ArrayLength(_) | Expression::ArraySize(_) => Some("ARRAY_LENGTH"),
5402 Expression::Cardinality(_) => Some("CARDINALITY"),
5403 Expression::ArrayToString(_) | Expression::ArrayJoin(_) => Some("ARRAY_TO_STRING"),
5404 Expression::StringToArray(_) => Some("STRING_TO_ARRAY"),
5405 Expression::ArrayContains(_)
5406 | Expression::ArrayPosition(_)
5407 | Expression::ArrayAppend(_)
5408 | Expression::ArrayPrepend(_)
5409 | Expression::ArrayConcat(_)
5410 | Expression::ArraySort(_)
5411 | Expression::ArrayReverse(_)
5412 | Expression::ArrayDistinct(_)
5413 | Expression::ArrayFilter(_)
5414 | Expression::ArrayTransform(_)
5415 | Expression::ArrayFlatten(_)
5416 | Expression::ArrayCompact(_)
5417 | Expression::ArrayIntersect(_)
5418 | Expression::ArrayUnion(_)
5419 | Expression::ArrayExcept(_)
5420 | Expression::ArrayRemove(_)
5421 | Expression::ArrayZip(_)
5422 | Expression::ArrayAll(_)
5423 | Expression::ArrayAny(_)
5424 | Expression::ArrayConstructCompact(_)
5425 | Expression::ArraySum(_) => Some("array functions"),
5426 Expression::ArrayContainsAll(_)
5427 | Expression::ArrayContainedBy(_)
5428 | Expression::ArrayOverlaps(_) => Some("array operators"),
5429 Expression::Function(function) => {
5430 Self::postgres_tsql_unsupported_array_function_name_str(&function.name)
5431 }
5432 Expression::AggregateFunction(function) => {
5433 Self::postgres_tsql_unsupported_array_function_name_str(&function.name)
5434 }
5435 _ => None,
5436 }
5437 }
5438
5439 fn postgres_tsql_unsupported_array_function_name_str(name: &str) -> Option<&'static str> {
5440 if name.eq_ignore_ascii_case("ARRAY") {
5441 Some("array literals")
5442 } else if name.eq_ignore_ascii_case("ARRAY_LENGTH")
5443 || name.eq_ignore_ascii_case("ARRAY_SIZE")
5444 {
5445 Some("ARRAY_LENGTH")
5446 } else if name.eq_ignore_ascii_case("CARDINALITY") {
5447 Some("CARDINALITY")
5448 } else if name.eq_ignore_ascii_case("ARRAY_TO_STRING")
5449 || name.eq_ignore_ascii_case("ARRAY_JOIN")
5450 {
5451 Some("ARRAY_TO_STRING")
5452 } else if name.eq_ignore_ascii_case("STRING_TO_ARRAY") {
5453 Some("STRING_TO_ARRAY")
5454 } else {
5455 None
5456 }
5457 }
5458
5459 fn node_is_regex_predicate(expr: &Expression) -> bool {
5460 matches!(
5461 expr,
5462 Expression::SimilarTo(_) | Expression::RegexpLike(_) | Expression::RegexpILike(_)
5463 ) || Self::node_is_function_named(expr, "REGEXP_LIKE")
5464 || Self::node_is_function_named(expr, "REGEXP_I_LIKE")
5465 || Self::node_is_function_named(expr, "REGEXP_ILIKE")
5466 }
5467
5468 fn node_is_non_subquery_any(expr: &Expression) -> bool {
5469 matches!(
5470 expr,
5471 Expression::Any(q) if !Self::quantified_rhs_is_subquery(&q.subquery)
5472 )
5473 }
5474
5475 fn quantified_rhs_is_subquery(expr: &Expression) -> bool {
5476 match expr {
5477 Expression::Select(_) | Expression::Subquery(_) => true,
5478 Expression::Paren(paren) => Self::quantified_rhs_is_subquery(&paren.this),
5479 _ => false,
5480 }
5481 }
5482
5483 fn node_is_row_value_subquery_comparison(expr: &Expression) -> bool {
5484 match expr {
5485 Expression::In(in_expr) => {
5486 Self::in_rhs_is_subquery_like(in_expr) && Self::expr_is_row_value(&in_expr.this)
5487 }
5488 Expression::Eq(op) | Expression::Neq(op) => {
5489 (Self::expr_is_row_value(&op.left) && Self::expr_is_subquery_like(&op.right))
5490 || (Self::expr_is_row_value(&op.right) && Self::expr_is_subquery_like(&op.left))
5491 }
5492 _ => false,
5493 }
5494 }
5495
5496 fn expr_is_row_value(expr: &Expression) -> bool {
5497 match expr {
5498 Expression::Tuple(tuple) => tuple.expressions.len() > 1,
5499 Expression::Function(function) if function.name.eq_ignore_ascii_case("ROW") => {
5500 function.args.len() > 1
5501 }
5502 Expression::Paren(paren) => Self::expr_is_row_value(&paren.this),
5503 _ => false,
5504 }
5505 }
5506
5507 fn expr_is_subquery_like(expr: &Expression) -> bool {
5508 match expr {
5509 Expression::Select(_) | Expression::Subquery(_) => true,
5510 Expression::Paren(paren) => Self::expr_is_subquery_like(&paren.this),
5511 _ => false,
5512 }
5513 }
5514
5515 fn in_rhs_is_subquery_like(in_expr: &crate::expressions::In) -> bool {
5516 if in_expr
5517 .query
5518 .as_ref()
5519 .is_some_and(Self::expr_is_subquery_like)
5520 {
5521 return true;
5522 }
5523
5524 in_expr.expressions.len() == 1 && Self::expr_is_subquery_like(&in_expr.expressions[0])
5525 }
5526
5527 fn normalize_tsql_fetch_overlaps_date_bin(expr: Expression) -> Result<Expression> {
5528 transform_recursive(expr, &|e| match e {
5529 Expression::Select(mut select) => {
5530 if select.top.is_none() && select.offset.is_none() {
5531 if let Some(fetch) = select.fetch.take() {
5532 if let Some(top) = Self::fetch_with_ties_to_top(fetch.clone()) {
5533 select.top = Some(top);
5534 } else {
5535 select.fetch = Some(fetch);
5536 }
5537 }
5538 }
5539 Self::rewrite_tsql_overlaps_in_select_predicates(&mut select)?;
5540 Ok(Expression::Select(select))
5541 }
5542 Expression::DateBin(date_bin) => {
5543 let date_bin = *date_bin;
5544 if let Some(rewritten) = Self::date_bin_to_date_bucket(date_bin.clone()) {
5545 Ok(rewritten)
5546 } else {
5547 Ok(Expression::DateBin(Box::new(date_bin)))
5548 }
5549 }
5550 Expression::Function(function) => {
5551 let function = *function;
5552 if function.name.eq_ignore_ascii_case("DATE_BIN") {
5553 if let Some(rewritten) = Self::date_bin_function_to_date_bucket(&function) {
5554 Ok(rewritten)
5555 } else {
5556 Ok(Expression::Function(Box::new(function)))
5557 }
5558 } else {
5559 Ok(Expression::Function(Box::new(function)))
5560 }
5561 }
5562 _ => Ok(e),
5563 })
5564 }
5565
5566 fn rewrite_tsql_overlaps_in_select_predicates(
5567 select: &mut crate::expressions::Select,
5568 ) -> Result<()> {
5569 if let Some(where_clause) = &mut select.where_clause {
5570 where_clause.this = Self::rewrite_tsql_overlaps_predicate(where_clause.this.clone())?;
5571 }
5572 if let Some(having) = &mut select.having {
5573 having.this = Self::rewrite_tsql_overlaps_predicate(having.this.clone())?;
5574 }
5575 if let Some(qualify) = &mut select.qualify {
5576 qualify.this = Self::rewrite_tsql_overlaps_predicate(qualify.this.clone())?;
5577 }
5578 for join in &mut select.joins {
5579 if let Some(on) = join.on.take() {
5580 join.on = Some(Self::rewrite_tsql_overlaps_predicate(on)?);
5581 }
5582 if let Some(match_condition) = join.match_condition.take() {
5583 join.match_condition =
5584 Some(Self::rewrite_tsql_overlaps_predicate(match_condition)?);
5585 }
5586 }
5587 Ok(())
5588 }
5589
5590 fn rewrite_tsql_overlaps_predicate(expr: Expression) -> Result<Expression> {
5591 transform_recursive(expr, &|e| match e {
5592 Expression::Overlaps(overlaps) => {
5593 let overlaps = *overlaps;
5594 if let Some(rewritten) = Self::rewrite_full_overlaps_for_tsql(&overlaps) {
5595 Ok(rewritten)
5596 } else {
5597 Ok(Expression::Overlaps(Box::new(overlaps)))
5598 }
5599 }
5600 _ => Ok(e),
5601 })
5602 }
5603
5604 fn fetch_with_ties_to_top(fetch: Fetch) -> Option<Top> {
5605 if !fetch.with_ties {
5606 return None;
5607 }
5608
5609 fetch.count.map(|count| Top {
5610 this: count,
5611 percent: fetch.percent,
5612 with_ties: true,
5613 parenthesized: true,
5614 })
5615 }
5616
5617 fn rewrite_full_overlaps_for_tsql(
5618 overlaps: &crate::expressions::OverlapsExpr,
5619 ) -> Option<Expression> {
5620 let (left_start, left_end, right_start, right_end) =
5621 if let (Some(left_start), Some(left_end), Some(right_start), Some(right_end)) = (
5622 overlaps.left_start.as_ref(),
5623 overlaps.left_end.as_ref(),
5624 overlaps.right_start.as_ref(),
5625 overlaps.right_end.as_ref(),
5626 ) {
5627 (left_start, left_end, right_start, right_end)
5628 } else if let (
5629 Some(Expression::Tuple(left_tuple)),
5630 Some(Expression::Tuple(right_tuple)),
5631 ) = (&overlaps.this, &overlaps.expression)
5632 {
5633 if left_tuple.expressions.len() != 2 || right_tuple.expressions.len() != 2 {
5634 return None;
5635 }
5636 (
5637 &left_tuple.expressions[0],
5638 &left_tuple.expressions[1],
5639 &right_tuple.expressions[0],
5640 &right_tuple.expressions[1],
5641 )
5642 } else {
5643 return None;
5644 };
5645
5646 let left_min = Self::case_min(left_start.clone(), left_end.clone());
5647 let left_max = Self::case_max(left_start.clone(), left_end.clone());
5648 let right_min = Self::case_min(right_start.clone(), right_end.clone());
5649 let right_max = Self::case_max(right_start.clone(), right_end.clone());
5650
5651 Some(Expression::And(Box::new(BinaryOp::new(
5652 Expression::Lte(Box::new(BinaryOp::new(left_min, right_max))),
5653 Expression::Lte(Box::new(BinaryOp::new(right_min, left_max))),
5654 ))))
5655 }
5656
5657 fn case_min(left: Expression, right: Expression) -> Expression {
5658 Expression::Case(Box::new(Case {
5659 operand: None,
5660 whens: vec![(
5661 Expression::Lte(Box::new(BinaryOp::new(left.clone(), right.clone()))),
5662 left,
5663 )],
5664 else_: Some(right),
5665 comments: Vec::new(),
5666 inferred_type: None,
5667 }))
5668 }
5669
5670 fn case_max(left: Expression, right: Expression) -> Expression {
5671 Expression::Case(Box::new(Case {
5672 operand: None,
5673 whens: vec![(
5674 Expression::Gte(Box::new(BinaryOp::new(left.clone(), right.clone()))),
5675 left,
5676 )],
5677 else_: Some(right),
5678 comments: Vec::new(),
5679 inferred_type: None,
5680 }))
5681 }
5682
5683 fn date_bin_to_date_bucket(date_bin: DateBin) -> Option<Expression> {
5684 if date_bin.unit.is_some() || date_bin.zone.is_some() {
5685 return None;
5686 }
5687
5688 let (datepart, number) = Self::date_bucket_parts(&date_bin.this)?;
5689 let mut args = vec![
5690 Self::date_bucket_datepart(datepart),
5691 number,
5692 *date_bin.expression,
5693 ];
5694 if let Some(origin) = date_bin.origin {
5695 args.push(*origin);
5696 }
5697
5698 Some(Expression::Function(Box::new(Function::new(
5699 "DATE_BUCKET".to_string(),
5700 args,
5701 ))))
5702 }
5703
5704 fn date_bin_function_to_date_bucket(function: &Function) -> Option<Expression> {
5705 if !(2..=3).contains(&function.args.len()) {
5706 return None;
5707 }
5708
5709 let (datepart, number) = Self::date_bucket_parts(&function.args[0])?;
5710 let mut args = vec![
5711 Self::date_bucket_datepart(datepart),
5712 number,
5713 function.args[1].clone(),
5714 ];
5715 if let Some(origin) = function.args.get(2) {
5716 args.push(origin.clone());
5717 }
5718
5719 Some(Expression::Function(Box::new(Function::new(
5720 "DATE_BUCKET".to_string(),
5721 args,
5722 ))))
5723 }
5724
5725 fn date_bucket_parts(stride: &Expression) -> Option<(&'static str, Expression)> {
5726 match stride {
5727 Expression::Literal(lit) => match lit.as_ref() {
5728 Literal::String(value) => Self::date_bucket_parts_from_string(value),
5729 _ => None,
5730 },
5731 Expression::Interval(interval) => Self::date_bucket_parts_from_interval(interval),
5732 _ => None,
5733 }
5734 }
5735
5736 fn date_bucket_parts_from_interval(interval: &Interval) -> Option<(&'static str, Expression)> {
5737 match &interval.unit {
5738 Some(IntervalUnitSpec::Simple { unit, .. }) => {
5739 let datepart = Self::date_bucket_datepart_from_unit(*unit)?;
5740 let amount = interval
5741 .this
5742 .as_ref()
5743 .and_then(Self::date_bucket_amount_expr)?;
5744 Some((datepart, amount))
5745 }
5746 None => interval.this.as_ref().and_then(|expr| match expr {
5747 Expression::Literal(lit) => match lit.as_ref() {
5748 Literal::String(value) => Self::date_bucket_parts_from_string(value),
5749 _ => None,
5750 },
5751 _ => None,
5752 }),
5753 _ => None,
5754 }
5755 }
5756
5757 fn date_bucket_parts_from_string(value: &str) -> Option<(&'static str, Expression)> {
5758 let mut parts = value.split_whitespace();
5759 let amount = parts.next()?;
5760 let unit = parts.next()?;
5761 if parts.next().is_some() {
5762 return None;
5763 }
5764
5765 Some((
5766 Self::date_bucket_datepart_from_name(unit)?,
5767 Self::positive_integer_expr(amount)?,
5768 ))
5769 }
5770
5771 fn date_bucket_amount_expr(expr: &Expression) -> Option<Expression> {
5772 match expr {
5773 Expression::Literal(lit) => match lit.as_ref() {
5774 Literal::Number(value) => Self::positive_integer_expr(value),
5775 Literal::String(value) => Self::positive_integer_expr(value),
5776 _ => None,
5777 },
5778 _ => Some(expr.clone()),
5779 }
5780 }
5781
5782 fn positive_integer_expr(value: &str) -> Option<Expression> {
5783 let parsed = value.trim().parse::<i64>().ok()?;
5784 (parsed > 0).then(|| Expression::number(parsed))
5785 }
5786
5787 fn date_bucket_datepart(datepart: &str) -> Expression {
5788 Expression::Var(Box::new(Var {
5789 this: datepart.to_string(),
5790 }))
5791 }
5792
5793 fn date_bucket_datepart_from_unit(unit: IntervalUnit) -> Option<&'static str> {
5794 match unit {
5795 IntervalUnit::Week => Some("WEEK"),
5796 IntervalUnit::Day => Some("DAY"),
5797 IntervalUnit::Hour => Some("HOUR"),
5798 IntervalUnit::Minute => Some("MINUTE"),
5799 IntervalUnit::Second => Some("SECOND"),
5800 IntervalUnit::Millisecond => Some("MILLISECOND"),
5801 _ => None,
5802 }
5803 }
5804
5805 fn date_bucket_datepart_from_name(unit: &str) -> Option<&'static str> {
5806 match unit.trim().to_ascii_uppercase().as_str() {
5807 "WEEK" | "WEEKS" | "W" | "WK" | "WKS" | "WW" => Some("WEEK"),
5808 "DAY" | "DAYS" | "D" | "DD" => Some("DAY"),
5809 "HOUR" | "HOURS" | "H" | "HH" | "HR" | "HRS" => Some("HOUR"),
5810 "MINUTE" | "MINUTES" | "MI" | "MIN" | "MINS" | "N" => Some("MINUTE"),
5811 "SECOND" | "SECONDS" | "S" | "SEC" | "SECS" | "SS" => Some("SECOND"),
5812 "MILLISECOND" | "MILLISECONDS" | "MS" | "MSEC" | "MSECS" | "MILLISEC" | "MILLISECS" => {
5813 Some("MILLISECOND")
5814 }
5815 _ => None,
5816 }
5817 }
5818
5819 fn node_has_fetch_with_ties(expr: &Expression) -> bool {
5820 matches!(
5821 expr,
5822 Expression::Select(select)
5823 if select
5824 .fetch
5825 .as_ref()
5826 .is_some_and(|fetch| fetch.with_ties)
5827 )
5828 }
5829
5830 fn node_is_overlaps(expr: &Expression) -> bool {
5831 matches!(expr, Expression::Overlaps(_))
5832 }
5833
5834 fn node_is_date_bin(expr: &Expression) -> bool {
5835 matches!(expr, Expression::DateBin(_)) || Self::node_is_function_named(expr, "DATE_BIN")
5836 }
5837
5838 fn node_is_function_named(expr: &Expression, name: &str) -> bool {
5839 match expr {
5840 Expression::Function(function) => function.name.eq_ignore_ascii_case(name),
5841 Expression::AggregateFunction(function) => function.name.eq_ignore_ascii_case(name),
5842 _ => false,
5843 }
5844 }
5845
5846 fn node_is_postgres_json_build_object(expr: &Expression) -> bool {
5847 match expr {
5848 Expression::Function(function) => {
5849 function.name.eq_ignore_ascii_case("JSON_BUILD_OBJECT")
5850 || function.name.eq_ignore_ascii_case("JSONB_BUILD_OBJECT")
5851 }
5852 _ => false,
5853 }
5854 }
5855
5856 fn postgres_json_build_object_can_lower_to_json_object(expr: &Expression) -> bool {
5857 matches!(
5858 expr,
5859 Expression::Function(function)
5860 if (function.name.eq_ignore_ascii_case("JSON_BUILD_OBJECT")
5861 || function.name.eq_ignore_ascii_case("JSONB_BUILD_OBJECT"))
5862 && !function.distinct
5863 && function.args.len() % 2 == 0
5864 )
5865 }
5866
5867 fn node_is_postgres_json_array_elements(expr: &Expression) -> bool {
5868 matches!(
5869 expr,
5870 Expression::Function(function)
5871 if function.name.eq_ignore_ascii_case("JSON_ARRAY_ELEMENTS")
5872 || function.name.eq_ignore_ascii_case("JSONB_ARRAY_ELEMENTS")
5873 || function.name.eq_ignore_ascii_case("JSON_ARRAY_ELEMENTS_TEXT")
5874 || function.name.eq_ignore_ascii_case("JSONB_ARRAY_ELEMENTS_TEXT")
5875 )
5876 }
5877
5878 fn postgres_tsql_unsupported_function_name(expr: &Expression) -> Option<&'static str> {
5879 match expr {
5880 Expression::Lpad(_) => Some("LPAD"),
5881 Expression::Rpad(_) => Some("RPAD"),
5882 Expression::SplitPart(_) => Some("SPLIT_PART"),
5883 Expression::Initcap(_) => Some("INITCAP"),
5884 Expression::ToJson(_) => Some("TO_JSON"),
5885 Expression::JSONBObjectAgg(_) => Some("JSONB_OBJECT_AGG"),
5886 Expression::ToNumber(_) => Some("TO_NUMBER"),
5887 Expression::WidthBucket(_) => Some("WIDTH_BUCKET"),
5888 Expression::BitwiseAndAgg(_) => Some("BIT_AND"),
5889 Expression::BitwiseOrAgg(_) => Some("BIT_OR"),
5890 Expression::BitwiseXorAgg(_) => Some("BIT_XOR"),
5891 Expression::Corr(_) => Some("CORR"),
5892 Expression::CovarPop(_) => Some("COVAR_POP"),
5893 Expression::CovarSamp(_) => Some("COVAR_SAMP"),
5894 Expression::RegrAvgx(_) => Some("REGR_AVGX"),
5895 Expression::RegrAvgy(_) => Some("REGR_AVGY"),
5896 Expression::RegrCount(_) => Some("REGR_COUNT"),
5897 Expression::RegrIntercept(_) => Some("REGR_INTERCEPT"),
5898 Expression::RegrR2(_) => Some("REGR_R2"),
5899 Expression::RegrSlope(_) => Some("REGR_SLOPE"),
5900 Expression::RegrSxx(_) => Some("REGR_SXX"),
5901 Expression::RegrSxy(_) => Some("REGR_SXY"),
5902 Expression::RegrSyy(_) => Some("REGR_SYY"),
5903 Expression::Function(function) => {
5904 Self::postgres_tsql_unsupported_function_name_str(&function.name)
5905 }
5906 Expression::AggregateFunction(function) => {
5907 Self::postgres_tsql_unsupported_function_name_str(&function.name)
5908 }
5909 _ => None,
5910 }
5911 }
5912
5913 fn postgres_tsql_unsupported_function_name_str(name: &str) -> Option<&'static str> {
5914 if name.eq_ignore_ascii_case("LPAD") {
5915 Some("LPAD")
5916 } else if name.eq_ignore_ascii_case("RPAD") {
5917 Some("RPAD")
5918 } else if name.eq_ignore_ascii_case("SPLIT_PART") {
5919 Some("SPLIT_PART")
5920 } else if name.eq_ignore_ascii_case("INITCAP") {
5921 Some("INITCAP")
5922 } else if name.eq_ignore_ascii_case("TO_JSON") {
5923 Some("TO_JSON")
5924 } else if name.eq_ignore_ascii_case("TO_JSONB") {
5925 Some("TO_JSONB")
5926 } else if name.eq_ignore_ascii_case("JSONB_OBJECT_AGG") {
5927 Some("JSONB_OBJECT_AGG")
5928 } else if name.eq_ignore_ascii_case("ROW_TO_JSON") {
5929 Some("ROW_TO_JSON")
5930 } else if name.eq_ignore_ascii_case("JSON_ARRAY_ELEMENTS") {
5931 Some("JSON_ARRAY_ELEMENTS")
5932 } else if name.eq_ignore_ascii_case("JSONB_ARRAY_ELEMENTS") {
5933 Some("JSONB_ARRAY_ELEMENTS")
5934 } else if name.eq_ignore_ascii_case("JSON_ARRAY_ELEMENTS_TEXT") {
5935 Some("JSON_ARRAY_ELEMENTS_TEXT")
5936 } else if name.eq_ignore_ascii_case("JSONB_ARRAY_ELEMENTS_TEXT") {
5937 Some("JSONB_ARRAY_ELEMENTS_TEXT")
5938 } else if name.eq_ignore_ascii_case("ENCODE") {
5939 Some("ENCODE")
5940 } else if name.eq_ignore_ascii_case("AGE") {
5941 Some("AGE")
5942 } else if name.eq_ignore_ascii_case("ERF") {
5943 Some("ERF")
5944 } else if name.eq_ignore_ascii_case("GCD") {
5945 Some("GCD")
5946 } else if name.eq_ignore_ascii_case("LCM") {
5947 Some("LCM")
5948 } else if name.eq_ignore_ascii_case("QUOTE_LITERAL") {
5949 Some("QUOTE_LITERAL")
5950 } else if name.eq_ignore_ascii_case("WIDTH_BUCKET") {
5951 Some("WIDTH_BUCKET")
5952 } else if name.eq_ignore_ascii_case("PG_TYPEOF") {
5953 Some("PG_TYPEOF")
5954 } else if name.eq_ignore_ascii_case("BIT_AND") {
5955 Some("BIT_AND")
5956 } else if name.eq_ignore_ascii_case("BIT_OR") {
5957 Some("BIT_OR")
5958 } else if name.eq_ignore_ascii_case("BIT_XOR") {
5959 Some("BIT_XOR")
5960 } else if name.eq_ignore_ascii_case("CORR") {
5961 Some("CORR")
5962 } else if name.eq_ignore_ascii_case("COVAR_POP") {
5963 Some("COVAR_POP")
5964 } else if name.eq_ignore_ascii_case("COVAR_SAMP") {
5965 Some("COVAR_SAMP")
5966 } else if name.eq_ignore_ascii_case("REGR_AVGX") {
5967 Some("REGR_AVGX")
5968 } else if name.eq_ignore_ascii_case("REGR_AVGY") {
5969 Some("REGR_AVGY")
5970 } else if name.eq_ignore_ascii_case("REGR_COUNT") {
5971 Some("REGR_COUNT")
5972 } else if name.eq_ignore_ascii_case("REGR_INTERCEPT") {
5973 Some("REGR_INTERCEPT")
5974 } else if name.eq_ignore_ascii_case("REGR_R2") {
5975 Some("REGR_R2")
5976 } else if name.eq_ignore_ascii_case("REGR_SLOPE") {
5977 Some("REGR_SLOPE")
5978 } else if name.eq_ignore_ascii_case("REGR_SXX") {
5979 Some("REGR_SXX")
5980 } else if name.eq_ignore_ascii_case("REGR_SXY") {
5981 Some("REGR_SXY")
5982 } else if name.eq_ignore_ascii_case("REGR_SYY") {
5983 Some("REGR_SYY")
5984 } else {
5985 None
5986 }
5987 }
5988
5989 fn normalize_postgres_trim_for_tsql(expr: Expression) -> Result<Expression> {
5990 transform_recursive(expr, &|e| match e {
5991 Expression::Trim(trim) => {
5992 let mut trim = *trim;
5993 match trim.position {
5994 crate::expressions::TrimPosition::Both
5995 if trim.position_explicit && trim.characters.is_some() =>
5996 {
5997 trim.position_explicit = false;
5998 trim.sql_standard_syntax = true;
5999 Ok(Expression::Trim(Box::new(trim)))
6000 }
6001 crate::expressions::TrimPosition::Leading if trim.characters.is_some() => {
6002 let characters = trim.characters.take().expect("checked above");
6003 Ok(Expression::Function(Box::new(Function::new(
6004 "LTRIM",
6005 vec![trim.this, characters],
6006 ))))
6007 }
6008 crate::expressions::TrimPosition::Trailing if trim.characters.is_some() => {
6009 let characters = trim.characters.take().expect("checked above");
6010 Ok(Expression::Function(Box::new(Function::new(
6011 "RTRIM",
6012 vec![trim.this, characters],
6013 ))))
6014 }
6015 _ => Ok(Expression::Trim(Box::new(trim))),
6016 }
6017 }
6018 other => Ok(other),
6019 })
6020 }
6021
6022 fn rewrite_postgres_json_array_elements_select_for_tsql(
6023 expr: Expression,
6024 ) -> Result<Expression> {
6025 let Expression::Select(select) = expr else {
6026 return Ok(expr);
6027 };
6028 let mut select = *select;
6029 if !Self::is_plain_single_projection_select(&select) {
6030 return Ok(Expression::Select(Box::new(select)));
6031 }
6032
6033 let Some(json_arg) =
6034 Self::postgres_json_array_elements_projection_arg(&select.expressions[0])
6035 else {
6036 return Ok(Expression::Select(Box::new(select)));
6037 };
6038
6039 select.expressions = vec![Expression::column("value")];
6040 select.from = Some(From {
6041 expressions: vec![Expression::OpenJSON(Box::new(
6042 crate::expressions::OpenJSON {
6043 this: Box::new(json_arg),
6044 path: None,
6045 expressions: Vec::new(),
6046 },
6047 ))],
6048 });
6049
6050 Ok(Expression::Select(Box::new(select)))
6051 }
6052
6053 fn is_plain_single_projection_select(select: &crate::expressions::Select) -> bool {
6054 select.expressions.len() == 1
6055 && select.from.is_none()
6056 && select.joins.is_empty()
6057 && select.lateral_views.is_empty()
6058 && select.prewhere.is_none()
6059 && select.where_clause.is_none()
6060 && select.group_by.is_none()
6061 && select.having.is_none()
6062 && select.qualify.is_none()
6063 && select.order_by.is_none()
6064 && select.distribute_by.is_none()
6065 && select.cluster_by.is_none()
6066 && select.sort_by.is_none()
6067 && select.limit.is_none()
6068 && select.offset.is_none()
6069 && select.limit_by.is_none()
6070 && select.fetch.is_none()
6071 && !select.distinct
6072 && select.distinct_on.is_none()
6073 && select.top.is_none()
6074 && select.with.is_none()
6075 && select.sample.is_none()
6076 && select.into.is_none()
6077 && select.locks.is_empty()
6078 && select.for_xml.is_empty()
6079 && select.for_json.is_empty()
6080 && select.exclude.is_none()
6081 }
6082
6083 fn postgres_json_array_elements_projection_arg(expr: &Expression) -> Option<Expression> {
6084 match expr {
6085 Expression::Function(function)
6086 if Self::node_is_postgres_json_array_elements(expr) && function.args.len() == 1 =>
6087 {
6088 Some(function.args[0].clone())
6089 }
6090 Expression::Alias(alias) => {
6091 Self::postgres_json_array_elements_projection_arg(&alias.this)
6092 }
6093 _ => None,
6094 }
6095 }
6096
6097 fn normalize_postgres_type_function_casts(expr: Expression) -> Result<Expression> {
6098 transform_recursive(expr, &|e| match e {
6099 Expression::Function(function) => {
6100 let mut function = *function;
6101 if function.args.len() == 1
6102 && !function.distinct
6103 && !function.quoted
6104 && !function.use_bracket_syntax
6105 && !function.name.contains('.')
6106 {
6107 if let Some(to) = Self::postgres_type_function_data_type(&function.name) {
6108 let this = function.args.remove(0);
6109 return Ok(Expression::Cast(Box::new(Cast {
6110 this,
6111 to,
6112 trailing_comments: function.trailing_comments,
6113 double_colon_syntax: false,
6114 format: None,
6115 default: None,
6116 inferred_type: function.inferred_type,
6117 })));
6118 }
6119 }
6120 Ok(Expression::Function(Box::new(function)))
6121 }
6122 _ => Ok(e),
6123 })
6124 }
6125
6126 fn node_is_postgres_type_function_cast(expr: &Expression) -> bool {
6127 matches!(
6128 expr,
6129 Expression::Function(function)
6130 if !function.quoted
6131 && !function.use_bracket_syntax
6132 && !function.name.contains('.')
6133 && Self::postgres_type_function_data_type(&function.name).is_some()
6134 )
6135 }
6136
6137 fn postgres_type_function_data_type(name: &str) -> Option<DataType> {
6138 match name.to_ascii_uppercase().as_str() {
6139 "NUMERIC" | "DECIMAL" | "DEC" => Some(DataType::Decimal {
6140 precision: None,
6141 scale: None,
6142 }),
6143 "INT2" | "SMALLINT" => Some(DataType::SmallInt { length: None }),
6144 "INT4" | "INT" => Some(DataType::Int {
6145 length: None,
6146 integer_spelling: false,
6147 }),
6148 "INTEGER" => Some(DataType::Int {
6149 length: None,
6150 integer_spelling: true,
6151 }),
6152 "INT8" | "BIGINT" => Some(DataType::BigInt { length: None }),
6153 "FLOAT4" | "REAL" => Some(DataType::Float {
6154 precision: None,
6155 scale: None,
6156 real_spelling: true,
6157 }),
6158 "FLOAT8" => Some(DataType::Double {
6159 precision: None,
6160 scale: None,
6161 }),
6162 "BOOL" | "BOOLEAN" => Some(DataType::Boolean),
6163 "TEXT" => Some(DataType::Text),
6164 "VARCHAR" => Some(DataType::VarChar {
6165 length: None,
6166 parenthesized_length: false,
6167 }),
6168 "UUID" => Some(DataType::Uuid),
6169 _ => None,
6170 }
6171 }
6172
6173 fn rewrite_boolean_values_for_tsql(expr: Expression) -> Result<Expression> {
6174 match expr {
6175 Expression::Select(select) => Self::rewrite_boolean_values_in_tsql_select(select),
6176 Expression::Subquery(mut subquery) => {
6177 subquery.this = Self::rewrite_boolean_values_for_tsql(subquery.this)?;
6178 Ok(Expression::Subquery(subquery))
6179 }
6180 Expression::Union(mut union) => {
6181 let left = std::mem::replace(&mut union.left, Expression::null());
6182 let right = std::mem::replace(&mut union.right, Expression::null());
6183 union.left = Self::rewrite_boolean_values_for_tsql(left)?;
6184 union.right = Self::rewrite_boolean_values_for_tsql(right)?;
6185 if let Some(mut with) = union.with.take() {
6186 with.ctes = with
6187 .ctes
6188 .into_iter()
6189 .map(|mut cte| {
6190 cte.this = Self::rewrite_boolean_values_for_tsql(cte.this)?;
6191 Ok(cte)
6192 })
6193 .collect::<Result<Vec<_>>>()?;
6194 union.with = Some(with);
6195 }
6196 Ok(Expression::Union(union))
6197 }
6198 Expression::Intersect(mut intersect) => {
6199 let left = std::mem::replace(&mut intersect.left, Expression::null());
6200 let right = std::mem::replace(&mut intersect.right, Expression::null());
6201 intersect.left = Self::rewrite_boolean_values_for_tsql(left)?;
6202 intersect.right = Self::rewrite_boolean_values_for_tsql(right)?;
6203 Ok(Expression::Intersect(intersect))
6204 }
6205 Expression::Except(mut except) => {
6206 let left = std::mem::replace(&mut except.left, Expression::null());
6207 let right = std::mem::replace(&mut except.right, Expression::null());
6208 except.left = Self::rewrite_boolean_values_for_tsql(left)?;
6209 except.right = Self::rewrite_boolean_values_for_tsql(right)?;
6210 Ok(Expression::Except(except))
6211 }
6212 other => Self::rewrite_tsql_boolean_embedded_queries(other),
6213 }
6214 }
6215
6216 fn rewrite_postgres_format_for_tsql(
6217 expr: Expression,
6218 target: DialectType,
6219 ) -> Result<Expression> {
6220 transform_recursive(expr, &|e| match e {
6221 Expression::Function(f) if f.name.eq_ignore_ascii_case("FORMAT") => {
6222 Self::postgres_format_function_to_tsql(*f, target)
6223 }
6224 other => Ok(other),
6225 })
6226 }
6227
6228 fn postgres_format_function_to_tsql(f: Function, target: DialectType) -> Result<Expression> {
6229 let Some(format_expr) = f.args.first() else {
6230 return Err(Self::unsupported_postgres_format_for_tsql(
6231 target,
6232 "missing format string",
6233 ));
6234 };
6235
6236 let format = match format_expr {
6237 Expression::Literal(lit) if lit.is_string() => lit.value_str(),
6238 _ => {
6239 return Err(Self::unsupported_postgres_format_for_tsql(
6240 target,
6241 "dynamic format strings",
6242 ))
6243 }
6244 };
6245
6246 let value_args = &f.args[1..];
6247 let mut arg_index = 0usize;
6248 let mut literal = String::new();
6249 let mut segments = Vec::new();
6250 let mut chars = format.chars();
6251
6252 while let Some(ch) = chars.next() {
6253 if ch != '%' {
6254 literal.push(ch);
6255 continue;
6256 }
6257
6258 let Some(specifier) = chars.next() else {
6259 return Err(Self::unsupported_postgres_format_for_tsql(
6260 target,
6261 "unterminated format specifier",
6262 ));
6263 };
6264
6265 match specifier {
6266 '%' => literal.push('%'),
6267 's' => {
6268 if !literal.is_empty() {
6269 segments.push(Expression::string(std::mem::take(&mut literal)));
6270 }
6271 let Some(arg) = value_args.get(arg_index) else {
6272 return Err(Self::unsupported_postgres_format_for_tsql(
6273 target,
6274 "not enough arguments",
6275 ));
6276 };
6277 segments.push(arg.clone());
6278 arg_index += 1;
6279 }
6280 other => {
6281 return Err(Self::unsupported_postgres_format_for_tsql(
6282 target,
6283 format!("unsupported format specifier %{other}"),
6284 ))
6285 }
6286 }
6287 }
6288
6289 if !literal.is_empty() {
6290 segments.push(Expression::string(literal));
6291 }
6292
6293 if arg_index != value_args.len() {
6294 return Err(Self::unsupported_postgres_format_for_tsql(
6295 target,
6296 "unused format arguments",
6297 ));
6298 }
6299
6300 Ok(Self::postgres_format_segments_to_tsql_concat(segments))
6301 }
6302
6303 fn postgres_format_segments_to_tsql_concat(mut segments: Vec<Expression>) -> Expression {
6304 if segments.is_empty() {
6305 return Expression::string("");
6306 }
6307
6308 if segments.len() == 1 {
6309 let only = segments.pop().expect("one segment");
6310 if matches!(&only, Expression::Literal(lit) if lit.is_string()) {
6311 return only;
6312 }
6313
6314 return Expression::Function(Box::new(Function::new(
6315 "CONCAT".to_string(),
6316 vec![only, Expression::string("")],
6317 )));
6318 }
6319
6320 Expression::Function(Box::new(Function::new("CONCAT".to_string(), segments)))
6321 }
6322
6323 fn unsupported_postgres_format_for_tsql(
6324 target: DialectType,
6325 reason: impl Into<String>,
6326 ) -> crate::error::Error {
6327 crate::error::Error::unsupported(
6328 format!("PostgreSQL format() ({})", reason.into()),
6329 target.to_string(),
6330 )
6331 }
6332
6333 fn rewrite_boolean_values_in_tsql_select(
6334 mut select: Box<crate::expressions::Select>,
6335 ) -> Result<Expression> {
6336 if let Some(mut with) = select.with.take() {
6337 with.ctes = with
6338 .ctes
6339 .into_iter()
6340 .map(|mut cte| {
6341 cte.this = Self::rewrite_boolean_values_for_tsql(cte.this)?;
6342 Ok(cte)
6343 })
6344 .collect::<Result<Vec<_>>>()?;
6345 select.with = Some(with);
6346 }
6347
6348 select.expressions = select
6349 .expressions
6350 .into_iter()
6351 .map(Self::rewrite_tsql_boolean_scalar_value)
6352 .collect::<Result<Vec<_>>>()?;
6353
6354 if let Some(mut from) = select.from.take() {
6355 from.expressions = from
6356 .expressions
6357 .into_iter()
6358 .map(Self::rewrite_tsql_boolean_embedded_queries)
6359 .collect::<Result<Vec<_>>>()?;
6360 select.from = Some(from);
6361 }
6362
6363 select.joins = select
6364 .joins
6365 .into_iter()
6366 .map(|mut join| {
6367 join.this = Self::rewrite_tsql_boolean_embedded_queries(join.this)?;
6368 if let Some(on) = join.on.take() {
6369 join.on = Some(Self::rewrite_tsql_boolean_predicate_context(on)?);
6370 }
6371 if let Some(match_condition) = join.match_condition.take() {
6372 join.match_condition = Some(Self::rewrite_tsql_boolean_predicate_context(
6373 match_condition,
6374 )?);
6375 }
6376 join.pivots = join
6377 .pivots
6378 .into_iter()
6379 .map(Self::rewrite_tsql_boolean_embedded_queries)
6380 .collect::<Result<Vec<_>>>()?;
6381 Ok(join)
6382 })
6383 .collect::<Result<Vec<_>>>()?;
6384
6385 select.lateral_views = select
6386 .lateral_views
6387 .into_iter()
6388 .map(|mut lateral_view| {
6389 lateral_view.this = Self::rewrite_tsql_boolean_embedded_queries(lateral_view.this)?;
6390 Ok(lateral_view)
6391 })
6392 .collect::<Result<Vec<_>>>()?;
6393
6394 if let Some(prewhere) = select.prewhere.take() {
6395 select.prewhere = Some(Self::rewrite_tsql_boolean_predicate_context(prewhere)?);
6396 }
6397
6398 if let Some(mut where_clause) = select.where_clause.take() {
6399 where_clause.this = Self::rewrite_tsql_boolean_predicate_context(where_clause.this)?;
6400 select.where_clause = Some(where_clause);
6401 }
6402
6403 if let Some(mut group_by) = select.group_by.take() {
6404 group_by.expressions = group_by
6405 .expressions
6406 .into_iter()
6407 .map(Self::rewrite_tsql_boolean_scalar_value)
6408 .collect::<Result<Vec<_>>>()?;
6409 select.group_by = Some(group_by);
6410 }
6411
6412 if let Some(mut having) = select.having.take() {
6413 having.this = Self::rewrite_tsql_boolean_predicate_context(having.this)?;
6414 select.having = Some(having);
6415 }
6416
6417 if let Some(mut qualify) = select.qualify.take() {
6418 qualify.this = Self::rewrite_tsql_boolean_predicate_context(qualify.this)?;
6419 select.qualify = Some(qualify);
6420 }
6421
6422 if let Some(mut order_by) = select.order_by.take() {
6423 order_by.expressions = Self::rewrite_tsql_boolean_ordered_values(order_by.expressions)?;
6424 select.order_by = Some(order_by);
6425 }
6426
6427 if let Some(mut distribute_by) = select.distribute_by.take() {
6428 distribute_by.expressions = distribute_by
6429 .expressions
6430 .into_iter()
6431 .map(Self::rewrite_tsql_boolean_scalar_value)
6432 .collect::<Result<Vec<_>>>()?;
6433 select.distribute_by = Some(distribute_by);
6434 }
6435
6436 if let Some(mut cluster_by) = select.cluster_by.take() {
6437 cluster_by.expressions =
6438 Self::rewrite_tsql_boolean_ordered_values(cluster_by.expressions)?;
6439 select.cluster_by = Some(cluster_by);
6440 }
6441
6442 if let Some(mut sort_by) = select.sort_by.take() {
6443 sort_by.expressions = Self::rewrite_tsql_boolean_ordered_values(sort_by.expressions)?;
6444 select.sort_by = Some(sort_by);
6445 }
6446
6447 if let Some(limit_by) = select.limit_by.take() {
6448 select.limit_by = Some(
6449 limit_by
6450 .into_iter()
6451 .map(Self::rewrite_tsql_boolean_scalar_value)
6452 .collect::<Result<Vec<_>>>()?,
6453 );
6454 }
6455
6456 if let Some(distinct_on) = select.distinct_on.take() {
6457 select.distinct_on = Some(
6458 distinct_on
6459 .into_iter()
6460 .map(Self::rewrite_tsql_boolean_scalar_value)
6461 .collect::<Result<Vec<_>>>()?,
6462 );
6463 }
6464
6465 if let Some(mut sample) = select.sample.take() {
6466 sample.size = Self::rewrite_tsql_boolean_embedded_queries(sample.size)?;
6467 if let Some(offset) = sample.offset.take() {
6468 sample.offset = Some(Self::rewrite_tsql_boolean_embedded_queries(offset)?);
6469 }
6470 if let Some(bucket_numerator) = sample.bucket_numerator.take() {
6471 sample.bucket_numerator = Some(Box::new(
6472 Self::rewrite_tsql_boolean_embedded_queries(*bucket_numerator)?,
6473 ));
6474 }
6475 if let Some(bucket_denominator) = sample.bucket_denominator.take() {
6476 sample.bucket_denominator = Some(Box::new(
6477 Self::rewrite_tsql_boolean_embedded_queries(*bucket_denominator)?,
6478 ));
6479 }
6480 if let Some(bucket_field) = sample.bucket_field.take() {
6481 sample.bucket_field = Some(Box::new(Self::rewrite_tsql_boolean_embedded_queries(
6482 *bucket_field,
6483 )?));
6484 }
6485 select.sample = Some(sample);
6486 }
6487
6488 if let Some(settings) = select.settings.take() {
6489 select.settings = Some(
6490 settings
6491 .into_iter()
6492 .map(Self::rewrite_tsql_boolean_embedded_queries)
6493 .collect::<Result<Vec<_>>>()?,
6494 );
6495 }
6496
6497 if let Some(format) = select.format.take() {
6498 select.format = Some(Self::rewrite_tsql_boolean_embedded_queries(format)?);
6499 }
6500
6501 if let Some(mut windows) = select.windows.take() {
6502 for window in windows.iter_mut() {
6503 Self::rewrite_tsql_boolean_over_values(&mut window.spec)?;
6504 }
6505 select.windows = Some(windows);
6506 }
6507
6508 Ok(Expression::Select(select))
6509 }
6510
6511 fn rewrite_tsql_boolean_scalar_value(expr: Expression) -> Result<Expression> {
6512 if Self::is_tsql_boolean_value_expression(&expr) {
6513 return Ok(Self::tsql_boolean_value_case(expr));
6514 }
6515
6516 match expr {
6517 Expression::Alias(mut alias) => {
6518 alias.this = Self::rewrite_tsql_boolean_scalar_value(alias.this)?;
6519 Ok(Expression::Alias(alias))
6520 }
6521 Expression::Paren(mut paren) => {
6522 paren.this = Self::rewrite_tsql_boolean_scalar_value(paren.this)?;
6523 Ok(Expression::Paren(paren))
6524 }
6525 Expression::Cast(mut cast) => {
6526 cast.this = Self::rewrite_tsql_boolean_scalar_value(cast.this)?;
6527 if let Some(format) = cast.format.take() {
6528 cast.format = Some(Box::new(Self::rewrite_tsql_boolean_embedded_queries(
6529 *format,
6530 )?));
6531 }
6532 if let Some(default) = cast.default.take() {
6533 cast.default =
6534 Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(*default)?));
6535 }
6536 Ok(Expression::Cast(cast))
6537 }
6538 Expression::TryCast(mut cast) => {
6539 cast.this = Self::rewrite_tsql_boolean_scalar_value(cast.this)?;
6540 if let Some(format) = cast.format.take() {
6541 cast.format = Some(Box::new(Self::rewrite_tsql_boolean_embedded_queries(
6542 *format,
6543 )?));
6544 }
6545 if let Some(default) = cast.default.take() {
6546 cast.default =
6547 Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(*default)?));
6548 }
6549 Ok(Expression::TryCast(cast))
6550 }
6551 Expression::SafeCast(mut cast) => {
6552 cast.this = Self::rewrite_tsql_boolean_scalar_value(cast.this)?;
6553 if let Some(format) = cast.format.take() {
6554 cast.format = Some(Box::new(Self::rewrite_tsql_boolean_embedded_queries(
6555 *format,
6556 )?));
6557 }
6558 if let Some(default) = cast.default.take() {
6559 cast.default =
6560 Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(*default)?));
6561 }
6562 Ok(Expression::SafeCast(cast))
6563 }
6564 Expression::Case(mut case) => {
6565 if let Some(operand) = case.operand.take() {
6566 case.operand = Some(Self::rewrite_tsql_boolean_scalar_value(operand)?);
6567 }
6568 case.whens = case
6569 .whens
6570 .into_iter()
6571 .map(|(condition, result)| {
6572 Ok((
6573 Self::rewrite_tsql_boolean_predicate_context(condition)?,
6574 Self::rewrite_tsql_boolean_scalar_value(result)?,
6575 ))
6576 })
6577 .collect::<Result<Vec<_>>>()?;
6578 if let Some(else_) = case.else_.take() {
6579 case.else_ = Some(Self::rewrite_tsql_boolean_scalar_value(else_)?);
6580 }
6581 Ok(Expression::Case(case))
6582 }
6583 Expression::IfFunc(mut if_func) => {
6584 if_func.condition =
6585 Self::rewrite_tsql_boolean_predicate_context(if_func.condition)?;
6586 if_func.true_value = Self::rewrite_tsql_boolean_scalar_value(if_func.true_value)?;
6587 if let Some(false_value) = if_func.false_value.take() {
6588 if_func.false_value =
6589 Some(Self::rewrite_tsql_boolean_scalar_value(false_value)?);
6590 }
6591 Ok(Expression::IfFunc(if_func))
6592 }
6593 Expression::WindowFunction(mut window_function) => {
6594 window_function.this =
6595 Self::rewrite_tsql_boolean_embedded_queries(window_function.this)?;
6596 Self::rewrite_tsql_boolean_over_values(&mut window_function.over)?;
6597 if let Some(mut keep) = window_function.keep.take() {
6598 keep.order_by = Self::rewrite_tsql_boolean_ordered_values(keep.order_by)?;
6599 window_function.keep = Some(keep);
6600 }
6601 Ok(Expression::WindowFunction(window_function))
6602 }
6603 Expression::WithinGroup(mut within_group) => {
6604 within_group.this = Self::rewrite_tsql_boolean_embedded_queries(within_group.this)?;
6605 within_group.order_by =
6606 Self::rewrite_tsql_boolean_ordered_values(within_group.order_by)?;
6607 Ok(Expression::WithinGroup(within_group))
6608 }
6609 Expression::Subquery(mut subquery) => {
6610 subquery.this = Self::rewrite_boolean_values_for_tsql(subquery.this)?;
6611 Ok(Expression::Subquery(subquery))
6612 }
6613 Expression::Select(select) => Self::rewrite_boolean_values_in_tsql_select(select),
6614 other => Self::rewrite_tsql_boolean_embedded_queries(other),
6615 }
6616 }
6617
6618 fn rewrite_tsql_boolean_predicate_context(expr: Expression) -> Result<Expression> {
6619 Self::rewrite_tsql_boolean_embedded_queries(expr)
6620 }
6621
6622 fn rewrite_tsql_boolean_embedded_queries(expr: Expression) -> Result<Expression> {
6623 transform_recursive(expr, &|e| match e {
6624 Expression::Select(select) => Self::rewrite_boolean_values_in_tsql_select(select),
6625 Expression::Subquery(mut subquery) => {
6626 subquery.this = Self::rewrite_boolean_values_for_tsql(subquery.this)?;
6627 Ok(Expression::Subquery(subquery))
6628 }
6629 Expression::Union(_) | Expression::Intersect(_) | Expression::Except(_) => {
6630 Self::rewrite_boolean_values_for_tsql(e)
6631 }
6632 other => Ok(other),
6633 })
6634 }
6635
6636 fn rewrite_tsql_boolean_ordered_values(
6637 ordered: Vec<crate::expressions::Ordered>,
6638 ) -> Result<Vec<crate::expressions::Ordered>> {
6639 ordered
6640 .into_iter()
6641 .map(|mut ordered| {
6642 ordered.this = Self::rewrite_tsql_boolean_scalar_value(ordered.this)?;
6643 if let Some(with_fill) = ordered.with_fill.take() {
6644 ordered.with_fill = Some(Box::new(
6645 Self::rewrite_tsql_boolean_with_fill_values(*with_fill)?,
6646 ));
6647 }
6648 Ok(ordered)
6649 })
6650 .collect()
6651 }
6652
6653 fn rewrite_tsql_boolean_with_fill_values(
6654 mut with_fill: crate::expressions::WithFill,
6655 ) -> Result<crate::expressions::WithFill> {
6656 if let Some(from) = with_fill.from_.take() {
6657 with_fill.from_ = Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(*from)?));
6658 }
6659 if let Some(to) = with_fill.to.take() {
6660 with_fill.to = Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(*to)?));
6661 }
6662 if let Some(step) = with_fill.step.take() {
6663 with_fill.step = Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(*step)?));
6664 }
6665 if let Some(staleness) = with_fill.staleness.take() {
6666 with_fill.staleness = Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(
6667 *staleness,
6668 )?));
6669 }
6670 if let Some(interpolate) = with_fill.interpolate.take() {
6671 with_fill.interpolate = Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(
6672 *interpolate,
6673 )?));
6674 }
6675 Ok(with_fill)
6676 }
6677
6678 fn rewrite_tsql_boolean_over_values(over: &mut crate::expressions::Over) -> Result<()> {
6679 over.partition_by = std::mem::take(&mut over.partition_by)
6680 .into_iter()
6681 .map(Self::rewrite_tsql_boolean_scalar_value)
6682 .collect::<Result<Vec<_>>>()?;
6683 over.order_by =
6684 Self::rewrite_tsql_boolean_ordered_values(std::mem::take(&mut over.order_by))?;
6685 Ok(())
6686 }
6687
6688 fn is_tsql_boolean_value_expression(expr: &Expression) -> bool {
6689 match expr {
6690 Expression::Paren(paren) => Self::is_tsql_boolean_value_expression(&paren.this),
6691 Expression::Eq(_)
6692 | Expression::Neq(_)
6693 | Expression::Lt(_)
6694 | Expression::Lte(_)
6695 | Expression::Gt(_)
6696 | Expression::Gte(_)
6697 | Expression::Is(_)
6698 | Expression::IsNull(_)
6699 | Expression::IsTrue(_)
6700 | Expression::IsFalse(_)
6701 | Expression::Like(_)
6702 | Expression::ILike(_)
6703 | Expression::StartsWith(_)
6704 | Expression::SimilarTo(_)
6705 | Expression::Glob(_)
6706 | Expression::RegexpLike(_)
6707 | Expression::In(_)
6708 | Expression::Between(_)
6709 | Expression::Exists(_)
6710 | Expression::And(_)
6711 | Expression::Or(_)
6712 | Expression::Not(_)
6713 | Expression::Any(_)
6714 | Expression::All(_)
6715 | Expression::NullSafeEq(_)
6716 | Expression::NullSafeNeq(_)
6717 | Expression::EqualNull(_) => true,
6718 _ => false,
6719 }
6720 }
6721
6722 fn tsql_boolean_value_case(predicate: Expression) -> Expression {
6723 let case = Expression::Case(Box::new(crate::expressions::Case {
6724 operand: None,
6725 whens: vec![(predicate, Expression::number(1))],
6726 else_: Some(Expression::number(0)),
6727 comments: Vec::new(),
6728 inferred_type: None,
6729 }));
6730
6731 Expression::Cast(Box::new(Cast {
6732 this: case,
6733 to: DataType::Boolean,
6734 trailing_comments: Vec::new(),
6735 double_colon_syntax: false,
6736 format: None,
6737 default: None,
6738 inferred_type: None,
6739 }))
6740 }
6741
6742 fn rewrite_aggregate_filters_for_tsql(expr: Expression) -> Result<Expression> {
6743 transform_recursive(expr, &|e| Self::rewrite_aggregate_filter_for_tsql(e))
6744 }
6745
6746 fn rewrite_aggregate_filter_for_tsql(expr: Expression) -> Result<Expression> {
6747 macro_rules! rewrite_agg_filter {
6748 ($variant:ident, $agg:expr) => {{
6749 let mut agg = $agg;
6750 if let Some(filter) = agg.filter.take() {
6751 let this = std::mem::replace(&mut agg.this, Expression::null());
6752 agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
6753 }
6754 Ok(Expression::$variant(agg))
6755 }};
6756 }
6757
6758 match expr {
6759 Expression::Filter(filter) => {
6760 let condition = match *filter.expression {
6761 Expression::Where(where_) => where_.this,
6762 other => other,
6763 };
6764 Ok(Self::push_filter_into_tsql_aggregate(
6765 *filter.this,
6766 condition,
6767 ))
6768 }
6769 Expression::AggregateFunction(mut agg) => {
6770 if let Some(filter) = agg.filter.take() {
6771 Self::rewrite_generic_aggregate_filter_for_tsql(&mut agg, filter);
6772 }
6773 Ok(Expression::AggregateFunction(agg))
6774 }
6775 Expression::Count(mut count) => {
6776 if let Some(filter) = count.filter.take() {
6777 let value = if count.star {
6778 Expression::number(1)
6779 } else {
6780 count.this.take().unwrap_or_else(|| Expression::number(1))
6781 };
6782 count.star = false;
6783 count.this = Some(Self::conditional_aggregate_value_for_tsql(filter, value));
6784 }
6785 Ok(Expression::Count(count))
6786 }
6787 Expression::Sum(agg) => rewrite_agg_filter!(Sum, agg),
6788 Expression::Avg(agg) => rewrite_agg_filter!(Avg, agg),
6789 Expression::Min(agg) => rewrite_agg_filter!(Min, agg),
6790 Expression::Max(agg) => rewrite_agg_filter!(Max, agg),
6791 Expression::ArrayAgg(agg) => rewrite_agg_filter!(ArrayAgg, agg),
6792 Expression::CountIf(agg) => Ok(Expression::CountIf(agg)),
6793 Expression::Stddev(agg) => rewrite_agg_filter!(Stddev, agg),
6794 Expression::StddevPop(agg) => rewrite_agg_filter!(StddevPop, agg),
6795 Expression::StddevSamp(agg) => rewrite_agg_filter!(StddevSamp, agg),
6796 Expression::Variance(agg) => rewrite_agg_filter!(Variance, agg),
6797 Expression::VarPop(agg) => rewrite_agg_filter!(VarPop, agg),
6798 Expression::VarSamp(agg) => rewrite_agg_filter!(VarSamp, agg),
6799 Expression::Median(agg) => rewrite_agg_filter!(Median, agg),
6800 Expression::Mode(agg) => rewrite_agg_filter!(Mode, agg),
6801 Expression::First(agg) => rewrite_agg_filter!(First, agg),
6802 Expression::Last(agg) => rewrite_agg_filter!(Last, agg),
6803 Expression::AnyValue(agg) => rewrite_agg_filter!(AnyValue, agg),
6804 Expression::ApproxDistinct(agg) => rewrite_agg_filter!(ApproxDistinct, agg),
6805 Expression::ApproxCountDistinct(agg) => {
6806 rewrite_agg_filter!(ApproxCountDistinct, agg)
6807 }
6808 Expression::LogicalAnd(agg) => rewrite_agg_filter!(LogicalAnd, agg),
6809 Expression::LogicalOr(agg) => rewrite_agg_filter!(LogicalOr, agg),
6810 Expression::Skewness(agg) => rewrite_agg_filter!(Skewness, agg),
6811 Expression::ArrayConcatAgg(agg) => rewrite_agg_filter!(ArrayConcatAgg, agg),
6812 Expression::ArrayUniqueAgg(agg) => rewrite_agg_filter!(ArrayUniqueAgg, agg),
6813 Expression::BoolXorAgg(agg) => rewrite_agg_filter!(BoolXorAgg, agg),
6814 Expression::BitwiseAndAgg(agg) => rewrite_agg_filter!(BitwiseAndAgg, agg),
6815 Expression::BitwiseOrAgg(agg) => rewrite_agg_filter!(BitwiseOrAgg, agg),
6816 Expression::BitwiseXorAgg(agg) => rewrite_agg_filter!(BitwiseXorAgg, agg),
6817 Expression::StringAgg(mut agg) => {
6818 if let Some(filter) = agg.filter.take() {
6819 let this = std::mem::replace(&mut agg.this, Expression::null());
6820 agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
6821 }
6822 Ok(Expression::StringAgg(agg))
6823 }
6824 Expression::GroupConcat(mut agg) => {
6825 if let Some(filter) = agg.filter.take() {
6826 let this = std::mem::replace(&mut agg.this, Expression::null());
6827 agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
6828 }
6829 Ok(Expression::GroupConcat(agg))
6830 }
6831 Expression::ListAgg(mut agg) => {
6832 if let Some(filter) = agg.filter.take() {
6833 let this = std::mem::replace(&mut agg.this, Expression::null());
6834 agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
6835 }
6836 Ok(Expression::ListAgg(agg))
6837 }
6838 Expression::WithinGroup(mut within_group) => {
6839 within_group.this = Self::rewrite_aggregate_filters_for_tsql(within_group.this)?;
6840 Ok(Expression::WithinGroup(within_group))
6841 }
6842 other => Ok(other),
6843 }
6844 }
6845
6846 fn push_filter_into_tsql_aggregate(expr: Expression, filter: Expression) -> Expression {
6847 macro_rules! push_agg_filter {
6848 ($variant:ident, $agg:expr) => {{
6849 let mut agg = $agg;
6850 let this = std::mem::replace(&mut agg.this, Expression::null());
6851 agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
6852 agg.filter = None;
6853 Expression::$variant(agg)
6854 }};
6855 }
6856
6857 match expr {
6858 Expression::AggregateFunction(mut agg) => {
6859 Self::rewrite_generic_aggregate_filter_for_tsql(&mut agg, filter);
6860 Expression::AggregateFunction(agg)
6861 }
6862 Expression::Count(mut count) => {
6863 let value = if count.star {
6864 Expression::number(1)
6865 } else {
6866 count.this.take().unwrap_or_else(|| Expression::number(1))
6867 };
6868 count.star = false;
6869 count.filter = None;
6870 count.this = Some(Self::conditional_aggregate_value_for_tsql(filter, value));
6871 Expression::Count(count)
6872 }
6873 Expression::Sum(agg) => push_agg_filter!(Sum, agg),
6874 Expression::Avg(agg) => push_agg_filter!(Avg, agg),
6875 Expression::Min(agg) => push_agg_filter!(Min, agg),
6876 Expression::Max(agg) => push_agg_filter!(Max, agg),
6877 Expression::ArrayAgg(agg) => push_agg_filter!(ArrayAgg, agg),
6878 Expression::CountIf(mut agg) => {
6879 agg.filter = Some(filter);
6880 Expression::CountIf(agg)
6881 }
6882 Expression::Stddev(agg) => push_agg_filter!(Stddev, agg),
6883 Expression::StddevPop(agg) => push_agg_filter!(StddevPop, agg),
6884 Expression::StddevSamp(agg) => push_agg_filter!(StddevSamp, agg),
6885 Expression::Variance(agg) => push_agg_filter!(Variance, agg),
6886 Expression::VarPop(agg) => push_agg_filter!(VarPop, agg),
6887 Expression::VarSamp(agg) => push_agg_filter!(VarSamp, agg),
6888 Expression::Median(agg) => push_agg_filter!(Median, agg),
6889 Expression::Mode(agg) => push_agg_filter!(Mode, agg),
6890 Expression::First(agg) => push_agg_filter!(First, agg),
6891 Expression::Last(agg) => push_agg_filter!(Last, agg),
6892 Expression::AnyValue(agg) => push_agg_filter!(AnyValue, agg),
6893 Expression::ApproxDistinct(agg) => push_agg_filter!(ApproxDistinct, agg),
6894 Expression::ApproxCountDistinct(agg) => {
6895 push_agg_filter!(ApproxCountDistinct, agg)
6896 }
6897 Expression::LogicalAnd(agg) => push_agg_filter!(LogicalAnd, agg),
6898 Expression::LogicalOr(agg) => push_agg_filter!(LogicalOr, agg),
6899 Expression::Skewness(agg) => push_agg_filter!(Skewness, agg),
6900 Expression::ArrayConcatAgg(agg) => push_agg_filter!(ArrayConcatAgg, agg),
6901 Expression::ArrayUniqueAgg(agg) => push_agg_filter!(ArrayUniqueAgg, agg),
6902 Expression::BoolXorAgg(agg) => push_agg_filter!(BoolXorAgg, agg),
6903 Expression::BitwiseAndAgg(agg) => push_agg_filter!(BitwiseAndAgg, agg),
6904 Expression::BitwiseOrAgg(agg) => push_agg_filter!(BitwiseOrAgg, agg),
6905 Expression::BitwiseXorAgg(agg) => push_agg_filter!(BitwiseXorAgg, agg),
6906 Expression::StringAgg(mut agg) => {
6907 let this = std::mem::replace(&mut agg.this, Expression::null());
6908 agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
6909 agg.filter = None;
6910 Expression::StringAgg(agg)
6911 }
6912 Expression::GroupConcat(mut agg) => {
6913 let this = std::mem::replace(&mut agg.this, Expression::null());
6914 agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
6915 agg.filter = None;
6916 Expression::GroupConcat(agg)
6917 }
6918 Expression::ListAgg(mut agg) => {
6919 let this = std::mem::replace(&mut agg.this, Expression::null());
6920 agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
6921 agg.filter = None;
6922 Expression::ListAgg(agg)
6923 }
6924 Expression::WithinGroup(mut within_group) => {
6925 within_group.this =
6926 Self::push_filter_into_tsql_aggregate(within_group.this, filter);
6927 Expression::WithinGroup(within_group)
6928 }
6929 other => Expression::Filter(Box::new(crate::expressions::Filter {
6930 this: Box::new(other),
6931 expression: Box::new(filter),
6932 })),
6933 }
6934 }
6935
6936 fn rewrite_generic_aggregate_filter_for_tsql(
6937 agg: &mut crate::expressions::AggregateFunction,
6938 filter: Expression,
6939 ) {
6940 let is_count =
6941 agg.name.eq_ignore_ascii_case("COUNT") || agg.name.eq_ignore_ascii_case("COUNT_BIG");
6942 let is_count_star = is_count
6943 && (agg.args.is_empty()
6944 || (agg.args.len() == 1 && matches!(agg.args[0], Expression::Star(_))));
6945
6946 if is_count_star {
6947 agg.args = vec![Self::conditional_aggregate_value_for_tsql(
6948 filter,
6949 Expression::number(1),
6950 )];
6951 } else if !agg.args.is_empty() {
6952 agg.args = agg
6953 .args
6954 .drain(..)
6955 .map(|arg| Self::conditional_aggregate_value_for_tsql(filter.clone(), arg))
6956 .collect();
6957 } else {
6958 agg.filter = Some(filter);
6959 }
6960 }
6961
6962 fn conditional_aggregate_value_for_tsql(filter: Expression, value: Expression) -> Expression {
6963 Expression::Case(Box::new(crate::expressions::Case {
6964 operand: None,
6965 whens: vec![(filter, value)],
6966 else_: None,
6967 comments: Vec::new(),
6968 inferred_type: None,
6969 }))
6970 }
6971
6972 fn reject_pgvector_distance_operators_for_sqlite(&self, sql: &str) -> Result<()> {
6973 let tokens = self.tokenize(sql)?;
6974 for (i, token) in tokens.iter().enumerate() {
6975 if token.token_type == TokenType::NullsafeEq {
6976 return Err(crate::error::Error::unsupported(
6977 "PostgreSQL pgvector cosine distance operator <=>",
6978 "SQLite",
6979 ));
6980 }
6981 if token.token_type == TokenType::Lt
6982 && tokens
6983 .get(i + 1)
6984 .is_some_and(|token| token.token_type == TokenType::Tilde)
6985 && tokens
6986 .get(i + 2)
6987 .is_some_and(|token| token.token_type == TokenType::Gt)
6988 {
6989 return Err(crate::error::Error::unsupported(
6990 "PostgreSQL pgvector Hamming distance operator <~>",
6991 "SQLite",
6992 ));
6993 }
6994 }
6995 Ok(())
6996 }
6997
6998 fn normalize_sqlite_double_quoted_defaults(expr: Expression) -> Result<Expression> {
6999 fn normalize_default_expr(expr: Expression) -> Result<Expression> {
7000 transform_recursive(expr, &|e| match e {
7001 Expression::Column(col)
7002 if col.table.is_none() && col.name.quoted && !col.join_mark =>
7003 {
7004 Ok(Expression::Literal(Box::new(Literal::String(
7005 col.name.name,
7006 ))))
7007 }
7008 Expression::Identifier(id) if id.quoted => {
7009 Ok(Expression::Literal(Box::new(Literal::String(id.name))))
7010 }
7011 _ => Ok(e),
7012 })
7013 }
7014
7015 fn normalize_column_default(col: &mut crate::expressions::ColumnDef) -> Result<()> {
7016 if let Some(default) = col.default.take() {
7017 col.default = Some(normalize_default_expr(default)?);
7018 }
7019
7020 for constraint in &mut col.constraints {
7021 if let ColumnConstraint::Default(default) = constraint {
7022 *default = normalize_default_expr(default.clone())?;
7023 }
7024 }
7025
7026 Ok(())
7027 }
7028
7029 transform_recursive(expr, &|e| match e {
7030 Expression::CreateTable(mut ct) => {
7031 for column in &mut ct.columns {
7032 normalize_column_default(column)?;
7033 }
7034 Ok(Expression::CreateTable(ct))
7035 }
7036 Expression::ColumnDef(mut col) => {
7037 normalize_column_default(&mut col)?;
7038 Ok(Expression::ColumnDef(col))
7039 }
7040 _ => Ok(e),
7041 })
7042 }
7043
7044 fn normalize_postgres_to_sqlite_types(expr: Expression) -> Result<Expression> {
7045 fn sqlite_type(dt: crate::expressions::DataType) -> crate::expressions::DataType {
7046 use crate::expressions::DataType;
7047
7048 match dt {
7049 DataType::Bit { .. } => DataType::Int {
7050 length: None,
7051 integer_spelling: true,
7052 },
7053 DataType::TextWithLength { .. } => DataType::Text,
7054 DataType::VarChar { .. } => DataType::Text,
7055 DataType::Char { .. } => DataType::Text,
7056 DataType::Timestamp { timezone: true, .. } => DataType::Text,
7057 DataType::Custom { name } => {
7058 let base = name
7059 .split_once('(')
7060 .map_or(name.as_str(), |(base, _)| base)
7061 .trim();
7062 if base.eq_ignore_ascii_case("TSVECTOR")
7063 || base.eq_ignore_ascii_case("TIMESTAMPTZ")
7064 || base.eq_ignore_ascii_case("TIMESTAMP WITH TIME ZONE")
7065 || base.eq_ignore_ascii_case("NVARCHAR")
7066 || base.eq_ignore_ascii_case("NCHAR")
7067 {
7068 DataType::Text
7069 } else {
7070 DataType::Custom { name }
7071 }
7072 }
7073 _ => dt,
7074 }
7075 }
7076
7077 transform_recursive(expr, &|e| match e {
7078 Expression::DataType(dt) => Ok(Expression::DataType(sqlite_type(dt))),
7079 Expression::CreateTable(mut ct) => {
7080 for column in &mut ct.columns {
7081 column.data_type = sqlite_type(column.data_type.clone());
7082 }
7083 Ok(Expression::CreateTable(ct))
7084 }
7085 _ => Ok(e),
7086 })
7087 }
7088
7089 fn normalize_postgres_to_fabric_types(expr: Expression) -> Result<Expression> {
7090 fn fabric_type(dt: crate::expressions::DataType) -> crate::expressions::DataType {
7091 use crate::expressions::DataType;
7092
7093 match dt {
7094 DataType::Decimal {
7095 precision: None,
7096 scale: None,
7097 } => DataType::Decimal {
7098 precision: Some(38),
7099 scale: Some(10),
7100 },
7101 DataType::Json | DataType::JsonB => DataType::Custom {
7102 name: "VARCHAR(MAX)".to_string(),
7103 },
7104 _ => dt,
7105 }
7106 }
7107
7108 transform_recursive(expr, &|e| match e {
7109 Expression::DataType(dt) => Ok(Expression::DataType(fabric_type(dt))),
7110 Expression::CreateTable(mut ct) => {
7111 for column in &mut ct.columns {
7112 column.data_type = fabric_type(column.data_type.clone());
7113 }
7114 Ok(Expression::CreateTable(ct))
7115 }
7116 Expression::ColumnDef(mut col) => {
7117 col.data_type = fabric_type(col.data_type);
7118 Ok(Expression::ColumnDef(col))
7119 }
7120 _ => Ok(e),
7121 })
7122 }
7123
7124 /// For DuckDB target: when FROM clause contains RANGE(n), replace
7125 /// `(ROW_NUMBER() OVER (ORDER BY 1 NULLS FIRST) - 1)` with `range` in select expressions.
7126 /// This handles SEQ1/2/4/8 → RANGE transpilation from Snowflake.
7127 fn seq_rownum_to_range(expr: Expression) -> Result<Expression> {
7128 if let Expression::Select(mut select) = expr {
7129 // Check if FROM contains a RANGE function
7130 let has_range_from = if let Some(ref from) = select.from {
7131 from.expressions.iter().any(|e| {
7132 // Check for direct RANGE(...) or aliased RANGE(...)
7133 match e {
7134 Expression::Function(f) => f.name.eq_ignore_ascii_case("RANGE"),
7135 Expression::Alias(a) => {
7136 matches!(&a.this, Expression::Function(f) if f.name.eq_ignore_ascii_case("RANGE"))
7137 }
7138 _ => false,
7139 }
7140 })
7141 } else {
7142 false
7143 };
7144
7145 if has_range_from {
7146 // Replace the ROW_NUMBER pattern in select expressions
7147 select.expressions = select
7148 .expressions
7149 .into_iter()
7150 .map(|e| Self::replace_rownum_with_range(e))
7151 .collect();
7152 }
7153
7154 Ok(Expression::Select(select))
7155 } else {
7156 Ok(expr)
7157 }
7158 }
7159
7160 /// Replace `(ROW_NUMBER() OVER (...) - 1)` with `range` column reference
7161 fn replace_rownum_with_range(expr: Expression) -> Expression {
7162 match expr {
7163 // Match: (ROW_NUMBER() OVER (...) - 1) % N → range % N
7164 Expression::Mod(op) => {
7165 let new_left = Self::try_replace_rownum_paren(&op.left);
7166 Expression::Mod(Box::new(crate::expressions::BinaryOp {
7167 left: new_left,
7168 right: op.right,
7169 left_comments: op.left_comments,
7170 operator_comments: op.operator_comments,
7171 trailing_comments: op.trailing_comments,
7172 inferred_type: op.inferred_type,
7173 }))
7174 }
7175 // Match: (CASE WHEN (ROW...) % N >= ... THEN ... ELSE ... END)
7176 Expression::Paren(p) => {
7177 let inner = Self::replace_rownum_with_range(p.this);
7178 Expression::Paren(Box::new(crate::expressions::Paren {
7179 this: inner,
7180 trailing_comments: p.trailing_comments,
7181 }))
7182 }
7183 Expression::Case(mut c) => {
7184 // Replace ROW_NUMBER in WHEN conditions and THEN expressions
7185 c.whens = c
7186 .whens
7187 .into_iter()
7188 .map(|(cond, then)| {
7189 (
7190 Self::replace_rownum_with_range(cond),
7191 Self::replace_rownum_with_range(then),
7192 )
7193 })
7194 .collect();
7195 if let Some(else_) = c.else_ {
7196 c.else_ = Some(Self::replace_rownum_with_range(else_));
7197 }
7198 Expression::Case(c)
7199 }
7200 Expression::Gte(op) => Expression::Gte(Box::new(crate::expressions::BinaryOp {
7201 left: Self::replace_rownum_with_range(op.left),
7202 right: op.right,
7203 left_comments: op.left_comments,
7204 operator_comments: op.operator_comments,
7205 trailing_comments: op.trailing_comments,
7206 inferred_type: op.inferred_type,
7207 })),
7208 Expression::Sub(op) => Expression::Sub(Box::new(crate::expressions::BinaryOp {
7209 left: Self::replace_rownum_with_range(op.left),
7210 right: op.right,
7211 left_comments: op.left_comments,
7212 operator_comments: op.operator_comments,
7213 trailing_comments: op.trailing_comments,
7214 inferred_type: op.inferred_type,
7215 })),
7216 Expression::Alias(mut a) => {
7217 a.this = Self::replace_rownum_with_range(a.this);
7218 Expression::Alias(a)
7219 }
7220 other => other,
7221 }
7222 }
7223
7224 /// Check if an expression is `(ROW_NUMBER() OVER (...) - 1)` and replace with `range`
7225 fn try_replace_rownum_paren(expr: &Expression) -> Expression {
7226 if let Expression::Paren(ref p) = expr {
7227 if let Expression::Sub(ref sub) = p.this {
7228 if let Expression::WindowFunction(ref wf) = sub.left {
7229 if let Expression::Function(ref f) = wf.this {
7230 if f.name.eq_ignore_ascii_case("ROW_NUMBER") {
7231 if let Expression::Literal(ref lit) = sub.right {
7232 if let crate::expressions::Literal::Number(ref n) = lit.as_ref() {
7233 if n == "1" {
7234 return Expression::column("range");
7235 }
7236 }
7237 }
7238 }
7239 }
7240 }
7241 }
7242 }
7243 expr.clone()
7244 }
7245
7246 /// Transform BigQuery GENERATE_DATE_ARRAY in UNNEST for Snowflake target.
7247 /// Converts:
7248 /// SELECT ..., alias, ... FROM t CROSS JOIN UNNEST(GENERATE_DATE_ARRAY(start, end, INTERVAL '1' unit)) AS alias
7249 /// To:
7250 /// SELECT ..., DATEADD(unit, CAST(alias AS INT), CAST(start AS DATE)) AS alias, ...
7251 /// FROM t, LATERAL FLATTEN(INPUT => ARRAY_GENERATE_RANGE(0, DATEDIFF(unit, start, end) + 1)) AS _t0(seq, key, path, index, alias, this)
7252 fn transform_generate_date_array_snowflake(expr: Expression) -> Result<Expression> {
7253 use crate::expressions::*;
7254 transform_recursive(expr, &|e| {
7255 // Handle ARRAY_SIZE(GENERATE_DATE_ARRAY(...)) -> ARRAY_SIZE((SELECT ARRAY_AGG(*) FROM subquery))
7256 if let Expression::ArraySize(ref af) = e {
7257 if let Expression::Function(ref f) = af.this {
7258 if f.name.eq_ignore_ascii_case("GENERATE_DATE_ARRAY") && f.args.len() >= 2 {
7259 let result = Self::convert_array_size_gda_snowflake(f)?;
7260 return Ok(result);
7261 }
7262 }
7263 }
7264
7265 let Expression::Select(mut sel) = e else {
7266 return Ok(e);
7267 };
7268
7269 // Find joins with UNNEST containing GenerateSeries (from GENERATE_DATE_ARRAY conversion)
7270 let mut gda_info: Option<(String, Expression, Expression, String)> = None; // (alias_name, start_expr, end_expr, unit)
7271 let mut gda_join_idx: Option<usize> = None;
7272
7273 for (idx, join) in sel.joins.iter().enumerate() {
7274 // The join.this may be:
7275 // 1. Unnest(UnnestFunc { alias: Some("mnth"), ... })
7276 // 2. Alias(Alias { this: Unnest(UnnestFunc { alias: None, ... }), alias: "mnth", ... })
7277 let (unnest_ref, alias_name) = match &join.this {
7278 Expression::Unnest(ref unnest) => {
7279 let alias = unnest.alias.as_ref().map(|id| id.name.clone());
7280 (Some(unnest.as_ref()), alias)
7281 }
7282 Expression::Alias(ref a) => {
7283 if let Expression::Unnest(ref unnest) = a.this {
7284 (Some(unnest.as_ref()), Some(a.alias.name.clone()))
7285 } else {
7286 (None, None)
7287 }
7288 }
7289 _ => (None, None),
7290 };
7291
7292 if let (Some(unnest), Some(alias)) = (unnest_ref, alias_name) {
7293 // Check the main expression (this) of the UNNEST for GENERATE_DATE_ARRAY function
7294 if let Expression::Function(ref f) = unnest.this {
7295 if f.name.eq_ignore_ascii_case("GENERATE_DATE_ARRAY") && f.args.len() >= 2 {
7296 let start_expr = f.args[0].clone();
7297 let end_expr = f.args[1].clone();
7298 let step = f.args.get(2).cloned();
7299
7300 // Extract unit from step interval
7301 let unit = if let Some(Expression::Interval(ref iv)) = step {
7302 if let Some(IntervalUnitSpec::Simple { ref unit, .. }) = iv.unit {
7303 Some(format!("{:?}", unit).to_ascii_uppercase())
7304 } else if let Some(ref this) = iv.this {
7305 // The interval may be stored as a string like "1 MONTH"
7306 if let Expression::Literal(lit) = this {
7307 if let Literal::String(ref s) = lit.as_ref() {
7308 let parts: Vec<&str> = s.split_whitespace().collect();
7309 if parts.len() == 2 {
7310 Some(parts[1].to_ascii_uppercase())
7311 } else if parts.len() == 1 {
7312 // Single word like "MONTH" or just "1"
7313 let upper = parts[0].to_ascii_uppercase();
7314 if matches!(
7315 upper.as_str(),
7316 "YEAR"
7317 | "QUARTER"
7318 | "MONTH"
7319 | "WEEK"
7320 | "DAY"
7321 | "HOUR"
7322 | "MINUTE"
7323 | "SECOND"
7324 ) {
7325 Some(upper)
7326 } else {
7327 None
7328 }
7329 } else {
7330 None
7331 }
7332 } else {
7333 None
7334 }
7335 } else {
7336 None
7337 }
7338 } else {
7339 None
7340 }
7341 } else {
7342 None
7343 };
7344
7345 if let Some(unit_str) = unit {
7346 gda_info = Some((alias, start_expr, end_expr, unit_str));
7347 gda_join_idx = Some(idx);
7348 }
7349 }
7350 }
7351 }
7352 if gda_info.is_some() {
7353 break;
7354 }
7355 }
7356
7357 let Some((alias_name, start_expr, end_expr, unit_str)) = gda_info else {
7358 // Also check FROM clause for UNNEST(GENERATE_DATE_ARRAY(...)) patterns
7359 // This handles Generic->Snowflake where GENERATE_DATE_ARRAY is in FROM, not in JOIN
7360 let result = Self::try_transform_from_gda_snowflake(sel);
7361 return result;
7362 };
7363 let join_idx = gda_join_idx.unwrap();
7364
7365 // Build ARRAY_GENERATE_RANGE(0, DATEDIFF(unit, start, end) + 1)
7366 // ARRAY_GENERATE_RANGE uses exclusive end, and we need DATEDIFF + 1 values
7367 // (inclusive date range), so the exclusive end is DATEDIFF + 1.
7368 let datediff = Expression::Function(Box::new(Function::new(
7369 "DATEDIFF".to_string(),
7370 vec![
7371 Expression::boxed_column(Column {
7372 name: Identifier::new(&unit_str),
7373 table: None,
7374 join_mark: false,
7375 trailing_comments: vec![],
7376 span: None,
7377 inferred_type: None,
7378 }),
7379 start_expr.clone(),
7380 end_expr.clone(),
7381 ],
7382 )));
7383 let datediff_plus_one = Expression::Add(Box::new(BinaryOp {
7384 left: datediff,
7385 right: Expression::Literal(Box::new(Literal::Number("1".to_string()))),
7386 left_comments: vec![],
7387 operator_comments: vec![],
7388 trailing_comments: vec![],
7389 inferred_type: None,
7390 }));
7391
7392 let array_gen_range = Expression::Function(Box::new(Function::new(
7393 "ARRAY_GENERATE_RANGE".to_string(),
7394 vec![
7395 Expression::Literal(Box::new(Literal::Number("0".to_string()))),
7396 datediff_plus_one,
7397 ],
7398 )));
7399
7400 // Build FLATTEN(INPUT => ARRAY_GENERATE_RANGE(...))
7401 let flatten_input = Expression::NamedArgument(Box::new(NamedArgument {
7402 name: Identifier::new("INPUT"),
7403 value: array_gen_range,
7404 separator: crate::expressions::NamedArgSeparator::DArrow,
7405 }));
7406 let flatten = Expression::Function(Box::new(Function::new(
7407 "FLATTEN".to_string(),
7408 vec![flatten_input],
7409 )));
7410
7411 // Build LATERAL FLATTEN(...) AS _t0(seq, key, path, index, alias, this)
7412 let alias_table = Alias {
7413 this: flatten,
7414 alias: Identifier::new("_t0"),
7415 column_aliases: vec![
7416 Identifier::new("seq"),
7417 Identifier::new("key"),
7418 Identifier::new("path"),
7419 Identifier::new("index"),
7420 Identifier::new(&alias_name),
7421 Identifier::new("this"),
7422 ],
7423 alias_explicit_as: false,
7424 alias_keyword: None,
7425 pre_alias_comments: vec![],
7426 trailing_comments: vec![],
7427 inferred_type: None,
7428 };
7429 let lateral_expr = Expression::Lateral(Box::new(Lateral {
7430 this: Box::new(Expression::Alias(Box::new(alias_table))),
7431 view: None,
7432 outer: None,
7433 alias: None,
7434 alias_quoted: false,
7435 cross_apply: None,
7436 ordinality: None,
7437 column_aliases: vec![],
7438 }));
7439
7440 // Remove the original join and add to FROM expressions
7441 sel.joins.remove(join_idx);
7442 if let Some(ref mut from) = sel.from {
7443 from.expressions.push(lateral_expr);
7444 }
7445
7446 // Build DATEADD(unit, CAST(alias AS INT), CAST(start AS DATE))
7447 let dateadd_expr = Expression::Function(Box::new(Function::new(
7448 "DATEADD".to_string(),
7449 vec![
7450 Expression::boxed_column(Column {
7451 name: Identifier::new(&unit_str),
7452 table: None,
7453 join_mark: false,
7454 trailing_comments: vec![],
7455 span: None,
7456 inferred_type: None,
7457 }),
7458 Expression::Cast(Box::new(Cast {
7459 this: Expression::boxed_column(Column {
7460 name: Identifier::new(&alias_name),
7461 table: None,
7462 join_mark: false,
7463 trailing_comments: vec![],
7464 span: None,
7465 inferred_type: None,
7466 }),
7467 to: DataType::Int {
7468 length: None,
7469 integer_spelling: false,
7470 },
7471 trailing_comments: vec![],
7472 double_colon_syntax: false,
7473 format: None,
7474 default: None,
7475 inferred_type: None,
7476 })),
7477 Expression::Cast(Box::new(Cast {
7478 this: start_expr.clone(),
7479 to: DataType::Date,
7480 trailing_comments: vec![],
7481 double_colon_syntax: false,
7482 format: None,
7483 default: None,
7484 inferred_type: None,
7485 })),
7486 ],
7487 )));
7488
7489 // Replace references to the alias in the SELECT list
7490 let new_exprs: Vec<Expression> = sel
7491 .expressions
7492 .iter()
7493 .map(|expr| Self::replace_column_ref_with_dateadd(expr, &alias_name, &dateadd_expr))
7494 .collect();
7495 sel.expressions = new_exprs;
7496
7497 Ok(Expression::Select(sel))
7498 })
7499 }
7500
7501 /// Helper: replace column references to `alias_name` with dateadd expression
7502 fn replace_column_ref_with_dateadd(
7503 expr: &Expression,
7504 alias_name: &str,
7505 dateadd: &Expression,
7506 ) -> Expression {
7507 use crate::expressions::*;
7508 match expr {
7509 Expression::Column(c) if c.name.name == alias_name && c.table.is_none() => {
7510 // Plain column reference -> DATEADD(...) AS alias_name
7511 Expression::Alias(Box::new(Alias {
7512 this: dateadd.clone(),
7513 alias: Identifier::new(alias_name),
7514 column_aliases: vec![],
7515 alias_explicit_as: false,
7516 alias_keyword: None,
7517 pre_alias_comments: vec![],
7518 trailing_comments: vec![],
7519 inferred_type: None,
7520 }))
7521 }
7522 Expression::Alias(a) => {
7523 // Check if the inner expression references the alias
7524 let new_this = Self::replace_column_ref_inner(&a.this, alias_name, dateadd);
7525 Expression::Alias(Box::new(Alias {
7526 this: new_this,
7527 alias: a.alias.clone(),
7528 column_aliases: a.column_aliases.clone(),
7529 alias_explicit_as: false,
7530 alias_keyword: None,
7531 pre_alias_comments: a.pre_alias_comments.clone(),
7532 trailing_comments: a.trailing_comments.clone(),
7533 inferred_type: None,
7534 }))
7535 }
7536 _ => expr.clone(),
7537 }
7538 }
7539
7540 /// Helper: replace column references in inner expression (not top-level)
7541 fn replace_column_ref_inner(
7542 expr: &Expression,
7543 alias_name: &str,
7544 dateadd: &Expression,
7545 ) -> Expression {
7546 use crate::expressions::*;
7547 match expr {
7548 Expression::Column(c) if c.name.name == alias_name && c.table.is_none() => {
7549 dateadd.clone()
7550 }
7551 Expression::Add(op) => {
7552 let left = Self::replace_column_ref_inner(&op.left, alias_name, dateadd);
7553 let right = Self::replace_column_ref_inner(&op.right, alias_name, dateadd);
7554 Expression::Add(Box::new(BinaryOp {
7555 left,
7556 right,
7557 left_comments: op.left_comments.clone(),
7558 operator_comments: op.operator_comments.clone(),
7559 trailing_comments: op.trailing_comments.clone(),
7560 inferred_type: None,
7561 }))
7562 }
7563 Expression::Sub(op) => {
7564 let left = Self::replace_column_ref_inner(&op.left, alias_name, dateadd);
7565 let right = Self::replace_column_ref_inner(&op.right, alias_name, dateadd);
7566 Expression::Sub(Box::new(BinaryOp {
7567 left,
7568 right,
7569 left_comments: op.left_comments.clone(),
7570 operator_comments: op.operator_comments.clone(),
7571 trailing_comments: op.trailing_comments.clone(),
7572 inferred_type: None,
7573 }))
7574 }
7575 Expression::Mul(op) => {
7576 let left = Self::replace_column_ref_inner(&op.left, alias_name, dateadd);
7577 let right = Self::replace_column_ref_inner(&op.right, alias_name, dateadd);
7578 Expression::Mul(Box::new(BinaryOp {
7579 left,
7580 right,
7581 left_comments: op.left_comments.clone(),
7582 operator_comments: op.operator_comments.clone(),
7583 trailing_comments: op.trailing_comments.clone(),
7584 inferred_type: None,
7585 }))
7586 }
7587 _ => expr.clone(),
7588 }
7589 }
7590
7591 /// Handle UNNEST(GENERATE_DATE_ARRAY(...)) in FROM clause for Snowflake target.
7592 /// Converts to a subquery with DATEADD + TABLE(FLATTEN(ARRAY_GENERATE_RANGE(...))).
7593 fn try_transform_from_gda_snowflake(
7594 mut sel: Box<crate::expressions::Select>,
7595 ) -> Result<Expression> {
7596 use crate::expressions::*;
7597
7598 // Extract GDA info from FROM clause
7599 let mut gda_info: Option<(
7600 usize,
7601 String,
7602 Expression,
7603 Expression,
7604 String,
7605 Option<(String, Vec<Identifier>)>,
7606 )> = None; // (from_idx, col_name, start, end, unit, outer_alias)
7607
7608 if let Some(ref from) = sel.from {
7609 for (idx, table_expr) in from.expressions.iter().enumerate() {
7610 // Pattern 1: UNNEST(GENERATE_DATE_ARRAY(...))
7611 // Pattern 2: Alias(UNNEST(GENERATE_DATE_ARRAY(...))) AS _q(date_week)
7612 let (unnest_opt, outer_alias_info) = match table_expr {
7613 Expression::Unnest(ref unnest) => (Some(unnest.as_ref()), None),
7614 Expression::Alias(ref a) => {
7615 if let Expression::Unnest(ref unnest) = a.this {
7616 let alias_info = (a.alias.name.clone(), a.column_aliases.clone());
7617 (Some(unnest.as_ref()), Some(alias_info))
7618 } else {
7619 (None, None)
7620 }
7621 }
7622 _ => (None, None),
7623 };
7624
7625 if let Some(unnest) = unnest_opt {
7626 // Check for GENERATE_DATE_ARRAY function
7627 let func_opt = match &unnest.this {
7628 Expression::Function(ref f)
7629 if f.name.eq_ignore_ascii_case("GENERATE_DATE_ARRAY")
7630 && f.args.len() >= 2 =>
7631 {
7632 Some(f)
7633 }
7634 // Also check for GenerateSeries (from earlier normalization)
7635 _ => None,
7636 };
7637
7638 if let Some(f) = func_opt {
7639 let start_expr = f.args[0].clone();
7640 let end_expr = f.args[1].clone();
7641 let step = f.args.get(2).cloned();
7642
7643 // Extract unit and column name
7644 let unit = Self::extract_interval_unit_str(&step);
7645 let col_name = outer_alias_info
7646 .as_ref()
7647 .and_then(|(_, cols)| cols.first().map(|id| id.name.clone()))
7648 .unwrap_or_else(|| "value".to_string());
7649
7650 if let Some(unit_str) = unit {
7651 gda_info = Some((
7652 idx,
7653 col_name,
7654 start_expr,
7655 end_expr,
7656 unit_str,
7657 outer_alias_info,
7658 ));
7659 break;
7660 }
7661 }
7662 }
7663 }
7664 }
7665
7666 let Some((from_idx, col_name, start_expr, end_expr, unit_str, outer_alias_info)) = gda_info
7667 else {
7668 return Ok(Expression::Select(sel));
7669 };
7670
7671 // Build the Snowflake subquery:
7672 // (SELECT DATEADD(unit, CAST(col_name AS INT), CAST(start AS DATE)) AS col_name
7673 // FROM TABLE(FLATTEN(INPUT => ARRAY_GENERATE_RANGE(0, DATEDIFF(unit, start, end) + 1))) AS _t0(seq, key, path, index, col_name, this))
7674
7675 // DATEDIFF(unit, start, end)
7676 let datediff = Expression::Function(Box::new(Function::new(
7677 "DATEDIFF".to_string(),
7678 vec![
7679 Expression::boxed_column(Column {
7680 name: Identifier::new(&unit_str),
7681 table: None,
7682 join_mark: false,
7683 trailing_comments: vec![],
7684 span: None,
7685 inferred_type: None,
7686 }),
7687 start_expr.clone(),
7688 end_expr.clone(),
7689 ],
7690 )));
7691 // DATEDIFF(...) + 1
7692 let datediff_plus_one = Expression::Add(Box::new(BinaryOp {
7693 left: datediff,
7694 right: Expression::Literal(Box::new(Literal::Number("1".to_string()))),
7695 left_comments: vec![],
7696 operator_comments: vec![],
7697 trailing_comments: vec![],
7698 inferred_type: None,
7699 }));
7700
7701 let array_gen_range = Expression::Function(Box::new(Function::new(
7702 "ARRAY_GENERATE_RANGE".to_string(),
7703 vec![
7704 Expression::Literal(Box::new(Literal::Number("0".to_string()))),
7705 datediff_plus_one,
7706 ],
7707 )));
7708
7709 // TABLE(FLATTEN(INPUT => ...))
7710 let flatten_input = Expression::NamedArgument(Box::new(NamedArgument {
7711 name: Identifier::new("INPUT"),
7712 value: array_gen_range,
7713 separator: crate::expressions::NamedArgSeparator::DArrow,
7714 }));
7715 let flatten = Expression::Function(Box::new(Function::new(
7716 "FLATTEN".to_string(),
7717 vec![flatten_input],
7718 )));
7719
7720 // Determine alias name for the table: use outer alias or _t0
7721 let table_alias_name = outer_alias_info
7722 .as_ref()
7723 .map(|(name, _)| name.clone())
7724 .unwrap_or_else(|| "_t0".to_string());
7725
7726 // TABLE(FLATTEN(...)) AS _t0(seq, key, path, index, col_name, this)
7727 let table_func =
7728 Expression::Function(Box::new(Function::new("TABLE".to_string(), vec![flatten])));
7729 let flatten_aliased = Expression::Alias(Box::new(Alias {
7730 this: table_func,
7731 alias: Identifier::new(&table_alias_name),
7732 column_aliases: vec![
7733 Identifier::new("seq"),
7734 Identifier::new("key"),
7735 Identifier::new("path"),
7736 Identifier::new("index"),
7737 Identifier::new(&col_name),
7738 Identifier::new("this"),
7739 ],
7740 alias_explicit_as: false,
7741 alias_keyword: None,
7742 pre_alias_comments: vec![],
7743 trailing_comments: vec![],
7744 inferred_type: None,
7745 }));
7746
7747 // SELECT DATEADD(unit, CAST(col_name AS INT), CAST(start AS DATE)) AS col_name
7748 let dateadd_expr = Expression::Function(Box::new(Function::new(
7749 "DATEADD".to_string(),
7750 vec![
7751 Expression::boxed_column(Column {
7752 name: Identifier::new(&unit_str),
7753 table: None,
7754 join_mark: false,
7755 trailing_comments: vec![],
7756 span: None,
7757 inferred_type: None,
7758 }),
7759 Expression::Cast(Box::new(Cast {
7760 this: Expression::boxed_column(Column {
7761 name: Identifier::new(&col_name),
7762 table: None,
7763 join_mark: false,
7764 trailing_comments: vec![],
7765 span: None,
7766 inferred_type: None,
7767 }),
7768 to: DataType::Int {
7769 length: None,
7770 integer_spelling: false,
7771 },
7772 trailing_comments: vec![],
7773 double_colon_syntax: false,
7774 format: None,
7775 default: None,
7776 inferred_type: None,
7777 })),
7778 // Use start_expr directly - it's already been normalized (DATE literal -> CAST)
7779 start_expr.clone(),
7780 ],
7781 )));
7782 let dateadd_aliased = Expression::Alias(Box::new(Alias {
7783 this: dateadd_expr,
7784 alias: Identifier::new(&col_name),
7785 column_aliases: vec![],
7786 alias_explicit_as: false,
7787 alias_keyword: None,
7788 pre_alias_comments: vec![],
7789 trailing_comments: vec![],
7790 inferred_type: None,
7791 }));
7792
7793 // Build inner SELECT
7794 let mut inner_select = Select::new();
7795 inner_select.expressions = vec![dateadd_aliased];
7796 inner_select.from = Some(From {
7797 expressions: vec![flatten_aliased],
7798 });
7799
7800 let inner_select_expr = Expression::Select(Box::new(inner_select));
7801 let subquery = Expression::Subquery(Box::new(Subquery {
7802 this: inner_select_expr,
7803 alias: None,
7804 column_aliases: vec![],
7805 alias_explicit_as: false,
7806 alias_keyword: None,
7807 order_by: None,
7808 limit: None,
7809 offset: None,
7810 distribute_by: None,
7811 sort_by: None,
7812 cluster_by: None,
7813 lateral: false,
7814 modifiers_inside: false,
7815 trailing_comments: vec![],
7816 inferred_type: None,
7817 }));
7818
7819 // If there was an outer alias (e.g., AS _q(date_week)), wrap with alias
7820 let replacement = if let Some((alias_name, col_aliases)) = outer_alias_info {
7821 Expression::Alias(Box::new(Alias {
7822 this: subquery,
7823 alias: Identifier::new(&alias_name),
7824 column_aliases: col_aliases,
7825 alias_explicit_as: false,
7826 alias_keyword: None,
7827 pre_alias_comments: vec![],
7828 trailing_comments: vec![],
7829 inferred_type: None,
7830 }))
7831 } else {
7832 subquery
7833 };
7834
7835 // Replace the FROM expression
7836 if let Some(ref mut from) = sel.from {
7837 from.expressions[from_idx] = replacement;
7838 }
7839
7840 Ok(Expression::Select(sel))
7841 }
7842
7843 /// Convert ARRAY_SIZE(GENERATE_DATE_ARRAY(start, end, step)) for Snowflake.
7844 /// Produces: ARRAY_SIZE((SELECT ARRAY_AGG(*) FROM (SELECT DATEADD(unit, CAST(value AS INT), start) AS value
7845 /// FROM TABLE(FLATTEN(INPUT => ARRAY_GENERATE_RANGE(0, DATEDIFF(unit, start, end) + 1))) AS _t0(...))))
7846 fn convert_array_size_gda_snowflake(f: &crate::expressions::Function) -> Result<Expression> {
7847 use crate::expressions::*;
7848
7849 let start_expr = f.args[0].clone();
7850 let end_expr = f.args[1].clone();
7851 let step = f.args.get(2).cloned();
7852 let unit_str = Self::extract_interval_unit_str(&step).unwrap_or_else(|| "DAY".to_string());
7853 let col_name = "value";
7854
7855 // Build the inner subquery: same as try_transform_from_gda_snowflake
7856 let datediff = Expression::Function(Box::new(Function::new(
7857 "DATEDIFF".to_string(),
7858 vec![
7859 Expression::boxed_column(Column {
7860 name: Identifier::new(&unit_str),
7861 table: None,
7862 join_mark: false,
7863 trailing_comments: vec![],
7864 span: None,
7865 inferred_type: None,
7866 }),
7867 start_expr.clone(),
7868 end_expr.clone(),
7869 ],
7870 )));
7871 // DATEDIFF(...) + 1
7872 let datediff_plus_one = Expression::Add(Box::new(BinaryOp {
7873 left: datediff,
7874 right: Expression::Literal(Box::new(Literal::Number("1".to_string()))),
7875 left_comments: vec![],
7876 operator_comments: vec![],
7877 trailing_comments: vec![],
7878 inferred_type: None,
7879 }));
7880
7881 let array_gen_range = Expression::Function(Box::new(Function::new(
7882 "ARRAY_GENERATE_RANGE".to_string(),
7883 vec![
7884 Expression::Literal(Box::new(Literal::Number("0".to_string()))),
7885 datediff_plus_one,
7886 ],
7887 )));
7888
7889 let flatten_input = Expression::NamedArgument(Box::new(NamedArgument {
7890 name: Identifier::new("INPUT"),
7891 value: array_gen_range,
7892 separator: crate::expressions::NamedArgSeparator::DArrow,
7893 }));
7894 let flatten = Expression::Function(Box::new(Function::new(
7895 "FLATTEN".to_string(),
7896 vec![flatten_input],
7897 )));
7898
7899 let table_func =
7900 Expression::Function(Box::new(Function::new("TABLE".to_string(), vec![flatten])));
7901 let flatten_aliased = Expression::Alias(Box::new(Alias {
7902 this: table_func,
7903 alias: Identifier::new("_t0"),
7904 column_aliases: vec![
7905 Identifier::new("seq"),
7906 Identifier::new("key"),
7907 Identifier::new("path"),
7908 Identifier::new("index"),
7909 Identifier::new(col_name),
7910 Identifier::new("this"),
7911 ],
7912 alias_explicit_as: false,
7913 alias_keyword: None,
7914 pre_alias_comments: vec![],
7915 trailing_comments: vec![],
7916 inferred_type: None,
7917 }));
7918
7919 let dateadd_expr = Expression::Function(Box::new(Function::new(
7920 "DATEADD".to_string(),
7921 vec![
7922 Expression::boxed_column(Column {
7923 name: Identifier::new(&unit_str),
7924 table: None,
7925 join_mark: false,
7926 trailing_comments: vec![],
7927 span: None,
7928 inferred_type: None,
7929 }),
7930 Expression::Cast(Box::new(Cast {
7931 this: Expression::boxed_column(Column {
7932 name: Identifier::new(col_name),
7933 table: None,
7934 join_mark: false,
7935 trailing_comments: vec![],
7936 span: None,
7937 inferred_type: None,
7938 }),
7939 to: DataType::Int {
7940 length: None,
7941 integer_spelling: false,
7942 },
7943 trailing_comments: vec![],
7944 double_colon_syntax: false,
7945 format: None,
7946 default: None,
7947 inferred_type: None,
7948 })),
7949 start_expr.clone(),
7950 ],
7951 )));
7952 let dateadd_aliased = Expression::Alias(Box::new(Alias {
7953 this: dateadd_expr,
7954 alias: Identifier::new(col_name),
7955 column_aliases: vec![],
7956 alias_explicit_as: false,
7957 alias_keyword: None,
7958 pre_alias_comments: vec![],
7959 trailing_comments: vec![],
7960 inferred_type: None,
7961 }));
7962
7963 // Inner SELECT: SELECT DATEADD(...) AS value FROM TABLE(FLATTEN(...)) AS _t0(...)
7964 let mut inner_select = Select::new();
7965 inner_select.expressions = vec![dateadd_aliased];
7966 inner_select.from = Some(From {
7967 expressions: vec![flatten_aliased],
7968 });
7969
7970 // Wrap in subquery for the inner part
7971 let inner_subquery = Expression::Subquery(Box::new(Subquery {
7972 this: Expression::Select(Box::new(inner_select)),
7973 alias: None,
7974 column_aliases: vec![],
7975 alias_explicit_as: false,
7976 alias_keyword: None,
7977 order_by: None,
7978 limit: None,
7979 offset: None,
7980 distribute_by: None,
7981 sort_by: None,
7982 cluster_by: None,
7983 lateral: false,
7984 modifiers_inside: false,
7985 trailing_comments: vec![],
7986 inferred_type: None,
7987 }));
7988
7989 // Outer: SELECT ARRAY_AGG(*) FROM (inner_subquery)
7990 let star = Expression::Star(Star {
7991 table: None,
7992 except: None,
7993 replace: None,
7994 rename: None,
7995 trailing_comments: vec![],
7996 span: None,
7997 });
7998 let array_agg = Expression::ArrayAgg(Box::new(AggFunc {
7999 this: star,
8000 distinct: false,
8001 filter: None,
8002 order_by: vec![],
8003 name: Some("ARRAY_AGG".to_string()),
8004 ignore_nulls: None,
8005 having_max: None,
8006 limit: None,
8007 inferred_type: None,
8008 }));
8009
8010 let mut outer_select = Select::new();
8011 outer_select.expressions = vec![array_agg];
8012 outer_select.from = Some(From {
8013 expressions: vec![inner_subquery],
8014 });
8015
8016 // Wrap in a subquery
8017 let outer_subquery = Expression::Subquery(Box::new(Subquery {
8018 this: Expression::Select(Box::new(outer_select)),
8019 alias: None,
8020 column_aliases: vec![],
8021 alias_explicit_as: false,
8022 alias_keyword: None,
8023 order_by: None,
8024 limit: None,
8025 offset: None,
8026 distribute_by: None,
8027 sort_by: None,
8028 cluster_by: None,
8029 lateral: false,
8030 modifiers_inside: false,
8031 trailing_comments: vec![],
8032 inferred_type: None,
8033 }));
8034
8035 // ARRAY_SIZE(subquery)
8036 Ok(Expression::ArraySize(Box::new(UnaryFunc::new(
8037 outer_subquery,
8038 ))))
8039 }
8040
8041 /// Extract interval unit string from an optional step expression.
8042 fn extract_interval_unit_str(step: &Option<Expression>) -> Option<String> {
8043 use crate::expressions::*;
8044 if let Some(Expression::Interval(ref iv)) = step {
8045 if let Some(IntervalUnitSpec::Simple { ref unit, .. }) = iv.unit {
8046 return Some(format!("{:?}", unit).to_ascii_uppercase());
8047 }
8048 if let Some(ref this) = iv.this {
8049 if let Expression::Literal(lit) = this {
8050 if let Literal::String(ref s) = lit.as_ref() {
8051 let parts: Vec<&str> = s.split_whitespace().collect();
8052 if parts.len() == 2 {
8053 return Some(parts[1].to_ascii_uppercase());
8054 } else if parts.len() == 1 {
8055 let upper = parts[0].to_ascii_uppercase();
8056 if matches!(
8057 upper.as_str(),
8058 "YEAR"
8059 | "QUARTER"
8060 | "MONTH"
8061 | "WEEK"
8062 | "DAY"
8063 | "HOUR"
8064 | "MINUTE"
8065 | "SECOND"
8066 ) {
8067 return Some(upper);
8068 }
8069 }
8070 }
8071 }
8072 }
8073 }
8074 // Default to DAY if no step or no interval
8075 if step.is_none() {
8076 return Some("DAY".to_string());
8077 }
8078 None
8079 }
8080
8081 fn normalize_snowflake_pretty(mut sql: String) -> String {
8082 if sql.contains("LATERAL IFF(_u.pos = _u_2.pos_2, _u_2.entity, NULL) AS datasource(SEQ, KEY, PATH, INDEX, VALUE, THIS)")
8083 && sql.contains("ARRAY_GENERATE_RANGE(0, (GREATEST(ARRAY_SIZE(INPUT => PARSE_JSON(flags))) - 1) + 1)")
8084 {
8085 sql = sql.replace(
8086 "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')",
8087 "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 )",
8088 );
8089
8090 sql = sql.replace(
8091 "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)",
8092 "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)",
8093 );
8094
8095 sql = sql.replace(
8096 "OR (_u.pos > (ARRAY_SIZE(INPUT => PARSE_JSON(flags)) - 1)\n AND _u_2.pos_2 = (ARRAY_SIZE(INPUT => PARSE_JSON(flags)) - 1))",
8097 "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 )",
8098 );
8099 }
8100
8101 sql
8102 }
8103
8104 #[cfg(feature = "transpile")]
8105 fn wrap_tsql_top_level_values(expr: Expression) -> Expression {
8106 match expr {
8107 Expression::Values(values) => Self::tsql_values_as_select(*values),
8108 Expression::Union(mut union) => {
8109 let left = std::mem::replace(&mut union.left, Expression::Null(Null));
8110 let right = std::mem::replace(&mut union.right, Expression::Null(Null));
8111 union.left = Self::wrap_tsql_values_set_operand(left);
8112 union.right = Self::wrap_tsql_values_set_operand(right);
8113 Expression::Union(union)
8114 }
8115 Expression::Intersect(mut intersect) => {
8116 let left = std::mem::replace(&mut intersect.left, Expression::Null(Null));
8117 let right = std::mem::replace(&mut intersect.right, Expression::Null(Null));
8118 intersect.left = Self::wrap_tsql_values_set_operand(left);
8119 intersect.right = Self::wrap_tsql_values_set_operand(right);
8120 Expression::Intersect(intersect)
8121 }
8122 Expression::Except(mut except) => {
8123 let left = std::mem::replace(&mut except.left, Expression::Null(Null));
8124 let right = std::mem::replace(&mut except.right, Expression::Null(Null));
8125 except.left = Self::wrap_tsql_values_set_operand(left);
8126 except.right = Self::wrap_tsql_values_set_operand(right);
8127 Expression::Except(except)
8128 }
8129 other => other,
8130 }
8131 }
8132
8133 #[cfg(feature = "transpile")]
8134 fn wrap_tsql_values_set_operand(expr: Expression) -> Expression {
8135 match expr {
8136 Expression::Values(values) => Self::tsql_values_as_select(*values),
8137 Expression::Union(mut union) => {
8138 let left = std::mem::replace(&mut union.left, Expression::Null(Null));
8139 let right = std::mem::replace(&mut union.right, Expression::Null(Null));
8140 union.left = Self::wrap_tsql_values_set_operand(left);
8141 union.right = Self::wrap_tsql_values_set_operand(right);
8142 Expression::Union(union)
8143 }
8144 Expression::Intersect(mut intersect) => {
8145 let left = std::mem::replace(&mut intersect.left, Expression::Null(Null));
8146 let right = std::mem::replace(&mut intersect.right, Expression::Null(Null));
8147 intersect.left = Self::wrap_tsql_values_set_operand(left);
8148 intersect.right = Self::wrap_tsql_values_set_operand(right);
8149 Expression::Intersect(intersect)
8150 }
8151 Expression::Except(mut except) => {
8152 let left = std::mem::replace(&mut except.left, Expression::Null(Null));
8153 let right = std::mem::replace(&mut except.right, Expression::Null(Null));
8154 except.left = Self::wrap_tsql_values_set_operand(left);
8155 except.right = Self::wrap_tsql_values_set_operand(right);
8156 Expression::Except(except)
8157 }
8158 other => other,
8159 }
8160 }
8161
8162 #[cfg(feature = "transpile")]
8163 fn tsql_values_as_select(mut values: crate::expressions::Values) -> Expression {
8164 let column_aliases = if values.column_aliases.is_empty() {
8165 let column_count = values
8166 .expressions
8167 .first()
8168 .map(|row| row.expressions.len())
8169 .unwrap_or(0);
8170 (1..=column_count)
8171 .map(|index| Identifier::new(format!("column{index}")))
8172 .collect()
8173 } else {
8174 std::mem::take(&mut values.column_aliases)
8175 };
8176
8177 values.alias = None;
8178
8179 let values_subquery = Expression::Subquery(Box::new(crate::expressions::Subquery {
8180 this: Expression::Values(Box::new(values)),
8181 alias: Some(Identifier::new("_v")),
8182 column_aliases,
8183 alias_explicit_as: false,
8184 alias_keyword: None,
8185 order_by: None,
8186 limit: None,
8187 offset: None,
8188 distribute_by: None,
8189 sort_by: None,
8190 cluster_by: None,
8191 lateral: false,
8192 modifiers_inside: false,
8193 trailing_comments: Vec::new(),
8194 inferred_type: None,
8195 }));
8196
8197 let mut select = crate::expressions::Select::new();
8198 select.expressions = vec![Expression::star()];
8199 select.from = Some(From {
8200 expressions: vec![values_subquery],
8201 });
8202
8203 Expression::Select(Box::new(select))
8204 }
8205
8206 /// Apply cross-dialect semantic normalizations that depend on knowing both source and target.
8207 /// This handles cases where the same syntax has different semantics across dialects.
8208 fn cross_dialect_normalize(
8209 expr: Expression,
8210 source: DialectType,
8211 target: DialectType,
8212 ) -> Result<Expression> {
8213 use crate::expressions::{
8214 AggFunc, BinaryOp, Case, Cast, ConvertTimezone, DataType, DateTimeField, DateTruncFunc,
8215 Function, Identifier, IsNull, Literal, Null, Paren,
8216 };
8217
8218 // Helper to tag which kind of transform to apply
8219 #[derive(Debug)]
8220 enum Action {
8221 None,
8222 GreatestLeastNull,
8223 ArrayGenerateRange,
8224 Div0TypedDivision,
8225 ArrayAggCollectList,
8226 ArrayAggWithinGroupFilter,
8227 ArrayAggFilter,
8228 CastTimestampToDatetime,
8229 DateTruncWrapCast,
8230 ToDateToCast,
8231 ConvertTimezoneToExpr,
8232 SetToVariable,
8233 RegexpReplaceSnowflakeToDuckDB,
8234 BigQueryFunctionNormalize,
8235 BigQuerySafeDivide,
8236 BigQueryCastType,
8237 BigQueryToHexBare, // _BQ_TO_HEX(x) with no LOWER/UPPER wrapper
8238 BigQueryToHexLower, // LOWER(_BQ_TO_HEX(x))
8239 BigQueryToHexUpper, // UPPER(_BQ_TO_HEX(x))
8240 BigQueryLastDayStripUnit, // LAST_DAY(date, MONTH) -> LAST_DAY(date)
8241 BigQueryCastFormat, // CAST(x AS type FORMAT 'fmt') -> PARSE_DATE/PARSE_TIMESTAMP etc.
8242 BigQueryAnyValueHaving, // ANY_VALUE(x HAVING MAX/MIN y) -> ARG_MAX_NULL/ARG_MIN_NULL for DuckDB
8243 BigQueryApproxQuantiles, // APPROX_QUANTILES(x, n) -> APPROX_QUANTILE(x, [quantiles]) for DuckDB
8244 GenericFunctionNormalize, // Cross-dialect function renaming (non-BigQuery sources)
8245 RegexpLikeToDuckDB, // RegexpLike -> REGEXP_MATCHES for DuckDB target
8246 RegexpLikeToTsqlPatindex, // RegexpLike/RegexpILike -> PATINDEX(...) > 0 for TSQL/Fabric
8247 SimilarToToTsqlLike, // SimilarTo -> LIKE for TSQL/Fabric-compatible patterns
8248 PostgresJsonBuildObjectToJsonObject, // json[b]_build_object -> JSON_OBJECT
8249 PostgresJsonAggToJsonArrayAgg, // json[b]_agg -> JSON_ARRAYAGG
8250 EpochConvert, // Expression::Epoch -> target-specific epoch function
8251 EpochMsConvert, // Expression::EpochMs -> target-specific epoch ms function
8252 TSQLTypeNormalize, // TSQL types (MONEY, SMALLMONEY, REAL, DATETIME2) -> standard types
8253 MySQLSafeDivide, // MySQL a/b -> a / NULLIF(b, 0) with optional CAST
8254 NullsOrdering, // Add NULLS FIRST/LAST for ORDER BY
8255 AlterTableRenameStripSchema, // ALTER TABLE db.t1 RENAME TO db.t2 -> ALTER TABLE db.t1 RENAME TO t2
8256 StringAggConvert, // STRING_AGG/WITHIN GROUP -> target-specific aggregate
8257 GroupConcatConvert, // GROUP_CONCAT -> target-specific aggregate
8258 TempTableHash, // TSQL #table -> temp table normalization
8259 ArrayLengthConvert, // CARDINALITY/ARRAY_LENGTH/ARRAY_SIZE -> target-specific
8260 DatePartUnquote, // DATE_PART('month', x) -> DATE_PART(month, x) for Snowflake target
8261 NvlClearOriginal, // Clear NVL original_name for cross-dialect transpilation
8262 HiveCastToTryCast, // Hive/Spark CAST -> TRY_CAST for targets that support it
8263 XorExpand, // MySQL XOR -> (a AND NOT b) OR (NOT a AND b) for non-XOR targets
8264 CastTimestampStripTz, // CAST(x AS TIMESTAMP WITH TIME ZONE) -> CAST(x AS TIMESTAMP) for Hive/Spark
8265 JsonExtractToGetJsonObject, // JSON_EXTRACT/JSON_EXTRACT_SCALAR -> GET_JSON_OBJECT for Hive/Spark
8266 JsonExtractScalarToGetJsonObject, // JSON_EXTRACT_SCALAR -> GET_JSON_OBJECT for Hive/Spark
8267 JsonQueryValueConvert, // JsonQuery/JsonValue -> target-specific (ISNULL wrapper for TSQL, GET_JSON_OBJECT for Spark, etc.)
8268 JsonLiteralToJsonParse, // JSON 'x' -> JSON_PARSE('x') for Presto, PARSE_JSON for Snowflake; also DuckDB CAST(x AS JSON)
8269 DuckDBCastJsonToVariant, // DuckDB CAST(x AS JSON) -> CAST(x AS VARIANT) for Snowflake
8270 DuckDBTryCastJsonToTryJsonParse, // DuckDB TRY_CAST(x AS JSON) -> TRY(JSON_PARSE(x)) for Trino/Presto/Athena
8271 DuckDBJsonFuncToJsonParse, // DuckDB json(x) -> JSON_PARSE(x) for Trino/Presto/Athena
8272 DuckDBJsonValidToIsJson, // DuckDB json_valid(x) -> x IS JSON for Trino/Presto/Athena
8273 ArraySyntaxConvert, // ARRAY[x] -> ARRAY(x) for Spark, [x] for BigQuery/DuckDB
8274 AtTimeZoneConvert, // AT TIME ZONE -> AT_TIMEZONE (Presto) / FROM_UTC_TIMESTAMP (Spark)
8275 DayOfWeekConvert, // DAY_OF_WEEK -> dialect-specific
8276 MaxByMinByConvert, // MAX_BY/MIN_BY -> argMax/argMin for ClickHouse
8277 ArrayAggToCollectList, // ARRAY_AGG(x ORDER BY ...) -> COLLECT_LIST(x) for Hive/Spark
8278 ArrayAggToGroupConcat, // ARRAY_AGG(x) -> GROUP_CONCAT(x) for MySQL-like targets
8279 ElementAtConvert, // ELEMENT_AT(arr, idx) -> arr[idx] for PostgreSQL, arr[SAFE_ORDINAL(idx)] for BigQuery
8280 CurrentUserParens, // CURRENT_USER -> CURRENT_USER() for Snowflake
8281 CastToJsonForSpark, // CAST(x AS JSON) -> TO_JSON(x) for Spark
8282 CastJsonToFromJson, // CAST(JSON_PARSE(literal) AS ARRAY/MAP) -> FROM_JSON(literal, type_string)
8283 ToJsonConvert, // TO_JSON(x) -> JSON_FORMAT(CAST(x AS JSON)) for Presto etc.
8284 ArrayAggNullFilter, // ARRAY_AGG(x) FILTER(WHERE cond) -> add AND NOT x IS NULL for DuckDB
8285 ArrayAggIgnoreNullsDuckDB, // ARRAY_AGG(x IGNORE NULLS ORDER BY ...) -> ARRAY_AGG(x ORDER BY a NULLS FIRST, ...) for DuckDB
8286 BigQueryPercentileContToDuckDB, // PERCENTILE_CONT(x, frac RESPECT NULLS) -> QUANTILE_CONT(x, frac) for DuckDB
8287 BigQueryArraySelectAsStructToSnowflake, // ARRAY(SELECT AS STRUCT ...) -> (SELECT ARRAY_AGG(OBJECT_CONSTRUCT(...)))
8288 CountDistinctMultiArg, // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN ... END)
8289 VarianceToClickHouse, // Expression::Variance -> varSamp for ClickHouse
8290 StddevToClickHouse, // Expression::Stddev -> stddevSamp for ClickHouse
8291 ApproxQuantileConvert, // Expression::ApproxQuantile -> APPROX_PERCENTILE for Snowflake
8292 ArrayIndexConvert, // array[1] -> array[0] for BigQuery (1-based to 0-based)
8293 DollarParamConvert, // $foo -> @foo for BigQuery
8294 TablesampleReservoir, // TABLESAMPLE (n ROWS) -> TABLESAMPLE RESERVOIR (n ROWS) for DuckDB
8295 BitAggFloatCast, // BIT_OR/BIT_AND/BIT_XOR float arg -> CAST(ROUND(CAST(arg)) AS INT) for DuckDB
8296 BitAggSnowflakeRename, // BIT_OR -> BITORAGG, BIT_AND -> BITANDAGG etc. for Snowflake
8297 StrftimeCastTimestamp, // CAST TIMESTAMP -> TIMESTAMP_NTZ for Spark in STRFTIME
8298 AnyValueIgnoreNulls, // ANY_VALUE(x) -> ANY_VALUE(x) IGNORE NULLS for Spark
8299 CreateTableStripComment, // Strip COMMENT column constraint, USING, PARTITIONED BY for DuckDB
8300 EscapeStringNormalize, // e'Hello\nworld' literal newline -> \n
8301 AnyToExists, // PostgreSQL x <op> ANY(array) -> EXISTS(array, x -> ...)
8302 ArrayConcatBracketConvert, // [1,2] -> ARRAY[1,2] for PostgreSQL in ARRAY_CAT
8303 SnowflakeIntervalFormat, // INTERVAL '2' HOUR -> INTERVAL '2 HOUR' for Snowflake
8304 AlterTableToSpRename, // ALTER TABLE RENAME -> EXEC sp_rename for TSQL
8305 StraightJoinCase, // STRAIGHT_JOIN -> straight_join for DuckDB
8306 RespectNullsConvert, // RESPECT NULLS window function handling
8307 MysqlNullsOrdering, // MySQL doesn't support NULLS ordering
8308 MysqlNullsLastRewrite, // Add CASE WHEN to ORDER BY for DuckDB -> MySQL (NULLS LAST simulation)
8309 BigQueryNullsOrdering, // BigQuery doesn't support NULLS FIRST/LAST - strip
8310 SnowflakeFloatProtect, // Protect FLOAT from being converted to DOUBLE by Snowflake target transform
8311 JsonToGetPath, // JSON arrow -> GET_PATH/PARSE_JSON for Snowflake
8312 FilterToIff, // FILTER(WHERE) -> IFF wrapping for Snowflake
8313 AggFilterToIff, // AggFunc.filter -> IFF wrapping for Snowflake (e.g., AVG(x) FILTER(WHERE cond))
8314 StructToRow, // DuckDB struct -> Presto ROW / BigQuery STRUCT
8315 SparkStructConvert, // Spark STRUCT(x AS col1, ...) -> ROW/DuckDB struct
8316 DecimalDefaultPrecision, // DECIMAL -> DECIMAL(18, 3) for Snowflake in BIT agg
8317 ApproxCountDistinctToApproxDistinct, // APPROX_COUNT_DISTINCT -> APPROX_DISTINCT for Presto/Trino
8318 CollectListToArrayAgg, // COLLECT_LIST -> ARRAY_AGG for Presto/DuckDB
8319 CollectSetConvert, // COLLECT_SET -> SET_AGG/ARRAY_AGG(DISTINCT)/ARRAY_UNIQUE_AGG
8320 PercentileConvert, // PERCENTILE -> QUANTILE/APPROX_PERCENTILE
8321 CorrIsnanWrap, // CORR(a,b) -> CASE WHEN ISNAN(CORR(a,b)) THEN NULL ELSE CORR(a,b) END
8322 TruncToDateTrunc, // TRUNC(ts, unit) -> DATE_TRUNC(unit, ts)
8323 ArrayContainsConvert, // ARRAY_CONTAINS -> CONTAINS/target-specific
8324 StrPositionExpand, // StrPosition with position -> complex STRPOS expansion for Presto/DuckDB
8325 TablesampleSnowflakeStrip, // Strip method and PERCENT for Snowflake target
8326 FirstToAnyValue, // FIRST(col) IGNORE NULLS -> ANY_VALUE(col) for DuckDB
8327 MonthsBetweenConvert, // Expression::MonthsBetween -> target-specific
8328 CurrentUserSparkParens, // CURRENT_USER -> CURRENT_USER() for Spark
8329 SparkDateFuncCast, // MONTH/YEAR/DAY('str') -> MONTH/YEAR/DAY(CAST('str' AS DATE)) from Spark
8330 MapFromArraysConvert, // Expression::MapFromArrays -> MAP/OBJECT_CONSTRUCT/MAP_FROM_ARRAYS
8331 AddMonthsConvert, // Expression::AddMonths -> target-specific DATEADD/DATE_ADD
8332 PercentileContConvert, // PERCENTILE_CONT/DISC WITHIN GROUP -> APPROX_PERCENTILE/PERCENTILE_APPROX
8333 GenerateSeriesConvert, // GENERATE_SERIES -> SEQUENCE/UNNEST(SEQUENCE)/EXPLODE(SEQUENCE)
8334 ConcatCoalesceWrap, // CONCAT(a, b) -> CONCAT(COALESCE(CAST(a), ''), ...) for Presto/ClickHouse
8335 PipeConcatToConcat, // a || b -> CONCAT(CAST(a), CAST(b)) for Presto
8336 DivFuncConvert, // DIV(a, b) -> target-specific integer division
8337 CbrtToPower, // CBRT(x) -> POWER(CAST(x AS FLOAT), 1.0 / 3.0)
8338 JsonObjectAggConvert, // JSON_OBJECT_AGG -> JSON_GROUP_OBJECT for DuckDB
8339 JsonbExistsConvert, // JSONB_EXISTS -> JSON_EXISTS for DuckDB
8340 DateBinConvert, // DATE_BIN -> TIME_BUCKET for DuckDB
8341 MysqlCastCharToText, // MySQL CAST(x AS CHAR) -> CAST(x AS TEXT/VARCHAR/STRING) for targets
8342 SparkCastVarcharToString, // Spark CAST(x AS VARCHAR/CHAR) -> CAST(x AS STRING) for Spark targets
8343 JsonExtractToArrow, // JSON_EXTRACT(x, path) -> x -> path for SQLite/DuckDB
8344 JsonExtractToTsql, // JSON operators/functions -> T-SQL/Fabric JSON_QUERY/JSON_VALUE
8345 JsonExtractToClickHouse, // JSON_EXTRACT/JSON_EXTRACT_SCALAR -> JSONExtractString for ClickHouse
8346 JsonExtractScalarConvert, // JSON_EXTRACT_SCALAR -> target-specific (PostgreSQL, Snowflake, SQLite)
8347 JsonPathNormalize, // Normalize JSON path format (brackets, wildcards, quotes) for various dialects
8348 MinMaxToLeastGreatest, // Multi-arg MIN(a,b,c) -> LEAST(a,b,c), MAX(a,b,c) -> GREATEST(a,b,c)
8349 ClickHouseUniqToApproxCountDistinct, // uniq(x) -> APPROX_COUNT_DISTINCT(x) for non-ClickHouse targets
8350 ClickHouseAnyToAnyValue, // any(x) -> ANY_VALUE(x) for non-ClickHouse targets
8351 OracleVarchar2ToVarchar, // VARCHAR2(N CHAR/BYTE) -> VARCHAR(N) for non-Oracle targets
8352 Nvl2Expand, // NVL2(a, b, c) -> CASE WHEN NOT a IS NULL THEN b ELSE c END
8353 IfnullToCoalesce, // IFNULL(a, b) -> COALESCE(a, b)
8354 IsAsciiConvert, // IS_ASCII(x) -> dialect-specific ASCII check
8355 StrPositionConvert, // STR_POSITION(haystack, needle[, pos]) -> dialect-specific
8356 DecodeSimplify, // DECODE with null-safe -> simple = comparison
8357 ArraySumConvert, // ARRAY_SUM -> target-specific
8358 ArraySizeConvert, // ARRAY_SIZE -> target-specific
8359 ArrayAnyConvert, // ARRAY_ANY -> target-specific
8360 CastTimestamptzToFunc, // CAST(x AS TIMESTAMPTZ) -> TIMESTAMP(x) for MySQL/StarRocks
8361 TsOrDsToDateConvert, // TS_OR_DS_TO_DATE(x[, fmt]) -> dialect-specific
8362 TsOrDsToDateStrConvert, // TS_OR_DS_TO_DATE_STR(x) -> SUBSTRING(CAST(x AS type), 1, 10)
8363 DateStrToDateConvert, // DATE_STR_TO_DATE(x) -> CAST(x AS DATE)
8364 TimeStrToDateConvert, // TIME_STR_TO_DATE(x) -> CAST(x AS DATE)
8365 TimeStrToTimeConvert, // TIME_STR_TO_TIME(x) -> CAST(x AS TIMESTAMP)
8366 DateToDateStrConvert, // DATE_TO_DATE_STR(x) -> CAST(x AS TEXT/VARCHAR/STRING)
8367 DateToDiConvert, // DATE_TO_DI(x) -> dialect-specific (CAST date to YYYYMMDD integer)
8368 DiToDateConvert, // DI_TO_DATE(x) -> dialect-specific (integer YYYYMMDD to date)
8369 TsOrDiToDiConvert, // TS_OR_DI_TO_DI(x) -> dialect-specific
8370 UnixToStrConvert, // UNIX_TO_STR(x, fmt) -> dialect-specific
8371 UnixToTimeConvert, // UNIX_TO_TIME(x) -> dialect-specific
8372 UnixToTimeStrConvert, // UNIX_TO_TIME_STR(x) -> dialect-specific
8373 TimeToUnixConvert, // TIME_TO_UNIX(x) -> dialect-specific
8374 TimeToStrConvert, // TIME_TO_STR(x, fmt) -> dialect-specific
8375 StrToUnixConvert, // STR_TO_UNIX(x, fmt) -> dialect-specific
8376 DateTruncSwapArgs, // DATE_TRUNC('unit', x) -> DATE_TRUNC(x, unit) / TRUNC(x, unit)
8377 TimestampTruncConvert, // TIMESTAMP_TRUNC(x, UNIT[, tz]) -> dialect-specific
8378 StrToDateConvert, // STR_TO_DATE(x, fmt) from Generic -> CAST(StrToTime(x,fmt) AS DATE)
8379 TsOrDsAddConvert, // TS_OR_DS_ADD(x, n, 'UNIT') from Generic -> DATE_ADD per dialect
8380 DateFromUnixDateConvert, // DATE_FROM_UNIX_DATE(n) -> DATEADD(DAY, n, '1970-01-01')
8381 TimeStrToUnixConvert, // TIME_STR_TO_UNIX(x) -> dialect-specific
8382 TimeToTimeStrConvert, // TIME_TO_TIME_STR(x) -> CAST(x AS type)
8383 CreateTableLikeToCtas, // CREATE TABLE a LIKE b -> CREATE TABLE a AS SELECT * FROM b LIMIT 0
8384 CreateTableLikeToSelectInto, // CREATE TABLE a LIKE b -> SELECT TOP 0 * INTO a FROM b AS temp
8385 CreateTableLikeToAs, // CREATE TABLE a LIKE b -> CREATE TABLE a AS b (ClickHouse)
8386 ArrayRemoveConvert, // ARRAY_REMOVE(arr, target) -> LIST_FILTER/arrayFilter/ARRAY subquery
8387 ArrayReverseConvert, // ARRAY_REVERSE(x) -> arrayReverse(x) for ClickHouse
8388 JsonKeysConvert, // JSON_KEYS -> JSON_OBJECT_KEYS/OBJECT_KEYS
8389 ParseJsonStrip, // PARSE_JSON(x) -> x (strip wrapper)
8390 ArraySizeDrill, // ARRAY_SIZE -> REPEATED_COUNT for Drill
8391 WeekOfYearToWeekIso, // WEEKOFYEAR -> WEEKISO for Snowflake cross-dialect
8392 RegexpSubstrSnowflakeToDuckDB, // REGEXP_SUBSTR(s, p, ...) -> REGEXP_EXTRACT variants for DuckDB
8393 RegexpSubstrSnowflakeIdentity, // REGEXP_SUBSTR/REGEXP_SUBSTR_ALL strip trailing group=0 for Snowflake identity
8394 RegexpSubstrAllSnowflakeToDuckDB, // REGEXP_SUBSTR_ALL(s, p, ...) -> REGEXP_EXTRACT_ALL variants for DuckDB
8395 RegexpCountSnowflakeToDuckDB, // REGEXP_COUNT(s, p, ...) -> LENGTH(REGEXP_EXTRACT_ALL(...)) for DuckDB
8396 RegexpInstrSnowflakeToDuckDB, // REGEXP_INSTR(s, p, ...) -> complex CASE expression for DuckDB
8397 RegexpReplacePositionSnowflakeToDuckDB, // REGEXP_REPLACE(s, p, r, pos, occ) -> DuckDB form
8398 RlikeSnowflakeToDuckDB, // RLIKE(a, b[, flags]) -> REGEXP_FULL_MATCH(a, b[, flags]) for DuckDB
8399 RegexpExtractAllToSnowflake, // BigQuery REGEXP_EXTRACT_ALL -> REGEXP_SUBSTR_ALL for Snowflake
8400 ArrayExceptConvert, // ARRAY_EXCEPT -> DuckDB complex CASE / Snowflake ARRAY_EXCEPT / Presto ARRAY_EXCEPT
8401 ArrayPositionSnowflakeSwap, // ARRAY_POSITION(arr, elem) -> ARRAY_POSITION(elem, arr) for Snowflake
8402 RegexpLikeExasolAnchor, // RegexpLike -> Exasol REGEXP_LIKE with .*pattern.* anchoring
8403 ArrayDistinctConvert, // ARRAY_DISTINCT -> DuckDB LIST_DISTINCT with NULL-aware CASE
8404 ArrayDistinctClickHouse, // ARRAY_DISTINCT -> arrayDistinct for ClickHouse
8405 ArrayContainsDuckDBConvert, // ARRAY_CONTAINS -> DuckDB CASE with NULL-aware check
8406 SnowflakeWindowFrameStrip, // Strip default ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING for Snowflake target
8407 SnowflakeWindowFrameAdd, // Add default ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING for non-Snowflake target
8408 SnowflakeArrayPositionToDuckDB, // ARRAY_POSITION(val, arr) -> ARRAY_POSITION(arr, val) - 1 for DuckDB
8409 }
8410
8411 // Handle SELECT INTO -> CREATE TABLE AS for DuckDB/Snowflake/etc.
8412 let expr = if matches!(source, DialectType::TSQL | DialectType::Fabric) {
8413 Self::transform_select_into(expr, source, target)
8414 } else {
8415 expr
8416 };
8417
8418 // Strip OFFSET ROWS for non-TSQL/Oracle targets
8419 let expr = if !matches!(
8420 target,
8421 DialectType::TSQL | DialectType::Oracle | DialectType::Fabric
8422 ) {
8423 if let Expression::Select(mut select) = expr {
8424 if let Some(ref mut offset) = select.offset {
8425 offset.rows = None;
8426 }
8427 Expression::Select(select)
8428 } else {
8429 expr
8430 }
8431 } else {
8432 expr
8433 };
8434
8435 // Oracle: LIMIT -> FETCH FIRST, OFFSET -> OFFSET ROWS
8436 let expr = if matches!(target, DialectType::Oracle) {
8437 if let Expression::Select(mut select) = expr {
8438 if let Some(limit) = select.limit.take() {
8439 // Convert LIMIT to FETCH FIRST n ROWS ONLY
8440 select.fetch = Some(crate::expressions::Fetch {
8441 direction: "FIRST".to_string(),
8442 count: Some(limit.this),
8443 percent: false,
8444 rows: true,
8445 with_ties: false,
8446 });
8447 }
8448 // Add ROWS to OFFSET if present
8449 if let Some(ref mut offset) = select.offset {
8450 offset.rows = Some(true);
8451 }
8452 Expression::Select(select)
8453 } else {
8454 expr
8455 }
8456 } else {
8457 expr
8458 };
8459
8460 // Handle CreateTable WITH properties transformation before recursive transforms
8461 let expr = if let Expression::CreateTable(mut ct) = expr {
8462 Self::transform_create_table_properties(&mut ct, source, target);
8463
8464 // Handle Hive-style PARTITIONED BY (col_name type, ...) -> target-specific
8465 // When the PARTITIONED BY clause contains column definitions, merge them into the
8466 // main column list and adjust the PARTITIONED BY clause for the target dialect.
8467 if matches!(
8468 source,
8469 DialectType::Hive | DialectType::Spark | DialectType::Databricks
8470 ) {
8471 let mut partition_col_names: Vec<String> = Vec::new();
8472 let mut partition_col_defs: Vec<crate::expressions::ColumnDef> = Vec::new();
8473 let mut has_col_def_partitions = false;
8474
8475 // Check if any PARTITIONED BY property contains ColumnDef expressions
8476 for prop in &ct.properties {
8477 if let Expression::PartitionedByProperty(ref pbp) = prop {
8478 if let Expression::Tuple(ref tuple) = *pbp.this {
8479 for expr in &tuple.expressions {
8480 if let Expression::ColumnDef(ref cd) = expr {
8481 has_col_def_partitions = true;
8482 partition_col_names.push(cd.name.name.clone());
8483 partition_col_defs.push(*cd.clone());
8484 }
8485 }
8486 }
8487 }
8488 }
8489
8490 if has_col_def_partitions && !matches!(target, DialectType::Hive) {
8491 // Merge partition columns into main column list
8492 for cd in partition_col_defs {
8493 ct.columns.push(cd);
8494 }
8495
8496 // Replace PARTITIONED BY property with column-name-only version
8497 ct.properties
8498 .retain(|p| !matches!(p, Expression::PartitionedByProperty(_)));
8499
8500 if matches!(
8501 target,
8502 DialectType::Presto | DialectType::Trino | DialectType::Athena
8503 ) {
8504 // Presto: WITH (PARTITIONED_BY=ARRAY['y', 'z'])
8505 let array_elements: Vec<String> = partition_col_names
8506 .iter()
8507 .map(|n| format!("'{}'", n))
8508 .collect();
8509 let array_value = format!("ARRAY[{}]", array_elements.join(", "));
8510 ct.with_properties
8511 .push(("PARTITIONED_BY".to_string(), array_value));
8512 } else if matches!(target, DialectType::Spark | DialectType::Databricks) {
8513 // Spark: PARTITIONED BY (y, z) - just column names
8514 let name_exprs: Vec<Expression> = partition_col_names
8515 .iter()
8516 .map(|n| {
8517 Expression::Column(Box::new(crate::expressions::Column {
8518 name: crate::expressions::Identifier::new(n.clone()),
8519 table: None,
8520 join_mark: false,
8521 trailing_comments: Vec::new(),
8522 span: None,
8523 inferred_type: None,
8524 }))
8525 })
8526 .collect();
8527 ct.properties.insert(
8528 0,
8529 Expression::PartitionedByProperty(Box::new(
8530 crate::expressions::PartitionedByProperty {
8531 this: Box::new(Expression::Tuple(Box::new(
8532 crate::expressions::Tuple {
8533 expressions: name_exprs,
8534 },
8535 ))),
8536 },
8537 )),
8538 );
8539 }
8540 // For DuckDB and other targets, just drop the PARTITIONED BY (already retained above)
8541 }
8542
8543 // Note: Non-ColumnDef partitions (e.g., function expressions like MONTHS(y))
8544 // are handled by transform_create_table_properties which runs first
8545 }
8546
8547 // Strip LOCATION property for Presto/Trino (not supported)
8548 if matches!(
8549 target,
8550 DialectType::Presto | DialectType::Trino | DialectType::Athena
8551 ) {
8552 ct.properties
8553 .retain(|p| !matches!(p, Expression::LocationProperty(_)));
8554 }
8555
8556 // Strip table-level constraints for Spark/Hive/Databricks
8557 // Keep PRIMARY KEY and LIKE constraints but strip TSQL-specific modifiers; remove all others
8558 if matches!(
8559 target,
8560 DialectType::Spark | DialectType::Databricks | DialectType::Hive
8561 ) {
8562 ct.constraints.retain(|c| {
8563 matches!(
8564 c,
8565 crate::expressions::TableConstraint::PrimaryKey { .. }
8566 | crate::expressions::TableConstraint::Like { .. }
8567 )
8568 });
8569 for constraint in &mut ct.constraints {
8570 if let crate::expressions::TableConstraint::PrimaryKey {
8571 columns,
8572 modifiers,
8573 ..
8574 } = constraint
8575 {
8576 // Strip ASC/DESC from column names
8577 for col in columns.iter_mut() {
8578 if col.name.ends_with(" ASC") {
8579 col.name = col.name[..col.name.len() - 4].to_string();
8580 } else if col.name.ends_with(" DESC") {
8581 col.name = col.name[..col.name.len() - 5].to_string();
8582 }
8583 }
8584 // Strip TSQL-specific modifiers
8585 modifiers.clustered = None;
8586 modifiers.with_options.clear();
8587 modifiers.on_filegroup = None;
8588 }
8589 }
8590 }
8591
8592 // Databricks: IDENTITY columns with INT/INTEGER -> BIGINT
8593 if matches!(target, DialectType::Databricks) {
8594 for col in &mut ct.columns {
8595 if col.auto_increment {
8596 if matches!(col.data_type, crate::expressions::DataType::Int { .. }) {
8597 col.data_type = crate::expressions::DataType::BigInt { length: None };
8598 }
8599 }
8600 }
8601 }
8602
8603 // Spark/Databricks: INTEGER -> INT in column definitions
8604 // Python sqlglot always outputs INT for Spark/Databricks
8605 if matches!(target, DialectType::Spark | DialectType::Databricks) {
8606 for col in &mut ct.columns {
8607 if let crate::expressions::DataType::Int {
8608 integer_spelling, ..
8609 } = &mut col.data_type
8610 {
8611 *integer_spelling = false;
8612 }
8613 }
8614 }
8615
8616 // Strip explicit NULL constraints for Hive/Spark (B INTEGER NULL -> B INTEGER)
8617 if matches!(target, DialectType::Hive | DialectType::Spark) {
8618 for col in &mut ct.columns {
8619 // If nullable is explicitly true (NULL), change to None (omit it)
8620 if col.nullable == Some(true) {
8621 col.nullable = None;
8622 }
8623 // Also remove from constraints if stored there
8624 col.constraints
8625 .retain(|c| !matches!(c, crate::expressions::ColumnConstraint::Null));
8626 }
8627 }
8628
8629 // Strip TSQL ON filegroup for non-TSQL/Fabric targets
8630 if ct.on_property.is_some()
8631 && !matches!(target, DialectType::TSQL | DialectType::Fabric)
8632 {
8633 ct.on_property = None;
8634 }
8635
8636 // Snowflake: strip ARRAY type parameters (ARRAY<INT> -> ARRAY, ARRAY<ARRAY<INT>> -> ARRAY)
8637 // Snowflake doesn't support typed arrays in DDL
8638 if matches!(target, DialectType::Snowflake) {
8639 fn strip_array_type_params(dt: &mut crate::expressions::DataType) {
8640 if let crate::expressions::DataType::Array { .. } = dt {
8641 *dt = crate::expressions::DataType::Custom {
8642 name: "ARRAY".to_string(),
8643 };
8644 }
8645 }
8646 for col in &mut ct.columns {
8647 strip_array_type_params(&mut col.data_type);
8648 }
8649 }
8650
8651 // PostgreSQL target: ensure IDENTITY columns have NOT NULL
8652 // If NOT NULL was explicit in source (present in constraint_order), preserve original order.
8653 // If NOT NULL was not explicit, add it after IDENTITY (GENERATED BY DEFAULT AS IDENTITY NOT NULL).
8654 if matches!(target, DialectType::PostgreSQL) {
8655 for col in &mut ct.columns {
8656 if col.auto_increment && !col.constraint_order.is_empty() {
8657 use crate::expressions::ConstraintType;
8658 let has_explicit_not_null = col
8659 .constraint_order
8660 .iter()
8661 .any(|ct| *ct == ConstraintType::NotNull);
8662
8663 if has_explicit_not_null {
8664 // Source had explicit NOT NULL - preserve original order
8665 // Just ensure nullable is set
8666 if col.nullable != Some(false) {
8667 col.nullable = Some(false);
8668 }
8669 } else {
8670 // Source didn't have explicit NOT NULL - build order with
8671 // AutoIncrement + NotNull first, then remaining constraints
8672 let mut new_order = Vec::new();
8673 // Put AutoIncrement (IDENTITY) first, followed by synthetic NotNull
8674 new_order.push(ConstraintType::AutoIncrement);
8675 new_order.push(ConstraintType::NotNull);
8676 // Add remaining constraints in original order (except AutoIncrement)
8677 for ct_type in &col.constraint_order {
8678 if *ct_type != ConstraintType::AutoIncrement {
8679 new_order.push(ct_type.clone());
8680 }
8681 }
8682 col.constraint_order = new_order;
8683 col.nullable = Some(false);
8684 }
8685 }
8686 }
8687 }
8688
8689 Expression::CreateTable(ct)
8690 } else {
8691 expr
8692 };
8693
8694 // Handle CreateView column stripping for Presto/Trino target
8695 let expr = if let Expression::CreateView(mut cv) = expr {
8696 // Presto/Trino: drop column list when view has a SELECT body
8697 if matches!(target, DialectType::Presto | DialectType::Trino) && !cv.columns.is_empty()
8698 {
8699 if !matches!(&cv.query, Expression::Null(_)) {
8700 cv.columns.clear();
8701 }
8702 }
8703 Expression::CreateView(cv)
8704 } else {
8705 expr
8706 };
8707
8708 // Wrap bare VALUES in CTE bodies with SELECT * FROM (...) AS _values for generic/non-Presto targets
8709 let expr = if !matches!(
8710 target,
8711 DialectType::Presto | DialectType::Trino | DialectType::Athena
8712 ) {
8713 if let Expression::Select(mut select) = expr {
8714 if let Some(ref mut with) = select.with {
8715 for cte in &mut with.ctes {
8716 if let Expression::Values(ref vals) = cte.this {
8717 // Build: SELECT * FROM (VALUES ...) AS _values
8718 let values_subquery =
8719 Expression::Subquery(Box::new(crate::expressions::Subquery {
8720 this: Expression::Values(vals.clone()),
8721 alias: Some(Identifier::new("_values".to_string())),
8722 column_aliases: Vec::new(),
8723 alias_explicit_as: false,
8724 alias_keyword: None,
8725 order_by: None,
8726 limit: None,
8727 offset: None,
8728 distribute_by: None,
8729 sort_by: None,
8730 cluster_by: None,
8731 lateral: false,
8732 modifiers_inside: false,
8733 trailing_comments: Vec::new(),
8734 inferred_type: None,
8735 }));
8736 let mut new_select = crate::expressions::Select::new();
8737 new_select.expressions =
8738 vec![Expression::Star(crate::expressions::Star {
8739 table: None,
8740 except: None,
8741 replace: None,
8742 rename: None,
8743 trailing_comments: Vec::new(),
8744 span: None,
8745 })];
8746 new_select.from = Some(crate::expressions::From {
8747 expressions: vec![values_subquery],
8748 });
8749 cte.this = Expression::Select(Box::new(new_select));
8750 }
8751 }
8752 }
8753 Expression::Select(select)
8754 } else {
8755 expr
8756 }
8757 } else {
8758 expr
8759 };
8760
8761 let expr = if matches!(target, DialectType::TSQL | DialectType::Fabric) {
8762 Self::wrap_tsql_top_level_values(expr)
8763 } else {
8764 expr
8765 };
8766
8767 // PostgreSQL CREATE INDEX: add NULLS FIRST to index columns that don't have nulls ordering
8768 let expr = if matches!(target, DialectType::PostgreSQL) {
8769 if let Expression::CreateIndex(mut ci) = expr {
8770 for col in &mut ci.columns {
8771 if col.nulls_first.is_none() {
8772 col.nulls_first = Some(true);
8773 }
8774 }
8775 Expression::CreateIndex(ci)
8776 } else {
8777 expr
8778 }
8779 } else {
8780 expr
8781 };
8782
8783 transform_recursive(expr, &|e| {
8784 if matches!(source, DialectType::PostgreSQL | DialectType::Redshift)
8785 && matches!(target, DialectType::TSQL | DialectType::Fabric)
8786 {
8787 if let Expression::Round(mut f) = e {
8788 if f.decimals.is_none() {
8789 f.decimals = Some(Expression::number(0));
8790 }
8791 return Ok(Expression::Round(f));
8792 }
8793
8794 if let Expression::Function(f) = &e {
8795 if f.name.eq_ignore_ascii_case("ROUND") && f.args.len() == 1 {
8796 let mut f = f.clone();
8797 f.args.push(Expression::number(0));
8798 return Ok(Expression::Function(f));
8799 }
8800 }
8801
8802 if let Expression::Log(f) = e {
8803 if f.base.is_none() {
8804 return Ok(Expression::Function(Box::new(Function::new(
8805 "LOG10".to_string(),
8806 vec![f.this],
8807 ))));
8808 }
8809 return Ok(Expression::Log(f));
8810 }
8811 }
8812
8813 // BigQuery CAST(ARRAY[STRUCT(...)] AS STRUCT_TYPE[]) -> DuckDB: convert unnamed Structs to ROW()
8814 // This converts auto-named struct literals {'_0': x, '_1': y} inside typed arrays to ROW(x, y)
8815 if matches!(source, DialectType::BigQuery) && matches!(target, DialectType::DuckDB) {
8816 if let Expression::Cast(ref c) = e {
8817 // Check if this is a CAST of an array to a struct array type
8818 let is_struct_array_cast =
8819 matches!(&c.to, crate::expressions::DataType::Array { .. });
8820 if is_struct_array_cast {
8821 let has_auto_named_structs = match &c.this {
8822 Expression::Array(arr) => arr.expressions.iter().any(|elem| {
8823 if let Expression::Struct(s) = elem {
8824 s.fields.iter().all(|(name, _)| {
8825 name.as_ref().map_or(true, |n| {
8826 n.starts_with('_') && n[1..].parse::<usize>().is_ok()
8827 })
8828 })
8829 } else {
8830 false
8831 }
8832 }),
8833 Expression::ArrayFunc(arr) => arr.expressions.iter().any(|elem| {
8834 if let Expression::Struct(s) = elem {
8835 s.fields.iter().all(|(name, _)| {
8836 name.as_ref().map_or(true, |n| {
8837 n.starts_with('_') && n[1..].parse::<usize>().is_ok()
8838 })
8839 })
8840 } else {
8841 false
8842 }
8843 }),
8844 _ => false,
8845 };
8846 if has_auto_named_structs {
8847 let convert_struct_to_row = |elem: Expression| -> Expression {
8848 if let Expression::Struct(s) = elem {
8849 let row_args: Vec<Expression> =
8850 s.fields.into_iter().map(|(_, v)| v).collect();
8851 Expression::Function(Box::new(Function::new(
8852 "ROW".to_string(),
8853 row_args,
8854 )))
8855 } else {
8856 elem
8857 }
8858 };
8859 let mut c_clone = c.as_ref().clone();
8860 match &mut c_clone.this {
8861 Expression::Array(arr) => {
8862 arr.expressions = arr
8863 .expressions
8864 .drain(..)
8865 .map(convert_struct_to_row)
8866 .collect();
8867 }
8868 Expression::ArrayFunc(arr) => {
8869 arr.expressions = arr
8870 .expressions
8871 .drain(..)
8872 .map(convert_struct_to_row)
8873 .collect();
8874 }
8875 _ => {}
8876 }
8877 return Ok(Expression::Cast(Box::new(c_clone)));
8878 }
8879 }
8880 }
8881 }
8882
8883 // BigQuery SELECT AS STRUCT -> DuckDB struct literal {'key': value, ...}
8884 if matches!(source, DialectType::BigQuery) && matches!(target, DialectType::DuckDB) {
8885 if let Expression::Select(ref sel) = e {
8886 if sel.kind.as_deref() == Some("STRUCT") {
8887 let mut fields = Vec::new();
8888 for expr in &sel.expressions {
8889 match expr {
8890 Expression::Alias(a) => {
8891 fields.push((Some(a.alias.name.clone()), a.this.clone()));
8892 }
8893 Expression::Column(c) => {
8894 fields.push((Some(c.name.name.clone()), expr.clone()));
8895 }
8896 _ => {
8897 fields.push((None, expr.clone()));
8898 }
8899 }
8900 }
8901 let struct_lit =
8902 Expression::Struct(Box::new(crate::expressions::Struct { fields }));
8903 let mut new_select = sel.as_ref().clone();
8904 new_select.kind = None;
8905 new_select.expressions = vec![struct_lit];
8906 return Ok(Expression::Select(Box::new(new_select)));
8907 }
8908 }
8909 }
8910
8911 // Convert @variable -> ${variable} for Spark/Hive/Databricks
8912 if matches!(source, DialectType::TSQL | DialectType::Fabric)
8913 && matches!(
8914 target,
8915 DialectType::Spark | DialectType::Databricks | DialectType::Hive
8916 )
8917 {
8918 if let Expression::Parameter(ref p) = e {
8919 if p.style == crate::expressions::ParameterStyle::At {
8920 if let Some(ref name) = p.name {
8921 return Ok(Expression::Parameter(Box::new(
8922 crate::expressions::Parameter {
8923 name: Some(name.clone()),
8924 index: p.index,
8925 style: crate::expressions::ParameterStyle::DollarBrace,
8926 quoted: p.quoted,
8927 string_quoted: p.string_quoted,
8928 expression: None,
8929 },
8930 )));
8931 }
8932 }
8933 }
8934 // Also handle Column("@x") -> Parameter("x", DollarBrace) for TSQL vars
8935 if let Expression::Column(ref col) = e {
8936 if col.name.name.starts_with('@') && col.table.is_none() {
8937 let var_name = col.name.name.trim_start_matches('@').to_string();
8938 return Ok(Expression::Parameter(Box::new(
8939 crate::expressions::Parameter {
8940 name: Some(var_name),
8941 index: None,
8942 style: crate::expressions::ParameterStyle::DollarBrace,
8943 quoted: false,
8944 string_quoted: false,
8945 expression: None,
8946 },
8947 )));
8948 }
8949 }
8950 }
8951
8952 // Convert @variable -> variable in SET statements for Spark/Databricks
8953 if matches!(source, DialectType::TSQL | DialectType::Fabric)
8954 && matches!(target, DialectType::Spark | DialectType::Databricks)
8955 {
8956 if let Expression::SetStatement(ref s) = e {
8957 let mut new_items = s.items.clone();
8958 let mut changed = false;
8959 for item in &mut new_items {
8960 // Strip @ from the SET name (Parameter style)
8961 if let Expression::Parameter(ref p) = item.name {
8962 if p.style == crate::expressions::ParameterStyle::At {
8963 if let Some(ref name) = p.name {
8964 item.name = Expression::Identifier(Identifier::new(name));
8965 changed = true;
8966 }
8967 }
8968 }
8969 // Strip @ from the SET name (Identifier style - SET parser)
8970 if let Expression::Identifier(ref id) = item.name {
8971 if id.name.starts_with('@') {
8972 let var_name = id.name.trim_start_matches('@').to_string();
8973 item.name = Expression::Identifier(Identifier::new(&var_name));
8974 changed = true;
8975 }
8976 }
8977 // Strip @ from the SET name (Column style - alternative parsing)
8978 if let Expression::Column(ref col) = item.name {
8979 if col.name.name.starts_with('@') && col.table.is_none() {
8980 let var_name = col.name.name.trim_start_matches('@').to_string();
8981 item.name = Expression::Identifier(Identifier::new(&var_name));
8982 changed = true;
8983 }
8984 }
8985 }
8986 if changed {
8987 let mut new_set = (**s).clone();
8988 new_set.items = new_items;
8989 return Ok(Expression::SetStatement(Box::new(new_set)));
8990 }
8991 }
8992 }
8993
8994 // Strip NOLOCK hint for non-TSQL targets
8995 if matches!(source, DialectType::TSQL | DialectType::Fabric)
8996 && !matches!(target, DialectType::TSQL | DialectType::Fabric)
8997 {
8998 if let Expression::Table(ref tr) = e {
8999 if !tr.hints.is_empty() {
9000 let mut new_tr = tr.clone();
9001 new_tr.hints.clear();
9002 return Ok(Expression::Table(new_tr));
9003 }
9004 }
9005 }
9006
9007 // Snowflake: TRUE IS TRUE -> TRUE, FALSE IS FALSE -> FALSE
9008 // Snowflake simplifies IS TRUE/IS FALSE on boolean literals
9009 if matches!(target, DialectType::Snowflake) {
9010 if let Expression::IsTrue(ref itf) = e {
9011 if let Expression::Boolean(ref b) = itf.this {
9012 if !itf.not {
9013 return Ok(Expression::Boolean(crate::expressions::BooleanLiteral {
9014 value: b.value,
9015 }));
9016 } else {
9017 return Ok(Expression::Boolean(crate::expressions::BooleanLiteral {
9018 value: !b.value,
9019 }));
9020 }
9021 }
9022 }
9023 if let Expression::IsFalse(ref itf) = e {
9024 if let Expression::Boolean(ref b) = itf.this {
9025 if !itf.not {
9026 return Ok(Expression::Boolean(crate::expressions::BooleanLiteral {
9027 value: !b.value,
9028 }));
9029 } else {
9030 return Ok(Expression::Boolean(crate::expressions::BooleanLiteral {
9031 value: b.value,
9032 }));
9033 }
9034 }
9035 }
9036 }
9037
9038 // BigQuery: split dotted backtick identifiers in table names
9039 // e.g., `a.b.c` -> "a"."b"."c" when source is BigQuery and target is not BigQuery
9040 if matches!(source, DialectType::BigQuery) && !matches!(target, DialectType::BigQuery) {
9041 if let Expression::CreateTable(ref ct) = e {
9042 let mut changed = false;
9043 let mut new_ct = ct.clone();
9044 // Split the table name
9045 if ct.name.schema.is_none() && ct.name.name.name.contains('.') {
9046 let parts: Vec<&str> = ct.name.name.name.split('.').collect();
9047 // Use quoted identifiers when the original was quoted (backtick in BigQuery)
9048 let was_quoted = ct.name.name.quoted;
9049 let mk_id = |s: &str| {
9050 if was_quoted {
9051 Identifier::quoted(s)
9052 } else {
9053 Identifier::new(s)
9054 }
9055 };
9056 if parts.len() == 3 {
9057 new_ct.name.catalog = Some(mk_id(parts[0]));
9058 new_ct.name.schema = Some(mk_id(parts[1]));
9059 new_ct.name.name = mk_id(parts[2]);
9060 changed = true;
9061 } else if parts.len() == 2 {
9062 new_ct.name.schema = Some(mk_id(parts[0]));
9063 new_ct.name.name = mk_id(parts[1]);
9064 changed = true;
9065 }
9066 }
9067 // Split the clone source name
9068 if let Some(ref clone_src) = ct.clone_source {
9069 if clone_src.schema.is_none() && clone_src.name.name.contains('.') {
9070 let parts: Vec<&str> = clone_src.name.name.split('.').collect();
9071 let was_quoted = clone_src.name.quoted;
9072 let mk_id = |s: &str| {
9073 if was_quoted {
9074 Identifier::quoted(s)
9075 } else {
9076 Identifier::new(s)
9077 }
9078 };
9079 let mut new_src = clone_src.clone();
9080 if parts.len() == 3 {
9081 new_src.catalog = Some(mk_id(parts[0]));
9082 new_src.schema = Some(mk_id(parts[1]));
9083 new_src.name = mk_id(parts[2]);
9084 new_ct.clone_source = Some(new_src);
9085 changed = true;
9086 } else if parts.len() == 2 {
9087 new_src.schema = Some(mk_id(parts[0]));
9088 new_src.name = mk_id(parts[1]);
9089 new_ct.clone_source = Some(new_src);
9090 changed = true;
9091 }
9092 }
9093 }
9094 if changed {
9095 return Ok(Expression::CreateTable(new_ct));
9096 }
9097 }
9098 }
9099
9100 // BigQuery array subscript: a[1], b[OFFSET(1)], c[ORDINAL(1)], d[SAFE_OFFSET(1)], e[SAFE_ORDINAL(1)]
9101 // -> DuckDB/Presto: convert 0-based to 1-based, handle SAFE_* -> ELEMENT_AT for Presto
9102 if matches!(source, DialectType::BigQuery)
9103 && matches!(
9104 target,
9105 DialectType::DuckDB
9106 | DialectType::Presto
9107 | DialectType::Trino
9108 | DialectType::Athena
9109 )
9110 {
9111 if let Expression::Subscript(ref sub) = e {
9112 let (new_index, is_safe) = match &sub.index {
9113 // a[1] -> a[1+1] = a[2] (plain index is 0-based in BQ)
9114 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
9115 let Literal::Number(n) = lit.as_ref() else {
9116 unreachable!()
9117 };
9118 if let Ok(val) = n.parse::<i64>() {
9119 (
9120 Some(Expression::Literal(Box::new(Literal::Number(
9121 (val + 1).to_string(),
9122 )))),
9123 false,
9124 )
9125 } else {
9126 (None, false)
9127 }
9128 }
9129 // OFFSET(n) -> n+1 (0-based)
9130 Expression::Function(ref f)
9131 if f.name.eq_ignore_ascii_case("OFFSET") && f.args.len() == 1 =>
9132 {
9133 if let Expression::Literal(lit) = &f.args[0] {
9134 if let Literal::Number(n) = lit.as_ref() {
9135 if let Ok(val) = n.parse::<i64>() {
9136 (
9137 Some(Expression::Literal(Box::new(Literal::Number(
9138 (val + 1).to_string(),
9139 )))),
9140 false,
9141 )
9142 } else {
9143 (
9144 Some(Expression::Add(Box::new(
9145 crate::expressions::BinaryOp::new(
9146 f.args[0].clone(),
9147 Expression::number(1),
9148 ),
9149 ))),
9150 false,
9151 )
9152 }
9153 } else {
9154 (None, false)
9155 }
9156 } else {
9157 (
9158 Some(Expression::Add(Box::new(
9159 crate::expressions::BinaryOp::new(
9160 f.args[0].clone(),
9161 Expression::number(1),
9162 ),
9163 ))),
9164 false,
9165 )
9166 }
9167 }
9168 // ORDINAL(n) -> n (already 1-based)
9169 Expression::Function(ref f)
9170 if f.name.eq_ignore_ascii_case("ORDINAL") && f.args.len() == 1 =>
9171 {
9172 (Some(f.args[0].clone()), false)
9173 }
9174 // SAFE_OFFSET(n) -> n+1 (0-based, safe)
9175 Expression::Function(ref f)
9176 if f.name.eq_ignore_ascii_case("SAFE_OFFSET") && f.args.len() == 1 =>
9177 {
9178 if let Expression::Literal(lit) = &f.args[0] {
9179 if let Literal::Number(n) = lit.as_ref() {
9180 if let Ok(val) = n.parse::<i64>() {
9181 (
9182 Some(Expression::Literal(Box::new(Literal::Number(
9183 (val + 1).to_string(),
9184 )))),
9185 true,
9186 )
9187 } else {
9188 (
9189 Some(Expression::Add(Box::new(
9190 crate::expressions::BinaryOp::new(
9191 f.args[0].clone(),
9192 Expression::number(1),
9193 ),
9194 ))),
9195 true,
9196 )
9197 }
9198 } else {
9199 (None, false)
9200 }
9201 } else {
9202 (
9203 Some(Expression::Add(Box::new(
9204 crate::expressions::BinaryOp::new(
9205 f.args[0].clone(),
9206 Expression::number(1),
9207 ),
9208 ))),
9209 true,
9210 )
9211 }
9212 }
9213 // SAFE_ORDINAL(n) -> n (already 1-based, safe)
9214 Expression::Function(ref f)
9215 if f.name.eq_ignore_ascii_case("SAFE_ORDINAL") && f.args.len() == 1 =>
9216 {
9217 (Some(f.args[0].clone()), true)
9218 }
9219 _ => (None, false),
9220 };
9221 if let Some(idx) = new_index {
9222 if is_safe
9223 && matches!(
9224 target,
9225 DialectType::Presto | DialectType::Trino | DialectType::Athena
9226 )
9227 {
9228 // Presto: SAFE_OFFSET/SAFE_ORDINAL -> ELEMENT_AT(arr, idx)
9229 return Ok(Expression::Function(Box::new(Function::new(
9230 "ELEMENT_AT".to_string(),
9231 vec![sub.this.clone(), idx],
9232 ))));
9233 } else {
9234 // DuckDB or non-safe: just use subscript with converted index
9235 return Ok(Expression::Subscript(Box::new(
9236 crate::expressions::Subscript {
9237 this: sub.this.clone(),
9238 index: idx,
9239 },
9240 )));
9241 }
9242 }
9243 }
9244 }
9245
9246 // BigQuery LENGTH(x) -> DuckDB CASE TYPEOF(x) WHEN 'BLOB' THEN OCTET_LENGTH(...) ELSE LENGTH(...) END
9247 if matches!(source, DialectType::BigQuery) && matches!(target, DialectType::DuckDB) {
9248 if let Expression::Length(ref uf) = e {
9249 let arg = uf.this.clone();
9250 let typeof_func = Expression::Function(Box::new(Function::new(
9251 "TYPEOF".to_string(),
9252 vec![arg.clone()],
9253 )));
9254 let blob_cast = Expression::Cast(Box::new(Cast {
9255 this: arg.clone(),
9256 to: DataType::VarBinary { length: None },
9257 trailing_comments: vec![],
9258 double_colon_syntax: false,
9259 format: None,
9260 default: None,
9261 inferred_type: None,
9262 }));
9263 let octet_length = Expression::Function(Box::new(Function::new(
9264 "OCTET_LENGTH".to_string(),
9265 vec![blob_cast],
9266 )));
9267 let text_cast = Expression::Cast(Box::new(Cast {
9268 this: arg,
9269 to: DataType::Text,
9270 trailing_comments: vec![],
9271 double_colon_syntax: false,
9272 format: None,
9273 default: None,
9274 inferred_type: None,
9275 }));
9276 let length_text = Expression::Length(Box::new(crate::expressions::UnaryFunc {
9277 this: text_cast,
9278 original_name: None,
9279 inferred_type: None,
9280 }));
9281 return Ok(Expression::Case(Box::new(Case {
9282 operand: Some(typeof_func),
9283 whens: vec![(
9284 Expression::Literal(Box::new(Literal::String("BLOB".to_string()))),
9285 octet_length,
9286 )],
9287 else_: Some(length_text),
9288 comments: Vec::new(),
9289 inferred_type: None,
9290 })));
9291 }
9292 }
9293
9294 // BigQuery UNNEST alias handling (only for non-BigQuery sources):
9295 // UNNEST(...) AS x -> UNNEST(...) (drop unused table alias)
9296 // UNNEST(...) AS x(y) -> UNNEST(...) AS y (use column alias as main alias)
9297 if matches!(target, DialectType::BigQuery) && !matches!(source, DialectType::BigQuery) {
9298 if let Expression::Alias(ref a) = e {
9299 if matches!(&a.this, Expression::Unnest(_)) {
9300 if a.column_aliases.is_empty() {
9301 // Drop the entire alias, return just the UNNEST expression
9302 return Ok(a.this.clone());
9303 } else {
9304 // Use first column alias as the main alias
9305 let mut new_alias = a.as_ref().clone();
9306 new_alias.alias = a.column_aliases[0].clone();
9307 new_alias.column_aliases.clear();
9308 return Ok(Expression::Alias(Box::new(new_alias)));
9309 }
9310 }
9311 }
9312 }
9313
9314 // BigQuery IN UNNEST(expr) -> IN (SELECT UNNEST/EXPLODE(expr)) for non-BigQuery targets
9315 if matches!(source, DialectType::BigQuery) && !matches!(target, DialectType::BigQuery) {
9316 if let Expression::In(ref in_expr) = e {
9317 if let Some(ref unnest_inner) = in_expr.unnest {
9318 // Build the function call for the target dialect
9319 let func_expr = if matches!(
9320 target,
9321 DialectType::Hive | DialectType::Spark | DialectType::Databricks
9322 ) {
9323 // Use EXPLODE for Hive/Spark
9324 Expression::Function(Box::new(Function::new(
9325 "EXPLODE".to_string(),
9326 vec![*unnest_inner.clone()],
9327 )))
9328 } else {
9329 // Use UNNEST for Presto/Trino/DuckDB/etc.
9330 Expression::Unnest(Box::new(crate::expressions::UnnestFunc {
9331 this: *unnest_inner.clone(),
9332 expressions: Vec::new(),
9333 with_ordinality: false,
9334 alias: None,
9335 offset_alias: None,
9336 }))
9337 };
9338
9339 // Wrap in SELECT
9340 let mut inner_select = crate::expressions::Select::new();
9341 inner_select.expressions = vec![func_expr];
9342
9343 let subquery_expr = Expression::Select(Box::new(inner_select));
9344
9345 return Ok(Expression::In(Box::new(crate::expressions::In {
9346 this: in_expr.this.clone(),
9347 expressions: Vec::new(),
9348 query: Some(subquery_expr),
9349 not: in_expr.not,
9350 global: in_expr.global,
9351 unnest: None,
9352 is_field: false,
9353 })));
9354 }
9355 }
9356 }
9357
9358 // SQLite: GENERATE_SERIES AS t(i) -> (SELECT value AS i FROM GENERATE_SERIES(...)) AS t
9359 // This handles the subquery wrapping for RANGE -> GENERATE_SERIES in FROM context
9360 if matches!(target, DialectType::SQLite) && matches!(source, DialectType::DuckDB) {
9361 if let Expression::Alias(ref a) = e {
9362 if let Expression::Function(ref f) = a.this {
9363 if f.name.eq_ignore_ascii_case("GENERATE_SERIES")
9364 && !a.column_aliases.is_empty()
9365 {
9366 // Build: (SELECT value AS col_alias FROM GENERATE_SERIES(start, end)) AS table_alias
9367 let col_alias = a.column_aliases[0].clone();
9368 let mut inner_select = crate::expressions::Select::new();
9369 inner_select.expressions =
9370 vec![Expression::Alias(Box::new(crate::expressions::Alias::new(
9371 Expression::Identifier(Identifier::new("value".to_string())),
9372 col_alias,
9373 )))];
9374 inner_select.from = Some(crate::expressions::From {
9375 expressions: vec![a.this.clone()],
9376 });
9377 let subquery =
9378 Expression::Subquery(Box::new(crate::expressions::Subquery {
9379 this: Expression::Select(Box::new(inner_select)),
9380 alias: Some(a.alias.clone()),
9381 column_aliases: Vec::new(),
9382 alias_explicit_as: false,
9383 alias_keyword: None,
9384 order_by: None,
9385 limit: None,
9386 offset: None,
9387 lateral: false,
9388 modifiers_inside: false,
9389 trailing_comments: Vec::new(),
9390 distribute_by: None,
9391 sort_by: None,
9392 cluster_by: None,
9393 inferred_type: None,
9394 }));
9395 return Ok(subquery);
9396 }
9397 }
9398 }
9399 }
9400
9401 // BigQuery implicit UNNEST: comma-join on array path -> CROSS JOIN UNNEST
9402 // e.g., SELECT results FROM Coordinates, Coordinates.position AS results
9403 // -> SELECT results FROM Coordinates CROSS JOIN UNNEST(Coordinates.position) AS results
9404 if matches!(source, DialectType::BigQuery) {
9405 if let Expression::Select(ref s) = e {
9406 if let Some(ref from) = s.from {
9407 if from.expressions.len() >= 2 {
9408 // Collect table names from first expression
9409 let first_tables: Vec<String> = from
9410 .expressions
9411 .iter()
9412 .take(1)
9413 .filter_map(|expr| {
9414 if let Expression::Table(t) = expr {
9415 Some(t.name.name.to_ascii_lowercase())
9416 } else {
9417 None
9418 }
9419 })
9420 .collect();
9421
9422 // Check if any subsequent FROM expressions are schema-qualified with a matching table name
9423 // or have a dotted name matching a table
9424 let mut needs_rewrite = false;
9425 for expr in from.expressions.iter().skip(1) {
9426 if let Expression::Table(t) = expr {
9427 if let Some(ref schema) = t.schema {
9428 if first_tables.contains(&schema.name.to_ascii_lowercase())
9429 {
9430 needs_rewrite = true;
9431 break;
9432 }
9433 }
9434 // Also check dotted names in quoted identifiers (e.g., `Coordinates.position`)
9435 if t.schema.is_none() && t.name.name.contains('.') {
9436 let parts: Vec<&str> = t.name.name.split('.').collect();
9437 if parts.len() >= 2
9438 && first_tables.contains(&parts[0].to_ascii_lowercase())
9439 {
9440 needs_rewrite = true;
9441 break;
9442 }
9443 }
9444 }
9445 }
9446
9447 if needs_rewrite {
9448 let mut new_select = s.clone();
9449 let mut new_from_exprs = vec![from.expressions[0].clone()];
9450 let mut new_joins = s.joins.clone();
9451
9452 for expr in from.expressions.iter().skip(1) {
9453 if let Expression::Table(ref t) = expr {
9454 if let Some(ref schema) = t.schema {
9455 if first_tables
9456 .contains(&schema.name.to_ascii_lowercase())
9457 {
9458 // This is an array path reference, convert to CROSS JOIN UNNEST
9459 let col_expr = Expression::Column(Box::new(
9460 crate::expressions::Column {
9461 name: t.name.clone(),
9462 table: Some(schema.clone()),
9463 join_mark: false,
9464 trailing_comments: vec![],
9465 span: None,
9466 inferred_type: None,
9467 },
9468 ));
9469 let unnest_expr = Expression::Unnest(Box::new(
9470 crate::expressions::UnnestFunc {
9471 this: col_expr,
9472 expressions: Vec::new(),
9473 with_ordinality: false,
9474 alias: None,
9475 offset_alias: None,
9476 },
9477 ));
9478 let join_this = if let Some(ref alias) = t.alias {
9479 if matches!(
9480 target,
9481 DialectType::Presto
9482 | DialectType::Trino
9483 | DialectType::Athena
9484 ) {
9485 // Presto: UNNEST(x) AS _t0(results)
9486 Expression::Alias(Box::new(
9487 crate::expressions::Alias {
9488 this: unnest_expr,
9489 alias: Identifier::new("_t0"),
9490 column_aliases: vec![alias.clone()],
9491 alias_explicit_as: false,
9492 alias_keyword: None,
9493 pre_alias_comments: vec![],
9494 trailing_comments: vec![],
9495 inferred_type: None,
9496 },
9497 ))
9498 } else {
9499 // BigQuery: UNNEST(x) AS results
9500 Expression::Alias(Box::new(
9501 crate::expressions::Alias {
9502 this: unnest_expr,
9503 alias: alias.clone(),
9504 column_aliases: vec![],
9505 alias_explicit_as: false,
9506 alias_keyword: None,
9507 pre_alias_comments: vec![],
9508 trailing_comments: vec![],
9509 inferred_type: None,
9510 },
9511 ))
9512 }
9513 } else {
9514 unnest_expr
9515 };
9516 new_joins.push(crate::expressions::Join {
9517 kind: crate::expressions::JoinKind::Cross,
9518 this: join_this,
9519 on: None,
9520 using: Vec::new(),
9521 use_inner_keyword: false,
9522 use_outer_keyword: false,
9523 deferred_condition: false,
9524 join_hint: None,
9525 match_condition: None,
9526 pivots: Vec::new(),
9527 comments: Vec::new(),
9528 nesting_group: 0,
9529 directed: false,
9530 });
9531 } else {
9532 new_from_exprs.push(expr.clone());
9533 }
9534 } else if t.schema.is_none() && t.name.name.contains('.') {
9535 // Dotted name in quoted identifier: `Coordinates.position`
9536 let parts: Vec<&str> = t.name.name.split('.').collect();
9537 if parts.len() >= 2
9538 && first_tables
9539 .contains(&parts[0].to_ascii_lowercase())
9540 {
9541 let join_this =
9542 if matches!(target, DialectType::BigQuery) {
9543 // BigQuery: keep as single quoted identifier, just convert comma -> CROSS JOIN
9544 Expression::Table(t.clone())
9545 } else {
9546 // Other targets: split into "schema"."name"
9547 let mut new_t = t.clone();
9548 new_t.schema =
9549 Some(Identifier::quoted(parts[0]));
9550 new_t.name = Identifier::quoted(parts[1]);
9551 Expression::Table(new_t)
9552 };
9553 new_joins.push(crate::expressions::Join {
9554 kind: crate::expressions::JoinKind::Cross,
9555 this: join_this,
9556 on: None,
9557 using: Vec::new(),
9558 use_inner_keyword: false,
9559 use_outer_keyword: false,
9560 deferred_condition: false,
9561 join_hint: None,
9562 match_condition: None,
9563 pivots: Vec::new(),
9564 comments: Vec::new(),
9565 nesting_group: 0,
9566 directed: false,
9567 });
9568 } else {
9569 new_from_exprs.push(expr.clone());
9570 }
9571 } else {
9572 new_from_exprs.push(expr.clone());
9573 }
9574 } else {
9575 new_from_exprs.push(expr.clone());
9576 }
9577 }
9578
9579 new_select.from = Some(crate::expressions::From {
9580 expressions: new_from_exprs,
9581 ..from.clone()
9582 });
9583 new_select.joins = new_joins;
9584 return Ok(Expression::Select(new_select));
9585 }
9586 }
9587 }
9588 }
9589 }
9590
9591 // CROSS JOIN UNNEST -> LATERAL VIEW EXPLODE for Hive/Spark
9592 if matches!(
9593 target,
9594 DialectType::Hive | DialectType::Spark | DialectType::Databricks
9595 ) {
9596 if let Expression::Select(ref s) = e {
9597 // Check if any joins are CROSS JOIN with UNNEST/EXPLODE
9598 let is_unnest_or_explode_expr = |expr: &Expression| -> bool {
9599 matches!(expr, Expression::Unnest(_))
9600 || matches!(expr, Expression::Function(f) if f.name.eq_ignore_ascii_case("EXPLODE"))
9601 };
9602 let has_unnest_join = s.joins.iter().any(|j| {
9603 j.kind == crate::expressions::JoinKind::Cross && (
9604 matches!(&j.this, Expression::Alias(a) if is_unnest_or_explode_expr(&a.this))
9605 || is_unnest_or_explode_expr(&j.this)
9606 )
9607 });
9608 if has_unnest_join {
9609 let mut select = s.clone();
9610 let mut new_joins = Vec::new();
9611 for join in select.joins.drain(..) {
9612 if join.kind == crate::expressions::JoinKind::Cross {
9613 // Extract the UNNEST/EXPLODE from the join
9614 let (func_expr, table_alias, col_aliases) = match &join.this {
9615 Expression::Alias(a) => {
9616 let ta = if a.alias.is_empty() {
9617 None
9618 } else {
9619 Some(a.alias.clone())
9620 };
9621 let cas = a.column_aliases.clone();
9622 match &a.this {
9623 Expression::Unnest(u) => {
9624 // Multi-arg UNNEST(y, z) -> INLINE(ARRAYS_ZIP(y, z))
9625 if !u.expressions.is_empty() {
9626 let mut all_args = vec![u.this.clone()];
9627 all_args.extend(u.expressions.clone());
9628 let arrays_zip =
9629 Expression::Function(Box::new(
9630 crate::expressions::Function::new(
9631 "ARRAYS_ZIP".to_string(),
9632 all_args,
9633 ),
9634 ));
9635 let inline = Expression::Function(Box::new(
9636 crate::expressions::Function::new(
9637 "INLINE".to_string(),
9638 vec![arrays_zip],
9639 ),
9640 ));
9641 (Some(inline), ta, a.column_aliases.clone())
9642 } else {
9643 // Convert UNNEST(x) to EXPLODE(x) or POSEXPLODE(x)
9644 let func_name = if u.with_ordinality {
9645 "POSEXPLODE"
9646 } else {
9647 "EXPLODE"
9648 };
9649 let explode = Expression::Function(Box::new(
9650 crate::expressions::Function::new(
9651 func_name.to_string(),
9652 vec![u.this.clone()],
9653 ),
9654 ));
9655 // For POSEXPLODE, add 'pos' to column aliases
9656 let cas = if u.with_ordinality {
9657 let mut pos_aliases =
9658 vec![Identifier::new(
9659 "pos".to_string(),
9660 )];
9661 pos_aliases
9662 .extend(a.column_aliases.clone());
9663 pos_aliases
9664 } else {
9665 a.column_aliases.clone()
9666 };
9667 (Some(explode), ta, cas)
9668 }
9669 }
9670 Expression::Function(f)
9671 if f.name.eq_ignore_ascii_case("EXPLODE") =>
9672 {
9673 (Some(Expression::Function(f.clone())), ta, cas)
9674 }
9675 _ => (None, None, Vec::new()),
9676 }
9677 }
9678 Expression::Unnest(u) => {
9679 let func_name = if u.with_ordinality {
9680 "POSEXPLODE"
9681 } else {
9682 "EXPLODE"
9683 };
9684 let explode = Expression::Function(Box::new(
9685 crate::expressions::Function::new(
9686 func_name.to_string(),
9687 vec![u.this.clone()],
9688 ),
9689 ));
9690 let ta = u.alias.clone();
9691 let col_aliases = if u.with_ordinality {
9692 vec![Identifier::new("pos".to_string())]
9693 } else {
9694 Vec::new()
9695 };
9696 (Some(explode), ta, col_aliases)
9697 }
9698 _ => (None, None, Vec::new()),
9699 };
9700 if let Some(func) = func_expr {
9701 select.lateral_views.push(crate::expressions::LateralView {
9702 this: func,
9703 table_alias,
9704 column_aliases: col_aliases,
9705 outer: false,
9706 });
9707 } else {
9708 new_joins.push(join);
9709 }
9710 } else {
9711 new_joins.push(join);
9712 }
9713 }
9714 select.joins = new_joins;
9715 return Ok(Expression::Select(select));
9716 }
9717 }
9718 }
9719
9720 // UNNEST expansion: DuckDB SELECT UNNEST(arr) in SELECT list -> expanded query
9721 // for BigQuery, Presto/Trino, Snowflake
9722 if matches!(source, DialectType::DuckDB | DialectType::PostgreSQL)
9723 && matches!(
9724 target,
9725 DialectType::BigQuery
9726 | DialectType::Presto
9727 | DialectType::Trino
9728 | DialectType::Snowflake
9729 )
9730 {
9731 if let Expression::Select(ref s) = e {
9732 // Check if any SELECT expressions contain UNNEST
9733 // Note: UNNEST can appear as Expression::Unnest OR Expression::Function("UNNEST")
9734 let has_unnest_in_select = s.expressions.iter().any(|expr| {
9735 fn contains_unnest(e: &Expression) -> bool {
9736 match e {
9737 Expression::Unnest(_) => true,
9738 Expression::Function(f)
9739 if f.name.eq_ignore_ascii_case("UNNEST") =>
9740 {
9741 true
9742 }
9743 Expression::Alias(a) => contains_unnest(&a.this),
9744 Expression::Add(op)
9745 | Expression::Sub(op)
9746 | Expression::Mul(op)
9747 | Expression::Div(op) => {
9748 contains_unnest(&op.left) || contains_unnest(&op.right)
9749 }
9750 _ => false,
9751 }
9752 }
9753 contains_unnest(expr)
9754 });
9755
9756 if has_unnest_in_select {
9757 let rewritten = Self::rewrite_unnest_expansion(s, target);
9758 if let Some(new_select) = rewritten {
9759 return Ok(Expression::Select(Box::new(new_select)));
9760 }
9761 }
9762 }
9763 }
9764
9765 // BigQuery -> PostgreSQL: convert escape sequences in string literals to actual characters
9766 // BigQuery '\n' -> PostgreSQL literal newline in string
9767 if matches!(source, DialectType::BigQuery) && matches!(target, DialectType::PostgreSQL)
9768 {
9769 if let Expression::Literal(ref lit) = e {
9770 if let Literal::String(ref s) = lit.as_ref() {
9771 if s.contains("\\n")
9772 || s.contains("\\t")
9773 || s.contains("\\r")
9774 || s.contains("\\\\")
9775 {
9776 let converted = s
9777 .replace("\\n", "\n")
9778 .replace("\\t", "\t")
9779 .replace("\\r", "\r")
9780 .replace("\\\\", "\\");
9781 return Ok(Expression::Literal(Box::new(Literal::String(converted))));
9782 }
9783 }
9784 }
9785 }
9786
9787 // Cross-dialect: convert Literal::Timestamp to target-specific CAST form
9788 // when source != target (identity tests keep the Literal::Timestamp for native handling)
9789 if source != target {
9790 if let Expression::Literal(ref lit) = e {
9791 if let Literal::Timestamp(ref s) = lit.as_ref() {
9792 let s = s.clone();
9793 // MySQL: TIMESTAMP handling depends on source dialect
9794 // BigQuery TIMESTAMP is timezone-aware -> TIMESTAMP() function in MySQL
9795 // Other sources' TIMESTAMP is non-timezone -> CAST('x' AS DATETIME) in MySQL
9796 if matches!(target, DialectType::MySQL) {
9797 if matches!(source, DialectType::BigQuery) {
9798 // BigQuery TIMESTAMP is timezone-aware -> MySQL TIMESTAMP() function
9799 return Ok(Expression::Function(Box::new(Function::new(
9800 "TIMESTAMP".to_string(),
9801 vec![Expression::Literal(Box::new(Literal::String(s)))],
9802 ))));
9803 } else {
9804 // Non-timezone TIMESTAMP -> CAST('x' AS DATETIME) in MySQL
9805 return Ok(Expression::Cast(Box::new(Cast {
9806 this: Expression::Literal(Box::new(Literal::String(s))),
9807 to: DataType::Custom {
9808 name: "DATETIME".to_string(),
9809 },
9810 trailing_comments: Vec::new(),
9811 double_colon_syntax: false,
9812 format: None,
9813 default: None,
9814 inferred_type: None,
9815 })));
9816 }
9817 }
9818 let dt = match target {
9819 DialectType::BigQuery | DialectType::StarRocks => DataType::Custom {
9820 name: "DATETIME".to_string(),
9821 },
9822 DialectType::Snowflake => {
9823 // BigQuery TIMESTAMP is timezone-aware -> use TIMESTAMPTZ for Snowflake
9824 if matches!(source, DialectType::BigQuery) {
9825 DataType::Custom {
9826 name: "TIMESTAMPTZ".to_string(),
9827 }
9828 } else if matches!(
9829 source,
9830 DialectType::PostgreSQL
9831 | DialectType::Redshift
9832 | DialectType::Snowflake
9833 ) {
9834 DataType::Timestamp {
9835 precision: None,
9836 timezone: false,
9837 }
9838 } else {
9839 DataType::Custom {
9840 name: "TIMESTAMPNTZ".to_string(),
9841 }
9842 }
9843 }
9844 DialectType::Spark | DialectType::Databricks => {
9845 // BigQuery TIMESTAMP is timezone-aware -> use plain TIMESTAMP for Spark/Databricks
9846 if matches!(source, DialectType::BigQuery) {
9847 DataType::Timestamp {
9848 precision: None,
9849 timezone: false,
9850 }
9851 } else {
9852 DataType::Custom {
9853 name: "TIMESTAMP_NTZ".to_string(),
9854 }
9855 }
9856 }
9857 DialectType::ClickHouse => DataType::Nullable {
9858 inner: Box::new(DataType::Custom {
9859 name: "DateTime".to_string(),
9860 }),
9861 },
9862 DialectType::TSQL | DialectType::Fabric => DataType::Custom {
9863 name: "DATETIME2".to_string(),
9864 },
9865 DialectType::DuckDB => {
9866 // DuckDB: use TIMESTAMPTZ when source is BigQuery (BQ TIMESTAMP is always UTC/tz-aware)
9867 // or when the timestamp string explicitly has timezone info
9868 if matches!(source, DialectType::BigQuery)
9869 || Self::timestamp_string_has_timezone(&s)
9870 {
9871 DataType::Custom {
9872 name: "TIMESTAMPTZ".to_string(),
9873 }
9874 } else {
9875 DataType::Timestamp {
9876 precision: None,
9877 timezone: false,
9878 }
9879 }
9880 }
9881 _ => DataType::Timestamp {
9882 precision: None,
9883 timezone: false,
9884 },
9885 };
9886 return Ok(Expression::Cast(Box::new(Cast {
9887 this: Expression::Literal(Box::new(Literal::String(s))),
9888 to: dt,
9889 trailing_comments: vec![],
9890 double_colon_syntax: false,
9891 format: None,
9892 default: None,
9893 inferred_type: None,
9894 })));
9895 }
9896 }
9897 }
9898
9899 // PostgreSQL DELETE requires explicit AS for table aliases
9900 if matches!(target, DialectType::PostgreSQL | DialectType::Redshift) {
9901 if let Expression::Delete(ref del) = e {
9902 if del.alias.is_some() && !del.alias_explicit_as {
9903 let mut new_del = del.clone();
9904 new_del.alias_explicit_as = true;
9905 return Ok(Expression::Delete(new_del));
9906 }
9907 }
9908 }
9909
9910 // UNION/INTERSECT/EXCEPT DISTINCT handling:
9911 // Some dialects require explicit DISTINCT (BigQuery, ClickHouse),
9912 // while others don't support it (Presto, Spark, DuckDB, etc.)
9913 {
9914 let needs_distinct =
9915 matches!(target, DialectType::BigQuery | DialectType::ClickHouse);
9916 let drop_distinct = matches!(
9917 target,
9918 DialectType::Presto
9919 | DialectType::Trino
9920 | DialectType::Athena
9921 | DialectType::Spark
9922 | DialectType::Databricks
9923 | DialectType::DuckDB
9924 | DialectType::Hive
9925 | DialectType::MySQL
9926 | DialectType::PostgreSQL
9927 | DialectType::SQLite
9928 | DialectType::TSQL
9929 | DialectType::Redshift
9930 | DialectType::Snowflake
9931 | DialectType::Oracle
9932 | DialectType::Teradata
9933 | DialectType::Drill
9934 | DialectType::Doris
9935 | DialectType::StarRocks
9936 );
9937 match &e {
9938 Expression::Union(u) if !u.all && needs_distinct && !u.distinct => {
9939 let mut new_u = (**u).clone();
9940 new_u.distinct = true;
9941 return Ok(Expression::Union(Box::new(new_u)));
9942 }
9943 Expression::Intersect(i) if !i.all && needs_distinct && !i.distinct => {
9944 let mut new_i = (**i).clone();
9945 new_i.distinct = true;
9946 return Ok(Expression::Intersect(Box::new(new_i)));
9947 }
9948 Expression::Except(ex) if !ex.all && needs_distinct && !ex.distinct => {
9949 let mut new_ex = (**ex).clone();
9950 new_ex.distinct = true;
9951 return Ok(Expression::Except(Box::new(new_ex)));
9952 }
9953 Expression::Union(u) if u.distinct && drop_distinct => {
9954 let mut new_u = (**u).clone();
9955 new_u.distinct = false;
9956 return Ok(Expression::Union(Box::new(new_u)));
9957 }
9958 Expression::Intersect(i) if i.distinct && drop_distinct => {
9959 let mut new_i = (**i).clone();
9960 new_i.distinct = false;
9961 return Ok(Expression::Intersect(Box::new(new_i)));
9962 }
9963 Expression::Except(ex) if ex.distinct && drop_distinct => {
9964 let mut new_ex = (**ex).clone();
9965 new_ex.distinct = false;
9966 return Ok(Expression::Except(Box::new(new_ex)));
9967 }
9968 _ => {}
9969 }
9970 }
9971
9972 // ClickHouse: MAP('a', '1') -> map('a', '1') (lowercase function name)
9973 if matches!(target, DialectType::ClickHouse) {
9974 if let Expression::Function(ref f) = e {
9975 if f.name.eq_ignore_ascii_case("MAP") && !f.args.is_empty() {
9976 let mut new_f = f.as_ref().clone();
9977 new_f.name = "map".to_string();
9978 return Ok(Expression::Function(Box::new(new_f)));
9979 }
9980 }
9981 }
9982
9983 // ClickHouse: INTERSECT ALL -> INTERSECT (ClickHouse doesn't support ALL on INTERSECT)
9984 if matches!(target, DialectType::ClickHouse) {
9985 if let Expression::Intersect(ref i) = e {
9986 if i.all {
9987 let mut new_i = (**i).clone();
9988 new_i.all = false;
9989 return Ok(Expression::Intersect(Box::new(new_i)));
9990 }
9991 }
9992 }
9993
9994 // Integer division: a / b -> CAST(a AS DOUBLE) / b for dialects that need it
9995 // Only from Generic source, to prevent double-wrapping
9996 if matches!(source, DialectType::Generic) {
9997 if let Expression::Div(ref op) = e {
9998 let cast_type = match target {
9999 DialectType::TSQL | DialectType::Fabric => Some(DataType::Float {
10000 precision: None,
10001 scale: None,
10002 real_spelling: false,
10003 }),
10004 DialectType::Drill
10005 | DialectType::Trino
10006 | DialectType::Athena
10007 | DialectType::Presto => Some(DataType::Double {
10008 precision: None,
10009 scale: None,
10010 }),
10011 DialectType::PostgreSQL
10012 | DialectType::Redshift
10013 | DialectType::Materialize
10014 | DialectType::Teradata
10015 | DialectType::RisingWave => Some(DataType::Double {
10016 precision: None,
10017 scale: None,
10018 }),
10019 _ => None,
10020 };
10021 if let Some(dt) = cast_type {
10022 let cast_left = Expression::Cast(Box::new(Cast {
10023 this: op.left.clone(),
10024 to: dt,
10025 double_colon_syntax: false,
10026 trailing_comments: Vec::new(),
10027 format: None,
10028 default: None,
10029 inferred_type: None,
10030 }));
10031 let new_op = crate::expressions::BinaryOp {
10032 left: cast_left,
10033 right: op.right.clone(),
10034 left_comments: op.left_comments.clone(),
10035 operator_comments: op.operator_comments.clone(),
10036 trailing_comments: op.trailing_comments.clone(),
10037 inferred_type: None,
10038 };
10039 return Ok(Expression::Div(Box::new(new_op)));
10040 }
10041 }
10042 }
10043
10044 // CREATE DATABASE -> CREATE SCHEMA for DuckDB target
10045 if matches!(target, DialectType::DuckDB) {
10046 if let Expression::CreateDatabase(db) = e {
10047 let mut schema = crate::expressions::CreateSchema::new(db.name.name.clone());
10048 schema.if_not_exists = db.if_not_exists;
10049 return Ok(Expression::CreateSchema(Box::new(schema)));
10050 }
10051 if let Expression::DropDatabase(db) = e {
10052 let mut schema = crate::expressions::DropSchema::new(db.name.name.clone());
10053 schema.if_exists = db.if_exists;
10054 return Ok(Expression::DropSchema(Box::new(schema)));
10055 }
10056 }
10057
10058 // Strip ClickHouse Nullable(...) wrapper for non-ClickHouse targets
10059 if matches!(source, DialectType::ClickHouse)
10060 && !matches!(target, DialectType::ClickHouse)
10061 {
10062 if let Expression::Cast(ref c) = e {
10063 if let DataType::Custom { ref name } = c.to {
10064 if name.len() >= 9
10065 && name[..9].eq_ignore_ascii_case("NULLABLE(")
10066 && name.ends_with(")")
10067 {
10068 let inner = &name[9..name.len() - 1]; // strip "Nullable(" and ")"
10069 let inner_upper = inner.to_ascii_uppercase();
10070 let new_dt = match inner_upper.as_str() {
10071 "DATETIME" | "DATETIME64" => DataType::Timestamp {
10072 precision: None,
10073 timezone: false,
10074 },
10075 "DATE" => DataType::Date,
10076 "INT64" | "BIGINT" => DataType::BigInt { length: None },
10077 "INT32" | "INT" | "INTEGER" => DataType::Int {
10078 length: None,
10079 integer_spelling: false,
10080 },
10081 "FLOAT64" | "DOUBLE" => DataType::Double {
10082 precision: None,
10083 scale: None,
10084 },
10085 "STRING" => DataType::Text,
10086 _ => DataType::Custom {
10087 name: inner.to_string(),
10088 },
10089 };
10090 let mut new_cast = c.clone();
10091 new_cast.to = new_dt;
10092 return Ok(Expression::Cast(new_cast));
10093 }
10094 }
10095 }
10096 }
10097
10098 // ARRAY_CONCAT_AGG -> Snowflake: ARRAY_FLATTEN(ARRAY_AGG(...))
10099 if matches!(target, DialectType::Snowflake) {
10100 if let Expression::ArrayConcatAgg(ref agg) = e {
10101 let mut agg_clone = agg.as_ref().clone();
10102 agg_clone.name = None; // Clear name so generator uses default "ARRAY_AGG"
10103 let array_agg = Expression::ArrayAgg(Box::new(agg_clone));
10104 let flatten = Expression::Function(Box::new(Function::new(
10105 "ARRAY_FLATTEN".to_string(),
10106 vec![array_agg],
10107 )));
10108 return Ok(flatten);
10109 }
10110 }
10111
10112 // ARRAY_CONCAT_AGG -> others: keep as function for cross-dialect
10113 if !matches!(target, DialectType::BigQuery | DialectType::Snowflake) {
10114 if let Expression::ArrayConcatAgg(agg) = e {
10115 let arg = agg.this;
10116 return Ok(Expression::Function(Box::new(Function::new(
10117 "ARRAY_CONCAT_AGG".to_string(),
10118 vec![arg],
10119 ))));
10120 }
10121 }
10122
10123 // Determine what action to take by inspecting e immutably
10124 let action = {
10125 let source_propagates_nulls =
10126 matches!(source, DialectType::Snowflake | DialectType::BigQuery);
10127 let target_ignores_nulls =
10128 matches!(target, DialectType::DuckDB | DialectType::PostgreSQL);
10129
10130 match &e {
10131 Expression::Function(f) => {
10132 let name = f.name.to_ascii_uppercase();
10133 // DuckDB json(x) is a synonym for CAST(x AS JSON) — parses a string.
10134 // Map to JSON_PARSE(x) for Trino/Presto/Athena to preserve semantics.
10135 if name == "JSON"
10136 && f.args.len() == 1
10137 && matches!(source, DialectType::DuckDB)
10138 && matches!(
10139 target,
10140 DialectType::Presto | DialectType::Trino | DialectType::Athena
10141 )
10142 {
10143 Action::DuckDBJsonFuncToJsonParse
10144 // DuckDB json_valid(x) has no direct Trino equivalent; emit the
10145 // SQL:2016 `x IS JSON` predicate which has matching semantics.
10146 } else if name == "JSON_VALID"
10147 && f.args.len() == 1
10148 && matches!(source, DialectType::DuckDB)
10149 && matches!(
10150 target,
10151 DialectType::Presto | DialectType::Trino | DialectType::Athena
10152 )
10153 {
10154 Action::DuckDBJsonValidToIsJson
10155 // DATE_PART: strip quotes from first arg when target is Snowflake (source != Snowflake)
10156 } else if (name == "DATE_PART" || name == "DATEPART")
10157 && f.args.len() == 2
10158 && matches!(target, DialectType::Snowflake)
10159 && !matches!(source, DialectType::Snowflake)
10160 && matches!(
10161 &f.args[0],
10162 Expression::Literal(lit) if matches!(lit.as_ref(), crate::expressions::Literal::String(_))
10163 )
10164 {
10165 Action::DatePartUnquote
10166 } else if source_propagates_nulls
10167 && target_ignores_nulls
10168 && (name == "GREATEST" || name == "LEAST")
10169 && f.args.len() >= 2
10170 {
10171 Action::GreatestLeastNull
10172 } else if matches!(source, DialectType::Snowflake)
10173 && name == "ARRAY_GENERATE_RANGE"
10174 && f.args.len() >= 2
10175 {
10176 Action::ArrayGenerateRange
10177 } else if matches!(source, DialectType::Snowflake)
10178 && matches!(target, DialectType::DuckDB)
10179 && name == "DATE_TRUNC"
10180 && f.args.len() == 2
10181 {
10182 // Determine if DuckDB DATE_TRUNC needs CAST wrapping to preserve input type.
10183 // Logic based on Python sqlglot's input_type_preserved flag:
10184 // - DATE + non-date-unit (HOUR, MINUTE, etc.) -> wrap
10185 // - TIMESTAMP + date-unit (YEAR, QUARTER, MONTH, WEEK, DAY) -> wrap
10186 // - TIMESTAMPTZ/TIMESTAMPLTZ/TIME -> always wrap
10187 let unit_str = match &f.args[0] {
10188 Expression::Literal(lit) if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) => {
10189 let crate::expressions::Literal::String(s) = lit.as_ref() else { unreachable!() };
10190 Some(s.to_ascii_uppercase())
10191 }
10192 _ => None,
10193 };
10194 let is_date_unit = unit_str.as_ref().map_or(false, |u| {
10195 matches!(u.as_str(), "YEAR" | "QUARTER" | "MONTH" | "WEEK" | "DAY")
10196 });
10197 match &f.args[1] {
10198 Expression::Cast(c) => match &c.to {
10199 DataType::Time { .. } => Action::DateTruncWrapCast,
10200 DataType::Custom { name }
10201 if name.eq_ignore_ascii_case("TIMESTAMPTZ")
10202 || name.eq_ignore_ascii_case("TIMESTAMPLTZ") =>
10203 {
10204 Action::DateTruncWrapCast
10205 }
10206 DataType::Timestamp { timezone: true, .. } => {
10207 Action::DateTruncWrapCast
10208 }
10209 DataType::Date if !is_date_unit => Action::DateTruncWrapCast,
10210 DataType::Timestamp {
10211 timezone: false, ..
10212 } if is_date_unit => Action::DateTruncWrapCast,
10213 _ => Action::None,
10214 },
10215 _ => Action::None,
10216 }
10217 } else if matches!(source, DialectType::Snowflake)
10218 && matches!(target, DialectType::DuckDB)
10219 && name == "TO_DATE"
10220 && f.args.len() == 1
10221 && !matches!(
10222 &f.args[0],
10223 Expression::Literal(lit) if matches!(lit.as_ref(), crate::expressions::Literal::String(_))
10224 )
10225 {
10226 Action::ToDateToCast
10227 } else if !matches!(source, DialectType::Redshift)
10228 && matches!(target, DialectType::Redshift)
10229 && name == "CONVERT_TIMEZONE"
10230 && (f.args.len() == 2 || f.args.len() == 3)
10231 {
10232 // Convert Function("CONVERT_TIMEZONE") to Expression::ConvertTimezone
10233 // so Redshift's transform_expr won't expand 2-arg to 3-arg with 'UTC'.
10234 // The Redshift parser adds 'UTC' as default source_tz, but when
10235 // transpiling from other dialects, we should preserve the original form.
10236 Action::ConvertTimezoneToExpr
10237 } else if matches!(source, DialectType::Snowflake)
10238 && matches!(target, DialectType::DuckDB)
10239 && name == "REGEXP_REPLACE"
10240 && f.args.len() == 4
10241 && !matches!(
10242 &f.args[3],
10243 Expression::Literal(lit) if matches!(lit.as_ref(), crate::expressions::Literal::String(_))
10244 )
10245 {
10246 // Snowflake REGEXP_REPLACE with position arg -> DuckDB needs 'g' flag
10247 Action::RegexpReplaceSnowflakeToDuckDB
10248 } else if matches!(source, DialectType::Snowflake)
10249 && matches!(target, DialectType::DuckDB)
10250 && name == "REGEXP_REPLACE"
10251 && f.args.len() == 5
10252 {
10253 // Snowflake REGEXP_REPLACE(s, p, r, pos, occ) -> DuckDB
10254 Action::RegexpReplacePositionSnowflakeToDuckDB
10255 } else if matches!(source, DialectType::Snowflake)
10256 && matches!(target, DialectType::DuckDB)
10257 && name == "REGEXP_SUBSTR"
10258 {
10259 // Snowflake REGEXP_SUBSTR -> DuckDB REGEXP_EXTRACT variants
10260 Action::RegexpSubstrSnowflakeToDuckDB
10261 } else if matches!(source, DialectType::Snowflake)
10262 && matches!(target, DialectType::Snowflake)
10263 && (name == "REGEXP_SUBSTR" || name == "REGEXP_SUBSTR_ALL")
10264 && f.args.len() == 6
10265 {
10266 // Snowflake identity: strip trailing group=0
10267 Action::RegexpSubstrSnowflakeIdentity
10268 } else if matches!(source, DialectType::Snowflake)
10269 && matches!(target, DialectType::DuckDB)
10270 && name == "REGEXP_SUBSTR_ALL"
10271 {
10272 // Snowflake REGEXP_SUBSTR_ALL -> DuckDB REGEXP_EXTRACT_ALL variants
10273 Action::RegexpSubstrAllSnowflakeToDuckDB
10274 } else if matches!(source, DialectType::Snowflake)
10275 && matches!(target, DialectType::DuckDB)
10276 && name == "REGEXP_COUNT"
10277 {
10278 // Snowflake REGEXP_COUNT -> DuckDB LENGTH(REGEXP_EXTRACT_ALL(...))
10279 Action::RegexpCountSnowflakeToDuckDB
10280 } else if matches!(source, DialectType::Snowflake)
10281 && matches!(target, DialectType::DuckDB)
10282 && name == "REGEXP_INSTR"
10283 {
10284 // Snowflake REGEXP_INSTR -> DuckDB complex CASE expression
10285 Action::RegexpInstrSnowflakeToDuckDB
10286 } else if matches!(source, DialectType::BigQuery)
10287 && matches!(target, DialectType::Snowflake)
10288 && name == "REGEXP_EXTRACT_ALL"
10289 {
10290 // BigQuery REGEXP_EXTRACT_ALL -> Snowflake REGEXP_SUBSTR_ALL
10291 Action::RegexpExtractAllToSnowflake
10292 } else if name == "_BQ_TO_HEX" {
10293 // Internal marker from TO_HEX conversion - bare (no LOWER/UPPER wrapper)
10294 Action::BigQueryToHexBare
10295 } else if matches!(source, DialectType::BigQuery)
10296 && !matches!(target, DialectType::BigQuery)
10297 {
10298 // BigQuery-specific functions that need to be converted to standard forms
10299 match name.as_str() {
10300 "TIMESTAMP_DIFF" | "DATETIME_DIFF" | "TIME_DIFF"
10301 | "DATE_DIFF"
10302 | "TIMESTAMP_ADD" | "TIMESTAMP_SUB"
10303 | "DATETIME_ADD" | "DATETIME_SUB"
10304 | "TIME_ADD" | "TIME_SUB"
10305 | "DATE_ADD" | "DATE_SUB"
10306 | "SAFE_DIVIDE"
10307 | "GENERATE_UUID"
10308 | "COUNTIF"
10309 | "EDIT_DISTANCE"
10310 | "TIMESTAMP_SECONDS" | "TIMESTAMP_MILLIS" | "TIMESTAMP_MICROS"
10311 | "TIMESTAMP_TRUNC" | "DATETIME_TRUNC" | "DATE_TRUNC"
10312 | "TO_HEX"
10313 | "TO_JSON_STRING"
10314 | "GENERATE_ARRAY" | "GENERATE_TIMESTAMP_ARRAY"
10315 | "DIV"
10316 | "UNIX_DATE" | "UNIX_SECONDS" | "UNIX_MILLIS" | "UNIX_MICROS"
10317 | "LAST_DAY"
10318 | "TIME" | "DATETIME" | "TIMESTAMP" | "STRING"
10319 | "REGEXP_CONTAINS"
10320 | "CONTAINS_SUBSTR"
10321 | "SAFE_ADD" | "SAFE_SUBTRACT" | "SAFE_MULTIPLY"
10322 | "SAFE_CAST"
10323 | "GENERATE_DATE_ARRAY"
10324 | "PARSE_DATE" | "PARSE_DATETIME" | "PARSE_TIMESTAMP"
10325 | "FORMAT_DATE" | "FORMAT_DATETIME" | "FORMAT_TIMESTAMP"
10326 | "ARRAY_CONCAT"
10327 | "JSON_QUERY" | "JSON_VALUE_ARRAY"
10328 | "INSTR"
10329 | "MD5" | "SHA1" | "SHA256" | "SHA512"
10330 | "GENERATE_UUID()" // just in case
10331 | "REGEXP_EXTRACT_ALL"
10332 | "REGEXP_EXTRACT"
10333 | "INT64"
10334 | "ARRAY_CONCAT_AGG"
10335 | "DATE_DIFF(" // just in case
10336 | "TO_HEX_MD5" // internal
10337 | "MOD"
10338 | "CONCAT"
10339 | "CURRENT_TIMESTAMP" | "CURRENT_DATE" | "CURRENT_DATETIME" | "CURRENT_TIME"
10340 | "STRUCT"
10341 | "ROUND"
10342 | "MAKE_INTERVAL"
10343 | "ARRAY_TO_STRING"
10344 | "PERCENTILE_CONT"
10345 => Action::BigQueryFunctionNormalize,
10346 "ARRAY" if matches!(target, DialectType::Snowflake)
10347 && f.args.len() == 1
10348 && matches!(&f.args[0], Expression::Select(s) if s.kind.as_deref() == Some("STRUCT"))
10349 => Action::BigQueryArraySelectAsStructToSnowflake,
10350 _ => Action::None,
10351 }
10352 } else if matches!(source, DialectType::BigQuery)
10353 && matches!(target, DialectType::BigQuery)
10354 {
10355 // BigQuery -> BigQuery normalizations
10356 match name.as_str() {
10357 "TIMESTAMP_DIFF"
10358 | "DATETIME_DIFF"
10359 | "TIME_DIFF"
10360 | "DATE_DIFF"
10361 | "DATE_ADD"
10362 | "TO_HEX"
10363 | "CURRENT_TIMESTAMP"
10364 | "CURRENT_DATE"
10365 | "CURRENT_TIME"
10366 | "CURRENT_DATETIME"
10367 | "GENERATE_DATE_ARRAY"
10368 | "INSTR"
10369 | "FORMAT_DATETIME"
10370 | "DATETIME"
10371 | "MAKE_INTERVAL" => Action::BigQueryFunctionNormalize,
10372 _ => Action::None,
10373 }
10374 } else {
10375 // Generic function normalization for non-BigQuery sources
10376 match name.as_str() {
10377 "ARBITRARY" | "AGGREGATE"
10378 | "REGEXP_MATCHES" | "REGEXP_FULL_MATCH"
10379 | "STRUCT_EXTRACT"
10380 | "LIST_FILTER" | "LIST_TRANSFORM" | "LIST_SORT" | "LIST_REVERSE_SORT"
10381 | "STRING_TO_ARRAY" | "STR_SPLIT" | "STR_SPLIT_REGEX" | "SPLIT_TO_ARRAY"
10382 | "SUBSTRINGINDEX"
10383 | "ARRAY_LENGTH" | "SIZE" | "CARDINALITY"
10384 | "UNICODE"
10385 | "XOR"
10386 | "ARRAY_REVERSE_SORT"
10387 | "ENCODE" | "DECODE"
10388 | "QUANTILE"
10389 | "EPOCH" | "EPOCH_MS"
10390 | "HASHBYTES"
10391 | "JSON_EXTRACT_PATH" | "JSON_EXTRACT_PATH_TEXT"
10392 | "APPROX_DISTINCT"
10393 | "DATE_PARSE" | "FORMAT_DATETIME"
10394 | "REGEXP_EXTRACT" | "REGEXP_SUBSTR" | "TO_DAYS"
10395 | "RLIKE"
10396 | "DATEDIFF" | "DATE_DIFF" | "MONTHS_BETWEEN"
10397 | "ADD_MONTHS" | "DATEADD" | "DATE_ADD" | "DATE_SUB" | "DATETRUNC"
10398 | "LAST_DAY" | "LAST_DAY_OF_MONTH" | "EOMONTH"
10399 | "ARRAY_CONSTRUCT" | "ARRAY_CAT" | "ARRAY_COMPACT"
10400 | "ARRAY_FILTER" | "FILTER" | "REDUCE" | "ARRAY_REVERSE"
10401 | "MAP" | "MAP_FROM_ENTRIES"
10402 | "COLLECT_LIST" | "COLLECT_SET"
10403 | "ISNAN" | "IS_NAN"
10404 | "TO_UTC_TIMESTAMP" | "FROM_UTC_TIMESTAMP"
10405 | "FORMAT_NUMBER"
10406 | "TOMONDAY" | "TOSTARTOFWEEK" | "TOSTARTOFMONTH" | "TOSTARTOFYEAR"
10407 | "ELEMENT_AT"
10408 | "EXPLODE" | "EXPLODE_OUTER" | "POSEXPLODE"
10409 | "SPLIT_PART"
10410 // GENERATE_SERIES: handled separately below
10411 | "JSON_EXTRACT" | "JSON_EXTRACT_SCALAR"
10412 | "JSON_QUERY" | "JSON_VALUE"
10413 | "JSON_SEARCH"
10414 | "JSON_EXTRACT_JSON" | "BSON_EXTRACT_BSON"
10415 | "TO_UNIX_TIMESTAMP" | "UNIX_TIMESTAMP"
10416 | "CURDATE" | "CURTIME"
10417 | "ARRAY_TO_STRING"
10418 | "ARRAY_SORT" | "SORT_ARRAY"
10419 | "LEFT" | "RIGHT"
10420 | "MAP_FROM_ARRAYS"
10421 | "LIKE" | "ILIKE"
10422 | "ARRAY_CONCAT" | "LIST_CONCAT"
10423 | "QUANTILE_CONT" | "QUANTILE_DISC"
10424 | "PERCENTILE_CONT" | "PERCENTILE_DISC"
10425 | "PERCENTILE_APPROX" | "APPROX_PERCENTILE"
10426 | "LOCATE" | "STRPOS" | "INSTR"
10427 | "CHAR"
10428 // CONCAT: handled separately for COALESCE wrapping
10429 | "ARRAY_JOIN"
10430 | "ARRAY_CONTAINS" | "HAS" | "CONTAINS"
10431 | "ISNULL"
10432 | "MONTHNAME"
10433 | "TO_TIMESTAMP"
10434 | "TO_DATE"
10435 | "TO_JSON"
10436 | "REGEXP_SPLIT"
10437 | "SPLIT"
10438 | "FORMATDATETIME"
10439 | "ARRAYJOIN"
10440 | "SPLITBYSTRING" | "SPLITBYREGEXP"
10441 | "NVL"
10442 | "TO_CHAR"
10443 | "DBMS_RANDOM.VALUE"
10444 | "REGEXP_LIKE"
10445 | "REPLICATE"
10446 | "LEN"
10447 | "COUNT_BIG"
10448 | "DATEFROMPARTS"
10449 | "DATETIMEFROMPARTS"
10450 | "CONVERT" | "TRY_CONVERT"
10451 | "STRFTIME" | "STRPTIME"
10452 | "DATE_FORMAT" | "FORMAT_DATE"
10453 | "PARSE_TIMESTAMP" | "PARSE_DATETIME" | "PARSE_DATE"
10454 | "FROM_ISO8601_TIMESTAMP" | "FROM_ISO8601_DATE"
10455 | "FROM_BASE64" | "TO_BASE64"
10456 | "GETDATE"
10457 | "TO_HEX" | "FROM_HEX" | "UNHEX" | "HEX"
10458 | "TO_UTF8" | "FROM_UTF8"
10459 | "STARTS_WITH" | "STARTSWITH"
10460 | "APPROX_COUNT_DISTINCT"
10461 | "JSON_FORMAT"
10462 | "SYSDATE"
10463 | "LOGICAL_OR" | "LOGICAL_AND"
10464 | "MONTHS_ADD"
10465 | "SCHEMA_NAME"
10466 | "STRTOL"
10467 | "EDITDIST3"
10468 | "FORMAT"
10469 | "LIST_CONTAINS" | "LIST_HAS"
10470 | "VARIANCE" | "STDDEV"
10471 | "ISINF"
10472 | "TO_UNIXTIME"
10473 | "FROM_UNIXTIME"
10474 | "DATEPART" | "DATE_PART"
10475 | "DATENAME"
10476 | "STRING_AGG"
10477 | "JSON_ARRAYAGG"
10478 | "APPROX_QUANTILE"
10479 | "MAKE_DATE"
10480 | "LIST_HAS_ANY" | "ARRAY_HAS_ANY"
10481 | "RANGE"
10482 | "TRY_ELEMENT_AT"
10483 | "STR_TO_MAP"
10484 | "STRING"
10485 | "STR_TO_TIME"
10486 | "CURRENT_SCHEMA"
10487 | "LTRIM" | "RTRIM"
10488 | "UUID"
10489 | "FARM_FINGERPRINT"
10490 | "JSON_KEYS"
10491 | "WEEKOFYEAR"
10492 | "CONCAT_WS"
10493 | "TRY_DIVIDE"
10494 | "ARRAY_SLICE"
10495 | "ARRAY_PREPEND"
10496 | "ARRAY_REMOVE"
10497 | "GENERATE_DATE_ARRAY"
10498 | "PARSE_JSON"
10499 | "JSON_REMOVE"
10500 | "JSON_SET"
10501 | "LEVENSHTEIN"
10502 | "CURRENT_VERSION"
10503 | "ARRAY_MAX"
10504 | "ARRAY_MIN"
10505 | "JAROWINKLER_SIMILARITY"
10506 | "CURRENT_SCHEMAS"
10507 | "TO_VARIANT"
10508 | "JSON_GROUP_ARRAY" | "JSON_GROUP_OBJECT"
10509 | "ARRAYS_OVERLAP" | "ARRAY_INTERSECTION"
10510 => Action::GenericFunctionNormalize,
10511 // Canonical date functions -> dialect-specific
10512 "TS_OR_DS_TO_DATE" => Action::TsOrDsToDateConvert,
10513 "TS_OR_DS_TO_DATE_STR" if f.args.len() == 1 => Action::TsOrDsToDateStrConvert,
10514 "DATE_STR_TO_DATE" if f.args.len() == 1 => Action::DateStrToDateConvert,
10515 "TIME_STR_TO_DATE" if f.args.len() == 1 => Action::TimeStrToDateConvert,
10516 "TIME_STR_TO_TIME" if f.args.len() <= 2 => Action::TimeStrToTimeConvert,
10517 "TIME_STR_TO_UNIX" if f.args.len() == 1 => Action::TimeStrToUnixConvert,
10518 "TIME_TO_TIME_STR" if f.args.len() == 1 => Action::TimeToTimeStrConvert,
10519 "DATE_TO_DATE_STR" if f.args.len() == 1 => Action::DateToDateStrConvert,
10520 "DATE_TO_DI" if f.args.len() == 1 => Action::DateToDiConvert,
10521 "DI_TO_DATE" if f.args.len() == 1 => Action::DiToDateConvert,
10522 "TS_OR_DI_TO_DI" if f.args.len() == 1 => Action::TsOrDiToDiConvert,
10523 "UNIX_TO_STR" if f.args.len() == 2 => Action::UnixToStrConvert,
10524 "UNIX_TO_TIME" if f.args.len() == 1 => Action::UnixToTimeConvert,
10525 "UNIX_TO_TIME_STR" if f.args.len() == 1 => Action::UnixToTimeStrConvert,
10526 "TIME_TO_UNIX" if f.args.len() == 1 => Action::TimeToUnixConvert,
10527 "TIME_TO_STR" if f.args.len() == 2 => Action::TimeToStrConvert,
10528 "STR_TO_UNIX" if f.args.len() == 2 => Action::StrToUnixConvert,
10529 // STR_TO_DATE(x, fmt) -> dialect-specific
10530 "STR_TO_DATE" if f.args.len() == 2
10531 && matches!(source, DialectType::Generic) => Action::StrToDateConvert,
10532 "STR_TO_DATE" => Action::GenericFunctionNormalize,
10533 // TS_OR_DS_ADD(x, n, 'UNIT') from Generic -> dialect-specific DATE_ADD
10534 "TS_OR_DS_ADD" if f.args.len() == 3
10535 && matches!(source, DialectType::Generic) => Action::TsOrDsAddConvert,
10536 // DATE_FROM_UNIX_DATE(n) -> DATEADD(DAY, n, '1970-01-01')
10537 "DATE_FROM_UNIX_DATE" if f.args.len() == 1 => Action::DateFromUnixDateConvert,
10538 // NVL2(a, b, c) -> CASE WHEN NOT a IS NULL THEN b [ELSE c] END
10539 "NVL2" if (f.args.len() == 2 || f.args.len() == 3) => Action::Nvl2Expand,
10540 // IFNULL(a, b) -> COALESCE(a, b) when coming from Generic source
10541 "IFNULL" if f.args.len() == 2 => Action::IfnullToCoalesce,
10542 // IS_ASCII(x) -> dialect-specific
10543 "IS_ASCII" if f.args.len() == 1 => Action::IsAsciiConvert,
10544 // STR_POSITION(haystack, needle[, pos[, occ]]) -> dialect-specific
10545 "STR_POSITION" => Action::StrPositionConvert,
10546 // ARRAY_SUM -> dialect-specific
10547 "ARRAY_SUM" => Action::ArraySumConvert,
10548 // ARRAY_SIZE -> dialect-specific (Drill only)
10549 "ARRAY_SIZE" if matches!(target, DialectType::Drill) => Action::ArraySizeConvert,
10550 // ARRAY_ANY -> dialect-specific
10551 "ARRAY_ANY" if f.args.len() == 2 => Action::ArrayAnyConvert,
10552 // Functions needing specific cross-dialect transforms
10553 "MAX_BY" | "MIN_BY" if matches!(target, DialectType::ClickHouse | DialectType::Spark | DialectType::Databricks | DialectType::DuckDB) => Action::MaxByMinByConvert,
10554 "STRUCT" if matches!(source, DialectType::Spark | DialectType::Databricks)
10555 && !matches!(target, DialectType::Spark | DialectType::Databricks | DialectType::Hive) => Action::SparkStructConvert,
10556 "ARRAY" if matches!(source, DialectType::BigQuery)
10557 && matches!(target, DialectType::Snowflake)
10558 && f.args.len() == 1
10559 && matches!(&f.args[0], Expression::Select(s) if s.kind.as_deref() == Some("STRUCT")) => Action::BigQueryArraySelectAsStructToSnowflake,
10560 "ARRAY" if matches!(target, DialectType::Presto | DialectType::Trino | DialectType::Athena | DialectType::BigQuery | DialectType::DuckDB | DialectType::Snowflake | DialectType::ClickHouse | DialectType::StarRocks) => Action::ArraySyntaxConvert,
10561 "TRUNC" if f.args.len() == 2 && matches!(&f.args[1], Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_))) && matches!(target, DialectType::Presto | DialectType::Trino | DialectType::ClickHouse) => Action::TruncToDateTrunc,
10562 "TRUNC" | "TRUNCATE" if f.args.len() <= 2 && !f.args.get(1).map_or(false, |a| matches!(a, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))) => Action::GenericFunctionNormalize,
10563 // DATE_TRUNC('unit', x) from Generic source -> arg swap for BigQuery/Doris/Spark/MySQL
10564 "DATE_TRUNC" if f.args.len() == 2
10565 && matches!(source, DialectType::Generic)
10566 && matches!(target, DialectType::BigQuery | DialectType::Doris | DialectType::StarRocks
10567 | DialectType::Spark | DialectType::Databricks | DialectType::MySQL) => Action::DateTruncSwapArgs,
10568 // TIMESTAMP_TRUNC(x, UNIT) from Generic source -> convert to per-dialect
10569 "TIMESTAMP_TRUNC" if f.args.len() >= 2
10570 && matches!(source, DialectType::Generic) => Action::TimestampTruncConvert,
10571 "UNIFORM" if matches!(target, DialectType::Snowflake) => Action::GenericFunctionNormalize,
10572 // GENERATE_SERIES -> SEQUENCE/UNNEST/EXPLODE for target dialects
10573 "GENERATE_SERIES" if matches!(source, DialectType::PostgreSQL | DialectType::Redshift)
10574 && !matches!(target, DialectType::PostgreSQL | DialectType::Redshift | DialectType::TSQL | DialectType::Fabric) => Action::GenerateSeriesConvert,
10575 // GENERATE_SERIES with interval normalization for PG target
10576 "GENERATE_SERIES" if f.args.len() >= 3
10577 && matches!(source, DialectType::PostgreSQL | DialectType::Redshift)
10578 && matches!(target, DialectType::PostgreSQL | DialectType::Redshift) => Action::GenerateSeriesConvert,
10579 "GENERATE_SERIES" => Action::None, // passthrough for other cases
10580 // CONCAT(a, b) -> COALESCE wrapping for Presto/ClickHouse from PostgreSQL
10581 "CONCAT" if matches!(source, DialectType::PostgreSQL | DialectType::Redshift)
10582 && matches!(target, DialectType::Presto | DialectType::Trino | DialectType::ClickHouse) => Action::ConcatCoalesceWrap,
10583 "CONCAT" => Action::GenericFunctionNormalize,
10584 // CBRT(x) -> POWER(CAST(x AS FLOAT), 1.0 / 3.0)
10585 "CBRT" if f.args.len() == 1
10586 && Self::is_postgres_family_source(source)
10587 && matches!(target, DialectType::TSQL | DialectType::Fabric) => Action::CbrtToPower,
10588 "JSON_BUILD_OBJECT" | "JSONB_BUILD_OBJECT"
10589 if Self::is_postgres_family_source(source)
10590 && matches!(target, DialectType::TSQL | DialectType::Fabric)
10591 && f.args.len() % 2 == 0 =>
10592 {
10593 Action::PostgresJsonBuildObjectToJsonObject
10594 }
10595 "JSON_AGG" | "JSONB_AGG"
10596 if Self::is_postgres_family_source(source)
10597 && matches!(target, DialectType::TSQL | DialectType::Fabric)
10598 && f.args.len() == 1 =>
10599 {
10600 Action::PostgresJsonAggToJsonArrayAgg
10601 }
10602 // DIV(a, b) -> target-specific integer division
10603 "DIV" if f.args.len() == 2
10604 && Self::is_postgres_family_source(source)
10605 && matches!(
10606 target,
10607 DialectType::DuckDB
10608 | DialectType::BigQuery
10609 | DialectType::SQLite
10610 | DialectType::TSQL
10611 | DialectType::Fabric
10612 ) => Action::DivFuncConvert,
10613 // JSON_OBJECT_AGG/JSONB_OBJECT_AGG -> JSON_GROUP_OBJECT for DuckDB
10614 "JSON_OBJECT_AGG" | "JSONB_OBJECT_AGG" if f.args.len() == 2
10615 && matches!(target, DialectType::DuckDB) => Action::JsonObjectAggConvert,
10616 // JSONB_EXISTS -> JSON_EXISTS for DuckDB
10617 "JSONB_EXISTS" if f.args.len() == 2
10618 && matches!(target, DialectType::DuckDB) => Action::JsonbExistsConvert,
10619 // DATE_BIN -> TIME_BUCKET for DuckDB
10620 "DATE_BIN" if matches!(target, DialectType::DuckDB) => Action::DateBinConvert,
10621 // Multi-arg MIN(a,b,c) -> LEAST, MAX(a,b,c) -> GREATEST
10622 "MIN" | "MAX" if f.args.len() > 1 && !matches!(target, DialectType::SQLite) => Action::MinMaxToLeastGreatest,
10623 // ClickHouse uniq -> APPROX_COUNT_DISTINCT for other dialects
10624 "UNIQ" if matches!(source, DialectType::ClickHouse) && !matches!(target, DialectType::ClickHouse) => Action::ClickHouseUniqToApproxCountDistinct,
10625 // ClickHouse any -> ANY_VALUE for other dialects
10626 "ANY" if f.args.len() == 1 && matches!(source, DialectType::ClickHouse) && !matches!(target, DialectType::ClickHouse) => Action::ClickHouseAnyToAnyValue,
10627 _ => Action::None,
10628 }
10629 }
10630 }
10631 Expression::AggregateFunction(af) => {
10632 let name = af.name.to_ascii_uppercase();
10633 match name.as_str() {
10634 "ARBITRARY" | "AGGREGATE" => Action::GenericFunctionNormalize,
10635 "JSON_AGG" | "JSONB_AGG"
10636 if Self::is_postgres_family_source(source)
10637 && matches!(target, DialectType::TSQL | DialectType::Fabric) =>
10638 {
10639 Action::PostgresJsonAggToJsonArrayAgg
10640 }
10641 "JSON_ARRAYAGG" => Action::GenericFunctionNormalize,
10642 // JSON_OBJECT_AGG/JSONB_OBJECT_AGG -> JSON_GROUP_OBJECT for DuckDB
10643 "JSON_OBJECT_AGG" | "JSONB_OBJECT_AGG"
10644 if matches!(target, DialectType::DuckDB) =>
10645 {
10646 Action::JsonObjectAggConvert
10647 }
10648 "ARRAY_AGG"
10649 if matches!(
10650 target,
10651 DialectType::Hive
10652 | DialectType::Spark
10653 | DialectType::Databricks
10654 ) =>
10655 {
10656 Action::ArrayAggToCollectList
10657 }
10658 "MAX_BY" | "MIN_BY"
10659 if matches!(
10660 target,
10661 DialectType::ClickHouse
10662 | DialectType::Spark
10663 | DialectType::Databricks
10664 | DialectType::DuckDB
10665 ) =>
10666 {
10667 Action::MaxByMinByConvert
10668 }
10669 "COLLECT_LIST"
10670 if matches!(
10671 target,
10672 DialectType::Presto | DialectType::Trino | DialectType::DuckDB
10673 ) =>
10674 {
10675 Action::CollectListToArrayAgg
10676 }
10677 "COLLECT_SET"
10678 if matches!(
10679 target,
10680 DialectType::Presto
10681 | DialectType::Trino
10682 | DialectType::Snowflake
10683 | DialectType::DuckDB
10684 ) =>
10685 {
10686 Action::CollectSetConvert
10687 }
10688 "PERCENTILE"
10689 if matches!(
10690 target,
10691 DialectType::DuckDB | DialectType::Presto | DialectType::Trino
10692 ) =>
10693 {
10694 Action::PercentileConvert
10695 }
10696 // CORR -> CASE WHEN ISNAN(CORR(a,b)) THEN NULL ELSE CORR(a,b) END for DuckDB
10697 "CORR"
10698 if matches!(target, DialectType::DuckDB)
10699 && matches!(source, DialectType::Snowflake) =>
10700 {
10701 Action::CorrIsnanWrap
10702 }
10703 // BigQuery APPROX_QUANTILES(x, n) -> APPROX_QUANTILE(x, [quantiles]) for DuckDB
10704 "APPROX_QUANTILES"
10705 if matches!(source, DialectType::BigQuery)
10706 && matches!(target, DialectType::DuckDB) =>
10707 {
10708 Action::BigQueryApproxQuantiles
10709 }
10710 // BigQuery PERCENTILE_CONT(x, frac RESPECT NULLS) -> QUANTILE_CONT(x, frac) for DuckDB
10711 "PERCENTILE_CONT"
10712 if matches!(source, DialectType::BigQuery)
10713 && matches!(target, DialectType::DuckDB)
10714 && af.args.len() >= 2 =>
10715 {
10716 Action::BigQueryPercentileContToDuckDB
10717 }
10718 _ => Action::None,
10719 }
10720 }
10721 Expression::JSONArrayAgg(_) => match target {
10722 DialectType::PostgreSQL => Action::GenericFunctionNormalize,
10723 _ => Action::None,
10724 },
10725 Expression::ToNumber(tn) => {
10726 // TO_NUMBER(x) with 1 arg -> CAST(x AS DOUBLE) for most targets
10727 if tn.format.is_none() && tn.precision.is_none() && tn.scale.is_none() {
10728 match target {
10729 DialectType::Oracle
10730 | DialectType::Snowflake
10731 | DialectType::Teradata => Action::None,
10732 _ => Action::GenericFunctionNormalize,
10733 }
10734 } else {
10735 Action::None
10736 }
10737 }
10738 Expression::Nvl2(_) => {
10739 // NVL2(a, b, c) -> CASE WHEN NOT a IS NULL THEN b ELSE c END for most dialects
10740 // Keep as NVL2 for dialects that support it natively
10741 match target {
10742 DialectType::Oracle
10743 | DialectType::Snowflake
10744 | DialectType::Teradata
10745 | DialectType::Spark
10746 | DialectType::Databricks
10747 | DialectType::Redshift => Action::None,
10748 _ => Action::Nvl2Expand,
10749 }
10750 }
10751 Expression::Decode(_) | Expression::DecodeCase(_) => {
10752 // DECODE(a, b, c[, d, e[, ...]]) -> CASE WHEN with null-safe comparisons
10753 // Keep as DECODE for Oracle/Snowflake
10754 match target {
10755 DialectType::Oracle | DialectType::Snowflake => Action::None,
10756 _ => Action::DecodeSimplify,
10757 }
10758 }
10759 Expression::Coalesce(ref cf) => {
10760 // IFNULL(a, b) -> COALESCE(a, b): clear original_name for cross-dialect
10761 // BigQuery keeps IFNULL natively when source is also BigQuery
10762 if cf.original_name.as_deref() == Some("IFNULL")
10763 && !(matches!(source, DialectType::BigQuery)
10764 && matches!(target, DialectType::BigQuery))
10765 {
10766 Action::IfnullToCoalesce
10767 } else {
10768 Action::None
10769 }
10770 }
10771 Expression::IfFunc(if_func) => {
10772 if matches!(source, DialectType::Snowflake)
10773 && matches!(
10774 target,
10775 DialectType::Presto | DialectType::Trino | DialectType::SQLite
10776 )
10777 && matches!(if_func.false_value, Some(Expression::Div(_)))
10778 {
10779 Action::Div0TypedDivision
10780 } else {
10781 Action::None
10782 }
10783 }
10784 Expression::ToJson(_) => match target {
10785 DialectType::Presto | DialectType::Trino => Action::ToJsonConvert,
10786 DialectType::BigQuery => Action::ToJsonConvert,
10787 DialectType::DuckDB => Action::ToJsonConvert,
10788 _ => Action::None,
10789 },
10790 Expression::ArrayAgg(ref agg) => {
10791 if matches!(target, DialectType::MySQL | DialectType::SingleStore) {
10792 Action::ArrayAggToGroupConcat
10793 } else if matches!(
10794 target,
10795 DialectType::Hive | DialectType::Spark | DialectType::Databricks
10796 ) {
10797 // Any source -> Hive/Spark: convert ARRAY_AGG to COLLECT_LIST
10798 Action::ArrayAggToCollectList
10799 } else if matches!(
10800 source,
10801 DialectType::Spark | DialectType::Databricks | DialectType::Hive
10802 ) && matches!(target, DialectType::DuckDB)
10803 && agg.filter.is_some()
10804 {
10805 // Spark/Hive ARRAY_AGG excludes NULLs, DuckDB includes them
10806 // Need to add NOT x IS NULL to existing filter
10807 Action::ArrayAggNullFilter
10808 } else if matches!(target, DialectType::DuckDB)
10809 && agg.ignore_nulls == Some(true)
10810 && !agg.order_by.is_empty()
10811 {
10812 // BigQuery ARRAY_AGG(x IGNORE NULLS ORDER BY ...) -> DuckDB ARRAY_AGG(x ORDER BY a NULLS FIRST, ...)
10813 Action::ArrayAggIgnoreNullsDuckDB
10814 } else if !matches!(source, DialectType::Snowflake) {
10815 Action::None
10816 } else if matches!(target, DialectType::Spark | DialectType::Databricks) {
10817 let is_array_agg = agg.name.as_deref().map_or(false, |n| n.eq_ignore_ascii_case("ARRAY_AGG"))
10818 || agg.name.is_none();
10819 if is_array_agg {
10820 Action::ArrayAggCollectList
10821 } else {
10822 Action::None
10823 }
10824 } else if matches!(
10825 target,
10826 DialectType::DuckDB | DialectType::Presto | DialectType::Trino
10827 ) && agg.filter.is_none()
10828 {
10829 Action::ArrayAggFilter
10830 } else {
10831 Action::None
10832 }
10833 }
10834 Expression::WithinGroup(wg) => {
10835 if matches!(source, DialectType::Snowflake)
10836 && matches!(
10837 target,
10838 DialectType::DuckDB | DialectType::Presto | DialectType::Trino
10839 )
10840 && matches!(wg.this, Expression::ArrayAgg(_))
10841 {
10842 Action::ArrayAggWithinGroupFilter
10843 } else if matches!(&wg.this, Expression::AggregateFunction(af) if af.name.eq_ignore_ascii_case("STRING_AGG"))
10844 || matches!(&wg.this, Expression::Function(f) if f.name.eq_ignore_ascii_case("STRING_AGG"))
10845 || matches!(&wg.this, Expression::StringAgg(_))
10846 {
10847 Action::StringAggConvert
10848 } else if matches!(
10849 target,
10850 DialectType::Presto
10851 | DialectType::Trino
10852 | DialectType::Athena
10853 | DialectType::Spark
10854 | DialectType::Databricks
10855 ) && (matches!(&wg.this, Expression::Function(f) if f.name.eq_ignore_ascii_case("PERCENTILE_CONT") || f.name.eq_ignore_ascii_case("PERCENTILE_DISC"))
10856 || matches!(&wg.this, Expression::AggregateFunction(af) if af.name.eq_ignore_ascii_case("PERCENTILE_CONT") || af.name.eq_ignore_ascii_case("PERCENTILE_DISC"))
10857 || matches!(&wg.this, Expression::PercentileCont(_)))
10858 {
10859 Action::PercentileContConvert
10860 } else {
10861 Action::None
10862 }
10863 }
10864 // For BigQuery: CAST(x AS TIMESTAMP) -> CAST(x AS DATETIME)
10865 // because BigQuery's TIMESTAMP is really TIMESTAMPTZ, and
10866 // DATETIME is the timezone-unaware type
10867 Expression::Cast(ref c) => {
10868 if c.format.is_some()
10869 && (matches!(source, DialectType::BigQuery)
10870 || matches!(source, DialectType::Teradata))
10871 {
10872 Action::BigQueryCastFormat
10873 } else if matches!(target, DialectType::BigQuery)
10874 && !matches!(source, DialectType::BigQuery)
10875 && matches!(
10876 c.to,
10877 DataType::Timestamp {
10878 timezone: false,
10879 ..
10880 }
10881 )
10882 {
10883 Action::CastTimestampToDatetime
10884 } else if matches!(target, DialectType::MySQL | DialectType::StarRocks)
10885 && !matches!(source, DialectType::MySQL | DialectType::StarRocks)
10886 && matches!(
10887 c.to,
10888 DataType::Timestamp {
10889 timezone: false,
10890 ..
10891 }
10892 )
10893 {
10894 // Generic/other -> MySQL/StarRocks: CAST(x AS TIMESTAMP) -> CAST(x AS DATETIME)
10895 // but MySQL-native CAST(x AS TIMESTAMP) stays as TIMESTAMP(x) via transform_cast
10896 Action::CastTimestampToDatetime
10897 } else if matches!(
10898 source,
10899 DialectType::Hive | DialectType::Spark | DialectType::Databricks
10900 ) && matches!(
10901 target,
10902 DialectType::Presto
10903 | DialectType::Trino
10904 | DialectType::Athena
10905 | DialectType::DuckDB
10906 | DialectType::Snowflake
10907 | DialectType::BigQuery
10908 | DialectType::Databricks
10909 | DialectType::TSQL
10910 ) {
10911 Action::HiveCastToTryCast
10912 } else if matches!(c.to, DataType::Timestamp { timezone: true, .. })
10913 && matches!(target, DialectType::MySQL | DialectType::StarRocks)
10914 {
10915 // CAST(x AS TIMESTAMPTZ) -> TIMESTAMP(x) function for MySQL/StarRocks
10916 Action::CastTimestamptzToFunc
10917 } else if matches!(c.to, DataType::Timestamp { timezone: true, .. })
10918 && matches!(
10919 target,
10920 DialectType::Hive
10921 | DialectType::Spark
10922 | DialectType::Databricks
10923 | DialectType::BigQuery
10924 )
10925 {
10926 // CAST(x AS TIMESTAMP WITH TIME ZONE) -> CAST(x AS TIMESTAMP) for Hive/Spark/BigQuery
10927 Action::CastTimestampStripTz
10928 } else if matches!(&c.to, DataType::Json)
10929 && matches!(source, DialectType::DuckDB)
10930 && matches!(target, DialectType::Snowflake)
10931 {
10932 Action::DuckDBCastJsonToVariant
10933 } else if matches!(&c.to, DataType::Json)
10934 && matches!(&c.this, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
10935 && matches!(
10936 target,
10937 DialectType::Presto
10938 | DialectType::Trino
10939 | DialectType::Athena
10940 | DialectType::Snowflake
10941 )
10942 {
10943 // CAST('x' AS JSON) -> JSON_PARSE('x') for Presto, PARSE_JSON for Snowflake
10944 // Only when the input is a string literal (JSON 'value' syntax)
10945 Action::JsonLiteralToJsonParse
10946 } else if matches!(&c.to, DataType::Json)
10947 && matches!(source, DialectType::DuckDB)
10948 && matches!(
10949 target,
10950 DialectType::Presto | DialectType::Trino | DialectType::Athena
10951 )
10952 {
10953 // DuckDB's CAST(x AS JSON) parses the string value into a JSON value.
10954 // Trino/Presto/Athena's CAST(x AS JSON) instead wraps the value as a
10955 // JSON string (no parsing) — different semantics. Use JSON_PARSE(x)
10956 // in the target to preserve DuckDB's parse semantics.
10957 Action::JsonLiteralToJsonParse
10958 } else if matches!(&c.to, DataType::Json | DataType::JsonB)
10959 && matches!(target, DialectType::Spark | DialectType::Databricks)
10960 {
10961 // CAST(x AS JSON) -> TO_JSON(x) for Spark
10962 Action::CastToJsonForSpark
10963 } else if (matches!(
10964 &c.to,
10965 DataType::Array { .. } | DataType::Map { .. } | DataType::Struct { .. }
10966 )) && matches!(
10967 target,
10968 DialectType::Spark | DialectType::Databricks
10969 ) && (matches!(&c.this, Expression::ParseJson(_))
10970 || matches!(
10971 &c.this,
10972 Expression::Function(f)
10973 if f.name.eq_ignore_ascii_case("JSON_EXTRACT")
10974 || f.name.eq_ignore_ascii_case("JSON_EXTRACT_SCALAR")
10975 || f.name.eq_ignore_ascii_case("GET_JSON_OBJECT")
10976 ))
10977 {
10978 // CAST(JSON_PARSE(...) AS ARRAY/MAP) or CAST(JSON_EXTRACT/GET_JSON_OBJECT(...) AS ARRAY/MAP)
10979 // -> FROM_JSON(..., type_string) for Spark
10980 Action::CastJsonToFromJson
10981 } else if matches!(target, DialectType::Spark | DialectType::Databricks)
10982 && matches!(
10983 c.to,
10984 DataType::Timestamp {
10985 timezone: false,
10986 ..
10987 }
10988 )
10989 && matches!(source, DialectType::DuckDB)
10990 {
10991 Action::StrftimeCastTimestamp
10992 } else if matches!(source, DialectType::DuckDB)
10993 && matches!(
10994 c.to,
10995 DataType::Decimal {
10996 precision: None,
10997 ..
10998 }
10999 )
11000 {
11001 Action::DecimalDefaultPrecision
11002 } else if matches!(source, DialectType::MySQL | DialectType::SingleStore)
11003 && matches!(c.to, DataType::Char { length: None })
11004 && !matches!(target, DialectType::MySQL | DialectType::SingleStore)
11005 {
11006 // MySQL CAST(x AS CHAR) was originally TEXT - convert to target text type
11007 Action::MysqlCastCharToText
11008 } else if matches!(
11009 source,
11010 DialectType::Spark | DialectType::Databricks | DialectType::Hive
11011 ) && matches!(
11012 target,
11013 DialectType::Spark | DialectType::Databricks | DialectType::Hive
11014 ) && Self::has_varchar_char_type(&c.to)
11015 {
11016 // Spark parses VARCHAR(n)/CHAR(n) as TEXT, so normalize back to STRING
11017 Action::SparkCastVarcharToString
11018 } else {
11019 Action::None
11020 }
11021 }
11022 Expression::SafeCast(ref c) => {
11023 if c.format.is_some()
11024 && matches!(source, DialectType::BigQuery)
11025 && !matches!(target, DialectType::BigQuery)
11026 {
11027 Action::BigQueryCastFormat
11028 } else {
11029 Action::None
11030 }
11031 }
11032 Expression::TryCast(ref c) => {
11033 if matches!(&c.to, DataType::Json)
11034 && matches!(source, DialectType::DuckDB)
11035 && matches!(
11036 target,
11037 DialectType::Presto | DialectType::Trino | DialectType::Athena
11038 )
11039 {
11040 // DuckDB's TRY_CAST(x AS JSON) tries to parse x as JSON, returning
11041 // NULL on parse failure. Trino/Presto/Athena's TRY_CAST(x AS JSON)
11042 // wraps the value as a JSON string (no parse). Emit TRY(JSON_PARSE(x))
11043 // to preserve DuckDB's parse-or-null semantics.
11044 Action::DuckDBTryCastJsonToTryJsonParse
11045 } else {
11046 Action::None
11047 }
11048 }
11049 Expression::JSONArray(ref ja)
11050 if matches!(target, DialectType::Snowflake)
11051 && ja.null_handling.is_none()
11052 && ja.return_type.is_none()
11053 && ja.strict.is_none() =>
11054 {
11055 Action::GenericFunctionNormalize
11056 }
11057 Expression::JsonArray(_) if matches!(target, DialectType::Snowflake) => {
11058 Action::GenericFunctionNormalize
11059 }
11060 // For DuckDB: DATE_TRUNC should preserve the input type
11061 Expression::DateTrunc(_) | Expression::TimestampTrunc(_) => {
11062 if matches!(source, DialectType::Snowflake)
11063 && matches!(target, DialectType::DuckDB)
11064 {
11065 Action::DateTruncWrapCast
11066 } else {
11067 Action::None
11068 }
11069 }
11070 // For DuckDB: SET a = 1 -> SET VARIABLE a = 1
11071 Expression::SetStatement(s) => {
11072 if matches!(target, DialectType::DuckDB)
11073 && !matches!(source, DialectType::TSQL | DialectType::Fabric)
11074 && s.items.iter().any(|item| item.kind.is_none())
11075 {
11076 Action::SetToVariable
11077 } else {
11078 Action::None
11079 }
11080 }
11081 // Cross-dialect NULL ordering normalization.
11082 // When nulls_first is not specified, fill in the source dialect's implied
11083 // default so the target generator can correctly add/strip NULLS FIRST/LAST.
11084 Expression::Ordered(o) => {
11085 // MySQL doesn't support NULLS FIRST/LAST - strip or rewrite
11086 if matches!(target, DialectType::MySQL) && o.nulls_first.is_some() {
11087 Action::MysqlNullsOrdering
11088 } else {
11089 // Skip targets that don't support NULLS FIRST/LAST syntax unless
11090 // the generator can preserve semantics with a CASE sort key.
11091 let target_rewrites_nulls =
11092 matches!(target, DialectType::TSQL | DialectType::Fabric);
11093 let target_supports_nulls = !matches!(
11094 target,
11095 DialectType::MySQL
11096 | DialectType::TSQL
11097 | DialectType::Fabric
11098 | DialectType::StarRocks
11099 | DialectType::Doris
11100 );
11101 if o.nulls_first.is_none()
11102 && source != target
11103 && (target_supports_nulls || target_rewrites_nulls)
11104 {
11105 Action::NullsOrdering
11106 } else {
11107 Action::None
11108 }
11109 }
11110 }
11111 // BigQuery data types: convert INT64, BYTES, NUMERIC etc. to standard types
11112 Expression::DataType(dt) => {
11113 if matches!(source, DialectType::BigQuery)
11114 && !matches!(target, DialectType::BigQuery)
11115 {
11116 match dt {
11117 DataType::Custom { ref name }
11118 if name.eq_ignore_ascii_case("INT64")
11119 || name.eq_ignore_ascii_case("FLOAT64")
11120 || name.eq_ignore_ascii_case("BOOL")
11121 || name.eq_ignore_ascii_case("BYTES")
11122 || name.eq_ignore_ascii_case("NUMERIC")
11123 || name.eq_ignore_ascii_case("STRING")
11124 || name.eq_ignore_ascii_case("DATETIME") =>
11125 {
11126 Action::BigQueryCastType
11127 }
11128 _ => Action::None,
11129 }
11130 } else if matches!(source, DialectType::TSQL) {
11131 // For TSQL source -> any target (including TSQL itself for REAL)
11132 match dt {
11133 // REAL -> FLOAT even for TSQL->TSQL
11134 DataType::Custom { ref name }
11135 if name.eq_ignore_ascii_case("REAL") =>
11136 {
11137 Action::TSQLTypeNormalize
11138 }
11139 DataType::Float {
11140 real_spelling: true,
11141 ..
11142 } => Action::TSQLTypeNormalize,
11143 // Other TSQL type normalizations only for non-TSQL targets
11144 DataType::Custom { ref name }
11145 if !matches!(target, DialectType::TSQL)
11146 && (name.eq_ignore_ascii_case("MONEY")
11147 || name.eq_ignore_ascii_case("SMALLMONEY")
11148 || name.eq_ignore_ascii_case("DATETIME2")
11149 || name.eq_ignore_ascii_case("IMAGE")
11150 || name.eq_ignore_ascii_case("BIT")
11151 || name.eq_ignore_ascii_case("ROWVERSION")
11152 || name.eq_ignore_ascii_case("UNIQUEIDENTIFIER")
11153 || name.eq_ignore_ascii_case("DATETIMEOFFSET")
11154 || (name.len() >= 7 && name[..7].eq_ignore_ascii_case("NUMERIC"))
11155 || (name.len() >= 10 && name[..10].eq_ignore_ascii_case("DATETIME2("))
11156 || (name.len() >= 5 && name[..5].eq_ignore_ascii_case("TIME("))) =>
11157 {
11158 Action::TSQLTypeNormalize
11159 }
11160 DataType::Float {
11161 precision: Some(_), ..
11162 } if !matches!(target, DialectType::TSQL) => {
11163 Action::TSQLTypeNormalize
11164 }
11165 DataType::TinyInt { .. }
11166 if !matches!(target, DialectType::TSQL) =>
11167 {
11168 Action::TSQLTypeNormalize
11169 }
11170 // INTEGER -> INT for Databricks/Spark targets
11171 DataType::Int {
11172 integer_spelling: true,
11173 ..
11174 } if matches!(
11175 target,
11176 DialectType::Databricks | DialectType::Spark
11177 ) =>
11178 {
11179 Action::TSQLTypeNormalize
11180 }
11181 _ => Action::None,
11182 }
11183 } else if (matches!(source, DialectType::Oracle)
11184 || matches!(source, DialectType::Generic))
11185 && !matches!(target, DialectType::Oracle)
11186 {
11187 match dt {
11188 DataType::Custom { ref name }
11189 if (name.len() >= 9 && name[..9].eq_ignore_ascii_case("VARCHAR2("))
11190 || (name.len() >= 10 && name[..10].eq_ignore_ascii_case("NVARCHAR2("))
11191 || name.eq_ignore_ascii_case("VARCHAR2")
11192 || name.eq_ignore_ascii_case("NVARCHAR2") =>
11193 {
11194 Action::OracleVarchar2ToVarchar
11195 }
11196 _ => Action::None,
11197 }
11198 } else if matches!(target, DialectType::Snowflake)
11199 && !matches!(source, DialectType::Snowflake)
11200 {
11201 // When target is Snowflake but source is NOT Snowflake,
11202 // protect FLOAT from being converted to DOUBLE by Snowflake's transform.
11203 // Snowflake treats FLOAT=DOUBLE internally, but non-Snowflake sources
11204 // should keep their FLOAT spelling.
11205 match dt {
11206 DataType::Float { .. } => Action::SnowflakeFloatProtect,
11207 _ => Action::None,
11208 }
11209 } else {
11210 Action::None
11211 }
11212 }
11213 // LOWER patterns from BigQuery TO_HEX conversions:
11214 // - LOWER(LOWER(HEX(x))) from non-BQ targets: flatten
11215 // - LOWER(Function("TO_HEX")) for BQ->BQ: strip LOWER
11216 Expression::Lower(uf) => {
11217 if matches!(source, DialectType::BigQuery) {
11218 match &uf.this {
11219 Expression::Lower(_) => Action::BigQueryToHexLower,
11220 Expression::Function(f)
11221 if f.name == "TO_HEX"
11222 && matches!(target, DialectType::BigQuery) =>
11223 {
11224 // BQ->BQ: LOWER(TO_HEX(x)) -> TO_HEX(x)
11225 Action::BigQueryToHexLower
11226 }
11227 _ => Action::None,
11228 }
11229 } else {
11230 Action::None
11231 }
11232 }
11233 // UPPER patterns from BigQuery TO_HEX conversions:
11234 // - UPPER(LOWER(HEX(x))) from non-BQ targets: extract inner
11235 // - UPPER(Function("TO_HEX")) for BQ->BQ: keep as UPPER(TO_HEX(x))
11236 Expression::Upper(uf) => {
11237 if matches!(source, DialectType::BigQuery) {
11238 match &uf.this {
11239 Expression::Lower(_) => Action::BigQueryToHexUpper,
11240 _ => Action::None,
11241 }
11242 } else {
11243 Action::None
11244 }
11245 }
11246 // BigQuery LAST_DAY(date, unit) -> strip unit for non-BigQuery targets
11247 // Snowflake supports LAST_DAY with unit, so keep it there
11248 Expression::LastDay(ld) => {
11249 if matches!(source, DialectType::BigQuery)
11250 && !matches!(target, DialectType::BigQuery | DialectType::Snowflake)
11251 && ld.unit.is_some()
11252 {
11253 Action::BigQueryLastDayStripUnit
11254 } else {
11255 Action::None
11256 }
11257 }
11258 // BigQuery SafeDivide expressions (already parsed as SafeDivide)
11259 Expression::SafeDivide(_) => {
11260 if matches!(source, DialectType::BigQuery)
11261 && !matches!(target, DialectType::BigQuery)
11262 {
11263 Action::BigQuerySafeDivide
11264 } else {
11265 Action::None
11266 }
11267 }
11268 // BigQuery ANY_VALUE(x HAVING MAX/MIN y) -> ARG_MAX_NULL/ARG_MIN_NULL for DuckDB
11269 // ANY_VALUE(x) -> ANY_VALUE(x) IGNORE NULLS for Spark
11270 Expression::AnyValue(ref agg) => {
11271 if matches!(source, DialectType::BigQuery)
11272 && matches!(target, DialectType::DuckDB)
11273 && agg.having_max.is_some()
11274 {
11275 Action::BigQueryAnyValueHaving
11276 } else if matches!(target, DialectType::Spark | DialectType::Databricks)
11277 && !matches!(source, DialectType::Spark | DialectType::Databricks)
11278 && agg.ignore_nulls.is_none()
11279 {
11280 Action::AnyValueIgnoreNulls
11281 } else {
11282 Action::None
11283 }
11284 }
11285 Expression::Any(ref q) => {
11286 if matches!(source, DialectType::PostgreSQL)
11287 && matches!(
11288 target,
11289 DialectType::Spark | DialectType::Databricks | DialectType::Hive
11290 )
11291 && q.op.is_some()
11292 && !matches!(
11293 q.subquery,
11294 Expression::Select(_) | Expression::Subquery(_)
11295 )
11296 {
11297 Action::AnyToExists
11298 } else {
11299 Action::None
11300 }
11301 }
11302 // BigQuery APPROX_QUANTILES(x, n) -> APPROX_QUANTILE(x, [quantiles]) for DuckDB
11303 // Snowflake RLIKE does full-string match; DuckDB REGEXP_FULL_MATCH also does full-string match
11304 Expression::RegexpLike(_)
11305 if matches!(source, DialectType::Snowflake)
11306 && matches!(target, DialectType::DuckDB) =>
11307 {
11308 Action::RlikeSnowflakeToDuckDB
11309 }
11310 // PostgreSQL regex predicates have no native T-SQL/Fabric equivalent.
11311 // Default mode emits a best-effort PATINDEX predicate; strict mode rejects
11312 // before this rewrite runs.
11313 Expression::RegexpLike(_) | Expression::RegexpILike(_)
11314 if matches!(source, DialectType::PostgreSQL | DialectType::CockroachDB)
11315 && matches!(target, DialectType::TSQL | DialectType::Fabric) =>
11316 {
11317 Action::RegexpLikeToTsqlPatindex
11318 }
11319 Expression::SimilarTo(s)
11320 if matches!(source, DialectType::PostgreSQL | DialectType::CockroachDB)
11321 && matches!(target, DialectType::TSQL | DialectType::Fabric)
11322 && Self::similar_to_can_lower_to_tsql_like(s) =>
11323 {
11324 Action::SimilarToToTsqlLike
11325 }
11326 // RegexpLike from non-DuckDB/non-Snowflake sources -> REGEXP_MATCHES for DuckDB target
11327 Expression::RegexpLike(_)
11328 if !matches!(source, DialectType::DuckDB)
11329 && matches!(target, DialectType::DuckDB) =>
11330 {
11331 Action::RegexpLikeToDuckDB
11332 }
11333 // RegexpLike -> Exasol: anchor pattern with .*...*
11334 Expression::RegexpLike(_)
11335 if matches!(target, DialectType::Exasol) =>
11336 {
11337 Action::RegexpLikeExasolAnchor
11338 }
11339 // Safe-division source -> non-safe target: NULLIF wrapping and/or CAST
11340 // Safe-division dialects: MySQL, DuckDB, SingleStore, TiDB, ClickHouse, Doris
11341 Expression::Div(ref op)
11342 if matches!(
11343 source,
11344 DialectType::MySQL
11345 | DialectType::DuckDB
11346 | DialectType::SingleStore
11347 | DialectType::TiDB
11348 | DialectType::ClickHouse
11349 | DialectType::Doris
11350 ) && matches!(
11351 target,
11352 DialectType::PostgreSQL
11353 | DialectType::Redshift
11354 | DialectType::Drill
11355 | DialectType::Trino
11356 | DialectType::Presto
11357 | DialectType::Athena
11358 | DialectType::TSQL
11359 | DialectType::Teradata
11360 | DialectType::SQLite
11361 | DialectType::BigQuery
11362 | DialectType::Snowflake
11363 | DialectType::Databricks
11364 | DialectType::Oracle
11365 | DialectType::Materialize
11366 | DialectType::RisingWave
11367 ) =>
11368 {
11369 // Only wrap if RHS is not already NULLIF
11370 if !matches!(&op.right, Expression::Function(f) if f.name.eq_ignore_ascii_case("NULLIF"))
11371 {
11372 Action::MySQLSafeDivide
11373 } else {
11374 Action::None
11375 }
11376 }
11377 // ALTER TABLE ... RENAME TO <schema>.<table> -> strip schema for most targets
11378 // For TSQL/Fabric, convert to sp_rename instead
11379 Expression::AlterTable(ref at) if !at.actions.is_empty() => {
11380 if let Some(crate::expressions::AlterTableAction::RenameTable(
11381 ref new_tbl,
11382 )) = at.actions.first()
11383 {
11384 if matches!(target, DialectType::TSQL | DialectType::Fabric) {
11385 // TSQL: ALTER TABLE RENAME -> EXEC sp_rename
11386 Action::AlterTableToSpRename
11387 } else if new_tbl.schema.is_some()
11388 && matches!(
11389 target,
11390 DialectType::BigQuery
11391 | DialectType::Doris
11392 | DialectType::StarRocks
11393 | DialectType::DuckDB
11394 | DialectType::PostgreSQL
11395 | DialectType::Redshift
11396 )
11397 {
11398 Action::AlterTableRenameStripSchema
11399 } else {
11400 Action::None
11401 }
11402 } else {
11403 Action::None
11404 }
11405 }
11406 // EPOCH(x) expression -> target-specific epoch conversion
11407 Expression::Epoch(_) if !matches!(target, DialectType::DuckDB) => {
11408 Action::EpochConvert
11409 }
11410 // EPOCH_MS(x) expression -> target-specific epoch ms conversion
11411 Expression::EpochMs(_) if !matches!(target, DialectType::DuckDB) => {
11412 Action::EpochMsConvert
11413 }
11414 // STRING_AGG -> GROUP_CONCAT for MySQL/SQLite
11415 Expression::StringAgg(_) => {
11416 if matches!(
11417 target,
11418 DialectType::MySQL
11419 | DialectType::SingleStore
11420 | DialectType::Doris
11421 | DialectType::StarRocks
11422 | DialectType::SQLite
11423 ) {
11424 Action::StringAggConvert
11425 } else if matches!(target, DialectType::Spark | DialectType::Databricks) {
11426 Action::StringAggConvert
11427 } else {
11428 Action::None
11429 }
11430 }
11431 Expression::CombinedParameterizedAgg(_) => Action::GenericFunctionNormalize,
11432 // GROUP_CONCAT -> STRING_AGG for PostgreSQL/Presto/etc.
11433 // Also handles GROUP_CONCAT normalization for MySQL/SQLite targets
11434 Expression::GroupConcat(_) => Action::GroupConcatConvert,
11435 // CARDINALITY/ARRAY_LENGTH/ARRAY_SIZE -> target-specific array length
11436 // DuckDB CARDINALITY -> keep as CARDINALITY for DuckDB target (used for maps)
11437 Expression::Cardinality(_)
11438 if matches!(source, DialectType::DuckDB)
11439 && matches!(target, DialectType::DuckDB) =>
11440 {
11441 Action::None
11442 }
11443 Expression::Cardinality(_) | Expression::ArrayLength(_) => {
11444 Action::ArrayLengthConvert
11445 }
11446 Expression::ArraySize(_) => {
11447 if matches!(target, DialectType::Drill) {
11448 Action::ArraySizeDrill
11449 } else {
11450 Action::ArrayLengthConvert
11451 }
11452 }
11453 // ARRAY_REMOVE(arr, target) -> LIST_FILTER/arrayFilter/ARRAY subquery
11454 Expression::ArrayRemove(_) => match target {
11455 DialectType::DuckDB | DialectType::ClickHouse | DialectType::BigQuery => {
11456 Action::ArrayRemoveConvert
11457 }
11458 _ => Action::None,
11459 },
11460 // ARRAY_REVERSE(x) -> arrayReverse for ClickHouse
11461 Expression::ArrayReverse(_) => match target {
11462 DialectType::ClickHouse => Action::ArrayReverseConvert,
11463 _ => Action::None,
11464 },
11465 // JSON_KEYS(x) -> JSON_OBJECT_KEYS/OBJECT_KEYS for Spark/Databricks/Snowflake
11466 Expression::JsonKeys(_) => match target {
11467 DialectType::Spark | DialectType::Databricks | DialectType::Snowflake => {
11468 Action::JsonKeysConvert
11469 }
11470 _ => Action::None,
11471 },
11472 // PARSE_JSON(x) -> strip for SQLite/Doris/MySQL/StarRocks
11473 Expression::ParseJson(_) => match target {
11474 DialectType::SQLite
11475 | DialectType::Doris
11476 | DialectType::MySQL
11477 | DialectType::StarRocks => Action::ParseJsonStrip,
11478 _ => Action::None,
11479 },
11480 // WeekOfYear -> WEEKISO for Snowflake (cross-dialect only)
11481 Expression::WeekOfYear(_)
11482 if matches!(target, DialectType::Snowflake)
11483 && !matches!(source, DialectType::Snowflake) =>
11484 {
11485 Action::WeekOfYearToWeekIso
11486 }
11487 // NVL: clear original_name so generator uses dialect-specific function names
11488 Expression::Nvl(f) if f.original_name.is_some() => Action::NvlClearOriginal,
11489 // XOR: expand for dialects that don't support the XOR keyword
11490 Expression::Xor(_) => {
11491 let target_supports_xor = matches!(
11492 target,
11493 DialectType::MySQL
11494 | DialectType::SingleStore
11495 | DialectType::Doris
11496 | DialectType::StarRocks
11497 );
11498 if !target_supports_xor {
11499 Action::XorExpand
11500 } else {
11501 Action::None
11502 }
11503 }
11504 // TSQL #table -> temp table normalization (CREATE TABLE)
11505 Expression::CreateTable(ct)
11506 if matches!(source, DialectType::TSQL | DialectType::Fabric)
11507 && !matches!(target, DialectType::TSQL | DialectType::Fabric)
11508 && ct.name.name.name.starts_with('#') =>
11509 {
11510 Action::TempTableHash
11511 }
11512 // TSQL #table -> strip # from table references in SELECT/etc.
11513 Expression::Table(tr)
11514 if matches!(source, DialectType::TSQL | DialectType::Fabric)
11515 && !matches!(target, DialectType::TSQL | DialectType::Fabric)
11516 && tr.name.name.starts_with('#') =>
11517 {
11518 Action::TempTableHash
11519 }
11520 // TSQL #table -> strip # from DROP TABLE names
11521 Expression::DropTable(ref dt)
11522 if matches!(source, DialectType::TSQL | DialectType::Fabric)
11523 && !matches!(target, DialectType::TSQL | DialectType::Fabric)
11524 && dt.names.iter().any(|n| n.name.name.starts_with('#')) =>
11525 {
11526 Action::TempTableHash
11527 }
11528 // JSON_EXTRACT / PostgreSQL `->` -> T-SQL JSON functions
11529 Expression::JsonExtract(_)
11530 if matches!(target, DialectType::TSQL | DialectType::Fabric) =>
11531 {
11532 Action::JsonExtractToTsql
11533 }
11534 // JSON_EXTRACT_SCALAR / PostgreSQL `->>`/`#>>` -> T-SQL JSON functions
11535 Expression::JsonExtractScalar(_)
11536 if matches!(target, DialectType::TSQL | DialectType::Fabric) =>
11537 {
11538 Action::JsonExtractToTsql
11539 }
11540 // PostgreSQL `#>` -> T-SQL/Fabric JSON_QUERY
11541 Expression::JsonExtractPath(_)
11542 if matches!(target, DialectType::TSQL | DialectType::Fabric) =>
11543 {
11544 Action::JsonExtractToTsql
11545 }
11546 // JSON_EXTRACT -> JSONExtractString for ClickHouse
11547 Expression::JsonExtract(_) if matches!(target, DialectType::ClickHouse) => {
11548 Action::JsonExtractToClickHouse
11549 }
11550 // JSON_EXTRACT_SCALAR -> JSONExtractString for ClickHouse
11551 Expression::JsonExtractScalar(_)
11552 if matches!(target, DialectType::ClickHouse) =>
11553 {
11554 Action::JsonExtractToClickHouse
11555 }
11556 // JSON_EXTRACT -> arrow syntax for SQLite/DuckDB
11557 Expression::JsonExtract(ref f)
11558 if !f.arrow_syntax
11559 && matches!(target, DialectType::SQLite | DialectType::DuckDB) =>
11560 {
11561 Action::JsonExtractToArrow
11562 }
11563 // JSON_EXTRACT with JSONPath -> JSON_EXTRACT_PATH for PostgreSQL (non-PG sources only)
11564 Expression::JsonExtract(ref f)
11565 if matches!(target, DialectType::PostgreSQL | DialectType::Redshift)
11566 && !matches!(
11567 source,
11568 DialectType::PostgreSQL
11569 | DialectType::Redshift
11570 | DialectType::Materialize
11571 )
11572 && matches!(&f.path, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(s) if s.starts_with('$'))) =>
11573 {
11574 Action::JsonExtractToGetJsonObject
11575 }
11576 // JSON_EXTRACT -> GET_JSON_OBJECT for Hive/Spark
11577 Expression::JsonExtract(_)
11578 if matches!(
11579 target,
11580 DialectType::Hive | DialectType::Spark | DialectType::Databricks
11581 ) =>
11582 {
11583 Action::JsonExtractToGetJsonObject
11584 }
11585 // JSON_EXTRACT_SCALAR -> target-specific for PostgreSQL, Snowflake, SQLite
11586 // Skip if already in arrow/hash_arrow syntax (same-dialect identity case)
11587 Expression::JsonExtractScalar(ref f)
11588 if !f.arrow_syntax
11589 && !f.hash_arrow_syntax
11590 && matches!(
11591 target,
11592 DialectType::PostgreSQL
11593 | DialectType::Redshift
11594 | DialectType::Snowflake
11595 | DialectType::SQLite
11596 | DialectType::DuckDB
11597 ) =>
11598 {
11599 Action::JsonExtractScalarConvert
11600 }
11601 // JSON_EXTRACT_SCALAR -> GET_JSON_OBJECT for Hive/Spark
11602 Expression::JsonExtractScalar(_)
11603 if matches!(
11604 target,
11605 DialectType::Hive | DialectType::Spark | DialectType::Databricks
11606 ) =>
11607 {
11608 Action::JsonExtractScalarToGetJsonObject
11609 }
11610 // JSON_EXTRACT path normalization for BigQuery, MySQL (bracket/wildcard handling)
11611 Expression::JsonExtract(ref f)
11612 if !f.arrow_syntax
11613 && matches!(target, DialectType::BigQuery | DialectType::MySQL) =>
11614 {
11615 Action::JsonPathNormalize
11616 }
11617 // JsonQuery (parsed JSON_QUERY) -> target-specific
11618 Expression::JsonQuery(_) => Action::JsonQueryValueConvert,
11619 // JsonValue (parsed JSON_VALUE) -> target-specific
11620 Expression::JsonValue(_) => Action::JsonQueryValueConvert,
11621 // AT TIME ZONE -> AT_TIMEZONE for Presto, FROM_UTC_TIMESTAMP for Spark,
11622 // TIMESTAMP(DATETIME(...)) for BigQuery, CONVERT_TIMEZONE for Snowflake
11623 Expression::AtTimeZone(_)
11624 if matches!(
11625 target,
11626 DialectType::Presto
11627 | DialectType::Trino
11628 | DialectType::Athena
11629 | DialectType::Spark
11630 | DialectType::Databricks
11631 | DialectType::BigQuery
11632 | DialectType::Snowflake
11633 ) =>
11634 {
11635 Action::AtTimeZoneConvert
11636 }
11637 // DAY_OF_WEEK -> dialect-specific
11638 Expression::DayOfWeek(_)
11639 if matches!(
11640 target,
11641 DialectType::DuckDB | DialectType::Spark | DialectType::Databricks
11642 ) =>
11643 {
11644 Action::DayOfWeekConvert
11645 }
11646 // CURRENT_USER -> CURRENT_USER() for Snowflake
11647 Expression::CurrentUser(_) if matches!(target, DialectType::Snowflake) => {
11648 Action::CurrentUserParens
11649 }
11650 // ELEMENT_AT(arr, idx) -> arr[idx] for PostgreSQL, arr[SAFE_ORDINAL(idx)] for BigQuery
11651 Expression::ElementAt(_)
11652 if matches!(target, DialectType::PostgreSQL | DialectType::BigQuery) =>
11653 {
11654 Action::ElementAtConvert
11655 }
11656 // ARRAY[...] (ArrayFunc bracket_notation=false) -> convert for target dialect
11657 Expression::ArrayFunc(ref arr)
11658 if !arr.bracket_notation
11659 && matches!(
11660 target,
11661 DialectType::Spark
11662 | DialectType::Databricks
11663 | DialectType::Hive
11664 | DialectType::BigQuery
11665 | DialectType::DuckDB
11666 | DialectType::Snowflake
11667 | DialectType::Presto
11668 | DialectType::Trino
11669 | DialectType::Athena
11670 | DialectType::ClickHouse
11671 | DialectType::StarRocks
11672 ) =>
11673 {
11674 Action::ArraySyntaxConvert
11675 }
11676 // VARIANCE expression -> varSamp for ClickHouse
11677 Expression::Variance(_) if matches!(target, DialectType::ClickHouse) => {
11678 Action::VarianceToClickHouse
11679 }
11680 // STDDEV expression -> stddevSamp for ClickHouse
11681 Expression::Stddev(_) if matches!(target, DialectType::ClickHouse) => {
11682 Action::StddevToClickHouse
11683 }
11684 // ApproxQuantile -> APPROX_PERCENTILE for Snowflake
11685 Expression::ApproxQuantile(_) if matches!(target, DialectType::Snowflake) => {
11686 Action::ApproxQuantileConvert
11687 }
11688 // MonthsBetween -> target-specific
11689 Expression::MonthsBetween(_)
11690 if !matches!(
11691 target,
11692 DialectType::Spark | DialectType::Databricks | DialectType::Hive
11693 ) =>
11694 {
11695 Action::MonthsBetweenConvert
11696 }
11697 // AddMonths -> target-specific DATEADD/DATE_ADD
11698 Expression::AddMonths(_) => Action::AddMonthsConvert,
11699 // MapFromArrays -> target-specific (MAP, OBJECT_CONSTRUCT, MAP_FROM_ARRAYS)
11700 Expression::MapFromArrays(_)
11701 if !matches!(target, DialectType::Spark | DialectType::Databricks) =>
11702 {
11703 Action::MapFromArraysConvert
11704 }
11705 // CURRENT_USER -> CURRENT_USER() for Spark
11706 Expression::CurrentUser(_)
11707 if matches!(target, DialectType::Spark | DialectType::Databricks) =>
11708 {
11709 Action::CurrentUserSparkParens
11710 }
11711 // MONTH/YEAR/DAY('string') from Spark -> cast string to DATE for DuckDB/Presto
11712 Expression::Month(ref f) | Expression::Year(ref f) | Expression::Day(ref f)
11713 if matches!(
11714 source,
11715 DialectType::Spark | DialectType::Databricks | DialectType::Hive
11716 ) && matches!(&f.this, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
11717 && matches!(
11718 target,
11719 DialectType::DuckDB
11720 | DialectType::Presto
11721 | DialectType::Trino
11722 | DialectType::Athena
11723 | DialectType::PostgreSQL
11724 | DialectType::Redshift
11725 ) =>
11726 {
11727 Action::SparkDateFuncCast
11728 }
11729 // $parameter -> @parameter for BigQuery
11730 Expression::Parameter(ref p)
11731 if matches!(target, DialectType::BigQuery)
11732 && matches!(source, DialectType::DuckDB)
11733 && (p.style == crate::expressions::ParameterStyle::Dollar
11734 || p.style == crate::expressions::ParameterStyle::DoubleDollar) =>
11735 {
11736 Action::DollarParamConvert
11737 }
11738 // EscapeString literal: normalize literal newlines to \n
11739 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::EscapeString(ref s) if s.contains('\n') || s.contains('\r') || s.contains('\t'))
11740 =>
11741 {
11742 Action::EscapeStringNormalize
11743 }
11744 // straight_join: keep lowercase for DuckDB, quote for MySQL
11745 Expression::Column(ref col)
11746 if col.name.name == "STRAIGHT_JOIN"
11747 && col.table.is_none()
11748 && matches!(source, DialectType::DuckDB)
11749 && matches!(target, DialectType::DuckDB | DialectType::MySQL) =>
11750 {
11751 Action::StraightJoinCase
11752 }
11753 // DATE and TIMESTAMP literal type conversions are now handled in the generator directly
11754 // Snowflake INTERVAL format: INTERVAL '2' HOUR -> INTERVAL '2 HOUR'
11755 Expression::Interval(ref iv)
11756 if matches!(
11757 target,
11758 DialectType::Snowflake
11759 | DialectType::PostgreSQL
11760 | DialectType::Redshift
11761 ) && iv.unit.is_some()
11762 && iv.this.as_ref().map_or(false, |t| matches!(t, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))) =>
11763 {
11764 Action::SnowflakeIntervalFormat
11765 }
11766 // TABLESAMPLE -> TABLESAMPLE RESERVOIR for DuckDB target
11767 Expression::TableSample(ref ts) if matches!(target, DialectType::DuckDB) => {
11768 if let Some(ref sample) = ts.sample {
11769 if !sample.explicit_method {
11770 Action::TablesampleReservoir
11771 } else {
11772 Action::None
11773 }
11774 } else {
11775 Action::None
11776 }
11777 }
11778 // TABLESAMPLE from non-Snowflake source to Snowflake: strip method and PERCENT
11779 // Handles both Expression::TableSample wrapper and Expression::Table with table_sample
11780 Expression::TableSample(ref ts)
11781 if matches!(target, DialectType::Snowflake)
11782 && !matches!(source, DialectType::Snowflake)
11783 && ts.sample.is_some() =>
11784 {
11785 if let Some(ref sample) = ts.sample {
11786 if !sample.explicit_method {
11787 Action::TablesampleSnowflakeStrip
11788 } else {
11789 Action::None
11790 }
11791 } else {
11792 Action::None
11793 }
11794 }
11795 Expression::Table(ref t)
11796 if matches!(target, DialectType::Snowflake)
11797 && !matches!(source, DialectType::Snowflake)
11798 && t.table_sample.is_some() =>
11799 {
11800 if let Some(ref sample) = t.table_sample {
11801 if !sample.explicit_method {
11802 Action::TablesampleSnowflakeStrip
11803 } else {
11804 Action::None
11805 }
11806 } else {
11807 Action::None
11808 }
11809 }
11810 // ALTER TABLE RENAME -> EXEC sp_rename for TSQL
11811 Expression::AlterTable(ref at)
11812 if matches!(target, DialectType::TSQL | DialectType::Fabric)
11813 && !at.actions.is_empty()
11814 && matches!(
11815 at.actions.first(),
11816 Some(crate::expressions::AlterTableAction::RenameTable(_))
11817 ) =>
11818 {
11819 Action::AlterTableToSpRename
11820 }
11821 // Subscript index: 1-based to 0-based for BigQuery/Hive/Spark
11822 Expression::Subscript(ref sub)
11823 if matches!(
11824 target,
11825 DialectType::BigQuery
11826 | DialectType::Hive
11827 | DialectType::Spark
11828 | DialectType::Databricks
11829 ) && matches!(
11830 source,
11831 DialectType::DuckDB
11832 | DialectType::PostgreSQL
11833 | DialectType::Presto
11834 | DialectType::Trino
11835 | DialectType::Redshift
11836 | DialectType::ClickHouse
11837 ) && matches!(&sub.index, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(ref n) if n.parse::<i64>().unwrap_or(0) > 0)) =>
11838 {
11839 Action::ArrayIndexConvert
11840 }
11841 // ANY_VALUE IGNORE NULLS detection moved to the AnyValue arm above
11842 // MysqlNullsOrdering for Ordered is now handled in the Ordered arm above
11843 // RESPECT NULLS handling for SQLite (strip it, add NULLS LAST to ORDER BY)
11844 // and for MySQL (rewrite ORDER BY with CASE WHEN for null ordering)
11845 Expression::WindowFunction(ref wf) => {
11846 // BigQuery doesn't support NULLS FIRST/LAST in window function ORDER BY
11847 // EXCEPT for ROW_NUMBER which keeps NULLS LAST
11848 let is_row_number = matches!(wf.this, Expression::RowNumber(_));
11849 if matches!(target, DialectType::BigQuery)
11850 && !is_row_number
11851 && !wf.over.order_by.is_empty()
11852 && wf.over.order_by.iter().any(|o| o.nulls_first.is_some())
11853 {
11854 Action::BigQueryNullsOrdering
11855 // DuckDB -> MySQL: Add CASE WHEN for NULLS LAST simulation in window ORDER BY
11856 // But NOT when frame is RANGE/GROUPS, since adding CASE WHEN would break value-based frames
11857 } else {
11858 let source_nulls_last = matches!(source, DialectType::DuckDB);
11859 let has_range_frame = wf.over.frame.as_ref().map_or(false, |f| {
11860 matches!(
11861 f.kind,
11862 crate::expressions::WindowFrameKind::Range
11863 | crate::expressions::WindowFrameKind::Groups
11864 )
11865 });
11866 if source_nulls_last
11867 && matches!(target, DialectType::MySQL)
11868 && !wf.over.order_by.is_empty()
11869 && wf.over.order_by.iter().any(|o| !o.desc)
11870 && !has_range_frame
11871 {
11872 Action::MysqlNullsLastRewrite
11873 } else {
11874 // Check for Snowflake window frame handling for FIRST_VALUE/LAST_VALUE/NTH_VALUE
11875 let is_ranking_window_func = matches!(
11876 &wf.this,
11877 Expression::FirstValue(_)
11878 | Expression::LastValue(_)
11879 | Expression::NthValue(_)
11880 );
11881 let has_full_unbounded_frame = wf.over.frame.as_ref().map_or(false, |f| {
11882 matches!(f.kind, crate::expressions::WindowFrameKind::Rows)
11883 && matches!(f.start, crate::expressions::WindowFrameBound::UnboundedPreceding)
11884 && matches!(f.end, Some(crate::expressions::WindowFrameBound::UnboundedFollowing))
11885 && f.exclude.is_none()
11886 });
11887 if is_ranking_window_func && matches!(source, DialectType::Snowflake) {
11888 if has_full_unbounded_frame && matches!(target, DialectType::Snowflake) {
11889 // Strip the default frame for Snowflake target
11890 Action::SnowflakeWindowFrameStrip
11891 } else if !has_full_unbounded_frame && wf.over.frame.is_none() && !matches!(target, DialectType::Snowflake) {
11892 // Add default frame for non-Snowflake target
11893 Action::SnowflakeWindowFrameAdd
11894 } else {
11895 match &wf.this {
11896 Expression::FirstValue(ref vf)
11897 | Expression::LastValue(ref vf)
11898 if vf.ignore_nulls == Some(false) =>
11899 {
11900 match target {
11901 DialectType::SQLite => Action::RespectNullsConvert,
11902 _ => Action::None,
11903 }
11904 }
11905 _ => Action::None,
11906 }
11907 }
11908 } else {
11909 match &wf.this {
11910 Expression::FirstValue(ref vf)
11911 | Expression::LastValue(ref vf)
11912 if vf.ignore_nulls == Some(false) =>
11913 {
11914 // RESPECT NULLS
11915 match target {
11916 DialectType::SQLite | DialectType::PostgreSQL => {
11917 Action::RespectNullsConvert
11918 }
11919 _ => Action::None,
11920 }
11921 }
11922 _ => Action::None,
11923 }
11924 }
11925 }
11926 }
11927 }
11928 // CREATE TABLE a LIKE b -> dialect-specific transformations
11929 Expression::CreateTable(ref ct)
11930 if ct.columns.is_empty()
11931 && ct.constraints.iter().any(|c| {
11932 matches!(c, crate::expressions::TableConstraint::Like { .. })
11933 })
11934 && matches!(
11935 target,
11936 DialectType::DuckDB | DialectType::SQLite | DialectType::Drill
11937 ) =>
11938 {
11939 Action::CreateTableLikeToCtas
11940 }
11941 Expression::CreateTable(ref ct)
11942 if ct.columns.is_empty()
11943 && ct.constraints.iter().any(|c| {
11944 matches!(c, crate::expressions::TableConstraint::Like { .. })
11945 })
11946 && matches!(target, DialectType::TSQL | DialectType::Fabric) =>
11947 {
11948 Action::CreateTableLikeToSelectInto
11949 }
11950 Expression::CreateTable(ref ct)
11951 if ct.columns.is_empty()
11952 && ct.constraints.iter().any(|c| {
11953 matches!(c, crate::expressions::TableConstraint::Like { .. })
11954 })
11955 && matches!(target, DialectType::ClickHouse) =>
11956 {
11957 Action::CreateTableLikeToAs
11958 }
11959 // CREATE TABLE: strip COMMENT column constraint, USING, PARTITIONED BY for DuckDB
11960 Expression::CreateTable(ref ct)
11961 if matches!(target, DialectType::DuckDB)
11962 && matches!(
11963 source,
11964 DialectType::DuckDB
11965 | DialectType::Spark
11966 | DialectType::Databricks
11967 | DialectType::Hive
11968 ) =>
11969 {
11970 let has_comment = ct.columns.iter().any(|c| {
11971 c.comment.is_some()
11972 || c.constraints.iter().any(|con| {
11973 matches!(con, crate::expressions::ColumnConstraint::Comment(_))
11974 })
11975 });
11976 let has_props = !ct.properties.is_empty();
11977 if has_comment || has_props {
11978 Action::CreateTableStripComment
11979 } else {
11980 Action::None
11981 }
11982 }
11983 // Array conversion: Expression::Array -> Expression::ArrayFunc for PostgreSQL
11984 Expression::Array(_)
11985 if matches!(target, DialectType::PostgreSQL | DialectType::Redshift) =>
11986 {
11987 Action::ArrayConcatBracketConvert
11988 }
11989 // ArrayFunc (bracket notation) -> Function("ARRAY") for Redshift (from BigQuery source)
11990 Expression::ArrayFunc(ref arr)
11991 if arr.bracket_notation
11992 && matches!(source, DialectType::BigQuery)
11993 && matches!(target, DialectType::Redshift) =>
11994 {
11995 Action::ArrayConcatBracketConvert
11996 }
11997 // BIT_OR/BIT_AND/BIT_XOR: float/decimal arg cast for DuckDB, or rename for Snowflake
11998 Expression::BitwiseOrAgg(ref f)
11999 | Expression::BitwiseAndAgg(ref f)
12000 | Expression::BitwiseXorAgg(ref f) => {
12001 if matches!(target, DialectType::DuckDB) {
12002 // Check if the arg is CAST(val AS FLOAT/DOUBLE/DECIMAL/REAL)
12003 if let Expression::Cast(ref c) = f.this {
12004 match &c.to {
12005 DataType::Float { .. }
12006 | DataType::Double { .. }
12007 | DataType::Decimal { .. } => Action::BitAggFloatCast,
12008 DataType::Custom { ref name }
12009 if name.eq_ignore_ascii_case("REAL") =>
12010 {
12011 Action::BitAggFloatCast
12012 }
12013 _ => Action::None,
12014 }
12015 } else {
12016 Action::None
12017 }
12018 } else if matches!(target, DialectType::Snowflake) {
12019 Action::BitAggSnowflakeRename
12020 } else {
12021 Action::None
12022 }
12023 }
12024 // FILTER -> IFF for Snowflake (aggregate functions with FILTER clause)
12025 Expression::Filter(ref _f) if matches!(target, DialectType::Snowflake) => {
12026 Action::FilterToIff
12027 }
12028 // AggFunc.filter -> IFF wrapping for Snowflake (e.g., AVG(x) FILTER(WHERE cond))
12029 Expression::Avg(ref f)
12030 | Expression::Sum(ref f)
12031 | Expression::Min(ref f)
12032 | Expression::Max(ref f)
12033 | Expression::CountIf(ref f)
12034 | Expression::Stddev(ref f)
12035 | Expression::StddevPop(ref f)
12036 | Expression::StddevSamp(ref f)
12037 | Expression::Variance(ref f)
12038 | Expression::VarPop(ref f)
12039 | Expression::VarSamp(ref f)
12040 | Expression::Median(ref f)
12041 | Expression::Mode(ref f)
12042 | Expression::First(ref f)
12043 | Expression::Last(ref f)
12044 | Expression::ApproxDistinct(ref f)
12045 if f.filter.is_some() && matches!(target, DialectType::Snowflake) =>
12046 {
12047 Action::AggFilterToIff
12048 }
12049 Expression::Count(ref c)
12050 if c.filter.is_some() && matches!(target, DialectType::Snowflake) =>
12051 {
12052 Action::AggFilterToIff
12053 }
12054 // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN ... END) for dialects that don't support multi-arg DISTINCT
12055 Expression::Count(ref c)
12056 if c.distinct
12057 && matches!(&c.this, Some(Expression::Tuple(_)))
12058 && matches!(
12059 target,
12060 DialectType::Presto
12061 | DialectType::Trino
12062 | DialectType::DuckDB
12063 | DialectType::PostgreSQL
12064 ) =>
12065 {
12066 Action::CountDistinctMultiArg
12067 }
12068 // JSON arrow -> GET_PATH/PARSE_JSON for Snowflake
12069 Expression::JsonExtract(_) if matches!(target, DialectType::Snowflake) => {
12070 Action::JsonToGetPath
12071 }
12072 // DuckDB struct/dict -> BigQuery STRUCT / Presto ROW
12073 Expression::Struct(_)
12074 if matches!(
12075 target,
12076 DialectType::BigQuery | DialectType::Presto | DialectType::Trino
12077 ) && matches!(source, DialectType::DuckDB) =>
12078 {
12079 Action::StructToRow
12080 }
12081 // DuckDB curly-brace dict {'key': value} -> BigQuery STRUCT / Presto ROW
12082 Expression::MapFunc(ref m)
12083 if m.curly_brace_syntax
12084 && matches!(
12085 target,
12086 DialectType::BigQuery | DialectType::Presto | DialectType::Trino
12087 )
12088 && matches!(source, DialectType::DuckDB) =>
12089 {
12090 Action::StructToRow
12091 }
12092 // APPROX_COUNT_DISTINCT -> APPROX_DISTINCT for Presto/Trino
12093 Expression::ApproxCountDistinct(_)
12094 if matches!(
12095 target,
12096 DialectType::Presto | DialectType::Trino | DialectType::Athena
12097 ) =>
12098 {
12099 Action::ApproxCountDistinctToApproxDistinct
12100 }
12101 // ARRAY_CONTAINS(arr, val) -> CONTAINS(arr, val) for Presto, ARRAY_CONTAINS(CAST(val AS VARIANT), arr) for Snowflake
12102 Expression::ArrayContains(_)
12103 if matches!(
12104 target,
12105 DialectType::Presto | DialectType::Trino | DialectType::Snowflake
12106 ) && !(matches!(source, DialectType::Snowflake) && matches!(target, DialectType::Snowflake)) =>
12107 {
12108 Action::ArrayContainsConvert
12109 }
12110 // ARRAY_CONTAINS -> DuckDB NULL-aware CASE (from Snowflake source with check_null semantics)
12111 Expression::ArrayContains(_)
12112 if matches!(target, DialectType::DuckDB)
12113 && matches!(source, DialectType::Snowflake) =>
12114 {
12115 Action::ArrayContainsDuckDBConvert
12116 }
12117 // ARRAY_EXCEPT -> target-specific conversion
12118 Expression::ArrayExcept(_)
12119 if matches!(
12120 target,
12121 DialectType::DuckDB | DialectType::Snowflake | DialectType::Presto | DialectType::Trino | DialectType::Athena
12122 ) =>
12123 {
12124 Action::ArrayExceptConvert
12125 }
12126 // ARRAY_POSITION -> swap args for Snowflake target (only when source is not Snowflake)
12127 Expression::ArrayPosition(_)
12128 if matches!(target, DialectType::Snowflake)
12129 && !matches!(source, DialectType::Snowflake) =>
12130 {
12131 Action::ArrayPositionSnowflakeSwap
12132 }
12133 // ARRAY_POSITION(val, arr) -> ARRAY_POSITION(arr, val) - 1 for DuckDB from Snowflake source
12134 Expression::ArrayPosition(_)
12135 if matches!(target, DialectType::DuckDB)
12136 && matches!(source, DialectType::Snowflake) =>
12137 {
12138 Action::SnowflakeArrayPositionToDuckDB
12139 }
12140 // ARRAY_DISTINCT -> arrayDistinct for ClickHouse
12141 Expression::ArrayDistinct(_)
12142 if matches!(target, DialectType::ClickHouse) =>
12143 {
12144 Action::ArrayDistinctClickHouse
12145 }
12146 // ARRAY_DISTINCT -> DuckDB LIST_DISTINCT with NULL-aware CASE
12147 Expression::ArrayDistinct(_)
12148 if matches!(target, DialectType::DuckDB)
12149 && matches!(source, DialectType::Snowflake) =>
12150 {
12151 Action::ArrayDistinctConvert
12152 }
12153 // StrPosition with position -> complex expansion for Presto/DuckDB
12154 // STRPOS doesn't support a position arg in these dialects
12155 Expression::StrPosition(ref sp)
12156 if sp.position.is_some()
12157 && matches!(
12158 target,
12159 DialectType::Presto
12160 | DialectType::Trino
12161 | DialectType::Athena
12162 | DialectType::DuckDB
12163 ) =>
12164 {
12165 Action::StrPositionExpand
12166 }
12167 // FIRST(col) IGNORE NULLS -> ANY_VALUE(col) for DuckDB
12168 Expression::First(ref f)
12169 if f.ignore_nulls == Some(true)
12170 && matches!(target, DialectType::DuckDB) =>
12171 {
12172 Action::FirstToAnyValue
12173 }
12174 // BEGIN -> START TRANSACTION for Presto/Trino
12175 Expression::Command(ref cmd)
12176 if cmd.this.eq_ignore_ascii_case("BEGIN")
12177 && matches!(
12178 target,
12179 DialectType::Presto | DialectType::Trino | DialectType::Athena
12180 ) =>
12181 {
12182 // Handled inline below
12183 Action::None // We'll handle it directly
12184 }
12185 // Note: PostgreSQL ^ is now parsed as Power directly (not BitwiseXor).
12186 // PostgreSQL # is parsed as BitwiseXor (which is correct).
12187 Expression::Cbrt(_)
12188 if Self::is_postgres_family_source(source)
12189 && matches!(target, DialectType::TSQL | DialectType::Fabric) =>
12190 {
12191 Action::CbrtToPower
12192 }
12193 // a || b (Concat operator) -> CONCAT function for Presto/Trino
12194 Expression::Concat(ref _op)
12195 if matches!(source, DialectType::PostgreSQL | DialectType::Redshift)
12196 && matches!(target, DialectType::Presto | DialectType::Trino) =>
12197 {
12198 Action::PipeConcatToConcat
12199 }
12200 _ => Action::None,
12201 }
12202 };
12203
12204 match action {
12205 Action::None => {
12206 // Handle inline transforms that don't need a dedicated action
12207 if matches!(target, DialectType::TSQL | DialectType::Fabric) {
12208 if let Some(rewritten) = Self::rewrite_tsql_interval_arithmetic(&e, source)
12209 {
12210 return Ok(rewritten);
12211 }
12212 }
12213
12214 // BETWEEN SYMMETRIC/ASYMMETRIC expansion for non-PostgreSQL/Dremio targets
12215 if let Expression::Between(ref b) = e {
12216 if let Some(sym) = b.symmetric {
12217 let keeps_symmetric =
12218 matches!(target, DialectType::PostgreSQL | DialectType::Dremio);
12219 if !keeps_symmetric {
12220 if sym {
12221 // SYMMETRIC: expand to (x BETWEEN a AND b OR x BETWEEN b AND a)
12222 let b = if let Expression::Between(b) = e {
12223 *b
12224 } else {
12225 unreachable!()
12226 };
12227 let between1 = Expression::Between(Box::new(
12228 crate::expressions::Between {
12229 this: b.this.clone(),
12230 low: b.low.clone(),
12231 high: b.high.clone(),
12232 not: b.not,
12233 symmetric: None,
12234 },
12235 ));
12236 let between2 = Expression::Between(Box::new(
12237 crate::expressions::Between {
12238 this: b.this,
12239 low: b.high,
12240 high: b.low,
12241 not: b.not,
12242 symmetric: None,
12243 },
12244 ));
12245 return Ok(Expression::Paren(Box::new(
12246 crate::expressions::Paren {
12247 this: Expression::Or(Box::new(
12248 crate::expressions::BinaryOp::new(
12249 between1, between2,
12250 ),
12251 )),
12252 trailing_comments: vec![],
12253 },
12254 )));
12255 } else {
12256 // ASYMMETRIC: strip qualifier, keep as regular BETWEEN
12257 let b = if let Expression::Between(b) = e {
12258 *b
12259 } else {
12260 unreachable!()
12261 };
12262 return Ok(Expression::Between(Box::new(
12263 crate::expressions::Between {
12264 this: b.this,
12265 low: b.low,
12266 high: b.high,
12267 not: b.not,
12268 symmetric: None,
12269 },
12270 )));
12271 }
12272 }
12273 }
12274 }
12275
12276 // ILIKE -> LOWER(x) LIKE LOWER(y) for StarRocks/Doris
12277 if let Expression::ILike(ref _like) = e {
12278 if matches!(target, DialectType::StarRocks | DialectType::Doris) {
12279 let like = if let Expression::ILike(l) = e {
12280 *l
12281 } else {
12282 unreachable!()
12283 };
12284 let lower_left = Expression::Function(Box::new(Function::new(
12285 "LOWER".to_string(),
12286 vec![like.left],
12287 )));
12288 let lower_right = Expression::Function(Box::new(Function::new(
12289 "LOWER".to_string(),
12290 vec![like.right],
12291 )));
12292 return Ok(Expression::Like(Box::new(crate::expressions::LikeOp {
12293 left: lower_left,
12294 right: lower_right,
12295 escape: like.escape,
12296 quantifier: like.quantifier,
12297 inferred_type: None,
12298 })));
12299 }
12300 }
12301
12302 // Oracle DBMS_RANDOM.VALUE() -> RANDOM() for PostgreSQL, RAND() for others
12303 if let Expression::MethodCall(ref mc) = e {
12304 if matches!(source, DialectType::Oracle)
12305 && mc.method.name.eq_ignore_ascii_case("VALUE")
12306 && mc.args.is_empty()
12307 {
12308 let is_dbms_random = match &mc.this {
12309 Expression::Identifier(id) => {
12310 id.name.eq_ignore_ascii_case("DBMS_RANDOM")
12311 }
12312 Expression::Column(col) => {
12313 col.table.is_none()
12314 && col.name.name.eq_ignore_ascii_case("DBMS_RANDOM")
12315 }
12316 _ => false,
12317 };
12318 if is_dbms_random {
12319 let func_name = match target {
12320 DialectType::PostgreSQL
12321 | DialectType::Redshift
12322 | DialectType::DuckDB
12323 | DialectType::SQLite => "RANDOM",
12324 DialectType::Oracle => "DBMS_RANDOM.VALUE",
12325 _ => "RAND",
12326 };
12327 return Ok(Expression::Function(Box::new(Function::new(
12328 func_name.to_string(),
12329 vec![],
12330 ))));
12331 }
12332 }
12333 }
12334 // TRIM without explicit position -> add BOTH for ClickHouse
12335 if let Expression::Trim(ref trim) = e {
12336 if matches!(target, DialectType::ClickHouse)
12337 && trim.sql_standard_syntax
12338 && trim.characters.is_some()
12339 && !trim.position_explicit
12340 {
12341 let mut new_trim = (**trim).clone();
12342 new_trim.position_explicit = true;
12343 return Ok(Expression::Trim(Box::new(new_trim)));
12344 }
12345 }
12346 // BEGIN -> START TRANSACTION for Presto/Trino
12347 if let Expression::Transaction(ref txn) = e {
12348 if matches!(
12349 target,
12350 DialectType::Presto | DialectType::Trino | DialectType::Athena
12351 ) {
12352 // Convert BEGIN to START TRANSACTION by setting mark to "START"
12353 let mut txn = txn.clone();
12354 txn.mark = Some(Box::new(Expression::Identifier(Identifier::new(
12355 "START".to_string(),
12356 ))));
12357 return Ok(Expression::Transaction(Box::new(*txn)));
12358 }
12359 }
12360 // IS TRUE/FALSE -> simplified forms for Presto/Trino
12361 if matches!(
12362 target,
12363 DialectType::Presto | DialectType::Trino | DialectType::Athena
12364 ) {
12365 match &e {
12366 Expression::IsTrue(itf) if !itf.not => {
12367 // x IS TRUE -> x
12368 return Ok(itf.this.clone());
12369 }
12370 Expression::IsTrue(itf) if itf.not => {
12371 // x IS NOT TRUE -> NOT x
12372 return Ok(Expression::Not(Box::new(
12373 crate::expressions::UnaryOp {
12374 this: itf.this.clone(),
12375 inferred_type: None,
12376 },
12377 )));
12378 }
12379 Expression::IsFalse(itf) if !itf.not => {
12380 // x IS FALSE -> NOT x
12381 return Ok(Expression::Not(Box::new(
12382 crate::expressions::UnaryOp {
12383 this: itf.this.clone(),
12384 inferred_type: None,
12385 },
12386 )));
12387 }
12388 Expression::IsFalse(itf) if itf.not => {
12389 // x IS NOT FALSE -> NOT NOT x
12390 let not_x =
12391 Expression::Not(Box::new(crate::expressions::UnaryOp {
12392 this: itf.this.clone(),
12393 inferred_type: None,
12394 }));
12395 return Ok(Expression::Not(Box::new(
12396 crate::expressions::UnaryOp {
12397 this: not_x,
12398 inferred_type: None,
12399 },
12400 )));
12401 }
12402 _ => {}
12403 }
12404 }
12405 // x IS NOT FALSE -> NOT x IS FALSE for Redshift
12406 if matches!(target, DialectType::Redshift) {
12407 if let Expression::IsFalse(ref itf) = e {
12408 if itf.not {
12409 return Ok(Expression::Not(Box::new(
12410 crate::expressions::UnaryOp {
12411 this: Expression::IsFalse(Box::new(
12412 crate::expressions::IsTrueFalse {
12413 this: itf.this.clone(),
12414 not: false,
12415 },
12416 )),
12417 inferred_type: None,
12418 },
12419 )));
12420 }
12421 }
12422 }
12423 // REGEXP_REPLACE: add 'g' flag when source defaults to global replacement
12424 // Snowflake default is global, PostgreSQL/DuckDB default is first-match-only
12425 if let Expression::Function(ref f) = e {
12426 if f.name.eq_ignore_ascii_case("REGEXP_REPLACE")
12427 && matches!(source, DialectType::Snowflake)
12428 && matches!(target, DialectType::PostgreSQL | DialectType::DuckDB)
12429 {
12430 if f.args.len() == 3 {
12431 let mut args = f.args.clone();
12432 args.push(Expression::string("g"));
12433 return Ok(Expression::Function(Box::new(Function::new(
12434 "REGEXP_REPLACE".to_string(),
12435 args,
12436 ))));
12437 } else if f.args.len() == 4 {
12438 // 4th arg might be position, add 'g' as 5th
12439 let mut args = f.args.clone();
12440 args.push(Expression::string("g"));
12441 return Ok(Expression::Function(Box::new(Function::new(
12442 "REGEXP_REPLACE".to_string(),
12443 args,
12444 ))));
12445 }
12446 }
12447 }
12448 Ok(e)
12449 }
12450
12451 Action::GreatestLeastNull => {
12452 let f = if let Expression::Function(f) = e {
12453 *f
12454 } else {
12455 unreachable!("action only triggered for Function expressions")
12456 };
12457 let mut null_checks: Vec<Expression> = f
12458 .args
12459 .iter()
12460 .map(|a| {
12461 Expression::IsNull(Box::new(IsNull {
12462 this: a.clone(),
12463 not: false,
12464 postfix_form: false,
12465 }))
12466 })
12467 .collect();
12468 let condition = if null_checks.len() == 1 {
12469 null_checks.remove(0)
12470 } else {
12471 let first = null_checks.remove(0);
12472 null_checks.into_iter().fold(first, |acc, check| {
12473 Expression::Or(Box::new(BinaryOp::new(acc, check)))
12474 })
12475 };
12476 Ok(Expression::Case(Box::new(Case {
12477 operand: None,
12478 whens: vec![(condition, Expression::Null(Null))],
12479 else_: Some(Expression::Function(Box::new(Function::new(
12480 f.name, f.args,
12481 )))),
12482 comments: Vec::new(),
12483 inferred_type: None,
12484 })))
12485 }
12486
12487 Action::ArrayGenerateRange => {
12488 let f = if let Expression::Function(f) = e {
12489 *f
12490 } else {
12491 unreachable!("action only triggered for Function expressions")
12492 };
12493 let start = f.args[0].clone();
12494 let end = f.args[1].clone();
12495 let step = f.args.get(2).cloned();
12496
12497 // Helper: compute end - 1 for converting exclusive→inclusive end.
12498 // When end is a literal number, simplify to a computed literal.
12499 fn exclusive_to_inclusive_end(end: &Expression) -> Expression {
12500 // Try to simplify literal numbers
12501 match end {
12502 Expression::Literal(lit)
12503 if matches!(lit.as_ref(), Literal::Number(_)) =>
12504 {
12505 let Literal::Number(n) = lit.as_ref() else {
12506 unreachable!()
12507 };
12508 if let Ok(val) = n.parse::<i64>() {
12509 return Expression::number(val - 1);
12510 }
12511 }
12512 Expression::Neg(u) => {
12513 if let Expression::Literal(lit) = &u.this {
12514 if let Literal::Number(n) = lit.as_ref() {
12515 if let Ok(val) = n.parse::<i64>() {
12516 return Expression::number(-val - 1);
12517 }
12518 }
12519 }
12520 }
12521 _ => {}
12522 }
12523 // Non-literal: produce end - 1 expression
12524 Expression::Sub(Box::new(BinaryOp::new(end.clone(), Expression::number(1))))
12525 }
12526
12527 match target {
12528 // Snowflake ARRAY_GENERATE_RANGE and DuckDB RANGE both use exclusive end,
12529 // so no adjustment needed — just rename the function.
12530 DialectType::Snowflake => {
12531 let mut args = vec![start, end];
12532 if let Some(s) = step {
12533 args.push(s);
12534 }
12535 Ok(Expression::Function(Box::new(Function::new(
12536 "ARRAY_GENERATE_RANGE".to_string(),
12537 args,
12538 ))))
12539 }
12540 DialectType::DuckDB => {
12541 let mut args = vec![start, end];
12542 if let Some(s) = step {
12543 args.push(s);
12544 }
12545 Ok(Expression::Function(Box::new(Function::new(
12546 "RANGE".to_string(),
12547 args,
12548 ))))
12549 }
12550 // These dialects use inclusive end, so convert exclusive→inclusive.
12551 // Presto/Trino: simplify literal numbers (3 → 2).
12552 DialectType::Presto | DialectType::Trino => {
12553 let end_inclusive = exclusive_to_inclusive_end(&end);
12554 let mut args = vec![start, end_inclusive];
12555 if let Some(s) = step {
12556 args.push(s);
12557 }
12558 Ok(Expression::Function(Box::new(Function::new(
12559 "SEQUENCE".to_string(),
12560 args,
12561 ))))
12562 }
12563 // PostgreSQL, Redshift, BigQuery: keep as end - 1 expression form.
12564 DialectType::PostgreSQL | DialectType::Redshift => {
12565 let end_minus_1 = Expression::Sub(Box::new(BinaryOp::new(
12566 end.clone(),
12567 Expression::number(1),
12568 )));
12569 let mut args = vec![start, end_minus_1];
12570 if let Some(s) = step {
12571 args.push(s);
12572 }
12573 Ok(Expression::Function(Box::new(Function::new(
12574 "GENERATE_SERIES".to_string(),
12575 args,
12576 ))))
12577 }
12578 DialectType::BigQuery => {
12579 let end_minus_1 = Expression::Sub(Box::new(BinaryOp::new(
12580 end.clone(),
12581 Expression::number(1),
12582 )));
12583 let mut args = vec![start, end_minus_1];
12584 if let Some(s) = step {
12585 args.push(s);
12586 }
12587 Ok(Expression::Function(Box::new(Function::new(
12588 "GENERATE_ARRAY".to_string(),
12589 args,
12590 ))))
12591 }
12592 _ => Ok(Expression::Function(Box::new(Function::new(
12593 f.name, f.args,
12594 )))),
12595 }
12596 }
12597
12598 Action::Div0TypedDivision => {
12599 let if_func = if let Expression::IfFunc(f) = e {
12600 *f
12601 } else {
12602 unreachable!("action only triggered for IfFunc expressions")
12603 };
12604 if let Some(Expression::Div(div)) = if_func.false_value {
12605 let cast_type = if matches!(target, DialectType::SQLite) {
12606 DataType::Float {
12607 precision: None,
12608 scale: None,
12609 real_spelling: true,
12610 }
12611 } else {
12612 DataType::Double {
12613 precision: None,
12614 scale: None,
12615 }
12616 };
12617 let casted_left = Expression::Cast(Box::new(Cast {
12618 this: div.left,
12619 to: cast_type,
12620 trailing_comments: vec![],
12621 double_colon_syntax: false,
12622 format: None,
12623 default: None,
12624 inferred_type: None,
12625 }));
12626 Ok(Expression::IfFunc(Box::new(crate::expressions::IfFunc {
12627 condition: if_func.condition,
12628 true_value: if_func.true_value,
12629 false_value: Some(Expression::Div(Box::new(BinaryOp::new(
12630 casted_left,
12631 div.right,
12632 )))),
12633 original_name: if_func.original_name,
12634 inferred_type: None,
12635 })))
12636 } else {
12637 // Not actually a Div, reconstruct
12638 Ok(Expression::IfFunc(Box::new(if_func)))
12639 }
12640 }
12641
12642 Action::ArrayAggCollectList => {
12643 let agg = if let Expression::ArrayAgg(a) = e {
12644 *a
12645 } else {
12646 unreachable!("action only triggered for ArrayAgg expressions")
12647 };
12648 Ok(Expression::ArrayAgg(Box::new(AggFunc {
12649 name: Some("COLLECT_LIST".to_string()),
12650 ..agg
12651 })))
12652 }
12653
12654 Action::ArrayAggToGroupConcat => {
12655 let agg = if let Expression::ArrayAgg(a) = e {
12656 *a
12657 } else {
12658 unreachable!("action only triggered for ArrayAgg expressions")
12659 };
12660 Ok(Expression::ArrayAgg(Box::new(AggFunc {
12661 name: Some("GROUP_CONCAT".to_string()),
12662 ..agg
12663 })))
12664 }
12665
12666 Action::ArrayAggWithinGroupFilter => {
12667 let wg = if let Expression::WithinGroup(w) = e {
12668 *w
12669 } else {
12670 unreachable!("action only triggered for WithinGroup expressions")
12671 };
12672 if let Expression::ArrayAgg(inner_agg) = wg.this {
12673 let col = inner_agg.this.clone();
12674 let filter = Expression::IsNull(Box::new(IsNull {
12675 this: col,
12676 not: true,
12677 postfix_form: false,
12678 }));
12679 // For DuckDB, add explicit NULLS FIRST for DESC ordering
12680 let order_by = if matches!(target, DialectType::DuckDB) {
12681 wg.order_by
12682 .into_iter()
12683 .map(|mut o| {
12684 if o.desc && o.nulls_first.is_none() {
12685 o.nulls_first = Some(true);
12686 }
12687 o
12688 })
12689 .collect()
12690 } else {
12691 wg.order_by
12692 };
12693 Ok(Expression::ArrayAgg(Box::new(AggFunc {
12694 this: inner_agg.this,
12695 distinct: inner_agg.distinct,
12696 filter: Some(filter),
12697 order_by,
12698 name: inner_agg.name,
12699 ignore_nulls: inner_agg.ignore_nulls,
12700 having_max: inner_agg.having_max,
12701 limit: inner_agg.limit,
12702 inferred_type: None,
12703 })))
12704 } else {
12705 Ok(Expression::WithinGroup(Box::new(wg)))
12706 }
12707 }
12708
12709 Action::ArrayAggFilter => {
12710 let agg = if let Expression::ArrayAgg(a) = e {
12711 *a
12712 } else {
12713 unreachable!("action only triggered for ArrayAgg expressions")
12714 };
12715 let col = agg.this.clone();
12716 let filter = Expression::IsNull(Box::new(IsNull {
12717 this: col,
12718 not: true,
12719 postfix_form: false,
12720 }));
12721 Ok(Expression::ArrayAgg(Box::new(AggFunc {
12722 filter: Some(filter),
12723 ..agg
12724 })))
12725 }
12726
12727 Action::ArrayAggNullFilter => {
12728 // ARRAY_AGG(x) FILTER(WHERE cond) -> ARRAY_AGG(x) FILTER(WHERE cond AND NOT x IS NULL)
12729 // For source dialects that exclude NULLs (Spark/Hive) targeting DuckDB which includes them
12730 let agg = if let Expression::ArrayAgg(a) = e {
12731 *a
12732 } else {
12733 unreachable!("action only triggered for ArrayAgg expressions")
12734 };
12735 let col = agg.this.clone();
12736 let not_null = Expression::IsNull(Box::new(IsNull {
12737 this: col,
12738 not: true,
12739 postfix_form: true, // Use "NOT x IS NULL" form (prefix NOT)
12740 }));
12741 let new_filter = if let Some(existing_filter) = agg.filter {
12742 // AND the NOT IS NULL with existing filter
12743 Expression::And(Box::new(crate::expressions::BinaryOp::new(
12744 existing_filter,
12745 not_null,
12746 )))
12747 } else {
12748 not_null
12749 };
12750 Ok(Expression::ArrayAgg(Box::new(AggFunc {
12751 filter: Some(new_filter),
12752 ..agg
12753 })))
12754 }
12755
12756 Action::BigQueryArraySelectAsStructToSnowflake => {
12757 // ARRAY(SELECT AS STRUCT x1 AS x1, x2 AS x2 FROM t)
12758 // -> (SELECT ARRAY_AGG(OBJECT_CONSTRUCT('x1', x1, 'x2', x2)) FROM t)
12759 if let Expression::Function(mut f) = e {
12760 let is_match = f.args.len() == 1
12761 && matches!(&f.args[0], Expression::Select(s) if s.kind.as_deref() == Some("STRUCT"));
12762 if is_match {
12763 let inner_select = match f.args.remove(0) {
12764 Expression::Select(s) => *s,
12765 _ => unreachable!(
12766 "argument already verified to be a Select expression"
12767 ),
12768 };
12769 // Build OBJECT_CONSTRUCT args from SELECT expressions
12770 let mut oc_args = Vec::new();
12771 for expr in &inner_select.expressions {
12772 match expr {
12773 Expression::Alias(a) => {
12774 let key = Expression::Literal(Box::new(Literal::String(
12775 a.alias.name.clone(),
12776 )));
12777 let value = a.this.clone();
12778 oc_args.push(key);
12779 oc_args.push(value);
12780 }
12781 Expression::Column(c) => {
12782 let key = Expression::Literal(Box::new(Literal::String(
12783 c.name.name.clone(),
12784 )));
12785 oc_args.push(key);
12786 oc_args.push(expr.clone());
12787 }
12788 _ => {
12789 oc_args.push(expr.clone());
12790 }
12791 }
12792 }
12793 let object_construct = Expression::Function(Box::new(Function::new(
12794 "OBJECT_CONSTRUCT".to_string(),
12795 oc_args,
12796 )));
12797 let array_agg = Expression::Function(Box::new(Function::new(
12798 "ARRAY_AGG".to_string(),
12799 vec![object_construct],
12800 )));
12801 let mut new_select = crate::expressions::Select::new();
12802 new_select.expressions = vec![array_agg];
12803 new_select.from = inner_select.from.clone();
12804 new_select.where_clause = inner_select.where_clause.clone();
12805 new_select.group_by = inner_select.group_by.clone();
12806 new_select.having = inner_select.having.clone();
12807 new_select.joins = inner_select.joins.clone();
12808 Ok(Expression::Subquery(Box::new(
12809 crate::expressions::Subquery {
12810 this: Expression::Select(Box::new(new_select)),
12811 alias: None,
12812 column_aliases: Vec::new(),
12813 alias_explicit_as: false,
12814 alias_keyword: None,
12815 order_by: None,
12816 limit: None,
12817 offset: None,
12818 distribute_by: None,
12819 sort_by: None,
12820 cluster_by: None,
12821 lateral: false,
12822 modifiers_inside: false,
12823 trailing_comments: Vec::new(),
12824 inferred_type: None,
12825 },
12826 )))
12827 } else {
12828 Ok(Expression::Function(f))
12829 }
12830 } else {
12831 Ok(e)
12832 }
12833 }
12834
12835 Action::BigQueryPercentileContToDuckDB => {
12836 // PERCENTILE_CONT(x, frac [RESPECT NULLS]) -> QUANTILE_CONT(x, frac) for DuckDB
12837 if let Expression::AggregateFunction(mut af) = e {
12838 af.name = "QUANTILE_CONT".to_string();
12839 af.ignore_nulls = None; // Strip RESPECT/IGNORE NULLS
12840 // Keep only first 2 args
12841 if af.args.len() > 2 {
12842 af.args.truncate(2);
12843 }
12844 Ok(Expression::AggregateFunction(af))
12845 } else {
12846 Ok(e)
12847 }
12848 }
12849
12850 Action::ArrayAggIgnoreNullsDuckDB => {
12851 // ARRAY_AGG(x IGNORE NULLS ORDER BY a, b DESC) -> ARRAY_AGG(x ORDER BY a NULLS FIRST, b DESC)
12852 // Strip IGNORE NULLS, add NULLS FIRST to first ORDER BY column
12853 let mut agg = if let Expression::ArrayAgg(a) = e {
12854 *a
12855 } else {
12856 unreachable!("action only triggered for ArrayAgg expressions")
12857 };
12858 agg.ignore_nulls = None; // Strip IGNORE NULLS
12859 if !agg.order_by.is_empty() {
12860 agg.order_by[0].nulls_first = Some(true);
12861 }
12862 Ok(Expression::ArrayAgg(Box::new(agg)))
12863 }
12864
12865 Action::CountDistinctMultiArg => {
12866 // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
12867 if let Expression::Count(c) = e {
12868 if let Some(Expression::Tuple(t)) = c.this {
12869 let args = t.expressions;
12870 // Build CASE expression:
12871 // WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
12872 let mut whens = Vec::new();
12873 for arg in &args {
12874 whens.push((
12875 Expression::IsNull(Box::new(IsNull {
12876 this: arg.clone(),
12877 not: false,
12878 postfix_form: false,
12879 })),
12880 Expression::Null(crate::expressions::Null),
12881 ));
12882 }
12883 // Build the tuple for ELSE
12884 let tuple_expr =
12885 Expression::Tuple(Box::new(crate::expressions::Tuple {
12886 expressions: args,
12887 }));
12888 let case_expr = Expression::Case(Box::new(crate::expressions::Case {
12889 operand: None,
12890 whens,
12891 else_: Some(tuple_expr),
12892 comments: Vec::new(),
12893 inferred_type: None,
12894 }));
12895 Ok(Expression::Count(Box::new(crate::expressions::CountFunc {
12896 this: Some(case_expr),
12897 star: false,
12898 distinct: true,
12899 filter: c.filter,
12900 ignore_nulls: c.ignore_nulls,
12901 original_name: c.original_name,
12902 inferred_type: None,
12903 })))
12904 } else {
12905 Ok(Expression::Count(c))
12906 }
12907 } else {
12908 Ok(e)
12909 }
12910 }
12911
12912 Action::CastTimestampToDatetime => {
12913 let c = if let Expression::Cast(c) = e {
12914 *c
12915 } else {
12916 unreachable!("action only triggered for Cast expressions")
12917 };
12918 Ok(Expression::Cast(Box::new(Cast {
12919 to: DataType::Custom {
12920 name: "DATETIME".to_string(),
12921 },
12922 ..c
12923 })))
12924 }
12925
12926 Action::CastTimestampStripTz => {
12927 // CAST(x AS TIMESTAMP(n) WITH TIME ZONE) -> CAST(x AS TIMESTAMP) for Hive/Spark/BigQuery
12928 let c = if let Expression::Cast(c) = e {
12929 *c
12930 } else {
12931 unreachable!("action only triggered for Cast expressions")
12932 };
12933 Ok(Expression::Cast(Box::new(Cast {
12934 to: DataType::Timestamp {
12935 precision: None,
12936 timezone: false,
12937 },
12938 ..c
12939 })))
12940 }
12941
12942 Action::CastTimestamptzToFunc => {
12943 // CAST(x AS TIMESTAMPTZ) -> TIMESTAMP(x) function for MySQL/StarRocks
12944 let c = if let Expression::Cast(c) = e {
12945 *c
12946 } else {
12947 unreachable!("action only triggered for Cast expressions")
12948 };
12949 Ok(Expression::Function(Box::new(Function::new(
12950 "TIMESTAMP".to_string(),
12951 vec![c.this],
12952 ))))
12953 }
12954
12955 Action::ToDateToCast => {
12956 // Convert TO_DATE(x) -> CAST(x AS DATE) for DuckDB
12957 if let Expression::Function(f) = e {
12958 let arg = f.args.into_iter().next().unwrap();
12959 Ok(Expression::Cast(Box::new(Cast {
12960 this: arg,
12961 to: DataType::Date,
12962 double_colon_syntax: false,
12963 trailing_comments: vec![],
12964 format: None,
12965 default: None,
12966 inferred_type: None,
12967 })))
12968 } else {
12969 Ok(e)
12970 }
12971 }
12972 Action::DateTruncWrapCast => {
12973 // Handle both Expression::DateTrunc/TimestampTrunc and
12974 // Expression::Function("DATE_TRUNC", [unit, expr])
12975 match e {
12976 Expression::DateTrunc(d) | Expression::TimestampTrunc(d) => {
12977 let input_type = match &d.this {
12978 Expression::Cast(c) => Some(c.to.clone()),
12979 _ => None,
12980 };
12981 if let Some(cast_type) = input_type {
12982 let is_time = matches!(cast_type, DataType::Time { .. });
12983 if is_time {
12984 let date_expr = Expression::Cast(Box::new(Cast {
12985 this: Expression::Literal(Box::new(
12986 crate::expressions::Literal::String(
12987 "1970-01-01".to_string(),
12988 ),
12989 )),
12990 to: DataType::Date,
12991 double_colon_syntax: false,
12992 trailing_comments: vec![],
12993 format: None,
12994 default: None,
12995 inferred_type: None,
12996 }));
12997 let add_expr =
12998 Expression::Add(Box::new(BinaryOp::new(date_expr, d.this)));
12999 let inner = Expression::DateTrunc(Box::new(DateTruncFunc {
13000 this: add_expr,
13001 unit: d.unit,
13002 }));
13003 Ok(Expression::Cast(Box::new(Cast {
13004 this: inner,
13005 to: cast_type,
13006 double_colon_syntax: false,
13007 trailing_comments: vec![],
13008 format: None,
13009 default: None,
13010 inferred_type: None,
13011 })))
13012 } else {
13013 let inner = Expression::DateTrunc(Box::new(*d));
13014 Ok(Expression::Cast(Box::new(Cast {
13015 this: inner,
13016 to: cast_type,
13017 double_colon_syntax: false,
13018 trailing_comments: vec![],
13019 format: None,
13020 default: None,
13021 inferred_type: None,
13022 })))
13023 }
13024 } else {
13025 Ok(Expression::DateTrunc(d))
13026 }
13027 }
13028 Expression::Function(f) if f.args.len() == 2 => {
13029 // Function-based DATE_TRUNC(unit, expr)
13030 let input_type = match &f.args[1] {
13031 Expression::Cast(c) => Some(c.to.clone()),
13032 _ => None,
13033 };
13034 if let Some(cast_type) = input_type {
13035 let is_time = matches!(cast_type, DataType::Time { .. });
13036 if is_time {
13037 let date_expr = Expression::Cast(Box::new(Cast {
13038 this: Expression::Literal(Box::new(
13039 crate::expressions::Literal::String(
13040 "1970-01-01".to_string(),
13041 ),
13042 )),
13043 to: DataType::Date,
13044 double_colon_syntax: false,
13045 trailing_comments: vec![],
13046 format: None,
13047 default: None,
13048 inferred_type: None,
13049 }));
13050 let mut args = f.args;
13051 let unit_arg = args.remove(0);
13052 let time_expr = args.remove(0);
13053 let add_expr = Expression::Add(Box::new(BinaryOp::new(
13054 date_expr, time_expr,
13055 )));
13056 let inner = Expression::Function(Box::new(Function::new(
13057 "DATE_TRUNC".to_string(),
13058 vec![unit_arg, add_expr],
13059 )));
13060 Ok(Expression::Cast(Box::new(Cast {
13061 this: inner,
13062 to: cast_type,
13063 double_colon_syntax: false,
13064 trailing_comments: vec![],
13065 format: None,
13066 default: None,
13067 inferred_type: None,
13068 })))
13069 } else {
13070 // Wrap the function in CAST
13071 Ok(Expression::Cast(Box::new(Cast {
13072 this: Expression::Function(f),
13073 to: cast_type,
13074 double_colon_syntax: false,
13075 trailing_comments: vec![],
13076 format: None,
13077 default: None,
13078 inferred_type: None,
13079 })))
13080 }
13081 } else {
13082 Ok(Expression::Function(f))
13083 }
13084 }
13085 other => Ok(other),
13086 }
13087 }
13088
13089 Action::RegexpReplaceSnowflakeToDuckDB => {
13090 // Snowflake REGEXP_REPLACE(s, p, r, position) -> REGEXP_REPLACE(s, p, r, 'g')
13091 if let Expression::Function(f) = e {
13092 let mut args = f.args;
13093 let subject = args.remove(0);
13094 let pattern = args.remove(0);
13095 let replacement = args.remove(0);
13096 Ok(Expression::Function(Box::new(Function::new(
13097 "REGEXP_REPLACE".to_string(),
13098 vec![
13099 subject,
13100 pattern,
13101 replacement,
13102 Expression::Literal(Box::new(crate::expressions::Literal::String(
13103 "g".to_string(),
13104 ))),
13105 ],
13106 ))))
13107 } else {
13108 Ok(e)
13109 }
13110 }
13111
13112 Action::RegexpReplacePositionSnowflakeToDuckDB => {
13113 // Snowflake REGEXP_REPLACE(s, p, r, pos, occ) -> DuckDB form
13114 // pos=1, occ=1 -> REGEXP_REPLACE(s, p, r) (single replace, no 'g')
13115 // pos>1, occ=0 -> SUBSTRING(s, 1, pos-1) || REGEXP_REPLACE(SUBSTRING(s, pos), p, r, 'g')
13116 // pos>1, occ=1 -> SUBSTRING(s, 1, pos-1) || REGEXP_REPLACE(SUBSTRING(s, pos), p, r)
13117 // pos=1, occ=0 -> REGEXP_REPLACE(s, p, r, 'g') (replace all)
13118 if let Expression::Function(f) = e {
13119 let mut args = f.args;
13120 let subject = args.remove(0);
13121 let pattern = args.remove(0);
13122 let replacement = args.remove(0);
13123 let position = args.remove(0);
13124 let occurrence = args.remove(0);
13125
13126 let is_pos_1 = matches!(&position, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "1"));
13127 let is_occ_0 = matches!(&occurrence, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "0"));
13128 let is_occ_1 = matches!(&occurrence, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "1"));
13129
13130 if is_pos_1 && is_occ_1 {
13131 // REGEXP_REPLACE(s, p, r) - single replace, no flags
13132 Ok(Expression::Function(Box::new(Function::new(
13133 "REGEXP_REPLACE".to_string(),
13134 vec![subject, pattern, replacement],
13135 ))))
13136 } else if is_pos_1 && is_occ_0 {
13137 // REGEXP_REPLACE(s, p, r, 'g') - global replace
13138 Ok(Expression::Function(Box::new(Function::new(
13139 "REGEXP_REPLACE".to_string(),
13140 vec![
13141 subject,
13142 pattern,
13143 replacement,
13144 Expression::Literal(Box::new(Literal::String("g".to_string()))),
13145 ],
13146 ))))
13147 } else {
13148 // pos>1: SUBSTRING(s, 1, pos-1) || REGEXP_REPLACE(SUBSTRING(s, pos), p, r[, 'g'])
13149 // Pre-compute pos-1 when position is a numeric literal
13150 let pos_minus_1 = if let Expression::Literal(ref lit) = position {
13151 if let Literal::Number(ref n) = lit.as_ref() {
13152 if let Ok(val) = n.parse::<i64>() {
13153 Expression::number(val - 1)
13154 } else {
13155 Expression::Sub(Box::new(BinaryOp::new(
13156 position.clone(),
13157 Expression::number(1),
13158 )))
13159 }
13160 } else {
13161 position.clone()
13162 }
13163 } else {
13164 Expression::Sub(Box::new(BinaryOp::new(
13165 position.clone(),
13166 Expression::number(1),
13167 )))
13168 };
13169 let prefix = Expression::Function(Box::new(Function::new(
13170 "SUBSTRING".to_string(),
13171 vec![subject.clone(), Expression::number(1), pos_minus_1],
13172 )));
13173 let suffix_subject = Expression::Function(Box::new(Function::new(
13174 "SUBSTRING".to_string(),
13175 vec![subject, position],
13176 )));
13177 let mut replace_args = vec![suffix_subject, pattern, replacement];
13178 if is_occ_0 {
13179 replace_args.push(Expression::Literal(Box::new(Literal::String(
13180 "g".to_string(),
13181 ))));
13182 }
13183 let replace_expr = Expression::Function(Box::new(Function::new(
13184 "REGEXP_REPLACE".to_string(),
13185 replace_args,
13186 )));
13187 Ok(Expression::DPipe(Box::new(crate::expressions::DPipe {
13188 this: Box::new(prefix),
13189 expression: Box::new(replace_expr),
13190 safe: None,
13191 })))
13192 }
13193 } else {
13194 Ok(e)
13195 }
13196 }
13197
13198 Action::RegexpSubstrSnowflakeToDuckDB => {
13199 // Snowflake REGEXP_SUBSTR -> DuckDB REGEXP_EXTRACT variants
13200 if let Expression::Function(f) = e {
13201 let mut args = f.args;
13202 let arg_count = args.len();
13203 match arg_count {
13204 // REGEXP_SUBSTR(s, p) -> REGEXP_EXTRACT(s, p)
13205 0..=2 => Ok(Expression::Function(Box::new(Function::new(
13206 "REGEXP_EXTRACT".to_string(),
13207 args,
13208 )))),
13209 // REGEXP_SUBSTR(s, p, pos) -> REGEXP_EXTRACT(NULLIF(SUBSTRING(s, pos), ''), p)
13210 3 => {
13211 let subject = args.remove(0);
13212 let pattern = args.remove(0);
13213 let position = args.remove(0);
13214 let is_pos_1 = matches!(&position, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "1"));
13215 if is_pos_1 {
13216 Ok(Expression::Function(Box::new(Function::new(
13217 "REGEXP_EXTRACT".to_string(),
13218 vec![subject, pattern],
13219 ))))
13220 } else {
13221 let substring_expr =
13222 Expression::Function(Box::new(Function::new(
13223 "SUBSTRING".to_string(),
13224 vec![subject, position],
13225 )));
13226 let nullif_expr =
13227 Expression::Function(Box::new(Function::new(
13228 "NULLIF".to_string(),
13229 vec![
13230 substring_expr,
13231 Expression::Literal(Box::new(Literal::String(
13232 String::new(),
13233 ))),
13234 ],
13235 )));
13236 Ok(Expression::Function(Box::new(Function::new(
13237 "REGEXP_EXTRACT".to_string(),
13238 vec![nullif_expr, pattern],
13239 ))))
13240 }
13241 }
13242 // REGEXP_SUBSTR(s, p, pos, occ) -> depends on pos and occ
13243 4 => {
13244 let subject = args.remove(0);
13245 let pattern = args.remove(0);
13246 let position = args.remove(0);
13247 let occurrence = args.remove(0);
13248 let is_pos_1 = matches!(&position, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "1"));
13249 let is_occ_1 = matches!(&occurrence, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "1"));
13250
13251 let effective_subject = if is_pos_1 {
13252 subject
13253 } else {
13254 let substring_expr =
13255 Expression::Function(Box::new(Function::new(
13256 "SUBSTRING".to_string(),
13257 vec![subject, position],
13258 )));
13259 Expression::Function(Box::new(Function::new(
13260 "NULLIF".to_string(),
13261 vec![
13262 substring_expr,
13263 Expression::Literal(Box::new(Literal::String(
13264 String::new(),
13265 ))),
13266 ],
13267 )))
13268 };
13269
13270 if is_occ_1 {
13271 Ok(Expression::Function(Box::new(Function::new(
13272 "REGEXP_EXTRACT".to_string(),
13273 vec![effective_subject, pattern],
13274 ))))
13275 } else {
13276 // ARRAY_EXTRACT(REGEXP_EXTRACT_ALL(s, p), occ)
13277 let extract_all =
13278 Expression::Function(Box::new(Function::new(
13279 "REGEXP_EXTRACT_ALL".to_string(),
13280 vec![effective_subject, pattern],
13281 )));
13282 Ok(Expression::Function(Box::new(Function::new(
13283 "ARRAY_EXTRACT".to_string(),
13284 vec![extract_all, occurrence],
13285 ))))
13286 }
13287 }
13288 // REGEXP_SUBSTR(s, p, 1, 1, 'e') -> REGEXP_EXTRACT(s, p)
13289 5 => {
13290 let subject = args.remove(0);
13291 let pattern = args.remove(0);
13292 let _position = args.remove(0);
13293 let _occurrence = args.remove(0);
13294 let _flags = args.remove(0);
13295 // Strip 'e' flag, convert to REGEXP_EXTRACT
13296 Ok(Expression::Function(Box::new(Function::new(
13297 "REGEXP_EXTRACT".to_string(),
13298 vec![subject, pattern],
13299 ))))
13300 }
13301 // REGEXP_SUBSTR(s, p, 1, 1, 'e', group) -> REGEXP_EXTRACT(s, p[, group])
13302 _ => {
13303 let subject = args.remove(0);
13304 let pattern = args.remove(0);
13305 let _position = args.remove(0);
13306 let _occurrence = args.remove(0);
13307 let _flags = args.remove(0);
13308 let group = args.remove(0);
13309 let is_group_0 = matches!(&group, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "0"));
13310 if is_group_0 {
13311 // Strip group=0 (default)
13312 Ok(Expression::Function(Box::new(Function::new(
13313 "REGEXP_EXTRACT".to_string(),
13314 vec![subject, pattern],
13315 ))))
13316 } else {
13317 Ok(Expression::Function(Box::new(Function::new(
13318 "REGEXP_EXTRACT".to_string(),
13319 vec![subject, pattern, group],
13320 ))))
13321 }
13322 }
13323 }
13324 } else {
13325 Ok(e)
13326 }
13327 }
13328
13329 Action::RegexpSubstrSnowflakeIdentity => {
13330 // Snowflake→Snowflake: REGEXP_SUBSTR/REGEXP_SUBSTR_ALL with 6 args
13331 // Strip trailing group=0
13332 if let Expression::Function(f) = e {
13333 let func_name = f.name.clone();
13334 let mut args = f.args;
13335 if args.len() == 6 {
13336 let is_group_0 = matches!(&args[5], Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "0"));
13337 if is_group_0 {
13338 args.truncate(5);
13339 }
13340 }
13341 Ok(Expression::Function(Box::new(Function::new(
13342 func_name, args,
13343 ))))
13344 } else {
13345 Ok(e)
13346 }
13347 }
13348
13349 Action::RegexpSubstrAllSnowflakeToDuckDB => {
13350 // Snowflake REGEXP_SUBSTR_ALL -> DuckDB REGEXP_EXTRACT_ALL variants
13351 if let Expression::Function(f) = e {
13352 let mut args = f.args;
13353 let arg_count = args.len();
13354 match arg_count {
13355 // REGEXP_SUBSTR_ALL(s, p) -> REGEXP_EXTRACT_ALL(s, p)
13356 0..=2 => Ok(Expression::Function(Box::new(Function::new(
13357 "REGEXP_EXTRACT_ALL".to_string(),
13358 args,
13359 )))),
13360 // REGEXP_SUBSTR_ALL(s, p, pos) -> REGEXP_EXTRACT_ALL(SUBSTRING(s, pos), p)
13361 3 => {
13362 let subject = args.remove(0);
13363 let pattern = args.remove(0);
13364 let position = args.remove(0);
13365 let is_pos_1 = matches!(&position, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "1"));
13366 if is_pos_1 {
13367 Ok(Expression::Function(Box::new(Function::new(
13368 "REGEXP_EXTRACT_ALL".to_string(),
13369 vec![subject, pattern],
13370 ))))
13371 } else {
13372 let substring_expr =
13373 Expression::Function(Box::new(Function::new(
13374 "SUBSTRING".to_string(),
13375 vec![subject, position],
13376 )));
13377 Ok(Expression::Function(Box::new(Function::new(
13378 "REGEXP_EXTRACT_ALL".to_string(),
13379 vec![substring_expr, pattern],
13380 ))))
13381 }
13382 }
13383 // REGEXP_SUBSTR_ALL(s, p, 1, occ) -> REGEXP_EXTRACT_ALL(s, p)[occ:]
13384 4 => {
13385 let subject = args.remove(0);
13386 let pattern = args.remove(0);
13387 let position = args.remove(0);
13388 let occurrence = args.remove(0);
13389 let is_pos_1 = matches!(&position, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "1"));
13390 let is_occ_1 = matches!(&occurrence, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "1"));
13391
13392 let effective_subject = if is_pos_1 {
13393 subject
13394 } else {
13395 Expression::Function(Box::new(Function::new(
13396 "SUBSTRING".to_string(),
13397 vec![subject, position],
13398 )))
13399 };
13400
13401 if is_occ_1 {
13402 Ok(Expression::Function(Box::new(Function::new(
13403 "REGEXP_EXTRACT_ALL".to_string(),
13404 vec![effective_subject, pattern],
13405 ))))
13406 } else {
13407 // REGEXP_EXTRACT_ALL(s, p)[occ:]
13408 let extract_all =
13409 Expression::Function(Box::new(Function::new(
13410 "REGEXP_EXTRACT_ALL".to_string(),
13411 vec![effective_subject, pattern],
13412 )));
13413 Ok(Expression::ArraySlice(Box::new(
13414 crate::expressions::ArraySlice {
13415 this: extract_all,
13416 start: Some(occurrence),
13417 end: None,
13418 },
13419 )))
13420 }
13421 }
13422 // REGEXP_SUBSTR_ALL(s, p, 1, 1, 'e') -> REGEXP_EXTRACT_ALL(s, p)
13423 5 => {
13424 let subject = args.remove(0);
13425 let pattern = args.remove(0);
13426 let _position = args.remove(0);
13427 let _occurrence = args.remove(0);
13428 let _flags = args.remove(0);
13429 Ok(Expression::Function(Box::new(Function::new(
13430 "REGEXP_EXTRACT_ALL".to_string(),
13431 vec![subject, pattern],
13432 ))))
13433 }
13434 // REGEXP_SUBSTR_ALL(s, p, 1, 1, 'e', 0) -> REGEXP_EXTRACT_ALL(s, p)
13435 _ => {
13436 let subject = args.remove(0);
13437 let pattern = args.remove(0);
13438 let _position = args.remove(0);
13439 let _occurrence = args.remove(0);
13440 let _flags = args.remove(0);
13441 let group = args.remove(0);
13442 let is_group_0 = matches!(&group, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "0"));
13443 if is_group_0 {
13444 Ok(Expression::Function(Box::new(Function::new(
13445 "REGEXP_EXTRACT_ALL".to_string(),
13446 vec![subject, pattern],
13447 ))))
13448 } else {
13449 Ok(Expression::Function(Box::new(Function::new(
13450 "REGEXP_EXTRACT_ALL".to_string(),
13451 vec![subject, pattern, group],
13452 ))))
13453 }
13454 }
13455 }
13456 } else {
13457 Ok(e)
13458 }
13459 }
13460
13461 Action::RegexpCountSnowflakeToDuckDB => {
13462 // Snowflake REGEXP_COUNT(s, p[, pos[, flags]]) ->
13463 // DuckDB: CASE WHEN p = '' THEN 0 ELSE LENGTH(REGEXP_EXTRACT_ALL(s, p)) END
13464 if let Expression::Function(f) = e {
13465 let mut args = f.args;
13466 let arg_count = args.len();
13467 let subject = args.remove(0);
13468 let pattern = args.remove(0);
13469
13470 // Handle position arg
13471 let effective_subject = if arg_count >= 3 {
13472 let position = args.remove(0);
13473 Expression::Function(Box::new(Function::new(
13474 "SUBSTRING".to_string(),
13475 vec![subject, position],
13476 )))
13477 } else {
13478 subject
13479 };
13480
13481 // Handle flags arg -> embed as (?flags) prefix in pattern
13482 let effective_pattern = if arg_count >= 4 {
13483 let flags = args.remove(0);
13484 match &flags {
13485 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(f_str) if !f_str.is_empty()) =>
13486 {
13487 let Literal::String(f_str) = lit.as_ref() else {
13488 unreachable!()
13489 };
13490 // Always use concatenation: '(?flags)' || pattern
13491 let prefix = Expression::Literal(Box::new(Literal::String(
13492 format!("(?{})", f_str),
13493 )));
13494 Expression::DPipe(Box::new(crate::expressions::DPipe {
13495 this: Box::new(prefix),
13496 expression: Box::new(pattern.clone()),
13497 safe: None,
13498 }))
13499 }
13500 _ => pattern.clone(),
13501 }
13502 } else {
13503 pattern.clone()
13504 };
13505
13506 // Build: CASE WHEN p = '' THEN 0 ELSE LENGTH(REGEXP_EXTRACT_ALL(s, p)) END
13507 let extract_all = Expression::Function(Box::new(Function::new(
13508 "REGEXP_EXTRACT_ALL".to_string(),
13509 vec![effective_subject, effective_pattern.clone()],
13510 )));
13511 let length_expr =
13512 Expression::Length(Box::new(crate::expressions::UnaryFunc {
13513 this: extract_all,
13514 original_name: None,
13515 inferred_type: None,
13516 }));
13517 let condition = Expression::Eq(Box::new(BinaryOp::new(
13518 effective_pattern,
13519 Expression::Literal(Box::new(Literal::String(String::new()))),
13520 )));
13521 Ok(Expression::Case(Box::new(Case {
13522 operand: None,
13523 whens: vec![(condition, Expression::number(0))],
13524 else_: Some(length_expr),
13525 comments: vec![],
13526 inferred_type: None,
13527 })))
13528 } else {
13529 Ok(e)
13530 }
13531 }
13532
13533 Action::RegexpInstrSnowflakeToDuckDB => {
13534 // Snowflake REGEXP_INSTR(s, p[, pos[, occ[, option[, flags[, group]]]]]) ->
13535 // DuckDB: CASE WHEN s IS NULL OR p IS NULL [OR ...] THEN NULL
13536 // WHEN p = '' THEN 0
13537 // WHEN LENGTH(REGEXP_EXTRACT_ALL(eff_s, eff_p)) < occ THEN 0
13538 // ELSE 1 + COALESCE(LIST_SUM(LIST_TRANSFORM(STRING_SPLIT_REGEX(eff_s, eff_p)[1:occ], x -> LENGTH(x))), 0)
13539 // + COALESCE(LIST_SUM(LIST_TRANSFORM(REGEXP_EXTRACT_ALL(eff_s, eff_p)[1:occ - 1], x -> LENGTH(x))), 0)
13540 // + pos_offset
13541 // END
13542 if let Expression::Function(f) = e {
13543 let mut args = f.args;
13544 let subject = args.remove(0);
13545 let pattern = if !args.is_empty() {
13546 args.remove(0)
13547 } else {
13548 Expression::Literal(Box::new(Literal::String(String::new())))
13549 };
13550
13551 // Collect all original args for NULL checks
13552 let position = if !args.is_empty() {
13553 Some(args.remove(0))
13554 } else {
13555 None
13556 };
13557 let occurrence = if !args.is_empty() {
13558 Some(args.remove(0))
13559 } else {
13560 None
13561 };
13562 let option = if !args.is_empty() {
13563 Some(args.remove(0))
13564 } else {
13565 None
13566 };
13567 let flags = if !args.is_empty() {
13568 Some(args.remove(0))
13569 } else {
13570 None
13571 };
13572 let _group = if !args.is_empty() {
13573 Some(args.remove(0))
13574 } else {
13575 None
13576 };
13577
13578 let is_pos_1 = position.as_ref().map_or(true, |p| matches!(p, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "1")));
13579 let occurrence_expr = occurrence.clone().unwrap_or(Expression::number(1));
13580
13581 // Build NULL check: subject IS NULL OR pattern IS NULL [OR pos IS NULL ...]
13582 let mut null_checks: Vec<Expression> = vec![
13583 Expression::Is(Box::new(BinaryOp::new(
13584 subject.clone(),
13585 Expression::Null(Null),
13586 ))),
13587 Expression::Is(Box::new(BinaryOp::new(
13588 pattern.clone(),
13589 Expression::Null(Null),
13590 ))),
13591 ];
13592 // Add NULL checks for all provided optional args
13593 for opt_arg in [&position, &occurrence, &option, &flags].iter() {
13594 if let Some(arg) = opt_arg {
13595 null_checks.push(Expression::Is(Box::new(BinaryOp::new(
13596 (*arg).clone(),
13597 Expression::Null(Null),
13598 ))));
13599 }
13600 }
13601 // Chain with OR
13602 let null_condition = null_checks
13603 .into_iter()
13604 .reduce(|a, b| Expression::Or(Box::new(BinaryOp::new(a, b))))
13605 .unwrap();
13606
13607 // Effective subject (apply position offset)
13608 let effective_subject = if is_pos_1 {
13609 subject.clone()
13610 } else {
13611 let pos = position.clone().unwrap_or(Expression::number(1));
13612 Expression::Function(Box::new(Function::new(
13613 "SUBSTRING".to_string(),
13614 vec![subject.clone(), pos],
13615 )))
13616 };
13617
13618 // Effective pattern (apply flags if present)
13619 let effective_pattern = if let Some(ref fl) = flags {
13620 if let Expression::Literal(lit) = fl {
13621 if let Literal::String(f_str) = lit.as_ref() {
13622 if !f_str.is_empty() {
13623 let prefix = Expression::Literal(Box::new(
13624 Literal::String(format!("(?{})", f_str)),
13625 ));
13626 Expression::DPipe(Box::new(crate::expressions::DPipe {
13627 this: Box::new(prefix),
13628 expression: Box::new(pattern.clone()),
13629 safe: None,
13630 }))
13631 } else {
13632 pattern.clone()
13633 }
13634 } else {
13635 fl.clone()
13636 }
13637 } else {
13638 pattern.clone()
13639 }
13640 } else {
13641 pattern.clone()
13642 };
13643
13644 // WHEN pattern = '' THEN 0
13645 let empty_pattern_check = Expression::Eq(Box::new(BinaryOp::new(
13646 effective_pattern.clone(),
13647 Expression::Literal(Box::new(Literal::String(String::new()))),
13648 )));
13649
13650 // WHEN LENGTH(REGEXP_EXTRACT_ALL(eff_s, eff_p)) < occ THEN 0
13651 let match_count_check = Expression::Lt(Box::new(BinaryOp::new(
13652 Expression::Length(Box::new(crate::expressions::UnaryFunc {
13653 this: Expression::Function(Box::new(Function::new(
13654 "REGEXP_EXTRACT_ALL".to_string(),
13655 vec![effective_subject.clone(), effective_pattern.clone()],
13656 ))),
13657 original_name: None,
13658 inferred_type: None,
13659 })),
13660 occurrence_expr.clone(),
13661 )));
13662
13663 // Helper: build LENGTH lambda for LIST_TRANSFORM
13664 let make_len_lambda = || {
13665 Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
13666 parameters: vec![crate::expressions::Identifier::new("x")],
13667 body: Expression::Length(Box::new(crate::expressions::UnaryFunc {
13668 this: Expression::Identifier(
13669 crate::expressions::Identifier::new("x"),
13670 ),
13671 original_name: None,
13672 inferred_type: None,
13673 })),
13674 colon: false,
13675 parameter_types: vec![],
13676 }))
13677 };
13678
13679 // COALESCE(LIST_SUM(LIST_TRANSFORM(STRING_SPLIT_REGEX(s, p)[1:occ], x -> LENGTH(x))), 0)
13680 let split_sliced =
13681 Expression::ArraySlice(Box::new(crate::expressions::ArraySlice {
13682 this: Expression::Function(Box::new(Function::new(
13683 "STRING_SPLIT_REGEX".to_string(),
13684 vec![effective_subject.clone(), effective_pattern.clone()],
13685 ))),
13686 start: Some(Expression::number(1)),
13687 end: Some(occurrence_expr.clone()),
13688 }));
13689 let split_sum = Expression::Function(Box::new(Function::new(
13690 "COALESCE".to_string(),
13691 vec![
13692 Expression::Function(Box::new(Function::new(
13693 "LIST_SUM".to_string(),
13694 vec![Expression::Function(Box::new(Function::new(
13695 "LIST_TRANSFORM".to_string(),
13696 vec![split_sliced, make_len_lambda()],
13697 )))],
13698 ))),
13699 Expression::number(0),
13700 ],
13701 )));
13702
13703 // COALESCE(LIST_SUM(LIST_TRANSFORM(REGEXP_EXTRACT_ALL(s, p)[1:occ - 1], x -> LENGTH(x))), 0)
13704 let extract_sliced =
13705 Expression::ArraySlice(Box::new(crate::expressions::ArraySlice {
13706 this: Expression::Function(Box::new(Function::new(
13707 "REGEXP_EXTRACT_ALL".to_string(),
13708 vec![effective_subject.clone(), effective_pattern.clone()],
13709 ))),
13710 start: Some(Expression::number(1)),
13711 end: Some(Expression::Sub(Box::new(BinaryOp::new(
13712 occurrence_expr.clone(),
13713 Expression::number(1),
13714 )))),
13715 }));
13716 let extract_sum = Expression::Function(Box::new(Function::new(
13717 "COALESCE".to_string(),
13718 vec![
13719 Expression::Function(Box::new(Function::new(
13720 "LIST_SUM".to_string(),
13721 vec![Expression::Function(Box::new(Function::new(
13722 "LIST_TRANSFORM".to_string(),
13723 vec![extract_sliced, make_len_lambda()],
13724 )))],
13725 ))),
13726 Expression::number(0),
13727 ],
13728 )));
13729
13730 // Position offset: pos - 1 when pos > 1, else 0
13731 let pos_offset: Expression = if !is_pos_1 {
13732 let pos = position.clone().unwrap_or(Expression::number(1));
13733 Expression::Sub(Box::new(BinaryOp::new(pos, Expression::number(1))))
13734 } else {
13735 Expression::number(0)
13736 };
13737
13738 // ELSE: 1 + split_sum + extract_sum + pos_offset
13739 let else_expr = Expression::Add(Box::new(BinaryOp::new(
13740 Expression::Add(Box::new(BinaryOp::new(
13741 Expression::Add(Box::new(BinaryOp::new(
13742 Expression::number(1),
13743 split_sum,
13744 ))),
13745 extract_sum,
13746 ))),
13747 pos_offset,
13748 )));
13749
13750 Ok(Expression::Case(Box::new(Case {
13751 operand: None,
13752 whens: vec![
13753 (null_condition, Expression::Null(Null)),
13754 (empty_pattern_check, Expression::number(0)),
13755 (match_count_check, Expression::number(0)),
13756 ],
13757 else_: Some(else_expr),
13758 comments: vec![],
13759 inferred_type: None,
13760 })))
13761 } else {
13762 Ok(e)
13763 }
13764 }
13765
13766 Action::RlikeSnowflakeToDuckDB => {
13767 // Snowflake RLIKE(a, b[, flags]) -> DuckDB REGEXP_FULL_MATCH(a, b[, flags])
13768 // Both do full-string matching, so no anchoring needed
13769 let (subject, pattern, flags) = match e {
13770 Expression::RegexpLike(ref rl) => {
13771 (rl.this.clone(), rl.pattern.clone(), rl.flags.clone())
13772 }
13773 Expression::Function(ref f) if f.args.len() >= 2 => {
13774 let s = f.args[0].clone();
13775 let p = f.args[1].clone();
13776 let fl = f.args.get(2).cloned();
13777 (s, p, fl)
13778 }
13779 _ => return Ok(e),
13780 };
13781
13782 let mut result_args = vec![subject, pattern];
13783 if let Some(fl) = flags {
13784 result_args.push(fl);
13785 }
13786 Ok(Expression::Function(Box::new(Function::new(
13787 "REGEXP_FULL_MATCH".to_string(),
13788 result_args,
13789 ))))
13790 }
13791
13792 Action::RegexpExtractAllToSnowflake => {
13793 // BigQuery REGEXP_EXTRACT_ALL(s, p) -> Snowflake REGEXP_SUBSTR_ALL(s, p)
13794 // With capture group: REGEXP_SUBSTR_ALL(s, p, 1, 1, 'c', 1)
13795 if let Expression::Function(f) = e {
13796 let mut args = f.args;
13797 if args.len() >= 2 {
13798 let str_expr = args.remove(0);
13799 let pattern = args.remove(0);
13800
13801 let has_groups = match &pattern {
13802 Expression::Literal(lit)
13803 if matches!(lit.as_ref(), Literal::String(_)) =>
13804 {
13805 let Literal::String(s) = lit.as_ref() else {
13806 unreachable!()
13807 };
13808 s.contains('(') && s.contains(')')
13809 }
13810 _ => false,
13811 };
13812
13813 if has_groups {
13814 Ok(Expression::Function(Box::new(Function::new(
13815 "REGEXP_SUBSTR_ALL".to_string(),
13816 vec![
13817 str_expr,
13818 pattern,
13819 Expression::number(1),
13820 Expression::number(1),
13821 Expression::Literal(Box::new(Literal::String(
13822 "c".to_string(),
13823 ))),
13824 Expression::number(1),
13825 ],
13826 ))))
13827 } else {
13828 Ok(Expression::Function(Box::new(Function::new(
13829 "REGEXP_SUBSTR_ALL".to_string(),
13830 vec![str_expr, pattern],
13831 ))))
13832 }
13833 } else {
13834 Ok(Expression::Function(Box::new(Function::new(
13835 "REGEXP_SUBSTR_ALL".to_string(),
13836 args,
13837 ))))
13838 }
13839 } else {
13840 Ok(e)
13841 }
13842 }
13843
13844 Action::SetToVariable => {
13845 // For DuckDB: SET a = 1 -> SET VARIABLE a = 1
13846 if let Expression::SetStatement(mut s) = e {
13847 for item in &mut s.items {
13848 if item.kind.is_none() {
13849 // Check if name already has VARIABLE prefix (from DuckDB source parsing)
13850 let already_variable = match &item.name {
13851 Expression::Identifier(id) => id.name.starts_with("VARIABLE "),
13852 _ => false,
13853 };
13854 if already_variable {
13855 // Extract the actual name and set kind
13856 if let Expression::Identifier(ref mut id) = item.name {
13857 let actual_name = id.name["VARIABLE ".len()..].to_string();
13858 id.name = actual_name;
13859 }
13860 }
13861 item.kind = Some("VARIABLE".to_string());
13862 }
13863 }
13864 Ok(Expression::SetStatement(s))
13865 } else {
13866 Ok(e)
13867 }
13868 }
13869
13870 Action::ConvertTimezoneToExpr => {
13871 // Convert Function("CONVERT_TIMEZONE", args) to Expression::ConvertTimezone
13872 // This prevents Redshift's transform_expr from expanding 2-arg to 3-arg with 'UTC'
13873 if let Expression::Function(f) = e {
13874 if f.args.len() == 2 {
13875 let mut args = f.args;
13876 let target_tz = args.remove(0);
13877 let timestamp = args.remove(0);
13878 Ok(Expression::ConvertTimezone(Box::new(ConvertTimezone {
13879 source_tz: None,
13880 target_tz: Some(Box::new(target_tz)),
13881 timestamp: Some(Box::new(timestamp)),
13882 options: vec![],
13883 })))
13884 } else if f.args.len() == 3 {
13885 let mut args = f.args;
13886 let source_tz = args.remove(0);
13887 let target_tz = args.remove(0);
13888 let timestamp = args.remove(0);
13889 Ok(Expression::ConvertTimezone(Box::new(ConvertTimezone {
13890 source_tz: Some(Box::new(source_tz)),
13891 target_tz: Some(Box::new(target_tz)),
13892 timestamp: Some(Box::new(timestamp)),
13893 options: vec![],
13894 })))
13895 } else {
13896 Ok(Expression::Function(f))
13897 }
13898 } else {
13899 Ok(e)
13900 }
13901 }
13902
13903 Action::BigQueryCastType => {
13904 // Convert BigQuery types to standard SQL types
13905 if let Expression::DataType(dt) = e {
13906 match dt {
13907 DataType::Custom { ref name } if name.eq_ignore_ascii_case("INT64") => {
13908 Ok(Expression::DataType(DataType::BigInt { length: None }))
13909 }
13910 DataType::Custom { ref name }
13911 if name.eq_ignore_ascii_case("FLOAT64") =>
13912 {
13913 Ok(Expression::DataType(DataType::Double {
13914 precision: None,
13915 scale: None,
13916 }))
13917 }
13918 DataType::Custom { ref name } if name.eq_ignore_ascii_case("BOOL") => {
13919 Ok(Expression::DataType(DataType::Boolean))
13920 }
13921 DataType::Custom { ref name } if name.eq_ignore_ascii_case("BYTES") => {
13922 Ok(Expression::DataType(DataType::VarBinary { length: None }))
13923 }
13924 DataType::Custom { ref name }
13925 if name.eq_ignore_ascii_case("NUMERIC") =>
13926 {
13927 // For DuckDB target, use Custom("DECIMAL") to avoid DuckDB's
13928 // default precision (18, 3) being added to bare DECIMAL
13929 if matches!(target, DialectType::DuckDB) {
13930 Ok(Expression::DataType(DataType::Custom {
13931 name: "DECIMAL".to_string(),
13932 }))
13933 } else {
13934 Ok(Expression::DataType(DataType::Decimal {
13935 precision: None,
13936 scale: None,
13937 }))
13938 }
13939 }
13940 DataType::Custom { ref name }
13941 if name.eq_ignore_ascii_case("STRING") =>
13942 {
13943 Ok(Expression::DataType(DataType::String { length: None }))
13944 }
13945 DataType::Custom { ref name }
13946 if name.eq_ignore_ascii_case("DATETIME") =>
13947 {
13948 Ok(Expression::DataType(DataType::Timestamp {
13949 precision: None,
13950 timezone: false,
13951 }))
13952 }
13953 _ => Ok(Expression::DataType(dt)),
13954 }
13955 } else {
13956 Ok(e)
13957 }
13958 }
13959
13960 Action::BigQuerySafeDivide => {
13961 // Convert SafeDivide expression to IF/CASE form for most targets
13962 if let Expression::SafeDivide(sd) = e {
13963 let x = *sd.this;
13964 let y = *sd.expression;
13965 // Wrap x and y in parens if they're complex expressions
13966 let y_ref = match &y {
13967 Expression::Column(_)
13968 | Expression::Literal(_)
13969 | Expression::Identifier(_) => y.clone(),
13970 _ => Expression::Paren(Box::new(Paren {
13971 this: y.clone(),
13972 trailing_comments: vec![],
13973 })),
13974 };
13975 let x_ref = match &x {
13976 Expression::Column(_)
13977 | Expression::Literal(_)
13978 | Expression::Identifier(_) => x.clone(),
13979 _ => Expression::Paren(Box::new(Paren {
13980 this: x.clone(),
13981 trailing_comments: vec![],
13982 })),
13983 };
13984 let condition = Expression::Neq(Box::new(BinaryOp::new(
13985 y_ref.clone(),
13986 Expression::number(0),
13987 )));
13988 let div_expr = Expression::Div(Box::new(BinaryOp::new(x_ref, y_ref)));
13989
13990 if matches!(target, DialectType::Spark | DialectType::Databricks) {
13991 Ok(Expression::Function(Box::new(Function::new(
13992 "TRY_DIVIDE".to_string(),
13993 vec![x, y],
13994 ))))
13995 } else if matches!(target, DialectType::Presto | DialectType::Trino) {
13996 // Presto/Trino: IF(y <> 0, CAST(x AS DOUBLE) / y, NULL)
13997 let cast_x = Expression::Cast(Box::new(Cast {
13998 this: match &x {
13999 Expression::Column(_)
14000 | Expression::Literal(_)
14001 | Expression::Identifier(_) => x,
14002 _ => Expression::Paren(Box::new(Paren {
14003 this: x,
14004 trailing_comments: vec![],
14005 })),
14006 },
14007 to: DataType::Double {
14008 precision: None,
14009 scale: None,
14010 },
14011 trailing_comments: vec![],
14012 double_colon_syntax: false,
14013 format: None,
14014 default: None,
14015 inferred_type: None,
14016 }));
14017 let cast_div = Expression::Div(Box::new(BinaryOp::new(
14018 cast_x,
14019 match &y {
14020 Expression::Column(_)
14021 | Expression::Literal(_)
14022 | Expression::Identifier(_) => y,
14023 _ => Expression::Paren(Box::new(Paren {
14024 this: y,
14025 trailing_comments: vec![],
14026 })),
14027 },
14028 )));
14029 Ok(Expression::IfFunc(Box::new(crate::expressions::IfFunc {
14030 condition,
14031 true_value: cast_div,
14032 false_value: Some(Expression::Null(Null)),
14033 original_name: None,
14034 inferred_type: None,
14035 })))
14036 } else if matches!(target, DialectType::PostgreSQL) {
14037 // PostgreSQL: CASE WHEN y <> 0 THEN CAST(x AS DOUBLE PRECISION) / y ELSE NULL END
14038 let cast_x = Expression::Cast(Box::new(Cast {
14039 this: match &x {
14040 Expression::Column(_)
14041 | Expression::Literal(_)
14042 | Expression::Identifier(_) => x,
14043 _ => Expression::Paren(Box::new(Paren {
14044 this: x,
14045 trailing_comments: vec![],
14046 })),
14047 },
14048 to: DataType::Custom {
14049 name: "DOUBLE PRECISION".to_string(),
14050 },
14051 trailing_comments: vec![],
14052 double_colon_syntax: false,
14053 format: None,
14054 default: None,
14055 inferred_type: None,
14056 }));
14057 let y_paren = match &y {
14058 Expression::Column(_)
14059 | Expression::Literal(_)
14060 | Expression::Identifier(_) => y,
14061 _ => Expression::Paren(Box::new(Paren {
14062 this: y,
14063 trailing_comments: vec![],
14064 })),
14065 };
14066 let cast_div =
14067 Expression::Div(Box::new(BinaryOp::new(cast_x, y_paren)));
14068 Ok(Expression::Case(Box::new(Case {
14069 operand: None,
14070 whens: vec![(condition, cast_div)],
14071 else_: Some(Expression::Null(Null)),
14072 comments: Vec::new(),
14073 inferred_type: None,
14074 })))
14075 } else if matches!(target, DialectType::DuckDB) {
14076 // DuckDB: CASE WHEN y <> 0 THEN x / y ELSE NULL END
14077 Ok(Expression::Case(Box::new(Case {
14078 operand: None,
14079 whens: vec![(condition, div_expr)],
14080 else_: Some(Expression::Null(Null)),
14081 comments: Vec::new(),
14082 inferred_type: None,
14083 })))
14084 } else if matches!(target, DialectType::Snowflake) {
14085 // Snowflake: IFF(y <> 0, x / y, NULL)
14086 Ok(Expression::IfFunc(Box::new(crate::expressions::IfFunc {
14087 condition,
14088 true_value: div_expr,
14089 false_value: Some(Expression::Null(Null)),
14090 original_name: Some("IFF".to_string()),
14091 inferred_type: None,
14092 })))
14093 } else {
14094 // All others: IF(y <> 0, x / y, NULL)
14095 Ok(Expression::IfFunc(Box::new(crate::expressions::IfFunc {
14096 condition,
14097 true_value: div_expr,
14098 false_value: Some(Expression::Null(Null)),
14099 original_name: None,
14100 inferred_type: None,
14101 })))
14102 }
14103 } else {
14104 Ok(e)
14105 }
14106 }
14107
14108 Action::BigQueryLastDayStripUnit => {
14109 if let Expression::LastDay(mut ld) = e {
14110 ld.unit = None; // Strip the unit (MONTH is default)
14111 match target {
14112 DialectType::PostgreSQL => {
14113 // LAST_DAY(date) -> CAST(DATE_TRUNC('MONTH', date) + INTERVAL '1 MONTH' - INTERVAL '1 DAY' AS DATE)
14114 let date_trunc = Expression::Function(Box::new(Function::new(
14115 "DATE_TRUNC".to_string(),
14116 vec![
14117 Expression::Literal(Box::new(
14118 crate::expressions::Literal::String(
14119 "MONTH".to_string(),
14120 ),
14121 )),
14122 ld.this.clone(),
14123 ],
14124 )));
14125 let plus_month =
14126 Expression::Add(Box::new(crate::expressions::BinaryOp::new(
14127 date_trunc,
14128 Expression::Interval(Box::new(
14129 crate::expressions::Interval {
14130 this: Some(Expression::Literal(Box::new(
14131 crate::expressions::Literal::String(
14132 "1 MONTH".to_string(),
14133 ),
14134 ))),
14135 unit: None,
14136 },
14137 )),
14138 )));
14139 let minus_day =
14140 Expression::Sub(Box::new(crate::expressions::BinaryOp::new(
14141 plus_month,
14142 Expression::Interval(Box::new(
14143 crate::expressions::Interval {
14144 this: Some(Expression::Literal(Box::new(
14145 crate::expressions::Literal::String(
14146 "1 DAY".to_string(),
14147 ),
14148 ))),
14149 unit: None,
14150 },
14151 )),
14152 )));
14153 Ok(Expression::Cast(Box::new(Cast {
14154 this: minus_day,
14155 to: DataType::Date,
14156 trailing_comments: vec![],
14157 double_colon_syntax: false,
14158 format: None,
14159 default: None,
14160 inferred_type: None,
14161 })))
14162 }
14163 DialectType::Presto => {
14164 // LAST_DAY(date) -> LAST_DAY_OF_MONTH(date)
14165 Ok(Expression::Function(Box::new(Function::new(
14166 "LAST_DAY_OF_MONTH".to_string(),
14167 vec![ld.this],
14168 ))))
14169 }
14170 DialectType::ClickHouse => {
14171 // ClickHouse LAST_DAY(CAST(x AS Nullable(DATE)))
14172 // Need to wrap the DATE type in Nullable
14173 let nullable_date = match ld.this {
14174 Expression::Cast(mut c) => {
14175 c.to = DataType::Nullable {
14176 inner: Box::new(DataType::Date),
14177 };
14178 Expression::Cast(c)
14179 }
14180 other => other,
14181 };
14182 ld.this = nullable_date;
14183 Ok(Expression::LastDay(ld))
14184 }
14185 _ => Ok(Expression::LastDay(ld)),
14186 }
14187 } else {
14188 Ok(e)
14189 }
14190 }
14191
14192 Action::BigQueryCastFormat => {
14193 // CAST(x AS DATE FORMAT 'fmt') -> PARSE_DATE('%m/%d/%Y', x) for BigQuery
14194 // CAST(x AS TIMESTAMP FORMAT 'fmt') -> PARSE_TIMESTAMP(...) for BigQuery
14195 // SAFE_CAST(x AS DATE FORMAT 'fmt') -> CAST(TRY_STRPTIME(x, ...) AS DATE) for DuckDB
14196 let (this, to, format_expr, is_safe) = match e {
14197 Expression::Cast(ref c) if c.format.is_some() => (
14198 c.this.clone(),
14199 c.to.clone(),
14200 c.format.as_ref().unwrap().as_ref().clone(),
14201 false,
14202 ),
14203 Expression::SafeCast(ref c) if c.format.is_some() => (
14204 c.this.clone(),
14205 c.to.clone(),
14206 c.format.as_ref().unwrap().as_ref().clone(),
14207 true,
14208 ),
14209 _ => return Ok(e),
14210 };
14211 // For CAST(x AS STRING FORMAT ...) when target is BigQuery, keep as-is
14212 if matches!(target, DialectType::BigQuery) {
14213 match &to {
14214 DataType::String { .. } | DataType::VarChar { .. } | DataType::Text => {
14215 // CAST(x AS STRING FORMAT 'fmt') stays as CAST expression for BigQuery
14216 return Ok(e);
14217 }
14218 _ => {}
14219 }
14220 }
14221 // Extract timezone from format if AT TIME ZONE is present
14222 let (actual_format_expr, timezone) = match &format_expr {
14223 Expression::AtTimeZone(ref atz) => {
14224 (atz.this.clone(), Some(atz.zone.clone()))
14225 }
14226 _ => (format_expr.clone(), None),
14227 };
14228 let strftime_fmt = Self::bq_cast_format_to_strftime(&actual_format_expr);
14229 match target {
14230 DialectType::BigQuery => {
14231 // CAST(x AS DATE FORMAT 'fmt') -> PARSE_DATE(strftime_fmt, x)
14232 // CAST(x AS TIMESTAMP FORMAT 'fmt' AT TIME ZONE 'tz') -> PARSE_TIMESTAMP(strftime_fmt, x, tz)
14233 let func_name = match &to {
14234 DataType::Date => "PARSE_DATE",
14235 DataType::Timestamp { .. } => "PARSE_TIMESTAMP",
14236 DataType::Time { .. } => "PARSE_TIMESTAMP",
14237 _ => "PARSE_TIMESTAMP",
14238 };
14239 let mut func_args = vec![strftime_fmt, this];
14240 if let Some(tz) = timezone {
14241 func_args.push(tz);
14242 }
14243 Ok(Expression::Function(Box::new(Function::new(
14244 func_name.to_string(),
14245 func_args,
14246 ))))
14247 }
14248 DialectType::DuckDB => {
14249 // SAFE_CAST(x AS DATE FORMAT 'fmt') -> CAST(TRY_STRPTIME(x, fmt) AS DATE)
14250 // CAST(x AS DATE FORMAT 'fmt') -> CAST(STRPTIME(x, fmt) AS DATE)
14251 let duck_fmt = Self::bq_format_to_duckdb(&strftime_fmt);
14252 let parse_fn_name = if is_safe { "TRY_STRPTIME" } else { "STRPTIME" };
14253 let parse_call = Expression::Function(Box::new(Function::new(
14254 parse_fn_name.to_string(),
14255 vec![this, duck_fmt],
14256 )));
14257 Ok(Expression::Cast(Box::new(Cast {
14258 this: parse_call,
14259 to,
14260 trailing_comments: vec![],
14261 double_colon_syntax: false,
14262 format: None,
14263 default: None,
14264 inferred_type: None,
14265 })))
14266 }
14267 _ => Ok(e),
14268 }
14269 }
14270
14271 Action::BigQueryFunctionNormalize => {
14272 Self::normalize_bigquery_function(e, source, target)
14273 }
14274
14275 Action::BigQueryToHexBare => {
14276 // Not used anymore - handled directly in normalize_bigquery_function
14277 Ok(e)
14278 }
14279
14280 Action::BigQueryToHexLower => {
14281 if let Expression::Lower(uf) = e {
14282 match uf.this {
14283 // BQ->BQ: LOWER(TO_HEX(x)) -> TO_HEX(x)
14284 Expression::Function(f)
14285 if matches!(target, DialectType::BigQuery)
14286 && f.name == "TO_HEX" =>
14287 {
14288 Ok(Expression::Function(f))
14289 }
14290 // LOWER(LOWER(HEX/TO_HEX(x))) patterns
14291 Expression::Lower(inner_uf) => {
14292 if matches!(target, DialectType::BigQuery) {
14293 // BQ->BQ: extract TO_HEX
14294 if let Expression::Function(f) = inner_uf.this {
14295 Ok(Expression::Function(Box::new(Function::new(
14296 "TO_HEX".to_string(),
14297 f.args,
14298 ))))
14299 } else {
14300 Ok(Expression::Lower(inner_uf))
14301 }
14302 } else {
14303 // Flatten: LOWER(LOWER(x)) -> LOWER(x)
14304 Ok(Expression::Lower(inner_uf))
14305 }
14306 }
14307 other => {
14308 Ok(Expression::Lower(Box::new(crate::expressions::UnaryFunc {
14309 this: other,
14310 original_name: None,
14311 inferred_type: None,
14312 })))
14313 }
14314 }
14315 } else {
14316 Ok(e)
14317 }
14318 }
14319
14320 Action::BigQueryToHexUpper => {
14321 // UPPER(LOWER(HEX(x))) -> HEX(x) (UPPER cancels LOWER, HEX is already uppercase)
14322 // UPPER(LOWER(TO_HEX(x))) -> TO_HEX(x) for Presto/Trino
14323 if let Expression::Upper(uf) = e {
14324 if let Expression::Lower(inner_uf) = uf.this {
14325 // For BQ->BQ: UPPER(TO_HEX(x)) should stay as UPPER(TO_HEX(x))
14326 if matches!(target, DialectType::BigQuery) {
14327 // Restore TO_HEX name in inner function
14328 if let Expression::Function(f) = inner_uf.this {
14329 let restored = Expression::Function(Box::new(Function::new(
14330 "TO_HEX".to_string(),
14331 f.args,
14332 )));
14333 Ok(Expression::Upper(Box::new(
14334 crate::expressions::UnaryFunc::new(restored),
14335 )))
14336 } else {
14337 Ok(Expression::Upper(inner_uf))
14338 }
14339 } else {
14340 // Extract the inner HEX/TO_HEX function (UPPER(LOWER(x)) = x when HEX is uppercase)
14341 Ok(inner_uf.this)
14342 }
14343 } else {
14344 Ok(Expression::Upper(uf))
14345 }
14346 } else {
14347 Ok(e)
14348 }
14349 }
14350
14351 Action::BigQueryAnyValueHaving => {
14352 // ANY_VALUE(x HAVING MAX y) -> ARG_MAX_NULL(x, y)
14353 // ANY_VALUE(x HAVING MIN y) -> ARG_MIN_NULL(x, y)
14354 if let Expression::AnyValue(agg) = e {
14355 if let Some((having_expr, is_max)) = agg.having_max {
14356 let func_name = if is_max {
14357 "ARG_MAX_NULL"
14358 } else {
14359 "ARG_MIN_NULL"
14360 };
14361 Ok(Expression::Function(Box::new(Function::new(
14362 func_name.to_string(),
14363 vec![agg.this, *having_expr],
14364 ))))
14365 } else {
14366 Ok(Expression::AnyValue(agg))
14367 }
14368 } else {
14369 Ok(e)
14370 }
14371 }
14372
14373 Action::BigQueryApproxQuantiles => {
14374 // APPROX_QUANTILES(x, n) -> APPROX_QUANTILE(x, [0, 1/n, 2/n, ..., 1])
14375 // APPROX_QUANTILES(DISTINCT x, n) -> APPROX_QUANTILE(DISTINCT x, [0, 1/n, ..., 1])
14376 if let Expression::AggregateFunction(agg) = e {
14377 if agg.args.len() >= 2 {
14378 let x_expr = agg.args[0].clone();
14379 let n_expr = &agg.args[1];
14380
14381 // Extract the numeric value from n_expr
14382 let n = match n_expr {
14383 Expression::Literal(lit)
14384 if matches!(
14385 lit.as_ref(),
14386 crate::expressions::Literal::Number(_)
14387 ) =>
14388 {
14389 let crate::expressions::Literal::Number(s) = lit.as_ref()
14390 else {
14391 unreachable!()
14392 };
14393 s.parse::<usize>().unwrap_or(2)
14394 }
14395 _ => 2,
14396 };
14397
14398 // Generate quantile array: [0, 1/n, 2/n, ..., 1]
14399 let mut quantiles = Vec::new();
14400 for i in 0..=n {
14401 let q = i as f64 / n as f64;
14402 // Format nicely: 0 -> 0, 0.25 -> 0.25, 1 -> 1
14403 if q == 0.0 {
14404 quantiles.push(Expression::number(0));
14405 } else if q == 1.0 {
14406 quantiles.push(Expression::number(1));
14407 } else {
14408 quantiles.push(Expression::Literal(Box::new(
14409 crate::expressions::Literal::Number(format!("{}", q)),
14410 )));
14411 }
14412 }
14413
14414 let array_expr =
14415 Expression::Array(Box::new(crate::expressions::Array {
14416 expressions: quantiles,
14417 }));
14418
14419 // Preserve DISTINCT modifier
14420 let mut new_func = Function::new(
14421 "APPROX_QUANTILE".to_string(),
14422 vec![x_expr, array_expr],
14423 );
14424 new_func.distinct = agg.distinct;
14425 Ok(Expression::Function(Box::new(new_func)))
14426 } else {
14427 Ok(Expression::AggregateFunction(agg))
14428 }
14429 } else {
14430 Ok(e)
14431 }
14432 }
14433
14434 Action::GenericFunctionNormalize => {
14435 // Helper closure to convert ARBITRARY to target-specific function
14436 fn convert_arbitrary(arg: Expression, target: DialectType) -> Expression {
14437 let name = match target {
14438 DialectType::ClickHouse => "any",
14439 DialectType::TSQL | DialectType::SQLite => "MAX",
14440 DialectType::Hive => "FIRST",
14441 DialectType::Presto | DialectType::Trino | DialectType::Athena => {
14442 "ARBITRARY"
14443 }
14444 _ => "ANY_VALUE",
14445 };
14446 Expression::Function(Box::new(Function::new(name.to_string(), vec![arg])))
14447 }
14448
14449 if let Expression::Function(f) = e {
14450 let name = f.name.to_ascii_uppercase();
14451 match name.as_str() {
14452 "ARBITRARY" if f.args.len() == 1 => {
14453 let arg = f.args.into_iter().next().unwrap();
14454 Ok(convert_arbitrary(arg, target))
14455 }
14456 "TO_NUMBER" if f.args.len() == 1 => {
14457 let arg = f.args.into_iter().next().unwrap();
14458 match target {
14459 DialectType::Oracle | DialectType::Snowflake => {
14460 Ok(Expression::Function(Box::new(Function::new(
14461 "TO_NUMBER".to_string(),
14462 vec![arg],
14463 ))))
14464 }
14465 _ => Ok(Expression::Cast(Box::new(crate::expressions::Cast {
14466 this: arg,
14467 to: crate::expressions::DataType::Double {
14468 precision: None,
14469 scale: None,
14470 },
14471 double_colon_syntax: false,
14472 trailing_comments: Vec::new(),
14473 format: None,
14474 default: None,
14475 inferred_type: None,
14476 }))),
14477 }
14478 }
14479 "AGGREGATE" if f.args.len() >= 3 => match target {
14480 DialectType::DuckDB
14481 | DialectType::Hive
14482 | DialectType::Presto
14483 | DialectType::Trino => Ok(Expression::Function(Box::new(
14484 Function::new("REDUCE".to_string(), f.args),
14485 ))),
14486 _ => Ok(Expression::Function(f)),
14487 },
14488 // REGEXP_MATCHES(x, y) -> RegexpLike for most targets, keep as-is for DuckDB
14489 "REGEXP_MATCHES" if f.args.len() >= 2 => {
14490 if matches!(target, DialectType::DuckDB) {
14491 Ok(Expression::Function(f))
14492 } else {
14493 let mut args = f.args;
14494 let this = args.remove(0);
14495 let pattern = args.remove(0);
14496 let flags = if args.is_empty() {
14497 None
14498 } else {
14499 Some(args.remove(0))
14500 };
14501 Ok(Expression::RegexpLike(Box::new(
14502 crate::expressions::RegexpFunc {
14503 this,
14504 pattern,
14505 flags,
14506 },
14507 )))
14508 }
14509 }
14510 // REGEXP_FULL_MATCH (Hive REGEXP) -> RegexpLike
14511 "REGEXP_FULL_MATCH" if f.args.len() >= 2 => {
14512 if matches!(target, DialectType::DuckDB) {
14513 Ok(Expression::Function(f))
14514 } else {
14515 let mut args = f.args;
14516 let this = args.remove(0);
14517 let pattern = args.remove(0);
14518 let flags = if args.is_empty() {
14519 None
14520 } else {
14521 Some(args.remove(0))
14522 };
14523 Ok(Expression::RegexpLike(Box::new(
14524 crate::expressions::RegexpFunc {
14525 this,
14526 pattern,
14527 flags,
14528 },
14529 )))
14530 }
14531 }
14532 // STRUCT_EXTRACT(x, 'field') -> x.field (StructExtract expression)
14533 "STRUCT_EXTRACT" if f.args.len() == 2 => {
14534 let mut args = f.args;
14535 let this = args.remove(0);
14536 let field_expr = args.remove(0);
14537 // Extract string literal to get field name
14538 let field_name = match &field_expr {
14539 Expression::Literal(lit)
14540 if matches!(
14541 lit.as_ref(),
14542 crate::expressions::Literal::String(_)
14543 ) =>
14544 {
14545 let crate::expressions::Literal::String(s) = lit.as_ref()
14546 else {
14547 unreachable!()
14548 };
14549 s.clone()
14550 }
14551 Expression::Identifier(id) => id.name.clone(),
14552 _ => {
14553 return Ok(Expression::Function(Box::new(Function::new(
14554 "STRUCT_EXTRACT".to_string(),
14555 vec![this, field_expr],
14556 ))))
14557 }
14558 };
14559 Ok(Expression::StructExtract(Box::new(
14560 crate::expressions::StructExtractFunc {
14561 this,
14562 field: crate::expressions::Identifier::new(field_name),
14563 },
14564 )))
14565 }
14566 // LIST_FILTER([4,5,6], x -> x > 4) -> FILTER(ARRAY(4,5,6), x -> x > 4)
14567 "LIST_FILTER" if f.args.len() == 2 => {
14568 let name = match target {
14569 DialectType::DuckDB => "LIST_FILTER",
14570 _ => "FILTER",
14571 };
14572 Ok(Expression::Function(Box::new(Function::new(
14573 name.to_string(),
14574 f.args,
14575 ))))
14576 }
14577 // LIST_TRANSFORM(x, y -> y + 1) -> TRANSFORM(x, y -> y + 1)
14578 "LIST_TRANSFORM" if f.args.len() == 2 => {
14579 let name = match target {
14580 DialectType::DuckDB => "LIST_TRANSFORM",
14581 _ => "TRANSFORM",
14582 };
14583 Ok(Expression::Function(Box::new(Function::new(
14584 name.to_string(),
14585 f.args,
14586 ))))
14587 }
14588 // LIST_SORT(x) -> LIST_SORT(x) for DuckDB, ARRAY_SORT(x) for Presto/Trino, SORT_ARRAY(x) for others
14589 "LIST_SORT" if f.args.len() >= 1 => {
14590 let name = match target {
14591 DialectType::DuckDB => "LIST_SORT",
14592 DialectType::Presto | DialectType::Trino => "ARRAY_SORT",
14593 _ => "SORT_ARRAY",
14594 };
14595 Ok(Expression::Function(Box::new(Function::new(
14596 name.to_string(),
14597 f.args,
14598 ))))
14599 }
14600 // LIST_REVERSE_SORT(x) -> SORT_ARRAY(x, FALSE) for Spark/Hive, ARRAY_SORT(x, lambda) for Presto
14601 "LIST_REVERSE_SORT" if f.args.len() >= 1 => {
14602 match target {
14603 DialectType::DuckDB => Ok(Expression::Function(Box::new(
14604 Function::new("ARRAY_REVERSE_SORT".to_string(), f.args),
14605 ))),
14606 DialectType::Spark
14607 | DialectType::Databricks
14608 | DialectType::Hive => {
14609 let mut args = f.args;
14610 args.push(Expression::Identifier(
14611 crate::expressions::Identifier::new("FALSE"),
14612 ));
14613 Ok(Expression::Function(Box::new(Function::new(
14614 "SORT_ARRAY".to_string(),
14615 args,
14616 ))))
14617 }
14618 DialectType::Presto
14619 | DialectType::Trino
14620 | DialectType::Athena => {
14621 // ARRAY_SORT(x, (a, b) -> CASE WHEN a < b THEN 1 WHEN a > b THEN -1 ELSE 0 END)
14622 let arr = f.args.into_iter().next().unwrap();
14623 let lambda = Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
14624 parameters: vec![
14625 crate::expressions::Identifier::new("a"),
14626 crate::expressions::Identifier::new("b"),
14627 ],
14628 body: Expression::Case(Box::new(Case {
14629 operand: None,
14630 whens: vec![
14631 (
14632 Expression::Lt(Box::new(BinaryOp::new(
14633 Expression::Identifier(crate::expressions::Identifier::new("a")),
14634 Expression::Identifier(crate::expressions::Identifier::new("b")),
14635 ))),
14636 Expression::number(1),
14637 ),
14638 (
14639 Expression::Gt(Box::new(BinaryOp::new(
14640 Expression::Identifier(crate::expressions::Identifier::new("a")),
14641 Expression::Identifier(crate::expressions::Identifier::new("b")),
14642 ))),
14643 Expression::Literal(Box::new(Literal::Number("-1".to_string()))),
14644 ),
14645 ],
14646 else_: Some(Expression::number(0)),
14647 comments: Vec::new(),
14648 inferred_type: None,
14649 })),
14650 colon: false,
14651 parameter_types: Vec::new(),
14652 }));
14653 Ok(Expression::Function(Box::new(Function::new(
14654 "ARRAY_SORT".to_string(),
14655 vec![arr, lambda],
14656 ))))
14657 }
14658 _ => Ok(Expression::Function(Box::new(Function::new(
14659 "LIST_REVERSE_SORT".to_string(),
14660 f.args,
14661 )))),
14662 }
14663 }
14664 // SPLIT_TO_ARRAY(x) with 1 arg -> add default ',' separator and rename
14665 "SPLIT_TO_ARRAY" if f.args.len() == 1 => {
14666 let mut args = f.args;
14667 args.push(Expression::string(","));
14668 let name = match target {
14669 DialectType::DuckDB => "STR_SPLIT",
14670 DialectType::Presto | DialectType::Trino => "SPLIT",
14671 DialectType::Spark
14672 | DialectType::Databricks
14673 | DialectType::Hive => "SPLIT",
14674 DialectType::PostgreSQL => "STRING_TO_ARRAY",
14675 DialectType::Redshift => "SPLIT_TO_ARRAY",
14676 _ => "SPLIT",
14677 };
14678 Ok(Expression::Function(Box::new(Function::new(
14679 name.to_string(),
14680 args,
14681 ))))
14682 }
14683 // SPLIT_TO_ARRAY(x, sep) with 2 args -> rename based on target
14684 "SPLIT_TO_ARRAY" if f.args.len() == 2 => {
14685 let name = match target {
14686 DialectType::DuckDB => "STR_SPLIT",
14687 DialectType::Presto | DialectType::Trino => "SPLIT",
14688 DialectType::Spark
14689 | DialectType::Databricks
14690 | DialectType::Hive => "SPLIT",
14691 DialectType::PostgreSQL => "STRING_TO_ARRAY",
14692 DialectType::Redshift => "SPLIT_TO_ARRAY",
14693 _ => "SPLIT",
14694 };
14695 Ok(Expression::Function(Box::new(Function::new(
14696 name.to_string(),
14697 f.args,
14698 ))))
14699 }
14700 // STRING_TO_ARRAY/STR_SPLIT -> target-specific split function
14701 "STRING_TO_ARRAY" | "STR_SPLIT" if f.args.len() >= 2 => {
14702 let name = match target {
14703 DialectType::DuckDB => "STR_SPLIT",
14704 DialectType::Presto | DialectType::Trino => "SPLIT",
14705 DialectType::Spark
14706 | DialectType::Databricks
14707 | DialectType::Hive => "SPLIT",
14708 DialectType::Doris | DialectType::StarRocks => {
14709 "SPLIT_BY_STRING"
14710 }
14711 DialectType::TSQL | DialectType::Fabric
14712 if name == "STRING_TO_ARRAY" =>
14713 {
14714 "STRING_TO_ARRAY"
14715 }
14716 DialectType::PostgreSQL | DialectType::Redshift => {
14717 "STRING_TO_ARRAY"
14718 }
14719 _ => "SPLIT",
14720 };
14721 // For Spark/Hive, SPLIT uses regex - need to escape literal with \Q...\E
14722 if matches!(
14723 target,
14724 DialectType::Spark
14725 | DialectType::Databricks
14726 | DialectType::Hive
14727 ) {
14728 let mut args = f.args;
14729 let x = args.remove(0);
14730 let sep = args.remove(0);
14731 // Wrap separator in CONCAT('\\Q', sep, '\\E')
14732 let escaped_sep =
14733 Expression::Function(Box::new(Function::new(
14734 "CONCAT".to_string(),
14735 vec![
14736 Expression::string("\\Q"),
14737 sep,
14738 Expression::string("\\E"),
14739 ],
14740 )));
14741 Ok(Expression::Function(Box::new(Function::new(
14742 name.to_string(),
14743 vec![x, escaped_sep],
14744 ))))
14745 } else {
14746 Ok(Expression::Function(Box::new(Function::new(
14747 name.to_string(),
14748 f.args,
14749 ))))
14750 }
14751 }
14752 // STR_SPLIT_REGEX(x, 'a') / REGEXP_SPLIT(x, 'a') -> target-specific regex split
14753 "STR_SPLIT_REGEX" | "REGEXP_SPLIT" if f.args.len() == 2 => {
14754 let name = match target {
14755 DialectType::DuckDB => "STR_SPLIT_REGEX",
14756 DialectType::Presto | DialectType::Trino => "REGEXP_SPLIT",
14757 DialectType::Spark
14758 | DialectType::Databricks
14759 | DialectType::Hive => "SPLIT",
14760 _ => "REGEXP_SPLIT",
14761 };
14762 Ok(Expression::Function(Box::new(Function::new(
14763 name.to_string(),
14764 f.args,
14765 ))))
14766 }
14767 // SPLIT(str, delim) from Snowflake -> DuckDB with CASE wrapper
14768 "SPLIT"
14769 if f.args.len() == 2
14770 && matches!(source, DialectType::Snowflake)
14771 && matches!(target, DialectType::DuckDB) =>
14772 {
14773 let mut args = f.args;
14774 let str_arg = args.remove(0);
14775 let delim_arg = args.remove(0);
14776
14777 // STR_SPLIT(str, delim) as the base
14778 let base_func = Expression::Function(Box::new(Function::new(
14779 "STR_SPLIT".to_string(),
14780 vec![str_arg.clone(), delim_arg.clone()],
14781 )));
14782
14783 // [str] - array with single element
14784 let array_with_input =
14785 Expression::Array(Box::new(crate::expressions::Array {
14786 expressions: vec![str_arg],
14787 }));
14788
14789 // CASE
14790 // WHEN delim IS NULL THEN NULL
14791 // WHEN delim = '' THEN [str]
14792 // ELSE STR_SPLIT(str, delim)
14793 // END
14794 Ok(Expression::Case(Box::new(Case {
14795 operand: None,
14796 whens: vec![
14797 (
14798 Expression::Is(Box::new(BinaryOp {
14799 left: delim_arg.clone(),
14800 right: Expression::Null(Null),
14801 left_comments: vec![],
14802 operator_comments: vec![],
14803 trailing_comments: vec![],
14804 inferred_type: None,
14805 })),
14806 Expression::Null(Null),
14807 ),
14808 (
14809 Expression::Eq(Box::new(BinaryOp {
14810 left: delim_arg,
14811 right: Expression::string(""),
14812 left_comments: vec![],
14813 operator_comments: vec![],
14814 trailing_comments: vec![],
14815 inferred_type: None,
14816 })),
14817 array_with_input,
14818 ),
14819 ],
14820 else_: Some(base_func),
14821 comments: vec![],
14822 inferred_type: None,
14823 })))
14824 }
14825 // SPLIT(x, sep) from Presto/StarRocks/Doris -> target-specific split with regex escaping for Hive/Spark
14826 "SPLIT"
14827 if f.args.len() == 2
14828 && matches!(
14829 source,
14830 DialectType::Presto
14831 | DialectType::Trino
14832 | DialectType::Athena
14833 | DialectType::StarRocks
14834 | DialectType::Doris
14835 )
14836 && matches!(
14837 target,
14838 DialectType::Spark
14839 | DialectType::Databricks
14840 | DialectType::Hive
14841 ) =>
14842 {
14843 // Presto/StarRocks SPLIT is literal, Hive/Spark SPLIT is regex
14844 let mut args = f.args;
14845 let x = args.remove(0);
14846 let sep = args.remove(0);
14847 let escaped_sep = Expression::Function(Box::new(Function::new(
14848 "CONCAT".to_string(),
14849 vec![Expression::string("\\Q"), sep, Expression::string("\\E")],
14850 )));
14851 Ok(Expression::Function(Box::new(Function::new(
14852 "SPLIT".to_string(),
14853 vec![x, escaped_sep],
14854 ))))
14855 }
14856 // SUBSTRINGINDEX -> SUBSTRING_INDEX (ClickHouse camelCase to standard)
14857 // For ClickHouse target, preserve original name to maintain camelCase
14858 "SUBSTRINGINDEX" => {
14859 let name = if matches!(target, DialectType::ClickHouse) {
14860 f.name.clone()
14861 } else {
14862 "SUBSTRING_INDEX".to_string()
14863 };
14864 Ok(Expression::Function(Box::new(Function::new(name, f.args))))
14865 }
14866 // ARRAY_LENGTH/SIZE/CARDINALITY -> target-specific array length function
14867 "ARRAY_LENGTH" | "SIZE" | "CARDINALITY" => {
14868 // DuckDB source CARDINALITY -> DuckDB target: keep as CARDINALITY (used for maps)
14869 if name == "CARDINALITY"
14870 && matches!(source, DialectType::DuckDB)
14871 && matches!(target, DialectType::DuckDB)
14872 {
14873 return Ok(Expression::Function(f));
14874 }
14875 // Get the array argument (first arg, drop dimension args)
14876 let mut args = f.args;
14877 let arr = if args.is_empty() {
14878 return Ok(Expression::Function(Box::new(Function::new(
14879 name.to_string(),
14880 args,
14881 ))));
14882 } else {
14883 args.remove(0)
14884 };
14885 let name =
14886 match target {
14887 DialectType::Spark
14888 | DialectType::Databricks
14889 | DialectType::Hive => "SIZE",
14890 DialectType::Presto | DialectType::Trino => "CARDINALITY",
14891 DialectType::BigQuery => "ARRAY_LENGTH",
14892 DialectType::DuckDB => {
14893 // DuckDB: use ARRAY_LENGTH with all args
14894 let mut all_args = vec![arr];
14895 all_args.extend(args);
14896 return Ok(Expression::Function(Box::new(
14897 Function::new("ARRAY_LENGTH".to_string(), all_args),
14898 )));
14899 }
14900 DialectType::PostgreSQL
14901 | DialectType::Redshift
14902 | DialectType::TSQL
14903 | DialectType::Fabric => {
14904 // Keep ARRAY_LENGTH with dimension args when there is
14905 // no safe target-specific array representation.
14906 let mut all_args = vec![arr];
14907 all_args.extend(args);
14908 return Ok(Expression::Function(Box::new(
14909 Function::new("ARRAY_LENGTH".to_string(), all_args),
14910 )));
14911 }
14912 DialectType::ClickHouse => "LENGTH",
14913 _ => "ARRAY_LENGTH",
14914 };
14915 Ok(Expression::Function(Box::new(Function::new(
14916 name.to_string(),
14917 vec![arr],
14918 ))))
14919 }
14920 // TO_VARIANT(x) -> CAST(x AS VARIANT) for DuckDB
14921 "TO_VARIANT" if f.args.len() == 1 => match target {
14922 DialectType::DuckDB => {
14923 let arg = f.args.into_iter().next().unwrap();
14924 Ok(Expression::Cast(Box::new(Cast {
14925 this: arg,
14926 to: DataType::Custom {
14927 name: "VARIANT".to_string(),
14928 },
14929 double_colon_syntax: false,
14930 trailing_comments: Vec::new(),
14931 format: None,
14932 default: None,
14933 inferred_type: None,
14934 })))
14935 }
14936 _ => Ok(Expression::Function(f)),
14937 },
14938 // JSON_GROUP_ARRAY(x) -> JSON_AGG(x) for PostgreSQL
14939 "JSON_GROUP_ARRAY" if f.args.len() == 1 => match target {
14940 DialectType::PostgreSQL => Ok(Expression::Function(Box::new(
14941 Function::new("JSON_AGG".to_string(), f.args),
14942 ))),
14943 _ => Ok(Expression::Function(f)),
14944 },
14945 // JSON_GROUP_OBJECT(key, value) -> JSON_OBJECT_AGG(key, value) for PostgreSQL
14946 "JSON_GROUP_OBJECT" if f.args.len() == 2 => match target {
14947 DialectType::PostgreSQL => Ok(Expression::Function(Box::new(
14948 Function::new("JSON_OBJECT_AGG".to_string(), f.args),
14949 ))),
14950 _ => Ok(Expression::Function(f)),
14951 },
14952 // UNICODE(x) -> target-specific codepoint function
14953 "UNICODE" if f.args.len() == 1 => {
14954 match target {
14955 DialectType::SQLite | DialectType::DuckDB => {
14956 Ok(Expression::Function(Box::new(Function::new(
14957 "UNICODE".to_string(),
14958 f.args,
14959 ))))
14960 }
14961 DialectType::Oracle => {
14962 // ASCII(UNISTR(x))
14963 let inner = Expression::Function(Box::new(Function::new(
14964 "UNISTR".to_string(),
14965 f.args,
14966 )));
14967 Ok(Expression::Function(Box::new(Function::new(
14968 "ASCII".to_string(),
14969 vec![inner],
14970 ))))
14971 }
14972 DialectType::MySQL => {
14973 // ORD(CONVERT(x USING utf32))
14974 let arg = f.args.into_iter().next().unwrap();
14975 let convert_expr = Expression::ConvertToCharset(Box::new(
14976 crate::expressions::ConvertToCharset {
14977 this: Box::new(arg),
14978 dest: Some(Box::new(Expression::Identifier(
14979 crate::expressions::Identifier::new("utf32"),
14980 ))),
14981 source: None,
14982 },
14983 ));
14984 Ok(Expression::Function(Box::new(Function::new(
14985 "ORD".to_string(),
14986 vec![convert_expr],
14987 ))))
14988 }
14989 _ => Ok(Expression::Function(Box::new(Function::new(
14990 "ASCII".to_string(),
14991 f.args,
14992 )))),
14993 }
14994 }
14995 // XOR(a, b, ...) -> a XOR b XOR ... for MySQL, BITWISE_XOR for Presto/Trino, # for PostgreSQL, ^ for BigQuery
14996 "XOR" if f.args.len() >= 2 => {
14997 match target {
14998 DialectType::ClickHouse => {
14999 // ClickHouse: keep as xor() function with lowercase name
15000 Ok(Expression::Function(Box::new(Function::new(
15001 "xor".to_string(),
15002 f.args,
15003 ))))
15004 }
15005 DialectType::Presto | DialectType::Trino => {
15006 if f.args.len() == 2 {
15007 Ok(Expression::Function(Box::new(Function::new(
15008 "BITWISE_XOR".to_string(),
15009 f.args,
15010 ))))
15011 } else {
15012 // Nest: BITWISE_XOR(BITWISE_XOR(a, b), c)
15013 let mut args = f.args;
15014 let first = args.remove(0);
15015 let second = args.remove(0);
15016 let mut result =
15017 Expression::Function(Box::new(Function::new(
15018 "BITWISE_XOR".to_string(),
15019 vec![first, second],
15020 )));
15021 for arg in args {
15022 result =
15023 Expression::Function(Box::new(Function::new(
15024 "BITWISE_XOR".to_string(),
15025 vec![result, arg],
15026 )));
15027 }
15028 Ok(result)
15029 }
15030 }
15031 DialectType::MySQL
15032 | DialectType::SingleStore
15033 | DialectType::Doris
15034 | DialectType::StarRocks => {
15035 // Convert XOR(a, b, c) -> Expression::Xor with expressions list
15036 let args = f.args;
15037 Ok(Expression::Xor(Box::new(crate::expressions::Xor {
15038 this: None,
15039 expression: None,
15040 expressions: args,
15041 })))
15042 }
15043 DialectType::PostgreSQL | DialectType::Redshift => {
15044 // PostgreSQL: a # b (hash operator for XOR)
15045 let mut args = f.args;
15046 let first = args.remove(0);
15047 let second = args.remove(0);
15048 let mut result = Expression::BitwiseXor(Box::new(
15049 BinaryOp::new(first, second),
15050 ));
15051 for arg in args {
15052 result = Expression::BitwiseXor(Box::new(
15053 BinaryOp::new(result, arg),
15054 ));
15055 }
15056 Ok(result)
15057 }
15058 DialectType::DuckDB => {
15059 // DuckDB: keep as XOR function (DuckDB ^ is Power, not XOR)
15060 Ok(Expression::Function(Box::new(Function::new(
15061 "XOR".to_string(),
15062 f.args,
15063 ))))
15064 }
15065 DialectType::BigQuery => {
15066 // BigQuery: a ^ b (caret operator for XOR)
15067 let mut args = f.args;
15068 let first = args.remove(0);
15069 let second = args.remove(0);
15070 let mut result = Expression::BitwiseXor(Box::new(
15071 BinaryOp::new(first, second),
15072 ));
15073 for arg in args {
15074 result = Expression::BitwiseXor(Box::new(
15075 BinaryOp::new(result, arg),
15076 ));
15077 }
15078 Ok(result)
15079 }
15080 _ => Ok(Expression::Function(Box::new(Function::new(
15081 "XOR".to_string(),
15082 f.args,
15083 )))),
15084 }
15085 }
15086 // ARRAY_REVERSE_SORT(x) -> SORT_ARRAY(x, FALSE) for Spark/Hive, ARRAY_SORT(x, lambda) for Presto
15087 "ARRAY_REVERSE_SORT" if f.args.len() >= 1 => {
15088 match target {
15089 DialectType::Spark
15090 | DialectType::Databricks
15091 | DialectType::Hive => {
15092 let mut args = f.args;
15093 args.push(Expression::Identifier(
15094 crate::expressions::Identifier::new("FALSE"),
15095 ));
15096 Ok(Expression::Function(Box::new(Function::new(
15097 "SORT_ARRAY".to_string(),
15098 args,
15099 ))))
15100 }
15101 DialectType::Presto
15102 | DialectType::Trino
15103 | DialectType::Athena => {
15104 // ARRAY_SORT(x, (a, b) -> CASE WHEN a < b THEN 1 WHEN a > b THEN -1 ELSE 0 END)
15105 let arr = f.args.into_iter().next().unwrap();
15106 let lambda = Expression::Lambda(Box::new(
15107 crate::expressions::LambdaExpr {
15108 parameters: vec![
15109 Identifier::new("a"),
15110 Identifier::new("b"),
15111 ],
15112 colon: false,
15113 parameter_types: Vec::new(),
15114 body: Expression::Case(Box::new(Case {
15115 operand: None,
15116 whens: vec![
15117 (
15118 Expression::Lt(Box::new(
15119 BinaryOp::new(
15120 Expression::Identifier(
15121 Identifier::new("a"),
15122 ),
15123 Expression::Identifier(
15124 Identifier::new("b"),
15125 ),
15126 ),
15127 )),
15128 Expression::number(1),
15129 ),
15130 (
15131 Expression::Gt(Box::new(
15132 BinaryOp::new(
15133 Expression::Identifier(
15134 Identifier::new("a"),
15135 ),
15136 Expression::Identifier(
15137 Identifier::new("b"),
15138 ),
15139 ),
15140 )),
15141 Expression::Neg(Box::new(
15142 crate::expressions::UnaryOp {
15143 this: Expression::number(1),
15144 inferred_type: None,
15145 },
15146 )),
15147 ),
15148 ],
15149 else_: Some(Expression::number(0)),
15150 comments: Vec::new(),
15151 inferred_type: None,
15152 })),
15153 },
15154 ));
15155 Ok(Expression::Function(Box::new(Function::new(
15156 "ARRAY_SORT".to_string(),
15157 vec![arr, lambda],
15158 ))))
15159 }
15160 _ => Ok(Expression::Function(Box::new(Function::new(
15161 "ARRAY_REVERSE_SORT".to_string(),
15162 f.args,
15163 )))),
15164 }
15165 }
15166 // ENCODE(x) -> ENCODE(x, 'utf-8') for Spark/Hive, TO_UTF8(x) for Presto
15167 "ENCODE" if f.args.len() == 1 => match target {
15168 DialectType::Spark
15169 | DialectType::Databricks
15170 | DialectType::Hive => {
15171 let mut args = f.args;
15172 args.push(Expression::string("utf-8"));
15173 Ok(Expression::Function(Box::new(Function::new(
15174 "ENCODE".to_string(),
15175 args,
15176 ))))
15177 }
15178 DialectType::Presto | DialectType::Trino | DialectType::Athena => {
15179 Ok(Expression::Function(Box::new(Function::new(
15180 "TO_UTF8".to_string(),
15181 f.args,
15182 ))))
15183 }
15184 _ => Ok(Expression::Function(Box::new(Function::new(
15185 "ENCODE".to_string(),
15186 f.args,
15187 )))),
15188 },
15189 // DECODE(x) -> DECODE(x, 'utf-8') for Spark/Hive, FROM_UTF8(x) for Presto
15190 "DECODE" if f.args.len() == 1 => match target {
15191 DialectType::Spark
15192 | DialectType::Databricks
15193 | DialectType::Hive => {
15194 let mut args = f.args;
15195 args.push(Expression::string("utf-8"));
15196 Ok(Expression::Function(Box::new(Function::new(
15197 "DECODE".to_string(),
15198 args,
15199 ))))
15200 }
15201 DialectType::Presto | DialectType::Trino | DialectType::Athena => {
15202 Ok(Expression::Function(Box::new(Function::new(
15203 "FROM_UTF8".to_string(),
15204 f.args,
15205 ))))
15206 }
15207 _ => Ok(Expression::Function(Box::new(Function::new(
15208 "DECODE".to_string(),
15209 f.args,
15210 )))),
15211 },
15212 // QUANTILE(x, p) -> PERCENTILE(x, p) for Spark/Hive
15213 "QUANTILE" if f.args.len() == 2 => {
15214 let name = match target {
15215 DialectType::Spark
15216 | DialectType::Databricks
15217 | DialectType::Hive => "PERCENTILE",
15218 DialectType::Presto | DialectType::Trino => "APPROX_PERCENTILE",
15219 DialectType::BigQuery => "PERCENTILE_CONT",
15220 _ => "QUANTILE",
15221 };
15222 Ok(Expression::Function(Box::new(Function::new(
15223 name.to_string(),
15224 f.args,
15225 ))))
15226 }
15227 // QUANTILE_CONT(x, q) -> PERCENTILE_CONT(q) WITHIN GROUP (ORDER BY x) for PostgreSQL/Snowflake
15228 "QUANTILE_CONT" if f.args.len() == 2 => {
15229 let mut args = f.args;
15230 let column = args.remove(0);
15231 let quantile = args.remove(0);
15232 match target {
15233 DialectType::DuckDB => {
15234 Ok(Expression::Function(Box::new(Function::new(
15235 "QUANTILE_CONT".to_string(),
15236 vec![column, quantile],
15237 ))))
15238 }
15239 DialectType::PostgreSQL
15240 | DialectType::Redshift
15241 | DialectType::Snowflake => {
15242 // PERCENTILE_CONT(q) WITHIN GROUP (ORDER BY x)
15243 let inner = Expression::PercentileCont(Box::new(
15244 crate::expressions::PercentileFunc {
15245 this: column.clone(),
15246 percentile: quantile,
15247 order_by: None,
15248 filter: None,
15249 },
15250 ));
15251 Ok(Expression::WithinGroup(Box::new(
15252 crate::expressions::WithinGroup {
15253 this: inner,
15254 order_by: vec![crate::expressions::Ordered {
15255 this: column,
15256 desc: false,
15257 nulls_first: None,
15258 explicit_asc: false,
15259 with_fill: None,
15260 }],
15261 },
15262 )))
15263 }
15264 _ => Ok(Expression::Function(Box::new(Function::new(
15265 "QUANTILE_CONT".to_string(),
15266 vec![column, quantile],
15267 )))),
15268 }
15269 }
15270 // QUANTILE_DISC(x, q) -> PERCENTILE_DISC(q) WITHIN GROUP (ORDER BY x) for PostgreSQL/Snowflake
15271 "QUANTILE_DISC" if f.args.len() == 2 => {
15272 let mut args = f.args;
15273 let column = args.remove(0);
15274 let quantile = args.remove(0);
15275 match target {
15276 DialectType::DuckDB => {
15277 Ok(Expression::Function(Box::new(Function::new(
15278 "QUANTILE_DISC".to_string(),
15279 vec![column, quantile],
15280 ))))
15281 }
15282 DialectType::PostgreSQL
15283 | DialectType::Redshift
15284 | DialectType::Snowflake => {
15285 // PERCENTILE_DISC(q) WITHIN GROUP (ORDER BY x)
15286 let inner = Expression::PercentileDisc(Box::new(
15287 crate::expressions::PercentileFunc {
15288 this: column.clone(),
15289 percentile: quantile,
15290 order_by: None,
15291 filter: None,
15292 },
15293 ));
15294 Ok(Expression::WithinGroup(Box::new(
15295 crate::expressions::WithinGroup {
15296 this: inner,
15297 order_by: vec![crate::expressions::Ordered {
15298 this: column,
15299 desc: false,
15300 nulls_first: None,
15301 explicit_asc: false,
15302 with_fill: None,
15303 }],
15304 },
15305 )))
15306 }
15307 _ => Ok(Expression::Function(Box::new(Function::new(
15308 "QUANTILE_DISC".to_string(),
15309 vec![column, quantile],
15310 )))),
15311 }
15312 }
15313 // PERCENTILE_APPROX(x, p) / APPROX_PERCENTILE(x, p) -> target-specific
15314 "PERCENTILE_APPROX" | "APPROX_PERCENTILE" if f.args.len() >= 2 => {
15315 let name = match target {
15316 DialectType::Presto
15317 | DialectType::Trino
15318 | DialectType::Athena => "APPROX_PERCENTILE",
15319 DialectType::Spark
15320 | DialectType::Databricks
15321 | DialectType::Hive => "PERCENTILE_APPROX",
15322 DialectType::DuckDB => "APPROX_QUANTILE",
15323 DialectType::PostgreSQL | DialectType::Redshift => {
15324 "PERCENTILE_CONT"
15325 }
15326 _ => &f.name,
15327 };
15328 Ok(Expression::Function(Box::new(Function::new(
15329 name.to_string(),
15330 f.args,
15331 ))))
15332 }
15333 // EPOCH(x) -> UNIX_TIMESTAMP(x) for Spark/Hive
15334 "EPOCH" if f.args.len() == 1 => {
15335 let name = match target {
15336 DialectType::Spark
15337 | DialectType::Databricks
15338 | DialectType::Hive => "UNIX_TIMESTAMP",
15339 DialectType::Presto | DialectType::Trino => "TO_UNIXTIME",
15340 _ => "EPOCH",
15341 };
15342 Ok(Expression::Function(Box::new(Function::new(
15343 name.to_string(),
15344 f.args,
15345 ))))
15346 }
15347 // EPOCH_MS(x) -> target-specific epoch milliseconds conversion
15348 "EPOCH_MS" if f.args.len() == 1 => {
15349 match target {
15350 DialectType::Spark | DialectType::Databricks => {
15351 Ok(Expression::Function(Box::new(Function::new(
15352 "TIMESTAMP_MILLIS".to_string(),
15353 f.args,
15354 ))))
15355 }
15356 DialectType::Hive => {
15357 // Hive: FROM_UNIXTIME(x / 1000)
15358 let arg = f.args.into_iter().next().unwrap();
15359 let div_expr = Expression::Div(Box::new(
15360 crate::expressions::BinaryOp::new(
15361 arg,
15362 Expression::number(1000),
15363 ),
15364 ));
15365 Ok(Expression::Function(Box::new(Function::new(
15366 "FROM_UNIXTIME".to_string(),
15367 vec![div_expr],
15368 ))))
15369 }
15370 DialectType::Presto | DialectType::Trino => {
15371 Ok(Expression::Function(Box::new(Function::new(
15372 "FROM_UNIXTIME".to_string(),
15373 vec![Expression::Div(Box::new(
15374 crate::expressions::BinaryOp::new(
15375 f.args.into_iter().next().unwrap(),
15376 Expression::number(1000),
15377 ),
15378 ))],
15379 ))))
15380 }
15381 _ => Ok(Expression::Function(Box::new(Function::new(
15382 "EPOCH_MS".to_string(),
15383 f.args,
15384 )))),
15385 }
15386 }
15387 // HASHBYTES('algorithm', x) -> target-specific hash function
15388 "HASHBYTES" if f.args.len() == 2 => {
15389 // Keep HASHBYTES as-is for TSQL target
15390 if matches!(target, DialectType::TSQL) {
15391 return Ok(Expression::Function(f));
15392 }
15393 let algo_expr = &f.args[0];
15394 let algo = match algo_expr {
15395 Expression::Literal(lit)
15396 if matches!(
15397 lit.as_ref(),
15398 crate::expressions::Literal::String(_)
15399 ) =>
15400 {
15401 let crate::expressions::Literal::String(s) = lit.as_ref()
15402 else {
15403 unreachable!()
15404 };
15405 s.to_ascii_uppercase()
15406 }
15407 _ => return Ok(Expression::Function(f)),
15408 };
15409 let data_arg = f.args.into_iter().nth(1).unwrap();
15410 match algo.as_str() {
15411 "SHA1" => {
15412 let name = match target {
15413 DialectType::Spark | DialectType::Databricks => "SHA",
15414 DialectType::Hive => "SHA1",
15415 _ => "SHA1",
15416 };
15417 Ok(Expression::Function(Box::new(Function::new(
15418 name.to_string(),
15419 vec![data_arg],
15420 ))))
15421 }
15422 "SHA2_256" => {
15423 Ok(Expression::Function(Box::new(Function::new(
15424 "SHA2".to_string(),
15425 vec![data_arg, Expression::number(256)],
15426 ))))
15427 }
15428 "SHA2_512" => {
15429 Ok(Expression::Function(Box::new(Function::new(
15430 "SHA2".to_string(),
15431 vec![data_arg, Expression::number(512)],
15432 ))))
15433 }
15434 "MD5" => Ok(Expression::Function(Box::new(Function::new(
15435 "MD5".to_string(),
15436 vec![data_arg],
15437 )))),
15438 _ => Ok(Expression::Function(Box::new(Function::new(
15439 "HASHBYTES".to_string(),
15440 vec![Expression::string(&algo), data_arg],
15441 )))),
15442 }
15443 }
15444 // JSON_EXTRACT_PATH(json, key1, key2, ...) -> target-specific JSON extraction
15445 "JSON_EXTRACT_PATH" | "JSON_EXTRACT_PATH_TEXT" if f.args.len() >= 2 => {
15446 let is_text = name == "JSON_EXTRACT_PATH_TEXT";
15447 let mut args = f.args;
15448 let json_expr = args.remove(0);
15449 // Build JSON path from remaining keys: $.key1.key2 or $.key1[0]
15450 let mut json_path = "$".to_string();
15451 for a in &args {
15452 match a {
15453 Expression::Literal(lit)
15454 if matches!(
15455 lit.as_ref(),
15456 crate::expressions::Literal::String(_)
15457 ) =>
15458 {
15459 let crate::expressions::Literal::String(s) =
15460 lit.as_ref()
15461 else {
15462 unreachable!()
15463 };
15464 // Numeric string keys become array indices: [0]
15465 if s.chars().all(|c| c.is_ascii_digit()) {
15466 json_path.push('[');
15467 json_path.push_str(s);
15468 json_path.push(']');
15469 } else {
15470 json_path.push('.');
15471 json_path.push_str(s);
15472 }
15473 }
15474 _ => {
15475 json_path.push_str(".?");
15476 }
15477 }
15478 }
15479 match target {
15480 DialectType::Spark
15481 | DialectType::Databricks
15482 | DialectType::Hive => {
15483 Ok(Expression::Function(Box::new(Function::new(
15484 "GET_JSON_OBJECT".to_string(),
15485 vec![json_expr, Expression::string(&json_path)],
15486 ))))
15487 }
15488 DialectType::Presto | DialectType::Trino => {
15489 let func_name = if is_text {
15490 "JSON_EXTRACT_SCALAR"
15491 } else {
15492 "JSON_EXTRACT"
15493 };
15494 Ok(Expression::Function(Box::new(Function::new(
15495 func_name.to_string(),
15496 vec![json_expr, Expression::string(&json_path)],
15497 ))))
15498 }
15499 DialectType::BigQuery | DialectType::MySQL => {
15500 let func_name = if is_text {
15501 "JSON_EXTRACT_SCALAR"
15502 } else {
15503 "JSON_EXTRACT"
15504 };
15505 Ok(Expression::Function(Box::new(Function::new(
15506 func_name.to_string(),
15507 vec![json_expr, Expression::string(&json_path)],
15508 ))))
15509 }
15510 DialectType::PostgreSQL | DialectType::Materialize => {
15511 // Keep as JSON_EXTRACT_PATH_TEXT / JSON_EXTRACT_PATH for PostgreSQL/Materialize
15512 let func_name = if is_text {
15513 "JSON_EXTRACT_PATH_TEXT"
15514 } else {
15515 "JSON_EXTRACT_PATH"
15516 };
15517 let mut new_args = vec![json_expr];
15518 new_args.extend(args);
15519 Ok(Expression::Function(Box::new(Function::new(
15520 func_name.to_string(),
15521 new_args,
15522 ))))
15523 }
15524 DialectType::DuckDB | DialectType::SQLite => {
15525 // Use -> for JSON_EXTRACT_PATH, ->> for JSON_EXTRACT_PATH_TEXT
15526 if is_text {
15527 Ok(Expression::JsonExtractScalar(Box::new(
15528 crate::expressions::JsonExtractFunc {
15529 this: json_expr,
15530 path: Expression::string(&json_path),
15531 returning: None,
15532 arrow_syntax: true,
15533 hash_arrow_syntax: false,
15534 wrapper_option: None,
15535 quotes_option: None,
15536 on_scalar_string: false,
15537 on_error: None,
15538 },
15539 )))
15540 } else {
15541 Ok(Expression::JsonExtract(Box::new(
15542 crate::expressions::JsonExtractFunc {
15543 this: json_expr,
15544 path: Expression::string(&json_path),
15545 returning: None,
15546 arrow_syntax: true,
15547 hash_arrow_syntax: false,
15548 wrapper_option: None,
15549 quotes_option: None,
15550 on_scalar_string: false,
15551 on_error: None,
15552 },
15553 )))
15554 }
15555 }
15556 DialectType::Redshift => {
15557 // Keep as JSON_EXTRACT_PATH_TEXT for Redshift
15558 let mut new_args = vec![json_expr];
15559 new_args.extend(args);
15560 Ok(Expression::Function(Box::new(Function::new(
15561 "JSON_EXTRACT_PATH_TEXT".to_string(),
15562 new_args,
15563 ))))
15564 }
15565 DialectType::TSQL | DialectType::Fabric => {
15566 // ISNULL(JSON_QUERY(json, '$.path'), JSON_VALUE(json, '$.path'))
15567 let jq = Expression::Function(Box::new(Function::new(
15568 "JSON_QUERY".to_string(),
15569 vec![json_expr.clone(), Expression::string(&json_path)],
15570 )));
15571 let jv = Expression::Function(Box::new(Function::new(
15572 "JSON_VALUE".to_string(),
15573 vec![json_expr, Expression::string(&json_path)],
15574 )));
15575 Ok(Expression::Function(Box::new(Function::new(
15576 "ISNULL".to_string(),
15577 vec![jq, jv],
15578 ))))
15579 }
15580 DialectType::ClickHouse => {
15581 let func_name = if is_text {
15582 "JSONExtractString"
15583 } else {
15584 "JSONExtractRaw"
15585 };
15586 let mut new_args = vec![json_expr];
15587 new_args.extend(args);
15588 Ok(Expression::Function(Box::new(Function::new(
15589 func_name.to_string(),
15590 new_args,
15591 ))))
15592 }
15593 _ => {
15594 let func_name = if is_text {
15595 "JSON_EXTRACT_SCALAR"
15596 } else {
15597 "JSON_EXTRACT"
15598 };
15599 Ok(Expression::Function(Box::new(Function::new(
15600 func_name.to_string(),
15601 vec![json_expr, Expression::string(&json_path)],
15602 ))))
15603 }
15604 }
15605 }
15606 // APPROX_DISTINCT(x) -> APPROX_COUNT_DISTINCT(x) for Spark/Hive/BigQuery
15607 "APPROX_DISTINCT" if f.args.len() >= 1 => {
15608 let name = match target {
15609 DialectType::Spark
15610 | DialectType::Databricks
15611 | DialectType::Hive
15612 | DialectType::BigQuery => "APPROX_COUNT_DISTINCT",
15613 _ => "APPROX_DISTINCT",
15614 };
15615 let mut args = f.args;
15616 // Hive doesn't support the accuracy parameter
15617 if name == "APPROX_COUNT_DISTINCT"
15618 && matches!(target, DialectType::Hive)
15619 {
15620 args.truncate(1);
15621 }
15622 Ok(Expression::Function(Box::new(Function::new(
15623 name.to_string(),
15624 args,
15625 ))))
15626 }
15627 // REGEXP_EXTRACT(x, pattern) - normalize default group index
15628 "REGEXP_EXTRACT" if f.args.len() == 2 => {
15629 // Determine source default group index
15630 let source_default = match source {
15631 DialectType::Presto
15632 | DialectType::Trino
15633 | DialectType::DuckDB => 0,
15634 _ => 1, // Hive/Spark/Databricks default = 1
15635 };
15636 // Determine target default group index
15637 let target_default = match target {
15638 DialectType::Presto
15639 | DialectType::Trino
15640 | DialectType::DuckDB
15641 | DialectType::BigQuery => 0,
15642 DialectType::Snowflake => {
15643 // Snowflake uses REGEXP_SUBSTR
15644 return Ok(Expression::Function(Box::new(Function::new(
15645 "REGEXP_SUBSTR".to_string(),
15646 f.args,
15647 ))));
15648 }
15649 _ => 1, // Hive/Spark/Databricks default = 1
15650 };
15651 if source_default != target_default {
15652 let mut args = f.args;
15653 args.push(Expression::number(source_default));
15654 Ok(Expression::Function(Box::new(Function::new(
15655 "REGEXP_EXTRACT".to_string(),
15656 args,
15657 ))))
15658 } else {
15659 Ok(Expression::Function(Box::new(Function::new(
15660 "REGEXP_EXTRACT".to_string(),
15661 f.args,
15662 ))))
15663 }
15664 }
15665 // RLIKE(str, pattern) -> RegexpLike expression (generates as target-specific form)
15666 "RLIKE" if f.args.len() == 2 => {
15667 let mut args = f.args;
15668 let str_expr = args.remove(0);
15669 let pattern = args.remove(0);
15670 match target {
15671 DialectType::DuckDB => {
15672 // REGEXP_MATCHES(str, pattern)
15673 Ok(Expression::Function(Box::new(Function::new(
15674 "REGEXP_MATCHES".to_string(),
15675 vec![str_expr, pattern],
15676 ))))
15677 }
15678 _ => {
15679 // Convert to RegexpLike which generates as RLIKE/~/REGEXP_LIKE per dialect
15680 Ok(Expression::RegexpLike(Box::new(
15681 crate::expressions::RegexpFunc {
15682 this: str_expr,
15683 pattern,
15684 flags: None,
15685 },
15686 )))
15687 }
15688 }
15689 }
15690 // EOMONTH(date[, month_offset]) -> target-specific
15691 "EOMONTH" if f.args.len() >= 1 => {
15692 let mut args = f.args;
15693 let date_arg = args.remove(0);
15694 let month_offset = if !args.is_empty() {
15695 Some(args.remove(0))
15696 } else {
15697 None
15698 };
15699
15700 // Helper: wrap date in CAST to DATE
15701 let cast_to_date = |e: Expression| -> Expression {
15702 Expression::Cast(Box::new(Cast {
15703 this: e,
15704 to: DataType::Date,
15705 trailing_comments: vec![],
15706 double_colon_syntax: false,
15707 format: None,
15708 default: None,
15709 inferred_type: None,
15710 }))
15711 };
15712
15713 match target {
15714 DialectType::TSQL | DialectType::Fabric => {
15715 // TSQL: EOMONTH(CAST(date AS DATE)) or EOMONTH(DATEADD(MONTH, offset, CAST(date AS DATE)))
15716 let date = cast_to_date(date_arg);
15717 let date = if let Some(offset) = month_offset {
15718 Expression::Function(Box::new(Function::new(
15719 "DATEADD".to_string(),
15720 vec![
15721 Expression::Identifier(Identifier::new(
15722 "MONTH",
15723 )),
15724 offset,
15725 date,
15726 ],
15727 )))
15728 } else {
15729 date
15730 };
15731 Ok(Expression::Function(Box::new(Function::new(
15732 "EOMONTH".to_string(),
15733 vec![date],
15734 ))))
15735 }
15736 DialectType::Presto
15737 | DialectType::Trino
15738 | DialectType::Athena => {
15739 // Presto: LAST_DAY_OF_MONTH(CAST(CAST(date AS TIMESTAMP) AS DATE))
15740 // or with offset: LAST_DAY_OF_MONTH(DATE_ADD('MONTH', offset, CAST(CAST(date AS TIMESTAMP) AS DATE)))
15741 let cast_ts = Expression::Cast(Box::new(Cast {
15742 this: date_arg,
15743 to: DataType::Timestamp {
15744 timezone: false,
15745 precision: None,
15746 },
15747 trailing_comments: vec![],
15748 double_colon_syntax: false,
15749 format: None,
15750 default: None,
15751 inferred_type: None,
15752 }));
15753 let date = cast_to_date(cast_ts);
15754 let date = if let Some(offset) = month_offset {
15755 Expression::Function(Box::new(Function::new(
15756 "DATE_ADD".to_string(),
15757 vec![Expression::string("MONTH"), offset, date],
15758 )))
15759 } else {
15760 date
15761 };
15762 Ok(Expression::Function(Box::new(Function::new(
15763 "LAST_DAY_OF_MONTH".to_string(),
15764 vec![date],
15765 ))))
15766 }
15767 DialectType::PostgreSQL => {
15768 // PostgreSQL: CAST(DATE_TRUNC('MONTH', CAST(date AS DATE) [+ INTERVAL 'offset MONTH']) + INTERVAL '1 MONTH' - INTERVAL '1 DAY' AS DATE)
15769 let date = cast_to_date(date_arg);
15770 let date = if let Some(offset) = month_offset {
15771 let interval_str = format!(
15772 "{} MONTH",
15773 Self::expr_to_string_static(&offset)
15774 );
15775 Expression::Add(Box::new(
15776 crate::expressions::BinaryOp::new(
15777 date,
15778 Expression::Interval(Box::new(
15779 crate::expressions::Interval {
15780 this: Some(Expression::string(
15781 &interval_str,
15782 )),
15783 unit: None,
15784 },
15785 )),
15786 ),
15787 ))
15788 } else {
15789 date
15790 };
15791 let truncated =
15792 Expression::Function(Box::new(Function::new(
15793 "DATE_TRUNC".to_string(),
15794 vec![Expression::string("MONTH"), date],
15795 )));
15796 let plus_month = Expression::Add(Box::new(
15797 crate::expressions::BinaryOp::new(
15798 truncated,
15799 Expression::Interval(Box::new(
15800 crate::expressions::Interval {
15801 this: Some(Expression::string("1 MONTH")),
15802 unit: None,
15803 },
15804 )),
15805 ),
15806 ));
15807 let minus_day = Expression::Sub(Box::new(
15808 crate::expressions::BinaryOp::new(
15809 plus_month,
15810 Expression::Interval(Box::new(
15811 crate::expressions::Interval {
15812 this: Some(Expression::string("1 DAY")),
15813 unit: None,
15814 },
15815 )),
15816 ),
15817 ));
15818 Ok(Expression::Cast(Box::new(Cast {
15819 this: minus_day,
15820 to: DataType::Date,
15821 trailing_comments: vec![],
15822 double_colon_syntax: false,
15823 format: None,
15824 default: None,
15825 inferred_type: None,
15826 })))
15827 }
15828 DialectType::DuckDB => {
15829 // DuckDB: LAST_DAY(CAST(date AS DATE) [+ INTERVAL (offset) MONTH])
15830 let date = cast_to_date(date_arg);
15831 let date = if let Some(offset) = month_offset {
15832 // Wrap negative numbers in parentheses for DuckDB INTERVAL
15833 let interval_val =
15834 if matches!(&offset, Expression::Neg(_)) {
15835 Expression::Paren(Box::new(
15836 crate::expressions::Paren {
15837 this: offset,
15838 trailing_comments: Vec::new(),
15839 },
15840 ))
15841 } else {
15842 offset
15843 };
15844 Expression::Add(Box::new(crate::expressions::BinaryOp::new(
15845 date,
15846 Expression::Interval(Box::new(crate::expressions::Interval {
15847 this: Some(interval_val),
15848 unit: Some(crate::expressions::IntervalUnitSpec::Simple {
15849 unit: crate::expressions::IntervalUnit::Month,
15850 use_plural: false,
15851 }),
15852 })),
15853 )))
15854 } else {
15855 date
15856 };
15857 Ok(Expression::Function(Box::new(Function::new(
15858 "LAST_DAY".to_string(),
15859 vec![date],
15860 ))))
15861 }
15862 DialectType::Snowflake | DialectType::Redshift => {
15863 // Snowflake/Redshift: LAST_DAY(TO_DATE(date) or CAST(date AS DATE))
15864 // With offset: LAST_DAY(DATEADD(MONTH, offset, TO_DATE(date)))
15865 let date = if matches!(target, DialectType::Snowflake) {
15866 Expression::Function(Box::new(Function::new(
15867 "TO_DATE".to_string(),
15868 vec![date_arg],
15869 )))
15870 } else {
15871 cast_to_date(date_arg)
15872 };
15873 let date = if let Some(offset) = month_offset {
15874 Expression::Function(Box::new(Function::new(
15875 "DATEADD".to_string(),
15876 vec![
15877 Expression::Identifier(Identifier::new(
15878 "MONTH",
15879 )),
15880 offset,
15881 date,
15882 ],
15883 )))
15884 } else {
15885 date
15886 };
15887 Ok(Expression::Function(Box::new(Function::new(
15888 "LAST_DAY".to_string(),
15889 vec![date],
15890 ))))
15891 }
15892 DialectType::Spark | DialectType::Databricks => {
15893 // Spark: LAST_DAY(TO_DATE(date))
15894 // With offset: LAST_DAY(ADD_MONTHS(TO_DATE(date), offset))
15895 let date = Expression::Function(Box::new(Function::new(
15896 "TO_DATE".to_string(),
15897 vec![date_arg],
15898 )));
15899 let date = if let Some(offset) = month_offset {
15900 Expression::Function(Box::new(Function::new(
15901 "ADD_MONTHS".to_string(),
15902 vec![date, offset],
15903 )))
15904 } else {
15905 date
15906 };
15907 Ok(Expression::Function(Box::new(Function::new(
15908 "LAST_DAY".to_string(),
15909 vec![date],
15910 ))))
15911 }
15912 DialectType::MySQL => {
15913 // MySQL: LAST_DAY(DATE(date)) - no offset
15914 // With offset: LAST_DAY(DATE_ADD(date, INTERVAL offset MONTH)) - no DATE() wrapper
15915 let date = if let Some(offset) = month_offset {
15916 let iu = crate::expressions::IntervalUnit::Month;
15917 Expression::DateAdd(Box::new(
15918 crate::expressions::DateAddFunc {
15919 this: date_arg,
15920 interval: offset,
15921 unit: iu,
15922 },
15923 ))
15924 } else {
15925 Expression::Function(Box::new(Function::new(
15926 "DATE".to_string(),
15927 vec![date_arg],
15928 )))
15929 };
15930 Ok(Expression::Function(Box::new(Function::new(
15931 "LAST_DAY".to_string(),
15932 vec![date],
15933 ))))
15934 }
15935 DialectType::BigQuery => {
15936 // BigQuery: LAST_DAY(CAST(date AS DATE))
15937 // With offset: LAST_DAY(DATE_ADD(CAST(date AS DATE), INTERVAL offset MONTH))
15938 let date = cast_to_date(date_arg);
15939 let date = if let Some(offset) = month_offset {
15940 let interval = Expression::Interval(Box::new(crate::expressions::Interval {
15941 this: Some(offset),
15942 unit: Some(crate::expressions::IntervalUnitSpec::Simple {
15943 unit: crate::expressions::IntervalUnit::Month,
15944 use_plural: false,
15945 }),
15946 }));
15947 Expression::Function(Box::new(Function::new(
15948 "DATE_ADD".to_string(),
15949 vec![date, interval],
15950 )))
15951 } else {
15952 date
15953 };
15954 Ok(Expression::Function(Box::new(Function::new(
15955 "LAST_DAY".to_string(),
15956 vec![date],
15957 ))))
15958 }
15959 DialectType::ClickHouse => {
15960 // ClickHouse: LAST_DAY(CAST(date AS Nullable(DATE)))
15961 let date = Expression::Cast(Box::new(Cast {
15962 this: date_arg,
15963 to: DataType::Nullable {
15964 inner: Box::new(DataType::Date),
15965 },
15966 trailing_comments: vec![],
15967 double_colon_syntax: false,
15968 format: None,
15969 default: None,
15970 inferred_type: None,
15971 }));
15972 let date = if let Some(offset) = month_offset {
15973 Expression::Function(Box::new(Function::new(
15974 "DATE_ADD".to_string(),
15975 vec![
15976 Expression::Identifier(Identifier::new(
15977 "MONTH",
15978 )),
15979 offset,
15980 date,
15981 ],
15982 )))
15983 } else {
15984 date
15985 };
15986 Ok(Expression::Function(Box::new(Function::new(
15987 "LAST_DAY".to_string(),
15988 vec![date],
15989 ))))
15990 }
15991 DialectType::Hive => {
15992 // Hive: LAST_DAY(date)
15993 let date = if let Some(offset) = month_offset {
15994 Expression::Function(Box::new(Function::new(
15995 "ADD_MONTHS".to_string(),
15996 vec![date_arg, offset],
15997 )))
15998 } else {
15999 date_arg
16000 };
16001 Ok(Expression::Function(Box::new(Function::new(
16002 "LAST_DAY".to_string(),
16003 vec![date],
16004 ))))
16005 }
16006 _ => {
16007 // Default: LAST_DAY(date)
16008 let date = if let Some(offset) = month_offset {
16009 let unit =
16010 Expression::Identifier(Identifier::new("MONTH"));
16011 Expression::Function(Box::new(Function::new(
16012 "DATEADD".to_string(),
16013 vec![unit, offset, date_arg],
16014 )))
16015 } else {
16016 date_arg
16017 };
16018 Ok(Expression::Function(Box::new(Function::new(
16019 "LAST_DAY".to_string(),
16020 vec![date],
16021 ))))
16022 }
16023 }
16024 }
16025 // LAST_DAY(x) / LAST_DAY_OF_MONTH(x) -> target-specific
16026 "LAST_DAY" | "LAST_DAY_OF_MONTH"
16027 if !matches!(source, DialectType::BigQuery)
16028 && f.args.len() >= 1 =>
16029 {
16030 let first_arg = f.args.into_iter().next().unwrap();
16031 match target {
16032 DialectType::TSQL | DialectType::Fabric => {
16033 Ok(Expression::Function(Box::new(Function::new(
16034 "EOMONTH".to_string(),
16035 vec![first_arg],
16036 ))))
16037 }
16038 DialectType::Presto
16039 | DialectType::Trino
16040 | DialectType::Athena => {
16041 Ok(Expression::Function(Box::new(Function::new(
16042 "LAST_DAY_OF_MONTH".to_string(),
16043 vec![first_arg],
16044 ))))
16045 }
16046 _ => Ok(Expression::Function(Box::new(Function::new(
16047 "LAST_DAY".to_string(),
16048 vec![first_arg],
16049 )))),
16050 }
16051 }
16052 // BigQuery PARSE_DATETIME(format, value) -> target-specific parsing calls.
16053 "PARSE_DATETIME"
16054 if matches!(source, DialectType::BigQuery) && f.args.len() == 2 =>
16055 {
16056 fn expand_bigquery_datetime_format(expr: Expression) -> Expression {
16057 match expr {
16058 Expression::Literal(lit) => match lit.as_ref() {
16059 Literal::String(s) => Expression::string(
16060 s.replace("%F", "%Y-%m-%d")
16061 .replace("%T", "%H:%M:%S"),
16062 ),
16063 _ => Expression::Literal(lit),
16064 },
16065 other => other,
16066 }
16067 }
16068
16069 let mut args = f.args;
16070 let format = expand_bigquery_datetime_format(args.remove(0));
16071 let value = args.remove(0);
16072 match target {
16073 DialectType::DuckDB => {
16074 let value_with_year = Expression::Concat(Box::new(
16075 crate::expressions::BinaryOp::new(
16076 Expression::string("1970 "),
16077 value,
16078 ),
16079 ));
16080 let format_with_year = Expression::Concat(Box::new(
16081 crate::expressions::BinaryOp::new(
16082 Expression::string("%Y "),
16083 format,
16084 ),
16085 ));
16086 Ok(Expression::Function(Box::new(Function::new(
16087 "STRPTIME".to_string(),
16088 vec![value_with_year, format_with_year],
16089 ))))
16090 }
16091 DialectType::Snowflake => {
16092 Ok(Expression::Function(Box::new(Function::new(
16093 "PARSE_DATETIME".to_string(),
16094 vec![value, format],
16095 ))))
16096 }
16097 _ => Ok(Expression::Function(Box::new(Function::new(
16098 "PARSE_DATETIME".to_string(),
16099 vec![format, value],
16100 )))),
16101 }
16102 }
16103 // Presto/Trino ISO-8601 helpers become casts outside Presto-family targets.
16104 "FROM_ISO8601_TIMESTAMP"
16105 if matches!(
16106 source,
16107 DialectType::Presto | DialectType::Trino | DialectType::Athena
16108 ) && f.args.len() == 1
16109 && !matches!(
16110 target,
16111 DialectType::Presto
16112 | DialectType::Trino
16113 | DialectType::Athena
16114 ) =>
16115 {
16116 Ok(Expression::Cast(Box::new(crate::expressions::Cast {
16117 this: f.args.into_iter().next().unwrap(),
16118 to: DataType::Timestamp {
16119 precision: None,
16120 timezone: matches!(
16121 target,
16122 DialectType::DuckDB | DialectType::Snowflake
16123 ),
16124 },
16125 trailing_comments: Vec::new(),
16126 double_colon_syntax: false,
16127 format: None,
16128 default: None,
16129 inferred_type: None,
16130 })))
16131 }
16132 "FROM_ISO8601_DATE"
16133 if matches!(
16134 source,
16135 DialectType::Presto | DialectType::Trino | DialectType::Athena
16136 ) && f.args.len() == 1
16137 && !matches!(
16138 target,
16139 DialectType::Presto
16140 | DialectType::Trino
16141 | DialectType::Athena
16142 ) =>
16143 {
16144 Ok(Expression::Cast(Box::new(crate::expressions::Cast {
16145 this: f.args.into_iter().next().unwrap(),
16146 to: DataType::Date,
16147 trailing_comments: Vec::new(),
16148 double_colon_syntax: false,
16149 format: None,
16150 default: None,
16151 inferred_type: None,
16152 })))
16153 }
16154 // MAP(keys_array, vals_array) from Presto (2-arg form) -> target-specific
16155 "MAP"
16156 if f.args.len() == 2
16157 && matches!(
16158 source,
16159 DialectType::Presto
16160 | DialectType::Trino
16161 | DialectType::Athena
16162 ) =>
16163 {
16164 let keys_arg = f.args[0].clone();
16165 let vals_arg = f.args[1].clone();
16166
16167 // Helper: extract array elements from Array/ArrayFunc/Function("ARRAY") expressions
16168 fn extract_array_elements(
16169 expr: &Expression,
16170 ) -> Option<&Vec<Expression>> {
16171 match expr {
16172 Expression::Array(arr) => Some(&arr.expressions),
16173 Expression::ArrayFunc(arr) => Some(&arr.expressions),
16174 Expression::Function(f)
16175 if f.name.eq_ignore_ascii_case("ARRAY") =>
16176 {
16177 Some(&f.args)
16178 }
16179 _ => None,
16180 }
16181 }
16182
16183 match target {
16184 DialectType::Spark | DialectType::Databricks => {
16185 // Presto MAP(keys, vals) -> Spark MAP_FROM_ARRAYS(keys, vals)
16186 Ok(Expression::Function(Box::new(Function::new(
16187 "MAP_FROM_ARRAYS".to_string(),
16188 f.args,
16189 ))))
16190 }
16191 DialectType::Hive => {
16192 // Presto MAP(ARRAY[k1,k2], ARRAY[v1,v2]) -> Hive MAP(k1, v1, k2, v2)
16193 if let (Some(keys), Some(vals)) = (
16194 extract_array_elements(&keys_arg),
16195 extract_array_elements(&vals_arg),
16196 ) {
16197 if keys.len() == vals.len() {
16198 let mut interleaved = Vec::new();
16199 for (k, v) in keys.iter().zip(vals.iter()) {
16200 interleaved.push(k.clone());
16201 interleaved.push(v.clone());
16202 }
16203 Ok(Expression::Function(Box::new(Function::new(
16204 "MAP".to_string(),
16205 interleaved,
16206 ))))
16207 } else {
16208 Ok(Expression::Function(Box::new(Function::new(
16209 "MAP".to_string(),
16210 f.args,
16211 ))))
16212 }
16213 } else {
16214 Ok(Expression::Function(Box::new(Function::new(
16215 "MAP".to_string(),
16216 f.args,
16217 ))))
16218 }
16219 }
16220 DialectType::Snowflake => {
16221 // Presto MAP(ARRAY[k1,k2], ARRAY[v1,v2]) -> Snowflake OBJECT_CONSTRUCT(k1, v1, k2, v2)
16222 if let (Some(keys), Some(vals)) = (
16223 extract_array_elements(&keys_arg),
16224 extract_array_elements(&vals_arg),
16225 ) {
16226 if keys.len() == vals.len() {
16227 let mut interleaved = Vec::new();
16228 for (k, v) in keys.iter().zip(vals.iter()) {
16229 interleaved.push(k.clone());
16230 interleaved.push(v.clone());
16231 }
16232 Ok(Expression::Function(Box::new(Function::new(
16233 "OBJECT_CONSTRUCT".to_string(),
16234 interleaved,
16235 ))))
16236 } else {
16237 Ok(Expression::Function(Box::new(Function::new(
16238 "MAP".to_string(),
16239 f.args,
16240 ))))
16241 }
16242 } else {
16243 Ok(Expression::Function(Box::new(Function::new(
16244 "MAP".to_string(),
16245 f.args,
16246 ))))
16247 }
16248 }
16249 _ => Ok(Expression::Function(f)),
16250 }
16251 }
16252 // MAP() with 0 args from Spark -> MAP(ARRAY[], ARRAY[]) for Presto/Trino
16253 "MAP"
16254 if f.args.is_empty()
16255 && matches!(
16256 source,
16257 DialectType::Hive
16258 | DialectType::Spark
16259 | DialectType::Databricks
16260 )
16261 && matches!(
16262 target,
16263 DialectType::Presto
16264 | DialectType::Trino
16265 | DialectType::Athena
16266 ) =>
16267 {
16268 let empty_keys =
16269 Expression::Array(Box::new(crate::expressions::Array {
16270 expressions: vec![],
16271 }));
16272 let empty_vals =
16273 Expression::Array(Box::new(crate::expressions::Array {
16274 expressions: vec![],
16275 }));
16276 Ok(Expression::Function(Box::new(Function::new(
16277 "MAP".to_string(),
16278 vec![empty_keys, empty_vals],
16279 ))))
16280 }
16281 // MAP(k1, v1, k2, v2, ...) from Hive/Spark -> target-specific
16282 "MAP"
16283 if f.args.len() >= 2
16284 && f.args.len() % 2 == 0
16285 && matches!(
16286 source,
16287 DialectType::Hive
16288 | DialectType::Spark
16289 | DialectType::Databricks
16290 | DialectType::ClickHouse
16291 | DialectType::StarRocks
16292 ) =>
16293 {
16294 let args = f.args;
16295 match target {
16296 DialectType::DuckDB => {
16297 // MAP([k1, k2], [v1, v2])
16298 let mut keys = Vec::new();
16299 let mut vals = Vec::new();
16300 for (i, arg) in args.into_iter().enumerate() {
16301 if i % 2 == 0 {
16302 keys.push(arg);
16303 } else {
16304 vals.push(arg);
16305 }
16306 }
16307 let keys_arr = Expression::Array(Box::new(
16308 crate::expressions::Array { expressions: keys },
16309 ));
16310 let vals_arr = Expression::Array(Box::new(
16311 crate::expressions::Array { expressions: vals },
16312 ));
16313 Ok(Expression::Function(Box::new(Function::new(
16314 "MAP".to_string(),
16315 vec![keys_arr, vals_arr],
16316 ))))
16317 }
16318 DialectType::Presto | DialectType::Trino => {
16319 // MAP(ARRAY[k1, k2], ARRAY[v1, v2])
16320 let mut keys = Vec::new();
16321 let mut vals = Vec::new();
16322 for (i, arg) in args.into_iter().enumerate() {
16323 if i % 2 == 0 {
16324 keys.push(arg);
16325 } else {
16326 vals.push(arg);
16327 }
16328 }
16329 let keys_arr = Expression::Array(Box::new(
16330 crate::expressions::Array { expressions: keys },
16331 ));
16332 let vals_arr = Expression::Array(Box::new(
16333 crate::expressions::Array { expressions: vals },
16334 ));
16335 Ok(Expression::Function(Box::new(Function::new(
16336 "MAP".to_string(),
16337 vec![keys_arr, vals_arr],
16338 ))))
16339 }
16340 DialectType::Snowflake => Ok(Expression::Function(Box::new(
16341 Function::new("OBJECT_CONSTRUCT".to_string(), args),
16342 ))),
16343 DialectType::ClickHouse => Ok(Expression::Function(Box::new(
16344 Function::new("map".to_string(), args),
16345 ))),
16346 _ => Ok(Expression::Function(Box::new(Function::new(
16347 "MAP".to_string(),
16348 args,
16349 )))),
16350 }
16351 }
16352 // COLLECT_LIST(x) -> ARRAY_AGG(x) for most targets
16353 "COLLECT_LIST" if f.args.len() >= 1 => {
16354 let name = match target {
16355 DialectType::Spark
16356 | DialectType::Databricks
16357 | DialectType::Hive => "COLLECT_LIST",
16358 DialectType::DuckDB
16359 | DialectType::PostgreSQL
16360 | DialectType::Redshift
16361 | DialectType::Snowflake
16362 | DialectType::BigQuery => "ARRAY_AGG",
16363 DialectType::Presto | DialectType::Trino => "ARRAY_AGG",
16364 _ => "ARRAY_AGG",
16365 };
16366 Ok(Expression::Function(Box::new(Function::new(
16367 name.to_string(),
16368 f.args,
16369 ))))
16370 }
16371 // COLLECT_SET(x) -> target-specific distinct array aggregation
16372 "COLLECT_SET" if f.args.len() >= 1 => {
16373 let name = match target {
16374 DialectType::Spark
16375 | DialectType::Databricks
16376 | DialectType::Hive => "COLLECT_SET",
16377 DialectType::Presto
16378 | DialectType::Trino
16379 | DialectType::Athena => "SET_AGG",
16380 DialectType::Snowflake => "ARRAY_UNIQUE_AGG",
16381 _ => "ARRAY_AGG",
16382 };
16383 Ok(Expression::Function(Box::new(Function::new(
16384 name.to_string(),
16385 f.args,
16386 ))))
16387 }
16388 // ISNAN(x) / IS_NAN(x) - normalize
16389 "ISNAN" | "IS_NAN" => {
16390 let name = match target {
16391 DialectType::Spark
16392 | DialectType::Databricks
16393 | DialectType::Hive => "ISNAN",
16394 DialectType::Presto
16395 | DialectType::Trino
16396 | DialectType::Athena => "IS_NAN",
16397 DialectType::BigQuery
16398 | DialectType::PostgreSQL
16399 | DialectType::Redshift => "IS_NAN",
16400 DialectType::ClickHouse => "IS_NAN",
16401 _ => "ISNAN",
16402 };
16403 Ok(Expression::Function(Box::new(Function::new(
16404 name.to_string(),
16405 f.args,
16406 ))))
16407 }
16408 // SPLIT_PART(str, delim, index) -> target-specific
16409 "SPLIT_PART" if f.args.len() == 3 => {
16410 match target {
16411 DialectType::Spark | DialectType::Databricks => {
16412 // Keep as SPLIT_PART (Spark 3.4+)
16413 Ok(Expression::Function(Box::new(Function::new(
16414 "SPLIT_PART".to_string(),
16415 f.args,
16416 ))))
16417 }
16418 DialectType::DuckDB
16419 if matches!(source, DialectType::Snowflake) =>
16420 {
16421 // Snowflake SPLIT_PART -> DuckDB with CASE wrapper:
16422 // - part_index 0 treated as 1
16423 // - empty delimiter: return whole string if index 1 or -1, else ''
16424 let mut args = f.args;
16425 let str_arg = args.remove(0);
16426 let delim_arg = args.remove(0);
16427 let idx_arg = args.remove(0);
16428
16429 // (CASE WHEN idx = 0 THEN 1 ELSE idx END)
16430 let adjusted_idx = Expression::Paren(Box::new(Paren {
16431 this: Expression::Case(Box::new(Case {
16432 operand: None,
16433 whens: vec![(
16434 Expression::Eq(Box::new(BinaryOp {
16435 left: idx_arg.clone(),
16436 right: Expression::number(0),
16437 left_comments: vec![],
16438 operator_comments: vec![],
16439 trailing_comments: vec![],
16440 inferred_type: None,
16441 })),
16442 Expression::number(1),
16443 )],
16444 else_: Some(idx_arg.clone()),
16445 comments: vec![],
16446 inferred_type: None,
16447 })),
16448 trailing_comments: vec![],
16449 }));
16450
16451 // SPLIT_PART(str, delim, adjusted_idx)
16452 let base_func =
16453 Expression::Function(Box::new(Function::new(
16454 "SPLIT_PART".to_string(),
16455 vec![
16456 str_arg.clone(),
16457 delim_arg.clone(),
16458 adjusted_idx.clone(),
16459 ],
16460 )));
16461
16462 // (CASE WHEN adjusted_idx = 1 OR adjusted_idx = -1 THEN str ELSE '' END)
16463 let empty_delim_case = Expression::Paren(Box::new(Paren {
16464 this: Expression::Case(Box::new(Case {
16465 operand: None,
16466 whens: vec![(
16467 Expression::Or(Box::new(BinaryOp {
16468 left: Expression::Eq(Box::new(BinaryOp {
16469 left: adjusted_idx.clone(),
16470 right: Expression::number(1),
16471 left_comments: vec![],
16472 operator_comments: vec![],
16473 trailing_comments: vec![],
16474 inferred_type: None,
16475 })),
16476 right: Expression::Eq(Box::new(BinaryOp {
16477 left: adjusted_idx,
16478 right: Expression::number(-1),
16479 left_comments: vec![],
16480 operator_comments: vec![],
16481 trailing_comments: vec![],
16482 inferred_type: None,
16483 })),
16484 left_comments: vec![],
16485 operator_comments: vec![],
16486 trailing_comments: vec![],
16487 inferred_type: None,
16488 })),
16489 str_arg,
16490 )],
16491 else_: Some(Expression::string("")),
16492 comments: vec![],
16493 inferred_type: None,
16494 })),
16495 trailing_comments: vec![],
16496 }));
16497
16498 // CASE WHEN delim = '' THEN (empty case) ELSE SPLIT_PART(...) END
16499 Ok(Expression::Case(Box::new(Case {
16500 operand: None,
16501 whens: vec![(
16502 Expression::Eq(Box::new(BinaryOp {
16503 left: delim_arg,
16504 right: Expression::string(""),
16505 left_comments: vec![],
16506 operator_comments: vec![],
16507 trailing_comments: vec![],
16508 inferred_type: None,
16509 })),
16510 empty_delim_case,
16511 )],
16512 else_: Some(base_func),
16513 comments: vec![],
16514 inferred_type: None,
16515 })))
16516 }
16517 DialectType::DuckDB
16518 | DialectType::PostgreSQL
16519 | DialectType::Snowflake
16520 | DialectType::Redshift
16521 | DialectType::Trino
16522 | DialectType::Presto => Ok(Expression::Function(Box::new(
16523 Function::new("SPLIT_PART".to_string(), f.args),
16524 ))),
16525 DialectType::Hive => {
16526 // SPLIT(str, delim)[index]
16527 // Complex conversion, just keep as-is for now
16528 Ok(Expression::Function(Box::new(Function::new(
16529 "SPLIT_PART".to_string(),
16530 f.args,
16531 ))))
16532 }
16533 _ => Ok(Expression::Function(Box::new(Function::new(
16534 "SPLIT_PART".to_string(),
16535 f.args,
16536 )))),
16537 }
16538 }
16539 // JSON_EXTRACT(json, path) -> target-specific JSON extraction
16540 "JSON_EXTRACT" | "JSON_EXTRACT_SCALAR" if f.args.len() == 2 => {
16541 let is_scalar = name == "JSON_EXTRACT_SCALAR";
16542 match target {
16543 DialectType::Spark
16544 | DialectType::Databricks
16545 | DialectType::Hive => {
16546 let mut args = f.args;
16547 // Spark/Hive don't support Presto's TRY(expr) wrapper form here.
16548 // Mirror sqlglot by unwrapping TRY(expr) to expr before GET_JSON_OBJECT.
16549 if let Some(Expression::Function(inner)) = args.first() {
16550 if inner.name.eq_ignore_ascii_case("TRY")
16551 && inner.args.len() == 1
16552 {
16553 let mut inner_args = inner.args.clone();
16554 args[0] = inner_args.remove(0);
16555 }
16556 }
16557 Ok(Expression::Function(Box::new(Function::new(
16558 "GET_JSON_OBJECT".to_string(),
16559 args,
16560 ))))
16561 }
16562 DialectType::DuckDB | DialectType::SQLite => {
16563 // json -> path syntax
16564 let mut args = f.args;
16565 let json_expr = args.remove(0);
16566 let path = args.remove(0);
16567 Ok(Expression::JsonExtract(Box::new(
16568 crate::expressions::JsonExtractFunc {
16569 this: json_expr,
16570 path,
16571 returning: None,
16572 arrow_syntax: true,
16573 hash_arrow_syntax: false,
16574 wrapper_option: None,
16575 quotes_option: None,
16576 on_scalar_string: false,
16577 on_error: None,
16578 },
16579 )))
16580 }
16581 DialectType::TSQL => {
16582 let func_name = if is_scalar {
16583 "JSON_VALUE"
16584 } else {
16585 "JSON_QUERY"
16586 };
16587 Ok(Expression::Function(Box::new(Function::new(
16588 func_name.to_string(),
16589 f.args,
16590 ))))
16591 }
16592 DialectType::PostgreSQL | DialectType::Redshift => {
16593 let func_name = if is_scalar {
16594 "JSON_EXTRACT_PATH_TEXT"
16595 } else {
16596 "JSON_EXTRACT_PATH"
16597 };
16598 Ok(Expression::Function(Box::new(Function::new(
16599 func_name.to_string(),
16600 f.args,
16601 ))))
16602 }
16603 _ => Ok(Expression::Function(Box::new(Function::new(
16604 name.to_string(),
16605 f.args,
16606 )))),
16607 }
16608 }
16609 // MySQL JSON_SEARCH(json_doc, mode, search[, escape_char[, path]]) -> DuckDB json_tree-based lookup
16610 "JSON_SEARCH"
16611 if matches!(target, DialectType::DuckDB)
16612 && (3..=5).contains(&f.args.len()) =>
16613 {
16614 let args = &f.args;
16615
16616 // Only rewrite deterministic modes and NULL/no escape-char variant.
16617 let mode = match &args[1] {
16618 Expression::Literal(lit)
16619 if matches!(
16620 lit.as_ref(),
16621 crate::expressions::Literal::String(_)
16622 ) =>
16623 {
16624 let crate::expressions::Literal::String(s) = lit.as_ref()
16625 else {
16626 unreachable!()
16627 };
16628 s.to_ascii_lowercase()
16629 }
16630 _ => return Ok(Expression::Function(f)),
16631 };
16632 if mode != "one" && mode != "all" {
16633 return Ok(Expression::Function(f));
16634 }
16635 if args.len() >= 4 && !matches!(&args[3], Expression::Null(_)) {
16636 return Ok(Expression::Function(f));
16637 }
16638
16639 let json_doc_sql = match Generator::sql(&args[0]) {
16640 Ok(sql) => sql,
16641 Err(_) => return Ok(Expression::Function(f)),
16642 };
16643 let search_sql = match Generator::sql(&args[2]) {
16644 Ok(sql) => sql,
16645 Err(_) => return Ok(Expression::Function(f)),
16646 };
16647 let path_sql = if args.len() == 5 {
16648 match Generator::sql(&args[4]) {
16649 Ok(sql) => sql,
16650 Err(_) => return Ok(Expression::Function(f)),
16651 }
16652 } else {
16653 "'$'".to_string()
16654 };
16655
16656 let rewrite_sql = if mode == "all" {
16657 format!(
16658 "(SELECT TO_JSON(LIST(__jt.fullkey)) FROM json_tree({}, {}) AS __jt WHERE __jt.atom = TO_JSON({}))",
16659 json_doc_sql, path_sql, search_sql
16660 )
16661 } else {
16662 format!(
16663 "(SELECT TO_JSON(__jt.fullkey) FROM json_tree({}, {}) AS __jt WHERE __jt.atom = TO_JSON({}) ORDER BY __jt.id LIMIT 1)",
16664 json_doc_sql, path_sql, search_sql
16665 )
16666 };
16667
16668 Ok(Expression::Raw(crate::expressions::Raw {
16669 sql: rewrite_sql,
16670 }))
16671 }
16672 // SingleStore JSON_EXTRACT_JSON(json, key1, key2, ...) -> JSON_EXTRACT(json, '$.key1.key2' or '$.key1[key2]')
16673 // BSON_EXTRACT_BSON(json, key1, ...) -> JSONB_EXTRACT(json, '$.key1')
16674 "JSON_EXTRACT_JSON" | "BSON_EXTRACT_BSON"
16675 if f.args.len() >= 2
16676 && matches!(source, DialectType::SingleStore) =>
16677 {
16678 let is_bson = name == "BSON_EXTRACT_BSON";
16679 let mut args = f.args;
16680 let json_expr = args.remove(0);
16681
16682 // Build JSONPath from remaining arguments
16683 let mut path = String::from("$");
16684 for arg in &args {
16685 if let Expression::Literal(lit) = arg {
16686 if let crate::expressions::Literal::String(s) = lit.as_ref()
16687 {
16688 // Check if it's a numeric string (array index)
16689 if s.parse::<i64>().is_ok() {
16690 path.push('[');
16691 path.push_str(s);
16692 path.push(']');
16693 } else {
16694 path.push('.');
16695 path.push_str(s);
16696 }
16697 }
16698 }
16699 }
16700
16701 let target_func = if is_bson {
16702 "JSONB_EXTRACT"
16703 } else {
16704 "JSON_EXTRACT"
16705 };
16706 Ok(Expression::Function(Box::new(Function::new(
16707 target_func.to_string(),
16708 vec![json_expr, Expression::string(&path)],
16709 ))))
16710 }
16711 // ARRAY_SUM(lambda, array) from Doris -> ClickHouse arraySum
16712 "ARRAY_SUM" if matches!(target, DialectType::ClickHouse) => {
16713 Ok(Expression::Function(Box::new(Function {
16714 name: "arraySum".to_string(),
16715 args: f.args,
16716 distinct: f.distinct,
16717 trailing_comments: f.trailing_comments,
16718 use_bracket_syntax: f.use_bracket_syntax,
16719 no_parens: f.no_parens,
16720 quoted: f.quoted,
16721 span: None,
16722 inferred_type: None,
16723 })))
16724 }
16725 // TSQL JSON_QUERY/JSON_VALUE -> target-specific
16726 // Note: For TSQL->TSQL, JsonQuery stays as Expression::JsonQuery (source transform not called)
16727 // and is handled by JsonQueryValueConvert action. This handles the case where
16728 // TSQL read transform converted JsonQuery to Function("JSON_QUERY") for cross-dialect.
16729 "JSON_QUERY" | "JSON_VALUE"
16730 if f.args.len() == 2
16731 && matches!(
16732 source,
16733 DialectType::TSQL | DialectType::Fabric
16734 ) =>
16735 {
16736 match target {
16737 DialectType::Spark
16738 | DialectType::Databricks
16739 | DialectType::Hive => Ok(Expression::Function(Box::new(
16740 Function::new("GET_JSON_OBJECT".to_string(), f.args),
16741 ))),
16742 _ => Ok(Expression::Function(Box::new(Function::new(
16743 name.to_string(),
16744 f.args,
16745 )))),
16746 }
16747 }
16748 // UNIX_TIMESTAMP(x) -> TO_UNIXTIME(x) for Presto
16749 "UNIX_TIMESTAMP" if f.args.len() == 1 => {
16750 let arg = f.args.into_iter().next().unwrap();
16751 let is_hive_source = matches!(
16752 source,
16753 DialectType::Hive
16754 | DialectType::Spark
16755 | DialectType::Databricks
16756 );
16757 match target {
16758 DialectType::DuckDB if is_hive_source => {
16759 // DuckDB: EPOCH(STRPTIME(x, '%Y-%m-%d %H:%M:%S'))
16760 let strptime =
16761 Expression::Function(Box::new(Function::new(
16762 "STRPTIME".to_string(),
16763 vec![arg, Expression::string("%Y-%m-%d %H:%M:%S")],
16764 )));
16765 Ok(Expression::Function(Box::new(Function::new(
16766 "EPOCH".to_string(),
16767 vec![strptime],
16768 ))))
16769 }
16770 DialectType::Presto | DialectType::Trino if is_hive_source => {
16771 // Presto: TO_UNIXTIME(COALESCE(TRY(DATE_PARSE(CAST(x AS VARCHAR), '%Y-%m-%d %T')), PARSE_DATETIME(DATE_FORMAT(x, '%Y-%m-%d %T'), 'yyyy-MM-dd HH:mm:ss')))
16772 let cast_varchar =
16773 Expression::Cast(Box::new(crate::expressions::Cast {
16774 this: arg.clone(),
16775 to: DataType::VarChar {
16776 length: None,
16777 parenthesized_length: false,
16778 },
16779 trailing_comments: vec![],
16780 double_colon_syntax: false,
16781 format: None,
16782 default: None,
16783 inferred_type: None,
16784 }));
16785 let date_parse =
16786 Expression::Function(Box::new(Function::new(
16787 "DATE_PARSE".to_string(),
16788 vec![
16789 cast_varchar,
16790 Expression::string("%Y-%m-%d %T"),
16791 ],
16792 )));
16793 let try_expr = Expression::Function(Box::new(
16794 Function::new("TRY".to_string(), vec![date_parse]),
16795 ));
16796 let date_format =
16797 Expression::Function(Box::new(Function::new(
16798 "DATE_FORMAT".to_string(),
16799 vec![arg, Expression::string("%Y-%m-%d %T")],
16800 )));
16801 let parse_datetime =
16802 Expression::Function(Box::new(Function::new(
16803 "PARSE_DATETIME".to_string(),
16804 vec![
16805 date_format,
16806 Expression::string("yyyy-MM-dd HH:mm:ss"),
16807 ],
16808 )));
16809 let coalesce =
16810 Expression::Function(Box::new(Function::new(
16811 "COALESCE".to_string(),
16812 vec![try_expr, parse_datetime],
16813 )));
16814 Ok(Expression::Function(Box::new(Function::new(
16815 "TO_UNIXTIME".to_string(),
16816 vec![coalesce],
16817 ))))
16818 }
16819 DialectType::Presto | DialectType::Trino => {
16820 Ok(Expression::Function(Box::new(Function::new(
16821 "TO_UNIXTIME".to_string(),
16822 vec![arg],
16823 ))))
16824 }
16825 _ => Ok(Expression::Function(Box::new(Function::new(
16826 "UNIX_TIMESTAMP".to_string(),
16827 vec![arg],
16828 )))),
16829 }
16830 }
16831 // TO_UNIX_TIMESTAMP(x) -> UNIX_TIMESTAMP(x) for Spark/Hive
16832 "TO_UNIX_TIMESTAMP" if f.args.len() >= 1 => match target {
16833 DialectType::Spark
16834 | DialectType::Databricks
16835 | DialectType::Hive => Ok(Expression::Function(Box::new(
16836 Function::new("UNIX_TIMESTAMP".to_string(), f.args),
16837 ))),
16838 _ => Ok(Expression::Function(Box::new(Function::new(
16839 "TO_UNIX_TIMESTAMP".to_string(),
16840 f.args,
16841 )))),
16842 },
16843 // CURDATE() -> CURRENT_DATE
16844 "CURDATE" => {
16845 Ok(Expression::CurrentDate(crate::expressions::CurrentDate))
16846 }
16847 // CURTIME() -> CURRENT_TIME
16848 "CURTIME" => {
16849 Ok(Expression::CurrentTime(crate::expressions::CurrentTime {
16850 precision: None,
16851 }))
16852 }
16853 // ARRAY_SORT(x) or ARRAY_SORT(x, lambda) -> SORT_ARRAY(x) for Hive, LIST_SORT for DuckDB
16854 "ARRAY_SORT" if f.args.len() >= 1 => {
16855 match target {
16856 DialectType::Hive => {
16857 let mut args = f.args;
16858 args.truncate(1); // Drop lambda comparator
16859 Ok(Expression::Function(Box::new(Function::new(
16860 "SORT_ARRAY".to_string(),
16861 args,
16862 ))))
16863 }
16864 DialectType::DuckDB
16865 if matches!(source, DialectType::Snowflake) =>
16866 {
16867 // Snowflake ARRAY_SORT(arr[, asc_bool[, nulls_first_bool]]) -> DuckDB LIST_SORT(arr[, 'ASC'/'DESC'[, 'NULLS FIRST']])
16868 let mut args_iter = f.args.into_iter();
16869 let arr = args_iter.next().unwrap();
16870 let asc_arg = args_iter.next();
16871 let nulls_first_arg = args_iter.next();
16872
16873 let is_asc_bool = asc_arg
16874 .as_ref()
16875 .map(|a| matches!(a, Expression::Boolean(_)))
16876 .unwrap_or(false);
16877 let is_nf_bool = nulls_first_arg
16878 .as_ref()
16879 .map(|a| matches!(a, Expression::Boolean(_)))
16880 .unwrap_or(false);
16881
16882 // No boolean args: pass through as-is
16883 if !is_asc_bool && !is_nf_bool {
16884 let mut result_args = vec![arr];
16885 if let Some(asc) = asc_arg {
16886 result_args.push(asc);
16887 if let Some(nf) = nulls_first_arg {
16888 result_args.push(nf);
16889 }
16890 }
16891 Ok(Expression::Function(Box::new(Function::new(
16892 "LIST_SORT".to_string(),
16893 result_args,
16894 ))))
16895 } else {
16896 // Has boolean args: convert to DuckDB LIST_SORT format
16897 let descending = matches!(&asc_arg, Some(Expression::Boolean(b)) if !b.value);
16898
16899 // Snowflake defaults: nulls_first = TRUE for DESC, FALSE for ASC
16900 let nulls_are_first = match &nulls_first_arg {
16901 Some(Expression::Boolean(b)) => b.value,
16902 None if is_asc_bool => descending, // Snowflake default
16903 _ => false,
16904 };
16905 let nulls_first_sql = if nulls_are_first {
16906 Some(Expression::string("NULLS FIRST"))
16907 } else {
16908 None
16909 };
16910
16911 if !is_asc_bool {
16912 // asc is non-boolean expression, nulls_first is boolean
16913 let mut result_args = vec![arr];
16914 if let Some(asc) = asc_arg {
16915 result_args.push(asc);
16916 }
16917 if let Some(nf) = nulls_first_sql {
16918 result_args.push(nf);
16919 }
16920 Ok(Expression::Function(Box::new(Function::new(
16921 "LIST_SORT".to_string(),
16922 result_args,
16923 ))))
16924 } else {
16925 if !descending && !nulls_are_first {
16926 // ASC, NULLS LAST (default) -> LIST_SORT(arr)
16927 Ok(Expression::Function(Box::new(
16928 Function::new(
16929 "LIST_SORT".to_string(),
16930 vec![arr],
16931 ),
16932 )))
16933 } else if descending && !nulls_are_first {
16934 // DESC, NULLS LAST -> ARRAY_REVERSE_SORT(arr)
16935 Ok(Expression::Function(Box::new(
16936 Function::new(
16937 "ARRAY_REVERSE_SORT".to_string(),
16938 vec![arr],
16939 ),
16940 )))
16941 } else {
16942 // NULLS FIRST -> LIST_SORT(arr, 'ASC'/'DESC', 'NULLS FIRST')
16943 let order_str =
16944 if descending { "DESC" } else { "ASC" };
16945 Ok(Expression::Function(Box::new(
16946 Function::new(
16947 "LIST_SORT".to_string(),
16948 vec![
16949 arr,
16950 Expression::string(order_str),
16951 Expression::string("NULLS FIRST"),
16952 ],
16953 ),
16954 )))
16955 }
16956 }
16957 }
16958 }
16959 DialectType::DuckDB => {
16960 // Non-Snowflake source: ARRAY_SORT(x, lambda) -> ARRAY_SORT(x) (drop comparator)
16961 let mut args = f.args;
16962 args.truncate(1); // Drop lambda comparator for DuckDB
16963 Ok(Expression::Function(Box::new(Function::new(
16964 "ARRAY_SORT".to_string(),
16965 args,
16966 ))))
16967 }
16968 _ => Ok(Expression::Function(f)),
16969 }
16970 }
16971 // SORT_ARRAY(x) -> LIST_SORT(x) for DuckDB, ARRAY_SORT(x) for Presto/Trino, keep for Hive/Spark
16972 "SORT_ARRAY" if f.args.len() == 1 => match target {
16973 DialectType::Hive
16974 | DialectType::Spark
16975 | DialectType::Databricks => Ok(Expression::Function(f)),
16976 DialectType::DuckDB => Ok(Expression::Function(Box::new(
16977 Function::new("LIST_SORT".to_string(), f.args),
16978 ))),
16979 _ => Ok(Expression::Function(Box::new(Function::new(
16980 "ARRAY_SORT".to_string(),
16981 f.args,
16982 )))),
16983 },
16984 // SORT_ARRAY(x, FALSE) -> ARRAY_REVERSE_SORT(x) for DuckDB, ARRAY_SORT(x, lambda) for Presto
16985 "SORT_ARRAY" if f.args.len() == 2 => {
16986 let is_desc =
16987 matches!(&f.args[1], Expression::Boolean(b) if !b.value);
16988 if is_desc {
16989 match target {
16990 DialectType::DuckDB => {
16991 Ok(Expression::Function(Box::new(Function::new(
16992 "ARRAY_REVERSE_SORT".to_string(),
16993 vec![f.args.into_iter().next().unwrap()],
16994 ))))
16995 }
16996 DialectType::Presto | DialectType::Trino => {
16997 let arr_arg = f.args.into_iter().next().unwrap();
16998 let a = Expression::Column(Box::new(
16999 crate::expressions::Column {
17000 name: crate::expressions::Identifier::new("a"),
17001 table: None,
17002 join_mark: false,
17003 trailing_comments: Vec::new(),
17004 span: None,
17005 inferred_type: None,
17006 },
17007 ));
17008 let b = Expression::Column(Box::new(
17009 crate::expressions::Column {
17010 name: crate::expressions::Identifier::new("b"),
17011 table: None,
17012 join_mark: false,
17013 trailing_comments: Vec::new(),
17014 span: None,
17015 inferred_type: None,
17016 },
17017 ));
17018 let case_expr = Expression::Case(Box::new(
17019 crate::expressions::Case {
17020 operand: None,
17021 whens: vec![
17022 (
17023 Expression::Lt(Box::new(
17024 BinaryOp::new(a.clone(), b.clone()),
17025 )),
17026 Expression::Literal(Box::new(
17027 Literal::Number("1".to_string()),
17028 )),
17029 ),
17030 (
17031 Expression::Gt(Box::new(
17032 BinaryOp::new(a.clone(), b.clone()),
17033 )),
17034 Expression::Literal(Box::new(
17035 Literal::Number("-1".to_string()),
17036 )),
17037 ),
17038 ],
17039 else_: Some(Expression::Literal(Box::new(
17040 Literal::Number("0".to_string()),
17041 ))),
17042 comments: Vec::new(),
17043 inferred_type: None,
17044 },
17045 ));
17046 let lambda = Expression::Lambda(Box::new(
17047 crate::expressions::LambdaExpr {
17048 parameters: vec![
17049 crate::expressions::Identifier::new("a"),
17050 crate::expressions::Identifier::new("b"),
17051 ],
17052 body: case_expr,
17053 colon: false,
17054 parameter_types: Vec::new(),
17055 },
17056 ));
17057 Ok(Expression::Function(Box::new(Function::new(
17058 "ARRAY_SORT".to_string(),
17059 vec![arr_arg, lambda],
17060 ))))
17061 }
17062 _ => Ok(Expression::Function(f)),
17063 }
17064 } else {
17065 // SORT_ARRAY(x, TRUE) -> LIST_SORT(x) for DuckDB, ARRAY_SORT(x) for others
17066 match target {
17067 DialectType::Hive => Ok(Expression::Function(f)),
17068 DialectType::DuckDB => {
17069 Ok(Expression::Function(Box::new(Function::new(
17070 "LIST_SORT".to_string(),
17071 vec![f.args.into_iter().next().unwrap()],
17072 ))))
17073 }
17074 _ => Ok(Expression::Function(Box::new(Function::new(
17075 "ARRAY_SORT".to_string(),
17076 vec![f.args.into_iter().next().unwrap()],
17077 )))),
17078 }
17079 }
17080 }
17081 // LEFT(x, n), RIGHT(x, n) -> SUBSTRING for targets without LEFT/RIGHT
17082 "LEFT" if f.args.len() == 2 => {
17083 match target {
17084 DialectType::Hive
17085 | DialectType::Presto
17086 | DialectType::Trino
17087 | DialectType::Athena => {
17088 let x = f.args[0].clone();
17089 let n = f.args[1].clone();
17090 Ok(Expression::Function(Box::new(Function::new(
17091 "SUBSTRING".to_string(),
17092 vec![x, Expression::number(1), n],
17093 ))))
17094 }
17095 DialectType::Spark | DialectType::Databricks
17096 if matches!(
17097 source,
17098 DialectType::TSQL | DialectType::Fabric
17099 ) =>
17100 {
17101 // TSQL LEFT(x, n) -> LEFT(CAST(x AS STRING), n) for Spark
17102 let x = f.args[0].clone();
17103 let n = f.args[1].clone();
17104 let cast_x = Expression::Cast(Box::new(Cast {
17105 this: x,
17106 to: DataType::VarChar {
17107 length: None,
17108 parenthesized_length: false,
17109 },
17110 double_colon_syntax: false,
17111 trailing_comments: Vec::new(),
17112 format: None,
17113 default: None,
17114 inferred_type: None,
17115 }));
17116 Ok(Expression::Function(Box::new(Function::new(
17117 "LEFT".to_string(),
17118 vec![cast_x, n],
17119 ))))
17120 }
17121 _ => Ok(Expression::Function(f)),
17122 }
17123 }
17124 "RIGHT" if f.args.len() == 2 => {
17125 match target {
17126 DialectType::Hive
17127 | DialectType::Presto
17128 | DialectType::Trino
17129 | DialectType::Athena => {
17130 let x = f.args[0].clone();
17131 let n = f.args[1].clone();
17132 // SUBSTRING(x, LENGTH(x) - (n - 1))
17133 let len_x = Expression::Function(Box::new(Function::new(
17134 "LENGTH".to_string(),
17135 vec![x.clone()],
17136 )));
17137 let n_minus_1 = Expression::Sub(Box::new(
17138 crate::expressions::BinaryOp::new(
17139 n,
17140 Expression::number(1),
17141 ),
17142 ));
17143 let n_minus_1_paren = Expression::Paren(Box::new(
17144 crate::expressions::Paren {
17145 this: n_minus_1,
17146 trailing_comments: Vec::new(),
17147 },
17148 ));
17149 let offset = Expression::Sub(Box::new(
17150 crate::expressions::BinaryOp::new(
17151 len_x,
17152 n_minus_1_paren,
17153 ),
17154 ));
17155 Ok(Expression::Function(Box::new(Function::new(
17156 "SUBSTRING".to_string(),
17157 vec![x, offset],
17158 ))))
17159 }
17160 DialectType::Spark | DialectType::Databricks
17161 if matches!(
17162 source,
17163 DialectType::TSQL | DialectType::Fabric
17164 ) =>
17165 {
17166 // TSQL RIGHT(x, n) -> RIGHT(CAST(x AS STRING), n) for Spark
17167 let x = f.args[0].clone();
17168 let n = f.args[1].clone();
17169 let cast_x = Expression::Cast(Box::new(Cast {
17170 this: x,
17171 to: DataType::VarChar {
17172 length: None,
17173 parenthesized_length: false,
17174 },
17175 double_colon_syntax: false,
17176 trailing_comments: Vec::new(),
17177 format: None,
17178 default: None,
17179 inferred_type: None,
17180 }));
17181 Ok(Expression::Function(Box::new(Function::new(
17182 "RIGHT".to_string(),
17183 vec![cast_x, n],
17184 ))))
17185 }
17186 _ => Ok(Expression::Function(f)),
17187 }
17188 }
17189 // MAP_FROM_ARRAYS(keys, vals) -> target-specific map construction
17190 "MAP_FROM_ARRAYS" if f.args.len() == 2 => match target {
17191 DialectType::Snowflake => Ok(Expression::Function(Box::new(
17192 Function::new("OBJECT_CONSTRUCT".to_string(), f.args),
17193 ))),
17194 DialectType::Spark | DialectType::Databricks => {
17195 Ok(Expression::Function(Box::new(Function::new(
17196 "MAP_FROM_ARRAYS".to_string(),
17197 f.args,
17198 ))))
17199 }
17200 _ => Ok(Expression::Function(Box::new(Function::new(
17201 "MAP".to_string(),
17202 f.args,
17203 )))),
17204 },
17205 // LIKE(foo, 'pat') -> foo LIKE 'pat'; LIKE(foo, 'pat', '!') -> foo LIKE 'pat' ESCAPE '!'
17206 // SQLite uses LIKE(pattern, string[, escape]) with args in reverse order
17207 "LIKE" if f.args.len() >= 2 => {
17208 let (this, pattern) = if matches!(source, DialectType::SQLite) {
17209 // SQLite: LIKE(pattern, string) -> string LIKE pattern
17210 (f.args[1].clone(), f.args[0].clone())
17211 } else {
17212 // Standard: LIKE(string, pattern) -> string LIKE pattern
17213 (f.args[0].clone(), f.args[1].clone())
17214 };
17215 let escape = if f.args.len() >= 3 {
17216 Some(f.args[2].clone())
17217 } else {
17218 None
17219 };
17220 Ok(Expression::Like(Box::new(crate::expressions::LikeOp {
17221 left: this,
17222 right: pattern,
17223 escape,
17224 quantifier: None,
17225 inferred_type: None,
17226 })))
17227 }
17228 // ILIKE(foo, 'pat') -> foo ILIKE 'pat'
17229 "ILIKE" if f.args.len() >= 2 => {
17230 let this = f.args[0].clone();
17231 let pattern = f.args[1].clone();
17232 let escape = if f.args.len() >= 3 {
17233 Some(f.args[2].clone())
17234 } else {
17235 None
17236 };
17237 Ok(Expression::ILike(Box::new(crate::expressions::LikeOp {
17238 left: this,
17239 right: pattern,
17240 escape,
17241 quantifier: None,
17242 inferred_type: None,
17243 })))
17244 }
17245 // CHAR(n) -> CHR(n) for non-MySQL/non-TSQL targets
17246 "CHAR" if f.args.len() == 1 => match target {
17247 DialectType::MySQL
17248 | DialectType::SingleStore
17249 | DialectType::TSQL => Ok(Expression::Function(f)),
17250 _ => Ok(Expression::Function(Box::new(Function::new(
17251 "CHR".to_string(),
17252 f.args,
17253 )))),
17254 },
17255 // CONCAT(a, b) -> a || b for PostgreSQL
17256 "CONCAT"
17257 if f.args.len() == 2
17258 && matches!(target, DialectType::PostgreSQL)
17259 && matches!(
17260 source,
17261 DialectType::ClickHouse | DialectType::MySQL
17262 ) =>
17263 {
17264 let mut args = f.args;
17265 let right = args.pop().unwrap();
17266 let left = args.pop().unwrap();
17267 Ok(Expression::DPipe(Box::new(crate::expressions::DPipe {
17268 this: Box::new(left),
17269 expression: Box::new(right),
17270 safe: None,
17271 })))
17272 }
17273 // ARRAY_TO_STRING(arr, delim) -> target-specific
17274 "ARRAY_TO_STRING"
17275 if f.args.len() == 2
17276 && matches!(target, DialectType::DuckDB)
17277 && matches!(source, DialectType::Snowflake) =>
17278 {
17279 let mut args = f.args;
17280 let arr = args.remove(0);
17281 let sep = args.remove(0);
17282 // sep IS NULL
17283 let sep_is_null = Expression::IsNull(Box::new(IsNull {
17284 this: sep.clone(),
17285 not: false,
17286 postfix_form: false,
17287 }));
17288 // COALESCE(CAST(x AS TEXT), '')
17289 let cast_x = Expression::Cast(Box::new(Cast {
17290 this: Expression::Identifier(Identifier::new("x")),
17291 to: DataType::Text,
17292 trailing_comments: Vec::new(),
17293 double_colon_syntax: false,
17294 format: None,
17295 default: None,
17296 inferred_type: None,
17297 }));
17298 let coalesce = Expression::Coalesce(Box::new(
17299 crate::expressions::VarArgFunc {
17300 original_name: None,
17301 expressions: vec![
17302 cast_x,
17303 Expression::Literal(Box::new(Literal::String(
17304 String::new(),
17305 ))),
17306 ],
17307 inferred_type: None,
17308 },
17309 ));
17310 let lambda =
17311 Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
17312 parameters: vec![Identifier::new("x")],
17313 body: coalesce,
17314 colon: false,
17315 parameter_types: Vec::new(),
17316 }));
17317 let list_transform = Expression::Function(Box::new(Function::new(
17318 "LIST_TRANSFORM".to_string(),
17319 vec![arr, lambda],
17320 )));
17321 let array_to_string =
17322 Expression::Function(Box::new(Function::new(
17323 "ARRAY_TO_STRING".to_string(),
17324 vec![list_transform, sep],
17325 )));
17326 Ok(Expression::Case(Box::new(Case {
17327 operand: None,
17328 whens: vec![(sep_is_null, Expression::Null(Null))],
17329 else_: Some(array_to_string),
17330 comments: Vec::new(),
17331 inferred_type: None,
17332 })))
17333 }
17334 "ARRAY_TO_STRING" if f.args.len() >= 2 => match target {
17335 DialectType::Presto | DialectType::Trino => {
17336 Ok(Expression::Function(Box::new(Function::new(
17337 "ARRAY_JOIN".to_string(),
17338 f.args,
17339 ))))
17340 }
17341 DialectType::TSQL | DialectType::Fabric
17342 if matches!(
17343 source,
17344 DialectType::PostgreSQL | DialectType::CockroachDB
17345 ) =>
17346 {
17347 Ok(Expression::Function(f))
17348 }
17349 DialectType::TSQL => Ok(Expression::Function(Box::new(
17350 Function::new("STRING_AGG".to_string(), f.args),
17351 ))),
17352 _ => Ok(Expression::Function(f)),
17353 },
17354 // ARRAY_CONCAT / LIST_CONCAT -> target-specific
17355 "ARRAY_CONCAT" | "LIST_CONCAT" if f.args.len() == 2 => match target {
17356 DialectType::Spark
17357 | DialectType::Databricks
17358 | DialectType::Hive => Ok(Expression::Function(Box::new(
17359 Function::new("CONCAT".to_string(), f.args),
17360 ))),
17361 DialectType::Snowflake => Ok(Expression::Function(Box::new(
17362 Function::new("ARRAY_CAT".to_string(), f.args),
17363 ))),
17364 DialectType::Redshift => Ok(Expression::Function(Box::new(
17365 Function::new("ARRAY_CONCAT".to_string(), f.args),
17366 ))),
17367 DialectType::PostgreSQL => Ok(Expression::Function(Box::new(
17368 Function::new("ARRAY_CAT".to_string(), f.args),
17369 ))),
17370 DialectType::DuckDB => Ok(Expression::Function(Box::new(
17371 Function::new("LIST_CONCAT".to_string(), f.args),
17372 ))),
17373 DialectType::Presto | DialectType::Trino => {
17374 Ok(Expression::Function(Box::new(Function::new(
17375 "CONCAT".to_string(),
17376 f.args,
17377 ))))
17378 }
17379 DialectType::BigQuery => Ok(Expression::Function(Box::new(
17380 Function::new("ARRAY_CONCAT".to_string(), f.args),
17381 ))),
17382 _ => Ok(Expression::Function(f)),
17383 },
17384 // ARRAY_CONTAINS(arr, x) / HAS(arr, x) / CONTAINS(arr, x) normalization
17385 "HAS" if f.args.len() == 2 => match target {
17386 DialectType::Spark
17387 | DialectType::Databricks
17388 | DialectType::Hive => Ok(Expression::Function(Box::new(
17389 Function::new("ARRAY_CONTAINS".to_string(), f.args),
17390 ))),
17391 DialectType::Presto | DialectType::Trino => {
17392 Ok(Expression::Function(Box::new(Function::new(
17393 "CONTAINS".to_string(),
17394 f.args,
17395 ))))
17396 }
17397 _ => Ok(Expression::Function(f)),
17398 },
17399 // NVL(a, b, c, d) -> COALESCE(a, b, c, d) - NVL should keep all args
17400 "NVL" if f.args.len() > 2 => Ok(Expression::Function(Box::new(
17401 Function::new("COALESCE".to_string(), f.args),
17402 ))),
17403 // ISNULL(x) in MySQL -> (x IS NULL)
17404 "ISNULL"
17405 if f.args.len() == 1
17406 && matches!(source, DialectType::MySQL)
17407 && matches!(target, DialectType::MySQL) =>
17408 {
17409 let arg = f.args.into_iter().next().unwrap();
17410 Ok(Expression::Paren(Box::new(crate::expressions::Paren {
17411 this: Expression::IsNull(Box::new(
17412 crate::expressions::IsNull {
17413 this: arg,
17414 not: false,
17415 postfix_form: false,
17416 },
17417 )),
17418 trailing_comments: Vec::new(),
17419 })))
17420 }
17421 // MONTHNAME(x) -> DATE_FORMAT(x, '%M') for MySQL -> MySQL
17422 "MONTHNAME"
17423 if f.args.len() == 1 && matches!(target, DialectType::MySQL) =>
17424 {
17425 let arg = f.args.into_iter().next().unwrap();
17426 Ok(Expression::Function(Box::new(Function::new(
17427 "DATE_FORMAT".to_string(),
17428 vec![arg, Expression::string("%M")],
17429 ))))
17430 }
17431 // ClickHouse splitByString('s', x) -> DuckDB STR_SPLIT(x, 's') / Hive SPLIT(x, CONCAT('\\Q', 's', '\\E'))
17432 "SPLITBYSTRING" if f.args.len() == 2 => {
17433 let sep = f.args[0].clone();
17434 let str_arg = f.args[1].clone();
17435 match target {
17436 DialectType::DuckDB => Ok(Expression::Function(Box::new(
17437 Function::new("STR_SPLIT".to_string(), vec![str_arg, sep]),
17438 ))),
17439 DialectType::Doris => {
17440 Ok(Expression::Function(Box::new(Function::new(
17441 "SPLIT_BY_STRING".to_string(),
17442 vec![str_arg, sep],
17443 ))))
17444 }
17445 DialectType::Hive
17446 | DialectType::Spark
17447 | DialectType::Databricks => {
17448 // SPLIT(x, CONCAT('\\Q', sep, '\\E'))
17449 let escaped =
17450 Expression::Function(Box::new(Function::new(
17451 "CONCAT".to_string(),
17452 vec![
17453 Expression::string("\\Q"),
17454 sep,
17455 Expression::string("\\E"),
17456 ],
17457 )));
17458 Ok(Expression::Function(Box::new(Function::new(
17459 "SPLIT".to_string(),
17460 vec![str_arg, escaped],
17461 ))))
17462 }
17463 _ => Ok(Expression::Function(f)),
17464 }
17465 }
17466 // ClickHouse splitByRegexp('pattern', x) -> DuckDB STR_SPLIT_REGEX(x, 'pattern')
17467 "SPLITBYREGEXP" if f.args.len() == 2 => {
17468 let sep = f.args[0].clone();
17469 let str_arg = f.args[1].clone();
17470 match target {
17471 DialectType::DuckDB => {
17472 Ok(Expression::Function(Box::new(Function::new(
17473 "STR_SPLIT_REGEX".to_string(),
17474 vec![str_arg, sep],
17475 ))))
17476 }
17477 DialectType::Hive
17478 | DialectType::Spark
17479 | DialectType::Databricks => {
17480 Ok(Expression::Function(Box::new(Function::new(
17481 "SPLIT".to_string(),
17482 vec![str_arg, sep],
17483 ))))
17484 }
17485 _ => Ok(Expression::Function(f)),
17486 }
17487 }
17488 // ClickHouse toMonday(x) -> DATE_TRUNC('WEEK', x) / DATE_TRUNC(x, 'WEEK') for Doris
17489 "TOMONDAY" => {
17490 if f.args.len() == 1 {
17491 let arg = f.args.into_iter().next().unwrap();
17492 match target {
17493 DialectType::Doris => {
17494 Ok(Expression::Function(Box::new(Function::new(
17495 "DATE_TRUNC".to_string(),
17496 vec![arg, Expression::string("WEEK")],
17497 ))))
17498 }
17499 _ => Ok(Expression::Function(Box::new(Function::new(
17500 "DATE_TRUNC".to_string(),
17501 vec![Expression::string("WEEK"), arg],
17502 )))),
17503 }
17504 } else {
17505 Ok(Expression::Function(f))
17506 }
17507 }
17508 // COLLECT_LIST with FILTER(WHERE x IS NOT NULL) for targets that need it
17509 "COLLECT_LIST" if f.args.len() == 1 => match target {
17510 DialectType::Spark
17511 | DialectType::Databricks
17512 | DialectType::Hive => Ok(Expression::Function(f)),
17513 _ => Ok(Expression::Function(Box::new(Function::new(
17514 "ARRAY_AGG".to_string(),
17515 f.args,
17516 )))),
17517 },
17518 // TO_CHAR(x) with 1 arg -> CAST(x AS STRING) for Doris
17519 "TO_CHAR"
17520 if f.args.len() == 1 && matches!(target, DialectType::Doris) =>
17521 {
17522 let arg = f.args.into_iter().next().unwrap();
17523 Ok(Expression::Cast(Box::new(crate::expressions::Cast {
17524 this: arg,
17525 to: DataType::Custom {
17526 name: "STRING".to_string(),
17527 },
17528 double_colon_syntax: false,
17529 trailing_comments: Vec::new(),
17530 format: None,
17531 default: None,
17532 inferred_type: None,
17533 })))
17534 }
17535 // DBMS_RANDOM.VALUE() -> RANDOM() for PostgreSQL
17536 "DBMS_RANDOM.VALUE" if f.args.is_empty() => match target {
17537 DialectType::PostgreSQL => Ok(Expression::Function(Box::new(
17538 Function::new("RANDOM".to_string(), vec![]),
17539 ))),
17540 _ => Ok(Expression::Function(f)),
17541 },
17542 // ClickHouse formatDateTime -> target-specific
17543 "FORMATDATETIME" if f.args.len() >= 2 => match target {
17544 DialectType::MySQL => Ok(Expression::Function(Box::new(
17545 Function::new("DATE_FORMAT".to_string(), f.args),
17546 ))),
17547 _ => Ok(Expression::Function(f)),
17548 },
17549 // REPLICATE('x', n) -> REPEAT('x', n) for non-TSQL targets
17550 "REPLICATE" if f.args.len() == 2 => match target {
17551 DialectType::TSQL => Ok(Expression::Function(f)),
17552 _ => Ok(Expression::Function(Box::new(Function::new(
17553 "REPEAT".to_string(),
17554 f.args,
17555 )))),
17556 },
17557 // LEN(x) -> LENGTH(x) for non-TSQL targets
17558 // No CAST needed when arg is already a string literal
17559 "LEN" if f.args.len() == 1 => {
17560 match target {
17561 DialectType::TSQL => Ok(Expression::Function(f)),
17562 DialectType::Spark | DialectType::Databricks => {
17563 let arg = f.args.into_iter().next().unwrap();
17564 // Don't wrap string literals with CAST - they're already strings
17565 let is_string = matches!(
17566 &arg,
17567 Expression::Literal(lit) if matches!(lit.as_ref(), crate::expressions::Literal::String(_))
17568 );
17569 let final_arg = if is_string {
17570 arg
17571 } else {
17572 Expression::Cast(Box::new(Cast {
17573 this: arg,
17574 to: DataType::VarChar {
17575 length: None,
17576 parenthesized_length: false,
17577 },
17578 double_colon_syntax: false,
17579 trailing_comments: Vec::new(),
17580 format: None,
17581 default: None,
17582 inferred_type: None,
17583 }))
17584 };
17585 Ok(Expression::Function(Box::new(Function::new(
17586 "LENGTH".to_string(),
17587 vec![final_arg],
17588 ))))
17589 }
17590 _ => {
17591 let arg = f.args.into_iter().next().unwrap();
17592 Ok(Expression::Function(Box::new(Function::new(
17593 "LENGTH".to_string(),
17594 vec![arg],
17595 ))))
17596 }
17597 }
17598 }
17599 // COUNT_BIG(x) -> COUNT(x) for non-TSQL targets
17600 "COUNT_BIG" if f.args.len() == 1 => match target {
17601 DialectType::TSQL => Ok(Expression::Function(f)),
17602 _ => Ok(Expression::Function(Box::new(Function::new(
17603 "COUNT".to_string(),
17604 f.args,
17605 )))),
17606 },
17607 // DATEFROMPARTS(y, m, d) -> MAKE_DATE(y, m, d) for non-TSQL targets
17608 "DATEFROMPARTS" if f.args.len() == 3 => match target {
17609 DialectType::TSQL => Ok(Expression::Function(f)),
17610 _ => Ok(Expression::Function(Box::new(Function::new(
17611 "MAKE_DATE".to_string(),
17612 f.args,
17613 )))),
17614 },
17615 // REGEXP_LIKE(str, pattern) -> RegexpLike expression (target-specific output)
17616 "REGEXP_LIKE" if f.args.len() >= 2 => {
17617 let str_expr = f.args[0].clone();
17618 let pattern = f.args[1].clone();
17619 let flags = if f.args.len() >= 3 {
17620 Some(f.args[2].clone())
17621 } else {
17622 None
17623 };
17624 match target {
17625 DialectType::DuckDB => {
17626 let mut new_args = vec![str_expr, pattern];
17627 if let Some(fl) = flags {
17628 new_args.push(fl);
17629 }
17630 Ok(Expression::Function(Box::new(Function::new(
17631 "REGEXP_MATCHES".to_string(),
17632 new_args,
17633 ))))
17634 }
17635 DialectType::TSQL | DialectType::Fabric
17636 if flags.is_none()
17637 && matches!(
17638 source,
17639 DialectType::PostgreSQL | DialectType::CockroachDB
17640 ) =>
17641 {
17642 Ok(Self::build_tsql_regex_patindex_predicate(
17643 str_expr, pattern, false,
17644 ))
17645 }
17646 _ => Ok(Expression::RegexpLike(Box::new(
17647 crate::expressions::RegexpFunc {
17648 this: str_expr,
17649 pattern,
17650 flags,
17651 },
17652 ))),
17653 }
17654 }
17655 // ClickHouse arrayJoin -> UNNEST for PostgreSQL
17656 "ARRAYJOIN" if f.args.len() == 1 => match target {
17657 DialectType::PostgreSQL => Ok(Expression::Function(Box::new(
17658 Function::new("UNNEST".to_string(), f.args),
17659 ))),
17660 _ => Ok(Expression::Function(f)),
17661 },
17662 // DATETIMEFROMPARTS(y, m, d, h, mi, s, ms) -> MAKE_TIMESTAMP / TIMESTAMP_FROM_PARTS
17663 "DATETIMEFROMPARTS" if f.args.len() == 7 => {
17664 match target {
17665 DialectType::TSQL => Ok(Expression::Function(f)),
17666 DialectType::DuckDB => {
17667 // MAKE_TIMESTAMP(y, m, d, h, mi, s + (ms / 1000.0))
17668 let mut args = f.args;
17669 let ms = args.pop().unwrap();
17670 let s = args.pop().unwrap();
17671 // s + (ms / 1000.0)
17672 let ms_frac = Expression::Div(Box::new(BinaryOp::new(
17673 ms,
17674 Expression::Literal(Box::new(
17675 crate::expressions::Literal::Number(
17676 "1000.0".to_string(),
17677 ),
17678 )),
17679 )));
17680 let s_with_ms = Expression::Add(Box::new(BinaryOp::new(
17681 s,
17682 Expression::Paren(Box::new(Paren {
17683 this: ms_frac,
17684 trailing_comments: vec![],
17685 })),
17686 )));
17687 args.push(s_with_ms);
17688 Ok(Expression::Function(Box::new(Function::new(
17689 "MAKE_TIMESTAMP".to_string(),
17690 args,
17691 ))))
17692 }
17693 DialectType::Snowflake => {
17694 // TIMESTAMP_FROM_PARTS(y, m, d, h, mi, s, ms * 1000000)
17695 let mut args = f.args;
17696 let ms = args.pop().unwrap();
17697 // ms * 1000000
17698 let ns = Expression::Mul(Box::new(BinaryOp::new(
17699 ms,
17700 Expression::number(1000000),
17701 )));
17702 args.push(ns);
17703 Ok(Expression::Function(Box::new(Function::new(
17704 "TIMESTAMP_FROM_PARTS".to_string(),
17705 args,
17706 ))))
17707 }
17708 _ => {
17709 // Default: keep function name for other targets
17710 Ok(Expression::Function(Box::new(Function::new(
17711 "DATETIMEFROMPARTS".to_string(),
17712 f.args,
17713 ))))
17714 }
17715 }
17716 }
17717 // CONVERT(type, expr [, style]) -> CAST(expr AS type) for non-TSQL targets
17718 // TRY_CONVERT(type, expr [, style]) -> TRY_CAST(expr AS type) for non-TSQL targets
17719 "CONVERT" | "TRY_CONVERT" if f.args.len() >= 2 => {
17720 let is_try = name == "TRY_CONVERT";
17721 let type_expr = f.args[0].clone();
17722 let value_expr = f.args[1].clone();
17723 let style = if f.args.len() >= 3 {
17724 Some(&f.args[2])
17725 } else {
17726 None
17727 };
17728
17729 // For TSQL->TSQL, normalize types and preserve CONVERT/TRY_CONVERT
17730 if matches!(target, DialectType::TSQL) {
17731 let normalized_type = match &type_expr {
17732 Expression::DataType(dt) => {
17733 let new_dt = match dt {
17734 DataType::Int { .. } => DataType::Custom {
17735 name: "INTEGER".to_string(),
17736 },
17737 _ => dt.clone(),
17738 };
17739 Expression::DataType(new_dt)
17740 }
17741 Expression::Identifier(id) => {
17742 if id.name.eq_ignore_ascii_case("INT") {
17743 Expression::Identifier(
17744 crate::expressions::Identifier::new("INTEGER"),
17745 )
17746 } else {
17747 let upper = id.name.to_ascii_uppercase();
17748 Expression::Identifier(
17749 crate::expressions::Identifier::new(upper),
17750 )
17751 }
17752 }
17753 Expression::Column(col) => {
17754 if col.name.name.eq_ignore_ascii_case("INT") {
17755 Expression::Identifier(
17756 crate::expressions::Identifier::new("INTEGER"),
17757 )
17758 } else {
17759 let upper = col.name.name.to_ascii_uppercase();
17760 Expression::Identifier(
17761 crate::expressions::Identifier::new(upper),
17762 )
17763 }
17764 }
17765 _ => type_expr.clone(),
17766 };
17767 let func_name = if is_try { "TRY_CONVERT" } else { "CONVERT" };
17768 let mut new_args = vec![normalized_type, value_expr];
17769 if let Some(s) = style {
17770 new_args.push(s.clone());
17771 }
17772 return Ok(Expression::Function(Box::new(Function::new(
17773 func_name.to_string(),
17774 new_args,
17775 ))));
17776 }
17777
17778 // For other targets: CONVERT(type, expr) -> CAST(expr AS type)
17779 fn expr_to_datatype(e: &Expression) -> Option<DataType> {
17780 match e {
17781 Expression::DataType(dt) => {
17782 // Convert NVARCHAR/NCHAR Custom types to standard VarChar/Char
17783 match dt {
17784 DataType::Custom { name }
17785 if name.starts_with("NVARCHAR(")
17786 || name.starts_with("NCHAR(") =>
17787 {
17788 // Extract the length from "NVARCHAR(200)" or "NCHAR(40)"
17789 let inner = &name[name.find('(').unwrap() + 1
17790 ..name.len() - 1];
17791 if inner.eq_ignore_ascii_case("MAX") {
17792 Some(DataType::Text)
17793 } else if let Ok(len) = inner.parse::<u32>() {
17794 if name.starts_with("NCHAR") {
17795 Some(DataType::Char {
17796 length: Some(len),
17797 })
17798 } else {
17799 Some(DataType::VarChar {
17800 length: Some(len),
17801 parenthesized_length: false,
17802 })
17803 }
17804 } else {
17805 Some(dt.clone())
17806 }
17807 }
17808 DataType::Custom { name } if name == "NVARCHAR" => {
17809 Some(DataType::VarChar {
17810 length: None,
17811 parenthesized_length: false,
17812 })
17813 }
17814 DataType::Custom { name } if name == "NCHAR" => {
17815 Some(DataType::Char { length: None })
17816 }
17817 DataType::Custom { name }
17818 if name == "NVARCHAR(MAX)"
17819 || name == "VARCHAR(MAX)" =>
17820 {
17821 Some(DataType::Text)
17822 }
17823 _ => Some(dt.clone()),
17824 }
17825 }
17826 Expression::Identifier(id) => {
17827 let name = id.name.to_ascii_uppercase();
17828 match name.as_str() {
17829 "INT" | "INTEGER" => Some(DataType::Int {
17830 length: None,
17831 integer_spelling: false,
17832 }),
17833 "BIGINT" => Some(DataType::BigInt { length: None }),
17834 "SMALLINT" => {
17835 Some(DataType::SmallInt { length: None })
17836 }
17837 "TINYINT" => {
17838 Some(DataType::TinyInt { length: None })
17839 }
17840 "FLOAT" => Some(DataType::Float {
17841 precision: None,
17842 scale: None,
17843 real_spelling: false,
17844 }),
17845 "REAL" => Some(DataType::Float {
17846 precision: None,
17847 scale: None,
17848 real_spelling: true,
17849 }),
17850 "DATETIME" | "DATETIME2" => {
17851 Some(DataType::Timestamp {
17852 timezone: false,
17853 precision: None,
17854 })
17855 }
17856 "DATE" => Some(DataType::Date),
17857 "BIT" => Some(DataType::Boolean),
17858 "TEXT" => Some(DataType::Text),
17859 "NUMERIC" => Some(DataType::Decimal {
17860 precision: None,
17861 scale: None,
17862 }),
17863 "MONEY" => Some(DataType::Decimal {
17864 precision: Some(15),
17865 scale: Some(4),
17866 }),
17867 "SMALLMONEY" => Some(DataType::Decimal {
17868 precision: Some(6),
17869 scale: Some(4),
17870 }),
17871 "VARCHAR" => Some(DataType::VarChar {
17872 length: None,
17873 parenthesized_length: false,
17874 }),
17875 "NVARCHAR" => Some(DataType::VarChar {
17876 length: None,
17877 parenthesized_length: false,
17878 }),
17879 "CHAR" => Some(DataType::Char { length: None }),
17880 "NCHAR" => Some(DataType::Char { length: None }),
17881 _ => Some(DataType::Custom { name }),
17882 }
17883 }
17884 Expression::Column(col) => {
17885 let name = col.name.name.to_ascii_uppercase();
17886 match name.as_str() {
17887 "INT" | "INTEGER" => Some(DataType::Int {
17888 length: None,
17889 integer_spelling: false,
17890 }),
17891 "BIGINT" => Some(DataType::BigInt { length: None }),
17892 "FLOAT" => Some(DataType::Float {
17893 precision: None,
17894 scale: None,
17895 real_spelling: false,
17896 }),
17897 "DATETIME" | "DATETIME2" => {
17898 Some(DataType::Timestamp {
17899 timezone: false,
17900 precision: None,
17901 })
17902 }
17903 "DATE" => Some(DataType::Date),
17904 "NUMERIC" => Some(DataType::Decimal {
17905 precision: None,
17906 scale: None,
17907 }),
17908 "VARCHAR" => Some(DataType::VarChar {
17909 length: None,
17910 parenthesized_length: false,
17911 }),
17912 "NVARCHAR" => Some(DataType::VarChar {
17913 length: None,
17914 parenthesized_length: false,
17915 }),
17916 "CHAR" => Some(DataType::Char { length: None }),
17917 "NCHAR" => Some(DataType::Char { length: None }),
17918 _ => Some(DataType::Custom { name }),
17919 }
17920 }
17921 // NVARCHAR(200) parsed as Function("NVARCHAR", [200])
17922 Expression::Function(f) => {
17923 let fname = f.name.to_ascii_uppercase();
17924 match fname.as_str() {
17925 "VARCHAR" | "NVARCHAR" => {
17926 let len = f.args.first().and_then(|a| {
17927 if let Expression::Literal(lit) = a
17928 {
17929 if let crate::expressions::Literal::Number(n) = lit.as_ref() {
17930 n.parse::<u32>().ok()
17931 } else { None }
17932 } else if let Expression::Identifier(id) = a
17933 {
17934 if id.name.eq_ignore_ascii_case("MAX") {
17935 None
17936 } else {
17937 None
17938 }
17939 } else {
17940 None
17941 }
17942 });
17943 // Check for VARCHAR(MAX) -> TEXT
17944 let is_max = f.args.first().map_or(false, |a| {
17945 matches!(a, Expression::Identifier(id) if id.name.eq_ignore_ascii_case("MAX"))
17946 || matches!(a, Expression::Column(col) if col.name.name.eq_ignore_ascii_case("MAX"))
17947 });
17948 if is_max {
17949 Some(DataType::Text)
17950 } else {
17951 Some(DataType::VarChar {
17952 length: len,
17953 parenthesized_length: false,
17954 })
17955 }
17956 }
17957 "NCHAR" | "CHAR" => {
17958 let len = f.args.first().and_then(|a| {
17959 if let Expression::Literal(lit) = a
17960 {
17961 if let crate::expressions::Literal::Number(n) = lit.as_ref() {
17962 n.parse::<u32>().ok()
17963 } else { None }
17964 } else {
17965 None
17966 }
17967 });
17968 Some(DataType::Char { length: len })
17969 }
17970 "NUMERIC" | "DECIMAL" => {
17971 let precision = f.args.first().and_then(|a| {
17972 if let Expression::Literal(lit) = a
17973 {
17974 if let crate::expressions::Literal::Number(n) = lit.as_ref() {
17975 n.parse::<u32>().ok()
17976 } else { None }
17977 } else {
17978 None
17979 }
17980 });
17981 let scale = f.args.get(1).and_then(|a| {
17982 if let Expression::Literal(lit) = a
17983 {
17984 if let crate::expressions::Literal::Number(n) = lit.as_ref() {
17985 n.parse::<u32>().ok()
17986 } else { None }
17987 } else {
17988 None
17989 }
17990 });
17991 Some(DataType::Decimal { precision, scale })
17992 }
17993 _ => None,
17994 }
17995 }
17996 _ => None,
17997 }
17998 }
17999
18000 if let Some(mut dt) = expr_to_datatype(&type_expr) {
18001 // For TSQL source: VARCHAR/CHAR without length defaults to 30
18002 let is_tsql_source =
18003 matches!(source, DialectType::TSQL | DialectType::Fabric);
18004 if is_tsql_source {
18005 match &dt {
18006 DataType::VarChar { length: None, .. } => {
18007 dt = DataType::VarChar {
18008 length: Some(30),
18009 parenthesized_length: false,
18010 };
18011 }
18012 DataType::Char { length: None } => {
18013 dt = DataType::Char { length: Some(30) };
18014 }
18015 _ => {}
18016 }
18017 }
18018
18019 // Determine if this is a string type
18020 let is_string_type = matches!(
18021 dt,
18022 DataType::VarChar { .. }
18023 | DataType::Char { .. }
18024 | DataType::Text
18025 ) || matches!(&dt, DataType::Custom { name } if name == "NVARCHAR" || name == "NCHAR"
18026 || name.starts_with("NVARCHAR(") || name.starts_with("NCHAR(")
18027 || name.starts_with("VARCHAR(") || name == "VARCHAR"
18028 || name == "STRING");
18029
18030 // Determine if this is a date/time type
18031 let is_datetime_type = matches!(
18032 dt,
18033 DataType::Timestamp { .. } | DataType::Date
18034 ) || matches!(&dt, DataType::Custom { name } if name == "DATETIME"
18035 || name == "DATETIME2" || name == "SMALLDATETIME");
18036
18037 // Check for date conversion with style
18038 if style.is_some() {
18039 let style_num = style.and_then(|s| {
18040 if let Expression::Literal(lit) = s {
18041 if let crate::expressions::Literal::Number(n) =
18042 lit.as_ref()
18043 {
18044 n.parse::<u32>().ok()
18045 } else {
18046 None
18047 }
18048 } else {
18049 None
18050 }
18051 });
18052
18053 // TSQL CONVERT date styles (Java format)
18054 let format_str = style_num.and_then(|n| match n {
18055 101 => Some("MM/dd/yyyy"),
18056 102 => Some("yyyy.MM.dd"),
18057 103 => Some("dd/MM/yyyy"),
18058 104 => Some("dd.MM.yyyy"),
18059 105 => Some("dd-MM-yyyy"),
18060 108 => Some("HH:mm:ss"),
18061 110 => Some("MM-dd-yyyy"),
18062 112 => Some("yyyyMMdd"),
18063 120 | 20 => Some("yyyy-MM-dd HH:mm:ss"),
18064 121 | 21 => Some("yyyy-MM-dd HH:mm:ss.SSSSSS"),
18065 126 | 127 => Some("yyyy-MM-dd'T'HH:mm:ss.SSS"),
18066 _ => None,
18067 });
18068
18069 // Non-string, non-datetime types with style: just CAST, ignore the style
18070 if !is_string_type && !is_datetime_type {
18071 let cast_expr = if is_try {
18072 Expression::TryCast(Box::new(
18073 crate::expressions::Cast {
18074 this: value_expr,
18075 to: dt,
18076 trailing_comments: Vec::new(),
18077 double_colon_syntax: false,
18078 format: None,
18079 default: None,
18080 inferred_type: None,
18081 },
18082 ))
18083 } else {
18084 Expression::Cast(Box::new(
18085 crate::expressions::Cast {
18086 this: value_expr,
18087 to: dt,
18088 trailing_comments: Vec::new(),
18089 double_colon_syntax: false,
18090 format: None,
18091 default: None,
18092 inferred_type: None,
18093 },
18094 ))
18095 };
18096 return Ok(cast_expr);
18097 }
18098
18099 if let Some(java_fmt) = format_str {
18100 let c_fmt = java_fmt
18101 .replace("yyyy", "%Y")
18102 .replace("MM", "%m")
18103 .replace("dd", "%d")
18104 .replace("HH", "%H")
18105 .replace("mm", "%M")
18106 .replace("ss", "%S")
18107 .replace("SSSSSS", "%f")
18108 .replace("SSS", "%f")
18109 .replace("'T'", "T");
18110
18111 // For datetime target types: style is the INPUT format for parsing strings -> dates
18112 if is_datetime_type {
18113 match target {
18114 DialectType::DuckDB => {
18115 return Ok(Expression::Function(Box::new(
18116 Function::new(
18117 "STRPTIME".to_string(),
18118 vec![
18119 value_expr,
18120 Expression::string(&c_fmt),
18121 ],
18122 ),
18123 )));
18124 }
18125 DialectType::Spark
18126 | DialectType::Databricks => {
18127 // CONVERT(DATETIME, x, style) -> TO_TIMESTAMP(x, fmt)
18128 // CONVERT(DATE, x, style) -> TO_DATE(x, fmt)
18129 let func_name =
18130 if matches!(dt, DataType::Date) {
18131 "TO_DATE"
18132 } else {
18133 "TO_TIMESTAMP"
18134 };
18135 return Ok(Expression::Function(Box::new(
18136 Function::new(
18137 func_name.to_string(),
18138 vec![
18139 value_expr,
18140 Expression::string(java_fmt),
18141 ],
18142 ),
18143 )));
18144 }
18145 DialectType::Hive => {
18146 return Ok(Expression::Function(Box::new(
18147 Function::new(
18148 "TO_TIMESTAMP".to_string(),
18149 vec![
18150 value_expr,
18151 Expression::string(java_fmt),
18152 ],
18153 ),
18154 )));
18155 }
18156 _ => {
18157 return Ok(Expression::Cast(Box::new(
18158 crate::expressions::Cast {
18159 this: value_expr,
18160 to: dt,
18161 trailing_comments: Vec::new(),
18162 double_colon_syntax: false,
18163 format: None,
18164 default: None,
18165 inferred_type: None,
18166 },
18167 )));
18168 }
18169 }
18170 }
18171
18172 // For string target types: style is the OUTPUT format for dates -> strings
18173 match target {
18174 DialectType::DuckDB => Ok(Expression::Function(
18175 Box::new(Function::new(
18176 "STRPTIME".to_string(),
18177 vec![
18178 value_expr,
18179 Expression::string(&c_fmt),
18180 ],
18181 )),
18182 )),
18183 DialectType::Spark | DialectType::Databricks => {
18184 // For string target types with style: CAST(DATE_FORMAT(x, fmt) AS type)
18185 // Determine the target string type
18186 let string_dt = match &dt {
18187 DataType::VarChar {
18188 length: Some(l),
18189 ..
18190 } => DataType::VarChar {
18191 length: Some(*l),
18192 parenthesized_length: false,
18193 },
18194 DataType::Text => DataType::Custom {
18195 name: "STRING".to_string(),
18196 },
18197 _ => DataType::Custom {
18198 name: "STRING".to_string(),
18199 },
18200 };
18201 let date_format_expr = Expression::Function(
18202 Box::new(Function::new(
18203 "DATE_FORMAT".to_string(),
18204 vec![
18205 value_expr,
18206 Expression::string(java_fmt),
18207 ],
18208 )),
18209 );
18210 let cast_expr = if is_try {
18211 Expression::TryCast(Box::new(
18212 crate::expressions::Cast {
18213 this: date_format_expr,
18214 to: string_dt,
18215 trailing_comments: Vec::new(),
18216 double_colon_syntax: false,
18217 format: None,
18218 default: None,
18219 inferred_type: None,
18220 },
18221 ))
18222 } else {
18223 Expression::Cast(Box::new(
18224 crate::expressions::Cast {
18225 this: date_format_expr,
18226 to: string_dt,
18227 trailing_comments: Vec::new(),
18228 double_colon_syntax: false,
18229 format: None,
18230 default: None,
18231 inferred_type: None,
18232 },
18233 ))
18234 };
18235 Ok(cast_expr)
18236 }
18237 DialectType::MySQL | DialectType::SingleStore => {
18238 // For MySQL: CAST(DATE_FORMAT(x, mysql_fmt) AS CHAR(n))
18239 let mysql_fmt = java_fmt
18240 .replace("yyyy", "%Y")
18241 .replace("MM", "%m")
18242 .replace("dd", "%d")
18243 .replace("HH:mm:ss.SSSSSS", "%T")
18244 .replace("HH:mm:ss", "%T")
18245 .replace("HH", "%H")
18246 .replace("mm", "%i")
18247 .replace("ss", "%S");
18248 let date_format_expr = Expression::Function(
18249 Box::new(Function::new(
18250 "DATE_FORMAT".to_string(),
18251 vec![
18252 value_expr,
18253 Expression::string(&mysql_fmt),
18254 ],
18255 )),
18256 );
18257 // MySQL uses CHAR for string casts
18258 let mysql_dt = match &dt {
18259 DataType::VarChar { length, .. } => {
18260 DataType::Char { length: *length }
18261 }
18262 _ => dt,
18263 };
18264 Ok(Expression::Cast(Box::new(
18265 crate::expressions::Cast {
18266 this: date_format_expr,
18267 to: mysql_dt,
18268 trailing_comments: Vec::new(),
18269 double_colon_syntax: false,
18270 format: None,
18271 default: None,
18272 inferred_type: None,
18273 },
18274 )))
18275 }
18276 DialectType::Hive => {
18277 let func_name = "TO_TIMESTAMP";
18278 Ok(Expression::Function(Box::new(
18279 Function::new(
18280 func_name.to_string(),
18281 vec![
18282 value_expr,
18283 Expression::string(java_fmt),
18284 ],
18285 ),
18286 )))
18287 }
18288 _ => Ok(Expression::Cast(Box::new(
18289 crate::expressions::Cast {
18290 this: value_expr,
18291 to: dt,
18292 trailing_comments: Vec::new(),
18293 double_colon_syntax: false,
18294 format: None,
18295 default: None,
18296 inferred_type: None,
18297 },
18298 ))),
18299 }
18300 } else {
18301 // Unknown style, just CAST
18302 let cast_expr = if is_try {
18303 Expression::TryCast(Box::new(
18304 crate::expressions::Cast {
18305 this: value_expr,
18306 to: dt,
18307 trailing_comments: Vec::new(),
18308 double_colon_syntax: false,
18309 format: None,
18310 default: None,
18311 inferred_type: None,
18312 },
18313 ))
18314 } else {
18315 Expression::Cast(Box::new(
18316 crate::expressions::Cast {
18317 this: value_expr,
18318 to: dt,
18319 trailing_comments: Vec::new(),
18320 double_colon_syntax: false,
18321 format: None,
18322 default: None,
18323 inferred_type: None,
18324 },
18325 ))
18326 };
18327 Ok(cast_expr)
18328 }
18329 } else {
18330 // No style - simple CAST
18331 let final_dt = if matches!(
18332 target,
18333 DialectType::MySQL | DialectType::SingleStore
18334 ) {
18335 match &dt {
18336 DataType::Int { .. }
18337 | DataType::BigInt { .. }
18338 | DataType::SmallInt { .. }
18339 | DataType::TinyInt { .. } => DataType::Custom {
18340 name: "SIGNED".to_string(),
18341 },
18342 DataType::VarChar { length, .. } => {
18343 DataType::Char { length: *length }
18344 }
18345 _ => dt,
18346 }
18347 } else {
18348 dt
18349 };
18350 let cast_expr = if is_try {
18351 Expression::TryCast(Box::new(
18352 crate::expressions::Cast {
18353 this: value_expr,
18354 to: final_dt,
18355 trailing_comments: Vec::new(),
18356 double_colon_syntax: false,
18357 format: None,
18358 default: None,
18359 inferred_type: None,
18360 },
18361 ))
18362 } else {
18363 Expression::Cast(Box::new(crate::expressions::Cast {
18364 this: value_expr,
18365 to: final_dt,
18366 trailing_comments: Vec::new(),
18367 double_colon_syntax: false,
18368 format: None,
18369 default: None,
18370 inferred_type: None,
18371 }))
18372 };
18373 Ok(cast_expr)
18374 }
18375 } else {
18376 // Can't convert type expression - keep as CONVERT/TRY_CONVERT function
18377 Ok(Expression::Function(f))
18378 }
18379 }
18380 // STRFTIME(val, fmt) from DuckDB / STRFTIME(fmt, val) from SQLite -> target-specific
18381 "STRFTIME" if f.args.len() == 2 => {
18382 // SQLite uses STRFTIME(fmt, val); DuckDB uses STRFTIME(val, fmt)
18383 let (val, fmt_expr) = if matches!(source, DialectType::SQLite) {
18384 // SQLite: args[0] = format, args[1] = value
18385 (f.args[1].clone(), &f.args[0])
18386 } else {
18387 // DuckDB and others: args[0] = value, args[1] = format
18388 (f.args[0].clone(), &f.args[1])
18389 };
18390
18391 // Helper to convert C-style format to Java-style
18392 fn c_to_java_format(fmt: &str) -> String {
18393 fmt.replace("%Y", "yyyy")
18394 .replace("%m", "MM")
18395 .replace("%d", "dd")
18396 .replace("%H", "HH")
18397 .replace("%M", "mm")
18398 .replace("%S", "ss")
18399 .replace("%f", "SSSSSS")
18400 .replace("%y", "yy")
18401 .replace("%-m", "M")
18402 .replace("%-d", "d")
18403 .replace("%-H", "H")
18404 .replace("%-I", "h")
18405 .replace("%I", "hh")
18406 .replace("%p", "a")
18407 .replace("%j", "DDD")
18408 .replace("%a", "EEE")
18409 .replace("%b", "MMM")
18410 .replace("%F", "yyyy-MM-dd")
18411 .replace("%T", "HH:mm:ss")
18412 }
18413
18414 // Helper: recursively convert format strings within expressions (handles CONCAT)
18415 fn convert_fmt_expr(
18416 expr: &Expression,
18417 converter: &dyn Fn(&str) -> String,
18418 ) -> Expression {
18419 match expr {
18420 Expression::Literal(lit)
18421 if matches!(
18422 lit.as_ref(),
18423 crate::expressions::Literal::String(_)
18424 ) =>
18425 {
18426 let crate::expressions::Literal::String(s) =
18427 lit.as_ref()
18428 else {
18429 unreachable!()
18430 };
18431 Expression::string(&converter(s))
18432 }
18433 Expression::Function(func)
18434 if func.name.eq_ignore_ascii_case("CONCAT") =>
18435 {
18436 let new_args: Vec<Expression> = func
18437 .args
18438 .iter()
18439 .map(|a| convert_fmt_expr(a, converter))
18440 .collect();
18441 Expression::Function(Box::new(Function::new(
18442 "CONCAT".to_string(),
18443 new_args,
18444 )))
18445 }
18446 other => other.clone(),
18447 }
18448 }
18449
18450 match target {
18451 DialectType::DuckDB => {
18452 if matches!(source, DialectType::SQLite) {
18453 // SQLite STRFTIME(fmt, val) -> DuckDB STRFTIME(CAST(val AS TIMESTAMP), fmt)
18454 let cast_val = Expression::Cast(Box::new(Cast {
18455 this: val,
18456 to: crate::expressions::DataType::Timestamp {
18457 precision: None,
18458 timezone: false,
18459 },
18460 trailing_comments: Vec::new(),
18461 double_colon_syntax: false,
18462 format: None,
18463 default: None,
18464 inferred_type: None,
18465 }));
18466 Ok(Expression::Function(Box::new(Function::new(
18467 "STRFTIME".to_string(),
18468 vec![cast_val, fmt_expr.clone()],
18469 ))))
18470 } else {
18471 Ok(Expression::Function(f))
18472 }
18473 }
18474 DialectType::Spark
18475 | DialectType::Databricks
18476 | DialectType::Hive => {
18477 // STRFTIME(val, fmt) -> DATE_FORMAT(val, java_fmt)
18478 let converted_fmt =
18479 convert_fmt_expr(fmt_expr, &c_to_java_format);
18480 Ok(Expression::Function(Box::new(Function::new(
18481 "DATE_FORMAT".to_string(),
18482 vec![val, converted_fmt],
18483 ))))
18484 }
18485 DialectType::TSQL | DialectType::Fabric => {
18486 // STRFTIME(val, fmt) -> FORMAT(val, java_fmt)
18487 let converted_fmt =
18488 convert_fmt_expr(fmt_expr, &c_to_java_format);
18489 Ok(Expression::Function(Box::new(Function::new(
18490 "FORMAT".to_string(),
18491 vec![val, converted_fmt],
18492 ))))
18493 }
18494 DialectType::Presto
18495 | DialectType::Trino
18496 | DialectType::Athena => {
18497 // STRFTIME(val, fmt) -> DATE_FORMAT(val, presto_fmt) (convert DuckDB format to Presto)
18498 if let Expression::Literal(lit) = fmt_expr {
18499 if let crate::expressions::Literal::String(s) =
18500 lit.as_ref()
18501 {
18502 let presto_fmt = duckdb_to_presto_format(s);
18503 Ok(Expression::Function(Box::new(Function::new(
18504 "DATE_FORMAT".to_string(),
18505 vec![val, Expression::string(&presto_fmt)],
18506 ))))
18507 } else {
18508 Ok(Expression::Function(Box::new(Function::new(
18509 "DATE_FORMAT".to_string(),
18510 vec![val, fmt_expr.clone()],
18511 ))))
18512 }
18513 } else {
18514 Ok(Expression::Function(Box::new(Function::new(
18515 "DATE_FORMAT".to_string(),
18516 vec![val, fmt_expr.clone()],
18517 ))))
18518 }
18519 }
18520 DialectType::BigQuery => {
18521 // STRFTIME(val, fmt) -> FORMAT_DATE(bq_fmt, val) - note reversed arg order
18522 if let Expression::Literal(lit) = fmt_expr {
18523 if let crate::expressions::Literal::String(s) =
18524 lit.as_ref()
18525 {
18526 let bq_fmt = duckdb_to_bigquery_format(s);
18527 Ok(Expression::Function(Box::new(Function::new(
18528 "FORMAT_DATE".to_string(),
18529 vec![Expression::string(&bq_fmt), val],
18530 ))))
18531 } else {
18532 Ok(Expression::Function(Box::new(Function::new(
18533 "FORMAT_DATE".to_string(),
18534 vec![fmt_expr.clone(), val],
18535 ))))
18536 }
18537 } else {
18538 Ok(Expression::Function(Box::new(Function::new(
18539 "FORMAT_DATE".to_string(),
18540 vec![fmt_expr.clone(), val],
18541 ))))
18542 }
18543 }
18544 DialectType::PostgreSQL | DialectType::Redshift => {
18545 // STRFTIME(val, fmt) -> TO_CHAR(val, pg_fmt)
18546 if let Expression::Literal(lit) = fmt_expr {
18547 if let crate::expressions::Literal::String(s) =
18548 lit.as_ref()
18549 {
18550 let pg_fmt = s
18551 .replace("%Y", "YYYY")
18552 .replace("%m", "MM")
18553 .replace("%d", "DD")
18554 .replace("%H", "HH24")
18555 .replace("%M", "MI")
18556 .replace("%S", "SS")
18557 .replace("%y", "YY")
18558 .replace("%-m", "FMMM")
18559 .replace("%-d", "FMDD")
18560 .replace("%-H", "FMHH24")
18561 .replace("%-I", "FMHH12")
18562 .replace("%p", "AM")
18563 .replace("%F", "YYYY-MM-DD")
18564 .replace("%T", "HH24:MI:SS");
18565 Ok(Expression::Function(Box::new(Function::new(
18566 "TO_CHAR".to_string(),
18567 vec![val, Expression::string(&pg_fmt)],
18568 ))))
18569 } else {
18570 Ok(Expression::Function(Box::new(Function::new(
18571 "TO_CHAR".to_string(),
18572 vec![val, fmt_expr.clone()],
18573 ))))
18574 }
18575 } else {
18576 Ok(Expression::Function(Box::new(Function::new(
18577 "TO_CHAR".to_string(),
18578 vec![val, fmt_expr.clone()],
18579 ))))
18580 }
18581 }
18582 _ => Ok(Expression::Function(f)),
18583 }
18584 }
18585 // STRPTIME(val, fmt) from DuckDB -> target-specific date parse function
18586 "STRPTIME" if f.args.len() == 2 => {
18587 let val = f.args[0].clone();
18588 let fmt_expr = &f.args[1];
18589
18590 fn c_to_java_format_parse(fmt: &str) -> String {
18591 fmt.replace("%Y", "yyyy")
18592 .replace("%m", "MM")
18593 .replace("%d", "dd")
18594 .replace("%H", "HH")
18595 .replace("%M", "mm")
18596 .replace("%S", "ss")
18597 .replace("%f", "SSSSSS")
18598 .replace("%y", "yy")
18599 .replace("%-m", "M")
18600 .replace("%-d", "d")
18601 .replace("%-H", "H")
18602 .replace("%-I", "h")
18603 .replace("%I", "hh")
18604 .replace("%p", "a")
18605 .replace("%F", "yyyy-MM-dd")
18606 .replace("%T", "HH:mm:ss")
18607 }
18608
18609 match target {
18610 DialectType::DuckDB => Ok(Expression::Function(f)),
18611 DialectType::Spark | DialectType::Databricks => {
18612 // STRPTIME(val, fmt) -> TO_TIMESTAMP(val, java_fmt)
18613 if let Expression::Literal(lit) = fmt_expr {
18614 if let crate::expressions::Literal::String(s) =
18615 lit.as_ref()
18616 {
18617 let java_fmt = c_to_java_format_parse(s);
18618 Ok(Expression::Function(Box::new(Function::new(
18619 "TO_TIMESTAMP".to_string(),
18620 vec![val, Expression::string(&java_fmt)],
18621 ))))
18622 } else {
18623 Ok(Expression::Function(Box::new(Function::new(
18624 "TO_TIMESTAMP".to_string(),
18625 vec![val, fmt_expr.clone()],
18626 ))))
18627 }
18628 } else {
18629 Ok(Expression::Function(Box::new(Function::new(
18630 "TO_TIMESTAMP".to_string(),
18631 vec![val, fmt_expr.clone()],
18632 ))))
18633 }
18634 }
18635 DialectType::Hive => {
18636 // STRPTIME(val, fmt) -> CAST(FROM_UNIXTIME(UNIX_TIMESTAMP(val, java_fmt)) AS TIMESTAMP)
18637 if let Expression::Literal(lit) = fmt_expr {
18638 if let crate::expressions::Literal::String(s) =
18639 lit.as_ref()
18640 {
18641 let java_fmt = c_to_java_format_parse(s);
18642 let unix_ts =
18643 Expression::Function(Box::new(Function::new(
18644 "UNIX_TIMESTAMP".to_string(),
18645 vec![val, Expression::string(&java_fmt)],
18646 )));
18647 let from_unix =
18648 Expression::Function(Box::new(Function::new(
18649 "FROM_UNIXTIME".to_string(),
18650 vec![unix_ts],
18651 )));
18652 Ok(Expression::Cast(Box::new(
18653 crate::expressions::Cast {
18654 this: from_unix,
18655 to: DataType::Timestamp {
18656 timezone: false,
18657 precision: None,
18658 },
18659 trailing_comments: Vec::new(),
18660 double_colon_syntax: false,
18661 format: None,
18662 default: None,
18663 inferred_type: None,
18664 },
18665 )))
18666 } else {
18667 Ok(Expression::Function(f))
18668 }
18669 } else {
18670 Ok(Expression::Function(f))
18671 }
18672 }
18673 DialectType::Presto
18674 | DialectType::Trino
18675 | DialectType::Athena => {
18676 // STRPTIME(val, fmt) -> DATE_PARSE(val, presto_fmt) (convert DuckDB format to Presto)
18677 if let Expression::Literal(lit) = fmt_expr {
18678 if let crate::expressions::Literal::String(s) =
18679 lit.as_ref()
18680 {
18681 let presto_fmt = duckdb_to_presto_format(s);
18682 Ok(Expression::Function(Box::new(Function::new(
18683 "DATE_PARSE".to_string(),
18684 vec![val, Expression::string(&presto_fmt)],
18685 ))))
18686 } else {
18687 Ok(Expression::Function(Box::new(Function::new(
18688 "DATE_PARSE".to_string(),
18689 vec![val, fmt_expr.clone()],
18690 ))))
18691 }
18692 } else {
18693 Ok(Expression::Function(Box::new(Function::new(
18694 "DATE_PARSE".to_string(),
18695 vec![val, fmt_expr.clone()],
18696 ))))
18697 }
18698 }
18699 DialectType::BigQuery => {
18700 // STRPTIME(val, fmt) -> PARSE_TIMESTAMP(bq_fmt, val) - note reversed arg order
18701 if let Expression::Literal(lit) = fmt_expr {
18702 if let crate::expressions::Literal::String(s) =
18703 lit.as_ref()
18704 {
18705 let bq_fmt = duckdb_to_bigquery_format(s);
18706 Ok(Expression::Function(Box::new(Function::new(
18707 "PARSE_TIMESTAMP".to_string(),
18708 vec![Expression::string(&bq_fmt), val],
18709 ))))
18710 } else {
18711 Ok(Expression::Function(Box::new(Function::new(
18712 "PARSE_TIMESTAMP".to_string(),
18713 vec![fmt_expr.clone(), val],
18714 ))))
18715 }
18716 } else {
18717 Ok(Expression::Function(Box::new(Function::new(
18718 "PARSE_TIMESTAMP".to_string(),
18719 vec![fmt_expr.clone(), val],
18720 ))))
18721 }
18722 }
18723 _ => Ok(Expression::Function(f)),
18724 }
18725 }
18726 // DATE_FORMAT(val, fmt) from Presto source (C-style format) -> target-specific
18727 "DATE_FORMAT"
18728 if f.args.len() >= 2
18729 && matches!(
18730 source,
18731 DialectType::Presto
18732 | DialectType::Trino
18733 | DialectType::Athena
18734 ) =>
18735 {
18736 let val = f.args[0].clone();
18737 let fmt_expr = &f.args[1];
18738
18739 match target {
18740 DialectType::Presto
18741 | DialectType::Trino
18742 | DialectType::Athena => {
18743 // Presto -> Presto: normalize format (e.g., %H:%i:%S -> %T)
18744 if let Expression::Literal(lit) = fmt_expr {
18745 if let crate::expressions::Literal::String(s) =
18746 lit.as_ref()
18747 {
18748 let normalized = normalize_presto_format(s);
18749 Ok(Expression::Function(Box::new(Function::new(
18750 "DATE_FORMAT".to_string(),
18751 vec![val, Expression::string(&normalized)],
18752 ))))
18753 } else {
18754 Ok(Expression::Function(f))
18755 }
18756 } else {
18757 Ok(Expression::Function(f))
18758 }
18759 }
18760 DialectType::Hive
18761 | DialectType::Spark
18762 | DialectType::Databricks => {
18763 // Convert Presto C-style to Java-style format
18764 if let Expression::Literal(lit) = fmt_expr {
18765 if let crate::expressions::Literal::String(s) =
18766 lit.as_ref()
18767 {
18768 let java_fmt = presto_to_java_format(s);
18769 Ok(Expression::Function(Box::new(Function::new(
18770 "DATE_FORMAT".to_string(),
18771 vec![val, Expression::string(&java_fmt)],
18772 ))))
18773 } else {
18774 Ok(Expression::Function(f))
18775 }
18776 } else {
18777 Ok(Expression::Function(f))
18778 }
18779 }
18780 DialectType::DuckDB => {
18781 // Convert to STRFTIME(val, duckdb_fmt)
18782 if let Expression::Literal(lit) = fmt_expr {
18783 if let crate::expressions::Literal::String(s) =
18784 lit.as_ref()
18785 {
18786 let duckdb_fmt = presto_to_duckdb_format(s);
18787 Ok(Expression::Function(Box::new(Function::new(
18788 "STRFTIME".to_string(),
18789 vec![val, Expression::string(&duckdb_fmt)],
18790 ))))
18791 } else {
18792 Ok(Expression::Function(Box::new(Function::new(
18793 "STRFTIME".to_string(),
18794 vec![val, fmt_expr.clone()],
18795 ))))
18796 }
18797 } else {
18798 Ok(Expression::Function(Box::new(Function::new(
18799 "STRFTIME".to_string(),
18800 vec![val, fmt_expr.clone()],
18801 ))))
18802 }
18803 }
18804 DialectType::BigQuery => {
18805 // Convert to FORMAT_DATE(bq_fmt, val) - reversed args
18806 if let Expression::Literal(lit) = fmt_expr {
18807 if let crate::expressions::Literal::String(s) =
18808 lit.as_ref()
18809 {
18810 let bq_fmt = presto_to_bigquery_format(s);
18811 Ok(Expression::Function(Box::new(Function::new(
18812 "FORMAT_DATE".to_string(),
18813 vec![Expression::string(&bq_fmt), val],
18814 ))))
18815 } else {
18816 Ok(Expression::Function(Box::new(Function::new(
18817 "FORMAT_DATE".to_string(),
18818 vec![fmt_expr.clone(), val],
18819 ))))
18820 }
18821 } else {
18822 Ok(Expression::Function(Box::new(Function::new(
18823 "FORMAT_DATE".to_string(),
18824 vec![fmt_expr.clone(), val],
18825 ))))
18826 }
18827 }
18828 _ => Ok(Expression::Function(f)),
18829 }
18830 }
18831 // DATE_PARSE(val, fmt) from Presto source -> target-specific parse function
18832 "DATE_PARSE"
18833 if f.args.len() >= 2
18834 && matches!(
18835 source,
18836 DialectType::Presto
18837 | DialectType::Trino
18838 | DialectType::Athena
18839 ) =>
18840 {
18841 let val = f.args[0].clone();
18842 let fmt_expr = &f.args[1];
18843
18844 match target {
18845 DialectType::Presto
18846 | DialectType::Trino
18847 | DialectType::Athena => {
18848 // Presto -> Presto: normalize format
18849 if let Expression::Literal(lit) = fmt_expr {
18850 if let crate::expressions::Literal::String(s) =
18851 lit.as_ref()
18852 {
18853 let normalized = normalize_presto_format(s);
18854 Ok(Expression::Function(Box::new(Function::new(
18855 "DATE_PARSE".to_string(),
18856 vec![val, Expression::string(&normalized)],
18857 ))))
18858 } else {
18859 Ok(Expression::Function(f))
18860 }
18861 } else {
18862 Ok(Expression::Function(f))
18863 }
18864 }
18865 DialectType::Hive => {
18866 // Presto -> Hive: if default format, just CAST(x AS TIMESTAMP)
18867 if let Expression::Literal(lit) = fmt_expr {
18868 if let crate::expressions::Literal::String(s) =
18869 lit.as_ref()
18870 {
18871 if is_default_presto_timestamp_format(s)
18872 || is_default_presto_date_format(s)
18873 {
18874 Ok(Expression::Cast(Box::new(
18875 crate::expressions::Cast {
18876 this: val,
18877 to: DataType::Timestamp {
18878 timezone: false,
18879 precision: None,
18880 },
18881 trailing_comments: Vec::new(),
18882 double_colon_syntax: false,
18883 format: None,
18884 default: None,
18885 inferred_type: None,
18886 },
18887 )))
18888 } else {
18889 let java_fmt = presto_to_java_format(s);
18890 Ok(Expression::Function(Box::new(
18891 Function::new(
18892 "TO_TIMESTAMP".to_string(),
18893 vec![
18894 val,
18895 Expression::string(&java_fmt),
18896 ],
18897 ),
18898 )))
18899 }
18900 } else {
18901 Ok(Expression::Function(f))
18902 }
18903 } else {
18904 Ok(Expression::Function(f))
18905 }
18906 }
18907 DialectType::Spark | DialectType::Databricks => {
18908 // Presto -> Spark: TO_TIMESTAMP(val, java_fmt)
18909 if let Expression::Literal(lit) = fmt_expr {
18910 if let crate::expressions::Literal::String(s) =
18911 lit.as_ref()
18912 {
18913 let java_fmt = presto_to_java_format(s);
18914 Ok(Expression::Function(Box::new(Function::new(
18915 "TO_TIMESTAMP".to_string(),
18916 vec![val, Expression::string(&java_fmt)],
18917 ))))
18918 } else {
18919 Ok(Expression::Function(f))
18920 }
18921 } else {
18922 Ok(Expression::Function(f))
18923 }
18924 }
18925 DialectType::DuckDB => {
18926 // Presto -> DuckDB: STRPTIME(val, duckdb_fmt)
18927 if let Expression::Literal(lit) = fmt_expr {
18928 if let crate::expressions::Literal::String(s) =
18929 lit.as_ref()
18930 {
18931 let duckdb_fmt = presto_to_duckdb_format(s);
18932 Ok(Expression::Function(Box::new(Function::new(
18933 "STRPTIME".to_string(),
18934 vec![val, Expression::string(&duckdb_fmt)],
18935 ))))
18936 } else {
18937 Ok(Expression::Function(Box::new(Function::new(
18938 "STRPTIME".to_string(),
18939 vec![val, fmt_expr.clone()],
18940 ))))
18941 }
18942 } else {
18943 Ok(Expression::Function(Box::new(Function::new(
18944 "STRPTIME".to_string(),
18945 vec![val, fmt_expr.clone()],
18946 ))))
18947 }
18948 }
18949 _ => Ok(Expression::Function(f)),
18950 }
18951 }
18952 // FROM_BASE64(x) / TO_BASE64(x) from Presto -> Hive-specific renames
18953 "FROM_BASE64"
18954 if f.args.len() == 1 && matches!(target, DialectType::Hive) =>
18955 {
18956 Ok(Expression::Function(Box::new(Function::new(
18957 "UNBASE64".to_string(),
18958 f.args,
18959 ))))
18960 }
18961 "TO_BASE64"
18962 if f.args.len() == 1 && matches!(target, DialectType::Hive) =>
18963 {
18964 Ok(Expression::Function(Box::new(Function::new(
18965 "BASE64".to_string(),
18966 f.args,
18967 ))))
18968 }
18969 // FROM_UNIXTIME(x) -> CAST(FROM_UNIXTIME(x) AS TIMESTAMP) for Spark
18970 "FROM_UNIXTIME"
18971 if f.args.len() == 1
18972 && matches!(
18973 source,
18974 DialectType::Presto
18975 | DialectType::Trino
18976 | DialectType::Athena
18977 )
18978 && matches!(
18979 target,
18980 DialectType::Spark | DialectType::Databricks
18981 ) =>
18982 {
18983 // Wrap FROM_UNIXTIME(x) in CAST(... AS TIMESTAMP)
18984 let from_unix = Expression::Function(Box::new(Function::new(
18985 "FROM_UNIXTIME".to_string(),
18986 f.args,
18987 )));
18988 Ok(Expression::Cast(Box::new(crate::expressions::Cast {
18989 this: from_unix,
18990 to: DataType::Timestamp {
18991 timezone: false,
18992 precision: None,
18993 },
18994 trailing_comments: Vec::new(),
18995 double_colon_syntax: false,
18996 format: None,
18997 default: None,
18998 inferred_type: None,
18999 })))
19000 }
19001 // DATE_FORMAT(val, fmt) from Hive/Spark/MySQL -> target-specific format function
19002 "DATE_FORMAT"
19003 if f.args.len() >= 2
19004 && !matches!(
19005 target,
19006 DialectType::Hive
19007 | DialectType::Spark
19008 | DialectType::Databricks
19009 | DialectType::MySQL
19010 | DialectType::SingleStore
19011 ) =>
19012 {
19013 let val = f.args[0].clone();
19014 let fmt_expr = &f.args[1];
19015 let is_hive_source = matches!(
19016 source,
19017 DialectType::Hive
19018 | DialectType::Spark
19019 | DialectType::Databricks
19020 );
19021
19022 fn java_to_c_format(fmt: &str) -> String {
19023 // Replace Java patterns with C strftime patterns.
19024 // Uses multi-pass to handle patterns that conflict.
19025 // First pass: replace multi-char patterns (longer first)
19026 let result = fmt
19027 .replace("yyyy", "%Y")
19028 .replace("SSSSSS", "%f")
19029 .replace("EEEE", "%W")
19030 .replace("MM", "%m")
19031 .replace("dd", "%d")
19032 .replace("HH", "%H")
19033 .replace("mm", "%M")
19034 .replace("ss", "%S")
19035 .replace("yy", "%y");
19036 // Second pass: handle single-char timezone patterns
19037 // z -> %Z (timezone name), Z -> %z (timezone offset)
19038 // Must be careful not to replace 'z'/'Z' inside already-replaced %Y, %M etc.
19039 let mut out = String::new();
19040 let chars: Vec<char> = result.chars().collect();
19041 let mut i = 0;
19042 while i < chars.len() {
19043 if chars[i] == '%' && i + 1 < chars.len() {
19044 // Already a format specifier, skip both chars
19045 out.push(chars[i]);
19046 out.push(chars[i + 1]);
19047 i += 2;
19048 } else if chars[i] == 'z' {
19049 out.push_str("%Z");
19050 i += 1;
19051 } else if chars[i] == 'Z' {
19052 out.push_str("%z");
19053 i += 1;
19054 } else {
19055 out.push(chars[i]);
19056 i += 1;
19057 }
19058 }
19059 out
19060 }
19061
19062 fn java_to_presto_format(fmt: &str) -> String {
19063 // Presto uses %T for HH:MM:SS
19064 let c_fmt = java_to_c_format(fmt);
19065 c_fmt.replace("%H:%M:%S", "%T")
19066 }
19067
19068 fn java_to_bq_format(fmt: &str) -> String {
19069 // BigQuery uses %F for yyyy-MM-dd and %T for HH:mm:ss
19070 let c_fmt = java_to_c_format(fmt);
19071 c_fmt.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T")
19072 }
19073
19074 // For Hive source, CAST string literals to appropriate type
19075 let cast_val = if is_hive_source {
19076 match &val {
19077 Expression::Literal(lit)
19078 if matches!(
19079 lit.as_ref(),
19080 crate::expressions::Literal::String(_)
19081 ) =>
19082 {
19083 match target {
19084 DialectType::DuckDB
19085 | DialectType::Presto
19086 | DialectType::Trino
19087 | DialectType::Athena => {
19088 Self::ensure_cast_timestamp(val.clone())
19089 }
19090 DialectType::BigQuery => {
19091 // BigQuery: CAST(val AS DATETIME)
19092 Expression::Cast(Box::new(
19093 crate::expressions::Cast {
19094 this: val.clone(),
19095 to: DataType::Custom {
19096 name: "DATETIME".to_string(),
19097 },
19098 trailing_comments: vec![],
19099 double_colon_syntax: false,
19100 format: None,
19101 default: None,
19102 inferred_type: None,
19103 },
19104 ))
19105 }
19106 _ => val.clone(),
19107 }
19108 }
19109 // For CAST(x AS DATE) or DATE literal, Presto needs CAST(CAST(x AS DATE) AS TIMESTAMP)
19110 Expression::Cast(c)
19111 if matches!(c.to, DataType::Date)
19112 && matches!(
19113 target,
19114 DialectType::Presto
19115 | DialectType::Trino
19116 | DialectType::Athena
19117 ) =>
19118 {
19119 Expression::Cast(Box::new(crate::expressions::Cast {
19120 this: val.clone(),
19121 to: DataType::Timestamp {
19122 timezone: false,
19123 precision: None,
19124 },
19125 trailing_comments: vec![],
19126 double_colon_syntax: false,
19127 format: None,
19128 default: None,
19129 inferred_type: None,
19130 }))
19131 }
19132 Expression::Literal(lit)
19133 if matches!(
19134 lit.as_ref(),
19135 crate::expressions::Literal::Date(_)
19136 ) && matches!(
19137 target,
19138 DialectType::Presto
19139 | DialectType::Trino
19140 | DialectType::Athena
19141 ) =>
19142 {
19143 // DATE 'x' -> CAST(CAST('x' AS DATE) AS TIMESTAMP)
19144 let cast_date = Self::date_literal_to_cast(val.clone());
19145 Expression::Cast(Box::new(crate::expressions::Cast {
19146 this: cast_date,
19147 to: DataType::Timestamp {
19148 timezone: false,
19149 precision: None,
19150 },
19151 trailing_comments: vec![],
19152 double_colon_syntax: false,
19153 format: None,
19154 default: None,
19155 inferred_type: None,
19156 }))
19157 }
19158 _ => val.clone(),
19159 }
19160 } else {
19161 val.clone()
19162 };
19163
19164 match target {
19165 DialectType::DuckDB => {
19166 if let Expression::Literal(lit) = fmt_expr {
19167 if let crate::expressions::Literal::String(s) =
19168 lit.as_ref()
19169 {
19170 let c_fmt = if is_hive_source {
19171 java_to_c_format(s)
19172 } else {
19173 s.clone()
19174 };
19175 Ok(Expression::Function(Box::new(Function::new(
19176 "STRFTIME".to_string(),
19177 vec![cast_val, Expression::string(&c_fmt)],
19178 ))))
19179 } else {
19180 Ok(Expression::Function(Box::new(Function::new(
19181 "STRFTIME".to_string(),
19182 vec![cast_val, fmt_expr.clone()],
19183 ))))
19184 }
19185 } else {
19186 Ok(Expression::Function(Box::new(Function::new(
19187 "STRFTIME".to_string(),
19188 vec![cast_val, fmt_expr.clone()],
19189 ))))
19190 }
19191 }
19192 DialectType::Presto
19193 | DialectType::Trino
19194 | DialectType::Athena => {
19195 if is_hive_source {
19196 if let Expression::Literal(lit) = fmt_expr {
19197 if let crate::expressions::Literal::String(s) =
19198 lit.as_ref()
19199 {
19200 let p_fmt = java_to_presto_format(s);
19201 Ok(Expression::Function(Box::new(
19202 Function::new(
19203 "DATE_FORMAT".to_string(),
19204 vec![
19205 cast_val,
19206 Expression::string(&p_fmt),
19207 ],
19208 ),
19209 )))
19210 } else {
19211 Ok(Expression::Function(Box::new(
19212 Function::new(
19213 "DATE_FORMAT".to_string(),
19214 vec![cast_val, fmt_expr.clone()],
19215 ),
19216 )))
19217 }
19218 } else {
19219 Ok(Expression::Function(Box::new(Function::new(
19220 "DATE_FORMAT".to_string(),
19221 vec![cast_val, fmt_expr.clone()],
19222 ))))
19223 }
19224 } else {
19225 Ok(Expression::Function(Box::new(Function::new(
19226 "DATE_FORMAT".to_string(),
19227 f.args,
19228 ))))
19229 }
19230 }
19231 DialectType::BigQuery => {
19232 // DATE_FORMAT(val, fmt) -> FORMAT_DATE(fmt, val)
19233 if let Expression::Literal(lit) = fmt_expr {
19234 if let crate::expressions::Literal::String(s) =
19235 lit.as_ref()
19236 {
19237 let bq_fmt = if is_hive_source {
19238 java_to_bq_format(s)
19239 } else {
19240 java_to_c_format(s)
19241 };
19242 Ok(Expression::Function(Box::new(Function::new(
19243 "FORMAT_DATE".to_string(),
19244 vec![Expression::string(&bq_fmt), cast_val],
19245 ))))
19246 } else {
19247 Ok(Expression::Function(Box::new(Function::new(
19248 "FORMAT_DATE".to_string(),
19249 vec![fmt_expr.clone(), cast_val],
19250 ))))
19251 }
19252 } else {
19253 Ok(Expression::Function(Box::new(Function::new(
19254 "FORMAT_DATE".to_string(),
19255 vec![fmt_expr.clone(), cast_val],
19256 ))))
19257 }
19258 }
19259 DialectType::PostgreSQL | DialectType::Redshift => {
19260 if let Expression::Literal(lit) = fmt_expr {
19261 if let crate::expressions::Literal::String(s) =
19262 lit.as_ref()
19263 {
19264 let pg_fmt = s
19265 .replace("yyyy", "YYYY")
19266 .replace("MM", "MM")
19267 .replace("dd", "DD")
19268 .replace("HH", "HH24")
19269 .replace("mm", "MI")
19270 .replace("ss", "SS")
19271 .replace("yy", "YY");
19272 Ok(Expression::Function(Box::new(Function::new(
19273 "TO_CHAR".to_string(),
19274 vec![val, Expression::string(&pg_fmt)],
19275 ))))
19276 } else {
19277 Ok(Expression::Function(Box::new(Function::new(
19278 "TO_CHAR".to_string(),
19279 vec![val, fmt_expr.clone()],
19280 ))))
19281 }
19282 } else {
19283 Ok(Expression::Function(Box::new(Function::new(
19284 "TO_CHAR".to_string(),
19285 vec![val, fmt_expr.clone()],
19286 ))))
19287 }
19288 }
19289 _ => Ok(Expression::Function(f)),
19290 }
19291 }
19292 // DATEDIFF(unit, start, end) - 3-arg form
19293 // SQLite uses DATEDIFF(date1, date2, unit_string) instead
19294 "DATEDIFF" if f.args.len() == 3 => {
19295 let mut args = f.args;
19296 // SQLite source: args = (date1, date2, unit_string)
19297 // Standard source: args = (unit, start, end)
19298 let (_arg0, arg1, arg2, unit_str) =
19299 if matches!(source, DialectType::SQLite) {
19300 let date1 = args.remove(0);
19301 let date2 = args.remove(0);
19302 let unit_expr = args.remove(0);
19303 let unit_s = Self::get_unit_str_static(&unit_expr);
19304
19305 // For SQLite target, generate JULIANDAY arithmetic directly
19306 if matches!(target, DialectType::SQLite) {
19307 let jd_first = Expression::Function(Box::new(
19308 Function::new("JULIANDAY".to_string(), vec![date1]),
19309 ));
19310 let jd_second = Expression::Function(Box::new(
19311 Function::new("JULIANDAY".to_string(), vec![date2]),
19312 ));
19313 let diff = Expression::Sub(Box::new(
19314 crate::expressions::BinaryOp::new(
19315 jd_first, jd_second,
19316 ),
19317 ));
19318 let paren_diff = Expression::Paren(Box::new(
19319 crate::expressions::Paren {
19320 this: diff,
19321 trailing_comments: Vec::new(),
19322 },
19323 ));
19324 let adjusted = match unit_s.as_str() {
19325 "HOUR" => Expression::Mul(Box::new(
19326 crate::expressions::BinaryOp::new(
19327 paren_diff,
19328 Expression::Literal(Box::new(
19329 Literal::Number("24.0".to_string()),
19330 )),
19331 ),
19332 )),
19333 "MINUTE" => Expression::Mul(Box::new(
19334 crate::expressions::BinaryOp::new(
19335 paren_diff,
19336 Expression::Literal(Box::new(
19337 Literal::Number("1440.0".to_string()),
19338 )),
19339 ),
19340 )),
19341 "SECOND" => Expression::Mul(Box::new(
19342 crate::expressions::BinaryOp::new(
19343 paren_diff,
19344 Expression::Literal(Box::new(
19345 Literal::Number("86400.0".to_string()),
19346 )),
19347 ),
19348 )),
19349 "MONTH" => Expression::Div(Box::new(
19350 crate::expressions::BinaryOp::new(
19351 paren_diff,
19352 Expression::Literal(Box::new(
19353 Literal::Number("30.0".to_string()),
19354 )),
19355 ),
19356 )),
19357 "YEAR" => Expression::Div(Box::new(
19358 crate::expressions::BinaryOp::new(
19359 paren_diff,
19360 Expression::Literal(Box::new(
19361 Literal::Number("365.0".to_string()),
19362 )),
19363 ),
19364 )),
19365 _ => paren_diff,
19366 };
19367 return Ok(Expression::Cast(Box::new(Cast {
19368 this: adjusted,
19369 to: DataType::Int {
19370 length: None,
19371 integer_spelling: true,
19372 },
19373 trailing_comments: vec![],
19374 double_colon_syntax: false,
19375 format: None,
19376 default: None,
19377 inferred_type: None,
19378 })));
19379 }
19380
19381 // For other targets, remap to standard (unit, start, end) form
19382 let unit_ident =
19383 Expression::Identifier(Identifier::new(&unit_s));
19384 (unit_ident, date1, date2, unit_s)
19385 } else {
19386 let arg0 = args.remove(0);
19387 let arg1 = args.remove(0);
19388 let arg2 = args.remove(0);
19389 let unit_s = Self::get_unit_str_static(&arg0);
19390 (arg0, arg1, arg2, unit_s)
19391 };
19392
19393 // For Hive/Spark source, string literal dates need to be cast
19394 // Note: Databricks is excluded - it handles string args like standard SQL
19395 let is_hive_spark =
19396 matches!(source, DialectType::Hive | DialectType::Spark);
19397
19398 match target {
19399 DialectType::Snowflake => {
19400 let unit =
19401 Expression::Identifier(Identifier::new(&unit_str));
19402 // Use ensure_to_date_preserved to add TO_DATE with a marker
19403 // that prevents the Snowflake TO_DATE handler from converting it to CAST
19404 let d1 = if is_hive_spark {
19405 Self::ensure_to_date_preserved(arg1)
19406 } else {
19407 arg1
19408 };
19409 let d2 = if is_hive_spark {
19410 Self::ensure_to_date_preserved(arg2)
19411 } else {
19412 arg2
19413 };
19414 Ok(Expression::Function(Box::new(Function::new(
19415 "DATEDIFF".to_string(),
19416 vec![unit, d1, d2],
19417 ))))
19418 }
19419 DialectType::Redshift => {
19420 let unit =
19421 Expression::Identifier(Identifier::new(&unit_str));
19422 let d1 = if is_hive_spark {
19423 Self::ensure_cast_date(arg1)
19424 } else {
19425 arg1
19426 };
19427 let d2 = if is_hive_spark {
19428 Self::ensure_cast_date(arg2)
19429 } else {
19430 arg2
19431 };
19432 Ok(Expression::Function(Box::new(Function::new(
19433 "DATEDIFF".to_string(),
19434 vec![unit, d1, d2],
19435 ))))
19436 }
19437 DialectType::TSQL => {
19438 let unit =
19439 Expression::Identifier(Identifier::new(&unit_str));
19440 Ok(Expression::Function(Box::new(Function::new(
19441 "DATEDIFF".to_string(),
19442 vec![unit, arg1, arg2],
19443 ))))
19444 }
19445 DialectType::DuckDB => {
19446 let is_redshift_tsql = matches!(
19447 source,
19448 DialectType::Redshift | DialectType::TSQL
19449 );
19450 if is_hive_spark {
19451 // For Hive/Spark source, CAST string args to DATE and emit DATE_DIFF directly
19452 let d1 = Self::ensure_cast_date(arg1);
19453 let d2 = Self::ensure_cast_date(arg2);
19454 Ok(Expression::Function(Box::new(Function::new(
19455 "DATE_DIFF".to_string(),
19456 vec![Expression::string(&unit_str), d1, d2],
19457 ))))
19458 } else if matches!(source, DialectType::Snowflake) {
19459 // For Snowflake source: special handling per unit
19460 match unit_str.as_str() {
19461 "NANOSECOND" => {
19462 // DATEDIFF(NANOSECOND, start, end) -> EPOCH_NS(CAST(end AS TIMESTAMP_NS)) - EPOCH_NS(CAST(start AS TIMESTAMP_NS))
19463 fn cast_to_timestamp_ns(
19464 expr: Expression,
19465 ) -> Expression
19466 {
19467 Expression::Cast(Box::new(Cast {
19468 this: expr,
19469 to: DataType::Custom {
19470 name: "TIMESTAMP_NS".to_string(),
19471 },
19472 trailing_comments: vec![],
19473 double_colon_syntax: false,
19474 format: None,
19475 default: None,
19476 inferred_type: None,
19477 }))
19478 }
19479 let epoch_end = Expression::Function(Box::new(
19480 Function::new(
19481 "EPOCH_NS".to_string(),
19482 vec![cast_to_timestamp_ns(arg2)],
19483 ),
19484 ));
19485 let epoch_start = Expression::Function(
19486 Box::new(Function::new(
19487 "EPOCH_NS".to_string(),
19488 vec![cast_to_timestamp_ns(arg1)],
19489 )),
19490 );
19491 Ok(Expression::Sub(Box::new(BinaryOp::new(
19492 epoch_end,
19493 epoch_start,
19494 ))))
19495 }
19496 "WEEK" => {
19497 // DATE_DIFF('WEEK', DATE_TRUNC('WEEK', CAST(x AS DATE)), DATE_TRUNC('WEEK', CAST(y AS DATE)))
19498 let d1 = Self::force_cast_date(arg1);
19499 let d2 = Self::force_cast_date(arg2);
19500 let dt1 = Expression::Function(Box::new(
19501 Function::new(
19502 "DATE_TRUNC".to_string(),
19503 vec![Expression::string("WEEK"), d1],
19504 ),
19505 ));
19506 let dt2 = Expression::Function(Box::new(
19507 Function::new(
19508 "DATE_TRUNC".to_string(),
19509 vec![Expression::string("WEEK"), d2],
19510 ),
19511 ));
19512 Ok(Expression::Function(Box::new(
19513 Function::new(
19514 "DATE_DIFF".to_string(),
19515 vec![
19516 Expression::string(&unit_str),
19517 dt1,
19518 dt2,
19519 ],
19520 ),
19521 )))
19522 }
19523 _ => {
19524 // YEAR, MONTH, QUARTER, DAY, etc.: CAST to DATE
19525 let d1 = Self::force_cast_date(arg1);
19526 let d2 = Self::force_cast_date(arg2);
19527 Ok(Expression::Function(Box::new(
19528 Function::new(
19529 "DATE_DIFF".to_string(),
19530 vec![
19531 Expression::string(&unit_str),
19532 d1,
19533 d2,
19534 ],
19535 ),
19536 )))
19537 }
19538 }
19539 } else if is_redshift_tsql {
19540 // For Redshift/TSQL source, CAST args to TIMESTAMP (always)
19541 let d1 = Self::force_cast_timestamp(arg1);
19542 let d2 = Self::force_cast_timestamp(arg2);
19543 Ok(Expression::Function(Box::new(Function::new(
19544 "DATE_DIFF".to_string(),
19545 vec![Expression::string(&unit_str), d1, d2],
19546 ))))
19547 } else {
19548 // Keep as DATEDIFF so DuckDB's transform_datediff handles
19549 // DATE_TRUNC for WEEK, CAST for string literals, etc.
19550 let unit =
19551 Expression::Identifier(Identifier::new(&unit_str));
19552 Ok(Expression::Function(Box::new(Function::new(
19553 "DATEDIFF".to_string(),
19554 vec![unit, arg1, arg2],
19555 ))))
19556 }
19557 }
19558 DialectType::BigQuery => {
19559 let is_redshift_tsql = matches!(
19560 source,
19561 DialectType::Redshift
19562 | DialectType::TSQL
19563 | DialectType::Snowflake
19564 );
19565 let cast_d1 = if is_hive_spark {
19566 Self::ensure_cast_date(arg1)
19567 } else if is_redshift_tsql {
19568 Self::force_cast_datetime(arg1)
19569 } else {
19570 Self::ensure_cast_datetime(arg1)
19571 };
19572 let cast_d2 = if is_hive_spark {
19573 Self::ensure_cast_date(arg2)
19574 } else if is_redshift_tsql {
19575 Self::force_cast_datetime(arg2)
19576 } else {
19577 Self::ensure_cast_datetime(arg2)
19578 };
19579 let unit =
19580 Expression::Identifier(Identifier::new(&unit_str));
19581 Ok(Expression::Function(Box::new(Function::new(
19582 "DATE_DIFF".to_string(),
19583 vec![cast_d2, cast_d1, unit],
19584 ))))
19585 }
19586 DialectType::Presto
19587 | DialectType::Trino
19588 | DialectType::Athena => {
19589 // For Hive/Spark source, string literals need double-cast: CAST(CAST(x AS TIMESTAMP) AS DATE)
19590 // For Redshift/TSQL source, args need CAST to TIMESTAMP (always)
19591 let is_redshift_tsql = matches!(
19592 source,
19593 DialectType::Redshift
19594 | DialectType::TSQL
19595 | DialectType::Snowflake
19596 );
19597 let d1 = if is_hive_spark {
19598 Self::double_cast_timestamp_date(arg1)
19599 } else if is_redshift_tsql {
19600 Self::force_cast_timestamp(arg1)
19601 } else {
19602 arg1
19603 };
19604 let d2 = if is_hive_spark {
19605 Self::double_cast_timestamp_date(arg2)
19606 } else if is_redshift_tsql {
19607 Self::force_cast_timestamp(arg2)
19608 } else {
19609 arg2
19610 };
19611 Ok(Expression::Function(Box::new(Function::new(
19612 "DATE_DIFF".to_string(),
19613 vec![Expression::string(&unit_str), d1, d2],
19614 ))))
19615 }
19616 DialectType::Hive => match unit_str.as_str() {
19617 "MONTH" => Ok(Expression::Cast(Box::new(Cast {
19618 this: Expression::Function(Box::new(Function::new(
19619 "MONTHS_BETWEEN".to_string(),
19620 vec![arg2, arg1],
19621 ))),
19622 to: DataType::Int {
19623 length: None,
19624 integer_spelling: false,
19625 },
19626 trailing_comments: vec![],
19627 double_colon_syntax: false,
19628 format: None,
19629 default: None,
19630 inferred_type: None,
19631 }))),
19632 "WEEK" => Ok(Expression::Cast(Box::new(Cast {
19633 this: Expression::Div(Box::new(
19634 crate::expressions::BinaryOp::new(
19635 Expression::Function(Box::new(Function::new(
19636 "DATEDIFF".to_string(),
19637 vec![arg2, arg1],
19638 ))),
19639 Expression::number(7),
19640 ),
19641 )),
19642 to: DataType::Int {
19643 length: None,
19644 integer_spelling: false,
19645 },
19646 trailing_comments: vec![],
19647 double_colon_syntax: false,
19648 format: None,
19649 default: None,
19650 inferred_type: None,
19651 }))),
19652 _ => Ok(Expression::Function(Box::new(Function::new(
19653 "DATEDIFF".to_string(),
19654 vec![arg2, arg1],
19655 )))),
19656 },
19657 DialectType::Spark | DialectType::Databricks => {
19658 let unit =
19659 Expression::Identifier(Identifier::new(&unit_str));
19660 Ok(Expression::Function(Box::new(Function::new(
19661 "DATEDIFF".to_string(),
19662 vec![unit, arg1, arg2],
19663 ))))
19664 }
19665 _ => {
19666 // For Hive/Spark source targeting PostgreSQL etc., cast string literals to DATE
19667 let d1 = if is_hive_spark {
19668 Self::ensure_cast_date(arg1)
19669 } else {
19670 arg1
19671 };
19672 let d2 = if is_hive_spark {
19673 Self::ensure_cast_date(arg2)
19674 } else {
19675 arg2
19676 };
19677 let unit =
19678 Expression::Identifier(Identifier::new(&unit_str));
19679 Ok(Expression::Function(Box::new(Function::new(
19680 "DATEDIFF".to_string(),
19681 vec![unit, d1, d2],
19682 ))))
19683 }
19684 }
19685 }
19686 // DATEDIFF(end, start) - 2-arg form from Hive/MySQL
19687 "DATEDIFF" if f.args.len() == 2 => {
19688 let mut args = f.args;
19689 let arg0 = args.remove(0);
19690 let arg1 = args.remove(0);
19691
19692 // Helper: unwrap TO_DATE(x) -> x (extracts inner arg)
19693 // Also recognizes TryCast/Cast to DATE that may have been produced by
19694 // cross-dialect TO_DATE -> TRY_CAST conversion
19695 let unwrap_to_date = |e: Expression| -> (Expression, bool) {
19696 if let Expression::Function(ref f) = e {
19697 if f.name.eq_ignore_ascii_case("TO_DATE")
19698 && f.args.len() == 1
19699 {
19700 return (f.args[0].clone(), true);
19701 }
19702 }
19703 // Also recognize TryCast(x, Date) as an already-converted TO_DATE
19704 if let Expression::TryCast(ref c) = e {
19705 if matches!(c.to, DataType::Date) {
19706 return (e, true); // Already properly cast, return as-is
19707 }
19708 }
19709 (e, false)
19710 };
19711
19712 match target {
19713 DialectType::DuckDB => {
19714 // For Hive source, always CAST to DATE
19715 // If arg is TO_DATE(x) or TRY_CAST(x AS DATE), use it directly
19716 let cast_d0 = if matches!(
19717 source,
19718 DialectType::Hive
19719 | DialectType::Spark
19720 | DialectType::Databricks
19721 ) {
19722 let (inner, was_to_date) = unwrap_to_date(arg1);
19723 if was_to_date {
19724 // Already a date expression, use directly
19725 if matches!(&inner, Expression::TryCast(_)) {
19726 inner // Already TRY_CAST(x AS DATE)
19727 } else {
19728 Self::try_cast_date(inner)
19729 }
19730 } else {
19731 Self::force_cast_date(inner)
19732 }
19733 } else {
19734 Self::ensure_cast_date(arg1)
19735 };
19736 let cast_d1 = if matches!(
19737 source,
19738 DialectType::Hive
19739 | DialectType::Spark
19740 | DialectType::Databricks
19741 ) {
19742 let (inner, was_to_date) = unwrap_to_date(arg0);
19743 if was_to_date {
19744 if matches!(&inner, Expression::TryCast(_)) {
19745 inner
19746 } else {
19747 Self::try_cast_date(inner)
19748 }
19749 } else {
19750 Self::force_cast_date(inner)
19751 }
19752 } else {
19753 Self::ensure_cast_date(arg0)
19754 };
19755 Ok(Expression::Function(Box::new(Function::new(
19756 "DATE_DIFF".to_string(),
19757 vec![Expression::string("DAY"), cast_d0, cast_d1],
19758 ))))
19759 }
19760 DialectType::Presto
19761 | DialectType::Trino
19762 | DialectType::Athena => {
19763 // For Hive/Spark source, apply double_cast_timestamp_date
19764 // For other sources (MySQL etc.), just swap args without casting
19765 if matches!(
19766 source,
19767 DialectType::Hive
19768 | DialectType::Spark
19769 | DialectType::Databricks
19770 ) {
19771 let cast_fn = |e: Expression| -> Expression {
19772 let (inner, was_to_date) = unwrap_to_date(e);
19773 if was_to_date {
19774 let first_cast =
19775 Self::double_cast_timestamp_date(inner);
19776 Self::double_cast_timestamp_date(first_cast)
19777 } else {
19778 Self::double_cast_timestamp_date(inner)
19779 }
19780 };
19781 Ok(Expression::Function(Box::new(Function::new(
19782 "DATE_DIFF".to_string(),
19783 vec![
19784 Expression::string("DAY"),
19785 cast_fn(arg1),
19786 cast_fn(arg0),
19787 ],
19788 ))))
19789 } else {
19790 Ok(Expression::Function(Box::new(Function::new(
19791 "DATE_DIFF".to_string(),
19792 vec![Expression::string("DAY"), arg1, arg0],
19793 ))))
19794 }
19795 }
19796 DialectType::Redshift => {
19797 let unit = Expression::Identifier(Identifier::new("DAY"));
19798 Ok(Expression::Function(Box::new(Function::new(
19799 "DATEDIFF".to_string(),
19800 vec![unit, arg1, arg0],
19801 ))))
19802 }
19803 _ => Ok(Expression::Function(Box::new(Function::new(
19804 "DATEDIFF".to_string(),
19805 vec![arg0, arg1],
19806 )))),
19807 }
19808 }
19809 // DATE_DIFF(unit, start, end) - 3-arg with string unit (ClickHouse/DuckDB style)
19810 "DATE_DIFF" if f.args.len() == 3 => {
19811 let mut args = f.args;
19812 let arg0 = args.remove(0);
19813 let arg1 = args.remove(0);
19814 let arg2 = args.remove(0);
19815 let unit_str = Self::get_unit_str_static(&arg0);
19816
19817 match target {
19818 DialectType::DuckDB => {
19819 // DuckDB: DATE_DIFF('UNIT', start, end)
19820 Ok(Expression::Function(Box::new(Function::new(
19821 "DATE_DIFF".to_string(),
19822 vec![Expression::string(&unit_str), arg1, arg2],
19823 ))))
19824 }
19825 DialectType::Presto
19826 | DialectType::Trino
19827 | DialectType::Athena => {
19828 Ok(Expression::Function(Box::new(Function::new(
19829 "DATE_DIFF".to_string(),
19830 vec![Expression::string(&unit_str), arg1, arg2],
19831 ))))
19832 }
19833 DialectType::ClickHouse => {
19834 // ClickHouse: DATE_DIFF(UNIT, start, end) - identifier unit
19835 let unit =
19836 Expression::Identifier(Identifier::new(&unit_str));
19837 Ok(Expression::Function(Box::new(Function::new(
19838 "DATE_DIFF".to_string(),
19839 vec![unit, arg1, arg2],
19840 ))))
19841 }
19842 DialectType::Snowflake | DialectType::Redshift => {
19843 let unit =
19844 Expression::Identifier(Identifier::new(&unit_str));
19845 Ok(Expression::Function(Box::new(Function::new(
19846 "DATEDIFF".to_string(),
19847 vec![unit, arg1, arg2],
19848 ))))
19849 }
19850 _ => {
19851 let unit =
19852 Expression::Identifier(Identifier::new(&unit_str));
19853 Ok(Expression::Function(Box::new(Function::new(
19854 "DATEDIFF".to_string(),
19855 vec![unit, arg1, arg2],
19856 ))))
19857 }
19858 }
19859 }
19860 // DATEADD(unit, val, date) - 3-arg form
19861 "DATEADD" if f.args.len() == 3 => {
19862 let mut args = f.args;
19863 let arg0 = args.remove(0);
19864 let arg1 = args.remove(0);
19865 let arg2 = args.remove(0);
19866 let unit_str = Self::get_unit_str_static(&arg0);
19867
19868 // Normalize TSQL unit abbreviations to standard names
19869 let unit_str = match unit_str.as_str() {
19870 "YY" | "YYYY" => "YEAR".to_string(),
19871 "QQ" | "Q" => "QUARTER".to_string(),
19872 "MM" | "M" => "MONTH".to_string(),
19873 "WK" | "WW" => "WEEK".to_string(),
19874 "DD" | "D" | "DY" => "DAY".to_string(),
19875 "HH" => "HOUR".to_string(),
19876 "MI" | "N" => "MINUTE".to_string(),
19877 "SS" | "S" => "SECOND".to_string(),
19878 "MS" => "MILLISECOND".to_string(),
19879 "MCS" | "US" => "MICROSECOND".to_string(),
19880 _ => unit_str,
19881 };
19882 match target {
19883 DialectType::Snowflake => {
19884 let unit =
19885 Expression::Identifier(Identifier::new(&unit_str));
19886 // Cast string literal to TIMESTAMP, but not for Snowflake source
19887 // (Snowflake natively accepts string literals in DATEADD)
19888 let arg2 = if matches!(
19889 &arg2,
19890 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_))
19891 ) && !matches!(source, DialectType::Snowflake)
19892 {
19893 Expression::Cast(Box::new(Cast {
19894 this: arg2,
19895 to: DataType::Timestamp {
19896 precision: None,
19897 timezone: false,
19898 },
19899 trailing_comments: Vec::new(),
19900 double_colon_syntax: false,
19901 format: None,
19902 default: None,
19903 inferred_type: None,
19904 }))
19905 } else {
19906 arg2
19907 };
19908 Ok(Expression::Function(Box::new(Function::new(
19909 "DATEADD".to_string(),
19910 vec![unit, arg1, arg2],
19911 ))))
19912 }
19913 DialectType::TSQL => {
19914 let unit =
19915 Expression::Identifier(Identifier::new(&unit_str));
19916 // Cast string literal to DATETIME2, but not when source is Spark/Databricks family
19917 let arg2 = if matches!(
19918 &arg2,
19919 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_))
19920 ) && !matches!(
19921 source,
19922 DialectType::Spark
19923 | DialectType::Databricks
19924 | DialectType::Hive
19925 ) {
19926 Expression::Cast(Box::new(Cast {
19927 this: arg2,
19928 to: DataType::Custom {
19929 name: "DATETIME2".to_string(),
19930 },
19931 trailing_comments: Vec::new(),
19932 double_colon_syntax: false,
19933 format: None,
19934 default: None,
19935 inferred_type: None,
19936 }))
19937 } else {
19938 arg2
19939 };
19940 Ok(Expression::Function(Box::new(Function::new(
19941 "DATEADD".to_string(),
19942 vec![unit, arg1, arg2],
19943 ))))
19944 }
19945 DialectType::Redshift => {
19946 let unit =
19947 Expression::Identifier(Identifier::new(&unit_str));
19948 Ok(Expression::Function(Box::new(Function::new(
19949 "DATEADD".to_string(),
19950 vec![unit, arg1, arg2],
19951 ))))
19952 }
19953 DialectType::Databricks => {
19954 let unit =
19955 Expression::Identifier(Identifier::new(&unit_str));
19956 // Sources with native DATEADD (TSQL, Databricks, Snowflake) -> DATEADD
19957 // Other sources (Redshift TsOrDsAdd, etc.) -> DATE_ADD
19958 let func_name = if matches!(
19959 source,
19960 DialectType::TSQL
19961 | DialectType::Fabric
19962 | DialectType::Databricks
19963 | DialectType::Snowflake
19964 ) {
19965 "DATEADD"
19966 } else {
19967 "DATE_ADD"
19968 };
19969 Ok(Expression::Function(Box::new(Function::new(
19970 func_name.to_string(),
19971 vec![unit, arg1, arg2],
19972 ))))
19973 }
19974 DialectType::DuckDB => {
19975 // Special handling for NANOSECOND from Snowflake
19976 if unit_str == "NANOSECOND"
19977 && matches!(source, DialectType::Snowflake)
19978 {
19979 // DATEADD(NANOSECOND, offset, ts) -> MAKE_TIMESTAMP_NS(EPOCH_NS(CAST(ts AS TIMESTAMP_NS)) + offset)
19980 let cast_ts = Expression::Cast(Box::new(Cast {
19981 this: arg2,
19982 to: DataType::Custom {
19983 name: "TIMESTAMP_NS".to_string(),
19984 },
19985 trailing_comments: vec![],
19986 double_colon_syntax: false,
19987 format: None,
19988 default: None,
19989 inferred_type: None,
19990 }));
19991 let epoch_ns =
19992 Expression::Function(Box::new(Function::new(
19993 "EPOCH_NS".to_string(),
19994 vec![cast_ts],
19995 )));
19996 let sum = Expression::Add(Box::new(BinaryOp::new(
19997 epoch_ns, arg1,
19998 )));
19999 Ok(Expression::Function(Box::new(Function::new(
20000 "MAKE_TIMESTAMP_NS".to_string(),
20001 vec![sum],
20002 ))))
20003 } else {
20004 // DuckDB: convert to date + INTERVAL syntax with CAST
20005 let iu = Self::parse_interval_unit_static(&unit_str);
20006 let interval = Expression::Interval(Box::new(crate::expressions::Interval {
20007 this: Some(arg1),
20008 unit: Some(crate::expressions::IntervalUnitSpec::Simple { unit: iu, use_plural: false }),
20009 }));
20010 // Cast string literal to TIMESTAMP
20011 let arg2 = if matches!(
20012 &arg2,
20013 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_))
20014 ) {
20015 Expression::Cast(Box::new(Cast {
20016 this: arg2,
20017 to: DataType::Timestamp {
20018 precision: None,
20019 timezone: false,
20020 },
20021 trailing_comments: Vec::new(),
20022 double_colon_syntax: false,
20023 format: None,
20024 default: None,
20025 inferred_type: None,
20026 }))
20027 } else {
20028 arg2
20029 };
20030 Ok(Expression::Add(Box::new(
20031 crate::expressions::BinaryOp::new(arg2, interval),
20032 )))
20033 }
20034 }
20035 DialectType::Spark => {
20036 // For TSQL source: convert to ADD_MONTHS/DATE_ADD(date, val)
20037 // For other sources: keep 3-arg DATE_ADD(UNIT, val, date) form
20038 if matches!(source, DialectType::TSQL | DialectType::Fabric)
20039 {
20040 fn multiply_expr_spark(
20041 expr: Expression,
20042 factor: i64,
20043 ) -> Expression
20044 {
20045 if let Expression::Literal(lit) = &expr {
20046 if let crate::expressions::Literal::Number(n) =
20047 lit.as_ref()
20048 {
20049 if let Ok(val) = n.parse::<i64>() {
20050 return Expression::Literal(Box::new(
20051 crate::expressions::Literal::Number(
20052 (val * factor).to_string(),
20053 ),
20054 ));
20055 }
20056 }
20057 }
20058 Expression::Mul(Box::new(
20059 crate::expressions::BinaryOp::new(
20060 expr,
20061 Expression::Literal(Box::new(
20062 crate::expressions::Literal::Number(
20063 factor.to_string(),
20064 ),
20065 )),
20066 ),
20067 ))
20068 }
20069 let normalized_unit = match unit_str.as_str() {
20070 "YEAR" | "YY" | "YYYY" => "YEAR",
20071 "QUARTER" | "QQ" | "Q" => "QUARTER",
20072 "MONTH" | "MM" | "M" => "MONTH",
20073 "WEEK" | "WK" | "WW" => "WEEK",
20074 "DAY" | "DD" | "D" | "DY" => "DAY",
20075 _ => &unit_str,
20076 };
20077 match normalized_unit {
20078 "YEAR" => {
20079 let months = multiply_expr_spark(arg1, 12);
20080 Ok(Expression::Function(Box::new(
20081 Function::new(
20082 "ADD_MONTHS".to_string(),
20083 vec![arg2, months],
20084 ),
20085 )))
20086 }
20087 "QUARTER" => {
20088 let months = multiply_expr_spark(arg1, 3);
20089 Ok(Expression::Function(Box::new(
20090 Function::new(
20091 "ADD_MONTHS".to_string(),
20092 vec![arg2, months],
20093 ),
20094 )))
20095 }
20096 "MONTH" => Ok(Expression::Function(Box::new(
20097 Function::new(
20098 "ADD_MONTHS".to_string(),
20099 vec![arg2, arg1],
20100 ),
20101 ))),
20102 "WEEK" => {
20103 let days = multiply_expr_spark(arg1, 7);
20104 Ok(Expression::Function(Box::new(
20105 Function::new(
20106 "DATE_ADD".to_string(),
20107 vec![arg2, days],
20108 ),
20109 )))
20110 }
20111 "DAY" => Ok(Expression::Function(Box::new(
20112 Function::new(
20113 "DATE_ADD".to_string(),
20114 vec![arg2, arg1],
20115 ),
20116 ))),
20117 _ => {
20118 let unit = Expression::Identifier(
20119 Identifier::new(&unit_str),
20120 );
20121 Ok(Expression::Function(Box::new(
20122 Function::new(
20123 "DATE_ADD".to_string(),
20124 vec![unit, arg1, arg2],
20125 ),
20126 )))
20127 }
20128 }
20129 } else {
20130 // Non-TSQL source: keep 3-arg DATE_ADD(UNIT, val, date)
20131 let unit =
20132 Expression::Identifier(Identifier::new(&unit_str));
20133 Ok(Expression::Function(Box::new(Function::new(
20134 "DATE_ADD".to_string(),
20135 vec![unit, arg1, arg2],
20136 ))))
20137 }
20138 }
20139 DialectType::Hive => match unit_str.as_str() {
20140 "MONTH" => {
20141 Ok(Expression::Function(Box::new(Function::new(
20142 "ADD_MONTHS".to_string(),
20143 vec![arg2, arg1],
20144 ))))
20145 }
20146 _ => Ok(Expression::Function(Box::new(Function::new(
20147 "DATE_ADD".to_string(),
20148 vec![arg2, arg1],
20149 )))),
20150 },
20151 DialectType::Presto
20152 | DialectType::Trino
20153 | DialectType::Athena => {
20154 // Cast string literal date to TIMESTAMP
20155 let arg2 = if matches!(
20156 &arg2,
20157 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_))
20158 ) {
20159 Expression::Cast(Box::new(Cast {
20160 this: arg2,
20161 to: DataType::Timestamp {
20162 precision: None,
20163 timezone: false,
20164 },
20165 trailing_comments: Vec::new(),
20166 double_colon_syntax: false,
20167 format: None,
20168 default: None,
20169 inferred_type: None,
20170 }))
20171 } else {
20172 arg2
20173 };
20174 Ok(Expression::Function(Box::new(Function::new(
20175 "DATE_ADD".to_string(),
20176 vec![Expression::string(&unit_str), arg1, arg2],
20177 ))))
20178 }
20179 DialectType::MySQL => {
20180 let iu = Self::parse_interval_unit_static(&unit_str);
20181 Ok(Expression::DateAdd(Box::new(
20182 crate::expressions::DateAddFunc {
20183 this: arg2,
20184 interval: arg1,
20185 unit: iu,
20186 },
20187 )))
20188 }
20189 DialectType::PostgreSQL => {
20190 // Cast string literal date to TIMESTAMP
20191 let arg2 = if matches!(
20192 &arg2,
20193 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_))
20194 ) {
20195 Expression::Cast(Box::new(Cast {
20196 this: arg2,
20197 to: DataType::Timestamp {
20198 precision: None,
20199 timezone: false,
20200 },
20201 trailing_comments: Vec::new(),
20202 double_colon_syntax: false,
20203 format: None,
20204 default: None,
20205 inferred_type: None,
20206 }))
20207 } else {
20208 arg2
20209 };
20210 let interval = Expression::Interval(Box::new(
20211 crate::expressions::Interval {
20212 this: Some(Expression::string(&format!(
20213 "{} {}",
20214 Self::expr_to_string_static(&arg1),
20215 unit_str
20216 ))),
20217 unit: None,
20218 },
20219 ));
20220 Ok(Expression::Add(Box::new(
20221 crate::expressions::BinaryOp::new(arg2, interval),
20222 )))
20223 }
20224 DialectType::BigQuery => {
20225 let iu = Self::parse_interval_unit_static(&unit_str);
20226 let interval = Expression::Interval(Box::new(
20227 crate::expressions::Interval {
20228 this: Some(arg1),
20229 unit: Some(
20230 crate::expressions::IntervalUnitSpec::Simple {
20231 unit: iu,
20232 use_plural: false,
20233 },
20234 ),
20235 },
20236 ));
20237 // Non-TSQL sources: CAST string literal to DATETIME
20238 let arg2 = if !matches!(
20239 source,
20240 DialectType::TSQL | DialectType::Fabric
20241 ) && matches!(
20242 &arg2,
20243 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_))
20244 ) {
20245 Expression::Cast(Box::new(Cast {
20246 this: arg2,
20247 to: DataType::Custom {
20248 name: "DATETIME".to_string(),
20249 },
20250 trailing_comments: Vec::new(),
20251 double_colon_syntax: false,
20252 format: None,
20253 default: None,
20254 inferred_type: None,
20255 }))
20256 } else {
20257 arg2
20258 };
20259 Ok(Expression::Function(Box::new(Function::new(
20260 "DATE_ADD".to_string(),
20261 vec![arg2, interval],
20262 ))))
20263 }
20264 _ => {
20265 let unit =
20266 Expression::Identifier(Identifier::new(&unit_str));
20267 Ok(Expression::Function(Box::new(Function::new(
20268 "DATEADD".to_string(),
20269 vec![unit, arg1, arg2],
20270 ))))
20271 }
20272 }
20273 }
20274 // DATE_ADD - 3-arg: either (unit, val, date) from Presto/ClickHouse
20275 // or (date, val, 'UNIT') from Generic canonical form
20276 "DATE_ADD" if f.args.len() == 3 => {
20277 let mut args = f.args;
20278 let arg0 = args.remove(0);
20279 let arg1 = args.remove(0);
20280 let arg2 = args.remove(0);
20281 // Detect Generic canonical form: DATE_ADD(date, amount, 'UNIT')
20282 // where arg2 is a string literal matching a unit name
20283 let arg2_unit = match &arg2 {
20284 Expression::Literal(lit)
20285 if matches!(lit.as_ref(), Literal::String(_)) =>
20286 {
20287 let Literal::String(s) = lit.as_ref() else {
20288 unreachable!()
20289 };
20290 let u = s.to_ascii_uppercase();
20291 if matches!(
20292 u.as_str(),
20293 "DAY"
20294 | "MONTH"
20295 | "YEAR"
20296 | "HOUR"
20297 | "MINUTE"
20298 | "SECOND"
20299 | "WEEK"
20300 | "QUARTER"
20301 | "MILLISECOND"
20302 | "MICROSECOND"
20303 ) {
20304 Some(u)
20305 } else {
20306 None
20307 }
20308 }
20309 _ => None,
20310 };
20311 // Reorder: if arg2 is the unit, swap to (unit, val, date) form
20312 let (unit_str, val, date) = if let Some(u) = arg2_unit {
20313 (u, arg1, arg0)
20314 } else {
20315 (Self::get_unit_str_static(&arg0), arg1, arg2)
20316 };
20317 // Alias for backward compat with the rest of the match
20318 let arg1 = val;
20319 let arg2 = date;
20320
20321 match target {
20322 DialectType::Presto
20323 | DialectType::Trino
20324 | DialectType::Athena => {
20325 Ok(Expression::Function(Box::new(Function::new(
20326 "DATE_ADD".to_string(),
20327 vec![Expression::string(&unit_str), arg1, arg2],
20328 ))))
20329 }
20330 DialectType::DuckDB => {
20331 let iu = Self::parse_interval_unit_static(&unit_str);
20332 let interval = Expression::Interval(Box::new(
20333 crate::expressions::Interval {
20334 this: Some(arg1),
20335 unit: Some(
20336 crate::expressions::IntervalUnitSpec::Simple {
20337 unit: iu,
20338 use_plural: false,
20339 },
20340 ),
20341 },
20342 ));
20343 Ok(Expression::Add(Box::new(
20344 crate::expressions::BinaryOp::new(arg2, interval),
20345 )))
20346 }
20347 DialectType::PostgreSQL
20348 | DialectType::Materialize
20349 | DialectType::RisingWave => {
20350 // PostgreSQL: x + INTERVAL '1 DAY'
20351 let amount_str = Self::expr_to_string_static(&arg1);
20352 let interval = Expression::Interval(Box::new(
20353 crate::expressions::Interval {
20354 this: Some(Expression::string(&format!(
20355 "{} {}",
20356 amount_str, unit_str
20357 ))),
20358 unit: None,
20359 },
20360 ));
20361 Ok(Expression::Add(Box::new(
20362 crate::expressions::BinaryOp::new(arg2, interval),
20363 )))
20364 }
20365 DialectType::Snowflake
20366 | DialectType::TSQL
20367 | DialectType::Redshift => {
20368 let unit =
20369 Expression::Identifier(Identifier::new(&unit_str));
20370 Ok(Expression::Function(Box::new(Function::new(
20371 "DATEADD".to_string(),
20372 vec![unit, arg1, arg2],
20373 ))))
20374 }
20375 DialectType::BigQuery
20376 | DialectType::MySQL
20377 | DialectType::Doris
20378 | DialectType::StarRocks
20379 | DialectType::Drill => {
20380 // DATE_ADD(date, INTERVAL amount UNIT)
20381 let iu = Self::parse_interval_unit_static(&unit_str);
20382 let interval = Expression::Interval(Box::new(
20383 crate::expressions::Interval {
20384 this: Some(arg1),
20385 unit: Some(
20386 crate::expressions::IntervalUnitSpec::Simple {
20387 unit: iu,
20388 use_plural: false,
20389 },
20390 ),
20391 },
20392 ));
20393 Ok(Expression::Function(Box::new(Function::new(
20394 "DATE_ADD".to_string(),
20395 vec![arg2, interval],
20396 ))))
20397 }
20398 DialectType::SQLite => {
20399 // SQLite: DATE(x, '1 DAY')
20400 // Build the string '1 DAY' from amount and unit
20401 let amount_str = match &arg1 {
20402 Expression::Literal(lit)
20403 if matches!(lit.as_ref(), Literal::Number(_)) =>
20404 {
20405 let Literal::Number(n) = lit.as_ref() else {
20406 unreachable!()
20407 };
20408 n.clone()
20409 }
20410 _ => "1".to_string(),
20411 };
20412 Ok(Expression::Function(Box::new(Function::new(
20413 "DATE".to_string(),
20414 vec![
20415 arg2,
20416 Expression::string(format!(
20417 "{} {}",
20418 amount_str, unit_str
20419 )),
20420 ],
20421 ))))
20422 }
20423 DialectType::Dremio => {
20424 // Dremio: DATE_ADD(date, amount) - drops unit
20425 Ok(Expression::Function(Box::new(Function::new(
20426 "DATE_ADD".to_string(),
20427 vec![arg2, arg1],
20428 ))))
20429 }
20430 DialectType::Spark => {
20431 // Spark: DATE_ADD(date, val) for DAY, or DATEADD(UNIT, val, date)
20432 if unit_str == "DAY" {
20433 Ok(Expression::Function(Box::new(Function::new(
20434 "DATE_ADD".to_string(),
20435 vec![arg2, arg1],
20436 ))))
20437 } else {
20438 let unit =
20439 Expression::Identifier(Identifier::new(&unit_str));
20440 Ok(Expression::Function(Box::new(Function::new(
20441 "DATE_ADD".to_string(),
20442 vec![unit, arg1, arg2],
20443 ))))
20444 }
20445 }
20446 DialectType::Databricks => {
20447 let unit =
20448 Expression::Identifier(Identifier::new(&unit_str));
20449 Ok(Expression::Function(Box::new(Function::new(
20450 "DATE_ADD".to_string(),
20451 vec![unit, arg1, arg2],
20452 ))))
20453 }
20454 DialectType::Hive => {
20455 // Hive: DATE_ADD(date, val) for DAY
20456 Ok(Expression::Function(Box::new(Function::new(
20457 "DATE_ADD".to_string(),
20458 vec![arg2, arg1],
20459 ))))
20460 }
20461 _ => {
20462 let unit =
20463 Expression::Identifier(Identifier::new(&unit_str));
20464 Ok(Expression::Function(Box::new(Function::new(
20465 "DATE_ADD".to_string(),
20466 vec![unit, arg1, arg2],
20467 ))))
20468 }
20469 }
20470 }
20471 // DATE_ADD(date, days) - 2-arg Hive/Spark/Generic form (add days)
20472 "DATE_ADD"
20473 if f.args.len() == 2
20474 && matches!(
20475 source,
20476 DialectType::Hive
20477 | DialectType::Spark
20478 | DialectType::Databricks
20479 | DialectType::Generic
20480 ) =>
20481 {
20482 let mut args = f.args;
20483 let date = args.remove(0);
20484 let days = args.remove(0);
20485 match target {
20486 DialectType::Hive | DialectType::Spark => {
20487 // Keep as DATE_ADD(date, days) for Hive/Spark
20488 Ok(Expression::Function(Box::new(Function::new(
20489 "DATE_ADD".to_string(),
20490 vec![date, days],
20491 ))))
20492 }
20493 DialectType::Databricks => Ok(Expression::Function(Box::new(
20494 Function::new("DATE_ADD".to_string(), vec![date, days]),
20495 ))),
20496 DialectType::DuckDB => {
20497 // DuckDB: CAST(date AS DATE) + INTERVAL days DAY
20498 let cast_date = Self::ensure_cast_date(date);
20499 // Wrap complex expressions (like Mul from DATE_SUB negation) in Paren
20500 let interval_val = if matches!(
20501 days,
20502 Expression::Mul(_)
20503 | Expression::Sub(_)
20504 | Expression::Add(_)
20505 ) {
20506 Expression::Paren(Box::new(crate::expressions::Paren {
20507 this: days,
20508 trailing_comments: vec![],
20509 }))
20510 } else {
20511 days
20512 };
20513 let interval = Expression::Interval(Box::new(
20514 crate::expressions::Interval {
20515 this: Some(interval_val),
20516 unit: Some(
20517 crate::expressions::IntervalUnitSpec::Simple {
20518 unit: crate::expressions::IntervalUnit::Day,
20519 use_plural: false,
20520 },
20521 ),
20522 },
20523 ));
20524 Ok(Expression::Add(Box::new(
20525 crate::expressions::BinaryOp::new(cast_date, interval),
20526 )))
20527 }
20528 DialectType::Snowflake => {
20529 // For Hive source with string literal date, use CAST(CAST(date AS TIMESTAMP) AS DATE)
20530 let cast_date = if matches!(
20531 source,
20532 DialectType::Hive
20533 | DialectType::Spark
20534 | DialectType::Databricks
20535 ) {
20536 if matches!(
20537 date,
20538 Expression::Literal(ref lit) if matches!(lit.as_ref(), Literal::String(_))
20539 ) {
20540 Self::double_cast_timestamp_date(date)
20541 } else {
20542 date
20543 }
20544 } else {
20545 date
20546 };
20547 Ok(Expression::Function(Box::new(Function::new(
20548 "DATEADD".to_string(),
20549 vec![
20550 Expression::Identifier(Identifier::new("DAY")),
20551 days,
20552 cast_date,
20553 ],
20554 ))))
20555 }
20556 DialectType::Redshift => {
20557 Ok(Expression::Function(Box::new(Function::new(
20558 "DATEADD".to_string(),
20559 vec![
20560 Expression::Identifier(Identifier::new("DAY")),
20561 days,
20562 date,
20563 ],
20564 ))))
20565 }
20566 DialectType::TSQL | DialectType::Fabric => {
20567 // For Hive source with string literal date, use CAST(CAST(date AS DATETIME2) AS DATE)
20568 // But Databricks DATE_ADD doesn't need this wrapping for TSQL
20569 let cast_date = if matches!(
20570 source,
20571 DialectType::Hive
20572 | DialectType::Spark
20573 | DialectType::Databricks
20574 ) {
20575 if matches!(
20576 date,
20577 Expression::Literal(ref lit) if matches!(lit.as_ref(), Literal::String(_))
20578 ) {
20579 Self::double_cast_datetime2_date(date)
20580 } else {
20581 date
20582 }
20583 } else {
20584 date
20585 };
20586 Ok(Expression::Function(Box::new(Function::new(
20587 "DATEADD".to_string(),
20588 vec![
20589 Expression::Identifier(Identifier::new("DAY")),
20590 days,
20591 cast_date,
20592 ],
20593 ))))
20594 }
20595 DialectType::Presto
20596 | DialectType::Trino
20597 | DialectType::Athena => {
20598 // For Hive source with string literal date, use CAST(CAST(date AS TIMESTAMP) AS DATE)
20599 let cast_date = if matches!(
20600 source,
20601 DialectType::Hive
20602 | DialectType::Spark
20603 | DialectType::Databricks
20604 ) {
20605 if matches!(
20606 date,
20607 Expression::Literal(ref lit) if matches!(lit.as_ref(), Literal::String(_))
20608 ) {
20609 Self::double_cast_timestamp_date(date)
20610 } else {
20611 date
20612 }
20613 } else {
20614 date
20615 };
20616 Ok(Expression::Function(Box::new(Function::new(
20617 "DATE_ADD".to_string(),
20618 vec![Expression::string("DAY"), days, cast_date],
20619 ))))
20620 }
20621 DialectType::BigQuery => {
20622 // For Hive/Spark source, wrap date in CAST(CAST(date AS DATETIME) AS DATE)
20623 let cast_date = if matches!(
20624 source,
20625 DialectType::Hive
20626 | DialectType::Spark
20627 | DialectType::Databricks
20628 ) {
20629 Self::double_cast_datetime_date(date)
20630 } else {
20631 date
20632 };
20633 // Wrap complex expressions in Paren for interval
20634 let interval_val = if matches!(
20635 days,
20636 Expression::Mul(_)
20637 | Expression::Sub(_)
20638 | Expression::Add(_)
20639 ) {
20640 Expression::Paren(Box::new(crate::expressions::Paren {
20641 this: days,
20642 trailing_comments: vec![],
20643 }))
20644 } else {
20645 days
20646 };
20647 let interval = Expression::Interval(Box::new(
20648 crate::expressions::Interval {
20649 this: Some(interval_val),
20650 unit: Some(
20651 crate::expressions::IntervalUnitSpec::Simple {
20652 unit: crate::expressions::IntervalUnit::Day,
20653 use_plural: false,
20654 },
20655 ),
20656 },
20657 ));
20658 Ok(Expression::Function(Box::new(Function::new(
20659 "DATE_ADD".to_string(),
20660 vec![cast_date, interval],
20661 ))))
20662 }
20663 DialectType::MySQL => {
20664 let iu = crate::expressions::IntervalUnit::Day;
20665 Ok(Expression::DateAdd(Box::new(
20666 crate::expressions::DateAddFunc {
20667 this: date,
20668 interval: days,
20669 unit: iu,
20670 },
20671 )))
20672 }
20673 DialectType::PostgreSQL => {
20674 let interval = Expression::Interval(Box::new(
20675 crate::expressions::Interval {
20676 this: Some(Expression::string(&format!(
20677 "{} DAY",
20678 Self::expr_to_string_static(&days)
20679 ))),
20680 unit: None,
20681 },
20682 ));
20683 Ok(Expression::Add(Box::new(
20684 crate::expressions::BinaryOp::new(date, interval),
20685 )))
20686 }
20687 DialectType::Doris
20688 | DialectType::StarRocks
20689 | DialectType::Drill => {
20690 // DATE_ADD(date, INTERVAL days DAY)
20691 let interval = Expression::Interval(Box::new(
20692 crate::expressions::Interval {
20693 this: Some(days),
20694 unit: Some(
20695 crate::expressions::IntervalUnitSpec::Simple {
20696 unit: crate::expressions::IntervalUnit::Day,
20697 use_plural: false,
20698 },
20699 ),
20700 },
20701 ));
20702 Ok(Expression::Function(Box::new(Function::new(
20703 "DATE_ADD".to_string(),
20704 vec![date, interval],
20705 ))))
20706 }
20707 _ => Ok(Expression::Function(Box::new(Function::new(
20708 "DATE_ADD".to_string(),
20709 vec![date, days],
20710 )))),
20711 }
20712 }
20713 // DATE_ADD(date, INTERVAL val UNIT) - MySQL 2-arg form with INTERVAL as 2nd arg
20714 "DATE_ADD"
20715 if f.args.len() == 2
20716 && matches!(
20717 source,
20718 DialectType::MySQL | DialectType::SingleStore
20719 )
20720 && matches!(&f.args[1], Expression::Interval(_)) =>
20721 {
20722 let mut args = f.args;
20723 let date = args.remove(0);
20724 let interval_expr = args.remove(0);
20725 let (val, unit) = Self::extract_interval_parts(&interval_expr)
20726 .unwrap_or_else(|| {
20727 (
20728 interval_expr.clone(),
20729 crate::expressions::IntervalUnit::Day,
20730 )
20731 });
20732 let unit_str = Self::interval_unit_to_string(&unit);
20733 let is_literal = matches!(&val,
20734 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_) | Literal::String(_))
20735 );
20736
20737 match target {
20738 DialectType::MySQL | DialectType::SingleStore => {
20739 // Keep as DATE_ADD(date, INTERVAL val UNIT)
20740 Ok(Expression::Function(Box::new(Function::new(
20741 "DATE_ADD".to_string(),
20742 vec![date, interval_expr],
20743 ))))
20744 }
20745 DialectType::PostgreSQL => {
20746 if is_literal {
20747 // Literal: date + INTERVAL 'val UNIT'
20748 let interval = Expression::Interval(Box::new(
20749 crate::expressions::Interval {
20750 this: Some(Expression::Literal(Box::new(
20751 Literal::String(format!(
20752 "{} {}",
20753 Self::expr_to_string(&val),
20754 unit_str
20755 )),
20756 ))),
20757 unit: None,
20758 },
20759 ));
20760 Ok(Expression::Add(Box::new(
20761 crate::expressions::BinaryOp::new(date, interval),
20762 )))
20763 } else {
20764 // Non-literal (column ref): date + INTERVAL '1 UNIT' * val
20765 let interval_one = Expression::Interval(Box::new(
20766 crate::expressions::Interval {
20767 this: Some(Expression::Literal(Box::new(
20768 Literal::String(format!("1 {}", unit_str)),
20769 ))),
20770 unit: None,
20771 },
20772 ));
20773 let mul = Expression::Mul(Box::new(
20774 crate::expressions::BinaryOp::new(
20775 interval_one,
20776 val,
20777 ),
20778 ));
20779 Ok(Expression::Add(Box::new(
20780 crate::expressions::BinaryOp::new(date, mul),
20781 )))
20782 }
20783 }
20784 _ => {
20785 // Default: keep as DATE_ADD(date, interval)
20786 Ok(Expression::Function(Box::new(Function::new(
20787 "DATE_ADD".to_string(),
20788 vec![date, interval_expr],
20789 ))))
20790 }
20791 }
20792 }
20793 // DATE_SUB(date, days) - 2-arg Hive/Spark form (subtract days)
20794 "DATE_SUB"
20795 if f.args.len() == 2
20796 && matches!(
20797 source,
20798 DialectType::Hive
20799 | DialectType::Spark
20800 | DialectType::Databricks
20801 ) =>
20802 {
20803 let mut args = f.args;
20804 let date = args.remove(0);
20805 let days = args.remove(0);
20806 // Helper to create days * -1
20807 let make_neg_days = |d: Expression| -> Expression {
20808 Expression::Mul(Box::new(crate::expressions::BinaryOp::new(
20809 d,
20810 Expression::Literal(Box::new(Literal::Number(
20811 "-1".to_string(),
20812 ))),
20813 )))
20814 };
20815 let is_string_literal = matches!(date, Expression::Literal(ref lit) if matches!(lit.as_ref(), Literal::String(_)));
20816 match target {
20817 DialectType::Hive
20818 | DialectType::Spark
20819 | DialectType::Databricks => {
20820 // Keep as DATE_SUB(date, days) for Hive/Spark
20821 Ok(Expression::Function(Box::new(Function::new(
20822 "DATE_SUB".to_string(),
20823 vec![date, days],
20824 ))))
20825 }
20826 DialectType::DuckDB => {
20827 let cast_date = Self::ensure_cast_date(date);
20828 let neg = make_neg_days(days);
20829 let interval = Expression::Interval(Box::new(
20830 crate::expressions::Interval {
20831 this: Some(Expression::Paren(Box::new(
20832 crate::expressions::Paren {
20833 this: neg,
20834 trailing_comments: vec![],
20835 },
20836 ))),
20837 unit: Some(
20838 crate::expressions::IntervalUnitSpec::Simple {
20839 unit: crate::expressions::IntervalUnit::Day,
20840 use_plural: false,
20841 },
20842 ),
20843 },
20844 ));
20845 Ok(Expression::Add(Box::new(
20846 crate::expressions::BinaryOp::new(cast_date, interval),
20847 )))
20848 }
20849 DialectType::Snowflake => {
20850 let cast_date = if is_string_literal {
20851 Self::double_cast_timestamp_date(date)
20852 } else {
20853 date
20854 };
20855 let neg = make_neg_days(days);
20856 Ok(Expression::Function(Box::new(Function::new(
20857 "DATEADD".to_string(),
20858 vec![
20859 Expression::Identifier(Identifier::new("DAY")),
20860 neg,
20861 cast_date,
20862 ],
20863 ))))
20864 }
20865 DialectType::Redshift => {
20866 let neg = make_neg_days(days);
20867 Ok(Expression::Function(Box::new(Function::new(
20868 "DATEADD".to_string(),
20869 vec![
20870 Expression::Identifier(Identifier::new("DAY")),
20871 neg,
20872 date,
20873 ],
20874 ))))
20875 }
20876 DialectType::TSQL | DialectType::Fabric => {
20877 let cast_date = if is_string_literal {
20878 Self::double_cast_datetime2_date(date)
20879 } else {
20880 date
20881 };
20882 let neg = make_neg_days(days);
20883 Ok(Expression::Function(Box::new(Function::new(
20884 "DATEADD".to_string(),
20885 vec![
20886 Expression::Identifier(Identifier::new("DAY")),
20887 neg,
20888 cast_date,
20889 ],
20890 ))))
20891 }
20892 DialectType::Presto
20893 | DialectType::Trino
20894 | DialectType::Athena => {
20895 let cast_date = if is_string_literal {
20896 Self::double_cast_timestamp_date(date)
20897 } else {
20898 date
20899 };
20900 let neg = make_neg_days(days);
20901 Ok(Expression::Function(Box::new(Function::new(
20902 "DATE_ADD".to_string(),
20903 vec![Expression::string("DAY"), neg, cast_date],
20904 ))))
20905 }
20906 DialectType::BigQuery => {
20907 let cast_date = if is_string_literal {
20908 Self::double_cast_datetime_date(date)
20909 } else {
20910 date
20911 };
20912 let neg = make_neg_days(days);
20913 let interval = Expression::Interval(Box::new(
20914 crate::expressions::Interval {
20915 this: Some(Expression::Paren(Box::new(
20916 crate::expressions::Paren {
20917 this: neg,
20918 trailing_comments: vec![],
20919 },
20920 ))),
20921 unit: Some(
20922 crate::expressions::IntervalUnitSpec::Simple {
20923 unit: crate::expressions::IntervalUnit::Day,
20924 use_plural: false,
20925 },
20926 ),
20927 },
20928 ));
20929 Ok(Expression::Function(Box::new(Function::new(
20930 "DATE_ADD".to_string(),
20931 vec![cast_date, interval],
20932 ))))
20933 }
20934 _ => Ok(Expression::Function(Box::new(Function::new(
20935 "DATE_SUB".to_string(),
20936 vec![date, days],
20937 )))),
20938 }
20939 }
20940 // ADD_MONTHS(date, val) -> target-specific
20941 "ADD_MONTHS" if f.args.len() == 2 => {
20942 let mut args = f.args;
20943 let date = args.remove(0);
20944 let val = args.remove(0);
20945 match target {
20946 DialectType::TSQL => {
20947 let cast_date = Self::ensure_cast_datetime2(date);
20948 Ok(Expression::Function(Box::new(Function::new(
20949 "DATEADD".to_string(),
20950 vec![
20951 Expression::Identifier(Identifier::new("MONTH")),
20952 val,
20953 cast_date,
20954 ],
20955 ))))
20956 }
20957 DialectType::DuckDB => {
20958 let interval = Expression::Interval(Box::new(
20959 crate::expressions::Interval {
20960 this: Some(val),
20961 unit: Some(
20962 crate::expressions::IntervalUnitSpec::Simple {
20963 unit:
20964 crate::expressions::IntervalUnit::Month,
20965 use_plural: false,
20966 },
20967 ),
20968 },
20969 ));
20970 Ok(Expression::Add(Box::new(
20971 crate::expressions::BinaryOp::new(date, interval),
20972 )))
20973 }
20974 DialectType::Snowflake => {
20975 // Keep ADD_MONTHS when source is Snowflake
20976 if matches!(source, DialectType::Snowflake) {
20977 Ok(Expression::Function(Box::new(Function::new(
20978 "ADD_MONTHS".to_string(),
20979 vec![date, val],
20980 ))))
20981 } else {
20982 Ok(Expression::Function(Box::new(Function::new(
20983 "DATEADD".to_string(),
20984 vec![
20985 Expression::Identifier(Identifier::new(
20986 "MONTH",
20987 )),
20988 val,
20989 date,
20990 ],
20991 ))))
20992 }
20993 }
20994 DialectType::Redshift => {
20995 Ok(Expression::Function(Box::new(Function::new(
20996 "DATEADD".to_string(),
20997 vec![
20998 Expression::Identifier(Identifier::new("MONTH")),
20999 val,
21000 date,
21001 ],
21002 ))))
21003 }
21004 DialectType::Presto
21005 | DialectType::Trino
21006 | DialectType::Athena => {
21007 Ok(Expression::Function(Box::new(Function::new(
21008 "DATE_ADD".to_string(),
21009 vec![Expression::string("MONTH"), val, date],
21010 ))))
21011 }
21012 DialectType::BigQuery => {
21013 let interval = Expression::Interval(Box::new(
21014 crate::expressions::Interval {
21015 this: Some(val),
21016 unit: Some(
21017 crate::expressions::IntervalUnitSpec::Simple {
21018 unit:
21019 crate::expressions::IntervalUnit::Month,
21020 use_plural: false,
21021 },
21022 ),
21023 },
21024 ));
21025 Ok(Expression::Function(Box::new(Function::new(
21026 "DATE_ADD".to_string(),
21027 vec![date, interval],
21028 ))))
21029 }
21030 _ => Ok(Expression::Function(Box::new(Function::new(
21031 "ADD_MONTHS".to_string(),
21032 vec![date, val],
21033 )))),
21034 }
21035 }
21036 // DATETRUNC(unit, date) - TSQL form -> DATE_TRUNC for other targets
21037 "DATETRUNC" if f.args.len() == 2 => {
21038 let mut args = f.args;
21039 let arg0 = args.remove(0);
21040 let arg1 = args.remove(0);
21041 let unit_str = Self::get_unit_str_static(&arg0);
21042 match target {
21043 DialectType::TSQL | DialectType::Fabric => {
21044 // Keep as DATETRUNC for TSQL - the target handler will uppercase the unit
21045 Ok(Expression::Function(Box::new(Function::new(
21046 "DATETRUNC".to_string(),
21047 vec![
21048 Expression::Identifier(Identifier::new(&unit_str)),
21049 arg1,
21050 ],
21051 ))))
21052 }
21053 DialectType::DuckDB => {
21054 // DuckDB: DATE_TRUNC('UNIT', expr) with CAST for string literals
21055 let date = Self::ensure_cast_timestamp(arg1);
21056 Ok(Expression::Function(Box::new(Function::new(
21057 "DATE_TRUNC".to_string(),
21058 vec![Expression::string(&unit_str), date],
21059 ))))
21060 }
21061 DialectType::ClickHouse => {
21062 // ClickHouse: dateTrunc('UNIT', expr)
21063 Ok(Expression::Function(Box::new(Function::new(
21064 "dateTrunc".to_string(),
21065 vec![Expression::string(&unit_str), arg1],
21066 ))))
21067 }
21068 _ => {
21069 // Standard: DATE_TRUNC('UNIT', expr)
21070 let unit = Expression::string(&unit_str);
21071 Ok(Expression::Function(Box::new(Function::new(
21072 "DATE_TRUNC".to_string(),
21073 vec![unit, arg1],
21074 ))))
21075 }
21076 }
21077 }
21078 // GETDATE() -> CURRENT_TIMESTAMP for non-TSQL targets
21079 "GETDATE" if f.args.is_empty() => match target {
21080 DialectType::TSQL => Ok(Expression::Function(f)),
21081 DialectType::Redshift => Ok(Expression::Function(Box::new(
21082 Function::new("GETDATE".to_string(), vec![]),
21083 ))),
21084 _ => Ok(Expression::CurrentTimestamp(
21085 crate::expressions::CurrentTimestamp {
21086 precision: None,
21087 sysdate: false,
21088 },
21089 )),
21090 },
21091 // TO_HEX(x) / HEX(x) -> target-specific hex function
21092 "TO_HEX" | "HEX" if f.args.len() == 1 => {
21093 let name = match target {
21094 DialectType::Presto | DialectType::Trino => "TO_HEX",
21095 DialectType::Spark
21096 | DialectType::Databricks
21097 | DialectType::Hive => "HEX",
21098 DialectType::DuckDB
21099 | DialectType::PostgreSQL
21100 | DialectType::Redshift => "TO_HEX",
21101 _ => &f.name,
21102 };
21103 Ok(Expression::Function(Box::new(Function::new(
21104 name.to_string(),
21105 f.args,
21106 ))))
21107 }
21108 // FROM_HEX(x) / UNHEX(x) -> target-specific hex decode function
21109 "FROM_HEX" | "UNHEX" if f.args.len() == 1 => {
21110 match target {
21111 DialectType::BigQuery => {
21112 // BigQuery: UNHEX(x) -> FROM_HEX(x)
21113 // Special case: UNHEX(MD5(x)) -> FROM_HEX(TO_HEX(MD5(x)))
21114 // because BigQuery MD5 returns BYTES, not hex string
21115 let arg = &f.args[0];
21116 let wrapped_arg = match arg {
21117 Expression::Function(inner_f)
21118 if inner_f.name.eq_ignore_ascii_case("MD5")
21119 || inner_f
21120 .name
21121 .eq_ignore_ascii_case("SHA1")
21122 || inner_f
21123 .name
21124 .eq_ignore_ascii_case("SHA256")
21125 || inner_f
21126 .name
21127 .eq_ignore_ascii_case("SHA512") =>
21128 {
21129 // Wrap hash function in TO_HEX for BigQuery
21130 Expression::Function(Box::new(Function::new(
21131 "TO_HEX".to_string(),
21132 vec![arg.clone()],
21133 )))
21134 }
21135 _ => f.args.into_iter().next().unwrap(),
21136 };
21137 Ok(Expression::Function(Box::new(Function::new(
21138 "FROM_HEX".to_string(),
21139 vec![wrapped_arg],
21140 ))))
21141 }
21142 _ => {
21143 let name = match target {
21144 DialectType::Presto | DialectType::Trino => "FROM_HEX",
21145 DialectType::Spark
21146 | DialectType::Databricks
21147 | DialectType::Hive => "UNHEX",
21148 _ => &f.name,
21149 };
21150 Ok(Expression::Function(Box::new(Function::new(
21151 name.to_string(),
21152 f.args,
21153 ))))
21154 }
21155 }
21156 }
21157 // TO_UTF8(x) -> ENCODE(x, 'utf-8') for Spark
21158 "TO_UTF8" if f.args.len() == 1 => match target {
21159 DialectType::Spark | DialectType::Databricks => {
21160 let mut args = f.args;
21161 args.push(Expression::string("utf-8"));
21162 Ok(Expression::Function(Box::new(Function::new(
21163 "ENCODE".to_string(),
21164 args,
21165 ))))
21166 }
21167 _ => Ok(Expression::Function(f)),
21168 },
21169 // FROM_UTF8(x) -> DECODE(x, 'utf-8') for Spark
21170 "FROM_UTF8" if f.args.len() == 1 => match target {
21171 DialectType::Spark | DialectType::Databricks => {
21172 let mut args = f.args;
21173 args.push(Expression::string("utf-8"));
21174 Ok(Expression::Function(Box::new(Function::new(
21175 "DECODE".to_string(),
21176 args,
21177 ))))
21178 }
21179 _ => Ok(Expression::Function(f)),
21180 },
21181 // STARTS_WITH(x, y) / STARTSWITH(x, y) -> target-specific
21182 "STARTS_WITH" | "STARTSWITH" if f.args.len() == 2 => {
21183 let name = match target {
21184 DialectType::Spark | DialectType::Databricks => "STARTSWITH",
21185 DialectType::Presto | DialectType::Trino => "STARTS_WITH",
21186 DialectType::PostgreSQL | DialectType::Redshift => {
21187 "STARTS_WITH"
21188 }
21189 _ => &f.name,
21190 };
21191 Ok(Expression::Function(Box::new(Function::new(
21192 name.to_string(),
21193 f.args,
21194 ))))
21195 }
21196 // APPROX_COUNT_DISTINCT(x) <-> APPROX_DISTINCT(x)
21197 "APPROX_COUNT_DISTINCT" if f.args.len() >= 1 => {
21198 let name = match target {
21199 DialectType::Presto
21200 | DialectType::Trino
21201 | DialectType::Athena => "APPROX_DISTINCT",
21202 _ => "APPROX_COUNT_DISTINCT",
21203 };
21204 Ok(Expression::Function(Box::new(Function::new(
21205 name.to_string(),
21206 f.args,
21207 ))))
21208 }
21209 // JSON_EXTRACT -> GET_JSON_OBJECT for Spark/Hive
21210 "JSON_EXTRACT"
21211 if f.args.len() == 2
21212 && !matches!(source, DialectType::BigQuery)
21213 && matches!(
21214 target,
21215 DialectType::Spark
21216 | DialectType::Databricks
21217 | DialectType::Hive
21218 ) =>
21219 {
21220 Ok(Expression::Function(Box::new(Function::new(
21221 "GET_JSON_OBJECT".to_string(),
21222 f.args,
21223 ))))
21224 }
21225 // JSON_EXTRACT(x, path) -> x -> path for SQLite (arrow syntax)
21226 "JSON_EXTRACT"
21227 if f.args.len() == 2 && matches!(target, DialectType::SQLite) =>
21228 {
21229 let mut args = f.args;
21230 let path = args.remove(1);
21231 let this = args.remove(0);
21232 Ok(Expression::JsonExtract(Box::new(
21233 crate::expressions::JsonExtractFunc {
21234 this,
21235 path,
21236 returning: None,
21237 arrow_syntax: true,
21238 hash_arrow_syntax: false,
21239 wrapper_option: None,
21240 quotes_option: None,
21241 on_scalar_string: false,
21242 on_error: None,
21243 },
21244 )))
21245 }
21246 // JSON_FORMAT(x) -> TO_JSON(x) for Spark, TO_JSON_STRING for BigQuery, CAST(TO_JSON(x) AS TEXT) for DuckDB
21247 "JSON_FORMAT" if f.args.len() == 1 => {
21248 match target {
21249 DialectType::Spark | DialectType::Databricks => {
21250 // Presto JSON_FORMAT(JSON '...') needs Spark's string-unquoting flow:
21251 // REGEXP_EXTRACT(TO_JSON(FROM_JSON('[...]', SCHEMA_OF_JSON('[...]'))), '^.(.*).$', 1)
21252 if matches!(
21253 source,
21254 DialectType::Presto
21255 | DialectType::Trino
21256 | DialectType::Athena
21257 ) {
21258 if let Some(Expression::ParseJson(pj)) = f.args.first()
21259 {
21260 if let Expression::Literal(lit) = &pj.this {
21261 if let Literal::String(s) = lit.as_ref() {
21262 let wrapped =
21263 Expression::Literal(Box::new(
21264 Literal::String(format!("[{}]", s)),
21265 ));
21266 let schema_of_json = Expression::Function(
21267 Box::new(Function::new(
21268 "SCHEMA_OF_JSON".to_string(),
21269 vec![wrapped.clone()],
21270 )),
21271 );
21272 let from_json = Expression::Function(
21273 Box::new(Function::new(
21274 "FROM_JSON".to_string(),
21275 vec![wrapped, schema_of_json],
21276 )),
21277 );
21278 let to_json = Expression::Function(
21279 Box::new(Function::new(
21280 "TO_JSON".to_string(),
21281 vec![from_json],
21282 )),
21283 );
21284 return Ok(Expression::Function(Box::new(
21285 Function::new(
21286 "REGEXP_EXTRACT".to_string(),
21287 vec![
21288 to_json,
21289 Expression::Literal(Box::new(
21290 Literal::String(
21291 "^.(.*).$".to_string(),
21292 ),
21293 )),
21294 Expression::Literal(Box::new(
21295 Literal::Number(
21296 "1".to_string(),
21297 ),
21298 )),
21299 ],
21300 ),
21301 )));
21302 }
21303 }
21304 }
21305 }
21306
21307 // Strip inner CAST(... AS JSON) or TO_JSON() if present
21308 // The CastToJsonForSpark may have already converted CAST(x AS JSON) to TO_JSON(x)
21309 let mut args = f.args;
21310 if let Some(Expression::Cast(ref c)) = args.first() {
21311 if matches!(&c.to, DataType::Json | DataType::JsonB) {
21312 args = vec![c.this.clone()];
21313 }
21314 } else if let Some(Expression::Function(ref inner_f)) =
21315 args.first()
21316 {
21317 if inner_f.name.eq_ignore_ascii_case("TO_JSON")
21318 && inner_f.args.len() == 1
21319 {
21320 // Already TO_JSON(x) from CastToJsonForSpark, just use the inner arg
21321 args = inner_f.args.clone();
21322 }
21323 }
21324 Ok(Expression::Function(Box::new(Function::new(
21325 "TO_JSON".to_string(),
21326 args,
21327 ))))
21328 }
21329 DialectType::BigQuery => Ok(Expression::Function(Box::new(
21330 Function::new("TO_JSON_STRING".to_string(), f.args),
21331 ))),
21332 DialectType::DuckDB => {
21333 // CAST(TO_JSON(x) AS TEXT)
21334 let to_json = Expression::Function(Box::new(
21335 Function::new("TO_JSON".to_string(), f.args),
21336 ));
21337 Ok(Expression::Cast(Box::new(Cast {
21338 this: to_json,
21339 to: DataType::Text,
21340 trailing_comments: Vec::new(),
21341 double_colon_syntax: false,
21342 format: None,
21343 default: None,
21344 inferred_type: None,
21345 })))
21346 }
21347 _ => Ok(Expression::Function(f)),
21348 }
21349 }
21350 // SYSDATE -> CURRENT_TIMESTAMP for non-Oracle/Redshift/Snowflake targets
21351 "SYSDATE" if f.args.is_empty() => {
21352 match target {
21353 DialectType::Oracle | DialectType::Redshift => {
21354 Ok(Expression::Function(f))
21355 }
21356 DialectType::Snowflake => {
21357 // Snowflake uses SYSDATE() with parens
21358 let mut f = *f;
21359 f.no_parens = false;
21360 Ok(Expression::Function(Box::new(f)))
21361 }
21362 DialectType::DuckDB => {
21363 // DuckDB: SYSDATE() -> CURRENT_TIMESTAMP AT TIME ZONE 'UTC'
21364 Ok(Expression::AtTimeZone(Box::new(
21365 crate::expressions::AtTimeZone {
21366 this: Expression::CurrentTimestamp(
21367 crate::expressions::CurrentTimestamp {
21368 precision: None,
21369 sysdate: false,
21370 },
21371 ),
21372 zone: Expression::Literal(Box::new(
21373 Literal::String("UTC".to_string()),
21374 )),
21375 },
21376 )))
21377 }
21378 _ => Ok(Expression::CurrentTimestamp(
21379 crate::expressions::CurrentTimestamp {
21380 precision: None,
21381 sysdate: true,
21382 },
21383 )),
21384 }
21385 }
21386 // LOGICAL_OR(x) -> BOOL_OR(x)
21387 "LOGICAL_OR" if f.args.len() == 1 => {
21388 let name = match target {
21389 DialectType::Spark | DialectType::Databricks => "BOOL_OR",
21390 _ => &f.name,
21391 };
21392 Ok(Expression::Function(Box::new(Function::new(
21393 name.to_string(),
21394 f.args,
21395 ))))
21396 }
21397 // LOGICAL_AND(x) -> BOOL_AND(x)
21398 "LOGICAL_AND" if f.args.len() == 1 => {
21399 let name = match target {
21400 DialectType::Spark | DialectType::Databricks => "BOOL_AND",
21401 _ => &f.name,
21402 };
21403 Ok(Expression::Function(Box::new(Function::new(
21404 name.to_string(),
21405 f.args,
21406 ))))
21407 }
21408 // MONTHS_ADD(d, n) -> ADD_MONTHS(d, n) for Oracle
21409 "MONTHS_ADD" if f.args.len() == 2 => match target {
21410 DialectType::Oracle => Ok(Expression::Function(Box::new(
21411 Function::new("ADD_MONTHS".to_string(), f.args),
21412 ))),
21413 _ => Ok(Expression::Function(f)),
21414 },
21415 // ARRAY_JOIN(arr, sep[, null_replacement]) -> target-specific
21416 "ARRAY_JOIN" if f.args.len() >= 2 => {
21417 match target {
21418 DialectType::Spark | DialectType::Databricks => {
21419 // Keep as ARRAY_JOIN for Spark (it supports null_replacement)
21420 Ok(Expression::Function(f))
21421 }
21422 DialectType::Hive => {
21423 // ARRAY_JOIN(arr, sep[, null_rep]) -> CONCAT_WS(sep, arr) (drop null_replacement)
21424 let mut args = f.args;
21425 let arr = args.remove(0);
21426 let sep = args.remove(0);
21427 // Drop any remaining args (null_replacement)
21428 Ok(Expression::Function(Box::new(Function::new(
21429 "CONCAT_WS".to_string(),
21430 vec![sep, arr],
21431 ))))
21432 }
21433 DialectType::Presto | DialectType::Trino => {
21434 Ok(Expression::Function(f))
21435 }
21436 _ => Ok(Expression::Function(f)),
21437 }
21438 }
21439 // LOCATE(substr, str, pos) 3-arg -> target-specific
21440 // For Presto/DuckDB: STRPOS doesn't support 3-arg, need complex expansion
21441 "LOCATE"
21442 if f.args.len() == 3
21443 && matches!(
21444 target,
21445 DialectType::Presto
21446 | DialectType::Trino
21447 | DialectType::Athena
21448 | DialectType::DuckDB
21449 ) =>
21450 {
21451 let mut args = f.args;
21452 let substr = args.remove(0);
21453 let string = args.remove(0);
21454 let pos = args.remove(0);
21455 // STRPOS(SUBSTRING(string, pos), substr)
21456 let substring_call = Expression::Function(Box::new(Function::new(
21457 "SUBSTRING".to_string(),
21458 vec![string.clone(), pos.clone()],
21459 )));
21460 let strpos_call = Expression::Function(Box::new(Function::new(
21461 "STRPOS".to_string(),
21462 vec![substring_call, substr.clone()],
21463 )));
21464 // STRPOS(...) + pos - 1
21465 let pos_adjusted =
21466 Expression::Sub(Box::new(crate::expressions::BinaryOp::new(
21467 Expression::Add(Box::new(
21468 crate::expressions::BinaryOp::new(
21469 strpos_call.clone(),
21470 pos.clone(),
21471 ),
21472 )),
21473 Expression::number(1),
21474 )));
21475 // STRPOS(...) = 0
21476 let is_zero =
21477 Expression::Eq(Box::new(crate::expressions::BinaryOp::new(
21478 strpos_call.clone(),
21479 Expression::number(0),
21480 )));
21481
21482 match target {
21483 DialectType::Presto
21484 | DialectType::Trino
21485 | DialectType::Athena => {
21486 // IF(STRPOS(...) = 0, 0, STRPOS(...) + pos - 1)
21487 Ok(Expression::Function(Box::new(Function::new(
21488 "IF".to_string(),
21489 vec![is_zero, Expression::number(0), pos_adjusted],
21490 ))))
21491 }
21492 DialectType::DuckDB => {
21493 // CASE WHEN STRPOS(...) = 0 THEN 0 ELSE STRPOS(...) + pos - 1 END
21494 Ok(Expression::Case(Box::new(crate::expressions::Case {
21495 operand: None,
21496 whens: vec![(is_zero, Expression::number(0))],
21497 else_: Some(pos_adjusted),
21498 comments: Vec::new(),
21499 inferred_type: None,
21500 })))
21501 }
21502 _ => Ok(Expression::Function(Box::new(Function::new(
21503 "LOCATE".to_string(),
21504 vec![substr, string, pos],
21505 )))),
21506 }
21507 }
21508 // STRPOS(haystack, needle, occurrence) 3-arg -> INSTR(haystack, needle, 1, occurrence)
21509 "STRPOS"
21510 if f.args.len() == 3
21511 && matches!(
21512 target,
21513 DialectType::BigQuery
21514 | DialectType::Oracle
21515 | DialectType::Teradata
21516 ) =>
21517 {
21518 let mut args = f.args;
21519 let haystack = args.remove(0);
21520 let needle = args.remove(0);
21521 let occurrence = args.remove(0);
21522 Ok(Expression::Function(Box::new(Function::new(
21523 "INSTR".to_string(),
21524 vec![haystack, needle, Expression::number(1), occurrence],
21525 ))))
21526 }
21527 // SCHEMA_NAME(id) -> target-specific
21528 "SCHEMA_NAME" if f.args.len() <= 1 => match target {
21529 DialectType::MySQL | DialectType::SingleStore => {
21530 Ok(Expression::Function(Box::new(Function::new(
21531 "SCHEMA".to_string(),
21532 vec![],
21533 ))))
21534 }
21535 DialectType::PostgreSQL => Ok(Expression::CurrentSchema(Box::new(
21536 crate::expressions::CurrentSchema { this: None },
21537 ))),
21538 DialectType::SQLite => Ok(Expression::string("main")),
21539 _ => Ok(Expression::Function(f)),
21540 },
21541 // STRTOL(str, base) -> FROM_BASE(str, base) for Trino/Presto
21542 "STRTOL" if f.args.len() == 2 => match target {
21543 DialectType::Presto | DialectType::Trino => {
21544 Ok(Expression::Function(Box::new(Function::new(
21545 "FROM_BASE".to_string(),
21546 f.args,
21547 ))))
21548 }
21549 _ => Ok(Expression::Function(f)),
21550 },
21551 // EDITDIST3(a, b) -> LEVENSHTEIN(a, b) for Spark
21552 "EDITDIST3" if f.args.len() == 2 => match target {
21553 DialectType::Spark | DialectType::Databricks => {
21554 Ok(Expression::Function(Box::new(Function::new(
21555 "LEVENSHTEIN".to_string(),
21556 f.args,
21557 ))))
21558 }
21559 _ => Ok(Expression::Function(f)),
21560 },
21561 // FORMAT(num, decimals) from MySQL -> DuckDB FORMAT('{:,.Xf}', num)
21562 "FORMAT"
21563 if f.args.len() == 2
21564 && matches!(
21565 source,
21566 DialectType::MySQL | DialectType::SingleStore
21567 )
21568 && matches!(target, DialectType::DuckDB) =>
21569 {
21570 let mut args = f.args;
21571 let num_expr = args.remove(0);
21572 let decimals_expr = args.remove(0);
21573 // Extract decimal count
21574 let dec_count = match &decimals_expr {
21575 Expression::Literal(lit)
21576 if matches!(lit.as_ref(), Literal::Number(_)) =>
21577 {
21578 let Literal::Number(n) = lit.as_ref() else {
21579 unreachable!()
21580 };
21581 n.clone()
21582 }
21583 _ => "0".to_string(),
21584 };
21585 let fmt_str = format!("{{:,.{}f}}", dec_count);
21586 Ok(Expression::Function(Box::new(Function::new(
21587 "FORMAT".to_string(),
21588 vec![Expression::string(&fmt_str), num_expr],
21589 ))))
21590 }
21591 // FORMAT(x, fmt) from TSQL -> DATE_FORMAT for Spark, or expand short codes
21592 "FORMAT"
21593 if f.args.len() == 2
21594 && matches!(
21595 source,
21596 DialectType::TSQL | DialectType::Fabric
21597 ) =>
21598 {
21599 let val_expr = f.args[0].clone();
21600 let fmt_expr = f.args[1].clone();
21601 // Expand unambiguous .NET single-char date format shortcodes to full patterns.
21602 // Only expand shortcodes that are NOT also valid numeric format specifiers.
21603 // Ambiguous: d, D, f, F, g, G (used for both dates and numbers)
21604 // Unambiguous date: m/M (Month day), t/T (Time), y/Y (Year month)
21605 let (expanded_fmt, is_shortcode) = match &fmt_expr {
21606 Expression::Literal(lit)
21607 if matches!(
21608 lit.as_ref(),
21609 crate::expressions::Literal::String(_)
21610 ) =>
21611 {
21612 let crate::expressions::Literal::String(s) = lit.as_ref()
21613 else {
21614 unreachable!()
21615 };
21616 match s.as_str() {
21617 "m" | "M" => (Expression::string("MMMM d"), true),
21618 "t" => (Expression::string("h:mm tt"), true),
21619 "T" => (Expression::string("h:mm:ss tt"), true),
21620 "y" | "Y" => (Expression::string("MMMM yyyy"), true),
21621 _ => (fmt_expr.clone(), false),
21622 }
21623 }
21624 _ => (fmt_expr.clone(), false),
21625 };
21626 // Check if the format looks like a date format
21627 let is_date_format = is_shortcode
21628 || match &expanded_fmt {
21629 Expression::Literal(lit)
21630 if matches!(
21631 lit.as_ref(),
21632 crate::expressions::Literal::String(_)
21633 ) =>
21634 {
21635 let crate::expressions::Literal::String(s) =
21636 lit.as_ref()
21637 else {
21638 unreachable!()
21639 };
21640 // Date formats typically contain yyyy, MM, dd, MMMM, HH, etc.
21641 s.contains("yyyy")
21642 || s.contains("YYYY")
21643 || s.contains("MM")
21644 || s.contains("dd")
21645 || s.contains("MMMM")
21646 || s.contains("HH")
21647 || s.contains("hh")
21648 || s.contains("ss")
21649 }
21650 _ => false,
21651 };
21652 match target {
21653 DialectType::Spark | DialectType::Databricks => {
21654 let func_name = if is_date_format {
21655 "DATE_FORMAT"
21656 } else {
21657 "FORMAT_NUMBER"
21658 };
21659 Ok(Expression::Function(Box::new(Function::new(
21660 func_name.to_string(),
21661 vec![val_expr, expanded_fmt],
21662 ))))
21663 }
21664 _ => {
21665 // For TSQL and other targets, expand shortcodes but keep FORMAT
21666 if is_shortcode {
21667 Ok(Expression::Function(Box::new(Function::new(
21668 "FORMAT".to_string(),
21669 vec![val_expr, expanded_fmt],
21670 ))))
21671 } else {
21672 Ok(Expression::Function(f))
21673 }
21674 }
21675 }
21676 }
21677 // FORMAT('%s', x) from Trino/Presto -> target-specific
21678 "FORMAT"
21679 if f.args.len() >= 2
21680 && matches!(
21681 source,
21682 DialectType::Trino
21683 | DialectType::Presto
21684 | DialectType::Athena
21685 ) =>
21686 {
21687 let fmt_expr = f.args[0].clone();
21688 let value_args: Vec<Expression> = f.args[1..].to_vec();
21689 match target {
21690 // DuckDB: replace %s with {} in format string
21691 DialectType::DuckDB => {
21692 let new_fmt = match &fmt_expr {
21693 Expression::Literal(lit)
21694 if matches!(lit.as_ref(), Literal::String(_)) =>
21695 {
21696 let Literal::String(s) = lit.as_ref() else {
21697 unreachable!()
21698 };
21699 Expression::Literal(Box::new(Literal::String(
21700 s.replace("%s", "{}"),
21701 )))
21702 }
21703 _ => fmt_expr,
21704 };
21705 let mut args = vec![new_fmt];
21706 args.extend(value_args);
21707 Ok(Expression::Function(Box::new(Function::new(
21708 "FORMAT".to_string(),
21709 args,
21710 ))))
21711 }
21712 // Snowflake: FORMAT('%s', x) -> TO_CHAR(x) when just %s
21713 DialectType::Snowflake => match &fmt_expr {
21714 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(s) if s == "%s" && value_args.len() == 1) =>
21715 {
21716 let Literal::String(_) = lit.as_ref() else {
21717 unreachable!()
21718 };
21719 Ok(Expression::Function(Box::new(Function::new(
21720 "TO_CHAR".to_string(),
21721 value_args,
21722 ))))
21723 }
21724 _ => Ok(Expression::Function(f)),
21725 },
21726 // Default: keep FORMAT as-is
21727 _ => Ok(Expression::Function(f)),
21728 }
21729 }
21730 // LIST_CONTAINS / LIST_HAS / ARRAY_CONTAINS -> target-specific
21731 "LIST_CONTAINS" | "LIST_HAS" | "ARRAY_CONTAINS"
21732 if f.args.len() == 2 =>
21733 {
21734 // When coming from Snowflake source: ARRAY_CONTAINS(value, array)
21735 // args[0]=value, args[1]=array. For DuckDB target, swap and add NULL-aware CASE.
21736 if matches!(target, DialectType::DuckDB)
21737 && matches!(source, DialectType::Snowflake)
21738 && f.name.eq_ignore_ascii_case("ARRAY_CONTAINS")
21739 {
21740 let value = f.args[0].clone();
21741 let array = f.args[1].clone();
21742
21743 // value IS NULL
21744 let value_is_null =
21745 Expression::IsNull(Box::new(crate::expressions::IsNull {
21746 this: value.clone(),
21747 not: false,
21748 postfix_form: false,
21749 }));
21750
21751 // ARRAY_LENGTH(array)
21752 let array_length =
21753 Expression::Function(Box::new(Function::new(
21754 "ARRAY_LENGTH".to_string(),
21755 vec![array.clone()],
21756 )));
21757 // LIST_COUNT(array)
21758 let list_count = Expression::Function(Box::new(Function::new(
21759 "LIST_COUNT".to_string(),
21760 vec![array.clone()],
21761 )));
21762 // ARRAY_LENGTH(array) <> LIST_COUNT(array)
21763 let neq =
21764 Expression::Neq(Box::new(crate::expressions::BinaryOp {
21765 left: array_length,
21766 right: list_count,
21767 left_comments: vec![],
21768 operator_comments: vec![],
21769 trailing_comments: vec![],
21770 inferred_type: None,
21771 }));
21772 // NULLIF(ARRAY_LENGTH(array) <> LIST_COUNT(array), FALSE)
21773 let nullif =
21774 Expression::Nullif(Box::new(crate::expressions::Nullif {
21775 this: Box::new(neq),
21776 expression: Box::new(Expression::Boolean(
21777 crate::expressions::BooleanLiteral { value: false },
21778 )),
21779 }));
21780
21781 // ARRAY_CONTAINS(array, value) - DuckDB syntax: array first, value second
21782 let array_contains =
21783 Expression::Function(Box::new(Function::new(
21784 "ARRAY_CONTAINS".to_string(),
21785 vec![array, value],
21786 )));
21787
21788 // CASE WHEN value IS NULL THEN NULLIF(...) ELSE ARRAY_CONTAINS(array, value) END
21789 return Ok(Expression::Case(Box::new(Case {
21790 operand: None,
21791 whens: vec![(value_is_null, nullif)],
21792 else_: Some(array_contains),
21793 comments: Vec::new(),
21794 inferred_type: None,
21795 })));
21796 }
21797 match target {
21798 DialectType::PostgreSQL | DialectType::Redshift => {
21799 // CASE WHEN needle IS NULL THEN NULL ELSE COALESCE(needle = ANY(arr), FALSE) END
21800 let arr = f.args[0].clone();
21801 let needle = f.args[1].clone();
21802 // Convert [] to ARRAY[] for PostgreSQL
21803 let pg_arr = match arr {
21804 Expression::Array(a) => Expression::ArrayFunc(
21805 Box::new(crate::expressions::ArrayConstructor {
21806 expressions: a.expressions,
21807 bracket_notation: false,
21808 use_list_keyword: false,
21809 }),
21810 ),
21811 _ => arr,
21812 };
21813 // needle = ANY(arr) using the Any quantified expression
21814 let any_expr = Expression::Any(Box::new(
21815 crate::expressions::QuantifiedExpr {
21816 this: needle.clone(),
21817 subquery: pg_arr,
21818 op: Some(crate::expressions::QuantifiedOp::Eq),
21819 },
21820 ));
21821 let coalesce = Expression::Coalesce(Box::new(
21822 crate::expressions::VarArgFunc {
21823 expressions: vec![
21824 any_expr,
21825 Expression::Boolean(
21826 crate::expressions::BooleanLiteral {
21827 value: false,
21828 },
21829 ),
21830 ],
21831 original_name: None,
21832 inferred_type: None,
21833 },
21834 ));
21835 let is_null_check = Expression::IsNull(Box::new(
21836 crate::expressions::IsNull {
21837 this: needle,
21838 not: false,
21839 postfix_form: false,
21840 },
21841 ));
21842 Ok(Expression::Case(Box::new(Case {
21843 operand: None,
21844 whens: vec![(
21845 is_null_check,
21846 Expression::Null(crate::expressions::Null),
21847 )],
21848 else_: Some(coalesce),
21849 comments: Vec::new(),
21850 inferred_type: None,
21851 })))
21852 }
21853 _ => Ok(Expression::Function(Box::new(Function::new(
21854 "ARRAY_CONTAINS".to_string(),
21855 f.args,
21856 )))),
21857 }
21858 }
21859 // LIST_HAS_ANY / ARRAY_HAS_ANY -> target-specific overlap operator
21860 "LIST_HAS_ANY" | "ARRAY_HAS_ANY" if f.args.len() == 2 => {
21861 match target {
21862 DialectType::PostgreSQL | DialectType::Redshift => {
21863 // arr1 && arr2 with ARRAY[] syntax
21864 let mut args = f.args;
21865 let arr1 = args.remove(0);
21866 let arr2 = args.remove(0);
21867 let pg_arr1 = match arr1 {
21868 Expression::Array(a) => Expression::ArrayFunc(
21869 Box::new(crate::expressions::ArrayConstructor {
21870 expressions: a.expressions,
21871 bracket_notation: false,
21872 use_list_keyword: false,
21873 }),
21874 ),
21875 _ => arr1,
21876 };
21877 let pg_arr2 = match arr2 {
21878 Expression::Array(a) => Expression::ArrayFunc(
21879 Box::new(crate::expressions::ArrayConstructor {
21880 expressions: a.expressions,
21881 bracket_notation: false,
21882 use_list_keyword: false,
21883 }),
21884 ),
21885 _ => arr2,
21886 };
21887 Ok(Expression::ArrayOverlaps(Box::new(BinaryOp::new(
21888 pg_arr1, pg_arr2,
21889 ))))
21890 }
21891 DialectType::DuckDB => {
21892 // DuckDB: arr1 && arr2 (native support)
21893 let mut args = f.args;
21894 let arr1 = args.remove(0);
21895 let arr2 = args.remove(0);
21896 Ok(Expression::ArrayOverlaps(Box::new(BinaryOp::new(
21897 arr1, arr2,
21898 ))))
21899 }
21900 _ => Ok(Expression::Function(Box::new(Function::new(
21901 "LIST_HAS_ANY".to_string(),
21902 f.args,
21903 )))),
21904 }
21905 }
21906 // APPROX_QUANTILE(x, q) -> target-specific
21907 "APPROX_QUANTILE" if f.args.len() == 2 => match target {
21908 DialectType::Snowflake => Ok(Expression::Function(Box::new(
21909 Function::new("APPROX_PERCENTILE".to_string(), f.args),
21910 ))),
21911 DialectType::DuckDB => Ok(Expression::Function(f)),
21912 _ => Ok(Expression::Function(f)),
21913 },
21914 // MAKE_DATE(y, m, d) -> DATE(y, m, d) for BigQuery
21915 "MAKE_DATE" if f.args.len() == 3 => match target {
21916 DialectType::BigQuery => Ok(Expression::Function(Box::new(
21917 Function::new("DATE".to_string(), f.args),
21918 ))),
21919 _ => Ok(Expression::Function(f)),
21920 },
21921 // RANGE(start, end[, step]) -> target-specific
21922 "RANGE"
21923 if f.args.len() >= 2 && !matches!(target, DialectType::DuckDB) =>
21924 {
21925 let start = f.args[0].clone();
21926 let end = f.args[1].clone();
21927 let step = f.args.get(2).cloned();
21928 match target {
21929 // Snowflake ARRAY_GENERATE_RANGE uses exclusive end (same as DuckDB RANGE),
21930 // so just rename without adjusting the end argument.
21931 DialectType::Snowflake => {
21932 let mut args = vec![start, end];
21933 if let Some(s) = step {
21934 args.push(s);
21935 }
21936 Ok(Expression::Function(Box::new(Function::new(
21937 "ARRAY_GENERATE_RANGE".to_string(),
21938 args,
21939 ))))
21940 }
21941 DialectType::Spark | DialectType::Databricks => {
21942 // RANGE(start, end) -> SEQUENCE(start, end-1)
21943 // RANGE(start, end, step) -> SEQUENCE(start, end-step, step) when step constant
21944 // RANGE(start, start) -> ARRAY() (empty)
21945 // RANGE(start, end, 0) -> ARRAY() (empty)
21946 // When end is variable: IF((end - 1) <= start, ARRAY(), SEQUENCE(start, (end - 1)))
21947
21948 // Check for constant args
21949 fn extract_i64(e: &Expression) -> Option<i64> {
21950 match e {
21951 Expression::Literal(lit)
21952 if matches!(
21953 lit.as_ref(),
21954 Literal::Number(_)
21955 ) =>
21956 {
21957 let Literal::Number(n) = lit.as_ref() else {
21958 unreachable!()
21959 };
21960 n.parse::<i64>().ok()
21961 }
21962 Expression::Neg(u) => {
21963 if let Expression::Literal(lit) = &u.this {
21964 if let Literal::Number(n) = lit.as_ref() {
21965 n.parse::<i64>().ok().map(|v| -v)
21966 } else {
21967 None
21968 }
21969 } else {
21970 None
21971 }
21972 }
21973 _ => None,
21974 }
21975 }
21976 let start_val = extract_i64(&start);
21977 let end_val = extract_i64(&end);
21978 let step_val = step.as_ref().and_then(|s| extract_i64(s));
21979
21980 // Check for RANGE(x, x) or RANGE(x, y, 0) -> empty array
21981 if step_val == Some(0) {
21982 return Ok(Expression::Function(Box::new(
21983 Function::new("ARRAY".to_string(), vec![]),
21984 )));
21985 }
21986 if let (Some(s), Some(e_val)) = (start_val, end_val) {
21987 if s == e_val {
21988 return Ok(Expression::Function(Box::new(
21989 Function::new("ARRAY".to_string(), vec![]),
21990 )));
21991 }
21992 }
21993
21994 if let (Some(_s_val), Some(e_val)) = (start_val, end_val) {
21995 // All constants - compute new end = end - step (if step provided) or end - 1
21996 match step_val {
21997 Some(st) if st < 0 => {
21998 // Negative step: SEQUENCE(start, end - step, step)
21999 let new_end = e_val - st; // end - step (= end + |step|)
22000 let mut args =
22001 vec![start, Expression::number(new_end)];
22002 if let Some(s) = step {
22003 args.push(s);
22004 }
22005 Ok(Expression::Function(Box::new(
22006 Function::new("SEQUENCE".to_string(), args),
22007 )))
22008 }
22009 Some(st) => {
22010 let new_end = e_val - st;
22011 let mut args =
22012 vec![start, Expression::number(new_end)];
22013 if let Some(s) = step {
22014 args.push(s);
22015 }
22016 Ok(Expression::Function(Box::new(
22017 Function::new("SEQUENCE".to_string(), args),
22018 )))
22019 }
22020 None => {
22021 // No step: SEQUENCE(start, end - 1)
22022 let new_end = e_val - 1;
22023 Ok(Expression::Function(Box::new(
22024 Function::new(
22025 "SEQUENCE".to_string(),
22026 vec![
22027 start,
22028 Expression::number(new_end),
22029 ],
22030 ),
22031 )))
22032 }
22033 }
22034 } else {
22035 // Variable end: IF((end - 1) < start, ARRAY(), SEQUENCE(start, (end - 1)))
22036 let end_m1 = Expression::Sub(Box::new(BinaryOp::new(
22037 end.clone(),
22038 Expression::number(1),
22039 )));
22040 let cond = Expression::Lt(Box::new(BinaryOp::new(
22041 Expression::Paren(Box::new(Paren {
22042 this: end_m1.clone(),
22043 trailing_comments: Vec::new(),
22044 })),
22045 start.clone(),
22046 )));
22047 let empty = Expression::Function(Box::new(
22048 Function::new("ARRAY".to_string(), vec![]),
22049 ));
22050 let mut seq_args = vec![
22051 start,
22052 Expression::Paren(Box::new(Paren {
22053 this: end_m1,
22054 trailing_comments: Vec::new(),
22055 })),
22056 ];
22057 if let Some(s) = step {
22058 seq_args.push(s);
22059 }
22060 let seq = Expression::Function(Box::new(
22061 Function::new("SEQUENCE".to_string(), seq_args),
22062 ));
22063 Ok(Expression::IfFunc(Box::new(
22064 crate::expressions::IfFunc {
22065 condition: cond,
22066 true_value: empty,
22067 false_value: Some(seq),
22068 original_name: None,
22069 inferred_type: None,
22070 },
22071 )))
22072 }
22073 }
22074 DialectType::SQLite => {
22075 // RANGE(start, end) -> GENERATE_SERIES(start, end)
22076 // The subquery wrapping is handled at the Alias level
22077 let mut args = vec![start, end];
22078 if let Some(s) = step {
22079 args.push(s);
22080 }
22081 Ok(Expression::Function(Box::new(Function::new(
22082 "GENERATE_SERIES".to_string(),
22083 args,
22084 ))))
22085 }
22086 _ => Ok(Expression::Function(f)),
22087 }
22088 }
22089 // ARRAY_REVERSE_SORT -> target-specific
22090 // (handled above as well, but also need DuckDB self-normalization)
22091 // MAP_FROM_ARRAYS(keys, values) -> target-specific map construction
22092 "MAP_FROM_ARRAYS" if f.args.len() == 2 => match target {
22093 DialectType::Snowflake => Ok(Expression::Function(Box::new(
22094 Function::new("OBJECT_CONSTRUCT".to_string(), f.args),
22095 ))),
22096 DialectType::Spark | DialectType::Databricks => {
22097 Ok(Expression::Function(Box::new(Function::new(
22098 "MAP_FROM_ARRAYS".to_string(),
22099 f.args,
22100 ))))
22101 }
22102 _ => Ok(Expression::Function(Box::new(Function::new(
22103 "MAP".to_string(),
22104 f.args,
22105 )))),
22106 },
22107 // VARIANCE(x) -> varSamp(x) for ClickHouse
22108 "VARIANCE" if f.args.len() == 1 => match target {
22109 DialectType::ClickHouse => Ok(Expression::Function(Box::new(
22110 Function::new("varSamp".to_string(), f.args),
22111 ))),
22112 _ => Ok(Expression::Function(f)),
22113 },
22114 // STDDEV(x) -> stddevSamp(x) for ClickHouse
22115 "STDDEV" if f.args.len() == 1 => match target {
22116 DialectType::ClickHouse => Ok(Expression::Function(Box::new(
22117 Function::new("stddevSamp".to_string(), f.args),
22118 ))),
22119 _ => Ok(Expression::Function(f)),
22120 },
22121 // ISINF(x) -> IS_INF(x) for BigQuery
22122 "ISINF" if f.args.len() == 1 => match target {
22123 DialectType::BigQuery => Ok(Expression::Function(Box::new(
22124 Function::new("IS_INF".to_string(), f.args),
22125 ))),
22126 _ => Ok(Expression::Function(f)),
22127 },
22128 // CONTAINS(arr, x) -> ARRAY_CONTAINS(arr, x) for Spark/Hive
22129 "CONTAINS" if f.args.len() == 2 => match target {
22130 DialectType::Spark
22131 | DialectType::Databricks
22132 | DialectType::Hive => Ok(Expression::Function(Box::new(
22133 Function::new("ARRAY_CONTAINS".to_string(), f.args),
22134 ))),
22135 _ => Ok(Expression::Function(f)),
22136 },
22137 // ARRAY_CONTAINS(arr, x) -> CONTAINS(arr, x) for Presto
22138 "ARRAY_CONTAINS" if f.args.len() == 2 => match target {
22139 DialectType::Presto | DialectType::Trino | DialectType::Athena => {
22140 Ok(Expression::Function(Box::new(Function::new(
22141 "CONTAINS".to_string(),
22142 f.args,
22143 ))))
22144 }
22145 DialectType::DuckDB => Ok(Expression::Function(Box::new(
22146 Function::new("ARRAY_CONTAINS".to_string(), f.args),
22147 ))),
22148 _ => Ok(Expression::Function(f)),
22149 },
22150 // TO_UNIXTIME(x) -> UNIX_TIMESTAMP(x) for Hive/Spark
22151 "TO_UNIXTIME" if f.args.len() == 1 => match target {
22152 DialectType::Hive
22153 | DialectType::Spark
22154 | DialectType::Databricks => Ok(Expression::Function(Box::new(
22155 Function::new("UNIX_TIMESTAMP".to_string(), f.args),
22156 ))),
22157 _ => Ok(Expression::Function(f)),
22158 },
22159 // FROM_UNIXTIME(x) -> target-specific
22160 "FROM_UNIXTIME" if f.args.len() == 1 => {
22161 match target {
22162 DialectType::Hive
22163 | DialectType::Spark
22164 | DialectType::Databricks
22165 | DialectType::Presto
22166 | DialectType::Trino => Ok(Expression::Function(f)),
22167 DialectType::DuckDB => {
22168 // DuckDB: TO_TIMESTAMP(x)
22169 let arg = f.args.into_iter().next().unwrap();
22170 Ok(Expression::Function(Box::new(Function::new(
22171 "TO_TIMESTAMP".to_string(),
22172 vec![arg],
22173 ))))
22174 }
22175 DialectType::PostgreSQL => {
22176 // PG: TO_TIMESTAMP(col)
22177 let arg = f.args.into_iter().next().unwrap();
22178 Ok(Expression::Function(Box::new(Function::new(
22179 "TO_TIMESTAMP".to_string(),
22180 vec![arg],
22181 ))))
22182 }
22183 DialectType::Redshift => {
22184 // Redshift: (TIMESTAMP 'epoch' + col * INTERVAL '1 SECOND')
22185 let arg = f.args.into_iter().next().unwrap();
22186 let epoch_ts = Expression::Literal(Box::new(
22187 Literal::Timestamp("epoch".to_string()),
22188 ));
22189 let interval = Expression::Interval(Box::new(
22190 crate::expressions::Interval {
22191 this: Some(Expression::string("1 SECOND")),
22192 unit: None,
22193 },
22194 ));
22195 let mul =
22196 Expression::Mul(Box::new(BinaryOp::new(arg, interval)));
22197 let add =
22198 Expression::Add(Box::new(BinaryOp::new(epoch_ts, mul)));
22199 Ok(Expression::Paren(Box::new(crate::expressions::Paren {
22200 this: add,
22201 trailing_comments: Vec::new(),
22202 })))
22203 }
22204 _ => Ok(Expression::Function(f)),
22205 }
22206 }
22207 // FROM_UNIXTIME(x, fmt) with 2 args from Hive/Spark -> target-specific
22208 "FROM_UNIXTIME"
22209 if f.args.len() == 2
22210 && matches!(
22211 source,
22212 DialectType::Hive
22213 | DialectType::Spark
22214 | DialectType::Databricks
22215 ) =>
22216 {
22217 let mut args = f.args;
22218 let unix_ts = args.remove(0);
22219 let fmt_expr = args.remove(0);
22220 match target {
22221 DialectType::DuckDB => {
22222 // DuckDB: STRFTIME(TO_TIMESTAMP(x), c_fmt)
22223 let to_ts = Expression::Function(Box::new(Function::new(
22224 "TO_TIMESTAMP".to_string(),
22225 vec![unix_ts],
22226 )));
22227 if let Expression::Literal(lit) = &fmt_expr {
22228 if let crate::expressions::Literal::String(s) =
22229 lit.as_ref()
22230 {
22231 let c_fmt = Self::hive_format_to_c_format(s);
22232 Ok(Expression::Function(Box::new(Function::new(
22233 "STRFTIME".to_string(),
22234 vec![to_ts, Expression::string(&c_fmt)],
22235 ))))
22236 } else {
22237 Ok(Expression::Function(Box::new(Function::new(
22238 "STRFTIME".to_string(),
22239 vec![to_ts, fmt_expr],
22240 ))))
22241 }
22242 } else {
22243 Ok(Expression::Function(Box::new(Function::new(
22244 "STRFTIME".to_string(),
22245 vec![to_ts, fmt_expr],
22246 ))))
22247 }
22248 }
22249 DialectType::Presto
22250 | DialectType::Trino
22251 | DialectType::Athena => {
22252 // Presto: DATE_FORMAT(FROM_UNIXTIME(x), presto_fmt)
22253 let from_unix =
22254 Expression::Function(Box::new(Function::new(
22255 "FROM_UNIXTIME".to_string(),
22256 vec![unix_ts],
22257 )));
22258 if let Expression::Literal(lit) = &fmt_expr {
22259 if let crate::expressions::Literal::String(s) =
22260 lit.as_ref()
22261 {
22262 let p_fmt = Self::hive_format_to_presto_format(s);
22263 Ok(Expression::Function(Box::new(Function::new(
22264 "DATE_FORMAT".to_string(),
22265 vec![from_unix, Expression::string(&p_fmt)],
22266 ))))
22267 } else {
22268 Ok(Expression::Function(Box::new(Function::new(
22269 "DATE_FORMAT".to_string(),
22270 vec![from_unix, fmt_expr],
22271 ))))
22272 }
22273 } else {
22274 Ok(Expression::Function(Box::new(Function::new(
22275 "DATE_FORMAT".to_string(),
22276 vec![from_unix, fmt_expr],
22277 ))))
22278 }
22279 }
22280 _ => {
22281 // Keep as FROM_UNIXTIME(x, fmt) for other targets
22282 Ok(Expression::Function(Box::new(Function::new(
22283 "FROM_UNIXTIME".to_string(),
22284 vec![unix_ts, fmt_expr],
22285 ))))
22286 }
22287 }
22288 }
22289 // DATEPART(unit, expr) -> EXTRACT(unit FROM expr) for Spark
22290 "DATEPART" | "DATE_PART" if f.args.len() == 2 => {
22291 let unit_str = Self::get_unit_str_static(&f.args[0]);
22292 // Get the raw unit text preserving original case
22293 let raw_unit = match &f.args[0] {
22294 Expression::Identifier(id) => id.name.clone(),
22295 Expression::Var(v) => v.this.clone(),
22296 Expression::Literal(lit)
22297 if matches!(
22298 lit.as_ref(),
22299 crate::expressions::Literal::String(_)
22300 ) =>
22301 {
22302 let crate::expressions::Literal::String(s) = lit.as_ref()
22303 else {
22304 unreachable!()
22305 };
22306 s.clone()
22307 }
22308 Expression::Cast(cast)
22309 if cast.format.is_none()
22310 && cast.default.is_none()
22311 && Self::unit_cast_target_is_string(&cast.to)
22312 && matches!(
22313 &cast.this,
22314 Expression::Literal(lit)
22315 if matches!(
22316 lit.as_ref(),
22317 crate::expressions::Literal::String(_)
22318 )
22319 ) =>
22320 {
22321 let Expression::Literal(lit) = &cast.this else {
22322 unreachable!()
22323 };
22324 let crate::expressions::Literal::String(s) = lit.as_ref()
22325 else {
22326 unreachable!()
22327 };
22328 s.clone()
22329 }
22330 Expression::Column(col) => col.name.name.clone(),
22331 _ => unit_str.clone(),
22332 };
22333 match target {
22334 DialectType::TSQL | DialectType::Fabric => {
22335 // Preserve original case of unit for TSQL
22336 let unit_name = match unit_str.as_str() {
22337 "YY" | "YYYY" => "YEAR".to_string(),
22338 "QQ" | "Q" => "QUARTER".to_string(),
22339 "MM" | "M" => "MONTH".to_string(),
22340 "WK" | "WW" => "WEEK".to_string(),
22341 "DD" | "D" | "DY" => "DAY".to_string(),
22342 "HH" => "HOUR".to_string(),
22343 "MI" | "N" => "MINUTE".to_string(),
22344 "SS" | "S" => "SECOND".to_string(),
22345 _ => raw_unit.clone(), // preserve original case
22346 };
22347 let mut args = f.args;
22348 args[0] =
22349 Expression::Identifier(Identifier::new(&unit_name));
22350 Ok(Expression::Function(Box::new(Function::new(
22351 "DATEPART".to_string(),
22352 args,
22353 ))))
22354 }
22355 DialectType::Spark | DialectType::Databricks => {
22356 // DATEPART(unit, expr) -> EXTRACT(unit FROM expr)
22357 // Preserve original case for non-abbreviation units
22358 let unit = match unit_str.as_str() {
22359 "YY" | "YYYY" => "YEAR".to_string(),
22360 "QQ" | "Q" => "QUARTER".to_string(),
22361 "MM" | "M" => "MONTH".to_string(),
22362 "WK" | "WW" => "WEEK".to_string(),
22363 "DD" | "D" | "DY" => "DAY".to_string(),
22364 "HH" => "HOUR".to_string(),
22365 "MI" | "N" => "MINUTE".to_string(),
22366 "SS" | "S" => "SECOND".to_string(),
22367 _ => raw_unit, // preserve original case
22368 };
22369 Ok(Expression::Extract(Box::new(
22370 crate::expressions::ExtractFunc {
22371 this: f.args[1].clone(),
22372 field: crate::expressions::DateTimeField::Custom(
22373 unit,
22374 ),
22375 },
22376 )))
22377 }
22378 _ => Ok(Expression::Function(Box::new(Function::new(
22379 "DATE_PART".to_string(),
22380 f.args,
22381 )))),
22382 }
22383 }
22384 // DATENAME(mm, date) -> FORMAT(CAST(date AS DATETIME2), 'MMMM') for TSQL
22385 // DATENAME(dw, date) -> FORMAT(CAST(date AS DATETIME2), 'dddd') for TSQL
22386 // DATENAME(mm, date) -> DATE_FORMAT(CAST(date AS TIMESTAMP), 'MMMM') for Spark
22387 // DATENAME(dw, date) -> DATE_FORMAT(CAST(date AS TIMESTAMP), 'EEEE') for Spark
22388 "DATENAME" if f.args.len() == 2 => {
22389 let unit_str = Self::get_unit_str_static(&f.args[0]);
22390 let date_expr = f.args[1].clone();
22391 match unit_str.as_str() {
22392 "MM" | "M" | "MONTH" => match target {
22393 DialectType::TSQL => {
22394 let cast_date = Expression::Cast(Box::new(
22395 crate::expressions::Cast {
22396 this: date_expr,
22397 to: DataType::Custom {
22398 name: "DATETIME2".to_string(),
22399 },
22400 trailing_comments: Vec::new(),
22401 double_colon_syntax: false,
22402 format: None,
22403 default: None,
22404 inferred_type: None,
22405 },
22406 ));
22407 Ok(Expression::Function(Box::new(Function::new(
22408 "FORMAT".to_string(),
22409 vec![cast_date, Expression::string("MMMM")],
22410 ))))
22411 }
22412 DialectType::Spark | DialectType::Databricks => {
22413 let cast_date = Expression::Cast(Box::new(
22414 crate::expressions::Cast {
22415 this: date_expr,
22416 to: DataType::Timestamp {
22417 timezone: false,
22418 precision: None,
22419 },
22420 trailing_comments: Vec::new(),
22421 double_colon_syntax: false,
22422 format: None,
22423 default: None,
22424 inferred_type: None,
22425 },
22426 ));
22427 Ok(Expression::Function(Box::new(Function::new(
22428 "DATE_FORMAT".to_string(),
22429 vec![cast_date, Expression::string("MMMM")],
22430 ))))
22431 }
22432 _ => Ok(Expression::Function(f)),
22433 },
22434 "DW" | "WEEKDAY" => match target {
22435 DialectType::TSQL => {
22436 let cast_date = Expression::Cast(Box::new(
22437 crate::expressions::Cast {
22438 this: date_expr,
22439 to: DataType::Custom {
22440 name: "DATETIME2".to_string(),
22441 },
22442 trailing_comments: Vec::new(),
22443 double_colon_syntax: false,
22444 format: None,
22445 default: None,
22446 inferred_type: None,
22447 },
22448 ));
22449 Ok(Expression::Function(Box::new(Function::new(
22450 "FORMAT".to_string(),
22451 vec![cast_date, Expression::string("dddd")],
22452 ))))
22453 }
22454 DialectType::Spark | DialectType::Databricks => {
22455 let cast_date = Expression::Cast(Box::new(
22456 crate::expressions::Cast {
22457 this: date_expr,
22458 to: DataType::Timestamp {
22459 timezone: false,
22460 precision: None,
22461 },
22462 trailing_comments: Vec::new(),
22463 double_colon_syntax: false,
22464 format: None,
22465 default: None,
22466 inferred_type: None,
22467 },
22468 ));
22469 Ok(Expression::Function(Box::new(Function::new(
22470 "DATE_FORMAT".to_string(),
22471 vec![cast_date, Expression::string("EEEE")],
22472 ))))
22473 }
22474 _ => Ok(Expression::Function(f)),
22475 },
22476 _ => Ok(Expression::Function(f)),
22477 }
22478 }
22479 // STRING_AGG(x, sep) without WITHIN GROUP -> target-specific
22480 "STRING_AGG" if f.args.len() >= 2 => {
22481 let x = f.args[0].clone();
22482 let sep = f.args[1].clone();
22483 match target {
22484 DialectType::MySQL
22485 | DialectType::SingleStore
22486 | DialectType::Doris
22487 | DialectType::StarRocks => Ok(Expression::GroupConcat(
22488 Box::new(crate::expressions::GroupConcatFunc {
22489 this: x,
22490 separator: Some(sep),
22491 order_by: None,
22492 distinct: false,
22493 filter: None,
22494 limit: None,
22495 inferred_type: None,
22496 }),
22497 )),
22498 DialectType::SQLite => Ok(Expression::GroupConcat(Box::new(
22499 crate::expressions::GroupConcatFunc {
22500 this: x,
22501 separator: Some(sep),
22502 order_by: None,
22503 distinct: false,
22504 filter: None,
22505 limit: None,
22506 inferred_type: None,
22507 },
22508 ))),
22509 DialectType::PostgreSQL | DialectType::Redshift => {
22510 Ok(Expression::StringAgg(Box::new(
22511 crate::expressions::StringAggFunc {
22512 this: x,
22513 separator: Some(sep),
22514 order_by: None,
22515 distinct: false,
22516 filter: None,
22517 limit: None,
22518 inferred_type: None,
22519 },
22520 )))
22521 }
22522 _ => Ok(Expression::Function(f)),
22523 }
22524 }
22525 "TRY_DIVIDE" if f.args.len() == 2 => {
22526 let mut args = f.args;
22527 let x = args.remove(0);
22528 let y = args.remove(0);
22529 match target {
22530 DialectType::Spark | DialectType::Databricks => {
22531 Ok(Expression::Function(Box::new(Function::new(
22532 "TRY_DIVIDE".to_string(),
22533 vec![x, y],
22534 ))))
22535 }
22536 DialectType::Snowflake => {
22537 let y_ref = match &y {
22538 Expression::Column(_)
22539 | Expression::Literal(_)
22540 | Expression::Identifier(_) => y.clone(),
22541 _ => Expression::Paren(Box::new(Paren {
22542 this: y.clone(),
22543 trailing_comments: vec![],
22544 })),
22545 };
22546 let x_ref = match &x {
22547 Expression::Column(_)
22548 | Expression::Literal(_)
22549 | Expression::Identifier(_) => x.clone(),
22550 _ => Expression::Paren(Box::new(Paren {
22551 this: x.clone(),
22552 trailing_comments: vec![],
22553 })),
22554 };
22555 let condition = Expression::Neq(Box::new(
22556 crate::expressions::BinaryOp::new(
22557 y_ref.clone(),
22558 Expression::number(0),
22559 ),
22560 ));
22561 let div_expr = Expression::Div(Box::new(
22562 crate::expressions::BinaryOp::new(x_ref, y_ref),
22563 ));
22564 Ok(Expression::IfFunc(Box::new(
22565 crate::expressions::IfFunc {
22566 condition,
22567 true_value: div_expr,
22568 false_value: Some(Expression::Null(Null)),
22569 original_name: Some("IFF".to_string()),
22570 inferred_type: None,
22571 },
22572 )))
22573 }
22574 DialectType::DuckDB => {
22575 let y_ref = match &y {
22576 Expression::Column(_)
22577 | Expression::Literal(_)
22578 | Expression::Identifier(_) => y.clone(),
22579 _ => Expression::Paren(Box::new(Paren {
22580 this: y.clone(),
22581 trailing_comments: vec![],
22582 })),
22583 };
22584 let x_ref = match &x {
22585 Expression::Column(_)
22586 | Expression::Literal(_)
22587 | Expression::Identifier(_) => x.clone(),
22588 _ => Expression::Paren(Box::new(Paren {
22589 this: x.clone(),
22590 trailing_comments: vec![],
22591 })),
22592 };
22593 let condition = Expression::Neq(Box::new(
22594 crate::expressions::BinaryOp::new(
22595 y_ref.clone(),
22596 Expression::number(0),
22597 ),
22598 ));
22599 let div_expr = Expression::Div(Box::new(
22600 crate::expressions::BinaryOp::new(x_ref, y_ref),
22601 ));
22602 Ok(Expression::Case(Box::new(Case {
22603 operand: None,
22604 whens: vec![(condition, div_expr)],
22605 else_: Some(Expression::Null(Null)),
22606 comments: Vec::new(),
22607 inferred_type: None,
22608 })))
22609 }
22610 _ => Ok(Expression::Function(Box::new(Function::new(
22611 "TRY_DIVIDE".to_string(),
22612 vec![x, y],
22613 )))),
22614 }
22615 }
22616 // JSON_ARRAYAGG -> JSON_AGG for PostgreSQL
22617 "JSON_ARRAYAGG" => match target {
22618 DialectType::PostgreSQL => {
22619 Ok(Expression::Function(Box::new(Function {
22620 name: "JSON_AGG".to_string(),
22621 ..(*f)
22622 })))
22623 }
22624 _ => Ok(Expression::Function(f)),
22625 },
22626 // SCHEMA_NAME(id) -> CURRENT_SCHEMA for PostgreSQL, 'main' for SQLite
22627 "SCHEMA_NAME" => match target {
22628 DialectType::PostgreSQL => Ok(Expression::CurrentSchema(Box::new(
22629 crate::expressions::CurrentSchema { this: None },
22630 ))),
22631 DialectType::SQLite => Ok(Expression::string("main")),
22632 _ => Ok(Expression::Function(f)),
22633 },
22634 // TO_TIMESTAMP(x, fmt) 2-arg from Spark/Hive: convert Java format to target format
22635 "TO_TIMESTAMP"
22636 if f.args.len() == 2
22637 && matches!(
22638 source,
22639 DialectType::Spark
22640 | DialectType::Databricks
22641 | DialectType::Hive
22642 )
22643 && matches!(target, DialectType::DuckDB) =>
22644 {
22645 let mut args = f.args;
22646 let val = args.remove(0);
22647 let fmt_expr = args.remove(0);
22648 if let Expression::Literal(ref lit) = fmt_expr {
22649 if let Literal::String(ref s) = lit.as_ref() {
22650 // Convert Java/Spark format to C strptime format
22651 fn java_to_c_fmt(fmt: &str) -> String {
22652 let result = fmt
22653 .replace("yyyy", "%Y")
22654 .replace("SSSSSS", "%f")
22655 .replace("EEEE", "%W")
22656 .replace("MM", "%m")
22657 .replace("dd", "%d")
22658 .replace("HH", "%H")
22659 .replace("mm", "%M")
22660 .replace("ss", "%S")
22661 .replace("yy", "%y");
22662 let mut out = String::new();
22663 let chars: Vec<char> = result.chars().collect();
22664 let mut i = 0;
22665 while i < chars.len() {
22666 if chars[i] == '%' && i + 1 < chars.len() {
22667 out.push(chars[i]);
22668 out.push(chars[i + 1]);
22669 i += 2;
22670 } else if chars[i] == 'z' {
22671 out.push_str("%Z");
22672 i += 1;
22673 } else if chars[i] == 'Z' {
22674 out.push_str("%z");
22675 i += 1;
22676 } else {
22677 out.push(chars[i]);
22678 i += 1;
22679 }
22680 }
22681 out
22682 }
22683 let c_fmt = java_to_c_fmt(s);
22684 Ok(Expression::Function(Box::new(Function::new(
22685 "STRPTIME".to_string(),
22686 vec![val, Expression::string(&c_fmt)],
22687 ))))
22688 } else {
22689 Ok(Expression::Function(Box::new(Function::new(
22690 "STRPTIME".to_string(),
22691 vec![val, fmt_expr],
22692 ))))
22693 }
22694 } else {
22695 Ok(Expression::Function(Box::new(Function::new(
22696 "STRPTIME".to_string(),
22697 vec![val, fmt_expr],
22698 ))))
22699 }
22700 }
22701 // TO_DATE(x) 1-arg from Doris: date conversion
22702 "TO_DATE"
22703 if f.args.len() == 1
22704 && matches!(
22705 source,
22706 DialectType::Doris | DialectType::StarRocks
22707 ) =>
22708 {
22709 let arg = f.args.into_iter().next().unwrap();
22710 match target {
22711 DialectType::Oracle
22712 | DialectType::DuckDB
22713 | DialectType::TSQL => {
22714 // CAST(x AS DATE)
22715 Ok(Expression::Cast(Box::new(Cast {
22716 this: arg,
22717 to: DataType::Date,
22718 double_colon_syntax: false,
22719 trailing_comments: vec![],
22720 format: None,
22721 default: None,
22722 inferred_type: None,
22723 })))
22724 }
22725 DialectType::MySQL | DialectType::SingleStore => {
22726 // DATE(x)
22727 Ok(Expression::Function(Box::new(Function::new(
22728 "DATE".to_string(),
22729 vec![arg],
22730 ))))
22731 }
22732 _ => {
22733 // Default: keep as TO_DATE(x) (Spark, PostgreSQL, etc.)
22734 Ok(Expression::Function(Box::new(Function::new(
22735 "TO_DATE".to_string(),
22736 vec![arg],
22737 ))))
22738 }
22739 }
22740 }
22741 // TO_DATE(x) 1-arg from Spark/Hive: safe date conversion
22742 "TO_DATE"
22743 if f.args.len() == 1
22744 && matches!(
22745 source,
22746 DialectType::Spark
22747 | DialectType::Databricks
22748 | DialectType::Hive
22749 ) =>
22750 {
22751 let arg = f.args.into_iter().next().unwrap();
22752 match target {
22753 DialectType::DuckDB => {
22754 // Spark TO_DATE is safe -> TRY_CAST(x AS DATE)
22755 Ok(Expression::TryCast(Box::new(Cast {
22756 this: arg,
22757 to: DataType::Date,
22758 double_colon_syntax: false,
22759 trailing_comments: vec![],
22760 format: None,
22761 default: None,
22762 inferred_type: None,
22763 })))
22764 }
22765 DialectType::Presto
22766 | DialectType::Trino
22767 | DialectType::Athena => {
22768 // CAST(CAST(x AS TIMESTAMP) AS DATE)
22769 Ok(Self::double_cast_timestamp_date(arg))
22770 }
22771 DialectType::Snowflake => {
22772 // Spark's TO_DATE is safe -> TRY_TO_DATE(x, 'yyyy-mm-DD')
22773 // The default Spark format 'yyyy-MM-dd' maps to Snowflake 'yyyy-mm-DD'
22774 Ok(Expression::Function(Box::new(Function::new(
22775 "TRY_TO_DATE".to_string(),
22776 vec![arg, Expression::string("yyyy-mm-DD")],
22777 ))))
22778 }
22779 _ => {
22780 // Default: keep as TO_DATE(x)
22781 Ok(Expression::Function(Box::new(Function::new(
22782 "TO_DATE".to_string(),
22783 vec![arg],
22784 ))))
22785 }
22786 }
22787 }
22788 // TO_DATE(x, fmt) 2-arg from Spark/Hive: format-based date conversion
22789 "TO_DATE"
22790 if f.args.len() == 2
22791 && matches!(
22792 source,
22793 DialectType::Spark
22794 | DialectType::Databricks
22795 | DialectType::Hive
22796 ) =>
22797 {
22798 let mut args = f.args;
22799 let val = args.remove(0);
22800 let fmt_expr = args.remove(0);
22801 let is_default_format = matches!(&fmt_expr, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(s) if s == "yyyy-MM-dd"));
22802
22803 if is_default_format {
22804 // Default format: same as 1-arg form
22805 match target {
22806 DialectType::DuckDB => {
22807 Ok(Expression::TryCast(Box::new(Cast {
22808 this: val,
22809 to: DataType::Date,
22810 double_colon_syntax: false,
22811 trailing_comments: vec![],
22812 format: None,
22813 default: None,
22814 inferred_type: None,
22815 })))
22816 }
22817 DialectType::Presto
22818 | DialectType::Trino
22819 | DialectType::Athena => {
22820 Ok(Self::double_cast_timestamp_date(val))
22821 }
22822 DialectType::Snowflake => {
22823 // TRY_TO_DATE(x, format) with Snowflake format mapping
22824 let sf_fmt = "yyyy-MM-dd"
22825 .replace("yyyy", "yyyy")
22826 .replace("MM", "mm")
22827 .replace("dd", "DD");
22828 Ok(Expression::Function(Box::new(Function::new(
22829 "TRY_TO_DATE".to_string(),
22830 vec![val, Expression::string(&sf_fmt)],
22831 ))))
22832 }
22833 _ => Ok(Expression::Function(Box::new(Function::new(
22834 "TO_DATE".to_string(),
22835 vec![val],
22836 )))),
22837 }
22838 } else {
22839 // Non-default format: use format-based parsing
22840 if let Expression::Literal(ref lit) = fmt_expr {
22841 if let Literal::String(ref s) = lit.as_ref() {
22842 match target {
22843 DialectType::DuckDB => {
22844 // CAST(CAST(TRY_STRPTIME(x, c_fmt) AS TIMESTAMP) AS DATE)
22845 fn java_to_c_fmt_todate(fmt: &str) -> String {
22846 let result = fmt
22847 .replace("yyyy", "%Y")
22848 .replace("SSSSSS", "%f")
22849 .replace("EEEE", "%W")
22850 .replace("MM", "%m")
22851 .replace("dd", "%d")
22852 .replace("HH", "%H")
22853 .replace("mm", "%M")
22854 .replace("ss", "%S")
22855 .replace("yy", "%y");
22856 let mut out = String::new();
22857 let chars: Vec<char> =
22858 result.chars().collect();
22859 let mut i = 0;
22860 while i < chars.len() {
22861 if chars[i] == '%'
22862 && i + 1 < chars.len()
22863 {
22864 out.push(chars[i]);
22865 out.push(chars[i + 1]);
22866 i += 2;
22867 } else if chars[i] == 'z' {
22868 out.push_str("%Z");
22869 i += 1;
22870 } else if chars[i] == 'Z' {
22871 out.push_str("%z");
22872 i += 1;
22873 } else {
22874 out.push(chars[i]);
22875 i += 1;
22876 }
22877 }
22878 out
22879 }
22880 let c_fmt = java_to_c_fmt_todate(s);
22881 // CAST(CAST(TRY_STRPTIME(x, fmt) AS TIMESTAMP) AS DATE)
22882 let try_strptime = Expression::Function(
22883 Box::new(Function::new(
22884 "TRY_STRPTIME".to_string(),
22885 vec![val, Expression::string(&c_fmt)],
22886 )),
22887 );
22888 let cast_ts =
22889 Expression::Cast(Box::new(Cast {
22890 this: try_strptime,
22891 to: DataType::Timestamp {
22892 precision: None,
22893 timezone: false,
22894 },
22895 double_colon_syntax: false,
22896 trailing_comments: vec![],
22897 format: None,
22898 default: None,
22899 inferred_type: None,
22900 }));
22901 Ok(Expression::Cast(Box::new(Cast {
22902 this: cast_ts,
22903 to: DataType::Date,
22904 double_colon_syntax: false,
22905 trailing_comments: vec![],
22906 format: None,
22907 default: None,
22908 inferred_type: None,
22909 })))
22910 }
22911 DialectType::Presto
22912 | DialectType::Trino
22913 | DialectType::Athena => {
22914 // CAST(DATE_PARSE(x, presto_fmt) AS DATE)
22915 let p_fmt = s
22916 .replace("yyyy", "%Y")
22917 .replace("SSSSSS", "%f")
22918 .replace("MM", "%m")
22919 .replace("dd", "%d")
22920 .replace("HH", "%H")
22921 .replace("mm", "%M")
22922 .replace("ss", "%S")
22923 .replace("yy", "%y");
22924 let date_parse = Expression::Function(
22925 Box::new(Function::new(
22926 "DATE_PARSE".to_string(),
22927 vec![val, Expression::string(&p_fmt)],
22928 )),
22929 );
22930 Ok(Expression::Cast(Box::new(Cast {
22931 this: date_parse,
22932 to: DataType::Date,
22933 double_colon_syntax: false,
22934 trailing_comments: vec![],
22935 format: None,
22936 default: None,
22937 inferred_type: None,
22938 })))
22939 }
22940 DialectType::Snowflake => {
22941 // TRY_TO_DATE(x, snowflake_fmt)
22942 Ok(Expression::Function(Box::new(
22943 Function::new(
22944 "TRY_TO_DATE".to_string(),
22945 vec![val, Expression::string(s)],
22946 ),
22947 )))
22948 }
22949 _ => Ok(Expression::Function(Box::new(
22950 Function::new(
22951 "TO_DATE".to_string(),
22952 vec![val, fmt_expr],
22953 ),
22954 ))),
22955 }
22956 } else {
22957 Ok(Expression::Function(Box::new(Function::new(
22958 "TO_DATE".to_string(),
22959 vec![val, fmt_expr],
22960 ))))
22961 }
22962 } else {
22963 Ok(Expression::Function(Box::new(Function::new(
22964 "TO_DATE".to_string(),
22965 vec![val, fmt_expr],
22966 ))))
22967 }
22968 }
22969 }
22970 // TO_TIMESTAMP(x) 1-arg: epoch conversion
22971 "TO_TIMESTAMP"
22972 if f.args.len() == 1
22973 && matches!(source, DialectType::DuckDB)
22974 && matches!(
22975 target,
22976 DialectType::BigQuery
22977 | DialectType::Presto
22978 | DialectType::Trino
22979 | DialectType::Hive
22980 | DialectType::Spark
22981 | DialectType::Databricks
22982 | DialectType::Athena
22983 ) =>
22984 {
22985 let arg = f.args.into_iter().next().unwrap();
22986 let func_name = match target {
22987 DialectType::BigQuery => "TIMESTAMP_SECONDS",
22988 DialectType::Presto
22989 | DialectType::Trino
22990 | DialectType::Athena
22991 | DialectType::Hive
22992 | DialectType::Spark
22993 | DialectType::Databricks => "FROM_UNIXTIME",
22994 _ => "TO_TIMESTAMP",
22995 };
22996 Ok(Expression::Function(Box::new(Function::new(
22997 func_name.to_string(),
22998 vec![arg],
22999 ))))
23000 }
23001 // CONCAT(x) single-arg: -> CONCAT(COALESCE(x, '')) for Spark
23002 "CONCAT" if f.args.len() == 1 => {
23003 let arg = f.args.into_iter().next().unwrap();
23004 match target {
23005 DialectType::Presto
23006 | DialectType::Trino
23007 | DialectType::Athena => {
23008 // CONCAT(a) -> CAST(a AS VARCHAR)
23009 Ok(Expression::Cast(Box::new(Cast {
23010 this: arg,
23011 to: DataType::VarChar {
23012 length: None,
23013 parenthesized_length: false,
23014 },
23015 trailing_comments: vec![],
23016 double_colon_syntax: false,
23017 format: None,
23018 default: None,
23019 inferred_type: None,
23020 })))
23021 }
23022 DialectType::TSQL => {
23023 // CONCAT(a) -> a
23024 Ok(arg)
23025 }
23026 DialectType::DuckDB => {
23027 // Keep CONCAT(a) for DuckDB (native support)
23028 Ok(Expression::Function(Box::new(Function::new(
23029 "CONCAT".to_string(),
23030 vec![arg],
23031 ))))
23032 }
23033 DialectType::Spark | DialectType::Databricks => {
23034 let coalesced = Expression::Coalesce(Box::new(
23035 crate::expressions::VarArgFunc {
23036 expressions: vec![arg, Expression::string("")],
23037 original_name: None,
23038 inferred_type: None,
23039 },
23040 ));
23041 Ok(Expression::Function(Box::new(Function::new(
23042 "CONCAT".to_string(),
23043 vec![coalesced],
23044 ))))
23045 }
23046 _ => Ok(Expression::Function(Box::new(Function::new(
23047 "CONCAT".to_string(),
23048 vec![arg],
23049 )))),
23050 }
23051 }
23052 // REGEXP_EXTRACT(a, p) 2-arg: BigQuery default group is 0 (no 3rd arg needed)
23053 "REGEXP_EXTRACT"
23054 if f.args.len() == 3 && matches!(target, DialectType::BigQuery) =>
23055 {
23056 // If group_index is 0, drop it
23057 let drop_group = match &f.args[2] {
23058 Expression::Literal(lit)
23059 if matches!(lit.as_ref(), Literal::Number(_)) =>
23060 {
23061 let Literal::Number(n) = lit.as_ref() else {
23062 unreachable!()
23063 };
23064 n == "0"
23065 }
23066 _ => false,
23067 };
23068 if drop_group {
23069 let mut args = f.args;
23070 args.truncate(2);
23071 Ok(Expression::Function(Box::new(Function::new(
23072 "REGEXP_EXTRACT".to_string(),
23073 args,
23074 ))))
23075 } else {
23076 Ok(Expression::Function(f))
23077 }
23078 }
23079 // REGEXP_EXTRACT(a, pattern, group, flags) 4-arg -> REGEXP_SUBSTR for Snowflake
23080 "REGEXP_EXTRACT"
23081 if f.args.len() == 4
23082 && matches!(target, DialectType::Snowflake) =>
23083 {
23084 // REGEXP_EXTRACT(a, 'pattern', 2, 'i') -> REGEXP_SUBSTR(a, 'pattern', 1, 1, 'i', 2)
23085 let mut args = f.args;
23086 let this = args.remove(0);
23087 let pattern = args.remove(0);
23088 let group = args.remove(0);
23089 let flags = args.remove(0);
23090 Ok(Expression::Function(Box::new(Function::new(
23091 "REGEXP_SUBSTR".to_string(),
23092 vec![
23093 this,
23094 pattern,
23095 Expression::number(1),
23096 Expression::number(1),
23097 flags,
23098 group,
23099 ],
23100 ))))
23101 }
23102 // REGEXP_SUBSTR(a, pattern, position) 3-arg -> REGEXP_EXTRACT(SUBSTRING(a, pos), pattern)
23103 "REGEXP_SUBSTR"
23104 if f.args.len() == 3
23105 && matches!(
23106 target,
23107 DialectType::DuckDB
23108 | DialectType::Presto
23109 | DialectType::Trino
23110 | DialectType::Spark
23111 | DialectType::Databricks
23112 ) =>
23113 {
23114 let mut args = f.args;
23115 let this = args.remove(0);
23116 let pattern = args.remove(0);
23117 let position = args.remove(0);
23118 // Wrap subject in SUBSTRING(this, position) to apply the offset
23119 let substring_expr = Expression::Function(Box::new(Function::new(
23120 "SUBSTRING".to_string(),
23121 vec![this, position],
23122 )));
23123 let target_name = match target {
23124 DialectType::DuckDB => "REGEXP_EXTRACT",
23125 _ => "REGEXP_EXTRACT",
23126 };
23127 Ok(Expression::Function(Box::new(Function::new(
23128 target_name.to_string(),
23129 vec![substring_expr, pattern],
23130 ))))
23131 }
23132 // TO_DAYS(x) -> (DATEDIFF(x, '0000-01-01') + 1) or target-specific
23133 "TO_DAYS" if f.args.len() == 1 => {
23134 let x = f.args.into_iter().next().unwrap();
23135 let epoch = Expression::string("0000-01-01");
23136 // Build the final target-specific expression directly
23137 let datediff_expr = match target {
23138 DialectType::MySQL | DialectType::SingleStore => {
23139 // MySQL: (DATEDIFF(x, '0000-01-01') + 1)
23140 Expression::Function(Box::new(Function::new(
23141 "DATEDIFF".to_string(),
23142 vec![x, epoch],
23143 )))
23144 }
23145 DialectType::DuckDB => {
23146 // DuckDB: (DATE_DIFF('DAY', CAST('0000-01-01' AS DATE), CAST(x AS DATE)) + 1)
23147 let cast_epoch = Expression::Cast(Box::new(Cast {
23148 this: epoch,
23149 to: DataType::Date,
23150 trailing_comments: Vec::new(),
23151 double_colon_syntax: false,
23152 format: None,
23153 default: None,
23154 inferred_type: None,
23155 }));
23156 let cast_x = Expression::Cast(Box::new(Cast {
23157 this: x,
23158 to: DataType::Date,
23159 trailing_comments: Vec::new(),
23160 double_colon_syntax: false,
23161 format: None,
23162 default: None,
23163 inferred_type: None,
23164 }));
23165 Expression::Function(Box::new(Function::new(
23166 "DATE_DIFF".to_string(),
23167 vec![Expression::string("DAY"), cast_epoch, cast_x],
23168 )))
23169 }
23170 DialectType::Presto
23171 | DialectType::Trino
23172 | DialectType::Athena => {
23173 // Presto: (DATE_DIFF('DAY', CAST(CAST('0000-01-01' AS TIMESTAMP) AS DATE), CAST(CAST(x AS TIMESTAMP) AS DATE)) + 1)
23174 let cast_epoch = Self::double_cast_timestamp_date(epoch);
23175 let cast_x = Self::double_cast_timestamp_date(x);
23176 Expression::Function(Box::new(Function::new(
23177 "DATE_DIFF".to_string(),
23178 vec![Expression::string("DAY"), cast_epoch, cast_x],
23179 )))
23180 }
23181 _ => {
23182 // Default: (DATEDIFF(x, '0000-01-01') + 1)
23183 Expression::Function(Box::new(Function::new(
23184 "DATEDIFF".to_string(),
23185 vec![x, epoch],
23186 )))
23187 }
23188 };
23189 let add_one = Expression::Add(Box::new(BinaryOp::new(
23190 datediff_expr,
23191 Expression::number(1),
23192 )));
23193 Ok(Expression::Paren(Box::new(crate::expressions::Paren {
23194 this: add_one,
23195 trailing_comments: Vec::new(),
23196 })))
23197 }
23198 // STR_TO_DATE(x, format) -> DATE_PARSE / STRPTIME / TO_DATE etc.
23199 "STR_TO_DATE"
23200 if f.args.len() == 2
23201 && matches!(
23202 target,
23203 DialectType::Presto | DialectType::Trino
23204 ) =>
23205 {
23206 let mut args = f.args;
23207 let x = args.remove(0);
23208 let format_expr = args.remove(0);
23209 // Check if the format contains time components
23210 let has_time = if let Expression::Literal(ref lit) = format_expr {
23211 if let Literal::String(ref fmt) = lit.as_ref() {
23212 fmt.contains("%H")
23213 || fmt.contains("%T")
23214 || fmt.contains("%M")
23215 || fmt.contains("%S")
23216 || fmt.contains("%I")
23217 || fmt.contains("%p")
23218 } else {
23219 false
23220 }
23221 } else {
23222 false
23223 };
23224 let date_parse = Expression::Function(Box::new(Function::new(
23225 "DATE_PARSE".to_string(),
23226 vec![x, format_expr],
23227 )));
23228 if has_time {
23229 // Has time components: just DATE_PARSE
23230 Ok(date_parse)
23231 } else {
23232 // Date-only: CAST(DATE_PARSE(...) AS DATE)
23233 Ok(Expression::Cast(Box::new(Cast {
23234 this: date_parse,
23235 to: DataType::Date,
23236 trailing_comments: Vec::new(),
23237 double_colon_syntax: false,
23238 format: None,
23239 default: None,
23240 inferred_type: None,
23241 })))
23242 }
23243 }
23244 "STR_TO_DATE"
23245 if f.args.len() == 2
23246 && matches!(
23247 target,
23248 DialectType::PostgreSQL | DialectType::Redshift
23249 ) =>
23250 {
23251 let mut args = f.args;
23252 let x = args.remove(0);
23253 let fmt = args.remove(0);
23254 let pg_fmt = match fmt {
23255 Expression::Literal(lit)
23256 if matches!(lit.as_ref(), Literal::String(_)) =>
23257 {
23258 let Literal::String(s) = lit.as_ref() else {
23259 unreachable!()
23260 };
23261 Expression::string(
23262 &s.replace("%Y", "YYYY")
23263 .replace("%m", "MM")
23264 .replace("%d", "DD")
23265 .replace("%H", "HH24")
23266 .replace("%M", "MI")
23267 .replace("%S", "SS"),
23268 )
23269 }
23270 other => other,
23271 };
23272 let to_date = Expression::Function(Box::new(Function::new(
23273 "TO_DATE".to_string(),
23274 vec![x, pg_fmt],
23275 )));
23276 Ok(Expression::Cast(Box::new(Cast {
23277 this: to_date,
23278 to: DataType::Timestamp {
23279 timezone: false,
23280 precision: None,
23281 },
23282 trailing_comments: Vec::new(),
23283 double_colon_syntax: false,
23284 format: None,
23285 default: None,
23286 inferred_type: None,
23287 })))
23288 }
23289 // RANGE(start, end) -> GENERATE_SERIES for SQLite
23290 "RANGE"
23291 if (f.args.len() == 1 || f.args.len() == 2)
23292 && matches!(target, DialectType::SQLite) =>
23293 {
23294 if f.args.len() == 2 {
23295 // RANGE(start, end) -> (SELECT value AS col_alias FROM GENERATE_SERIES(start, end))
23296 // For SQLite, RANGE is exclusive on end, GENERATE_SERIES is inclusive
23297 let mut args = f.args;
23298 let start = args.remove(0);
23299 let end = args.remove(0);
23300 Ok(Expression::Function(Box::new(Function::new(
23301 "GENERATE_SERIES".to_string(),
23302 vec![start, end],
23303 ))))
23304 } else {
23305 Ok(Expression::Function(f))
23306 }
23307 }
23308 // UNIFORM(low, high[, seed]) -> UNIFORM(low, high, RANDOM([seed])) for Snowflake
23309 // When source is Snowflake, keep as-is (args already in correct form)
23310 "UNIFORM"
23311 if matches!(target, DialectType::Snowflake)
23312 && (f.args.len() == 2 || f.args.len() == 3) =>
23313 {
23314 if matches!(source, DialectType::Snowflake) {
23315 // Snowflake -> Snowflake: keep as-is
23316 Ok(Expression::Function(f))
23317 } else {
23318 let mut args = f.args;
23319 let low = args.remove(0);
23320 let high = args.remove(0);
23321 let random = if !args.is_empty() {
23322 let seed = args.remove(0);
23323 Expression::Function(Box::new(Function::new(
23324 "RANDOM".to_string(),
23325 vec![seed],
23326 )))
23327 } else {
23328 Expression::Function(Box::new(Function::new(
23329 "RANDOM".to_string(),
23330 vec![],
23331 )))
23332 };
23333 Ok(Expression::Function(Box::new(Function::new(
23334 "UNIFORM".to_string(),
23335 vec![low, high, random],
23336 ))))
23337 }
23338 }
23339 // TO_UTC_TIMESTAMP(ts, tz) -> target-specific UTC conversion
23340 "TO_UTC_TIMESTAMP" if f.args.len() == 2 => {
23341 let mut args = f.args;
23342 let ts_arg = args.remove(0);
23343 let tz_arg = args.remove(0);
23344 // Cast string literal to TIMESTAMP for all targets
23345 let ts_cast = if matches!(&ts_arg, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
23346 {
23347 Expression::Cast(Box::new(Cast {
23348 this: ts_arg,
23349 to: DataType::Timestamp {
23350 timezone: false,
23351 precision: None,
23352 },
23353 trailing_comments: vec![],
23354 double_colon_syntax: false,
23355 format: None,
23356 default: None,
23357 inferred_type: None,
23358 }))
23359 } else {
23360 ts_arg
23361 };
23362 match target {
23363 DialectType::Spark | DialectType::Databricks => {
23364 Ok(Expression::Function(Box::new(Function::new(
23365 "TO_UTC_TIMESTAMP".to_string(),
23366 vec![ts_cast, tz_arg],
23367 ))))
23368 }
23369 DialectType::Snowflake => {
23370 // CONVERT_TIMEZONE(tz, 'UTC', CAST(ts AS TIMESTAMP))
23371 Ok(Expression::Function(Box::new(Function::new(
23372 "CONVERT_TIMEZONE".to_string(),
23373 vec![tz_arg, Expression::string("UTC"), ts_cast],
23374 ))))
23375 }
23376 DialectType::Presto
23377 | DialectType::Trino
23378 | DialectType::Athena => {
23379 // WITH_TIMEZONE(CAST(ts AS TIMESTAMP), tz) AT TIME ZONE 'UTC'
23380 let wtz = Expression::Function(Box::new(Function::new(
23381 "WITH_TIMEZONE".to_string(),
23382 vec![ts_cast, tz_arg],
23383 )));
23384 Ok(Expression::AtTimeZone(Box::new(
23385 crate::expressions::AtTimeZone {
23386 this: wtz,
23387 zone: Expression::string("UTC"),
23388 },
23389 )))
23390 }
23391 DialectType::BigQuery => {
23392 // DATETIME(TIMESTAMP(CAST(ts AS DATETIME), tz), 'UTC')
23393 let cast_dt = Expression::Cast(Box::new(Cast {
23394 this: if let Expression::Cast(c) = ts_cast {
23395 c.this
23396 } else {
23397 ts_cast.clone()
23398 },
23399 to: DataType::Custom {
23400 name: "DATETIME".to_string(),
23401 },
23402 trailing_comments: vec![],
23403 double_colon_syntax: false,
23404 format: None,
23405 default: None,
23406 inferred_type: None,
23407 }));
23408 let ts_func =
23409 Expression::Function(Box::new(Function::new(
23410 "TIMESTAMP".to_string(),
23411 vec![cast_dt, tz_arg],
23412 )));
23413 Ok(Expression::Function(Box::new(Function::new(
23414 "DATETIME".to_string(),
23415 vec![ts_func, Expression::string("UTC")],
23416 ))))
23417 }
23418 _ => {
23419 // DuckDB, PostgreSQL, Redshift: CAST(ts AS TIMESTAMP) AT TIME ZONE tz AT TIME ZONE 'UTC'
23420 let atz1 = Expression::AtTimeZone(Box::new(
23421 crate::expressions::AtTimeZone {
23422 this: ts_cast,
23423 zone: tz_arg,
23424 },
23425 ));
23426 Ok(Expression::AtTimeZone(Box::new(
23427 crate::expressions::AtTimeZone {
23428 this: atz1,
23429 zone: Expression::string("UTC"),
23430 },
23431 )))
23432 }
23433 }
23434 }
23435 // FROM_UTC_TIMESTAMP(ts, tz) -> target-specific UTC conversion
23436 "FROM_UTC_TIMESTAMP" if f.args.len() == 2 => {
23437 let mut args = f.args;
23438 let ts_arg = args.remove(0);
23439 let tz_arg = args.remove(0);
23440 // Cast string literal to TIMESTAMP
23441 let ts_cast = if matches!(&ts_arg, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
23442 {
23443 Expression::Cast(Box::new(Cast {
23444 this: ts_arg,
23445 to: DataType::Timestamp {
23446 timezone: false,
23447 precision: None,
23448 },
23449 trailing_comments: vec![],
23450 double_colon_syntax: false,
23451 format: None,
23452 default: None,
23453 inferred_type: None,
23454 }))
23455 } else {
23456 ts_arg
23457 };
23458 match target {
23459 DialectType::Spark | DialectType::Databricks => {
23460 Ok(Expression::Function(Box::new(Function::new(
23461 "FROM_UTC_TIMESTAMP".to_string(),
23462 vec![ts_cast, tz_arg],
23463 ))))
23464 }
23465 DialectType::Presto
23466 | DialectType::Trino
23467 | DialectType::Athena => {
23468 // AT_TIMEZONE(CAST(ts AS TIMESTAMP), tz)
23469 Ok(Expression::Function(Box::new(Function::new(
23470 "AT_TIMEZONE".to_string(),
23471 vec![ts_cast, tz_arg],
23472 ))))
23473 }
23474 DialectType::Snowflake => {
23475 // CONVERT_TIMEZONE('UTC', tz, CAST(ts AS TIMESTAMP))
23476 Ok(Expression::Function(Box::new(Function::new(
23477 "CONVERT_TIMEZONE".to_string(),
23478 vec![Expression::string("UTC"), tz_arg, ts_cast],
23479 ))))
23480 }
23481 _ => {
23482 // DuckDB, PostgreSQL, Redshift: CAST(ts AS TIMESTAMP) AT TIME ZONE tz
23483 Ok(Expression::AtTimeZone(Box::new(
23484 crate::expressions::AtTimeZone {
23485 this: ts_cast,
23486 zone: tz_arg,
23487 },
23488 )))
23489 }
23490 }
23491 }
23492 // MAP_FROM_ARRAYS(keys, values) -> target-specific map construction
23493 "MAP_FROM_ARRAYS" if f.args.len() == 2 => {
23494 let name = match target {
23495 DialectType::Snowflake => "OBJECT_CONSTRUCT",
23496 _ => "MAP",
23497 };
23498 Ok(Expression::Function(Box::new(Function::new(
23499 name.to_string(),
23500 f.args,
23501 ))))
23502 }
23503 // STR_TO_MAP(s, pair_delim, kv_delim) -> SPLIT_TO_MAP for Presto
23504 "STR_TO_MAP" if f.args.len() >= 1 => match target {
23505 DialectType::Presto | DialectType::Trino | DialectType::Athena => {
23506 Ok(Expression::Function(Box::new(Function::new(
23507 "SPLIT_TO_MAP".to_string(),
23508 f.args,
23509 ))))
23510 }
23511 _ => Ok(Expression::Function(f)),
23512 },
23513 // TIME_TO_STR(x, fmt) -> Expression::TimeToStr for proper generation
23514 "TIME_TO_STR" if f.args.len() == 2 => {
23515 let mut args = f.args;
23516 let this = args.remove(0);
23517 let fmt_expr = args.remove(0);
23518 let format = if let Expression::Literal(lit) = fmt_expr {
23519 if let Literal::String(s) = lit.as_ref() {
23520 s.clone()
23521 } else {
23522 String::new()
23523 }
23524 } else {
23525 "%Y-%m-%d %H:%M:%S".to_string()
23526 };
23527 Ok(Expression::TimeToStr(Box::new(
23528 crate::expressions::TimeToStr {
23529 this: Box::new(this),
23530 format,
23531 culture: None,
23532 zone: None,
23533 },
23534 )))
23535 }
23536 // STR_TO_TIME(x, fmt) -> Expression::StrToTime for proper generation
23537 "STR_TO_TIME" if f.args.len() == 2 => {
23538 let mut args = f.args;
23539 let this = args.remove(0);
23540 let fmt_expr = args.remove(0);
23541 let format = if let Expression::Literal(lit) = fmt_expr {
23542 if let Literal::String(s) = lit.as_ref() {
23543 s.clone()
23544 } else {
23545 String::new()
23546 }
23547 } else {
23548 "%Y-%m-%d %H:%M:%S".to_string()
23549 };
23550 Ok(Expression::StrToTime(Box::new(
23551 crate::expressions::StrToTime {
23552 this: Box::new(this),
23553 format,
23554 zone: None,
23555 safe: None,
23556 target_type: None,
23557 },
23558 )))
23559 }
23560 // STR_TO_UNIX(x, fmt) -> Expression::StrToUnix for proper generation
23561 "STR_TO_UNIX" if f.args.len() >= 1 => {
23562 let mut args = f.args;
23563 let this = args.remove(0);
23564 let format = if !args.is_empty() {
23565 if let Expression::Literal(lit) = args.remove(0) {
23566 if let Literal::String(s) = lit.as_ref() {
23567 Some(s.clone())
23568 } else {
23569 None
23570 }
23571 } else {
23572 None
23573 }
23574 } else {
23575 None
23576 };
23577 Ok(Expression::StrToUnix(Box::new(
23578 crate::expressions::StrToUnix {
23579 this: Some(Box::new(this)),
23580 format,
23581 },
23582 )))
23583 }
23584 // TIME_TO_UNIX(x) -> Expression::TimeToUnix for proper generation
23585 "TIME_TO_UNIX" if f.args.len() == 1 => {
23586 let mut args = f.args;
23587 let this = args.remove(0);
23588 Ok(Expression::TimeToUnix(Box::new(
23589 crate::expressions::UnaryFunc {
23590 this,
23591 original_name: None,
23592 inferred_type: None,
23593 },
23594 )))
23595 }
23596 // UNIX_TO_STR(x, fmt) -> Expression::UnixToStr for proper generation
23597 "UNIX_TO_STR" if f.args.len() >= 1 => {
23598 let mut args = f.args;
23599 let this = args.remove(0);
23600 let format = if !args.is_empty() {
23601 if let Expression::Literal(lit) = args.remove(0) {
23602 if let Literal::String(s) = lit.as_ref() {
23603 Some(s.clone())
23604 } else {
23605 None
23606 }
23607 } else {
23608 None
23609 }
23610 } else {
23611 None
23612 };
23613 Ok(Expression::UnixToStr(Box::new(
23614 crate::expressions::UnixToStr {
23615 this: Box::new(this),
23616 format,
23617 },
23618 )))
23619 }
23620 // UNIX_TO_TIME(x) -> Expression::UnixToTime for proper generation
23621 "UNIX_TO_TIME" if f.args.len() == 1 => {
23622 let mut args = f.args;
23623 let this = args.remove(0);
23624 Ok(Expression::UnixToTime(Box::new(
23625 crate::expressions::UnixToTime {
23626 this: Box::new(this),
23627 scale: None,
23628 zone: None,
23629 hours: None,
23630 minutes: None,
23631 format: None,
23632 target_type: None,
23633 },
23634 )))
23635 }
23636 // TIME_STR_TO_DATE(x) -> Expression::TimeStrToDate for proper generation
23637 "TIME_STR_TO_DATE" if f.args.len() == 1 => {
23638 let mut args = f.args;
23639 let this = args.remove(0);
23640 Ok(Expression::TimeStrToDate(Box::new(
23641 crate::expressions::UnaryFunc {
23642 this,
23643 original_name: None,
23644 inferred_type: None,
23645 },
23646 )))
23647 }
23648 // TIME_STR_TO_TIME(x) -> Expression::TimeStrToTime for proper generation
23649 "TIME_STR_TO_TIME" if f.args.len() == 1 => {
23650 let mut args = f.args;
23651 let this = args.remove(0);
23652 Ok(Expression::TimeStrToTime(Box::new(
23653 crate::expressions::TimeStrToTime {
23654 this: Box::new(this),
23655 zone: None,
23656 },
23657 )))
23658 }
23659 // MONTHS_BETWEEN(end, start) -> DuckDB complex expansion
23660 "MONTHS_BETWEEN" if f.args.len() == 2 => {
23661 match target {
23662 DialectType::DuckDB => {
23663 let mut args = f.args;
23664 let end_date = args.remove(0);
23665 let start_date = args.remove(0);
23666 let cast_end = Self::ensure_cast_date(end_date);
23667 let cast_start = Self::ensure_cast_date(start_date);
23668 // DATE_DIFF('MONTH', start, end) + CASE WHEN DAY(end) = DAY(LAST_DAY(end)) AND DAY(start) = DAY(LAST_DAY(start)) THEN 0 ELSE (DAY(end) - DAY(start)) / 31.0 END
23669 let dd = Expression::Function(Box::new(Function::new(
23670 "DATE_DIFF".to_string(),
23671 vec![
23672 Expression::string("MONTH"),
23673 cast_start.clone(),
23674 cast_end.clone(),
23675 ],
23676 )));
23677 let day_end =
23678 Expression::Function(Box::new(Function::new(
23679 "DAY".to_string(),
23680 vec![cast_end.clone()],
23681 )));
23682 let day_start =
23683 Expression::Function(Box::new(Function::new(
23684 "DAY".to_string(),
23685 vec![cast_start.clone()],
23686 )));
23687 let last_day_end =
23688 Expression::Function(Box::new(Function::new(
23689 "LAST_DAY".to_string(),
23690 vec![cast_end.clone()],
23691 )));
23692 let last_day_start =
23693 Expression::Function(Box::new(Function::new(
23694 "LAST_DAY".to_string(),
23695 vec![cast_start.clone()],
23696 )));
23697 let day_last_end = Expression::Function(Box::new(
23698 Function::new("DAY".to_string(), vec![last_day_end]),
23699 ));
23700 let day_last_start = Expression::Function(Box::new(
23701 Function::new("DAY".to_string(), vec![last_day_start]),
23702 ));
23703 let cond1 = Expression::Eq(Box::new(BinaryOp::new(
23704 day_end.clone(),
23705 day_last_end,
23706 )));
23707 let cond2 = Expression::Eq(Box::new(BinaryOp::new(
23708 day_start.clone(),
23709 day_last_start,
23710 )));
23711 let both_cond =
23712 Expression::And(Box::new(BinaryOp::new(cond1, cond2)));
23713 let day_diff = Expression::Sub(Box::new(BinaryOp::new(
23714 day_end, day_start,
23715 )));
23716 let day_diff_paren = Expression::Paren(Box::new(
23717 crate::expressions::Paren {
23718 this: day_diff,
23719 trailing_comments: Vec::new(),
23720 },
23721 ));
23722 let frac = Expression::Div(Box::new(BinaryOp::new(
23723 day_diff_paren,
23724 Expression::Literal(Box::new(Literal::Number(
23725 "31.0".to_string(),
23726 ))),
23727 )));
23728 let case_expr = Expression::Case(Box::new(Case {
23729 operand: None,
23730 whens: vec![(both_cond, Expression::number(0))],
23731 else_: Some(frac),
23732 comments: Vec::new(),
23733 inferred_type: None,
23734 }));
23735 Ok(Expression::Add(Box::new(BinaryOp::new(dd, case_expr))))
23736 }
23737 DialectType::Snowflake | DialectType::Redshift => {
23738 let mut args = f.args;
23739 let end_date = args.remove(0);
23740 let start_date = args.remove(0);
23741 let unit = Expression::Identifier(Identifier::new("MONTH"));
23742 Ok(Expression::Function(Box::new(Function::new(
23743 "DATEDIFF".to_string(),
23744 vec![unit, start_date, end_date],
23745 ))))
23746 }
23747 DialectType::Presto
23748 | DialectType::Trino
23749 | DialectType::Athena => {
23750 let mut args = f.args;
23751 let end_date = args.remove(0);
23752 let start_date = args.remove(0);
23753 Ok(Expression::Function(Box::new(Function::new(
23754 "DATE_DIFF".to_string(),
23755 vec![Expression::string("MONTH"), start_date, end_date],
23756 ))))
23757 }
23758 _ => Ok(Expression::Function(f)),
23759 }
23760 }
23761 // MONTHS_BETWEEN(end, start, roundOff) - 3-arg form (Spark-specific)
23762 // Drop the roundOff arg for non-Spark targets, keep it for Spark
23763 "MONTHS_BETWEEN" if f.args.len() == 3 => {
23764 match target {
23765 DialectType::Spark | DialectType::Databricks => {
23766 Ok(Expression::Function(f))
23767 }
23768 _ => {
23769 // Drop the 3rd arg and delegate to the 2-arg logic
23770 let mut args = f.args;
23771 let end_date = args.remove(0);
23772 let start_date = args.remove(0);
23773 // Re-create as 2-arg and process
23774 let f2 = Function::new(
23775 "MONTHS_BETWEEN".to_string(),
23776 vec![end_date, start_date],
23777 );
23778 let e2 = Expression::Function(Box::new(f2));
23779 Self::cross_dialect_normalize(e2, source, target)
23780 }
23781 }
23782 }
23783 // TO_TIMESTAMP(x) with 1 arg -> CAST(x AS TIMESTAMP) for most targets
23784 "TO_TIMESTAMP"
23785 if f.args.len() == 1
23786 && matches!(
23787 source,
23788 DialectType::Spark
23789 | DialectType::Databricks
23790 | DialectType::Hive
23791 ) =>
23792 {
23793 let arg = f.args.into_iter().next().unwrap();
23794 Ok(Expression::Cast(Box::new(Cast {
23795 this: arg,
23796 to: DataType::Timestamp {
23797 timezone: false,
23798 precision: None,
23799 },
23800 trailing_comments: vec![],
23801 double_colon_syntax: false,
23802 format: None,
23803 default: None,
23804 inferred_type: None,
23805 })))
23806 }
23807 // STRING(x) -> CAST(x AS STRING) for Spark target
23808 "STRING"
23809 if f.args.len() == 1
23810 && matches!(
23811 source,
23812 DialectType::Spark | DialectType::Databricks
23813 ) =>
23814 {
23815 let arg = f.args.into_iter().next().unwrap();
23816 let dt = match target {
23817 DialectType::Spark
23818 | DialectType::Databricks
23819 | DialectType::Hive => DataType::Custom {
23820 name: "STRING".to_string(),
23821 },
23822 _ => DataType::Text,
23823 };
23824 Ok(Expression::Cast(Box::new(Cast {
23825 this: arg,
23826 to: dt,
23827 trailing_comments: vec![],
23828 double_colon_syntax: false,
23829 format: None,
23830 default: None,
23831 inferred_type: None,
23832 })))
23833 }
23834 // LOGICAL_OR(x) -> BOOL_OR(x) for Spark target
23835 "LOGICAL_OR" if f.args.len() == 1 => {
23836 let name = match target {
23837 DialectType::Spark | DialectType::Databricks => "BOOL_OR",
23838 _ => "LOGICAL_OR",
23839 };
23840 Ok(Expression::Function(Box::new(Function::new(
23841 name.to_string(),
23842 f.args,
23843 ))))
23844 }
23845 // SPLIT(x, pattern) from Spark -> STR_SPLIT_REGEX for DuckDB, REGEXP_SPLIT for Presto
23846 "SPLIT"
23847 if f.args.len() == 2
23848 && matches!(
23849 source,
23850 DialectType::Spark
23851 | DialectType::Databricks
23852 | DialectType::Hive
23853 ) =>
23854 {
23855 let name = match target {
23856 DialectType::DuckDB => "STR_SPLIT_REGEX",
23857 DialectType::Presto
23858 | DialectType::Trino
23859 | DialectType::Athena => "REGEXP_SPLIT",
23860 DialectType::Spark
23861 | DialectType::Databricks
23862 | DialectType::Hive => "SPLIT",
23863 _ => "SPLIT",
23864 };
23865 Ok(Expression::Function(Box::new(Function::new(
23866 name.to_string(),
23867 f.args,
23868 ))))
23869 }
23870 // TRY_ELEMENT_AT -> ELEMENT_AT for Presto, array[idx] for DuckDB
23871 "TRY_ELEMENT_AT" if f.args.len() == 2 => match target {
23872 DialectType::Presto | DialectType::Trino | DialectType::Athena => {
23873 Ok(Expression::Function(Box::new(Function::new(
23874 "ELEMENT_AT".to_string(),
23875 f.args,
23876 ))))
23877 }
23878 DialectType::DuckDB => {
23879 let mut args = f.args;
23880 let arr = args.remove(0);
23881 let idx = args.remove(0);
23882 Ok(Expression::Subscript(Box::new(
23883 crate::expressions::Subscript {
23884 this: arr,
23885 index: idx,
23886 },
23887 )))
23888 }
23889 _ => Ok(Expression::Function(f)),
23890 },
23891 // ARRAY_FILTER(arr, lambda) -> FILTER for Hive/Spark/Presto, LIST_FILTER for DuckDB
23892 "ARRAY_FILTER" if f.args.len() == 2 => {
23893 let name = match target {
23894 DialectType::DuckDB => "LIST_FILTER",
23895 DialectType::StarRocks => "ARRAY_FILTER",
23896 _ => "FILTER",
23897 };
23898 Ok(Expression::Function(Box::new(Function::new(
23899 name.to_string(),
23900 f.args,
23901 ))))
23902 }
23903 // FILTER(arr, lambda) -> ARRAY_FILTER for StarRocks, LIST_FILTER for DuckDB
23904 "FILTER" if f.args.len() == 2 => {
23905 let name = match target {
23906 DialectType::DuckDB => "LIST_FILTER",
23907 DialectType::StarRocks => "ARRAY_FILTER",
23908 _ => "FILTER",
23909 };
23910 Ok(Expression::Function(Box::new(Function::new(
23911 name.to_string(),
23912 f.args,
23913 ))))
23914 }
23915 // REDUCE(arr, init, lambda1, lambda2) -> AGGREGATE for Spark
23916 "REDUCE" if f.args.len() >= 3 => {
23917 let name = match target {
23918 DialectType::Spark | DialectType::Databricks => "AGGREGATE",
23919 _ => "REDUCE",
23920 };
23921 Ok(Expression::Function(Box::new(Function::new(
23922 name.to_string(),
23923 f.args,
23924 ))))
23925 }
23926 // CURRENT_SCHEMA() -> dialect-specific
23927 "CURRENT_SCHEMA" => {
23928 match target {
23929 DialectType::PostgreSQL => {
23930 // PostgreSQL: CURRENT_SCHEMA (no parens)
23931 Ok(Expression::Function(Box::new(Function {
23932 name: "CURRENT_SCHEMA".to_string(),
23933 args: vec![],
23934 distinct: false,
23935 trailing_comments: vec![],
23936 use_bracket_syntax: false,
23937 no_parens: true,
23938 quoted: false,
23939 span: None,
23940 inferred_type: None,
23941 })))
23942 }
23943 DialectType::MySQL
23944 | DialectType::Doris
23945 | DialectType::StarRocks => Ok(Expression::Function(Box::new(
23946 Function::new("SCHEMA".to_string(), vec![]),
23947 ))),
23948 DialectType::TSQL => Ok(Expression::Function(Box::new(
23949 Function::new("SCHEMA_NAME".to_string(), vec![]),
23950 ))),
23951 DialectType::SQLite => Ok(Expression::Literal(Box::new(
23952 Literal::String("main".to_string()),
23953 ))),
23954 _ => Ok(Expression::Function(f)),
23955 }
23956 }
23957 // LTRIM(str, chars) 2-arg -> TRIM(LEADING chars FROM str) for Spark/Hive/Databricks/ClickHouse
23958 "LTRIM" if f.args.len() == 2 => match target {
23959 DialectType::Spark
23960 | DialectType::Hive
23961 | DialectType::Databricks
23962 | DialectType::ClickHouse => {
23963 let mut args = f.args;
23964 let str_expr = args.remove(0);
23965 let chars = args.remove(0);
23966 Ok(Expression::Trim(Box::new(crate::expressions::TrimFunc {
23967 this: str_expr,
23968 characters: Some(chars),
23969 position: crate::expressions::TrimPosition::Leading,
23970 sql_standard_syntax: true,
23971 position_explicit: true,
23972 })))
23973 }
23974 _ => Ok(Expression::Function(f)),
23975 },
23976 // RTRIM(str, chars) 2-arg -> TRIM(TRAILING chars FROM str) for Spark/Hive/Databricks/ClickHouse
23977 "RTRIM" if f.args.len() == 2 => match target {
23978 DialectType::Spark
23979 | DialectType::Hive
23980 | DialectType::Databricks
23981 | DialectType::ClickHouse => {
23982 let mut args = f.args;
23983 let str_expr = args.remove(0);
23984 let chars = args.remove(0);
23985 Ok(Expression::Trim(Box::new(crate::expressions::TrimFunc {
23986 this: str_expr,
23987 characters: Some(chars),
23988 position: crate::expressions::TrimPosition::Trailing,
23989 sql_standard_syntax: true,
23990 position_explicit: true,
23991 })))
23992 }
23993 _ => Ok(Expression::Function(f)),
23994 },
23995 // ARRAY_REVERSE(x) -> arrayReverse(x) for ClickHouse
23996 "ARRAY_REVERSE" if f.args.len() == 1 => match target {
23997 DialectType::ClickHouse => {
23998 let mut new_f = *f;
23999 new_f.name = "arrayReverse".to_string();
24000 Ok(Expression::Function(Box::new(new_f)))
24001 }
24002 _ => Ok(Expression::Function(f)),
24003 },
24004 // UUID() -> NEWID() for TSQL
24005 "UUID" if f.args.is_empty() => match target {
24006 DialectType::TSQL | DialectType::Fabric => {
24007 Ok(Expression::Function(Box::new(Function::new(
24008 "NEWID".to_string(),
24009 vec![],
24010 ))))
24011 }
24012 _ => Ok(Expression::Function(f)),
24013 },
24014 // FARM_FINGERPRINT(x) -> farmFingerprint64(x) for ClickHouse, FARMFINGERPRINT64(x) for Redshift
24015 "FARM_FINGERPRINT" if f.args.len() == 1 => match target {
24016 DialectType::ClickHouse => {
24017 let mut new_f = *f;
24018 new_f.name = "farmFingerprint64".to_string();
24019 Ok(Expression::Function(Box::new(new_f)))
24020 }
24021 DialectType::Redshift => {
24022 let mut new_f = *f;
24023 new_f.name = "FARMFINGERPRINT64".to_string();
24024 Ok(Expression::Function(Box::new(new_f)))
24025 }
24026 _ => Ok(Expression::Function(f)),
24027 },
24028 // JSON_KEYS(x) -> JSON_OBJECT_KEYS(x) for Databricks/Spark, OBJECT_KEYS(x) for Snowflake
24029 "JSON_KEYS" => match target {
24030 DialectType::Databricks | DialectType::Spark => {
24031 let mut new_f = *f;
24032 new_f.name = "JSON_OBJECT_KEYS".to_string();
24033 Ok(Expression::Function(Box::new(new_f)))
24034 }
24035 DialectType::Snowflake => {
24036 let mut new_f = *f;
24037 new_f.name = "OBJECT_KEYS".to_string();
24038 Ok(Expression::Function(Box::new(new_f)))
24039 }
24040 _ => Ok(Expression::Function(f)),
24041 },
24042 // WEEKOFYEAR(x) -> WEEKISO(x) for Snowflake
24043 "WEEKOFYEAR" => match target {
24044 DialectType::Snowflake => {
24045 let mut new_f = *f;
24046 new_f.name = "WEEKISO".to_string();
24047 Ok(Expression::Function(Box::new(new_f)))
24048 }
24049 _ => Ok(Expression::Function(f)),
24050 },
24051 // FORMAT(fmt, args...) -> FORMAT_STRING(fmt, args...) for Databricks
24052 "FORMAT"
24053 if f.args.len() >= 2 && matches!(source, DialectType::Generic) =>
24054 {
24055 match target {
24056 DialectType::Databricks | DialectType::Spark => {
24057 let mut new_f = *f;
24058 new_f.name = "FORMAT_STRING".to_string();
24059 Ok(Expression::Function(Box::new(new_f)))
24060 }
24061 _ => Ok(Expression::Function(f)),
24062 }
24063 }
24064 // CONCAT_WS from Generic is null-propagating in SQLGlot fixtures.
24065 // Trino also requires non-separator arguments cast to VARCHAR.
24066 "CONCAT_WS" if f.args.len() >= 2 => {
24067 fn concat_ws_null_case(
24068 args: Vec<Expression>,
24069 else_expr: Expression,
24070 ) -> Expression {
24071 let mut null_checks = args.iter().cloned().map(|arg| {
24072 Expression::IsNull(Box::new(crate::expressions::IsNull {
24073 this: arg,
24074 not: false,
24075 postfix_form: false,
24076 }))
24077 });
24078 let first_null_check = null_checks
24079 .next()
24080 .expect("CONCAT_WS with >= 2 args must yield a null check");
24081 let null_check =
24082 null_checks.fold(first_null_check, |left, right| {
24083 Expression::Or(Box::new(BinaryOp {
24084 left,
24085 right,
24086 left_comments: Vec::new(),
24087 operator_comments: Vec::new(),
24088 trailing_comments: Vec::new(),
24089 inferred_type: None,
24090 }))
24091 });
24092 Expression::Case(Box::new(Case {
24093 operand: None,
24094 whens: vec![(null_check, Expression::Null(Null))],
24095 else_: Some(else_expr),
24096 comments: vec![],
24097 inferred_type: None,
24098 }))
24099 }
24100
24101 match target {
24102 DialectType::Trino
24103 if matches!(source, DialectType::Generic) =>
24104 {
24105 let original_args = f.args.clone();
24106 let mut args = f.args;
24107 let sep = args.remove(0);
24108 let cast_args: Vec<Expression> = args
24109 .into_iter()
24110 .map(|a| {
24111 Expression::Cast(Box::new(Cast {
24112 this: a,
24113 to: DataType::VarChar {
24114 length: None,
24115 parenthesized_length: false,
24116 },
24117 double_colon_syntax: false,
24118 trailing_comments: Vec::new(),
24119 format: None,
24120 default: None,
24121 inferred_type: None,
24122 }))
24123 })
24124 .collect();
24125 let mut new_args = vec![sep];
24126 new_args.extend(cast_args);
24127 let else_expr = Expression::Function(Box::new(
24128 Function::new("CONCAT_WS".to_string(), new_args),
24129 ));
24130 Ok(concat_ws_null_case(original_args, else_expr))
24131 }
24132 DialectType::Presto
24133 | DialectType::Trino
24134 | DialectType::Athena => {
24135 let mut args = f.args;
24136 let sep = args.remove(0);
24137 let cast_args: Vec<Expression> = args
24138 .into_iter()
24139 .map(|a| {
24140 Expression::Cast(Box::new(Cast {
24141 this: a,
24142 to: DataType::VarChar {
24143 length: None,
24144 parenthesized_length: false,
24145 },
24146 double_colon_syntax: false,
24147 trailing_comments: Vec::new(),
24148 format: None,
24149 default: None,
24150 inferred_type: None,
24151 }))
24152 })
24153 .collect();
24154 let mut new_args = vec![sep];
24155 new_args.extend(cast_args);
24156 Ok(Expression::Function(Box::new(Function::new(
24157 "CONCAT_WS".to_string(),
24158 new_args,
24159 ))))
24160 }
24161 DialectType::Spark
24162 | DialectType::Hive
24163 | DialectType::DuckDB
24164 if matches!(source, DialectType::Generic) =>
24165 {
24166 let args = f.args;
24167 let else_expr = Expression::Function(Box::new(
24168 Function::new("CONCAT_WS".to_string(), args.clone()),
24169 ));
24170 Ok(concat_ws_null_case(args, else_expr))
24171 }
24172 _ => Ok(Expression::Function(f)),
24173 }
24174 }
24175 // ARRAY_SLICE(x, start, end) -> SLICE(x, start, end) for Presto/Trino/Databricks, arraySlice for ClickHouse
24176 "ARRAY_SLICE" if f.args.len() >= 2 => match target {
24177 DialectType::DuckDB
24178 if f.args.len() == 3
24179 && matches!(source, DialectType::Snowflake) =>
24180 {
24181 // Snowflake ARRAY_SLICE (0-indexed, exclusive end)
24182 // -> DuckDB ARRAY_SLICE (1-indexed, inclusive end)
24183 let mut args = f.args;
24184 let arr = args.remove(0);
24185 let start = args.remove(0);
24186 let end = args.remove(0);
24187
24188 // CASE WHEN start >= 0 THEN start + 1 ELSE start END
24189 let adjusted_start = Expression::Case(Box::new(Case {
24190 operand: None,
24191 whens: vec![(
24192 Expression::Gte(Box::new(BinaryOp {
24193 left: start.clone(),
24194 right: Expression::number(0),
24195 left_comments: vec![],
24196 operator_comments: vec![],
24197 trailing_comments: vec![],
24198 inferred_type: None,
24199 })),
24200 Expression::Add(Box::new(BinaryOp {
24201 left: start.clone(),
24202 right: Expression::number(1),
24203 left_comments: vec![],
24204 operator_comments: vec![],
24205 trailing_comments: vec![],
24206 inferred_type: None,
24207 })),
24208 )],
24209 else_: Some(start),
24210 comments: vec![],
24211 inferred_type: None,
24212 }));
24213
24214 // CASE WHEN end < 0 THEN end - 1 ELSE end END
24215 let adjusted_end = Expression::Case(Box::new(Case {
24216 operand: None,
24217 whens: vec![(
24218 Expression::Lt(Box::new(BinaryOp {
24219 left: end.clone(),
24220 right: Expression::number(0),
24221 left_comments: vec![],
24222 operator_comments: vec![],
24223 trailing_comments: vec![],
24224 inferred_type: None,
24225 })),
24226 Expression::Sub(Box::new(BinaryOp {
24227 left: end.clone(),
24228 right: Expression::number(1),
24229 left_comments: vec![],
24230 operator_comments: vec![],
24231 trailing_comments: vec![],
24232 inferred_type: None,
24233 })),
24234 )],
24235 else_: Some(end),
24236 comments: vec![],
24237 inferred_type: None,
24238 }));
24239
24240 Ok(Expression::Function(Box::new(Function::new(
24241 "ARRAY_SLICE".to_string(),
24242 vec![arr, adjusted_start, adjusted_end],
24243 ))))
24244 }
24245 DialectType::Presto
24246 | DialectType::Trino
24247 | DialectType::Athena
24248 | DialectType::Databricks
24249 | DialectType::Spark => {
24250 let mut new_f = *f;
24251 new_f.name = "SLICE".to_string();
24252 Ok(Expression::Function(Box::new(new_f)))
24253 }
24254 DialectType::ClickHouse => {
24255 let mut new_f = *f;
24256 new_f.name = "arraySlice".to_string();
24257 Ok(Expression::Function(Box::new(new_f)))
24258 }
24259 _ => Ok(Expression::Function(f)),
24260 },
24261 // ARRAY_PREPEND(arr, x) -> LIST_PREPEND(x, arr) for DuckDB (swap args)
24262 "ARRAY_PREPEND" if f.args.len() == 2 => match target {
24263 DialectType::DuckDB => {
24264 let mut args = f.args;
24265 let arr = args.remove(0);
24266 let val = args.remove(0);
24267 Ok(Expression::Function(Box::new(Function::new(
24268 "LIST_PREPEND".to_string(),
24269 vec![val, arr],
24270 ))))
24271 }
24272 _ => Ok(Expression::Function(f)),
24273 },
24274 // ARRAY_REMOVE(arr, target) -> dialect-specific
24275 "ARRAY_REMOVE" if f.args.len() == 2 => {
24276 match target {
24277 DialectType::DuckDB => {
24278 let mut args = f.args;
24279 let arr = args.remove(0);
24280 let target_val = args.remove(0);
24281 let u_id = crate::expressions::Identifier::new("_u");
24282 // LIST_FILTER(arr, _u -> _u <> target)
24283 let lambda = Expression::Lambda(Box::new(
24284 crate::expressions::LambdaExpr {
24285 parameters: vec![u_id.clone()],
24286 body: Expression::Neq(Box::new(BinaryOp {
24287 left: Expression::Identifier(u_id),
24288 right: target_val,
24289 left_comments: Vec::new(),
24290 operator_comments: Vec::new(),
24291 trailing_comments: Vec::new(),
24292 inferred_type: None,
24293 })),
24294 colon: false,
24295 parameter_types: Vec::new(),
24296 },
24297 ));
24298 Ok(Expression::Function(Box::new(Function::new(
24299 "LIST_FILTER".to_string(),
24300 vec![arr, lambda],
24301 ))))
24302 }
24303 DialectType::ClickHouse => {
24304 let mut args = f.args;
24305 let arr = args.remove(0);
24306 let target_val = args.remove(0);
24307 let u_id = crate::expressions::Identifier::new("_u");
24308 // arrayFilter(_u -> _u <> target, arr)
24309 let lambda = Expression::Lambda(Box::new(
24310 crate::expressions::LambdaExpr {
24311 parameters: vec![u_id.clone()],
24312 body: Expression::Neq(Box::new(BinaryOp {
24313 left: Expression::Identifier(u_id),
24314 right: target_val,
24315 left_comments: Vec::new(),
24316 operator_comments: Vec::new(),
24317 trailing_comments: Vec::new(),
24318 inferred_type: None,
24319 })),
24320 colon: false,
24321 parameter_types: Vec::new(),
24322 },
24323 ));
24324 Ok(Expression::Function(Box::new(Function::new(
24325 "arrayFilter".to_string(),
24326 vec![lambda, arr],
24327 ))))
24328 }
24329 DialectType::BigQuery => {
24330 // ARRAY(SELECT _u FROM UNNEST(the_array) AS _u WHERE _u <> target)
24331 let mut args = f.args;
24332 let arr = args.remove(0);
24333 let target_val = args.remove(0);
24334 let u_id = crate::expressions::Identifier::new("_u");
24335 let u_col = Expression::Column(Box::new(
24336 crate::expressions::Column {
24337 name: u_id.clone(),
24338 table: None,
24339 join_mark: false,
24340 trailing_comments: Vec::new(),
24341 span: None,
24342 inferred_type: None,
24343 },
24344 ));
24345 // UNNEST(the_array) AS _u
24346 let unnest_expr = Expression::Unnest(Box::new(
24347 crate::expressions::UnnestFunc {
24348 this: arr,
24349 expressions: Vec::new(),
24350 with_ordinality: false,
24351 alias: None,
24352 offset_alias: None,
24353 },
24354 ));
24355 let aliased_unnest = Expression::Alias(Box::new(
24356 crate::expressions::Alias {
24357 this: unnest_expr,
24358 alias: u_id.clone(),
24359 column_aliases: Vec::new(),
24360 alias_explicit_as: false,
24361 alias_keyword: None,
24362 pre_alias_comments: Vec::new(),
24363 trailing_comments: Vec::new(),
24364 inferred_type: None,
24365 },
24366 ));
24367 // _u <> target
24368 let where_cond = Expression::Neq(Box::new(BinaryOp {
24369 left: u_col.clone(),
24370 right: target_val,
24371 left_comments: Vec::new(),
24372 operator_comments: Vec::new(),
24373 trailing_comments: Vec::new(),
24374 inferred_type: None,
24375 }));
24376 // SELECT _u FROM UNNEST(the_array) AS _u WHERE _u <> target
24377 let subquery = Expression::Select(Box::new(
24378 crate::expressions::Select::new()
24379 .column(u_col)
24380 .from(aliased_unnest)
24381 .where_(where_cond),
24382 ));
24383 // ARRAY(subquery) -- use ArrayFunc with subquery as single element
24384 Ok(Expression::ArrayFunc(Box::new(
24385 crate::expressions::ArrayConstructor {
24386 expressions: vec![subquery],
24387 bracket_notation: false,
24388 use_list_keyword: false,
24389 },
24390 )))
24391 }
24392 _ => Ok(Expression::Function(f)),
24393 }
24394 }
24395 // PARSE_JSON(str) -> remove for SQLite/Doris (just use the string literal)
24396 "PARSE_JSON" if f.args.len() == 1 => {
24397 match target {
24398 DialectType::SQLite
24399 | DialectType::Doris
24400 | DialectType::MySQL
24401 | DialectType::StarRocks => {
24402 // Strip PARSE_JSON, return the inner argument
24403 Ok(f.args.into_iter().next().unwrap())
24404 }
24405 _ => Ok(Expression::Function(f)),
24406 }
24407 }
24408 // JSON_REMOVE(PARSE_JSON(str), path...) -> for SQLite strip PARSE_JSON
24409 // This is handled by PARSE_JSON stripping above; JSON_REMOVE is passed through
24410 "JSON_REMOVE" => Ok(Expression::Function(f)),
24411 // JSON_SET(PARSE_JSON(str), path, PARSE_JSON(val)) -> for SQLite strip PARSE_JSON
24412 // This is handled by PARSE_JSON stripping above; JSON_SET is passed through
24413 "JSON_SET" => Ok(Expression::Function(f)),
24414 // DECODE(x, search1, result1, ..., default) -> CASE WHEN
24415 // Behavior per search value type:
24416 // NULL literal -> CASE WHEN x IS NULL THEN result
24417 // Literal (number, string, bool) -> CASE WHEN x = literal THEN result
24418 // Non-literal (column, expr) -> CASE WHEN x = search OR (x IS NULL AND search IS NULL) THEN result
24419 "DECODE" if f.args.len() >= 3 => {
24420 // Keep as DECODE for targets that support it natively
24421 let keep_as_decode = matches!(
24422 target,
24423 DialectType::Oracle
24424 | DialectType::Snowflake
24425 | DialectType::Redshift
24426 | DialectType::Teradata
24427 | DialectType::Spark
24428 | DialectType::Databricks
24429 );
24430 if keep_as_decode {
24431 return Ok(Expression::Function(f));
24432 }
24433
24434 let mut args = f.args;
24435 let this_expr = args.remove(0);
24436 let mut pairs = Vec::new();
24437 let mut default = None;
24438 let mut i = 0;
24439 while i + 1 < args.len() {
24440 pairs.push((args[i].clone(), args[i + 1].clone()));
24441 i += 2;
24442 }
24443 if i < args.len() {
24444 default = Some(args[i].clone());
24445 }
24446 // Helper: check if expression is a literal value
24447 fn is_literal(e: &Expression) -> bool {
24448 matches!(
24449 e,
24450 Expression::Literal(_)
24451 | Expression::Boolean(_)
24452 | Expression::Neg(_)
24453 )
24454 }
24455 let whens: Vec<(Expression, Expression)> = pairs
24456 .into_iter()
24457 .map(|(search, result)| {
24458 if matches!(&search, Expression::Null(_)) {
24459 // NULL search -> IS NULL
24460 let condition = Expression::Is(Box::new(BinaryOp {
24461 left: this_expr.clone(),
24462 right: Expression::Null(crate::expressions::Null),
24463 left_comments: Vec::new(),
24464 operator_comments: Vec::new(),
24465 trailing_comments: Vec::new(),
24466 inferred_type: None,
24467 }));
24468 (condition, result)
24469 } else if is_literal(&search) {
24470 // Literal search -> simple equality
24471 let eq = Expression::Eq(Box::new(BinaryOp {
24472 left: this_expr.clone(),
24473 right: search,
24474 left_comments: Vec::new(),
24475 operator_comments: Vec::new(),
24476 trailing_comments: Vec::new(),
24477 inferred_type: None,
24478 }));
24479 (eq, result)
24480 } else {
24481 // Non-literal (column ref, expression) -> null-safe comparison
24482 let needs_paren = matches!(
24483 &search,
24484 Expression::Eq(_)
24485 | Expression::Neq(_)
24486 | Expression::Gt(_)
24487 | Expression::Gte(_)
24488 | Expression::Lt(_)
24489 | Expression::Lte(_)
24490 );
24491 let search_for_eq = if needs_paren {
24492 Expression::Paren(Box::new(
24493 crate::expressions::Paren {
24494 this: search.clone(),
24495 trailing_comments: Vec::new(),
24496 },
24497 ))
24498 } else {
24499 search.clone()
24500 };
24501 let eq = Expression::Eq(Box::new(BinaryOp {
24502 left: this_expr.clone(),
24503 right: search_for_eq,
24504 left_comments: Vec::new(),
24505 operator_comments: Vec::new(),
24506 trailing_comments: Vec::new(),
24507 inferred_type: None,
24508 }));
24509 let search_for_null = if needs_paren {
24510 Expression::Paren(Box::new(
24511 crate::expressions::Paren {
24512 this: search.clone(),
24513 trailing_comments: Vec::new(),
24514 },
24515 ))
24516 } else {
24517 search.clone()
24518 };
24519 let x_is_null = Expression::Is(Box::new(BinaryOp {
24520 left: this_expr.clone(),
24521 right: Expression::Null(crate::expressions::Null),
24522 left_comments: Vec::new(),
24523 operator_comments: Vec::new(),
24524 trailing_comments: Vec::new(),
24525 inferred_type: None,
24526 }));
24527 let s_is_null = Expression::Is(Box::new(BinaryOp {
24528 left: search_for_null,
24529 right: Expression::Null(crate::expressions::Null),
24530 left_comments: Vec::new(),
24531 operator_comments: Vec::new(),
24532 trailing_comments: Vec::new(),
24533 inferred_type: None,
24534 }));
24535 let both_null = Expression::And(Box::new(BinaryOp {
24536 left: x_is_null,
24537 right: s_is_null,
24538 left_comments: Vec::new(),
24539 operator_comments: Vec::new(),
24540 trailing_comments: Vec::new(),
24541 inferred_type: None,
24542 }));
24543 let condition = Expression::Or(Box::new(BinaryOp {
24544 left: eq,
24545 right: Expression::Paren(Box::new(
24546 crate::expressions::Paren {
24547 this: both_null,
24548 trailing_comments: Vec::new(),
24549 },
24550 )),
24551 left_comments: Vec::new(),
24552 operator_comments: Vec::new(),
24553 trailing_comments: Vec::new(),
24554 inferred_type: None,
24555 }));
24556 (condition, result)
24557 }
24558 })
24559 .collect();
24560 Ok(Expression::Case(Box::new(Case {
24561 operand: None,
24562 whens,
24563 else_: default,
24564 comments: Vec::new(),
24565 inferred_type: None,
24566 })))
24567 }
24568 // LEVENSHTEIN(a, b, ...) -> dialect-specific
24569 "LEVENSHTEIN" => {
24570 match target {
24571 DialectType::BigQuery => {
24572 let mut new_f = *f;
24573 new_f.name = "EDIT_DISTANCE".to_string();
24574 Ok(Expression::Function(Box::new(new_f)))
24575 }
24576 DialectType::Drill => {
24577 let mut new_f = *f;
24578 new_f.name = "LEVENSHTEIN_DISTANCE".to_string();
24579 Ok(Expression::Function(Box::new(new_f)))
24580 }
24581 DialectType::PostgreSQL if f.args.len() == 6 => {
24582 // PostgreSQL: LEVENSHTEIN(src, tgt, ins, del, sub, max_d) -> LEVENSHTEIN_LESS_EQUAL
24583 // 2 args: basic, 5 args: with costs, 6 args: with costs + max_distance
24584 let mut new_f = *f;
24585 new_f.name = "LEVENSHTEIN_LESS_EQUAL".to_string();
24586 Ok(Expression::Function(Box::new(new_f)))
24587 }
24588 _ => Ok(Expression::Function(f)),
24589 }
24590 }
24591 // ARRAY_MAX(x) -> arrayMax(x) for ClickHouse, LIST_MAX(x) for DuckDB
24592 "ARRAY_MAX" => {
24593 let name = match target {
24594 DialectType::ClickHouse => "arrayMax",
24595 DialectType::DuckDB => "LIST_MAX",
24596 _ => "ARRAY_MAX",
24597 };
24598 let mut new_f = *f;
24599 new_f.name = name.to_string();
24600 Ok(Expression::Function(Box::new(new_f)))
24601 }
24602 // ARRAY_MIN(x) -> arrayMin(x) for ClickHouse, LIST_MIN(x) for DuckDB
24603 "ARRAY_MIN" => {
24604 let name = match target {
24605 DialectType::ClickHouse => "arrayMin",
24606 DialectType::DuckDB => "LIST_MIN",
24607 _ => "ARRAY_MIN",
24608 };
24609 let mut new_f = *f;
24610 new_f.name = name.to_string();
24611 Ok(Expression::Function(Box::new(new_f)))
24612 }
24613 // JAROWINKLER_SIMILARITY(a, b) -> jaroWinklerSimilarity(UPPER(a), UPPER(b)) for ClickHouse
24614 // -> JARO_WINKLER_SIMILARITY(UPPER(a), UPPER(b)) for DuckDB
24615 "JAROWINKLER_SIMILARITY" if f.args.len() == 2 => {
24616 let mut args = f.args;
24617 let b = args.pop().unwrap();
24618 let a = args.pop().unwrap();
24619 match target {
24620 DialectType::ClickHouse => {
24621 let upper_a = Expression::Upper(Box::new(
24622 crate::expressions::UnaryFunc::new(a),
24623 ));
24624 let upper_b = Expression::Upper(Box::new(
24625 crate::expressions::UnaryFunc::new(b),
24626 ));
24627 Ok(Expression::Function(Box::new(Function::new(
24628 "jaroWinklerSimilarity".to_string(),
24629 vec![upper_a, upper_b],
24630 ))))
24631 }
24632 DialectType::DuckDB => {
24633 let upper_a = Expression::Upper(Box::new(
24634 crate::expressions::UnaryFunc::new(a),
24635 ));
24636 let upper_b = Expression::Upper(Box::new(
24637 crate::expressions::UnaryFunc::new(b),
24638 ));
24639 let score = Expression::Function(Box::new(Function::new(
24640 "JARO_WINKLER_SIMILARITY".to_string(),
24641 vec![upper_a, upper_b],
24642 )));
24643 let scaled = Expression::Mul(Box::new(BinaryOp {
24644 left: score,
24645 right: Expression::number(100),
24646 left_comments: Vec::new(),
24647 operator_comments: Vec::new(),
24648 trailing_comments: Vec::new(),
24649 inferred_type: None,
24650 }));
24651 Ok(Expression::Cast(Box::new(Cast {
24652 this: scaled,
24653 to: DataType::Int {
24654 length: None,
24655 integer_spelling: false,
24656 },
24657 trailing_comments: Vec::new(),
24658 double_colon_syntax: false,
24659 format: None,
24660 default: None,
24661 inferred_type: None,
24662 })))
24663 }
24664 _ => Ok(Expression::Function(Box::new(Function::new(
24665 "JAROWINKLER_SIMILARITY".to_string(),
24666 vec![a, b],
24667 )))),
24668 }
24669 }
24670 // CURRENT_SCHEMAS(x) -> CURRENT_SCHEMAS() for Snowflake (drop arg)
24671 "CURRENT_SCHEMAS" => match target {
24672 DialectType::Snowflake => Ok(Expression::Function(Box::new(
24673 Function::new("CURRENT_SCHEMAS".to_string(), vec![]),
24674 ))),
24675 _ => Ok(Expression::Function(f)),
24676 },
24677 // TRUNC/TRUNCATE (numeric) -> dialect-specific
24678 "TRUNC" | "TRUNCATE" if f.args.len() <= 2 => {
24679 match target {
24680 DialectType::TSQL | DialectType::Fabric => {
24681 // ROUND(x, decimals, 1) - the 1 flag means truncation
24682 let mut args = f.args;
24683 let this = if args.is_empty() {
24684 return Ok(Expression::Function(Box::new(
24685 Function::new("TRUNC".to_string(), args),
24686 )));
24687 } else {
24688 args.remove(0)
24689 };
24690 let decimals = if args.is_empty() {
24691 Expression::Literal(Box::new(Literal::Number(
24692 "0".to_string(),
24693 )))
24694 } else {
24695 args.remove(0)
24696 };
24697 Ok(Expression::Function(Box::new(Function::new(
24698 "ROUND".to_string(),
24699 vec![
24700 this,
24701 decimals,
24702 Expression::Literal(Box::new(Literal::Number(
24703 "1".to_string(),
24704 ))),
24705 ],
24706 ))))
24707 }
24708 DialectType::Presto
24709 | DialectType::Trino
24710 | DialectType::Athena => {
24711 // TRUNCATE(x, decimals)
24712 let mut new_f = *f;
24713 new_f.name = "TRUNCATE".to_string();
24714 Ok(Expression::Function(Box::new(new_f)))
24715 }
24716 DialectType::MySQL
24717 | DialectType::SingleStore
24718 | DialectType::TiDB => {
24719 // TRUNCATE(x, decimals)
24720 let mut new_f = *f;
24721 new_f.name = "TRUNCATE".to_string();
24722 Ok(Expression::Function(Box::new(new_f)))
24723 }
24724 DialectType::DuckDB => {
24725 // DuckDB supports TRUNC(x, decimals) — preserve both args
24726 let mut args = f.args;
24727 // Snowflake fractions_supported: wrap non-INT decimals in CAST(... AS INT)
24728 if args.len() == 2
24729 && matches!(source, DialectType::Snowflake)
24730 {
24731 let decimals = args.remove(1);
24732 let is_int = matches!(&decimals, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)))
24733 || matches!(&decimals, Expression::Cast(c) if matches!(c.to, DataType::Int { .. } | DataType::SmallInt { .. } | DataType::BigInt { .. } | DataType::TinyInt { .. }));
24734 let wrapped = if !is_int {
24735 Expression::Cast(Box::new(
24736 crate::expressions::Cast {
24737 this: decimals,
24738 to: DataType::Int {
24739 length: None,
24740 integer_spelling: false,
24741 },
24742 double_colon_syntax: false,
24743 trailing_comments: Vec::new(),
24744 format: None,
24745 default: None,
24746 inferred_type: None,
24747 },
24748 ))
24749 } else {
24750 decimals
24751 };
24752 args.push(wrapped);
24753 }
24754 Ok(Expression::Function(Box::new(Function::new(
24755 "TRUNC".to_string(),
24756 args,
24757 ))))
24758 }
24759 DialectType::ClickHouse => {
24760 // trunc(x, decimals) - lowercase
24761 let mut new_f = *f;
24762 new_f.name = "trunc".to_string();
24763 Ok(Expression::Function(Box::new(new_f)))
24764 }
24765 DialectType::Spark | DialectType::Databricks => {
24766 // Spark: TRUNC is date-only; numeric TRUNC → CAST(x AS BIGINT)
24767 let this = f.args.into_iter().next().unwrap_or(
24768 Expression::Literal(Box::new(Literal::Number(
24769 "0".to_string(),
24770 ))),
24771 );
24772 Ok(Expression::Cast(Box::new(crate::expressions::Cast {
24773 this,
24774 to: crate::expressions::DataType::BigInt {
24775 length: None,
24776 },
24777 double_colon_syntax: false,
24778 trailing_comments: Vec::new(),
24779 format: None,
24780 default: None,
24781 inferred_type: None,
24782 })))
24783 }
24784 _ => {
24785 // TRUNC(x, decimals) for PostgreSQL, Oracle, Snowflake, etc.
24786 let mut new_f = *f;
24787 new_f.name = "TRUNC".to_string();
24788 Ok(Expression::Function(Box::new(new_f)))
24789 }
24790 }
24791 }
24792 // CURRENT_VERSION() -> VERSION() for most dialects
24793 "CURRENT_VERSION" => match target {
24794 DialectType::Snowflake
24795 | DialectType::Databricks
24796 | DialectType::StarRocks => Ok(Expression::Function(f)),
24797 DialectType::SQLite => {
24798 let mut new_f = *f;
24799 new_f.name = "SQLITE_VERSION".to_string();
24800 Ok(Expression::Function(Box::new(new_f)))
24801 }
24802 _ => {
24803 let mut new_f = *f;
24804 new_f.name = "VERSION".to_string();
24805 Ok(Expression::Function(Box::new(new_f)))
24806 }
24807 },
24808 // ARRAY_REVERSE(x) -> arrayReverse(x) for ClickHouse
24809 "ARRAY_REVERSE" => match target {
24810 DialectType::ClickHouse => {
24811 let mut new_f = *f;
24812 new_f.name = "arrayReverse".to_string();
24813 Ok(Expression::Function(Box::new(new_f)))
24814 }
24815 _ => Ok(Expression::Function(f)),
24816 },
24817 // GENERATE_DATE_ARRAY(start, end[, step]) -> target-specific
24818 "GENERATE_DATE_ARRAY" => {
24819 let mut args = f.args;
24820 if matches!(target, DialectType::BigQuery) {
24821 // BigQuery keeps GENERATE_DATE_ARRAY; add default interval if not present
24822 if args.len() == 2 {
24823 let default_interval = Expression::Interval(Box::new(
24824 crate::expressions::Interval {
24825 this: Some(Expression::Literal(Box::new(
24826 Literal::String("1".to_string()),
24827 ))),
24828 unit: Some(
24829 crate::expressions::IntervalUnitSpec::Simple {
24830 unit: crate::expressions::IntervalUnit::Day,
24831 use_plural: false,
24832 },
24833 ),
24834 },
24835 ));
24836 args.push(default_interval);
24837 }
24838 Ok(Expression::Function(Box::new(Function::new(
24839 "GENERATE_DATE_ARRAY".to_string(),
24840 args,
24841 ))))
24842 } else if matches!(target, DialectType::DuckDB) {
24843 // DuckDB: CAST(GENERATE_SERIES(start, end, step) AS DATE[])
24844 let start = args.get(0).cloned();
24845 let end = args.get(1).cloned();
24846 let step = args.get(2).cloned().or_else(|| {
24847 Some(Expression::Interval(Box::new(
24848 crate::expressions::Interval {
24849 this: Some(Expression::Literal(Box::new(
24850 Literal::String("1".to_string()),
24851 ))),
24852 unit: Some(
24853 crate::expressions::IntervalUnitSpec::Simple {
24854 unit: crate::expressions::IntervalUnit::Day,
24855 use_plural: false,
24856 },
24857 ),
24858 },
24859 )))
24860 });
24861 let gen_series = Expression::GenerateSeries(Box::new(
24862 crate::expressions::GenerateSeries {
24863 start: start.map(Box::new),
24864 end: end.map(Box::new),
24865 step: step.map(Box::new),
24866 is_end_exclusive: None,
24867 },
24868 ));
24869 Ok(Expression::Cast(Box::new(Cast {
24870 this: gen_series,
24871 to: DataType::Array {
24872 element_type: Box::new(DataType::Date),
24873 dimension: None,
24874 },
24875 trailing_comments: vec![],
24876 double_colon_syntax: false,
24877 format: None,
24878 default: None,
24879 inferred_type: None,
24880 })))
24881 } else if matches!(
24882 target,
24883 DialectType::Presto | DialectType::Trino | DialectType::Athena
24884 ) {
24885 // Presto/Trino: SEQUENCE(start, end, interval) with interval normalization
24886 let start = args.get(0).cloned();
24887 let end = args.get(1).cloned();
24888 let step = args.get(2).cloned().or_else(|| {
24889 Some(Expression::Interval(Box::new(
24890 crate::expressions::Interval {
24891 this: Some(Expression::Literal(Box::new(
24892 Literal::String("1".to_string()),
24893 ))),
24894 unit: Some(
24895 crate::expressions::IntervalUnitSpec::Simple {
24896 unit: crate::expressions::IntervalUnit::Day,
24897 use_plural: false,
24898 },
24899 ),
24900 },
24901 )))
24902 });
24903 let gen_series = Expression::GenerateSeries(Box::new(
24904 crate::expressions::GenerateSeries {
24905 start: start.map(Box::new),
24906 end: end.map(Box::new),
24907 step: step.map(Box::new),
24908 is_end_exclusive: None,
24909 },
24910 ));
24911 Ok(gen_series)
24912 } else if matches!(
24913 target,
24914 DialectType::Spark | DialectType::Databricks
24915 ) {
24916 // Spark/Databricks: SEQUENCE(start, end, step) - keep step as-is
24917 let start = args.get(0).cloned();
24918 let end = args.get(1).cloned();
24919 let step = args.get(2).cloned().or_else(|| {
24920 Some(Expression::Interval(Box::new(
24921 crate::expressions::Interval {
24922 this: Some(Expression::Literal(Box::new(
24923 Literal::String("1".to_string()),
24924 ))),
24925 unit: Some(
24926 crate::expressions::IntervalUnitSpec::Simple {
24927 unit: crate::expressions::IntervalUnit::Day,
24928 use_plural: false,
24929 },
24930 ),
24931 },
24932 )))
24933 });
24934 let gen_series = Expression::GenerateSeries(Box::new(
24935 crate::expressions::GenerateSeries {
24936 start: start.map(Box::new),
24937 end: end.map(Box::new),
24938 step: step.map(Box::new),
24939 is_end_exclusive: None,
24940 },
24941 ));
24942 Ok(gen_series)
24943 } else if matches!(target, DialectType::Snowflake) {
24944 // Snowflake: keep as GENERATE_DATE_ARRAY for later transform
24945 if args.len() == 2 {
24946 let default_interval = Expression::Interval(Box::new(
24947 crate::expressions::Interval {
24948 this: Some(Expression::Literal(Box::new(
24949 Literal::String("1".to_string()),
24950 ))),
24951 unit: Some(
24952 crate::expressions::IntervalUnitSpec::Simple {
24953 unit: crate::expressions::IntervalUnit::Day,
24954 use_plural: false,
24955 },
24956 ),
24957 },
24958 ));
24959 args.push(default_interval);
24960 }
24961 Ok(Expression::Function(Box::new(Function::new(
24962 "GENERATE_DATE_ARRAY".to_string(),
24963 args,
24964 ))))
24965 } else if matches!(
24966 target,
24967 DialectType::MySQL
24968 | DialectType::TSQL
24969 | DialectType::Fabric
24970 | DialectType::Redshift
24971 ) {
24972 // MySQL/TSQL/Redshift: keep as GENERATE_DATE_ARRAY for the preprocess
24973 // step (unnest_generate_date_array_using_recursive_cte) to convert to CTE
24974 Ok(Expression::Function(Box::new(Function::new(
24975 "GENERATE_DATE_ARRAY".to_string(),
24976 args,
24977 ))))
24978 } else {
24979 // PostgreSQL/others: convert to GenerateSeries
24980 let start = args.get(0).cloned();
24981 let end = args.get(1).cloned();
24982 let step = args.get(2).cloned().or_else(|| {
24983 Some(Expression::Interval(Box::new(
24984 crate::expressions::Interval {
24985 this: Some(Expression::Literal(Box::new(
24986 Literal::String("1".to_string()),
24987 ))),
24988 unit: Some(
24989 crate::expressions::IntervalUnitSpec::Simple {
24990 unit: crate::expressions::IntervalUnit::Day,
24991 use_plural: false,
24992 },
24993 ),
24994 },
24995 )))
24996 });
24997 Ok(Expression::GenerateSeries(Box::new(
24998 crate::expressions::GenerateSeries {
24999 start: start.map(Box::new),
25000 end: end.map(Box::new),
25001 step: step.map(Box::new),
25002 is_end_exclusive: None,
25003 },
25004 )))
25005 }
25006 }
25007 // ARRAYS_OVERLAP(arr1, arr2) from Snowflake -> DuckDB:
25008 // (arr1 && arr2) OR (ARRAY_LENGTH(arr1) <> LIST_COUNT(arr1) AND ARRAY_LENGTH(arr2) <> LIST_COUNT(arr2))
25009 "ARRAYS_OVERLAP"
25010 if f.args.len() == 2
25011 && matches!(source, DialectType::Snowflake)
25012 && matches!(target, DialectType::DuckDB) =>
25013 {
25014 let mut args = f.args;
25015 let arr1 = args.remove(0);
25016 let arr2 = args.remove(0);
25017
25018 // (arr1 && arr2)
25019 let overlap = Expression::Paren(Box::new(Paren {
25020 this: Expression::ArrayOverlaps(Box::new(BinaryOp {
25021 left: arr1.clone(),
25022 right: arr2.clone(),
25023 left_comments: vec![],
25024 operator_comments: vec![],
25025 trailing_comments: vec![],
25026 inferred_type: None,
25027 })),
25028 trailing_comments: vec![],
25029 }));
25030
25031 // ARRAY_LENGTH(arr1) <> LIST_COUNT(arr1)
25032 let arr1_has_null = Expression::Neq(Box::new(BinaryOp {
25033 left: Expression::Function(Box::new(Function::new(
25034 "ARRAY_LENGTH".to_string(),
25035 vec![arr1.clone()],
25036 ))),
25037 right: Expression::Function(Box::new(Function::new(
25038 "LIST_COUNT".to_string(),
25039 vec![arr1],
25040 ))),
25041 left_comments: vec![],
25042 operator_comments: vec![],
25043 trailing_comments: vec![],
25044 inferred_type: None,
25045 }));
25046
25047 // ARRAY_LENGTH(arr2) <> LIST_COUNT(arr2)
25048 let arr2_has_null = Expression::Neq(Box::new(BinaryOp {
25049 left: Expression::Function(Box::new(Function::new(
25050 "ARRAY_LENGTH".to_string(),
25051 vec![arr2.clone()],
25052 ))),
25053 right: Expression::Function(Box::new(Function::new(
25054 "LIST_COUNT".to_string(),
25055 vec![arr2],
25056 ))),
25057 left_comments: vec![],
25058 operator_comments: vec![],
25059 trailing_comments: vec![],
25060 inferred_type: None,
25061 }));
25062
25063 // (ARRAY_LENGTH(arr1) <> LIST_COUNT(arr1) AND ARRAY_LENGTH(arr2) <> LIST_COUNT(arr2))
25064 let null_check = Expression::Paren(Box::new(Paren {
25065 this: Expression::And(Box::new(BinaryOp {
25066 left: arr1_has_null,
25067 right: arr2_has_null,
25068 left_comments: vec![],
25069 operator_comments: vec![],
25070 trailing_comments: vec![],
25071 inferred_type: None,
25072 })),
25073 trailing_comments: vec![],
25074 }));
25075
25076 // (arr1 && arr2) OR (null_check)
25077 Ok(Expression::Or(Box::new(BinaryOp {
25078 left: overlap,
25079 right: null_check,
25080 left_comments: vec![],
25081 operator_comments: vec![],
25082 trailing_comments: vec![],
25083 inferred_type: None,
25084 })))
25085 }
25086 // ARRAY_INTERSECTION([1, 2], [2, 3]) from Snowflake -> DuckDB:
25087 // Bag semantics using LIST_TRANSFORM/LIST_FILTER with GENERATE_SERIES
25088 "ARRAY_INTERSECTION"
25089 if f.args.len() == 2
25090 && matches!(source, DialectType::Snowflake)
25091 && matches!(target, DialectType::DuckDB) =>
25092 {
25093 let mut args = f.args;
25094 let arr1 = args.remove(0);
25095 let arr2 = args.remove(0);
25096
25097 // Build: arr1 IS NULL
25098 let arr1_is_null = Expression::IsNull(Box::new(IsNull {
25099 this: arr1.clone(),
25100 not: false,
25101 postfix_form: false,
25102 }));
25103 let arr2_is_null = Expression::IsNull(Box::new(IsNull {
25104 this: arr2.clone(),
25105 not: false,
25106 postfix_form: false,
25107 }));
25108 let null_check = Expression::Or(Box::new(BinaryOp {
25109 left: arr1_is_null,
25110 right: arr2_is_null,
25111 left_comments: vec![],
25112 operator_comments: vec![],
25113 trailing_comments: vec![],
25114 inferred_type: None,
25115 }));
25116
25117 // GENERATE_SERIES(1, LENGTH(arr1))
25118 let gen_series = Expression::Function(Box::new(Function::new(
25119 "GENERATE_SERIES".to_string(),
25120 vec![
25121 Expression::number(1),
25122 Expression::Function(Box::new(Function::new(
25123 "LENGTH".to_string(),
25124 vec![arr1.clone()],
25125 ))),
25126 ],
25127 )));
25128
25129 // LIST_ZIP(arr1, GENERATE_SERIES(1, LENGTH(arr1)))
25130 let list_zip = Expression::Function(Box::new(Function::new(
25131 "LIST_ZIP".to_string(),
25132 vec![arr1.clone(), gen_series],
25133 )));
25134
25135 // pair[1] and pair[2]
25136 let pair_col = Expression::column("pair");
25137 let pair_1 = Expression::Subscript(Box::new(
25138 crate::expressions::Subscript {
25139 this: pair_col.clone(),
25140 index: Expression::number(1),
25141 },
25142 ));
25143 let pair_2 = Expression::Subscript(Box::new(
25144 crate::expressions::Subscript {
25145 this: pair_col.clone(),
25146 index: Expression::number(2),
25147 },
25148 ));
25149
25150 // arr1[1:pair[2]]
25151 let arr1_slice = Expression::ArraySlice(Box::new(
25152 crate::expressions::ArraySlice {
25153 this: arr1.clone(),
25154 start: Some(Expression::number(1)),
25155 end: Some(pair_2),
25156 },
25157 ));
25158
25159 // e IS NOT DISTINCT FROM pair[1]
25160 let e_col = Expression::column("e");
25161 let is_not_distinct = Expression::NullSafeEq(Box::new(BinaryOp {
25162 left: e_col.clone(),
25163 right: pair_1.clone(),
25164 left_comments: vec![],
25165 operator_comments: vec![],
25166 trailing_comments: vec![],
25167 inferred_type: None,
25168 }));
25169
25170 // e -> e IS NOT DISTINCT FROM pair[1]
25171 let inner_lambda1 =
25172 Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
25173 parameters: vec![crate::expressions::Identifier::new("e")],
25174 body: is_not_distinct,
25175 colon: false,
25176 parameter_types: vec![],
25177 }));
25178
25179 // LIST_FILTER(arr1[1:pair[2]], e -> e IS NOT DISTINCT FROM pair[1])
25180 let inner_filter1 = Expression::Function(Box::new(Function::new(
25181 "LIST_FILTER".to_string(),
25182 vec![arr1_slice, inner_lambda1],
25183 )));
25184
25185 // LENGTH(LIST_FILTER(arr1[1:pair[2]], ...))
25186 let len1 = Expression::Function(Box::new(Function::new(
25187 "LENGTH".to_string(),
25188 vec![inner_filter1],
25189 )));
25190
25191 // e -> e IS NOT DISTINCT FROM pair[1]
25192 let inner_lambda2 =
25193 Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
25194 parameters: vec![crate::expressions::Identifier::new("e")],
25195 body: Expression::NullSafeEq(Box::new(BinaryOp {
25196 left: e_col,
25197 right: pair_1.clone(),
25198 left_comments: vec![],
25199 operator_comments: vec![],
25200 trailing_comments: vec![],
25201 inferred_type: None,
25202 })),
25203 colon: false,
25204 parameter_types: vec![],
25205 }));
25206
25207 // LIST_FILTER(arr2, e -> e IS NOT DISTINCT FROM pair[1])
25208 let inner_filter2 = Expression::Function(Box::new(Function::new(
25209 "LIST_FILTER".to_string(),
25210 vec![arr2.clone(), inner_lambda2],
25211 )));
25212
25213 // LENGTH(LIST_FILTER(arr2, ...))
25214 let len2 = Expression::Function(Box::new(Function::new(
25215 "LENGTH".to_string(),
25216 vec![inner_filter2],
25217 )));
25218
25219 // LENGTH(...) <= LENGTH(...)
25220 let cond = Expression::Paren(Box::new(Paren {
25221 this: Expression::Lte(Box::new(BinaryOp {
25222 left: len1,
25223 right: len2,
25224 left_comments: vec![],
25225 operator_comments: vec![],
25226 trailing_comments: vec![],
25227 inferred_type: None,
25228 })),
25229 trailing_comments: vec![],
25230 }));
25231
25232 // pair -> (condition)
25233 let filter_lambda =
25234 Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
25235 parameters: vec![crate::expressions::Identifier::new(
25236 "pair",
25237 )],
25238 body: cond,
25239 colon: false,
25240 parameter_types: vec![],
25241 }));
25242
25243 // LIST_FILTER(LIST_ZIP(...), pair -> ...)
25244 let outer_filter = Expression::Function(Box::new(Function::new(
25245 "LIST_FILTER".to_string(),
25246 vec![list_zip, filter_lambda],
25247 )));
25248
25249 // pair -> pair[1]
25250 let transform_lambda =
25251 Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
25252 parameters: vec![crate::expressions::Identifier::new(
25253 "pair",
25254 )],
25255 body: pair_1,
25256 colon: false,
25257 parameter_types: vec![],
25258 }));
25259
25260 // LIST_TRANSFORM(LIST_FILTER(...), pair -> pair[1])
25261 let list_transform = Expression::Function(Box::new(Function::new(
25262 "LIST_TRANSFORM".to_string(),
25263 vec![outer_filter, transform_lambda],
25264 )));
25265
25266 // CASE WHEN arr1 IS NULL OR arr2 IS NULL THEN NULL
25267 // ELSE LIST_TRANSFORM(LIST_FILTER(...), pair -> pair[1])
25268 // END
25269 Ok(Expression::Case(Box::new(Case {
25270 operand: None,
25271 whens: vec![(null_check, Expression::Null(Null))],
25272 else_: Some(list_transform),
25273 comments: vec![],
25274 inferred_type: None,
25275 })))
25276 }
25277 // ARRAY_CONSTRUCT(args) -> Expression::Array for all targets
25278 "ARRAY_CONSTRUCT" => {
25279 if matches!(target, DialectType::Snowflake) {
25280 Ok(Expression::Function(f))
25281 } else {
25282 Ok(Expression::Array(Box::new(crate::expressions::Array {
25283 expressions: f.args,
25284 })))
25285 }
25286 }
25287 // ARRAY(args) function -> Expression::Array for DuckDB/Snowflake/Presto/Trino/Athena
25288 "ARRAY"
25289 if !f.args.iter().any(|a| {
25290 matches!(a, Expression::Select(_) | Expression::Subquery(_))
25291 }) =>
25292 {
25293 match target {
25294 DialectType::DuckDB
25295 | DialectType::Snowflake
25296 | DialectType::Presto
25297 | DialectType::Trino
25298 | DialectType::Athena => {
25299 Ok(Expression::Array(Box::new(crate::expressions::Array {
25300 expressions: f.args,
25301 })))
25302 }
25303 _ => Ok(Expression::Function(f)),
25304 }
25305 }
25306 _ => Ok(Expression::Function(f)),
25307 }
25308 } else if let Expression::AggregateFunction(mut af) = e {
25309 let name = af.name.to_ascii_uppercase();
25310 match name.as_str() {
25311 "ARBITRARY" if af.args.len() == 1 => {
25312 let arg = af.args.into_iter().next().unwrap();
25313 Ok(convert_arbitrary(arg, target))
25314 }
25315 "JSON_ARRAYAGG" => {
25316 match target {
25317 DialectType::PostgreSQL => {
25318 af.name = "JSON_AGG".to_string();
25319 // Add NULLS FIRST to ORDER BY items for PostgreSQL
25320 for ordered in af.order_by.iter_mut() {
25321 if ordered.nulls_first.is_none() {
25322 ordered.nulls_first = Some(true);
25323 }
25324 }
25325 Ok(Expression::AggregateFunction(af))
25326 }
25327 _ => Ok(Expression::AggregateFunction(af)),
25328 }
25329 }
25330 _ => Ok(Expression::AggregateFunction(af)),
25331 }
25332 } else if let Expression::JSONArrayAgg(ja) = e {
25333 // JSONArrayAgg -> JSON_AGG for PostgreSQL, JSON_ARRAYAGG for others
25334 match target {
25335 DialectType::PostgreSQL => {
25336 let mut order_by = Vec::new();
25337 if let Some(order_expr) = ja.order {
25338 if let Expression::OrderBy(ob) = *order_expr {
25339 for mut ordered in ob.expressions {
25340 if ordered.nulls_first.is_none() {
25341 ordered.nulls_first = Some(true);
25342 }
25343 order_by.push(ordered);
25344 }
25345 }
25346 }
25347 Ok(Expression::AggregateFunction(Box::new(
25348 crate::expressions::AggregateFunction {
25349 name: "JSON_AGG".to_string(),
25350 args: vec![*ja.this],
25351 distinct: false,
25352 filter: None,
25353 order_by,
25354 limit: None,
25355 ignore_nulls: None,
25356 inferred_type: None,
25357 },
25358 )))
25359 }
25360 _ => Ok(Expression::JSONArrayAgg(ja)),
25361 }
25362 } else if let Expression::JSONArray(ja) = e {
25363 match target {
25364 DialectType::Snowflake
25365 if ja.null_handling.is_none()
25366 && ja.return_type.is_none()
25367 && ja.strict.is_none() =>
25368 {
25369 let array_construct = Expression::ArrayFunc(Box::new(
25370 crate::expressions::ArrayConstructor {
25371 expressions: ja.expressions,
25372 bracket_notation: false,
25373 use_list_keyword: false,
25374 },
25375 ));
25376 Ok(Expression::Function(Box::new(Function::new(
25377 "TO_VARIANT".to_string(),
25378 vec![array_construct],
25379 ))))
25380 }
25381 _ => Ok(Expression::JSONArray(ja)),
25382 }
25383 } else if let Expression::JsonArray(f) = e {
25384 match target {
25385 DialectType::Snowflake => {
25386 let array_construct = Expression::ArrayFunc(Box::new(
25387 crate::expressions::ArrayConstructor {
25388 expressions: f.expressions,
25389 bracket_notation: false,
25390 use_list_keyword: false,
25391 },
25392 ));
25393 Ok(Expression::Function(Box::new(Function::new(
25394 "TO_VARIANT".to_string(),
25395 vec![array_construct],
25396 ))))
25397 }
25398 _ => Ok(Expression::JsonArray(f)),
25399 }
25400 } else if let Expression::CombinedParameterizedAgg(cpa) = e {
25401 let function_name = match cpa.this.as_ref() {
25402 Expression::Identifier(ident) => Some(ident.name.as_str()),
25403 _ => None,
25404 };
25405 match function_name {
25406 Some(name)
25407 if name.eq_ignore_ascii_case("groupConcat")
25408 && cpa.expressions.len() == 1 =>
25409 {
25410 match target {
25411 DialectType::MySQL | DialectType::SingleStore => {
25412 let this = cpa.expressions[0].clone();
25413 let separator = cpa.params.first().cloned();
25414 Ok(Expression::GroupConcat(Box::new(
25415 crate::expressions::GroupConcatFunc {
25416 this,
25417 separator,
25418 order_by: None,
25419 distinct: false,
25420 filter: None,
25421 limit: None,
25422 inferred_type: None,
25423 },
25424 )))
25425 }
25426 DialectType::DuckDB => Ok(Expression::ListAgg(Box::new({
25427 let this = cpa.expressions[0].clone();
25428 let separator = cpa.params.first().cloned();
25429 crate::expressions::ListAggFunc {
25430 this,
25431 separator,
25432 on_overflow: None,
25433 order_by: None,
25434 distinct: false,
25435 filter: None,
25436 inferred_type: None,
25437 }
25438 }))),
25439 _ => Ok(Expression::CombinedParameterizedAgg(cpa)),
25440 }
25441 }
25442 _ => Ok(Expression::CombinedParameterizedAgg(cpa)),
25443 }
25444 } else if let Expression::ToNumber(tn) = e {
25445 // TO_NUMBER(x) with no format/precision/scale -> CAST(x AS DOUBLE)
25446 let arg = *tn.this;
25447 Ok(Expression::Cast(Box::new(crate::expressions::Cast {
25448 this: arg,
25449 to: crate::expressions::DataType::Double {
25450 precision: None,
25451 scale: None,
25452 },
25453 double_colon_syntax: false,
25454 trailing_comments: Vec::new(),
25455 format: None,
25456 default: None,
25457 inferred_type: None,
25458 })))
25459 } else {
25460 Ok(e)
25461 }
25462 }
25463
25464 Action::RegexpLikeToDuckDB => {
25465 if let Expression::RegexpLike(f) = e {
25466 let mut args = vec![f.this, f.pattern];
25467 if let Some(flags) = f.flags {
25468 args.push(flags);
25469 }
25470 Ok(Expression::Function(Box::new(Function::new(
25471 "REGEXP_MATCHES".to_string(),
25472 args,
25473 ))))
25474 } else {
25475 Ok(e)
25476 }
25477 }
25478 Action::RegexpLikeToTsqlPatindex => {
25479 match e {
25480 Expression::RegexpLike(f) => Ok(Self::build_tsql_regex_patindex_predicate(
25481 f.this, f.pattern, false,
25482 )),
25483 Expression::RegexpILike(f) => Ok(
25484 Self::build_tsql_regex_patindex_predicate(*f.this, *f.expression, true),
25485 ),
25486 _ => Ok(e),
25487 }
25488 }
25489 Action::SimilarToToTsqlLike => match e {
25490 Expression::SimilarTo(f) => {
25491 let like = Expression::Like(Box::new(LikeOp {
25492 left: f.this,
25493 right: f.pattern,
25494 escape: f.escape,
25495 quantifier: None,
25496 inferred_type: None,
25497 }));
25498 if f.not {
25499 Ok(Expression::Not(Box::new(crate::expressions::UnaryOp::new(
25500 like,
25501 ))))
25502 } else {
25503 Ok(like)
25504 }
25505 }
25506 _ => Ok(e),
25507 },
25508 Action::PostgresJsonBuildObjectToJsonObject => match e {
25509 Expression::Function(f) => Ok(Expression::JsonObject(
25510 Self::build_json_object_from_pairs(f.args)
25511 .expect("action selected only for even key/value argument lists"),
25512 )),
25513 _ => Ok(e),
25514 },
25515 Action::PostgresJsonAggToJsonArrayAgg => match e {
25516 Expression::Function(f) if f.args.len() == 1 => {
25517 let mut args = f.args;
25518 Ok(Expression::JsonArrayAgg(Box::new(
25519 crate::expressions::JsonArrayAggFunc {
25520 this: args.remove(0),
25521 order_by: None,
25522 null_handling: None,
25523 filter: None,
25524 },
25525 )))
25526 }
25527 Expression::AggregateFunction(mut af) => {
25528 af.name = "JSON_ARRAYAGG".to_string();
25529 Ok(Expression::AggregateFunction(af))
25530 }
25531 _ => Ok(e),
25532 },
25533 Action::EpochConvert => {
25534 if let Expression::Epoch(f) = e {
25535 let arg = f.this;
25536 let name = match target {
25537 DialectType::Spark | DialectType::Databricks | DialectType::Hive => {
25538 "UNIX_TIMESTAMP"
25539 }
25540 DialectType::Presto | DialectType::Trino => "TO_UNIXTIME",
25541 DialectType::BigQuery => "TIME_TO_UNIX",
25542 _ => "EPOCH",
25543 };
25544 Ok(Expression::Function(Box::new(Function::new(
25545 name.to_string(),
25546 vec![arg],
25547 ))))
25548 } else {
25549 Ok(e)
25550 }
25551 }
25552 Action::EpochMsConvert => {
25553 use crate::expressions::{BinaryOp, Cast};
25554 if let Expression::EpochMs(f) = e {
25555 let arg = f.this;
25556 match target {
25557 DialectType::Spark | DialectType::Databricks => {
25558 Ok(Expression::Function(Box::new(Function::new(
25559 "TIMESTAMP_MILLIS".to_string(),
25560 vec![arg],
25561 ))))
25562 }
25563 DialectType::BigQuery => Ok(Expression::Function(Box::new(
25564 Function::new("TIMESTAMP_MILLIS".to_string(), vec![arg]),
25565 ))),
25566 DialectType::Presto | DialectType::Trino => {
25567 // FROM_UNIXTIME(CAST(x AS DOUBLE) / POW(10, 3))
25568 let cast_arg = Expression::Cast(Box::new(Cast {
25569 this: arg,
25570 to: DataType::Double {
25571 precision: None,
25572 scale: None,
25573 },
25574 trailing_comments: Vec::new(),
25575 double_colon_syntax: false,
25576 format: None,
25577 default: None,
25578 inferred_type: None,
25579 }));
25580 let div = Expression::Div(Box::new(BinaryOp::new(
25581 cast_arg,
25582 Expression::Function(Box::new(Function::new(
25583 "POW".to_string(),
25584 vec![Expression::number(10), Expression::number(3)],
25585 ))),
25586 )));
25587 Ok(Expression::Function(Box::new(Function::new(
25588 "FROM_UNIXTIME".to_string(),
25589 vec![div],
25590 ))))
25591 }
25592 DialectType::MySQL => {
25593 // FROM_UNIXTIME(x / POWER(10, 3))
25594 let div = Expression::Div(Box::new(BinaryOp::new(
25595 arg,
25596 Expression::Function(Box::new(Function::new(
25597 "POWER".to_string(),
25598 vec![Expression::number(10), Expression::number(3)],
25599 ))),
25600 )));
25601 Ok(Expression::Function(Box::new(Function::new(
25602 "FROM_UNIXTIME".to_string(),
25603 vec![div],
25604 ))))
25605 }
25606 DialectType::PostgreSQL | DialectType::Redshift => {
25607 // TO_TIMESTAMP(CAST(x AS DOUBLE PRECISION) / POWER(10, 3))
25608 let cast_arg = Expression::Cast(Box::new(Cast {
25609 this: arg,
25610 to: DataType::Custom {
25611 name: "DOUBLE PRECISION".to_string(),
25612 },
25613 trailing_comments: Vec::new(),
25614 double_colon_syntax: false,
25615 format: None,
25616 default: None,
25617 inferred_type: None,
25618 }));
25619 let div = Expression::Div(Box::new(BinaryOp::new(
25620 cast_arg,
25621 Expression::Function(Box::new(Function::new(
25622 "POWER".to_string(),
25623 vec![Expression::number(10), Expression::number(3)],
25624 ))),
25625 )));
25626 Ok(Expression::Function(Box::new(Function::new(
25627 "TO_TIMESTAMP".to_string(),
25628 vec![div],
25629 ))))
25630 }
25631 DialectType::ClickHouse => {
25632 // fromUnixTimestamp64Milli(CAST(x AS Nullable(Int64)))
25633 let cast_arg = Expression::Cast(Box::new(Cast {
25634 this: arg,
25635 to: DataType::Nullable {
25636 inner: Box::new(DataType::BigInt { length: None }),
25637 },
25638 trailing_comments: Vec::new(),
25639 double_colon_syntax: false,
25640 format: None,
25641 default: None,
25642 inferred_type: None,
25643 }));
25644 Ok(Expression::Function(Box::new(Function::new(
25645 "fromUnixTimestamp64Milli".to_string(),
25646 vec![cast_arg],
25647 ))))
25648 }
25649 _ => Ok(Expression::Function(Box::new(Function::new(
25650 "EPOCH_MS".to_string(),
25651 vec![arg],
25652 )))),
25653 }
25654 } else {
25655 Ok(e)
25656 }
25657 }
25658 Action::TSQLTypeNormalize => {
25659 if let Expression::DataType(dt) = e {
25660 let new_dt = match &dt {
25661 DataType::Custom { name } if name.eq_ignore_ascii_case("MONEY") => {
25662 DataType::Decimal {
25663 precision: Some(15),
25664 scale: Some(4),
25665 }
25666 }
25667 DataType::Custom { name }
25668 if name.eq_ignore_ascii_case("SMALLMONEY") =>
25669 {
25670 DataType::Decimal {
25671 precision: Some(6),
25672 scale: Some(4),
25673 }
25674 }
25675 DataType::Custom { name } if name.eq_ignore_ascii_case("DATETIME2") => {
25676 DataType::Timestamp {
25677 timezone: false,
25678 precision: None,
25679 }
25680 }
25681 DataType::Custom { name } if name.eq_ignore_ascii_case("REAL") => {
25682 DataType::Float {
25683 precision: None,
25684 scale: None,
25685 real_spelling: false,
25686 }
25687 }
25688 DataType::Float {
25689 real_spelling: true,
25690 ..
25691 } => DataType::Float {
25692 precision: None,
25693 scale: None,
25694 real_spelling: false,
25695 },
25696 DataType::Custom { name } if name.eq_ignore_ascii_case("IMAGE") => {
25697 DataType::Custom {
25698 name: "BLOB".to_string(),
25699 }
25700 }
25701 DataType::Custom { name } if name.eq_ignore_ascii_case("BIT") => {
25702 DataType::Boolean
25703 }
25704 DataType::Custom { name }
25705 if name.eq_ignore_ascii_case("ROWVERSION") =>
25706 {
25707 DataType::Custom {
25708 name: "BINARY".to_string(),
25709 }
25710 }
25711 DataType::Custom { name }
25712 if name.eq_ignore_ascii_case("UNIQUEIDENTIFIER") =>
25713 {
25714 match target {
25715 DialectType::Spark
25716 | DialectType::Databricks
25717 | DialectType::Hive => DataType::Custom {
25718 name: "STRING".to_string(),
25719 },
25720 _ => DataType::VarChar {
25721 length: Some(36),
25722 parenthesized_length: true,
25723 },
25724 }
25725 }
25726 DataType::Custom { name }
25727 if name.eq_ignore_ascii_case("DATETIMEOFFSET") =>
25728 {
25729 match target {
25730 DialectType::Spark
25731 | DialectType::Databricks
25732 | DialectType::Hive => DataType::Timestamp {
25733 timezone: false,
25734 precision: None,
25735 },
25736 _ => DataType::Timestamp {
25737 timezone: true,
25738 precision: None,
25739 },
25740 }
25741 }
25742 DataType::Custom { ref name }
25743 if name.len() >= 10
25744 && name[..10].eq_ignore_ascii_case("DATETIME2(") =>
25745 {
25746 // DATETIME2(n) -> TIMESTAMP
25747 DataType::Timestamp {
25748 timezone: false,
25749 precision: None,
25750 }
25751 }
25752 DataType::Custom { ref name }
25753 if name.len() >= 5 && name[..5].eq_ignore_ascii_case("TIME(") =>
25754 {
25755 // TIME(n) -> TIMESTAMP for Spark, keep as TIME for others
25756 match target {
25757 DialectType::Spark
25758 | DialectType::Databricks
25759 | DialectType::Hive => DataType::Timestamp {
25760 timezone: false,
25761 precision: None,
25762 },
25763 _ => return Ok(Expression::DataType(dt)),
25764 }
25765 }
25766 DataType::Custom { ref name }
25767 if name.len() >= 7 && name[..7].eq_ignore_ascii_case("NUMERIC") =>
25768 {
25769 // Parse NUMERIC(p,s) back to Decimal(p,s)
25770 let upper = name.to_ascii_uppercase();
25771 if let Some(inner) = upper
25772 .strip_prefix("NUMERIC(")
25773 .and_then(|s| s.strip_suffix(')'))
25774 {
25775 let parts: Vec<&str> = inner.split(',').collect();
25776 let precision =
25777 parts.first().and_then(|s| s.trim().parse::<u32>().ok());
25778 let scale =
25779 parts.get(1).and_then(|s| s.trim().parse::<u32>().ok());
25780 DataType::Decimal { precision, scale }
25781 } else if upper == "NUMERIC" {
25782 DataType::Decimal {
25783 precision: None,
25784 scale: None,
25785 }
25786 } else {
25787 return Ok(Expression::DataType(dt));
25788 }
25789 }
25790 DataType::Float {
25791 precision: Some(p), ..
25792 } => {
25793 // For Hive/Spark: FLOAT(1-32) -> FLOAT, FLOAT(33+) -> DOUBLE (IEEE 754 boundary)
25794 // For other targets: FLOAT(1-24) -> FLOAT, FLOAT(25+) -> DOUBLE (TSQL boundary)
25795 let boundary = match target {
25796 DialectType::Hive
25797 | DialectType::Spark
25798 | DialectType::Databricks => 32,
25799 _ => 24,
25800 };
25801 if *p <= boundary {
25802 DataType::Float {
25803 precision: None,
25804 scale: None,
25805 real_spelling: false,
25806 }
25807 } else {
25808 DataType::Double {
25809 precision: None,
25810 scale: None,
25811 }
25812 }
25813 }
25814 DataType::TinyInt { .. } => match target {
25815 DialectType::DuckDB => DataType::Custom {
25816 name: "UTINYINT".to_string(),
25817 },
25818 DialectType::Hive
25819 | DialectType::Spark
25820 | DialectType::Databricks => DataType::SmallInt { length: None },
25821 _ => return Ok(Expression::DataType(dt)),
25822 },
25823 // INTEGER -> INT for Spark/Databricks
25824 DataType::Int {
25825 length,
25826 integer_spelling: true,
25827 } => DataType::Int {
25828 length: *length,
25829 integer_spelling: false,
25830 },
25831 _ => return Ok(Expression::DataType(dt)),
25832 };
25833 Ok(Expression::DataType(new_dt))
25834 } else {
25835 Ok(e)
25836 }
25837 }
25838 Action::MySQLSafeDivide => {
25839 use crate::expressions::{BinaryOp, Cast};
25840 if let Expression::Div(op) = e {
25841 let left = op.left;
25842 let right = op.right;
25843 // For SQLite: CAST left as REAL but NO NULLIF wrapping
25844 if matches!(target, DialectType::SQLite) {
25845 let new_left = Expression::Cast(Box::new(Cast {
25846 this: left,
25847 to: DataType::Float {
25848 precision: None,
25849 scale: None,
25850 real_spelling: true,
25851 },
25852 trailing_comments: Vec::new(),
25853 double_colon_syntax: false,
25854 format: None,
25855 default: None,
25856 inferred_type: None,
25857 }));
25858 return Ok(Expression::Div(Box::new(BinaryOp::new(new_left, right))));
25859 }
25860 // Wrap right in NULLIF(right, 0)
25861 let nullif_right = Expression::Function(Box::new(Function::new(
25862 "NULLIF".to_string(),
25863 vec![right, Expression::number(0)],
25864 )));
25865 // For some dialects, also CAST the left side
25866 let new_left = match target {
25867 DialectType::PostgreSQL
25868 | DialectType::Redshift
25869 | DialectType::Teradata
25870 | DialectType::Materialize
25871 | DialectType::RisingWave => Expression::Cast(Box::new(Cast {
25872 this: left,
25873 to: DataType::Custom {
25874 name: "DOUBLE PRECISION".to_string(),
25875 },
25876 trailing_comments: Vec::new(),
25877 double_colon_syntax: false,
25878 format: None,
25879 default: None,
25880 inferred_type: None,
25881 })),
25882 DialectType::Drill
25883 | DialectType::Trino
25884 | DialectType::Presto
25885 | DialectType::Athena => Expression::Cast(Box::new(Cast {
25886 this: left,
25887 to: DataType::Double {
25888 precision: None,
25889 scale: None,
25890 },
25891 trailing_comments: Vec::new(),
25892 double_colon_syntax: false,
25893 format: None,
25894 default: None,
25895 inferred_type: None,
25896 })),
25897 DialectType::TSQL => Expression::Cast(Box::new(Cast {
25898 this: left,
25899 to: DataType::Float {
25900 precision: None,
25901 scale: None,
25902 real_spelling: false,
25903 },
25904 trailing_comments: Vec::new(),
25905 double_colon_syntax: false,
25906 format: None,
25907 default: None,
25908 inferred_type: None,
25909 })),
25910 _ => left,
25911 };
25912 Ok(Expression::Div(Box::new(BinaryOp::new(
25913 new_left,
25914 nullif_right,
25915 ))))
25916 } else {
25917 Ok(e)
25918 }
25919 }
25920 Action::AlterTableRenameStripSchema => {
25921 if let Expression::AlterTable(mut at) = e {
25922 if let Some(crate::expressions::AlterTableAction::RenameTable(
25923 ref mut new_tbl,
25924 )) = at.actions.first_mut()
25925 {
25926 new_tbl.schema = None;
25927 new_tbl.catalog = None;
25928 }
25929 Ok(Expression::AlterTable(at))
25930 } else {
25931 Ok(e)
25932 }
25933 }
25934 Action::NullsOrdering => {
25935 // Fill in the source dialect's implied null ordering default.
25936 // This makes implicit null ordering explicit so the target generator
25937 // can correctly strip or keep it.
25938 //
25939 // Dialect null ordering categories:
25940 // nulls_are_large (Oracle, PostgreSQL, Redshift, Snowflake):
25941 // ASC -> NULLS LAST, DESC -> NULLS FIRST
25942 // nulls_are_small (Spark, Hive, BigQuery, MySQL, Databricks, ClickHouse, etc.):
25943 // ASC -> NULLS FIRST, DESC -> NULLS LAST
25944 // nulls_are_last (DuckDB, Presto, Trino, Dremio, Athena):
25945 // NULLS LAST always (both ASC and DESC)
25946 if let Expression::Ordered(mut o) = e {
25947 let is_asc = !o.desc;
25948
25949 let is_source_nulls_large = matches!(
25950 source,
25951 DialectType::Oracle
25952 | DialectType::PostgreSQL
25953 | DialectType::Redshift
25954 | DialectType::Snowflake
25955 );
25956 let is_source_nulls_last = matches!(
25957 source,
25958 DialectType::DuckDB
25959 | DialectType::Presto
25960 | DialectType::Trino
25961 | DialectType::Dremio
25962 | DialectType::Athena
25963 | DialectType::ClickHouse
25964 | DialectType::Drill
25965 | DialectType::Exasol
25966 | DialectType::DataFusion
25967 );
25968
25969 // Determine target category to check if default matches
25970 let is_target_nulls_large = matches!(
25971 target,
25972 DialectType::Oracle
25973 | DialectType::PostgreSQL
25974 | DialectType::Redshift
25975 | DialectType::Snowflake
25976 );
25977 let is_target_nulls_last = matches!(
25978 target,
25979 DialectType::DuckDB
25980 | DialectType::Presto
25981 | DialectType::Trino
25982 | DialectType::Dremio
25983 | DialectType::Athena
25984 | DialectType::ClickHouse
25985 | DialectType::Drill
25986 | DialectType::Exasol
25987 | DialectType::DataFusion
25988 );
25989
25990 // Compute the implied nulls_first for source
25991 let source_nulls_first = if is_source_nulls_large {
25992 !is_asc // ASC -> NULLS LAST (false), DESC -> NULLS FIRST (true)
25993 } else if is_source_nulls_last {
25994 false // NULLS LAST always
25995 } else {
25996 is_asc // nulls_are_small: ASC -> NULLS FIRST (true), DESC -> NULLS LAST (false)
25997 };
25998
25999 // Compute the target's default
26000 let target_nulls_first = if is_target_nulls_large {
26001 !is_asc
26002 } else if is_target_nulls_last {
26003 false
26004 } else {
26005 is_asc
26006 };
26007
26008 // Only add explicit nulls ordering if source and target defaults differ
26009 if source_nulls_first != target_nulls_first {
26010 o.nulls_first = Some(source_nulls_first);
26011 }
26012 // If they match, leave nulls_first as None so the generator won't output it
26013
26014 Ok(Expression::Ordered(o))
26015 } else {
26016 Ok(e)
26017 }
26018 }
26019 Action::StringAggConvert => {
26020 match e {
26021 Expression::WithinGroup(wg) => {
26022 // STRING_AGG(x, sep) WITHIN GROUP (ORDER BY z) -> target-specific
26023 // Extract args and distinct flag from either Function, AggregateFunction, or StringAgg
26024 let (x_opt, sep_opt, distinct) = match wg.this {
26025 Expression::AggregateFunction(ref af)
26026 if af.name.eq_ignore_ascii_case("STRING_AGG")
26027 && af.args.len() >= 2 =>
26028 {
26029 (
26030 Some(af.args[0].clone()),
26031 Some(af.args[1].clone()),
26032 af.distinct,
26033 )
26034 }
26035 Expression::Function(ref f)
26036 if f.name.eq_ignore_ascii_case("STRING_AGG")
26037 && f.args.len() >= 2 =>
26038 {
26039 (Some(f.args[0].clone()), Some(f.args[1].clone()), false)
26040 }
26041 Expression::StringAgg(ref sa) => {
26042 (Some(sa.this.clone()), sa.separator.clone(), sa.distinct)
26043 }
26044 _ => (None, None, false),
26045 };
26046 if let (Some(x), Some(sep)) = (x_opt, sep_opt) {
26047 let order_by = wg.order_by;
26048
26049 match target {
26050 DialectType::TSQL | DialectType::Fabric => {
26051 // Keep as WithinGroup(StringAgg) for TSQL
26052 Ok(Expression::WithinGroup(Box::new(
26053 crate::expressions::WithinGroup {
26054 this: Expression::StringAgg(Box::new(
26055 crate::expressions::StringAggFunc {
26056 this: x,
26057 separator: Some(sep),
26058 order_by: None, // order_by goes in WithinGroup, not StringAgg
26059 distinct,
26060 filter: None,
26061 limit: None,
26062 inferred_type: None,
26063 },
26064 )),
26065 order_by,
26066 },
26067 )))
26068 }
26069 DialectType::MySQL
26070 | DialectType::SingleStore
26071 | DialectType::Doris
26072 | DialectType::StarRocks => {
26073 // GROUP_CONCAT(x ORDER BY z SEPARATOR sep)
26074 Ok(Expression::GroupConcat(Box::new(
26075 crate::expressions::GroupConcatFunc {
26076 this: x,
26077 separator: Some(sep),
26078 order_by: Some(order_by),
26079 distinct,
26080 filter: None,
26081 limit: None,
26082 inferred_type: None,
26083 },
26084 )))
26085 }
26086 DialectType::SQLite => {
26087 // GROUP_CONCAT(x, sep) - no ORDER BY support
26088 Ok(Expression::GroupConcat(Box::new(
26089 crate::expressions::GroupConcatFunc {
26090 this: x,
26091 separator: Some(sep),
26092 order_by: None,
26093 distinct,
26094 filter: None,
26095 limit: None,
26096 inferred_type: None,
26097 },
26098 )))
26099 }
26100 DialectType::PostgreSQL | DialectType::Redshift => {
26101 // STRING_AGG(x, sep ORDER BY z)
26102 Ok(Expression::StringAgg(Box::new(
26103 crate::expressions::StringAggFunc {
26104 this: x,
26105 separator: Some(sep),
26106 order_by: Some(order_by),
26107 distinct,
26108 filter: None,
26109 limit: None,
26110 inferred_type: None,
26111 },
26112 )))
26113 }
26114 _ => {
26115 // Default: keep as STRING_AGG(x, sep) with ORDER BY inside
26116 Ok(Expression::StringAgg(Box::new(
26117 crate::expressions::StringAggFunc {
26118 this: x,
26119 separator: Some(sep),
26120 order_by: Some(order_by),
26121 distinct,
26122 filter: None,
26123 limit: None,
26124 inferred_type: None,
26125 },
26126 )))
26127 }
26128 }
26129 } else {
26130 Ok(Expression::WithinGroup(wg))
26131 }
26132 }
26133 Expression::StringAgg(sa) => {
26134 match target {
26135 DialectType::MySQL
26136 | DialectType::SingleStore
26137 | DialectType::Doris
26138 | DialectType::StarRocks => {
26139 // STRING_AGG(x, sep) -> GROUP_CONCAT(x SEPARATOR sep)
26140 Ok(Expression::GroupConcat(Box::new(
26141 crate::expressions::GroupConcatFunc {
26142 this: sa.this,
26143 separator: sa.separator,
26144 order_by: sa.order_by,
26145 distinct: sa.distinct,
26146 filter: sa.filter,
26147 limit: None,
26148 inferred_type: None,
26149 },
26150 )))
26151 }
26152 DialectType::SQLite => {
26153 // STRING_AGG(x, sep) -> GROUP_CONCAT(x, sep)
26154 Ok(Expression::GroupConcat(Box::new(
26155 crate::expressions::GroupConcatFunc {
26156 this: sa.this,
26157 separator: sa.separator,
26158 order_by: None, // SQLite doesn't support ORDER BY in GROUP_CONCAT
26159 distinct: sa.distinct,
26160 filter: sa.filter,
26161 limit: None,
26162 inferred_type: None,
26163 },
26164 )))
26165 }
26166 DialectType::Spark | DialectType::Databricks => {
26167 // STRING_AGG(x, sep) -> LISTAGG(x, sep)
26168 Ok(Expression::ListAgg(Box::new(
26169 crate::expressions::ListAggFunc {
26170 this: sa.this,
26171 separator: sa.separator,
26172 on_overflow: None,
26173 order_by: sa.order_by,
26174 distinct: sa.distinct,
26175 filter: None,
26176 inferred_type: None,
26177 },
26178 )))
26179 }
26180 _ => Ok(Expression::StringAgg(sa)),
26181 }
26182 }
26183 _ => Ok(e),
26184 }
26185 }
26186 Action::GroupConcatConvert => {
26187 // Helper to expand CONCAT(a, b, c) -> a || b || c (for PostgreSQL/SQLite)
26188 // or CONCAT(a, b, c) -> a + b + c (for TSQL)
26189 fn expand_concat_to_dpipe(expr: Expression) -> Expression {
26190 if let Expression::Function(ref f) = expr {
26191 if f.name.eq_ignore_ascii_case("CONCAT") && f.args.len() > 1 {
26192 let mut result = f.args[0].clone();
26193 for arg in &f.args[1..] {
26194 result = Expression::Concat(Box::new(BinaryOp {
26195 left: result,
26196 right: arg.clone(),
26197 left_comments: vec![],
26198 operator_comments: vec![],
26199 trailing_comments: vec![],
26200 inferred_type: None,
26201 }));
26202 }
26203 return result;
26204 }
26205 }
26206 expr
26207 }
26208 fn expand_concat_to_plus(expr: Expression) -> Expression {
26209 if let Expression::Function(ref f) = expr {
26210 if f.name.eq_ignore_ascii_case("CONCAT") && f.args.len() > 1 {
26211 let mut result = f.args[0].clone();
26212 for arg in &f.args[1..] {
26213 result = Expression::Add(Box::new(BinaryOp {
26214 left: result,
26215 right: arg.clone(),
26216 left_comments: vec![],
26217 operator_comments: vec![],
26218 trailing_comments: vec![],
26219 inferred_type: None,
26220 }));
26221 }
26222 return result;
26223 }
26224 }
26225 expr
26226 }
26227 // Helper to wrap each arg in CAST(arg AS VARCHAR) for Presto/Trino CONCAT
26228 fn wrap_concat_args_in_varchar_cast(expr: Expression) -> Expression {
26229 if let Expression::Function(ref f) = expr {
26230 if f.name.eq_ignore_ascii_case("CONCAT") && f.args.len() > 1 {
26231 let new_args: Vec<Expression> = f
26232 .args
26233 .iter()
26234 .map(|arg| {
26235 Expression::Cast(Box::new(crate::expressions::Cast {
26236 this: arg.clone(),
26237 to: crate::expressions::DataType::VarChar {
26238 length: None,
26239 parenthesized_length: false,
26240 },
26241 trailing_comments: Vec::new(),
26242 double_colon_syntax: false,
26243 format: None,
26244 default: None,
26245 inferred_type: None,
26246 }))
26247 })
26248 .collect();
26249 return Expression::Function(Box::new(
26250 crate::expressions::Function::new(
26251 "CONCAT".to_string(),
26252 new_args,
26253 ),
26254 ));
26255 }
26256 }
26257 expr
26258 }
26259 if let Expression::GroupConcat(gc) = e {
26260 match target {
26261 DialectType::Presto => {
26262 // GROUP_CONCAT(x [, sep]) -> ARRAY_JOIN(ARRAY_AGG(x), sep)
26263 let sep = gc.separator.unwrap_or(Expression::string(","));
26264 // For multi-arg CONCAT, wrap each arg in CAST(... AS VARCHAR)
26265 let this = wrap_concat_args_in_varchar_cast(gc.this);
26266 let array_agg =
26267 Expression::ArrayAgg(Box::new(crate::expressions::AggFunc {
26268 this,
26269 distinct: gc.distinct,
26270 filter: gc.filter,
26271 order_by: gc.order_by.unwrap_or_default(),
26272 name: None,
26273 ignore_nulls: None,
26274 having_max: None,
26275 limit: None,
26276 inferred_type: None,
26277 }));
26278 Ok(Expression::ArrayJoin(Box::new(
26279 crate::expressions::ArrayJoinFunc {
26280 this: array_agg,
26281 separator: sep,
26282 null_replacement: None,
26283 },
26284 )))
26285 }
26286 DialectType::Trino => {
26287 // GROUP_CONCAT(x [, sep]) -> LISTAGG(x, sep)
26288 let sep = gc.separator.unwrap_or(Expression::string(","));
26289 // For multi-arg CONCAT, wrap each arg in CAST(... AS VARCHAR)
26290 let this = wrap_concat_args_in_varchar_cast(gc.this);
26291 Ok(Expression::ListAgg(Box::new(
26292 crate::expressions::ListAggFunc {
26293 this,
26294 separator: Some(sep),
26295 on_overflow: None,
26296 order_by: gc.order_by,
26297 distinct: gc.distinct,
26298 filter: gc.filter,
26299 inferred_type: None,
26300 },
26301 )))
26302 }
26303 DialectType::PostgreSQL
26304 | DialectType::Redshift
26305 | DialectType::Snowflake
26306 | DialectType::DuckDB
26307 | DialectType::Hive
26308 | DialectType::ClickHouse => {
26309 // GROUP_CONCAT(x [, sep]) -> STRING_AGG(x, sep)
26310 let sep = gc.separator.unwrap_or(Expression::string(","));
26311 // Expand CONCAT(a,b,c) -> a || b || c for || dialects
26312 let this = expand_concat_to_dpipe(gc.this);
26313 // For PostgreSQL, add NULLS LAST for DESC / NULLS FIRST for ASC
26314 let order_by = if target == DialectType::PostgreSQL {
26315 gc.order_by.map(|ords| {
26316 ords.into_iter()
26317 .map(|mut o| {
26318 if o.nulls_first.is_none() {
26319 if o.desc {
26320 o.nulls_first = Some(false);
26321 // NULLS LAST
26322 } else {
26323 o.nulls_first = Some(true);
26324 // NULLS FIRST
26325 }
26326 }
26327 o
26328 })
26329 .collect()
26330 })
26331 } else {
26332 gc.order_by
26333 };
26334 Ok(Expression::StringAgg(Box::new(
26335 crate::expressions::StringAggFunc {
26336 this,
26337 separator: Some(sep),
26338 order_by,
26339 distinct: gc.distinct,
26340 filter: gc.filter,
26341 limit: None,
26342 inferred_type: None,
26343 },
26344 )))
26345 }
26346 DialectType::TSQL => {
26347 // GROUP_CONCAT(x [, sep]) -> STRING_AGG(x, sep) WITHIN GROUP (ORDER BY ...)
26348 // TSQL doesn't support DISTINCT in STRING_AGG
26349 let sep = gc.separator.unwrap_or(Expression::string(","));
26350 // Expand CONCAT(a,b,c) -> a + b + c for TSQL
26351 let this = expand_concat_to_plus(gc.this);
26352 Ok(Expression::StringAgg(Box::new(
26353 crate::expressions::StringAggFunc {
26354 this,
26355 separator: Some(sep),
26356 order_by: gc.order_by,
26357 distinct: false, // TSQL doesn't support DISTINCT in STRING_AGG
26358 filter: gc.filter,
26359 limit: None,
26360 inferred_type: None,
26361 },
26362 )))
26363 }
26364 DialectType::SQLite => {
26365 // GROUP_CONCAT stays as GROUP_CONCAT but ORDER BY is removed
26366 // SQLite GROUP_CONCAT doesn't support ORDER BY
26367 // Expand CONCAT(a,b,c) -> a || b || c
26368 let this = expand_concat_to_dpipe(gc.this);
26369 Ok(Expression::GroupConcat(Box::new(
26370 crate::expressions::GroupConcatFunc {
26371 this,
26372 separator: gc.separator,
26373 order_by: None, // SQLite doesn't support ORDER BY in GROUP_CONCAT
26374 distinct: gc.distinct,
26375 filter: gc.filter,
26376 limit: None,
26377 inferred_type: None,
26378 },
26379 )))
26380 }
26381 DialectType::Spark | DialectType::Databricks => {
26382 // GROUP_CONCAT(x [, sep]) -> LISTAGG(x, sep)
26383 let sep = gc.separator.unwrap_or(Expression::string(","));
26384 Ok(Expression::ListAgg(Box::new(
26385 crate::expressions::ListAggFunc {
26386 this: gc.this,
26387 separator: Some(sep),
26388 on_overflow: None,
26389 order_by: gc.order_by,
26390 distinct: gc.distinct,
26391 filter: None,
26392 inferred_type: None,
26393 },
26394 )))
26395 }
26396 DialectType::MySQL
26397 | DialectType::SingleStore
26398 | DialectType::StarRocks => {
26399 // MySQL GROUP_CONCAT should have explicit SEPARATOR (default ',')
26400 if gc.separator.is_none() {
26401 let mut gc = gc;
26402 gc.separator = Some(Expression::string(","));
26403 Ok(Expression::GroupConcat(gc))
26404 } else {
26405 Ok(Expression::GroupConcat(gc))
26406 }
26407 }
26408 _ => Ok(Expression::GroupConcat(gc)),
26409 }
26410 } else {
26411 Ok(e)
26412 }
26413 }
26414 Action::TempTableHash => {
26415 match e {
26416 Expression::CreateTable(mut ct) => {
26417 // TSQL #table -> TEMPORARY TABLE with # stripped from name
26418 let name = &ct.name.name.name;
26419 if name.starts_with('#') {
26420 ct.name.name.name = name.trim_start_matches('#').to_string();
26421 }
26422 // Set temporary flag
26423 ct.temporary = true;
26424 Ok(Expression::CreateTable(ct))
26425 }
26426 Expression::Table(mut tr) => {
26427 // Strip # from table references
26428 let name = &tr.name.name;
26429 if name.starts_with('#') {
26430 tr.name.name = name.trim_start_matches('#').to_string();
26431 }
26432 Ok(Expression::Table(tr))
26433 }
26434 Expression::DropTable(mut dt) => {
26435 // Strip # from DROP TABLE names
26436 for table_ref in &mut dt.names {
26437 if table_ref.name.name.starts_with('#') {
26438 table_ref.name.name =
26439 table_ref.name.name.trim_start_matches('#').to_string();
26440 }
26441 }
26442 Ok(Expression::DropTable(dt))
26443 }
26444 _ => Ok(e),
26445 }
26446 }
26447 Action::NvlClearOriginal => {
26448 if let Expression::Nvl(mut f) = e {
26449 f.original_name = None;
26450 Ok(Expression::Nvl(f))
26451 } else {
26452 Ok(e)
26453 }
26454 }
26455 Action::HiveCastToTryCast => {
26456 // Convert Hive/Spark CAST to TRY_CAST for targets that support it
26457 if let Expression::Cast(mut c) = e {
26458 // For Spark/Hive -> DuckDB: TIMESTAMP -> TIMESTAMPTZ
26459 // (Spark's TIMESTAMP is always timezone-aware)
26460 if matches!(target, DialectType::DuckDB)
26461 && matches!(source, DialectType::Spark | DialectType::Databricks)
26462 && matches!(
26463 c.to,
26464 DataType::Timestamp {
26465 timezone: false,
26466 ..
26467 }
26468 )
26469 {
26470 c.to = DataType::Custom {
26471 name: "TIMESTAMPTZ".to_string(),
26472 };
26473 }
26474 // For Spark source -> Databricks: VARCHAR/CHAR -> STRING
26475 // Spark parses VARCHAR(n)/CHAR(n) as TEXT, normalize to STRING
26476 if matches!(target, DialectType::Databricks | DialectType::Spark)
26477 && matches!(
26478 source,
26479 DialectType::Spark | DialectType::Databricks | DialectType::Hive
26480 )
26481 && Self::has_varchar_char_type(&c.to)
26482 {
26483 c.to = Self::normalize_varchar_to_string(c.to);
26484 }
26485 Ok(Expression::TryCast(c))
26486 } else {
26487 Ok(e)
26488 }
26489 }
26490 Action::XorExpand => {
26491 // Expand XOR to (a AND NOT b) OR (NOT a AND b) for dialects without XOR keyword
26492 // Snowflake: use BOOLXOR(a, b) instead
26493 if let Expression::Xor(xor) = e {
26494 // Collect all XOR operands
26495 let mut operands = Vec::new();
26496 if let Some(this) = xor.this {
26497 operands.push(*this);
26498 }
26499 if let Some(expr) = xor.expression {
26500 operands.push(*expr);
26501 }
26502 operands.extend(xor.expressions);
26503
26504 // Snowflake: use BOOLXOR(a, b)
26505 if matches!(target, DialectType::Snowflake) && operands.len() == 2 {
26506 let a = operands.remove(0);
26507 let b = operands.remove(0);
26508 return Ok(Expression::Function(Box::new(Function::new(
26509 "BOOLXOR".to_string(),
26510 vec![a, b],
26511 ))));
26512 }
26513
26514 // Helper to build (a AND NOT b) OR (NOT a AND b)
26515 let make_xor = |a: Expression, b: Expression| -> Expression {
26516 let not_b = Expression::Not(Box::new(
26517 crate::expressions::UnaryOp::new(b.clone()),
26518 ));
26519 let not_a = Expression::Not(Box::new(
26520 crate::expressions::UnaryOp::new(a.clone()),
26521 ));
26522 let left_and = Expression::And(Box::new(BinaryOp {
26523 left: a,
26524 right: Expression::Paren(Box::new(Paren {
26525 this: not_b,
26526 trailing_comments: Vec::new(),
26527 })),
26528 left_comments: Vec::new(),
26529 operator_comments: Vec::new(),
26530 trailing_comments: Vec::new(),
26531 inferred_type: None,
26532 }));
26533 let right_and = Expression::And(Box::new(BinaryOp {
26534 left: Expression::Paren(Box::new(Paren {
26535 this: not_a,
26536 trailing_comments: Vec::new(),
26537 })),
26538 right: b,
26539 left_comments: Vec::new(),
26540 operator_comments: Vec::new(),
26541 trailing_comments: Vec::new(),
26542 inferred_type: None,
26543 }));
26544 Expression::Or(Box::new(BinaryOp {
26545 left: Expression::Paren(Box::new(Paren {
26546 this: left_and,
26547 trailing_comments: Vec::new(),
26548 })),
26549 right: Expression::Paren(Box::new(Paren {
26550 this: right_and,
26551 trailing_comments: Vec::new(),
26552 })),
26553 left_comments: Vec::new(),
26554 operator_comments: Vec::new(),
26555 trailing_comments: Vec::new(),
26556 inferred_type: None,
26557 }))
26558 };
26559
26560 if operands.len() >= 2 {
26561 let mut result = make_xor(operands.remove(0), operands.remove(0));
26562 for operand in operands {
26563 result = make_xor(result, operand);
26564 }
26565 Ok(result)
26566 } else if operands.len() == 1 {
26567 Ok(operands.remove(0))
26568 } else {
26569 // No operands - return FALSE (shouldn't happen)
26570 Ok(Expression::Boolean(crate::expressions::BooleanLiteral {
26571 value: false,
26572 }))
26573 }
26574 } else {
26575 Ok(e)
26576 }
26577 }
26578 Action::DatePartUnquote => {
26579 // DATE_PART('month', x) -> DATE_PART(month, x) for Snowflake target
26580 // Convert the quoted string first arg to a bare Column/Identifier
26581 if let Expression::Function(mut f) = e {
26582 if let Some(Expression::Literal(lit)) = f.args.first() {
26583 if let crate::expressions::Literal::String(s) = lit.as_ref() {
26584 let bare_name = s.to_ascii_lowercase();
26585 f.args[0] =
26586 Expression::Column(Box::new(crate::expressions::Column {
26587 name: Identifier::new(bare_name),
26588 table: None,
26589 join_mark: false,
26590 trailing_comments: Vec::new(),
26591 span: None,
26592 inferred_type: None,
26593 }));
26594 }
26595 }
26596 Ok(Expression::Function(f))
26597 } else {
26598 Ok(e)
26599 }
26600 }
26601 Action::ArrayLengthConvert => {
26602 // Extract the argument from the expression
26603 let arg = match e {
26604 Expression::Cardinality(ref f) => f.this.clone(),
26605 Expression::ArrayLength(ref f) => f.this.clone(),
26606 Expression::ArraySize(ref f) => f.this.clone(),
26607 _ => return Ok(e),
26608 };
26609 match target {
26610 DialectType::Spark | DialectType::Databricks | DialectType::Hive => {
26611 Ok(Expression::Function(Box::new(Function::new(
26612 "SIZE".to_string(),
26613 vec![arg],
26614 ))))
26615 }
26616 DialectType::Presto | DialectType::Trino | DialectType::Athena => {
26617 Ok(Expression::Cardinality(Box::new(
26618 crate::expressions::UnaryFunc::new(arg),
26619 )))
26620 }
26621 DialectType::BigQuery => Ok(Expression::ArrayLength(Box::new(
26622 crate::expressions::UnaryFunc::new(arg),
26623 ))),
26624 DialectType::DuckDB => Ok(Expression::ArrayLength(Box::new(
26625 crate::expressions::UnaryFunc::new(arg),
26626 ))),
26627 DialectType::PostgreSQL | DialectType::Redshift => {
26628 // PostgreSQL ARRAY_LENGTH requires dimension arg
26629 Ok(Expression::Function(Box::new(Function::new(
26630 "ARRAY_LENGTH".to_string(),
26631 vec![arg, Expression::number(1)],
26632 ))))
26633 }
26634 DialectType::Snowflake => Ok(Expression::ArraySize(Box::new(
26635 crate::expressions::UnaryFunc::new(arg),
26636 ))),
26637 _ => Ok(e), // Keep original
26638 }
26639 }
26640
26641 Action::JsonExtractToArrow => {
26642 // JSON_EXTRACT(x, path) -> x -> path for SQLite/DuckDB (set arrow_syntax = true)
26643 if let Expression::JsonExtract(mut f) = e {
26644 f.arrow_syntax = true;
26645 // Transform path: convert bracket notation to dot notation
26646 // SQLite strips wildcards, DuckDB preserves them
26647 if let Expression::Literal(ref lit) = f.path {
26648 if let Literal::String(ref s) = lit.as_ref() {
26649 let mut transformed = s.clone();
26650 if matches!(target, DialectType::SQLite) {
26651 transformed = Self::strip_json_wildcards(&transformed);
26652 }
26653 transformed = Self::bracket_to_dot_notation(&transformed);
26654 if transformed != *s {
26655 f.path = Expression::string(&transformed);
26656 }
26657 }
26658 }
26659 Ok(Expression::JsonExtract(f))
26660 } else {
26661 Ok(e)
26662 }
26663 }
26664
26665 Action::JsonExtractToGetJsonObject => {
26666 if let Expression::JsonExtract(f) = e {
26667 if matches!(target, DialectType::PostgreSQL | DialectType::Redshift) {
26668 // JSON_EXTRACT(x, '$.key') -> JSON_EXTRACT_PATH(x, 'key') for PostgreSQL
26669 // Use proper decomposition that handles brackets
26670 let keys: Vec<Expression> = if let Expression::Literal(lit) = f.path {
26671 if let Literal::String(ref s) = lit.as_ref() {
26672 let parts = Self::decompose_json_path(s);
26673 parts.into_iter().map(|k| Expression::string(&k)).collect()
26674 } else {
26675 vec![]
26676 }
26677 } else {
26678 vec![f.path]
26679 };
26680 let func_name = if matches!(target, DialectType::Redshift) {
26681 "JSON_EXTRACT_PATH_TEXT"
26682 } else {
26683 "JSON_EXTRACT_PATH"
26684 };
26685 let mut args = vec![f.this];
26686 args.extend(keys);
26687 Ok(Expression::Function(Box::new(Function::new(
26688 func_name.to_string(),
26689 args,
26690 ))))
26691 } else {
26692 // GET_JSON_OBJECT(x, '$.path') for Hive/Spark
26693 // Convert bracket double quotes to single quotes
26694 let path = if let Expression::Literal(ref lit) = f.path {
26695 if let Literal::String(ref s) = lit.as_ref() {
26696 let normalized = Self::bracket_to_single_quotes(s);
26697 if normalized != *s {
26698 Expression::string(&normalized)
26699 } else {
26700 f.path.clone()
26701 }
26702 } else {
26703 f.path.clone()
26704 }
26705 } else {
26706 f.path.clone()
26707 };
26708 Ok(Expression::Function(Box::new(Function::new(
26709 "GET_JSON_OBJECT".to_string(),
26710 vec![f.this, path],
26711 ))))
26712 }
26713 } else {
26714 Ok(e)
26715 }
26716 }
26717
26718 Action::JsonExtractScalarToGetJsonObject => {
26719 // JSON_EXTRACT_SCALAR(x, '$.path') -> GET_JSON_OBJECT(x, '$.path') for Hive/Spark
26720 if let Expression::JsonExtractScalar(f) = e {
26721 Ok(Expression::Function(Box::new(Function::new(
26722 "GET_JSON_OBJECT".to_string(),
26723 vec![f.this, f.path],
26724 ))))
26725 } else {
26726 Ok(e)
26727 }
26728 }
26729
26730 Action::JsonExtractToTsql => {
26731 // PostgreSQL JSON operators need rooted T-SQL JSON paths. Generic
26732 // JSON_EXTRACT keeps the historical SQLGlot-style query/value fallback.
26733 let (this, path, func_name) = match e {
26734 Expression::JsonExtract(f) => {
26735 let path = Self::normalize_tsql_json_path_expr(f.path, false, false);
26736 if f.arrow_syntax {
26737 (f.this, path, Some("JSON_QUERY"))
26738 } else {
26739 (f.this, path, None)
26740 }
26741 }
26742 Expression::JsonExtractScalar(f) => {
26743 let path = Self::normalize_tsql_json_path_expr(
26744 f.path,
26745 f.hash_arrow_syntax,
26746 f.hash_arrow_syntax,
26747 );
26748 if f.arrow_syntax || f.hash_arrow_syntax {
26749 (f.this, path, Some("JSON_VALUE"))
26750 } else {
26751 (f.this, path, None)
26752 }
26753 }
26754 Expression::JsonExtractPath(f) => {
26755 let path = Self::normalize_tsql_json_path_parts(f.paths);
26756 (f.this, path, Some("JSON_QUERY"))
26757 }
26758 _ => return Ok(e),
26759 };
26760
26761 if let Some(func_name) = func_name {
26762 return Ok(Self::build_tsql_json_function(func_name, this, path));
26763 }
26764
26765 // Transform path: strip wildcards, convert bracket notation to dot notation,
26766 // and make sure the path is rooted for T-SQL JSON functions.
26767 let transformed_path = if let Expression::Literal(ref lit) = path {
26768 if let Literal::String(ref s) = lit.as_ref() {
26769 let stripped = Self::strip_json_wildcards(s);
26770 let dotted = Self::bracket_to_dot_notation(&stripped);
26771 Self::normalize_tsql_json_path_expr(
26772 Expression::string(&dotted),
26773 false,
26774 false,
26775 )
26776 } else {
26777 path.clone()
26778 }
26779 } else {
26780 path
26781 };
26782 let json_query = Expression::Function(Box::new(Function::new(
26783 "JSON_QUERY".to_string(),
26784 vec![this.clone(), transformed_path.clone()],
26785 )));
26786 let json_value = Expression::Function(Box::new(Function::new(
26787 "JSON_VALUE".to_string(),
26788 vec![this, transformed_path],
26789 )));
26790 Ok(Expression::Function(Box::new(Function::new(
26791 "ISNULL".to_string(),
26792 vec![json_query, json_value],
26793 ))))
26794 }
26795
26796 Action::JsonExtractToClickHouse => {
26797 // JSON_EXTRACT/JSON_EXTRACT_SCALAR -> JSONExtractString(x, 'key1', idx, 'key2') for ClickHouse
26798 let (this, path) = match e {
26799 Expression::JsonExtract(f) => (f.this, f.path),
26800 Expression::JsonExtractScalar(f) => (f.this, f.path),
26801 _ => return Ok(e),
26802 };
26803 let args: Vec<Expression> = if let Expression::Literal(lit) = path {
26804 if let Literal::String(ref s) = lit.as_ref() {
26805 let parts = Self::decompose_json_path(s);
26806 let mut result = vec![this];
26807 for part in parts {
26808 // ClickHouse uses 1-based integer indices for array access
26809 if let Ok(idx) = part.parse::<i64>() {
26810 result.push(Expression::number(idx + 1));
26811 } else {
26812 result.push(Expression::string(&part));
26813 }
26814 }
26815 result
26816 } else {
26817 vec![]
26818 }
26819 } else {
26820 vec![this, path]
26821 };
26822 Ok(Expression::Function(Box::new(Function::new(
26823 "JSONExtractString".to_string(),
26824 args,
26825 ))))
26826 }
26827
26828 Action::JsonExtractScalarConvert => {
26829 // JSON_EXTRACT_SCALAR -> target-specific
26830 if let Expression::JsonExtractScalar(f) = e {
26831 match target {
26832 DialectType::PostgreSQL | DialectType::Redshift => {
26833 // JSON_EXTRACT_SCALAR(x, '$.path') -> JSON_EXTRACT_PATH_TEXT(x, 'key1', 'key2')
26834 let keys: Vec<Expression> = if let Expression::Literal(lit) = f.path
26835 {
26836 if let Literal::String(ref s) = lit.as_ref() {
26837 let parts = Self::decompose_json_path(s);
26838 parts.into_iter().map(|k| Expression::string(&k)).collect()
26839 } else {
26840 vec![]
26841 }
26842 } else {
26843 vec![f.path]
26844 };
26845 let mut args = vec![f.this];
26846 args.extend(keys);
26847 Ok(Expression::Function(Box::new(Function::new(
26848 "JSON_EXTRACT_PATH_TEXT".to_string(),
26849 args,
26850 ))))
26851 }
26852 DialectType::Snowflake => {
26853 // JSON_EXTRACT_SCALAR(x, '$.path') -> JSON_EXTRACT_PATH_TEXT(x, 'stripped_path')
26854 let stripped_path = if let Expression::Literal(ref lit) = f.path {
26855 if let Literal::String(ref s) = lit.as_ref() {
26856 let stripped = Self::strip_json_dollar_prefix(s);
26857 Expression::string(&stripped)
26858 } else {
26859 f.path.clone()
26860 }
26861 } else {
26862 f.path
26863 };
26864 Ok(Expression::Function(Box::new(Function::new(
26865 "JSON_EXTRACT_PATH_TEXT".to_string(),
26866 vec![f.this, stripped_path],
26867 ))))
26868 }
26869 DialectType::SQLite | DialectType::DuckDB => {
26870 // JSON_EXTRACT_SCALAR(x, '$.path') -> x ->> '$.path'
26871 Ok(Expression::JsonExtractScalar(Box::new(
26872 crate::expressions::JsonExtractFunc {
26873 this: f.this,
26874 path: f.path,
26875 returning: f.returning,
26876 arrow_syntax: true,
26877 hash_arrow_syntax: false,
26878 wrapper_option: None,
26879 quotes_option: None,
26880 on_scalar_string: false,
26881 on_error: None,
26882 },
26883 )))
26884 }
26885 _ => Ok(Expression::JsonExtractScalar(f)),
26886 }
26887 } else {
26888 Ok(e)
26889 }
26890 }
26891
26892 Action::JsonPathNormalize => {
26893 // Normalize JSON path format for BigQuery, MySQL, etc.
26894 if let Expression::JsonExtract(mut f) = e {
26895 if let Expression::Literal(ref lit) = f.path {
26896 if let Literal::String(ref s) = lit.as_ref() {
26897 let mut normalized = s.clone();
26898 // Convert bracket notation and handle wildcards per dialect
26899 match target {
26900 DialectType::BigQuery => {
26901 // BigQuery strips wildcards and uses single quotes in brackets
26902 normalized = Self::strip_json_wildcards(&normalized);
26903 normalized = Self::bracket_to_single_quotes(&normalized);
26904 }
26905 DialectType::MySQL => {
26906 // MySQL preserves wildcards, converts brackets to dot notation
26907 normalized = Self::bracket_to_dot_notation(&normalized);
26908 }
26909 _ => {}
26910 }
26911 if normalized != *s {
26912 f.path = Expression::string(&normalized);
26913 }
26914 }
26915 }
26916 Ok(Expression::JsonExtract(f))
26917 } else {
26918 Ok(e)
26919 }
26920 }
26921
26922 Action::JsonQueryValueConvert => {
26923 // JsonQuery/JsonValue -> target-specific
26924 let (f, is_query) = match e {
26925 Expression::JsonQuery(f) => (f, true),
26926 Expression::JsonValue(f) => (f, false),
26927 _ => return Ok(e),
26928 };
26929 match target {
26930 DialectType::TSQL | DialectType::Fabric => {
26931 // ISNULL(JSON_QUERY(...), JSON_VALUE(...))
26932 let json_query = Expression::Function(Box::new(Function::new(
26933 "JSON_QUERY".to_string(),
26934 vec![f.this.clone(), f.path.clone()],
26935 )));
26936 let json_value = Expression::Function(Box::new(Function::new(
26937 "JSON_VALUE".to_string(),
26938 vec![f.this, f.path],
26939 )));
26940 Ok(Expression::Function(Box::new(Function::new(
26941 "ISNULL".to_string(),
26942 vec![json_query, json_value],
26943 ))))
26944 }
26945 DialectType::Spark | DialectType::Databricks | DialectType::Hive => {
26946 Ok(Expression::Function(Box::new(Function::new(
26947 "GET_JSON_OBJECT".to_string(),
26948 vec![f.this, f.path],
26949 ))))
26950 }
26951 DialectType::PostgreSQL | DialectType::Redshift => {
26952 Ok(Expression::Function(Box::new(Function::new(
26953 "JSON_EXTRACT_PATH_TEXT".to_string(),
26954 vec![f.this, f.path],
26955 ))))
26956 }
26957 DialectType::DuckDB | DialectType::SQLite => {
26958 // json -> path arrow syntax
26959 Ok(Expression::JsonExtract(Box::new(
26960 crate::expressions::JsonExtractFunc {
26961 this: f.this,
26962 path: f.path,
26963 returning: f.returning,
26964 arrow_syntax: true,
26965 hash_arrow_syntax: false,
26966 wrapper_option: f.wrapper_option,
26967 quotes_option: f.quotes_option,
26968 on_scalar_string: f.on_scalar_string,
26969 on_error: f.on_error,
26970 },
26971 )))
26972 }
26973 DialectType::Snowflake => {
26974 // GET_PATH(PARSE_JSON(json), 'path')
26975 // Strip $. prefix from path
26976 // Only wrap in PARSE_JSON if not already a PARSE_JSON call or ParseJson expression
26977 let json_expr = match &f.this {
26978 Expression::Function(ref inner_f)
26979 if inner_f.name.eq_ignore_ascii_case("PARSE_JSON") =>
26980 {
26981 f.this
26982 }
26983 Expression::ParseJson(_) => {
26984 // Already a ParseJson expression, which generates as PARSE_JSON(...)
26985 f.this
26986 }
26987 _ => Expression::Function(Box::new(Function::new(
26988 "PARSE_JSON".to_string(),
26989 vec![f.this],
26990 ))),
26991 };
26992 let path_str = match &f.path {
26993 Expression::Literal(lit)
26994 if matches!(lit.as_ref(), Literal::String(_)) =>
26995 {
26996 let Literal::String(s) = lit.as_ref() else {
26997 unreachable!()
26998 };
26999 let stripped = s.strip_prefix("$.").unwrap_or(s);
27000 Expression::Literal(Box::new(Literal::String(
27001 stripped.to_string(),
27002 )))
27003 }
27004 other => other.clone(),
27005 };
27006 Ok(Expression::Function(Box::new(Function::new(
27007 "GET_PATH".to_string(),
27008 vec![json_expr, path_str],
27009 ))))
27010 }
27011 _ => {
27012 // Default: keep as JSON_QUERY/JSON_VALUE function
27013 let func_name = if is_query { "JSON_QUERY" } else { "JSON_VALUE" };
27014 Ok(Expression::Function(Box::new(Function::new(
27015 func_name.to_string(),
27016 vec![f.this, f.path],
27017 ))))
27018 }
27019 }
27020 }
27021
27022 Action::JsonLiteralToJsonParse => {
27023 // CAST('x' AS JSON) -> JSON_PARSE('x') for Presto, PARSE_JSON for Snowflake
27024 // Also DuckDB CAST(x AS JSON) -> JSON_PARSE(x) for Trino/Presto/Athena
27025 if let Expression::Cast(c) = e {
27026 let func_name = if matches!(target, DialectType::Snowflake) {
27027 "PARSE_JSON"
27028 } else {
27029 "JSON_PARSE"
27030 };
27031 Ok(Expression::Function(Box::new(Function::new(
27032 func_name.to_string(),
27033 vec![c.this],
27034 ))))
27035 } else {
27036 Ok(e)
27037 }
27038 }
27039
27040 Action::DuckDBCastJsonToVariant => {
27041 if let Expression::Cast(c) = e {
27042 Ok(Expression::Cast(Box::new(Cast {
27043 this: c.this,
27044 to: DataType::Custom {
27045 name: "VARIANT".to_string(),
27046 },
27047 trailing_comments: c.trailing_comments,
27048 double_colon_syntax: false,
27049 format: None,
27050 default: None,
27051 inferred_type: None,
27052 })))
27053 } else {
27054 Ok(e)
27055 }
27056 }
27057
27058 Action::DuckDBTryCastJsonToTryJsonParse => {
27059 // DuckDB TRY_CAST(x AS JSON) -> TRY(JSON_PARSE(x)) for Trino/Presto/Athena
27060 if let Expression::TryCast(c) = e {
27061 let json_parse = Expression::Function(Box::new(Function::new(
27062 "JSON_PARSE".to_string(),
27063 vec![c.this],
27064 )));
27065 Ok(Expression::Function(Box::new(Function::new(
27066 "TRY".to_string(),
27067 vec![json_parse],
27068 ))))
27069 } else {
27070 Ok(e)
27071 }
27072 }
27073
27074 Action::DuckDBJsonFuncToJsonParse => {
27075 // DuckDB json(x) -> JSON_PARSE(x) for Trino/Presto/Athena
27076 if let Expression::Function(f) = e {
27077 let args = f.args;
27078 Ok(Expression::Function(Box::new(Function::new(
27079 "JSON_PARSE".to_string(),
27080 args,
27081 ))))
27082 } else {
27083 Ok(e)
27084 }
27085 }
27086
27087 Action::DuckDBJsonValidToIsJson => {
27088 // DuckDB json_valid(x) -> x IS JSON (SQL:2016 predicate) for Trino/Presto/Athena
27089 if let Expression::Function(mut f) = e {
27090 let arg = f.args.remove(0);
27091 Ok(Expression::IsJson(Box::new(crate::expressions::IsJson {
27092 this: arg,
27093 json_type: None,
27094 unique_keys: None,
27095 negated: false,
27096 })))
27097 } else {
27098 Ok(e)
27099 }
27100 }
27101
27102 Action::AtTimeZoneConvert => {
27103 // AT TIME ZONE -> target-specific conversion
27104 if let Expression::AtTimeZone(atz) = e {
27105 match target {
27106 DialectType::Presto | DialectType::Trino | DialectType::Athena => {
27107 Ok(Expression::Function(Box::new(Function::new(
27108 "AT_TIMEZONE".to_string(),
27109 vec![atz.this, atz.zone],
27110 ))))
27111 }
27112 DialectType::Spark | DialectType::Databricks => {
27113 Ok(Expression::Function(Box::new(Function::new(
27114 "FROM_UTC_TIMESTAMP".to_string(),
27115 vec![atz.this, atz.zone],
27116 ))))
27117 }
27118 DialectType::Snowflake => {
27119 // CONVERT_TIMEZONE('zone', expr)
27120 Ok(Expression::Function(Box::new(Function::new(
27121 "CONVERT_TIMEZONE".to_string(),
27122 vec![atz.zone, atz.this],
27123 ))))
27124 }
27125 DialectType::BigQuery => {
27126 // TIMESTAMP(DATETIME(expr, 'zone'))
27127 let datetime_call = Expression::Function(Box::new(Function::new(
27128 "DATETIME".to_string(),
27129 vec![atz.this, atz.zone],
27130 )));
27131 Ok(Expression::Function(Box::new(Function::new(
27132 "TIMESTAMP".to_string(),
27133 vec![datetime_call],
27134 ))))
27135 }
27136 _ => Ok(Expression::Function(Box::new(Function::new(
27137 "AT_TIMEZONE".to_string(),
27138 vec![atz.this, atz.zone],
27139 )))),
27140 }
27141 } else {
27142 Ok(e)
27143 }
27144 }
27145
27146 Action::DayOfWeekConvert => {
27147 // DAY_OF_WEEK -> ISODOW for DuckDB, ((DAYOFWEEK(x) % 7) + 1) for Spark
27148 if let Expression::DayOfWeek(f) = e {
27149 match target {
27150 DialectType::DuckDB => Ok(Expression::Function(Box::new(
27151 Function::new("ISODOW".to_string(), vec![f.this]),
27152 ))),
27153 DialectType::Spark | DialectType::Databricks => {
27154 // ((DAYOFWEEK(x) % 7) + 1)
27155 let dayofweek = Expression::Function(Box::new(Function::new(
27156 "DAYOFWEEK".to_string(),
27157 vec![f.this],
27158 )));
27159 let modulo = Expression::Mod(Box::new(BinaryOp {
27160 left: dayofweek,
27161 right: Expression::number(7),
27162 left_comments: Vec::new(),
27163 operator_comments: Vec::new(),
27164 trailing_comments: Vec::new(),
27165 inferred_type: None,
27166 }));
27167 let paren_mod = Expression::Paren(Box::new(Paren {
27168 this: modulo,
27169 trailing_comments: Vec::new(),
27170 }));
27171 let add_one = Expression::Add(Box::new(BinaryOp {
27172 left: paren_mod,
27173 right: Expression::number(1),
27174 left_comments: Vec::new(),
27175 operator_comments: Vec::new(),
27176 trailing_comments: Vec::new(),
27177 inferred_type: None,
27178 }));
27179 Ok(Expression::Paren(Box::new(Paren {
27180 this: add_one,
27181 trailing_comments: Vec::new(),
27182 })))
27183 }
27184 _ => Ok(Expression::DayOfWeek(f)),
27185 }
27186 } else {
27187 Ok(e)
27188 }
27189 }
27190
27191 Action::MaxByMinByConvert => {
27192 // MAX_BY -> argMax for ClickHouse, drop 3rd arg for Spark
27193 // MIN_BY -> argMin for ClickHouse, ARG_MIN for DuckDB, drop 3rd arg for Spark/ClickHouse
27194 // Handle both Expression::Function and Expression::AggregateFunction
27195 let (is_max, args) = match &e {
27196 Expression::Function(f) => {
27197 (f.name.eq_ignore_ascii_case("MAX_BY"), f.args.clone())
27198 }
27199 Expression::AggregateFunction(af) => {
27200 (af.name.eq_ignore_ascii_case("MAX_BY"), af.args.clone())
27201 }
27202 _ => return Ok(e),
27203 };
27204 match target {
27205 DialectType::ClickHouse => {
27206 let name = if is_max { "argMax" } else { "argMin" };
27207 let mut args = args;
27208 args.truncate(2);
27209 Ok(Expression::Function(Box::new(Function::new(
27210 name.to_string(),
27211 args,
27212 ))))
27213 }
27214 DialectType::DuckDB => {
27215 let name = if is_max { "ARG_MAX" } else { "ARG_MIN" };
27216 Ok(Expression::Function(Box::new(Function::new(
27217 name.to_string(),
27218 args,
27219 ))))
27220 }
27221 DialectType::Spark | DialectType::Databricks => {
27222 let mut args = args;
27223 args.truncate(2);
27224 let name = if is_max { "MAX_BY" } else { "MIN_BY" };
27225 Ok(Expression::Function(Box::new(Function::new(
27226 name.to_string(),
27227 args,
27228 ))))
27229 }
27230 _ => Ok(e),
27231 }
27232 }
27233
27234 Action::ElementAtConvert => {
27235 // ELEMENT_AT(arr, idx) -> arr[idx] for PostgreSQL, arr[SAFE_ORDINAL(idx)] for BigQuery
27236 let (arr, idx) = if let Expression::ElementAt(bf) = e {
27237 (bf.this, bf.expression)
27238 } else if let Expression::Function(ref f) = e {
27239 if f.args.len() >= 2 {
27240 if let Expression::Function(f) = e {
27241 let mut args = f.args;
27242 let arr = args.remove(0);
27243 let idx = args.remove(0);
27244 (arr, idx)
27245 } else {
27246 unreachable!("outer condition already matched Expression::Function")
27247 }
27248 } else {
27249 return Ok(e);
27250 }
27251 } else {
27252 return Ok(e);
27253 };
27254 match target {
27255 DialectType::PostgreSQL => {
27256 // Wrap array in parens for PostgreSQL: (ARRAY[1,2,3])[4]
27257 let arr_expr = Expression::Paren(Box::new(Paren {
27258 this: arr,
27259 trailing_comments: vec![],
27260 }));
27261 Ok(Expression::Subscript(Box::new(
27262 crate::expressions::Subscript {
27263 this: arr_expr,
27264 index: idx,
27265 },
27266 )))
27267 }
27268 DialectType::BigQuery => {
27269 // BigQuery: convert ARRAY[...] to bare [...] for subscript
27270 let arr_expr = match arr {
27271 Expression::ArrayFunc(af) => Expression::ArrayFunc(Box::new(
27272 crate::expressions::ArrayConstructor {
27273 expressions: af.expressions,
27274 bracket_notation: true,
27275 use_list_keyword: false,
27276 },
27277 )),
27278 other => other,
27279 };
27280 let safe_ordinal = Expression::Function(Box::new(Function::new(
27281 "SAFE_ORDINAL".to_string(),
27282 vec![idx],
27283 )));
27284 Ok(Expression::Subscript(Box::new(
27285 crate::expressions::Subscript {
27286 this: arr_expr,
27287 index: safe_ordinal,
27288 },
27289 )))
27290 }
27291 _ => Ok(Expression::Function(Box::new(Function::new(
27292 "ELEMENT_AT".to_string(),
27293 vec![arr, idx],
27294 )))),
27295 }
27296 }
27297
27298 Action::CurrentUserParens => {
27299 // CURRENT_USER -> CURRENT_USER() for Snowflake
27300 Ok(Expression::Function(Box::new(Function::new(
27301 "CURRENT_USER".to_string(),
27302 vec![],
27303 ))))
27304 }
27305
27306 Action::ArrayAggToCollectList => {
27307 // ARRAY_AGG(x ORDER BY ...) -> COLLECT_LIST(x) for Hive/Spark
27308 // Python sqlglot Hive.arrayagg_sql strips ORDER BY for simple cases
27309 // but preserves it when DISTINCT/IGNORE NULLS/LIMIT are present
27310 match e {
27311 Expression::AggregateFunction(mut af) => {
27312 let is_simple =
27313 !af.distinct && af.ignore_nulls.is_none() && af.limit.is_none();
27314 let args = if af.args.is_empty() {
27315 vec![]
27316 } else {
27317 vec![af.args[0].clone()]
27318 };
27319 af.name = "COLLECT_LIST".to_string();
27320 af.args = args;
27321 if is_simple {
27322 af.order_by = Vec::new();
27323 }
27324 Ok(Expression::AggregateFunction(af))
27325 }
27326 Expression::ArrayAgg(agg) => {
27327 let is_simple =
27328 !agg.distinct && agg.ignore_nulls.is_none() && agg.limit.is_none();
27329 Ok(Expression::AggregateFunction(Box::new(
27330 crate::expressions::AggregateFunction {
27331 name: "COLLECT_LIST".to_string(),
27332 args: vec![agg.this.clone()],
27333 distinct: agg.distinct,
27334 filter: agg.filter.clone(),
27335 order_by: if is_simple {
27336 Vec::new()
27337 } else {
27338 agg.order_by.clone()
27339 },
27340 limit: agg.limit.clone(),
27341 ignore_nulls: agg.ignore_nulls,
27342 inferred_type: None,
27343 },
27344 )))
27345 }
27346 _ => Ok(e),
27347 }
27348 }
27349
27350 Action::ArraySyntaxConvert => {
27351 match e {
27352 // ARRAY[1, 2] (ArrayFunc bracket_notation=false) -> set bracket_notation=true
27353 // so the generator uses dialect-specific output (ARRAY() for Spark, [] for BigQuery)
27354 Expression::ArrayFunc(arr) if !arr.bracket_notation => Ok(
27355 Expression::ArrayFunc(Box::new(crate::expressions::ArrayConstructor {
27356 expressions: arr.expressions,
27357 bracket_notation: true,
27358 use_list_keyword: false,
27359 })),
27360 ),
27361 // ARRAY(y) function style -> ArrayFunc for target dialect
27362 // bracket_notation=true for BigQuery/DuckDB/ClickHouse/StarRocks (output []), false for Presto (output ARRAY[])
27363 Expression::Function(f) if f.name.eq_ignore_ascii_case("ARRAY") => {
27364 let bracket = matches!(
27365 target,
27366 DialectType::BigQuery
27367 | DialectType::DuckDB
27368 | DialectType::Snowflake
27369 | DialectType::ClickHouse
27370 | DialectType::StarRocks
27371 );
27372 Ok(Expression::ArrayFunc(Box::new(
27373 crate::expressions::ArrayConstructor {
27374 expressions: f.args,
27375 bracket_notation: bracket,
27376 use_list_keyword: false,
27377 },
27378 )))
27379 }
27380 _ => Ok(e),
27381 }
27382 }
27383
27384 Action::CastToJsonForSpark => {
27385 // CAST(x AS JSON) -> TO_JSON(x) for Spark
27386 if let Expression::Cast(c) = e {
27387 Ok(Expression::Function(Box::new(Function::new(
27388 "TO_JSON".to_string(),
27389 vec![c.this],
27390 ))))
27391 } else {
27392 Ok(e)
27393 }
27394 }
27395
27396 Action::CastJsonToFromJson => {
27397 // CAST(ParseJson(literal) AS ARRAY/MAP/STRUCT) -> FROM_JSON(literal, type_string) for Spark
27398 if let Expression::Cast(c) = e {
27399 // Extract the string literal from ParseJson
27400 let literal_expr = if let Expression::ParseJson(pj) = c.this {
27401 pj.this
27402 } else {
27403 c.this
27404 };
27405 // Convert the target DataType to Spark's type string format
27406 let type_str = Self::data_type_to_spark_string(&c.to);
27407 Ok(Expression::Function(Box::new(Function::new(
27408 "FROM_JSON".to_string(),
27409 vec![
27410 literal_expr,
27411 Expression::Literal(Box::new(Literal::String(type_str))),
27412 ],
27413 ))))
27414 } else {
27415 Ok(e)
27416 }
27417 }
27418
27419 Action::ToJsonConvert => {
27420 // TO_JSON(x) -> target-specific conversion
27421 if let Expression::ToJson(f) = e {
27422 let arg = f.this;
27423 match target {
27424 DialectType::Presto | DialectType::Trino => {
27425 // JSON_FORMAT(CAST(x AS JSON))
27426 let cast_json = Expression::Cast(Box::new(Cast {
27427 this: arg,
27428 to: DataType::Custom {
27429 name: "JSON".to_string(),
27430 },
27431 trailing_comments: vec![],
27432 double_colon_syntax: false,
27433 format: None,
27434 default: None,
27435 inferred_type: None,
27436 }));
27437 Ok(Expression::Function(Box::new(Function::new(
27438 "JSON_FORMAT".to_string(),
27439 vec![cast_json],
27440 ))))
27441 }
27442 DialectType::BigQuery => Ok(Expression::Function(Box::new(
27443 Function::new("TO_JSON_STRING".to_string(), vec![arg]),
27444 ))),
27445 DialectType::DuckDB => {
27446 // CAST(TO_JSON(x) AS TEXT)
27447 let to_json =
27448 Expression::ToJson(Box::new(crate::expressions::UnaryFunc {
27449 this: arg,
27450 original_name: None,
27451 inferred_type: None,
27452 }));
27453 Ok(Expression::Cast(Box::new(Cast {
27454 this: to_json,
27455 to: DataType::Text,
27456 trailing_comments: vec![],
27457 double_colon_syntax: false,
27458 format: None,
27459 default: None,
27460 inferred_type: None,
27461 })))
27462 }
27463 _ => Ok(Expression::ToJson(Box::new(
27464 crate::expressions::UnaryFunc {
27465 this: arg,
27466 original_name: None,
27467 inferred_type: None,
27468 },
27469 ))),
27470 }
27471 } else {
27472 Ok(e)
27473 }
27474 }
27475
27476 Action::VarianceToClickHouse => {
27477 if let Expression::Variance(f) = e {
27478 Ok(Expression::Function(Box::new(Function::new(
27479 "varSamp".to_string(),
27480 vec![f.this],
27481 ))))
27482 } else {
27483 Ok(e)
27484 }
27485 }
27486
27487 Action::StddevToClickHouse => {
27488 if let Expression::Stddev(f) = e {
27489 Ok(Expression::Function(Box::new(Function::new(
27490 "stddevSamp".to_string(),
27491 vec![f.this],
27492 ))))
27493 } else {
27494 Ok(e)
27495 }
27496 }
27497
27498 Action::ApproxQuantileConvert => {
27499 if let Expression::ApproxQuantile(aq) = e {
27500 let mut args = vec![*aq.this];
27501 if let Some(q) = aq.quantile {
27502 args.push(*q);
27503 }
27504 Ok(Expression::Function(Box::new(Function::new(
27505 "APPROX_PERCENTILE".to_string(),
27506 args,
27507 ))))
27508 } else {
27509 Ok(e)
27510 }
27511 }
27512
27513 Action::DollarParamConvert => {
27514 if let Expression::Parameter(p) = e {
27515 Ok(Expression::Parameter(Box::new(
27516 crate::expressions::Parameter {
27517 name: p.name,
27518 index: p.index,
27519 style: crate::expressions::ParameterStyle::At,
27520 quoted: p.quoted,
27521 string_quoted: p.string_quoted,
27522 expression: p.expression,
27523 },
27524 )))
27525 } else {
27526 Ok(e)
27527 }
27528 }
27529
27530 Action::EscapeStringNormalize => {
27531 if let Expression::Literal(ref lit) = e {
27532 if let Literal::EscapeString(s) = lit.as_ref() {
27533 // Strip prefix (e.g., "e:" or "E:") if present from tokenizer
27534 let stripped = if s.starts_with("e:") || s.starts_with("E:") {
27535 s[2..].to_string()
27536 } else {
27537 s.clone()
27538 };
27539 let normalized = stripped
27540 .replace('\n', "\\n")
27541 .replace('\r', "\\r")
27542 .replace('\t', "\\t");
27543 match target {
27544 DialectType::BigQuery => {
27545 // BigQuery: e'...' -> CAST(b'...' AS STRING)
27546 // Use Raw for the b'...' part to avoid double-escaping
27547 let raw_sql = format!("CAST(b'{}' AS STRING)", normalized);
27548 Ok(Expression::Raw(crate::expressions::Raw { sql: raw_sql }))
27549 }
27550 _ => Ok(Expression::Literal(Box::new(Literal::EscapeString(
27551 normalized,
27552 )))),
27553 }
27554 } else {
27555 Ok(e)
27556 }
27557 } else {
27558 Ok(e)
27559 }
27560 }
27561
27562 Action::StraightJoinCase => {
27563 // straight_join: keep lowercase for DuckDB, quote for MySQL
27564 if let Expression::Column(col) = e {
27565 if col.name.name == "STRAIGHT_JOIN" {
27566 let mut new_col = col;
27567 new_col.name.name = "straight_join".to_string();
27568 if matches!(target, DialectType::MySQL) {
27569 // MySQL: needs quoting since it's a reserved keyword
27570 new_col.name.quoted = true;
27571 }
27572 Ok(Expression::Column(new_col))
27573 } else {
27574 Ok(Expression::Column(col))
27575 }
27576 } else {
27577 Ok(e)
27578 }
27579 }
27580
27581 Action::TablesampleReservoir => {
27582 // TABLESAMPLE -> TABLESAMPLE RESERVOIR for DuckDB
27583 if let Expression::TableSample(mut ts) = e {
27584 if let Some(ref mut sample) = ts.sample {
27585 sample.method = crate::expressions::SampleMethod::Reservoir;
27586 sample.explicit_method = true;
27587 }
27588 Ok(Expression::TableSample(ts))
27589 } else {
27590 Ok(e)
27591 }
27592 }
27593
27594 Action::TablesampleSnowflakeStrip => {
27595 // Strip method and PERCENT for Snowflake target from non-Snowflake source
27596 match e {
27597 Expression::TableSample(mut ts) => {
27598 if let Some(ref mut sample) = ts.sample {
27599 sample.suppress_method_output = true;
27600 sample.unit_after_size = false;
27601 sample.is_percent = false;
27602 }
27603 Ok(Expression::TableSample(ts))
27604 }
27605 Expression::Table(mut t) => {
27606 if let Some(ref mut sample) = t.table_sample {
27607 sample.suppress_method_output = true;
27608 sample.unit_after_size = false;
27609 sample.is_percent = false;
27610 }
27611 Ok(Expression::Table(t))
27612 }
27613 _ => Ok(e),
27614 }
27615 }
27616
27617 Action::FirstToAnyValue => {
27618 // FIRST(col) IGNORE NULLS -> ANY_VALUE(col) for DuckDB
27619 if let Expression::First(mut agg) = e {
27620 agg.ignore_nulls = None;
27621 agg.name = Some("ANY_VALUE".to_string());
27622 Ok(Expression::AnyValue(agg))
27623 } else {
27624 Ok(e)
27625 }
27626 }
27627
27628 Action::ArrayIndexConvert => {
27629 // Subscript index: 1-based to 0-based for BigQuery
27630 if let Expression::Subscript(mut sub) = e {
27631 if let Expression::Literal(ref lit) = sub.index {
27632 if let Literal::Number(ref n) = lit.as_ref() {
27633 if let Ok(val) = n.parse::<i64>() {
27634 sub.index = Expression::Literal(Box::new(Literal::Number(
27635 (val - 1).to_string(),
27636 )));
27637 }
27638 }
27639 }
27640 Ok(Expression::Subscript(sub))
27641 } else {
27642 Ok(e)
27643 }
27644 }
27645
27646 Action::AnyValueIgnoreNulls => {
27647 // ANY_VALUE(x) -> ANY_VALUE(x) IGNORE NULLS for Spark
27648 if let Expression::AnyValue(mut av) = e {
27649 if av.ignore_nulls.is_none() {
27650 av.ignore_nulls = Some(true);
27651 }
27652 Ok(Expression::AnyValue(av))
27653 } else {
27654 Ok(e)
27655 }
27656 }
27657
27658 Action::BigQueryNullsOrdering => {
27659 // BigQuery doesn't support NULLS FIRST/LAST in window function ORDER BY
27660 if let Expression::WindowFunction(mut wf) = e {
27661 for o in &mut wf.over.order_by {
27662 o.nulls_first = None;
27663 }
27664 Ok(Expression::WindowFunction(wf))
27665 } else if let Expression::Ordered(mut o) = e {
27666 o.nulls_first = None;
27667 Ok(Expression::Ordered(o))
27668 } else {
27669 Ok(e)
27670 }
27671 }
27672
27673 Action::SnowflakeFloatProtect => {
27674 // Convert DataType::Float to DataType::Custom("FLOAT") to prevent
27675 // Snowflake's target transform from converting it to DOUBLE.
27676 // Non-Snowflake sources should keep their FLOAT spelling.
27677 if let Expression::DataType(DataType::Float { .. }) = e {
27678 Ok(Expression::DataType(DataType::Custom {
27679 name: "FLOAT".to_string(),
27680 }))
27681 } else {
27682 Ok(e)
27683 }
27684 }
27685
27686 Action::MysqlNullsOrdering => {
27687 // MySQL doesn't support NULLS FIRST/LAST - strip or rewrite
27688 if let Expression::Ordered(mut o) = e {
27689 let nulls_last = o.nulls_first == Some(false);
27690 let desc = o.desc;
27691 // MySQL default: ASC -> NULLS LAST, DESC -> NULLS FIRST
27692 // If requested ordering matches default, just strip NULLS clause
27693 let matches_default = if desc {
27694 // DESC default is NULLS FIRST, so nulls_first=true matches
27695 o.nulls_first == Some(true)
27696 } else {
27697 // ASC default is NULLS LAST, so nulls_first=false matches
27698 nulls_last
27699 };
27700 if matches_default {
27701 o.nulls_first = None;
27702 Ok(Expression::Ordered(o))
27703 } else {
27704 // Need CASE WHEN x IS NULL THEN 0/1 ELSE 0/1 END, x
27705 // For ASC NULLS FIRST: ORDER BY CASE WHEN x IS NULL THEN 0 ELSE 1 END, x ASC
27706 // For DESC NULLS LAST: ORDER BY CASE WHEN x IS NULL THEN 1 ELSE 0 END, x DESC
27707 let null_val = if desc { 1 } else { 0 };
27708 let non_null_val = if desc { 0 } else { 1 };
27709 let _case_expr = Expression::Case(Box::new(Case {
27710 operand: None,
27711 whens: vec![(
27712 Expression::IsNull(Box::new(crate::expressions::IsNull {
27713 this: o.this.clone(),
27714 not: false,
27715 postfix_form: false,
27716 })),
27717 Expression::number(null_val),
27718 )],
27719 else_: Some(Expression::number(non_null_val)),
27720 comments: Vec::new(),
27721 inferred_type: None,
27722 }));
27723 o.nulls_first = None;
27724 // Return a tuple of [case_expr, ordered_expr]
27725 // We need to return both as part of the ORDER BY
27726 // But since transform_recursive processes individual expressions,
27727 // we can't easily add extra ORDER BY items here.
27728 // Instead, strip the nulls_first
27729 o.nulls_first = None;
27730 Ok(Expression::Ordered(o))
27731 }
27732 } else {
27733 Ok(e)
27734 }
27735 }
27736
27737 Action::MysqlNullsLastRewrite => {
27738 // DuckDB -> MySQL: Add CASE WHEN IS NULL THEN 1 ELSE 0 END to ORDER BY
27739 // to simulate NULLS LAST for ASC ordering
27740 if let Expression::WindowFunction(mut wf) = e {
27741 let mut new_order_by = Vec::new();
27742 for o in wf.over.order_by {
27743 if !o.desc {
27744 // ASC: DuckDB has NULLS LAST, MySQL has NULLS FIRST
27745 // Add CASE WHEN expr IS NULL THEN 1 ELSE 0 END before expr
27746 let case_expr = Expression::Case(Box::new(Case {
27747 operand: None,
27748 whens: vec![(
27749 Expression::IsNull(Box::new(crate::expressions::IsNull {
27750 this: o.this.clone(),
27751 not: false,
27752 postfix_form: false,
27753 })),
27754 Expression::Literal(Box::new(Literal::Number(
27755 "1".to_string(),
27756 ))),
27757 )],
27758 else_: Some(Expression::Literal(Box::new(Literal::Number(
27759 "0".to_string(),
27760 )))),
27761 comments: Vec::new(),
27762 inferred_type: None,
27763 }));
27764 new_order_by.push(crate::expressions::Ordered {
27765 this: case_expr,
27766 desc: false,
27767 nulls_first: None,
27768 explicit_asc: false,
27769 with_fill: None,
27770 });
27771 let mut ordered = o;
27772 ordered.nulls_first = None;
27773 new_order_by.push(ordered);
27774 } else {
27775 // DESC: DuckDB has NULLS LAST, MySQL also has NULLS LAST (NULLs smallest in DESC)
27776 // No change needed
27777 let mut ordered = o;
27778 ordered.nulls_first = None;
27779 new_order_by.push(ordered);
27780 }
27781 }
27782 wf.over.order_by = new_order_by;
27783 Ok(Expression::WindowFunction(wf))
27784 } else {
27785 Ok(e)
27786 }
27787 }
27788
27789 Action::RespectNullsConvert => {
27790 // RESPECT NULLS -> strip for SQLite (FIRST_VALUE(c) OVER (...))
27791 if let Expression::WindowFunction(mut wf) = e {
27792 match &mut wf.this {
27793 Expression::FirstValue(ref mut vf) => {
27794 if vf.ignore_nulls == Some(false) {
27795 vf.ignore_nulls = None;
27796 // For SQLite, we'd need to add NULLS LAST to ORDER BY in the OVER clause
27797 // but that's handled by the generator's NULLS ordering
27798 }
27799 }
27800 Expression::LastValue(ref mut vf) => {
27801 if vf.ignore_nulls == Some(false) {
27802 vf.ignore_nulls = None;
27803 }
27804 }
27805 _ => {}
27806 }
27807 Ok(Expression::WindowFunction(wf))
27808 } else {
27809 Ok(e)
27810 }
27811 }
27812
27813 Action::SnowflakeWindowFrameStrip => {
27814 // Strip the default ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
27815 // for FIRST_VALUE/LAST_VALUE/NTH_VALUE when targeting Snowflake
27816 if let Expression::WindowFunction(mut wf) = e {
27817 wf.over.frame = None;
27818 Ok(Expression::WindowFunction(wf))
27819 } else {
27820 Ok(e)
27821 }
27822 }
27823
27824 Action::SnowflakeWindowFrameAdd => {
27825 // Add default ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
27826 // for FIRST_VALUE/LAST_VALUE/NTH_VALUE when transpiling from Snowflake to non-Snowflake
27827 if let Expression::WindowFunction(mut wf) = e {
27828 wf.over.frame = Some(crate::expressions::WindowFrame {
27829 kind: crate::expressions::WindowFrameKind::Rows,
27830 start: crate::expressions::WindowFrameBound::UnboundedPreceding,
27831 end: Some(crate::expressions::WindowFrameBound::UnboundedFollowing),
27832 exclude: None,
27833 kind_text: None,
27834 start_side_text: None,
27835 end_side_text: None,
27836 });
27837 Ok(Expression::WindowFunction(wf))
27838 } else {
27839 Ok(e)
27840 }
27841 }
27842
27843 Action::CreateTableStripComment => {
27844 // Strip COMMENT column constraint, USING, PARTITIONED BY for DuckDB
27845 if let Expression::CreateTable(mut ct) = e {
27846 for col in &mut ct.columns {
27847 col.comment = None;
27848 col.constraints.retain(|c| {
27849 !matches!(c, crate::expressions::ColumnConstraint::Comment(_))
27850 });
27851 // Also remove Comment from constraint_order
27852 col.constraint_order.retain(|c| {
27853 !matches!(c, crate::expressions::ConstraintType::Comment)
27854 });
27855 }
27856 // Strip properties (USING, PARTITIONED BY, etc.)
27857 ct.properties.clear();
27858 Ok(Expression::CreateTable(ct))
27859 } else {
27860 Ok(e)
27861 }
27862 }
27863
27864 Action::AlterTableToSpRename => {
27865 // ALTER TABLE db.t1 RENAME TO db.t2 -> EXEC sp_rename 'db.t1', 't2'
27866 if let Expression::AlterTable(ref at) = e {
27867 if let Some(crate::expressions::AlterTableAction::RenameTable(
27868 ref new_tbl,
27869 )) = at.actions.first()
27870 {
27871 // Build the old table name using TSQL bracket quoting
27872 let old_name = if let Some(ref schema) = at.name.schema {
27873 if at.name.name.quoted || schema.quoted {
27874 format!("[{}].[{}]", schema.name, at.name.name.name)
27875 } else {
27876 format!("{}.{}", schema.name, at.name.name.name)
27877 }
27878 } else {
27879 if at.name.name.quoted {
27880 format!("[{}]", at.name.name.name)
27881 } else {
27882 at.name.name.name.clone()
27883 }
27884 };
27885 let new_name = new_tbl.name.name.clone();
27886 // EXEC sp_rename 'old_name', 'new_name'
27887 let sql = format!("EXEC sp_rename '{}', '{}'", old_name, new_name);
27888 Ok(Expression::Raw(crate::expressions::Raw { sql }))
27889 } else {
27890 Ok(e)
27891 }
27892 } else {
27893 Ok(e)
27894 }
27895 }
27896
27897 Action::SnowflakeIntervalFormat => {
27898 // INTERVAL '2' HOUR -> INTERVAL '2 HOUR' for Snowflake
27899 if let Expression::Interval(mut iv) = e {
27900 if let (Some(Expression::Literal(lit)), Some(ref unit_spec)) =
27901 (&iv.this, &iv.unit)
27902 {
27903 if let Literal::String(ref val) = lit.as_ref() {
27904 let unit_str = match unit_spec {
27905 crate::expressions::IntervalUnitSpec::Simple {
27906 unit, ..
27907 } => match unit {
27908 crate::expressions::IntervalUnit::Year => "YEAR",
27909 crate::expressions::IntervalUnit::Quarter => "QUARTER",
27910 crate::expressions::IntervalUnit::Month => "MONTH",
27911 crate::expressions::IntervalUnit::Week => "WEEK",
27912 crate::expressions::IntervalUnit::Day => "DAY",
27913 crate::expressions::IntervalUnit::Hour => "HOUR",
27914 crate::expressions::IntervalUnit::Minute => "MINUTE",
27915 crate::expressions::IntervalUnit::Second => "SECOND",
27916 crate::expressions::IntervalUnit::Millisecond => {
27917 "MILLISECOND"
27918 }
27919 crate::expressions::IntervalUnit::Microsecond => {
27920 "MICROSECOND"
27921 }
27922 crate::expressions::IntervalUnit::Nanosecond => {
27923 "NANOSECOND"
27924 }
27925 },
27926 _ => "",
27927 };
27928 if !unit_str.is_empty() {
27929 let combined = format!("{} {}", val, unit_str);
27930 iv.this = Some(Expression::Literal(Box::new(Literal::String(
27931 combined,
27932 ))));
27933 iv.unit = None;
27934 }
27935 }
27936 }
27937 Ok(Expression::Interval(iv))
27938 } else {
27939 Ok(e)
27940 }
27941 }
27942
27943 Action::ArrayConcatBracketConvert => {
27944 // Expression::Array/ArrayFunc -> target-specific
27945 // For PostgreSQL: Array -> ArrayFunc (bracket_notation: false)
27946 // For Redshift: Array/ArrayFunc -> Function("ARRAY", args) to produce ARRAY(1, 2) with parens
27947 match e {
27948 Expression::Array(arr) => {
27949 if matches!(target, DialectType::Redshift) {
27950 Ok(Expression::Function(Box::new(Function::new(
27951 "ARRAY".to_string(),
27952 arr.expressions,
27953 ))))
27954 } else {
27955 Ok(Expression::ArrayFunc(Box::new(
27956 crate::expressions::ArrayConstructor {
27957 expressions: arr.expressions,
27958 bracket_notation: false,
27959 use_list_keyword: false,
27960 },
27961 )))
27962 }
27963 }
27964 Expression::ArrayFunc(arr) => {
27965 // Only for Redshift: convert bracket-notation ArrayFunc to Function("ARRAY")
27966 if matches!(target, DialectType::Redshift) {
27967 Ok(Expression::Function(Box::new(Function::new(
27968 "ARRAY".to_string(),
27969 arr.expressions,
27970 ))))
27971 } else {
27972 Ok(Expression::ArrayFunc(arr))
27973 }
27974 }
27975 _ => Ok(e),
27976 }
27977 }
27978
27979 Action::BitAggFloatCast => {
27980 // BIT_OR/BIT_AND/BIT_XOR with float/decimal cast arg -> wrap with ROUND+INT cast for DuckDB
27981 // For FLOAT/DOUBLE/REAL: CAST(ROUND(CAST(val AS type)) AS INT)
27982 // For DECIMAL: CAST(CAST(val AS DECIMAL(p,s)) AS INT)
27983 let int_type = DataType::Int {
27984 length: None,
27985 integer_spelling: false,
27986 };
27987 let wrap_agg = |agg_this: Expression, int_dt: DataType| -> Expression {
27988 if let Expression::Cast(c) = agg_this {
27989 match &c.to {
27990 DataType::Float { .. }
27991 | DataType::Double { .. }
27992 | DataType::Custom { .. } => {
27993 // FLOAT/DOUBLE/REAL: CAST(ROUND(CAST(val AS type)) AS INT)
27994 // Change FLOAT to REAL (Float with real_spelling=true) for DuckDB generator
27995 let inner_type = match &c.to {
27996 DataType::Float {
27997 precision, scale, ..
27998 } => DataType::Float {
27999 precision: *precision,
28000 scale: *scale,
28001 real_spelling: true,
28002 },
28003 other => other.clone(),
28004 };
28005 let inner_cast =
28006 Expression::Cast(Box::new(crate::expressions::Cast {
28007 this: c.this.clone(),
28008 to: inner_type,
28009 trailing_comments: Vec::new(),
28010 double_colon_syntax: false,
28011 format: None,
28012 default: None,
28013 inferred_type: None,
28014 }));
28015 let rounded = Expression::Function(Box::new(Function::new(
28016 "ROUND".to_string(),
28017 vec![inner_cast],
28018 )));
28019 Expression::Cast(Box::new(crate::expressions::Cast {
28020 this: rounded,
28021 to: int_dt,
28022 trailing_comments: Vec::new(),
28023 double_colon_syntax: false,
28024 format: None,
28025 default: None,
28026 inferred_type: None,
28027 }))
28028 }
28029 DataType::Decimal { .. } => {
28030 // DECIMAL: CAST(CAST(val AS DECIMAL(p,s)) AS INT)
28031 Expression::Cast(Box::new(crate::expressions::Cast {
28032 this: Expression::Cast(c),
28033 to: int_dt,
28034 trailing_comments: Vec::new(),
28035 double_colon_syntax: false,
28036 format: None,
28037 default: None,
28038 inferred_type: None,
28039 }))
28040 }
28041 _ => Expression::Cast(c),
28042 }
28043 } else {
28044 agg_this
28045 }
28046 };
28047 match e {
28048 Expression::BitwiseOrAgg(mut f) => {
28049 f.this = wrap_agg(f.this, int_type);
28050 Ok(Expression::BitwiseOrAgg(f))
28051 }
28052 Expression::BitwiseAndAgg(mut f) => {
28053 let int_type = DataType::Int {
28054 length: None,
28055 integer_spelling: false,
28056 };
28057 f.this = wrap_agg(f.this, int_type);
28058 Ok(Expression::BitwiseAndAgg(f))
28059 }
28060 Expression::BitwiseXorAgg(mut f) => {
28061 let int_type = DataType::Int {
28062 length: None,
28063 integer_spelling: false,
28064 };
28065 f.this = wrap_agg(f.this, int_type);
28066 Ok(Expression::BitwiseXorAgg(f))
28067 }
28068 _ => Ok(e),
28069 }
28070 }
28071
28072 Action::BitAggSnowflakeRename => {
28073 // BIT_OR -> BITORAGG, BIT_AND -> BITANDAGG, BIT_XOR -> BITXORAGG for Snowflake
28074 match e {
28075 Expression::BitwiseOrAgg(f) => Ok(Expression::Function(Box::new(
28076 Function::new("BITORAGG".to_string(), vec![f.this]),
28077 ))),
28078 Expression::BitwiseAndAgg(f) => Ok(Expression::Function(Box::new(
28079 Function::new("BITANDAGG".to_string(), vec![f.this]),
28080 ))),
28081 Expression::BitwiseXorAgg(f) => Ok(Expression::Function(Box::new(
28082 Function::new("BITXORAGG".to_string(), vec![f.this]),
28083 ))),
28084 _ => Ok(e),
28085 }
28086 }
28087
28088 Action::StrftimeCastTimestamp => {
28089 // CAST(x AS TIMESTAMP) -> CAST(x AS TIMESTAMP_NTZ) for Spark
28090 if let Expression::Cast(mut c) = e {
28091 if matches!(
28092 c.to,
28093 DataType::Timestamp {
28094 timezone: false,
28095 ..
28096 }
28097 ) {
28098 c.to = DataType::Custom {
28099 name: "TIMESTAMP_NTZ".to_string(),
28100 };
28101 }
28102 Ok(Expression::Cast(c))
28103 } else {
28104 Ok(e)
28105 }
28106 }
28107
28108 Action::DecimalDefaultPrecision => {
28109 // DECIMAL without precision -> DECIMAL(18, 3) for Snowflake
28110 if let Expression::Cast(mut c) = e {
28111 if matches!(
28112 c.to,
28113 DataType::Decimal {
28114 precision: None,
28115 ..
28116 }
28117 ) {
28118 c.to = DataType::Decimal {
28119 precision: Some(18),
28120 scale: Some(3),
28121 };
28122 }
28123 Ok(Expression::Cast(c))
28124 } else {
28125 Ok(e)
28126 }
28127 }
28128
28129 Action::FilterToIff => {
28130 // FILTER(WHERE cond) -> rewrite aggregate: AGG(IFF(cond, val, NULL))
28131 if let Expression::Filter(f) = e {
28132 let condition = *f.expression;
28133 let agg = *f.this;
28134 // Strip WHERE from condition
28135 let cond = match condition {
28136 Expression::Where(w) => w.this,
28137 other => other,
28138 };
28139 // Extract the aggregate function and its argument
28140 // We want AVG(IFF(condition, x, NULL))
28141 match agg {
28142 Expression::Function(mut func) => {
28143 if !func.args.is_empty() {
28144 let orig_arg = func.args[0].clone();
28145 let iff_call = Expression::Function(Box::new(Function::new(
28146 "IFF".to_string(),
28147 vec![cond, orig_arg, Expression::Null(Null)],
28148 )));
28149 func.args[0] = iff_call;
28150 Ok(Expression::Function(func))
28151 } else {
28152 Ok(Expression::Filter(Box::new(crate::expressions::Filter {
28153 this: Box::new(Expression::Function(func)),
28154 expression: Box::new(cond),
28155 })))
28156 }
28157 }
28158 Expression::Avg(mut avg) => {
28159 let iff_call = Expression::Function(Box::new(Function::new(
28160 "IFF".to_string(),
28161 vec![cond, avg.this.clone(), Expression::Null(Null)],
28162 )));
28163 avg.this = iff_call;
28164 Ok(Expression::Avg(avg))
28165 }
28166 Expression::Sum(mut s) => {
28167 let iff_call = Expression::Function(Box::new(Function::new(
28168 "IFF".to_string(),
28169 vec![cond, s.this.clone(), Expression::Null(Null)],
28170 )));
28171 s.this = iff_call;
28172 Ok(Expression::Sum(s))
28173 }
28174 Expression::Count(mut c) => {
28175 if let Some(ref this_expr) = c.this {
28176 let iff_call = Expression::Function(Box::new(Function::new(
28177 "IFF".to_string(),
28178 vec![cond, this_expr.clone(), Expression::Null(Null)],
28179 )));
28180 c.this = Some(iff_call);
28181 }
28182 Ok(Expression::Count(c))
28183 }
28184 other => {
28185 // Fallback: keep as Filter
28186 Ok(Expression::Filter(Box::new(crate::expressions::Filter {
28187 this: Box::new(other),
28188 expression: Box::new(cond),
28189 })))
28190 }
28191 }
28192 } else {
28193 Ok(e)
28194 }
28195 }
28196
28197 Action::AggFilterToIff => {
28198 // AggFunc.filter -> IFF wrapping: AVG(x) FILTER(WHERE cond) -> AVG(IFF(cond, x, NULL))
28199 // Helper macro to handle the common AggFunc case
28200 macro_rules! handle_agg_filter_to_iff {
28201 ($variant:ident, $agg:expr) => {{
28202 let mut agg = $agg;
28203 if let Some(filter_cond) = agg.filter.take() {
28204 let iff_call = Expression::Function(Box::new(Function::new(
28205 "IFF".to_string(),
28206 vec![filter_cond, agg.this.clone(), Expression::Null(Null)],
28207 )));
28208 agg.this = iff_call;
28209 }
28210 Ok(Expression::$variant(agg))
28211 }};
28212 }
28213
28214 match e {
28215 Expression::Avg(agg) => handle_agg_filter_to_iff!(Avg, agg),
28216 Expression::Sum(agg) => handle_agg_filter_to_iff!(Sum, agg),
28217 Expression::Min(agg) => handle_agg_filter_to_iff!(Min, agg),
28218 Expression::Max(agg) => handle_agg_filter_to_iff!(Max, agg),
28219 Expression::ArrayAgg(agg) => handle_agg_filter_to_iff!(ArrayAgg, agg),
28220 Expression::CountIf(agg) => handle_agg_filter_to_iff!(CountIf, agg),
28221 Expression::Stddev(agg) => handle_agg_filter_to_iff!(Stddev, agg),
28222 Expression::StddevPop(agg) => handle_agg_filter_to_iff!(StddevPop, agg),
28223 Expression::StddevSamp(agg) => handle_agg_filter_to_iff!(StddevSamp, agg),
28224 Expression::Variance(agg) => handle_agg_filter_to_iff!(Variance, agg),
28225 Expression::VarPop(agg) => handle_agg_filter_to_iff!(VarPop, agg),
28226 Expression::VarSamp(agg) => handle_agg_filter_to_iff!(VarSamp, agg),
28227 Expression::Median(agg) => handle_agg_filter_to_iff!(Median, agg),
28228 Expression::Mode(agg) => handle_agg_filter_to_iff!(Mode, agg),
28229 Expression::First(agg) => handle_agg_filter_to_iff!(First, agg),
28230 Expression::Last(agg) => handle_agg_filter_to_iff!(Last, agg),
28231 Expression::AnyValue(agg) => handle_agg_filter_to_iff!(AnyValue, agg),
28232 Expression::ApproxDistinct(agg) => {
28233 handle_agg_filter_to_iff!(ApproxDistinct, agg)
28234 }
28235 Expression::Count(mut c) => {
28236 if let Some(filter_cond) = c.filter.take() {
28237 if let Some(ref this_expr) = c.this {
28238 let iff_call = Expression::Function(Box::new(Function::new(
28239 "IFF".to_string(),
28240 vec![
28241 filter_cond,
28242 this_expr.clone(),
28243 Expression::Null(Null),
28244 ],
28245 )));
28246 c.this = Some(iff_call);
28247 }
28248 }
28249 Ok(Expression::Count(c))
28250 }
28251 other => Ok(other),
28252 }
28253 }
28254
28255 Action::JsonToGetPath => {
28256 // JSON_EXTRACT(x, '$.key') -> GET_PATH(PARSE_JSON(x), 'key')
28257 if let Expression::JsonExtract(je) = e {
28258 // Convert to PARSE_JSON() wrapper:
28259 // - JSON(x) -> PARSE_JSON(x)
28260 // - PARSE_JSON(x) -> keep as-is
28261 // - anything else -> wrap in PARSE_JSON()
28262 let this = match &je.this {
28263 Expression::Function(f)
28264 if f.name.eq_ignore_ascii_case("JSON") && f.args.len() == 1 =>
28265 {
28266 Expression::Function(Box::new(Function::new(
28267 "PARSE_JSON".to_string(),
28268 f.args.clone(),
28269 )))
28270 }
28271 Expression::Function(f)
28272 if f.name.eq_ignore_ascii_case("PARSE_JSON") =>
28273 {
28274 je.this.clone()
28275 }
28276 // GET_PATH result is already JSON, don't wrap
28277 Expression::Function(f) if f.name.eq_ignore_ascii_case("GET_PATH") => {
28278 je.this.clone()
28279 }
28280 other => {
28281 // Wrap non-JSON expressions in PARSE_JSON()
28282 Expression::Function(Box::new(Function::new(
28283 "PARSE_JSON".to_string(),
28284 vec![other.clone()],
28285 )))
28286 }
28287 };
28288 // Convert path: extract key from JSONPath or strip $. prefix from string
28289 let path = match &je.path {
28290 Expression::JSONPath(jp) => {
28291 // Extract the key from JSONPath: $root.key -> 'key'
28292 let mut key_parts = Vec::new();
28293 for expr in &jp.expressions {
28294 match expr {
28295 Expression::JSONPathRoot(_) => {} // skip root
28296 Expression::JSONPathKey(k) => {
28297 if let Expression::Literal(lit) = &*k.this {
28298 if let Literal::String(s) = lit.as_ref() {
28299 key_parts.push(s.clone());
28300 }
28301 }
28302 }
28303 _ => {}
28304 }
28305 }
28306 if !key_parts.is_empty() {
28307 Expression::Literal(Box::new(Literal::String(
28308 key_parts.join("."),
28309 )))
28310 } else {
28311 je.path.clone()
28312 }
28313 }
28314 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(s) if s.starts_with("$.")) =>
28315 {
28316 let Literal::String(s) = lit.as_ref() else {
28317 unreachable!()
28318 };
28319 let stripped = Self::strip_json_wildcards(&s[2..].to_string());
28320 Expression::Literal(Box::new(Literal::String(stripped)))
28321 }
28322 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(s) if s.starts_with('$')) =>
28323 {
28324 let Literal::String(s) = lit.as_ref() else {
28325 unreachable!()
28326 };
28327 let stripped = Self::strip_json_wildcards(&s[1..].to_string());
28328 Expression::Literal(Box::new(Literal::String(stripped)))
28329 }
28330 _ => je.path.clone(),
28331 };
28332 Ok(Expression::Function(Box::new(Function::new(
28333 "GET_PATH".to_string(),
28334 vec![this, path],
28335 ))))
28336 } else {
28337 Ok(e)
28338 }
28339 }
28340
28341 Action::StructToRow => {
28342 // DuckDB struct/dict -> BigQuery STRUCT(value AS key, ...) / Presto ROW
28343 // Handles both Expression::Struct and Expression::MapFunc(curly_brace_syntax=true)
28344
28345 // Extract key-value pairs from either Struct or MapFunc
28346 let kv_pairs: Option<Vec<(String, Expression)>> = match &e {
28347 Expression::Struct(s) => Some(
28348 s.fields
28349 .iter()
28350 .map(|(opt_name, field_expr)| {
28351 if let Some(name) = opt_name {
28352 (name.clone(), field_expr.clone())
28353 } else if let Expression::NamedArgument(na) = field_expr {
28354 (na.name.name.clone(), na.value.clone())
28355 } else {
28356 (String::new(), field_expr.clone())
28357 }
28358 })
28359 .collect(),
28360 ),
28361 Expression::MapFunc(m) if m.curly_brace_syntax => Some(
28362 m.keys
28363 .iter()
28364 .zip(m.values.iter())
28365 .map(|(key, value)| {
28366 let key_name = match key {
28367 Expression::Literal(lit)
28368 if matches!(lit.as_ref(), Literal::String(_)) =>
28369 {
28370 let Literal::String(s) = lit.as_ref() else {
28371 unreachable!()
28372 };
28373 s.clone()
28374 }
28375 Expression::Identifier(id) => id.name.clone(),
28376 _ => String::new(),
28377 };
28378 (key_name, value.clone())
28379 })
28380 .collect(),
28381 ),
28382 _ => None,
28383 };
28384
28385 if let Some(pairs) = kv_pairs {
28386 let mut named_args = Vec::new();
28387 for (key_name, value) in pairs {
28388 if matches!(target, DialectType::BigQuery) && !key_name.is_empty() {
28389 named_args.push(Expression::Alias(Box::new(
28390 crate::expressions::Alias::new(
28391 value,
28392 Identifier::new(key_name),
28393 ),
28394 )));
28395 } else if matches!(target, DialectType::Presto | DialectType::Trino) {
28396 named_args.push(value);
28397 } else {
28398 named_args.push(value);
28399 }
28400 }
28401
28402 if matches!(target, DialectType::BigQuery) {
28403 Ok(Expression::Function(Box::new(Function::new(
28404 "STRUCT".to_string(),
28405 named_args,
28406 ))))
28407 } else if matches!(target, DialectType::Presto | DialectType::Trino) {
28408 // For Presto/Trino, infer types and wrap in CAST(ROW(...) AS ROW(name TYPE, ...))
28409 let row_func = Expression::Function(Box::new(Function::new(
28410 "ROW".to_string(),
28411 named_args,
28412 )));
28413
28414 // Try to infer types for each pair
28415 let kv_pairs_again: Option<Vec<(String, Expression)>> = match &e {
28416 Expression::Struct(s) => Some(
28417 s.fields
28418 .iter()
28419 .map(|(opt_name, field_expr)| {
28420 if let Some(name) = opt_name {
28421 (name.clone(), field_expr.clone())
28422 } else if let Expression::NamedArgument(na) = field_expr
28423 {
28424 (na.name.name.clone(), na.value.clone())
28425 } else {
28426 (String::new(), field_expr.clone())
28427 }
28428 })
28429 .collect(),
28430 ),
28431 Expression::MapFunc(m) if m.curly_brace_syntax => Some(
28432 m.keys
28433 .iter()
28434 .zip(m.values.iter())
28435 .map(|(key, value)| {
28436 let key_name = match key {
28437 Expression::Literal(lit)
28438 if matches!(
28439 lit.as_ref(),
28440 Literal::String(_)
28441 ) =>
28442 {
28443 let Literal::String(s) = lit.as_ref() else {
28444 unreachable!()
28445 };
28446 s.clone()
28447 }
28448 Expression::Identifier(id) => id.name.clone(),
28449 _ => String::new(),
28450 };
28451 (key_name, value.clone())
28452 })
28453 .collect(),
28454 ),
28455 _ => None,
28456 };
28457
28458 if let Some(pairs) = kv_pairs_again {
28459 // Infer types for all values
28460 let mut all_inferred = true;
28461 let mut fields = Vec::new();
28462 for (name, value) in &pairs {
28463 let inferred_type = match value {
28464 Expression::Literal(lit)
28465 if matches!(lit.as_ref(), Literal::Number(_)) =>
28466 {
28467 let Literal::Number(n) = lit.as_ref() else {
28468 unreachable!()
28469 };
28470 if n.contains('.') {
28471 Some(DataType::Double {
28472 precision: None,
28473 scale: None,
28474 })
28475 } else {
28476 Some(DataType::Int {
28477 length: None,
28478 integer_spelling: true,
28479 })
28480 }
28481 }
28482 Expression::Literal(lit)
28483 if matches!(lit.as_ref(), Literal::String(_)) =>
28484 {
28485 Some(DataType::VarChar {
28486 length: None,
28487 parenthesized_length: false,
28488 })
28489 }
28490 Expression::Boolean(_) => Some(DataType::Boolean),
28491 _ => None,
28492 };
28493 if let Some(dt) = inferred_type {
28494 fields.push(crate::expressions::StructField::new(
28495 name.clone(),
28496 dt,
28497 ));
28498 } else {
28499 all_inferred = false;
28500 break;
28501 }
28502 }
28503
28504 if all_inferred && !fields.is_empty() {
28505 let row_type = DataType::Struct {
28506 fields,
28507 nested: true,
28508 };
28509 Ok(Expression::Cast(Box::new(Cast {
28510 this: row_func,
28511 to: row_type,
28512 trailing_comments: Vec::new(),
28513 double_colon_syntax: false,
28514 format: None,
28515 default: None,
28516 inferred_type: None,
28517 })))
28518 } else {
28519 Ok(row_func)
28520 }
28521 } else {
28522 Ok(row_func)
28523 }
28524 } else {
28525 Ok(Expression::Function(Box::new(Function::new(
28526 "ROW".to_string(),
28527 named_args,
28528 ))))
28529 }
28530 } else {
28531 Ok(e)
28532 }
28533 }
28534
28535 Action::SparkStructConvert => {
28536 // Spark STRUCT(val AS name, ...) -> Presto CAST(ROW(...) AS ROW(name TYPE, ...))
28537 // or DuckDB {'name': val, ...}
28538 if let Expression::Function(f) = e {
28539 // Extract name-value pairs from aliased args
28540 let mut pairs: Vec<(String, Expression)> = Vec::new();
28541 for arg in &f.args {
28542 match arg {
28543 Expression::Alias(a) => {
28544 pairs.push((a.alias.name.clone(), a.this.clone()));
28545 }
28546 _ => {
28547 pairs.push((String::new(), arg.clone()));
28548 }
28549 }
28550 }
28551
28552 match target {
28553 DialectType::DuckDB => {
28554 // Convert to DuckDB struct literal {'name': value, ...}
28555 let mut keys = Vec::new();
28556 let mut values = Vec::new();
28557 for (name, value) in &pairs {
28558 keys.push(Expression::Literal(Box::new(Literal::String(
28559 name.clone(),
28560 ))));
28561 values.push(value.clone());
28562 }
28563 Ok(Expression::MapFunc(Box::new(
28564 crate::expressions::MapConstructor {
28565 keys,
28566 values,
28567 curly_brace_syntax: true,
28568 with_map_keyword: false,
28569 },
28570 )))
28571 }
28572 DialectType::Presto | DialectType::Trino | DialectType::Athena => {
28573 // Convert to CAST(ROW(val1, val2) AS ROW(name1 TYPE1, name2 TYPE2))
28574 let row_args: Vec<Expression> =
28575 pairs.iter().map(|(_, v)| v.clone()).collect();
28576 let row_func = Expression::Function(Box::new(Function::new(
28577 "ROW".to_string(),
28578 row_args,
28579 )));
28580
28581 // Infer types
28582 let mut all_inferred = true;
28583 let mut fields = Vec::new();
28584 for (name, value) in &pairs {
28585 let inferred_type = match value {
28586 Expression::Literal(lit)
28587 if matches!(lit.as_ref(), Literal::Number(_)) =>
28588 {
28589 let Literal::Number(n) = lit.as_ref() else {
28590 unreachable!()
28591 };
28592 if n.contains('.') {
28593 Some(DataType::Double {
28594 precision: None,
28595 scale: None,
28596 })
28597 } else {
28598 Some(DataType::Int {
28599 length: None,
28600 integer_spelling: true,
28601 })
28602 }
28603 }
28604 Expression::Literal(lit)
28605 if matches!(lit.as_ref(), Literal::String(_)) =>
28606 {
28607 Some(DataType::VarChar {
28608 length: None,
28609 parenthesized_length: false,
28610 })
28611 }
28612 Expression::Boolean(_) => Some(DataType::Boolean),
28613 _ => None,
28614 };
28615 if let Some(dt) = inferred_type {
28616 fields.push(crate::expressions::StructField::new(
28617 name.clone(),
28618 dt,
28619 ));
28620 } else {
28621 all_inferred = false;
28622 break;
28623 }
28624 }
28625
28626 if all_inferred && !fields.is_empty() {
28627 let row_type = DataType::Struct {
28628 fields,
28629 nested: true,
28630 };
28631 Ok(Expression::Cast(Box::new(Cast {
28632 this: row_func,
28633 to: row_type,
28634 trailing_comments: Vec::new(),
28635 double_colon_syntax: false,
28636 format: None,
28637 default: None,
28638 inferred_type: None,
28639 })))
28640 } else {
28641 Ok(row_func)
28642 }
28643 }
28644 _ => Ok(Expression::Function(f)),
28645 }
28646 } else {
28647 Ok(e)
28648 }
28649 }
28650
28651 Action::ApproxCountDistinctToApproxDistinct => {
28652 // APPROX_COUNT_DISTINCT(x) -> APPROX_DISTINCT(x)
28653 if let Expression::ApproxCountDistinct(f) = e {
28654 Ok(Expression::ApproxDistinct(f))
28655 } else {
28656 Ok(e)
28657 }
28658 }
28659
28660 Action::CollectListToArrayAgg => {
28661 // COLLECT_LIST(x) -> ARRAY_AGG(x) FILTER(WHERE x IS NOT NULL)
28662 if let Expression::AggregateFunction(f) = e {
28663 let filter_expr = if !f.args.is_empty() {
28664 let arg = f.args[0].clone();
28665 Some(Expression::IsNull(Box::new(crate::expressions::IsNull {
28666 this: arg,
28667 not: true,
28668 postfix_form: false,
28669 })))
28670 } else {
28671 None
28672 };
28673 let agg = crate::expressions::AggFunc {
28674 this: if f.args.is_empty() {
28675 Expression::Null(crate::expressions::Null)
28676 } else {
28677 f.args[0].clone()
28678 },
28679 distinct: f.distinct,
28680 order_by: f.order_by.clone(),
28681 filter: filter_expr,
28682 ignore_nulls: None,
28683 name: None,
28684 having_max: None,
28685 limit: None,
28686 inferred_type: None,
28687 };
28688 Ok(Expression::ArrayAgg(Box::new(agg)))
28689 } else {
28690 Ok(e)
28691 }
28692 }
28693
28694 Action::CollectSetConvert => {
28695 // COLLECT_SET(x) -> target-specific
28696 if let Expression::AggregateFunction(f) = e {
28697 match target {
28698 DialectType::Presto => Ok(Expression::AggregateFunction(Box::new(
28699 crate::expressions::AggregateFunction {
28700 name: "SET_AGG".to_string(),
28701 args: f.args,
28702 distinct: false,
28703 order_by: f.order_by,
28704 filter: f.filter,
28705 limit: f.limit,
28706 ignore_nulls: f.ignore_nulls,
28707 inferred_type: None,
28708 },
28709 ))),
28710 DialectType::Snowflake => Ok(Expression::AggregateFunction(Box::new(
28711 crate::expressions::AggregateFunction {
28712 name: "ARRAY_UNIQUE_AGG".to_string(),
28713 args: f.args,
28714 distinct: false,
28715 order_by: f.order_by,
28716 filter: f.filter,
28717 limit: f.limit,
28718 ignore_nulls: f.ignore_nulls,
28719 inferred_type: None,
28720 },
28721 ))),
28722 DialectType::Trino | DialectType::DuckDB => {
28723 let agg = crate::expressions::AggFunc {
28724 this: if f.args.is_empty() {
28725 Expression::Null(crate::expressions::Null)
28726 } else {
28727 f.args[0].clone()
28728 },
28729 distinct: true,
28730 order_by: Vec::new(),
28731 filter: None,
28732 ignore_nulls: None,
28733 name: None,
28734 having_max: None,
28735 limit: None,
28736 inferred_type: None,
28737 };
28738 Ok(Expression::ArrayAgg(Box::new(agg)))
28739 }
28740 _ => Ok(Expression::AggregateFunction(f)),
28741 }
28742 } else {
28743 Ok(e)
28744 }
28745 }
28746
28747 Action::PercentileConvert => {
28748 // PERCENTILE(x, 0.5) -> QUANTILE(x, 0.5) / APPROX_PERCENTILE(x, 0.5)
28749 if let Expression::AggregateFunction(f) = e {
28750 let name = match target {
28751 DialectType::DuckDB => "QUANTILE",
28752 DialectType::Presto | DialectType::Trino => "APPROX_PERCENTILE",
28753 _ => "PERCENTILE",
28754 };
28755 Ok(Expression::AggregateFunction(Box::new(
28756 crate::expressions::AggregateFunction {
28757 name: name.to_string(),
28758 args: f.args,
28759 distinct: f.distinct,
28760 order_by: f.order_by,
28761 filter: f.filter,
28762 limit: f.limit,
28763 ignore_nulls: f.ignore_nulls,
28764 inferred_type: None,
28765 },
28766 )))
28767 } else {
28768 Ok(e)
28769 }
28770 }
28771
28772 Action::CorrIsnanWrap => {
28773 // CORR(a, b) -> CASE WHEN ISNAN(CORR(a, b)) THEN NULL ELSE CORR(a, b) END
28774 // The CORR expression could be AggregateFunction, WindowFunction, or Filter-wrapped
28775 let corr_clone = e.clone();
28776 let isnan = Expression::Function(Box::new(Function::new(
28777 "ISNAN".to_string(),
28778 vec![corr_clone.clone()],
28779 )));
28780 let case_expr = Expression::Case(Box::new(Case {
28781 operand: None,
28782 whens: vec![(isnan, Expression::Null(crate::expressions::Null))],
28783 else_: Some(corr_clone),
28784 comments: Vec::new(),
28785 inferred_type: None,
28786 }));
28787 Ok(case_expr)
28788 }
28789
28790 Action::TruncToDateTrunc => {
28791 // TRUNC(timestamp, 'MONTH') -> DATE_TRUNC('MONTH', timestamp)
28792 if let Expression::Function(f) = e {
28793 if f.args.len() == 2 {
28794 let timestamp = f.args[0].clone();
28795 let unit_expr = f.args[1].clone();
28796
28797 if matches!(target, DialectType::ClickHouse) {
28798 // For ClickHouse, produce Expression::DateTrunc which the generator
28799 // outputs as DATE_TRUNC(...) without going through the ClickHouse
28800 // target transform that would convert it to dateTrunc
28801 let unit_str = Self::get_unit_str_static(&unit_expr);
28802 let dt_field = match unit_str.as_str() {
28803 "YEAR" => DateTimeField::Year,
28804 "MONTH" => DateTimeField::Month,
28805 "DAY" => DateTimeField::Day,
28806 "HOUR" => DateTimeField::Hour,
28807 "MINUTE" => DateTimeField::Minute,
28808 "SECOND" => DateTimeField::Second,
28809 "WEEK" => DateTimeField::Week,
28810 "QUARTER" => DateTimeField::Quarter,
28811 _ => DateTimeField::Custom(unit_str),
28812 };
28813 Ok(Expression::DateTrunc(Box::new(
28814 crate::expressions::DateTruncFunc {
28815 this: timestamp,
28816 unit: dt_field,
28817 },
28818 )))
28819 } else {
28820 let new_args = vec![unit_expr, timestamp];
28821 Ok(Expression::Function(Box::new(Function::new(
28822 "DATE_TRUNC".to_string(),
28823 new_args,
28824 ))))
28825 }
28826 } else {
28827 Ok(Expression::Function(f))
28828 }
28829 } else {
28830 Ok(e)
28831 }
28832 }
28833
28834 Action::ArrayContainsConvert => {
28835 if let Expression::ArrayContains(f) = e {
28836 match target {
28837 DialectType::Presto | DialectType::Trino => {
28838 // ARRAY_CONTAINS(arr, val) -> CONTAINS(arr, val)
28839 Ok(Expression::Function(Box::new(Function::new(
28840 "CONTAINS".to_string(),
28841 vec![f.this, f.expression],
28842 ))))
28843 }
28844 DialectType::Snowflake => {
28845 // ARRAY_CONTAINS(arr, val) -> ARRAY_CONTAINS(CAST(val AS VARIANT), arr)
28846 let cast_val =
28847 Expression::Cast(Box::new(crate::expressions::Cast {
28848 this: f.expression,
28849 to: crate::expressions::DataType::Custom {
28850 name: "VARIANT".to_string(),
28851 },
28852 trailing_comments: Vec::new(),
28853 double_colon_syntax: false,
28854 format: None,
28855 default: None,
28856 inferred_type: None,
28857 }));
28858 Ok(Expression::Function(Box::new(Function::new(
28859 "ARRAY_CONTAINS".to_string(),
28860 vec![cast_val, f.this],
28861 ))))
28862 }
28863 _ => Ok(Expression::ArrayContains(f)),
28864 }
28865 } else {
28866 Ok(e)
28867 }
28868 }
28869
28870 Action::ArrayExceptConvert => {
28871 if let Expression::ArrayExcept(f) = e {
28872 let source_arr = f.this;
28873 let exclude_arr = f.expression;
28874 match target {
28875 DialectType::DuckDB if matches!(source, DialectType::Snowflake) => {
28876 // Snowflake ARRAY_EXCEPT -> DuckDB bag semantics:
28877 // CASE WHEN source IS NULL OR exclude IS NULL THEN NULL
28878 // ELSE LIST_TRANSFORM(LIST_FILTER(
28879 // LIST_ZIP(source, GENERATE_SERIES(1, LENGTH(source))),
28880 // pair -> (LENGTH(LIST_FILTER(source[1:pair[2]], e -> e IS NOT DISTINCT FROM pair[1]))
28881 // > LENGTH(LIST_FILTER(exclude, e -> e IS NOT DISTINCT FROM pair[1])))),
28882 // pair -> pair[1])
28883 // END
28884
28885 // Build null check
28886 let source_is_null =
28887 Expression::IsNull(Box::new(crate::expressions::IsNull {
28888 this: source_arr.clone(),
28889 not: false,
28890 postfix_form: false,
28891 }));
28892 let exclude_is_null =
28893 Expression::IsNull(Box::new(crate::expressions::IsNull {
28894 this: exclude_arr.clone(),
28895 not: false,
28896 postfix_form: false,
28897 }));
28898 let null_check =
28899 Expression::Or(Box::new(crate::expressions::BinaryOp {
28900 left: source_is_null,
28901 right: exclude_is_null,
28902 left_comments: vec![],
28903 operator_comments: vec![],
28904 trailing_comments: vec![],
28905 inferred_type: None,
28906 }));
28907
28908 // GENERATE_SERIES(1, LENGTH(source))
28909 let gen_series = Expression::Function(Box::new(Function::new(
28910 "GENERATE_SERIES".to_string(),
28911 vec![
28912 Expression::number(1),
28913 Expression::Function(Box::new(Function::new(
28914 "LENGTH".to_string(),
28915 vec![source_arr.clone()],
28916 ))),
28917 ],
28918 )));
28919
28920 // LIST_ZIP(source, GENERATE_SERIES(1, LENGTH(source)))
28921 let list_zip = Expression::Function(Box::new(Function::new(
28922 "LIST_ZIP".to_string(),
28923 vec![source_arr.clone(), gen_series],
28924 )));
28925
28926 // pair[1] and pair[2]
28927 let pair_col = Expression::column("pair");
28928 let pair_1 = Expression::Subscript(Box::new(
28929 crate::expressions::Subscript {
28930 this: pair_col.clone(),
28931 index: Expression::number(1),
28932 },
28933 ));
28934 let pair_2 = Expression::Subscript(Box::new(
28935 crate::expressions::Subscript {
28936 this: pair_col.clone(),
28937 index: Expression::number(2),
28938 },
28939 ));
28940
28941 // source[1:pair[2]]
28942 let source_slice = Expression::ArraySlice(Box::new(
28943 crate::expressions::ArraySlice {
28944 this: source_arr.clone(),
28945 start: Some(Expression::number(1)),
28946 end: Some(pair_2),
28947 },
28948 ));
28949
28950 let e_col = Expression::column("e");
28951
28952 // e -> e IS NOT DISTINCT FROM pair[1]
28953 let inner_lambda1 =
28954 Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
28955 parameters: vec![crate::expressions::Identifier::new("e")],
28956 body: Expression::NullSafeEq(Box::new(
28957 crate::expressions::BinaryOp {
28958 left: e_col.clone(),
28959 right: pair_1.clone(),
28960 left_comments: vec![],
28961 operator_comments: vec![],
28962 trailing_comments: vec![],
28963 inferred_type: None,
28964 },
28965 )),
28966 colon: false,
28967 parameter_types: vec![],
28968 }));
28969
28970 // LIST_FILTER(source[1:pair[2]], e -> e IS NOT DISTINCT FROM pair[1])
28971 let inner_filter1 = Expression::Function(Box::new(Function::new(
28972 "LIST_FILTER".to_string(),
28973 vec![source_slice, inner_lambda1],
28974 )));
28975
28976 // LENGTH(LIST_FILTER(source[1:pair[2]], ...))
28977 let len1 = Expression::Function(Box::new(Function::new(
28978 "LENGTH".to_string(),
28979 vec![inner_filter1],
28980 )));
28981
28982 // e -> e IS NOT DISTINCT FROM pair[1]
28983 let inner_lambda2 =
28984 Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
28985 parameters: vec![crate::expressions::Identifier::new("e")],
28986 body: Expression::NullSafeEq(Box::new(
28987 crate::expressions::BinaryOp {
28988 left: e_col,
28989 right: pair_1.clone(),
28990 left_comments: vec![],
28991 operator_comments: vec![],
28992 trailing_comments: vec![],
28993 inferred_type: None,
28994 },
28995 )),
28996 colon: false,
28997 parameter_types: vec![],
28998 }));
28999
29000 // LIST_FILTER(exclude, e -> e IS NOT DISTINCT FROM pair[1])
29001 let inner_filter2 = Expression::Function(Box::new(Function::new(
29002 "LIST_FILTER".to_string(),
29003 vec![exclude_arr.clone(), inner_lambda2],
29004 )));
29005
29006 // LENGTH(LIST_FILTER(exclude, ...))
29007 let len2 = Expression::Function(Box::new(Function::new(
29008 "LENGTH".to_string(),
29009 vec![inner_filter2],
29010 )));
29011
29012 // (LENGTH(...) > LENGTH(...))
29013 let cond = Expression::Paren(Box::new(Paren {
29014 this: Expression::Gt(Box::new(crate::expressions::BinaryOp {
29015 left: len1,
29016 right: len2,
29017 left_comments: vec![],
29018 operator_comments: vec![],
29019 trailing_comments: vec![],
29020 inferred_type: None,
29021 })),
29022 trailing_comments: vec![],
29023 }));
29024
29025 // pair -> (condition)
29026 let filter_lambda =
29027 Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
29028 parameters: vec![crate::expressions::Identifier::new(
29029 "pair",
29030 )],
29031 body: cond,
29032 colon: false,
29033 parameter_types: vec![],
29034 }));
29035
29036 // LIST_FILTER(LIST_ZIP(...), pair -> ...)
29037 let outer_filter = Expression::Function(Box::new(Function::new(
29038 "LIST_FILTER".to_string(),
29039 vec![list_zip, filter_lambda],
29040 )));
29041
29042 // pair -> pair[1]
29043 let transform_lambda =
29044 Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
29045 parameters: vec![crate::expressions::Identifier::new(
29046 "pair",
29047 )],
29048 body: pair_1,
29049 colon: false,
29050 parameter_types: vec![],
29051 }));
29052
29053 // LIST_TRANSFORM(LIST_FILTER(...), pair -> pair[1])
29054 let list_transform = Expression::Function(Box::new(Function::new(
29055 "LIST_TRANSFORM".to_string(),
29056 vec![outer_filter, transform_lambda],
29057 )));
29058
29059 Ok(Expression::Case(Box::new(Case {
29060 operand: None,
29061 whens: vec![(null_check, Expression::Null(Null))],
29062 else_: Some(list_transform),
29063 comments: Vec::new(),
29064 inferred_type: None,
29065 })))
29066 }
29067 DialectType::DuckDB => {
29068 // ARRAY_EXCEPT(source, exclude) -> set semantics for DuckDB:
29069 // CASE WHEN source IS NULL OR exclude IS NULL THEN NULL
29070 // ELSE LIST_FILTER(LIST_DISTINCT(source),
29071 // e -> LENGTH(LIST_FILTER(exclude, x -> x IS NOT DISTINCT FROM e)) = 0)
29072 // END
29073
29074 // Build: source IS NULL
29075 let source_is_null =
29076 Expression::IsNull(Box::new(crate::expressions::IsNull {
29077 this: source_arr.clone(),
29078 not: false,
29079 postfix_form: false,
29080 }));
29081 // Build: exclude IS NULL
29082 let exclude_is_null =
29083 Expression::IsNull(Box::new(crate::expressions::IsNull {
29084 this: exclude_arr.clone(),
29085 not: false,
29086 postfix_form: false,
29087 }));
29088 // source IS NULL OR exclude IS NULL
29089 let null_check =
29090 Expression::Or(Box::new(crate::expressions::BinaryOp {
29091 left: source_is_null,
29092 right: exclude_is_null,
29093 left_comments: vec![],
29094 operator_comments: vec![],
29095 trailing_comments: vec![],
29096 inferred_type: None,
29097 }));
29098
29099 // LIST_DISTINCT(source)
29100 let list_distinct = Expression::Function(Box::new(Function::new(
29101 "LIST_DISTINCT".to_string(),
29102 vec![source_arr.clone()],
29103 )));
29104
29105 // x IS NOT DISTINCT FROM e
29106 let x_col = Expression::column("x");
29107 let e_col = Expression::column("e");
29108 let is_not_distinct = Expression::NullSafeEq(Box::new(
29109 crate::expressions::BinaryOp {
29110 left: x_col,
29111 right: e_col.clone(),
29112 left_comments: vec![],
29113 operator_comments: vec![],
29114 trailing_comments: vec![],
29115 inferred_type: None,
29116 },
29117 ));
29118
29119 // x -> x IS NOT DISTINCT FROM e
29120 let inner_lambda =
29121 Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
29122 parameters: vec![crate::expressions::Identifier::new("x")],
29123 body: is_not_distinct,
29124 colon: false,
29125 parameter_types: vec![],
29126 }));
29127
29128 // LIST_FILTER(exclude, x -> x IS NOT DISTINCT FROM e)
29129 let inner_list_filter =
29130 Expression::Function(Box::new(Function::new(
29131 "LIST_FILTER".to_string(),
29132 vec![exclude_arr.clone(), inner_lambda],
29133 )));
29134
29135 // LENGTH(LIST_FILTER(exclude, x -> x IS NOT DISTINCT FROM e))
29136 let len_inner = Expression::Function(Box::new(Function::new(
29137 "LENGTH".to_string(),
29138 vec![inner_list_filter],
29139 )));
29140
29141 // LENGTH(...) = 0
29142 let eq_zero =
29143 Expression::Eq(Box::new(crate::expressions::BinaryOp {
29144 left: len_inner,
29145 right: Expression::number(0),
29146 left_comments: vec![],
29147 operator_comments: vec![],
29148 trailing_comments: vec![],
29149 inferred_type: None,
29150 }));
29151
29152 // e -> LENGTH(LIST_FILTER(...)) = 0
29153 let outer_lambda =
29154 Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
29155 parameters: vec![crate::expressions::Identifier::new("e")],
29156 body: eq_zero,
29157 colon: false,
29158 parameter_types: vec![],
29159 }));
29160
29161 // LIST_FILTER(LIST_DISTINCT(source), e -> ...)
29162 let outer_list_filter =
29163 Expression::Function(Box::new(Function::new(
29164 "LIST_FILTER".to_string(),
29165 vec![list_distinct, outer_lambda],
29166 )));
29167
29168 // CASE WHEN ... IS NULL ... THEN NULL ELSE LIST_FILTER(...) END
29169 Ok(Expression::Case(Box::new(Case {
29170 operand: None,
29171 whens: vec![(null_check, Expression::Null(Null))],
29172 else_: Some(outer_list_filter),
29173 comments: Vec::new(),
29174 inferred_type: None,
29175 })))
29176 }
29177 DialectType::Snowflake => {
29178 // Snowflake: ARRAY_EXCEPT(source, exclude) - keep as-is
29179 Ok(Expression::ArrayExcept(Box::new(
29180 crate::expressions::BinaryFunc {
29181 this: source_arr,
29182 expression: exclude_arr,
29183 original_name: None,
29184 inferred_type: None,
29185 },
29186 )))
29187 }
29188 DialectType::Presto | DialectType::Trino | DialectType::Athena => {
29189 // Presto/Trino: ARRAY_EXCEPT(source, exclude) - keep function name, array syntax already converted
29190 Ok(Expression::Function(Box::new(Function::new(
29191 "ARRAY_EXCEPT".to_string(),
29192 vec![source_arr, exclude_arr],
29193 ))))
29194 }
29195 _ => Ok(Expression::ArrayExcept(Box::new(
29196 crate::expressions::BinaryFunc {
29197 this: source_arr,
29198 expression: exclude_arr,
29199 original_name: None,
29200 inferred_type: None,
29201 },
29202 ))),
29203 }
29204 } else {
29205 Ok(e)
29206 }
29207 }
29208
29209 Action::RegexpLikeExasolAnchor => {
29210 // RegexpLike -> Exasol: wrap pattern with .*...*
29211 // Exasol REGEXP_LIKE does full-string match, but RLIKE/REGEXP from other
29212 // dialects does partial match, so we need to anchor with .* on both sides
29213 if let Expression::RegexpLike(mut f) = e {
29214 match &f.pattern {
29215 Expression::Literal(lit)
29216 if matches!(lit.as_ref(), Literal::String(_)) =>
29217 {
29218 let Literal::String(s) = lit.as_ref() else {
29219 unreachable!()
29220 };
29221 // String literal: wrap with .*...*
29222 f.pattern = Expression::Literal(Box::new(Literal::String(
29223 format!(".*{}.*", s),
29224 )));
29225 }
29226 _ => {
29227 // Non-literal: wrap with CONCAT('.*', pattern, '.*')
29228 f.pattern =
29229 Expression::Paren(Box::new(crate::expressions::Paren {
29230 this: Expression::Concat(Box::new(
29231 crate::expressions::BinaryOp {
29232 left: Expression::Concat(Box::new(
29233 crate::expressions::BinaryOp {
29234 left: Expression::Literal(Box::new(
29235 Literal::String(".*".to_string()),
29236 )),
29237 right: f.pattern,
29238 left_comments: vec![],
29239 operator_comments: vec![],
29240 trailing_comments: vec![],
29241 inferred_type: None,
29242 },
29243 )),
29244 right: Expression::Literal(Box::new(
29245 Literal::String(".*".to_string()),
29246 )),
29247 left_comments: vec![],
29248 operator_comments: vec![],
29249 trailing_comments: vec![],
29250 inferred_type: None,
29251 },
29252 )),
29253 trailing_comments: vec![],
29254 }));
29255 }
29256 }
29257 Ok(Expression::RegexpLike(f))
29258 } else {
29259 Ok(e)
29260 }
29261 }
29262
29263 Action::ArrayPositionSnowflakeSwap => {
29264 // ARRAY_POSITION(arr, elem) -> ARRAY_POSITION(elem, arr) for Snowflake
29265 if let Expression::ArrayPosition(f) = e {
29266 Ok(Expression::ArrayPosition(Box::new(
29267 crate::expressions::BinaryFunc {
29268 this: f.expression,
29269 expression: f.this,
29270 original_name: f.original_name,
29271 inferred_type: f.inferred_type,
29272 },
29273 )))
29274 } else {
29275 Ok(e)
29276 }
29277 }
29278
29279 Action::SnowflakeArrayPositionToDuckDB => {
29280 // Snowflake ARRAY_POSITION(value, array) -> DuckDB ARRAY_POSITION(array, value) - 1
29281 // Snowflake uses 0-based indexing, DuckDB uses 1-based
29282 // The parser has this=value, expression=array (Snowflake order)
29283 if let Expression::ArrayPosition(f) = e {
29284 // Create ARRAY_POSITION(array, value) in standard order
29285 let standard_pos =
29286 Expression::ArrayPosition(Box::new(crate::expressions::BinaryFunc {
29287 this: f.expression, // array
29288 expression: f.this, // value
29289 original_name: f.original_name,
29290 inferred_type: f.inferred_type,
29291 }));
29292 // Subtract 1 for zero-based indexing
29293 Ok(Expression::Sub(Box::new(BinaryOp {
29294 left: standard_pos,
29295 right: Expression::number(1),
29296 left_comments: vec![],
29297 operator_comments: vec![],
29298 trailing_comments: vec![],
29299 inferred_type: None,
29300 })))
29301 } else {
29302 Ok(e)
29303 }
29304 }
29305
29306 Action::ArrayDistinctConvert => {
29307 // ARRAY_DISTINCT(arr) -> DuckDB NULL-aware CASE:
29308 // CASE WHEN ARRAY_LENGTH(arr) <> LIST_COUNT(arr)
29309 // THEN LIST_APPEND(LIST_DISTINCT(LIST_FILTER(arr, _u -> NOT _u IS NULL)), NULL)
29310 // ELSE LIST_DISTINCT(arr)
29311 // END
29312 if let Expression::ArrayDistinct(f) = e {
29313 let arr = f.this;
29314
29315 // ARRAY_LENGTH(arr)
29316 let array_length = Expression::Function(Box::new(Function::new(
29317 "ARRAY_LENGTH".to_string(),
29318 vec![arr.clone()],
29319 )));
29320 // LIST_COUNT(arr)
29321 let list_count = Expression::Function(Box::new(Function::new(
29322 "LIST_COUNT".to_string(),
29323 vec![arr.clone()],
29324 )));
29325 // ARRAY_LENGTH(arr) <> LIST_COUNT(arr)
29326 let neq = Expression::Neq(Box::new(crate::expressions::BinaryOp {
29327 left: array_length,
29328 right: list_count,
29329 left_comments: vec![],
29330 operator_comments: vec![],
29331 trailing_comments: vec![],
29332 inferred_type: None,
29333 }));
29334
29335 // _u column
29336 let u_col = Expression::column("_u");
29337 // NOT _u IS NULL
29338 let u_is_null = Expression::IsNull(Box::new(crate::expressions::IsNull {
29339 this: u_col.clone(),
29340 not: false,
29341 postfix_form: false,
29342 }));
29343 let not_u_is_null =
29344 Expression::Not(Box::new(crate::expressions::UnaryOp {
29345 this: u_is_null,
29346 inferred_type: None,
29347 }));
29348 // _u -> NOT _u IS NULL
29349 let filter_lambda =
29350 Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
29351 parameters: vec![crate::expressions::Identifier::new("_u")],
29352 body: not_u_is_null,
29353 colon: false,
29354 parameter_types: vec![],
29355 }));
29356 // LIST_FILTER(arr, _u -> NOT _u IS NULL)
29357 let list_filter = Expression::Function(Box::new(Function::new(
29358 "LIST_FILTER".to_string(),
29359 vec![arr.clone(), filter_lambda],
29360 )));
29361 // LIST_DISTINCT(LIST_FILTER(arr, ...))
29362 let list_distinct_filtered = Expression::Function(Box::new(Function::new(
29363 "LIST_DISTINCT".to_string(),
29364 vec![list_filter],
29365 )));
29366 // LIST_APPEND(LIST_DISTINCT(LIST_FILTER(...)), NULL)
29367 let list_append = Expression::Function(Box::new(Function::new(
29368 "LIST_APPEND".to_string(),
29369 vec![list_distinct_filtered, Expression::Null(Null)],
29370 )));
29371
29372 // LIST_DISTINCT(arr)
29373 let list_distinct = Expression::Function(Box::new(Function::new(
29374 "LIST_DISTINCT".to_string(),
29375 vec![arr],
29376 )));
29377
29378 // CASE WHEN neq THEN list_append ELSE list_distinct END
29379 Ok(Expression::Case(Box::new(Case {
29380 operand: None,
29381 whens: vec![(neq, list_append)],
29382 else_: Some(list_distinct),
29383 comments: Vec::new(),
29384 inferred_type: None,
29385 })))
29386 } else {
29387 Ok(e)
29388 }
29389 }
29390
29391 Action::ArrayDistinctClickHouse => {
29392 // ARRAY_DISTINCT(arr) -> arrayDistinct(arr) for ClickHouse
29393 if let Expression::ArrayDistinct(f) = e {
29394 Ok(Expression::Function(Box::new(Function::new(
29395 "arrayDistinct".to_string(),
29396 vec![f.this],
29397 ))))
29398 } else {
29399 Ok(e)
29400 }
29401 }
29402
29403 Action::ArrayContainsDuckDBConvert => {
29404 // Snowflake ARRAY_CONTAINS(value, array) -> DuckDB NULL-aware:
29405 // CASE WHEN value IS NULL
29406 // THEN NULLIF(ARRAY_LENGTH(array) <> LIST_COUNT(array), FALSE)
29407 // ELSE ARRAY_CONTAINS(array, value)
29408 // END
29409 // Note: In Rust AST from Snowflake parse, this=value (first arg), expression=array (second arg)
29410 if let Expression::ArrayContains(f) = e {
29411 let value = f.this;
29412 let array = f.expression;
29413
29414 // value IS NULL
29415 let value_is_null =
29416 Expression::IsNull(Box::new(crate::expressions::IsNull {
29417 this: value.clone(),
29418 not: false,
29419 postfix_form: false,
29420 }));
29421
29422 // ARRAY_LENGTH(array)
29423 let array_length = Expression::Function(Box::new(Function::new(
29424 "ARRAY_LENGTH".to_string(),
29425 vec![array.clone()],
29426 )));
29427 // LIST_COUNT(array)
29428 let list_count = Expression::Function(Box::new(Function::new(
29429 "LIST_COUNT".to_string(),
29430 vec![array.clone()],
29431 )));
29432 // ARRAY_LENGTH(array) <> LIST_COUNT(array)
29433 let neq = Expression::Neq(Box::new(crate::expressions::BinaryOp {
29434 left: array_length,
29435 right: list_count,
29436 left_comments: vec![],
29437 operator_comments: vec![],
29438 trailing_comments: vec![],
29439 inferred_type: None,
29440 }));
29441 // NULLIF(ARRAY_LENGTH(array) <> LIST_COUNT(array), FALSE)
29442 let nullif = Expression::Nullif(Box::new(crate::expressions::Nullif {
29443 this: Box::new(neq),
29444 expression: Box::new(Expression::Boolean(
29445 crate::expressions::BooleanLiteral { value: false },
29446 )),
29447 }));
29448
29449 // ARRAY_CONTAINS(array, value) - DuckDB syntax: array first, value second
29450 let array_contains = Expression::Function(Box::new(Function::new(
29451 "ARRAY_CONTAINS".to_string(),
29452 vec![array, value],
29453 )));
29454
29455 // CASE WHEN value IS NULL THEN NULLIF(...) ELSE ARRAY_CONTAINS(array, value) END
29456 Ok(Expression::Case(Box::new(Case {
29457 operand: None,
29458 whens: vec![(value_is_null, nullif)],
29459 else_: Some(array_contains),
29460 comments: Vec::new(),
29461 inferred_type: None,
29462 })))
29463 } else {
29464 Ok(e)
29465 }
29466 }
29467
29468 Action::StrPositionExpand => {
29469 // StrPosition with position arg -> complex STRPOS expansion for Presto/DuckDB
29470 // For Presto: IF(STRPOS(SUBSTRING(str, pos), substr) = 0, 0, STRPOS(SUBSTRING(str, pos), substr) + pos - 1)
29471 // For DuckDB: CASE WHEN STRPOS(SUBSTRING(str, pos), substr) = 0 THEN 0 ELSE STRPOS(SUBSTRING(str, pos), substr) + pos - 1 END
29472 if let Expression::StrPosition(sp) = e {
29473 let crate::expressions::StrPosition {
29474 this,
29475 substr,
29476 position,
29477 occurrence,
29478 } = *sp;
29479 let string = *this;
29480 let substr_expr = match substr {
29481 Some(s) => *s,
29482 None => Expression::Null(Null),
29483 };
29484 let pos = match position {
29485 Some(p) => *p,
29486 None => Expression::number(1),
29487 };
29488
29489 // SUBSTRING(string, pos)
29490 let substring_call = Expression::Function(Box::new(Function::new(
29491 "SUBSTRING".to_string(),
29492 vec![string.clone(), pos.clone()],
29493 )));
29494 // STRPOS(SUBSTRING(string, pos), substr)
29495 let strpos_call = Expression::Function(Box::new(Function::new(
29496 "STRPOS".to_string(),
29497 vec![substring_call, substr_expr.clone()],
29498 )));
29499 // STRPOS(...) + pos - 1
29500 let pos_adjusted =
29501 Expression::Sub(Box::new(crate::expressions::BinaryOp::new(
29502 Expression::Add(Box::new(crate::expressions::BinaryOp::new(
29503 strpos_call.clone(),
29504 pos.clone(),
29505 ))),
29506 Expression::number(1),
29507 )));
29508 // STRPOS(...) = 0
29509 let is_zero = Expression::Eq(Box::new(crate::expressions::BinaryOp::new(
29510 strpos_call.clone(),
29511 Expression::number(0),
29512 )));
29513
29514 match target {
29515 DialectType::Presto | DialectType::Trino | DialectType::Athena => {
29516 // IF(STRPOS(SUBSTRING(str, pos), substr) = 0, 0, STRPOS(SUBSTRING(str, pos), substr) + pos - 1)
29517 Ok(Expression::Function(Box::new(Function::new(
29518 "IF".to_string(),
29519 vec![is_zero, Expression::number(0), pos_adjusted],
29520 ))))
29521 }
29522 DialectType::DuckDB => {
29523 // CASE WHEN STRPOS(SUBSTRING(str, pos), substr) = 0 THEN 0 ELSE STRPOS(SUBSTRING(str, pos), substr) + pos - 1 END
29524 Ok(Expression::Case(Box::new(Case {
29525 operand: None,
29526 whens: vec![(is_zero, Expression::number(0))],
29527 else_: Some(pos_adjusted),
29528 comments: Vec::new(),
29529 inferred_type: None,
29530 })))
29531 }
29532 _ => {
29533 // Reconstruct StrPosition
29534 Ok(Expression::StrPosition(Box::new(
29535 crate::expressions::StrPosition {
29536 this: Box::new(string),
29537 substr: Some(Box::new(substr_expr)),
29538 position: Some(Box::new(pos)),
29539 occurrence,
29540 },
29541 )))
29542 }
29543 }
29544 } else {
29545 Ok(e)
29546 }
29547 }
29548
29549 Action::MonthsBetweenConvert => {
29550 if let Expression::MonthsBetween(mb) = e {
29551 let crate::expressions::BinaryFunc {
29552 this: end_date,
29553 expression: start_date,
29554 ..
29555 } = *mb;
29556 match target {
29557 DialectType::DuckDB => {
29558 let cast_end = Self::ensure_cast_date(end_date);
29559 let cast_start = Self::ensure_cast_date(start_date);
29560 let dd = Expression::Function(Box::new(Function::new(
29561 "DATE_DIFF".to_string(),
29562 vec![
29563 Expression::string("MONTH"),
29564 cast_start.clone(),
29565 cast_end.clone(),
29566 ],
29567 )));
29568 let day_end = Expression::Function(Box::new(Function::new(
29569 "DAY".to_string(),
29570 vec![cast_end.clone()],
29571 )));
29572 let day_start = Expression::Function(Box::new(Function::new(
29573 "DAY".to_string(),
29574 vec![cast_start.clone()],
29575 )));
29576 let last_day_end = Expression::Function(Box::new(Function::new(
29577 "LAST_DAY".to_string(),
29578 vec![cast_end.clone()],
29579 )));
29580 let last_day_start = Expression::Function(Box::new(Function::new(
29581 "LAST_DAY".to_string(),
29582 vec![cast_start.clone()],
29583 )));
29584 let day_last_end = Expression::Function(Box::new(Function::new(
29585 "DAY".to_string(),
29586 vec![last_day_end],
29587 )));
29588 let day_last_start = Expression::Function(Box::new(Function::new(
29589 "DAY".to_string(),
29590 vec![last_day_start],
29591 )));
29592 let cond1 = Expression::Eq(Box::new(BinaryOp::new(
29593 day_end.clone(),
29594 day_last_end,
29595 )));
29596 let cond2 = Expression::Eq(Box::new(BinaryOp::new(
29597 day_start.clone(),
29598 day_last_start,
29599 )));
29600 let both_cond =
29601 Expression::And(Box::new(BinaryOp::new(cond1, cond2)));
29602 let day_diff =
29603 Expression::Sub(Box::new(BinaryOp::new(day_end, day_start)));
29604 let day_diff_paren =
29605 Expression::Paren(Box::new(crate::expressions::Paren {
29606 this: day_diff,
29607 trailing_comments: Vec::new(),
29608 }));
29609 let frac = Expression::Div(Box::new(BinaryOp::new(
29610 day_diff_paren,
29611 Expression::Literal(Box::new(Literal::Number(
29612 "31.0".to_string(),
29613 ))),
29614 )));
29615 let case_expr = Expression::Case(Box::new(Case {
29616 operand: None,
29617 whens: vec![(both_cond, Expression::number(0))],
29618 else_: Some(frac),
29619 comments: Vec::new(),
29620 inferred_type: None,
29621 }));
29622 Ok(Expression::Add(Box::new(BinaryOp::new(dd, case_expr))))
29623 }
29624 DialectType::Snowflake | DialectType::Redshift => {
29625 let unit = Expression::Identifier(Identifier::new("MONTH"));
29626 Ok(Expression::Function(Box::new(Function::new(
29627 "DATEDIFF".to_string(),
29628 vec![unit, start_date, end_date],
29629 ))))
29630 }
29631 DialectType::Presto | DialectType::Trino | DialectType::Athena => {
29632 Ok(Expression::Function(Box::new(Function::new(
29633 "DATE_DIFF".to_string(),
29634 vec![Expression::string("MONTH"), start_date, end_date],
29635 ))))
29636 }
29637 _ => Ok(Expression::MonthsBetween(Box::new(
29638 crate::expressions::BinaryFunc {
29639 this: end_date,
29640 expression: start_date,
29641 original_name: None,
29642 inferred_type: None,
29643 },
29644 ))),
29645 }
29646 } else {
29647 Ok(e)
29648 }
29649 }
29650
29651 Action::AddMonthsConvert => {
29652 if let Expression::AddMonths(am) = e {
29653 let date = am.this;
29654 let val = am.expression;
29655 match target {
29656 DialectType::TSQL | DialectType::Fabric => {
29657 let cast_date = Self::ensure_cast_datetime2(date);
29658 Ok(Expression::Function(Box::new(Function::new(
29659 "DATEADD".to_string(),
29660 vec![
29661 Expression::Identifier(Identifier::new("MONTH")),
29662 val,
29663 cast_date,
29664 ],
29665 ))))
29666 }
29667 DialectType::DuckDB if matches!(source, DialectType::Snowflake) => {
29668 // DuckDB ADD_MONTHS from Snowflake: CASE WHEN LAST_DAY(date) = date THEN LAST_DAY(date + interval) ELSE date + interval END
29669 // Optionally wrapped in CAST(... AS type) if the input had a specific type
29670
29671 // Determine the cast type from the date expression
29672 let (cast_date, return_type) = match &date {
29673 Expression::Literal(lit)
29674 if matches!(lit.as_ref(), Literal::String(_)) =>
29675 {
29676 // String literal: CAST(str AS TIMESTAMP), no outer CAST
29677 (
29678 Expression::Cast(Box::new(Cast {
29679 this: date.clone(),
29680 to: DataType::Timestamp {
29681 precision: None,
29682 timezone: false,
29683 },
29684 trailing_comments: Vec::new(),
29685 double_colon_syntax: false,
29686 format: None,
29687 default: None,
29688 inferred_type: None,
29689 })),
29690 None,
29691 )
29692 }
29693 Expression::Cast(c) => {
29694 // Already cast (e.g., '2023-01-31'::DATE) - keep the cast, wrap result in CAST(... AS type)
29695 (date.clone(), Some(c.to.clone()))
29696 }
29697 _ => {
29698 // Expression or NULL::TYPE - keep as-is, check for cast type
29699 if let Expression::Cast(c) = &date {
29700 (date.clone(), Some(c.to.clone()))
29701 } else {
29702 (date.clone(), None)
29703 }
29704 }
29705 };
29706
29707 // Build the interval expression
29708 // For non-integer values (float, decimal, cast), use TO_MONTHS(CAST(ROUND(val) AS INT))
29709 // For integer values, use INTERVAL val MONTH
29710 let is_non_integer_val = match &val {
29711 Expression::Literal(lit)
29712 if matches!(lit.as_ref(), Literal::Number(_)) =>
29713 {
29714 let Literal::Number(n) = lit.as_ref() else {
29715 unreachable!()
29716 };
29717 n.contains('.')
29718 }
29719 Expression::Cast(_) => true, // e.g., 3.2::DECIMAL(10,2)
29720 Expression::Neg(n) => {
29721 if let Expression::Literal(lit) = &n.this {
29722 if let Literal::Number(s) = lit.as_ref() {
29723 s.contains('.')
29724 } else {
29725 false
29726 }
29727 } else {
29728 false
29729 }
29730 }
29731 _ => false,
29732 };
29733
29734 let add_interval = if is_non_integer_val {
29735 // TO_MONTHS(CAST(ROUND(val) AS INT))
29736 let round_val = Expression::Function(Box::new(Function::new(
29737 "ROUND".to_string(),
29738 vec![val.clone()],
29739 )));
29740 let cast_int = Expression::Cast(Box::new(Cast {
29741 this: round_val,
29742 to: DataType::Int {
29743 length: None,
29744 integer_spelling: false,
29745 },
29746 trailing_comments: Vec::new(),
29747 double_colon_syntax: false,
29748 format: None,
29749 default: None,
29750 inferred_type: None,
29751 }));
29752 Expression::Function(Box::new(Function::new(
29753 "TO_MONTHS".to_string(),
29754 vec![cast_int],
29755 )))
29756 } else {
29757 // INTERVAL val MONTH
29758 // For negative numbers, wrap in parens
29759 let interval_val = match &val {
29760 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n.starts_with('-')) =>
29761 {
29762 let Literal::Number(_) = lit.as_ref() else {
29763 unreachable!()
29764 };
29765 Expression::Paren(Box::new(Paren {
29766 this: val.clone(),
29767 trailing_comments: Vec::new(),
29768 }))
29769 }
29770 Expression::Neg(_) => Expression::Paren(Box::new(Paren {
29771 this: val.clone(),
29772 trailing_comments: Vec::new(),
29773 })),
29774 Expression::Null(_) => Expression::Paren(Box::new(Paren {
29775 this: val.clone(),
29776 trailing_comments: Vec::new(),
29777 })),
29778 _ => val.clone(),
29779 };
29780 Expression::Interval(Box::new(crate::expressions::Interval {
29781 this: Some(interval_val),
29782 unit: Some(crate::expressions::IntervalUnitSpec::Simple {
29783 unit: crate::expressions::IntervalUnit::Month,
29784 use_plural: false,
29785 }),
29786 }))
29787 };
29788
29789 // Build: date + interval
29790 let date_plus_interval = Expression::Add(Box::new(BinaryOp::new(
29791 cast_date.clone(),
29792 add_interval.clone(),
29793 )));
29794
29795 // Build LAST_DAY(date)
29796 let last_day_date = Expression::Function(Box::new(Function::new(
29797 "LAST_DAY".to_string(),
29798 vec![cast_date.clone()],
29799 )));
29800
29801 // Build LAST_DAY(date + interval)
29802 let last_day_date_plus =
29803 Expression::Function(Box::new(Function::new(
29804 "LAST_DAY".to_string(),
29805 vec![date_plus_interval.clone()],
29806 )));
29807
29808 // Build: CASE WHEN LAST_DAY(date) = date THEN LAST_DAY(date + interval) ELSE date + interval END
29809 let case_expr = Expression::Case(Box::new(Case {
29810 operand: None,
29811 whens: vec![(
29812 Expression::Eq(Box::new(BinaryOp::new(
29813 last_day_date,
29814 cast_date.clone(),
29815 ))),
29816 last_day_date_plus,
29817 )],
29818 else_: Some(date_plus_interval),
29819 comments: Vec::new(),
29820 inferred_type: None,
29821 }));
29822
29823 // Wrap in CAST(... AS type) if needed
29824 if let Some(dt) = return_type {
29825 Ok(Expression::Cast(Box::new(Cast {
29826 this: case_expr,
29827 to: dt,
29828 trailing_comments: Vec::new(),
29829 double_colon_syntax: false,
29830 format: None,
29831 default: None,
29832 inferred_type: None,
29833 })))
29834 } else {
29835 Ok(case_expr)
29836 }
29837 }
29838 DialectType::DuckDB => {
29839 // Non-Snowflake source: simple date + INTERVAL
29840 let cast_date = if matches!(&date, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
29841 {
29842 Expression::Cast(Box::new(Cast {
29843 this: date,
29844 to: DataType::Timestamp {
29845 precision: None,
29846 timezone: false,
29847 },
29848 trailing_comments: Vec::new(),
29849 double_colon_syntax: false,
29850 format: None,
29851 default: None,
29852 inferred_type: None,
29853 }))
29854 } else {
29855 date
29856 };
29857 let interval =
29858 Expression::Interval(Box::new(crate::expressions::Interval {
29859 this: Some(val),
29860 unit: Some(crate::expressions::IntervalUnitSpec::Simple {
29861 unit: crate::expressions::IntervalUnit::Month,
29862 use_plural: false,
29863 }),
29864 }));
29865 Ok(Expression::Add(Box::new(BinaryOp::new(
29866 cast_date, interval,
29867 ))))
29868 }
29869 DialectType::Snowflake => {
29870 // Keep ADD_MONTHS when source is also Snowflake
29871 if matches!(source, DialectType::Snowflake) {
29872 Ok(Expression::Function(Box::new(Function::new(
29873 "ADD_MONTHS".to_string(),
29874 vec![date, val],
29875 ))))
29876 } else {
29877 Ok(Expression::Function(Box::new(Function::new(
29878 "DATEADD".to_string(),
29879 vec![
29880 Expression::Identifier(Identifier::new("MONTH")),
29881 val,
29882 date,
29883 ],
29884 ))))
29885 }
29886 }
29887 DialectType::Redshift => {
29888 Ok(Expression::Function(Box::new(Function::new(
29889 "DATEADD".to_string(),
29890 vec![
29891 Expression::Identifier(Identifier::new("MONTH")),
29892 val,
29893 date,
29894 ],
29895 ))))
29896 }
29897 DialectType::Presto | DialectType::Trino | DialectType::Athena => {
29898 let cast_date = if matches!(&date, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
29899 {
29900 Expression::Cast(Box::new(Cast {
29901 this: date,
29902 to: DataType::Timestamp {
29903 precision: None,
29904 timezone: false,
29905 },
29906 trailing_comments: Vec::new(),
29907 double_colon_syntax: false,
29908 format: None,
29909 default: None,
29910 inferred_type: None,
29911 }))
29912 } else {
29913 date
29914 };
29915 Ok(Expression::Function(Box::new(Function::new(
29916 "DATE_ADD".to_string(),
29917 vec![Expression::string("MONTH"), val, cast_date],
29918 ))))
29919 }
29920 DialectType::BigQuery => {
29921 let interval =
29922 Expression::Interval(Box::new(crate::expressions::Interval {
29923 this: Some(val),
29924 unit: Some(crate::expressions::IntervalUnitSpec::Simple {
29925 unit: crate::expressions::IntervalUnit::Month,
29926 use_plural: false,
29927 }),
29928 }));
29929 let cast_date = if matches!(&date, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
29930 {
29931 Expression::Cast(Box::new(Cast {
29932 this: date,
29933 to: DataType::Custom {
29934 name: "DATETIME".to_string(),
29935 },
29936 trailing_comments: Vec::new(),
29937 double_colon_syntax: false,
29938 format: None,
29939 default: None,
29940 inferred_type: None,
29941 }))
29942 } else {
29943 date
29944 };
29945 Ok(Expression::Function(Box::new(Function::new(
29946 "DATE_ADD".to_string(),
29947 vec![cast_date, interval],
29948 ))))
29949 }
29950 DialectType::Spark | DialectType::Databricks | DialectType::Hive => {
29951 Ok(Expression::Function(Box::new(Function::new(
29952 "ADD_MONTHS".to_string(),
29953 vec![date, val],
29954 ))))
29955 }
29956 _ => {
29957 // Default: keep as AddMonths expression
29958 Ok(Expression::AddMonths(Box::new(
29959 crate::expressions::BinaryFunc {
29960 this: date,
29961 expression: val,
29962 original_name: None,
29963 inferred_type: None,
29964 },
29965 )))
29966 }
29967 }
29968 } else {
29969 Ok(e)
29970 }
29971 }
29972
29973 Action::PercentileContConvert => {
29974 // PERCENTILE_CONT(p) WITHIN GROUP (ORDER BY col) ->
29975 // Presto/Trino: APPROX_PERCENTILE(col, p)
29976 // Spark/Databricks: PERCENTILE_APPROX(col, p)
29977 if let Expression::WithinGroup(wg) = e {
29978 // Extract percentile value and order by column
29979 let (percentile, _is_disc) = match &wg.this {
29980 Expression::Function(f) => {
29981 let is_disc = f.name.eq_ignore_ascii_case("PERCENTILE_DISC");
29982 let pct = f.args.first().cloned().unwrap_or(Expression::Literal(
29983 Box::new(Literal::Number("0.5".to_string())),
29984 ));
29985 (pct, is_disc)
29986 }
29987 Expression::AggregateFunction(af) => {
29988 let is_disc = af.name.eq_ignore_ascii_case("PERCENTILE_DISC");
29989 let pct = af.args.first().cloned().unwrap_or(Expression::Literal(
29990 Box::new(Literal::Number("0.5".to_string())),
29991 ));
29992 (pct, is_disc)
29993 }
29994 Expression::PercentileCont(pc) => (pc.percentile.clone(), false),
29995 _ => return Ok(Expression::WithinGroup(wg)),
29996 };
29997 let col = wg.order_by.first().map(|o| o.this.clone()).unwrap_or(
29998 Expression::Literal(Box::new(Literal::Number("1".to_string()))),
29999 );
30000
30001 let func_name = match target {
30002 DialectType::Presto | DialectType::Trino | DialectType::Athena => {
30003 "APPROX_PERCENTILE"
30004 }
30005 _ => "PERCENTILE_APPROX", // Spark, Databricks
30006 };
30007 Ok(Expression::Function(Box::new(Function::new(
30008 func_name.to_string(),
30009 vec![col, percentile],
30010 ))))
30011 } else {
30012 Ok(e)
30013 }
30014 }
30015
30016 Action::CurrentUserSparkParens => {
30017 // CURRENT_USER -> CURRENT_USER() for Spark
30018 if let Expression::CurrentUser(_) = e {
30019 Ok(Expression::Function(Box::new(Function::new(
30020 "CURRENT_USER".to_string(),
30021 vec![],
30022 ))))
30023 } else {
30024 Ok(e)
30025 }
30026 }
30027
30028 Action::SparkDateFuncCast => {
30029 // MONTH/YEAR/DAY('string') from Spark -> wrap arg in CAST to DATE
30030 let cast_arg = |arg: Expression| -> Expression {
30031 match target {
30032 DialectType::Presto | DialectType::Trino | DialectType::Athena => {
30033 Self::double_cast_timestamp_date(arg)
30034 }
30035 _ => {
30036 // DuckDB, PostgreSQL, etc: CAST(arg AS DATE)
30037 Self::ensure_cast_date(arg)
30038 }
30039 }
30040 };
30041 match e {
30042 Expression::Month(f) => Ok(Expression::Month(Box::new(
30043 crate::expressions::UnaryFunc::new(cast_arg(f.this)),
30044 ))),
30045 Expression::Year(f) => Ok(Expression::Year(Box::new(
30046 crate::expressions::UnaryFunc::new(cast_arg(f.this)),
30047 ))),
30048 Expression::Day(f) => Ok(Expression::Day(Box::new(
30049 crate::expressions::UnaryFunc::new(cast_arg(f.this)),
30050 ))),
30051 other => Ok(other),
30052 }
30053 }
30054
30055 Action::MapFromArraysConvert => {
30056 // Expression::MapFromArrays -> target-specific
30057 if let Expression::MapFromArrays(mfa) = e {
30058 let keys = mfa.this;
30059 let values = mfa.expression;
30060 match target {
30061 DialectType::Snowflake => Ok(Expression::Function(Box::new(
30062 Function::new("OBJECT_CONSTRUCT".to_string(), vec![keys, values]),
30063 ))),
30064 _ => {
30065 // Hive, Presto, DuckDB, etc.: MAP(keys, values)
30066 Ok(Expression::Function(Box::new(Function::new(
30067 "MAP".to_string(),
30068 vec![keys, values],
30069 ))))
30070 }
30071 }
30072 } else {
30073 Ok(e)
30074 }
30075 }
30076
30077 Action::AnyToExists => {
30078 if let Expression::Any(q) = e {
30079 if let Some(op) = q.op.clone() {
30080 let lambda_param = crate::expressions::Identifier::new("x");
30081 let rhs = Expression::Identifier(lambda_param.clone());
30082 let body = match op {
30083 crate::expressions::QuantifiedOp::Eq => {
30084 Expression::Eq(Box::new(BinaryOp::new(q.this, rhs)))
30085 }
30086 crate::expressions::QuantifiedOp::Neq => {
30087 Expression::Neq(Box::new(BinaryOp::new(q.this, rhs)))
30088 }
30089 crate::expressions::QuantifiedOp::Lt => {
30090 Expression::Lt(Box::new(BinaryOp::new(q.this, rhs)))
30091 }
30092 crate::expressions::QuantifiedOp::Lte => {
30093 Expression::Lte(Box::new(BinaryOp::new(q.this, rhs)))
30094 }
30095 crate::expressions::QuantifiedOp::Gt => {
30096 Expression::Gt(Box::new(BinaryOp::new(q.this, rhs)))
30097 }
30098 crate::expressions::QuantifiedOp::Gte => {
30099 Expression::Gte(Box::new(BinaryOp::new(q.this, rhs)))
30100 }
30101 };
30102 let lambda =
30103 Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
30104 parameters: vec![lambda_param],
30105 body,
30106 colon: false,
30107 parameter_types: Vec::new(),
30108 }));
30109 Ok(Expression::Function(Box::new(Function::new(
30110 "EXISTS".to_string(),
30111 vec![q.subquery, lambda],
30112 ))))
30113 } else {
30114 Ok(Expression::Any(q))
30115 }
30116 } else {
30117 Ok(e)
30118 }
30119 }
30120
30121 Action::GenerateSeriesConvert => {
30122 // GENERATE_SERIES(start, end[, step]) -> SEQUENCE for Spark/Databricks/Hive, wrapped in UNNEST/EXPLODE
30123 // For DuckDB target: wrap in UNNEST(GENERATE_SERIES(...))
30124 // For PG/Redshift target: keep as GENERATE_SERIES but normalize interval string step
30125 if let Expression::Function(f) = e {
30126 if f.name.eq_ignore_ascii_case("GENERATE_SERIES") && f.args.len() >= 2 {
30127 let start = f.args[0].clone();
30128 let end = f.args[1].clone();
30129 let step = f.args.get(2).cloned();
30130
30131 // Normalize step: convert string interval like '1day' or ' 2 days ' to INTERVAL expression
30132 let step = step.map(|s| Self::normalize_interval_string(s, target));
30133
30134 // Helper: wrap CURRENT_TIMESTAMP in CAST(... AS TIMESTAMP) for Presto/Trino/Spark
30135 let maybe_cast_timestamp = |arg: Expression| -> Expression {
30136 if matches!(
30137 target,
30138 DialectType::Presto
30139 | DialectType::Trino
30140 | DialectType::Athena
30141 | DialectType::Spark
30142 | DialectType::Databricks
30143 | DialectType::Hive
30144 ) {
30145 match &arg {
30146 Expression::CurrentTimestamp(_) => {
30147 Expression::Cast(Box::new(Cast {
30148 this: arg,
30149 to: DataType::Timestamp {
30150 precision: None,
30151 timezone: false,
30152 },
30153 trailing_comments: Vec::new(),
30154 double_colon_syntax: false,
30155 format: None,
30156 default: None,
30157 inferred_type: None,
30158 }))
30159 }
30160 _ => arg,
30161 }
30162 } else {
30163 arg
30164 }
30165 };
30166
30167 let start = maybe_cast_timestamp(start);
30168 let end = maybe_cast_timestamp(end);
30169
30170 // For PostgreSQL/Redshift target, keep as GENERATE_SERIES
30171 if matches!(target, DialectType::PostgreSQL | DialectType::Redshift) {
30172 let mut gs_args = vec![start, end];
30173 if let Some(step) = step {
30174 gs_args.push(step);
30175 }
30176 return Ok(Expression::Function(Box::new(Function::new(
30177 "GENERATE_SERIES".to_string(),
30178 gs_args,
30179 ))));
30180 }
30181
30182 // For DuckDB target: wrap in UNNEST(GENERATE_SERIES(...))
30183 if matches!(target, DialectType::DuckDB) {
30184 let mut gs_args = vec![start, end];
30185 if let Some(step) = step {
30186 gs_args.push(step);
30187 }
30188 let gs = Expression::Function(Box::new(Function::new(
30189 "GENERATE_SERIES".to_string(),
30190 gs_args,
30191 )));
30192 return Ok(Expression::Function(Box::new(Function::new(
30193 "UNNEST".to_string(),
30194 vec![gs],
30195 ))));
30196 }
30197
30198 let mut seq_args = vec![start, end];
30199 if let Some(step) = step {
30200 seq_args.push(step);
30201 }
30202
30203 let seq = Expression::Function(Box::new(Function::new(
30204 "SEQUENCE".to_string(),
30205 seq_args,
30206 )));
30207
30208 match target {
30209 DialectType::Presto | DialectType::Trino | DialectType::Athena => {
30210 // Wrap in UNNEST
30211 Ok(Expression::Function(Box::new(Function::new(
30212 "UNNEST".to_string(),
30213 vec![seq],
30214 ))))
30215 }
30216 DialectType::Spark
30217 | DialectType::Databricks
30218 | DialectType::Hive => {
30219 // Wrap in EXPLODE
30220 Ok(Expression::Function(Box::new(Function::new(
30221 "EXPLODE".to_string(),
30222 vec![seq],
30223 ))))
30224 }
30225 _ => {
30226 // Just SEQUENCE for others
30227 Ok(seq)
30228 }
30229 }
30230 } else {
30231 Ok(Expression::Function(f))
30232 }
30233 } else {
30234 Ok(e)
30235 }
30236 }
30237
30238 Action::ConcatCoalesceWrap => {
30239 // CONCAT(a, b) function -> CONCAT(COALESCE(CAST(a AS VARCHAR), ''), ...) for Presto
30240 // CONCAT(a, b) function -> CONCAT(COALESCE(a, ''), ...) for ClickHouse
30241 if let Expression::Function(f) = e {
30242 if f.name.eq_ignore_ascii_case("CONCAT") {
30243 let new_args: Vec<Expression> = f
30244 .args
30245 .into_iter()
30246 .map(|arg| {
30247 let cast_arg = if matches!(
30248 target,
30249 DialectType::Presto
30250 | DialectType::Trino
30251 | DialectType::Athena
30252 ) {
30253 Expression::Cast(Box::new(Cast {
30254 this: arg,
30255 to: DataType::VarChar {
30256 length: None,
30257 parenthesized_length: false,
30258 },
30259 trailing_comments: Vec::new(),
30260 double_colon_syntax: false,
30261 format: None,
30262 default: None,
30263 inferred_type: None,
30264 }))
30265 } else {
30266 arg
30267 };
30268 Expression::Function(Box::new(Function::new(
30269 "COALESCE".to_string(),
30270 vec![cast_arg, Expression::string("")],
30271 )))
30272 })
30273 .collect();
30274 Ok(Expression::Function(Box::new(Function::new(
30275 "CONCAT".to_string(),
30276 new_args,
30277 ))))
30278 } else {
30279 Ok(Expression::Function(f))
30280 }
30281 } else {
30282 Ok(e)
30283 }
30284 }
30285
30286 Action::PipeConcatToConcat => {
30287 // a || b (Concat operator) -> CONCAT(CAST(a AS VARCHAR), CAST(b AS VARCHAR)) for Presto/Trino
30288 if let Expression::Concat(op) = e {
30289 let cast_left = Expression::Cast(Box::new(Cast {
30290 this: op.left,
30291 to: DataType::VarChar {
30292 length: None,
30293 parenthesized_length: false,
30294 },
30295 trailing_comments: Vec::new(),
30296 double_colon_syntax: false,
30297 format: None,
30298 default: None,
30299 inferred_type: None,
30300 }));
30301 let cast_right = Expression::Cast(Box::new(Cast {
30302 this: op.right,
30303 to: DataType::VarChar {
30304 length: None,
30305 parenthesized_length: false,
30306 },
30307 trailing_comments: Vec::new(),
30308 double_colon_syntax: false,
30309 format: None,
30310 default: None,
30311 inferred_type: None,
30312 }));
30313 Ok(Expression::Function(Box::new(Function::new(
30314 "CONCAT".to_string(),
30315 vec![cast_left, cast_right],
30316 ))))
30317 } else {
30318 Ok(e)
30319 }
30320 }
30321
30322 Action::DivFuncConvert => {
30323 // DIV(a, b) -> target-specific integer division
30324 if let Expression::Function(f) = e {
30325 if f.name.eq_ignore_ascii_case("DIV") && f.args.len() == 2 {
30326 let a = f.args[0].clone();
30327 let b = f.args[1].clone();
30328 match target {
30329 DialectType::DuckDB => {
30330 // DIV(a, b) -> CAST(a // b AS DECIMAL)
30331 let int_div = Expression::IntDiv(Box::new(
30332 crate::expressions::BinaryFunc {
30333 this: a,
30334 expression: b,
30335 original_name: None,
30336 inferred_type: None,
30337 },
30338 ));
30339 Ok(Expression::Cast(Box::new(Cast {
30340 this: int_div,
30341 to: DataType::Decimal {
30342 precision: None,
30343 scale: None,
30344 },
30345 trailing_comments: Vec::new(),
30346 double_colon_syntax: false,
30347 format: None,
30348 default: None,
30349 inferred_type: None,
30350 })))
30351 }
30352 DialectType::BigQuery => {
30353 // DIV(a, b) -> CAST(DIV(a, b) AS NUMERIC)
30354 let div_func = Expression::Function(Box::new(Function::new(
30355 "DIV".to_string(),
30356 vec![a, b],
30357 )));
30358 Ok(Expression::Cast(Box::new(Cast {
30359 this: div_func,
30360 to: DataType::Custom {
30361 name: "NUMERIC".to_string(),
30362 },
30363 trailing_comments: Vec::new(),
30364 double_colon_syntax: false,
30365 format: None,
30366 default: None,
30367 inferred_type: None,
30368 })))
30369 }
30370 DialectType::SQLite => {
30371 // DIV(a, b) -> CAST(CAST(CAST(a AS REAL) / b AS INTEGER) AS REAL)
30372 let cast_a = Expression::Cast(Box::new(Cast {
30373 this: a,
30374 to: DataType::Custom {
30375 name: "REAL".to_string(),
30376 },
30377 trailing_comments: Vec::new(),
30378 double_colon_syntax: false,
30379 format: None,
30380 default: None,
30381 inferred_type: None,
30382 }));
30383 let div = Expression::Div(Box::new(BinaryOp::new(cast_a, b)));
30384 let cast_int = Expression::Cast(Box::new(Cast {
30385 this: div,
30386 to: DataType::Int {
30387 length: None,
30388 integer_spelling: true,
30389 },
30390 trailing_comments: Vec::new(),
30391 double_colon_syntax: false,
30392 format: None,
30393 default: None,
30394 inferred_type: None,
30395 }));
30396 Ok(Expression::Cast(Box::new(Cast {
30397 this: cast_int,
30398 to: DataType::Custom {
30399 name: "REAL".to_string(),
30400 },
30401 trailing_comments: Vec::new(),
30402 double_colon_syntax: false,
30403 format: None,
30404 default: None,
30405 inferred_type: None,
30406 })))
30407 }
30408 DialectType::TSQL | DialectType::Fabric => {
30409 Ok(Self::build_tsql_div_func(a, b, target))
30410 }
30411 _ => Ok(Expression::Function(f)),
30412 }
30413 } else {
30414 Ok(Expression::Function(f))
30415 }
30416 } else {
30417 Ok(e)
30418 }
30419 }
30420
30421 Action::CbrtToPower => match e {
30422 Expression::Cbrt(f) => Ok(Self::build_tsql_cbrt_power(f.this)),
30423 Expression::Function(f) if f.args.len() == 1 => {
30424 let mut args = f.args;
30425 Ok(Self::build_tsql_cbrt_power(args.remove(0)))
30426 }
30427 _ => Ok(e),
30428 },
30429
30430 Action::JsonObjectAggConvert => {
30431 // JSON_OBJECT_AGG/JSONB_OBJECT_AGG -> JSON_GROUP_OBJECT for DuckDB
30432 match e {
30433 Expression::Function(f) => Ok(Expression::Function(Box::new(
30434 Function::new("JSON_GROUP_OBJECT".to_string(), f.args),
30435 ))),
30436 Expression::AggregateFunction(af) => {
30437 // AggregateFunction stores all args in the `args` vec
30438 Ok(Expression::Function(Box::new(Function::new(
30439 "JSON_GROUP_OBJECT".to_string(),
30440 af.args,
30441 ))))
30442 }
30443 other => Ok(other),
30444 }
30445 }
30446
30447 Action::JsonbExistsConvert => {
30448 // JSONB_EXISTS('json', 'key') -> JSON_EXISTS('json', '$.key') for DuckDB
30449 if let Expression::Function(f) = e {
30450 if f.args.len() == 2 {
30451 let json_expr = f.args[0].clone();
30452 let key = match &f.args[1] {
30453 Expression::Literal(lit)
30454 if matches!(
30455 lit.as_ref(),
30456 crate::expressions::Literal::String(_)
30457 ) =>
30458 {
30459 let crate::expressions::Literal::String(s) = lit.as_ref()
30460 else {
30461 unreachable!()
30462 };
30463 format!("$.{}", s)
30464 }
30465 _ => return Ok(Expression::Function(f)),
30466 };
30467 Ok(Expression::Function(Box::new(Function::new(
30468 "JSON_EXISTS".to_string(),
30469 vec![json_expr, Expression::string(&key)],
30470 ))))
30471 } else {
30472 Ok(Expression::Function(f))
30473 }
30474 } else {
30475 Ok(e)
30476 }
30477 }
30478
30479 Action::DateBinConvert => {
30480 // DATE_BIN('interval', ts, origin) -> TIME_BUCKET('interval', ts, origin) for DuckDB
30481 if let Expression::Function(f) = e {
30482 Ok(Expression::Function(Box::new(Function::new(
30483 "TIME_BUCKET".to_string(),
30484 f.args,
30485 ))))
30486 } else {
30487 Ok(e)
30488 }
30489 }
30490
30491 Action::MysqlCastCharToText => {
30492 // MySQL CAST(x AS CHAR) was originally TEXT -> convert to target text type
30493 if let Expression::Cast(mut c) = e {
30494 c.to = DataType::Text;
30495 Ok(Expression::Cast(c))
30496 } else {
30497 Ok(e)
30498 }
30499 }
30500
30501 Action::SparkCastVarcharToString => {
30502 // Spark parses VARCHAR(n)/CHAR(n) as TEXT -> normalize to STRING
30503 match e {
30504 Expression::Cast(mut c) => {
30505 c.to = Self::normalize_varchar_to_string(c.to);
30506 Ok(Expression::Cast(c))
30507 }
30508 Expression::TryCast(mut c) => {
30509 c.to = Self::normalize_varchar_to_string(c.to);
30510 Ok(Expression::TryCast(c))
30511 }
30512 _ => Ok(e),
30513 }
30514 }
30515
30516 Action::MinMaxToLeastGreatest => {
30517 // Multi-arg MIN(a,b,c) -> LEAST(a,b,c), MAX(a,b,c) -> GREATEST(a,b,c)
30518 if let Expression::Function(f) = e {
30519 let new_name = if f.name.eq_ignore_ascii_case("MIN") {
30520 "LEAST"
30521 } else if f.name.eq_ignore_ascii_case("MAX") {
30522 "GREATEST"
30523 } else {
30524 return Ok(Expression::Function(f));
30525 };
30526 Ok(Expression::Function(Box::new(Function::new(
30527 new_name.to_string(),
30528 f.args,
30529 ))))
30530 } else {
30531 Ok(e)
30532 }
30533 }
30534
30535 Action::ClickHouseUniqToApproxCountDistinct => {
30536 // ClickHouse uniq(x) -> APPROX_COUNT_DISTINCT(x) for non-ClickHouse targets
30537 if let Expression::Function(f) = e {
30538 Ok(Expression::Function(Box::new(Function::new(
30539 "APPROX_COUNT_DISTINCT".to_string(),
30540 f.args,
30541 ))))
30542 } else {
30543 Ok(e)
30544 }
30545 }
30546
30547 Action::ClickHouseAnyToAnyValue => {
30548 // ClickHouse any(x) -> ANY_VALUE(x) for non-ClickHouse targets
30549 if let Expression::Function(f) = e {
30550 Ok(Expression::Function(Box::new(Function::new(
30551 "ANY_VALUE".to_string(),
30552 f.args,
30553 ))))
30554 } else {
30555 Ok(e)
30556 }
30557 }
30558
30559 Action::OracleVarchar2ToVarchar => {
30560 // Oracle VARCHAR2(N CHAR/BYTE) / NVARCHAR2(N) -> VarChar(N) for non-Oracle targets
30561 if let Expression::DataType(DataType::Custom { ref name }) = e {
30562 // Extract length from VARCHAR2(N ...) or NVARCHAR2(N ...)
30563 let starts_varchar2 =
30564 name.len() >= 9 && name[..9].eq_ignore_ascii_case("VARCHAR2(");
30565 let starts_nvarchar2 =
30566 name.len() >= 10 && name[..10].eq_ignore_ascii_case("NVARCHAR2(");
30567 let inner = if starts_varchar2 || starts_nvarchar2 {
30568 let start = if starts_nvarchar2 { 10 } else { 9 }; // skip "NVARCHAR2(" or "VARCHAR2("
30569 let end = name.len() - 1; // skip trailing ")"
30570 Some(&name[start..end])
30571 } else {
30572 Option::None
30573 };
30574 if let Some(inner_str) = inner {
30575 // Parse the number part, ignoring BYTE/CHAR qualifier
30576 let num_str = inner_str.split_whitespace().next().unwrap_or("");
30577 if let Ok(n) = num_str.parse::<u32>() {
30578 Ok(Expression::DataType(DataType::VarChar {
30579 length: Some(n),
30580 parenthesized_length: false,
30581 }))
30582 } else {
30583 Ok(e)
30584 }
30585 } else {
30586 // Plain VARCHAR2 / NVARCHAR2 without parens
30587 Ok(Expression::DataType(DataType::VarChar {
30588 length: Option::None,
30589 parenthesized_length: false,
30590 }))
30591 }
30592 } else {
30593 Ok(e)
30594 }
30595 }
30596
30597 Action::Nvl2Expand => {
30598 // NVL2(a, b[, c]) -> CASE WHEN NOT a IS NULL THEN b [ELSE c] END
30599 // But keep as NVL2 for dialects that support it natively
30600 let nvl2_native = matches!(
30601 target,
30602 DialectType::Oracle
30603 | DialectType::Snowflake
30604 | DialectType::Redshift
30605 | DialectType::Teradata
30606 | DialectType::Spark
30607 | DialectType::Databricks
30608 );
30609 let (a, b, c) = if let Expression::Nvl2(nvl2) = e {
30610 if nvl2_native {
30611 return Ok(Expression::Nvl2(nvl2));
30612 }
30613 (nvl2.this, nvl2.true_value, Some(nvl2.false_value))
30614 } else if let Expression::Function(f) = e {
30615 if nvl2_native {
30616 return Ok(Expression::Function(Box::new(Function::new(
30617 "NVL2".to_string(),
30618 f.args,
30619 ))));
30620 }
30621 if f.args.len() < 2 {
30622 return Ok(Expression::Function(f));
30623 }
30624 let mut args = f.args;
30625 let a = args.remove(0);
30626 let b = args.remove(0);
30627 let c = if !args.is_empty() {
30628 Some(args.remove(0))
30629 } else {
30630 Option::None
30631 };
30632 (a, b, c)
30633 } else {
30634 return Ok(e);
30635 };
30636 // Build: NOT (a IS NULL)
30637 let is_null = Expression::IsNull(Box::new(IsNull {
30638 this: a,
30639 not: false,
30640 postfix_form: false,
30641 }));
30642 let not_null = Expression::Not(Box::new(crate::expressions::UnaryOp {
30643 this: is_null,
30644 inferred_type: None,
30645 }));
30646 Ok(Expression::Case(Box::new(Case {
30647 operand: Option::None,
30648 whens: vec![(not_null, b)],
30649 else_: c,
30650 comments: Vec::new(),
30651 inferred_type: None,
30652 })))
30653 }
30654
30655 Action::IfnullToCoalesce => {
30656 // IFNULL(a, b) -> COALESCE(a, b): clear original_name to output COALESCE
30657 if let Expression::Coalesce(mut cf) = e {
30658 cf.original_name = Option::None;
30659 Ok(Expression::Coalesce(cf))
30660 } else if let Expression::Function(f) = e {
30661 Ok(Expression::Function(Box::new(Function::new(
30662 "COALESCE".to_string(),
30663 f.args,
30664 ))))
30665 } else {
30666 Ok(e)
30667 }
30668 }
30669
30670 Action::IsAsciiConvert => {
30671 // IS_ASCII(x) -> dialect-specific ASCII check
30672 if let Expression::Function(f) = e {
30673 let arg = f.args.into_iter().next().unwrap();
30674 match target {
30675 DialectType::MySQL | DialectType::SingleStore | DialectType::TiDB => {
30676 // REGEXP_LIKE(x, '^[[:ascii:]]*$')
30677 Ok(Expression::Function(Box::new(Function::new(
30678 "REGEXP_LIKE".to_string(),
30679 vec![
30680 arg,
30681 Expression::Literal(Box::new(Literal::String(
30682 "^[[:ascii:]]*$".to_string(),
30683 ))),
30684 ],
30685 ))))
30686 }
30687 DialectType::PostgreSQL
30688 | DialectType::Redshift
30689 | DialectType::Materialize
30690 | DialectType::RisingWave => {
30691 // (x ~ '^[[:ascii:]]*$')
30692 Ok(Expression::Paren(Box::new(Paren {
30693 this: Expression::RegexpLike(Box::new(
30694 crate::expressions::RegexpFunc {
30695 this: arg,
30696 pattern: Expression::Literal(Box::new(
30697 Literal::String("^[[:ascii:]]*$".to_string()),
30698 )),
30699 flags: Option::None,
30700 },
30701 )),
30702 trailing_comments: Vec::new(),
30703 })))
30704 }
30705 DialectType::SQLite => {
30706 // (NOT x GLOB CAST(x'2a5b5e012d7f5d2a' AS TEXT))
30707 let hex_lit = Expression::Literal(Box::new(Literal::HexString(
30708 "2a5b5e012d7f5d2a".to_string(),
30709 )));
30710 let cast_expr = Expression::Cast(Box::new(Cast {
30711 this: hex_lit,
30712 to: DataType::Text,
30713 trailing_comments: Vec::new(),
30714 double_colon_syntax: false,
30715 format: Option::None,
30716 default: Option::None,
30717 inferred_type: None,
30718 }));
30719 let glob = Expression::Glob(Box::new(BinaryOp {
30720 left: arg,
30721 right: cast_expr,
30722 left_comments: Vec::new(),
30723 operator_comments: Vec::new(),
30724 trailing_comments: Vec::new(),
30725 inferred_type: None,
30726 }));
30727 Ok(Expression::Paren(Box::new(Paren {
30728 this: Expression::Not(Box::new(crate::expressions::UnaryOp {
30729 this: glob,
30730 inferred_type: None,
30731 })),
30732 trailing_comments: Vec::new(),
30733 })))
30734 }
30735 DialectType::TSQL | DialectType::Fabric => {
30736 // (PATINDEX(CONVERT(VARCHAR(MAX), 0x255b5e002d7f5d25) COLLATE Latin1_General_BIN, x) = 0)
30737 let hex_lit = Expression::Literal(Box::new(Literal::HexNumber(
30738 "255b5e002d7f5d25".to_string(),
30739 )));
30740 let convert_expr = Expression::Convert(Box::new(
30741 crate::expressions::ConvertFunc {
30742 this: hex_lit,
30743 to: DataType::Text, // Text generates as VARCHAR(MAX) for TSQL
30744 style: None,
30745 },
30746 ));
30747 let collated = Expression::Collation(Box::new(
30748 crate::expressions::CollationExpr {
30749 this: convert_expr,
30750 collation: "Latin1_General_BIN".to_string(),
30751 quoted: false,
30752 double_quoted: false,
30753 },
30754 ));
30755 let patindex = Expression::Function(Box::new(Function::new(
30756 "PATINDEX".to_string(),
30757 vec![collated, arg],
30758 )));
30759 let zero =
30760 Expression::Literal(Box::new(Literal::Number("0".to_string())));
30761 let eq_zero = Expression::Eq(Box::new(BinaryOp {
30762 left: patindex,
30763 right: zero,
30764 left_comments: Vec::new(),
30765 operator_comments: Vec::new(),
30766 trailing_comments: Vec::new(),
30767 inferred_type: None,
30768 }));
30769 Ok(Expression::Paren(Box::new(Paren {
30770 this: eq_zero,
30771 trailing_comments: Vec::new(),
30772 })))
30773 }
30774 DialectType::Oracle => {
30775 // NVL(REGEXP_LIKE(x, '^[' || CHR(1) || '-' || CHR(127) || ']*$'), TRUE)
30776 // Build the pattern: '^[' || CHR(1) || '-' || CHR(127) || ']*$'
30777 let s1 = Expression::Literal(Box::new(Literal::String(
30778 "^[".to_string(),
30779 )));
30780 let chr1 = Expression::Function(Box::new(Function::new(
30781 "CHR".to_string(),
30782 vec![Expression::Literal(Box::new(Literal::Number(
30783 "1".to_string(),
30784 )))],
30785 )));
30786 let dash =
30787 Expression::Literal(Box::new(Literal::String("-".to_string())));
30788 let chr127 = Expression::Function(Box::new(Function::new(
30789 "CHR".to_string(),
30790 vec![Expression::Literal(Box::new(Literal::Number(
30791 "127".to_string(),
30792 )))],
30793 )));
30794 let s2 = Expression::Literal(Box::new(Literal::String(
30795 "]*$".to_string(),
30796 )));
30797 // Build: '^[' || CHR(1) || '-' || CHR(127) || ']*$'
30798 let concat1 =
30799 Expression::DPipe(Box::new(crate::expressions::DPipe {
30800 this: Box::new(s1),
30801 expression: Box::new(chr1),
30802 safe: None,
30803 }));
30804 let concat2 =
30805 Expression::DPipe(Box::new(crate::expressions::DPipe {
30806 this: Box::new(concat1),
30807 expression: Box::new(dash),
30808 safe: None,
30809 }));
30810 let concat3 =
30811 Expression::DPipe(Box::new(crate::expressions::DPipe {
30812 this: Box::new(concat2),
30813 expression: Box::new(chr127),
30814 safe: None,
30815 }));
30816 let concat4 =
30817 Expression::DPipe(Box::new(crate::expressions::DPipe {
30818 this: Box::new(concat3),
30819 expression: Box::new(s2),
30820 safe: None,
30821 }));
30822 let regexp_like = Expression::Function(Box::new(Function::new(
30823 "REGEXP_LIKE".to_string(),
30824 vec![arg, concat4],
30825 )));
30826 // Use Column("TRUE") to output literal TRUE keyword (not boolean 1/0)
30827 let true_expr =
30828 Expression::Column(Box::new(crate::expressions::Column {
30829 name: Identifier {
30830 name: "TRUE".to_string(),
30831 quoted: false,
30832 trailing_comments: Vec::new(),
30833 span: None,
30834 },
30835 table: None,
30836 join_mark: false,
30837 trailing_comments: Vec::new(),
30838 span: None,
30839 inferred_type: None,
30840 }));
30841 let nvl = Expression::Function(Box::new(Function::new(
30842 "NVL".to_string(),
30843 vec![regexp_like, true_expr],
30844 )));
30845 Ok(nvl)
30846 }
30847 _ => Ok(Expression::Function(Box::new(Function::new(
30848 "IS_ASCII".to_string(),
30849 vec![arg],
30850 )))),
30851 }
30852 } else {
30853 Ok(e)
30854 }
30855 }
30856
30857 Action::StrPositionConvert => {
30858 // STR_POSITION(haystack, needle[, position[, occurrence]]) -> dialect-specific
30859 if let Expression::Function(f) = e {
30860 if f.args.len() < 2 {
30861 return Ok(Expression::Function(f));
30862 }
30863 let mut args = f.args;
30864
30865 let haystack = args.remove(0);
30866 let needle = args.remove(0);
30867 let position = if !args.is_empty() {
30868 Some(args.remove(0))
30869 } else {
30870 Option::None
30871 };
30872 let occurrence = if !args.is_empty() {
30873 Some(args.remove(0))
30874 } else {
30875 Option::None
30876 };
30877
30878 // Helper to build: STRPOS/INSTR(SUBSTRING(haystack, pos), needle) expansion
30879 // Returns: CASE/IF WHEN func(SUBSTRING(haystack, pos), needle[, occ]) = 0 THEN 0 ELSE ... + pos - 1 END
30880 fn build_position_expansion(
30881 haystack: Expression,
30882 needle: Expression,
30883 pos: Expression,
30884 occurrence: Option<Expression>,
30885 inner_func: &str,
30886 wrapper: &str, // "CASE", "IF", "IIF"
30887 ) -> Expression {
30888 let substr = Expression::Function(Box::new(Function::new(
30889 "SUBSTRING".to_string(),
30890 vec![haystack, pos.clone()],
30891 )));
30892 let mut inner_args = vec![substr, needle];
30893 if let Some(occ) = occurrence {
30894 inner_args.push(occ);
30895 }
30896 let inner_call = Expression::Function(Box::new(Function::new(
30897 inner_func.to_string(),
30898 inner_args,
30899 )));
30900 let zero =
30901 Expression::Literal(Box::new(Literal::Number("0".to_string())));
30902 let one =
30903 Expression::Literal(Box::new(Literal::Number("1".to_string())));
30904 let eq_zero = Expression::Eq(Box::new(BinaryOp {
30905 left: inner_call.clone(),
30906 right: zero.clone(),
30907 left_comments: Vec::new(),
30908 operator_comments: Vec::new(),
30909 trailing_comments: Vec::new(),
30910 inferred_type: None,
30911 }));
30912 let add_pos = Expression::Add(Box::new(BinaryOp {
30913 left: inner_call,
30914 right: pos,
30915 left_comments: Vec::new(),
30916 operator_comments: Vec::new(),
30917 trailing_comments: Vec::new(),
30918 inferred_type: None,
30919 }));
30920 let sub_one = Expression::Sub(Box::new(BinaryOp {
30921 left: add_pos,
30922 right: one,
30923 left_comments: Vec::new(),
30924 operator_comments: Vec::new(),
30925 trailing_comments: Vec::new(),
30926 inferred_type: None,
30927 }));
30928
30929 match wrapper {
30930 "CASE" => Expression::Case(Box::new(Case {
30931 operand: Option::None,
30932 whens: vec![(eq_zero, zero)],
30933 else_: Some(sub_one),
30934 comments: Vec::new(),
30935 inferred_type: None,
30936 })),
30937 "IIF" => Expression::Function(Box::new(Function::new(
30938 "IIF".to_string(),
30939 vec![eq_zero, zero, sub_one],
30940 ))),
30941 _ => Expression::Function(Box::new(Function::new(
30942 "IF".to_string(),
30943 vec![eq_zero, zero, sub_one],
30944 ))),
30945 }
30946 }
30947
30948 match target {
30949 // STRPOS group: Athena, DuckDB, Presto, Trino, Drill
30950 DialectType::Athena
30951 | DialectType::DuckDB
30952 | DialectType::Presto
30953 | DialectType::Trino
30954 | DialectType::Drill => {
30955 if let Some(pos) = position {
30956 let wrapper = if matches!(target, DialectType::DuckDB) {
30957 "CASE"
30958 } else {
30959 "IF"
30960 };
30961 let result = build_position_expansion(
30962 haystack, needle, pos, occurrence, "STRPOS", wrapper,
30963 );
30964 if matches!(target, DialectType::Drill) {
30965 // Drill uses backtick-quoted `IF`
30966 if let Expression::Function(mut f) = result {
30967 f.name = "`IF`".to_string();
30968 Ok(Expression::Function(f))
30969 } else {
30970 Ok(result)
30971 }
30972 } else {
30973 Ok(result)
30974 }
30975 } else {
30976 Ok(Expression::Function(Box::new(Function::new(
30977 "STRPOS".to_string(),
30978 vec![haystack, needle],
30979 ))))
30980 }
30981 }
30982 // SQLite: IIF wrapper
30983 DialectType::SQLite => {
30984 if let Some(pos) = position {
30985 Ok(build_position_expansion(
30986 haystack, needle, pos, occurrence, "INSTR", "IIF",
30987 ))
30988 } else {
30989 Ok(Expression::Function(Box::new(Function::new(
30990 "INSTR".to_string(),
30991 vec![haystack, needle],
30992 ))))
30993 }
30994 }
30995 // INSTR group: Teradata, BigQuery, Oracle
30996 DialectType::Teradata | DialectType::BigQuery | DialectType::Oracle => {
30997 let mut a = vec![haystack, needle];
30998 if let Some(pos) = position {
30999 a.push(pos);
31000 }
31001 if let Some(occ) = occurrence {
31002 a.push(occ);
31003 }
31004 Ok(Expression::Function(Box::new(Function::new(
31005 "INSTR".to_string(),
31006 a,
31007 ))))
31008 }
31009 // CHARINDEX group: Snowflake, TSQL
31010 DialectType::Snowflake | DialectType::TSQL | DialectType::Fabric => {
31011 let mut a = vec![needle, haystack];
31012 if let Some(pos) = position {
31013 a.push(pos);
31014 }
31015 Ok(Expression::Function(Box::new(Function::new(
31016 "CHARINDEX".to_string(),
31017 a,
31018 ))))
31019 }
31020 // POSITION(needle IN haystack): PostgreSQL, Materialize, RisingWave, Redshift
31021 DialectType::PostgreSQL
31022 | DialectType::Materialize
31023 | DialectType::RisingWave
31024 | DialectType::Redshift => {
31025 if let Some(pos) = position {
31026 // Build: CASE WHEN POSITION(needle IN SUBSTRING(haystack FROM pos)) = 0 THEN 0
31027 // ELSE POSITION(...) + pos - 1 END
31028 let substr = Expression::Substring(Box::new(
31029 crate::expressions::SubstringFunc {
31030 this: haystack,
31031 start: pos.clone(),
31032 length: Option::None,
31033 from_for_syntax: true,
31034 },
31035 ));
31036 let pos_in = Expression::StrPosition(Box::new(
31037 crate::expressions::StrPosition {
31038 this: Box::new(substr),
31039 substr: Some(Box::new(needle)),
31040 position: Option::None,
31041 occurrence: Option::None,
31042 },
31043 ));
31044 let zero = Expression::Literal(Box::new(Literal::Number(
31045 "0".to_string(),
31046 )));
31047 let one = Expression::Literal(Box::new(Literal::Number(
31048 "1".to_string(),
31049 )));
31050 let eq_zero = Expression::Eq(Box::new(BinaryOp {
31051 left: pos_in.clone(),
31052 right: zero.clone(),
31053 left_comments: Vec::new(),
31054 operator_comments: Vec::new(),
31055 trailing_comments: Vec::new(),
31056 inferred_type: None,
31057 }));
31058 let add_pos = Expression::Add(Box::new(BinaryOp {
31059 left: pos_in,
31060 right: pos,
31061 left_comments: Vec::new(),
31062 operator_comments: Vec::new(),
31063 trailing_comments: Vec::new(),
31064 inferred_type: None,
31065 }));
31066 let sub_one = Expression::Sub(Box::new(BinaryOp {
31067 left: add_pos,
31068 right: one,
31069 left_comments: Vec::new(),
31070 operator_comments: Vec::new(),
31071 trailing_comments: Vec::new(),
31072 inferred_type: None,
31073 }));
31074 Ok(Expression::Case(Box::new(Case {
31075 operand: Option::None,
31076 whens: vec![(eq_zero, zero)],
31077 else_: Some(sub_one),
31078 comments: Vec::new(),
31079 inferred_type: None,
31080 })))
31081 } else {
31082 Ok(Expression::StrPosition(Box::new(
31083 crate::expressions::StrPosition {
31084 this: Box::new(haystack),
31085 substr: Some(Box::new(needle)),
31086 position: Option::None,
31087 occurrence: Option::None,
31088 },
31089 )))
31090 }
31091 }
31092 // LOCATE group: MySQL, Hive, Spark, Databricks, Doris
31093 DialectType::MySQL
31094 | DialectType::SingleStore
31095 | DialectType::TiDB
31096 | DialectType::Hive
31097 | DialectType::Spark
31098 | DialectType::Databricks
31099 | DialectType::Doris
31100 | DialectType::StarRocks => {
31101 let mut a = vec![needle, haystack];
31102 if let Some(pos) = position {
31103 a.push(pos);
31104 }
31105 Ok(Expression::Function(Box::new(Function::new(
31106 "LOCATE".to_string(),
31107 a,
31108 ))))
31109 }
31110 // ClickHouse: POSITION(haystack, needle[, position])
31111 DialectType::ClickHouse => {
31112 let mut a = vec![haystack, needle];
31113 if let Some(pos) = position {
31114 a.push(pos);
31115 }
31116 Ok(Expression::Function(Box::new(Function::new(
31117 "POSITION".to_string(),
31118 a,
31119 ))))
31120 }
31121 _ => {
31122 let mut a = vec![haystack, needle];
31123 if let Some(pos) = position {
31124 a.push(pos);
31125 }
31126 if let Some(occ) = occurrence {
31127 a.push(occ);
31128 }
31129 Ok(Expression::Function(Box::new(Function::new(
31130 "STR_POSITION".to_string(),
31131 a,
31132 ))))
31133 }
31134 }
31135 } else {
31136 Ok(e)
31137 }
31138 }
31139
31140 Action::ArraySumConvert => {
31141 // ARRAY_SUM(arr) -> dialect-specific
31142 if let Expression::Function(f) = e {
31143 let args = f.args;
31144 match target {
31145 DialectType::DuckDB => Ok(Expression::Function(Box::new(
31146 Function::new("LIST_SUM".to_string(), args),
31147 ))),
31148 DialectType::Spark | DialectType::Databricks => {
31149 // AGGREGATE(arr, 0, (acc, x) -> acc + x, acc -> acc)
31150 let arr = args.into_iter().next().unwrap();
31151 let zero =
31152 Expression::Literal(Box::new(Literal::Number("0".to_string())));
31153 let acc_id = Identifier::new("acc");
31154 let x_id = Identifier::new("x");
31155 let acc = Expression::Identifier(acc_id.clone());
31156 let x = Expression::Identifier(x_id.clone());
31157 let add = Expression::Add(Box::new(BinaryOp {
31158 left: acc.clone(),
31159 right: x,
31160 left_comments: Vec::new(),
31161 operator_comments: Vec::new(),
31162 trailing_comments: Vec::new(),
31163 inferred_type: None,
31164 }));
31165 let lambda1 =
31166 Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
31167 parameters: vec![acc_id.clone(), x_id],
31168 body: add,
31169 colon: false,
31170 parameter_types: Vec::new(),
31171 }));
31172 let lambda2 =
31173 Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
31174 parameters: vec![acc_id],
31175 body: acc,
31176 colon: false,
31177 parameter_types: Vec::new(),
31178 }));
31179 Ok(Expression::Function(Box::new(Function::new(
31180 "AGGREGATE".to_string(),
31181 vec![arr, zero, lambda1, lambda2],
31182 ))))
31183 }
31184 DialectType::Presto | DialectType::Athena => {
31185 // Presto/Athena keep ARRAY_SUM natively
31186 Ok(Expression::Function(Box::new(Function::new(
31187 "ARRAY_SUM".to_string(),
31188 args,
31189 ))))
31190 }
31191 DialectType::Trino => {
31192 // REDUCE(arr, 0, (acc, x) -> acc + x, acc -> acc)
31193 if args.len() == 1 {
31194 let arr = args.into_iter().next().unwrap();
31195 let zero = Expression::Literal(Box::new(Literal::Number(
31196 "0".to_string(),
31197 )));
31198 let acc_id = Identifier::new("acc");
31199 let x_id = Identifier::new("x");
31200 let acc = Expression::Identifier(acc_id.clone());
31201 let x = Expression::Identifier(x_id.clone());
31202 let add = Expression::Add(Box::new(BinaryOp {
31203 left: acc.clone(),
31204 right: x,
31205 left_comments: Vec::new(),
31206 operator_comments: Vec::new(),
31207 trailing_comments: Vec::new(),
31208 inferred_type: None,
31209 }));
31210 let lambda1 = Expression::Lambda(Box::new(
31211 crate::expressions::LambdaExpr {
31212 parameters: vec![acc_id.clone(), x_id],
31213 body: add,
31214 colon: false,
31215 parameter_types: Vec::new(),
31216 },
31217 ));
31218 let lambda2 = Expression::Lambda(Box::new(
31219 crate::expressions::LambdaExpr {
31220 parameters: vec![acc_id],
31221 body: acc,
31222 colon: false,
31223 parameter_types: Vec::new(),
31224 },
31225 ));
31226 Ok(Expression::Function(Box::new(Function::new(
31227 "REDUCE".to_string(),
31228 vec![arr, zero, lambda1, lambda2],
31229 ))))
31230 } else {
31231 Ok(Expression::Function(Box::new(Function::new(
31232 "ARRAY_SUM".to_string(),
31233 args,
31234 ))))
31235 }
31236 }
31237 DialectType::ClickHouse => {
31238 // arraySum(lambda, arr) or arraySum(arr)
31239 Ok(Expression::Function(Box::new(Function::new(
31240 "arraySum".to_string(),
31241 args,
31242 ))))
31243 }
31244 _ => Ok(Expression::Function(Box::new(Function::new(
31245 "ARRAY_SUM".to_string(),
31246 args,
31247 )))),
31248 }
31249 } else {
31250 Ok(e)
31251 }
31252 }
31253
31254 Action::ArraySizeConvert => {
31255 if let Expression::Function(f) = e {
31256 Ok(Expression::Function(Box::new(Function::new(
31257 "REPEATED_COUNT".to_string(),
31258 f.args,
31259 ))))
31260 } else {
31261 Ok(e)
31262 }
31263 }
31264
31265 Action::ArrayAnyConvert => {
31266 if let Expression::Function(f) = e {
31267 let mut args = f.args;
31268 if args.len() == 2 {
31269 let arr = args.remove(0);
31270 let lambda = args.remove(0);
31271
31272 // Extract lambda parameter name and body
31273 let (param_name, pred_body) =
31274 if let Expression::Lambda(ref lam) = lambda {
31275 let name = if let Some(p) = lam.parameters.first() {
31276 p.name.clone()
31277 } else {
31278 "x".to_string()
31279 };
31280 (name, lam.body.clone())
31281 } else {
31282 ("x".to_string(), lambda.clone())
31283 };
31284
31285 // Helper: build a function call Expression
31286 let make_func = |name: &str, args: Vec<Expression>| -> Expression {
31287 Expression::Function(Box::new(Function::new(
31288 name.to_string(),
31289 args,
31290 )))
31291 };
31292
31293 // Helper: build (len_func(arr) = 0 OR len_func(filter_expr) <> 0) wrapped in Paren
31294 let build_filter_pattern = |len_func: &str,
31295 len_args_extra: Vec<Expression>,
31296 filter_expr: Expression|
31297 -> Expression {
31298 // len_func(arr, ...extra) = 0
31299 let mut len_arr_args = vec![arr.clone()];
31300 len_arr_args.extend(len_args_extra.clone());
31301 let len_arr = make_func(len_func, len_arr_args);
31302 let eq_zero = Expression::Eq(Box::new(BinaryOp::new(
31303 len_arr,
31304 Expression::number(0),
31305 )));
31306
31307 // len_func(filter_expr, ...extra) <> 0
31308 let mut len_filter_args = vec![filter_expr];
31309 len_filter_args.extend(len_args_extra);
31310 let len_filter = make_func(len_func, len_filter_args);
31311 let neq_zero = Expression::Neq(Box::new(BinaryOp::new(
31312 len_filter,
31313 Expression::number(0),
31314 )));
31315
31316 // (eq_zero OR neq_zero)
31317 let or_expr =
31318 Expression::Or(Box::new(BinaryOp::new(eq_zero, neq_zero)));
31319 Expression::Paren(Box::new(Paren {
31320 this: or_expr,
31321 trailing_comments: Vec::new(),
31322 }))
31323 };
31324
31325 match target {
31326 DialectType::Trino | DialectType::Presto | DialectType::Athena => {
31327 Ok(make_func("ANY_MATCH", vec![arr, lambda]))
31328 }
31329 DialectType::ClickHouse => {
31330 // (LENGTH(arr) = 0 OR LENGTH(arrayFilter(x -> pred, arr)) <> 0)
31331 // ClickHouse arrayFilter takes lambda first, then array
31332 let filter_expr =
31333 make_func("arrayFilter", vec![lambda, arr.clone()]);
31334 Ok(build_filter_pattern("LENGTH", vec![], filter_expr))
31335 }
31336 DialectType::Databricks | DialectType::Spark => {
31337 // (SIZE(arr) = 0 OR SIZE(FILTER(arr, x -> pred)) <> 0)
31338 let filter_expr =
31339 make_func("FILTER", vec![arr.clone(), lambda]);
31340 Ok(build_filter_pattern("SIZE", vec![], filter_expr))
31341 }
31342 DialectType::DuckDB => {
31343 // (ARRAY_LENGTH(arr) = 0 OR ARRAY_LENGTH(LIST_FILTER(arr, x -> pred)) <> 0)
31344 let filter_expr =
31345 make_func("LIST_FILTER", vec![arr.clone(), lambda]);
31346 Ok(build_filter_pattern("ARRAY_LENGTH", vec![], filter_expr))
31347 }
31348 DialectType::Teradata => {
31349 // (CARDINALITY(arr) = 0 OR CARDINALITY(FILTER(arr, x -> pred)) <> 0)
31350 let filter_expr =
31351 make_func("FILTER", vec![arr.clone(), lambda]);
31352 Ok(build_filter_pattern("CARDINALITY", vec![], filter_expr))
31353 }
31354 DialectType::BigQuery => {
31355 // (ARRAY_LENGTH(arr) = 0 OR ARRAY_LENGTH(ARRAY(SELECT x FROM UNNEST(arr) AS x WHERE pred)) <> 0)
31356 // Build: SELECT x FROM UNNEST(arr) AS x WHERE pred
31357 let param_col = Expression::column(¶m_name);
31358 let unnest_expr = Expression::Unnest(Box::new(
31359 crate::expressions::UnnestFunc {
31360 this: arr.clone(),
31361 expressions: vec![],
31362 with_ordinality: false,
31363 alias: Some(Identifier::new(¶m_name)),
31364 offset_alias: None,
31365 },
31366 ));
31367 let mut sel = crate::expressions::Select::default();
31368 sel.expressions = vec![param_col];
31369 sel.from = Some(crate::expressions::From {
31370 expressions: vec![unnest_expr],
31371 });
31372 sel.where_clause =
31373 Some(crate::expressions::Where { this: pred_body });
31374 let array_subquery =
31375 make_func("ARRAY", vec![Expression::Select(Box::new(sel))]);
31376 Ok(build_filter_pattern("ARRAY_LENGTH", vec![], array_subquery))
31377 }
31378 DialectType::PostgreSQL => {
31379 // (ARRAY_LENGTH(arr, 1) = 0 OR ARRAY_LENGTH(ARRAY(SELECT x FROM UNNEST(arr) AS _t0(x) WHERE pred), 1) <> 0)
31380 // Build: SELECT x FROM UNNEST(arr) AS _t0(x) WHERE pred
31381 let param_col = Expression::column(¶m_name);
31382 // For PostgreSQL, UNNEST uses AS _t0(x) syntax - use TableAlias
31383 let unnest_with_alias =
31384 Expression::Alias(Box::new(crate::expressions::Alias {
31385 this: Expression::Unnest(Box::new(
31386 crate::expressions::UnnestFunc {
31387 this: arr.clone(),
31388 expressions: vec![],
31389 with_ordinality: false,
31390 alias: None,
31391 offset_alias: None,
31392 },
31393 )),
31394 alias: Identifier::new("_t0"),
31395 column_aliases: vec![Identifier::new(¶m_name)],
31396 alias_explicit_as: false,
31397 alias_keyword: None,
31398 pre_alias_comments: Vec::new(),
31399 trailing_comments: Vec::new(),
31400 inferred_type: None,
31401 }));
31402 let mut sel = crate::expressions::Select::default();
31403 sel.expressions = vec![param_col];
31404 sel.from = Some(crate::expressions::From {
31405 expressions: vec![unnest_with_alias],
31406 });
31407 sel.where_clause =
31408 Some(crate::expressions::Where { this: pred_body });
31409 let array_subquery =
31410 make_func("ARRAY", vec![Expression::Select(Box::new(sel))]);
31411 Ok(build_filter_pattern(
31412 "ARRAY_LENGTH",
31413 vec![Expression::number(1)],
31414 array_subquery,
31415 ))
31416 }
31417 _ => Ok(Expression::Function(Box::new(Function::new(
31418 "ARRAY_ANY".to_string(),
31419 vec![arr, lambda],
31420 )))),
31421 }
31422 } else {
31423 Ok(Expression::Function(Box::new(Function::new(
31424 "ARRAY_ANY".to_string(),
31425 args,
31426 ))))
31427 }
31428 } else {
31429 Ok(e)
31430 }
31431 }
31432
31433 Action::DecodeSimplify => {
31434 // DECODE(x, search1, result1, ..., default) -> CASE WHEN ... THEN result1 ... [ELSE default] END
31435 // For literal search values: CASE WHEN x = search THEN result
31436 // For NULL search: CASE WHEN x IS NULL THEN result
31437 // For non-literal (column, expr): CASE WHEN x = search OR (x IS NULL AND search IS NULL) THEN result
31438 fn is_decode_literal(e: &Expression) -> bool {
31439 matches!(
31440 e,
31441 Expression::Literal(_) | Expression::Boolean(_) | Expression::Neg(_)
31442 )
31443 }
31444
31445 let build_decode_case =
31446 |this_expr: Expression,
31447 pairs: Vec<(Expression, Expression)>,
31448 default: Option<Expression>| {
31449 let whens: Vec<(Expression, Expression)> = pairs
31450 .into_iter()
31451 .map(|(search, result)| {
31452 if matches!(&search, Expression::Null(_)) {
31453 // NULL search -> IS NULL
31454 let condition = Expression::Is(Box::new(BinaryOp {
31455 left: this_expr.clone(),
31456 right: Expression::Null(crate::expressions::Null),
31457 left_comments: Vec::new(),
31458 operator_comments: Vec::new(),
31459 trailing_comments: Vec::new(),
31460 inferred_type: None,
31461 }));
31462 (condition, result)
31463 } else if is_decode_literal(&search)
31464 || is_decode_literal(&this_expr)
31465 {
31466 // At least one side is a literal -> simple equality (no NULL check needed)
31467 let eq = Expression::Eq(Box::new(BinaryOp {
31468 left: this_expr.clone(),
31469 right: search,
31470 left_comments: Vec::new(),
31471 operator_comments: Vec::new(),
31472 trailing_comments: Vec::new(),
31473 inferred_type: None,
31474 }));
31475 (eq, result)
31476 } else {
31477 // Non-literal -> null-safe comparison
31478 let needs_paren = matches!(
31479 &search,
31480 Expression::Eq(_)
31481 | Expression::Neq(_)
31482 | Expression::Gt(_)
31483 | Expression::Gte(_)
31484 | Expression::Lt(_)
31485 | Expression::Lte(_)
31486 );
31487 let search_ref = if needs_paren {
31488 Expression::Paren(Box::new(crate::expressions::Paren {
31489 this: search.clone(),
31490 trailing_comments: Vec::new(),
31491 }))
31492 } else {
31493 search.clone()
31494 };
31495 // Build: x = search OR (x IS NULL AND search IS NULL)
31496 let eq = Expression::Eq(Box::new(BinaryOp {
31497 left: this_expr.clone(),
31498 right: search_ref,
31499 left_comments: Vec::new(),
31500 operator_comments: Vec::new(),
31501 trailing_comments: Vec::new(),
31502 inferred_type: None,
31503 }));
31504 let search_in_null = if needs_paren {
31505 Expression::Paren(Box::new(crate::expressions::Paren {
31506 this: search.clone(),
31507 trailing_comments: Vec::new(),
31508 }))
31509 } else {
31510 search.clone()
31511 };
31512 let x_is_null = Expression::Is(Box::new(BinaryOp {
31513 left: this_expr.clone(),
31514 right: Expression::Null(crate::expressions::Null),
31515 left_comments: Vec::new(),
31516 operator_comments: Vec::new(),
31517 trailing_comments: Vec::new(),
31518 inferred_type: None,
31519 }));
31520 let search_is_null = Expression::Is(Box::new(BinaryOp {
31521 left: search_in_null,
31522 right: Expression::Null(crate::expressions::Null),
31523 left_comments: Vec::new(),
31524 operator_comments: Vec::new(),
31525 trailing_comments: Vec::new(),
31526 inferred_type: None,
31527 }));
31528 let both_null = Expression::And(Box::new(BinaryOp {
31529 left: x_is_null,
31530 right: search_is_null,
31531 left_comments: Vec::new(),
31532 operator_comments: Vec::new(),
31533 trailing_comments: Vec::new(),
31534 inferred_type: None,
31535 }));
31536 let condition = Expression::Or(Box::new(BinaryOp {
31537 left: eq,
31538 right: Expression::Paren(Box::new(
31539 crate::expressions::Paren {
31540 this: both_null,
31541 trailing_comments: Vec::new(),
31542 },
31543 )),
31544 left_comments: Vec::new(),
31545 operator_comments: Vec::new(),
31546 trailing_comments: Vec::new(),
31547 inferred_type: None,
31548 }));
31549 (condition, result)
31550 }
31551 })
31552 .collect();
31553 Expression::Case(Box::new(Case {
31554 operand: None,
31555 whens,
31556 else_: default,
31557 comments: Vec::new(),
31558 inferred_type: None,
31559 }))
31560 };
31561
31562 if let Expression::Decode(decode) = e {
31563 Ok(build_decode_case(
31564 decode.this,
31565 decode.search_results,
31566 decode.default,
31567 ))
31568 } else if let Expression::DecodeCase(dc) = e {
31569 // DecodeCase has flat expressions: [x, s1, r1, s2, r2, ..., default?]
31570 let mut exprs = dc.expressions;
31571 if exprs.len() < 3 {
31572 return Ok(Expression::DecodeCase(Box::new(
31573 crate::expressions::DecodeCase { expressions: exprs },
31574 )));
31575 }
31576 let this_expr = exprs.remove(0);
31577 let mut pairs = Vec::new();
31578 let mut default = None;
31579 let mut i = 0;
31580 while i + 1 < exprs.len() {
31581 pairs.push((exprs[i].clone(), exprs[i + 1].clone()));
31582 i += 2;
31583 }
31584 if i < exprs.len() {
31585 // Odd remaining element is the default
31586 default = Some(exprs[i].clone());
31587 }
31588 Ok(build_decode_case(this_expr, pairs, default))
31589 } else {
31590 Ok(e)
31591 }
31592 }
31593
31594 Action::CreateTableLikeToCtas => {
31595 // CREATE TABLE a LIKE b -> CREATE TABLE a AS SELECT * FROM b LIMIT 0
31596 if let Expression::CreateTable(ct) = e {
31597 let like_source = ct.constraints.iter().find_map(|c| {
31598 if let crate::expressions::TableConstraint::Like { source, .. } = c {
31599 Some(source.clone())
31600 } else {
31601 None
31602 }
31603 });
31604 if let Some(source_table) = like_source {
31605 let mut new_ct = *ct;
31606 new_ct.constraints.clear();
31607 // Build: SELECT * FROM b LIMIT 0
31608 let select = Expression::Select(Box::new(crate::expressions::Select {
31609 expressions: vec![Expression::Star(crate::expressions::Star {
31610 table: None,
31611 except: None,
31612 replace: None,
31613 rename: None,
31614 trailing_comments: Vec::new(),
31615 span: None,
31616 })],
31617 from: Some(crate::expressions::From {
31618 expressions: vec![Expression::Table(Box::new(source_table))],
31619 }),
31620 limit: Some(crate::expressions::Limit {
31621 this: Expression::Literal(Box::new(Literal::Number(
31622 "0".to_string(),
31623 ))),
31624 percent: false,
31625 comments: Vec::new(),
31626 }),
31627 ..Default::default()
31628 }));
31629 new_ct.as_select = Some(select);
31630 Ok(Expression::CreateTable(Box::new(new_ct)))
31631 } else {
31632 Ok(Expression::CreateTable(ct))
31633 }
31634 } else {
31635 Ok(e)
31636 }
31637 }
31638
31639 Action::CreateTableLikeToSelectInto => {
31640 // CREATE TABLE a LIKE b -> SELECT TOP 0 * INTO a FROM b AS temp
31641 if let Expression::CreateTable(ct) = e {
31642 let like_source = ct.constraints.iter().find_map(|c| {
31643 if let crate::expressions::TableConstraint::Like { source, .. } = c {
31644 Some(source.clone())
31645 } else {
31646 None
31647 }
31648 });
31649 if let Some(source_table) = like_source {
31650 let mut aliased_source = source_table;
31651 aliased_source.alias = Some(Identifier::new("temp"));
31652 // Build: SELECT TOP 0 * INTO a FROM b AS temp
31653 let select = Expression::Select(Box::new(crate::expressions::Select {
31654 expressions: vec![Expression::Star(crate::expressions::Star {
31655 table: None,
31656 except: None,
31657 replace: None,
31658 rename: None,
31659 trailing_comments: Vec::new(),
31660 span: None,
31661 })],
31662 from: Some(crate::expressions::From {
31663 expressions: vec![Expression::Table(Box::new(aliased_source))],
31664 }),
31665 into: Some(crate::expressions::SelectInto {
31666 this: Expression::Table(Box::new(ct.name.clone())),
31667 temporary: false,
31668 unlogged: false,
31669 bulk_collect: false,
31670 expressions: Vec::new(),
31671 }),
31672 top: Some(crate::expressions::Top {
31673 this: Expression::Literal(Box::new(Literal::Number(
31674 "0".to_string(),
31675 ))),
31676 percent: false,
31677 with_ties: false,
31678 parenthesized: false,
31679 }),
31680 ..Default::default()
31681 }));
31682 Ok(select)
31683 } else {
31684 Ok(Expression::CreateTable(ct))
31685 }
31686 } else {
31687 Ok(e)
31688 }
31689 }
31690
31691 Action::CreateTableLikeToAs => {
31692 // CREATE TABLE a LIKE b -> CREATE TABLE a AS b (ClickHouse)
31693 if let Expression::CreateTable(ct) = e {
31694 let like_source = ct.constraints.iter().find_map(|c| {
31695 if let crate::expressions::TableConstraint::Like { source, .. } = c {
31696 Some(source.clone())
31697 } else {
31698 None
31699 }
31700 });
31701 if let Some(source_table) = like_source {
31702 let mut new_ct = *ct;
31703 new_ct.constraints.clear();
31704 // AS b (just a table reference, not a SELECT)
31705 new_ct.as_select = Some(Expression::Table(Box::new(source_table)));
31706 Ok(Expression::CreateTable(Box::new(new_ct)))
31707 } else {
31708 Ok(Expression::CreateTable(ct))
31709 }
31710 } else {
31711 Ok(e)
31712 }
31713 }
31714
31715 Action::TsOrDsToDateConvert => {
31716 // TS_OR_DS_TO_DATE(x[, fmt]) -> dialect-specific date conversion
31717 if let Expression::Function(f) = e {
31718 let mut args = f.args;
31719 let this = args.remove(0);
31720 let fmt = if !args.is_empty() {
31721 match &args[0] {
31722 Expression::Literal(lit)
31723 if matches!(lit.as_ref(), Literal::String(_)) =>
31724 {
31725 let Literal::String(s) = lit.as_ref() else {
31726 unreachable!()
31727 };
31728 Some(s.clone())
31729 }
31730 _ => None,
31731 }
31732 } else {
31733 None
31734 };
31735 Ok(Expression::TsOrDsToDate(Box::new(
31736 crate::expressions::TsOrDsToDate {
31737 this: Box::new(this),
31738 format: fmt,
31739 safe: None,
31740 },
31741 )))
31742 } else {
31743 Ok(e)
31744 }
31745 }
31746
31747 Action::TsOrDsToDateStrConvert => {
31748 // TS_OR_DS_TO_DATE_STR(x) -> SUBSTRING(CAST(x AS type), 1, 10)
31749 if let Expression::Function(f) = e {
31750 let arg = f.args.into_iter().next().unwrap();
31751 let str_type = match target {
31752 DialectType::DuckDB
31753 | DialectType::PostgreSQL
31754 | DialectType::Materialize => DataType::Text,
31755 DialectType::Hive | DialectType::Spark | DialectType::Databricks => {
31756 DataType::Custom {
31757 name: "STRING".to_string(),
31758 }
31759 }
31760 DialectType::Presto
31761 | DialectType::Trino
31762 | DialectType::Athena
31763 | DialectType::Drill => DataType::VarChar {
31764 length: None,
31765 parenthesized_length: false,
31766 },
31767 DialectType::MySQL | DialectType::Doris | DialectType::StarRocks => {
31768 DataType::Custom {
31769 name: "STRING".to_string(),
31770 }
31771 }
31772 _ => DataType::VarChar {
31773 length: None,
31774 parenthesized_length: false,
31775 },
31776 };
31777 let cast_expr = Expression::Cast(Box::new(Cast {
31778 this: arg,
31779 to: str_type,
31780 double_colon_syntax: false,
31781 trailing_comments: Vec::new(),
31782 format: None,
31783 default: None,
31784 inferred_type: None,
31785 }));
31786 Ok(Expression::Substring(Box::new(
31787 crate::expressions::SubstringFunc {
31788 this: cast_expr,
31789 start: Expression::number(1),
31790 length: Some(Expression::number(10)),
31791 from_for_syntax: false,
31792 },
31793 )))
31794 } else {
31795 Ok(e)
31796 }
31797 }
31798
31799 Action::DateStrToDateConvert => {
31800 // DATE_STR_TO_DATE(x) -> dialect-specific
31801 if let Expression::Function(f) = e {
31802 let arg = f.args.into_iter().next().unwrap();
31803 match target {
31804 DialectType::SQLite => {
31805 // SQLite: just the bare expression (dates are strings)
31806 Ok(arg)
31807 }
31808 _ => Ok(Expression::Cast(Box::new(Cast {
31809 this: arg,
31810 to: DataType::Date,
31811 double_colon_syntax: false,
31812 trailing_comments: Vec::new(),
31813 format: None,
31814 default: None,
31815 inferred_type: None,
31816 }))),
31817 }
31818 } else {
31819 Ok(e)
31820 }
31821 }
31822
31823 Action::TimeStrToDateConvert => {
31824 // TIME_STR_TO_DATE(x) -> dialect-specific
31825 if let Expression::Function(f) = e {
31826 let arg = f.args.into_iter().next().unwrap();
31827 match target {
31828 DialectType::Hive
31829 | DialectType::Doris
31830 | DialectType::StarRocks
31831 | DialectType::Snowflake => Ok(Expression::Function(Box::new(
31832 Function::new("TO_DATE".to_string(), vec![arg]),
31833 ))),
31834 DialectType::Presto | DialectType::Trino | DialectType::Athena => {
31835 // Presto: CAST(x AS TIMESTAMP)
31836 Ok(Expression::Cast(Box::new(Cast {
31837 this: arg,
31838 to: DataType::Timestamp {
31839 timezone: false,
31840 precision: None,
31841 },
31842 double_colon_syntax: false,
31843 trailing_comments: Vec::new(),
31844 format: None,
31845 default: None,
31846 inferred_type: None,
31847 })))
31848 }
31849 _ => {
31850 // Default: CAST(x AS DATE)
31851 Ok(Expression::Cast(Box::new(Cast {
31852 this: arg,
31853 to: DataType::Date,
31854 double_colon_syntax: false,
31855 trailing_comments: Vec::new(),
31856 format: None,
31857 default: None,
31858 inferred_type: None,
31859 })))
31860 }
31861 }
31862 } else {
31863 Ok(e)
31864 }
31865 }
31866
31867 Action::TimeStrToTimeConvert => {
31868 // TIME_STR_TO_TIME(x[, zone]) -> dialect-specific CAST to timestamp type
31869 if let Expression::Function(f) = e {
31870 let mut args = f.args;
31871 let this = args.remove(0);
31872 let zone = if !args.is_empty() {
31873 match &args[0] {
31874 Expression::Literal(lit)
31875 if matches!(lit.as_ref(), Literal::String(_)) =>
31876 {
31877 let Literal::String(s) = lit.as_ref() else {
31878 unreachable!()
31879 };
31880 Some(s.clone())
31881 }
31882 _ => None,
31883 }
31884 } else {
31885 None
31886 };
31887 let has_zone = zone.is_some();
31888
31889 match target {
31890 DialectType::SQLite => {
31891 // SQLite: just the bare expression
31892 Ok(this)
31893 }
31894 DialectType::MySQL => {
31895 if has_zone {
31896 // MySQL with zone: TIMESTAMP(x)
31897 Ok(Expression::Function(Box::new(Function::new(
31898 "TIMESTAMP".to_string(),
31899 vec![this],
31900 ))))
31901 } else {
31902 // MySQL: CAST(x AS DATETIME) or with precision
31903 // Use DataType::Custom to avoid MySQL's transform_cast converting
31904 // CAST(x AS TIMESTAMP) -> TIMESTAMP(x)
31905 let precision = if let Expression::Literal(ref lit) = this {
31906 if let Literal::String(ref s) = lit.as_ref() {
31907 if let Some(dot_pos) = s.rfind('.') {
31908 let frac = &s[dot_pos + 1..];
31909 let digit_count = frac
31910 .chars()
31911 .take_while(|c| c.is_ascii_digit())
31912 .count();
31913 if digit_count > 0 {
31914 Some(digit_count)
31915 } else {
31916 None
31917 }
31918 } else {
31919 None
31920 }
31921 } else {
31922 None
31923 }
31924 } else {
31925 None
31926 };
31927 let type_name = match precision {
31928 Some(p) => format!("DATETIME({})", p),
31929 None => "DATETIME".to_string(),
31930 };
31931 Ok(Expression::Cast(Box::new(Cast {
31932 this,
31933 to: DataType::Custom { name: type_name },
31934 double_colon_syntax: false,
31935 trailing_comments: Vec::new(),
31936 format: None,
31937 default: None,
31938 inferred_type: None,
31939 })))
31940 }
31941 }
31942 DialectType::ClickHouse => {
31943 if has_zone {
31944 // ClickHouse with zone: CAST(x AS DateTime64(6, 'zone'))
31945 // We need to strip the timezone offset from the literal if present
31946 let clean_this = if let Expression::Literal(ref lit) = this {
31947 if let Literal::String(ref s) = lit.as_ref() {
31948 // Strip timezone offset like "-08:00" or "+00:00"
31949 let re_offset = s.rfind(|c: char| c == '+' || c == '-');
31950 if let Some(offset_pos) = re_offset {
31951 if offset_pos > 10 {
31952 // After the date part
31953 let trimmed = s[..offset_pos].to_string();
31954 Expression::Literal(Box::new(Literal::String(
31955 trimmed,
31956 )))
31957 } else {
31958 this.clone()
31959 }
31960 } else {
31961 this.clone()
31962 }
31963 } else {
31964 this.clone()
31965 }
31966 } else {
31967 this.clone()
31968 };
31969 let zone_str = zone.unwrap();
31970 // Build: CAST(x AS DateTime64(6, 'zone'))
31971 let type_name = format!("DateTime64(6, '{}')", zone_str);
31972 Ok(Expression::Cast(Box::new(Cast {
31973 this: clean_this,
31974 to: DataType::Custom { name: type_name },
31975 double_colon_syntax: false,
31976 trailing_comments: Vec::new(),
31977 format: None,
31978 default: None,
31979 inferred_type: None,
31980 })))
31981 } else {
31982 Ok(Expression::Cast(Box::new(Cast {
31983 this,
31984 to: DataType::Custom {
31985 name: "DateTime64(6)".to_string(),
31986 },
31987 double_colon_syntax: false,
31988 trailing_comments: Vec::new(),
31989 format: None,
31990 default: None,
31991 inferred_type: None,
31992 })))
31993 }
31994 }
31995 DialectType::BigQuery => {
31996 if has_zone {
31997 // BigQuery with zone: CAST(x AS TIMESTAMP)
31998 Ok(Expression::Cast(Box::new(Cast {
31999 this,
32000 to: DataType::Timestamp {
32001 timezone: false,
32002 precision: None,
32003 },
32004 double_colon_syntax: false,
32005 trailing_comments: Vec::new(),
32006 format: None,
32007 default: None,
32008 inferred_type: None,
32009 })))
32010 } else {
32011 // BigQuery: CAST(x AS DATETIME) - Timestamp{tz:false} renders as DATETIME for BigQuery
32012 Ok(Expression::Cast(Box::new(Cast {
32013 this,
32014 to: DataType::Custom {
32015 name: "DATETIME".to_string(),
32016 },
32017 double_colon_syntax: false,
32018 trailing_comments: Vec::new(),
32019 format: None,
32020 default: None,
32021 inferred_type: None,
32022 })))
32023 }
32024 }
32025 DialectType::Doris => {
32026 // Doris: CAST(x AS DATETIME)
32027 Ok(Expression::Cast(Box::new(Cast {
32028 this,
32029 to: DataType::Custom {
32030 name: "DATETIME".to_string(),
32031 },
32032 double_colon_syntax: false,
32033 trailing_comments: Vec::new(),
32034 format: None,
32035 default: None,
32036 inferred_type: None,
32037 })))
32038 }
32039 DialectType::TSQL | DialectType::Fabric => {
32040 if has_zone {
32041 // TSQL with zone: CAST(x AS DATETIMEOFFSET) AT TIME ZONE 'UTC'
32042 let cast_expr = Expression::Cast(Box::new(Cast {
32043 this,
32044 to: DataType::Custom {
32045 name: "DATETIMEOFFSET".to_string(),
32046 },
32047 double_colon_syntax: false,
32048 trailing_comments: Vec::new(),
32049 format: None,
32050 default: None,
32051 inferred_type: None,
32052 }));
32053 Ok(Expression::AtTimeZone(Box::new(
32054 crate::expressions::AtTimeZone {
32055 this: cast_expr,
32056 zone: Expression::Literal(Box::new(Literal::String(
32057 "UTC".to_string(),
32058 ))),
32059 },
32060 )))
32061 } else {
32062 // TSQL: CAST(x AS DATETIME2)
32063 Ok(Expression::Cast(Box::new(Cast {
32064 this,
32065 to: DataType::Custom {
32066 name: "DATETIME2".to_string(),
32067 },
32068 double_colon_syntax: false,
32069 trailing_comments: Vec::new(),
32070 format: None,
32071 default: None,
32072 inferred_type: None,
32073 })))
32074 }
32075 }
32076 DialectType::DuckDB => {
32077 if has_zone {
32078 // DuckDB with zone: CAST(x AS TIMESTAMPTZ)
32079 Ok(Expression::Cast(Box::new(Cast {
32080 this,
32081 to: DataType::Timestamp {
32082 timezone: true,
32083 precision: None,
32084 },
32085 double_colon_syntax: false,
32086 trailing_comments: Vec::new(),
32087 format: None,
32088 default: None,
32089 inferred_type: None,
32090 })))
32091 } else {
32092 // DuckDB: CAST(x AS TIMESTAMP)
32093 Ok(Expression::Cast(Box::new(Cast {
32094 this,
32095 to: DataType::Timestamp {
32096 timezone: false,
32097 precision: None,
32098 },
32099 double_colon_syntax: false,
32100 trailing_comments: Vec::new(),
32101 format: None,
32102 default: None,
32103 inferred_type: None,
32104 })))
32105 }
32106 }
32107 DialectType::PostgreSQL
32108 | DialectType::Materialize
32109 | DialectType::RisingWave => {
32110 if has_zone {
32111 // PostgreSQL with zone: CAST(x AS TIMESTAMPTZ)
32112 Ok(Expression::Cast(Box::new(Cast {
32113 this,
32114 to: DataType::Timestamp {
32115 timezone: true,
32116 precision: None,
32117 },
32118 double_colon_syntax: false,
32119 trailing_comments: Vec::new(),
32120 format: None,
32121 default: None,
32122 inferred_type: None,
32123 })))
32124 } else {
32125 // PostgreSQL: CAST(x AS TIMESTAMP)
32126 Ok(Expression::Cast(Box::new(Cast {
32127 this,
32128 to: DataType::Timestamp {
32129 timezone: false,
32130 precision: None,
32131 },
32132 double_colon_syntax: false,
32133 trailing_comments: Vec::new(),
32134 format: None,
32135 default: None,
32136 inferred_type: None,
32137 })))
32138 }
32139 }
32140 DialectType::Snowflake => {
32141 if has_zone {
32142 // Snowflake with zone: CAST(x AS TIMESTAMPTZ)
32143 Ok(Expression::Cast(Box::new(Cast {
32144 this,
32145 to: DataType::Timestamp {
32146 timezone: true,
32147 precision: None,
32148 },
32149 double_colon_syntax: false,
32150 trailing_comments: Vec::new(),
32151 format: None,
32152 default: None,
32153 inferred_type: None,
32154 })))
32155 } else {
32156 // Snowflake: CAST(x AS TIMESTAMP)
32157 Ok(Expression::Cast(Box::new(Cast {
32158 this,
32159 to: DataType::Timestamp {
32160 timezone: false,
32161 precision: None,
32162 },
32163 double_colon_syntax: false,
32164 trailing_comments: Vec::new(),
32165 format: None,
32166 default: None,
32167 inferred_type: None,
32168 })))
32169 }
32170 }
32171 DialectType::Presto | DialectType::Trino | DialectType::Athena => {
32172 if has_zone {
32173 // Presto/Trino with zone: CAST(x AS TIMESTAMP WITH TIME ZONE)
32174 // Check for precision from sub-second digits
32175 let precision = if let Expression::Literal(ref lit) = this {
32176 if let Literal::String(ref s) = lit.as_ref() {
32177 if let Some(dot_pos) = s.rfind('.') {
32178 let frac = &s[dot_pos + 1..];
32179 let digit_count = frac
32180 .chars()
32181 .take_while(|c| c.is_ascii_digit())
32182 .count();
32183 if digit_count > 0
32184 && matches!(target, DialectType::Trino)
32185 {
32186 Some(digit_count as u32)
32187 } else {
32188 None
32189 }
32190 } else {
32191 None
32192 }
32193 } else {
32194 None
32195 }
32196 } else {
32197 None
32198 };
32199 let dt = if let Some(prec) = precision {
32200 DataType::Timestamp {
32201 timezone: true,
32202 precision: Some(prec),
32203 }
32204 } else {
32205 DataType::Timestamp {
32206 timezone: true,
32207 precision: None,
32208 }
32209 };
32210 Ok(Expression::Cast(Box::new(Cast {
32211 this,
32212 to: dt,
32213 double_colon_syntax: false,
32214 trailing_comments: Vec::new(),
32215 format: None,
32216 default: None,
32217 inferred_type: None,
32218 })))
32219 } else {
32220 // Check for sub-second precision for Trino
32221 let precision = if let Expression::Literal(ref lit) = this {
32222 if let Literal::String(ref s) = lit.as_ref() {
32223 if let Some(dot_pos) = s.rfind('.') {
32224 let frac = &s[dot_pos + 1..];
32225 let digit_count = frac
32226 .chars()
32227 .take_while(|c| c.is_ascii_digit())
32228 .count();
32229 if digit_count > 0
32230 && matches!(target, DialectType::Trino)
32231 {
32232 Some(digit_count as u32)
32233 } else {
32234 None
32235 }
32236 } else {
32237 None
32238 }
32239 } else {
32240 None
32241 }
32242 } else {
32243 None
32244 };
32245 let dt = DataType::Timestamp {
32246 timezone: false,
32247 precision,
32248 };
32249 Ok(Expression::Cast(Box::new(Cast {
32250 this,
32251 to: dt,
32252 double_colon_syntax: false,
32253 trailing_comments: Vec::new(),
32254 format: None,
32255 default: None,
32256 inferred_type: None,
32257 })))
32258 }
32259 }
32260 DialectType::Redshift => {
32261 if has_zone {
32262 // Redshift with zone: CAST(x AS TIMESTAMP WITH TIME ZONE)
32263 Ok(Expression::Cast(Box::new(Cast {
32264 this,
32265 to: DataType::Timestamp {
32266 timezone: true,
32267 precision: None,
32268 },
32269 double_colon_syntax: false,
32270 trailing_comments: Vec::new(),
32271 format: None,
32272 default: None,
32273 inferred_type: None,
32274 })))
32275 } else {
32276 // Redshift: CAST(x AS TIMESTAMP)
32277 Ok(Expression::Cast(Box::new(Cast {
32278 this,
32279 to: DataType::Timestamp {
32280 timezone: false,
32281 precision: None,
32282 },
32283 double_colon_syntax: false,
32284 trailing_comments: Vec::new(),
32285 format: None,
32286 default: None,
32287 inferred_type: None,
32288 })))
32289 }
32290 }
32291 _ => {
32292 // Default: CAST(x AS TIMESTAMP)
32293 Ok(Expression::Cast(Box::new(Cast {
32294 this,
32295 to: DataType::Timestamp {
32296 timezone: false,
32297 precision: None,
32298 },
32299 double_colon_syntax: false,
32300 trailing_comments: Vec::new(),
32301 format: None,
32302 default: None,
32303 inferred_type: None,
32304 })))
32305 }
32306 }
32307 } else {
32308 Ok(e)
32309 }
32310 }
32311
32312 Action::DateToDateStrConvert => {
32313 // DATE_TO_DATE_STR(x) -> CAST(x AS text_type) per dialect
32314 if let Expression::Function(f) = e {
32315 let arg = f.args.into_iter().next().unwrap();
32316 let str_type = match target {
32317 DialectType::DuckDB => DataType::Text,
32318 DialectType::Hive | DialectType::Spark | DialectType::Databricks => {
32319 DataType::Custom {
32320 name: "STRING".to_string(),
32321 }
32322 }
32323 DialectType::Presto
32324 | DialectType::Trino
32325 | DialectType::Athena
32326 | DialectType::Drill => DataType::VarChar {
32327 length: None,
32328 parenthesized_length: false,
32329 },
32330 _ => DataType::VarChar {
32331 length: None,
32332 parenthesized_length: false,
32333 },
32334 };
32335 Ok(Expression::Cast(Box::new(Cast {
32336 this: arg,
32337 to: str_type,
32338 double_colon_syntax: false,
32339 trailing_comments: Vec::new(),
32340 format: None,
32341 default: None,
32342 inferred_type: None,
32343 })))
32344 } else {
32345 Ok(e)
32346 }
32347 }
32348
32349 Action::DateToDiConvert => {
32350 // DATE_TO_DI(x) -> CAST(format_func(x, fmt) AS INT)
32351 if let Expression::Function(f) = e {
32352 let arg = f.args.into_iter().next().unwrap();
32353 let inner = match target {
32354 DialectType::DuckDB => {
32355 // STRFTIME(x, '%Y%m%d')
32356 Expression::Function(Box::new(Function::new(
32357 "STRFTIME".to_string(),
32358 vec![arg, Expression::string("%Y%m%d")],
32359 )))
32360 }
32361 DialectType::Hive | DialectType::Spark | DialectType::Databricks => {
32362 // DATE_FORMAT(x, 'yyyyMMdd')
32363 Expression::Function(Box::new(Function::new(
32364 "DATE_FORMAT".to_string(),
32365 vec![arg, Expression::string("yyyyMMdd")],
32366 )))
32367 }
32368 DialectType::Presto | DialectType::Trino | DialectType::Athena => {
32369 // DATE_FORMAT(x, '%Y%m%d')
32370 Expression::Function(Box::new(Function::new(
32371 "DATE_FORMAT".to_string(),
32372 vec![arg, Expression::string("%Y%m%d")],
32373 )))
32374 }
32375 DialectType::Drill => {
32376 // TO_DATE(x, 'yyyyMMdd')
32377 Expression::Function(Box::new(Function::new(
32378 "TO_DATE".to_string(),
32379 vec![arg, Expression::string("yyyyMMdd")],
32380 )))
32381 }
32382 _ => {
32383 // Default: STRFTIME(x, '%Y%m%d')
32384 Expression::Function(Box::new(Function::new(
32385 "STRFTIME".to_string(),
32386 vec![arg, Expression::string("%Y%m%d")],
32387 )))
32388 }
32389 };
32390 // Use INT (not INTEGER) for Presto/Trino
32391 let int_type = match target {
32392 DialectType::Presto
32393 | DialectType::Trino
32394 | DialectType::Athena
32395 | DialectType::TSQL
32396 | DialectType::Fabric
32397 | DialectType::SQLite
32398 | DialectType::Redshift => DataType::Custom {
32399 name: "INT".to_string(),
32400 },
32401 _ => DataType::Int {
32402 length: None,
32403 integer_spelling: false,
32404 },
32405 };
32406 Ok(Expression::Cast(Box::new(Cast {
32407 this: inner,
32408 to: int_type,
32409 double_colon_syntax: false,
32410 trailing_comments: Vec::new(),
32411 format: None,
32412 default: None,
32413 inferred_type: None,
32414 })))
32415 } else {
32416 Ok(e)
32417 }
32418 }
32419
32420 Action::DiToDateConvert => {
32421 // DI_TO_DATE(x) -> dialect-specific integer-to-date conversion
32422 if let Expression::Function(f) = e {
32423 let arg = f.args.into_iter().next().unwrap();
32424 match target {
32425 DialectType::DuckDB => {
32426 // CAST(STRPTIME(CAST(x AS TEXT), '%Y%m%d') AS DATE)
32427 let cast_text = Expression::Cast(Box::new(Cast {
32428 this: arg,
32429 to: DataType::Text,
32430 double_colon_syntax: false,
32431 trailing_comments: Vec::new(),
32432 format: None,
32433 default: None,
32434 inferred_type: None,
32435 }));
32436 let strptime = Expression::Function(Box::new(Function::new(
32437 "STRPTIME".to_string(),
32438 vec![cast_text, Expression::string("%Y%m%d")],
32439 )));
32440 Ok(Expression::Cast(Box::new(Cast {
32441 this: strptime,
32442 to: DataType::Date,
32443 double_colon_syntax: false,
32444 trailing_comments: Vec::new(),
32445 format: None,
32446 default: None,
32447 inferred_type: None,
32448 })))
32449 }
32450 DialectType::Hive | DialectType::Spark | DialectType::Databricks => {
32451 // TO_DATE(CAST(x AS STRING), 'yyyyMMdd')
32452 let cast_str = Expression::Cast(Box::new(Cast {
32453 this: arg,
32454 to: DataType::Custom {
32455 name: "STRING".to_string(),
32456 },
32457 double_colon_syntax: false,
32458 trailing_comments: Vec::new(),
32459 format: None,
32460 default: None,
32461 inferred_type: None,
32462 }));
32463 Ok(Expression::Function(Box::new(Function::new(
32464 "TO_DATE".to_string(),
32465 vec![cast_str, Expression::string("yyyyMMdd")],
32466 ))))
32467 }
32468 DialectType::Presto | DialectType::Trino | DialectType::Athena => {
32469 // CAST(DATE_PARSE(CAST(x AS VARCHAR), '%Y%m%d') AS DATE)
32470 let cast_varchar = Expression::Cast(Box::new(Cast {
32471 this: arg,
32472 to: DataType::VarChar {
32473 length: None,
32474 parenthesized_length: false,
32475 },
32476 double_colon_syntax: false,
32477 trailing_comments: Vec::new(),
32478 format: None,
32479 default: None,
32480 inferred_type: None,
32481 }));
32482 let date_parse = Expression::Function(Box::new(Function::new(
32483 "DATE_PARSE".to_string(),
32484 vec![cast_varchar, Expression::string("%Y%m%d")],
32485 )));
32486 Ok(Expression::Cast(Box::new(Cast {
32487 this: date_parse,
32488 to: DataType::Date,
32489 double_colon_syntax: false,
32490 trailing_comments: Vec::new(),
32491 format: None,
32492 default: None,
32493 inferred_type: None,
32494 })))
32495 }
32496 DialectType::Drill => {
32497 // TO_DATE(CAST(x AS VARCHAR), 'yyyyMMdd')
32498 let cast_varchar = Expression::Cast(Box::new(Cast {
32499 this: arg,
32500 to: DataType::VarChar {
32501 length: None,
32502 parenthesized_length: false,
32503 },
32504 double_colon_syntax: false,
32505 trailing_comments: Vec::new(),
32506 format: None,
32507 default: None,
32508 inferred_type: None,
32509 }));
32510 Ok(Expression::Function(Box::new(Function::new(
32511 "TO_DATE".to_string(),
32512 vec![cast_varchar, Expression::string("yyyyMMdd")],
32513 ))))
32514 }
32515 _ => Ok(Expression::Function(Box::new(Function::new(
32516 "DI_TO_DATE".to_string(),
32517 vec![arg],
32518 )))),
32519 }
32520 } else {
32521 Ok(e)
32522 }
32523 }
32524
32525 Action::TsOrDiToDiConvert => {
32526 // TS_OR_DI_TO_DI(x) -> CAST(SUBSTR(REPLACE(CAST(x AS type), '-', ''), 1, 8) AS INT)
32527 if let Expression::Function(f) = e {
32528 let arg = f.args.into_iter().next().unwrap();
32529 let str_type = match target {
32530 DialectType::DuckDB => DataType::Text,
32531 DialectType::Hive | DialectType::Spark | DialectType::Databricks => {
32532 DataType::Custom {
32533 name: "STRING".to_string(),
32534 }
32535 }
32536 DialectType::Presto | DialectType::Trino | DialectType::Athena => {
32537 DataType::VarChar {
32538 length: None,
32539 parenthesized_length: false,
32540 }
32541 }
32542 _ => DataType::VarChar {
32543 length: None,
32544 parenthesized_length: false,
32545 },
32546 };
32547 let cast_str = Expression::Cast(Box::new(Cast {
32548 this: arg,
32549 to: str_type,
32550 double_colon_syntax: false,
32551 trailing_comments: Vec::new(),
32552 format: None,
32553 default: None,
32554 inferred_type: None,
32555 }));
32556 let replace_expr = Expression::Function(Box::new(Function::new(
32557 "REPLACE".to_string(),
32558 vec![cast_str, Expression::string("-"), Expression::string("")],
32559 )));
32560 let substr_name = match target {
32561 DialectType::DuckDB
32562 | DialectType::Hive
32563 | DialectType::Spark
32564 | DialectType::Databricks => "SUBSTR",
32565 _ => "SUBSTR",
32566 };
32567 let substr = Expression::Function(Box::new(Function::new(
32568 substr_name.to_string(),
32569 vec![replace_expr, Expression::number(1), Expression::number(8)],
32570 )));
32571 // Use INT (not INTEGER) for Presto/Trino etc.
32572 let int_type = match target {
32573 DialectType::Presto
32574 | DialectType::Trino
32575 | DialectType::Athena
32576 | DialectType::TSQL
32577 | DialectType::Fabric
32578 | DialectType::SQLite
32579 | DialectType::Redshift => DataType::Custom {
32580 name: "INT".to_string(),
32581 },
32582 _ => DataType::Int {
32583 length: None,
32584 integer_spelling: false,
32585 },
32586 };
32587 Ok(Expression::Cast(Box::new(Cast {
32588 this: substr,
32589 to: int_type,
32590 double_colon_syntax: false,
32591 trailing_comments: Vec::new(),
32592 format: None,
32593 default: None,
32594 inferred_type: None,
32595 })))
32596 } else {
32597 Ok(e)
32598 }
32599 }
32600
32601 Action::UnixToStrConvert => {
32602 // UNIX_TO_STR(x, fmt) -> convert to Expression::UnixToStr for generator
32603 if let Expression::Function(f) = e {
32604 let mut args = f.args;
32605 let this = args.remove(0);
32606 let fmt_expr = if !args.is_empty() {
32607 Some(args.remove(0))
32608 } else {
32609 None
32610 };
32611
32612 // Check if format is a string literal
32613 let fmt_str = fmt_expr.as_ref().and_then(|f| {
32614 if let Expression::Literal(lit) = f {
32615 if let Literal::String(s) = lit.as_ref() {
32616 Some(s.clone())
32617 } else {
32618 None
32619 }
32620 } else {
32621 None
32622 }
32623 });
32624
32625 if let Some(fmt_string) = fmt_str {
32626 // String literal format -> use UnixToStr expression (generator handles it)
32627 Ok(Expression::UnixToStr(Box::new(
32628 crate::expressions::UnixToStr {
32629 this: Box::new(this),
32630 format: Some(fmt_string),
32631 },
32632 )))
32633 } else if let Some(fmt_e) = fmt_expr {
32634 // Non-literal format (e.g., identifier `y`) -> build target expression directly
32635 match target {
32636 DialectType::DuckDB => {
32637 // STRFTIME(TO_TIMESTAMP(x), y)
32638 let to_ts = Expression::Function(Box::new(Function::new(
32639 "TO_TIMESTAMP".to_string(),
32640 vec![this],
32641 )));
32642 Ok(Expression::Function(Box::new(Function::new(
32643 "STRFTIME".to_string(),
32644 vec![to_ts, fmt_e],
32645 ))))
32646 }
32647 DialectType::Presto | DialectType::Trino | DialectType::Athena => {
32648 // DATE_FORMAT(FROM_UNIXTIME(x), y)
32649 let from_unix = Expression::Function(Box::new(Function::new(
32650 "FROM_UNIXTIME".to_string(),
32651 vec![this],
32652 )));
32653 Ok(Expression::Function(Box::new(Function::new(
32654 "DATE_FORMAT".to_string(),
32655 vec![from_unix, fmt_e],
32656 ))))
32657 }
32658 DialectType::Hive
32659 | DialectType::Spark
32660 | DialectType::Databricks
32661 | DialectType::Doris
32662 | DialectType::StarRocks => {
32663 // FROM_UNIXTIME(x, y)
32664 Ok(Expression::Function(Box::new(Function::new(
32665 "FROM_UNIXTIME".to_string(),
32666 vec![this, fmt_e],
32667 ))))
32668 }
32669 _ => {
32670 // Default: keep as UNIX_TO_STR(x, y)
32671 Ok(Expression::Function(Box::new(Function::new(
32672 "UNIX_TO_STR".to_string(),
32673 vec![this, fmt_e],
32674 ))))
32675 }
32676 }
32677 } else {
32678 Ok(Expression::UnixToStr(Box::new(
32679 crate::expressions::UnixToStr {
32680 this: Box::new(this),
32681 format: None,
32682 },
32683 )))
32684 }
32685 } else {
32686 Ok(e)
32687 }
32688 }
32689
32690 Action::UnixToTimeConvert => {
32691 // UNIX_TO_TIME(x) -> convert to Expression::UnixToTime for generator
32692 if let Expression::Function(f) = e {
32693 let arg = f.args.into_iter().next().unwrap();
32694 Ok(Expression::UnixToTime(Box::new(
32695 crate::expressions::UnixToTime {
32696 this: Box::new(arg),
32697 scale: None,
32698 zone: None,
32699 hours: None,
32700 minutes: None,
32701 format: None,
32702 target_type: None,
32703 },
32704 )))
32705 } else {
32706 Ok(e)
32707 }
32708 }
32709
32710 Action::UnixToTimeStrConvert => {
32711 // UNIX_TO_TIME_STR(x) -> dialect-specific
32712 if let Expression::Function(f) = e {
32713 let arg = f.args.into_iter().next().unwrap();
32714 match target {
32715 DialectType::Hive | DialectType::Spark | DialectType::Databricks => {
32716 // FROM_UNIXTIME(x)
32717 Ok(Expression::Function(Box::new(Function::new(
32718 "FROM_UNIXTIME".to_string(),
32719 vec![arg],
32720 ))))
32721 }
32722 DialectType::Presto | DialectType::Trino | DialectType::Athena => {
32723 // CAST(FROM_UNIXTIME(x) AS VARCHAR)
32724 let from_unix = Expression::Function(Box::new(Function::new(
32725 "FROM_UNIXTIME".to_string(),
32726 vec![arg],
32727 )));
32728 Ok(Expression::Cast(Box::new(Cast {
32729 this: from_unix,
32730 to: DataType::VarChar {
32731 length: None,
32732 parenthesized_length: false,
32733 },
32734 double_colon_syntax: false,
32735 trailing_comments: Vec::new(),
32736 format: None,
32737 default: None,
32738 inferred_type: None,
32739 })))
32740 }
32741 DialectType::DuckDB => {
32742 // CAST(TO_TIMESTAMP(x) AS TEXT)
32743 let to_ts = Expression::Function(Box::new(Function::new(
32744 "TO_TIMESTAMP".to_string(),
32745 vec![arg],
32746 )));
32747 Ok(Expression::Cast(Box::new(Cast {
32748 this: to_ts,
32749 to: DataType::Text,
32750 double_colon_syntax: false,
32751 trailing_comments: Vec::new(),
32752 format: None,
32753 default: None,
32754 inferred_type: None,
32755 })))
32756 }
32757 _ => Ok(Expression::Function(Box::new(Function::new(
32758 "UNIX_TO_TIME_STR".to_string(),
32759 vec![arg],
32760 )))),
32761 }
32762 } else {
32763 Ok(e)
32764 }
32765 }
32766
32767 Action::TimeToUnixConvert => {
32768 // TIME_TO_UNIX(x) -> convert to Expression::TimeToUnix for generator
32769 if let Expression::Function(f) = e {
32770 let arg = f.args.into_iter().next().unwrap();
32771 Ok(Expression::TimeToUnix(Box::new(
32772 crate::expressions::UnaryFunc {
32773 this: arg,
32774 original_name: None,
32775 inferred_type: None,
32776 },
32777 )))
32778 } else {
32779 Ok(e)
32780 }
32781 }
32782
32783 Action::TimeToStrConvert => {
32784 // TIME_TO_STR(x, fmt) -> convert to Expression::TimeToStr for generator
32785 if let Expression::Function(f) = e {
32786 let mut args = f.args;
32787 let this = args.remove(0);
32788 let fmt = match args.remove(0) {
32789 Expression::Literal(lit)
32790 if matches!(lit.as_ref(), Literal::String(_)) =>
32791 {
32792 let Literal::String(s) = lit.as_ref() else {
32793 unreachable!()
32794 };
32795 s.clone()
32796 }
32797 other => {
32798 return Ok(Expression::Function(Box::new(Function::new(
32799 "TIME_TO_STR".to_string(),
32800 vec![this, other],
32801 ))));
32802 }
32803 };
32804 Ok(Expression::TimeToStr(Box::new(
32805 crate::expressions::TimeToStr {
32806 this: Box::new(this),
32807 format: fmt,
32808 culture: None,
32809 zone: None,
32810 },
32811 )))
32812 } else {
32813 Ok(e)
32814 }
32815 }
32816
32817 Action::StrToUnixConvert => {
32818 // STR_TO_UNIX(x, fmt) -> convert to Expression::StrToUnix for generator
32819 if let Expression::Function(f) = e {
32820 let mut args = f.args;
32821 let this = args.remove(0);
32822 let fmt = match args.remove(0) {
32823 Expression::Literal(lit)
32824 if matches!(lit.as_ref(), Literal::String(_)) =>
32825 {
32826 let Literal::String(s) = lit.as_ref() else {
32827 unreachable!()
32828 };
32829 s.clone()
32830 }
32831 other => {
32832 return Ok(Expression::Function(Box::new(Function::new(
32833 "STR_TO_UNIX".to_string(),
32834 vec![this, other],
32835 ))));
32836 }
32837 };
32838 Ok(Expression::StrToUnix(Box::new(
32839 crate::expressions::StrToUnix {
32840 this: Some(Box::new(this)),
32841 format: Some(fmt),
32842 },
32843 )))
32844 } else {
32845 Ok(e)
32846 }
32847 }
32848
32849 Action::TimeStrToUnixConvert => {
32850 // TIME_STR_TO_UNIX(x) -> dialect-specific
32851 if let Expression::Function(f) = e {
32852 let arg = f.args.into_iter().next().unwrap();
32853 match target {
32854 DialectType::DuckDB => {
32855 // EPOCH(CAST(x AS TIMESTAMP))
32856 let cast_ts = Expression::Cast(Box::new(Cast {
32857 this: arg,
32858 to: DataType::Timestamp {
32859 timezone: false,
32860 precision: None,
32861 },
32862 double_colon_syntax: false,
32863 trailing_comments: Vec::new(),
32864 format: None,
32865 default: None,
32866 inferred_type: None,
32867 }));
32868 Ok(Expression::Function(Box::new(Function::new(
32869 "EPOCH".to_string(),
32870 vec![cast_ts],
32871 ))))
32872 }
32873 DialectType::Hive
32874 | DialectType::Doris
32875 | DialectType::StarRocks
32876 | DialectType::MySQL => {
32877 // UNIX_TIMESTAMP(x)
32878 Ok(Expression::Function(Box::new(Function::new(
32879 "UNIX_TIMESTAMP".to_string(),
32880 vec![arg],
32881 ))))
32882 }
32883 DialectType::Presto | DialectType::Trino | DialectType::Athena => {
32884 // TO_UNIXTIME(DATE_PARSE(x, '%Y-%m-%d %T'))
32885 let date_parse = Expression::Function(Box::new(Function::new(
32886 "DATE_PARSE".to_string(),
32887 vec![arg, Expression::string("%Y-%m-%d %T")],
32888 )));
32889 Ok(Expression::Function(Box::new(Function::new(
32890 "TO_UNIXTIME".to_string(),
32891 vec![date_parse],
32892 ))))
32893 }
32894 _ => Ok(Expression::Function(Box::new(Function::new(
32895 "TIME_STR_TO_UNIX".to_string(),
32896 vec![arg],
32897 )))),
32898 }
32899 } else {
32900 Ok(e)
32901 }
32902 }
32903
32904 Action::TimeToTimeStrConvert => {
32905 // TIME_TO_TIME_STR(x) -> CAST(x AS str_type) per dialect
32906 if let Expression::Function(f) = e {
32907 let arg = f.args.into_iter().next().unwrap();
32908 let str_type = match target {
32909 DialectType::DuckDB => DataType::Text,
32910 DialectType::Hive
32911 | DialectType::Spark
32912 | DialectType::Databricks
32913 | DialectType::Doris
32914 | DialectType::StarRocks => DataType::Custom {
32915 name: "STRING".to_string(),
32916 },
32917 DialectType::Redshift => DataType::Custom {
32918 name: "VARCHAR(MAX)".to_string(),
32919 },
32920 _ => DataType::VarChar {
32921 length: None,
32922 parenthesized_length: false,
32923 },
32924 };
32925 Ok(Expression::Cast(Box::new(Cast {
32926 this: arg,
32927 to: str_type,
32928 double_colon_syntax: false,
32929 trailing_comments: Vec::new(),
32930 format: None,
32931 default: None,
32932 inferred_type: None,
32933 })))
32934 } else {
32935 Ok(e)
32936 }
32937 }
32938
32939 Action::DateTruncSwapArgs => {
32940 // DATE_TRUNC('unit', x) from Generic -> target-specific
32941 if let Expression::Function(f) = e {
32942 if f.args.len() == 2 {
32943 let unit_arg = f.args[0].clone();
32944 let expr_arg = f.args[1].clone();
32945 // Extract unit string from the first arg
32946 let unit_str = match &unit_arg {
32947 Expression::Literal(lit)
32948 if matches!(lit.as_ref(), Literal::String(_)) =>
32949 {
32950 let Literal::String(s) = lit.as_ref() else {
32951 unreachable!()
32952 };
32953 s.to_ascii_uppercase()
32954 }
32955 _ => return Ok(Expression::Function(f)),
32956 };
32957 match target {
32958 DialectType::BigQuery => {
32959 // BigQuery: DATE_TRUNC(x, UNIT) - unquoted unit
32960 let unit_ident =
32961 Expression::Column(Box::new(crate::expressions::Column {
32962 name: crate::expressions::Identifier::new(unit_str),
32963 table: None,
32964 join_mark: false,
32965 trailing_comments: Vec::new(),
32966 span: None,
32967 inferred_type: None,
32968 }));
32969 Ok(Expression::Function(Box::new(Function::new(
32970 "DATE_TRUNC".to_string(),
32971 vec![expr_arg, unit_ident],
32972 ))))
32973 }
32974 DialectType::Doris => {
32975 // Doris: DATE_TRUNC(x, 'UNIT')
32976 Ok(Expression::Function(Box::new(Function::new(
32977 "DATE_TRUNC".to_string(),
32978 vec![expr_arg, Expression::string(&unit_str)],
32979 ))))
32980 }
32981 DialectType::StarRocks => {
32982 // StarRocks: DATE_TRUNC('UNIT', x) - keep standard order
32983 Ok(Expression::Function(Box::new(Function::new(
32984 "DATE_TRUNC".to_string(),
32985 vec![Expression::string(&unit_str), expr_arg],
32986 ))))
32987 }
32988 DialectType::Spark | DialectType::Databricks => {
32989 // Spark: TRUNC(x, 'UNIT')
32990 Ok(Expression::Function(Box::new(Function::new(
32991 "TRUNC".to_string(),
32992 vec![expr_arg, Expression::string(&unit_str)],
32993 ))))
32994 }
32995 DialectType::MySQL => {
32996 // MySQL: complex expansion based on unit
32997 Self::date_trunc_to_mysql(&unit_str, &expr_arg)
32998 }
32999 _ => Ok(Expression::Function(f)),
33000 }
33001 } else {
33002 Ok(Expression::Function(f))
33003 }
33004 } else {
33005 Ok(e)
33006 }
33007 }
33008
33009 Action::TimestampTruncConvert => {
33010 // TIMESTAMP_TRUNC(x, UNIT[, tz]) from Generic -> target-specific
33011 if let Expression::Function(f) = e {
33012 if f.args.len() >= 2 {
33013 let expr_arg = f.args[0].clone();
33014 let unit_arg = f.args[1].clone();
33015 let tz_arg = if f.args.len() >= 3 {
33016 Some(f.args[2].clone())
33017 } else {
33018 None
33019 };
33020 // Extract unit string
33021 let unit_str = match &unit_arg {
33022 Expression::Literal(lit)
33023 if matches!(lit.as_ref(), Literal::String(_)) =>
33024 {
33025 let Literal::String(s) = lit.as_ref() else {
33026 unreachable!()
33027 };
33028 s.to_ascii_uppercase()
33029 }
33030 Expression::Column(c) => c.name.name.to_ascii_uppercase(),
33031 _ => {
33032 return Ok(Expression::Function(f));
33033 }
33034 };
33035 match target {
33036 DialectType::Spark | DialectType::Databricks => {
33037 // Spark: DATE_TRUNC('UNIT', x)
33038 Ok(Expression::Function(Box::new(Function::new(
33039 "DATE_TRUNC".to_string(),
33040 vec![Expression::string(&unit_str), expr_arg],
33041 ))))
33042 }
33043 DialectType::Doris | DialectType::StarRocks => {
33044 // Doris: DATE_TRUNC(x, 'UNIT')
33045 Ok(Expression::Function(Box::new(Function::new(
33046 "DATE_TRUNC".to_string(),
33047 vec![expr_arg, Expression::string(&unit_str)],
33048 ))))
33049 }
33050 DialectType::BigQuery => {
33051 // BigQuery: TIMESTAMP_TRUNC(x, UNIT) - keep but with unquoted unit
33052 let unit_ident =
33053 Expression::Column(Box::new(crate::expressions::Column {
33054 name: crate::expressions::Identifier::new(unit_str),
33055 table: None,
33056 join_mark: false,
33057 trailing_comments: Vec::new(),
33058 span: None,
33059 inferred_type: None,
33060 }));
33061 let mut args = vec![expr_arg, unit_ident];
33062 if let Some(tz) = tz_arg {
33063 args.push(tz);
33064 }
33065 Ok(Expression::Function(Box::new(Function::new(
33066 "TIMESTAMP_TRUNC".to_string(),
33067 args,
33068 ))))
33069 }
33070 DialectType::DuckDB => {
33071 // DuckDB with timezone: DATE_TRUNC('UNIT', x AT TIME ZONE 'tz') AT TIME ZONE 'tz'
33072 if let Some(tz) = tz_arg {
33073 let tz_str = match &tz {
33074 Expression::Literal(lit)
33075 if matches!(lit.as_ref(), Literal::String(_)) =>
33076 {
33077 let Literal::String(s) = lit.as_ref() else {
33078 unreachable!()
33079 };
33080 s.clone()
33081 }
33082 _ => "UTC".to_string(),
33083 };
33084 // x AT TIME ZONE 'tz'
33085 let at_tz = Expression::AtTimeZone(Box::new(
33086 crate::expressions::AtTimeZone {
33087 this: expr_arg,
33088 zone: Expression::string(&tz_str),
33089 },
33090 ));
33091 // DATE_TRUNC('UNIT', x AT TIME ZONE 'tz')
33092 let trunc = Expression::Function(Box::new(Function::new(
33093 "DATE_TRUNC".to_string(),
33094 vec![Expression::string(&unit_str), at_tz],
33095 )));
33096 // DATE_TRUNC(...) AT TIME ZONE 'tz'
33097 Ok(Expression::AtTimeZone(Box::new(
33098 crate::expressions::AtTimeZone {
33099 this: trunc,
33100 zone: Expression::string(&tz_str),
33101 },
33102 )))
33103 } else {
33104 Ok(Expression::Function(Box::new(Function::new(
33105 "DATE_TRUNC".to_string(),
33106 vec![Expression::string(&unit_str), expr_arg],
33107 ))))
33108 }
33109 }
33110 DialectType::Presto
33111 | DialectType::Trino
33112 | DialectType::Athena
33113 | DialectType::Snowflake => {
33114 // Presto/Snowflake: DATE_TRUNC('UNIT', x) - drop timezone
33115 Ok(Expression::Function(Box::new(Function::new(
33116 "DATE_TRUNC".to_string(),
33117 vec![Expression::string(&unit_str), expr_arg],
33118 ))))
33119 }
33120 _ => {
33121 // For most dialects: DATE_TRUNC('UNIT', x) + tz handling
33122 let mut args = vec![Expression::string(&unit_str), expr_arg];
33123 if let Some(tz) = tz_arg {
33124 args.push(tz);
33125 }
33126 Ok(Expression::Function(Box::new(Function::new(
33127 "DATE_TRUNC".to_string(),
33128 args,
33129 ))))
33130 }
33131 }
33132 } else {
33133 Ok(Expression::Function(f))
33134 }
33135 } else {
33136 Ok(e)
33137 }
33138 }
33139
33140 Action::StrToDateConvert => {
33141 // STR_TO_DATE(x, fmt) from Generic -> dialect-specific date parsing
33142 if let Expression::Function(f) = e {
33143 if f.args.len() == 2 {
33144 let mut args = f.args;
33145 let this = args.remove(0);
33146 let fmt_expr = args.remove(0);
33147 let fmt_str = match &fmt_expr {
33148 Expression::Literal(lit)
33149 if matches!(lit.as_ref(), Literal::String(_)) =>
33150 {
33151 let Literal::String(s) = lit.as_ref() else {
33152 unreachable!()
33153 };
33154 Some(s.clone())
33155 }
33156 _ => None,
33157 };
33158 let default_date = "%Y-%m-%d";
33159 let default_time = "%Y-%m-%d %H:%M:%S";
33160 let is_default = fmt_str
33161 .as_ref()
33162 .map_or(false, |f| f == default_date || f == default_time);
33163
33164 if is_default {
33165 // Default format: handle per-dialect
33166 match target {
33167 DialectType::MySQL
33168 | DialectType::Doris
33169 | DialectType::StarRocks => {
33170 // Keep STR_TO_DATE(x, fmt) as-is
33171 Ok(Expression::Function(Box::new(Function::new(
33172 "STR_TO_DATE".to_string(),
33173 vec![this, fmt_expr],
33174 ))))
33175 }
33176 DialectType::Hive => {
33177 // Hive: CAST(x AS DATE)
33178 Ok(Expression::Cast(Box::new(Cast {
33179 this,
33180 to: DataType::Date,
33181 double_colon_syntax: false,
33182 trailing_comments: Vec::new(),
33183 format: None,
33184 default: None,
33185 inferred_type: None,
33186 })))
33187 }
33188 DialectType::Presto
33189 | DialectType::Trino
33190 | DialectType::Athena => {
33191 // Presto: CAST(DATE_PARSE(x, '%Y-%m-%d') AS DATE)
33192 let date_parse =
33193 Expression::Function(Box::new(Function::new(
33194 "DATE_PARSE".to_string(),
33195 vec![this, fmt_expr],
33196 )));
33197 Ok(Expression::Cast(Box::new(Cast {
33198 this: date_parse,
33199 to: DataType::Date,
33200 double_colon_syntax: false,
33201 trailing_comments: Vec::new(),
33202 format: None,
33203 default: None,
33204 inferred_type: None,
33205 })))
33206 }
33207 _ => {
33208 // Others: TsOrDsToDate (delegates to generator)
33209 Ok(Expression::TsOrDsToDate(Box::new(
33210 crate::expressions::TsOrDsToDate {
33211 this: Box::new(this),
33212 format: None,
33213 safe: None,
33214 },
33215 )))
33216 }
33217 }
33218 } else if let Some(fmt) = fmt_str {
33219 match target {
33220 DialectType::Doris
33221 | DialectType::StarRocks
33222 | DialectType::MySQL => {
33223 // Keep STR_TO_DATE but with normalized format (%H:%M:%S -> %T, %-d -> %e)
33224 let mut normalized = fmt.clone();
33225 normalized = normalized.replace("%-d", "%e");
33226 normalized = normalized.replace("%-m", "%c");
33227 normalized = normalized.replace("%H:%M:%S", "%T");
33228 Ok(Expression::Function(Box::new(Function::new(
33229 "STR_TO_DATE".to_string(),
33230 vec![this, Expression::string(&normalized)],
33231 ))))
33232 }
33233 DialectType::Hive => {
33234 // Hive: CAST(FROM_UNIXTIME(UNIX_TIMESTAMP(x, java_fmt)) AS DATE)
33235 let java_fmt = crate::generator::Generator::strftime_to_java_format_static(&fmt);
33236 let unix_ts =
33237 Expression::Function(Box::new(Function::new(
33238 "UNIX_TIMESTAMP".to_string(),
33239 vec![this, Expression::string(&java_fmt)],
33240 )));
33241 let from_unix =
33242 Expression::Function(Box::new(Function::new(
33243 "FROM_UNIXTIME".to_string(),
33244 vec![unix_ts],
33245 )));
33246 Ok(Expression::Cast(Box::new(Cast {
33247 this: from_unix,
33248 to: DataType::Date,
33249 double_colon_syntax: false,
33250 trailing_comments: Vec::new(),
33251 format: None,
33252 default: None,
33253 inferred_type: None,
33254 })))
33255 }
33256 DialectType::Spark | DialectType::Databricks => {
33257 // Spark: TO_DATE(x, java_fmt)
33258 let java_fmt = crate::generator::Generator::strftime_to_java_format_static(&fmt);
33259 Ok(Expression::Function(Box::new(Function::new(
33260 "TO_DATE".to_string(),
33261 vec![this, Expression::string(&java_fmt)],
33262 ))))
33263 }
33264 DialectType::Drill => {
33265 // Drill: TO_DATE(x, java_fmt) with T quoted as 'T' in Java format
33266 // The generator's string literal escaping will double the quotes: 'T' -> ''T''
33267 let java_fmt = crate::generator::Generator::strftime_to_java_format_static(&fmt);
33268 let java_fmt = java_fmt.replace('T', "'T'");
33269 Ok(Expression::Function(Box::new(Function::new(
33270 "TO_DATE".to_string(),
33271 vec![this, Expression::string(&java_fmt)],
33272 ))))
33273 }
33274 _ => {
33275 // For other dialects: use TsOrDsToDate which delegates to generator
33276 Ok(Expression::TsOrDsToDate(Box::new(
33277 crate::expressions::TsOrDsToDate {
33278 this: Box::new(this),
33279 format: Some(fmt),
33280 safe: None,
33281 },
33282 )))
33283 }
33284 }
33285 } else {
33286 // Non-string format - keep as-is
33287 let mut new_args = Vec::new();
33288 new_args.push(this);
33289 new_args.push(fmt_expr);
33290 Ok(Expression::Function(Box::new(Function::new(
33291 "STR_TO_DATE".to_string(),
33292 new_args,
33293 ))))
33294 }
33295 } else {
33296 Ok(Expression::Function(f))
33297 }
33298 } else {
33299 Ok(e)
33300 }
33301 }
33302
33303 Action::TsOrDsAddConvert => {
33304 // TS_OR_DS_ADD(x, n, 'UNIT') from Generic -> dialect-specific DATE_ADD
33305 if let Expression::Function(f) = e {
33306 if f.args.len() == 3 {
33307 let mut args = f.args;
33308 let x = args.remove(0);
33309 let n = args.remove(0);
33310 let unit_expr = args.remove(0);
33311 let unit_str = match &unit_expr {
33312 Expression::Literal(lit)
33313 if matches!(lit.as_ref(), Literal::String(_)) =>
33314 {
33315 let Literal::String(s) = lit.as_ref() else {
33316 unreachable!()
33317 };
33318 s.to_ascii_uppercase()
33319 }
33320 _ => "DAY".to_string(),
33321 };
33322
33323 match target {
33324 DialectType::Hive
33325 | DialectType::Spark
33326 | DialectType::Databricks => {
33327 // DATE_ADD(x, n) - only supports DAY unit
33328 Ok(Expression::Function(Box::new(Function::new(
33329 "DATE_ADD".to_string(),
33330 vec![x, n],
33331 ))))
33332 }
33333 DialectType::MySQL => {
33334 // DATE_ADD(x, INTERVAL n UNIT)
33335 let iu = match unit_str.as_str() {
33336 "YEAR" => crate::expressions::IntervalUnit::Year,
33337 "QUARTER" => crate::expressions::IntervalUnit::Quarter,
33338 "MONTH" => crate::expressions::IntervalUnit::Month,
33339 "WEEK" => crate::expressions::IntervalUnit::Week,
33340 "HOUR" => crate::expressions::IntervalUnit::Hour,
33341 "MINUTE" => crate::expressions::IntervalUnit::Minute,
33342 "SECOND" => crate::expressions::IntervalUnit::Second,
33343 _ => crate::expressions::IntervalUnit::Day,
33344 };
33345 let interval = Expression::Interval(Box::new(
33346 crate::expressions::Interval {
33347 this: Some(n),
33348 unit: Some(
33349 crate::expressions::IntervalUnitSpec::Simple {
33350 unit: iu,
33351 use_plural: false,
33352 },
33353 ),
33354 },
33355 ));
33356 Ok(Expression::Function(Box::new(Function::new(
33357 "DATE_ADD".to_string(),
33358 vec![x, interval],
33359 ))))
33360 }
33361 DialectType::Presto | DialectType::Trino | DialectType::Athena => {
33362 // DATE_ADD('UNIT', n, CAST(CAST(x AS TIMESTAMP) AS DATE))
33363 let cast_ts = Expression::Cast(Box::new(Cast {
33364 this: x,
33365 to: DataType::Timestamp {
33366 precision: None,
33367 timezone: false,
33368 },
33369 double_colon_syntax: false,
33370 trailing_comments: Vec::new(),
33371 format: None,
33372 default: None,
33373 inferred_type: None,
33374 }));
33375 let cast_date = Expression::Cast(Box::new(Cast {
33376 this: cast_ts,
33377 to: DataType::Date,
33378 double_colon_syntax: false,
33379 trailing_comments: Vec::new(),
33380 format: None,
33381 default: None,
33382 inferred_type: None,
33383 }));
33384 Ok(Expression::Function(Box::new(Function::new(
33385 "DATE_ADD".to_string(),
33386 vec![Expression::string(&unit_str), n, cast_date],
33387 ))))
33388 }
33389 DialectType::DuckDB => {
33390 // CAST(x AS DATE) + INTERVAL n UNIT
33391 let cast_date = Expression::Cast(Box::new(Cast {
33392 this: x,
33393 to: DataType::Date,
33394 double_colon_syntax: false,
33395 trailing_comments: Vec::new(),
33396 format: None,
33397 default: None,
33398 inferred_type: None,
33399 }));
33400 let iu = match unit_str.as_str() {
33401 "YEAR" => crate::expressions::IntervalUnit::Year,
33402 "QUARTER" => crate::expressions::IntervalUnit::Quarter,
33403 "MONTH" => crate::expressions::IntervalUnit::Month,
33404 "WEEK" => crate::expressions::IntervalUnit::Week,
33405 "HOUR" => crate::expressions::IntervalUnit::Hour,
33406 "MINUTE" => crate::expressions::IntervalUnit::Minute,
33407 "SECOND" => crate::expressions::IntervalUnit::Second,
33408 _ => crate::expressions::IntervalUnit::Day,
33409 };
33410 let interval = Expression::Interval(Box::new(
33411 crate::expressions::Interval {
33412 this: Some(n),
33413 unit: Some(
33414 crate::expressions::IntervalUnitSpec::Simple {
33415 unit: iu,
33416 use_plural: false,
33417 },
33418 ),
33419 },
33420 ));
33421 Ok(Expression::Add(Box::new(crate::expressions::BinaryOp {
33422 left: cast_date,
33423 right: interval,
33424 left_comments: Vec::new(),
33425 operator_comments: Vec::new(),
33426 trailing_comments: Vec::new(),
33427 inferred_type: None,
33428 })))
33429 }
33430 DialectType::Drill => {
33431 // DATE_ADD(CAST(x AS DATE), INTERVAL n UNIT)
33432 let cast_date = Expression::Cast(Box::new(Cast {
33433 this: x,
33434 to: DataType::Date,
33435 double_colon_syntax: false,
33436 trailing_comments: Vec::new(),
33437 format: None,
33438 default: None,
33439 inferred_type: None,
33440 }));
33441 let iu = match unit_str.as_str() {
33442 "YEAR" => crate::expressions::IntervalUnit::Year,
33443 "QUARTER" => crate::expressions::IntervalUnit::Quarter,
33444 "MONTH" => crate::expressions::IntervalUnit::Month,
33445 "WEEK" => crate::expressions::IntervalUnit::Week,
33446 "HOUR" => crate::expressions::IntervalUnit::Hour,
33447 "MINUTE" => crate::expressions::IntervalUnit::Minute,
33448 "SECOND" => crate::expressions::IntervalUnit::Second,
33449 _ => crate::expressions::IntervalUnit::Day,
33450 };
33451 let interval = Expression::Interval(Box::new(
33452 crate::expressions::Interval {
33453 this: Some(n),
33454 unit: Some(
33455 crate::expressions::IntervalUnitSpec::Simple {
33456 unit: iu,
33457 use_plural: false,
33458 },
33459 ),
33460 },
33461 ));
33462 Ok(Expression::Function(Box::new(Function::new(
33463 "DATE_ADD".to_string(),
33464 vec![cast_date, interval],
33465 ))))
33466 }
33467 _ => {
33468 // Default: keep as TS_OR_DS_ADD
33469 Ok(Expression::Function(Box::new(Function::new(
33470 "TS_OR_DS_ADD".to_string(),
33471 vec![x, n, unit_expr],
33472 ))))
33473 }
33474 }
33475 } else {
33476 Ok(Expression::Function(f))
33477 }
33478 } else {
33479 Ok(e)
33480 }
33481 }
33482
33483 Action::DateFromUnixDateConvert => {
33484 // DATE_FROM_UNIX_DATE(n) -> DATEADD(DAY, n, CAST('1970-01-01' AS DATE))
33485 if let Expression::Function(f) = e {
33486 // Keep as-is for dialects that support DATE_FROM_UNIX_DATE natively
33487 if matches!(
33488 target,
33489 DialectType::Spark | DialectType::Databricks | DialectType::BigQuery
33490 ) {
33491 return Ok(Expression::Function(Box::new(Function::new(
33492 "DATE_FROM_UNIX_DATE".to_string(),
33493 f.args,
33494 ))));
33495 }
33496 let n = f.args.into_iter().next().unwrap();
33497 let epoch_date = Expression::Cast(Box::new(Cast {
33498 this: Expression::string("1970-01-01"),
33499 to: DataType::Date,
33500 double_colon_syntax: false,
33501 trailing_comments: Vec::new(),
33502 format: None,
33503 default: None,
33504 inferred_type: None,
33505 }));
33506 match target {
33507 DialectType::DuckDB => {
33508 // CAST('1970-01-01' AS DATE) + INTERVAL n DAY
33509 let interval =
33510 Expression::Interval(Box::new(crate::expressions::Interval {
33511 this: Some(n),
33512 unit: Some(crate::expressions::IntervalUnitSpec::Simple {
33513 unit: crate::expressions::IntervalUnit::Day,
33514 use_plural: false,
33515 }),
33516 }));
33517 Ok(Expression::Add(Box::new(
33518 crate::expressions::BinaryOp::new(epoch_date, interval),
33519 )))
33520 }
33521 DialectType::Presto | DialectType::Trino | DialectType::Athena => {
33522 // DATE_ADD('DAY', n, CAST('1970-01-01' AS DATE))
33523 Ok(Expression::Function(Box::new(Function::new(
33524 "DATE_ADD".to_string(),
33525 vec![Expression::string("DAY"), n, epoch_date],
33526 ))))
33527 }
33528 DialectType::Snowflake | DialectType::Redshift | DialectType::TSQL => {
33529 // DATEADD(DAY, n, CAST('1970-01-01' AS DATE))
33530 Ok(Expression::Function(Box::new(Function::new(
33531 "DATEADD".to_string(),
33532 vec![
33533 Expression::Identifier(Identifier::new("DAY")),
33534 n,
33535 epoch_date,
33536 ],
33537 ))))
33538 }
33539 DialectType::BigQuery => {
33540 // DATE_ADD(CAST('1970-01-01' AS DATE), INTERVAL n DAY)
33541 let interval =
33542 Expression::Interval(Box::new(crate::expressions::Interval {
33543 this: Some(n),
33544 unit: Some(crate::expressions::IntervalUnitSpec::Simple {
33545 unit: crate::expressions::IntervalUnit::Day,
33546 use_plural: false,
33547 }),
33548 }));
33549 Ok(Expression::Function(Box::new(Function::new(
33550 "DATE_ADD".to_string(),
33551 vec![epoch_date, interval],
33552 ))))
33553 }
33554 DialectType::MySQL
33555 | DialectType::Doris
33556 | DialectType::StarRocks
33557 | DialectType::Drill => {
33558 // DATE_ADD(CAST('1970-01-01' AS DATE), INTERVAL n DAY)
33559 let interval =
33560 Expression::Interval(Box::new(crate::expressions::Interval {
33561 this: Some(n),
33562 unit: Some(crate::expressions::IntervalUnitSpec::Simple {
33563 unit: crate::expressions::IntervalUnit::Day,
33564 use_plural: false,
33565 }),
33566 }));
33567 Ok(Expression::Function(Box::new(Function::new(
33568 "DATE_ADD".to_string(),
33569 vec![epoch_date, interval],
33570 ))))
33571 }
33572 DialectType::Hive | DialectType::Spark | DialectType::Databricks => {
33573 // DATE_ADD(CAST('1970-01-01' AS DATE), n)
33574 Ok(Expression::Function(Box::new(Function::new(
33575 "DATE_ADD".to_string(),
33576 vec![epoch_date, n],
33577 ))))
33578 }
33579 DialectType::PostgreSQL
33580 | DialectType::Materialize
33581 | DialectType::RisingWave => {
33582 // CAST('1970-01-01' AS DATE) + INTERVAL 'n DAY'
33583 let n_str = match &n {
33584 Expression::Literal(lit)
33585 if matches!(lit.as_ref(), Literal::Number(_)) =>
33586 {
33587 let Literal::Number(s) = lit.as_ref() else {
33588 unreachable!()
33589 };
33590 s.clone()
33591 }
33592 _ => Self::expr_to_string_static(&n),
33593 };
33594 let interval =
33595 Expression::Interval(Box::new(crate::expressions::Interval {
33596 this: Some(Expression::string(&format!("{} DAY", n_str))),
33597 unit: None,
33598 }));
33599 Ok(Expression::Add(Box::new(
33600 crate::expressions::BinaryOp::new(epoch_date, interval),
33601 )))
33602 }
33603 _ => {
33604 // Default: keep as-is
33605 Ok(Expression::Function(Box::new(Function::new(
33606 "DATE_FROM_UNIX_DATE".to_string(),
33607 vec![n],
33608 ))))
33609 }
33610 }
33611 } else {
33612 Ok(e)
33613 }
33614 }
33615
33616 Action::ArrayRemoveConvert => {
33617 // ARRAY_REMOVE(arr, target) -> LIST_FILTER/arrayFilter
33618 if let Expression::ArrayRemove(bf) = e {
33619 let arr = bf.this;
33620 let target_val = bf.expression;
33621 match target {
33622 DialectType::DuckDB => {
33623 let u_id = crate::expressions::Identifier::new("_u");
33624 let lambda =
33625 Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
33626 parameters: vec![u_id.clone()],
33627 body: Expression::Neq(Box::new(BinaryOp {
33628 left: Expression::Identifier(u_id),
33629 right: target_val,
33630 left_comments: Vec::new(),
33631 operator_comments: Vec::new(),
33632 trailing_comments: Vec::new(),
33633 inferred_type: None,
33634 })),
33635 colon: false,
33636 parameter_types: Vec::new(),
33637 }));
33638 Ok(Expression::Function(Box::new(Function::new(
33639 "LIST_FILTER".to_string(),
33640 vec![arr, lambda],
33641 ))))
33642 }
33643 DialectType::ClickHouse => {
33644 let u_id = crate::expressions::Identifier::new("_u");
33645 let lambda =
33646 Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
33647 parameters: vec![u_id.clone()],
33648 body: Expression::Neq(Box::new(BinaryOp {
33649 left: Expression::Identifier(u_id),
33650 right: target_val,
33651 left_comments: Vec::new(),
33652 operator_comments: Vec::new(),
33653 trailing_comments: Vec::new(),
33654 inferred_type: None,
33655 })),
33656 colon: false,
33657 parameter_types: Vec::new(),
33658 }));
33659 Ok(Expression::Function(Box::new(Function::new(
33660 "arrayFilter".to_string(),
33661 vec![lambda, arr],
33662 ))))
33663 }
33664 DialectType::BigQuery => {
33665 // ARRAY(SELECT _u FROM UNNEST(the_array) AS _u WHERE _u <> target)
33666 let u_id = crate::expressions::Identifier::new("_u");
33667 let u_col =
33668 Expression::Column(Box::new(crate::expressions::Column {
33669 name: u_id.clone(),
33670 table: None,
33671 join_mark: false,
33672 trailing_comments: Vec::new(),
33673 span: None,
33674 inferred_type: None,
33675 }));
33676 let unnest_expr =
33677 Expression::Unnest(Box::new(crate::expressions::UnnestFunc {
33678 this: arr,
33679 expressions: Vec::new(),
33680 with_ordinality: false,
33681 alias: None,
33682 offset_alias: None,
33683 }));
33684 let aliased_unnest =
33685 Expression::Alias(Box::new(crate::expressions::Alias {
33686 this: unnest_expr,
33687 alias: u_id.clone(),
33688 column_aliases: Vec::new(),
33689 alias_explicit_as: false,
33690 alias_keyword: None,
33691 pre_alias_comments: Vec::new(),
33692 trailing_comments: Vec::new(),
33693 inferred_type: None,
33694 }));
33695 let where_cond = Expression::Neq(Box::new(BinaryOp {
33696 left: u_col.clone(),
33697 right: target_val,
33698 left_comments: Vec::new(),
33699 operator_comments: Vec::new(),
33700 trailing_comments: Vec::new(),
33701 inferred_type: None,
33702 }));
33703 let subquery = Expression::Select(Box::new(
33704 crate::expressions::Select::new()
33705 .column(u_col)
33706 .from(aliased_unnest)
33707 .where_(where_cond),
33708 ));
33709 Ok(Expression::ArrayFunc(Box::new(
33710 crate::expressions::ArrayConstructor {
33711 expressions: vec![subquery],
33712 bracket_notation: false,
33713 use_list_keyword: false,
33714 },
33715 )))
33716 }
33717 _ => Ok(Expression::ArrayRemove(Box::new(
33718 crate::expressions::BinaryFunc {
33719 original_name: None,
33720 this: arr,
33721 expression: target_val,
33722 inferred_type: None,
33723 },
33724 ))),
33725 }
33726 } else {
33727 Ok(e)
33728 }
33729 }
33730
33731 Action::ArrayReverseConvert => {
33732 // ARRAY_REVERSE(x) -> arrayReverse(x) for ClickHouse
33733 if let Expression::ArrayReverse(af) = e {
33734 Ok(Expression::Function(Box::new(Function::new(
33735 "arrayReverse".to_string(),
33736 vec![af.this],
33737 ))))
33738 } else {
33739 Ok(e)
33740 }
33741 }
33742
33743 Action::JsonKeysConvert => {
33744 // JSON_KEYS(x) -> JSON_OBJECT_KEYS/OBJECT_KEYS
33745 if let Expression::JsonKeys(uf) = e {
33746 match target {
33747 DialectType::Spark | DialectType::Databricks => {
33748 Ok(Expression::Function(Box::new(Function::new(
33749 "JSON_OBJECT_KEYS".to_string(),
33750 vec![uf.this],
33751 ))))
33752 }
33753 DialectType::Snowflake => Ok(Expression::Function(Box::new(
33754 Function::new("OBJECT_KEYS".to_string(), vec![uf.this]),
33755 ))),
33756 _ => Ok(Expression::JsonKeys(uf)),
33757 }
33758 } else {
33759 Ok(e)
33760 }
33761 }
33762
33763 Action::ParseJsonStrip => {
33764 // PARSE_JSON(x) -> x (strip wrapper for SQLite/Doris)
33765 if let Expression::ParseJson(uf) = e {
33766 Ok(uf.this)
33767 } else {
33768 Ok(e)
33769 }
33770 }
33771
33772 Action::ArraySizeDrill => {
33773 // ARRAY_SIZE(x) -> REPEATED_COUNT(x) for Drill
33774 if let Expression::ArraySize(uf) = e {
33775 Ok(Expression::Function(Box::new(Function::new(
33776 "REPEATED_COUNT".to_string(),
33777 vec![uf.this],
33778 ))))
33779 } else {
33780 Ok(e)
33781 }
33782 }
33783
33784 Action::WeekOfYearToWeekIso => {
33785 // WEEKOFYEAR(x) -> WEEKISO(x) for Snowflake (cross-dialect normalization)
33786 if let Expression::WeekOfYear(uf) = e {
33787 Ok(Expression::Function(Box::new(Function::new(
33788 "WEEKISO".to_string(),
33789 vec![uf.this],
33790 ))))
33791 } else {
33792 Ok(e)
33793 }
33794 }
33795 }
33796 })
33797 }
33798
33799 /// Convert DATE_TRUNC('unit', x) to MySQL-specific expansion
33800 fn date_trunc_to_mysql(unit: &str, expr: &Expression) -> Result<Expression> {
33801 use crate::expressions::Function;
33802 match unit {
33803 "DAY" => {
33804 // DATE(x)
33805 Ok(Expression::Function(Box::new(Function::new(
33806 "DATE".to_string(),
33807 vec![expr.clone()],
33808 ))))
33809 }
33810 "WEEK" => {
33811 // STR_TO_DATE(CONCAT(YEAR(x), ' ', WEEK(x, 1), ' 1'), '%Y %u %w')
33812 let year_x = Expression::Function(Box::new(Function::new(
33813 "YEAR".to_string(),
33814 vec![expr.clone()],
33815 )));
33816 let week_x = Expression::Function(Box::new(Function::new(
33817 "WEEK".to_string(),
33818 vec![expr.clone(), Expression::number(1)],
33819 )));
33820 let concat_args = vec![
33821 year_x,
33822 Expression::string(" "),
33823 week_x,
33824 Expression::string(" 1"),
33825 ];
33826 let concat = Expression::Function(Box::new(Function::new(
33827 "CONCAT".to_string(),
33828 concat_args,
33829 )));
33830 Ok(Expression::Function(Box::new(Function::new(
33831 "STR_TO_DATE".to_string(),
33832 vec![concat, Expression::string("%Y %u %w")],
33833 ))))
33834 }
33835 "MONTH" => {
33836 // STR_TO_DATE(CONCAT(YEAR(x), ' ', MONTH(x), ' 1'), '%Y %c %e')
33837 let year_x = Expression::Function(Box::new(Function::new(
33838 "YEAR".to_string(),
33839 vec![expr.clone()],
33840 )));
33841 let month_x = Expression::Function(Box::new(Function::new(
33842 "MONTH".to_string(),
33843 vec![expr.clone()],
33844 )));
33845 let concat_args = vec![
33846 year_x,
33847 Expression::string(" "),
33848 month_x,
33849 Expression::string(" 1"),
33850 ];
33851 let concat = Expression::Function(Box::new(Function::new(
33852 "CONCAT".to_string(),
33853 concat_args,
33854 )));
33855 Ok(Expression::Function(Box::new(Function::new(
33856 "STR_TO_DATE".to_string(),
33857 vec![concat, Expression::string("%Y %c %e")],
33858 ))))
33859 }
33860 "QUARTER" => {
33861 // STR_TO_DATE(CONCAT(YEAR(x), ' ', QUARTER(x) * 3 - 2, ' 1'), '%Y %c %e')
33862 let year_x = Expression::Function(Box::new(Function::new(
33863 "YEAR".to_string(),
33864 vec![expr.clone()],
33865 )));
33866 let quarter_x = Expression::Function(Box::new(Function::new(
33867 "QUARTER".to_string(),
33868 vec![expr.clone()],
33869 )));
33870 // QUARTER(x) * 3 - 2
33871 let mul = Expression::Mul(Box::new(crate::expressions::BinaryOp {
33872 left: quarter_x,
33873 right: Expression::number(3),
33874 left_comments: Vec::new(),
33875 operator_comments: Vec::new(),
33876 trailing_comments: Vec::new(),
33877 inferred_type: None,
33878 }));
33879 let sub = Expression::Sub(Box::new(crate::expressions::BinaryOp {
33880 left: mul,
33881 right: Expression::number(2),
33882 left_comments: Vec::new(),
33883 operator_comments: Vec::new(),
33884 trailing_comments: Vec::new(),
33885 inferred_type: None,
33886 }));
33887 let concat_args = vec![
33888 year_x,
33889 Expression::string(" "),
33890 sub,
33891 Expression::string(" 1"),
33892 ];
33893 let concat = Expression::Function(Box::new(Function::new(
33894 "CONCAT".to_string(),
33895 concat_args,
33896 )));
33897 Ok(Expression::Function(Box::new(Function::new(
33898 "STR_TO_DATE".to_string(),
33899 vec![concat, Expression::string("%Y %c %e")],
33900 ))))
33901 }
33902 "YEAR" => {
33903 // STR_TO_DATE(CONCAT(YEAR(x), ' 1 1'), '%Y %c %e')
33904 let year_x = Expression::Function(Box::new(Function::new(
33905 "YEAR".to_string(),
33906 vec![expr.clone()],
33907 )));
33908 let concat_args = vec![year_x, Expression::string(" 1 1")];
33909 let concat = Expression::Function(Box::new(Function::new(
33910 "CONCAT".to_string(),
33911 concat_args,
33912 )));
33913 Ok(Expression::Function(Box::new(Function::new(
33914 "STR_TO_DATE".to_string(),
33915 vec![concat, Expression::string("%Y %c %e")],
33916 ))))
33917 }
33918 _ => {
33919 // Unsupported unit -> keep as DATE_TRUNC
33920 Ok(Expression::Function(Box::new(Function::new(
33921 "DATE_TRUNC".to_string(),
33922 vec![Expression::string(unit), expr.clone()],
33923 ))))
33924 }
33925 }
33926 }
33927
33928 /// Check if a DataType is or contains VARCHAR/CHAR (for Spark VARCHAR->STRING normalization)
33929 fn has_varchar_char_type(dt: &crate::expressions::DataType) -> bool {
33930 use crate::expressions::DataType;
33931 match dt {
33932 DataType::VarChar { .. } | DataType::Char { .. } => true,
33933 DataType::Struct { fields, .. } => fields
33934 .iter()
33935 .any(|f| Self::has_varchar_char_type(&f.data_type)),
33936 _ => false,
33937 }
33938 }
33939
33940 /// Recursively normalize VARCHAR/CHAR to STRING in a DataType (for Spark)
33941 fn normalize_varchar_to_string(
33942 dt: crate::expressions::DataType,
33943 ) -> crate::expressions::DataType {
33944 use crate::expressions::DataType;
33945 match dt {
33946 DataType::VarChar { .. } | DataType::Char { .. } => DataType::Custom {
33947 name: "STRING".to_string(),
33948 },
33949 DataType::Struct { fields, nested } => {
33950 let fields = fields
33951 .into_iter()
33952 .map(|mut f| {
33953 f.data_type = Self::normalize_varchar_to_string(f.data_type);
33954 f
33955 })
33956 .collect();
33957 DataType::Struct { fields, nested }
33958 }
33959 other => other,
33960 }
33961 }
33962
33963 /// Normalize an interval string like '1day' or ' 2 days ' to proper INTERVAL expression
33964 fn normalize_interval_string(expr: Expression, target: DialectType) -> Expression {
33965 if let Expression::Literal(ref lit) = expr {
33966 if let crate::expressions::Literal::String(ref s) = lit.as_ref() {
33967 // Try to parse patterns like '1day', '1 day', '2 days', ' 2 days '
33968 let trimmed = s.trim();
33969
33970 // Find where digits end and unit text begins
33971 let digit_end = trimmed
33972 .find(|c: char| !c.is_ascii_digit())
33973 .unwrap_or(trimmed.len());
33974 if digit_end == 0 || digit_end == trimmed.len() {
33975 return expr;
33976 }
33977 let num = &trimmed[..digit_end];
33978 let unit_text = trimmed[digit_end..].trim().to_ascii_uppercase();
33979 if unit_text.is_empty() {
33980 return expr;
33981 }
33982
33983 let known_units = [
33984 "DAY", "DAYS", "HOUR", "HOURS", "MINUTE", "MINUTES", "SECOND", "SECONDS",
33985 "WEEK", "WEEKS", "MONTH", "MONTHS", "YEAR", "YEARS",
33986 ];
33987 if !known_units.contains(&unit_text.as_str()) {
33988 return expr;
33989 }
33990
33991 let unit_str = unit_text.clone();
33992 // Singularize
33993 let unit_singular = if unit_str.ends_with('S') && unit_str.len() > 3 {
33994 &unit_str[..unit_str.len() - 1]
33995 } else {
33996 &unit_str
33997 };
33998 let unit = unit_singular;
33999
34000 match target {
34001 DialectType::Presto | DialectType::Trino | DialectType::Athena => {
34002 // INTERVAL '2' DAY
34003 let iu = match unit {
34004 "DAY" => crate::expressions::IntervalUnit::Day,
34005 "HOUR" => crate::expressions::IntervalUnit::Hour,
34006 "MINUTE" => crate::expressions::IntervalUnit::Minute,
34007 "SECOND" => crate::expressions::IntervalUnit::Second,
34008 "WEEK" => crate::expressions::IntervalUnit::Week,
34009 "MONTH" => crate::expressions::IntervalUnit::Month,
34010 "YEAR" => crate::expressions::IntervalUnit::Year,
34011 _ => return expr,
34012 };
34013 return Expression::Interval(Box::new(crate::expressions::Interval {
34014 this: Some(Expression::string(num)),
34015 unit: Some(crate::expressions::IntervalUnitSpec::Simple {
34016 unit: iu,
34017 use_plural: false,
34018 }),
34019 }));
34020 }
34021 DialectType::PostgreSQL | DialectType::Redshift | DialectType::DuckDB => {
34022 // INTERVAL '2 DAYS'
34023 let plural = if num != "1" && !unit_str.ends_with('S') {
34024 format!("{} {}S", num, unit)
34025 } else if unit_str.ends_with('S') {
34026 format!("{} {}", num, unit_str)
34027 } else {
34028 format!("{} {}", num, unit)
34029 };
34030 return Expression::Interval(Box::new(crate::expressions::Interval {
34031 this: Some(Expression::string(&plural)),
34032 unit: None,
34033 }));
34034 }
34035 _ => {
34036 // Spark/Databricks/Hive: INTERVAL '1' DAY
34037 let iu = match unit {
34038 "DAY" => crate::expressions::IntervalUnit::Day,
34039 "HOUR" => crate::expressions::IntervalUnit::Hour,
34040 "MINUTE" => crate::expressions::IntervalUnit::Minute,
34041 "SECOND" => crate::expressions::IntervalUnit::Second,
34042 "WEEK" => crate::expressions::IntervalUnit::Week,
34043 "MONTH" => crate::expressions::IntervalUnit::Month,
34044 "YEAR" => crate::expressions::IntervalUnit::Year,
34045 _ => return expr,
34046 };
34047 return Expression::Interval(Box::new(crate::expressions::Interval {
34048 this: Some(Expression::string(num)),
34049 unit: Some(crate::expressions::IntervalUnitSpec::Simple {
34050 unit: iu,
34051 use_plural: false,
34052 }),
34053 }));
34054 }
34055 }
34056 }
34057 }
34058 // If it's already an INTERVAL expression, pass through
34059 expr
34060 }
34061
34062 /// Rewrite SELECT expressions containing UNNEST into expanded form with CROSS JOINs.
34063 /// DuckDB: SELECT UNNEST(arr1), UNNEST(arr2) ->
34064 /// BigQuery: SELECT IF(pos = pos_2, col, NULL) AS col, ... FROM UNNEST(GENERATE_ARRAY(0, ...)) AS pos CROSS JOIN ...
34065 /// Presto: SELECT IF(_u.pos = _u_2.pos_2, _u_2.col) AS col, ... FROM UNNEST(SEQUENCE(1, ...)) AS _u(pos) CROSS JOIN ...
34066 fn rewrite_unnest_expansion(
34067 select: &crate::expressions::Select,
34068 target: DialectType,
34069 ) -> Option<crate::expressions::Select> {
34070 use crate::expressions::{
34071 Alias, BinaryOp, Column, From, Function, Identifier, Join, JoinKind, Literal,
34072 UnnestFunc,
34073 };
34074
34075 let index_offset: i64 = match target {
34076 DialectType::Presto | DialectType::Trino => 1,
34077 _ => 0, // BigQuery, Snowflake
34078 };
34079
34080 let if_func_name = match target {
34081 DialectType::Snowflake => "IFF",
34082 _ => "IF",
34083 };
34084
34085 let array_length_func = match target {
34086 DialectType::BigQuery => "ARRAY_LENGTH",
34087 DialectType::Presto | DialectType::Trino => "CARDINALITY",
34088 DialectType::Snowflake => "ARRAY_SIZE",
34089 _ => "ARRAY_LENGTH",
34090 };
34091
34092 let use_table_aliases = matches!(
34093 target,
34094 DialectType::Presto | DialectType::Trino | DialectType::Snowflake
34095 );
34096 let null_third_arg = matches!(target, DialectType::BigQuery | DialectType::Snowflake);
34097
34098 fn make_col(name: &str, table: Option<&str>) -> Expression {
34099 if let Some(tbl) = table {
34100 Expression::boxed_column(Column {
34101 name: Identifier::new(name.to_string()),
34102 table: Some(Identifier::new(tbl.to_string())),
34103 join_mark: false,
34104 trailing_comments: Vec::new(),
34105 span: None,
34106 inferred_type: None,
34107 })
34108 } else {
34109 Expression::Identifier(Identifier::new(name.to_string()))
34110 }
34111 }
34112
34113 fn make_join(this: Expression) -> Join {
34114 Join {
34115 this,
34116 on: None,
34117 using: Vec::new(),
34118 kind: JoinKind::Cross,
34119 use_inner_keyword: false,
34120 use_outer_keyword: false,
34121 deferred_condition: false,
34122 join_hint: None,
34123 match_condition: None,
34124 pivots: Vec::new(),
34125 comments: Vec::new(),
34126 nesting_group: 0,
34127 directed: false,
34128 }
34129 }
34130
34131 // Collect UNNEST info from SELECT expressions
34132 struct UnnestInfo {
34133 arr_expr: Expression,
34134 col_alias: String,
34135 pos_alias: String,
34136 source_alias: String,
34137 original_expr: Expression,
34138 has_outer_alias: Option<String>,
34139 }
34140
34141 let mut unnest_infos: Vec<UnnestInfo> = Vec::new();
34142 let mut col_counter = 0usize;
34143 let mut pos_counter = 1usize;
34144 let mut source_counter = 1usize;
34145
34146 fn extract_unnest_arg(expr: &Expression) -> Option<Expression> {
34147 match expr {
34148 Expression::Unnest(u) => Some(u.this.clone()),
34149 Expression::Function(f)
34150 if f.name.eq_ignore_ascii_case("UNNEST") && !f.args.is_empty() =>
34151 {
34152 Some(f.args[0].clone())
34153 }
34154 Expression::Alias(a) => extract_unnest_arg(&a.this),
34155 Expression::Add(op)
34156 | Expression::Sub(op)
34157 | Expression::Mul(op)
34158 | Expression::Div(op) => {
34159 extract_unnest_arg(&op.left).or_else(|| extract_unnest_arg(&op.right))
34160 }
34161 _ => None,
34162 }
34163 }
34164
34165 fn get_alias_name(expr: &Expression) -> Option<String> {
34166 if let Expression::Alias(a) = expr {
34167 Some(a.alias.name.clone())
34168 } else {
34169 None
34170 }
34171 }
34172
34173 for sel_expr in &select.expressions {
34174 if let Some(arr) = extract_unnest_arg(sel_expr) {
34175 col_counter += 1;
34176 pos_counter += 1;
34177 source_counter += 1;
34178
34179 let col_alias = if col_counter == 1 {
34180 "col".to_string()
34181 } else {
34182 format!("col_{}", col_counter)
34183 };
34184 let pos_alias = format!("pos_{}", pos_counter);
34185 let source_alias = format!("_u_{}", source_counter);
34186 let has_outer_alias = get_alias_name(sel_expr);
34187
34188 unnest_infos.push(UnnestInfo {
34189 arr_expr: arr,
34190 col_alias,
34191 pos_alias,
34192 source_alias,
34193 original_expr: sel_expr.clone(),
34194 has_outer_alias,
34195 });
34196 }
34197 }
34198
34199 if unnest_infos.is_empty() {
34200 return None;
34201 }
34202
34203 let series_alias = "pos".to_string();
34204 let series_source_alias = "_u".to_string();
34205 let tbl_ref = if use_table_aliases {
34206 Some(series_source_alias.as_str())
34207 } else {
34208 None
34209 };
34210
34211 // Build new SELECT expressions
34212 let mut new_select_exprs = Vec::new();
34213 for info in &unnest_infos {
34214 let actual_col_name = info.has_outer_alias.as_ref().unwrap_or(&info.col_alias);
34215 let src_ref = if use_table_aliases {
34216 Some(info.source_alias.as_str())
34217 } else {
34218 None
34219 };
34220
34221 let pos_col = make_col(&series_alias, tbl_ref);
34222 let unnest_pos_col = make_col(&info.pos_alias, src_ref);
34223 let col_ref = make_col(actual_col_name, src_ref);
34224
34225 let eq_cond = Expression::Eq(Box::new(BinaryOp::new(
34226 pos_col.clone(),
34227 unnest_pos_col.clone(),
34228 )));
34229 let mut if_args = vec![eq_cond, col_ref];
34230 if null_third_arg {
34231 if_args.push(Expression::Null(crate::expressions::Null));
34232 }
34233
34234 let if_expr =
34235 Expression::Function(Box::new(Function::new(if_func_name.to_string(), if_args)));
34236 let final_expr = Self::replace_unnest_with_if(&info.original_expr, &if_expr);
34237
34238 new_select_exprs.push(Expression::Alias(Box::new(Alias::new(
34239 final_expr,
34240 Identifier::new(actual_col_name.clone()),
34241 ))));
34242 }
34243
34244 // Build array size expressions for GREATEST
34245 let size_exprs: Vec<Expression> = unnest_infos
34246 .iter()
34247 .map(|info| {
34248 Expression::Function(Box::new(Function::new(
34249 array_length_func.to_string(),
34250 vec![info.arr_expr.clone()],
34251 )))
34252 })
34253 .collect();
34254
34255 let greatest =
34256 Expression::Function(Box::new(Function::new("GREATEST".to_string(), size_exprs)));
34257
34258 let series_end = if index_offset == 0 {
34259 Expression::Sub(Box::new(BinaryOp::new(
34260 greatest,
34261 Expression::Literal(Box::new(Literal::Number("1".to_string()))),
34262 )))
34263 } else {
34264 greatest
34265 };
34266
34267 // Build the position array source
34268 let series_unnest_expr = match target {
34269 DialectType::BigQuery => {
34270 let gen_array = Expression::Function(Box::new(Function::new(
34271 "GENERATE_ARRAY".to_string(),
34272 vec![
34273 Expression::Literal(Box::new(Literal::Number("0".to_string()))),
34274 series_end,
34275 ],
34276 )));
34277 Expression::Unnest(Box::new(UnnestFunc {
34278 this: gen_array,
34279 expressions: Vec::new(),
34280 with_ordinality: false,
34281 alias: None,
34282 offset_alias: None,
34283 }))
34284 }
34285 DialectType::Presto | DialectType::Trino => {
34286 let sequence = Expression::Function(Box::new(Function::new(
34287 "SEQUENCE".to_string(),
34288 vec![
34289 Expression::Literal(Box::new(Literal::Number("1".to_string()))),
34290 series_end,
34291 ],
34292 )));
34293 Expression::Unnest(Box::new(UnnestFunc {
34294 this: sequence,
34295 expressions: Vec::new(),
34296 with_ordinality: false,
34297 alias: None,
34298 offset_alias: None,
34299 }))
34300 }
34301 DialectType::Snowflake => {
34302 let range_end = Expression::Add(Box::new(BinaryOp::new(
34303 Expression::Paren(Box::new(crate::expressions::Paren {
34304 this: series_end,
34305 trailing_comments: Vec::new(),
34306 })),
34307 Expression::Literal(Box::new(Literal::Number("1".to_string()))),
34308 )));
34309 let gen_range = Expression::Function(Box::new(Function::new(
34310 "ARRAY_GENERATE_RANGE".to_string(),
34311 vec![
34312 Expression::Literal(Box::new(Literal::Number("0".to_string()))),
34313 range_end,
34314 ],
34315 )));
34316 let flatten_arg =
34317 Expression::NamedArgument(Box::new(crate::expressions::NamedArgument {
34318 name: Identifier::new("INPUT".to_string()),
34319 value: gen_range,
34320 separator: crate::expressions::NamedArgSeparator::DArrow,
34321 }));
34322 let flatten = Expression::Function(Box::new(Function::new(
34323 "FLATTEN".to_string(),
34324 vec![flatten_arg],
34325 )));
34326 Expression::Function(Box::new(Function::new("TABLE".to_string(), vec![flatten])))
34327 }
34328 _ => return None,
34329 };
34330
34331 // Build series alias expression
34332 let series_alias_expr = if use_table_aliases {
34333 let col_aliases = if matches!(target, DialectType::Snowflake) {
34334 vec![
34335 Identifier::new("seq".to_string()),
34336 Identifier::new("key".to_string()),
34337 Identifier::new("path".to_string()),
34338 Identifier::new("index".to_string()),
34339 Identifier::new(series_alias.clone()),
34340 Identifier::new("this".to_string()),
34341 ]
34342 } else {
34343 vec![Identifier::new(series_alias.clone())]
34344 };
34345 Expression::Alias(Box::new(Alias {
34346 this: series_unnest_expr,
34347 alias: Identifier::new(series_source_alias.clone()),
34348 column_aliases: col_aliases,
34349 alias_explicit_as: false,
34350 alias_keyword: None,
34351 pre_alias_comments: Vec::new(),
34352 trailing_comments: Vec::new(),
34353 inferred_type: None,
34354 }))
34355 } else {
34356 Expression::Alias(Box::new(Alias::new(
34357 series_unnest_expr,
34358 Identifier::new(series_alias.clone()),
34359 )))
34360 };
34361
34362 // Build CROSS JOINs for each UNNEST
34363 let mut joins = Vec::new();
34364 for info in &unnest_infos {
34365 let actual_col_name = info.has_outer_alias.as_ref().unwrap_or(&info.col_alias);
34366
34367 let unnest_join_expr = match target {
34368 DialectType::BigQuery => {
34369 // UNNEST([1,2,3]) AS col WITH OFFSET AS pos_2
34370 let unnest = UnnestFunc {
34371 this: info.arr_expr.clone(),
34372 expressions: Vec::new(),
34373 with_ordinality: true,
34374 alias: Some(Identifier::new(actual_col_name.clone())),
34375 offset_alias: Some(Identifier::new(info.pos_alias.clone())),
34376 };
34377 Expression::Unnest(Box::new(unnest))
34378 }
34379 DialectType::Presto | DialectType::Trino => {
34380 let unnest = UnnestFunc {
34381 this: info.arr_expr.clone(),
34382 expressions: Vec::new(),
34383 with_ordinality: true,
34384 alias: None,
34385 offset_alias: None,
34386 };
34387 Expression::Alias(Box::new(Alias {
34388 this: Expression::Unnest(Box::new(unnest)),
34389 alias: Identifier::new(info.source_alias.clone()),
34390 column_aliases: vec![
34391 Identifier::new(actual_col_name.clone()),
34392 Identifier::new(info.pos_alias.clone()),
34393 ],
34394 alias_explicit_as: false,
34395 alias_keyword: None,
34396 pre_alias_comments: Vec::new(),
34397 trailing_comments: Vec::new(),
34398 inferred_type: None,
34399 }))
34400 }
34401 DialectType::Snowflake => {
34402 let flatten_arg =
34403 Expression::NamedArgument(Box::new(crate::expressions::NamedArgument {
34404 name: Identifier::new("INPUT".to_string()),
34405 value: info.arr_expr.clone(),
34406 separator: crate::expressions::NamedArgSeparator::DArrow,
34407 }));
34408 let flatten = Expression::Function(Box::new(Function::new(
34409 "FLATTEN".to_string(),
34410 vec![flatten_arg],
34411 )));
34412 let table_fn = Expression::Function(Box::new(Function::new(
34413 "TABLE".to_string(),
34414 vec![flatten],
34415 )));
34416 Expression::Alias(Box::new(Alias {
34417 this: table_fn,
34418 alias: Identifier::new(info.source_alias.clone()),
34419 column_aliases: vec![
34420 Identifier::new("seq".to_string()),
34421 Identifier::new("key".to_string()),
34422 Identifier::new("path".to_string()),
34423 Identifier::new(info.pos_alias.clone()),
34424 Identifier::new(actual_col_name.clone()),
34425 Identifier::new("this".to_string()),
34426 ],
34427 alias_explicit_as: false,
34428 alias_keyword: None,
34429 pre_alias_comments: Vec::new(),
34430 trailing_comments: Vec::new(),
34431 inferred_type: None,
34432 }))
34433 }
34434 _ => return None,
34435 };
34436
34437 joins.push(make_join(unnest_join_expr));
34438 }
34439
34440 // Build WHERE clause
34441 let mut where_conditions: Vec<Expression> = Vec::new();
34442 for info in &unnest_infos {
34443 let src_ref = if use_table_aliases {
34444 Some(info.source_alias.as_str())
34445 } else {
34446 None
34447 };
34448 let pos_col = make_col(&series_alias, tbl_ref);
34449 let unnest_pos_col = make_col(&info.pos_alias, src_ref);
34450
34451 let arr_size = Expression::Function(Box::new(Function::new(
34452 array_length_func.to_string(),
34453 vec![info.arr_expr.clone()],
34454 )));
34455
34456 let size_ref = if index_offset == 0 {
34457 Expression::Paren(Box::new(crate::expressions::Paren {
34458 this: Expression::Sub(Box::new(BinaryOp::new(
34459 arr_size,
34460 Expression::Literal(Box::new(Literal::Number("1".to_string()))),
34461 ))),
34462 trailing_comments: Vec::new(),
34463 }))
34464 } else {
34465 arr_size
34466 };
34467
34468 let eq = Expression::Eq(Box::new(BinaryOp::new(
34469 pos_col.clone(),
34470 unnest_pos_col.clone(),
34471 )));
34472 let gt = Expression::Gt(Box::new(BinaryOp::new(pos_col, size_ref.clone())));
34473 let pos_eq_size = Expression::Eq(Box::new(BinaryOp::new(unnest_pos_col, size_ref)));
34474 let and_cond = Expression::And(Box::new(BinaryOp::new(gt, pos_eq_size)));
34475 let paren_and = Expression::Paren(Box::new(crate::expressions::Paren {
34476 this: and_cond,
34477 trailing_comments: Vec::new(),
34478 }));
34479 let or_cond = Expression::Or(Box::new(BinaryOp::new(eq, paren_and)));
34480
34481 where_conditions.push(or_cond);
34482 }
34483
34484 let where_expr = if where_conditions.len() == 1 {
34485 // Single condition: no parens needed
34486 where_conditions.into_iter().next().unwrap()
34487 } else {
34488 // Multiple conditions: wrap each OR in parens, then combine with AND
34489 let wrap = |e: Expression| {
34490 Expression::Paren(Box::new(crate::expressions::Paren {
34491 this: e,
34492 trailing_comments: Vec::new(),
34493 }))
34494 };
34495 let mut iter = where_conditions.into_iter();
34496 let first = wrap(iter.next().unwrap());
34497 let second = wrap(iter.next().unwrap());
34498 let mut combined = Expression::Paren(Box::new(crate::expressions::Paren {
34499 this: Expression::And(Box::new(BinaryOp::new(first, second))),
34500 trailing_comments: Vec::new(),
34501 }));
34502 for cond in iter {
34503 combined = Expression::And(Box::new(BinaryOp::new(combined, wrap(cond))));
34504 }
34505 combined
34506 };
34507
34508 // Build the new SELECT
34509 let mut new_select = select.clone();
34510 new_select.expressions = new_select_exprs;
34511
34512 if new_select.from.is_some() {
34513 let mut all_joins = vec![make_join(series_alias_expr)];
34514 all_joins.extend(joins);
34515 new_select.joins.extend(all_joins);
34516 } else {
34517 new_select.from = Some(From {
34518 expressions: vec![series_alias_expr],
34519 });
34520 new_select.joins.extend(joins);
34521 }
34522
34523 if let Some(ref existing_where) = new_select.where_clause {
34524 let combined = Expression::And(Box::new(BinaryOp::new(
34525 existing_where.this.clone(),
34526 where_expr,
34527 )));
34528 new_select.where_clause = Some(crate::expressions::Where { this: combined });
34529 } else {
34530 new_select.where_clause = Some(crate::expressions::Where { this: where_expr });
34531 }
34532
34533 Some(new_select)
34534 }
34535
34536 /// Helper to replace UNNEST(...) inside an expression with a replacement expression.
34537 fn replace_unnest_with_if(original: &Expression, replacement: &Expression) -> Expression {
34538 match original {
34539 Expression::Unnest(_) => replacement.clone(),
34540 Expression::Function(f) if f.name.eq_ignore_ascii_case("UNNEST") => replacement.clone(),
34541 Expression::Alias(a) => Self::replace_unnest_with_if(&a.this, replacement),
34542 Expression::Add(op) => {
34543 let left = Self::replace_unnest_with_if(&op.left, replacement);
34544 let right = Self::replace_unnest_with_if(&op.right, replacement);
34545 Expression::Add(Box::new(crate::expressions::BinaryOp::new(left, right)))
34546 }
34547 Expression::Sub(op) => {
34548 let left = Self::replace_unnest_with_if(&op.left, replacement);
34549 let right = Self::replace_unnest_with_if(&op.right, replacement);
34550 Expression::Sub(Box::new(crate::expressions::BinaryOp::new(left, right)))
34551 }
34552 Expression::Mul(op) => {
34553 let left = Self::replace_unnest_with_if(&op.left, replacement);
34554 let right = Self::replace_unnest_with_if(&op.right, replacement);
34555 Expression::Mul(Box::new(crate::expressions::BinaryOp::new(left, right)))
34556 }
34557 Expression::Div(op) => {
34558 let left = Self::replace_unnest_with_if(&op.left, replacement);
34559 let right = Self::replace_unnest_with_if(&op.right, replacement);
34560 Expression::Div(Box::new(crate::expressions::BinaryOp::new(left, right)))
34561 }
34562 _ => original.clone(),
34563 }
34564 }
34565
34566 /// Decompose a JSON path like `$.y[0].z` into individual parts: `["y", "0", "z"]`.
34567 /// Strips `$` prefix, handles bracket notation, quoted strings, and removes `[*]` wildcards.
34568 fn decompose_json_path(path: &str) -> Vec<String> {
34569 let mut parts = Vec::new();
34570 let path = if path.starts_with("$.") {
34571 &path[2..]
34572 } else if path.starts_with('$') {
34573 &path[1..]
34574 } else {
34575 path
34576 };
34577 if path.is_empty() {
34578 return parts;
34579 }
34580 let mut current = String::new();
34581 let chars: Vec<char> = path.chars().collect();
34582 let mut i = 0;
34583 while i < chars.len() {
34584 match chars[i] {
34585 '.' => {
34586 if !current.is_empty() {
34587 parts.push(current.clone());
34588 current.clear();
34589 }
34590 i += 1;
34591 }
34592 '[' => {
34593 if !current.is_empty() {
34594 parts.push(current.clone());
34595 current.clear();
34596 }
34597 i += 1;
34598 let mut bracket_content = String::new();
34599 while i < chars.len() && chars[i] != ']' {
34600 if chars[i] == '"' || chars[i] == '\'' {
34601 let quote = chars[i];
34602 i += 1;
34603 while i < chars.len() && chars[i] != quote {
34604 bracket_content.push(chars[i]);
34605 i += 1;
34606 }
34607 if i < chars.len() {
34608 i += 1;
34609 }
34610 } else {
34611 bracket_content.push(chars[i]);
34612 i += 1;
34613 }
34614 }
34615 if i < chars.len() {
34616 i += 1;
34617 }
34618 if bracket_content != "*" {
34619 parts.push(bracket_content);
34620 }
34621 }
34622 _ => {
34623 current.push(chars[i]);
34624 i += 1;
34625 }
34626 }
34627 }
34628 if !current.is_empty() {
34629 parts.push(current);
34630 }
34631 parts
34632 }
34633
34634 /// Normalize a JSON path operand for T-SQL/Fabric JSON_QUERY/JSON_VALUE.
34635 ///
34636 /// PostgreSQL arrow operators accept bare keys (`json -> 'name'`) and numeric
34637 /// array indexes (`json -> 0`), while T-SQL JSON paths must be rooted at `$`.
34638 /// PostgreSQL #>/#>> path arrays (`'{a,0}'`) are also converted here.
34639 fn normalize_tsql_json_path_expr(
34640 path: Expression,
34641 postgres_path_array_literal: bool,
34642 string_numbers_are_indexes: bool,
34643 ) -> Expression {
34644 match path {
34645 Expression::Literal(lit) => match lit.as_ref() {
34646 Literal::String(s) => {
34647 let normalized = if postgres_path_array_literal {
34648 if let Some(parts) = Self::parse_postgres_json_path_array_literal(s) {
34649 Self::tsql_json_path_from_segments(&parts, true)
34650 } else {
34651 Self::normalize_tsql_json_path_string(s, string_numbers_are_indexes)
34652 }
34653 } else {
34654 Self::normalize_tsql_json_path_string(s, string_numbers_are_indexes)
34655 };
34656 Expression::string(normalized)
34657 }
34658 Literal::Number(n) => Expression::string(format!("$[{n}]")),
34659 _ => Expression::Literal(lit),
34660 },
34661 other => other,
34662 }
34663 }
34664
34665 fn normalize_tsql_json_path_parts(paths: Vec<Expression>) -> Expression {
34666 if paths.len() == 1 {
34667 return Self::normalize_tsql_json_path_expr(
34668 paths.into_iter().next().expect("checked len"),
34669 true,
34670 true,
34671 );
34672 }
34673
34674 let mut segments = Vec::new();
34675 for path in paths {
34676 match path {
34677 Expression::Literal(lit) => match lit.as_ref() {
34678 Literal::String(s) => {
34679 if let Some(parts) = Self::parse_postgres_json_path_array_literal(s) {
34680 segments.extend(parts);
34681 } else {
34682 segments.push(s.clone());
34683 }
34684 }
34685 Literal::Number(n) => segments.push(n.clone()),
34686 _ => return Expression::Literal(lit),
34687 },
34688 other => return other,
34689 }
34690 }
34691
34692 Expression::string(Self::tsql_json_path_from_segments(&segments, true))
34693 }
34694
34695 fn normalize_tsql_json_path_string(path: &str, string_numbers_are_indexes: bool) -> String {
34696 let trimmed = path.trim();
34697 let lower = trimmed.to_ascii_lowercase();
34698
34699 if trimmed.starts_with('$') || lower.starts_with("lax $") || lower.starts_with("strict $") {
34700 return Self::bracket_to_dot_notation(trimmed);
34701 }
34702
34703 if trimmed.starts_with('[') {
34704 return format!("${}", Self::bracket_to_dot_notation(trimmed));
34705 }
34706
34707 if string_numbers_are_indexes && Self::is_json_array_index(trimmed) {
34708 return format!("$[{trimmed}]");
34709 }
34710
34711 let mut result = "$".to_string();
34712 Self::push_tsql_json_path_segment(&mut result, trimmed, false);
34713 result
34714 }
34715
34716 fn parse_postgres_json_path_array_literal(path: &str) -> Option<Vec<String>> {
34717 let trimmed = path.trim();
34718 if !(trimmed.starts_with('{') && trimmed.ends_with('}')) {
34719 return None;
34720 }
34721
34722 let inner = &trimmed[1..trimmed.len().saturating_sub(1)];
34723 let mut parts = Vec::new();
34724 let mut current = String::new();
34725 let mut chars = inner.chars().peekable();
34726 let mut in_quotes = false;
34727 let mut escaped = false;
34728
34729 while let Some(ch) = chars.next() {
34730 if escaped {
34731 current.push(ch);
34732 escaped = false;
34733 continue;
34734 }
34735
34736 if in_quotes {
34737 match ch {
34738 '\\' => escaped = true,
34739 '"' => {
34740 if matches!(chars.peek(), Some('"')) {
34741 current.push('"');
34742 chars.next();
34743 } else {
34744 in_quotes = false;
34745 }
34746 }
34747 _ => current.push(ch),
34748 }
34749 continue;
34750 }
34751
34752 match ch {
34753 '"' => in_quotes = true,
34754 ',' => {
34755 parts.push(current.trim().to_string());
34756 current.clear();
34757 }
34758 _ => current.push(ch),
34759 }
34760 }
34761
34762 parts.push(current.trim().to_string());
34763 Some(parts)
34764 }
34765
34766 fn tsql_json_path_from_segments(
34767 segments: &[String],
34768 numeric_strings_are_indexes: bool,
34769 ) -> String {
34770 let mut path = "$".to_string();
34771 for segment in segments {
34772 Self::push_tsql_json_path_segment(&mut path, segment, numeric_strings_are_indexes);
34773 }
34774 path
34775 }
34776
34777 fn push_tsql_json_path_segment(
34778 path: &mut String,
34779 segment: &str,
34780 numeric_string_is_index: bool,
34781 ) {
34782 if numeric_string_is_index && Self::is_json_array_index(segment) {
34783 path.push('[');
34784 path.push_str(segment);
34785 path.push(']');
34786 return;
34787 }
34788
34789 if Self::is_simple_json_path_key(segment) {
34790 path.push('.');
34791 path.push_str(segment);
34792 } else {
34793 path.push_str(".\"");
34794 path.push_str(&segment.replace('\\', "\\\\").replace('"', "\\\""));
34795 path.push('"');
34796 }
34797 }
34798
34799 fn is_json_array_index(segment: &str) -> bool {
34800 !segment.is_empty() && segment.chars().all(|c| c.is_ascii_digit())
34801 }
34802
34803 fn is_simple_json_path_key(segment: &str) -> bool {
34804 !segment.is_empty()
34805 && !segment.starts_with('$')
34806 && segment
34807 .chars()
34808 .all(|c| c.is_ascii_alphanumeric() || c == '_')
34809 }
34810
34811 fn build_tsql_json_function(name: &str, this: Expression, path: Expression) -> Expression {
34812 let (this, path) = Self::collapse_nested_tsql_json_query(this, path);
34813 Expression::Function(Box::new(crate::expressions::Function::new(
34814 name.to_string(),
34815 vec![this, path],
34816 )))
34817 }
34818
34819 fn collapse_nested_tsql_json_query(
34820 this: Expression,
34821 path: Expression,
34822 ) -> (Expression, Expression) {
34823 if let Expression::Function(f) = &this {
34824 if f.name.eq_ignore_ascii_case("JSON_QUERY") && f.args.len() == 2 {
34825 if let (Some(prefix), Some(suffix)) = (
34826 Self::literal_string_value(&f.args[1]),
34827 Self::literal_string_value(&path),
34828 ) {
34829 if let Some(combined) = Self::join_tsql_json_paths(prefix, suffix) {
34830 return (f.args[0].clone(), Expression::string(combined));
34831 }
34832 }
34833 }
34834 }
34835
34836 (this, path)
34837 }
34838
34839 fn literal_string_value(expr: &Expression) -> Option<&str> {
34840 match expr {
34841 Expression::Literal(lit) => match lit.as_ref() {
34842 Literal::String(s) => Some(s.as_str()),
34843 _ => None,
34844 },
34845 _ => None,
34846 }
34847 }
34848
34849 fn join_tsql_json_paths(prefix: &str, suffix: &str) -> Option<String> {
34850 if prefix == "$" {
34851 return Some(suffix.to_string());
34852 }
34853 if suffix == "$" {
34854 return Some(prefix.to_string());
34855 }
34856
34857 if let Some(rest) = suffix.strip_prefix("$.") {
34858 Some(format!("{prefix}.{rest}"))
34859 } else {
34860 suffix
34861 .strip_prefix("$[")
34862 .map(|rest| format!("{prefix}[{rest}"))
34863 }
34864 }
34865
34866 /// Strip `$` prefix from a JSON path, keeping the rest.
34867 /// `$.y[0].z` -> `y[0].z`, `$["a b"]` -> `["a b"]`
34868 fn strip_json_dollar_prefix(path: &str) -> String {
34869 if path.starts_with("$.") {
34870 path[2..].to_string()
34871 } else if path.starts_with('$') {
34872 path[1..].to_string()
34873 } else {
34874 path.to_string()
34875 }
34876 }
34877
34878 /// Strip `[*]` wildcards from a JSON path.
34879 /// `$.y[*]` -> `$.y`, `$.y[*].z` -> `$.y.z`
34880 fn strip_json_wildcards(path: &str) -> String {
34881 path.replace("[*]", "")
34882 .replace("..", ".") // Clean double dots from `$.y[*].z` -> `$.y..z`
34883 .trim_end_matches('.')
34884 .to_string()
34885 }
34886
34887 /// Convert bracket notation to dot notation for JSON paths.
34888 /// `$["a b"]` -> `$."a b"`, `$["key"]` -> `$.key`
34889 fn bracket_to_dot_notation(path: &str) -> String {
34890 let mut result = String::new();
34891 let chars: Vec<char> = path.chars().collect();
34892 let mut i = 0;
34893 while i < chars.len() {
34894 if chars[i] == '[' {
34895 // Read bracket content
34896 i += 1;
34897 let mut bracket_content = String::new();
34898 let mut is_quoted = false;
34899 let mut _quote_char = '"';
34900 while i < chars.len() && chars[i] != ']' {
34901 if chars[i] == '"' || chars[i] == '\'' {
34902 is_quoted = true;
34903 _quote_char = chars[i];
34904 i += 1;
34905 while i < chars.len() && chars[i] != _quote_char {
34906 bracket_content.push(chars[i]);
34907 i += 1;
34908 }
34909 if i < chars.len() {
34910 i += 1;
34911 }
34912 } else {
34913 bracket_content.push(chars[i]);
34914 i += 1;
34915 }
34916 }
34917 if i < chars.len() {
34918 i += 1;
34919 } // skip ]
34920 if bracket_content == "*" {
34921 // Keep wildcard as-is
34922 result.push_str("[*]");
34923 } else if is_quoted {
34924 // Quoted bracket -> dot notation with quotes
34925 result.push('.');
34926 result.push('"');
34927 result.push_str(&bracket_content);
34928 result.push('"');
34929 } else {
34930 // Numeric index -> keep as bracket
34931 result.push('[');
34932 result.push_str(&bracket_content);
34933 result.push(']');
34934 }
34935 } else {
34936 result.push(chars[i]);
34937 i += 1;
34938 }
34939 }
34940 result
34941 }
34942
34943 /// Convert JSON path bracket quoted strings to use single quotes instead of double quotes.
34944 /// `$["a b"]` -> `$['a b']`
34945 fn bracket_to_single_quotes(path: &str) -> String {
34946 let mut result = String::new();
34947 let chars: Vec<char> = path.chars().collect();
34948 let mut i = 0;
34949 while i < chars.len() {
34950 if chars[i] == '[' && i + 1 < chars.len() && chars[i + 1] == '"' {
34951 result.push('[');
34952 result.push('\'');
34953 i += 2; // skip [ and "
34954 while i < chars.len() && chars[i] != '"' {
34955 result.push(chars[i]);
34956 i += 1;
34957 }
34958 if i < chars.len() {
34959 i += 1;
34960 } // skip closing "
34961 result.push('\'');
34962 } else {
34963 result.push(chars[i]);
34964 i += 1;
34965 }
34966 }
34967 result
34968 }
34969
34970 /// Transform TSQL SELECT INTO -> CREATE TABLE AS for DuckDB/Snowflake
34971 /// or PostgreSQL #temp -> TEMPORARY.
34972 /// Also strips # from INSERT INTO #table for non-TSQL targets.
34973 fn transform_select_into(
34974 expr: Expression,
34975 _source: DialectType,
34976 target: DialectType,
34977 ) -> Expression {
34978 use crate::expressions::{CreateTable, Expression, TableRef};
34979
34980 // Handle INSERT INTO #temp -> INSERT INTO temp for non-TSQL targets
34981 if let Expression::Insert(ref insert) = expr {
34982 if insert.table.name.name.starts_with('#')
34983 && !matches!(target, DialectType::TSQL | DialectType::Fabric)
34984 {
34985 let mut new_insert = insert.clone();
34986 new_insert.table.name.name =
34987 insert.table.name.name.trim_start_matches('#').to_string();
34988 return Expression::Insert(new_insert);
34989 }
34990 return expr;
34991 }
34992
34993 if let Expression::Select(ref select) = expr {
34994 if let Some(ref into) = select.into {
34995 let table_name_raw = match &into.this {
34996 Expression::Table(tr) => tr.name.name.clone(),
34997 Expression::Identifier(id) => id.name.clone(),
34998 _ => String::new(),
34999 };
35000 let is_temp = table_name_raw.starts_with('#') || into.temporary;
35001 let clean_name = table_name_raw.trim_start_matches('#').to_string();
35002
35003 match target {
35004 DialectType::DuckDB | DialectType::Snowflake => {
35005 // SELECT INTO -> CREATE TABLE AS SELECT
35006 let mut new_select = select.clone();
35007 new_select.into = None;
35008 let ct = CreateTable {
35009 name: TableRef::new(clean_name),
35010 on_cluster: None,
35011 columns: Vec::new(),
35012 constraints: Vec::new(),
35013 if_not_exists: false,
35014 temporary: is_temp,
35015 or_replace: false,
35016 table_modifier: None,
35017 as_select: Some(Expression::Select(new_select)),
35018 as_select_parenthesized: false,
35019 on_commit: None,
35020 clone_source: None,
35021 clone_at_clause: None,
35022 shallow_clone: false,
35023 deep_clone: false,
35024 is_copy: false,
35025 leading_comments: Vec::new(),
35026 with_properties: Vec::new(),
35027 teradata_post_name_options: Vec::new(),
35028 with_data: None,
35029 with_statistics: None,
35030 teradata_indexes: Vec::new(),
35031 with_cte: None,
35032 properties: Vec::new(),
35033 partition_of: None,
35034 post_table_properties: Vec::new(),
35035 mysql_table_options: Vec::new(),
35036 inherits: Vec::new(),
35037 on_property: None,
35038 copy_grants: false,
35039 using_template: None,
35040 rollup: None,
35041 uuid: None,
35042 with_partition_columns: Vec::new(),
35043 with_connection: None,
35044 };
35045 return Expression::CreateTable(Box::new(ct));
35046 }
35047 DialectType::PostgreSQL | DialectType::Redshift => {
35048 // PostgreSQL: #foo -> INTO TEMPORARY foo
35049 if is_temp && !into.temporary {
35050 let mut new_select = select.clone();
35051 let mut new_into = into.clone();
35052 new_into.temporary = true;
35053 new_into.unlogged = false;
35054 new_into.this = Expression::Table(Box::new(TableRef::new(clean_name)));
35055 new_select.into = Some(new_into);
35056 Expression::Select(new_select)
35057 } else {
35058 expr
35059 }
35060 }
35061 _ => expr,
35062 }
35063 } else {
35064 expr
35065 }
35066 } else {
35067 expr
35068 }
35069 }
35070
35071 /// Transform CREATE TABLE WITH properties for cross-dialect transpilation.
35072 /// Handles FORMAT, PARTITIONED_BY, and other Presto WITH properties.
35073 fn transform_create_table_properties(
35074 ct: &mut crate::expressions::CreateTable,
35075 _source: DialectType,
35076 target: DialectType,
35077 ) {
35078 use crate::expressions::{
35079 BinaryOp, BooleanLiteral, Expression, FileFormatProperty, Identifier, Literal,
35080 Properties,
35081 };
35082
35083 // Helper to convert a raw property value string to the correct Expression
35084 let value_to_expr = |v: &str| -> Expression {
35085 let trimmed = v.trim();
35086 // Check if it's a quoted string (starts and ends with ')
35087 if trimmed.starts_with('\'') && trimmed.ends_with('\'') {
35088 Expression::Literal(Box::new(Literal::String(
35089 trimmed[1..trimmed.len() - 1].to_string(),
35090 )))
35091 }
35092 // Check if it's a number
35093 else if trimmed.parse::<i64>().is_ok() || trimmed.parse::<f64>().is_ok() {
35094 Expression::Literal(Box::new(Literal::Number(trimmed.to_string())))
35095 }
35096 // Check if it's ARRAY[...] or ARRAY(...)
35097 else if trimmed.len() >= 5 && trimmed[..5].eq_ignore_ascii_case("ARRAY") {
35098 // Convert ARRAY['y'] to ARRAY('y') for Hive/Spark
35099 let inner = trimmed
35100 .trim_start_matches(|c: char| c.is_alphabetic()) // Remove ARRAY
35101 .trim_start_matches('[')
35102 .trim_start_matches('(')
35103 .trim_end_matches(']')
35104 .trim_end_matches(')');
35105 let elements: Vec<Expression> = inner
35106 .split(',')
35107 .map(|e| {
35108 let elem = e.trim().trim_matches('\'');
35109 Expression::Literal(Box::new(Literal::String(elem.to_string())))
35110 })
35111 .collect();
35112 Expression::Function(Box::new(crate::expressions::Function::new(
35113 "ARRAY".to_string(),
35114 elements,
35115 )))
35116 }
35117 // Otherwise, just output as identifier (unquoted)
35118 else {
35119 Expression::Identifier(Identifier::new(trimmed.to_string()))
35120 }
35121 };
35122
35123 if ct.with_properties.is_empty() && ct.properties.is_empty() {
35124 return;
35125 }
35126
35127 // Handle Presto-style WITH properties
35128 if !ct.with_properties.is_empty() {
35129 // Extract FORMAT property and remaining properties
35130 let mut format_value: Option<String> = None;
35131 let mut partitioned_by: Option<String> = None;
35132 let mut other_props: Vec<(String, String)> = Vec::new();
35133
35134 for (key, value) in ct.with_properties.drain(..) {
35135 if key.eq_ignore_ascii_case("FORMAT") {
35136 // Strip surrounding quotes from value if present
35137 format_value = Some(value.trim_matches('\'').to_string());
35138 } else if key.eq_ignore_ascii_case("PARTITIONED_BY") {
35139 partitioned_by = Some(value);
35140 } else {
35141 other_props.push((key, value));
35142 }
35143 }
35144
35145 match target {
35146 DialectType::Presto | DialectType::Trino | DialectType::Athena => {
35147 // Presto: keep WITH properties but lowercase 'format' key
35148 if let Some(fmt) = format_value {
35149 ct.with_properties
35150 .push(("format".to_string(), format!("'{}'", fmt)));
35151 }
35152 if let Some(part) = partitioned_by {
35153 // Convert (col1, col2) to ARRAY['col1', 'col2'] format
35154 let trimmed = part.trim();
35155 let inner = trimmed.trim_start_matches('(').trim_end_matches(')');
35156 // Also handle ARRAY['...'] format - keep as-is
35157 if trimmed.len() >= 5 && trimmed[..5].eq_ignore_ascii_case("ARRAY") {
35158 ct.with_properties
35159 .push(("PARTITIONED_BY".to_string(), part));
35160 } else {
35161 // Parse column names from the parenthesized list
35162 let cols: Vec<&str> = inner
35163 .split(',')
35164 .map(|c| c.trim().trim_matches('"').trim_matches('\''))
35165 .collect();
35166 let array_val = format!(
35167 "ARRAY[{}]",
35168 cols.iter()
35169 .map(|c| format!("'{}'", c))
35170 .collect::<Vec<_>>()
35171 .join(", ")
35172 );
35173 ct.with_properties
35174 .push(("PARTITIONED_BY".to_string(), array_val));
35175 }
35176 }
35177 ct.with_properties.extend(other_props);
35178 }
35179 DialectType::Hive => {
35180 // Hive: FORMAT -> STORED AS, other props -> TBLPROPERTIES
35181 if let Some(fmt) = format_value {
35182 ct.properties.push(Expression::FileFormatProperty(Box::new(
35183 FileFormatProperty {
35184 this: Some(Box::new(Expression::Identifier(Identifier::new(fmt)))),
35185 expressions: vec![],
35186 hive_format: Some(Box::new(Expression::Boolean(BooleanLiteral {
35187 value: true,
35188 }))),
35189 },
35190 )));
35191 }
35192 if let Some(_part) = partitioned_by {
35193 // PARTITIONED_BY handling is complex - move columns to partitioned by
35194 // For now, the partition columns are extracted from the column list
35195 Self::apply_partitioned_by(ct, &_part, target);
35196 }
35197 if !other_props.is_empty() {
35198 let eq_exprs: Vec<Expression> = other_props
35199 .into_iter()
35200 .map(|(k, v)| {
35201 Expression::Eq(Box::new(BinaryOp::new(
35202 Expression::Literal(Box::new(Literal::String(k))),
35203 value_to_expr(&v),
35204 )))
35205 })
35206 .collect();
35207 ct.properties
35208 .push(Expression::Properties(Box::new(Properties {
35209 expressions: eq_exprs,
35210 })));
35211 }
35212 }
35213 DialectType::Spark | DialectType::Databricks => {
35214 // Spark: FORMAT -> USING, other props -> TBLPROPERTIES
35215 if let Some(fmt) = format_value {
35216 ct.properties.push(Expression::FileFormatProperty(Box::new(
35217 FileFormatProperty {
35218 this: Some(Box::new(Expression::Identifier(Identifier::new(fmt)))),
35219 expressions: vec![],
35220 hive_format: None, // None means USING syntax
35221 },
35222 )));
35223 }
35224 if let Some(_part) = partitioned_by {
35225 Self::apply_partitioned_by(ct, &_part, target);
35226 }
35227 if !other_props.is_empty() {
35228 let eq_exprs: Vec<Expression> = other_props
35229 .into_iter()
35230 .map(|(k, v)| {
35231 Expression::Eq(Box::new(BinaryOp::new(
35232 Expression::Literal(Box::new(Literal::String(k))),
35233 value_to_expr(&v),
35234 )))
35235 })
35236 .collect();
35237 ct.properties
35238 .push(Expression::Properties(Box::new(Properties {
35239 expressions: eq_exprs,
35240 })));
35241 }
35242 }
35243 DialectType::DuckDB => {
35244 // DuckDB: strip all WITH properties (FORMAT, PARTITIONED_BY, etc.)
35245 // Keep nothing
35246 }
35247 _ => {
35248 // For other dialects, keep WITH properties as-is
35249 if let Some(fmt) = format_value {
35250 ct.with_properties
35251 .push(("FORMAT".to_string(), format!("'{}'", fmt)));
35252 }
35253 if let Some(part) = partitioned_by {
35254 ct.with_properties
35255 .push(("PARTITIONED_BY".to_string(), part));
35256 }
35257 ct.with_properties.extend(other_props);
35258 }
35259 }
35260 }
35261
35262 // Handle STORED AS 'PARQUET' (quoted format name) -> STORED AS PARQUET (unquoted)
35263 // and Hive STORED AS -> Presto WITH (format=...) conversion
35264 if !ct.properties.is_empty() {
35265 let is_presto_target = matches!(
35266 target,
35267 DialectType::Presto | DialectType::Trino | DialectType::Athena
35268 );
35269 let is_duckdb_target = matches!(target, DialectType::DuckDB);
35270
35271 if is_presto_target || is_duckdb_target {
35272 let mut new_properties = Vec::new();
35273 for prop in ct.properties.drain(..) {
35274 match &prop {
35275 Expression::FileFormatProperty(ffp) => {
35276 if is_presto_target {
35277 // Convert STORED AS/USING to WITH (format=...)
35278 if let Some(ref fmt_expr) = ffp.this {
35279 let fmt_str = match fmt_expr.as_ref() {
35280 Expression::Identifier(id) => id.name.clone(),
35281 Expression::Literal(lit)
35282 if matches!(lit.as_ref(), Literal::String(_)) =>
35283 {
35284 let Literal::String(s) = lit.as_ref() else {
35285 unreachable!()
35286 };
35287 s.clone()
35288 }
35289 _ => {
35290 new_properties.push(prop);
35291 continue;
35292 }
35293 };
35294 ct.with_properties
35295 .push(("format".to_string(), format!("'{}'", fmt_str)));
35296 }
35297 }
35298 // DuckDB: just strip file format properties
35299 }
35300 // Convert TBLPROPERTIES to WITH properties for Presto target
35301 Expression::Properties(props) if is_presto_target => {
35302 for expr in &props.expressions {
35303 if let Expression::Eq(eq) = expr {
35304 // Extract key and value from the Eq expression
35305 let key = match &eq.left {
35306 Expression::Literal(lit)
35307 if matches!(lit.as_ref(), Literal::String(_)) =>
35308 {
35309 let Literal::String(s) = lit.as_ref() else {
35310 unreachable!()
35311 };
35312 s.clone()
35313 }
35314 Expression::Identifier(id) => id.name.clone(),
35315 _ => continue,
35316 };
35317 let value = match &eq.right {
35318 Expression::Literal(lit)
35319 if matches!(lit.as_ref(), Literal::String(_)) =>
35320 {
35321 let Literal::String(s) = lit.as_ref() else {
35322 unreachable!()
35323 };
35324 format!("'{}'", s)
35325 }
35326 Expression::Literal(lit)
35327 if matches!(lit.as_ref(), Literal::Number(_)) =>
35328 {
35329 let Literal::Number(n) = lit.as_ref() else {
35330 unreachable!()
35331 };
35332 n.clone()
35333 }
35334 Expression::Identifier(id) => id.name.clone(),
35335 _ => continue,
35336 };
35337 ct.with_properties.push((key, value));
35338 }
35339 }
35340 }
35341 // Convert PartitionedByProperty for Presto target
35342 Expression::PartitionedByProperty(ref pbp) if is_presto_target => {
35343 // Check if it contains ColumnDef expressions (Hive-style with types)
35344 if let Expression::Tuple(ref tuple) = *pbp.this {
35345 let mut col_names: Vec<String> = Vec::new();
35346 let mut col_defs: Vec<crate::expressions::ColumnDef> = Vec::new();
35347 let mut has_col_defs = false;
35348 for expr in &tuple.expressions {
35349 if let Expression::ColumnDef(ref cd) = expr {
35350 has_col_defs = true;
35351 col_names.push(cd.name.name.clone());
35352 col_defs.push(*cd.clone());
35353 } else if let Expression::Column(ref col) = expr {
35354 col_names.push(col.name.name.clone());
35355 } else if let Expression::Identifier(ref id) = expr {
35356 col_names.push(id.name.clone());
35357 } else {
35358 // For function expressions like MONTHS(y), serialize to SQL
35359 let generic = Dialect::get(DialectType::Generic);
35360 if let Ok(sql) = generic.generate(expr) {
35361 col_names.push(sql);
35362 }
35363 }
35364 }
35365 if has_col_defs {
35366 // Merge partition column defs into the main column list
35367 for cd in col_defs {
35368 ct.columns.push(cd);
35369 }
35370 }
35371 if !col_names.is_empty() {
35372 // Add PARTITIONED_BY property
35373 let array_val = format!(
35374 "ARRAY[{}]",
35375 col_names
35376 .iter()
35377 .map(|n| format!("'{}'", n))
35378 .collect::<Vec<_>>()
35379 .join(", ")
35380 );
35381 ct.with_properties
35382 .push(("PARTITIONED_BY".to_string(), array_val));
35383 }
35384 }
35385 // Skip - don't keep in properties
35386 }
35387 _ => {
35388 if !is_duckdb_target {
35389 new_properties.push(prop);
35390 }
35391 }
35392 }
35393 }
35394 ct.properties = new_properties;
35395 } else {
35396 // For Hive/Spark targets, unquote format names in STORED AS
35397 for prop in &mut ct.properties {
35398 if let Expression::FileFormatProperty(ref mut ffp) = prop {
35399 if let Some(ref mut fmt_expr) = ffp.this {
35400 if let Expression::Literal(lit) = fmt_expr.as_ref() {
35401 if let Literal::String(s) = lit.as_ref() {
35402 // Convert STORED AS 'PARQUET' to STORED AS PARQUET (unquote)
35403 let unquoted = s.clone();
35404 *fmt_expr =
35405 Box::new(Expression::Identifier(Identifier::new(unquoted)));
35406 }
35407 }
35408 }
35409 }
35410 }
35411 }
35412 }
35413 }
35414
35415 /// Apply PARTITIONED_BY conversion: move partition columns from column list to PARTITIONED BY
35416 fn apply_partitioned_by(
35417 ct: &mut crate::expressions::CreateTable,
35418 partitioned_by_value: &str,
35419 target: DialectType,
35420 ) {
35421 use crate::expressions::{Column, Expression, Identifier, PartitionedByProperty, Tuple};
35422
35423 // Parse the ARRAY['col1', 'col2'] value to extract column names
35424 let mut col_names: Vec<String> = Vec::new();
35425 // The value looks like ARRAY['y', 'z'] or ARRAY('y', 'z')
35426 let inner = partitioned_by_value
35427 .trim()
35428 .trim_start_matches("ARRAY")
35429 .trim_start_matches('[')
35430 .trim_start_matches('(')
35431 .trim_end_matches(']')
35432 .trim_end_matches(')');
35433 for part in inner.split(',') {
35434 let col = part.trim().trim_matches('\'').trim_matches('"');
35435 if !col.is_empty() {
35436 col_names.push(col.to_string());
35437 }
35438 }
35439
35440 if col_names.is_empty() {
35441 return;
35442 }
35443
35444 if matches!(target, DialectType::Hive) {
35445 // Hive: PARTITIONED BY (col_name type, ...) - move columns out of column list
35446 let mut partition_col_defs = Vec::new();
35447 for col_name in &col_names {
35448 // Find and remove from columns
35449 if let Some(pos) = ct
35450 .columns
35451 .iter()
35452 .position(|c| c.name.name.eq_ignore_ascii_case(col_name))
35453 {
35454 let col_def = ct.columns.remove(pos);
35455 partition_col_defs.push(Expression::ColumnDef(Box::new(col_def)));
35456 }
35457 }
35458 if !partition_col_defs.is_empty() {
35459 ct.properties
35460 .push(Expression::PartitionedByProperty(Box::new(
35461 PartitionedByProperty {
35462 this: Box::new(Expression::Tuple(Box::new(Tuple {
35463 expressions: partition_col_defs,
35464 }))),
35465 },
35466 )));
35467 }
35468 } else if matches!(target, DialectType::Spark | DialectType::Databricks) {
35469 // Spark: PARTITIONED BY (col1, col2) - just column names, keep in column list
35470 // Use quoted identifiers to match the quoting style of the original column definitions
35471 let partition_exprs: Vec<Expression> = col_names
35472 .iter()
35473 .map(|name| {
35474 // Check if the column exists in the column list and use its quoting
35475 let is_quoted = ct
35476 .columns
35477 .iter()
35478 .any(|c| c.name.name.eq_ignore_ascii_case(name) && c.name.quoted);
35479 let ident = if is_quoted {
35480 Identifier::quoted(name.clone())
35481 } else {
35482 Identifier::new(name.clone())
35483 };
35484 Expression::boxed_column(Column {
35485 name: ident,
35486 table: None,
35487 join_mark: false,
35488 trailing_comments: Vec::new(),
35489 span: None,
35490 inferred_type: None,
35491 })
35492 })
35493 .collect();
35494 ct.properties
35495 .push(Expression::PartitionedByProperty(Box::new(
35496 PartitionedByProperty {
35497 this: Box::new(Expression::Tuple(Box::new(Tuple {
35498 expressions: partition_exprs,
35499 }))),
35500 },
35501 )));
35502 }
35503 // DuckDB: strip partitioned_by entirely (already handled)
35504 }
35505
35506 /// Convert a DataType to Spark's type string format (using angle brackets)
35507 fn data_type_to_spark_string(dt: &crate::expressions::DataType) -> String {
35508 use crate::expressions::DataType;
35509 match dt {
35510 DataType::Int { .. } => "INT".to_string(),
35511 DataType::BigInt { .. } => "BIGINT".to_string(),
35512 DataType::SmallInt { .. } => "SMALLINT".to_string(),
35513 DataType::TinyInt { .. } => "TINYINT".to_string(),
35514 DataType::Float { .. } => "FLOAT".to_string(),
35515 DataType::Double { .. } => "DOUBLE".to_string(),
35516 DataType::Decimal {
35517 precision: Some(p),
35518 scale: Some(s),
35519 } => format!("DECIMAL({}, {})", p, s),
35520 DataType::Decimal {
35521 precision: Some(p), ..
35522 } => format!("DECIMAL({})", p),
35523 DataType::Decimal { .. } => "DECIMAL".to_string(),
35524 DataType::VarChar { .. } | DataType::Text | DataType::String { .. } => {
35525 "STRING".to_string()
35526 }
35527 DataType::Char { .. } => "STRING".to_string(),
35528 DataType::Boolean => "BOOLEAN".to_string(),
35529 DataType::Date => "DATE".to_string(),
35530 DataType::Timestamp { .. } => "TIMESTAMP".to_string(),
35531 DataType::Json | DataType::JsonB => "STRING".to_string(),
35532 DataType::Binary { .. } => "BINARY".to_string(),
35533 DataType::Array { element_type, .. } => {
35534 format!("ARRAY<{}>", Self::data_type_to_spark_string(element_type))
35535 }
35536 DataType::Map {
35537 key_type,
35538 value_type,
35539 } => format!(
35540 "MAP<{}, {}>",
35541 Self::data_type_to_spark_string(key_type),
35542 Self::data_type_to_spark_string(value_type)
35543 ),
35544 DataType::Struct { fields, .. } => {
35545 let field_strs: Vec<String> = fields
35546 .iter()
35547 .map(|f| {
35548 if f.name.is_empty() {
35549 Self::data_type_to_spark_string(&f.data_type)
35550 } else {
35551 format!(
35552 "{}: {}",
35553 f.name,
35554 Self::data_type_to_spark_string(&f.data_type)
35555 )
35556 }
35557 })
35558 .collect();
35559 format!("STRUCT<{}>", field_strs.join(", "))
35560 }
35561 DataType::Custom { name } => name.clone(),
35562 _ => format!("{:?}", dt),
35563 }
35564 }
35565
35566 fn cast_expr(this: Expression, to: DataType) -> Expression {
35567 Expression::Cast(Box::new(Cast {
35568 this,
35569 to,
35570 trailing_comments: Vec::new(),
35571 double_colon_syntax: false,
35572 format: None,
35573 default: None,
35574 inferred_type: None,
35575 }))
35576 }
35577
35578 fn lower_expr(this: Expression) -> Expression {
35579 Expression::Function(Box::new(Function::new("LOWER".to_string(), vec![this])))
35580 }
35581
35582 fn build_tsql_regex_patindex_predicate(
35583 this: Expression,
35584 pattern: Expression,
35585 case_insensitive: bool,
35586 ) -> Expression {
35587 let (this, pattern) = if case_insensitive {
35588 (Self::lower_expr(this), Self::lower_expr(pattern))
35589 } else {
35590 (this, pattern)
35591 };
35592 let patindex = Expression::Function(Box::new(Function::new(
35593 "PATINDEX".to_string(),
35594 vec![pattern, this],
35595 )));
35596
35597 Expression::Gt(Box::new(BinaryOp::new(patindex, Expression::number(0))))
35598 }
35599
35600 fn similar_to_can_lower_to_tsql_like(f: &crate::expressions::SimilarToExpr) -> bool {
35601 match &f.pattern {
35602 Expression::Literal(literal) if literal.is_string() => {
35603 Self::similar_to_literal_pattern_is_like_compatible(literal.value_str())
35604 }
35605 _ => false,
35606 }
35607 }
35608
35609 fn similar_to_literal_pattern_is_like_compatible(pattern: &str) -> bool {
35610 if pattern.contains('\\') {
35611 return false;
35612 }
35613
35614 !pattern
35615 .chars()
35616 .any(|ch| matches!(ch, '|' | '*' | '+' | '?' | '{' | '}' | '(' | ')'))
35617 }
35618
35619 fn build_json_object_from_pairs(
35620 args: Vec<Expression>,
35621 ) -> Option<Box<crate::expressions::JsonObjectFunc>> {
35622 if args.len() % 2 != 0 {
35623 return None;
35624 }
35625
35626 let mut pairs = Vec::with_capacity(args.len() / 2);
35627 let mut iter = args.into_iter();
35628 while let Some(key) = iter.next() {
35629 let value = iter.next()?;
35630 pairs.push((key, value));
35631 }
35632
35633 Some(Box::new(crate::expressions::JsonObjectFunc {
35634 pairs,
35635 null_handling: None,
35636 with_unique_keys: false,
35637 returning_type: None,
35638 format_json: false,
35639 encoding: None,
35640 star: false,
35641 }))
35642 }
35643
35644 fn build_tsql_div_func(left: Expression, right: Expression, target: DialectType) -> Expression {
35645 let cast_left = Self::cast_expr(
35646 left,
35647 DataType::Double {
35648 precision: None,
35649 scale: None,
35650 },
35651 );
35652 let divided = Expression::Div(Box::new(BinaryOp::new(cast_left, right)));
35653 let cast_int = Self::cast_expr(
35654 divided,
35655 DataType::Int {
35656 length: None,
35657 integer_spelling: true,
35658 },
35659 );
35660 let numeric_type = match target {
35661 DialectType::Fabric => DataType::Custom {
35662 name: "DECIMAL".to_string(),
35663 },
35664 _ => DataType::Custom {
35665 name: "NUMERIC".to_string(),
35666 },
35667 };
35668 Self::cast_expr(cast_int, numeric_type)
35669 }
35670
35671 fn build_tsql_cbrt_power(this: Expression) -> Expression {
35672 let base = Self::cast_expr(
35673 this,
35674 DataType::Double {
35675 precision: None,
35676 scale: None,
35677 },
35678 );
35679 let exponent = Expression::Div(Box::new(BinaryOp::new(
35680 Expression::Literal(Box::new(Literal::Number("1.0".to_string()))),
35681 Expression::Literal(Box::new(Literal::Number("3.0".to_string()))),
35682 )));
35683
35684 Expression::Power(Box::new(crate::expressions::BinaryFunc {
35685 this: base,
35686 expression: exponent,
35687 original_name: None,
35688 inferred_type: None,
35689 }))
35690 }
35691
35692 /// Extract value and unit from an Interval expression
35693 /// Returns (value_expression, IntervalUnit)
35694 fn extract_interval_parts(
35695 interval_expr: &Expression,
35696 ) -> Option<(Expression, crate::expressions::IntervalUnit)> {
35697 use crate::expressions::{DataType, IntervalUnit, IntervalUnitSpec, Literal};
35698
35699 fn unit_from_str(unit: &str) -> Option<IntervalUnit> {
35700 match unit.trim().to_ascii_uppercase().as_str() {
35701 "YEAR" | "YEARS" | "Y" | "YR" | "YRS" | "YY" | "YYYY" => Some(IntervalUnit::Year),
35702 "QUARTER" | "QUARTERS" | "Q" | "QTR" | "QTRS" | "QQ" => Some(IntervalUnit::Quarter),
35703 "MONTH" | "MONTHS" | "MON" | "MONS" | "MM" => Some(IntervalUnit::Month),
35704 "WEEK" | "WEEKS" | "W" | "WK" | "WKS" | "WW" | "ISOWEEK" => {
35705 Some(IntervalUnit::Week)
35706 }
35707 "DAY" | "DAYS" | "D" | "DD" => Some(IntervalUnit::Day),
35708 "HOUR" | "HOURS" | "H" | "HH" | "HR" | "HRS" => Some(IntervalUnit::Hour),
35709 "MINUTE" | "MINUTES" | "MI" | "MIN" | "MINS" | "N" => Some(IntervalUnit::Minute),
35710 "SECOND" | "SECONDS" | "S" | "SEC" | "SECS" | "SS" => Some(IntervalUnit::Second),
35711 "MILLISECOND" | "MILLISECONDS" | "MS" | "MSEC" | "MSECS" | "MSECOND"
35712 | "MSECONDS" | "MILLISEC" | "MILLISECS" | "MILLISECON" => {
35713 Some(IntervalUnit::Millisecond)
35714 }
35715 "MICROSECOND" | "MICROSECONDS" | "US" | "USEC" | "USECS" | "USECOND"
35716 | "USECONDS" | "MICROSEC" | "MICROSECS" | "MCS" => Some(IntervalUnit::Microsecond),
35717 "NANOSECOND" | "NANOSECONDS" | "NS" | "NSEC" | "NSECS" | "NSECOND" | "NSECONDS"
35718 | "NANOSEC" | "NANOSECS" => Some(IntervalUnit::Nanosecond),
35719 _ => None,
35720 }
35721 }
35722
35723 fn parts_from_literal_string(s: &str) -> Option<(Expression, IntervalUnit)> {
35724 let mut parts = s.split_whitespace();
35725 let value = parts.next()?;
35726 let unit = unit_from_str(parts.next()?)?;
35727 Some((
35728 Expression::Literal(Box::new(Literal::String(value.to_string()))),
35729 unit,
35730 ))
35731 }
35732
35733 fn unit_from_spec(unit: &IntervalUnitSpec) -> Option<IntervalUnit> {
35734 match unit {
35735 IntervalUnitSpec::Simple { unit, .. } => Some(*unit),
35736 IntervalUnitSpec::Expr(expr) => match expr.as_ref() {
35737 Expression::Day(_) => Some(IntervalUnit::Day),
35738 Expression::Month(_) => Some(IntervalUnit::Month),
35739 Expression::Year(_) => Some(IntervalUnit::Year),
35740 Expression::Identifier(id) => unit_from_str(&id.name),
35741 Expression::Var(v) => unit_from_str(&v.this),
35742 Expression::Column(col) => unit_from_str(&col.name.name),
35743 _ => None,
35744 },
35745 _ => None,
35746 }
35747 }
35748
35749 match interval_expr {
35750 Expression::Interval(iv) => {
35751 let val = iv.this.clone().unwrap_or(Expression::number(0));
35752 if let Expression::Literal(lit) = &val {
35753 if let Literal::String(s) = lit.as_ref() {
35754 if let Some(parts) = parts_from_literal_string(s) {
35755 return Some(parts);
35756 }
35757 }
35758 }
35759 let unit = iv
35760 .unit
35761 .as_ref()
35762 .and_then(unit_from_spec)
35763 .unwrap_or(IntervalUnit::Day);
35764 Some((val, unit))
35765 }
35766 Expression::Cast(cast) if matches!(cast.to, DataType::Interval { .. }) => {
35767 if let Expression::Literal(lit) = &cast.this {
35768 if let Literal::String(s) = lit.as_ref() {
35769 if let Some(parts) = parts_from_literal_string(s) {
35770 return Some(parts);
35771 }
35772 }
35773 }
35774 let unit = match &cast.to {
35775 DataType::Interval {
35776 unit: Some(unit), ..
35777 } => unit_from_str(unit).unwrap_or(IntervalUnit::Day),
35778 _ => IntervalUnit::Day,
35779 };
35780 Some((cast.this.clone(), unit))
35781 }
35782 _ => None,
35783 }
35784 }
35785
35786 fn data_type_is_interval(dt: &DataType) -> bool {
35787 match dt {
35788 DataType::Interval { .. } => true,
35789 DataType::Custom { name } => name.trim().eq_ignore_ascii_case("INTERVAL"),
35790 _ => false,
35791 }
35792 }
35793
35794 fn node_is_interval_cast(node: &Expression) -> bool {
35795 match node {
35796 Expression::Cast(c) | Expression::TryCast(c) | Expression::SafeCast(c) => {
35797 Self::data_type_is_interval(&c.to)
35798 }
35799 _ => false,
35800 }
35801 }
35802
35803 fn reject_tsql_interval_casts(
35804 expr: &Expression,
35805 target: DialectType,
35806 opts: &TranspileOptions,
35807 ) -> Result<()> {
35808 if !matches!(
35809 opts.unsupported_level,
35810 UnsupportedLevel::Raise | UnsupportedLevel::Immediate
35811 ) {
35812 return Ok(());
35813 }
35814
35815 if expr.dfs().any(Self::node_is_interval_cast) {
35816 return Err(crate::error::Error::unsupported(
35817 "INTERVAL casts",
35818 target.to_string(),
35819 ));
35820 }
35821
35822 Ok(())
35823 }
35824
35825 fn tsql_varchar_max_type() -> DataType {
35826 DataType::Custom {
35827 name: "VARCHAR(MAX)".to_string(),
35828 }
35829 }
35830
35831 fn rewrite_tsql_interval_casts_to_varchar(expr: Expression) -> Result<Expression> {
35832 transform_recursive(expr, &|e| match e {
35833 Expression::Cast(mut cast) if Self::data_type_is_interval(&cast.to) => {
35834 cast.to = Self::tsql_varchar_max_type();
35835 cast.double_colon_syntax = false;
35836 Ok(Expression::Cast(cast))
35837 }
35838 Expression::TryCast(mut cast) if Self::data_type_is_interval(&cast.to) => {
35839 cast.to = Self::tsql_varchar_max_type();
35840 cast.double_colon_syntax = false;
35841 Ok(Expression::TryCast(cast))
35842 }
35843 Expression::SafeCast(mut cast) if Self::data_type_is_interval(&cast.to) => {
35844 cast.to = Self::tsql_varchar_max_type();
35845 cast.double_colon_syntax = false;
35846 Ok(Expression::SafeCast(cast))
35847 }
35848 _ => Ok(e),
35849 })
35850 }
35851
35852 fn rewrite_tsql_interval_arithmetic(
35853 expr: &Expression,
35854 source: DialectType,
35855 ) -> Option<Expression> {
35856 match expr {
35857 Expression::Add(op) => {
35858 if Self::extract_interval_parts(&op.right).is_some() {
35859 return Some(Self::build_tsql_dateadd_from_interval(
35860 op.left.clone(),
35861 &op.right,
35862 false,
35863 ));
35864 }
35865
35866 if Self::is_postgres_family_source(source) {
35867 if Self::is_explicit_date_expr(&op.left)
35868 && Self::is_integer_day_offset_expr(&op.right)
35869 {
35870 return Some(Self::build_tsql_dateadd_days(
35871 op.left.clone(),
35872 op.right.clone(),
35873 false,
35874 ));
35875 }
35876
35877 if Self::is_integer_day_offset_expr(&op.left)
35878 && Self::is_explicit_date_expr(&op.right)
35879 {
35880 return Some(Self::build_tsql_dateadd_days(
35881 op.right.clone(),
35882 op.left.clone(),
35883 false,
35884 ));
35885 }
35886 }
35887
35888 None
35889 }
35890 Expression::Sub(op) => {
35891 if Self::extract_interval_parts(&op.right).is_some() {
35892 return Some(Self::build_tsql_dateadd_from_interval(
35893 op.left.clone(),
35894 &op.right,
35895 true,
35896 ));
35897 }
35898
35899 if Self::is_postgres_family_source(source) {
35900 if Self::is_explicit_date_expr(&op.left)
35901 && Self::is_explicit_date_expr(&op.right)
35902 {
35903 return Some(Self::build_tsql_datediff_days(
35904 op.right.clone(),
35905 op.left.clone(),
35906 ));
35907 }
35908
35909 if Self::is_explicit_date_expr(&op.left)
35910 && Self::is_integer_day_offset_expr(&op.right)
35911 {
35912 return Some(Self::build_tsql_dateadd_days(
35913 op.left.clone(),
35914 op.right.clone(),
35915 true,
35916 ));
35917 }
35918 }
35919
35920 None
35921 }
35922 _ => None,
35923 }
35924 }
35925
35926 fn is_postgres_family_source(source: DialectType) -> bool {
35927 matches!(
35928 source,
35929 DialectType::PostgreSQL
35930 | DialectType::Redshift
35931 | DialectType::Materialize
35932 | DialectType::RisingWave
35933 | DialectType::CockroachDB
35934 )
35935 }
35936
35937 fn is_explicit_date_expr(expr: &Expression) -> bool {
35938 use crate::expressions::Literal;
35939
35940 match expr {
35941 Expression::Literal(lit) => matches!(lit.as_ref(), Literal::Date(_)),
35942 Expression::Cast(c) | Expression::TryCast(c) | Expression::SafeCast(c) => {
35943 matches!(c.to, crate::expressions::DataType::Date)
35944 }
35945 Expression::Paren(p) => Self::is_explicit_date_expr(&p.this),
35946 Expression::CurrentDate(_)
35947 | Expression::Date(_)
35948 | Expression::MakeDate(_)
35949 | Expression::ToDate(_)
35950 | Expression::DateStrToDate(_) => true,
35951 _ => false,
35952 }
35953 }
35954
35955 fn is_integer_day_offset_expr(expr: &Expression) -> bool {
35956 use crate::expressions::Literal;
35957
35958 match expr {
35959 Expression::Literal(lit) => match lit.as_ref() {
35960 Literal::Number(n) => n.parse::<i64>().is_ok(),
35961 _ => false,
35962 },
35963 Expression::Parameter(_) | Expression::Placeholder(_) => true,
35964 Expression::Neg(op) => Self::is_integer_day_offset_expr(&op.this),
35965 Expression::Paren(p) => Self::is_integer_day_offset_expr(&p.this),
35966 _ => false,
35967 }
35968 }
35969
35970 fn build_tsql_datediff_days(start: Expression, end: Expression) -> Expression {
35971 Expression::Function(Box::new(Function::new(
35972 "DATEDIFF".to_string(),
35973 vec![Expression::Identifier(Identifier::new("DAY")), start, end],
35974 )))
35975 }
35976
35977 fn build_tsql_dateadd_days(date: Expression, amount: Expression, subtract: bool) -> Expression {
35978 Expression::Function(Box::new(Function::new(
35979 "DATEADD".to_string(),
35980 vec![
35981 Expression::Identifier(Identifier::new("DAY")),
35982 Self::tsql_dateadd_amount(amount, subtract),
35983 date,
35984 ],
35985 )))
35986 }
35987
35988 fn build_tsql_dateadd_from_interval(
35989 date: Expression,
35990 interval: &Expression,
35991 subtract: bool,
35992 ) -> Expression {
35993 let (value, unit) = Self::extract_interval_parts(interval)
35994 .unwrap_or_else(|| (interval.clone(), crate::expressions::IntervalUnit::Day));
35995 let unit = Self::interval_unit_to_string(&unit);
35996 let amount = Self::tsql_dateadd_amount(value, subtract);
35997
35998 Expression::Function(Box::new(Function::new(
35999 "DATEADD".to_string(),
36000 vec![Expression::Identifier(Identifier::new(unit)), amount, date],
36001 )))
36002 }
36003
36004 fn tsql_dateadd_amount(value: Expression, negate: bool) -> Expression {
36005 use crate::expressions::{Parameter, ParameterStyle, UnaryOp};
36006
36007 fn numeric_literal_value(value: &Expression) -> Option<&str> {
36008 match value {
36009 Expression::Literal(lit) => match lit.as_ref() {
36010 crate::expressions::Literal::Number(n)
36011 | crate::expressions::Literal::String(n) => Some(n.as_str()),
36012 _ => None,
36013 },
36014 _ => None,
36015 }
36016 }
36017
36018 fn colon_parameter(value: &Expression) -> Option<Expression> {
36019 let Expression::Literal(lit) = value else {
36020 return None;
36021 };
36022 let crate::expressions::Literal::String(s) = lit.as_ref() else {
36023 return None;
36024 };
36025 let name = s.strip_prefix(':')?;
36026 if name.is_empty()
36027 || !name
36028 .chars()
36029 .all(|ch| ch.is_ascii_alphanumeric() || ch == '_')
36030 {
36031 return None;
36032 }
36033
36034 Some(Expression::Parameter(Box::new(Parameter {
36035 name: if name.chars().all(|ch| ch.is_ascii_digit()) {
36036 None
36037 } else {
36038 Some(name.to_string())
36039 },
36040 index: name.parse::<u32>().ok(),
36041 style: ParameterStyle::Colon,
36042 quoted: false,
36043 string_quoted: false,
36044 expression: None,
36045 })))
36046 }
36047
36048 let value = colon_parameter(&value).unwrap_or(value);
36049
36050 if let Some(n) = numeric_literal_value(&value) {
36051 if let Ok(parsed) = n.parse::<f64>() {
36052 let normalized = if negate { -parsed } else { parsed };
36053 let rendered = if normalized.fract() == 0.0 {
36054 format!("{}", normalized as i64)
36055 } else {
36056 normalized.to_string()
36057 };
36058 return Expression::Literal(Box::new(crate::expressions::Literal::Number(
36059 rendered,
36060 )));
36061 }
36062 }
36063
36064 if !negate {
36065 return value;
36066 }
36067
36068 match value {
36069 Expression::Neg(op) => op.this,
36070 other => Expression::Neg(Box::new(UnaryOp {
36071 this: other,
36072 inferred_type: None,
36073 })),
36074 }
36075 }
36076
36077 /// Normalize BigQuery-specific functions to standard forms that target dialects can handle
36078 fn normalize_bigquery_function(
36079 e: Expression,
36080 source: DialectType,
36081 target: DialectType,
36082 ) -> Result<Expression> {
36083 use crate::expressions::{BinaryOp, Cast, DataType, Function, Identifier, Literal, Paren};
36084
36085 let f = if let Expression::Function(f) = e {
36086 *f
36087 } else {
36088 return Ok(e);
36089 };
36090 let name = f.name.to_ascii_uppercase();
36091 let mut args = f.args;
36092
36093 /// Helper to extract unit string from an identifier, column, or literal expression
36094 fn get_unit_str(expr: &Expression) -> String {
36095 match expr {
36096 Expression::Identifier(id) => id.name.to_ascii_uppercase(),
36097 Expression::Var(v) => v.this.to_ascii_uppercase(),
36098 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
36099 let Literal::String(s) = lit.as_ref() else {
36100 unreachable!()
36101 };
36102 s.to_ascii_uppercase()
36103 }
36104 Expression::Column(col) => col.name.name.to_ascii_uppercase(),
36105 // Handle WEEK(MONDAY), WEEK(SUNDAY) etc. which are parsed as Function("WEEK", [Column("MONDAY")])
36106 Expression::Function(f) => {
36107 let base = f.name.to_ascii_uppercase();
36108 if !f.args.is_empty() {
36109 // e.g., WEEK(MONDAY) -> "WEEK(MONDAY)"
36110 let inner = get_unit_str(&f.args[0]);
36111 format!("{}({})", base, inner)
36112 } else {
36113 base
36114 }
36115 }
36116 _ => "DAY".to_string(),
36117 }
36118 }
36119
36120 /// Parse unit string to IntervalUnit
36121 fn parse_interval_unit(s: &str) -> crate::expressions::IntervalUnit {
36122 match s {
36123 "YEAR" => crate::expressions::IntervalUnit::Year,
36124 "QUARTER" => crate::expressions::IntervalUnit::Quarter,
36125 "MONTH" => crate::expressions::IntervalUnit::Month,
36126 "WEEK" | "ISOWEEK" => crate::expressions::IntervalUnit::Week,
36127 "DAY" => crate::expressions::IntervalUnit::Day,
36128 "HOUR" => crate::expressions::IntervalUnit::Hour,
36129 "MINUTE" => crate::expressions::IntervalUnit::Minute,
36130 "SECOND" => crate::expressions::IntervalUnit::Second,
36131 "MILLISECOND" => crate::expressions::IntervalUnit::Millisecond,
36132 "MICROSECOND" => crate::expressions::IntervalUnit::Microsecond,
36133 _ if s.starts_with("WEEK(") => crate::expressions::IntervalUnit::Week,
36134 _ => crate::expressions::IntervalUnit::Day,
36135 }
36136 }
36137
36138 match name.as_str() {
36139 // TIMESTAMP_DIFF(date1, date2, unit) -> TIMESTAMPDIFF(unit, date2, date1)
36140 // (BigQuery: result = date1 - date2, Standard: result = end - start)
36141 "TIMESTAMP_DIFF" | "DATETIME_DIFF" | "TIME_DIFF" if args.len() == 3 => {
36142 let date1 = args.remove(0);
36143 let date2 = args.remove(0);
36144 let unit_expr = args.remove(0);
36145 let unit_str = get_unit_str(&unit_expr);
36146
36147 if matches!(target, DialectType::BigQuery) {
36148 // BigQuery -> BigQuery: just uppercase the unit
36149 let unit = Expression::Identifier(Identifier::new(unit_str.clone()));
36150 return Ok(Expression::Function(Box::new(Function::new(
36151 f.name,
36152 vec![date1, date2, unit],
36153 ))));
36154 }
36155
36156 // For Snowflake: use TimestampDiff expression so it generates TIMESTAMPDIFF
36157 // (Function("TIMESTAMPDIFF") would be converted to DATEDIFF by Snowflake's function normalization)
36158 if matches!(target, DialectType::Snowflake) {
36159 return Ok(Expression::TimestampDiff(Box::new(
36160 crate::expressions::TimestampDiff {
36161 this: Box::new(date2),
36162 expression: Box::new(date1),
36163 unit: Some(unit_str),
36164 },
36165 )));
36166 }
36167
36168 // For DuckDB: DATE_DIFF('UNIT', start, end) with proper CAST
36169 if matches!(target, DialectType::DuckDB) {
36170 let (cast_d1, cast_d2) = if name == "TIME_DIFF" {
36171 // CAST to TIME
36172 let cast_fn = |e: Expression| -> Expression {
36173 match e {
36174 Expression::Literal(lit)
36175 if matches!(lit.as_ref(), Literal::String(_)) =>
36176 {
36177 let Literal::String(s) = lit.as_ref() else {
36178 unreachable!()
36179 };
36180 Expression::Cast(Box::new(Cast {
36181 this: Expression::Literal(Box::new(Literal::String(
36182 s.clone(),
36183 ))),
36184 to: DataType::Custom {
36185 name: "TIME".to_string(),
36186 },
36187 trailing_comments: vec![],
36188 double_colon_syntax: false,
36189 format: None,
36190 default: None,
36191 inferred_type: None,
36192 }))
36193 }
36194 other => other,
36195 }
36196 };
36197 (cast_fn(date1), cast_fn(date2))
36198 } else if name == "DATETIME_DIFF" {
36199 // CAST to TIMESTAMP
36200 (
36201 Self::ensure_cast_timestamp(date1),
36202 Self::ensure_cast_timestamp(date2),
36203 )
36204 } else {
36205 // TIMESTAMP_DIFF: CAST to TIMESTAMPTZ
36206 (
36207 Self::ensure_cast_timestamptz(date1),
36208 Self::ensure_cast_timestamptz(date2),
36209 )
36210 };
36211 return Ok(Expression::Function(Box::new(Function::new(
36212 "DATE_DIFF".to_string(),
36213 vec![
36214 Expression::Literal(Box::new(Literal::String(unit_str))),
36215 cast_d2,
36216 cast_d1,
36217 ],
36218 ))));
36219 }
36220
36221 // Convert to standard TIMESTAMPDIFF(unit, start, end)
36222 let unit = Expression::Identifier(Identifier::new(unit_str));
36223 Ok(Expression::Function(Box::new(Function::new(
36224 "TIMESTAMPDIFF".to_string(),
36225 vec![unit, date2, date1],
36226 ))))
36227 }
36228
36229 // DATEDIFF(unit, start, end) -> target-specific form
36230 // Used by: Redshift, Snowflake, TSQL, Databricks, Spark
36231 "DATEDIFF" if args.len() == 3 => {
36232 let arg0 = args.remove(0);
36233 let arg1 = args.remove(0);
36234 let arg2 = args.remove(0);
36235 let unit_str = get_unit_str(&arg0);
36236
36237 // Redshift DATEDIFF(unit, start, end) order: result = end - start
36238 // Snowflake DATEDIFF(unit, start, end) order: result = end - start
36239 // TSQL DATEDIFF(unit, start, end) order: result = end - start
36240
36241 if matches!(target, DialectType::Snowflake) {
36242 // Snowflake: DATEDIFF(UNIT, start, end) - uppercase unit
36243 let unit = Expression::Identifier(Identifier::new(unit_str));
36244 return Ok(Expression::Function(Box::new(Function::new(
36245 "DATEDIFF".to_string(),
36246 vec![unit, arg1, arg2],
36247 ))));
36248 }
36249
36250 if matches!(target, DialectType::DuckDB) {
36251 // DuckDB: DATE_DIFF('UNIT', start, end) with CAST
36252 let cast_d1 = Self::ensure_cast_timestamp(arg1);
36253 let cast_d2 = Self::ensure_cast_timestamp(arg2);
36254 return Ok(Expression::Function(Box::new(Function::new(
36255 "DATE_DIFF".to_string(),
36256 vec![
36257 Expression::Literal(Box::new(Literal::String(unit_str))),
36258 cast_d1,
36259 cast_d2,
36260 ],
36261 ))));
36262 }
36263
36264 if matches!(target, DialectType::BigQuery) {
36265 // BigQuery: DATE_DIFF(end_date, start_date, UNIT) - reversed args, CAST to DATETIME
36266 let cast_d1 = Self::ensure_cast_datetime(arg1);
36267 let cast_d2 = Self::ensure_cast_datetime(arg2);
36268 let unit = Expression::Identifier(Identifier::new(unit_str));
36269 return Ok(Expression::Function(Box::new(Function::new(
36270 "DATE_DIFF".to_string(),
36271 vec![cast_d2, cast_d1, unit],
36272 ))));
36273 }
36274
36275 if matches!(target, DialectType::Spark | DialectType::Databricks) {
36276 // Spark/Databricks: DATEDIFF(UNIT, start, end) - uppercase unit
36277 let unit = Expression::Identifier(Identifier::new(unit_str));
36278 return Ok(Expression::Function(Box::new(Function::new(
36279 "DATEDIFF".to_string(),
36280 vec![unit, arg1, arg2],
36281 ))));
36282 }
36283
36284 if matches!(target, DialectType::Hive) {
36285 // Hive: DATEDIFF(end, start) for DAY only, use MONTHS_BETWEEN for MONTH
36286 match unit_str.as_str() {
36287 "MONTH" => {
36288 return Ok(Expression::Function(Box::new(Function::new(
36289 "CAST".to_string(),
36290 vec![Expression::Function(Box::new(Function::new(
36291 "MONTHS_BETWEEN".to_string(),
36292 vec![arg2, arg1],
36293 )))],
36294 ))));
36295 }
36296 "WEEK" => {
36297 return Ok(Expression::Cast(Box::new(Cast {
36298 this: Expression::Div(Box::new(crate::expressions::BinaryOp::new(
36299 Expression::Function(Box::new(Function::new(
36300 "DATEDIFF".to_string(),
36301 vec![arg2, arg1],
36302 ))),
36303 Expression::Literal(Box::new(Literal::Number("7".to_string()))),
36304 ))),
36305 to: DataType::Int {
36306 length: None,
36307 integer_spelling: false,
36308 },
36309 trailing_comments: vec![],
36310 double_colon_syntax: false,
36311 format: None,
36312 default: None,
36313 inferred_type: None,
36314 })));
36315 }
36316 _ => {
36317 // Default: DATEDIFF(end, start) for DAY
36318 return Ok(Expression::Function(Box::new(Function::new(
36319 "DATEDIFF".to_string(),
36320 vec![arg2, arg1],
36321 ))));
36322 }
36323 }
36324 }
36325
36326 if matches!(
36327 target,
36328 DialectType::Presto | DialectType::Trino | DialectType::Athena
36329 ) {
36330 // Presto/Trino: DATE_DIFF('UNIT', start, end)
36331 return Ok(Expression::Function(Box::new(Function::new(
36332 "DATE_DIFF".to_string(),
36333 vec![
36334 Expression::Literal(Box::new(Literal::String(unit_str))),
36335 arg1,
36336 arg2,
36337 ],
36338 ))));
36339 }
36340
36341 if matches!(target, DialectType::TSQL) {
36342 // TSQL: DATEDIFF(UNIT, start, CAST(end AS DATETIME2))
36343 let cast_d2 = Self::ensure_cast_datetime2(arg2);
36344 let unit = Expression::Identifier(Identifier::new(unit_str));
36345 return Ok(Expression::Function(Box::new(Function::new(
36346 "DATEDIFF".to_string(),
36347 vec![unit, arg1, cast_d2],
36348 ))));
36349 }
36350
36351 if matches!(target, DialectType::PostgreSQL) {
36352 // PostgreSQL doesn't have DATEDIFF - use date subtraction or EXTRACT
36353 // For now, use DATEDIFF (passthrough) with uppercased unit
36354 let unit = Expression::Identifier(Identifier::new(unit_str));
36355 return Ok(Expression::Function(Box::new(Function::new(
36356 "DATEDIFF".to_string(),
36357 vec![unit, arg1, arg2],
36358 ))));
36359 }
36360
36361 // Default: DATEDIFF(UNIT, start, end) with uppercase unit
36362 let unit = Expression::Identifier(Identifier::new(unit_str));
36363 Ok(Expression::Function(Box::new(Function::new(
36364 "DATEDIFF".to_string(),
36365 vec![unit, arg1, arg2],
36366 ))))
36367 }
36368
36369 // DATE_DIFF(date1, date2, unit) -> standard form
36370 "DATE_DIFF" if args.len() == 3 => {
36371 let date1 = args.remove(0);
36372 let date2 = args.remove(0);
36373 let unit_expr = args.remove(0);
36374 let unit_str = get_unit_str(&unit_expr);
36375
36376 if matches!(target, DialectType::BigQuery) {
36377 // BigQuery -> BigQuery: just uppercase the unit, normalize WEEK(SUNDAY) -> WEEK
36378 let norm_unit = if unit_str == "WEEK(SUNDAY)" {
36379 "WEEK".to_string()
36380 } else {
36381 unit_str
36382 };
36383 let norm_d1 = Self::date_literal_to_cast(date1);
36384 let norm_d2 = Self::date_literal_to_cast(date2);
36385 let unit = Expression::Identifier(Identifier::new(norm_unit));
36386 return Ok(Expression::Function(Box::new(Function::new(
36387 f.name,
36388 vec![norm_d1, norm_d2, unit],
36389 ))));
36390 }
36391
36392 if matches!(target, DialectType::MySQL) {
36393 // MySQL DATEDIFF only takes 2 args (date1, date2), returns day difference
36394 let norm_d1 = Self::date_literal_to_cast(date1);
36395 let norm_d2 = Self::date_literal_to_cast(date2);
36396 return Ok(Expression::Function(Box::new(Function::new(
36397 "DATEDIFF".to_string(),
36398 vec![norm_d1, norm_d2],
36399 ))));
36400 }
36401
36402 if matches!(target, DialectType::StarRocks) {
36403 // StarRocks: DATE_DIFF('UNIT', date1, date2) - unit as string, args NOT swapped
36404 let norm_d1 = Self::date_literal_to_cast(date1);
36405 let norm_d2 = Self::date_literal_to_cast(date2);
36406 return Ok(Expression::Function(Box::new(Function::new(
36407 "DATE_DIFF".to_string(),
36408 vec![
36409 Expression::Literal(Box::new(Literal::String(unit_str))),
36410 norm_d1,
36411 norm_d2,
36412 ],
36413 ))));
36414 }
36415
36416 if matches!(target, DialectType::DuckDB) {
36417 // DuckDB: DATE_DIFF('UNIT', date2, date1) with proper CAST for dates
36418 let norm_d1 = Self::ensure_cast_date(date1);
36419 let norm_d2 = Self::ensure_cast_date(date2);
36420
36421 // Handle WEEK variants: WEEK(MONDAY)/WEEK(SUNDAY)/ISOWEEK/WEEK
36422 let is_week_variant = unit_str == "WEEK"
36423 || unit_str.starts_with("WEEK(")
36424 || unit_str == "ISOWEEK";
36425 if is_week_variant {
36426 // For DuckDB, WEEK-based diffs use DATE_TRUNC approach
36427 // WEEK(MONDAY) / ISOWEEK: DATE_DIFF('WEEK', DATE_TRUNC('WEEK', d2), DATE_TRUNC('WEEK', d1))
36428 // WEEK / WEEK(SUNDAY): DATE_DIFF('WEEK', DATE_TRUNC('WEEK', d2 + INTERVAL '1' DAY), DATE_TRUNC('WEEK', d1 + INTERVAL '1' DAY))
36429 // WEEK(SATURDAY): DATE_DIFF('WEEK', DATE_TRUNC('WEEK', d2 + INTERVAL '-5' DAY), DATE_TRUNC('WEEK', d1 + INTERVAL '-5' DAY))
36430 let day_offset = if unit_str == "WEEK(MONDAY)" || unit_str == "ISOWEEK" {
36431 None // ISO weeks start on Monday, aligned with DATE_TRUNC('WEEK')
36432 } else if unit_str == "WEEK" || unit_str == "WEEK(SUNDAY)" {
36433 Some("1") // Shift Sunday to Monday alignment
36434 } else if unit_str == "WEEK(SATURDAY)" {
36435 Some("-5")
36436 } else if unit_str == "WEEK(TUESDAY)" {
36437 Some("-1")
36438 } else if unit_str == "WEEK(WEDNESDAY)" {
36439 Some("-2")
36440 } else if unit_str == "WEEK(THURSDAY)" {
36441 Some("-3")
36442 } else if unit_str == "WEEK(FRIDAY)" {
36443 Some("-4")
36444 } else {
36445 Some("1") // default to Sunday
36446 };
36447
36448 let make_trunc = |date: Expression, offset: Option<&str>| -> Expression {
36449 let shifted = if let Some(off) = offset {
36450 let interval =
36451 Expression::Interval(Box::new(crate::expressions::Interval {
36452 this: Some(Expression::Literal(Box::new(Literal::String(
36453 off.to_string(),
36454 )))),
36455 unit: Some(crate::expressions::IntervalUnitSpec::Simple {
36456 unit: crate::expressions::IntervalUnit::Day,
36457 use_plural: false,
36458 }),
36459 }));
36460 Expression::Add(Box::new(crate::expressions::BinaryOp::new(
36461 date, interval,
36462 )))
36463 } else {
36464 date
36465 };
36466 Expression::Function(Box::new(Function::new(
36467 "DATE_TRUNC".to_string(),
36468 vec![
36469 Expression::Literal(Box::new(Literal::String(
36470 "WEEK".to_string(),
36471 ))),
36472 shifted,
36473 ],
36474 )))
36475 };
36476
36477 let trunc_d2 = make_trunc(norm_d2, day_offset);
36478 let trunc_d1 = make_trunc(norm_d1, day_offset);
36479 return Ok(Expression::Function(Box::new(Function::new(
36480 "DATE_DIFF".to_string(),
36481 vec![
36482 Expression::Literal(Box::new(Literal::String("WEEK".to_string()))),
36483 trunc_d2,
36484 trunc_d1,
36485 ],
36486 ))));
36487 }
36488
36489 return Ok(Expression::Function(Box::new(Function::new(
36490 "DATE_DIFF".to_string(),
36491 vec![
36492 Expression::Literal(Box::new(Literal::String(unit_str))),
36493 norm_d2,
36494 norm_d1,
36495 ],
36496 ))));
36497 }
36498
36499 // Default: DATEDIFF(unit, date2, date1)
36500 let unit = Expression::Identifier(Identifier::new(unit_str));
36501 Ok(Expression::Function(Box::new(Function::new(
36502 "DATEDIFF".to_string(),
36503 vec![unit, date2, date1],
36504 ))))
36505 }
36506
36507 // TIMESTAMP_ADD(ts, INTERVAL n UNIT) -> target-specific
36508 "TIMESTAMP_ADD" | "DATETIME_ADD" | "TIME_ADD" if args.len() == 2 => {
36509 let ts = args.remove(0);
36510 let interval_expr = args.remove(0);
36511 let (val, unit) =
36512 Self::extract_interval_parts(&interval_expr).unwrap_or_else(|| {
36513 (interval_expr.clone(), crate::expressions::IntervalUnit::Day)
36514 });
36515
36516 match target {
36517 DialectType::Snowflake => {
36518 // TIMESTAMPADD(UNIT, val, CAST(ts AS TIMESTAMPTZ))
36519 // Use TimestampAdd expression so Snowflake generates TIMESTAMPADD
36520 // (Function("TIMESTAMPADD") would be converted to DATEADD by Snowflake's function normalization)
36521 let unit_str = Self::interval_unit_to_string(&unit);
36522 let cast_ts = Self::maybe_cast_ts_to_tz(ts, &name);
36523 Ok(Expression::TimestampAdd(Box::new(
36524 crate::expressions::TimestampAdd {
36525 this: Box::new(val),
36526 expression: Box::new(cast_ts),
36527 unit: Some(unit_str.to_string()),
36528 },
36529 )))
36530 }
36531 DialectType::Spark | DialectType::Databricks => {
36532 if name == "DATETIME_ADD" && matches!(target, DialectType::Spark) {
36533 // Spark DATETIME_ADD: ts + INTERVAL val UNIT
36534 let interval =
36535 Expression::Interval(Box::new(crate::expressions::Interval {
36536 this: Some(val),
36537 unit: Some(crate::expressions::IntervalUnitSpec::Simple {
36538 unit,
36539 use_plural: false,
36540 }),
36541 }));
36542 Ok(Expression::Add(Box::new(
36543 crate::expressions::BinaryOp::new(ts, interval),
36544 )))
36545 } else if name == "DATETIME_ADD"
36546 && matches!(target, DialectType::Databricks)
36547 {
36548 // Databricks DATETIME_ADD: TIMESTAMPADD(UNIT, val, ts)
36549 let unit_str = Self::interval_unit_to_string(&unit);
36550 Ok(Expression::Function(Box::new(Function::new(
36551 "TIMESTAMPADD".to_string(),
36552 vec![Expression::Identifier(Identifier::new(unit_str)), val, ts],
36553 ))))
36554 } else {
36555 // Presto-style: DATE_ADD('unit', val, CAST(ts AS TIMESTAMP))
36556 let unit_str = Self::interval_unit_to_string(&unit);
36557 let cast_ts =
36558 if name.starts_with("TIMESTAMP") || name.starts_with("DATETIME") {
36559 Self::maybe_cast_ts(ts)
36560 } else {
36561 ts
36562 };
36563 Ok(Expression::Function(Box::new(Function::new(
36564 "DATE_ADD".to_string(),
36565 vec![
36566 Expression::Identifier(Identifier::new(unit_str)),
36567 val,
36568 cast_ts,
36569 ],
36570 ))))
36571 }
36572 }
36573 DialectType::MySQL => {
36574 // DATE_ADD(TIMESTAMP(ts), INTERVAL val UNIT) for MySQL
36575 let mysql_ts = if name.starts_with("TIMESTAMP") {
36576 // Check if already wrapped in TIMESTAMP() function (from cross-dialect normalization)
36577 match &ts {
36578 Expression::Function(ref inner_f)
36579 if inner_f.name.eq_ignore_ascii_case("TIMESTAMP") =>
36580 {
36581 // Already wrapped, keep as-is
36582 ts
36583 }
36584 _ => {
36585 // Unwrap typed literals: TIMESTAMP '...' -> '...' for TIMESTAMP() wrapper
36586 let unwrapped = match ts {
36587 Expression::Literal(lit)
36588 if matches!(lit.as_ref(), Literal::Timestamp(_)) =>
36589 {
36590 let Literal::Timestamp(s) = lit.as_ref() else {
36591 unreachable!()
36592 };
36593 Expression::Literal(Box::new(Literal::String(
36594 s.clone(),
36595 )))
36596 }
36597 other => other,
36598 };
36599 Expression::Function(Box::new(Function::new(
36600 "TIMESTAMP".to_string(),
36601 vec![unwrapped],
36602 )))
36603 }
36604 }
36605 } else {
36606 ts
36607 };
36608 Ok(Expression::DateAdd(Box::new(
36609 crate::expressions::DateAddFunc {
36610 this: mysql_ts,
36611 interval: val,
36612 unit,
36613 },
36614 )))
36615 }
36616 _ => {
36617 // DuckDB and others use DateAdd expression (DuckDB converts to + INTERVAL)
36618 let cast_ts = if matches!(target, DialectType::DuckDB) {
36619 if name == "DATETIME_ADD" {
36620 Self::ensure_cast_timestamp(ts)
36621 } else if name.starts_with("TIMESTAMP") {
36622 Self::maybe_cast_ts_to_tz(ts, &name)
36623 } else {
36624 ts
36625 }
36626 } else {
36627 ts
36628 };
36629 Ok(Expression::DateAdd(Box::new(
36630 crate::expressions::DateAddFunc {
36631 this: cast_ts,
36632 interval: val,
36633 unit,
36634 },
36635 )))
36636 }
36637 }
36638 }
36639
36640 // TIMESTAMP_SUB(ts, INTERVAL n UNIT) -> target-specific
36641 "TIMESTAMP_SUB" | "DATETIME_SUB" | "TIME_SUB" if args.len() == 2 => {
36642 let ts = args.remove(0);
36643 let interval_expr = args.remove(0);
36644 let (val, unit) =
36645 Self::extract_interval_parts(&interval_expr).unwrap_or_else(|| {
36646 (interval_expr.clone(), crate::expressions::IntervalUnit::Day)
36647 });
36648
36649 match target {
36650 DialectType::Snowflake => {
36651 // TIMESTAMPADD(UNIT, val * -1, CAST(ts AS TIMESTAMPTZ))
36652 let unit_str = Self::interval_unit_to_string(&unit);
36653 let cast_ts = Self::maybe_cast_ts_to_tz(ts, &name);
36654 let neg_val = Expression::Mul(Box::new(crate::expressions::BinaryOp::new(
36655 val,
36656 Expression::Neg(Box::new(crate::expressions::UnaryOp {
36657 this: Expression::number(1),
36658 inferred_type: None,
36659 })),
36660 )));
36661 Ok(Expression::TimestampAdd(Box::new(
36662 crate::expressions::TimestampAdd {
36663 this: Box::new(neg_val),
36664 expression: Box::new(cast_ts),
36665 unit: Some(unit_str.to_string()),
36666 },
36667 )))
36668 }
36669 DialectType::Spark | DialectType::Databricks => {
36670 if (name == "DATETIME_SUB" && matches!(target, DialectType::Spark))
36671 || (name == "TIMESTAMP_SUB" && matches!(target, DialectType::Spark))
36672 {
36673 // Spark: ts - INTERVAL val UNIT
36674 let cast_ts = if name.starts_with("TIMESTAMP") {
36675 Self::maybe_cast_ts(ts)
36676 } else {
36677 ts
36678 };
36679 let interval =
36680 Expression::Interval(Box::new(crate::expressions::Interval {
36681 this: Some(val),
36682 unit: Some(crate::expressions::IntervalUnitSpec::Simple {
36683 unit,
36684 use_plural: false,
36685 }),
36686 }));
36687 Ok(Expression::Sub(Box::new(
36688 crate::expressions::BinaryOp::new(cast_ts, interval),
36689 )))
36690 } else {
36691 // Databricks: TIMESTAMPADD(UNIT, val * -1, ts)
36692 let unit_str = Self::interval_unit_to_string(&unit);
36693 let neg_val =
36694 Expression::Mul(Box::new(crate::expressions::BinaryOp::new(
36695 val,
36696 Expression::Neg(Box::new(crate::expressions::UnaryOp {
36697 this: Expression::number(1),
36698 inferred_type: None,
36699 })),
36700 )));
36701 Ok(Expression::Function(Box::new(Function::new(
36702 "TIMESTAMPADD".to_string(),
36703 vec![
36704 Expression::Identifier(Identifier::new(unit_str)),
36705 neg_val,
36706 ts,
36707 ],
36708 ))))
36709 }
36710 }
36711 DialectType::MySQL => {
36712 let mysql_ts = if name.starts_with("TIMESTAMP") {
36713 // Check if already wrapped in TIMESTAMP() function (from cross-dialect normalization)
36714 match &ts {
36715 Expression::Function(ref inner_f)
36716 if inner_f.name.eq_ignore_ascii_case("TIMESTAMP") =>
36717 {
36718 // Already wrapped, keep as-is
36719 ts
36720 }
36721 _ => {
36722 let unwrapped = match ts {
36723 Expression::Literal(lit)
36724 if matches!(lit.as_ref(), Literal::Timestamp(_)) =>
36725 {
36726 let Literal::Timestamp(s) = lit.as_ref() else {
36727 unreachable!()
36728 };
36729 Expression::Literal(Box::new(Literal::String(
36730 s.clone(),
36731 )))
36732 }
36733 other => other,
36734 };
36735 Expression::Function(Box::new(Function::new(
36736 "TIMESTAMP".to_string(),
36737 vec![unwrapped],
36738 )))
36739 }
36740 }
36741 } else {
36742 ts
36743 };
36744 Ok(Expression::DateSub(Box::new(
36745 crate::expressions::DateAddFunc {
36746 this: mysql_ts,
36747 interval: val,
36748 unit,
36749 },
36750 )))
36751 }
36752 _ => {
36753 let cast_ts = if matches!(target, DialectType::DuckDB) {
36754 if name == "DATETIME_SUB" {
36755 Self::ensure_cast_timestamp(ts)
36756 } else if name.starts_with("TIMESTAMP") {
36757 Self::maybe_cast_ts_to_tz(ts, &name)
36758 } else {
36759 ts
36760 }
36761 } else {
36762 ts
36763 };
36764 Ok(Expression::DateSub(Box::new(
36765 crate::expressions::DateAddFunc {
36766 this: cast_ts,
36767 interval: val,
36768 unit,
36769 },
36770 )))
36771 }
36772 }
36773 }
36774
36775 // DATE_SUB(date, INTERVAL n UNIT) -> target-specific
36776 "DATE_SUB" if args.len() == 2 => {
36777 let date = args.remove(0);
36778 let interval_expr = args.remove(0);
36779 let (val, unit) =
36780 Self::extract_interval_parts(&interval_expr).unwrap_or_else(|| {
36781 (interval_expr.clone(), crate::expressions::IntervalUnit::Day)
36782 });
36783
36784 match target {
36785 DialectType::Databricks | DialectType::Spark => {
36786 // Databricks/Spark: DATE_ADD(date, -val)
36787 // Use DateAdd expression with negative val so it generates correctly
36788 // The generator will output DATE_ADD(date, INTERVAL -val DAY)
36789 // Then Databricks transform converts 2-arg DATE_ADD(date, interval) to DATEADD(DAY, interval, date)
36790 // Instead, we directly output as a simple negated DateSub
36791 Ok(Expression::DateSub(Box::new(
36792 crate::expressions::DateAddFunc {
36793 this: date,
36794 interval: val,
36795 unit,
36796 },
36797 )))
36798 }
36799 DialectType::DuckDB => {
36800 // DuckDB: CAST(date AS DATE) - INTERVAL 'val' UNIT
36801 let cast_date = Self::ensure_cast_date(date);
36802 let interval =
36803 Expression::Interval(Box::new(crate::expressions::Interval {
36804 this: Some(val),
36805 unit: Some(crate::expressions::IntervalUnitSpec::Simple {
36806 unit,
36807 use_plural: false,
36808 }),
36809 }));
36810 Ok(Expression::Sub(Box::new(
36811 crate::expressions::BinaryOp::new(cast_date, interval),
36812 )))
36813 }
36814 DialectType::Snowflake => {
36815 // Snowflake: Let Snowflake's own DateSub -> DATEADD(UNIT, val * -1, date) handler work
36816 // Just ensure the date is cast properly
36817 let cast_date = Self::ensure_cast_date(date);
36818 Ok(Expression::DateSub(Box::new(
36819 crate::expressions::DateAddFunc {
36820 this: cast_date,
36821 interval: val,
36822 unit,
36823 },
36824 )))
36825 }
36826 DialectType::PostgreSQL => {
36827 // PostgreSQL: date - INTERVAL 'val UNIT'
36828 let unit_str = Self::interval_unit_to_string(&unit);
36829 let interval =
36830 Expression::Interval(Box::new(crate::expressions::Interval {
36831 this: Some(Expression::Literal(Box::new(Literal::String(
36832 format!("{} {}", Self::expr_to_string(&val), unit_str),
36833 )))),
36834 unit: None,
36835 }));
36836 Ok(Expression::Sub(Box::new(
36837 crate::expressions::BinaryOp::new(date, interval),
36838 )))
36839 }
36840 _ => Ok(Expression::DateSub(Box::new(
36841 crate::expressions::DateAddFunc {
36842 this: date,
36843 interval: val,
36844 unit,
36845 },
36846 ))),
36847 }
36848 }
36849
36850 // DATEADD(unit, val, date) -> target-specific form
36851 // Used by: Redshift, Snowflake, TSQL, ClickHouse
36852 "DATEADD" if args.len() == 3 => {
36853 let arg0 = args.remove(0);
36854 let arg1 = args.remove(0);
36855 let arg2 = args.remove(0);
36856 let unit_str = get_unit_str(&arg0);
36857
36858 if matches!(target, DialectType::Snowflake | DialectType::TSQL) {
36859 // Keep DATEADD(UNIT, val, date) with uppercased unit
36860 let unit = Expression::Identifier(Identifier::new(unit_str));
36861 // Only CAST to DATETIME2 for TSQL target when source is NOT Spark/Databricks family
36862 let date = if matches!(target, DialectType::TSQL)
36863 && !matches!(
36864 source,
36865 DialectType::Spark | DialectType::Databricks | DialectType::Hive
36866 ) {
36867 Self::ensure_cast_datetime2(arg2)
36868 } else {
36869 arg2
36870 };
36871 return Ok(Expression::Function(Box::new(Function::new(
36872 "DATEADD".to_string(),
36873 vec![unit, arg1, date],
36874 ))));
36875 }
36876
36877 if matches!(target, DialectType::DuckDB) {
36878 // DuckDB: date + INTERVAL 'val' UNIT
36879 let iu = parse_interval_unit(&unit_str);
36880 let interval = Expression::Interval(Box::new(crate::expressions::Interval {
36881 this: Some(arg1),
36882 unit: Some(crate::expressions::IntervalUnitSpec::Simple {
36883 unit: iu,
36884 use_plural: false,
36885 }),
36886 }));
36887 let cast_date = Self::ensure_cast_timestamp(arg2);
36888 return Ok(Expression::Add(Box::new(
36889 crate::expressions::BinaryOp::new(cast_date, interval),
36890 )));
36891 }
36892
36893 if matches!(target, DialectType::BigQuery) {
36894 // BigQuery: DATE_ADD(date, INTERVAL val UNIT) or TIMESTAMP_ADD(ts, INTERVAL val UNIT)
36895 let iu = parse_interval_unit(&unit_str);
36896 let interval = Expression::Interval(Box::new(crate::expressions::Interval {
36897 this: Some(arg1),
36898 unit: Some(crate::expressions::IntervalUnitSpec::Simple {
36899 unit: iu,
36900 use_plural: false,
36901 }),
36902 }));
36903 return Ok(Expression::Function(Box::new(Function::new(
36904 "DATE_ADD".to_string(),
36905 vec![arg2, interval],
36906 ))));
36907 }
36908
36909 if matches!(target, DialectType::Databricks) {
36910 // Databricks: keep DATEADD(UNIT, val, date) format
36911 let unit = Expression::Identifier(Identifier::new(unit_str));
36912 return Ok(Expression::Function(Box::new(Function::new(
36913 "DATEADD".to_string(),
36914 vec![unit, arg1, arg2],
36915 ))));
36916 }
36917
36918 if matches!(target, DialectType::Spark) {
36919 // Spark: convert month-based units to ADD_MONTHS, rest to DATE_ADD
36920 fn multiply_expr_dateadd(expr: Expression, factor: i64) -> Expression {
36921 if let Expression::Literal(lit) = &expr {
36922 if let crate::expressions::Literal::Number(n) = lit.as_ref() {
36923 if let Ok(val) = n.parse::<i64>() {
36924 return Expression::Literal(Box::new(
36925 crate::expressions::Literal::Number(
36926 (val * factor).to_string(),
36927 ),
36928 ));
36929 }
36930 }
36931 }
36932 Expression::Mul(Box::new(crate::expressions::BinaryOp::new(
36933 expr,
36934 Expression::Literal(Box::new(crate::expressions::Literal::Number(
36935 factor.to_string(),
36936 ))),
36937 )))
36938 }
36939 match unit_str.as_str() {
36940 "YEAR" => {
36941 let months = multiply_expr_dateadd(arg1, 12);
36942 return Ok(Expression::Function(Box::new(Function::new(
36943 "ADD_MONTHS".to_string(),
36944 vec![arg2, months],
36945 ))));
36946 }
36947 "QUARTER" => {
36948 let months = multiply_expr_dateadd(arg1, 3);
36949 return Ok(Expression::Function(Box::new(Function::new(
36950 "ADD_MONTHS".to_string(),
36951 vec![arg2, months],
36952 ))));
36953 }
36954 "MONTH" => {
36955 return Ok(Expression::Function(Box::new(Function::new(
36956 "ADD_MONTHS".to_string(),
36957 vec![arg2, arg1],
36958 ))));
36959 }
36960 "WEEK" => {
36961 let days = multiply_expr_dateadd(arg1, 7);
36962 return Ok(Expression::Function(Box::new(Function::new(
36963 "DATE_ADD".to_string(),
36964 vec![arg2, days],
36965 ))));
36966 }
36967 "DAY" => {
36968 return Ok(Expression::Function(Box::new(Function::new(
36969 "DATE_ADD".to_string(),
36970 vec![arg2, arg1],
36971 ))));
36972 }
36973 _ => {
36974 let unit = Expression::Identifier(Identifier::new(unit_str));
36975 return Ok(Expression::Function(Box::new(Function::new(
36976 "DATE_ADD".to_string(),
36977 vec![unit, arg1, arg2],
36978 ))));
36979 }
36980 }
36981 }
36982
36983 if matches!(target, DialectType::Hive) {
36984 // Hive: DATE_ADD(date, val) for DAY, or date + INTERVAL for others
36985 match unit_str.as_str() {
36986 "DAY" => {
36987 return Ok(Expression::Function(Box::new(Function::new(
36988 "DATE_ADD".to_string(),
36989 vec![arg2, arg1],
36990 ))));
36991 }
36992 "MONTH" => {
36993 return Ok(Expression::Function(Box::new(Function::new(
36994 "ADD_MONTHS".to_string(),
36995 vec![arg2, arg1],
36996 ))));
36997 }
36998 _ => {
36999 let iu = parse_interval_unit(&unit_str);
37000 let interval =
37001 Expression::Interval(Box::new(crate::expressions::Interval {
37002 this: Some(arg1),
37003 unit: Some(crate::expressions::IntervalUnitSpec::Simple {
37004 unit: iu,
37005 use_plural: false,
37006 }),
37007 }));
37008 return Ok(Expression::Add(Box::new(
37009 crate::expressions::BinaryOp::new(arg2, interval),
37010 )));
37011 }
37012 }
37013 }
37014
37015 if matches!(target, DialectType::PostgreSQL) {
37016 // PostgreSQL: date + INTERVAL 'val UNIT'
37017 let interval = Expression::Interval(Box::new(crate::expressions::Interval {
37018 this: Some(Expression::Literal(Box::new(Literal::String(format!(
37019 "{} {}",
37020 Self::expr_to_string(&arg1),
37021 unit_str
37022 ))))),
37023 unit: None,
37024 }));
37025 return Ok(Expression::Add(Box::new(
37026 crate::expressions::BinaryOp::new(arg2, interval),
37027 )));
37028 }
37029
37030 if matches!(
37031 target,
37032 DialectType::Presto | DialectType::Trino | DialectType::Athena
37033 ) {
37034 // Presto/Trino: DATE_ADD('UNIT', val, date)
37035 return Ok(Expression::Function(Box::new(Function::new(
37036 "DATE_ADD".to_string(),
37037 vec![
37038 Expression::Literal(Box::new(Literal::String(unit_str))),
37039 arg1,
37040 arg2,
37041 ],
37042 ))));
37043 }
37044
37045 if matches!(target, DialectType::ClickHouse) {
37046 // ClickHouse: DATE_ADD(UNIT, val, date)
37047 let unit = Expression::Identifier(Identifier::new(unit_str));
37048 return Ok(Expression::Function(Box::new(Function::new(
37049 "DATE_ADD".to_string(),
37050 vec![unit, arg1, arg2],
37051 ))));
37052 }
37053
37054 // Default: keep DATEADD with uppercased unit
37055 let unit = Expression::Identifier(Identifier::new(unit_str));
37056 Ok(Expression::Function(Box::new(Function::new(
37057 "DATEADD".to_string(),
37058 vec![unit, arg1, arg2],
37059 ))))
37060 }
37061
37062 // DATE_ADD(unit, val, date) - 3 arg form from ClickHouse/Presto
37063 "DATE_ADD" if args.len() == 3 => {
37064 let arg0 = args.remove(0);
37065 let arg1 = args.remove(0);
37066 let arg2 = args.remove(0);
37067 let unit_str = get_unit_str(&arg0);
37068
37069 if matches!(
37070 target,
37071 DialectType::Presto | DialectType::Trino | DialectType::Athena
37072 ) {
37073 // Presto/Trino: DATE_ADD('UNIT', val, date)
37074 return Ok(Expression::Function(Box::new(Function::new(
37075 "DATE_ADD".to_string(),
37076 vec![
37077 Expression::Literal(Box::new(Literal::String(unit_str))),
37078 arg1,
37079 arg2,
37080 ],
37081 ))));
37082 }
37083
37084 if matches!(
37085 target,
37086 DialectType::Snowflake | DialectType::TSQL | DialectType::Redshift
37087 ) {
37088 // DATEADD(UNIT, val, date)
37089 let unit = Expression::Identifier(Identifier::new(unit_str));
37090 let date = if matches!(target, DialectType::TSQL) {
37091 Self::ensure_cast_datetime2(arg2)
37092 } else {
37093 arg2
37094 };
37095 return Ok(Expression::Function(Box::new(Function::new(
37096 "DATEADD".to_string(),
37097 vec![unit, arg1, date],
37098 ))));
37099 }
37100
37101 if matches!(target, DialectType::DuckDB) {
37102 // DuckDB: date + INTERVAL val UNIT
37103 let iu = parse_interval_unit(&unit_str);
37104 let interval = Expression::Interval(Box::new(crate::expressions::Interval {
37105 this: Some(arg1),
37106 unit: Some(crate::expressions::IntervalUnitSpec::Simple {
37107 unit: iu,
37108 use_plural: false,
37109 }),
37110 }));
37111 return Ok(Expression::Add(Box::new(
37112 crate::expressions::BinaryOp::new(arg2, interval),
37113 )));
37114 }
37115
37116 if matches!(target, DialectType::Spark | DialectType::Databricks) {
37117 // Spark: DATE_ADD(UNIT, val, date) with uppercased unit
37118 let unit = Expression::Identifier(Identifier::new(unit_str));
37119 return Ok(Expression::Function(Box::new(Function::new(
37120 "DATE_ADD".to_string(),
37121 vec![unit, arg1, arg2],
37122 ))));
37123 }
37124
37125 // Default: DATE_ADD(UNIT, val, date)
37126 let unit = Expression::Identifier(Identifier::new(unit_str));
37127 Ok(Expression::Function(Box::new(Function::new(
37128 "DATE_ADD".to_string(),
37129 vec![unit, arg1, arg2],
37130 ))))
37131 }
37132
37133 // DATE_ADD(date, INTERVAL val UNIT) - 2 arg BigQuery form
37134 "DATE_ADD" if args.len() == 2 => {
37135 let date = args.remove(0);
37136 let interval_expr = args.remove(0);
37137 let (val, unit) =
37138 Self::extract_interval_parts(&interval_expr).unwrap_or_else(|| {
37139 (interval_expr.clone(), crate::expressions::IntervalUnit::Day)
37140 });
37141 let unit_str = Self::interval_unit_to_string(&unit);
37142
37143 match target {
37144 DialectType::DuckDB => {
37145 // DuckDB: CAST(date AS DATE) + INTERVAL 'val' UNIT
37146 let cast_date = Self::ensure_cast_date(date);
37147 let quoted_val = Self::quote_interval_val(&val);
37148 let interval =
37149 Expression::Interval(Box::new(crate::expressions::Interval {
37150 this: Some(quoted_val),
37151 unit: Some(crate::expressions::IntervalUnitSpec::Simple {
37152 unit,
37153 use_plural: false,
37154 }),
37155 }));
37156 Ok(Expression::Add(Box::new(
37157 crate::expressions::BinaryOp::new(cast_date, interval),
37158 )))
37159 }
37160 DialectType::PostgreSQL => {
37161 // PostgreSQL: date + INTERVAL 'val UNIT'
37162 let interval =
37163 Expression::Interval(Box::new(crate::expressions::Interval {
37164 this: Some(Expression::Literal(Box::new(Literal::String(
37165 format!("{} {}", Self::expr_to_string(&val), unit_str),
37166 )))),
37167 unit: None,
37168 }));
37169 Ok(Expression::Add(Box::new(
37170 crate::expressions::BinaryOp::new(date, interval),
37171 )))
37172 }
37173 DialectType::Presto | DialectType::Trino | DialectType::Athena => {
37174 // Presto: DATE_ADD('UNIT', CAST('val' AS BIGINT), date)
37175 let val_str = Self::expr_to_string(&val);
37176 Ok(Expression::Function(Box::new(Function::new(
37177 "DATE_ADD".to_string(),
37178 vec![
37179 Expression::Literal(Box::new(Literal::String(
37180 unit_str.to_string(),
37181 ))),
37182 Expression::Cast(Box::new(Cast {
37183 this: Expression::Literal(Box::new(Literal::String(val_str))),
37184 to: DataType::BigInt { length: None },
37185 trailing_comments: vec![],
37186 double_colon_syntax: false,
37187 format: None,
37188 default: None,
37189 inferred_type: None,
37190 })),
37191 date,
37192 ],
37193 ))))
37194 }
37195 DialectType::Spark | DialectType::Hive => {
37196 // Spark/Hive: DATE_ADD(date, val) for DAY
37197 match unit_str {
37198 "DAY" => Ok(Expression::Function(Box::new(Function::new(
37199 "DATE_ADD".to_string(),
37200 vec![date, val],
37201 )))),
37202 "MONTH" => Ok(Expression::Function(Box::new(Function::new(
37203 "ADD_MONTHS".to_string(),
37204 vec![date, val],
37205 )))),
37206 _ => {
37207 let iu = parse_interval_unit(&unit_str);
37208 let interval =
37209 Expression::Interval(Box::new(crate::expressions::Interval {
37210 this: Some(val),
37211 unit: Some(crate::expressions::IntervalUnitSpec::Simple {
37212 unit: iu,
37213 use_plural: false,
37214 }),
37215 }));
37216 Ok(Expression::Function(Box::new(Function::new(
37217 "DATE_ADD".to_string(),
37218 vec![date, interval],
37219 ))))
37220 }
37221 }
37222 }
37223 DialectType::Snowflake => {
37224 // Snowflake: DATEADD(UNIT, 'val', CAST(date AS DATE))
37225 let cast_date = Self::ensure_cast_date(date);
37226 let val_str = Self::expr_to_string(&val);
37227 Ok(Expression::Function(Box::new(Function::new(
37228 "DATEADD".to_string(),
37229 vec![
37230 Expression::Identifier(Identifier::new(unit_str)),
37231 Expression::Literal(Box::new(Literal::String(val_str))),
37232 cast_date,
37233 ],
37234 ))))
37235 }
37236 DialectType::TSQL | DialectType::Fabric => {
37237 let cast_date = Self::ensure_cast_datetime2(date);
37238 Ok(Expression::Function(Box::new(Function::new(
37239 "DATEADD".to_string(),
37240 vec![
37241 Expression::Identifier(Identifier::new(unit_str)),
37242 val,
37243 cast_date,
37244 ],
37245 ))))
37246 }
37247 DialectType::Redshift => Ok(Expression::Function(Box::new(Function::new(
37248 "DATEADD".to_string(),
37249 vec![Expression::Identifier(Identifier::new(unit_str)), val, date],
37250 )))),
37251 DialectType::MySQL => {
37252 // MySQL: DATE_ADD(date, INTERVAL 'val' UNIT)
37253 let quoted_val = Self::quote_interval_val(&val);
37254 let iu = parse_interval_unit(&unit_str);
37255 let interval =
37256 Expression::Interval(Box::new(crate::expressions::Interval {
37257 this: Some(quoted_val),
37258 unit: Some(crate::expressions::IntervalUnitSpec::Simple {
37259 unit: iu,
37260 use_plural: false,
37261 }),
37262 }));
37263 Ok(Expression::Function(Box::new(Function::new(
37264 "DATE_ADD".to_string(),
37265 vec![date, interval],
37266 ))))
37267 }
37268 DialectType::BigQuery => {
37269 // BigQuery: DATE_ADD(date, INTERVAL 'val' UNIT)
37270 let quoted_val = Self::quote_interval_val(&val);
37271 let iu = parse_interval_unit(&unit_str);
37272 let interval =
37273 Expression::Interval(Box::new(crate::expressions::Interval {
37274 this: Some(quoted_val),
37275 unit: Some(crate::expressions::IntervalUnitSpec::Simple {
37276 unit: iu,
37277 use_plural: false,
37278 }),
37279 }));
37280 Ok(Expression::Function(Box::new(Function::new(
37281 "DATE_ADD".to_string(),
37282 vec![date, interval],
37283 ))))
37284 }
37285 DialectType::Databricks => Ok(Expression::Function(Box::new(Function::new(
37286 "DATEADD".to_string(),
37287 vec![Expression::Identifier(Identifier::new(unit_str)), val, date],
37288 )))),
37289 _ => {
37290 // Default: keep as DATE_ADD with decomposed interval
37291 Ok(Expression::DateAdd(Box::new(
37292 crate::expressions::DateAddFunc {
37293 this: date,
37294 interval: val,
37295 unit,
37296 },
37297 )))
37298 }
37299 }
37300 }
37301
37302 // ADD_MONTHS(date, val) -> target-specific form
37303 "ADD_MONTHS" if args.len() == 2 => {
37304 let date = args.remove(0);
37305 let val = args.remove(0);
37306
37307 if matches!(target, DialectType::TSQL) {
37308 // TSQL: DATEADD(MONTH, val, CAST(date AS DATETIME2))
37309 let cast_date = Self::ensure_cast_datetime2(date);
37310 return Ok(Expression::Function(Box::new(Function::new(
37311 "DATEADD".to_string(),
37312 vec![
37313 Expression::Identifier(Identifier::new("MONTH")),
37314 val,
37315 cast_date,
37316 ],
37317 ))));
37318 }
37319
37320 if matches!(target, DialectType::DuckDB) {
37321 // DuckDB: date + INTERVAL val MONTH
37322 let interval = Expression::Interval(Box::new(crate::expressions::Interval {
37323 this: Some(val),
37324 unit: Some(crate::expressions::IntervalUnitSpec::Simple {
37325 unit: crate::expressions::IntervalUnit::Month,
37326 use_plural: false,
37327 }),
37328 }));
37329 return Ok(Expression::Add(Box::new(
37330 crate::expressions::BinaryOp::new(date, interval),
37331 )));
37332 }
37333
37334 if matches!(target, DialectType::Snowflake) {
37335 // Snowflake: keep ADD_MONTHS when source is also Snowflake, else DATEADD
37336 if matches!(source, DialectType::Snowflake) {
37337 return Ok(Expression::Function(Box::new(Function::new(
37338 "ADD_MONTHS".to_string(),
37339 vec![date, val],
37340 ))));
37341 }
37342 return Ok(Expression::Function(Box::new(Function::new(
37343 "DATEADD".to_string(),
37344 vec![Expression::Identifier(Identifier::new("MONTH")), val, date],
37345 ))));
37346 }
37347
37348 if matches!(target, DialectType::Spark | DialectType::Databricks) {
37349 // Spark: ADD_MONTHS(date, val) - keep as is
37350 return Ok(Expression::Function(Box::new(Function::new(
37351 "ADD_MONTHS".to_string(),
37352 vec![date, val],
37353 ))));
37354 }
37355
37356 if matches!(target, DialectType::Hive) {
37357 return Ok(Expression::Function(Box::new(Function::new(
37358 "ADD_MONTHS".to_string(),
37359 vec![date, val],
37360 ))));
37361 }
37362
37363 if matches!(
37364 target,
37365 DialectType::Presto | DialectType::Trino | DialectType::Athena
37366 ) {
37367 // Presto: DATE_ADD('MONTH', val, date)
37368 return Ok(Expression::Function(Box::new(Function::new(
37369 "DATE_ADD".to_string(),
37370 vec![
37371 Expression::Literal(Box::new(Literal::String("MONTH".to_string()))),
37372 val,
37373 date,
37374 ],
37375 ))));
37376 }
37377
37378 // Default: keep ADD_MONTHS
37379 Ok(Expression::Function(Box::new(Function::new(
37380 "ADD_MONTHS".to_string(),
37381 vec![date, val],
37382 ))))
37383 }
37384
37385 // SAFE_DIVIDE(x, y) -> target-specific form directly
37386 "SAFE_DIVIDE" if args.len() == 2 => {
37387 let x = args.remove(0);
37388 let y = args.remove(0);
37389 // Wrap x and y in parens if they're complex expressions
37390 let y_ref = match &y {
37391 Expression::Column(_) | Expression::Literal(_) | Expression::Identifier(_) => {
37392 y.clone()
37393 }
37394 _ => Expression::Paren(Box::new(Paren {
37395 this: y.clone(),
37396 trailing_comments: vec![],
37397 })),
37398 };
37399 let x_ref = match &x {
37400 Expression::Column(_) | Expression::Literal(_) | Expression::Identifier(_) => {
37401 x.clone()
37402 }
37403 _ => Expression::Paren(Box::new(Paren {
37404 this: x.clone(),
37405 trailing_comments: vec![],
37406 })),
37407 };
37408 let condition = Expression::Neq(Box::new(crate::expressions::BinaryOp::new(
37409 y_ref.clone(),
37410 Expression::number(0),
37411 )));
37412 let div_expr = Expression::Div(Box::new(crate::expressions::BinaryOp::new(
37413 x_ref.clone(),
37414 y_ref.clone(),
37415 )));
37416
37417 match target {
37418 DialectType::Spark | DialectType::Databricks => Ok(Expression::Function(
37419 Box::new(Function::new("TRY_DIVIDE".to_string(), vec![x, y])),
37420 )),
37421 DialectType::DuckDB | DialectType::PostgreSQL => {
37422 // CASE WHEN y <> 0 THEN x / y ELSE NULL END
37423 let result_div = if matches!(target, DialectType::PostgreSQL) {
37424 let cast_x = Expression::Cast(Box::new(Cast {
37425 this: x_ref,
37426 to: DataType::Custom {
37427 name: "DOUBLE PRECISION".to_string(),
37428 },
37429 trailing_comments: vec![],
37430 double_colon_syntax: false,
37431 format: None,
37432 default: None,
37433 inferred_type: None,
37434 }));
37435 Expression::Div(Box::new(crate::expressions::BinaryOp::new(
37436 cast_x, y_ref,
37437 )))
37438 } else {
37439 div_expr
37440 };
37441 Ok(Expression::Case(Box::new(crate::expressions::Case {
37442 operand: None,
37443 whens: vec![(condition, result_div)],
37444 else_: Some(Expression::Null(crate::expressions::Null)),
37445 comments: Vec::new(),
37446 inferred_type: None,
37447 })))
37448 }
37449 DialectType::Snowflake => {
37450 // IFF(y <> 0, x / y, NULL)
37451 Ok(Expression::IfFunc(Box::new(crate::expressions::IfFunc {
37452 condition,
37453 true_value: div_expr,
37454 false_value: Some(Expression::Null(crate::expressions::Null)),
37455 original_name: Some("IFF".to_string()),
37456 inferred_type: None,
37457 })))
37458 }
37459 DialectType::Presto | DialectType::Trino => {
37460 // IF(y <> 0, CAST(x AS DOUBLE) / y, NULL)
37461 let cast_x = Expression::Cast(Box::new(Cast {
37462 this: x_ref,
37463 to: DataType::Double {
37464 precision: None,
37465 scale: None,
37466 },
37467 trailing_comments: vec![],
37468 double_colon_syntax: false,
37469 format: None,
37470 default: None,
37471 inferred_type: None,
37472 }));
37473 let cast_div = Expression::Div(Box::new(
37474 crate::expressions::BinaryOp::new(cast_x, y_ref),
37475 ));
37476 Ok(Expression::IfFunc(Box::new(crate::expressions::IfFunc {
37477 condition,
37478 true_value: cast_div,
37479 false_value: Some(Expression::Null(crate::expressions::Null)),
37480 original_name: None,
37481 inferred_type: None,
37482 })))
37483 }
37484 _ => {
37485 // IF(y <> 0, x / y, NULL)
37486 Ok(Expression::IfFunc(Box::new(crate::expressions::IfFunc {
37487 condition,
37488 true_value: div_expr,
37489 false_value: Some(Expression::Null(crate::expressions::Null)),
37490 original_name: None,
37491 inferred_type: None,
37492 })))
37493 }
37494 }
37495 }
37496
37497 // GENERATE_UUID() -> UUID() with CAST to string
37498 "GENERATE_UUID" => {
37499 let uuid_expr = Expression::Uuid(Box::new(crate::expressions::Uuid {
37500 this: None,
37501 name: None,
37502 is_string: None,
37503 }));
37504 // Most targets need CAST(UUID() AS TEXT/VARCHAR/STRING)
37505 let cast_type = match target {
37506 DialectType::DuckDB => Some(DataType::Text),
37507 DialectType::Presto | DialectType::Trino => Some(DataType::VarChar {
37508 length: None,
37509 parenthesized_length: false,
37510 }),
37511 DialectType::Spark | DialectType::Databricks | DialectType::Hive => {
37512 Some(DataType::String { length: None })
37513 }
37514 _ => None,
37515 };
37516 if let Some(dt) = cast_type {
37517 Ok(Expression::Cast(Box::new(Cast {
37518 this: uuid_expr,
37519 to: dt,
37520 trailing_comments: vec![],
37521 double_colon_syntax: false,
37522 format: None,
37523 default: None,
37524 inferred_type: None,
37525 })))
37526 } else {
37527 Ok(uuid_expr)
37528 }
37529 }
37530
37531 // COUNTIF(x) -> CountIf expression
37532 "COUNTIF" if args.len() == 1 => {
37533 let arg = args.remove(0);
37534 Ok(Expression::CountIf(Box::new(crate::expressions::AggFunc {
37535 this: arg,
37536 distinct: false,
37537 filter: None,
37538 order_by: vec![],
37539 name: None,
37540 ignore_nulls: None,
37541 having_max: None,
37542 limit: None,
37543 inferred_type: None,
37544 })))
37545 }
37546
37547 // EDIT_DISTANCE(col1, col2, ...) -> Levenshtein expression
37548 "EDIT_DISTANCE" => {
37549 // Strip named arguments (max_distance => N) and pass as positional
37550 let mut positional_args: Vec<Expression> = vec![];
37551 for arg in args {
37552 match arg {
37553 Expression::NamedArgument(na) => {
37554 positional_args.push(na.value);
37555 }
37556 other => positional_args.push(other),
37557 }
37558 }
37559 if positional_args.len() >= 2 {
37560 let col1 = positional_args.remove(0);
37561 let col2 = positional_args.remove(0);
37562 let levenshtein = crate::expressions::BinaryFunc {
37563 this: col1,
37564 expression: col2,
37565 original_name: None,
37566 inferred_type: None,
37567 };
37568 // Pass extra args through a function wrapper with all args
37569 if !positional_args.is_empty() {
37570 let max_dist = positional_args.remove(0);
37571 // DuckDB: CASE WHEN LEVENSHTEIN(a, b) IS NULL OR max IS NULL THEN NULL ELSE LEAST(LEVENSHTEIN(a, b), max) END
37572 if matches!(target, DialectType::DuckDB) {
37573 let lev = Expression::Function(Box::new(Function::new(
37574 "LEVENSHTEIN".to_string(),
37575 vec![levenshtein.this, levenshtein.expression],
37576 )));
37577 let lev_is_null =
37578 Expression::IsNull(Box::new(crate::expressions::IsNull {
37579 this: lev.clone(),
37580 not: false,
37581 postfix_form: false,
37582 }));
37583 let max_is_null =
37584 Expression::IsNull(Box::new(crate::expressions::IsNull {
37585 this: max_dist.clone(),
37586 not: false,
37587 postfix_form: false,
37588 }));
37589 let null_check =
37590 Expression::Or(Box::new(crate::expressions::BinaryOp {
37591 left: lev_is_null,
37592 right: max_is_null,
37593 left_comments: Vec::new(),
37594 operator_comments: Vec::new(),
37595 trailing_comments: Vec::new(),
37596 inferred_type: None,
37597 }));
37598 let least =
37599 Expression::Least(Box::new(crate::expressions::VarArgFunc {
37600 expressions: vec![lev, max_dist],
37601 original_name: None,
37602 inferred_type: None,
37603 }));
37604 return Ok(Expression::Case(Box::new(crate::expressions::Case {
37605 operand: None,
37606 whens: vec![(
37607 null_check,
37608 Expression::Null(crate::expressions::Null),
37609 )],
37610 else_: Some(least),
37611 comments: Vec::new(),
37612 inferred_type: None,
37613 })));
37614 }
37615 let mut all_args = vec![levenshtein.this, levenshtein.expression, max_dist];
37616 all_args.extend(positional_args);
37617 // PostgreSQL: use LEVENSHTEIN_LESS_EQUAL when max_distance is provided
37618 let func_name = if matches!(target, DialectType::PostgreSQL) {
37619 "LEVENSHTEIN_LESS_EQUAL"
37620 } else {
37621 "LEVENSHTEIN"
37622 };
37623 return Ok(Expression::Function(Box::new(Function::new(
37624 func_name.to_string(),
37625 all_args,
37626 ))));
37627 }
37628 Ok(Expression::Levenshtein(Box::new(levenshtein)))
37629 } else {
37630 Ok(Expression::Function(Box::new(Function::new(
37631 "EDIT_DISTANCE".to_string(),
37632 positional_args,
37633 ))))
37634 }
37635 }
37636
37637 // TIMESTAMP_SECONDS(x) -> UnixToTime with scale 0
37638 "TIMESTAMP_SECONDS" if args.len() == 1 => {
37639 let arg = args.remove(0);
37640 Ok(Expression::UnixToTime(Box::new(
37641 crate::expressions::UnixToTime {
37642 this: Box::new(arg),
37643 scale: Some(0),
37644 zone: None,
37645 hours: None,
37646 minutes: None,
37647 format: None,
37648 target_type: None,
37649 },
37650 )))
37651 }
37652
37653 // TIMESTAMP_MILLIS(x) -> UnixToTime with scale 3
37654 "TIMESTAMP_MILLIS" if args.len() == 1 => {
37655 let arg = args.remove(0);
37656 Ok(Expression::UnixToTime(Box::new(
37657 crate::expressions::UnixToTime {
37658 this: Box::new(arg),
37659 scale: Some(3),
37660 zone: None,
37661 hours: None,
37662 minutes: None,
37663 format: None,
37664 target_type: None,
37665 },
37666 )))
37667 }
37668
37669 // TIMESTAMP_MICROS(x) -> UnixToTime with scale 6
37670 "TIMESTAMP_MICROS" if args.len() == 1 => {
37671 let arg = args.remove(0);
37672 Ok(Expression::UnixToTime(Box::new(
37673 crate::expressions::UnixToTime {
37674 this: Box::new(arg),
37675 scale: Some(6),
37676 zone: None,
37677 hours: None,
37678 minutes: None,
37679 format: None,
37680 target_type: None,
37681 },
37682 )))
37683 }
37684
37685 // DIV(x, y) -> IntDiv expression
37686 "DIV" if args.len() == 2 => {
37687 let x = args.remove(0);
37688 let y = args.remove(0);
37689 Ok(Expression::IntDiv(Box::new(
37690 crate::expressions::BinaryFunc {
37691 this: x,
37692 expression: y,
37693 original_name: None,
37694 inferred_type: None,
37695 },
37696 )))
37697 }
37698
37699 // TO_HEX(x) -> target-specific form
37700 "TO_HEX" if args.len() == 1 => {
37701 let arg = args.remove(0);
37702 // Check if inner function already returns hex string in certain targets
37703 let inner_returns_hex = matches!(&arg, Expression::Function(f) if matches!(f.name.as_str(), "MD5" | "SHA1" | "SHA256" | "SHA512"));
37704 if matches!(target, DialectType::BigQuery) {
37705 // BQ->BQ: keep as TO_HEX
37706 Ok(Expression::Function(Box::new(Function::new(
37707 "TO_HEX".to_string(),
37708 vec![arg],
37709 ))))
37710 } else if matches!(target, DialectType::DuckDB) && inner_returns_hex {
37711 // DuckDB: MD5/SHA already return hex strings, so TO_HEX is redundant
37712 Ok(arg)
37713 } else if matches!(target, DialectType::Snowflake) && inner_returns_hex {
37714 // Snowflake: TO_HEX(SHA1(x)) -> TO_CHAR(SHA1_BINARY(x))
37715 // TO_HEX(MD5(x)) -> TO_CHAR(MD5_BINARY(x))
37716 // TO_HEX(SHA256(x)) -> TO_CHAR(SHA2_BINARY(x, 256))
37717 // TO_HEX(SHA512(x)) -> TO_CHAR(SHA2_BINARY(x, 512))
37718 if let Expression::Function(ref inner_f) = arg {
37719 let inner_args = inner_f.args.clone();
37720 let binary_func = match inner_f.name.to_ascii_uppercase().as_str() {
37721 "SHA1" => Expression::Function(Box::new(Function::new(
37722 "SHA1_BINARY".to_string(),
37723 inner_args,
37724 ))),
37725 "MD5" => Expression::Function(Box::new(Function::new(
37726 "MD5_BINARY".to_string(),
37727 inner_args,
37728 ))),
37729 "SHA256" => {
37730 let mut a = inner_args;
37731 a.push(Expression::number(256));
37732 Expression::Function(Box::new(Function::new(
37733 "SHA2_BINARY".to_string(),
37734 a,
37735 )))
37736 }
37737 "SHA512" => {
37738 let mut a = inner_args;
37739 a.push(Expression::number(512));
37740 Expression::Function(Box::new(Function::new(
37741 "SHA2_BINARY".to_string(),
37742 a,
37743 )))
37744 }
37745 _ => arg.clone(),
37746 };
37747 Ok(Expression::Function(Box::new(Function::new(
37748 "TO_CHAR".to_string(),
37749 vec![binary_func],
37750 ))))
37751 } else {
37752 let inner = Expression::Function(Box::new(Function::new(
37753 "HEX".to_string(),
37754 vec![arg],
37755 )));
37756 Ok(Expression::Lower(Box::new(
37757 crate::expressions::UnaryFunc::new(inner),
37758 )))
37759 }
37760 } else if matches!(target, DialectType::Presto | DialectType::Trino) {
37761 let inner = Expression::Function(Box::new(Function::new(
37762 "TO_HEX".to_string(),
37763 vec![arg],
37764 )));
37765 Ok(Expression::Lower(Box::new(
37766 crate::expressions::UnaryFunc::new(inner),
37767 )))
37768 } else {
37769 let inner =
37770 Expression::Function(Box::new(Function::new("HEX".to_string(), vec![arg])));
37771 Ok(Expression::Lower(Box::new(
37772 crate::expressions::UnaryFunc::new(inner),
37773 )))
37774 }
37775 }
37776
37777 // LAST_DAY(date, unit) -> strip unit for most targets, or transform for PostgreSQL
37778 "LAST_DAY" if args.len() == 2 => {
37779 let date = args.remove(0);
37780 let _unit = args.remove(0); // Strip the unit (MONTH is default)
37781 Ok(Expression::Function(Box::new(Function::new(
37782 "LAST_DAY".to_string(),
37783 vec![date],
37784 ))))
37785 }
37786
37787 // GENERATE_ARRAY(start, end, step?) -> GenerateSeries expression
37788 "GENERATE_ARRAY" => {
37789 let start = args.get(0).cloned();
37790 let end = args.get(1).cloned();
37791 let step = args.get(2).cloned();
37792 Ok(Expression::GenerateSeries(Box::new(
37793 crate::expressions::GenerateSeries {
37794 start: start.map(Box::new),
37795 end: end.map(Box::new),
37796 step: step.map(Box::new),
37797 is_end_exclusive: None,
37798 },
37799 )))
37800 }
37801
37802 // GENERATE_TIMESTAMP_ARRAY(start, end, step) -> GenerateSeries expression
37803 "GENERATE_TIMESTAMP_ARRAY" => {
37804 let start = args.get(0).cloned();
37805 let end = args.get(1).cloned();
37806 let step = args.get(2).cloned();
37807
37808 if matches!(target, DialectType::DuckDB) {
37809 // DuckDB: GENERATE_SERIES(CAST(start AS TIMESTAMP), CAST(end AS TIMESTAMP), step)
37810 // Only cast string literals - leave columns/expressions as-is
37811 let maybe_cast_ts = |expr: Expression| -> Expression {
37812 if matches!(&expr, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
37813 {
37814 Expression::Cast(Box::new(Cast {
37815 this: expr,
37816 to: DataType::Timestamp {
37817 precision: None,
37818 timezone: false,
37819 },
37820 trailing_comments: vec![],
37821 double_colon_syntax: false,
37822 format: None,
37823 default: None,
37824 inferred_type: None,
37825 }))
37826 } else {
37827 expr
37828 }
37829 };
37830 let cast_start = start.map(maybe_cast_ts);
37831 let cast_end = end.map(maybe_cast_ts);
37832 Ok(Expression::GenerateSeries(Box::new(
37833 crate::expressions::GenerateSeries {
37834 start: cast_start.map(Box::new),
37835 end: cast_end.map(Box::new),
37836 step: step.map(Box::new),
37837 is_end_exclusive: None,
37838 },
37839 )))
37840 } else {
37841 Ok(Expression::GenerateSeries(Box::new(
37842 crate::expressions::GenerateSeries {
37843 start: start.map(Box::new),
37844 end: end.map(Box::new),
37845 step: step.map(Box::new),
37846 is_end_exclusive: None,
37847 },
37848 )))
37849 }
37850 }
37851
37852 // TO_JSON(x) -> target-specific (from Spark/Hive)
37853 "TO_JSON" => {
37854 match target {
37855 DialectType::Presto | DialectType::Trino => {
37856 // JSON_FORMAT(CAST(x AS JSON))
37857 let arg = args
37858 .into_iter()
37859 .next()
37860 .unwrap_or(Expression::Null(crate::expressions::Null));
37861 let cast_json = Expression::Cast(Box::new(Cast {
37862 this: arg,
37863 to: DataType::Custom {
37864 name: "JSON".to_string(),
37865 },
37866 trailing_comments: vec![],
37867 double_colon_syntax: false,
37868 format: None,
37869 default: None,
37870 inferred_type: None,
37871 }));
37872 Ok(Expression::Function(Box::new(Function::new(
37873 "JSON_FORMAT".to_string(),
37874 vec![cast_json],
37875 ))))
37876 }
37877 DialectType::BigQuery => Ok(Expression::Function(Box::new(Function::new(
37878 "TO_JSON_STRING".to_string(),
37879 args,
37880 )))),
37881 DialectType::DuckDB => {
37882 // CAST(TO_JSON(x) AS TEXT)
37883 let arg = args
37884 .into_iter()
37885 .next()
37886 .unwrap_or(Expression::Null(crate::expressions::Null));
37887 let to_json = Expression::Function(Box::new(Function::new(
37888 "TO_JSON".to_string(),
37889 vec![arg],
37890 )));
37891 Ok(Expression::Cast(Box::new(Cast {
37892 this: to_json,
37893 to: DataType::Text,
37894 trailing_comments: vec![],
37895 double_colon_syntax: false,
37896 format: None,
37897 default: None,
37898 inferred_type: None,
37899 })))
37900 }
37901 _ => Ok(Expression::Function(Box::new(Function::new(
37902 "TO_JSON".to_string(),
37903 args,
37904 )))),
37905 }
37906 }
37907
37908 // TO_JSON_STRING(x) -> target-specific
37909 "TO_JSON_STRING" => {
37910 match target {
37911 DialectType::Spark | DialectType::Databricks | DialectType::Hive => Ok(
37912 Expression::Function(Box::new(Function::new("TO_JSON".to_string(), args))),
37913 ),
37914 DialectType::Presto | DialectType::Trino => {
37915 // JSON_FORMAT(CAST(x AS JSON))
37916 let arg = args
37917 .into_iter()
37918 .next()
37919 .unwrap_or(Expression::Null(crate::expressions::Null));
37920 let cast_json = Expression::Cast(Box::new(Cast {
37921 this: arg,
37922 to: DataType::Custom {
37923 name: "JSON".to_string(),
37924 },
37925 trailing_comments: vec![],
37926 double_colon_syntax: false,
37927 format: None,
37928 default: None,
37929 inferred_type: None,
37930 }));
37931 Ok(Expression::Function(Box::new(Function::new(
37932 "JSON_FORMAT".to_string(),
37933 vec![cast_json],
37934 ))))
37935 }
37936 DialectType::DuckDB => {
37937 // CAST(TO_JSON(x) AS TEXT)
37938 let arg = args
37939 .into_iter()
37940 .next()
37941 .unwrap_or(Expression::Null(crate::expressions::Null));
37942 let to_json = Expression::Function(Box::new(Function::new(
37943 "TO_JSON".to_string(),
37944 vec![arg],
37945 )));
37946 Ok(Expression::Cast(Box::new(Cast {
37947 this: to_json,
37948 to: DataType::Text,
37949 trailing_comments: vec![],
37950 double_colon_syntax: false,
37951 format: None,
37952 default: None,
37953 inferred_type: None,
37954 })))
37955 }
37956 DialectType::Snowflake => {
37957 // TO_JSON(x)
37958 Ok(Expression::Function(Box::new(Function::new(
37959 "TO_JSON".to_string(),
37960 args,
37961 ))))
37962 }
37963 _ => Ok(Expression::Function(Box::new(Function::new(
37964 "TO_JSON_STRING".to_string(),
37965 args,
37966 )))),
37967 }
37968 }
37969
37970 // SAFE_ADD(x, y) -> SafeAdd expression
37971 "SAFE_ADD" if args.len() == 2 => {
37972 let x = args.remove(0);
37973 let y = args.remove(0);
37974 Ok(Expression::SafeAdd(Box::new(crate::expressions::SafeAdd {
37975 this: Box::new(x),
37976 expression: Box::new(y),
37977 })))
37978 }
37979
37980 // SAFE_SUBTRACT(x, y) -> SafeSubtract expression
37981 "SAFE_SUBTRACT" if args.len() == 2 => {
37982 let x = args.remove(0);
37983 let y = args.remove(0);
37984 Ok(Expression::SafeSubtract(Box::new(
37985 crate::expressions::SafeSubtract {
37986 this: Box::new(x),
37987 expression: Box::new(y),
37988 },
37989 )))
37990 }
37991
37992 // SAFE_MULTIPLY(x, y) -> SafeMultiply expression
37993 "SAFE_MULTIPLY" if args.len() == 2 => {
37994 let x = args.remove(0);
37995 let y = args.remove(0);
37996 Ok(Expression::SafeMultiply(Box::new(
37997 crate::expressions::SafeMultiply {
37998 this: Box::new(x),
37999 expression: Box::new(y),
38000 },
38001 )))
38002 }
38003
38004 // REGEXP_CONTAINS(str, pattern) -> RegexpLike expression
38005 "REGEXP_CONTAINS" if args.len() == 2 => {
38006 let str_expr = args.remove(0);
38007 let pattern = args.remove(0);
38008 Ok(Expression::RegexpLike(Box::new(
38009 crate::expressions::RegexpFunc {
38010 this: str_expr,
38011 pattern,
38012 flags: None,
38013 },
38014 )))
38015 }
38016
38017 // CONTAINS_SUBSTR(a, b) -> CONTAINS(LOWER(a), LOWER(b))
38018 "CONTAINS_SUBSTR" if args.len() == 2 => {
38019 let a = args.remove(0);
38020 let b = args.remove(0);
38021 let lower_a = Expression::Lower(Box::new(crate::expressions::UnaryFunc::new(a)));
38022 let lower_b = Expression::Lower(Box::new(crate::expressions::UnaryFunc::new(b)));
38023 Ok(Expression::Function(Box::new(Function::new(
38024 "CONTAINS".to_string(),
38025 vec![lower_a, lower_b],
38026 ))))
38027 }
38028
38029 // INT64(x) -> CAST(x AS BIGINT)
38030 "INT64" if args.len() == 1 => {
38031 let arg = args.remove(0);
38032 Ok(Expression::Cast(Box::new(Cast {
38033 this: arg,
38034 to: DataType::BigInt { length: None },
38035 trailing_comments: vec![],
38036 double_colon_syntax: false,
38037 format: None,
38038 default: None,
38039 inferred_type: None,
38040 })))
38041 }
38042
38043 // INSTR(str, substr) -> target-specific
38044 "INSTR" if args.len() >= 2 => {
38045 let str_expr = args.remove(0);
38046 let substr = args.remove(0);
38047 if matches!(target, DialectType::Snowflake) {
38048 // CHARINDEX(substr, str)
38049 Ok(Expression::Function(Box::new(Function::new(
38050 "CHARINDEX".to_string(),
38051 vec![substr, str_expr],
38052 ))))
38053 } else if matches!(target, DialectType::BigQuery) {
38054 // Keep as INSTR
38055 Ok(Expression::Function(Box::new(Function::new(
38056 "INSTR".to_string(),
38057 vec![str_expr, substr],
38058 ))))
38059 } else {
38060 // Default: keep as INSTR
38061 Ok(Expression::Function(Box::new(Function::new(
38062 "INSTR".to_string(),
38063 vec![str_expr, substr],
38064 ))))
38065 }
38066 }
38067
38068 // BigQuery DATE_TRUNC(expr, unit) -> DATE_TRUNC('unit', expr) for standard SQL
38069 "DATE_TRUNC" if args.len() == 2 => {
38070 let expr = args.remove(0);
38071 let unit_expr = args.remove(0);
38072 let unit_str = get_unit_str(&unit_expr);
38073
38074 match target {
38075 DialectType::DuckDB
38076 | DialectType::Snowflake
38077 | DialectType::PostgreSQL
38078 | DialectType::Presto
38079 | DialectType::Trino
38080 | DialectType::Databricks
38081 | DialectType::Spark
38082 | DialectType::Redshift
38083 | DialectType::ClickHouse
38084 | DialectType::TSQL => {
38085 // Standard: DATE_TRUNC('UNIT', expr)
38086 Ok(Expression::Function(Box::new(Function::new(
38087 "DATE_TRUNC".to_string(),
38088 vec![
38089 Expression::Literal(Box::new(Literal::String(unit_str))),
38090 expr,
38091 ],
38092 ))))
38093 }
38094 _ => {
38095 // Keep BigQuery arg order: DATE_TRUNC(expr, unit)
38096 Ok(Expression::Function(Box::new(Function::new(
38097 "DATE_TRUNC".to_string(),
38098 vec![expr, unit_expr],
38099 ))))
38100 }
38101 }
38102 }
38103
38104 // TIMESTAMP_TRUNC / DATETIME_TRUNC -> target-specific
38105 "TIMESTAMP_TRUNC" | "DATETIME_TRUNC" if args.len() >= 2 => {
38106 // TIMESTAMP_TRUNC(ts, unit) or TIMESTAMP_TRUNC(ts, unit, timezone)
38107 let ts = args.remove(0);
38108 let unit_expr = args.remove(0);
38109 let tz = if !args.is_empty() {
38110 Some(args.remove(0))
38111 } else {
38112 None
38113 };
38114 let unit_str = get_unit_str(&unit_expr);
38115
38116 match target {
38117 DialectType::DuckDB => {
38118 // DuckDB: DATE_TRUNC('UNIT', CAST(ts AS TIMESTAMPTZ))
38119 // With timezone: DATE_TRUNC('UNIT', ts AT TIME ZONE 'tz') AT TIME ZONE 'tz' (for DAY granularity)
38120 // Without timezone for MINUTE+ granularity: just DATE_TRUNC
38121 let is_coarse = matches!(
38122 unit_str.as_str(),
38123 "DAY" | "WEEK" | "MONTH" | "QUARTER" | "YEAR"
38124 );
38125 // For DATETIME_TRUNC, cast string args to TIMESTAMP
38126 let cast_ts = if name == "DATETIME_TRUNC" {
38127 match ts {
38128 Expression::Literal(ref lit)
38129 if matches!(lit.as_ref(), Literal::String(ref _s)) =>
38130 {
38131 Expression::Cast(Box::new(Cast {
38132 this: ts,
38133 to: DataType::Timestamp {
38134 precision: None,
38135 timezone: false,
38136 },
38137 trailing_comments: vec![],
38138 double_colon_syntax: false,
38139 format: None,
38140 default: None,
38141 inferred_type: None,
38142 }))
38143 }
38144 _ => Self::maybe_cast_ts_to_tz(ts, &name),
38145 }
38146 } else {
38147 Self::maybe_cast_ts_to_tz(ts, &name)
38148 };
38149
38150 if let Some(tz_arg) = tz {
38151 if is_coarse {
38152 // DATE_TRUNC('UNIT', ts AT TIME ZONE 'tz') AT TIME ZONE 'tz'
38153 let at_tz = Expression::AtTimeZone(Box::new(
38154 crate::expressions::AtTimeZone {
38155 this: cast_ts,
38156 zone: tz_arg.clone(),
38157 },
38158 ));
38159 let date_trunc = Expression::Function(Box::new(Function::new(
38160 "DATE_TRUNC".to_string(),
38161 vec![
38162 Expression::Literal(Box::new(Literal::String(unit_str))),
38163 at_tz,
38164 ],
38165 )));
38166 Ok(Expression::AtTimeZone(Box::new(
38167 crate::expressions::AtTimeZone {
38168 this: date_trunc,
38169 zone: tz_arg,
38170 },
38171 )))
38172 } else {
38173 // For MINUTE/HOUR: no AT TIME ZONE wrapper, just DATE_TRUNC('UNIT', ts)
38174 Ok(Expression::Function(Box::new(Function::new(
38175 "DATE_TRUNC".to_string(),
38176 vec![
38177 Expression::Literal(Box::new(Literal::String(unit_str))),
38178 cast_ts,
38179 ],
38180 ))))
38181 }
38182 } else {
38183 // No timezone: DATE_TRUNC('UNIT', CAST(ts AS TIMESTAMPTZ))
38184 Ok(Expression::Function(Box::new(Function::new(
38185 "DATE_TRUNC".to_string(),
38186 vec![
38187 Expression::Literal(Box::new(Literal::String(unit_str))),
38188 cast_ts,
38189 ],
38190 ))))
38191 }
38192 }
38193 DialectType::Databricks | DialectType::Spark => {
38194 // Databricks/Spark: DATE_TRUNC('UNIT', ts)
38195 Ok(Expression::Function(Box::new(Function::new(
38196 "DATE_TRUNC".to_string(),
38197 vec![Expression::Literal(Box::new(Literal::String(unit_str))), ts],
38198 ))))
38199 }
38200 _ => {
38201 // Default: keep as TIMESTAMP_TRUNC('UNIT', ts, [tz])
38202 let unit = Expression::Literal(Box::new(Literal::String(unit_str)));
38203 let mut date_trunc_args = vec![unit, ts];
38204 if let Some(tz_arg) = tz {
38205 date_trunc_args.push(tz_arg);
38206 }
38207 Ok(Expression::Function(Box::new(Function::new(
38208 "TIMESTAMP_TRUNC".to_string(),
38209 date_trunc_args,
38210 ))))
38211 }
38212 }
38213 }
38214
38215 // TIME(h, m, s) -> target-specific, TIME('string') -> CAST('string' AS TIME)
38216 "TIME" => {
38217 if args.len() == 3 {
38218 // TIME(h, m, s) constructor
38219 match target {
38220 DialectType::TSQL => {
38221 // TIMEFROMPARTS(h, m, s, 0, 0)
38222 args.push(Expression::number(0));
38223 args.push(Expression::number(0));
38224 Ok(Expression::Function(Box::new(Function::new(
38225 "TIMEFROMPARTS".to_string(),
38226 args,
38227 ))))
38228 }
38229 DialectType::MySQL => Ok(Expression::Function(Box::new(Function::new(
38230 "MAKETIME".to_string(),
38231 args,
38232 )))),
38233 DialectType::PostgreSQL => Ok(Expression::Function(Box::new(
38234 Function::new("MAKE_TIME".to_string(), args),
38235 ))),
38236 _ => Ok(Expression::Function(Box::new(Function::new(
38237 "TIME".to_string(),
38238 args,
38239 )))),
38240 }
38241 } else if args.len() == 1 {
38242 let arg = args.remove(0);
38243 if matches!(target, DialectType::Spark) {
38244 // Spark: CAST(x AS TIMESTAMP) (yes, TIMESTAMP not TIME)
38245 Ok(Expression::Cast(Box::new(Cast {
38246 this: arg,
38247 to: DataType::Timestamp {
38248 timezone: false,
38249 precision: None,
38250 },
38251 trailing_comments: vec![],
38252 double_colon_syntax: false,
38253 format: None,
38254 default: None,
38255 inferred_type: None,
38256 })))
38257 } else {
38258 // Most targets: CAST(x AS TIME)
38259 Ok(Expression::Cast(Box::new(Cast {
38260 this: arg,
38261 to: DataType::Time {
38262 precision: None,
38263 timezone: false,
38264 },
38265 trailing_comments: vec![],
38266 double_colon_syntax: false,
38267 format: None,
38268 default: None,
38269 inferred_type: None,
38270 })))
38271 }
38272 } else if args.len() == 2 {
38273 // TIME(expr, timezone) -> CAST(CAST(expr AS TIMESTAMPTZ) AT TIME ZONE tz AS TIME)
38274 let expr = args.remove(0);
38275 let tz = args.remove(0);
38276 let cast_tstz = Expression::Cast(Box::new(Cast {
38277 this: expr,
38278 to: DataType::Timestamp {
38279 timezone: true,
38280 precision: None,
38281 },
38282 trailing_comments: vec![],
38283 double_colon_syntax: false,
38284 format: None,
38285 default: None,
38286 inferred_type: None,
38287 }));
38288 let at_tz = Expression::AtTimeZone(Box::new(crate::expressions::AtTimeZone {
38289 this: cast_tstz,
38290 zone: tz,
38291 }));
38292 Ok(Expression::Cast(Box::new(Cast {
38293 this: at_tz,
38294 to: DataType::Time {
38295 precision: None,
38296 timezone: false,
38297 },
38298 trailing_comments: vec![],
38299 double_colon_syntax: false,
38300 format: None,
38301 default: None,
38302 inferred_type: None,
38303 })))
38304 } else {
38305 Ok(Expression::Function(Box::new(Function::new(
38306 "TIME".to_string(),
38307 args,
38308 ))))
38309 }
38310 }
38311
38312 // DATETIME('string') -> CAST('string' AS TIMESTAMP)
38313 // DATETIME('date', TIME 'time') -> CAST(CAST('date' AS DATE) + CAST('time' AS TIME) AS TIMESTAMP)
38314 // DATETIME('string', 'timezone') -> CAST(CAST('string' AS TIMESTAMPTZ) AT TIME ZONE tz AS TIMESTAMP)
38315 // DATETIME(y, m, d, h, min, s) -> target-specific
38316 "DATETIME" => {
38317 // For BigQuery target: keep DATETIME function but convert TIME literal to CAST
38318 if matches!(target, DialectType::BigQuery) {
38319 if args.len() == 2 {
38320 let has_time_literal = matches!(&args[1], Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Time(_)));
38321 if has_time_literal {
38322 let first = args.remove(0);
38323 let second = args.remove(0);
38324 let time_as_cast = match second {
38325 Expression::Literal(lit)
38326 if matches!(lit.as_ref(), Literal::Time(_)) =>
38327 {
38328 let Literal::Time(s) = lit.as_ref() else {
38329 unreachable!()
38330 };
38331 Expression::Cast(Box::new(Cast {
38332 this: Expression::Literal(Box::new(Literal::String(
38333 s.clone(),
38334 ))),
38335 to: DataType::Time {
38336 precision: None,
38337 timezone: false,
38338 },
38339 trailing_comments: vec![],
38340 double_colon_syntax: false,
38341 format: None,
38342 default: None,
38343 inferred_type: None,
38344 }))
38345 }
38346 other => other,
38347 };
38348 return Ok(Expression::Function(Box::new(Function::new(
38349 "DATETIME".to_string(),
38350 vec![first, time_as_cast],
38351 ))));
38352 }
38353 }
38354 return Ok(Expression::Function(Box::new(Function::new(
38355 "DATETIME".to_string(),
38356 args,
38357 ))));
38358 }
38359
38360 if args.len() == 1 {
38361 let arg = args.remove(0);
38362 Ok(Expression::Cast(Box::new(Cast {
38363 this: arg,
38364 to: DataType::Timestamp {
38365 timezone: false,
38366 precision: None,
38367 },
38368 trailing_comments: vec![],
38369 double_colon_syntax: false,
38370 format: None,
38371 default: None,
38372 inferred_type: None,
38373 })))
38374 } else if args.len() == 2 {
38375 let first = args.remove(0);
38376 let second = args.remove(0);
38377 // Check if second arg is a TIME literal
38378 let is_time_literal = matches!(&second, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Time(_)));
38379 if is_time_literal {
38380 // DATETIME('date', TIME 'time') -> CAST(CAST(date AS DATE) + CAST('time' AS TIME) AS TIMESTAMP)
38381 let cast_date = Expression::Cast(Box::new(Cast {
38382 this: first,
38383 to: DataType::Date,
38384 trailing_comments: vec![],
38385 double_colon_syntax: false,
38386 format: None,
38387 default: None,
38388 inferred_type: None,
38389 }));
38390 // Convert TIME 'x' literal to string 'x' so CAST produces CAST('x' AS TIME) not CAST(TIME 'x' AS TIME)
38391 let time_as_string = match second {
38392 Expression::Literal(lit)
38393 if matches!(lit.as_ref(), Literal::Time(_)) =>
38394 {
38395 let Literal::Time(s) = lit.as_ref() else {
38396 unreachable!()
38397 };
38398 Expression::Literal(Box::new(Literal::String(s.clone())))
38399 }
38400 other => other,
38401 };
38402 let cast_time = Expression::Cast(Box::new(Cast {
38403 this: time_as_string,
38404 to: DataType::Time {
38405 precision: None,
38406 timezone: false,
38407 },
38408 trailing_comments: vec![],
38409 double_colon_syntax: false,
38410 format: None,
38411 default: None,
38412 inferred_type: None,
38413 }));
38414 let add_expr =
38415 Expression::Add(Box::new(BinaryOp::new(cast_date, cast_time)));
38416 Ok(Expression::Cast(Box::new(Cast {
38417 this: add_expr,
38418 to: DataType::Timestamp {
38419 timezone: false,
38420 precision: None,
38421 },
38422 trailing_comments: vec![],
38423 double_colon_syntax: false,
38424 format: None,
38425 default: None,
38426 inferred_type: None,
38427 })))
38428 } else {
38429 // DATETIME('string', 'timezone')
38430 let cast_tstz = Expression::Cast(Box::new(Cast {
38431 this: first,
38432 to: DataType::Timestamp {
38433 timezone: true,
38434 precision: None,
38435 },
38436 trailing_comments: vec![],
38437 double_colon_syntax: false,
38438 format: None,
38439 default: None,
38440 inferred_type: None,
38441 }));
38442 let at_tz =
38443 Expression::AtTimeZone(Box::new(crate::expressions::AtTimeZone {
38444 this: cast_tstz,
38445 zone: second,
38446 }));
38447 Ok(Expression::Cast(Box::new(Cast {
38448 this: at_tz,
38449 to: DataType::Timestamp {
38450 timezone: false,
38451 precision: None,
38452 },
38453 trailing_comments: vec![],
38454 double_colon_syntax: false,
38455 format: None,
38456 default: None,
38457 inferred_type: None,
38458 })))
38459 }
38460 } else if args.len() >= 3 {
38461 // DATETIME(y, m, d, h, min, s) -> TIMESTAMP_FROM_PARTS for Snowflake
38462 // For other targets, use MAKE_TIMESTAMP or similar
38463 if matches!(target, DialectType::Snowflake) {
38464 Ok(Expression::Function(Box::new(Function::new(
38465 "TIMESTAMP_FROM_PARTS".to_string(),
38466 args,
38467 ))))
38468 } else {
38469 Ok(Expression::Function(Box::new(Function::new(
38470 "DATETIME".to_string(),
38471 args,
38472 ))))
38473 }
38474 } else {
38475 Ok(Expression::Function(Box::new(Function::new(
38476 "DATETIME".to_string(),
38477 args,
38478 ))))
38479 }
38480 }
38481
38482 // TIMESTAMP(x) -> CAST(x AS TIMESTAMP WITH TIME ZONE) for Presto
38483 // TIMESTAMP(x, tz) -> CAST(x AS TIMESTAMP) AT TIME ZONE tz for DuckDB
38484 "TIMESTAMP" => {
38485 if args.len() == 1 {
38486 let arg = args.remove(0);
38487 Ok(Expression::Cast(Box::new(Cast {
38488 this: arg,
38489 to: DataType::Timestamp {
38490 timezone: true,
38491 precision: None,
38492 },
38493 trailing_comments: vec![],
38494 double_colon_syntax: false,
38495 format: None,
38496 default: None,
38497 inferred_type: None,
38498 })))
38499 } else if args.len() == 2 {
38500 let arg = args.remove(0);
38501 let tz = args.remove(0);
38502 let cast_ts = Expression::Cast(Box::new(Cast {
38503 this: arg,
38504 to: DataType::Timestamp {
38505 timezone: false,
38506 precision: None,
38507 },
38508 trailing_comments: vec![],
38509 double_colon_syntax: false,
38510 format: None,
38511 default: None,
38512 inferred_type: None,
38513 }));
38514 if matches!(target, DialectType::Snowflake) {
38515 // CONVERT_TIMEZONE('tz', CAST(x AS TIMESTAMP))
38516 Ok(Expression::Function(Box::new(Function::new(
38517 "CONVERT_TIMEZONE".to_string(),
38518 vec![tz, cast_ts],
38519 ))))
38520 } else {
38521 Ok(Expression::AtTimeZone(Box::new(
38522 crate::expressions::AtTimeZone {
38523 this: cast_ts,
38524 zone: tz,
38525 },
38526 )))
38527 }
38528 } else {
38529 Ok(Expression::Function(Box::new(Function::new(
38530 "TIMESTAMP".to_string(),
38531 args,
38532 ))))
38533 }
38534 }
38535
38536 // STRING(x) -> CAST(x AS VARCHAR/TEXT)
38537 // STRING(x, tz) -> CAST(CAST(x AS TIMESTAMP) AT TIME ZONE 'UTC' AT TIME ZONE tz AS VARCHAR/TEXT)
38538 "STRING" => {
38539 if args.len() == 1 {
38540 let arg = args.remove(0);
38541 let cast_type = match target {
38542 DialectType::DuckDB => DataType::Text,
38543 _ => DataType::VarChar {
38544 length: None,
38545 parenthesized_length: false,
38546 },
38547 };
38548 Ok(Expression::Cast(Box::new(Cast {
38549 this: arg,
38550 to: cast_type,
38551 trailing_comments: vec![],
38552 double_colon_syntax: false,
38553 format: None,
38554 default: None,
38555 inferred_type: None,
38556 })))
38557 } else if args.len() == 2 {
38558 let arg = args.remove(0);
38559 let tz = args.remove(0);
38560 let cast_type = match target {
38561 DialectType::DuckDB => DataType::Text,
38562 _ => DataType::VarChar {
38563 length: None,
38564 parenthesized_length: false,
38565 },
38566 };
38567 if matches!(target, DialectType::Snowflake) {
38568 // STRING(x, tz) -> CAST(CONVERT_TIMEZONE('UTC', tz, x) AS VARCHAR)
38569 let convert_tz = Expression::Function(Box::new(Function::new(
38570 "CONVERT_TIMEZONE".to_string(),
38571 vec![
38572 Expression::Literal(Box::new(Literal::String("UTC".to_string()))),
38573 tz,
38574 arg,
38575 ],
38576 )));
38577 Ok(Expression::Cast(Box::new(Cast {
38578 this: convert_tz,
38579 to: cast_type,
38580 trailing_comments: vec![],
38581 double_colon_syntax: false,
38582 format: None,
38583 default: None,
38584 inferred_type: None,
38585 })))
38586 } else {
38587 // STRING(x, tz) -> CAST(CAST(x AS TIMESTAMP) AT TIME ZONE 'UTC' AT TIME ZONE tz AS TEXT/VARCHAR)
38588 let cast_ts = Expression::Cast(Box::new(Cast {
38589 this: arg,
38590 to: DataType::Timestamp {
38591 timezone: false,
38592 precision: None,
38593 },
38594 trailing_comments: vec![],
38595 double_colon_syntax: false,
38596 format: None,
38597 default: None,
38598 inferred_type: None,
38599 }));
38600 let at_utc =
38601 Expression::AtTimeZone(Box::new(crate::expressions::AtTimeZone {
38602 this: cast_ts,
38603 zone: Expression::Literal(Box::new(Literal::String(
38604 "UTC".to_string(),
38605 ))),
38606 }));
38607 let at_tz =
38608 Expression::AtTimeZone(Box::new(crate::expressions::AtTimeZone {
38609 this: at_utc,
38610 zone: tz,
38611 }));
38612 Ok(Expression::Cast(Box::new(Cast {
38613 this: at_tz,
38614 to: cast_type,
38615 trailing_comments: vec![],
38616 double_colon_syntax: false,
38617 format: None,
38618 default: None,
38619 inferred_type: None,
38620 })))
38621 }
38622 } else {
38623 Ok(Expression::Function(Box::new(Function::new(
38624 "STRING".to_string(),
38625 args,
38626 ))))
38627 }
38628 }
38629
38630 // UNIX_SECONDS, UNIX_MILLIS, UNIX_MICROS as functions (not expressions)
38631 "UNIX_SECONDS" if args.len() == 1 => {
38632 let ts = args.remove(0);
38633 match target {
38634 DialectType::DuckDB => {
38635 // CAST(EPOCH(CAST(ts AS TIMESTAMPTZ)) AS BIGINT)
38636 let cast_ts = Self::ensure_cast_timestamptz(ts);
38637 let epoch = Expression::Function(Box::new(Function::new(
38638 "EPOCH".to_string(),
38639 vec![cast_ts],
38640 )));
38641 Ok(Expression::Cast(Box::new(Cast {
38642 this: epoch,
38643 to: DataType::BigInt { length: None },
38644 trailing_comments: vec![],
38645 double_colon_syntax: false,
38646 format: None,
38647 default: None,
38648 inferred_type: None,
38649 })))
38650 }
38651 DialectType::Snowflake => {
38652 // TIMESTAMPDIFF(SECONDS, CAST('1970-01-01 00:00:00+00' AS TIMESTAMPTZ), ts)
38653 let epoch = Expression::Cast(Box::new(Cast {
38654 this: Expression::Literal(Box::new(Literal::String(
38655 "1970-01-01 00:00:00+00".to_string(),
38656 ))),
38657 to: DataType::Timestamp {
38658 timezone: true,
38659 precision: None,
38660 },
38661 trailing_comments: vec![],
38662 double_colon_syntax: false,
38663 format: None,
38664 default: None,
38665 inferred_type: None,
38666 }));
38667 Ok(Expression::TimestampDiff(Box::new(
38668 crate::expressions::TimestampDiff {
38669 this: Box::new(epoch),
38670 expression: Box::new(ts),
38671 unit: Some("SECONDS".to_string()),
38672 },
38673 )))
38674 }
38675 _ => Ok(Expression::Function(Box::new(Function::new(
38676 "UNIX_SECONDS".to_string(),
38677 vec![ts],
38678 )))),
38679 }
38680 }
38681
38682 "UNIX_MILLIS" if args.len() == 1 => {
38683 let ts = args.remove(0);
38684 match target {
38685 DialectType::DuckDB => {
38686 // EPOCH_MS(CAST(ts AS TIMESTAMPTZ))
38687 let cast_ts = Self::ensure_cast_timestamptz(ts);
38688 Ok(Expression::Function(Box::new(Function::new(
38689 "EPOCH_MS".to_string(),
38690 vec![cast_ts],
38691 ))))
38692 }
38693 _ => Ok(Expression::Function(Box::new(Function::new(
38694 "UNIX_MILLIS".to_string(),
38695 vec![ts],
38696 )))),
38697 }
38698 }
38699
38700 "UNIX_MICROS" if args.len() == 1 => {
38701 let ts = args.remove(0);
38702 match target {
38703 DialectType::DuckDB => {
38704 // EPOCH_US(CAST(ts AS TIMESTAMPTZ))
38705 let cast_ts = Self::ensure_cast_timestamptz(ts);
38706 Ok(Expression::Function(Box::new(Function::new(
38707 "EPOCH_US".to_string(),
38708 vec![cast_ts],
38709 ))))
38710 }
38711 _ => Ok(Expression::Function(Box::new(Function::new(
38712 "UNIX_MICROS".to_string(),
38713 vec![ts],
38714 )))),
38715 }
38716 }
38717
38718 // ARRAY_CONCAT / LIST_CONCAT -> target-specific
38719 "ARRAY_CONCAT" | "LIST_CONCAT" => {
38720 match target {
38721 DialectType::Spark | DialectType::Databricks | DialectType::Hive => {
38722 // CONCAT(arr1, arr2, ...)
38723 Ok(Expression::Function(Box::new(Function::new(
38724 "CONCAT".to_string(),
38725 args,
38726 ))))
38727 }
38728 DialectType::Presto | DialectType::Trino => {
38729 // CONCAT(arr1, arr2, ...)
38730 Ok(Expression::Function(Box::new(Function::new(
38731 "CONCAT".to_string(),
38732 args,
38733 ))))
38734 }
38735 DialectType::Snowflake => {
38736 // ARRAY_CAT(arr1, ARRAY_CAT(arr2, arr3))
38737 if args.len() == 1 {
38738 // ARRAY_CAT requires 2 args, add empty array as []
38739 let empty_arr = Expression::ArrayFunc(Box::new(
38740 crate::expressions::ArrayConstructor {
38741 expressions: vec![],
38742 bracket_notation: true,
38743 use_list_keyword: false,
38744 },
38745 ));
38746 let mut new_args = args;
38747 new_args.push(empty_arr);
38748 Ok(Expression::Function(Box::new(Function::new(
38749 "ARRAY_CAT".to_string(),
38750 new_args,
38751 ))))
38752 } else if args.is_empty() {
38753 Ok(Expression::Function(Box::new(Function::new(
38754 "ARRAY_CAT".to_string(),
38755 args,
38756 ))))
38757 } else {
38758 let mut it = args.into_iter().rev();
38759 let mut result = it.next().unwrap();
38760 for arr in it {
38761 result = Expression::Function(Box::new(Function::new(
38762 "ARRAY_CAT".to_string(),
38763 vec![arr, result],
38764 )));
38765 }
38766 Ok(result)
38767 }
38768 }
38769 DialectType::PostgreSQL => {
38770 // ARRAY_CAT(arr1, ARRAY_CAT(arr2, arr3))
38771 if args.len() <= 1 {
38772 Ok(Expression::Function(Box::new(Function::new(
38773 "ARRAY_CAT".to_string(),
38774 args,
38775 ))))
38776 } else {
38777 let mut it = args.into_iter().rev();
38778 let mut result = it.next().unwrap();
38779 for arr in it {
38780 result = Expression::Function(Box::new(Function::new(
38781 "ARRAY_CAT".to_string(),
38782 vec![arr, result],
38783 )));
38784 }
38785 Ok(result)
38786 }
38787 }
38788 DialectType::Redshift => {
38789 // ARRAY_CONCAT(arr1, ARRAY_CONCAT(arr2, arr3))
38790 if args.len() <= 2 {
38791 Ok(Expression::Function(Box::new(Function::new(
38792 "ARRAY_CONCAT".to_string(),
38793 args,
38794 ))))
38795 } else {
38796 let mut it = args.into_iter().rev();
38797 let mut result = it.next().unwrap();
38798 for arr in it {
38799 result = Expression::Function(Box::new(Function::new(
38800 "ARRAY_CONCAT".to_string(),
38801 vec![arr, result],
38802 )));
38803 }
38804 Ok(result)
38805 }
38806 }
38807 DialectType::DuckDB => {
38808 // LIST_CONCAT supports multiple args natively in DuckDB
38809 Ok(Expression::Function(Box::new(Function::new(
38810 "LIST_CONCAT".to_string(),
38811 args,
38812 ))))
38813 }
38814 _ => Ok(Expression::Function(Box::new(Function::new(
38815 "ARRAY_CONCAT".to_string(),
38816 args,
38817 )))),
38818 }
38819 }
38820
38821 // ARRAY_CONCAT_AGG -> Snowflake: ARRAY_FLATTEN(ARRAY_AGG(x))
38822 "ARRAY_CONCAT_AGG" if args.len() == 1 => {
38823 let arg = args.remove(0);
38824 match target {
38825 DialectType::Snowflake => {
38826 let array_agg =
38827 Expression::ArrayAgg(Box::new(crate::expressions::AggFunc {
38828 this: arg,
38829 distinct: false,
38830 filter: None,
38831 order_by: vec![],
38832 name: None,
38833 ignore_nulls: None,
38834 having_max: None,
38835 limit: None,
38836 inferred_type: None,
38837 }));
38838 Ok(Expression::Function(Box::new(Function::new(
38839 "ARRAY_FLATTEN".to_string(),
38840 vec![array_agg],
38841 ))))
38842 }
38843 _ => Ok(Expression::Function(Box::new(Function::new(
38844 "ARRAY_CONCAT_AGG".to_string(),
38845 vec![arg],
38846 )))),
38847 }
38848 }
38849
38850 // MD5/SHA1/SHA256/SHA512 -> target-specific hash functions
38851 "MD5" if args.len() == 1 => {
38852 let arg = args.remove(0);
38853 match target {
38854 DialectType::Spark | DialectType::Databricks | DialectType::Hive => {
38855 // UNHEX(MD5(x))
38856 let md5 = Expression::Function(Box::new(Function::new(
38857 "MD5".to_string(),
38858 vec![arg],
38859 )));
38860 Ok(Expression::Function(Box::new(Function::new(
38861 "UNHEX".to_string(),
38862 vec![md5],
38863 ))))
38864 }
38865 DialectType::Snowflake => {
38866 // MD5_BINARY(x)
38867 Ok(Expression::Function(Box::new(Function::new(
38868 "MD5_BINARY".to_string(),
38869 vec![arg],
38870 ))))
38871 }
38872 _ => Ok(Expression::Function(Box::new(Function::new(
38873 "MD5".to_string(),
38874 vec![arg],
38875 )))),
38876 }
38877 }
38878
38879 "SHA1" if args.len() == 1 => {
38880 let arg = args.remove(0);
38881 match target {
38882 DialectType::DuckDB => {
38883 // UNHEX(SHA1(x))
38884 let sha1 = Expression::Function(Box::new(Function::new(
38885 "SHA1".to_string(),
38886 vec![arg],
38887 )));
38888 Ok(Expression::Function(Box::new(Function::new(
38889 "UNHEX".to_string(),
38890 vec![sha1],
38891 ))))
38892 }
38893 _ => Ok(Expression::Function(Box::new(Function::new(
38894 "SHA1".to_string(),
38895 vec![arg],
38896 )))),
38897 }
38898 }
38899
38900 "SHA256" if args.len() == 1 => {
38901 let arg = args.remove(0);
38902 match target {
38903 DialectType::DuckDB => {
38904 // UNHEX(SHA256(x))
38905 let sha = Expression::Function(Box::new(Function::new(
38906 "SHA256".to_string(),
38907 vec![arg],
38908 )));
38909 Ok(Expression::Function(Box::new(Function::new(
38910 "UNHEX".to_string(),
38911 vec![sha],
38912 ))))
38913 }
38914 DialectType::Snowflake => {
38915 // SHA2_BINARY(x, 256)
38916 Ok(Expression::Function(Box::new(Function::new(
38917 "SHA2_BINARY".to_string(),
38918 vec![arg, Expression::number(256)],
38919 ))))
38920 }
38921 DialectType::Redshift | DialectType::Spark => {
38922 // SHA2(x, 256)
38923 Ok(Expression::Function(Box::new(Function::new(
38924 "SHA2".to_string(),
38925 vec![arg, Expression::number(256)],
38926 ))))
38927 }
38928 _ => Ok(Expression::Function(Box::new(Function::new(
38929 "SHA256".to_string(),
38930 vec![arg],
38931 )))),
38932 }
38933 }
38934
38935 "SHA512" if args.len() == 1 => {
38936 let arg = args.remove(0);
38937 match target {
38938 DialectType::Snowflake => {
38939 // SHA2_BINARY(x, 512)
38940 Ok(Expression::Function(Box::new(Function::new(
38941 "SHA2_BINARY".to_string(),
38942 vec![arg, Expression::number(512)],
38943 ))))
38944 }
38945 DialectType::Redshift | DialectType::Spark => {
38946 // SHA2(x, 512)
38947 Ok(Expression::Function(Box::new(Function::new(
38948 "SHA2".to_string(),
38949 vec![arg, Expression::number(512)],
38950 ))))
38951 }
38952 _ => Ok(Expression::Function(Box::new(Function::new(
38953 "SHA512".to_string(),
38954 vec![arg],
38955 )))),
38956 }
38957 }
38958
38959 // REGEXP_EXTRACT_ALL(str, pattern) -> add default group arg
38960 "REGEXP_EXTRACT_ALL" if args.len() == 2 => {
38961 let str_expr = args.remove(0);
38962 let pattern = args.remove(0);
38963
38964 // Check if pattern contains capturing groups (parentheses)
38965 let has_groups = match &pattern {
38966 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
38967 let Literal::String(s) = lit.as_ref() else {
38968 unreachable!()
38969 };
38970 s.contains('(') && s.contains(')')
38971 }
38972 _ => false,
38973 };
38974
38975 match target {
38976 DialectType::DuckDB => {
38977 let group = if has_groups {
38978 Expression::number(1)
38979 } else {
38980 Expression::number(0)
38981 };
38982 Ok(Expression::Function(Box::new(Function::new(
38983 "REGEXP_EXTRACT_ALL".to_string(),
38984 vec![str_expr, pattern, group],
38985 ))))
38986 }
38987 DialectType::Spark | DialectType::Databricks => {
38988 // Spark's default group_index is 1 (same as BigQuery), so omit for capturing groups
38989 if has_groups {
38990 Ok(Expression::Function(Box::new(Function::new(
38991 "REGEXP_EXTRACT_ALL".to_string(),
38992 vec![str_expr, pattern],
38993 ))))
38994 } else {
38995 Ok(Expression::Function(Box::new(Function::new(
38996 "REGEXP_EXTRACT_ALL".to_string(),
38997 vec![str_expr, pattern, Expression::number(0)],
38998 ))))
38999 }
39000 }
39001 DialectType::Presto | DialectType::Trino => {
39002 if has_groups {
39003 Ok(Expression::Function(Box::new(Function::new(
39004 "REGEXP_EXTRACT_ALL".to_string(),
39005 vec![str_expr, pattern, Expression::number(1)],
39006 ))))
39007 } else {
39008 Ok(Expression::Function(Box::new(Function::new(
39009 "REGEXP_EXTRACT_ALL".to_string(),
39010 vec![str_expr, pattern],
39011 ))))
39012 }
39013 }
39014 DialectType::Snowflake => {
39015 if has_groups {
39016 // REGEXP_EXTRACT_ALL(str, pattern, 1, 1, 'c', 1)
39017 Ok(Expression::Function(Box::new(Function::new(
39018 "REGEXP_EXTRACT_ALL".to_string(),
39019 vec![
39020 str_expr,
39021 pattern,
39022 Expression::number(1),
39023 Expression::number(1),
39024 Expression::Literal(Box::new(Literal::String("c".to_string()))),
39025 Expression::number(1),
39026 ],
39027 ))))
39028 } else {
39029 Ok(Expression::Function(Box::new(Function::new(
39030 "REGEXP_EXTRACT_ALL".to_string(),
39031 vec![str_expr, pattern],
39032 ))))
39033 }
39034 }
39035 _ => Ok(Expression::Function(Box::new(Function::new(
39036 "REGEXP_EXTRACT_ALL".to_string(),
39037 vec![str_expr, pattern],
39038 )))),
39039 }
39040 }
39041
39042 // MOD(x, y) -> x % y for dialects that prefer or require the infix operator.
39043 "MOD" if args.len() == 2 => {
39044 match target {
39045 DialectType::PostgreSQL
39046 | DialectType::DuckDB
39047 | DialectType::Presto
39048 | DialectType::Trino
39049 | DialectType::Athena
39050 | DialectType::Snowflake
39051 | DialectType::TSQL
39052 | DialectType::Fabric => {
39053 let x = args.remove(0);
39054 let y = args.remove(0);
39055 // Wrap complex expressions in parens to preserve precedence
39056 let needs_paren = |e: &Expression| {
39057 matches!(
39058 e,
39059 Expression::Add(_)
39060 | Expression::Sub(_)
39061 | Expression::Mul(_)
39062 | Expression::Div(_)
39063 | Expression::Mod(_)
39064 | Expression::ModFunc(_)
39065 )
39066 };
39067 let x = if needs_paren(&x) {
39068 Expression::Paren(Box::new(crate::expressions::Paren {
39069 this: x,
39070 trailing_comments: vec![],
39071 }))
39072 } else {
39073 x
39074 };
39075 let y = if needs_paren(&y) {
39076 Expression::Paren(Box::new(crate::expressions::Paren {
39077 this: y,
39078 trailing_comments: vec![],
39079 }))
39080 } else {
39081 y
39082 };
39083 Ok(Expression::Mod(Box::new(
39084 crate::expressions::BinaryOp::new(x, y),
39085 )))
39086 }
39087 DialectType::Hive | DialectType::Spark | DialectType::Databricks => {
39088 // Hive/Spark: a % b
39089 let x = args.remove(0);
39090 let y = args.remove(0);
39091 let needs_paren = |e: &Expression| {
39092 matches!(
39093 e,
39094 Expression::Add(_)
39095 | Expression::Sub(_)
39096 | Expression::Mul(_)
39097 | Expression::Div(_)
39098 | Expression::Mod(_)
39099 | Expression::ModFunc(_)
39100 )
39101 };
39102 let x = if needs_paren(&x) {
39103 Expression::Paren(Box::new(crate::expressions::Paren {
39104 this: x,
39105 trailing_comments: vec![],
39106 }))
39107 } else {
39108 x
39109 };
39110 let y = if needs_paren(&y) {
39111 Expression::Paren(Box::new(crate::expressions::Paren {
39112 this: y,
39113 trailing_comments: vec![],
39114 }))
39115 } else {
39116 y
39117 };
39118 Ok(Expression::Mod(Box::new(
39119 crate::expressions::BinaryOp::new(x, y),
39120 )))
39121 }
39122 _ => Ok(Expression::Function(Box::new(Function::new(
39123 "MOD".to_string(),
39124 args,
39125 )))),
39126 }
39127 }
39128
39129 // ARRAY_FILTER(arr, lambda) -> FILTER for Hive/Spark/Presto, ARRAY_FILTER for StarRocks
39130 "ARRAY_FILTER" if args.len() == 2 => {
39131 let name = match target {
39132 DialectType::DuckDB => "LIST_FILTER",
39133 DialectType::StarRocks => "ARRAY_FILTER",
39134 _ => "FILTER",
39135 };
39136 Ok(Expression::Function(Box::new(Function::new(
39137 name.to_string(),
39138 args,
39139 ))))
39140 }
39141 // FILTER(arr, lambda) -> ARRAY_FILTER for StarRocks, LIST_FILTER for DuckDB
39142 "FILTER" if args.len() == 2 => {
39143 let name = match target {
39144 DialectType::DuckDB => "LIST_FILTER",
39145 DialectType::StarRocks => "ARRAY_FILTER",
39146 _ => "FILTER",
39147 };
39148 Ok(Expression::Function(Box::new(Function::new(
39149 name.to_string(),
39150 args,
39151 ))))
39152 }
39153 // REDUCE(arr, init, lambda1, lambda2) -> AGGREGATE for Spark
39154 "REDUCE" if args.len() >= 3 => {
39155 let name = match target {
39156 DialectType::Spark | DialectType::Databricks => "AGGREGATE",
39157 _ => "REDUCE",
39158 };
39159 Ok(Expression::Function(Box::new(Function::new(
39160 name.to_string(),
39161 args,
39162 ))))
39163 }
39164 // ARRAY_REVERSE(x) -> arrayReverse for ClickHouse (handled by generator)
39165 "ARRAY_REVERSE" if args.len() == 1 => Ok(Expression::Function(Box::new(
39166 Function::new("ARRAY_REVERSE".to_string(), args),
39167 ))),
39168
39169 // CONCAT(a, b, ...) -> a || b || ... for DuckDB with 3+ args
39170 "CONCAT" if args.len() > 2 => match target {
39171 DialectType::DuckDB => {
39172 let mut it = args.into_iter();
39173 let mut result = it.next().unwrap();
39174 for arg in it {
39175 result = Expression::DPipe(Box::new(crate::expressions::DPipe {
39176 this: Box::new(result),
39177 expression: Box::new(arg),
39178 safe: None,
39179 }));
39180 }
39181 Ok(result)
39182 }
39183 _ => Ok(Expression::Function(Box::new(Function::new(
39184 "CONCAT".to_string(),
39185 args,
39186 )))),
39187 },
39188
39189 // GENERATE_DATE_ARRAY(start, end[, step]) -> target-specific
39190 "GENERATE_DATE_ARRAY" => {
39191 if matches!(target, DialectType::BigQuery) {
39192 // BQ->BQ: add default interval if not present
39193 if args.len() == 2 {
39194 let start = args.remove(0);
39195 let end = args.remove(0);
39196 let default_interval =
39197 Expression::Interval(Box::new(crate::expressions::Interval {
39198 this: Some(Expression::Literal(Box::new(Literal::String(
39199 "1".to_string(),
39200 )))),
39201 unit: Some(crate::expressions::IntervalUnitSpec::Simple {
39202 unit: crate::expressions::IntervalUnit::Day,
39203 use_plural: false,
39204 }),
39205 }));
39206 Ok(Expression::Function(Box::new(Function::new(
39207 "GENERATE_DATE_ARRAY".to_string(),
39208 vec![start, end, default_interval],
39209 ))))
39210 } else {
39211 Ok(Expression::Function(Box::new(Function::new(
39212 "GENERATE_DATE_ARRAY".to_string(),
39213 args,
39214 ))))
39215 }
39216 } else if matches!(target, DialectType::DuckDB) {
39217 // DuckDB: CAST(GENERATE_SERIES(CAST(start AS DATE), CAST(end AS DATE), step) AS DATE[])
39218 let start = args.get(0).cloned();
39219 let end = args.get(1).cloned();
39220 let step = args.get(2).cloned().or_else(|| {
39221 Some(Expression::Interval(Box::new(
39222 crate::expressions::Interval {
39223 this: Some(Expression::Literal(Box::new(Literal::String(
39224 "1".to_string(),
39225 )))),
39226 unit: Some(crate::expressions::IntervalUnitSpec::Simple {
39227 unit: crate::expressions::IntervalUnit::Day,
39228 use_plural: false,
39229 }),
39230 },
39231 )))
39232 });
39233
39234 // Wrap start/end in CAST(... AS DATE) only for string literals
39235 let maybe_cast_date = |expr: Expression| -> Expression {
39236 if matches!(&expr, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
39237 {
39238 Expression::Cast(Box::new(Cast {
39239 this: expr,
39240 to: DataType::Date,
39241 trailing_comments: vec![],
39242 double_colon_syntax: false,
39243 format: None,
39244 default: None,
39245 inferred_type: None,
39246 }))
39247 } else {
39248 expr
39249 }
39250 };
39251 let cast_start = start.map(maybe_cast_date);
39252 let cast_end = end.map(maybe_cast_date);
39253
39254 let gen_series =
39255 Expression::GenerateSeries(Box::new(crate::expressions::GenerateSeries {
39256 start: cast_start.map(Box::new),
39257 end: cast_end.map(Box::new),
39258 step: step.map(Box::new),
39259 is_end_exclusive: None,
39260 }));
39261
39262 // Wrap in CAST(... AS DATE[])
39263 Ok(Expression::Cast(Box::new(Cast {
39264 this: gen_series,
39265 to: DataType::Array {
39266 element_type: Box::new(DataType::Date),
39267 dimension: None,
39268 },
39269 trailing_comments: vec![],
39270 double_colon_syntax: false,
39271 format: None,
39272 default: None,
39273 inferred_type: None,
39274 })))
39275 } else if matches!(target, DialectType::Snowflake) {
39276 // Snowflake: keep as GENERATE_DATE_ARRAY function for later transform
39277 // (transform_generate_date_array_snowflake will convert to ARRAY_GENERATE_RANGE + DATEADD)
39278 if args.len() == 2 {
39279 let start = args.remove(0);
39280 let end = args.remove(0);
39281 let default_interval =
39282 Expression::Interval(Box::new(crate::expressions::Interval {
39283 this: Some(Expression::Literal(Box::new(Literal::String(
39284 "1".to_string(),
39285 )))),
39286 unit: Some(crate::expressions::IntervalUnitSpec::Simple {
39287 unit: crate::expressions::IntervalUnit::Day,
39288 use_plural: false,
39289 }),
39290 }));
39291 Ok(Expression::Function(Box::new(Function::new(
39292 "GENERATE_DATE_ARRAY".to_string(),
39293 vec![start, end, default_interval],
39294 ))))
39295 } else {
39296 Ok(Expression::Function(Box::new(Function::new(
39297 "GENERATE_DATE_ARRAY".to_string(),
39298 args,
39299 ))))
39300 }
39301 } else {
39302 // Convert to GenerateSeries for other targets
39303 let start = args.get(0).cloned();
39304 let end = args.get(1).cloned();
39305 let step = args.get(2).cloned().or_else(|| {
39306 Some(Expression::Interval(Box::new(
39307 crate::expressions::Interval {
39308 this: Some(Expression::Literal(Box::new(Literal::String(
39309 "1".to_string(),
39310 )))),
39311 unit: Some(crate::expressions::IntervalUnitSpec::Simple {
39312 unit: crate::expressions::IntervalUnit::Day,
39313 use_plural: false,
39314 }),
39315 },
39316 )))
39317 });
39318 Ok(Expression::GenerateSeries(Box::new(
39319 crate::expressions::GenerateSeries {
39320 start: start.map(Box::new),
39321 end: end.map(Box::new),
39322 step: step.map(Box::new),
39323 is_end_exclusive: None,
39324 },
39325 )))
39326 }
39327 }
39328
39329 // PARSE_DATE(format, str) -> target-specific
39330 "PARSE_DATE" if args.len() == 2 => {
39331 let format = args.remove(0);
39332 let str_expr = args.remove(0);
39333 match target {
39334 DialectType::DuckDB => {
39335 // CAST(STRPTIME(str, duck_format) AS DATE)
39336 let duck_format = Self::bq_format_to_duckdb(&format);
39337 let strptime = Expression::Function(Box::new(Function::new(
39338 "STRPTIME".to_string(),
39339 vec![str_expr, duck_format],
39340 )));
39341 Ok(Expression::Cast(Box::new(Cast {
39342 this: strptime,
39343 to: DataType::Date,
39344 trailing_comments: vec![],
39345 double_colon_syntax: false,
39346 format: None,
39347 default: None,
39348 inferred_type: None,
39349 })))
39350 }
39351 DialectType::Snowflake => {
39352 // _POLYGLOT_DATE(str, snowflake_format)
39353 // Use marker so Snowflake target transform keeps it as DATE() instead of TO_DATE()
39354 let sf_format = Self::bq_format_to_snowflake(&format);
39355 Ok(Expression::Function(Box::new(Function::new(
39356 "_POLYGLOT_DATE".to_string(),
39357 vec![str_expr, sf_format],
39358 ))))
39359 }
39360 _ => Ok(Expression::Function(Box::new(Function::new(
39361 "PARSE_DATE".to_string(),
39362 vec![format, str_expr],
39363 )))),
39364 }
39365 }
39366
39367 // PARSE_DATETIME(format, str) -> target-specific
39368 "PARSE_DATETIME" if args.len() == 2 => {
39369 let format = args.remove(0);
39370 let str_expr = args.remove(0);
39371 let c_format = Self::bq_format_to_duckdb(&format);
39372 match target {
39373 DialectType::DuckDB => {
39374 // DuckDB STRPTIME needs a date-bearing format for DATETIME parsing.
39375 let str_with_year = Expression::Concat(Box::new(BinaryOp::new(
39376 Expression::string("1970 "),
39377 str_expr,
39378 )));
39379 let format_with_year = Expression::Concat(Box::new(BinaryOp::new(
39380 Expression::string("%Y "),
39381 c_format,
39382 )));
39383 Ok(Expression::Function(Box::new(Function::new(
39384 "STRPTIME".to_string(),
39385 vec![str_with_year, format_with_year],
39386 ))))
39387 }
39388 DialectType::Snowflake => {
39389 // SQLGlot emits PARSE_DATETIME(value, format) with expanded C-style format tokens.
39390 Ok(Expression::Function(Box::new(Function::new(
39391 "PARSE_DATETIME".to_string(),
39392 vec![str_expr, c_format],
39393 ))))
39394 }
39395 _ => Ok(Expression::Function(Box::new(Function::new(
39396 "PARSE_DATETIME".to_string(),
39397 vec![format, str_expr],
39398 )))),
39399 }
39400 }
39401
39402 // PARSE_TIMESTAMP(format, str) -> target-specific
39403 "PARSE_TIMESTAMP" if args.len() >= 2 => {
39404 let format = args.remove(0);
39405 let str_expr = args.remove(0);
39406 let tz = if !args.is_empty() {
39407 Some(args.remove(0))
39408 } else {
39409 None
39410 };
39411 match target {
39412 DialectType::DuckDB => {
39413 let duck_format = Self::bq_format_to_duckdb(&format);
39414 let strptime = Expression::Function(Box::new(Function::new(
39415 "STRPTIME".to_string(),
39416 vec![str_expr, duck_format],
39417 )));
39418 Ok(strptime)
39419 }
39420 _ => {
39421 let mut result_args = vec![format, str_expr];
39422 if let Some(tz_arg) = tz {
39423 result_args.push(tz_arg);
39424 }
39425 Ok(Expression::Function(Box::new(Function::new(
39426 "PARSE_TIMESTAMP".to_string(),
39427 result_args,
39428 ))))
39429 }
39430 }
39431 }
39432
39433 // FORMAT_DATE(format, date) -> target-specific
39434 "FORMAT_DATE" if args.len() == 2 => {
39435 let format = args.remove(0);
39436 let date_expr = args.remove(0);
39437 match target {
39438 DialectType::DuckDB => {
39439 // STRFTIME(CAST(date AS DATE), format)
39440 let cast_date = Expression::Cast(Box::new(Cast {
39441 this: date_expr,
39442 to: DataType::Date,
39443 trailing_comments: vec![],
39444 double_colon_syntax: false,
39445 format: None,
39446 default: None,
39447 inferred_type: None,
39448 }));
39449 Ok(Expression::Function(Box::new(Function::new(
39450 "STRFTIME".to_string(),
39451 vec![cast_date, format],
39452 ))))
39453 }
39454 _ => Ok(Expression::Function(Box::new(Function::new(
39455 "FORMAT_DATE".to_string(),
39456 vec![format, date_expr],
39457 )))),
39458 }
39459 }
39460
39461 // FORMAT_DATETIME(format, datetime) -> target-specific
39462 "FORMAT_DATETIME" if args.len() == 2 => {
39463 let format = args.remove(0);
39464 let dt_expr = args.remove(0);
39465
39466 if matches!(target, DialectType::BigQuery) {
39467 // BQ->BQ: normalize %H:%M:%S to %T, %x to %D
39468 let norm_format = Self::bq_format_normalize_bq(&format);
39469 // Also strip DATETIME keyword from typed literals
39470 let norm_dt = match dt_expr {
39471 Expression::Literal(lit)
39472 if matches!(lit.as_ref(), Literal::Timestamp(_)) =>
39473 {
39474 let Literal::Timestamp(s) = lit.as_ref() else {
39475 unreachable!()
39476 };
39477 Expression::Cast(Box::new(Cast {
39478 this: Expression::Literal(Box::new(Literal::String(s.clone()))),
39479 to: DataType::Custom {
39480 name: "DATETIME".to_string(),
39481 },
39482 trailing_comments: vec![],
39483 double_colon_syntax: false,
39484 format: None,
39485 default: None,
39486 inferred_type: None,
39487 }))
39488 }
39489 other => other,
39490 };
39491 return Ok(Expression::Function(Box::new(Function::new(
39492 "FORMAT_DATETIME".to_string(),
39493 vec![norm_format, norm_dt],
39494 ))));
39495 }
39496
39497 match target {
39498 DialectType::DuckDB => {
39499 // STRFTIME(CAST(dt AS TIMESTAMP), duckdb_format)
39500 let cast_dt = Self::ensure_cast_timestamp(dt_expr);
39501 let duck_format = Self::bq_format_to_duckdb(&format);
39502 Ok(Expression::Function(Box::new(Function::new(
39503 "STRFTIME".to_string(),
39504 vec![cast_dt, duck_format],
39505 ))))
39506 }
39507 _ => Ok(Expression::Function(Box::new(Function::new(
39508 "FORMAT_DATETIME".to_string(),
39509 vec![format, dt_expr],
39510 )))),
39511 }
39512 }
39513
39514 // FORMAT_TIMESTAMP(format, ts) -> target-specific
39515 "FORMAT_TIMESTAMP" if args.len() == 2 => {
39516 let format = args.remove(0);
39517 let ts_expr = args.remove(0);
39518 match target {
39519 DialectType::DuckDB => {
39520 // STRFTIME(CAST(CAST(ts AS TIMESTAMPTZ) AS TIMESTAMP), format)
39521 let cast_tstz = Self::ensure_cast_timestamptz(ts_expr);
39522 let cast_ts = Expression::Cast(Box::new(Cast {
39523 this: cast_tstz,
39524 to: DataType::Timestamp {
39525 timezone: false,
39526 precision: None,
39527 },
39528 trailing_comments: vec![],
39529 double_colon_syntax: false,
39530 format: None,
39531 default: None,
39532 inferred_type: None,
39533 }));
39534 Ok(Expression::Function(Box::new(Function::new(
39535 "STRFTIME".to_string(),
39536 vec![cast_ts, format],
39537 ))))
39538 }
39539 DialectType::Snowflake => {
39540 // TO_CHAR(CAST(CAST(ts AS TIMESTAMPTZ) AS TIMESTAMP), snowflake_format)
39541 let cast_tstz = Self::ensure_cast_timestamptz(ts_expr);
39542 let cast_ts = Expression::Cast(Box::new(Cast {
39543 this: cast_tstz,
39544 to: DataType::Timestamp {
39545 timezone: false,
39546 precision: None,
39547 },
39548 trailing_comments: vec![],
39549 double_colon_syntax: false,
39550 format: None,
39551 default: None,
39552 inferred_type: None,
39553 }));
39554 let sf_format = Self::bq_format_to_snowflake(&format);
39555 Ok(Expression::Function(Box::new(Function::new(
39556 "TO_CHAR".to_string(),
39557 vec![cast_ts, sf_format],
39558 ))))
39559 }
39560 _ => Ok(Expression::Function(Box::new(Function::new(
39561 "FORMAT_TIMESTAMP".to_string(),
39562 vec![format, ts_expr],
39563 )))),
39564 }
39565 }
39566
39567 // UNIX_DATE(date) -> DATE_DIFF('DAY', '1970-01-01', date) for DuckDB
39568 "UNIX_DATE" if args.len() == 1 => {
39569 let date = args.remove(0);
39570 match target {
39571 DialectType::DuckDB => {
39572 let epoch = Expression::Cast(Box::new(Cast {
39573 this: Expression::Literal(Box::new(Literal::String(
39574 "1970-01-01".to_string(),
39575 ))),
39576 to: DataType::Date,
39577 trailing_comments: vec![],
39578 double_colon_syntax: false,
39579 format: None,
39580 default: None,
39581 inferred_type: None,
39582 }));
39583 // DATE_DIFF('DAY', epoch, date) but date might be DATE '...' literal
39584 // Need to convert DATE literal to CAST
39585 let norm_date = Self::date_literal_to_cast(date);
39586 Ok(Expression::Function(Box::new(Function::new(
39587 "DATE_DIFF".to_string(),
39588 vec![
39589 Expression::Literal(Box::new(Literal::String("DAY".to_string()))),
39590 epoch,
39591 norm_date,
39592 ],
39593 ))))
39594 }
39595 _ => Ok(Expression::Function(Box::new(Function::new(
39596 "UNIX_DATE".to_string(),
39597 vec![date],
39598 )))),
39599 }
39600 }
39601
39602 // UNIX_SECONDS(ts) -> target-specific
39603 "UNIX_SECONDS" if args.len() == 1 => {
39604 let ts = args.remove(0);
39605 match target {
39606 DialectType::DuckDB => {
39607 // CAST(EPOCH(CAST(ts AS TIMESTAMPTZ)) AS BIGINT)
39608 let norm_ts = Self::ts_literal_to_cast_tz(ts);
39609 let epoch = Expression::Function(Box::new(Function::new(
39610 "EPOCH".to_string(),
39611 vec![norm_ts],
39612 )));
39613 Ok(Expression::Cast(Box::new(Cast {
39614 this: epoch,
39615 to: DataType::BigInt { length: None },
39616 trailing_comments: vec![],
39617 double_colon_syntax: false,
39618 format: None,
39619 default: None,
39620 inferred_type: None,
39621 })))
39622 }
39623 DialectType::Snowflake => {
39624 // TIMESTAMPDIFF(SECONDS, CAST('1970-01-01 00:00:00+00' AS TIMESTAMPTZ), ts)
39625 let epoch = Expression::Cast(Box::new(Cast {
39626 this: Expression::Literal(Box::new(Literal::String(
39627 "1970-01-01 00:00:00+00".to_string(),
39628 ))),
39629 to: DataType::Timestamp {
39630 timezone: true,
39631 precision: None,
39632 },
39633 trailing_comments: vec![],
39634 double_colon_syntax: false,
39635 format: None,
39636 default: None,
39637 inferred_type: None,
39638 }));
39639 Ok(Expression::Function(Box::new(Function::new(
39640 "TIMESTAMPDIFF".to_string(),
39641 vec![
39642 Expression::Identifier(Identifier::new("SECONDS".to_string())),
39643 epoch,
39644 ts,
39645 ],
39646 ))))
39647 }
39648 _ => Ok(Expression::Function(Box::new(Function::new(
39649 "UNIX_SECONDS".to_string(),
39650 vec![ts],
39651 )))),
39652 }
39653 }
39654
39655 // UNIX_MILLIS(ts) -> target-specific
39656 "UNIX_MILLIS" if args.len() == 1 => {
39657 let ts = args.remove(0);
39658 match target {
39659 DialectType::DuckDB => {
39660 let norm_ts = Self::ts_literal_to_cast_tz(ts);
39661 Ok(Expression::Function(Box::new(Function::new(
39662 "EPOCH_MS".to_string(),
39663 vec![norm_ts],
39664 ))))
39665 }
39666 _ => Ok(Expression::Function(Box::new(Function::new(
39667 "UNIX_MILLIS".to_string(),
39668 vec![ts],
39669 )))),
39670 }
39671 }
39672
39673 // UNIX_MICROS(ts) -> target-specific
39674 "UNIX_MICROS" if args.len() == 1 => {
39675 let ts = args.remove(0);
39676 match target {
39677 DialectType::DuckDB => {
39678 let norm_ts = Self::ts_literal_to_cast_tz(ts);
39679 Ok(Expression::Function(Box::new(Function::new(
39680 "EPOCH_US".to_string(),
39681 vec![norm_ts],
39682 ))))
39683 }
39684 _ => Ok(Expression::Function(Box::new(Function::new(
39685 "UNIX_MICROS".to_string(),
39686 vec![ts],
39687 )))),
39688 }
39689 }
39690
39691 // INSTR(str, substr) -> target-specific
39692 "INSTR" => {
39693 if matches!(target, DialectType::BigQuery) {
39694 // BQ->BQ: keep as INSTR
39695 Ok(Expression::Function(Box::new(Function::new(
39696 "INSTR".to_string(),
39697 args,
39698 ))))
39699 } else if matches!(target, DialectType::Snowflake) && args.len() == 2 {
39700 // Snowflake: CHARINDEX(substr, str) - swap args
39701 let str_expr = args.remove(0);
39702 let substr = args.remove(0);
39703 Ok(Expression::Function(Box::new(Function::new(
39704 "CHARINDEX".to_string(),
39705 vec![substr, str_expr],
39706 ))))
39707 } else {
39708 // Keep as INSTR for other targets
39709 Ok(Expression::Function(Box::new(Function::new(
39710 "INSTR".to_string(),
39711 args,
39712 ))))
39713 }
39714 }
39715
39716 // CURRENT_TIMESTAMP / CURRENT_DATE handling - parens normalization and timezone
39717 "CURRENT_TIMESTAMP" | "CURRENT_DATE" | "CURRENT_DATETIME" | "CURRENT_TIME" => {
39718 if matches!(target, DialectType::BigQuery) {
39719 // BQ->BQ: always output with parens (function form), keep any timezone arg
39720 Ok(Expression::Function(Box::new(Function::new(name, args))))
39721 } else if name == "CURRENT_DATE" && args.len() == 1 {
39722 // CURRENT_DATE('UTC') - has timezone arg
39723 let tz_arg = args.remove(0);
39724 match target {
39725 DialectType::DuckDB => {
39726 // CAST(CURRENT_TIMESTAMP AT TIME ZONE 'UTC' AS DATE)
39727 let ct = Expression::CurrentTimestamp(
39728 crate::expressions::CurrentTimestamp {
39729 precision: None,
39730 sysdate: false,
39731 },
39732 );
39733 let at_tz =
39734 Expression::AtTimeZone(Box::new(crate::expressions::AtTimeZone {
39735 this: ct,
39736 zone: tz_arg,
39737 }));
39738 Ok(Expression::Cast(Box::new(Cast {
39739 this: at_tz,
39740 to: DataType::Date,
39741 trailing_comments: vec![],
39742 double_colon_syntax: false,
39743 format: None,
39744 default: None,
39745 inferred_type: None,
39746 })))
39747 }
39748 DialectType::Snowflake => {
39749 // CAST(CONVERT_TIMEZONE('UTC', CURRENT_TIMESTAMP()) AS DATE)
39750 let ct = Expression::Function(Box::new(Function::new(
39751 "CURRENT_TIMESTAMP".to_string(),
39752 vec![],
39753 )));
39754 let convert = Expression::Function(Box::new(Function::new(
39755 "CONVERT_TIMEZONE".to_string(),
39756 vec![tz_arg, ct],
39757 )));
39758 Ok(Expression::Cast(Box::new(Cast {
39759 this: convert,
39760 to: DataType::Date,
39761 trailing_comments: vec![],
39762 double_colon_syntax: false,
39763 format: None,
39764 default: None,
39765 inferred_type: None,
39766 })))
39767 }
39768 _ => {
39769 // PostgreSQL, MySQL, etc.: CURRENT_DATE AT TIME ZONE 'UTC'
39770 let cd = Expression::CurrentDate(crate::expressions::CurrentDate);
39771 Ok(Expression::AtTimeZone(Box::new(
39772 crate::expressions::AtTimeZone {
39773 this: cd,
39774 zone: tz_arg,
39775 },
39776 )))
39777 }
39778 }
39779 } else if (name == "CURRENT_TIMESTAMP"
39780 || name == "CURRENT_TIME"
39781 || name == "CURRENT_DATE")
39782 && args.is_empty()
39783 && matches!(
39784 target,
39785 DialectType::PostgreSQL
39786 | DialectType::DuckDB
39787 | DialectType::Presto
39788 | DialectType::Trino
39789 )
39790 {
39791 // These targets want no-parens CURRENT_TIMESTAMP / CURRENT_DATE / CURRENT_TIME
39792 if name == "CURRENT_TIMESTAMP" {
39793 Ok(Expression::CurrentTimestamp(
39794 crate::expressions::CurrentTimestamp {
39795 precision: None,
39796 sysdate: false,
39797 },
39798 ))
39799 } else if name == "CURRENT_DATE" {
39800 Ok(Expression::CurrentDate(crate::expressions::CurrentDate))
39801 } else {
39802 // CURRENT_TIME
39803 Ok(Expression::CurrentTime(crate::expressions::CurrentTime {
39804 precision: None,
39805 }))
39806 }
39807 } else {
39808 // All other targets: keep as function (with parens)
39809 Ok(Expression::Function(Box::new(Function::new(name, args))))
39810 }
39811 }
39812
39813 // JSON_QUERY(json, path) -> target-specific
39814 "JSON_QUERY" if args.len() == 2 => {
39815 match target {
39816 DialectType::DuckDB | DialectType::SQLite => {
39817 // json -> path syntax
39818 let json_expr = args.remove(0);
39819 let path = args.remove(0);
39820 Ok(Expression::JsonExtract(Box::new(
39821 crate::expressions::JsonExtractFunc {
39822 this: json_expr,
39823 path,
39824 returning: None,
39825 arrow_syntax: true,
39826 hash_arrow_syntax: false,
39827 wrapper_option: None,
39828 quotes_option: None,
39829 on_scalar_string: false,
39830 on_error: None,
39831 },
39832 )))
39833 }
39834 DialectType::Spark | DialectType::Databricks | DialectType::Hive => {
39835 Ok(Expression::Function(Box::new(Function::new(
39836 "GET_JSON_OBJECT".to_string(),
39837 args,
39838 ))))
39839 }
39840 DialectType::PostgreSQL | DialectType::Redshift => Ok(Expression::Function(
39841 Box::new(Function::new("JSON_EXTRACT_PATH".to_string(), args)),
39842 )),
39843 _ => Ok(Expression::Function(Box::new(Function::new(
39844 "JSON_QUERY".to_string(),
39845 args,
39846 )))),
39847 }
39848 }
39849
39850 // JSON_VALUE_ARRAY(json, path) -> target-specific
39851 "JSON_VALUE_ARRAY" if args.len() == 2 => {
39852 match target {
39853 DialectType::DuckDB => {
39854 // CAST(json -> path AS TEXT[])
39855 let json_expr = args.remove(0);
39856 let path = args.remove(0);
39857 let arrow = Expression::JsonExtract(Box::new(
39858 crate::expressions::JsonExtractFunc {
39859 this: json_expr,
39860 path,
39861 returning: None,
39862 arrow_syntax: true,
39863 hash_arrow_syntax: false,
39864 wrapper_option: None,
39865 quotes_option: None,
39866 on_scalar_string: false,
39867 on_error: None,
39868 },
39869 ));
39870 Ok(Expression::Cast(Box::new(Cast {
39871 this: arrow,
39872 to: DataType::Array {
39873 element_type: Box::new(DataType::Text),
39874 dimension: None,
39875 },
39876 trailing_comments: vec![],
39877 double_colon_syntax: false,
39878 format: None,
39879 default: None,
39880 inferred_type: None,
39881 })))
39882 }
39883 DialectType::Snowflake => {
39884 let json_expr = args.remove(0);
39885 let path_expr = args.remove(0);
39886 // Convert JSON path from $.path to just path
39887 let sf_path = if let Expression::Literal(ref lit) = path_expr {
39888 if let Literal::String(ref s) = lit.as_ref() {
39889 let trimmed = s.trim_start_matches('$').trim_start_matches('.');
39890 Expression::Literal(Box::new(Literal::String(trimmed.to_string())))
39891 } else {
39892 path_expr.clone()
39893 }
39894 } else {
39895 path_expr
39896 };
39897 let parse_json = Expression::Function(Box::new(Function::new(
39898 "PARSE_JSON".to_string(),
39899 vec![json_expr],
39900 )));
39901 let get_path = Expression::Function(Box::new(Function::new(
39902 "GET_PATH".to_string(),
39903 vec![parse_json, sf_path],
39904 )));
39905 // TRANSFORM(get_path, x -> CAST(x AS VARCHAR))
39906 let cast_expr = Expression::Cast(Box::new(Cast {
39907 this: Expression::Identifier(Identifier::new("x")),
39908 to: DataType::VarChar {
39909 length: None,
39910 parenthesized_length: false,
39911 },
39912 trailing_comments: vec![],
39913 double_colon_syntax: false,
39914 format: None,
39915 default: None,
39916 inferred_type: None,
39917 }));
39918 let lambda = Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
39919 parameters: vec![Identifier::new("x")],
39920 body: cast_expr,
39921 colon: false,
39922 parameter_types: vec![],
39923 }));
39924 Ok(Expression::Function(Box::new(Function::new(
39925 "TRANSFORM".to_string(),
39926 vec![get_path, lambda],
39927 ))))
39928 }
39929 _ => Ok(Expression::Function(Box::new(Function::new(
39930 "JSON_VALUE_ARRAY".to_string(),
39931 args,
39932 )))),
39933 }
39934 }
39935
39936 // BigQuery REGEXP_EXTRACT(val, regex[, position[, occurrence]]) -> target dialects
39937 // BigQuery's 3rd arg is "position" (starting char index), 4th is "occurrence" (which match to return)
39938 // This is different from Hive/Spark where 3rd arg is "group_index"
39939 "REGEXP_EXTRACT" if matches!(source, DialectType::BigQuery) => {
39940 match target {
39941 DialectType::DuckDB
39942 | DialectType::Presto
39943 | DialectType::Trino
39944 | DialectType::Athena => {
39945 if args.len() == 2 {
39946 // REGEXP_EXTRACT(val, regex) -> REGEXP_EXTRACT(val, regex, 1)
39947 args.push(Expression::number(1));
39948 Ok(Expression::Function(Box::new(Function::new(
39949 "REGEXP_EXTRACT".to_string(),
39950 args,
39951 ))))
39952 } else if args.len() == 3 {
39953 let val = args.remove(0);
39954 let regex = args.remove(0);
39955 let position = args.remove(0);
39956 let is_pos_1 = matches!(&position, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "1"));
39957 if is_pos_1 {
39958 Ok(Expression::Function(Box::new(Function::new(
39959 "REGEXP_EXTRACT".to_string(),
39960 vec![val, regex, Expression::number(1)],
39961 ))))
39962 } else {
39963 let substring_expr = Expression::Function(Box::new(Function::new(
39964 "SUBSTRING".to_string(),
39965 vec![val, position],
39966 )));
39967 let nullif_expr = Expression::Function(Box::new(Function::new(
39968 "NULLIF".to_string(),
39969 vec![
39970 substring_expr,
39971 Expression::Literal(Box::new(Literal::String(
39972 String::new(),
39973 ))),
39974 ],
39975 )));
39976 Ok(Expression::Function(Box::new(Function::new(
39977 "REGEXP_EXTRACT".to_string(),
39978 vec![nullif_expr, regex, Expression::number(1)],
39979 ))))
39980 }
39981 } else if args.len() == 4 {
39982 let val = args.remove(0);
39983 let regex = args.remove(0);
39984 let position = args.remove(0);
39985 let occurrence = args.remove(0);
39986 let is_pos_1 = matches!(&position, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "1"));
39987 let is_occ_1 = matches!(&occurrence, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "1"));
39988 if is_pos_1 && is_occ_1 {
39989 Ok(Expression::Function(Box::new(Function::new(
39990 "REGEXP_EXTRACT".to_string(),
39991 vec![val, regex, Expression::number(1)],
39992 ))))
39993 } else {
39994 let subject = if is_pos_1 {
39995 val
39996 } else {
39997 let substring_expr = Expression::Function(Box::new(
39998 Function::new("SUBSTRING".to_string(), vec![val, position]),
39999 ));
40000 Expression::Function(Box::new(Function::new(
40001 "NULLIF".to_string(),
40002 vec![
40003 substring_expr,
40004 Expression::Literal(Box::new(Literal::String(
40005 String::new(),
40006 ))),
40007 ],
40008 )))
40009 };
40010 let extract_all = Expression::Function(Box::new(Function::new(
40011 "REGEXP_EXTRACT_ALL".to_string(),
40012 vec![subject, regex, Expression::number(1)],
40013 )));
40014 Ok(Expression::Function(Box::new(Function::new(
40015 "ARRAY_EXTRACT".to_string(),
40016 vec![extract_all, occurrence],
40017 ))))
40018 }
40019 } else {
40020 Ok(Expression::Function(Box::new(Function {
40021 name: f.name,
40022 args,
40023 distinct: f.distinct,
40024 trailing_comments: f.trailing_comments,
40025 use_bracket_syntax: f.use_bracket_syntax,
40026 no_parens: f.no_parens,
40027 quoted: f.quoted,
40028 span: None,
40029 inferred_type: None,
40030 })))
40031 }
40032 }
40033 DialectType::Snowflake => {
40034 // BigQuery REGEXP_EXTRACT -> Snowflake REGEXP_SUBSTR
40035 Ok(Expression::Function(Box::new(Function::new(
40036 "REGEXP_SUBSTR".to_string(),
40037 args,
40038 ))))
40039 }
40040 _ => {
40041 // For other targets (Hive/Spark/BigQuery): pass through as-is
40042 // BigQuery's default group behavior matches Hive/Spark for 2-arg case
40043 Ok(Expression::Function(Box::new(Function {
40044 name: f.name,
40045 args,
40046 distinct: f.distinct,
40047 trailing_comments: f.trailing_comments,
40048 use_bracket_syntax: f.use_bracket_syntax,
40049 no_parens: f.no_parens,
40050 quoted: f.quoted,
40051 span: None,
40052 inferred_type: None,
40053 })))
40054 }
40055 }
40056 }
40057
40058 // BigQuery STRUCT(args) -> target-specific struct expression
40059 "STRUCT" => {
40060 // Convert Function args to Struct fields
40061 let mut fields: Vec<(Option<String>, Expression)> = Vec::new();
40062 for (i, arg) in args.into_iter().enumerate() {
40063 match arg {
40064 Expression::Alias(a) => {
40065 // Named field: expr AS name
40066 fields.push((Some(a.alias.name.clone()), a.this));
40067 }
40068 other => {
40069 // Unnamed field: for Spark/Hive, keep as None
40070 // For Snowflake, auto-name as _N
40071 // For DuckDB, use column name for column refs, _N for others
40072 if matches!(target, DialectType::Snowflake) {
40073 fields.push((Some(format!("_{}", i)), other));
40074 } else if matches!(target, DialectType::DuckDB) {
40075 let auto_name = match &other {
40076 Expression::Column(col) => col.name.name.clone(),
40077 _ => format!("_{}", i),
40078 };
40079 fields.push((Some(auto_name), other));
40080 } else {
40081 fields.push((None, other));
40082 }
40083 }
40084 }
40085 }
40086
40087 match target {
40088 DialectType::Snowflake => {
40089 // OBJECT_CONSTRUCT('name', value, ...)
40090 let mut oc_args = Vec::new();
40091 for (name, val) in &fields {
40092 if let Some(n) = name {
40093 oc_args.push(Expression::Literal(Box::new(Literal::String(
40094 n.clone(),
40095 ))));
40096 oc_args.push(val.clone());
40097 } else {
40098 oc_args.push(val.clone());
40099 }
40100 }
40101 Ok(Expression::Function(Box::new(Function::new(
40102 "OBJECT_CONSTRUCT".to_string(),
40103 oc_args,
40104 ))))
40105 }
40106 DialectType::DuckDB => {
40107 // {'name': value, ...}
40108 Ok(Expression::Struct(Box::new(crate::expressions::Struct {
40109 fields,
40110 })))
40111 }
40112 DialectType::Hive => {
40113 // STRUCT(val1, val2, ...) - strip aliases
40114 let hive_fields: Vec<(Option<String>, Expression)> =
40115 fields.into_iter().map(|(_, v)| (None, v)).collect();
40116 Ok(Expression::Struct(Box::new(crate::expressions::Struct {
40117 fields: hive_fields,
40118 })))
40119 }
40120 DialectType::Spark | DialectType::Databricks => {
40121 // Use Expression::Struct to bypass Spark target transform auto-naming
40122 Ok(Expression::Struct(Box::new(crate::expressions::Struct {
40123 fields,
40124 })))
40125 }
40126 DialectType::Presto | DialectType::Trino | DialectType::Athena => {
40127 // Check if all fields are named AND all have inferable types - if so, wrap in CAST(ROW(...) AS ROW(name TYPE, ...))
40128 let all_named =
40129 !fields.is_empty() && fields.iter().all(|(name, _)| name.is_some());
40130 let all_types_inferable = all_named
40131 && fields
40132 .iter()
40133 .all(|(_, val)| Self::can_infer_presto_type(val));
40134 let row_args: Vec<Expression> =
40135 fields.iter().map(|(_, v)| v.clone()).collect();
40136 let row_expr = Expression::Function(Box::new(Function::new(
40137 "ROW".to_string(),
40138 row_args,
40139 )));
40140 if all_named && all_types_inferable {
40141 // Build ROW type with inferred types
40142 let mut row_type_fields = Vec::new();
40143 for (name, val) in &fields {
40144 if let Some(n) = name {
40145 let type_str = Self::infer_sql_type_for_presto(val);
40146 row_type_fields.push(crate::expressions::StructField::new(
40147 n.clone(),
40148 crate::expressions::DataType::Custom { name: type_str },
40149 ));
40150 }
40151 }
40152 let row_type = crate::expressions::DataType::Struct {
40153 fields: row_type_fields,
40154 nested: true,
40155 };
40156 Ok(Expression::Cast(Box::new(Cast {
40157 this: row_expr,
40158 to: row_type,
40159 trailing_comments: Vec::new(),
40160 double_colon_syntax: false,
40161 format: None,
40162 default: None,
40163 inferred_type: None,
40164 })))
40165 } else {
40166 Ok(row_expr)
40167 }
40168 }
40169 _ => {
40170 // Default: keep as STRUCT function with original args
40171 let mut new_args = Vec::new();
40172 for (name, val) in fields {
40173 if let Some(n) = name {
40174 new_args.push(Expression::Alias(Box::new(
40175 crate::expressions::Alias::new(val, Identifier::new(n)),
40176 )));
40177 } else {
40178 new_args.push(val);
40179 }
40180 }
40181 Ok(Expression::Function(Box::new(Function::new(
40182 "STRUCT".to_string(),
40183 new_args,
40184 ))))
40185 }
40186 }
40187 }
40188
40189 // ROUND(x, n, 'ROUND_HALF_EVEN') -> ROUND_EVEN(x, n) for DuckDB
40190 "ROUND" if args.len() == 3 => {
40191 let x = args.remove(0);
40192 let n = args.remove(0);
40193 let mode = args.remove(0);
40194 // Check if mode is 'ROUND_HALF_EVEN'
40195 let is_half_even = matches!(&mode, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(s) if s.eq_ignore_ascii_case("ROUND_HALF_EVEN")));
40196 if is_half_even && matches!(target, DialectType::DuckDB) {
40197 Ok(Expression::Function(Box::new(Function::new(
40198 "ROUND_EVEN".to_string(),
40199 vec![x, n],
40200 ))))
40201 } else {
40202 // Pass through with all args
40203 Ok(Expression::Function(Box::new(Function::new(
40204 "ROUND".to_string(),
40205 vec![x, n, mode],
40206 ))))
40207 }
40208 }
40209
40210 // MAKE_INTERVAL(year, month, named_args...) -> INTERVAL string for Snowflake/DuckDB
40211 "MAKE_INTERVAL" => {
40212 // MAKE_INTERVAL(1, 2, minute => 5, day => 3)
40213 // The positional args are: year, month
40214 // Named args are: day =>, minute =>, etc.
40215 // For Snowflake: INTERVAL '1 year, 2 month, 5 minute, 3 day'
40216 // For DuckDB: INTERVAL '1 year 2 month 5 minute 3 day'
40217 // For BigQuery->BigQuery: reorder named args (day before minute)
40218 if matches!(target, DialectType::Snowflake | DialectType::DuckDB) {
40219 let mut parts: Vec<(String, String)> = Vec::new();
40220 let mut pos_idx = 0;
40221 let pos_units = ["year", "month"];
40222 for arg in &args {
40223 if let Expression::NamedArgument(na) = arg {
40224 // Named arg like minute => 5
40225 let unit = na.name.name.clone();
40226 if let Expression::Literal(lit) = &na.value {
40227 if let Literal::Number(n) = lit.as_ref() {
40228 parts.push((unit, n.clone()));
40229 }
40230 }
40231 } else if pos_idx < pos_units.len() {
40232 if let Expression::Literal(lit) = arg {
40233 if let Literal::Number(n) = lit.as_ref() {
40234 parts.push((pos_units[pos_idx].to_string(), n.clone()));
40235 }
40236 }
40237 pos_idx += 1;
40238 }
40239 }
40240 // Don't sort - preserve original argument order
40241 let separator = if matches!(target, DialectType::Snowflake) {
40242 ", "
40243 } else {
40244 " "
40245 };
40246 let interval_str = parts
40247 .iter()
40248 .map(|(u, v)| format!("{} {}", v, u))
40249 .collect::<Vec<_>>()
40250 .join(separator);
40251 Ok(Expression::Interval(Box::new(
40252 crate::expressions::Interval {
40253 this: Some(Expression::Literal(Box::new(Literal::String(
40254 interval_str,
40255 )))),
40256 unit: None,
40257 },
40258 )))
40259 } else if matches!(target, DialectType::BigQuery) {
40260 // BigQuery->BigQuery: reorder named args (day, minute, etc.)
40261 let mut positional = Vec::new();
40262 let mut named: Vec<(
40263 String,
40264 Expression,
40265 crate::expressions::NamedArgSeparator,
40266 )> = Vec::new();
40267 let _pos_units = ["year", "month"];
40268 let mut _pos_idx = 0;
40269 for arg in args {
40270 if let Expression::NamedArgument(na) = arg {
40271 named.push((na.name.name.clone(), na.value, na.separator));
40272 } else {
40273 positional.push(arg);
40274 _pos_idx += 1;
40275 }
40276 }
40277 // Sort named args by: day, hour, minute, second
40278 let unit_order = |u: &str| -> usize {
40279 match u.to_ascii_lowercase().as_str() {
40280 "day" => 0,
40281 "hour" => 1,
40282 "minute" => 2,
40283 "second" => 3,
40284 _ => 4,
40285 }
40286 };
40287 named.sort_by_key(|(u, _, _)| unit_order(u));
40288 let mut result_args = positional;
40289 for (name, value, sep) in named {
40290 result_args.push(Expression::NamedArgument(Box::new(
40291 crate::expressions::NamedArgument {
40292 name: Identifier::new(&name),
40293 value,
40294 separator: sep,
40295 },
40296 )));
40297 }
40298 Ok(Expression::Function(Box::new(Function::new(
40299 "MAKE_INTERVAL".to_string(),
40300 result_args,
40301 ))))
40302 } else {
40303 Ok(Expression::Function(Box::new(Function::new(
40304 "MAKE_INTERVAL".to_string(),
40305 args,
40306 ))))
40307 }
40308 }
40309
40310 // ARRAY_TO_STRING(array, sep, null_text) -> ARRAY_TO_STRING(LIST_TRANSFORM(array, x -> COALESCE(x, null_text)), sep) for DuckDB
40311 "ARRAY_TO_STRING" if args.len() == 3 => {
40312 let arr = args.remove(0);
40313 let sep = args.remove(0);
40314 let null_text = args.remove(0);
40315 match target {
40316 DialectType::DuckDB => {
40317 // LIST_TRANSFORM(array, x -> COALESCE(x, null_text))
40318 let _lambda_param =
40319 Expression::Identifier(crate::expressions::Identifier::new("x"));
40320 let coalesce =
40321 Expression::Coalesce(Box::new(crate::expressions::VarArgFunc {
40322 original_name: None,
40323 expressions: vec![
40324 Expression::Identifier(crate::expressions::Identifier::new(
40325 "x",
40326 )),
40327 null_text,
40328 ],
40329 inferred_type: None,
40330 }));
40331 let lambda = Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
40332 parameters: vec![crate::expressions::Identifier::new("x")],
40333 body: coalesce,
40334 colon: false,
40335 parameter_types: vec![],
40336 }));
40337 let list_transform = Expression::Function(Box::new(Function::new(
40338 "LIST_TRANSFORM".to_string(),
40339 vec![arr, lambda],
40340 )));
40341 Ok(Expression::Function(Box::new(Function::new(
40342 "ARRAY_TO_STRING".to_string(),
40343 vec![list_transform, sep],
40344 ))))
40345 }
40346 _ => Ok(Expression::Function(Box::new(Function::new(
40347 "ARRAY_TO_STRING".to_string(),
40348 vec![arr, sep, null_text],
40349 )))),
40350 }
40351 }
40352
40353 // LENGTH(x) -> CASE TYPEOF(x) ... for DuckDB
40354 "LENGTH" if args.len() == 1 => {
40355 let arg = args.remove(0);
40356 match target {
40357 DialectType::DuckDB => {
40358 // CASE TYPEOF(foo) WHEN 'BLOB' THEN OCTET_LENGTH(CAST(foo AS BLOB)) ELSE LENGTH(CAST(foo AS TEXT)) END
40359 let typeof_func = Expression::Function(Box::new(Function::new(
40360 "TYPEOF".to_string(),
40361 vec![arg.clone()],
40362 )));
40363 let blob_cast = Expression::Cast(Box::new(Cast {
40364 this: arg.clone(),
40365 to: DataType::VarBinary { length: None },
40366 trailing_comments: vec![],
40367 double_colon_syntax: false,
40368 format: None,
40369 default: None,
40370 inferred_type: None,
40371 }));
40372 let octet_length = Expression::Function(Box::new(Function::new(
40373 "OCTET_LENGTH".to_string(),
40374 vec![blob_cast],
40375 )));
40376 let text_cast = Expression::Cast(Box::new(Cast {
40377 this: arg,
40378 to: DataType::Text,
40379 trailing_comments: vec![],
40380 double_colon_syntax: false,
40381 format: None,
40382 default: None,
40383 inferred_type: None,
40384 }));
40385 let length_text = Expression::Function(Box::new(Function::new(
40386 "LENGTH".to_string(),
40387 vec![text_cast],
40388 )));
40389 Ok(Expression::Case(Box::new(crate::expressions::Case {
40390 operand: Some(typeof_func),
40391 whens: vec![(
40392 Expression::Literal(Box::new(Literal::String("BLOB".to_string()))),
40393 octet_length,
40394 )],
40395 else_: Some(length_text),
40396 comments: Vec::new(),
40397 inferred_type: None,
40398 })))
40399 }
40400 _ => Ok(Expression::Function(Box::new(Function::new(
40401 "LENGTH".to_string(),
40402 vec![arg],
40403 )))),
40404 }
40405 }
40406
40407 // PERCENTILE_CONT(x, fraction RESPECT NULLS) -> QUANTILE_CONT(x, fraction) for DuckDB
40408 "PERCENTILE_CONT" if args.len() >= 2 && matches!(source, DialectType::BigQuery) => {
40409 // BigQuery PERCENTILE_CONT(x, fraction [RESPECT|IGNORE NULLS]) OVER ()
40410 // The args should be [x, fraction] with the null handling stripped
40411 // For DuckDB: QUANTILE_CONT(x, fraction)
40412 // For Spark: PERCENTILE_CONT(x, fraction) RESPECT NULLS (handled at window level)
40413 match target {
40414 DialectType::DuckDB => {
40415 // Strip down to just 2 args, rename to QUANTILE_CONT
40416 let x = args[0].clone();
40417 let frac = args[1].clone();
40418 Ok(Expression::Function(Box::new(Function::new(
40419 "QUANTILE_CONT".to_string(),
40420 vec![x, frac],
40421 ))))
40422 }
40423 _ => Ok(Expression::Function(Box::new(Function::new(
40424 "PERCENTILE_CONT".to_string(),
40425 args,
40426 )))),
40427 }
40428 }
40429
40430 // All others: pass through
40431 _ => Ok(Expression::Function(Box::new(Function {
40432 name: f.name,
40433 args,
40434 distinct: f.distinct,
40435 trailing_comments: f.trailing_comments,
40436 use_bracket_syntax: f.use_bracket_syntax,
40437 no_parens: f.no_parens,
40438 quoted: f.quoted,
40439 span: None,
40440 inferred_type: None,
40441 }))),
40442 }
40443 }
40444
40445 /// Check if we can reliably infer the SQL type for Presto/Trino ROW CAST.
40446 /// Returns false for column references and other non-literal expressions where the type is unknown.
40447 fn can_infer_presto_type(expr: &Expression) -> bool {
40448 match expr {
40449 Expression::Literal(_) => true,
40450 Expression::Boolean(_) => true,
40451 Expression::Array(_) | Expression::ArrayFunc(_) => true,
40452 Expression::Struct(_) | Expression::StructFunc(_) => true,
40453 Expression::Function(f) => {
40454 f.name.eq_ignore_ascii_case("STRUCT")
40455 || f.name.eq_ignore_ascii_case("ROW")
40456 || f.name.eq_ignore_ascii_case("CURRENT_DATE")
40457 || f.name.eq_ignore_ascii_case("CURRENT_TIMESTAMP")
40458 || f.name.eq_ignore_ascii_case("NOW")
40459 }
40460 Expression::Cast(_) => true,
40461 Expression::Neg(inner) => Self::can_infer_presto_type(&inner.this),
40462 _ => false,
40463 }
40464 }
40465
40466 /// Infer SQL type name for a Presto/Trino ROW CAST from a literal expression
40467 fn infer_sql_type_for_presto(expr: &Expression) -> String {
40468 use crate::expressions::Literal;
40469 match expr {
40470 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
40471 "VARCHAR".to_string()
40472 }
40473 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
40474 let Literal::Number(n) = lit.as_ref() else {
40475 unreachable!()
40476 };
40477 if n.contains('.') {
40478 "DOUBLE".to_string()
40479 } else {
40480 "INTEGER".to_string()
40481 }
40482 }
40483 Expression::Boolean(_) => "BOOLEAN".to_string(),
40484 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Date(_)) => {
40485 "DATE".to_string()
40486 }
40487 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Timestamp(_)) => {
40488 "TIMESTAMP".to_string()
40489 }
40490 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Datetime(_)) => {
40491 "TIMESTAMP".to_string()
40492 }
40493 Expression::Array(_) | Expression::ArrayFunc(_) => "ARRAY(VARCHAR)".to_string(),
40494 Expression::Struct(_) | Expression::StructFunc(_) => "ROW".to_string(),
40495 Expression::Function(f) => {
40496 if f.name.eq_ignore_ascii_case("STRUCT") || f.name.eq_ignore_ascii_case("ROW") {
40497 "ROW".to_string()
40498 } else if f.name.eq_ignore_ascii_case("CURRENT_DATE") {
40499 "DATE".to_string()
40500 } else if f.name.eq_ignore_ascii_case("CURRENT_TIMESTAMP")
40501 || f.name.eq_ignore_ascii_case("NOW")
40502 {
40503 "TIMESTAMP".to_string()
40504 } else {
40505 "VARCHAR".to_string()
40506 }
40507 }
40508 Expression::Cast(c) => {
40509 // If already cast, use the target type
40510 Self::data_type_to_presto_string(&c.to)
40511 }
40512 _ => "VARCHAR".to_string(),
40513 }
40514 }
40515
40516 /// Convert a DataType to its Presto/Trino string representation for ROW type
40517 fn data_type_to_presto_string(dt: &crate::expressions::DataType) -> String {
40518 use crate::expressions::DataType;
40519 match dt {
40520 DataType::VarChar { .. } | DataType::Text | DataType::String { .. } => {
40521 "VARCHAR".to_string()
40522 }
40523 DataType::Int { .. }
40524 | DataType::BigInt { .. }
40525 | DataType::SmallInt { .. }
40526 | DataType::TinyInt { .. } => "INTEGER".to_string(),
40527 DataType::Float { .. } | DataType::Double { .. } => "DOUBLE".to_string(),
40528 DataType::Boolean => "BOOLEAN".to_string(),
40529 DataType::Date => "DATE".to_string(),
40530 DataType::Timestamp { .. } => "TIMESTAMP".to_string(),
40531 DataType::Struct { fields, .. } => {
40532 let field_strs: Vec<String> = fields
40533 .iter()
40534 .map(|f| {
40535 format!(
40536 "{} {}",
40537 f.name,
40538 Self::data_type_to_presto_string(&f.data_type)
40539 )
40540 })
40541 .collect();
40542 format!("ROW({})", field_strs.join(", "))
40543 }
40544 DataType::Array { element_type, .. } => {
40545 format!("ARRAY({})", Self::data_type_to_presto_string(element_type))
40546 }
40547 DataType::Custom { name } => {
40548 // Pass through custom type names (e.g., "INTEGER", "VARCHAR" from earlier inference)
40549 name.clone()
40550 }
40551 _ => "VARCHAR".to_string(),
40552 }
40553 }
40554
40555 /// Convert IntervalUnit to string
40556 fn interval_unit_to_string(unit: &crate::expressions::IntervalUnit) -> &'static str {
40557 match unit {
40558 crate::expressions::IntervalUnit::Year => "YEAR",
40559 crate::expressions::IntervalUnit::Quarter => "QUARTER",
40560 crate::expressions::IntervalUnit::Month => "MONTH",
40561 crate::expressions::IntervalUnit::Week => "WEEK",
40562 crate::expressions::IntervalUnit::Day => "DAY",
40563 crate::expressions::IntervalUnit::Hour => "HOUR",
40564 crate::expressions::IntervalUnit::Minute => "MINUTE",
40565 crate::expressions::IntervalUnit::Second => "SECOND",
40566 crate::expressions::IntervalUnit::Millisecond => "MILLISECOND",
40567 crate::expressions::IntervalUnit::Microsecond => "MICROSECOND",
40568 crate::expressions::IntervalUnit::Nanosecond => "NANOSECOND",
40569 }
40570 }
40571
40572 /// Extract unit string from an expression (uppercased)
40573 fn get_unit_str_static(expr: &Expression) -> String {
40574 use crate::expressions::Literal;
40575 match expr {
40576 Expression::Identifier(id) => id.name.to_ascii_uppercase(),
40577 Expression::Var(v) => v.this.to_ascii_uppercase(),
40578 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
40579 let Literal::String(s) = lit.as_ref() else {
40580 unreachable!()
40581 };
40582 s.to_ascii_uppercase()
40583 }
40584 Expression::Cast(cast)
40585 if cast.format.is_none()
40586 && cast.default.is_none()
40587 && Self::unit_cast_target_is_string(&cast.to)
40588 && matches!(
40589 &cast.this,
40590 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_))
40591 ) =>
40592 {
40593 let Expression::Literal(lit) = &cast.this else {
40594 unreachable!()
40595 };
40596 let Literal::String(s) = lit.as_ref() else {
40597 unreachable!()
40598 };
40599 s.to_ascii_uppercase()
40600 }
40601 Expression::Column(col) => col.name.name.to_ascii_uppercase(),
40602 Expression::Function(f) => {
40603 let base = f.name.to_ascii_uppercase();
40604 if !f.args.is_empty() {
40605 let inner = Self::get_unit_str_static(&f.args[0]);
40606 format!("{}({})", base, inner)
40607 } else {
40608 base
40609 }
40610 }
40611 _ => "DAY".to_string(),
40612 }
40613 }
40614
40615 fn unit_cast_target_is_string(data_type: &DataType) -> bool {
40616 matches!(
40617 data_type,
40618 DataType::Char { .. }
40619 | DataType::VarChar { .. }
40620 | DataType::String { .. }
40621 | DataType::Text
40622 | DataType::TextWithLength { .. }
40623 ) || matches!(
40624 data_type,
40625 DataType::Custom { name, .. }
40626 if name.eq_ignore_ascii_case("VARCHAR")
40627 || name.eq_ignore_ascii_case("NVARCHAR")
40628 || name.eq_ignore_ascii_case("VARCHAR(MAX)")
40629 || name.eq_ignore_ascii_case("NVARCHAR(MAX)")
40630 )
40631 }
40632
40633 /// Parse unit string to IntervalUnit
40634 fn parse_interval_unit_static(s: &str) -> crate::expressions::IntervalUnit {
40635 match s {
40636 "YEAR" | "YY" | "YYYY" => crate::expressions::IntervalUnit::Year,
40637 "QUARTER" | "QQ" | "Q" => crate::expressions::IntervalUnit::Quarter,
40638 "MONTH" | "MONTHS" | "MON" | "MONS" | "MM" | "M" => {
40639 crate::expressions::IntervalUnit::Month
40640 }
40641 "WEEK" | "WK" | "WW" | "ISOWEEK" => crate::expressions::IntervalUnit::Week,
40642 "DAY" | "DD" | "D" | "DY" => crate::expressions::IntervalUnit::Day,
40643 "HOUR" | "HH" => crate::expressions::IntervalUnit::Hour,
40644 "MINUTE" | "MI" | "N" => crate::expressions::IntervalUnit::Minute,
40645 "SECOND" | "SS" | "S" => crate::expressions::IntervalUnit::Second,
40646 "MILLISECOND" | "MS" => crate::expressions::IntervalUnit::Millisecond,
40647 "MICROSECOND" | "MCS" | "US" => crate::expressions::IntervalUnit::Microsecond,
40648 _ if s.starts_with("WEEK(") => crate::expressions::IntervalUnit::Week,
40649 _ => crate::expressions::IntervalUnit::Day,
40650 }
40651 }
40652
40653 /// Convert expression to simple string for interval building
40654 fn expr_to_string_static(expr: &Expression) -> String {
40655 use crate::expressions::Literal;
40656 match expr {
40657 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
40658 let Literal::Number(s) = lit.as_ref() else {
40659 unreachable!()
40660 };
40661 s.clone()
40662 }
40663 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
40664 let Literal::String(s) = lit.as_ref() else {
40665 unreachable!()
40666 };
40667 s.clone()
40668 }
40669 Expression::Identifier(id) => id.name.clone(),
40670 Expression::Neg(f) => format!("-{}", Self::expr_to_string_static(&f.this)),
40671 _ => "1".to_string(),
40672 }
40673 }
40674
40675 /// Extract a simple string representation from a literal expression
40676 fn expr_to_string(expr: &Expression) -> String {
40677 use crate::expressions::Literal;
40678 match expr {
40679 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
40680 let Literal::Number(s) = lit.as_ref() else {
40681 unreachable!()
40682 };
40683 s.clone()
40684 }
40685 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
40686 let Literal::String(s) = lit.as_ref() else {
40687 unreachable!()
40688 };
40689 s.clone()
40690 }
40691 Expression::Neg(f) => format!("-{}", Self::expr_to_string(&f.this)),
40692 Expression::Identifier(id) => id.name.clone(),
40693 _ => "1".to_string(),
40694 }
40695 }
40696
40697 /// Quote an interval value expression as a string literal if it's a number (or negated number)
40698 fn quote_interval_val(expr: &Expression) -> Expression {
40699 use crate::expressions::Literal;
40700 match expr {
40701 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
40702 let Literal::Number(n) = lit.as_ref() else {
40703 unreachable!()
40704 };
40705 Expression::Literal(Box::new(Literal::String(n.clone())))
40706 }
40707 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => expr.clone(),
40708 Expression::Neg(inner) => {
40709 if let Expression::Literal(lit) = &inner.this {
40710 if let Literal::Number(n) = lit.as_ref() {
40711 Expression::Literal(Box::new(Literal::String(format!("-{}", n))))
40712 } else {
40713 inner.this.clone()
40714 }
40715 } else {
40716 expr.clone()
40717 }
40718 }
40719 _ => expr.clone(),
40720 }
40721 }
40722
40723 /// Check if a timestamp string contains timezone info (offset like +02:00, or named timezone)
40724 fn timestamp_string_has_timezone(ts: &str) -> bool {
40725 let trimmed = ts.trim();
40726 // Check for numeric timezone offsets: +N, -N, +NN:NN, -NN:NN at end
40727 if let Some(last_space) = trimmed.rfind(' ') {
40728 let suffix = &trimmed[last_space + 1..];
40729 if (suffix.starts_with('+') || suffix.starts_with('-')) && suffix.len() > 1 {
40730 let rest = &suffix[1..];
40731 if rest.chars().all(|c| c.is_ascii_digit() || c == ':') {
40732 return true;
40733 }
40734 }
40735 }
40736 // Check for named timezone abbreviations
40737 let ts_lower = trimmed.to_ascii_lowercase();
40738 let tz_abbrevs = [" utc", " gmt", " cet", " est", " pst", " cst", " mst"];
40739 for abbrev in &tz_abbrevs {
40740 if ts_lower.ends_with(abbrev) {
40741 return true;
40742 }
40743 }
40744 false
40745 }
40746
40747 /// Maybe CAST timestamp literal to TIMESTAMPTZ for Snowflake
40748 fn maybe_cast_ts_to_tz(expr: Expression, func_name: &str) -> Expression {
40749 use crate::expressions::{Cast, DataType, Literal};
40750 match expr {
40751 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Timestamp(_)) => {
40752 let Literal::Timestamp(s) = lit.as_ref() else {
40753 unreachable!()
40754 };
40755 let tz = func_name.starts_with("TIMESTAMP");
40756 Expression::Cast(Box::new(Cast {
40757 this: Expression::Literal(Box::new(Literal::String(s.clone()))),
40758 to: if tz {
40759 DataType::Timestamp {
40760 timezone: true,
40761 precision: None,
40762 }
40763 } else {
40764 DataType::Timestamp {
40765 timezone: false,
40766 precision: None,
40767 }
40768 },
40769 trailing_comments: vec![],
40770 double_colon_syntax: false,
40771 format: None,
40772 default: None,
40773 inferred_type: None,
40774 }))
40775 }
40776 other => other,
40777 }
40778 }
40779
40780 /// Maybe CAST timestamp literal to TIMESTAMP (no tz)
40781 fn maybe_cast_ts(expr: Expression) -> Expression {
40782 use crate::expressions::{Cast, DataType, Literal};
40783 match expr {
40784 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Timestamp(_)) => {
40785 let Literal::Timestamp(s) = lit.as_ref() else {
40786 unreachable!()
40787 };
40788 Expression::Cast(Box::new(Cast {
40789 this: Expression::Literal(Box::new(Literal::String(s.clone()))),
40790 to: DataType::Timestamp {
40791 timezone: false,
40792 precision: None,
40793 },
40794 trailing_comments: vec![],
40795 double_colon_syntax: false,
40796 format: None,
40797 default: None,
40798 inferred_type: None,
40799 }))
40800 }
40801 other => other,
40802 }
40803 }
40804
40805 /// Convert DATE 'x' literal to CAST('x' AS DATE)
40806 fn date_literal_to_cast(expr: Expression) -> Expression {
40807 use crate::expressions::{Cast, DataType, Literal};
40808 match expr {
40809 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Date(_)) => {
40810 let Literal::Date(s) = lit.as_ref() else {
40811 unreachable!()
40812 };
40813 Expression::Cast(Box::new(Cast {
40814 this: Expression::Literal(Box::new(Literal::String(s.clone()))),
40815 to: DataType::Date,
40816 trailing_comments: vec![],
40817 double_colon_syntax: false,
40818 format: None,
40819 default: None,
40820 inferred_type: None,
40821 }))
40822 }
40823 other => other,
40824 }
40825 }
40826
40827 /// Ensure an expression that should be a date is CAST(... AS DATE).
40828 /// Handles both DATE literals and string literals that look like dates.
40829 fn ensure_cast_date(expr: Expression) -> Expression {
40830 use crate::expressions::{Cast, DataType, Literal};
40831 match expr {
40832 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Date(_)) => {
40833 let Literal::Date(s) = lit.as_ref() else {
40834 unreachable!()
40835 };
40836 Expression::Cast(Box::new(Cast {
40837 this: Expression::Literal(Box::new(Literal::String(s.clone()))),
40838 to: DataType::Date,
40839 trailing_comments: vec![],
40840 double_colon_syntax: false,
40841 format: None,
40842 default: None,
40843 inferred_type: None,
40844 }))
40845 }
40846 Expression::Literal(ref lit) if matches!(lit.as_ref(), Literal::String(ref _s)) => {
40847 // String literal that should be a date -> CAST('s' AS DATE)
40848 Expression::Cast(Box::new(Cast {
40849 this: expr,
40850 to: DataType::Date,
40851 trailing_comments: vec![],
40852 double_colon_syntax: false,
40853 format: None,
40854 default: None,
40855 inferred_type: None,
40856 }))
40857 }
40858 // Already a CAST or other expression -> leave as-is
40859 other => other,
40860 }
40861 }
40862
40863 /// Force CAST(expr AS DATE) for any expression (not just literals)
40864 /// Skips if the expression is already a CAST to DATE
40865 fn force_cast_date(expr: Expression) -> Expression {
40866 use crate::expressions::{Cast, DataType};
40867 // If it's already a CAST to DATE, don't double-wrap
40868 if let Expression::Cast(ref c) = expr {
40869 if matches!(c.to, DataType::Date) {
40870 return expr;
40871 }
40872 }
40873 Expression::Cast(Box::new(Cast {
40874 this: expr,
40875 to: DataType::Date,
40876 trailing_comments: vec![],
40877 double_colon_syntax: false,
40878 format: None,
40879 default: None,
40880 inferred_type: None,
40881 }))
40882 }
40883
40884 /// Internal TO_DATE function that won't be converted to CAST by the Snowflake handler.
40885 /// Uses the name `_POLYGLOT_TO_DATE` which is not recognized by the TO_DATE -> CAST logic.
40886 /// The Snowflake DATEDIFF handler converts these back to TO_DATE.
40887 const PRESERVED_TO_DATE: &'static str = "_POLYGLOT_TO_DATE";
40888
40889 fn ensure_to_date_preserved(expr: Expression) -> Expression {
40890 use crate::expressions::{Function, Literal};
40891 if matches!(expr, Expression::Literal(ref lit) if matches!(lit.as_ref(), Literal::String(_)))
40892 {
40893 Expression::Function(Box::new(Function::new(
40894 Self::PRESERVED_TO_DATE.to_string(),
40895 vec![expr],
40896 )))
40897 } else {
40898 expr
40899 }
40900 }
40901
40902 /// TRY_CAST(expr AS DATE) - used for DuckDB when TO_DATE is unwrapped
40903 fn try_cast_date(expr: Expression) -> Expression {
40904 use crate::expressions::{Cast, DataType};
40905 Expression::TryCast(Box::new(Cast {
40906 this: expr,
40907 to: DataType::Date,
40908 trailing_comments: vec![],
40909 double_colon_syntax: false,
40910 format: None,
40911 default: None,
40912 inferred_type: None,
40913 }))
40914 }
40915
40916 /// CAST(CAST(expr AS TIMESTAMP) AS DATE) - used when Hive string dates need to be cast
40917 fn double_cast_timestamp_date(expr: Expression) -> Expression {
40918 use crate::expressions::{Cast, DataType};
40919 let inner = Expression::Cast(Box::new(Cast {
40920 this: expr,
40921 to: DataType::Timestamp {
40922 timezone: false,
40923 precision: None,
40924 },
40925 trailing_comments: vec![],
40926 double_colon_syntax: false,
40927 format: None,
40928 default: None,
40929 inferred_type: None,
40930 }));
40931 Expression::Cast(Box::new(Cast {
40932 this: inner,
40933 to: DataType::Date,
40934 trailing_comments: vec![],
40935 double_colon_syntax: false,
40936 format: None,
40937 default: None,
40938 inferred_type: None,
40939 }))
40940 }
40941
40942 /// CAST(CAST(expr AS DATETIME) AS DATE) - BigQuery variant
40943 fn double_cast_datetime_date(expr: Expression) -> Expression {
40944 use crate::expressions::{Cast, DataType};
40945 let inner = Expression::Cast(Box::new(Cast {
40946 this: expr,
40947 to: DataType::Custom {
40948 name: "DATETIME".to_string(),
40949 },
40950 trailing_comments: vec![],
40951 double_colon_syntax: false,
40952 format: None,
40953 default: None,
40954 inferred_type: None,
40955 }));
40956 Expression::Cast(Box::new(Cast {
40957 this: inner,
40958 to: DataType::Date,
40959 trailing_comments: vec![],
40960 double_colon_syntax: false,
40961 format: None,
40962 default: None,
40963 inferred_type: None,
40964 }))
40965 }
40966
40967 /// CAST(CAST(expr AS DATETIME2) AS DATE) - TSQL variant
40968 fn double_cast_datetime2_date(expr: Expression) -> Expression {
40969 use crate::expressions::{Cast, DataType};
40970 let inner = Expression::Cast(Box::new(Cast {
40971 this: expr,
40972 to: DataType::Custom {
40973 name: "DATETIME2".to_string(),
40974 },
40975 trailing_comments: vec![],
40976 double_colon_syntax: false,
40977 format: None,
40978 default: None,
40979 inferred_type: None,
40980 }));
40981 Expression::Cast(Box::new(Cast {
40982 this: inner,
40983 to: DataType::Date,
40984 trailing_comments: vec![],
40985 double_colon_syntax: false,
40986 format: None,
40987 default: None,
40988 inferred_type: None,
40989 }))
40990 }
40991
40992 /// Convert Hive/Java-style date format strings to C-style (strftime) format
40993 /// e.g., "yyyy-MM-dd'T'HH" -> "%Y-%m-%d'T'%H"
40994 fn hive_format_to_c_format(fmt: &str) -> String {
40995 let mut result = String::new();
40996 let chars: Vec<char> = fmt.chars().collect();
40997 let mut i = 0;
40998 while i < chars.len() {
40999 match chars[i] {
41000 'y' => {
41001 let mut count = 0;
41002 while i < chars.len() && chars[i] == 'y' {
41003 count += 1;
41004 i += 1;
41005 }
41006 if count >= 4 {
41007 result.push_str("%Y");
41008 } else if count == 2 {
41009 result.push_str("%y");
41010 } else {
41011 result.push_str("%Y");
41012 }
41013 }
41014 'M' => {
41015 let mut count = 0;
41016 while i < chars.len() && chars[i] == 'M' {
41017 count += 1;
41018 i += 1;
41019 }
41020 if count >= 3 {
41021 result.push_str("%b");
41022 } else if count == 2 {
41023 result.push_str("%m");
41024 } else {
41025 result.push_str("%m");
41026 }
41027 }
41028 'd' => {
41029 let mut _count = 0;
41030 while i < chars.len() && chars[i] == 'd' {
41031 _count += 1;
41032 i += 1;
41033 }
41034 result.push_str("%d");
41035 }
41036 'H' => {
41037 let mut _count = 0;
41038 while i < chars.len() && chars[i] == 'H' {
41039 _count += 1;
41040 i += 1;
41041 }
41042 result.push_str("%H");
41043 }
41044 'h' => {
41045 let mut _count = 0;
41046 while i < chars.len() && chars[i] == 'h' {
41047 _count += 1;
41048 i += 1;
41049 }
41050 result.push_str("%I");
41051 }
41052 'm' => {
41053 let mut _count = 0;
41054 while i < chars.len() && chars[i] == 'm' {
41055 _count += 1;
41056 i += 1;
41057 }
41058 result.push_str("%M");
41059 }
41060 's' => {
41061 let mut _count = 0;
41062 while i < chars.len() && chars[i] == 's' {
41063 _count += 1;
41064 i += 1;
41065 }
41066 result.push_str("%S");
41067 }
41068 'S' => {
41069 // Fractional seconds - skip
41070 while i < chars.len() && chars[i] == 'S' {
41071 i += 1;
41072 }
41073 result.push_str("%f");
41074 }
41075 'a' => {
41076 // AM/PM
41077 while i < chars.len() && chars[i] == 'a' {
41078 i += 1;
41079 }
41080 result.push_str("%p");
41081 }
41082 'E' => {
41083 let mut count = 0;
41084 while i < chars.len() && chars[i] == 'E' {
41085 count += 1;
41086 i += 1;
41087 }
41088 if count >= 4 {
41089 result.push_str("%A");
41090 } else {
41091 result.push_str("%a");
41092 }
41093 }
41094 '\'' => {
41095 // Quoted literal text - pass through the quotes and content
41096 result.push('\'');
41097 i += 1;
41098 while i < chars.len() && chars[i] != '\'' {
41099 result.push(chars[i]);
41100 i += 1;
41101 }
41102 if i < chars.len() {
41103 result.push('\'');
41104 i += 1;
41105 }
41106 }
41107 c => {
41108 result.push(c);
41109 i += 1;
41110 }
41111 }
41112 }
41113 result
41114 }
41115
41116 /// Convert Hive/Java format to Presto format (uses %T for HH:mm:ss)
41117 fn hive_format_to_presto_format(fmt: &str) -> String {
41118 let c_fmt = Self::hive_format_to_c_format(fmt);
41119 // Presto uses %T for HH:MM:SS
41120 c_fmt.replace("%H:%M:%S", "%T")
41121 }
41122
41123 /// Ensure a timestamp-like expression for DuckDB with CAST(... AS TIMESTAMP)
41124 fn ensure_cast_timestamp(expr: Expression) -> Expression {
41125 use crate::expressions::{Cast, DataType, Literal};
41126 match expr {
41127 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Timestamp(_)) => {
41128 let Literal::Timestamp(s) = lit.as_ref() else {
41129 unreachable!()
41130 };
41131 Expression::Cast(Box::new(Cast {
41132 this: Expression::Literal(Box::new(Literal::String(s.clone()))),
41133 to: DataType::Timestamp {
41134 timezone: false,
41135 precision: None,
41136 },
41137 trailing_comments: vec![],
41138 double_colon_syntax: false,
41139 format: None,
41140 default: None,
41141 inferred_type: None,
41142 }))
41143 }
41144 Expression::Literal(ref lit) if matches!(lit.as_ref(), Literal::String(ref _s)) => {
41145 Expression::Cast(Box::new(Cast {
41146 this: expr,
41147 to: DataType::Timestamp {
41148 timezone: false,
41149 precision: None,
41150 },
41151 trailing_comments: vec![],
41152 double_colon_syntax: false,
41153 format: None,
41154 default: None,
41155 inferred_type: None,
41156 }))
41157 }
41158 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Datetime(_)) => {
41159 let Literal::Datetime(s) = lit.as_ref() else {
41160 unreachable!()
41161 };
41162 Expression::Cast(Box::new(Cast {
41163 this: Expression::Literal(Box::new(Literal::String(s.clone()))),
41164 to: DataType::Timestamp {
41165 timezone: false,
41166 precision: None,
41167 },
41168 trailing_comments: vec![],
41169 double_colon_syntax: false,
41170 format: None,
41171 default: None,
41172 inferred_type: None,
41173 }))
41174 }
41175 other => other,
41176 }
41177 }
41178
41179 /// Force CAST to TIMESTAMP for any expression (not just literals)
41180 /// Used when transpiling from Redshift/TSQL where DATEDIFF/DATEADD args need explicit timestamp cast
41181 fn force_cast_timestamp(expr: Expression) -> Expression {
41182 use crate::expressions::{Cast, DataType};
41183 // Don't double-wrap if already a CAST to TIMESTAMP
41184 if let Expression::Cast(ref c) = expr {
41185 if matches!(c.to, DataType::Timestamp { .. }) {
41186 return expr;
41187 }
41188 }
41189 Expression::Cast(Box::new(Cast {
41190 this: expr,
41191 to: DataType::Timestamp {
41192 timezone: false,
41193 precision: None,
41194 },
41195 trailing_comments: vec![],
41196 double_colon_syntax: false,
41197 format: None,
41198 default: None,
41199 inferred_type: None,
41200 }))
41201 }
41202
41203 /// Ensure a timestamp-like expression for DuckDB with CAST(... AS TIMESTAMPTZ)
41204 fn ensure_cast_timestamptz(expr: Expression) -> Expression {
41205 use crate::expressions::{Cast, DataType, Literal};
41206 match expr {
41207 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Timestamp(_)) => {
41208 let Literal::Timestamp(s) = lit.as_ref() else {
41209 unreachable!()
41210 };
41211 Expression::Cast(Box::new(Cast {
41212 this: Expression::Literal(Box::new(Literal::String(s.clone()))),
41213 to: DataType::Timestamp {
41214 timezone: true,
41215 precision: None,
41216 },
41217 trailing_comments: vec![],
41218 double_colon_syntax: false,
41219 format: None,
41220 default: None,
41221 inferred_type: None,
41222 }))
41223 }
41224 Expression::Literal(ref lit) if matches!(lit.as_ref(), Literal::String(ref _s)) => {
41225 Expression::Cast(Box::new(Cast {
41226 this: expr,
41227 to: DataType::Timestamp {
41228 timezone: true,
41229 precision: None,
41230 },
41231 trailing_comments: vec![],
41232 double_colon_syntax: false,
41233 format: None,
41234 default: None,
41235 inferred_type: None,
41236 }))
41237 }
41238 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Datetime(_)) => {
41239 let Literal::Datetime(s) = lit.as_ref() else {
41240 unreachable!()
41241 };
41242 Expression::Cast(Box::new(Cast {
41243 this: Expression::Literal(Box::new(Literal::String(s.clone()))),
41244 to: DataType::Timestamp {
41245 timezone: true,
41246 precision: None,
41247 },
41248 trailing_comments: vec![],
41249 double_colon_syntax: false,
41250 format: None,
41251 default: None,
41252 inferred_type: None,
41253 }))
41254 }
41255 other => other,
41256 }
41257 }
41258
41259 /// Ensure expression is CAST to DATETIME (for BigQuery)
41260 fn ensure_cast_datetime(expr: Expression) -> Expression {
41261 use crate::expressions::{Cast, DataType, Literal};
41262 match expr {
41263 Expression::Literal(ref lit) if matches!(lit.as_ref(), Literal::String(ref _s)) => {
41264 Expression::Cast(Box::new(Cast {
41265 this: expr,
41266 to: DataType::Custom {
41267 name: "DATETIME".to_string(),
41268 },
41269 trailing_comments: vec![],
41270 double_colon_syntax: false,
41271 format: None,
41272 default: None,
41273 inferred_type: None,
41274 }))
41275 }
41276 other => other,
41277 }
41278 }
41279
41280 /// Force CAST expression to DATETIME (for BigQuery) - always wraps unless already DATETIME
41281 fn force_cast_datetime(expr: Expression) -> Expression {
41282 use crate::expressions::{Cast, DataType};
41283 if let Expression::Cast(ref c) = expr {
41284 if let DataType::Custom { ref name } = c.to {
41285 if name.eq_ignore_ascii_case("DATETIME") {
41286 return expr;
41287 }
41288 }
41289 }
41290 Expression::Cast(Box::new(Cast {
41291 this: expr,
41292 to: DataType::Custom {
41293 name: "DATETIME".to_string(),
41294 },
41295 trailing_comments: vec![],
41296 double_colon_syntax: false,
41297 format: None,
41298 default: None,
41299 inferred_type: None,
41300 }))
41301 }
41302
41303 /// Ensure expression is CAST to DATETIME2 (for TSQL)
41304 fn ensure_cast_datetime2(expr: Expression) -> Expression {
41305 use crate::expressions::{Cast, DataType, Literal};
41306 match expr {
41307 Expression::Literal(ref lit) if matches!(lit.as_ref(), Literal::String(ref _s)) => {
41308 Expression::Cast(Box::new(Cast {
41309 this: expr,
41310 to: DataType::Custom {
41311 name: "DATETIME2".to_string(),
41312 },
41313 trailing_comments: vec![],
41314 double_colon_syntax: false,
41315 format: None,
41316 default: None,
41317 inferred_type: None,
41318 }))
41319 }
41320 other => other,
41321 }
41322 }
41323
41324 /// Convert TIMESTAMP 'x' literal to CAST('x' AS TIMESTAMPTZ) for DuckDB
41325 fn ts_literal_to_cast_tz(expr: Expression) -> Expression {
41326 use crate::expressions::{Cast, DataType, Literal};
41327 match expr {
41328 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Timestamp(_)) => {
41329 let Literal::Timestamp(s) = lit.as_ref() else {
41330 unreachable!()
41331 };
41332 Expression::Cast(Box::new(Cast {
41333 this: Expression::Literal(Box::new(Literal::String(s.clone()))),
41334 to: DataType::Timestamp {
41335 timezone: true,
41336 precision: None,
41337 },
41338 trailing_comments: vec![],
41339 double_colon_syntax: false,
41340 format: None,
41341 default: None,
41342 inferred_type: None,
41343 }))
41344 }
41345 other => other,
41346 }
41347 }
41348
41349 /// Convert BigQuery format string to Snowflake format string
41350 fn bq_format_to_snowflake(format_expr: &Expression) -> Expression {
41351 use crate::expressions::Literal;
41352 if let Expression::Literal(lit) = format_expr {
41353 if let Literal::String(s) = lit.as_ref() {
41354 let sf = s
41355 .replace("%Y", "yyyy")
41356 .replace("%m", "mm")
41357 .replace("%d", "DD")
41358 .replace("%H", "HH24")
41359 .replace("%M", "MI")
41360 .replace("%S", "SS")
41361 .replace("%b", "mon")
41362 .replace("%B", "Month")
41363 .replace("%e", "FMDD");
41364 Expression::Literal(Box::new(Literal::String(sf)))
41365 } else {
41366 format_expr.clone()
41367 }
41368 } else {
41369 format_expr.clone()
41370 }
41371 }
41372
41373 /// Convert BigQuery format string to DuckDB format string
41374 fn bq_format_to_duckdb(format_expr: &Expression) -> Expression {
41375 use crate::expressions::Literal;
41376 if let Expression::Literal(lit) = format_expr {
41377 if let Literal::String(s) = lit.as_ref() {
41378 let duck = s
41379 .replace("%T", "%H:%M:%S")
41380 .replace("%F", "%Y-%m-%d")
41381 .replace("%D", "%m/%d/%y")
41382 .replace("%x", "%m/%d/%y")
41383 .replace("%c", "%a %b %-d %H:%M:%S %Y")
41384 .replace("%e", "%-d")
41385 .replace("%E6S", "%S.%f");
41386 Expression::Literal(Box::new(Literal::String(duck)))
41387 } else {
41388 format_expr.clone()
41389 }
41390 } else {
41391 format_expr.clone()
41392 }
41393 }
41394
41395 /// Convert BigQuery CAST FORMAT elements (like YYYY, MM, DD) to strftime (like %Y, %m, %d)
41396 fn bq_cast_format_to_strftime(format_expr: &Expression) -> Expression {
41397 use crate::expressions::Literal;
41398 if let Expression::Literal(lit) = format_expr {
41399 if let Literal::String(s) = lit.as_ref() {
41400 // Replace format elements from longest to shortest to avoid partial matches
41401 let result = s
41402 .replace("YYYYMMDD", "%Y%m%d")
41403 .replace("YYYY", "%Y")
41404 .replace("YY", "%y")
41405 .replace("MONTH", "%B")
41406 .replace("MON", "%b")
41407 .replace("MM", "%m")
41408 .replace("DD", "%d")
41409 .replace("HH24", "%H")
41410 .replace("HH12", "%I")
41411 .replace("HH", "%I")
41412 .replace("MI", "%M")
41413 .replace("SSTZH", "%S%z")
41414 .replace("SS", "%S")
41415 .replace("TZH", "%z");
41416 Expression::Literal(Box::new(Literal::String(result)))
41417 } else {
41418 format_expr.clone()
41419 }
41420 } else {
41421 format_expr.clone()
41422 }
41423 }
41424
41425 /// Normalize BigQuery format strings for BQ->BQ output
41426 fn bq_format_normalize_bq(format_expr: &Expression) -> Expression {
41427 use crate::expressions::Literal;
41428 if let Expression::Literal(lit) = format_expr {
41429 if let Literal::String(s) = lit.as_ref() {
41430 let norm = s.replace("%H:%M:%S", "%T").replace("%x", "%D");
41431 Expression::Literal(Box::new(Literal::String(norm)))
41432 } else {
41433 format_expr.clone()
41434 }
41435 } else {
41436 format_expr.clone()
41437 }
41438 }
41439}
41440
41441#[cfg(test)]
41442mod tests {
41443 use super::*;
41444
41445 #[test]
41446 fn test_dialect_type_from_str() {
41447 assert_eq!(
41448 "postgres".parse::<DialectType>().unwrap(),
41449 DialectType::PostgreSQL
41450 );
41451 assert_eq!(
41452 "postgresql".parse::<DialectType>().unwrap(),
41453 DialectType::PostgreSQL
41454 );
41455 assert_eq!("mysql".parse::<DialectType>().unwrap(), DialectType::MySQL);
41456 assert_eq!(
41457 "bigquery".parse::<DialectType>().unwrap(),
41458 DialectType::BigQuery
41459 );
41460 }
41461
41462 #[test]
41463 fn test_basic_transpile() {
41464 let dialect = Dialect::get(DialectType::Generic);
41465 let result = dialect
41466 .transpile("SELECT 1", DialectType::PostgreSQL)
41467 .unwrap();
41468 assert_eq!(result.len(), 1);
41469 assert_eq!(result[0], "SELECT 1");
41470 }
41471
41472 #[test]
41473 fn test_sqlite_double_quoted_column_defaults_to_postgres_strings() {
41474 let sqlite = Dialect::get(DialectType::SQLite);
41475 let result = sqlite
41476 .transpile(
41477 r#"CREATE TABLE "_collections" (
41478 "type" TEXT DEFAULT "base" NOT NULL,
41479 "fields" JSON DEFAULT "[]" NOT NULL,
41480 "options" JSON DEFAULT "{}" NOT NULL
41481 )"#,
41482 DialectType::PostgreSQL,
41483 )
41484 .unwrap();
41485
41486 assert!(result[0].contains(r#""type" TEXT DEFAULT 'base' NOT NULL"#));
41487 assert!(result[0].contains(r#""fields" JSON DEFAULT '[]' NOT NULL"#));
41488 assert!(result[0].contains(r#""options" JSON DEFAULT '{}' NOT NULL"#));
41489 }
41490
41491 #[test]
41492 fn test_sqlite_identity_preserves_double_quoted_column_defaults() {
41493 let sqlite = Dialect::get(DialectType::SQLite);
41494 let result = sqlite
41495 .transpile(
41496 r#"CREATE TABLE "_collections" ("type" TEXT DEFAULT "base" NOT NULL)"#,
41497 DialectType::SQLite,
41498 )
41499 .unwrap();
41500
41501 assert_eq!(
41502 result[0],
41503 r#"CREATE TABLE "_collections" ("type" TEXT DEFAULT "base" NOT NULL)"#
41504 );
41505 }
41506
41507 #[test]
41508 fn test_function_transformation_mysql() {
41509 // NVL should be transformed to IFNULL in MySQL
41510 let dialect = Dialect::get(DialectType::Generic);
41511 let result = dialect
41512 .transpile("SELECT NVL(a, b)", DialectType::MySQL)
41513 .unwrap();
41514 assert_eq!(result[0], "SELECT IFNULL(a, b)");
41515 }
41516
41517 #[test]
41518 fn test_get_path_duckdb() {
41519 // Test: step by step
41520 let snowflake = Dialect::get(DialectType::Snowflake);
41521
41522 // Step 1: Parse and check what Snowflake produces as intermediate
41523 let result_sf_sf = snowflake
41524 .transpile(
41525 "SELECT PARSE_JSON('{\"fruit\":\"banana\"}'):fruit",
41526 DialectType::Snowflake,
41527 )
41528 .unwrap();
41529 eprintln!("Snowflake->Snowflake colon: {}", result_sf_sf[0]);
41530
41531 // Step 2: DuckDB target
41532 let result_sf_dk = snowflake
41533 .transpile(
41534 "SELECT PARSE_JSON('{\"fruit\":\"banana\"}'):fruit",
41535 DialectType::DuckDB,
41536 )
41537 .unwrap();
41538 eprintln!("Snowflake->DuckDB colon: {}", result_sf_dk[0]);
41539
41540 // Step 3: GET_PATH directly
41541 let result_gp = snowflake
41542 .transpile(
41543 "SELECT GET_PATH(PARSE_JSON('{\"fruit\":\"banana\"}'), 'fruit')",
41544 DialectType::DuckDB,
41545 )
41546 .unwrap();
41547 eprintln!("Snowflake->DuckDB explicit GET_PATH: {}", result_gp[0]);
41548 }
41549
41550 #[test]
41551 fn test_function_transformation_postgres() {
41552 // IFNULL should be transformed to COALESCE in PostgreSQL
41553 let dialect = Dialect::get(DialectType::Generic);
41554 let result = dialect
41555 .transpile("SELECT IFNULL(a, b)", DialectType::PostgreSQL)
41556 .unwrap();
41557 assert_eq!(result[0], "SELECT COALESCE(a, b)");
41558
41559 // NVL should also be transformed to COALESCE
41560 let result = dialect
41561 .transpile("SELECT NVL(a, b)", DialectType::PostgreSQL)
41562 .unwrap();
41563 assert_eq!(result[0], "SELECT COALESCE(a, b)");
41564 }
41565
41566 #[test]
41567 fn test_hive_cast_to_trycast() {
41568 // Hive CAST should become TRY_CAST for targets that support it
41569 let hive = Dialect::get(DialectType::Hive);
41570 let result = hive
41571 .transpile("CAST(1 AS INT)", DialectType::DuckDB)
41572 .unwrap();
41573 assert_eq!(result[0], "TRY_CAST(1 AS INT)");
41574
41575 let result = hive
41576 .transpile("CAST(1 AS INT)", DialectType::Presto)
41577 .unwrap();
41578 assert_eq!(result[0], "TRY_CAST(1 AS INTEGER)");
41579 }
41580
41581 #[test]
41582 fn test_hive_array_identity() {
41583 // Hive ARRAY<DATE> should preserve angle bracket syntax
41584 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')";
41585 let hive = Dialect::get(DialectType::Hive);
41586
41587 // Test via transpile (this works)
41588 let result = hive.transpile(sql, DialectType::Hive).unwrap();
41589 eprintln!("Hive ARRAY via transpile: {}", result[0]);
41590 assert!(
41591 result[0].contains("ARRAY<DATE>"),
41592 "transpile: Expected ARRAY<DATE>, got: {}",
41593 result[0]
41594 );
41595
41596 // Test via parse -> transform -> generate (identity test path)
41597 let ast = hive.parse(sql).unwrap();
41598 let transformed = hive.transform(ast[0].clone()).unwrap();
41599 let output = hive.generate(&transformed).unwrap();
41600 eprintln!("Hive ARRAY via identity path: {}", output);
41601 assert!(
41602 output.contains("ARRAY<DATE>"),
41603 "identity path: Expected ARRAY<DATE>, got: {}",
41604 output
41605 );
41606 }
41607
41608 #[test]
41609 fn test_starrocks_delete_between_expansion() {
41610 // StarRocks doesn't support BETWEEN in DELETE statements
41611 let dialect = Dialect::get(DialectType::Generic);
41612
41613 // BETWEEN should be expanded to >= AND <= in DELETE
41614 let result = dialect
41615 .transpile(
41616 "DELETE FROM t WHERE a BETWEEN b AND c",
41617 DialectType::StarRocks,
41618 )
41619 .unwrap();
41620 assert_eq!(result[0], "DELETE FROM t WHERE a >= b AND a <= c");
41621
41622 // NOT BETWEEN should be expanded to < OR > in DELETE
41623 let result = dialect
41624 .transpile(
41625 "DELETE FROM t WHERE a NOT BETWEEN b AND c",
41626 DialectType::StarRocks,
41627 )
41628 .unwrap();
41629 assert_eq!(result[0], "DELETE FROM t WHERE a < b OR a > c");
41630
41631 // BETWEEN in SELECT should NOT be expanded (StarRocks supports it there)
41632 let result = dialect
41633 .transpile(
41634 "SELECT * FROM t WHERE a BETWEEN b AND c",
41635 DialectType::StarRocks,
41636 )
41637 .unwrap();
41638 assert!(
41639 result[0].contains("BETWEEN"),
41640 "BETWEEN should be preserved in SELECT"
41641 );
41642 }
41643
41644 #[test]
41645 fn test_snowflake_ltrim_rtrim_parse() {
41646 let sf = Dialect::get(DialectType::Snowflake);
41647 let sql = "SELECT LTRIM(RTRIM(col)) FROM t1";
41648 let result = sf.transpile(sql, DialectType::DuckDB);
41649 match &result {
41650 Ok(r) => eprintln!("LTRIM/RTRIM result: {}", r[0]),
41651 Err(e) => eprintln!("LTRIM/RTRIM error: {}", e),
41652 }
41653 assert!(
41654 result.is_ok(),
41655 "Expected successful parse of LTRIM(RTRIM(col)), got error: {:?}",
41656 result.err()
41657 );
41658 }
41659
41660 #[test]
41661 fn test_duckdb_count_if_parse() {
41662 let duck = Dialect::get(DialectType::DuckDB);
41663 let sql = "COUNT_IF(x)";
41664 let result = duck.transpile(sql, DialectType::DuckDB);
41665 match &result {
41666 Ok(r) => eprintln!("COUNT_IF result: {}", r[0]),
41667 Err(e) => eprintln!("COUNT_IF error: {}", e),
41668 }
41669 assert!(
41670 result.is_ok(),
41671 "Expected successful parse of COUNT_IF(x), got error: {:?}",
41672 result.err()
41673 );
41674 }
41675
41676 #[test]
41677 fn test_tsql_cast_tinyint_parse() {
41678 let tsql = Dialect::get(DialectType::TSQL);
41679 let sql = "CAST(X AS TINYINT)";
41680 let result = tsql.transpile(sql, DialectType::DuckDB);
41681 match &result {
41682 Ok(r) => eprintln!("TSQL CAST TINYINT result: {}", r[0]),
41683 Err(e) => eprintln!("TSQL CAST TINYINT error: {}", e),
41684 }
41685 assert!(
41686 result.is_ok(),
41687 "Expected successful transpile, got error: {:?}",
41688 result.err()
41689 );
41690 }
41691
41692 #[test]
41693 fn test_pg_hash_bitwise_xor() {
41694 let dialect = Dialect::get(DialectType::PostgreSQL);
41695 let result = dialect.transpile("x # y", DialectType::PostgreSQL).unwrap();
41696 assert_eq!(result[0], "x # y");
41697 }
41698
41699 #[test]
41700 fn test_pg_array_to_duckdb() {
41701 let dialect = Dialect::get(DialectType::PostgreSQL);
41702 let result = dialect
41703 .transpile("SELECT ARRAY[1, 2, 3] @> ARRAY[1, 2]", DialectType::DuckDB)
41704 .unwrap();
41705 assert_eq!(result[0], "SELECT [1, 2, 3] @> [1, 2]");
41706 }
41707
41708 #[test]
41709 fn test_array_remove_bigquery() {
41710 let dialect = Dialect::get(DialectType::Generic);
41711 let result = dialect
41712 .transpile("ARRAY_REMOVE(the_array, target)", DialectType::BigQuery)
41713 .unwrap();
41714 assert_eq!(
41715 result[0],
41716 "ARRAY(SELECT _u FROM UNNEST(the_array) AS _u WHERE _u <> target)"
41717 );
41718 }
41719
41720 #[test]
41721 fn test_map_clickhouse_case() {
41722 let dialect = Dialect::get(DialectType::Generic);
41723 let parsed = dialect
41724 .parse("CAST(MAP('a', '1') AS MAP(TEXT, TEXT))")
41725 .unwrap();
41726 eprintln!("MAP parsed: {:?}", parsed);
41727 let result = dialect
41728 .transpile(
41729 "CAST(MAP('a', '1') AS MAP(TEXT, TEXT))",
41730 DialectType::ClickHouse,
41731 )
41732 .unwrap();
41733 eprintln!("MAP result: {}", result[0]);
41734 }
41735
41736 #[test]
41737 fn test_generate_date_array_presto() {
41738 let dialect = Dialect::get(DialectType::Generic);
41739 let result = dialect.transpile(
41740 "SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
41741 DialectType::Presto,
41742 ).unwrap();
41743 eprintln!("GDA -> Presto: {}", result[0]);
41744 assert_eq!(result[0], "SELECT * FROM UNNEST(SEQUENCE(CAST('2020-01-01' AS DATE), CAST('2020-02-01' AS DATE), (1 * INTERVAL '7' DAY)))");
41745 }
41746
41747 #[test]
41748 fn test_generate_date_array_postgres() {
41749 let dialect = Dialect::get(DialectType::Generic);
41750 let result = dialect.transpile(
41751 "SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
41752 DialectType::PostgreSQL,
41753 ).unwrap();
41754 eprintln!("GDA -> PostgreSQL: {}", result[0]);
41755 }
41756
41757 #[test]
41758 fn test_generate_date_array_snowflake() {
41759 let dialect = Dialect::get(DialectType::Generic);
41760 let result = dialect
41761 .transpile(
41762 "SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
41763 DialectType::Snowflake,
41764 )
41765 .unwrap();
41766 eprintln!("GDA -> Snowflake: {}", result[0]);
41767 }
41768
41769 #[test]
41770 fn test_array_length_generate_date_array_snowflake() {
41771 let dialect = Dialect::get(DialectType::Generic);
41772 let result = dialect.transpile(
41773 "SELECT ARRAY_LENGTH(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
41774 DialectType::Snowflake,
41775 ).unwrap();
41776 eprintln!("ARRAY_LENGTH(GDA) -> Snowflake: {}", result[0]);
41777 }
41778
41779 #[test]
41780 fn test_generate_date_array_mysql() {
41781 let dialect = Dialect::get(DialectType::Generic);
41782 let result = dialect.transpile(
41783 "SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
41784 DialectType::MySQL,
41785 ).unwrap();
41786 eprintln!("GDA -> MySQL: {}", result[0]);
41787 }
41788
41789 #[test]
41790 fn test_generate_date_array_redshift() {
41791 let dialect = Dialect::get(DialectType::Generic);
41792 let result = dialect.transpile(
41793 "SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
41794 DialectType::Redshift,
41795 ).unwrap();
41796 eprintln!("GDA -> Redshift: {}", result[0]);
41797 }
41798
41799 #[test]
41800 fn test_generate_date_array_tsql() {
41801 let dialect = Dialect::get(DialectType::Generic);
41802 let result = dialect.transpile(
41803 "SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
41804 DialectType::TSQL,
41805 ).unwrap();
41806 eprintln!("GDA -> TSQL: {}", result[0]);
41807 }
41808
41809 #[test]
41810 fn test_struct_colon_syntax() {
41811 let dialect = Dialect::get(DialectType::Generic);
41812 // Test without colon first
41813 let result = dialect.transpile(
41814 "CAST((1, 2, 3, 4) AS STRUCT<a TINYINT, b SMALLINT, c INT, d BIGINT>)",
41815 DialectType::ClickHouse,
41816 );
41817 match result {
41818 Ok(r) => eprintln!("STRUCT no colon -> ClickHouse: {}", r[0]),
41819 Err(e) => eprintln!("STRUCT no colon error: {}", e),
41820 }
41821 // Now test with colon
41822 let result = dialect.transpile(
41823 "CAST((1, 2, 3, 4) AS STRUCT<a: TINYINT, b: SMALLINT, c: INT, d: BIGINT>)",
41824 DialectType::ClickHouse,
41825 );
41826 match result {
41827 Ok(r) => eprintln!("STRUCT colon -> ClickHouse: {}", r[0]),
41828 Err(e) => eprintln!("STRUCT colon error: {}", e),
41829 }
41830 }
41831
41832 #[test]
41833 fn test_generate_date_array_cte_wrapped_mysql() {
41834 let dialect = Dialect::get(DialectType::Generic);
41835 let result = dialect.transpile(
41836 "WITH dates AS (SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))) SELECT * FROM dates",
41837 DialectType::MySQL,
41838 ).unwrap();
41839 eprintln!("GDA CTE -> MySQL: {}", result[0]);
41840 }
41841
41842 #[test]
41843 fn test_generate_date_array_cte_wrapped_tsql() {
41844 let dialect = Dialect::get(DialectType::Generic);
41845 let result = dialect.transpile(
41846 "WITH dates AS (SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))) SELECT * FROM dates",
41847 DialectType::TSQL,
41848 ).unwrap();
41849 eprintln!("GDA CTE -> TSQL: {}", result[0]);
41850 }
41851
41852 #[test]
41853 fn test_decode_literal_no_null_check() {
41854 // Oracle DECODE with all literals should produce simple equality, no IS NULL
41855 let dialect = Dialect::get(DialectType::Oracle);
41856 let result = dialect
41857 .transpile("SELECT decode(1,2,3,4)", DialectType::DuckDB)
41858 .unwrap();
41859 assert_eq!(
41860 result[0], "SELECT CASE WHEN 1 = 2 THEN 3 ELSE 4 END",
41861 "Literal DECODE should not have IS NULL checks"
41862 );
41863 }
41864
41865 #[test]
41866 fn test_decode_column_vs_literal_no_null_check() {
41867 // Oracle DECODE with column vs literal should use simple equality (like sqlglot)
41868 let dialect = Dialect::get(DialectType::Oracle);
41869 let result = dialect
41870 .transpile("SELECT decode(col, 2, 3, 4) FROM t", DialectType::DuckDB)
41871 .unwrap();
41872 assert_eq!(
41873 result[0], "SELECT CASE WHEN col = 2 THEN 3 ELSE 4 END FROM t",
41874 "Column vs literal DECODE should not have IS NULL checks"
41875 );
41876 }
41877
41878 #[test]
41879 fn test_decode_column_vs_column_keeps_null_check() {
41880 // Oracle DECODE with column vs column should keep null-safe comparison
41881 let dialect = Dialect::get(DialectType::Oracle);
41882 let result = dialect
41883 .transpile("SELECT decode(col, col2, 3, 4) FROM t", DialectType::DuckDB)
41884 .unwrap();
41885 assert!(
41886 result[0].contains("IS NULL"),
41887 "Column vs column DECODE should have IS NULL checks, got: {}",
41888 result[0]
41889 );
41890 }
41891
41892 #[test]
41893 fn test_decode_null_search() {
41894 // Oracle DECODE with NULL search should use IS NULL
41895 let dialect = Dialect::get(DialectType::Oracle);
41896 let result = dialect
41897 .transpile("SELECT decode(col, NULL, 3, 4) FROM t", DialectType::DuckDB)
41898 .unwrap();
41899 assert_eq!(
41900 result[0],
41901 "SELECT CASE WHEN col IS NULL THEN 3 ELSE 4 END FROM t",
41902 );
41903 }
41904
41905 // =========================================================================
41906 // REGEXP function transpilation tests
41907 // =========================================================================
41908
41909 #[test]
41910 fn test_regexp_substr_snowflake_to_duckdb_2arg() {
41911 let dialect = Dialect::get(DialectType::Snowflake);
41912 let result = dialect
41913 .transpile("SELECT REGEXP_SUBSTR(s, 'pattern')", DialectType::DuckDB)
41914 .unwrap();
41915 assert_eq!(result[0], "SELECT REGEXP_EXTRACT(s, 'pattern')");
41916 }
41917
41918 #[test]
41919 fn test_regexp_substr_snowflake_to_duckdb_3arg_pos1() {
41920 let dialect = Dialect::get(DialectType::Snowflake);
41921 let result = dialect
41922 .transpile("SELECT REGEXP_SUBSTR(s, 'pattern', 1)", DialectType::DuckDB)
41923 .unwrap();
41924 assert_eq!(result[0], "SELECT REGEXP_EXTRACT(s, 'pattern')");
41925 }
41926
41927 #[test]
41928 fn test_regexp_substr_snowflake_to_duckdb_3arg_pos_gt1() {
41929 let dialect = Dialect::get(DialectType::Snowflake);
41930 let result = dialect
41931 .transpile("SELECT REGEXP_SUBSTR(s, 'pattern', 3)", DialectType::DuckDB)
41932 .unwrap();
41933 assert_eq!(
41934 result[0],
41935 "SELECT REGEXP_EXTRACT(NULLIF(SUBSTRING(s, 3), ''), 'pattern')"
41936 );
41937 }
41938
41939 #[test]
41940 fn test_regexp_substr_snowflake_to_duckdb_4arg_occ_gt1() {
41941 let dialect = Dialect::get(DialectType::Snowflake);
41942 let result = dialect
41943 .transpile(
41944 "SELECT REGEXP_SUBSTR(s, 'pattern', 1, 3)",
41945 DialectType::DuckDB,
41946 )
41947 .unwrap();
41948 assert_eq!(
41949 result[0],
41950 "SELECT ARRAY_EXTRACT(REGEXP_EXTRACT_ALL(s, 'pattern'), 3)"
41951 );
41952 }
41953
41954 #[test]
41955 fn test_regexp_substr_snowflake_to_duckdb_5arg_e_flag() {
41956 let dialect = Dialect::get(DialectType::Snowflake);
41957 let result = dialect
41958 .transpile(
41959 "SELECT REGEXP_SUBSTR(s, 'pattern', 1, 1, 'e')",
41960 DialectType::DuckDB,
41961 )
41962 .unwrap();
41963 assert_eq!(result[0], "SELECT REGEXP_EXTRACT(s, 'pattern')");
41964 }
41965
41966 #[test]
41967 fn test_regexp_substr_snowflake_to_duckdb_6arg_group0() {
41968 let dialect = Dialect::get(DialectType::Snowflake);
41969 let result = dialect
41970 .transpile(
41971 "SELECT REGEXP_SUBSTR(s, 'pattern', 1, 1, 'e', 0)",
41972 DialectType::DuckDB,
41973 )
41974 .unwrap();
41975 assert_eq!(result[0], "SELECT REGEXP_EXTRACT(s, 'pattern')");
41976 }
41977
41978 #[test]
41979 fn test_regexp_substr_snowflake_identity_strip_group0() {
41980 let dialect = Dialect::get(DialectType::Snowflake);
41981 let result = dialect
41982 .transpile(
41983 "SELECT REGEXP_SUBSTR(s, 'pattern', 1, 1, 'e', 0)",
41984 DialectType::Snowflake,
41985 )
41986 .unwrap();
41987 assert_eq!(result[0], "SELECT REGEXP_SUBSTR(s, 'pattern', 1, 1, 'e')");
41988 }
41989
41990 #[test]
41991 fn test_regexp_substr_all_snowflake_to_duckdb_2arg() {
41992 let dialect = Dialect::get(DialectType::Snowflake);
41993 let result = dialect
41994 .transpile(
41995 "SELECT REGEXP_SUBSTR_ALL(s, 'pattern')",
41996 DialectType::DuckDB,
41997 )
41998 .unwrap();
41999 assert_eq!(result[0], "SELECT REGEXP_EXTRACT_ALL(s, 'pattern')");
42000 }
42001
42002 #[test]
42003 fn test_regexp_substr_all_snowflake_to_duckdb_3arg_pos_gt1() {
42004 let dialect = Dialect::get(DialectType::Snowflake);
42005 let result = dialect
42006 .transpile(
42007 "SELECT REGEXP_SUBSTR_ALL(s, 'pattern', 3)",
42008 DialectType::DuckDB,
42009 )
42010 .unwrap();
42011 assert_eq!(
42012 result[0],
42013 "SELECT REGEXP_EXTRACT_ALL(SUBSTRING(s, 3), 'pattern')"
42014 );
42015 }
42016
42017 #[test]
42018 fn test_regexp_substr_all_snowflake_to_duckdb_5arg_e_flag() {
42019 let dialect = Dialect::get(DialectType::Snowflake);
42020 let result = dialect
42021 .transpile(
42022 "SELECT REGEXP_SUBSTR_ALL(s, 'pattern', 1, 1, 'e')",
42023 DialectType::DuckDB,
42024 )
42025 .unwrap();
42026 assert_eq!(result[0], "SELECT REGEXP_EXTRACT_ALL(s, 'pattern')");
42027 }
42028
42029 #[test]
42030 fn test_regexp_substr_all_snowflake_to_duckdb_6arg_group0() {
42031 let dialect = Dialect::get(DialectType::Snowflake);
42032 let result = dialect
42033 .transpile(
42034 "SELECT REGEXP_SUBSTR_ALL(s, 'pattern', 1, 1, 'e', 0)",
42035 DialectType::DuckDB,
42036 )
42037 .unwrap();
42038 assert_eq!(result[0], "SELECT REGEXP_EXTRACT_ALL(s, 'pattern')");
42039 }
42040
42041 #[test]
42042 fn test_regexp_substr_all_snowflake_identity_strip_group0() {
42043 let dialect = Dialect::get(DialectType::Snowflake);
42044 let result = dialect
42045 .transpile(
42046 "SELECT REGEXP_SUBSTR_ALL(s, 'pattern', 1, 1, 'e', 0)",
42047 DialectType::Snowflake,
42048 )
42049 .unwrap();
42050 assert_eq!(
42051 result[0],
42052 "SELECT REGEXP_SUBSTR_ALL(s, 'pattern', 1, 1, 'e')"
42053 );
42054 }
42055
42056 #[test]
42057 fn test_regexp_count_snowflake_to_duckdb_2arg() {
42058 let dialect = Dialect::get(DialectType::Snowflake);
42059 let result = dialect
42060 .transpile("SELECT REGEXP_COUNT(s, 'pattern')", DialectType::DuckDB)
42061 .unwrap();
42062 assert_eq!(
42063 result[0],
42064 "SELECT CASE WHEN 'pattern' = '' THEN 0 ELSE LENGTH(REGEXP_EXTRACT_ALL(s, 'pattern')) END"
42065 );
42066 }
42067
42068 #[test]
42069 fn test_regexp_count_snowflake_to_duckdb_3arg() {
42070 let dialect = Dialect::get(DialectType::Snowflake);
42071 let result = dialect
42072 .transpile("SELECT REGEXP_COUNT(s, 'pattern', 3)", DialectType::DuckDB)
42073 .unwrap();
42074 assert_eq!(
42075 result[0],
42076 "SELECT CASE WHEN 'pattern' = '' THEN 0 ELSE LENGTH(REGEXP_EXTRACT_ALL(SUBSTRING(s, 3), 'pattern')) END"
42077 );
42078 }
42079
42080 #[test]
42081 fn test_regexp_count_snowflake_to_duckdb_4arg_flags() {
42082 let dialect = Dialect::get(DialectType::Snowflake);
42083 let result = dialect
42084 .transpile(
42085 "SELECT REGEXP_COUNT(s, 'pattern', 1, 'i')",
42086 DialectType::DuckDB,
42087 )
42088 .unwrap();
42089 assert_eq!(
42090 result[0],
42091 "SELECT CASE WHEN '(?i)' || 'pattern' = '' THEN 0 ELSE LENGTH(REGEXP_EXTRACT_ALL(SUBSTRING(s, 1), '(?i)' || 'pattern')) END"
42092 );
42093 }
42094
42095 #[test]
42096 fn test_regexp_count_snowflake_to_duckdb_4arg_flags_literal_string() {
42097 let dialect = Dialect::get(DialectType::Snowflake);
42098 let result = dialect
42099 .transpile(
42100 "SELECT REGEXP_COUNT('Hello World', 'L', 1, 'im')",
42101 DialectType::DuckDB,
42102 )
42103 .unwrap();
42104 assert_eq!(
42105 result[0],
42106 "SELECT CASE WHEN '(?im)' || 'L' = '' THEN 0 ELSE LENGTH(REGEXP_EXTRACT_ALL(SUBSTRING('Hello World', 1), '(?im)' || 'L')) END"
42107 );
42108 }
42109
42110 #[test]
42111 fn test_regexp_replace_snowflake_to_duckdb_5arg_pos1_occ1() {
42112 let dialect = Dialect::get(DialectType::Snowflake);
42113 let result = dialect
42114 .transpile(
42115 "SELECT REGEXP_REPLACE(s, 'pattern', 'repl', 1, 1)",
42116 DialectType::DuckDB,
42117 )
42118 .unwrap();
42119 assert_eq!(result[0], "SELECT REGEXP_REPLACE(s, 'pattern', 'repl')");
42120 }
42121
42122 #[test]
42123 fn test_regexp_replace_snowflake_to_duckdb_5arg_pos_gt1_occ0() {
42124 let dialect = Dialect::get(DialectType::Snowflake);
42125 let result = dialect
42126 .transpile(
42127 "SELECT REGEXP_REPLACE(s, 'pattern', 'repl', 3, 0)",
42128 DialectType::DuckDB,
42129 )
42130 .unwrap();
42131 assert_eq!(
42132 result[0],
42133 "SELECT SUBSTRING(s, 1, 2) || REGEXP_REPLACE(SUBSTRING(s, 3), 'pattern', 'repl', 'g')"
42134 );
42135 }
42136
42137 #[test]
42138 fn test_regexp_replace_snowflake_to_duckdb_5arg_pos_gt1_occ1() {
42139 let dialect = Dialect::get(DialectType::Snowflake);
42140 let result = dialect
42141 .transpile(
42142 "SELECT REGEXP_REPLACE(s, 'pattern', 'repl', 3, 1)",
42143 DialectType::DuckDB,
42144 )
42145 .unwrap();
42146 assert_eq!(
42147 result[0],
42148 "SELECT SUBSTRING(s, 1, 2) || REGEXP_REPLACE(SUBSTRING(s, 3), 'pattern', 'repl')"
42149 );
42150 }
42151
42152 #[test]
42153 fn test_rlike_snowflake_to_duckdb_2arg() {
42154 let dialect = Dialect::get(DialectType::Snowflake);
42155 let result = dialect
42156 .transpile("SELECT RLIKE(a, b)", DialectType::DuckDB)
42157 .unwrap();
42158 assert_eq!(result[0], "SELECT REGEXP_FULL_MATCH(a, b)");
42159 }
42160
42161 #[test]
42162 fn test_rlike_snowflake_to_duckdb_3arg_flags() {
42163 let dialect = Dialect::get(DialectType::Snowflake);
42164 let result = dialect
42165 .transpile("SELECT RLIKE(a, b, 'i')", DialectType::DuckDB)
42166 .unwrap();
42167 assert_eq!(result[0], "SELECT REGEXP_FULL_MATCH(a, b, 'i')");
42168 }
42169
42170 #[test]
42171 fn test_regexp_extract_all_bigquery_to_snowflake_no_capture() {
42172 let dialect = Dialect::get(DialectType::BigQuery);
42173 let result = dialect
42174 .transpile(
42175 "SELECT REGEXP_EXTRACT_ALL(s, 'pattern')",
42176 DialectType::Snowflake,
42177 )
42178 .unwrap();
42179 assert_eq!(result[0], "SELECT REGEXP_SUBSTR_ALL(s, 'pattern')");
42180 }
42181
42182 #[test]
42183 fn test_regexp_extract_all_bigquery_to_snowflake_with_capture() {
42184 let dialect = Dialect::get(DialectType::BigQuery);
42185 let result = dialect
42186 .transpile(
42187 "SELECT REGEXP_EXTRACT_ALL(s, '(a)[0-9]')",
42188 DialectType::Snowflake,
42189 )
42190 .unwrap();
42191 assert_eq!(
42192 result[0],
42193 "SELECT REGEXP_SUBSTR_ALL(s, '(a)[0-9]', 1, 1, 'c', 1)"
42194 );
42195 }
42196
42197 #[test]
42198 fn test_regexp_instr_snowflake_to_duckdb_2arg() {
42199 let dialect = Dialect::get(DialectType::Snowflake);
42200 let result = dialect
42201 .transpile("SELECT REGEXP_INSTR(s, 'pattern')", DialectType::DuckDB)
42202 .unwrap();
42203 assert!(
42204 result[0].contains("CASE WHEN"),
42205 "Expected CASE WHEN in result: {}",
42206 result[0]
42207 );
42208 assert!(
42209 result[0].contains("LIST_SUM"),
42210 "Expected LIST_SUM in result: {}",
42211 result[0]
42212 );
42213 }
42214
42215 #[test]
42216 fn test_array_except_generic_to_duckdb() {
42217 let dialect = Dialect::get(DialectType::Generic);
42218 let result = dialect
42219 .transpile(
42220 "SELECT ARRAY_EXCEPT(ARRAY(1, 2, 3), ARRAY(2))",
42221 DialectType::DuckDB,
42222 )
42223 .unwrap();
42224 eprintln!("ARRAY_EXCEPT Generic->DuckDB: {}", result[0]);
42225 assert!(
42226 result[0].contains("CASE WHEN"),
42227 "Expected CASE WHEN: {}",
42228 result[0]
42229 );
42230 assert!(
42231 result[0].contains("LIST_FILTER"),
42232 "Expected LIST_FILTER: {}",
42233 result[0]
42234 );
42235 assert!(
42236 result[0].contains("LIST_DISTINCT"),
42237 "Expected LIST_DISTINCT: {}",
42238 result[0]
42239 );
42240 assert!(
42241 result[0].contains("IS NOT DISTINCT FROM"),
42242 "Expected IS NOT DISTINCT FROM: {}",
42243 result[0]
42244 );
42245 assert!(
42246 result[0].contains("= 0"),
42247 "Expected = 0 filter: {}",
42248 result[0]
42249 );
42250 }
42251
42252 #[test]
42253 fn test_array_except_generic_to_snowflake() {
42254 let dialect = Dialect::get(DialectType::Generic);
42255 let result = dialect
42256 .transpile(
42257 "SELECT ARRAY_EXCEPT(ARRAY(1, 2, 3), ARRAY(2))",
42258 DialectType::Snowflake,
42259 )
42260 .unwrap();
42261 eprintln!("ARRAY_EXCEPT Generic->Snowflake: {}", result[0]);
42262 assert_eq!(result[0], "SELECT ARRAY_EXCEPT([1, 2, 3], [2])");
42263 }
42264
42265 #[test]
42266 fn test_array_except_generic_to_presto() {
42267 let dialect = Dialect::get(DialectType::Generic);
42268 let result = dialect
42269 .transpile(
42270 "SELECT ARRAY_EXCEPT(ARRAY(1, 2, 3), ARRAY(2))",
42271 DialectType::Presto,
42272 )
42273 .unwrap();
42274 eprintln!("ARRAY_EXCEPT Generic->Presto: {}", result[0]);
42275 assert_eq!(result[0], "SELECT ARRAY_EXCEPT(ARRAY[1, 2, 3], ARRAY[2])");
42276 }
42277
42278 #[test]
42279 fn test_array_except_snowflake_to_duckdb() {
42280 let dialect = Dialect::get(DialectType::Snowflake);
42281 let result = dialect
42282 .transpile("SELECT ARRAY_EXCEPT([1, 2, 3], [2])", DialectType::DuckDB)
42283 .unwrap();
42284 eprintln!("ARRAY_EXCEPT Snowflake->DuckDB: {}", result[0]);
42285 assert!(
42286 result[0].contains("CASE WHEN"),
42287 "Expected CASE WHEN: {}",
42288 result[0]
42289 );
42290 assert!(
42291 result[0].contains("LIST_TRANSFORM"),
42292 "Expected LIST_TRANSFORM: {}",
42293 result[0]
42294 );
42295 }
42296
42297 #[test]
42298 fn test_array_contains_snowflake_to_snowflake() {
42299 let dialect = Dialect::get(DialectType::Snowflake);
42300 let result = dialect
42301 .transpile(
42302 "SELECT ARRAY_CONTAINS(x, [1, NULL, 3])",
42303 DialectType::Snowflake,
42304 )
42305 .unwrap();
42306 eprintln!("ARRAY_CONTAINS Snowflake->Snowflake: {}", result[0]);
42307 assert_eq!(result[0], "SELECT ARRAY_CONTAINS(x, [1, NULL, 3])");
42308 }
42309
42310 #[test]
42311 fn test_array_contains_snowflake_to_duckdb() {
42312 let dialect = Dialect::get(DialectType::Snowflake);
42313 let result = dialect
42314 .transpile(
42315 "SELECT ARRAY_CONTAINS(x, [1, NULL, 3])",
42316 DialectType::DuckDB,
42317 )
42318 .unwrap();
42319 eprintln!("ARRAY_CONTAINS Snowflake->DuckDB: {}", result[0]);
42320 assert!(
42321 result[0].contains("CASE WHEN"),
42322 "Expected CASE WHEN: {}",
42323 result[0]
42324 );
42325 assert!(
42326 result[0].contains("NULLIF"),
42327 "Expected NULLIF: {}",
42328 result[0]
42329 );
42330 assert!(
42331 result[0].contains("ARRAY_CONTAINS"),
42332 "Expected ARRAY_CONTAINS: {}",
42333 result[0]
42334 );
42335 }
42336
42337 #[test]
42338 fn test_array_distinct_snowflake_to_duckdb() {
42339 let dialect = Dialect::get(DialectType::Snowflake);
42340 let result = dialect
42341 .transpile(
42342 "SELECT ARRAY_DISTINCT([1, 2, 2, 3, 1])",
42343 DialectType::DuckDB,
42344 )
42345 .unwrap();
42346 eprintln!("ARRAY_DISTINCT Snowflake->DuckDB: {}", result[0]);
42347 assert!(
42348 result[0].contains("CASE WHEN"),
42349 "Expected CASE WHEN: {}",
42350 result[0]
42351 );
42352 assert!(
42353 result[0].contains("LIST_DISTINCT"),
42354 "Expected LIST_DISTINCT: {}",
42355 result[0]
42356 );
42357 assert!(
42358 result[0].contains("LIST_APPEND"),
42359 "Expected LIST_APPEND: {}",
42360 result[0]
42361 );
42362 assert!(
42363 result[0].contains("LIST_FILTER"),
42364 "Expected LIST_FILTER: {}",
42365 result[0]
42366 );
42367 }
42368}