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::ILike {
415 expr,
416 pattern,
417 negated,
418 escape,
419 } if !supports_ilike(target) => Expr::Like {
420 expr: Box::new(Expr::Function {
421 name: "LOWER".to_string(),
422 args: vec![transform_expr(*expr, target)],
423 distinct: false,
424 filter: None,
425 over: None,
426 }),
427 pattern: Box::new(Expr::Function {
428 name: "LOWER".to_string(),
429 args: vec![transform_expr(*pattern, target)],
430 distinct: false,
431 filter: None,
432 over: None,
433 }),
434 negated,
435 escape,
436 },
437 Expr::Cast { expr, data_type } => Expr::Cast {
439 expr: Box::new(transform_expr(*expr, target)),
440 data_type: map_data_type(data_type, target),
441 },
442 Expr::BinaryOp { left, op, right } => Expr::BinaryOp {
444 left: Box::new(transform_expr(*left, target)),
445 op,
446 right: Box::new(transform_expr(*right, target)),
447 },
448 Expr::UnaryOp { op, expr } => Expr::UnaryOp {
449 op,
450 expr: Box::new(transform_expr(*expr, target)),
451 },
452 Expr::Nested(inner) => Expr::Nested(Box::new(transform_expr(*inner, target))),
453 Expr::Column {
455 table,
456 name,
457 quote_style,
458 table_quote_style,
459 } => {
460 let new_qs = if quote_style.is_quoted() {
461 QuoteStyle::for_dialect(target)
462 } else {
463 QuoteStyle::None
464 };
465 let new_tqs = if table_quote_style.is_quoted() {
466 QuoteStyle::for_dialect(target)
467 } else {
468 QuoteStyle::None
469 };
470 Expr::Column {
471 table,
472 name,
473 quote_style: new_qs,
474 table_quote_style: new_tqs,
475 }
476 }
477 other => other,
479 }
480}
481
482fn map_function_name(name: &str, target: Dialect) -> String {
488 let upper = name.to_uppercase();
489 match upper.as_str() {
490 "NOW" => {
492 if is_tsql_family(target) {
493 "GETDATE".to_string()
494 } else if matches!(
495 target,
496 Dialect::Ansi
497 | Dialect::BigQuery
498 | Dialect::Snowflake
499 | Dialect::Oracle
500 | Dialect::ClickHouse
501 | Dialect::Exasol
502 | Dialect::Teradata
503 | Dialect::Druid
504 | Dialect::Dremio
505 | Dialect::Tableau
506 ) || is_presto_family(target)
507 || is_hive_family(target)
508 {
509 "CURRENT_TIMESTAMP".to_string()
510 } else {
511 name.to_string()
513 }
514 }
515 "GETDATE" => {
516 if is_tsql_family(target) {
517 name.to_string()
518 } else if is_postgres_family(target)
519 || matches!(target, Dialect::Mysql | Dialect::DuckDb | Dialect::Sqlite)
520 {
521 "NOW".to_string()
522 } else {
523 "CURRENT_TIMESTAMP".to_string()
524 }
525 }
526
527 "LEN" => {
529 if is_tsql_family(target) || matches!(target, Dialect::BigQuery | Dialect::Snowflake) {
530 name.to_string()
531 } else {
532 "LENGTH".to_string()
533 }
534 }
535 "LENGTH" if is_tsql_family(target) => "LEN".to_string(),
536
537 "SUBSTR" => {
539 if is_mysql_family(target)
540 || matches!(target, Dialect::Sqlite | Dialect::Oracle)
541 || is_hive_family(target)
542 {
543 "SUBSTR".to_string()
544 } else {
545 "SUBSTRING".to_string()
546 }
547 }
548 "SUBSTRING" => {
549 if is_mysql_family(target)
550 || matches!(target, Dialect::Sqlite | Dialect::Oracle)
551 || is_hive_family(target)
552 {
553 "SUBSTR".to_string()
554 } else {
555 name.to_string()
556 }
557 }
558
559 "IFNULL" => {
561 if is_tsql_family(target) {
562 "ISNULL".to_string()
563 } else if is_mysql_family(target) || matches!(target, Dialect::Sqlite) {
564 name.to_string()
566 } else {
567 "COALESCE".to_string()
568 }
569 }
570 "ISNULL" => {
571 if is_tsql_family(target) {
572 name.to_string()
573 } else if is_mysql_family(target) || matches!(target, Dialect::Sqlite) {
574 "IFNULL".to_string()
575 } else {
576 "COALESCE".to_string()
577 }
578 }
579
580 "NVL" => {
582 if matches!(target, Dialect::Oracle | Dialect::Snowflake) {
583 name.to_string()
584 } else if is_mysql_family(target) || matches!(target, Dialect::Sqlite) {
585 "IFNULL".to_string()
586 } else if is_tsql_family(target) {
587 "ISNULL".to_string()
588 } else {
589 "COALESCE".to_string()
590 }
591 }
592
593 "RANDOM" => {
595 if matches!(
596 target,
597 Dialect::Postgres | Dialect::Sqlite | Dialect::DuckDb
598 ) {
599 name.to_string()
600 } else {
601 "RAND".to_string()
602 }
603 }
604 "RAND" => {
605 if matches!(
606 target,
607 Dialect::Postgres | Dialect::Sqlite | Dialect::DuckDb
608 ) {
609 "RANDOM".to_string()
610 } else {
611 name.to_string()
612 }
613 }
614
615 _ => name.to_string(),
617 }
618}
619
620fn map_data_type(dt: DataType, target: Dialect) -> DataType {
626 match (dt, target) {
627 (DataType::Text, t) if matches!(t, Dialect::BigQuery) || is_hive_family(t) => {
630 DataType::String
631 }
632 (DataType::String, t)
634 if is_postgres_family(t) || is_mysql_family(t) || matches!(t, Dialect::Sqlite) =>
635 {
636 DataType::Text
637 }
638
639 (DataType::Int, Dialect::BigQuery) => DataType::BigInt,
641
642 (DataType::Float, Dialect::BigQuery) => DataType::Double,
644
645 (DataType::Bytea, t)
647 if is_mysql_family(t)
648 || matches!(t, Dialect::Sqlite | Dialect::Oracle)
649 || is_hive_family(t) =>
650 {
651 DataType::Blob
652 }
653 (DataType::Blob, t) if is_postgres_family(t) => DataType::Bytea,
654
655 (DataType::Boolean, Dialect::Mysql) => DataType::Boolean,
657
658 (dt, _) => dt,
660 }
661}
662
663fn transform_limit(sel: &mut SelectStatement, target: Dialect) {
673 if is_tsql_family(target) {
674 if let Some(limit) = sel.limit.take() {
676 if sel.offset.is_none() {
677 sel.top = Some(Box::new(limit));
678 } else {
679 sel.fetch_first = Some(limit);
681 }
682 }
683 if sel.offset.is_none() {
685 if let Some(fetch) = sel.fetch_first.take() {
686 sel.top = Some(Box::new(fetch));
687 }
688 }
689 } else if matches!(target, Dialect::Oracle) {
690 if let Some(limit) = sel.limit.take() {
692 sel.fetch_first = Some(limit);
693 }
694 if let Some(top) = sel.top.take() {
695 sel.fetch_first = Some(*top);
696 }
697 } else {
698 if let Some(top) = sel.top.take() {
700 if sel.limit.is_none() {
701 sel.limit = Some(*top);
702 }
703 }
704 if let Some(fetch) = sel.fetch_first.take() {
705 if sel.limit.is_none() {
706 sel.limit = Some(fetch);
707 }
708 }
709 }
710}
711
712fn transform_quotes(expr: Expr, target: Dialect) -> Expr {
719 match expr {
720 Expr::Column {
721 table,
722 name,
723 quote_style,
724 table_quote_style,
725 } => {
726 let new_qs = if quote_style.is_quoted() {
727 QuoteStyle::for_dialect(target)
728 } else {
729 QuoteStyle::None
730 };
731 let new_tqs = if table_quote_style.is_quoted() {
732 QuoteStyle::for_dialect(target)
733 } else {
734 QuoteStyle::None
735 };
736 Expr::Column {
737 table,
738 name,
739 quote_style: new_qs,
740 table_quote_style: new_tqs,
741 }
742 }
743 Expr::BinaryOp { left, op, right } => Expr::BinaryOp {
745 left: Box::new(transform_quotes(*left, target)),
746 op,
747 right: Box::new(transform_quotes(*right, target)),
748 },
749 Expr::UnaryOp { op, expr } => Expr::UnaryOp {
750 op,
751 expr: Box::new(transform_quotes(*expr, target)),
752 },
753 Expr::Function {
754 name,
755 args,
756 distinct,
757 filter,
758 over,
759 } => Expr::Function {
760 name,
761 args: args
762 .into_iter()
763 .map(|a| transform_quotes(a, target))
764 .collect(),
765 distinct,
766 filter: filter.map(|f| Box::new(transform_quotes(*f, target))),
767 over,
768 },
769 Expr::Nested(inner) => Expr::Nested(Box::new(transform_quotes(*inner, target))),
770 Expr::Alias { expr, name } => Expr::Alias {
771 expr: Box::new(transform_quotes(*expr, target)),
772 name,
773 },
774 other => other,
775 }
776}
777
778fn transform_quotes_in_select(sel: &mut SelectStatement, target: Dialect) {
780 for item in &mut sel.columns {
782 if let SelectItem::Expr { expr, .. } = item {
783 *expr = transform_quotes(expr.clone(), target);
784 }
785 }
786 if let Some(wh) = &mut sel.where_clause {
788 *wh = transform_quotes(wh.clone(), target);
789 }
790 for gb in &mut sel.group_by {
792 *gb = transform_quotes(gb.clone(), target);
793 }
794 if let Some(having) = &mut sel.having {
796 *having = transform_quotes(having.clone(), target);
797 }
798 for ob in &mut sel.order_by {
800 ob.expr = transform_quotes(ob.expr.clone(), target);
801 }
802 if let Some(from) = &mut sel.from {
804 transform_quotes_in_table_source(&mut from.source, target);
805 }
806 for join in &mut sel.joins {
807 transform_quotes_in_table_source(&mut join.table, target);
808 if let Some(on) = &mut join.on {
809 *on = transform_quotes(on.clone(), target);
810 }
811 }
812}
813
814fn transform_quotes_in_table_source(source: &mut TableSource, target: Dialect) {
815 match source {
816 TableSource::Table(tref) => {
817 if tref.name_quote_style.is_quoted() {
818 tref.name_quote_style = QuoteStyle::for_dialect(target);
819 }
820 }
821 TableSource::Subquery { .. } => {}
822 TableSource::TableFunction { .. } => {}
823 TableSource::Lateral { source } => transform_quotes_in_table_source(source, target),
824 TableSource::Unnest { .. } => {}
825 }
826}