1use serde::{Deserialize, Serialize};
2
3use crate::ast::*;
4
5pub mod plugin;
6pub mod time;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
14pub enum Dialect {
15 Ansi,
18
19 Athena,
22 BigQuery,
24 ClickHouse,
26 Databricks,
28 DuckDb,
30 Hive,
32 Mysql,
34 Oracle,
36 Postgres,
38 Presto,
40 Redshift,
42 Snowflake,
44 Spark,
46 Sqlite,
48 StarRocks,
50 Trino,
52 Tsql,
54
55 Doris,
58 Dremio,
60 Drill,
62 Druid,
64 Exasol,
66 Fabric,
68 Materialize,
70 Prql,
72 RisingWave,
74 SingleStore,
76 Tableau,
78 Teradata,
80}
81
82impl std::fmt::Display for Dialect {
83 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84 match self {
85 Dialect::Ansi => write!(f, "ANSI SQL"),
86 Dialect::Athena => write!(f, "Athena"),
87 Dialect::BigQuery => write!(f, "BigQuery"),
88 Dialect::ClickHouse => write!(f, "ClickHouse"),
89 Dialect::Databricks => write!(f, "Databricks"),
90 Dialect::DuckDb => write!(f, "DuckDB"),
91 Dialect::Hive => write!(f, "Hive"),
92 Dialect::Mysql => write!(f, "MySQL"),
93 Dialect::Oracle => write!(f, "Oracle"),
94 Dialect::Postgres => write!(f, "PostgreSQL"),
95 Dialect::Presto => write!(f, "Presto"),
96 Dialect::Redshift => write!(f, "Redshift"),
97 Dialect::Snowflake => write!(f, "Snowflake"),
98 Dialect::Spark => write!(f, "Spark"),
99 Dialect::Sqlite => write!(f, "SQLite"),
100 Dialect::StarRocks => write!(f, "StarRocks"),
101 Dialect::Trino => write!(f, "Trino"),
102 Dialect::Tsql => write!(f, "T-SQL"),
103 Dialect::Doris => write!(f, "Doris"),
104 Dialect::Dremio => write!(f, "Dremio"),
105 Dialect::Drill => write!(f, "Drill"),
106 Dialect::Druid => write!(f, "Druid"),
107 Dialect::Exasol => write!(f, "Exasol"),
108 Dialect::Fabric => write!(f, "Fabric"),
109 Dialect::Materialize => write!(f, "Materialize"),
110 Dialect::Prql => write!(f, "PRQL"),
111 Dialect::RisingWave => write!(f, "RisingWave"),
112 Dialect::SingleStore => write!(f, "SingleStore"),
113 Dialect::Tableau => write!(f, "Tableau"),
114 Dialect::Teradata => write!(f, "Teradata"),
115 }
116 }
117}
118
119impl Dialect {
120 #[must_use]
122 pub fn support_level(&self) -> &'static str {
123 match self {
124 Dialect::Ansi
125 | Dialect::Athena
126 | Dialect::BigQuery
127 | Dialect::ClickHouse
128 | Dialect::Databricks
129 | Dialect::DuckDb
130 | Dialect::Hive
131 | Dialect::Mysql
132 | Dialect::Oracle
133 | Dialect::Postgres
134 | Dialect::Presto
135 | Dialect::Redshift
136 | Dialect::Snowflake
137 | Dialect::Spark
138 | Dialect::Sqlite
139 | Dialect::StarRocks
140 | Dialect::Trino
141 | Dialect::Tsql => "Official",
142
143 Dialect::Doris
144 | Dialect::Dremio
145 | Dialect::Drill
146 | Dialect::Druid
147 | Dialect::Exasol
148 | Dialect::Fabric
149 | Dialect::Materialize
150 | Dialect::Prql
151 | Dialect::RisingWave
152 | Dialect::SingleStore
153 | Dialect::Tableau
154 | Dialect::Teradata => "Community",
155 }
156 }
157
158 #[must_use]
160 pub fn all() -> &'static [Dialect] {
161 &[
162 Dialect::Ansi,
163 Dialect::Athena,
164 Dialect::BigQuery,
165 Dialect::ClickHouse,
166 Dialect::Databricks,
167 Dialect::Doris,
168 Dialect::Dremio,
169 Dialect::Drill,
170 Dialect::Druid,
171 Dialect::DuckDb,
172 Dialect::Exasol,
173 Dialect::Fabric,
174 Dialect::Hive,
175 Dialect::Materialize,
176 Dialect::Mysql,
177 Dialect::Oracle,
178 Dialect::Postgres,
179 Dialect::Presto,
180 Dialect::Prql,
181 Dialect::Redshift,
182 Dialect::RisingWave,
183 Dialect::SingleStore,
184 Dialect::Snowflake,
185 Dialect::Spark,
186 Dialect::Sqlite,
187 Dialect::StarRocks,
188 Dialect::Tableau,
189 Dialect::Teradata,
190 Dialect::Trino,
191 Dialect::Tsql,
192 ]
193 }
194
195 pub fn from_str(s: &str) -> Option<Dialect> {
197 match s.to_lowercase().as_str() {
198 "" | "ansi" => Some(Dialect::Ansi),
199 "athena" => Some(Dialect::Athena),
200 "bigquery" => Some(Dialect::BigQuery),
201 "clickhouse" => Some(Dialect::ClickHouse),
202 "databricks" => Some(Dialect::Databricks),
203 "doris" => Some(Dialect::Doris),
204 "dremio" => Some(Dialect::Dremio),
205 "drill" => Some(Dialect::Drill),
206 "druid" => Some(Dialect::Druid),
207 "duckdb" => Some(Dialect::DuckDb),
208 "exasol" => Some(Dialect::Exasol),
209 "fabric" => Some(Dialect::Fabric),
210 "hive" => Some(Dialect::Hive),
211 "materialize" => Some(Dialect::Materialize),
212 "mysql" => Some(Dialect::Mysql),
213 "oracle" => Some(Dialect::Oracle),
214 "postgres" | "postgresql" => Some(Dialect::Postgres),
215 "presto" => Some(Dialect::Presto),
216 "prql" => Some(Dialect::Prql),
217 "redshift" => Some(Dialect::Redshift),
218 "risingwave" => Some(Dialect::RisingWave),
219 "singlestore" => Some(Dialect::SingleStore),
220 "snowflake" => Some(Dialect::Snowflake),
221 "spark" => Some(Dialect::Spark),
222 "sqlite" => Some(Dialect::Sqlite),
223 "starrocks" => Some(Dialect::StarRocks),
224 "tableau" => Some(Dialect::Tableau),
225 "teradata" => Some(Dialect::Teradata),
226 "trino" => Some(Dialect::Trino),
227 "tsql" | "mssql" | "sqlserver" => Some(Dialect::Tsql),
228 _ => None,
229 }
230 }
231}
232
233fn is_mysql_family(d: Dialect) -> bool {
239 matches!(
240 d,
241 Dialect::Mysql | Dialect::Doris | Dialect::SingleStore | Dialect::StarRocks
242 )
243}
244
245fn is_postgres_family(d: Dialect) -> bool {
247 matches!(
248 d,
249 Dialect::Postgres | Dialect::Redshift | Dialect::Materialize | Dialect::RisingWave
250 )
251}
252
253fn is_presto_family(d: Dialect) -> bool {
255 matches!(d, Dialect::Presto | Dialect::Trino | Dialect::Athena)
256}
257
258fn is_hive_family(d: Dialect) -> bool {
260 matches!(d, Dialect::Hive | Dialect::Spark | Dialect::Databricks)
261}
262
263pub(crate) fn is_tsql_family(d: Dialect) -> bool {
265 matches!(d, Dialect::Tsql | Dialect::Fabric)
266}
267
268#[must_use]
278pub(crate) fn is_tsql_reserved(name: &str) -> bool {
279 const RESERVED: &[&str] = &[
283 "ABSOLUTE",
284 "ACTION",
285 "ADA",
286 "ADD",
287 "ALL",
288 "ALLOCATE",
289 "ALTER",
290 "AND",
291 "ANY",
292 "ARE",
293 "AS",
294 "ASC",
295 "ASSERTION",
296 "AT",
297 "AUTHORIZATION",
298 "AVG",
299 "BACKUP",
300 "BEGIN",
301 "BETWEEN",
302 "BIT",
303 "BIT_LENGTH",
304 "BOTH",
305 "BREAK",
306 "BROWSE",
307 "BULK",
308 "BY",
309 "CASCADE",
310 "CASCADED",
311 "CASE",
312 "CAST",
313 "CATALOG",
314 "CHAR",
315 "CHARACTER",
316 "CHARACTER_LENGTH",
317 "CHAR_LENGTH",
318 "CHECK",
319 "CHECKPOINT",
320 "CLOSE",
321 "CLUSTERED",
322 "COALESCE",
323 "COLLATE",
324 "COLLATION",
325 "COLUMN",
326 "COMMIT",
327 "COMPUTE",
328 "CONNECT",
329 "CONNECTION",
330 "CONSTRAINT",
331 "CONSTRAINTS",
332 "CONTAINS",
333 "CONTAINSTABLE",
334 "CONTINUE",
335 "CONVERT",
336 "CORRESPONDING",
337 "COUNT",
338 "CREATE",
339 "CROSS",
340 "CURRENT",
341 "CURRENT_DATE",
342 "CURRENT_TIME",
343 "CURRENT_TIMESTAMP",
344 "CURRENT_USER",
345 "CURSOR",
346 "DATABASE",
347 "DATE",
348 "DBCC",
349 "DEALLOCATE",
350 "DEC",
351 "DECIMAL",
352 "DECLARE",
353 "DEFAULT",
354 "DEFERRABLE",
355 "DEFERRED",
356 "DELETE",
357 "DENY",
358 "DESC",
359 "DESCRIBE",
360 "DESCRIPTOR",
361 "DIAGNOSTICS",
362 "DISCONNECT",
363 "DISK",
364 "DISTINCT",
365 "DISTRIBUTED",
366 "DOMAIN",
367 "DOUBLE",
368 "DROP",
369 "DUMP",
370 "ELSE",
371 "END",
372 "ERRLVL",
373 "ESCAPE",
374 "EXCEPT",
375 "EXCEPTION",
376 "EXEC",
377 "EXECUTE",
378 "EXISTS",
379 "EXIT",
380 "EXTERNAL",
381 "EXTRACT",
382 "FETCH",
383 "FILE",
384 "FILLFACTOR",
385 "FLOAT",
386 "FOR",
387 "FOREIGN",
388 "FORTRAN",
389 "FOUND",
390 "FREETEXT",
391 "FREETEXTTABLE",
392 "FROM",
393 "FULL",
394 "FUNCTION",
395 "GET",
396 "GLOBAL",
397 "GO",
398 "GOTO",
399 "GRANT",
400 "GROUP",
401 "HAVING",
402 "HOLDLOCK",
403 "HOUR",
404 "IDENTITY",
405 "IDENTITYCOL",
406 "IDENTITY_INSERT",
407 "IF",
408 "IMMEDIATE",
409 "IN",
410 "INCLUDE",
411 "INDEX",
412 "INDICATOR",
413 "INITIALLY",
414 "INNER",
415 "INPUT",
416 "INSENSITIVE",
417 "INSERT",
418 "INT",
419 "INTEGER",
420 "INTERSECT",
421 "INTERVAL",
422 "INTO",
423 "IS",
424 "ISOLATION",
425 "JOIN",
426 "KEY",
427 "KILL",
428 "LANGUAGE",
429 "LAST",
430 "LEADING",
431 "LEFT",
432 "LEVEL",
433 "LIKE",
434 "LINENO",
435 "LOAD",
436 "LOCAL",
437 "LOWER",
438 "MATCH",
439 "MAX",
440 "MERGE",
441 "MIN",
442 "MINUTE",
443 "MODULE",
444 "MONTH",
445 "NAMES",
446 "NATIONAL",
447 "NATURAL",
448 "NCHAR",
449 "NEXT",
450 "NO",
451 "NOCHECK",
452 "NONCLUSTERED",
453 "NONE",
454 "NOT",
455 "NULL",
456 "NULLIF",
457 "NUMERIC",
458 "OCTET_LENGTH",
459 "OF",
460 "OFF",
461 "OFFSETS",
462 "ON",
463 "ONLY",
464 "OPEN",
465 "OPENDATASOURCE",
466 "OPENQUERY",
467 "OPENROWSET",
468 "OPENXML",
469 "OPTION",
470 "OR",
471 "ORDER",
472 "OUTER",
473 "OUTPUT",
474 "OVER",
475 "OVERLAPS",
476 "PAD",
477 "PARTIAL",
478 "PASCAL",
479 "PERCENT",
480 "PIVOT",
481 "PLAN",
482 "POSITION",
483 "PRECISION",
484 "PREPARE",
485 "PRESERVE",
486 "PRIMARY",
487 "PRINT",
488 "PRIOR",
489 "PRIVILEGES",
490 "PROC",
491 "PROCEDURE",
492 "PUBLIC",
493 "RAISERROR",
494 "READ",
495 "READTEXT",
496 "REAL",
497 "RECONFIGURE",
498 "REFERENCES",
499 "RELATIVE",
500 "REPLICATION",
501 "RESTORE",
502 "RESTRICT",
503 "RETURN",
504 "REVERT",
505 "REVOKE",
506 "RIGHT",
507 "ROLLBACK",
508 "ROWCOUNT",
509 "ROWGUIDCOL",
510 "ROWS",
511 "RULE",
512 "SAVE",
513 "SCHEMA",
514 "SCROLL",
515 "SECOND",
516 "SECTION",
517 "SECURITYAUDIT",
518 "SELECT",
519 "SEMANTICKEYPHRASETABLE",
520 "SEMANTICSIMILARITYDETAILSTABLE",
521 "SEMANTICSIMILARITYTABLE",
522 "SESSION",
523 "SESSION_USER",
524 "SET",
525 "SETUSER",
526 "SHUTDOWN",
527 "SIZE",
528 "SMALLINT",
529 "SOME",
530 "SPACE",
531 "SQL",
532 "SQLCA",
533 "SQLCODE",
534 "SQLERROR",
535 "SQLSTATE",
536 "SQLWARNING",
537 "STATISTICS",
538 "SUBSTRING",
539 "SUM",
540 "SYSTEM_USER",
541 "TABLE",
542 "TABLESAMPLE",
543 "TEMPORARY",
544 "TEXTSIZE",
545 "THEN",
546 "TIME",
547 "TIMESTAMP",
548 "TIMEZONE_HOUR",
549 "TIMEZONE_MINUTE",
550 "TO",
551 "TOP",
552 "TRAILING",
553 "TRAN",
554 "TRANSACTION",
555 "TRANSLATE",
556 "TRANSLATION",
557 "TRIGGER",
558 "TRIM",
559 "TRUE",
560 "TRUNCATE",
561 "TRY_CONVERT",
562 "TSEQUAL",
563 "UNION",
564 "UNIQUE",
565 "UNKNOWN",
566 "UNPIVOT",
567 "UPDATE",
568 "UPDATETEXT",
569 "UPPER",
570 "USAGE",
571 "USE",
572 "USER",
573 "USING",
574 "VALUE",
575 "VALUES",
576 "VARCHAR",
577 "VARYING",
578 "VIEW",
579 "WAITFOR",
580 "WHEN",
581 "WHENEVER",
582 "WHERE",
583 "WHILE",
584 "WITH",
585 "WITHIN GROUP",
586 "WORK",
587 "WRITE",
588 "WRITETEXT",
589 "YEAR",
590 "ZONE",
591 ];
592
593 if name.is_empty() || name.len() > 32 {
595 return false;
596 }
597 let mut buf = [0u8; 32];
598 for (i, b) in name.as_bytes().iter().enumerate() {
599 buf[i] = b.to_ascii_uppercase();
600 }
601 let upper = match std::str::from_utf8(&buf[..name.len()]) {
602 Ok(s) => s,
603 Err(_) => return false,
604 };
605 RESERVED.binary_search(&upper).is_ok()
606}
607
608pub(crate) fn supports_ilike_builtin(d: Dialect) -> bool {
610 matches!(
611 d,
612 Dialect::Postgres
613 | Dialect::Redshift
614 | Dialect::Materialize
615 | Dialect::RisingWave
616 | Dialect::DuckDb
617 | Dialect::Snowflake
618 | Dialect::ClickHouse
619 | Dialect::Trino
620 | Dialect::Presto
621 | Dialect::Athena
622 | Dialect::Databricks
623 | Dialect::Spark
624 | Dialect::Hive
625 | Dialect::StarRocks
626 | Dialect::Exasol
627 | Dialect::Druid
628 | Dialect::Dremio
629 )
630}
631
632#[must_use]
643pub fn transform(statement: &Statement, from: Dialect, to: Dialect) -> Statement {
644 if from == to {
645 return statement.clone();
646 }
647 let mut stmt = statement.clone();
648 transform_statement(&mut stmt, to);
649 stmt
650}
651
652fn transform_statement(statement: &mut Statement, target: Dialect) {
653 match statement {
654 Statement::Select(sel) => {
655 transform_limit(sel, target);
657 transform_quotes_in_select(sel, target);
659
660 for item in &mut sel.columns {
661 if let SelectItem::Expr { expr, .. } = item {
662 *expr = transform_expr(expr.clone(), target);
663 }
664 }
665 if let Some(wh) = &mut sel.where_clause {
666 *wh = transform_expr(wh.clone(), target);
667 }
668 for gb in &mut sel.group_by {
669 *gb = transform_expr(gb.clone(), target);
670 }
671 if let Some(having) = &mut sel.having {
672 *having = transform_expr(having.clone(), target);
673 }
674 }
675 Statement::Insert(ins) => {
676 if let InsertSource::Values(rows) = &mut ins.source {
677 for row in rows {
678 for val in row {
679 *val = transform_expr(val.clone(), target);
680 }
681 }
682 }
683 for item in &mut ins.returning {
685 if let SelectItem::Expr { expr, .. } = item {
686 *expr = transform_expr(expr.clone(), target);
687 }
688 }
689 }
690 Statement::Update(upd) => {
691 for (_, val) in &mut upd.assignments {
692 *val = transform_expr(val.clone(), target);
693 }
694 if let Some(wh) = &mut upd.where_clause {
695 *wh = transform_expr(wh.clone(), target);
696 }
697 for item in &mut upd.returning {
699 if let SelectItem::Expr { expr, .. } = item {
700 *expr = transform_expr(expr.clone(), target);
701 }
702 }
703 }
704 Statement::Delete(del) => {
705 if let Some(wh) = &mut del.where_clause {
706 *wh = transform_expr(wh.clone(), target);
707 }
708 for item in &mut del.returning {
710 if let SelectItem::Expr { expr, .. } = item {
711 *expr = transform_expr(expr.clone(), target);
712 }
713 }
714 }
715 Statement::CreateTable(ct) => {
717 for col in &mut ct.columns {
718 col.data_type = map_data_type(col.data_type.clone(), target);
719 if let Some(default) = &mut col.default {
720 *default = transform_expr(default.clone(), target);
721 }
722 }
723 for constraint in &mut ct.constraints {
725 if let TableConstraint::Check { expr, .. } = constraint {
726 *expr = transform_expr(expr.clone(), target);
727 }
728 }
729 if let Some(as_select) = &mut ct.as_select {
731 transform_statement(as_select, target);
732 }
733 }
734 Statement::AlterTable(alt) => {
736 for action in &mut alt.actions {
737 match action {
738 AlterTableAction::AddColumn(col) => {
739 col.data_type = map_data_type(col.data_type.clone(), target);
740 if let Some(default) = &mut col.default {
741 *default = transform_expr(default.clone(), target);
742 }
743 }
744 AlterTableAction::AlterColumnType { data_type, .. } => {
745 *data_type = map_data_type(data_type.clone(), target);
746 }
747 _ => {}
748 }
749 }
750 }
751 _ => {}
752 }
753}
754
755fn is_zero_guarded_divisor(expr: &Expr) -> bool {
761 let is_zero = |e: &Expr| matches!(e, Expr::Number(n) if n == "0");
762 match expr {
763 Expr::NullIf { r#else, .. } => is_zero(r#else),
764 Expr::Function { name, args, .. } => {
765 name.eq_ignore_ascii_case("NULLIF") && args.len() == 2 && is_zero(&args[1])
766 }
767 _ => false,
768 }
769}
770
771fn transform_expr(expr: Expr, target: Dialect) -> Expr {
773 match expr {
774 Expr::Function {
776 name,
777 args,
778 distinct,
779 filter,
780 over,
781 order_by,
782 within_group,
783 } => {
784 let new_name = map_function_name(&name, target);
785 let new_args: Vec<Expr> = args
786 .into_iter()
787 .map(|a| transform_expr(a, target))
788 .collect();
789 Expr::Function {
790 name: new_name,
791 args: new_args,
792 distinct,
793 filter: filter.map(|f| Box::new(transform_expr(*f, target))),
794 over,
795 order_by,
796 within_group,
797 }
798 }
799 Expr::TypedFunction { func, filter, over } => {
802 let transformed_func = transform_typed_function(func, target);
803 Expr::TypedFunction {
804 func: transformed_func,
805 filter: filter.map(|f| Box::new(transform_expr(*f, target))),
806 over,
807 }
808 }
809 Expr::ILike {
811 expr,
812 pattern,
813 negated,
814 escape,
815 } if !supports_ilike_builtin(target) => Expr::Like {
816 expr: Box::new(Expr::TypedFunction {
817 func: TypedFunction::Lower {
818 expr: Box::new(transform_expr(*expr, target)),
819 },
820 filter: None,
821 over: None,
822 }),
823 pattern: Box::new(Expr::TypedFunction {
824 func: TypedFunction::Lower {
825 expr: Box::new(transform_expr(*pattern, target)),
826 },
827 filter: None,
828 over: None,
829 }),
830 negated,
831 escape,
832 },
833 Expr::SimilarTo {
835 expr,
836 pattern,
837 negated,
838 escape,
839 } if is_tsql_family(target) => {
840 let transformed_pattern = transform_expr(*pattern, target);
841 let simplified = simplify_similar_to_pattern(&transformed_pattern);
842 Expr::Like {
843 expr: Box::new(transform_expr(*expr, target)),
844 pattern: Box::new(simplified),
845 negated,
846 escape,
847 }
848 }
849 Expr::Cast { expr, data_type } => Expr::Cast {
851 expr: Box::new(transform_expr(*expr, target)),
852 data_type: map_data_type(data_type, target),
853 },
854 Expr::BinaryOp { left, op, right } => {
856 if op == BinaryOperator::Concat && is_tsql_family(target) {
859 let mut args = Vec::new();
860 collect_concat_args(
861 &Expr::BinaryOp {
862 left,
863 op: BinaryOperator::Concat,
864 right,
865 },
866 &mut args,
867 );
868 let args = args
870 .into_iter()
871 .map(|a| transform_expr(a, target))
872 .collect();
873 return Expr::Function {
874 name: "CONCAT".to_string(),
875 args,
876 distinct: false,
877 filter: None,
878 over: None,
879 order_by: vec![],
880 within_group: false,
881 };
882 }
883
884 let left_transformed = transform_expr(*left, target);
885 let right_transformed = transform_expr(*right, target);
886
887 if is_tsql_family(target) && matches!(op, BinaryOperator::Plus | BinaryOperator::Minus)
889 {
890 if let Some(dateadd) =
891 try_transform_interval_arithmetic(&left_transformed, &op, &right_transformed)
892 {
893 return dateadd;
894 }
895 }
896
897 if is_tsql_family(target)
907 && matches!(op, BinaryOperator::Divide | BinaryOperator::Modulo)
908 && !is_zero_guarded_divisor(&right_transformed)
909 {
910 return Expr::BinaryOp {
911 left: Box::new(left_transformed),
912 op,
913 right: Box::new(Expr::NullIf {
914 expr: Box::new(right_transformed),
915 r#else: Box::new(Expr::Number("0".to_string())),
916 }),
917 };
918 }
919
920 Expr::BinaryOp {
921 left: Box::new(left_transformed),
922 op,
923 right: Box::new(right_transformed),
924 }
925 }
926 Expr::UnaryOp { op, expr } => Expr::UnaryOp {
927 op,
928 expr: Box::new(transform_expr(*expr, target)),
929 },
930 Expr::Nested(inner) => Expr::Nested(Box::new(transform_expr(*inner, target))),
931 Expr::Column {
933 table,
934 name,
935 quote_style,
936 table_quote_style,
937 } => {
938 let new_qs = if quote_style.is_quoted() {
939 QuoteStyle::for_dialect(target)
940 } else {
941 QuoteStyle::None
942 };
943 let new_tqs = if table_quote_style.is_quoted() {
944 QuoteStyle::for_dialect(target)
945 } else {
946 QuoteStyle::None
947 };
948 Expr::Column {
949 table,
950 name,
951 quote_style: new_qs,
952 table_quote_style: new_tqs,
953 }
954 }
955 other => other,
957 }
958}
959
960fn transform_typed_function(func: TypedFunction, target: Dialect) -> TypedFunction {
969 match func {
970 TypedFunction::TimeToStr { expr, format } => {
971 let transformed_expr = Box::new(transform_expr(*expr, target));
972 let transformed_format = transform_format_expr(*format, target);
973 TypedFunction::TimeToStr {
974 expr: transformed_expr,
975 format: Box::new(transformed_format),
976 }
977 }
978 TypedFunction::StrToTime { expr, format } => {
979 let transformed_expr = Box::new(transform_expr(*expr, target));
980 let transformed_format = transform_format_expr(*format, target);
981 TypedFunction::StrToTime {
982 expr: transformed_expr,
983 format: Box::new(transformed_format),
984 }
985 }
986 other => other.transform_children(&|e| transform_expr(e, target)),
988 }
989}
990
991fn transform_format_expr(expr: Expr, target: Dialect) -> Expr {
996 match &expr {
1000 Expr::StringLiteral(s) | Expr::NationalStringLiteral(s) => {
1001 let detected_source = detect_format_style(s);
1002 let target_style = time::TimeFormatStyle::for_dialect(target);
1003
1004 if detected_source != target_style {
1006 let converted = time::format_time(s, detected_source, target_style);
1007 match expr {
1008 Expr::NationalStringLiteral(_) => Expr::NationalStringLiteral(converted),
1009 _ => Expr::StringLiteral(converted),
1010 }
1011 } else {
1012 expr
1013 }
1014 }
1015 _ => transform_expr(expr, target),
1016 }
1017}
1018
1019fn detect_format_style(format_str: &str) -> time::TimeFormatStyle {
1021 if format_str.contains('%') {
1023 if format_str.contains("%i") {
1025 time::TimeFormatStyle::Mysql
1027 } else {
1028 time::TimeFormatStyle::Strftime
1030 }
1031 } else if format_str.contains("YYYY") || format_str.contains("yyyy") {
1032 if format_str.contains("HH24") || format_str.contains("MI") || format_str.contains("SS") {
1034 time::TimeFormatStyle::Postgres
1036 } else if format_str.contains("mm") && format_str.contains("ss") {
1037 time::TimeFormatStyle::Java
1039 } else if format_str.contains("FF") {
1040 time::TimeFormatStyle::Snowflake
1042 } else if format_str.contains("MM") && format_str.contains("DD") {
1043 time::TimeFormatStyle::Postgres
1045 } else {
1046 time::TimeFormatStyle::Java
1048 }
1049 } else {
1050 time::TimeFormatStyle::Strftime
1052 }
1053}
1054
1055pub(crate) fn map_function_name(name: &str, target: Dialect) -> String {
1061 let upper = name.to_uppercase();
1062 match upper.as_str() {
1063 "NOW" => {
1065 if is_tsql_family(target) {
1066 "GETDATE".to_string()
1067 } else if matches!(
1068 target,
1069 Dialect::Ansi
1070 | Dialect::BigQuery
1071 | Dialect::Snowflake
1072 | Dialect::Oracle
1073 | Dialect::ClickHouse
1074 | Dialect::Exasol
1075 | Dialect::Teradata
1076 | Dialect::Druid
1077 | Dialect::Dremio
1078 | Dialect::Tableau
1079 ) || is_presto_family(target)
1080 || is_hive_family(target)
1081 {
1082 "CURRENT_TIMESTAMP".to_string()
1083 } else {
1084 name.to_string()
1086 }
1087 }
1088 "GETDATE" => {
1089 if is_tsql_family(target) {
1090 name.to_string()
1091 } else if is_postgres_family(target)
1092 || matches!(target, Dialect::Mysql | Dialect::DuckDb | Dialect::Sqlite)
1093 {
1094 "NOW".to_string()
1095 } else {
1096 "CURRENT_TIMESTAMP".to_string()
1097 }
1098 }
1099
1100 "LEN" => {
1102 if is_tsql_family(target) || matches!(target, Dialect::BigQuery | Dialect::Snowflake) {
1103 name.to_string()
1104 } else {
1105 "LENGTH".to_string()
1106 }
1107 }
1108 "LENGTH" if is_tsql_family(target) => "LEN".to_string(),
1109
1110 "SUBSTR" => {
1112 if is_mysql_family(target)
1113 || matches!(target, Dialect::Sqlite | Dialect::Oracle)
1114 || is_hive_family(target)
1115 {
1116 "SUBSTR".to_string()
1117 } else {
1118 "SUBSTRING".to_string()
1119 }
1120 }
1121 "SUBSTRING" => {
1122 if is_mysql_family(target)
1123 || matches!(target, Dialect::Sqlite | Dialect::Oracle)
1124 || is_hive_family(target)
1125 {
1126 "SUBSTR".to_string()
1127 } else {
1128 name.to_string()
1129 }
1130 }
1131
1132 "IFNULL" => {
1134 if is_tsql_family(target) {
1135 "ISNULL".to_string()
1136 } else if is_mysql_family(target) || matches!(target, Dialect::Sqlite) {
1137 name.to_string()
1139 } else {
1140 "COALESCE".to_string()
1141 }
1142 }
1143 "ISNULL" => {
1144 if is_tsql_family(target) {
1145 name.to_string()
1146 } else if is_mysql_family(target) || matches!(target, Dialect::Sqlite) {
1147 "IFNULL".to_string()
1148 } else {
1149 "COALESCE".to_string()
1150 }
1151 }
1152
1153 "NVL" => {
1155 if matches!(target, Dialect::Oracle | Dialect::Snowflake) {
1156 name.to_string()
1157 } else if is_mysql_family(target) || matches!(target, Dialect::Sqlite) {
1158 "IFNULL".to_string()
1159 } else if is_tsql_family(target) {
1160 "ISNULL".to_string()
1161 } else {
1162 "COALESCE".to_string()
1163 }
1164 }
1165
1166 "RANDOM" => {
1168 if matches!(
1169 target,
1170 Dialect::Postgres | Dialect::Sqlite | Dialect::DuckDb
1171 ) {
1172 name.to_string()
1173 } else {
1174 "RAND".to_string()
1175 }
1176 }
1177 "RAND" => {
1178 if matches!(
1179 target,
1180 Dialect::Postgres | Dialect::Sqlite | Dialect::DuckDb
1181 ) {
1182 "RANDOM".to_string()
1183 } else {
1184 name.to_string()
1185 }
1186 }
1187
1188 "POSITION" if is_tsql_family(target) => "CHARINDEX".to_string(),
1190 "CHARINDEX" if is_postgres_family(target) => "POSITION".to_string(),
1191
1192 _ => name.to_string(),
1194 }
1195}
1196
1197pub(crate) fn map_data_type(dt: DataType, target: Dialect) -> DataType {
1203 match (dt, target) {
1204 (DataType::Text, t) if is_tsql_family(t) => {
1206 DataType::VarcharMax
1209 }
1210 (DataType::Boolean, t) if is_tsql_family(t) => DataType::Bit(None),
1211 (DataType::Bytea, t) if is_tsql_family(t) => DataType::Varbinary(None),
1212 (DataType::Json, t) if is_tsql_family(t) => DataType::Varchar(None),
1213 (DataType::Jsonb, t) if is_tsql_family(t) => DataType::Varchar(None),
1214 (DataType::Uuid, t) if is_tsql_family(t) => {
1215 DataType::Unknown("UNIQUEIDENTIFIER".to_string())
1216 }
1217 (DataType::Serial, t) if is_tsql_family(t) => DataType::Int,
1218 (DataType::BigSerial, t) if is_tsql_family(t) => DataType::BigInt,
1219 (DataType::SmallSerial, t) if is_tsql_family(t) => DataType::SmallInt,
1220 (DataType::Timestamp { .. }, t) if is_tsql_family(t) => {
1221 DataType::Unknown("DATETIME2".to_string())
1222 }
1223 (DataType::Real, t) if is_tsql_family(t) => DataType::Real,
1224
1225 (DataType::Text, t) if matches!(t, Dialect::BigQuery) || is_hive_family(t) => {
1228 DataType::String
1229 }
1230 (DataType::String, t)
1232 if is_postgres_family(t) || is_mysql_family(t) || matches!(t, Dialect::Sqlite) =>
1233 {
1234 DataType::Text
1235 }
1236
1237 (DataType::Int, Dialect::BigQuery) => DataType::BigInt,
1239
1240 (DataType::Float, Dialect::BigQuery) => DataType::Double,
1242
1243 (DataType::Bytea, t)
1245 if is_mysql_family(t)
1246 || matches!(t, Dialect::Sqlite | Dialect::Oracle)
1247 || is_hive_family(t) =>
1248 {
1249 DataType::Blob
1250 }
1251 (DataType::Blob, t) if is_postgres_family(t) => DataType::Bytea,
1252 (DataType::Varbinary(_), t) if is_postgres_family(t) => DataType::Bytea,
1253
1254 (DataType::Boolean, Dialect::Mysql) => DataType::Boolean,
1256
1257 (dt, _) => dt,
1259 }
1260}
1261
1262fn transform_limit(sel: &mut SelectStatement, target: Dialect) {
1272 if is_tsql_family(target) {
1273 if let Some(limit) = sel.limit.take() {
1275 if sel.offset.is_none() {
1276 sel.top = Some(Box::new(limit));
1277 } else {
1278 sel.fetch_first = Some(limit);
1280 if sel.order_by.is_empty() {
1282 sel.order_by = vec![OrderByItem {
1283 expr: Expr::Subquery(Box::new(Statement::Select(SelectStatement {
1284 comments: Vec::new(),
1285 ctes: Vec::new(),
1286 distinct: false,
1287 top: None,
1288 columns: vec![SelectItem::Expr {
1289 expr: Expr::Null,
1290 alias: None,
1291 alias_quote_style: QuoteStyle::None,
1292 }],
1293 from: None,
1294 joins: Vec::new(),
1295 where_clause: None,
1296 group_by: Vec::new(),
1297 having: None,
1298 order_by: Vec::new(),
1299 limit: None,
1300 offset: None,
1301 fetch_first: None,
1302 qualify: None,
1303 window_definitions: Vec::new(),
1304 }))),
1305 ascending: true,
1306 nulls_first: None,
1307 }];
1308 }
1309 }
1310 }
1311 if sel.offset.is_none() {
1313 if let Some(fetch) = sel.fetch_first.take() {
1314 sel.top = Some(Box::new(fetch));
1315 }
1316 }
1317 } else if matches!(target, Dialect::Oracle) {
1318 if let Some(limit) = sel.limit.take() {
1320 sel.fetch_first = Some(limit);
1321 }
1322 if let Some(top) = sel.top.take() {
1323 sel.fetch_first = Some(*top);
1324 }
1325 } else {
1326 if let Some(top) = sel.top.take() {
1328 if sel.limit.is_none() {
1329 sel.limit = Some(*top);
1330 }
1331 }
1332 if let Some(fetch) = sel.fetch_first.take() {
1333 if sel.limit.is_none() {
1334 sel.limit = Some(fetch);
1335 }
1336 }
1337 }
1338}
1339
1340fn transform_quotes(expr: Expr, target: Dialect) -> Expr {
1347 match expr {
1348 Expr::Column {
1349 table,
1350 name,
1351 quote_style,
1352 table_quote_style,
1353 } => {
1354 let new_qs = if quote_style.is_quoted() {
1355 QuoteStyle::for_dialect(target)
1356 } else {
1357 QuoteStyle::None
1358 };
1359 let new_tqs = if table_quote_style.is_quoted() {
1360 QuoteStyle::for_dialect(target)
1361 } else {
1362 QuoteStyle::None
1363 };
1364 Expr::Column {
1365 table,
1366 name,
1367 quote_style: new_qs,
1368 table_quote_style: new_tqs,
1369 }
1370 }
1371 Expr::BinaryOp { left, op, right } => Expr::BinaryOp {
1373 left: Box::new(transform_quotes(*left, target)),
1374 op,
1375 right: Box::new(transform_quotes(*right, target)),
1376 },
1377 Expr::UnaryOp { op, expr } => Expr::UnaryOp {
1378 op,
1379 expr: Box::new(transform_quotes(*expr, target)),
1380 },
1381 Expr::Function {
1382 name,
1383 args,
1384 distinct,
1385 filter,
1386 over,
1387 order_by,
1388 within_group,
1389 } => Expr::Function {
1390 name,
1391 args: args
1392 .into_iter()
1393 .map(|a| transform_quotes(a, target))
1394 .collect(),
1395 distinct,
1396 filter: filter.map(|f| Box::new(transform_quotes(*f, target))),
1397 over,
1398 order_by,
1399 within_group,
1400 },
1401 Expr::TypedFunction { func, filter, over } => Expr::TypedFunction {
1402 func: func.transform_children(&|e| transform_quotes(e, target)),
1403 filter: filter.map(|f| Box::new(transform_quotes(*f, target))),
1404 over,
1405 },
1406 Expr::Nested(inner) => Expr::Nested(Box::new(transform_quotes(*inner, target))),
1407 Expr::Alias { expr, name } => Expr::Alias {
1408 expr: Box::new(transform_quotes(*expr, target)),
1409 name,
1410 },
1411 other => other,
1412 }
1413}
1414
1415fn transform_quotes_in_select(sel: &mut SelectStatement, target: Dialect) {
1417 for item in &mut sel.columns {
1419 if let SelectItem::Expr { expr, .. } = item {
1420 *expr = transform_quotes(expr.clone(), target);
1421 }
1422 }
1423 if let Some(wh) = &mut sel.where_clause {
1425 *wh = transform_quotes(wh.clone(), target);
1426 }
1427 for gb in &mut sel.group_by {
1429 *gb = transform_quotes(gb.clone(), target);
1430 }
1431 if let Some(having) = &mut sel.having {
1433 *having = transform_quotes(having.clone(), target);
1434 }
1435 for ob in &mut sel.order_by {
1437 ob.expr = transform_quotes(ob.expr.clone(), target);
1438 }
1439 if let Some(from) = &mut sel.from {
1441 transform_quotes_in_table_source(&mut from.source, target);
1442 }
1443 for join in &mut sel.joins {
1444 transform_quotes_in_table_source(&mut join.table, target);
1445 if let Some(on) = &mut join.on {
1446 *on = transform_quotes(on.clone(), target);
1447 }
1448 }
1449}
1450
1451fn transform_quotes_in_table_source(source: &mut TableSource, target: Dialect) {
1452 match source {
1453 TableSource::Table(tref) => {
1454 if tref.name_quote_style.is_quoted() {
1455 tref.name_quote_style = QuoteStyle::for_dialect(target);
1456 }
1457 }
1458 TableSource::Subquery { .. } => {}
1459 TableSource::TableFunction { .. } => {}
1460 TableSource::Lateral { source } => transform_quotes_in_table_source(source, target),
1461 TableSource::Pivot { source, .. } | TableSource::Unpivot { source, .. } => {
1462 transform_quotes_in_table_source(source, target);
1463 }
1464 TableSource::Unnest { .. } => {}
1465 }
1466}
1467
1468fn collect_concat_args(expr: &Expr, args: &mut Vec<Expr>) {
1474 match expr {
1475 Expr::BinaryOp {
1476 left,
1477 op: BinaryOperator::Concat,
1478 right,
1479 } => {
1480 collect_concat_args(left, args);
1481 collect_concat_args(right, args);
1482 }
1483 other => args.push(other.clone()),
1484 }
1485}
1486
1487fn try_transform_interval_arithmetic(
1494 left: &Expr,
1495 op: &BinaryOperator,
1496 right: &Expr,
1497) -> Option<Expr> {
1498 if let Expr::Interval { value, unit } = right {
1500 if let Some((count, unit_name)) = parse_interval_value(value, unit) {
1501 let final_count = if matches!(op, BinaryOperator::Minus) {
1502 -count
1503 } else {
1504 count
1505 };
1506 return Some(Expr::Function {
1507 name: "DATEADD".to_string(),
1508 args: vec![
1509 Expr::Column {
1511 table: None,
1512 name: unit_name,
1513 quote_style: QuoteStyle::None,
1514 table_quote_style: QuoteStyle::None,
1515 },
1516 Expr::Number(final_count.to_string()),
1517 left.clone(),
1518 ],
1519 distinct: false,
1520 filter: None,
1521 over: None,
1522 order_by: vec![],
1523 within_group: false,
1524 });
1525 }
1526 }
1527
1528 if let Expr::Interval { value, unit } = left {
1530 if matches!(op, BinaryOperator::Plus) {
1531 if let Some((count, unit_name)) = parse_interval_value(value, unit) {
1532 return Some(Expr::Function {
1533 name: "DATEADD".to_string(),
1534 args: vec![
1535 Expr::Column {
1536 table: None,
1537 name: unit_name,
1538 quote_style: QuoteStyle::None,
1539 table_quote_style: QuoteStyle::None,
1540 },
1541 Expr::Number(count.to_string()),
1542 right.clone(),
1543 ],
1544 distinct: false,
1545 filter: None,
1546 over: None,
1547 order_by: vec![],
1548 within_group: false,
1549 });
1550 }
1551 }
1552 }
1553
1554 None
1555}
1556
1557fn parse_interval_value(value: &Expr, unit: &Option<DateTimeField>) -> Option<(i64, String)> {
1559 if let Expr::StringLiteral(s) = value {
1561 let parts: Vec<&str> = s.trim().split_whitespace().collect();
1562 if parts.len() == 2 {
1563 let count: i64 = parts[0].parse().ok()?;
1564 let unit_name = normalize_interval_unit(parts[1])?;
1565 return Some((count, unit_name));
1566 }
1567 if parts.len() == 1 {
1568 let count: i64 = parts[0].parse().ok()?;
1570 if let Some(u) = unit {
1571 let unit_name = datetime_field_to_tsql(u)?;
1572 return Some((count, unit_name));
1573 }
1574 }
1575 }
1576
1577 if let Expr::Number(n) = value {
1579 let count: i64 = n.parse().ok()?;
1580 if let Some(u) = unit {
1581 let unit_name = datetime_field_to_tsql(u)?;
1582 return Some((count, unit_name));
1583 }
1584 }
1585
1586 None
1587}
1588
1589fn normalize_interval_unit(unit: &str) -> Option<String> {
1591 let lower = unit.to_lowercase();
1592 let normalized = lower.trim_end_matches('s');
1593 match normalized {
1594 "year" => Some("YEAR".to_string()),
1595 "month" => Some("MONTH".to_string()),
1596 "week" => Some("WEEK".to_string()),
1597 "day" => Some("DAY".to_string()),
1598 "hour" => Some("HOUR".to_string()),
1599 "minute" => Some("MINUTE".to_string()),
1600 "second" => Some("SECOND".to_string()),
1601 "millisecond" => Some("MILLISECOND".to_string()),
1602 "microsecond" => Some("MICROSECOND".to_string()),
1603 _ => None,
1604 }
1605}
1606
1607fn datetime_field_to_tsql(field: &DateTimeField) -> Option<String> {
1609 match field {
1610 DateTimeField::Year => Some("YEAR".to_string()),
1611 DateTimeField::Quarter => Some("QUARTER".to_string()),
1612 DateTimeField::Month => Some("MONTH".to_string()),
1613 DateTimeField::Week => Some("WEEK".to_string()),
1614 DateTimeField::Day => Some("DAY".to_string()),
1615 DateTimeField::Hour => Some("HOUR".to_string()),
1616 DateTimeField::Minute => Some("MINUTE".to_string()),
1617 DateTimeField::Second => Some("SECOND".to_string()),
1618 DateTimeField::Millisecond => Some("MILLISECOND".to_string()),
1619 DateTimeField::Microsecond => Some("MICROSECOND".to_string()),
1620 _ => None,
1621 }
1622}
1623
1624fn simplify_similar_to_pattern(pattern: &Expr) -> Expr {
1631 if let Expr::StringLiteral(s) = pattern {
1632 let simplified = s.replace('|', "%").replace('(', "").replace(')', "");
1633 Expr::StringLiteral(simplified)
1634 } else {
1635 pattern.clone()
1636 }
1637}