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