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
263fn is_tsql_family(d: Dialect) -> bool {
265 matches!(d, Dialect::Tsql | Dialect::Fabric)
266}
267
268pub(crate) fn supports_ilike_builtin(d: Dialect) -> bool {
270 matches!(
271 d,
272 Dialect::Postgres
273 | Dialect::Redshift
274 | Dialect::Materialize
275 | Dialect::RisingWave
276 | Dialect::DuckDb
277 | Dialect::Snowflake
278 | Dialect::ClickHouse
279 | Dialect::Trino
280 | Dialect::Presto
281 | Dialect::Athena
282 | Dialect::Databricks
283 | Dialect::Spark
284 | Dialect::Hive
285 | Dialect::StarRocks
286 | Dialect::Exasol
287 | Dialect::Druid
288 | Dialect::Dremio
289 )
290}
291
292#[must_use]
303pub fn transform(statement: &Statement, from: Dialect, to: Dialect) -> Statement {
304 if from == to {
305 return statement.clone();
306 }
307 let mut stmt = statement.clone();
308 transform_statement(&mut stmt, to);
309 stmt
310}
311
312fn transform_statement(statement: &mut Statement, target: Dialect) {
313 match statement {
314 Statement::Select(sel) => {
315 transform_limit(sel, target);
317 transform_quotes_in_select(sel, target);
319
320 for item in &mut sel.columns {
321 if let SelectItem::Expr { expr, .. } = item {
322 *expr = transform_expr(expr.clone(), target);
323 }
324 }
325 if let Some(wh) = &mut sel.where_clause {
326 *wh = transform_expr(wh.clone(), target);
327 }
328 for gb in &mut sel.group_by {
329 *gb = transform_expr(gb.clone(), target);
330 }
331 if let Some(having) = &mut sel.having {
332 *having = transform_expr(having.clone(), target);
333 }
334 }
335 Statement::Insert(ins) => {
336 if let InsertSource::Values(rows) = &mut ins.source {
337 for row in rows {
338 for val in row {
339 *val = transform_expr(val.clone(), target);
340 }
341 }
342 }
343 for item in &mut ins.returning {
345 if let SelectItem::Expr { expr, .. } = item {
346 *expr = transform_expr(expr.clone(), target);
347 }
348 }
349 }
350 Statement::Update(upd) => {
351 for (_, val) in &mut upd.assignments {
352 *val = transform_expr(val.clone(), target);
353 }
354 if let Some(wh) = &mut upd.where_clause {
355 *wh = transform_expr(wh.clone(), target);
356 }
357 for item in &mut upd.returning {
359 if let SelectItem::Expr { expr, .. } = item {
360 *expr = transform_expr(expr.clone(), target);
361 }
362 }
363 }
364 Statement::CreateTable(ct) => {
366 for col in &mut ct.columns {
367 col.data_type = map_data_type(col.data_type.clone(), target);
368 if let Some(default) = &mut col.default {
369 *default = transform_expr(default.clone(), target);
370 }
371 }
372 for constraint in &mut ct.constraints {
374 if let TableConstraint::Check { expr, .. } = constraint {
375 *expr = transform_expr(expr.clone(), target);
376 }
377 }
378 if let Some(as_select) = &mut ct.as_select {
380 transform_statement(as_select, target);
381 }
382 }
383 Statement::AlterTable(alt) => {
385 for action in &mut alt.actions {
386 match action {
387 AlterTableAction::AddColumn(col) => {
388 col.data_type = map_data_type(col.data_type.clone(), target);
389 if let Some(default) = &mut col.default {
390 *default = transform_expr(default.clone(), target);
391 }
392 }
393 AlterTableAction::AlterColumnType { data_type, .. } => {
394 *data_type = map_data_type(data_type.clone(), target);
395 }
396 _ => {}
397 }
398 }
399 }
400 _ => {}
401 }
402}
403
404fn transform_expr(expr: Expr, target: Dialect) -> Expr {
406 match expr {
407 Expr::Function {
409 name,
410 args,
411 distinct,
412 filter,
413 over,
414 } => {
415 let new_name = map_function_name(&name, target);
416 let new_args: Vec<Expr> = args
417 .into_iter()
418 .map(|a| transform_expr(a, target))
419 .collect();
420 Expr::Function {
421 name: new_name,
422 args: new_args,
423 distinct,
424 filter: filter.map(|f| Box::new(transform_expr(*f, target))),
425 over,
426 }
427 }
428 Expr::TypedFunction { func, filter, over } => {
431 let transformed_func = transform_typed_function(func, target);
432 Expr::TypedFunction {
433 func: transformed_func,
434 filter: filter.map(|f| Box::new(transform_expr(*f, target))),
435 over,
436 }
437 }
438 Expr::ILike {
440 expr,
441 pattern,
442 negated,
443 escape,
444 } if !supports_ilike_builtin(target) => Expr::Like {
445 expr: Box::new(Expr::TypedFunction {
446 func: TypedFunction::Lower {
447 expr: Box::new(transform_expr(*expr, target)),
448 },
449 filter: None,
450 over: None,
451 }),
452 pattern: Box::new(Expr::TypedFunction {
453 func: TypedFunction::Lower {
454 expr: Box::new(transform_expr(*pattern, target)),
455 },
456 filter: None,
457 over: None,
458 }),
459 negated,
460 escape,
461 },
462 Expr::SimilarTo {
464 expr,
465 pattern,
466 negated,
467 escape,
468 } if is_tsql_family(target) => {
469 let transformed_pattern = transform_expr(*pattern, target);
470 let simplified = simplify_similar_to_pattern(&transformed_pattern);
471 Expr::Like {
472 expr: Box::new(transform_expr(*expr, target)),
473 pattern: Box::new(simplified),
474 negated,
475 escape,
476 }
477 }
478 Expr::Cast { expr, data_type } => Expr::Cast {
480 expr: Box::new(transform_expr(*expr, target)),
481 data_type: map_data_type(data_type, target),
482 },
483 Expr::BinaryOp { left, op, right } => {
485 if op == BinaryOperator::Concat && is_tsql_family(target) {
488 let mut args = Vec::new();
489 collect_concat_args(
490 &Expr::BinaryOp {
491 left,
492 op: BinaryOperator::Concat,
493 right,
494 },
495 &mut args,
496 );
497 let args = args
499 .into_iter()
500 .map(|a| transform_expr(a, target))
501 .collect();
502 return Expr::Function {
503 name: "CONCAT".to_string(),
504 args,
505 distinct: false,
506 filter: None,
507 over: None,
508 };
509 }
510
511 let left_transformed = transform_expr(*left, target);
512 let right_transformed = transform_expr(*right, target);
513
514 if is_tsql_family(target) && matches!(op, BinaryOperator::Plus | BinaryOperator::Minus)
516 {
517 if let Some(dateadd) =
518 try_transform_interval_arithmetic(&left_transformed, &op, &right_transformed)
519 {
520 return dateadd;
521 }
522 }
523
524 Expr::BinaryOp {
525 left: Box::new(left_transformed),
526 op,
527 right: Box::new(right_transformed),
528 }
529 }
530 Expr::UnaryOp { op, expr } => Expr::UnaryOp {
531 op,
532 expr: Box::new(transform_expr(*expr, target)),
533 },
534 Expr::Nested(inner) => Expr::Nested(Box::new(transform_expr(*inner, target))),
535 Expr::Column {
537 table,
538 name,
539 quote_style,
540 table_quote_style,
541 } => {
542 let new_qs = if quote_style.is_quoted() {
543 QuoteStyle::for_dialect(target)
544 } else {
545 QuoteStyle::None
546 };
547 let new_tqs = if table_quote_style.is_quoted() {
548 QuoteStyle::for_dialect(target)
549 } else {
550 QuoteStyle::None
551 };
552 Expr::Column {
553 table,
554 name,
555 quote_style: new_qs,
556 table_quote_style: new_tqs,
557 }
558 }
559 other => other,
561 }
562}
563
564fn transform_typed_function(func: TypedFunction, target: Dialect) -> TypedFunction {
573 match func {
574 TypedFunction::TimeToStr { expr, format } => {
575 let transformed_expr = Box::new(transform_expr(*expr, target));
576 let transformed_format = transform_format_expr(*format, target);
577 TypedFunction::TimeToStr {
578 expr: transformed_expr,
579 format: Box::new(transformed_format),
580 }
581 }
582 TypedFunction::StrToTime { expr, format } => {
583 let transformed_expr = Box::new(transform_expr(*expr, target));
584 let transformed_format = transform_format_expr(*format, target);
585 TypedFunction::StrToTime {
586 expr: transformed_expr,
587 format: Box::new(transformed_format),
588 }
589 }
590 other => other.transform_children(&|e| transform_expr(e, target)),
592 }
593}
594
595fn transform_format_expr(expr: Expr, target: Dialect) -> Expr {
600 match &expr {
604 Expr::StringLiteral(s) | Expr::NationalStringLiteral(s) => {
605 let detected_source = detect_format_style(s);
606 let target_style = time::TimeFormatStyle::for_dialect(target);
607
608 if detected_source != target_style {
610 let converted = time::format_time(s, detected_source, target_style);
611 match expr {
612 Expr::NationalStringLiteral(_) => Expr::NationalStringLiteral(converted),
613 _ => Expr::StringLiteral(converted),
614 }
615 } else {
616 expr
617 }
618 }
619 _ => transform_expr(expr, target),
620 }
621}
622
623fn detect_format_style(format_str: &str) -> time::TimeFormatStyle {
625 if format_str.contains('%') {
627 if format_str.contains("%i") {
629 time::TimeFormatStyle::Mysql
631 } else {
632 time::TimeFormatStyle::Strftime
634 }
635 } else if format_str.contains("YYYY") || format_str.contains("yyyy") {
636 if format_str.contains("HH24") || format_str.contains("MI") || format_str.contains("SS") {
638 time::TimeFormatStyle::Postgres
640 } else if format_str.contains("mm") && format_str.contains("ss") {
641 time::TimeFormatStyle::Java
643 } else if format_str.contains("FF") {
644 time::TimeFormatStyle::Snowflake
646 } else if format_str.contains("MM") && format_str.contains("DD") {
647 time::TimeFormatStyle::Postgres
649 } else {
650 time::TimeFormatStyle::Java
652 }
653 } else {
654 time::TimeFormatStyle::Strftime
656 }
657}
658
659pub(crate) fn map_function_name(name: &str, target: Dialect) -> String {
665 let upper = name.to_uppercase();
666 match upper.as_str() {
667 "NOW" => {
669 if is_tsql_family(target) {
670 "GETDATE".to_string()
671 } else if matches!(
672 target,
673 Dialect::Ansi
674 | Dialect::BigQuery
675 | Dialect::Snowflake
676 | Dialect::Oracle
677 | Dialect::ClickHouse
678 | Dialect::Exasol
679 | Dialect::Teradata
680 | Dialect::Druid
681 | Dialect::Dremio
682 | Dialect::Tableau
683 ) || is_presto_family(target)
684 || is_hive_family(target)
685 {
686 "CURRENT_TIMESTAMP".to_string()
687 } else {
688 name.to_string()
690 }
691 }
692 "GETDATE" => {
693 if is_tsql_family(target) {
694 name.to_string()
695 } else if is_postgres_family(target)
696 || matches!(target, Dialect::Mysql | Dialect::DuckDb | Dialect::Sqlite)
697 {
698 "NOW".to_string()
699 } else {
700 "CURRENT_TIMESTAMP".to_string()
701 }
702 }
703
704 "LEN" => {
706 if is_tsql_family(target) || matches!(target, Dialect::BigQuery | Dialect::Snowflake) {
707 name.to_string()
708 } else {
709 "LENGTH".to_string()
710 }
711 }
712 "LENGTH" if is_tsql_family(target) => "LEN".to_string(),
713
714 "SUBSTR" => {
716 if is_mysql_family(target)
717 || matches!(target, Dialect::Sqlite | Dialect::Oracle)
718 || is_hive_family(target)
719 {
720 "SUBSTR".to_string()
721 } else {
722 "SUBSTRING".to_string()
723 }
724 }
725 "SUBSTRING" => {
726 if is_mysql_family(target)
727 || matches!(target, Dialect::Sqlite | Dialect::Oracle)
728 || is_hive_family(target)
729 {
730 "SUBSTR".to_string()
731 } else {
732 name.to_string()
733 }
734 }
735
736 "IFNULL" => {
738 if is_tsql_family(target) {
739 "ISNULL".to_string()
740 } else if is_mysql_family(target) || matches!(target, Dialect::Sqlite) {
741 name.to_string()
743 } else {
744 "COALESCE".to_string()
745 }
746 }
747 "ISNULL" => {
748 if is_tsql_family(target) {
749 name.to_string()
750 } else if is_mysql_family(target) || matches!(target, Dialect::Sqlite) {
751 "IFNULL".to_string()
752 } else {
753 "COALESCE".to_string()
754 }
755 }
756
757 "NVL" => {
759 if matches!(target, Dialect::Oracle | Dialect::Snowflake) {
760 name.to_string()
761 } else if is_mysql_family(target) || matches!(target, Dialect::Sqlite) {
762 "IFNULL".to_string()
763 } else if is_tsql_family(target) {
764 "ISNULL".to_string()
765 } else {
766 "COALESCE".to_string()
767 }
768 }
769
770 "RANDOM" => {
772 if matches!(
773 target,
774 Dialect::Postgres | Dialect::Sqlite | Dialect::DuckDb
775 ) {
776 name.to_string()
777 } else {
778 "RAND".to_string()
779 }
780 }
781 "RAND" => {
782 if matches!(
783 target,
784 Dialect::Postgres | Dialect::Sqlite | Dialect::DuckDb
785 ) {
786 "RANDOM".to_string()
787 } else {
788 name.to_string()
789 }
790 }
791
792 "POSITION" if is_tsql_family(target) => "CHARINDEX".to_string(),
794 "CHARINDEX" if is_postgres_family(target) => "POSITION".to_string(),
795
796 _ => name.to_string(),
798 }
799}
800
801pub(crate) fn map_data_type(dt: DataType, target: Dialect) -> DataType {
807 match (dt, target) {
808 (DataType::Text, t) if is_tsql_family(t) => {
810 DataType::Varchar(None) }
812 (DataType::Boolean, t) if is_tsql_family(t) => DataType::Bit(None),
813 (DataType::Bytea, t) if is_tsql_family(t) => DataType::Varbinary(None),
814 (DataType::Json, t) if is_tsql_family(t) => DataType::Varchar(None),
815 (DataType::Jsonb, t) if is_tsql_family(t) => DataType::Varchar(None),
816 (DataType::Uuid, t) if is_tsql_family(t) => {
817 DataType::Unknown("UNIQUEIDENTIFIER".to_string())
818 }
819 (DataType::Serial, t) if is_tsql_family(t) => DataType::Int,
820 (DataType::BigSerial, t) if is_tsql_family(t) => DataType::BigInt,
821 (DataType::SmallSerial, t) if is_tsql_family(t) => DataType::SmallInt,
822 (DataType::Timestamp { .. }, t) if is_tsql_family(t) => {
823 DataType::Unknown("DATETIME2".to_string())
824 }
825 (DataType::Real, t) if is_tsql_family(t) => DataType::Real,
826
827 (DataType::Text, t) if matches!(t, Dialect::BigQuery) || is_hive_family(t) => {
830 DataType::String
831 }
832 (DataType::String, t)
834 if is_postgres_family(t) || is_mysql_family(t) || matches!(t, Dialect::Sqlite) =>
835 {
836 DataType::Text
837 }
838
839 (DataType::Int, Dialect::BigQuery) => DataType::BigInt,
841
842 (DataType::Float, Dialect::BigQuery) => DataType::Double,
844
845 (DataType::Bytea, t)
847 if is_mysql_family(t)
848 || matches!(t, Dialect::Sqlite | Dialect::Oracle)
849 || is_hive_family(t) =>
850 {
851 DataType::Blob
852 }
853 (DataType::Blob, t) if is_postgres_family(t) => DataType::Bytea,
854 (DataType::Varbinary(_), t) if is_postgres_family(t) => DataType::Bytea,
855
856 (DataType::Boolean, Dialect::Mysql) => DataType::Boolean,
858
859 (dt, _) => dt,
861 }
862}
863
864fn transform_limit(sel: &mut SelectStatement, target: Dialect) {
874 if is_tsql_family(target) {
875 if let Some(limit) = sel.limit.take() {
877 if sel.offset.is_none() {
878 sel.top = Some(Box::new(limit));
879 } else {
880 sel.fetch_first = Some(limit);
882 if sel.order_by.is_empty() {
884 sel.order_by = vec![OrderByItem {
885 expr: Expr::Subquery(Box::new(Statement::Select(SelectStatement {
886 comments: Vec::new(),
887 ctes: Vec::new(),
888 distinct: false,
889 top: None,
890 columns: vec![SelectItem::Expr {
891 expr: Expr::Null,
892 alias: None,
893 alias_quote_style: QuoteStyle::None,
894 }],
895 from: None,
896 joins: Vec::new(),
897 where_clause: None,
898 group_by: Vec::new(),
899 having: None,
900 order_by: Vec::new(),
901 limit: None,
902 offset: None,
903 fetch_first: None,
904 qualify: None,
905 window_definitions: Vec::new(),
906 }))),
907 ascending: true,
908 nulls_first: None,
909 }];
910 }
911 }
912 }
913 if sel.offset.is_none() {
915 if let Some(fetch) = sel.fetch_first.take() {
916 sel.top = Some(Box::new(fetch));
917 }
918 }
919 } else if matches!(target, Dialect::Oracle) {
920 if let Some(limit) = sel.limit.take() {
922 sel.fetch_first = Some(limit);
923 }
924 if let Some(top) = sel.top.take() {
925 sel.fetch_first = Some(*top);
926 }
927 } else {
928 if let Some(top) = sel.top.take() {
930 if sel.limit.is_none() {
931 sel.limit = Some(*top);
932 }
933 }
934 if let Some(fetch) = sel.fetch_first.take() {
935 if sel.limit.is_none() {
936 sel.limit = Some(fetch);
937 }
938 }
939 }
940}
941
942fn transform_quotes(expr: Expr, target: Dialect) -> Expr {
949 match expr {
950 Expr::Column {
951 table,
952 name,
953 quote_style,
954 table_quote_style,
955 } => {
956 let new_qs = if quote_style.is_quoted() {
957 QuoteStyle::for_dialect(target)
958 } else {
959 QuoteStyle::None
960 };
961 let new_tqs = if table_quote_style.is_quoted() {
962 QuoteStyle::for_dialect(target)
963 } else {
964 QuoteStyle::None
965 };
966 Expr::Column {
967 table,
968 name,
969 quote_style: new_qs,
970 table_quote_style: new_tqs,
971 }
972 }
973 Expr::BinaryOp { left, op, right } => Expr::BinaryOp {
975 left: Box::new(transform_quotes(*left, target)),
976 op,
977 right: Box::new(transform_quotes(*right, target)),
978 },
979 Expr::UnaryOp { op, expr } => Expr::UnaryOp {
980 op,
981 expr: Box::new(transform_quotes(*expr, target)),
982 },
983 Expr::Function {
984 name,
985 args,
986 distinct,
987 filter,
988 over,
989 } => Expr::Function {
990 name,
991 args: args
992 .into_iter()
993 .map(|a| transform_quotes(a, target))
994 .collect(),
995 distinct,
996 filter: filter.map(|f| Box::new(transform_quotes(*f, target))),
997 over,
998 },
999 Expr::TypedFunction { func, filter, over } => Expr::TypedFunction {
1000 func: func.transform_children(&|e| transform_quotes(e, target)),
1001 filter: filter.map(|f| Box::new(transform_quotes(*f, target))),
1002 over,
1003 },
1004 Expr::Nested(inner) => Expr::Nested(Box::new(transform_quotes(*inner, target))),
1005 Expr::Alias { expr, name } => Expr::Alias {
1006 expr: Box::new(transform_quotes(*expr, target)),
1007 name,
1008 },
1009 other => other,
1010 }
1011}
1012
1013fn transform_quotes_in_select(sel: &mut SelectStatement, target: Dialect) {
1015 for item in &mut sel.columns {
1017 if let SelectItem::Expr { expr, .. } = item {
1018 *expr = transform_quotes(expr.clone(), target);
1019 }
1020 }
1021 if let Some(wh) = &mut sel.where_clause {
1023 *wh = transform_quotes(wh.clone(), target);
1024 }
1025 for gb in &mut sel.group_by {
1027 *gb = transform_quotes(gb.clone(), target);
1028 }
1029 if let Some(having) = &mut sel.having {
1031 *having = transform_quotes(having.clone(), target);
1032 }
1033 for ob in &mut sel.order_by {
1035 ob.expr = transform_quotes(ob.expr.clone(), target);
1036 }
1037 if let Some(from) = &mut sel.from {
1039 transform_quotes_in_table_source(&mut from.source, target);
1040 }
1041 for join in &mut sel.joins {
1042 transform_quotes_in_table_source(&mut join.table, target);
1043 if let Some(on) = &mut join.on {
1044 *on = transform_quotes(on.clone(), target);
1045 }
1046 }
1047}
1048
1049fn transform_quotes_in_table_source(source: &mut TableSource, target: Dialect) {
1050 match source {
1051 TableSource::Table(tref) => {
1052 if tref.name_quote_style.is_quoted() {
1053 tref.name_quote_style = QuoteStyle::for_dialect(target);
1054 }
1055 }
1056 TableSource::Subquery { .. } => {}
1057 TableSource::TableFunction { .. } => {}
1058 TableSource::Lateral { source } => transform_quotes_in_table_source(source, target),
1059 TableSource::Pivot { source, .. } | TableSource::Unpivot { source, .. } => {
1060 transform_quotes_in_table_source(source, target);
1061 }
1062 TableSource::Unnest { .. } => {}
1063 }
1064}
1065
1066fn collect_concat_args(expr: &Expr, args: &mut Vec<Expr>) {
1072 match expr {
1073 Expr::BinaryOp {
1074 left,
1075 op: BinaryOperator::Concat,
1076 right,
1077 } => {
1078 collect_concat_args(left, args);
1079 collect_concat_args(right, args);
1080 }
1081 other => args.push(other.clone()),
1082 }
1083}
1084
1085fn try_transform_interval_arithmetic(
1092 left: &Expr,
1093 op: &BinaryOperator,
1094 right: &Expr,
1095) -> Option<Expr> {
1096 if let Expr::Interval { value, unit } = right {
1098 if let Some((count, unit_name)) = parse_interval_value(value, unit) {
1099 let final_count = if matches!(op, BinaryOperator::Minus) {
1100 -count
1101 } else {
1102 count
1103 };
1104 return Some(Expr::Function {
1105 name: "DATEADD".to_string(),
1106 args: vec![
1107 Expr::Column {
1109 table: None,
1110 name: unit_name,
1111 quote_style: QuoteStyle::None,
1112 table_quote_style: QuoteStyle::None,
1113 },
1114 Expr::Number(final_count.to_string()),
1115 left.clone(),
1116 ],
1117 distinct: false,
1118 filter: None,
1119 over: None,
1120 });
1121 }
1122 }
1123
1124 if let Expr::Interval { value, unit } = left {
1126 if matches!(op, BinaryOperator::Plus) {
1127 if let Some((count, unit_name)) = parse_interval_value(value, unit) {
1128 return Some(Expr::Function {
1129 name: "DATEADD".to_string(),
1130 args: vec![
1131 Expr::Column {
1132 table: None,
1133 name: unit_name,
1134 quote_style: QuoteStyle::None,
1135 table_quote_style: QuoteStyle::None,
1136 },
1137 Expr::Number(count.to_string()),
1138 right.clone(),
1139 ],
1140 distinct: false,
1141 filter: None,
1142 over: None,
1143 });
1144 }
1145 }
1146 }
1147
1148 None
1149}
1150
1151fn parse_interval_value(value: &Expr, unit: &Option<DateTimeField>) -> Option<(i64, String)> {
1153 if let Expr::StringLiteral(s) = value {
1155 let parts: Vec<&str> = s.trim().split_whitespace().collect();
1156 if parts.len() == 2 {
1157 let count: i64 = parts[0].parse().ok()?;
1158 let unit_name = normalize_interval_unit(parts[1])?;
1159 return Some((count, unit_name));
1160 }
1161 if parts.len() == 1 {
1162 let count: i64 = parts[0].parse().ok()?;
1164 if let Some(u) = unit {
1165 let unit_name = datetime_field_to_tsql(u)?;
1166 return Some((count, unit_name));
1167 }
1168 }
1169 }
1170
1171 if let Expr::Number(n) = value {
1173 let count: i64 = n.parse().ok()?;
1174 if let Some(u) = unit {
1175 let unit_name = datetime_field_to_tsql(u)?;
1176 return Some((count, unit_name));
1177 }
1178 }
1179
1180 None
1181}
1182
1183fn normalize_interval_unit(unit: &str) -> Option<String> {
1185 let lower = unit.to_lowercase();
1186 let normalized = lower.trim_end_matches('s');
1187 match normalized {
1188 "year" => Some("YEAR".to_string()),
1189 "month" => Some("MONTH".to_string()),
1190 "week" => Some("WEEK".to_string()),
1191 "day" => Some("DAY".to_string()),
1192 "hour" => Some("HOUR".to_string()),
1193 "minute" => Some("MINUTE".to_string()),
1194 "second" => Some("SECOND".to_string()),
1195 "millisecond" => Some("MILLISECOND".to_string()),
1196 "microsecond" => Some("MICROSECOND".to_string()),
1197 _ => None,
1198 }
1199}
1200
1201fn datetime_field_to_tsql(field: &DateTimeField) -> Option<String> {
1203 match field {
1204 DateTimeField::Year => Some("YEAR".to_string()),
1205 DateTimeField::Quarter => Some("QUARTER".to_string()),
1206 DateTimeField::Month => Some("MONTH".to_string()),
1207 DateTimeField::Week => Some("WEEK".to_string()),
1208 DateTimeField::Day => Some("DAY".to_string()),
1209 DateTimeField::Hour => Some("HOUR".to_string()),
1210 DateTimeField::Minute => Some("MINUTE".to_string()),
1211 DateTimeField::Second => Some("SECOND".to_string()),
1212 DateTimeField::Millisecond => Some("MILLISECOND".to_string()),
1213 DateTimeField::Microsecond => Some("MICROSECOND".to_string()),
1214 _ => None,
1215 }
1216}
1217
1218fn simplify_similar_to_pattern(pattern: &Expr) -> Expr {
1225 if let Expr::StringLiteral(s) = pattern {
1226 let simplified = s.replace('|', "%").replace('(', "").replace(')', "");
1227 Expr::StringLiteral(simplified)
1228 } else {
1229 pattern.clone()
1230 }
1231}