1use serde::{Deserialize, Serialize};
2
3use crate::ast::*;
4
5pub mod time;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
13pub enum Dialect {
14 Ansi,
17
18 Athena,
21 BigQuery,
23 ClickHouse,
25 Databricks,
27 DuckDb,
29 Hive,
31 Mysql,
33 Oracle,
35 Postgres,
37 Presto,
39 Redshift,
41 Snowflake,
43 Spark,
45 Sqlite,
47 StarRocks,
49 Trino,
51 Tsql,
53
54 Doris,
57 Dremio,
59 Drill,
61 Druid,
63 Exasol,
65 Fabric,
67 Materialize,
69 Prql,
71 RisingWave,
73 SingleStore,
75 Tableau,
77 Teradata,
79}
80
81impl std::fmt::Display for Dialect {
82 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83 match self {
84 Dialect::Ansi => write!(f, "ANSI SQL"),
85 Dialect::Athena => write!(f, "Athena"),
86 Dialect::BigQuery => write!(f, "BigQuery"),
87 Dialect::ClickHouse => write!(f, "ClickHouse"),
88 Dialect::Databricks => write!(f, "Databricks"),
89 Dialect::DuckDb => write!(f, "DuckDB"),
90 Dialect::Hive => write!(f, "Hive"),
91 Dialect::Mysql => write!(f, "MySQL"),
92 Dialect::Oracle => write!(f, "Oracle"),
93 Dialect::Postgres => write!(f, "PostgreSQL"),
94 Dialect::Presto => write!(f, "Presto"),
95 Dialect::Redshift => write!(f, "Redshift"),
96 Dialect::Snowflake => write!(f, "Snowflake"),
97 Dialect::Spark => write!(f, "Spark"),
98 Dialect::Sqlite => write!(f, "SQLite"),
99 Dialect::StarRocks => write!(f, "StarRocks"),
100 Dialect::Trino => write!(f, "Trino"),
101 Dialect::Tsql => write!(f, "T-SQL"),
102 Dialect::Doris => write!(f, "Doris"),
103 Dialect::Dremio => write!(f, "Dremio"),
104 Dialect::Drill => write!(f, "Drill"),
105 Dialect::Druid => write!(f, "Druid"),
106 Dialect::Exasol => write!(f, "Exasol"),
107 Dialect::Fabric => write!(f, "Fabric"),
108 Dialect::Materialize => write!(f, "Materialize"),
109 Dialect::Prql => write!(f, "PRQL"),
110 Dialect::RisingWave => write!(f, "RisingWave"),
111 Dialect::SingleStore => write!(f, "SingleStore"),
112 Dialect::Tableau => write!(f, "Tableau"),
113 Dialect::Teradata => write!(f, "Teradata"),
114 }
115 }
116}
117
118impl Dialect {
119 #[must_use]
121 pub fn support_level(&self) -> &'static str {
122 match self {
123 Dialect::Ansi
124 | Dialect::Athena
125 | Dialect::BigQuery
126 | Dialect::ClickHouse
127 | Dialect::Databricks
128 | Dialect::DuckDb
129 | Dialect::Hive
130 | Dialect::Mysql
131 | Dialect::Oracle
132 | Dialect::Postgres
133 | Dialect::Presto
134 | Dialect::Redshift
135 | Dialect::Snowflake
136 | Dialect::Spark
137 | Dialect::Sqlite
138 | Dialect::StarRocks
139 | Dialect::Trino
140 | Dialect::Tsql => "Official",
141
142 Dialect::Doris
143 | Dialect::Dremio
144 | Dialect::Drill
145 | Dialect::Druid
146 | Dialect::Exasol
147 | Dialect::Fabric
148 | Dialect::Materialize
149 | Dialect::Prql
150 | Dialect::RisingWave
151 | Dialect::SingleStore
152 | Dialect::Tableau
153 | Dialect::Teradata => "Community",
154 }
155 }
156
157 #[must_use]
159 pub fn all() -> &'static [Dialect] {
160 &[
161 Dialect::Ansi,
162 Dialect::Athena,
163 Dialect::BigQuery,
164 Dialect::ClickHouse,
165 Dialect::Databricks,
166 Dialect::Doris,
167 Dialect::Dremio,
168 Dialect::Drill,
169 Dialect::Druid,
170 Dialect::DuckDb,
171 Dialect::Exasol,
172 Dialect::Fabric,
173 Dialect::Hive,
174 Dialect::Materialize,
175 Dialect::Mysql,
176 Dialect::Oracle,
177 Dialect::Postgres,
178 Dialect::Presto,
179 Dialect::Prql,
180 Dialect::Redshift,
181 Dialect::RisingWave,
182 Dialect::SingleStore,
183 Dialect::Snowflake,
184 Dialect::Spark,
185 Dialect::Sqlite,
186 Dialect::StarRocks,
187 Dialect::Tableau,
188 Dialect::Teradata,
189 Dialect::Trino,
190 Dialect::Tsql,
191 ]
192 }
193
194 pub fn from_str(s: &str) -> Option<Dialect> {
196 match s.to_lowercase().as_str() {
197 "" | "ansi" => Some(Dialect::Ansi),
198 "athena" => Some(Dialect::Athena),
199 "bigquery" => Some(Dialect::BigQuery),
200 "clickhouse" => Some(Dialect::ClickHouse),
201 "databricks" => Some(Dialect::Databricks),
202 "doris" => Some(Dialect::Doris),
203 "dremio" => Some(Dialect::Dremio),
204 "drill" => Some(Dialect::Drill),
205 "druid" => Some(Dialect::Druid),
206 "duckdb" => Some(Dialect::DuckDb),
207 "exasol" => Some(Dialect::Exasol),
208 "fabric" => Some(Dialect::Fabric),
209 "hive" => Some(Dialect::Hive),
210 "materialize" => Some(Dialect::Materialize),
211 "mysql" => Some(Dialect::Mysql),
212 "oracle" => Some(Dialect::Oracle),
213 "postgres" | "postgresql" => Some(Dialect::Postgres),
214 "presto" => Some(Dialect::Presto),
215 "prql" => Some(Dialect::Prql),
216 "redshift" => Some(Dialect::Redshift),
217 "risingwave" => Some(Dialect::RisingWave),
218 "singlestore" => Some(Dialect::SingleStore),
219 "snowflake" => Some(Dialect::Snowflake),
220 "spark" => Some(Dialect::Spark),
221 "sqlite" => Some(Dialect::Sqlite),
222 "starrocks" => Some(Dialect::StarRocks),
223 "tableau" => Some(Dialect::Tableau),
224 "teradata" => Some(Dialect::Teradata),
225 "trino" => Some(Dialect::Trino),
226 "tsql" | "mssql" | "sqlserver" => Some(Dialect::Tsql),
227 _ => None,
228 }
229 }
230}
231
232fn is_mysql_family(d: Dialect) -> bool {
238 matches!(
239 d,
240 Dialect::Mysql | Dialect::Doris | Dialect::SingleStore | Dialect::StarRocks
241 )
242}
243
244fn is_postgres_family(d: Dialect) -> bool {
246 matches!(
247 d,
248 Dialect::Postgres | Dialect::Redshift | Dialect::Materialize | Dialect::RisingWave
249 )
250}
251
252fn is_presto_family(d: Dialect) -> bool {
254 matches!(d, Dialect::Presto | Dialect::Trino | Dialect::Athena)
255}
256
257fn is_hive_family(d: Dialect) -> bool {
259 matches!(d, Dialect::Hive | Dialect::Spark | Dialect::Databricks)
260}
261
262fn is_tsql_family(d: Dialect) -> bool {
264 matches!(d, Dialect::Tsql | Dialect::Fabric)
265}
266
267fn supports_ilike(d: Dialect) -> bool {
269 matches!(
270 d,
271 Dialect::Postgres
272 | Dialect::Redshift
273 | Dialect::Materialize
274 | Dialect::RisingWave
275 | Dialect::DuckDb
276 | Dialect::Snowflake
277 | Dialect::ClickHouse
278 | Dialect::Trino
279 | Dialect::Presto
280 | Dialect::Athena
281 | Dialect::Databricks
282 | Dialect::Spark
283 | Dialect::Hive
284 | Dialect::StarRocks
285 | Dialect::Exasol
286 | Dialect::Druid
287 | Dialect::Dremio
288 )
289}
290
291#[must_use]
302pub fn transform(statement: &Statement, from: Dialect, to: Dialect) -> Statement {
303 if from == to {
304 return statement.clone();
305 }
306 let mut stmt = statement.clone();
307 transform_statement(&mut stmt, to);
308 stmt
309}
310
311fn transform_statement(statement: &mut Statement, target: Dialect) {
312 match statement {
313 Statement::Select(sel) => {
314 transform_limit(sel, target);
316 transform_quotes_in_select(sel, target);
318
319 for item in &mut sel.columns {
320 if let SelectItem::Expr { expr, .. } = item {
321 *expr = transform_expr(expr.clone(), target);
322 }
323 }
324 if let Some(wh) = &mut sel.where_clause {
325 *wh = transform_expr(wh.clone(), target);
326 }
327 for gb in &mut sel.group_by {
328 *gb = transform_expr(gb.clone(), target);
329 }
330 if let Some(having) = &mut sel.having {
331 *having = transform_expr(having.clone(), target);
332 }
333 }
334 Statement::Insert(ins) => {
335 if let InsertSource::Values(rows) = &mut ins.source {
336 for row in rows {
337 for val in row {
338 *val = transform_expr(val.clone(), target);
339 }
340 }
341 }
342 }
343 Statement::Update(upd) => {
344 for (_, val) in &mut upd.assignments {
345 *val = transform_expr(val.clone(), target);
346 }
347 if let Some(wh) = &mut upd.where_clause {
348 *wh = transform_expr(wh.clone(), target);
349 }
350 }
351 Statement::CreateTable(ct) => {
353 for col in &mut ct.columns {
354 col.data_type = map_data_type(col.data_type.clone(), target);
355 if let Some(default) = &mut col.default {
356 *default = transform_expr(default.clone(), target);
357 }
358 }
359 for constraint in &mut ct.constraints {
361 if let TableConstraint::Check { expr, .. } = constraint {
362 *expr = transform_expr(expr.clone(), target);
363 }
364 }
365 if let Some(as_select) = &mut ct.as_select {
367 transform_statement(as_select, target);
368 }
369 }
370 Statement::AlterTable(alt) => {
372 for action in &mut alt.actions {
373 match action {
374 AlterTableAction::AddColumn(col) => {
375 col.data_type = map_data_type(col.data_type.clone(), target);
376 if let Some(default) = &mut col.default {
377 *default = transform_expr(default.clone(), target);
378 }
379 }
380 AlterTableAction::AlterColumnType { data_type, .. } => {
381 *data_type = map_data_type(data_type.clone(), target);
382 }
383 _ => {}
384 }
385 }
386 }
387 _ => {}
388 }
389}
390
391fn transform_expr(expr: Expr, target: Dialect) -> Expr {
393 match expr {
394 Expr::Function {
396 name,
397 args,
398 distinct,
399 filter,
400 over,
401 } => {
402 let new_name = map_function_name(&name, target);
403 let new_args: Vec<Expr> = args
404 .into_iter()
405 .map(|a| transform_expr(a, target))
406 .collect();
407 Expr::Function {
408 name: new_name,
409 args: new_args,
410 distinct,
411 filter: filter.map(|f| Box::new(transform_expr(*f, target))),
412 over,
413 }
414 }
415 Expr::TypedFunction { func, filter, over } => {
418 let transformed_func = transform_typed_function(func, target);
419 Expr::TypedFunction {
420 func: transformed_func,
421 filter: filter.map(|f| Box::new(transform_expr(*f, target))),
422 over,
423 }
424 }
425 Expr::ILike {
427 expr,
428 pattern,
429 negated,
430 escape,
431 } if !supports_ilike(target) => Expr::Like {
432 expr: Box::new(Expr::TypedFunction {
433 func: TypedFunction::Lower {
434 expr: Box::new(transform_expr(*expr, target)),
435 },
436 filter: None,
437 over: None,
438 }),
439 pattern: Box::new(Expr::TypedFunction {
440 func: TypedFunction::Lower {
441 expr: Box::new(transform_expr(*pattern, target)),
442 },
443 filter: None,
444 over: None,
445 }),
446 negated,
447 escape,
448 },
449 Expr::Cast { expr, data_type } => Expr::Cast {
451 expr: Box::new(transform_expr(*expr, target)),
452 data_type: map_data_type(data_type, target),
453 },
454 Expr::BinaryOp { left, op, right } => Expr::BinaryOp {
456 left: Box::new(transform_expr(*left, target)),
457 op,
458 right: Box::new(transform_expr(*right, target)),
459 },
460 Expr::UnaryOp { op, expr } => Expr::UnaryOp {
461 op,
462 expr: Box::new(transform_expr(*expr, target)),
463 },
464 Expr::Nested(inner) => Expr::Nested(Box::new(transform_expr(*inner, target))),
465 Expr::Column {
467 table,
468 name,
469 quote_style,
470 table_quote_style,
471 } => {
472 let new_qs = if quote_style.is_quoted() {
473 QuoteStyle::for_dialect(target)
474 } else {
475 QuoteStyle::None
476 };
477 let new_tqs = if table_quote_style.is_quoted() {
478 QuoteStyle::for_dialect(target)
479 } else {
480 QuoteStyle::None
481 };
482 Expr::Column {
483 table,
484 name,
485 quote_style: new_qs,
486 table_quote_style: new_tqs,
487 }
488 }
489 other => other,
491 }
492}
493
494fn transform_typed_function(func: TypedFunction, target: Dialect) -> TypedFunction {
503 match func {
504 TypedFunction::TimeToStr { expr, format } => {
505 let transformed_expr = Box::new(transform_expr(*expr, target));
506 let transformed_format = transform_format_expr(*format, target);
507 TypedFunction::TimeToStr {
508 expr: transformed_expr,
509 format: Box::new(transformed_format),
510 }
511 }
512 TypedFunction::StrToTime { expr, format } => {
513 let transformed_expr = Box::new(transform_expr(*expr, target));
514 let transformed_format = transform_format_expr(*format, target);
515 TypedFunction::StrToTime {
516 expr: transformed_expr,
517 format: Box::new(transformed_format),
518 }
519 }
520 other => other.transform_children(&|e| transform_expr(e, target)),
522 }
523}
524
525fn transform_format_expr(expr: Expr, target: Dialect) -> Expr {
530 match &expr {
534 Expr::StringLiteral(s) => {
535 let detected_source = detect_format_style(s);
536 let target_style = time::TimeFormatStyle::for_dialect(target);
537
538 if detected_source != target_style {
540 let converted = time::format_time(s, detected_source, target_style);
541 Expr::StringLiteral(converted)
542 } else {
543 expr
544 }
545 }
546 _ => transform_expr(expr, target),
547 }
548}
549
550fn detect_format_style(format_str: &str) -> time::TimeFormatStyle {
552 if format_str.contains('%') {
554 if format_str.contains("%i") {
556 time::TimeFormatStyle::Mysql
558 } else {
559 time::TimeFormatStyle::Strftime
561 }
562 } else if format_str.contains("YYYY") || format_str.contains("yyyy") {
563 if format_str.contains("HH24") || format_str.contains("MI") || format_str.contains("SS") {
565 time::TimeFormatStyle::Postgres
567 } else if format_str.contains("mm") && format_str.contains("ss") {
568 time::TimeFormatStyle::Java
570 } else if format_str.contains("FF") {
571 time::TimeFormatStyle::Snowflake
573 } else if format_str.contains("MM") && format_str.contains("DD") {
574 time::TimeFormatStyle::Postgres
576 } else {
577 time::TimeFormatStyle::Java
579 }
580 } else {
581 time::TimeFormatStyle::Strftime
583 }
584}
585
586fn map_function_name(name: &str, target: Dialect) -> String {
592 let upper = name.to_uppercase();
593 match upper.as_str() {
594 "NOW" => {
596 if is_tsql_family(target) {
597 "GETDATE".to_string()
598 } else if matches!(
599 target,
600 Dialect::Ansi
601 | Dialect::BigQuery
602 | Dialect::Snowflake
603 | Dialect::Oracle
604 | Dialect::ClickHouse
605 | Dialect::Exasol
606 | Dialect::Teradata
607 | Dialect::Druid
608 | Dialect::Dremio
609 | Dialect::Tableau
610 ) || is_presto_family(target)
611 || is_hive_family(target)
612 {
613 "CURRENT_TIMESTAMP".to_string()
614 } else {
615 name.to_string()
617 }
618 }
619 "GETDATE" => {
620 if is_tsql_family(target) {
621 name.to_string()
622 } else if is_postgres_family(target)
623 || matches!(target, Dialect::Mysql | Dialect::DuckDb | Dialect::Sqlite)
624 {
625 "NOW".to_string()
626 } else {
627 "CURRENT_TIMESTAMP".to_string()
628 }
629 }
630
631 "LEN" => {
633 if is_tsql_family(target) || matches!(target, Dialect::BigQuery | Dialect::Snowflake) {
634 name.to_string()
635 } else {
636 "LENGTH".to_string()
637 }
638 }
639 "LENGTH" if is_tsql_family(target) => "LEN".to_string(),
640
641 "SUBSTR" => {
643 if is_mysql_family(target)
644 || matches!(target, Dialect::Sqlite | Dialect::Oracle)
645 || is_hive_family(target)
646 {
647 "SUBSTR".to_string()
648 } else {
649 "SUBSTRING".to_string()
650 }
651 }
652 "SUBSTRING" => {
653 if is_mysql_family(target)
654 || matches!(target, Dialect::Sqlite | Dialect::Oracle)
655 || is_hive_family(target)
656 {
657 "SUBSTR".to_string()
658 } else {
659 name.to_string()
660 }
661 }
662
663 "IFNULL" => {
665 if is_tsql_family(target) {
666 "ISNULL".to_string()
667 } else if is_mysql_family(target) || matches!(target, Dialect::Sqlite) {
668 name.to_string()
670 } else {
671 "COALESCE".to_string()
672 }
673 }
674 "ISNULL" => {
675 if is_tsql_family(target) {
676 name.to_string()
677 } else if is_mysql_family(target) || matches!(target, Dialect::Sqlite) {
678 "IFNULL".to_string()
679 } else {
680 "COALESCE".to_string()
681 }
682 }
683
684 "NVL" => {
686 if matches!(target, Dialect::Oracle | Dialect::Snowflake) {
687 name.to_string()
688 } else if is_mysql_family(target) || matches!(target, Dialect::Sqlite) {
689 "IFNULL".to_string()
690 } else if is_tsql_family(target) {
691 "ISNULL".to_string()
692 } else {
693 "COALESCE".to_string()
694 }
695 }
696
697 "RANDOM" => {
699 if matches!(
700 target,
701 Dialect::Postgres | Dialect::Sqlite | Dialect::DuckDb
702 ) {
703 name.to_string()
704 } else {
705 "RAND".to_string()
706 }
707 }
708 "RAND" => {
709 if matches!(
710 target,
711 Dialect::Postgres | Dialect::Sqlite | Dialect::DuckDb
712 ) {
713 "RANDOM".to_string()
714 } else {
715 name.to_string()
716 }
717 }
718
719 _ => name.to_string(),
721 }
722}
723
724fn map_data_type(dt: DataType, target: Dialect) -> DataType {
730 match (dt, target) {
731 (DataType::Text, t) if matches!(t, Dialect::BigQuery) || is_hive_family(t) => {
734 DataType::String
735 }
736 (DataType::String, t)
738 if is_postgres_family(t) || is_mysql_family(t) || matches!(t, Dialect::Sqlite) =>
739 {
740 DataType::Text
741 }
742
743 (DataType::Int, Dialect::BigQuery) => DataType::BigInt,
745
746 (DataType::Float, Dialect::BigQuery) => DataType::Double,
748
749 (DataType::Bytea, t)
751 if is_mysql_family(t)
752 || matches!(t, Dialect::Sqlite | Dialect::Oracle)
753 || is_hive_family(t) =>
754 {
755 DataType::Blob
756 }
757 (DataType::Blob, t) if is_postgres_family(t) => DataType::Bytea,
758
759 (DataType::Boolean, Dialect::Mysql) => DataType::Boolean,
761
762 (dt, _) => dt,
764 }
765}
766
767fn transform_limit(sel: &mut SelectStatement, target: Dialect) {
777 if is_tsql_family(target) {
778 if let Some(limit) = sel.limit.take() {
780 if sel.offset.is_none() {
781 sel.top = Some(Box::new(limit));
782 } else {
783 sel.fetch_first = Some(limit);
785 }
786 }
787 if sel.offset.is_none() {
789 if let Some(fetch) = sel.fetch_first.take() {
790 sel.top = Some(Box::new(fetch));
791 }
792 }
793 } else if matches!(target, Dialect::Oracle) {
794 if let Some(limit) = sel.limit.take() {
796 sel.fetch_first = Some(limit);
797 }
798 if let Some(top) = sel.top.take() {
799 sel.fetch_first = Some(*top);
800 }
801 } else {
802 if let Some(top) = sel.top.take() {
804 if sel.limit.is_none() {
805 sel.limit = Some(*top);
806 }
807 }
808 if let Some(fetch) = sel.fetch_first.take() {
809 if sel.limit.is_none() {
810 sel.limit = Some(fetch);
811 }
812 }
813 }
814}
815
816fn transform_quotes(expr: Expr, target: Dialect) -> Expr {
823 match expr {
824 Expr::Column {
825 table,
826 name,
827 quote_style,
828 table_quote_style,
829 } => {
830 let new_qs = if quote_style.is_quoted() {
831 QuoteStyle::for_dialect(target)
832 } else {
833 QuoteStyle::None
834 };
835 let new_tqs = if table_quote_style.is_quoted() {
836 QuoteStyle::for_dialect(target)
837 } else {
838 QuoteStyle::None
839 };
840 Expr::Column {
841 table,
842 name,
843 quote_style: new_qs,
844 table_quote_style: new_tqs,
845 }
846 }
847 Expr::BinaryOp { left, op, right } => Expr::BinaryOp {
849 left: Box::new(transform_quotes(*left, target)),
850 op,
851 right: Box::new(transform_quotes(*right, target)),
852 },
853 Expr::UnaryOp { op, expr } => Expr::UnaryOp {
854 op,
855 expr: Box::new(transform_quotes(*expr, target)),
856 },
857 Expr::Function {
858 name,
859 args,
860 distinct,
861 filter,
862 over,
863 } => Expr::Function {
864 name,
865 args: args
866 .into_iter()
867 .map(|a| transform_quotes(a, target))
868 .collect(),
869 distinct,
870 filter: filter.map(|f| Box::new(transform_quotes(*f, target))),
871 over,
872 },
873 Expr::TypedFunction { func, filter, over } => Expr::TypedFunction {
874 func: func.transform_children(&|e| transform_quotes(e, target)),
875 filter: filter.map(|f| Box::new(transform_quotes(*f, target))),
876 over,
877 },
878 Expr::Nested(inner) => Expr::Nested(Box::new(transform_quotes(*inner, target))),
879 Expr::Alias { expr, name } => Expr::Alias {
880 expr: Box::new(transform_quotes(*expr, target)),
881 name,
882 },
883 other => other,
884 }
885}
886
887fn transform_quotes_in_select(sel: &mut SelectStatement, target: Dialect) {
889 for item in &mut sel.columns {
891 if let SelectItem::Expr { expr, .. } = item {
892 *expr = transform_quotes(expr.clone(), target);
893 }
894 }
895 if let Some(wh) = &mut sel.where_clause {
897 *wh = transform_quotes(wh.clone(), target);
898 }
899 for gb in &mut sel.group_by {
901 *gb = transform_quotes(gb.clone(), target);
902 }
903 if let Some(having) = &mut sel.having {
905 *having = transform_quotes(having.clone(), target);
906 }
907 for ob in &mut sel.order_by {
909 ob.expr = transform_quotes(ob.expr.clone(), target);
910 }
911 if let Some(from) = &mut sel.from {
913 transform_quotes_in_table_source(&mut from.source, target);
914 }
915 for join in &mut sel.joins {
916 transform_quotes_in_table_source(&mut join.table, target);
917 if let Some(on) = &mut join.on {
918 *on = transform_quotes(on.clone(), target);
919 }
920 }
921}
922
923fn transform_quotes_in_table_source(source: &mut TableSource, target: Dialect) {
924 match source {
925 TableSource::Table(tref) => {
926 if tref.name_quote_style.is_quoted() {
927 tref.name_quote_style = QuoteStyle::for_dialect(target);
928 }
929 }
930 TableSource::Subquery { .. } => {}
931 TableSource::TableFunction { .. } => {}
932 TableSource::Lateral { source } => transform_quotes_in_table_source(source, target),
933 TableSource::Pivot { source, .. } | TableSource::Unpivot { source, .. } => {
934 transform_quotes_in_table_source(source, target);
935 }
936 TableSource::Unnest { .. } => {}
937 }
938}