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 transform_expr(expr: Expr, target: Dialect) -> Expr {
757 match expr {
758 Expr::Function {
760 name,
761 args,
762 distinct,
763 filter,
764 over,
765 order_by,
766 within_group,
767 } => {
768 let new_name = map_function_name(&name, target);
769 let new_args: Vec<Expr> = args
770 .into_iter()
771 .map(|a| transform_expr(a, target))
772 .collect();
773 Expr::Function {
774 name: new_name,
775 args: new_args,
776 distinct,
777 filter: filter.map(|f| Box::new(transform_expr(*f, target))),
778 over,
779 order_by,
780 within_group,
781 }
782 }
783 Expr::TypedFunction { func, filter, over } => {
786 let transformed_func = transform_typed_function(func, target);
787 Expr::TypedFunction {
788 func: transformed_func,
789 filter: filter.map(|f| Box::new(transform_expr(*f, target))),
790 over,
791 }
792 }
793 Expr::ILike {
795 expr,
796 pattern,
797 negated,
798 escape,
799 } if !supports_ilike_builtin(target) => Expr::Like {
800 expr: Box::new(Expr::TypedFunction {
801 func: TypedFunction::Lower {
802 expr: Box::new(transform_expr(*expr, target)),
803 },
804 filter: None,
805 over: None,
806 }),
807 pattern: Box::new(Expr::TypedFunction {
808 func: TypedFunction::Lower {
809 expr: Box::new(transform_expr(*pattern, target)),
810 },
811 filter: None,
812 over: None,
813 }),
814 negated,
815 escape,
816 },
817 Expr::SimilarTo {
819 expr,
820 pattern,
821 negated,
822 escape,
823 } if is_tsql_family(target) => {
824 let transformed_pattern = transform_expr(*pattern, target);
825 let simplified = simplify_similar_to_pattern(&transformed_pattern);
826 Expr::Like {
827 expr: Box::new(transform_expr(*expr, target)),
828 pattern: Box::new(simplified),
829 negated,
830 escape,
831 }
832 }
833 Expr::Cast { expr, data_type } => Expr::Cast {
835 expr: Box::new(transform_expr(*expr, target)),
836 data_type: map_data_type(data_type, target),
837 },
838 Expr::BinaryOp { left, op, right } => {
840 if op == BinaryOperator::Concat && is_tsql_family(target) {
843 let mut args = Vec::new();
844 collect_concat_args(
845 &Expr::BinaryOp {
846 left,
847 op: BinaryOperator::Concat,
848 right,
849 },
850 &mut args,
851 );
852 let args = args
854 .into_iter()
855 .map(|a| transform_expr(a, target))
856 .collect();
857 return Expr::Function {
858 name: "CONCAT".to_string(),
859 args,
860 distinct: false,
861 filter: None,
862 over: None,
863 order_by: vec![],
864 within_group: false,
865 };
866 }
867
868 let left_transformed = transform_expr(*left, target);
869 let right_transformed = transform_expr(*right, target);
870
871 if is_tsql_family(target) && matches!(op, BinaryOperator::Plus | BinaryOperator::Minus)
873 {
874 if let Some(dateadd) =
875 try_transform_interval_arithmetic(&left_transformed, &op, &right_transformed)
876 {
877 return dateadd;
878 }
879 }
880
881 Expr::BinaryOp {
882 left: Box::new(left_transformed),
883 op,
884 right: Box::new(right_transformed),
885 }
886 }
887 Expr::UnaryOp { op, expr } => Expr::UnaryOp {
888 op,
889 expr: Box::new(transform_expr(*expr, target)),
890 },
891 Expr::Nested(inner) => Expr::Nested(Box::new(transform_expr(*inner, target))),
892 Expr::Column {
894 table,
895 name,
896 quote_style,
897 table_quote_style,
898 } => {
899 let new_qs = if quote_style.is_quoted() {
900 QuoteStyle::for_dialect(target)
901 } else {
902 QuoteStyle::None
903 };
904 let new_tqs = if table_quote_style.is_quoted() {
905 QuoteStyle::for_dialect(target)
906 } else {
907 QuoteStyle::None
908 };
909 Expr::Column {
910 table,
911 name,
912 quote_style: new_qs,
913 table_quote_style: new_tqs,
914 }
915 }
916 other => other,
918 }
919}
920
921fn transform_typed_function(func: TypedFunction, target: Dialect) -> TypedFunction {
930 match func {
931 TypedFunction::TimeToStr { expr, format } => {
932 let transformed_expr = Box::new(transform_expr(*expr, target));
933 let transformed_format = transform_format_expr(*format, target);
934 TypedFunction::TimeToStr {
935 expr: transformed_expr,
936 format: Box::new(transformed_format),
937 }
938 }
939 TypedFunction::StrToTime { expr, format } => {
940 let transformed_expr = Box::new(transform_expr(*expr, target));
941 let transformed_format = transform_format_expr(*format, target);
942 TypedFunction::StrToTime {
943 expr: transformed_expr,
944 format: Box::new(transformed_format),
945 }
946 }
947 other => other.transform_children(&|e| transform_expr(e, target)),
949 }
950}
951
952fn transform_format_expr(expr: Expr, target: Dialect) -> Expr {
957 match &expr {
961 Expr::StringLiteral(s) | Expr::NationalStringLiteral(s) => {
962 let detected_source = detect_format_style(s);
963 let target_style = time::TimeFormatStyle::for_dialect(target);
964
965 if detected_source != target_style {
967 let converted = time::format_time(s, detected_source, target_style);
968 match expr {
969 Expr::NationalStringLiteral(_) => Expr::NationalStringLiteral(converted),
970 _ => Expr::StringLiteral(converted),
971 }
972 } else {
973 expr
974 }
975 }
976 _ => transform_expr(expr, target),
977 }
978}
979
980fn detect_format_style(format_str: &str) -> time::TimeFormatStyle {
982 if format_str.contains('%') {
984 if format_str.contains("%i") {
986 time::TimeFormatStyle::Mysql
988 } else {
989 time::TimeFormatStyle::Strftime
991 }
992 } else if format_str.contains("YYYY") || format_str.contains("yyyy") {
993 if format_str.contains("HH24") || format_str.contains("MI") || format_str.contains("SS") {
995 time::TimeFormatStyle::Postgres
997 } else if format_str.contains("mm") && format_str.contains("ss") {
998 time::TimeFormatStyle::Java
1000 } else if format_str.contains("FF") {
1001 time::TimeFormatStyle::Snowflake
1003 } else if format_str.contains("MM") && format_str.contains("DD") {
1004 time::TimeFormatStyle::Postgres
1006 } else {
1007 time::TimeFormatStyle::Java
1009 }
1010 } else {
1011 time::TimeFormatStyle::Strftime
1013 }
1014}
1015
1016pub(crate) fn map_function_name(name: &str, target: Dialect) -> String {
1022 let upper = name.to_uppercase();
1023 match upper.as_str() {
1024 "NOW" => {
1026 if is_tsql_family(target) {
1027 "GETDATE".to_string()
1028 } else if matches!(
1029 target,
1030 Dialect::Ansi
1031 | Dialect::BigQuery
1032 | Dialect::Snowflake
1033 | Dialect::Oracle
1034 | Dialect::ClickHouse
1035 | Dialect::Exasol
1036 | Dialect::Teradata
1037 | Dialect::Druid
1038 | Dialect::Dremio
1039 | Dialect::Tableau
1040 ) || is_presto_family(target)
1041 || is_hive_family(target)
1042 {
1043 "CURRENT_TIMESTAMP".to_string()
1044 } else {
1045 name.to_string()
1047 }
1048 }
1049 "GETDATE" => {
1050 if is_tsql_family(target) {
1051 name.to_string()
1052 } else if is_postgres_family(target)
1053 || matches!(target, Dialect::Mysql | Dialect::DuckDb | Dialect::Sqlite)
1054 {
1055 "NOW".to_string()
1056 } else {
1057 "CURRENT_TIMESTAMP".to_string()
1058 }
1059 }
1060
1061 "LEN" => {
1063 if is_tsql_family(target) || matches!(target, Dialect::BigQuery | Dialect::Snowflake) {
1064 name.to_string()
1065 } else {
1066 "LENGTH".to_string()
1067 }
1068 }
1069 "LENGTH" if is_tsql_family(target) => "LEN".to_string(),
1070
1071 "SUBSTR" => {
1073 if is_mysql_family(target)
1074 || matches!(target, Dialect::Sqlite | Dialect::Oracle)
1075 || is_hive_family(target)
1076 {
1077 "SUBSTR".to_string()
1078 } else {
1079 "SUBSTRING".to_string()
1080 }
1081 }
1082 "SUBSTRING" => {
1083 if is_mysql_family(target)
1084 || matches!(target, Dialect::Sqlite | Dialect::Oracle)
1085 || is_hive_family(target)
1086 {
1087 "SUBSTR".to_string()
1088 } else {
1089 name.to_string()
1090 }
1091 }
1092
1093 "IFNULL" => {
1095 if is_tsql_family(target) {
1096 "ISNULL".to_string()
1097 } else if is_mysql_family(target) || matches!(target, Dialect::Sqlite) {
1098 name.to_string()
1100 } else {
1101 "COALESCE".to_string()
1102 }
1103 }
1104 "ISNULL" => {
1105 if is_tsql_family(target) {
1106 name.to_string()
1107 } else if is_mysql_family(target) || matches!(target, Dialect::Sqlite) {
1108 "IFNULL".to_string()
1109 } else {
1110 "COALESCE".to_string()
1111 }
1112 }
1113
1114 "NVL" => {
1116 if matches!(target, Dialect::Oracle | Dialect::Snowflake) {
1117 name.to_string()
1118 } else if is_mysql_family(target) || matches!(target, Dialect::Sqlite) {
1119 "IFNULL".to_string()
1120 } else if is_tsql_family(target) {
1121 "ISNULL".to_string()
1122 } else {
1123 "COALESCE".to_string()
1124 }
1125 }
1126
1127 "RANDOM" => {
1129 if matches!(
1130 target,
1131 Dialect::Postgres | Dialect::Sqlite | Dialect::DuckDb
1132 ) {
1133 name.to_string()
1134 } else {
1135 "RAND".to_string()
1136 }
1137 }
1138 "RAND" => {
1139 if matches!(
1140 target,
1141 Dialect::Postgres | Dialect::Sqlite | Dialect::DuckDb
1142 ) {
1143 "RANDOM".to_string()
1144 } else {
1145 name.to_string()
1146 }
1147 }
1148
1149 "POSITION" if is_tsql_family(target) => "CHARINDEX".to_string(),
1151 "CHARINDEX" if is_postgres_family(target) => "POSITION".to_string(),
1152
1153 _ => name.to_string(),
1155 }
1156}
1157
1158pub(crate) fn map_data_type(dt: DataType, target: Dialect) -> DataType {
1164 match (dt, target) {
1165 (DataType::Text, t) if is_tsql_family(t) => {
1167 DataType::Varchar(None) }
1169 (DataType::Boolean, t) if is_tsql_family(t) => DataType::Bit(None),
1170 (DataType::Bytea, t) if is_tsql_family(t) => DataType::Varbinary(None),
1171 (DataType::Json, t) if is_tsql_family(t) => DataType::Varchar(None),
1172 (DataType::Jsonb, t) if is_tsql_family(t) => DataType::Varchar(None),
1173 (DataType::Uuid, t) if is_tsql_family(t) => {
1174 DataType::Unknown("UNIQUEIDENTIFIER".to_string())
1175 }
1176 (DataType::Serial, t) if is_tsql_family(t) => DataType::Int,
1177 (DataType::BigSerial, t) if is_tsql_family(t) => DataType::BigInt,
1178 (DataType::SmallSerial, t) if is_tsql_family(t) => DataType::SmallInt,
1179 (DataType::Timestamp { .. }, t) if is_tsql_family(t) => {
1180 DataType::Unknown("DATETIME2".to_string())
1181 }
1182 (DataType::Real, t) if is_tsql_family(t) => DataType::Real,
1183
1184 (DataType::Text, t) if matches!(t, Dialect::BigQuery) || is_hive_family(t) => {
1187 DataType::String
1188 }
1189 (DataType::String, t)
1191 if is_postgres_family(t) || is_mysql_family(t) || matches!(t, Dialect::Sqlite) =>
1192 {
1193 DataType::Text
1194 }
1195
1196 (DataType::Int, Dialect::BigQuery) => DataType::BigInt,
1198
1199 (DataType::Float, Dialect::BigQuery) => DataType::Double,
1201
1202 (DataType::Bytea, t)
1204 if is_mysql_family(t)
1205 || matches!(t, Dialect::Sqlite | Dialect::Oracle)
1206 || is_hive_family(t) =>
1207 {
1208 DataType::Blob
1209 }
1210 (DataType::Blob, t) if is_postgres_family(t) => DataType::Bytea,
1211 (DataType::Varbinary(_), t) if is_postgres_family(t) => DataType::Bytea,
1212
1213 (DataType::Boolean, Dialect::Mysql) => DataType::Boolean,
1215
1216 (dt, _) => dt,
1218 }
1219}
1220
1221fn transform_limit(sel: &mut SelectStatement, target: Dialect) {
1231 if is_tsql_family(target) {
1232 if let Some(limit) = sel.limit.take() {
1234 if sel.offset.is_none() {
1235 sel.top = Some(Box::new(limit));
1236 } else {
1237 sel.fetch_first = Some(limit);
1239 if sel.order_by.is_empty() {
1241 sel.order_by = vec![OrderByItem {
1242 expr: Expr::Subquery(Box::new(Statement::Select(SelectStatement {
1243 comments: Vec::new(),
1244 ctes: Vec::new(),
1245 distinct: false,
1246 top: None,
1247 columns: vec![SelectItem::Expr {
1248 expr: Expr::Null,
1249 alias: None,
1250 alias_quote_style: QuoteStyle::None,
1251 }],
1252 from: None,
1253 joins: Vec::new(),
1254 where_clause: None,
1255 group_by: Vec::new(),
1256 having: None,
1257 order_by: Vec::new(),
1258 limit: None,
1259 offset: None,
1260 fetch_first: None,
1261 qualify: None,
1262 window_definitions: Vec::new(),
1263 }))),
1264 ascending: true,
1265 nulls_first: None,
1266 }];
1267 }
1268 }
1269 }
1270 if sel.offset.is_none() {
1272 if let Some(fetch) = sel.fetch_first.take() {
1273 sel.top = Some(Box::new(fetch));
1274 }
1275 }
1276 } else if matches!(target, Dialect::Oracle) {
1277 if let Some(limit) = sel.limit.take() {
1279 sel.fetch_first = Some(limit);
1280 }
1281 if let Some(top) = sel.top.take() {
1282 sel.fetch_first = Some(*top);
1283 }
1284 } else {
1285 if let Some(top) = sel.top.take() {
1287 if sel.limit.is_none() {
1288 sel.limit = Some(*top);
1289 }
1290 }
1291 if let Some(fetch) = sel.fetch_first.take() {
1292 if sel.limit.is_none() {
1293 sel.limit = Some(fetch);
1294 }
1295 }
1296 }
1297}
1298
1299fn transform_quotes(expr: Expr, target: Dialect) -> Expr {
1306 match expr {
1307 Expr::Column {
1308 table,
1309 name,
1310 quote_style,
1311 table_quote_style,
1312 } => {
1313 let new_qs = if quote_style.is_quoted() {
1314 QuoteStyle::for_dialect(target)
1315 } else {
1316 QuoteStyle::None
1317 };
1318 let new_tqs = if table_quote_style.is_quoted() {
1319 QuoteStyle::for_dialect(target)
1320 } else {
1321 QuoteStyle::None
1322 };
1323 Expr::Column {
1324 table,
1325 name,
1326 quote_style: new_qs,
1327 table_quote_style: new_tqs,
1328 }
1329 }
1330 Expr::BinaryOp { left, op, right } => Expr::BinaryOp {
1332 left: Box::new(transform_quotes(*left, target)),
1333 op,
1334 right: Box::new(transform_quotes(*right, target)),
1335 },
1336 Expr::UnaryOp { op, expr } => Expr::UnaryOp {
1337 op,
1338 expr: Box::new(transform_quotes(*expr, target)),
1339 },
1340 Expr::Function {
1341 name,
1342 args,
1343 distinct,
1344 filter,
1345 over,
1346 order_by,
1347 within_group,
1348 } => Expr::Function {
1349 name,
1350 args: args
1351 .into_iter()
1352 .map(|a| transform_quotes(a, target))
1353 .collect(),
1354 distinct,
1355 filter: filter.map(|f| Box::new(transform_quotes(*f, target))),
1356 over,
1357 order_by,
1358 within_group,
1359 },
1360 Expr::TypedFunction { func, filter, over } => Expr::TypedFunction {
1361 func: func.transform_children(&|e| transform_quotes(e, target)),
1362 filter: filter.map(|f| Box::new(transform_quotes(*f, target))),
1363 over,
1364 },
1365 Expr::Nested(inner) => Expr::Nested(Box::new(transform_quotes(*inner, target))),
1366 Expr::Alias { expr, name } => Expr::Alias {
1367 expr: Box::new(transform_quotes(*expr, target)),
1368 name,
1369 },
1370 other => other,
1371 }
1372}
1373
1374fn transform_quotes_in_select(sel: &mut SelectStatement, target: Dialect) {
1376 for item in &mut sel.columns {
1378 if let SelectItem::Expr { expr, .. } = item {
1379 *expr = transform_quotes(expr.clone(), target);
1380 }
1381 }
1382 if let Some(wh) = &mut sel.where_clause {
1384 *wh = transform_quotes(wh.clone(), target);
1385 }
1386 for gb in &mut sel.group_by {
1388 *gb = transform_quotes(gb.clone(), target);
1389 }
1390 if let Some(having) = &mut sel.having {
1392 *having = transform_quotes(having.clone(), target);
1393 }
1394 for ob in &mut sel.order_by {
1396 ob.expr = transform_quotes(ob.expr.clone(), target);
1397 }
1398 if let Some(from) = &mut sel.from {
1400 transform_quotes_in_table_source(&mut from.source, target);
1401 }
1402 for join in &mut sel.joins {
1403 transform_quotes_in_table_source(&mut join.table, target);
1404 if let Some(on) = &mut join.on {
1405 *on = transform_quotes(on.clone(), target);
1406 }
1407 }
1408}
1409
1410fn transform_quotes_in_table_source(source: &mut TableSource, target: Dialect) {
1411 match source {
1412 TableSource::Table(tref) => {
1413 if tref.name_quote_style.is_quoted() {
1414 tref.name_quote_style = QuoteStyle::for_dialect(target);
1415 }
1416 }
1417 TableSource::Subquery { .. } => {}
1418 TableSource::TableFunction { .. } => {}
1419 TableSource::Lateral { source } => transform_quotes_in_table_source(source, target),
1420 TableSource::Pivot { source, .. } | TableSource::Unpivot { source, .. } => {
1421 transform_quotes_in_table_source(source, target);
1422 }
1423 TableSource::Unnest { .. } => {}
1424 }
1425}
1426
1427fn collect_concat_args(expr: &Expr, args: &mut Vec<Expr>) {
1433 match expr {
1434 Expr::BinaryOp {
1435 left,
1436 op: BinaryOperator::Concat,
1437 right,
1438 } => {
1439 collect_concat_args(left, args);
1440 collect_concat_args(right, args);
1441 }
1442 other => args.push(other.clone()),
1443 }
1444}
1445
1446fn try_transform_interval_arithmetic(
1453 left: &Expr,
1454 op: &BinaryOperator,
1455 right: &Expr,
1456) -> Option<Expr> {
1457 if let Expr::Interval { value, unit } = right {
1459 if let Some((count, unit_name)) = parse_interval_value(value, unit) {
1460 let final_count = if matches!(op, BinaryOperator::Minus) {
1461 -count
1462 } else {
1463 count
1464 };
1465 return Some(Expr::Function {
1466 name: "DATEADD".to_string(),
1467 args: vec![
1468 Expr::Column {
1470 table: None,
1471 name: unit_name,
1472 quote_style: QuoteStyle::None,
1473 table_quote_style: QuoteStyle::None,
1474 },
1475 Expr::Number(final_count.to_string()),
1476 left.clone(),
1477 ],
1478 distinct: false,
1479 filter: None,
1480 over: None,
1481 order_by: vec![],
1482 within_group: false,
1483 });
1484 }
1485 }
1486
1487 if let Expr::Interval { value, unit } = left {
1489 if matches!(op, BinaryOperator::Plus) {
1490 if let Some((count, unit_name)) = parse_interval_value(value, unit) {
1491 return Some(Expr::Function {
1492 name: "DATEADD".to_string(),
1493 args: vec![
1494 Expr::Column {
1495 table: None,
1496 name: unit_name,
1497 quote_style: QuoteStyle::None,
1498 table_quote_style: QuoteStyle::None,
1499 },
1500 Expr::Number(count.to_string()),
1501 right.clone(),
1502 ],
1503 distinct: false,
1504 filter: None,
1505 over: None,
1506 order_by: vec![],
1507 within_group: false,
1508 });
1509 }
1510 }
1511 }
1512
1513 None
1514}
1515
1516fn parse_interval_value(value: &Expr, unit: &Option<DateTimeField>) -> Option<(i64, String)> {
1518 if let Expr::StringLiteral(s) = value {
1520 let parts: Vec<&str> = s.trim().split_whitespace().collect();
1521 if parts.len() == 2 {
1522 let count: i64 = parts[0].parse().ok()?;
1523 let unit_name = normalize_interval_unit(parts[1])?;
1524 return Some((count, unit_name));
1525 }
1526 if parts.len() == 1 {
1527 let count: i64 = parts[0].parse().ok()?;
1529 if let Some(u) = unit {
1530 let unit_name = datetime_field_to_tsql(u)?;
1531 return Some((count, unit_name));
1532 }
1533 }
1534 }
1535
1536 if let Expr::Number(n) = value {
1538 let count: i64 = n.parse().ok()?;
1539 if let Some(u) = unit {
1540 let unit_name = datetime_field_to_tsql(u)?;
1541 return Some((count, unit_name));
1542 }
1543 }
1544
1545 None
1546}
1547
1548fn normalize_interval_unit(unit: &str) -> Option<String> {
1550 let lower = unit.to_lowercase();
1551 let normalized = lower.trim_end_matches('s');
1552 match normalized {
1553 "year" => Some("YEAR".to_string()),
1554 "month" => Some("MONTH".to_string()),
1555 "week" => Some("WEEK".to_string()),
1556 "day" => Some("DAY".to_string()),
1557 "hour" => Some("HOUR".to_string()),
1558 "minute" => Some("MINUTE".to_string()),
1559 "second" => Some("SECOND".to_string()),
1560 "millisecond" => Some("MILLISECOND".to_string()),
1561 "microsecond" => Some("MICROSECOND".to_string()),
1562 _ => None,
1563 }
1564}
1565
1566fn datetime_field_to_tsql(field: &DateTimeField) -> Option<String> {
1568 match field {
1569 DateTimeField::Year => Some("YEAR".to_string()),
1570 DateTimeField::Quarter => Some("QUARTER".to_string()),
1571 DateTimeField::Month => Some("MONTH".to_string()),
1572 DateTimeField::Week => Some("WEEK".to_string()),
1573 DateTimeField::Day => Some("DAY".to_string()),
1574 DateTimeField::Hour => Some("HOUR".to_string()),
1575 DateTimeField::Minute => Some("MINUTE".to_string()),
1576 DateTimeField::Second => Some("SECOND".to_string()),
1577 DateTimeField::Millisecond => Some("MILLISECOND".to_string()),
1578 DateTimeField::Microsecond => Some("MICROSECOND".to_string()),
1579 _ => None,
1580 }
1581}
1582
1583fn simplify_similar_to_pattern(pattern: &Expr) -> Expr {
1590 if let Expr::StringLiteral(s) = pattern {
1591 let simplified = s.replace('|', "%").replace('(', "").replace(')', "");
1592 Expr::StringLiteral(simplified)
1593 } else {
1594 pattern.clone()
1595 }
1596}