1use serde::{Deserialize, Serialize};
2
3use crate::ast::*;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
11pub enum Dialect {
12 Ansi,
15
16 Athena,
19 BigQuery,
21 ClickHouse,
23 Databricks,
25 DuckDb,
27 Hive,
29 Mysql,
31 Oracle,
33 Postgres,
35 Presto,
37 Redshift,
39 Snowflake,
41 Spark,
43 Sqlite,
45 StarRocks,
47 Trino,
49 Tsql,
51
52 Doris,
55 Dremio,
57 Drill,
59 Druid,
61 Exasol,
63 Fabric,
65 Materialize,
67 Prql,
69 RisingWave,
71 SingleStore,
73 Tableau,
75 Teradata,
77}
78
79impl std::fmt::Display for Dialect {
80 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81 match self {
82 Dialect::Ansi => write!(f, "ANSI SQL"),
83 Dialect::Athena => write!(f, "Athena"),
84 Dialect::BigQuery => write!(f, "BigQuery"),
85 Dialect::ClickHouse => write!(f, "ClickHouse"),
86 Dialect::Databricks => write!(f, "Databricks"),
87 Dialect::DuckDb => write!(f, "DuckDB"),
88 Dialect::Hive => write!(f, "Hive"),
89 Dialect::Mysql => write!(f, "MySQL"),
90 Dialect::Oracle => write!(f, "Oracle"),
91 Dialect::Postgres => write!(f, "PostgreSQL"),
92 Dialect::Presto => write!(f, "Presto"),
93 Dialect::Redshift => write!(f, "Redshift"),
94 Dialect::Snowflake => write!(f, "Snowflake"),
95 Dialect::Spark => write!(f, "Spark"),
96 Dialect::Sqlite => write!(f, "SQLite"),
97 Dialect::StarRocks => write!(f, "StarRocks"),
98 Dialect::Trino => write!(f, "Trino"),
99 Dialect::Tsql => write!(f, "T-SQL"),
100 Dialect::Doris => write!(f, "Doris"),
101 Dialect::Dremio => write!(f, "Dremio"),
102 Dialect::Drill => write!(f, "Drill"),
103 Dialect::Druid => write!(f, "Druid"),
104 Dialect::Exasol => write!(f, "Exasol"),
105 Dialect::Fabric => write!(f, "Fabric"),
106 Dialect::Materialize => write!(f, "Materialize"),
107 Dialect::Prql => write!(f, "PRQL"),
108 Dialect::RisingWave => write!(f, "RisingWave"),
109 Dialect::SingleStore => write!(f, "SingleStore"),
110 Dialect::Tableau => write!(f, "Tableau"),
111 Dialect::Teradata => write!(f, "Teradata"),
112 }
113 }
114}
115
116impl Dialect {
117 #[must_use]
119 pub fn support_level(&self) -> &'static str {
120 match self {
121 Dialect::Ansi
122 | Dialect::Athena
123 | Dialect::BigQuery
124 | Dialect::ClickHouse
125 | Dialect::Databricks
126 | Dialect::DuckDb
127 | Dialect::Hive
128 | Dialect::Mysql
129 | Dialect::Oracle
130 | Dialect::Postgres
131 | Dialect::Presto
132 | Dialect::Redshift
133 | Dialect::Snowflake
134 | Dialect::Spark
135 | Dialect::Sqlite
136 | Dialect::StarRocks
137 | Dialect::Trino
138 | Dialect::Tsql => "Official",
139
140 Dialect::Doris
141 | Dialect::Dremio
142 | Dialect::Drill
143 | Dialect::Druid
144 | Dialect::Exasol
145 | Dialect::Fabric
146 | Dialect::Materialize
147 | Dialect::Prql
148 | Dialect::RisingWave
149 | Dialect::SingleStore
150 | Dialect::Tableau
151 | Dialect::Teradata => "Community",
152 }
153 }
154
155 #[must_use]
157 pub fn all() -> &'static [Dialect] {
158 &[
159 Dialect::Ansi,
160 Dialect::Athena,
161 Dialect::BigQuery,
162 Dialect::ClickHouse,
163 Dialect::Databricks,
164 Dialect::Doris,
165 Dialect::Dremio,
166 Dialect::Drill,
167 Dialect::Druid,
168 Dialect::DuckDb,
169 Dialect::Exasol,
170 Dialect::Fabric,
171 Dialect::Hive,
172 Dialect::Materialize,
173 Dialect::Mysql,
174 Dialect::Oracle,
175 Dialect::Postgres,
176 Dialect::Presto,
177 Dialect::Prql,
178 Dialect::Redshift,
179 Dialect::RisingWave,
180 Dialect::SingleStore,
181 Dialect::Snowflake,
182 Dialect::Spark,
183 Dialect::Sqlite,
184 Dialect::StarRocks,
185 Dialect::Tableau,
186 Dialect::Teradata,
187 Dialect::Trino,
188 Dialect::Tsql,
189 ]
190 }
191
192 pub fn from_str(s: &str) -> Option<Dialect> {
194 match s.to_lowercase().as_str() {
195 "" | "ansi" => Some(Dialect::Ansi),
196 "athena" => Some(Dialect::Athena),
197 "bigquery" => Some(Dialect::BigQuery),
198 "clickhouse" => Some(Dialect::ClickHouse),
199 "databricks" => Some(Dialect::Databricks),
200 "doris" => Some(Dialect::Doris),
201 "dremio" => Some(Dialect::Dremio),
202 "drill" => Some(Dialect::Drill),
203 "druid" => Some(Dialect::Druid),
204 "duckdb" => Some(Dialect::DuckDb),
205 "exasol" => Some(Dialect::Exasol),
206 "fabric" => Some(Dialect::Fabric),
207 "hive" => Some(Dialect::Hive),
208 "materialize" => Some(Dialect::Materialize),
209 "mysql" => Some(Dialect::Mysql),
210 "oracle" => Some(Dialect::Oracle),
211 "postgres" | "postgresql" => Some(Dialect::Postgres),
212 "presto" => Some(Dialect::Presto),
213 "prql" => Some(Dialect::Prql),
214 "redshift" => Some(Dialect::Redshift),
215 "risingwave" => Some(Dialect::RisingWave),
216 "singlestore" => Some(Dialect::SingleStore),
217 "snowflake" => Some(Dialect::Snowflake),
218 "spark" => Some(Dialect::Spark),
219 "sqlite" => Some(Dialect::Sqlite),
220 "starrocks" => Some(Dialect::StarRocks),
221 "tableau" => Some(Dialect::Tableau),
222 "teradata" => Some(Dialect::Teradata),
223 "trino" => Some(Dialect::Trino),
224 "tsql" | "mssql" | "sqlserver" => Some(Dialect::Tsql),
225 _ => None,
226 }
227 }
228}
229
230fn is_mysql_family(d: Dialect) -> bool {
236 matches!(
237 d,
238 Dialect::Mysql | Dialect::Doris | Dialect::SingleStore | Dialect::StarRocks
239 )
240}
241
242fn is_postgres_family(d: Dialect) -> bool {
244 matches!(
245 d,
246 Dialect::Postgres | Dialect::Redshift | Dialect::Materialize | Dialect::RisingWave
247 )
248}
249
250fn is_presto_family(d: Dialect) -> bool {
252 matches!(d, Dialect::Presto | Dialect::Trino | Dialect::Athena)
253}
254
255fn is_hive_family(d: Dialect) -> bool {
257 matches!(d, Dialect::Hive | Dialect::Spark | Dialect::Databricks)
258}
259
260fn is_tsql_family(d: Dialect) -> bool {
262 matches!(d, Dialect::Tsql | Dialect::Fabric)
263}
264
265fn supports_ilike(d: Dialect) -> bool {
267 matches!(
268 d,
269 Dialect::Postgres
270 | Dialect::Redshift
271 | Dialect::Materialize
272 | Dialect::RisingWave
273 | Dialect::DuckDb
274 | Dialect::Snowflake
275 | Dialect::ClickHouse
276 | Dialect::Trino
277 | Dialect::Presto
278 | Dialect::Athena
279 | Dialect::Databricks
280 | Dialect::Spark
281 | Dialect::Hive
282 | Dialect::StarRocks
283 | Dialect::Exasol
284 | Dialect::Druid
285 | Dialect::Dremio
286 )
287}
288
289#[must_use]
300pub fn transform(statement: &Statement, from: Dialect, to: Dialect) -> Statement {
301 if from == to {
302 return statement.clone();
303 }
304 let mut stmt = statement.clone();
305 transform_statement(&mut stmt, to);
306 stmt
307}
308
309fn transform_statement(statement: &mut Statement, target: Dialect) {
310 match statement {
311 Statement::Select(sel) => {
312 transform_limit(sel, target);
314 transform_quotes_in_select(sel, target);
316
317 for item in &mut sel.columns {
318 if let SelectItem::Expr { expr, .. } = item {
319 *expr = transform_expr(expr.clone(), target);
320 }
321 }
322 if let Some(wh) = &mut sel.where_clause {
323 *wh = transform_expr(wh.clone(), target);
324 }
325 for gb in &mut sel.group_by {
326 *gb = transform_expr(gb.clone(), target);
327 }
328 if let Some(having) = &mut sel.having {
329 *having = transform_expr(having.clone(), target);
330 }
331 }
332 Statement::Insert(ins) => {
333 if let InsertSource::Values(rows) = &mut ins.source {
334 for row in rows {
335 for val in row {
336 *val = transform_expr(val.clone(), target);
337 }
338 }
339 }
340 }
341 Statement::Update(upd) => {
342 for (_, val) in &mut upd.assignments {
343 *val = transform_expr(val.clone(), target);
344 }
345 if let Some(wh) = &mut upd.where_clause {
346 *wh = transform_expr(wh.clone(), target);
347 }
348 }
349 Statement::CreateTable(ct) => {
351 for col in &mut ct.columns {
352 col.data_type = map_data_type(col.data_type.clone(), target);
353 if let Some(default) = &mut col.default {
354 *default = transform_expr(default.clone(), target);
355 }
356 }
357 for constraint in &mut ct.constraints {
359 if let TableConstraint::Check { expr, .. } = constraint {
360 *expr = transform_expr(expr.clone(), target);
361 }
362 }
363 if let Some(as_select) = &mut ct.as_select {
365 transform_statement(as_select, target);
366 }
367 }
368 Statement::AlterTable(alt) => {
370 for action in &mut alt.actions {
371 match action {
372 AlterTableAction::AddColumn(col) => {
373 col.data_type = map_data_type(col.data_type.clone(), target);
374 if let Some(default) = &mut col.default {
375 *default = transform_expr(default.clone(), target);
376 }
377 }
378 AlterTableAction::AlterColumnType { data_type, .. } => {
379 *data_type = map_data_type(data_type.clone(), target);
380 }
381 _ => {}
382 }
383 }
384 }
385 _ => {}
386 }
387}
388
389fn transform_expr(expr: Expr, target: Dialect) -> Expr {
391 match expr {
392 Expr::Function {
394 name,
395 args,
396 distinct,
397 filter,
398 over,
399 } => {
400 let new_name = map_function_name(&name, target);
401 let new_args: Vec<Expr> = args
402 .into_iter()
403 .map(|a| transform_expr(a, target))
404 .collect();
405 Expr::Function {
406 name: new_name,
407 args: new_args,
408 distinct,
409 filter: filter.map(|f| Box::new(transform_expr(*f, target))),
410 over,
411 }
412 }
413 Expr::TypedFunction { func, filter, over } => {
415 let transformed_func = func.transform_children(&|e| transform_expr(e, target));
416 Expr::TypedFunction {
417 func: transformed_func,
418 filter: filter.map(|f| Box::new(transform_expr(*f, target))),
419 over,
420 }
421 }
422 Expr::ILike {
424 expr,
425 pattern,
426 negated,
427 escape,
428 } if !supports_ilike(target) => Expr::Like {
429 expr: Box::new(Expr::TypedFunction {
430 func: TypedFunction::Lower {
431 expr: Box::new(transform_expr(*expr, target)),
432 },
433 filter: None,
434 over: None,
435 }),
436 pattern: Box::new(Expr::TypedFunction {
437 func: TypedFunction::Lower {
438 expr: Box::new(transform_expr(*pattern, target)),
439 },
440 filter: None,
441 over: None,
442 }),
443 negated,
444 escape,
445 },
446 Expr::Cast { expr, data_type } => Expr::Cast {
448 expr: Box::new(transform_expr(*expr, target)),
449 data_type: map_data_type(data_type, target),
450 },
451 Expr::BinaryOp { left, op, right } => Expr::BinaryOp {
453 left: Box::new(transform_expr(*left, target)),
454 op,
455 right: Box::new(transform_expr(*right, target)),
456 },
457 Expr::UnaryOp { op, expr } => Expr::UnaryOp {
458 op,
459 expr: Box::new(transform_expr(*expr, target)),
460 },
461 Expr::Nested(inner) => Expr::Nested(Box::new(transform_expr(*inner, target))),
462 Expr::Column {
464 table,
465 name,
466 quote_style,
467 table_quote_style,
468 } => {
469 let new_qs = if quote_style.is_quoted() {
470 QuoteStyle::for_dialect(target)
471 } else {
472 QuoteStyle::None
473 };
474 let new_tqs = if table_quote_style.is_quoted() {
475 QuoteStyle::for_dialect(target)
476 } else {
477 QuoteStyle::None
478 };
479 Expr::Column {
480 table,
481 name,
482 quote_style: new_qs,
483 table_quote_style: new_tqs,
484 }
485 }
486 other => other,
488 }
489}
490
491fn map_function_name(name: &str, target: Dialect) -> String {
497 let upper = name.to_uppercase();
498 match upper.as_str() {
499 "NOW" => {
501 if is_tsql_family(target) {
502 "GETDATE".to_string()
503 } else if matches!(
504 target,
505 Dialect::Ansi
506 | Dialect::BigQuery
507 | Dialect::Snowflake
508 | Dialect::Oracle
509 | Dialect::ClickHouse
510 | Dialect::Exasol
511 | Dialect::Teradata
512 | Dialect::Druid
513 | Dialect::Dremio
514 | Dialect::Tableau
515 ) || is_presto_family(target)
516 || is_hive_family(target)
517 {
518 "CURRENT_TIMESTAMP".to_string()
519 } else {
520 name.to_string()
522 }
523 }
524 "GETDATE" => {
525 if is_tsql_family(target) {
526 name.to_string()
527 } else if is_postgres_family(target)
528 || matches!(target, Dialect::Mysql | Dialect::DuckDb | Dialect::Sqlite)
529 {
530 "NOW".to_string()
531 } else {
532 "CURRENT_TIMESTAMP".to_string()
533 }
534 }
535
536 "LEN" => {
538 if is_tsql_family(target) || matches!(target, Dialect::BigQuery | Dialect::Snowflake) {
539 name.to_string()
540 } else {
541 "LENGTH".to_string()
542 }
543 }
544 "LENGTH" if is_tsql_family(target) => "LEN".to_string(),
545
546 "SUBSTR" => {
548 if is_mysql_family(target)
549 || matches!(target, Dialect::Sqlite | Dialect::Oracle)
550 || is_hive_family(target)
551 {
552 "SUBSTR".to_string()
553 } else {
554 "SUBSTRING".to_string()
555 }
556 }
557 "SUBSTRING" => {
558 if is_mysql_family(target)
559 || matches!(target, Dialect::Sqlite | Dialect::Oracle)
560 || is_hive_family(target)
561 {
562 "SUBSTR".to_string()
563 } else {
564 name.to_string()
565 }
566 }
567
568 "IFNULL" => {
570 if is_tsql_family(target) {
571 "ISNULL".to_string()
572 } else if is_mysql_family(target) || matches!(target, Dialect::Sqlite) {
573 name.to_string()
575 } else {
576 "COALESCE".to_string()
577 }
578 }
579 "ISNULL" => {
580 if is_tsql_family(target) {
581 name.to_string()
582 } else if is_mysql_family(target) || matches!(target, Dialect::Sqlite) {
583 "IFNULL".to_string()
584 } else {
585 "COALESCE".to_string()
586 }
587 }
588
589 "NVL" => {
591 if matches!(target, Dialect::Oracle | Dialect::Snowflake) {
592 name.to_string()
593 } else if is_mysql_family(target) || matches!(target, Dialect::Sqlite) {
594 "IFNULL".to_string()
595 } else if is_tsql_family(target) {
596 "ISNULL".to_string()
597 } else {
598 "COALESCE".to_string()
599 }
600 }
601
602 "RANDOM" => {
604 if matches!(
605 target,
606 Dialect::Postgres | Dialect::Sqlite | Dialect::DuckDb
607 ) {
608 name.to_string()
609 } else {
610 "RAND".to_string()
611 }
612 }
613 "RAND" => {
614 if matches!(
615 target,
616 Dialect::Postgres | Dialect::Sqlite | Dialect::DuckDb
617 ) {
618 "RANDOM".to_string()
619 } else {
620 name.to_string()
621 }
622 }
623
624 _ => name.to_string(),
626 }
627}
628
629fn map_data_type(dt: DataType, target: Dialect) -> DataType {
635 match (dt, target) {
636 (DataType::Text, t) if matches!(t, Dialect::BigQuery) || is_hive_family(t) => {
639 DataType::String
640 }
641 (DataType::String, t)
643 if is_postgres_family(t) || is_mysql_family(t) || matches!(t, Dialect::Sqlite) =>
644 {
645 DataType::Text
646 }
647
648 (DataType::Int, Dialect::BigQuery) => DataType::BigInt,
650
651 (DataType::Float, Dialect::BigQuery) => DataType::Double,
653
654 (DataType::Bytea, t)
656 if is_mysql_family(t)
657 || matches!(t, Dialect::Sqlite | Dialect::Oracle)
658 || is_hive_family(t) =>
659 {
660 DataType::Blob
661 }
662 (DataType::Blob, t) if is_postgres_family(t) => DataType::Bytea,
663
664 (DataType::Boolean, Dialect::Mysql) => DataType::Boolean,
666
667 (dt, _) => dt,
669 }
670}
671
672fn transform_limit(sel: &mut SelectStatement, target: Dialect) {
682 if is_tsql_family(target) {
683 if let Some(limit) = sel.limit.take() {
685 if sel.offset.is_none() {
686 sel.top = Some(Box::new(limit));
687 } else {
688 sel.fetch_first = Some(limit);
690 }
691 }
692 if sel.offset.is_none() {
694 if let Some(fetch) = sel.fetch_first.take() {
695 sel.top = Some(Box::new(fetch));
696 }
697 }
698 } else if matches!(target, Dialect::Oracle) {
699 if let Some(limit) = sel.limit.take() {
701 sel.fetch_first = Some(limit);
702 }
703 if let Some(top) = sel.top.take() {
704 sel.fetch_first = Some(*top);
705 }
706 } else {
707 if let Some(top) = sel.top.take() {
709 if sel.limit.is_none() {
710 sel.limit = Some(*top);
711 }
712 }
713 if let Some(fetch) = sel.fetch_first.take() {
714 if sel.limit.is_none() {
715 sel.limit = Some(fetch);
716 }
717 }
718 }
719}
720
721fn transform_quotes(expr: Expr, target: Dialect) -> Expr {
728 match expr {
729 Expr::Column {
730 table,
731 name,
732 quote_style,
733 table_quote_style,
734 } => {
735 let new_qs = if quote_style.is_quoted() {
736 QuoteStyle::for_dialect(target)
737 } else {
738 QuoteStyle::None
739 };
740 let new_tqs = if table_quote_style.is_quoted() {
741 QuoteStyle::for_dialect(target)
742 } else {
743 QuoteStyle::None
744 };
745 Expr::Column {
746 table,
747 name,
748 quote_style: new_qs,
749 table_quote_style: new_tqs,
750 }
751 }
752 Expr::BinaryOp { left, op, right } => Expr::BinaryOp {
754 left: Box::new(transform_quotes(*left, target)),
755 op,
756 right: Box::new(transform_quotes(*right, target)),
757 },
758 Expr::UnaryOp { op, expr } => Expr::UnaryOp {
759 op,
760 expr: Box::new(transform_quotes(*expr, target)),
761 },
762 Expr::Function {
763 name,
764 args,
765 distinct,
766 filter,
767 over,
768 } => Expr::Function {
769 name,
770 args: args
771 .into_iter()
772 .map(|a| transform_quotes(a, target))
773 .collect(),
774 distinct,
775 filter: filter.map(|f| Box::new(transform_quotes(*f, target))),
776 over,
777 },
778 Expr::TypedFunction { func, filter, over } => Expr::TypedFunction {
779 func: func.transform_children(&|e| transform_quotes(e, target)),
780 filter: filter.map(|f| Box::new(transform_quotes(*f, target))),
781 over,
782 },
783 Expr::Nested(inner) => Expr::Nested(Box::new(transform_quotes(*inner, target))),
784 Expr::Alias { expr, name } => Expr::Alias {
785 expr: Box::new(transform_quotes(*expr, target)),
786 name,
787 },
788 other => other,
789 }
790}
791
792fn transform_quotes_in_select(sel: &mut SelectStatement, target: Dialect) {
794 for item in &mut sel.columns {
796 if let SelectItem::Expr { expr, .. } = item {
797 *expr = transform_quotes(expr.clone(), target);
798 }
799 }
800 if let Some(wh) = &mut sel.where_clause {
802 *wh = transform_quotes(wh.clone(), target);
803 }
804 for gb in &mut sel.group_by {
806 *gb = transform_quotes(gb.clone(), target);
807 }
808 if let Some(having) = &mut sel.having {
810 *having = transform_quotes(having.clone(), target);
811 }
812 for ob in &mut sel.order_by {
814 ob.expr = transform_quotes(ob.expr.clone(), target);
815 }
816 if let Some(from) = &mut sel.from {
818 transform_quotes_in_table_source(&mut from.source, target);
819 }
820 for join in &mut sel.joins {
821 transform_quotes_in_table_source(&mut join.table, target);
822 if let Some(on) = &mut join.on {
823 *on = transform_quotes(on.clone(), target);
824 }
825 }
826}
827
828fn transform_quotes_in_table_source(source: &mut TableSource, target: Dialect) {
829 match source {
830 TableSource::Table(tref) => {
831 if tref.name_quote_style.is_quoted() {
832 tref.name_quote_style = QuoteStyle::for_dialect(target);
833 }
834 }
835 TableSource::Subquery { .. } => {}
836 TableSource::TableFunction { .. } => {}
837 TableSource::Lateral { source } => transform_quotes_in_table_source(source, target),
838 TableSource::Pivot { source, .. } | TableSource::Unpivot { source, .. } => {
839 transform_quotes_in_table_source(source, target);
840 }
841 TableSource::Unnest { .. } => {}
842 }
843}