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