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::Varchar(None) }
1208 (DataType::Boolean, t) if is_tsql_family(t) => DataType::Bit(None),
1209 (DataType::Bytea, t) if is_tsql_family(t) => DataType::Varbinary(None),
1210 (DataType::Json, t) if is_tsql_family(t) => DataType::Varchar(None),
1211 (DataType::Jsonb, t) if is_tsql_family(t) => DataType::Varchar(None),
1212 (DataType::Uuid, t) if is_tsql_family(t) => {
1213 DataType::Unknown("UNIQUEIDENTIFIER".to_string())
1214 }
1215 (DataType::Serial, t) if is_tsql_family(t) => DataType::Int,
1216 (DataType::BigSerial, t) if is_tsql_family(t) => DataType::BigInt,
1217 (DataType::SmallSerial, t) if is_tsql_family(t) => DataType::SmallInt,
1218 (DataType::Timestamp { .. }, t) if is_tsql_family(t) => {
1219 DataType::Unknown("DATETIME2".to_string())
1220 }
1221 (DataType::Real, t) if is_tsql_family(t) => DataType::Real,
1222
1223 (DataType::Text, t) if matches!(t, Dialect::BigQuery) || is_hive_family(t) => {
1226 DataType::String
1227 }
1228 (DataType::String, t)
1230 if is_postgres_family(t) || is_mysql_family(t) || matches!(t, Dialect::Sqlite) =>
1231 {
1232 DataType::Text
1233 }
1234
1235 (DataType::Int, Dialect::BigQuery) => DataType::BigInt,
1237
1238 (DataType::Float, Dialect::BigQuery) => DataType::Double,
1240
1241 (DataType::Bytea, t)
1243 if is_mysql_family(t)
1244 || matches!(t, Dialect::Sqlite | Dialect::Oracle)
1245 || is_hive_family(t) =>
1246 {
1247 DataType::Blob
1248 }
1249 (DataType::Blob, t) if is_postgres_family(t) => DataType::Bytea,
1250 (DataType::Varbinary(_), t) if is_postgres_family(t) => DataType::Bytea,
1251
1252 (DataType::Boolean, Dialect::Mysql) => DataType::Boolean,
1254
1255 (dt, _) => dt,
1257 }
1258}
1259
1260fn transform_limit(sel: &mut SelectStatement, target: Dialect) {
1270 if is_tsql_family(target) {
1271 if let Some(limit) = sel.limit.take() {
1273 if sel.offset.is_none() {
1274 sel.top = Some(Box::new(limit));
1275 } else {
1276 sel.fetch_first = Some(limit);
1278 if sel.order_by.is_empty() {
1280 sel.order_by = vec![OrderByItem {
1281 expr: Expr::Subquery(Box::new(Statement::Select(SelectStatement {
1282 comments: Vec::new(),
1283 ctes: Vec::new(),
1284 distinct: false,
1285 top: None,
1286 columns: vec![SelectItem::Expr {
1287 expr: Expr::Null,
1288 alias: None,
1289 alias_quote_style: QuoteStyle::None,
1290 }],
1291 from: None,
1292 joins: Vec::new(),
1293 where_clause: None,
1294 group_by: Vec::new(),
1295 having: None,
1296 order_by: Vec::new(),
1297 limit: None,
1298 offset: None,
1299 fetch_first: None,
1300 qualify: None,
1301 window_definitions: Vec::new(),
1302 }))),
1303 ascending: true,
1304 nulls_first: None,
1305 }];
1306 }
1307 }
1308 }
1309 if sel.offset.is_none() {
1311 if let Some(fetch) = sel.fetch_first.take() {
1312 sel.top = Some(Box::new(fetch));
1313 }
1314 }
1315 } else if matches!(target, Dialect::Oracle) {
1316 if let Some(limit) = sel.limit.take() {
1318 sel.fetch_first = Some(limit);
1319 }
1320 if let Some(top) = sel.top.take() {
1321 sel.fetch_first = Some(*top);
1322 }
1323 } else {
1324 if let Some(top) = sel.top.take() {
1326 if sel.limit.is_none() {
1327 sel.limit = Some(*top);
1328 }
1329 }
1330 if let Some(fetch) = sel.fetch_first.take() {
1331 if sel.limit.is_none() {
1332 sel.limit = Some(fetch);
1333 }
1334 }
1335 }
1336}
1337
1338fn transform_quotes(expr: Expr, target: Dialect) -> Expr {
1345 match expr {
1346 Expr::Column {
1347 table,
1348 name,
1349 quote_style,
1350 table_quote_style,
1351 } => {
1352 let new_qs = if quote_style.is_quoted() {
1353 QuoteStyle::for_dialect(target)
1354 } else {
1355 QuoteStyle::None
1356 };
1357 let new_tqs = if table_quote_style.is_quoted() {
1358 QuoteStyle::for_dialect(target)
1359 } else {
1360 QuoteStyle::None
1361 };
1362 Expr::Column {
1363 table,
1364 name,
1365 quote_style: new_qs,
1366 table_quote_style: new_tqs,
1367 }
1368 }
1369 Expr::BinaryOp { left, op, right } => Expr::BinaryOp {
1371 left: Box::new(transform_quotes(*left, target)),
1372 op,
1373 right: Box::new(transform_quotes(*right, target)),
1374 },
1375 Expr::UnaryOp { op, expr } => Expr::UnaryOp {
1376 op,
1377 expr: Box::new(transform_quotes(*expr, target)),
1378 },
1379 Expr::Function {
1380 name,
1381 args,
1382 distinct,
1383 filter,
1384 over,
1385 order_by,
1386 within_group,
1387 } => Expr::Function {
1388 name,
1389 args: args
1390 .into_iter()
1391 .map(|a| transform_quotes(a, target))
1392 .collect(),
1393 distinct,
1394 filter: filter.map(|f| Box::new(transform_quotes(*f, target))),
1395 over,
1396 order_by,
1397 within_group,
1398 },
1399 Expr::TypedFunction { func, filter, over } => Expr::TypedFunction {
1400 func: func.transform_children(&|e| transform_quotes(e, target)),
1401 filter: filter.map(|f| Box::new(transform_quotes(*f, target))),
1402 over,
1403 },
1404 Expr::Nested(inner) => Expr::Nested(Box::new(transform_quotes(*inner, target))),
1405 Expr::Alias { expr, name } => Expr::Alias {
1406 expr: Box::new(transform_quotes(*expr, target)),
1407 name,
1408 },
1409 other => other,
1410 }
1411}
1412
1413fn transform_quotes_in_select(sel: &mut SelectStatement, target: Dialect) {
1415 for item in &mut sel.columns {
1417 if let SelectItem::Expr { expr, .. } = item {
1418 *expr = transform_quotes(expr.clone(), target);
1419 }
1420 }
1421 if let Some(wh) = &mut sel.where_clause {
1423 *wh = transform_quotes(wh.clone(), target);
1424 }
1425 for gb in &mut sel.group_by {
1427 *gb = transform_quotes(gb.clone(), target);
1428 }
1429 if let Some(having) = &mut sel.having {
1431 *having = transform_quotes(having.clone(), target);
1432 }
1433 for ob in &mut sel.order_by {
1435 ob.expr = transform_quotes(ob.expr.clone(), target);
1436 }
1437 if let Some(from) = &mut sel.from {
1439 transform_quotes_in_table_source(&mut from.source, target);
1440 }
1441 for join in &mut sel.joins {
1442 transform_quotes_in_table_source(&mut join.table, target);
1443 if let Some(on) = &mut join.on {
1444 *on = transform_quotes(on.clone(), target);
1445 }
1446 }
1447}
1448
1449fn transform_quotes_in_table_source(source: &mut TableSource, target: Dialect) {
1450 match source {
1451 TableSource::Table(tref) => {
1452 if tref.name_quote_style.is_quoted() {
1453 tref.name_quote_style = QuoteStyle::for_dialect(target);
1454 }
1455 }
1456 TableSource::Subquery { .. } => {}
1457 TableSource::TableFunction { .. } => {}
1458 TableSource::Lateral { source } => transform_quotes_in_table_source(source, target),
1459 TableSource::Pivot { source, .. } | TableSource::Unpivot { source, .. } => {
1460 transform_quotes_in_table_source(source, target);
1461 }
1462 TableSource::Unnest { .. } => {}
1463 }
1464}
1465
1466fn collect_concat_args(expr: &Expr, args: &mut Vec<Expr>) {
1472 match expr {
1473 Expr::BinaryOp {
1474 left,
1475 op: BinaryOperator::Concat,
1476 right,
1477 } => {
1478 collect_concat_args(left, args);
1479 collect_concat_args(right, args);
1480 }
1481 other => args.push(other.clone()),
1482 }
1483}
1484
1485fn try_transform_interval_arithmetic(
1492 left: &Expr,
1493 op: &BinaryOperator,
1494 right: &Expr,
1495) -> Option<Expr> {
1496 if let Expr::Interval { value, unit } = right {
1498 if let Some((count, unit_name)) = parse_interval_value(value, unit) {
1499 let final_count = if matches!(op, BinaryOperator::Minus) {
1500 -count
1501 } else {
1502 count
1503 };
1504 return Some(Expr::Function {
1505 name: "DATEADD".to_string(),
1506 args: vec![
1507 Expr::Column {
1509 table: None,
1510 name: unit_name,
1511 quote_style: QuoteStyle::None,
1512 table_quote_style: QuoteStyle::None,
1513 },
1514 Expr::Number(final_count.to_string()),
1515 left.clone(),
1516 ],
1517 distinct: false,
1518 filter: None,
1519 over: None,
1520 order_by: vec![],
1521 within_group: false,
1522 });
1523 }
1524 }
1525
1526 if let Expr::Interval { value, unit } = left {
1528 if matches!(op, BinaryOperator::Plus) {
1529 if let Some((count, unit_name)) = parse_interval_value(value, unit) {
1530 return Some(Expr::Function {
1531 name: "DATEADD".to_string(),
1532 args: vec![
1533 Expr::Column {
1534 table: None,
1535 name: unit_name,
1536 quote_style: QuoteStyle::None,
1537 table_quote_style: QuoteStyle::None,
1538 },
1539 Expr::Number(count.to_string()),
1540 right.clone(),
1541 ],
1542 distinct: false,
1543 filter: None,
1544 over: None,
1545 order_by: vec![],
1546 within_group: false,
1547 });
1548 }
1549 }
1550 }
1551
1552 None
1553}
1554
1555fn parse_interval_value(value: &Expr, unit: &Option<DateTimeField>) -> Option<(i64, String)> {
1557 if let Expr::StringLiteral(s) = value {
1559 let parts: Vec<&str> = s.trim().split_whitespace().collect();
1560 if parts.len() == 2 {
1561 let count: i64 = parts[0].parse().ok()?;
1562 let unit_name = normalize_interval_unit(parts[1])?;
1563 return Some((count, unit_name));
1564 }
1565 if parts.len() == 1 {
1566 let count: i64 = parts[0].parse().ok()?;
1568 if let Some(u) = unit {
1569 let unit_name = datetime_field_to_tsql(u)?;
1570 return Some((count, unit_name));
1571 }
1572 }
1573 }
1574
1575 if let Expr::Number(n) = value {
1577 let count: i64 = n.parse().ok()?;
1578 if let Some(u) = unit {
1579 let unit_name = datetime_field_to_tsql(u)?;
1580 return Some((count, unit_name));
1581 }
1582 }
1583
1584 None
1585}
1586
1587fn normalize_interval_unit(unit: &str) -> Option<String> {
1589 let lower = unit.to_lowercase();
1590 let normalized = lower.trim_end_matches('s');
1591 match normalized {
1592 "year" => Some("YEAR".to_string()),
1593 "month" => Some("MONTH".to_string()),
1594 "week" => Some("WEEK".to_string()),
1595 "day" => Some("DAY".to_string()),
1596 "hour" => Some("HOUR".to_string()),
1597 "minute" => Some("MINUTE".to_string()),
1598 "second" => Some("SECOND".to_string()),
1599 "millisecond" => Some("MILLISECOND".to_string()),
1600 "microsecond" => Some("MICROSECOND".to_string()),
1601 _ => None,
1602 }
1603}
1604
1605fn datetime_field_to_tsql(field: &DateTimeField) -> Option<String> {
1607 match field {
1608 DateTimeField::Year => Some("YEAR".to_string()),
1609 DateTimeField::Quarter => Some("QUARTER".to_string()),
1610 DateTimeField::Month => Some("MONTH".to_string()),
1611 DateTimeField::Week => Some("WEEK".to_string()),
1612 DateTimeField::Day => Some("DAY".to_string()),
1613 DateTimeField::Hour => Some("HOUR".to_string()),
1614 DateTimeField::Minute => Some("MINUTE".to_string()),
1615 DateTimeField::Second => Some("SECOND".to_string()),
1616 DateTimeField::Millisecond => Some("MILLISECOND".to_string()),
1617 DateTimeField::Microsecond => Some("MICROSECOND".to_string()),
1618 _ => None,
1619 }
1620}
1621
1622fn simplify_similar_to_pattern(pattern: &Expr) -> Expr {
1629 if let Expr::StringLiteral(s) = pattern {
1630 let simplified = s.replace('|', "%").replace('(', "").replace(')', "");
1631 Expr::StringLiteral(simplified)
1632 } else {
1633 pattern.clone()
1634 }
1635}