1use crate::dialects::DialectType;
9use crate::expressions::{
10 Alias, BinaryOp, Column, DotAccess, Expression, Identifier, Join, JoinKind, LateralView,
11 Literal, Over, Paren, Select, TableRef, VarArgFunc, With,
12};
13use crate::resolver::{Resolver, ResolverError};
14use crate::schema::{normalize_name, Schema};
15use crate::scope::{
16 build_scope_with_ctes, selected_reference_scope, traverse_scope, Scope, SourceInfo, SourceKind,
17};
18use std::cell::RefCell;
19use std::collections::{HashMap, HashSet};
20use thiserror::Error;
21
22#[derive(Debug, Error, Clone)]
24pub enum QualifyColumnsError {
25 #[error("Unknown table: {0}")]
26 UnknownTable(String),
27
28 #[error("Unknown column: {0}")]
29 UnknownColumn(String),
30
31 #[error("Ambiguous column: {0}")]
32 AmbiguousColumn(String),
33
34 #[error("Cannot automatically join: {0}")]
35 CannotAutoJoin(String),
36
37 #[error("Unknown output column: {0}")]
38 UnknownOutputColumn(String),
39
40 #[error("Column could not be resolved: {column}{for_table}")]
41 ColumnNotResolved { column: String, for_table: String },
42
43 #[error("Resolver error: {0}")]
44 ResolverError(#[from] ResolverError),
45}
46
47pub type QualifyColumnsResult<T> = Result<T, QualifyColumnsError>;
49
50#[derive(Debug, Clone, Default)]
52pub struct QualifyColumnsOptions {
53 pub expand_alias_refs: bool,
55 pub expand_stars: bool,
57 pub infer_schema: Option<bool>,
59 pub allow_partial_qualification: bool,
61 pub dialect: Option<DialectType>,
63}
64
65impl QualifyColumnsOptions {
66 pub fn new() -> Self {
68 Self {
69 expand_alias_refs: true,
70 expand_stars: true,
71 infer_schema: None,
72 allow_partial_qualification: false,
73 dialect: None,
74 }
75 }
76
77 pub fn with_expand_alias_refs(mut self, expand: bool) -> Self {
79 self.expand_alias_refs = expand;
80 self
81 }
82
83 pub fn with_expand_stars(mut self, expand: bool) -> Self {
85 self.expand_stars = expand;
86 self
87 }
88
89 pub fn with_dialect(mut self, dialect: DialectType) -> Self {
91 self.dialect = Some(dialect);
92 self
93 }
94
95 pub fn with_allow_partial(mut self, allow: bool) -> Self {
97 self.allow_partial_qualification = allow;
98 self
99 }
100}
101
102pub fn qualify_columns(
117 expression: Expression,
118 schema: &dyn Schema,
119 options: &QualifyColumnsOptions,
120) -> QualifyColumnsResult<Expression> {
121 qualify_query(expression, schema, options, &HashMap::new(), &[])
122}
123
124fn transform_in_scope<E>(
127 expression: Expression,
128 mut transform: impl FnMut(Expression) -> Result<Expression, E>,
129) -> Result<Expression, E> {
130 enum Task {
131 Visit(Expression),
132 Finish(Expression, usize),
133 }
134 let mut tasks = vec![Task::Visit(expression)];
135 let mut results = Vec::new();
136 while let Some(task) = tasks.pop() {
137 match task {
138 Task::Visit(mut expression) => {
139 if matches!(
140 expression,
141 Expression::Select(_)
142 | Expression::Union(_)
143 | Expression::Intersect(_)
144 | Expression::Except(_)
145 ) {
146 results.push(transform(expression)?);
147 continue;
148 }
149 let mut children = Vec::new();
150 crate::ast_children::for_each_child_mut(&mut expression, |child| {
151 children.push(std::mem::replace(
152 child,
153 Expression::Null(crate::expressions::Null),
154 ));
155 });
156 tasks.push(Task::Finish(expression, children.len()));
157 tasks.extend(children.into_iter().rev().map(Task::Visit));
158 }
159 Task::Finish(mut expression, count) => {
160 let mut children = results.split_off(results.len() - count).into_iter();
161 crate::ast_children::for_each_child_mut(&mut expression, |child| {
162 *child = children.next().expect("scope transform child");
163 });
164 results.push(transform(expression)?);
165 }
166 }
167 }
168 Ok(results.pop().expect("scope transform result"))
169}
170
171fn qualify_nested_queries(
172 expression: Expression,
173 schema: &dyn Schema,
174 options: &QualifyColumnsOptions,
175 ctes: &HashMap<String, SourceInfo>,
176 outer: &[Scope],
177) -> QualifyColumnsResult<Expression> {
178 transform_in_scope(expression, |node| match node {
179 Expression::Select(_)
180 | Expression::Union(_)
181 | Expression::Intersect(_)
182 | Expression::Except(_) => qualify_query(node, schema, options, ctes, outer),
183 other => Ok(other),
184 })
185}
186
187fn qualify_query(
188 expression: Expression,
189 schema: &dyn Schema,
190 options: &QualifyColumnsOptions,
191 inherited_ctes: &HashMap<String, SourceInfo>,
192 outer: &[Scope],
193) -> QualifyColumnsResult<Expression> {
194 #[cfg(feature = "stacker")]
195 {
196 let red_zone = if cfg!(debug_assertions) {
197 4 * 1024 * 1024
198 } else {
199 1024 * 1024
200 };
201 stacker::maybe_grow(red_zone, 8 * 1024 * 1024, || {
202 qualify_query_inner(expression, schema, options, inherited_ctes, outer)
203 })
204 }
205 #[cfg(not(feature = "stacker"))]
206 qualify_query_inner(expression, schema, options, inherited_ctes, outer)
207}
208
209fn is_lateral_source(expression: &Expression) -> bool {
210 match expression {
211 Expression::Lateral(_) => true,
212 Expression::Subquery(subquery) => subquery.lateral,
213 Expression::Alias(alias) => is_lateral_source(&alias.this),
214 Expression::Paren(paren) => is_lateral_source(&paren.this),
215 _ => false,
216 }
217}
218
219fn qualify_query_inner(
220 mut expression: Expression,
221 schema: &dyn Schema,
222 options: &QualifyColumnsOptions,
223 inherited_ctes: &HashMap<String, SourceInfo>,
224 outer: &[Scope],
225) -> QualifyColumnsResult<Expression> {
226 let infer_schema = options.infer_schema.unwrap_or(schema.is_empty());
227 let dialect = options.dialect.or_else(|| schema.dialect());
228 let mut ctes = inherited_ctes.clone();
229 let with = match &mut expression {
230 Expression::Select(query) => query.with.take(),
231 Expression::Union(query) => query.with.take(),
232 Expression::Intersect(query) => query.with.take(),
233 Expression::Except(query) => query.with.take(),
234 _ => return qualify_nested_queries(expression, schema, options, inherited_ctes, outer),
235 };
236 let mut with = with;
237 if let Some(with) = &mut with {
238 pushdown_cte_alias_columns_with(with);
239 for cte in &mut with.ctes {
240 let name = cte.alias.name.clone();
241 if with.recursive {
242 ctes.retain(|existing, _| {
243 normalize_name(existing, dialect, true, true)
244 != normalize_name(&name, dialect, true, true)
245 });
246 ctes.insert(
247 name.clone(),
248 SourceInfo::new(
249 Expression::Cte(Box::new(cte.clone())),
250 true,
251 SourceKind::Cte,
252 ),
253 );
254 }
255 cte.this = qualify_nested_queries(cte.this.clone(), schema, options, &ctes, outer)?;
258 ctes.retain(|existing, _| {
259 normalize_name(existing, dialect, true, true)
260 != normalize_name(&name, dialect, true, true)
261 });
262 ctes.insert(
263 name,
264 SourceInfo::new(
265 Expression::Cte(Box::new(cte.clone())),
266 true,
267 SourceKind::Cte,
268 ),
269 );
270 }
271 }
272
273 if let Expression::Select(select) = &mut expression {
274 let joins = std::mem::take(&mut select.joins);
277 if let Some(from) = &mut select.from {
278 let sources = std::mem::take(&mut from.expressions);
279 for source in sources {
280 let mut visible_outer = outer.to_vec();
281 if is_lateral_source(&source) {
282 visible_outer.push(selected_reference_scope(&build_scope_with_ctes(
283 &Expression::Select(select.clone()),
284 &ctes,
285 )));
286 }
287 let qualified =
288 qualify_nested_queries(source, schema, options, &ctes, &visible_outer)?;
289 select.from.as_mut().unwrap().expressions.push(qualified);
290 }
291 }
292 for mut join in joins {
293 let mut visible_outer = outer.to_vec();
294 if is_lateral_source(&join.this)
295 || matches!(
296 join.kind,
297 JoinKind::Lateral
298 | JoinKind::LeftLateral
299 | JoinKind::CrossApply
300 | JoinKind::OuterApply
301 )
302 {
303 visible_outer.push(selected_reference_scope(&build_scope_with_ctes(
304 &Expression::Select(select.clone()),
305 &ctes,
306 )));
307 }
308 join.this = qualify_nested_queries(join.this, schema, options, &ctes, &visible_outer)?;
309 select.joins.push(join);
310 }
311 }
312
313 if matches!(expression, Expression::Select(_)) {
314 let scope = selected_reference_scope(&build_scope_with_ctes(&expression, &ctes));
315 let mut nested_outer = outer.to_vec();
316 nested_outer.push(scope.clone());
317 let Expression::Select(select) = &mut expression else {
320 unreachable!()
321 };
322 let from = select.from.take();
323 let mut joins = std::mem::take(&mut select.joins);
324 let mut error = None;
325 crate::ast_children::for_each_child_mut(&mut expression, |child| {
326 if error.is_none() {
327 match qualify_nested_queries(child.clone(), schema, options, &ctes, &nested_outer) {
328 Ok(qualified) => *child = qualified,
329 Err(err) => error = Some(err),
330 }
331 }
332 });
333 if let Some(error) = error {
334 return Err(error);
335 }
336 for join in &mut joins {
337 if let Some(on) = &mut join.on {
338 *on = qualify_nested_queries(on.clone(), schema, options, &ctes, &nested_outer)?;
339 }
340 }
341 let Expression::Select(select) = &mut expression else {
342 unreachable!()
343 };
344 select.from = from;
345 select.joins = joins;
346 let mut resolver = Resolver::new(&scope, schema, infer_schema).with_outer_scopes(outer);
347 let column_tables = expand_using(select, &scope, &mut resolver)?;
348 if options.expand_alias_refs {
349 expand_alias_refs(select, &mut resolver, dialect)?;
350 }
351 normalize_dotted_columns_in_scope(select, &scope, &mut resolver)?;
352 qualify_columns_in_scope(
353 select,
354 &scope,
355 &mut resolver,
356 options.allow_partial_qualification,
357 )?;
358 if options.expand_stars {
359 expand_stars(select, &scope, &mut resolver, &column_tables)?;
360 }
361 qualify_outputs_select(select)?;
362 expand_group_by(select, dialect)?;
363 select.with = with;
364 } else {
365 let (left, right) = match &mut expression {
366 Expression::Union(query) => (&mut query.left, &mut query.right),
367 Expression::Intersect(query) => (&mut query.left, &mut query.right),
368 Expression::Except(query) => (&mut query.left, &mut query.right),
369 _ => unreachable!(),
370 };
371 *left = qualify_nested_queries(left.clone(), schema, options, &ctes, outer)?;
372 *right = qualify_nested_queries(right.clone(), schema, options, &ctes, outer)?;
373 match &mut expression {
374 Expression::Union(query) => query.with = with,
375 Expression::Intersect(query) => query.with = with,
376 Expression::Except(query) => query.with = with,
377 _ => unreachable!(),
378 }
379 }
380 Ok(expression)
381}
382
383pub fn validate_qualify_columns(expression: &Expression) -> QualifyColumnsResult<()> {
388 let mut all_unqualified = Vec::new();
389
390 for scope in traverse_scope(expression) {
391 if let Expression::Select(_) = &scope.expression {
392 let unqualified = get_unqualified_columns(&scope);
394
395 let external = get_external_columns(&scope);
397 if !external.is_empty() && !is_correlated_subquery(&scope) {
398 let first = &external[0];
399 let for_table = if first.table.is_some() {
400 format!(" for table: '{}'", first.table.as_ref().unwrap())
401 } else {
402 String::new()
403 };
404 return Err(QualifyColumnsError::ColumnNotResolved {
405 column: first.name.clone(),
406 for_table,
407 });
408 }
409
410 all_unqualified.extend(unqualified);
411 }
412 }
413
414 if !all_unqualified.is_empty() {
415 let first = &all_unqualified[0];
416 return Err(QualifyColumnsError::AmbiguousColumn(first.name.clone()));
417 }
418
419 Ok(())
420}
421
422fn get_source_name(expr: &Expression) -> Option<String> {
424 match expr {
425 Expression::Table(t) => Some(
426 t.alias
427 .as_ref()
428 .map(|a| a.name.clone())
429 .unwrap_or_else(|| t.name.name.clone()),
430 ),
431 Expression::Subquery(sq) => sq.alias.as_ref().map(|a| a.name.clone()),
432 Expression::Pivot(pivot) => Some(pivot_source_name(
433 &pivot.this,
434 pivot.alias.as_ref().map(|alias| alias.name.as_str()),
435 )),
436 Expression::Unpivot(unpivot) => Some(pivot_source_name(
437 &unpivot.this,
438 unpivot.alias.as_ref().map(|alias| alias.name.as_str()),
439 )),
440 _ => None,
441 }
442}
443
444fn pivot_source_name(source: &Expression, explicit_alias: Option<&str>) -> String {
445 if let Some(alias) = explicit_alias {
446 return alias.to_string();
447 }
448
449 match source {
450 Expression::Table(table) => table
451 .alias
452 .as_ref()
453 .map(|alias| alias.name.clone())
454 .unwrap_or_else(|| table.name.name.clone()),
455 Expression::Subquery(subquery) => subquery
456 .alias
457 .as_ref()
458 .map(|alias| alias.name.clone())
459 .unwrap_or_else(|| "_0".to_string()),
460 Expression::Paren(paren) => pivot_source_name(&paren.this, explicit_alias),
461 _ => "_0".to_string(),
462 }
463}
464
465fn get_ordered_source_names(select: &Select) -> Vec<String> {
468 let mut ordered = Vec::new();
469 if let Some(from) = &select.from {
470 for expr in &from.expressions {
471 if let Some(name) = get_source_name(expr) {
472 ordered.push(name);
473 }
474 }
475 }
476 for join in &select.joins {
477 if is_semi_or_anti_join_kind(join.kind) {
478 continue;
479 }
480 if let Some(name) = get_source_name(&join.this) {
481 ordered.push(name);
482 }
483 }
484 ordered
485}
486
487fn is_semi_or_anti_join_kind(kind: JoinKind) -> bool {
488 matches!(
489 kind,
490 JoinKind::Semi
491 | JoinKind::Anti
492 | JoinKind::LeftSemi
493 | JoinKind::LeftAnti
494 | JoinKind::RightSemi
495 | JoinKind::RightAnti
496 )
497}
498
499fn make_coalesce(column_name: &str, tables: &[String]) -> Expression {
501 let args: Vec<Expression> = tables
502 .iter()
503 .map(|t| Expression::qualified_column(t.as_str(), column_name))
504 .collect();
505 Expression::Coalesce(Box::new(VarArgFunc {
506 expressions: args,
507 original_name: None,
508 inferred_type: None,
509 }))
510}
511
512fn expand_using(
518 select: &mut Select,
519 _scope: &Scope,
520 resolver: &mut Resolver,
521) -> QualifyColumnsResult<HashMap<String, Vec<String>>> {
522 let mut columns: HashMap<String, String> = HashMap::new();
525 let mut column_order: Vec<String> = Vec::new();
528
529 let mut column_tables: HashMap<String, Vec<String>> = HashMap::new();
532
533 let join_names: HashSet<String> = select
535 .joins
536 .iter()
537 .filter_map(|j| get_source_name(&j.this))
538 .collect();
539
540 let all_ordered = get_ordered_source_names(select);
541 let mut ordered: Vec<String> = all_ordered
542 .iter()
543 .filter(|name| !join_names.contains(name.as_str()))
544 .cloned()
545 .collect();
546 let mut accumulated_schema_known = true;
547
548 if join_names.is_empty() {
549 return Ok(column_tables);
550 }
551
552 fn update_source_columns(
554 source_name: &str,
555 columns: &mut HashMap<String, String>,
556 column_order: &mut Vec<String>,
557 resolver: &mut Resolver,
558 ) -> bool {
559 let Ok(source_cols) = resolver.get_source_columns(source_name) else {
560 return false;
561 };
562 let schema_known = !source_cols.is_empty()
563 && !source_cols
564 .iter()
565 .any(|column| column == "*" || column.is_empty());
566 for col_name in source_cols {
567 let normalized = normalize_column_name(&col_name, resolver.dialect);
568 if let std::collections::hash_map::Entry::Vacant(entry) = columns.entry(normalized) {
569 entry.insert(source_name.to_string());
570 column_order.push(col_name);
571 }
572 }
573 schema_known
574 }
575
576 for source_name in &ordered {
578 accumulated_schema_known &=
579 update_source_columns(source_name, &mut columns, &mut column_order, resolver);
580 }
581
582 for i in 0..select.joins.len() {
583 let source_table = ordered.last().cloned().unwrap_or_default();
585 if !source_table.is_empty() {
586 accumulated_schema_known &=
587 update_source_columns(&source_table, &mut columns, &mut column_order, resolver);
588 }
589
590 let join_table = get_source_name(&select.joins[i].this).unwrap_or_default();
592 ordered.push(join_table.clone());
593
594 let join_columns: Vec<String> =
595 resolver.get_source_columns(&join_table).unwrap_or_default();
596 let star = normalize_column_name("*", resolver.dialect);
597 let right_schema_known = !join_columns.is_empty()
598 && !join_columns
599 .iter()
600 .any(|column| normalize_column_name(column, resolver.dialect) == star);
601
602 let join_kind = select.joins[i].kind;
607 if select.joins[i].using.is_empty()
608 && matches!(
609 join_kind,
610 JoinKind::Natural
611 | JoinKind::NaturalLeft
612 | JoinKind::NaturalRight
613 | JoinKind::NaturalFull
614 )
615 {
616 let left_schema_known =
617 accumulated_schema_known && !columns.is_empty() && !columns.contains_key(&star);
618
619 if left_schema_known && right_schema_known {
620 let right_columns: HashSet<String> = join_columns
621 .iter()
622 .map(|column| normalize_column_name(column, resolver.dialect))
623 .collect();
624 let implicit_using: Vec<Identifier> = column_order
625 .iter()
626 .filter(|column| {
627 right_columns.contains(&normalize_column_name(column, resolver.dialect))
628 })
629 .map(|column| Identifier::new(column))
630 .collect();
631
632 if !implicit_using.is_empty() {
633 select.joins[i].using = implicit_using;
634 select.joins[i].kind = match join_kind {
635 JoinKind::Natural => JoinKind::Inner,
636 JoinKind::NaturalLeft => JoinKind::Left,
637 JoinKind::NaturalRight => JoinKind::Right,
638 JoinKind::NaturalFull => JoinKind::Full,
639 _ => unreachable!("checked NATURAL join kind above"),
640 };
641 }
642 }
643 }
644
645 if select.joins[i].using.is_empty() {
648 accumulated_schema_known &= right_schema_known;
649 continue;
650 }
651
652 let using_identifiers: Vec<String> = select.joins[i]
653 .using
654 .iter()
655 .map(|id| id.name.clone())
656 .collect();
657
658 let using_count = using_identifiers.len();
659 let is_semi_or_anti = matches!(
660 select.joins[i].kind,
661 crate::expressions::JoinKind::Semi
662 | crate::expressions::JoinKind::Anti
663 | crate::expressions::JoinKind::LeftSemi
664 | crate::expressions::JoinKind::LeftAnti
665 | crate::expressions::JoinKind::RightSemi
666 | crate::expressions::JoinKind::RightAnti
667 );
668
669 let mut conditions: Vec<Expression> = Vec::new();
670
671 for identifier in &using_identifiers {
672 let normalized_identifier = normalize_column_name(identifier, resolver.dialect);
673 let table = columns
674 .get(&normalized_identifier)
675 .cloned()
676 .unwrap_or_else(|| source_table.clone());
677
678 let lhs = if i == 0 || using_count == 1 {
680 Expression::qualified_column(table.as_str(), identifier.as_str())
682 } else {
683 let coalesce_cols: Vec<String> = ordered[..ordered.len() - 1]
686 .iter()
687 .filter(|t| {
688 resolver
689 .get_source_columns(t)
690 .unwrap_or_default()
691 .iter()
692 .any(|column| {
693 normalize_column_name(column, resolver.dialect)
694 == normalized_identifier
695 })
696 })
697 .cloned()
698 .collect();
699
700 if coalesce_cols.len() > 1 {
701 make_coalesce(identifier, &coalesce_cols)
702 } else {
703 Expression::qualified_column(table.as_str(), identifier.as_str())
704 }
705 };
706
707 let rhs = Expression::qualified_column(join_table.as_str(), identifier.as_str());
709
710 conditions.push(Expression::Eq(Box::new(BinaryOp::new(lhs, rhs))));
711
712 if !is_semi_or_anti {
714 let tables = column_tables
715 .entry(normalized_identifier)
716 .or_insert_with(Vec::new);
717 if !tables.contains(&table) {
718 tables.push(table.clone());
719 }
720 if !tables.contains(&join_table) {
721 tables.push(join_table.clone());
722 }
723 }
724 }
725
726 let on_condition = conditions
728 .into_iter()
729 .reduce(|acc, cond| Expression::And(Box::new(BinaryOp::new(acc, cond))))
730 .expect("at least one USING column");
731
732 select.joins[i].on = Some(on_condition);
734 select.joins[i].using = vec![];
735 accumulated_schema_known &= right_schema_known;
736 }
737
738 if !column_tables.is_empty() {
740 let mut new_expressions = Vec::with_capacity(select.expressions.len());
742 for expr in &select.expressions {
743 match expr {
744 Expression::Column(col) if col.table.is_none() => {
745 let normalized = normalize_column_name(&col.name.name, resolver.dialect);
746 let Some(tables) = column_tables.get(&normalized) else {
747 new_expressions.push(expr.clone());
748 continue;
749 };
750 let coalesce = make_coalesce(&col.name.name, tables);
751 new_expressions.push(Expression::Alias(Box::new(Alias {
753 this: coalesce,
754 alias: col.name.clone(),
755 column_aliases: vec![],
756 alias_explicit_as: false,
757 alias_keyword: None,
758 pre_alias_comments: vec![],
759 trailing_comments: vec![],
760 inferred_type: None,
761 })));
762 }
763 _ => {
764 let mut rewritten = expr.clone();
765 rewrite_using_columns_in_expression(
766 &mut rewritten,
767 &column_tables,
768 resolver.dialect,
769 );
770 new_expressions.push(rewritten);
771 }
772 }
773 }
774 select.expressions = new_expressions;
775
776 if let Some(where_clause) = &mut select.where_clause {
778 rewrite_using_columns_in_expression(
779 &mut where_clause.this,
780 &column_tables,
781 resolver.dialect,
782 );
783 }
784
785 if let Some(group_by) = &mut select.group_by {
787 for expr in &mut group_by.expressions {
788 rewrite_using_columns_in_expression(expr, &column_tables, resolver.dialect);
789 }
790 }
791
792 if let Some(having) = &mut select.having {
794 rewrite_using_columns_in_expression(&mut having.this, &column_tables, resolver.dialect);
795 }
796
797 if let Some(qualify) = &mut select.qualify {
799 rewrite_using_columns_in_expression(
800 &mut qualify.this,
801 &column_tables,
802 resolver.dialect,
803 );
804 }
805
806 if let Some(order_by) = &mut select.order_by {
808 for ordered in &mut order_by.expressions {
809 rewrite_using_columns_in_expression(
810 &mut ordered.this,
811 &column_tables,
812 resolver.dialect,
813 );
814 }
815 }
816 }
817
818 Ok(column_tables)
819}
820
821fn rewrite_using_columns_in_expression(
823 expr: &mut Expression,
824 column_tables: &HashMap<String, Vec<String>>,
825 dialect: Option<DialectType>,
826) {
827 let transformed: crate::Result<_> = transform_in_scope(expr.clone(), |node| match node {
828 Expression::Column(col) if col.table.is_none() => {
829 let normalized = normalize_column_name(&col.name.name, dialect);
830 if let Some(tables) = column_tables.get(&normalized) {
831 Ok(make_coalesce(&col.name.name, tables))
832 } else {
833 Ok(Expression::Column(col))
834 }
835 }
836 other => Ok(other),
837 });
838
839 if let Ok(next) = transformed {
840 *expr = next;
841 }
842}
843
844pub(crate) fn normalize_dotted_columns_in_scope(
852 select: &mut Select,
853 scope: &Scope,
854 resolver: &mut Resolver,
855) -> QualifyColumnsResult<()> {
856 for expression in &mut select.expressions {
857 normalize_dotted_columns_in_expression(expression, scope, resolver)?;
858 }
859 if let Some(where_clause) = &mut select.where_clause {
860 normalize_dotted_columns_in_expression(&mut where_clause.this, scope, resolver)?;
861 }
862 if let Some(group_by) = &mut select.group_by {
863 for expression in &mut group_by.expressions {
864 normalize_dotted_columns_in_expression(expression, scope, resolver)?;
865 }
866 }
867 if let Some(having) = &mut select.having {
868 normalize_dotted_columns_in_expression(&mut having.this, scope, resolver)?;
869 }
870 if let Some(qualify) = &mut select.qualify {
871 normalize_dotted_columns_in_expression(&mut qualify.this, scope, resolver)?;
872 }
873 if let Some(order_by) = &mut select.order_by {
874 for ordered in &mut order_by.expressions {
875 normalize_dotted_columns_in_expression(&mut ordered.this, scope, resolver)?;
876 }
877 }
878 for join in &mut select.joins {
879 normalize_dotted_columns_in_expression(&mut join.this, scope, resolver)?;
880 if let Some(on) = &mut join.on {
881 normalize_dotted_columns_in_expression(on, scope, resolver)?;
882 }
883 }
884 Ok(())
885}
886
887pub(crate) fn normalize_dotted_columns_in_expression(
888 expression: &mut Expression,
889 scope: &Scope,
890 resolver: &mut Resolver,
891) -> QualifyColumnsResult<()> {
892 let resolver = RefCell::new(resolver);
893 let transformed = transform_in_scope(expression.clone(), |node| {
894 let Expression::Column(column) = node else {
895 return Ok(node);
896 };
897 let Some(root) = column.table.as_ref() else {
898 return Ok(Expression::Column(column));
899 };
900
901 let root_is_source = scope
902 .sources
903 .keys()
904 .any(|source| source.eq_ignore_ascii_case(&root.name));
905 if root_is_source || resolver.borrow().outer_source_exists(&root.name) {
906 return Ok(Expression::Column(column));
907 }
908
909 let Some(source_name) = resolver.borrow_mut().get_table(&root.name) else {
910 return Ok(Expression::Column(column));
911 };
912
913 let root_column = Expression::boxed_column(Column {
914 name: root.clone(),
915 table: Some(Identifier::new(source_name)),
916 join_mark: column.join_mark,
917 trailing_comments: column.trailing_comments.clone(),
918 span: column.span,
919 inferred_type: None,
920 });
921
922 Ok(Expression::Dot(Box::new(DotAccess {
923 this: root_column,
924 field: column.name.clone(),
925 inferred_type: None,
926 })))
927 })
928 .map_err(|error: crate::Error| QualifyColumnsError::CannotAutoJoin(error.to_string()))?;
929
930 *expression = transformed;
931 Ok(())
932}
933
934fn qualify_columns_in_scope(
936 select: &mut Select,
937 scope: &Scope,
938 resolver: &mut Resolver,
939 allow_partial: bool,
940) -> QualifyColumnsResult<()> {
941 for expr in &mut select.expressions {
942 qualify_columns_in_expression(expr, scope, resolver, allow_partial)?;
943 }
944 if let Some(where_clause) = &mut select.where_clause {
945 qualify_columns_in_expression(&mut where_clause.this, scope, resolver, allow_partial)?;
946 }
947 if let Some(group_by) = &mut select.group_by {
948 for expr in &mut group_by.expressions {
949 qualify_columns_in_expression(expr, scope, resolver, allow_partial)?;
950 }
951 }
952 if let Some(having) = &mut select.having {
953 qualify_columns_in_expression(&mut having.this, scope, resolver, allow_partial)?;
954 }
955 if let Some(qualify) = &mut select.qualify {
956 qualify_columns_in_expression(&mut qualify.this, scope, resolver, allow_partial)?;
957 }
958 if let Some(order_by) = &mut select.order_by {
959 for ordered in &mut order_by.expressions {
960 qualify_columns_in_expression(&mut ordered.this, scope, resolver, allow_partial)?;
961 }
962 }
963 for join in &mut select.joins {
964 qualify_columns_in_expression(&mut join.this, scope, resolver, allow_partial)?;
965 if let Some(on) = &mut join.on {
966 qualify_columns_in_expression(on, scope, resolver, allow_partial)?;
967 }
968 }
969 Ok(())
970}
971
972fn expand_alias_refs(
979 select: &mut Select,
980 _resolver: &mut Resolver,
981 _dialect: Option<DialectType>,
982) -> QualifyColumnsResult<()> {
983 let mut alias_to_expression: HashMap<String, (Expression, usize)> = HashMap::new();
984
985 for (i, expr) in select.expressions.iter_mut().enumerate() {
986 replace_alias_refs_in_expression(expr, &alias_to_expression, false);
987 if let Expression::Alias(alias) = expr {
988 alias_to_expression.insert(alias.alias.name.clone(), (alias.this.clone(), i + 1));
989 }
990 }
991
992 if let Some(where_clause) = &mut select.where_clause {
993 replace_alias_refs_in_expression(&mut where_clause.this, &alias_to_expression, false);
994 }
995 if let Some(group_by) = &mut select.group_by {
996 for expr in &mut group_by.expressions {
997 replace_alias_refs_in_expression(expr, &alias_to_expression, true);
998 }
999 }
1000 if let Some(having) = &mut select.having {
1001 replace_alias_refs_in_expression(&mut having.this, &alias_to_expression, false);
1002 }
1003 if let Some(qualify) = &mut select.qualify {
1004 replace_alias_refs_in_expression(&mut qualify.this, &alias_to_expression, false);
1005 }
1006 if let Some(order_by) = &mut select.order_by {
1007 for ordered in &mut order_by.expressions {
1008 replace_alias_refs_in_expression(&mut ordered.this, &alias_to_expression, false);
1009 }
1010 }
1011
1012 Ok(())
1013}
1014
1015fn expand_group_by(select: &mut Select, _dialect: Option<DialectType>) -> QualifyColumnsResult<()> {
1022 let projections = select.expressions.clone();
1023
1024 if let Some(group_by) = &mut select.group_by {
1025 for group_expr in &mut group_by.expressions {
1026 if let Some(index) = positional_reference(group_expr) {
1027 let replacement = select_expression_at_position(&projections, index)?;
1028 *group_expr = replacement;
1029 }
1030 }
1031 }
1032 Ok(())
1033}
1034
1035fn expand_stars(
1045 select: &mut Select,
1046 _scope: &Scope,
1047 resolver: &mut Resolver,
1048 column_tables: &HashMap<String, Vec<String>>,
1049) -> QualifyColumnsResult<()> {
1050 let mut new_selections: Vec<Expression> = Vec::new();
1051 let mut has_star = false;
1052 let mut coalesced_columns: HashSet<String> = HashSet::new();
1053
1054 let ordered_sources = get_ordered_source_names(select);
1056
1057 for expr in &select.expressions {
1058 match expr {
1059 Expression::Star(star) if star.has_except_pattern() => {
1060 new_selections.push(expr.clone());
1061 }
1062 Expression::Star(star) => {
1063 has_star = true;
1064 if let Some(table) = &star.table {
1065 let table_name = &table.name;
1066 if !ordered_sources.contains(table_name) {
1067 return Err(QualifyColumnsError::UnknownTable(table_name.clone()));
1068 }
1069 if let Ok(columns) = resolver.get_source_columns(table_name) {
1070 if columns.contains(&"*".to_string()) || columns.is_empty() {
1071 return Ok(());
1072 }
1073 for col_name in &columns {
1074 let normalized = normalize_column_name(col_name, resolver.dialect);
1075 if star.excludes(col_name, resolver.dialect)
1076 || coalesced_columns.contains(&normalized)
1077 {
1078 continue;
1079 }
1080 if let Some(tables) = column_tables.get(&normalized) {
1081 if tables.contains(table_name) {
1082 coalesced_columns.insert(normalized);
1083 let coalesce = make_coalesce(col_name, tables);
1084 if let Some(expanded) =
1085 star.expand_column(col_name, coalesce, resolver.dialect)
1086 {
1087 new_selections.push(match expanded {
1088 Expression::Alias(_) => expanded,
1089 _ => Expression::Alias(Box::new(Alias::new(
1090 expanded,
1091 Identifier::new(col_name),
1092 ))),
1093 });
1094 }
1095 continue;
1096 }
1097 }
1098 new_selections.extend(star.expand_column(
1099 col_name,
1100 create_qualified_column(col_name, Some(table_name)),
1101 resolver.dialect,
1102 ));
1103 }
1104 }
1105 } else {
1106 for source_name in &ordered_sources {
1107 if let Ok(columns) = resolver.get_source_columns(source_name) {
1108 if columns.contains(&"*".to_string()) || columns.is_empty() {
1109 return Ok(());
1110 }
1111 for col_name in &columns {
1112 let normalized = normalize_column_name(col_name, resolver.dialect);
1113 if star.excludes(col_name, resolver.dialect)
1114 || coalesced_columns.contains(&normalized)
1115 {
1116 continue;
1118 }
1119 if let Some(tables) = column_tables.get(&normalized) {
1120 if tables.contains(source_name) {
1121 coalesced_columns.insert(normalized);
1123 let coalesce = make_coalesce(col_name, tables);
1124 if let Some(expanded) =
1125 star.expand_column(col_name, coalesce, resolver.dialect)
1126 {
1127 new_selections.push(match expanded {
1128 Expression::Alias(_) => expanded,
1129 _ => Expression::Alias(Box::new(Alias::new(
1130 expanded,
1131 Identifier::new(col_name),
1132 ))),
1133 });
1134 }
1135 continue;
1136 }
1137 }
1138 new_selections.extend(star.expand_column(
1139 col_name,
1140 create_qualified_column(col_name, Some(source_name)),
1141 resolver.dialect,
1142 ));
1143 }
1144 }
1145 }
1146 }
1147 }
1148 Expression::Column(col) if is_star_column(col) => {
1149 has_star = true;
1150 if let Some(table) = &col.table {
1151 let table_name = &table.name;
1152 if !ordered_sources.contains(table_name) {
1153 return Err(QualifyColumnsError::UnknownTable(table_name.clone()));
1154 }
1155 if let Ok(columns) = resolver.get_source_columns(table_name) {
1156 if columns.contains(&"*".to_string()) || columns.is_empty() {
1157 return Ok(());
1158 }
1159 for col_name in &columns {
1160 let normalized = normalize_column_name(col_name, resolver.dialect);
1161 if coalesced_columns.contains(&normalized) {
1162 continue;
1163 }
1164 if let Some(tables) = column_tables.get(&normalized) {
1165 if tables.contains(table_name) {
1166 coalesced_columns.insert(normalized);
1167 let coalesce = make_coalesce(col_name, tables);
1168 new_selections.push(Expression::Alias(Box::new(Alias {
1169 this: coalesce,
1170 alias: Identifier::new(col_name),
1171 column_aliases: vec![],
1172 alias_explicit_as: false,
1173 alias_keyword: None,
1174 pre_alias_comments: vec![],
1175 trailing_comments: vec![],
1176 inferred_type: None,
1177 })));
1178 continue;
1179 }
1180 }
1181 new_selections
1182 .push(create_qualified_column(col_name, Some(table_name)));
1183 }
1184 }
1185 }
1186 }
1187 _ => new_selections.push(expr.clone()),
1188 }
1189 }
1190
1191 if has_star {
1192 select.expressions = new_selections;
1193 }
1194
1195 Ok(())
1196}
1197
1198pub fn qualify_outputs(scope: &Scope) -> QualifyColumnsResult<()> {
1205 if let Expression::Select(mut select) = scope.expression.clone() {
1206 qualify_outputs_select(&mut select)?;
1207 }
1208 Ok(())
1209}
1210
1211fn qualify_outputs_select(select: &mut Select) -> QualifyColumnsResult<()> {
1212 let mut new_selections: Vec<Expression> = Vec::new();
1213
1214 for (i, expr) in select.expressions.iter().enumerate() {
1215 match expr {
1216 Expression::Alias(_) => new_selections.push(expr.clone()),
1217 Expression::Column(col) => {
1218 new_selections.push(create_alias(expr.clone(), &col.name.name));
1219 }
1220 Expression::Star(_) => new_selections.push(expr.clone()),
1221 _ => {
1222 let alias_name = get_output_name(expr).unwrap_or_else(|| format!("_col_{}", i));
1223 new_selections.push(create_alias(expr.clone(), &alias_name));
1224 }
1225 }
1226 }
1227
1228 select.expressions = new_selections;
1229 Ok(())
1230}
1231
1232fn qualify_columns_in_expression(
1233 expr: &mut Expression,
1234 scope: &Scope,
1235 resolver: &mut Resolver,
1236 allow_partial: bool,
1237) -> QualifyColumnsResult<()> {
1238 let first_error: RefCell<Option<QualifyColumnsError>> = RefCell::new(None);
1239 let resolver_cell: RefCell<&mut Resolver> = RefCell::new(resolver);
1240
1241 let transformed = transform_in_scope(expr.clone(), |node| {
1242 if first_error.borrow().is_some() {
1243 return Ok(node);
1244 }
1245
1246 match node {
1247 Expression::Column(mut col) => {
1248 if let Err(err) = qualify_single_column(
1249 &mut col,
1250 scope,
1251 &mut resolver_cell.borrow_mut(),
1252 allow_partial,
1253 ) {
1254 *first_error.borrow_mut() = Some(err);
1255 }
1256 Ok(Expression::Column(col))
1257 }
1258 _ => Ok(node),
1259 }
1260 })
1261 .map_err(|err: crate::Error| QualifyColumnsError::CannotAutoJoin(err.to_string()))?;
1262
1263 if let Some(err) = first_error.into_inner() {
1264 return Err(err);
1265 }
1266
1267 *expr = transformed;
1268 Ok(())
1269}
1270
1271fn qualify_single_column(
1272 col: &mut Column,
1273 scope: &Scope,
1274 resolver: &mut Resolver,
1275 allow_partial: bool,
1276) -> QualifyColumnsResult<()> {
1277 if is_star_column(col) {
1278 return Ok(());
1279 }
1280
1281 if let Some(table) = &col.table {
1282 let table_name = &table.name;
1283 let source_name = scope.sources.keys().find(|source| {
1284 normalize_name(source, resolver.dialect, true, true)
1285 == normalize_name(table_name, resolver.dialect, true, true)
1286 });
1287 let Some(source_name) = source_name else {
1288 if resolver.outer_source_exists(table_name) {
1289 return Ok(());
1290 }
1291 return Err(QualifyColumnsError::UnknownTable(table_name.clone()));
1292 };
1293
1294 if let Ok(source_columns) = resolver.get_source_columns(source_name) {
1295 let normalized_column_name = normalize_column_name(&col.name.name, resolver.dialect);
1296 if !allow_partial
1297 && !source_columns.is_empty()
1298 && !source_columns.iter().any(|column| {
1299 normalize_column_name(column, resolver.dialect) == normalized_column_name
1300 })
1301 && !source_columns.contains(&"*".to_string())
1302 {
1303 return Err(QualifyColumnsError::UnknownColumn(col.name.name.clone()));
1304 }
1305 }
1306 return Ok(());
1307 }
1308
1309 if resolver.is_ambiguous(&col.name.name) {
1310 if allow_partial {
1311 return Ok(());
1312 }
1313 return Err(QualifyColumnsError::AmbiguousColumn(col.name.name.clone()));
1314 }
1315 if let Some(table_name) = resolver.get_table(&col.name.name) {
1316 col.table = Some(Identifier::new(table_name));
1317 return Ok(());
1318 }
1319
1320 if let Some(outer_table) = resolver.find_column_in_outer_scopes(&col.name.name) {
1321 col.table = Some(Identifier::new(outer_table));
1322 return Ok(());
1323 }
1324
1325 if !allow_partial {
1326 return Err(QualifyColumnsError::UnknownColumn(col.name.name.clone()));
1327 }
1328
1329 Ok(())
1330}
1331
1332fn normalize_column_name(name: &str, dialect: Option<DialectType>) -> String {
1333 normalize_name(name, dialect, false, true)
1334}
1335
1336fn replace_alias_refs_in_expression(
1337 expr: &mut Expression,
1338 alias_to_expression: &HashMap<String, (Expression, usize)>,
1339 literal_index: bool,
1340) {
1341 let transformed: crate::Result<_> = transform_in_scope(expr.clone(), |node| match node {
1342 Expression::Column(col) if col.table.is_none() => {
1343 if let Some((alias_expr, index)) = alias_to_expression.get(&col.name.name) {
1344 if literal_index && matches!(alias_expr, Expression::Literal(_)) {
1345 return Ok(Expression::number(*index as i64));
1346 }
1347 return Ok(Expression::Paren(Box::new(Paren {
1348 this: alias_expr.clone(),
1349 trailing_comments: vec![],
1350 })));
1351 }
1352 Ok(Expression::Column(col))
1353 }
1354 other => Ok(other),
1355 });
1356
1357 if let Ok(next) = transformed {
1358 *expr = next;
1359 }
1360}
1361
1362fn positional_reference(expr: &Expression) -> Option<usize> {
1363 match expr {
1364 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
1365 let Literal::Number(value) = lit.as_ref() else {
1366 unreachable!()
1367 };
1368 value.parse::<usize>().ok()
1369 }
1370 _ => None,
1371 }
1372}
1373
1374fn select_expression_at_position(
1375 projections: &[Expression],
1376 index: usize,
1377) -> QualifyColumnsResult<Expression> {
1378 if index == 0 || index > projections.len() {
1379 return Err(QualifyColumnsError::UnknownOutputColumn(index.to_string()));
1380 }
1381
1382 let projection = projections[index - 1].clone();
1383 Ok(match projection {
1384 Expression::Alias(alias) => alias.this.clone(),
1385 other => other,
1386 })
1387}
1388
1389fn get_reserved_words(dialect: Option<DialectType>) -> HashSet<&'static str> {
1392 let mut words: HashSet<&'static str> = [
1394 "ADD",
1396 "ALL",
1397 "ALTER",
1398 "AND",
1399 "ANY",
1400 "AS",
1401 "ASC",
1402 "BETWEEN",
1403 "BY",
1404 "CASE",
1405 "CAST",
1406 "CHECK",
1407 "COLUMN",
1408 "CONSTRAINT",
1409 "CREATE",
1410 "CROSS",
1411 "CURRENT",
1412 "CURRENT_DATE",
1413 "CURRENT_TIME",
1414 "CURRENT_TIMESTAMP",
1415 "CURRENT_USER",
1416 "DATABASE",
1417 "DEFAULT",
1418 "DELETE",
1419 "DESC",
1420 "DISTINCT",
1421 "DROP",
1422 "ELSE",
1423 "END",
1424 "ESCAPE",
1425 "EXCEPT",
1426 "EXISTS",
1427 "FALSE",
1428 "FETCH",
1429 "FOR",
1430 "FOREIGN",
1431 "FROM",
1432 "FULL",
1433 "GRANT",
1434 "GROUP",
1435 "HAVING",
1436 "IF",
1437 "IN",
1438 "INDEX",
1439 "INNER",
1440 "INSERT",
1441 "INTERSECT",
1442 "INTO",
1443 "IS",
1444 "JOIN",
1445 "KEY",
1446 "LEFT",
1447 "LIKE",
1448 "LIMIT",
1449 "NATURAL",
1450 "NOT",
1451 "NULL",
1452 "OFFSET",
1453 "ON",
1454 "OR",
1455 "ORDER",
1456 "OUTER",
1457 "PRIMARY",
1458 "REFERENCES",
1459 "REPLACE",
1460 "RETURNING",
1461 "RIGHT",
1462 "ROLLBACK",
1463 "ROW",
1464 "ROWS",
1465 "SELECT",
1466 "SESSION_USER",
1467 "SET",
1468 "SOME",
1469 "TABLE",
1470 "THEN",
1471 "TO",
1472 "TRUE",
1473 "TRUNCATE",
1474 "UNION",
1475 "UNIQUE",
1476 "UPDATE",
1477 "USING",
1478 "VALUES",
1479 "VIEW",
1480 "WHEN",
1481 "WHERE",
1482 "WINDOW",
1483 "WITH",
1484 ]
1485 .iter()
1486 .copied()
1487 .collect();
1488
1489 match dialect {
1491 Some(DialectType::MySQL) => {
1492 words.extend(
1493 [
1494 "ANALYZE",
1495 "BOTH",
1496 "CHANGE",
1497 "CONDITION",
1498 "DATABASES",
1499 "DAY_HOUR",
1500 "DAY_MICROSECOND",
1501 "DAY_MINUTE",
1502 "DAY_SECOND",
1503 "DELAYED",
1504 "DETERMINISTIC",
1505 "DIV",
1506 "DUAL",
1507 "EACH",
1508 "ELSEIF",
1509 "ENCLOSED",
1510 "EXPLAIN",
1511 "FLOAT4",
1512 "FLOAT8",
1513 "FORCE",
1514 "HOUR_MICROSECOND",
1515 "HOUR_MINUTE",
1516 "HOUR_SECOND",
1517 "IGNORE",
1518 "INFILE",
1519 "INT1",
1520 "INT2",
1521 "INT3",
1522 "INT4",
1523 "INT8",
1524 "ITERATE",
1525 "KEYS",
1526 "KILL",
1527 "LEADING",
1528 "LEAVE",
1529 "LINES",
1530 "LOAD",
1531 "LOCK",
1532 "LONG",
1533 "LONGBLOB",
1534 "LONGTEXT",
1535 "LOOP",
1536 "LOW_PRIORITY",
1537 "MATCH",
1538 "MEDIUMBLOB",
1539 "MEDIUMINT",
1540 "MEDIUMTEXT",
1541 "MINUTE_MICROSECOND",
1542 "MINUTE_SECOND",
1543 "MOD",
1544 "MODIFIES",
1545 "NO_WRITE_TO_BINLOG",
1546 "OPTIMIZE",
1547 "OPTIONALLY",
1548 "OUT",
1549 "OUTFILE",
1550 "PURGE",
1551 "READS",
1552 "REGEXP",
1553 "RELEASE",
1554 "RENAME",
1555 "REPEAT",
1556 "REQUIRE",
1557 "RESIGNAL",
1558 "RETURN",
1559 "REVOKE",
1560 "RLIKE",
1561 "SCHEMA",
1562 "SCHEMAS",
1563 "SECOND_MICROSECOND",
1564 "SENSITIVE",
1565 "SEPARATOR",
1566 "SHOW",
1567 "SIGNAL",
1568 "SPATIAL",
1569 "SQL",
1570 "SQLEXCEPTION",
1571 "SQLSTATE",
1572 "SQLWARNING",
1573 "SQL_BIG_RESULT",
1574 "SQL_CALC_FOUND_ROWS",
1575 "SQL_SMALL_RESULT",
1576 "SSL",
1577 "STARTING",
1578 "STRAIGHT_JOIN",
1579 "TERMINATED",
1580 "TINYBLOB",
1581 "TINYINT",
1582 "TINYTEXT",
1583 "TRAILING",
1584 "TRIGGER",
1585 "UNDO",
1586 "UNLOCK",
1587 "UNSIGNED",
1588 "USAGE",
1589 "UTC_DATE",
1590 "UTC_TIME",
1591 "UTC_TIMESTAMP",
1592 "VARBINARY",
1593 "VARCHARACTER",
1594 "WHILE",
1595 "WRITE",
1596 "XOR",
1597 "YEAR_MONTH",
1598 "ZEROFILL",
1599 ]
1600 .iter()
1601 .copied(),
1602 );
1603 }
1604 Some(DialectType::PostgreSQL) | Some(DialectType::CockroachDB) => {
1605 words.extend(
1606 [
1607 "ANALYSE",
1608 "ANALYZE",
1609 "ARRAY",
1610 "AUTHORIZATION",
1611 "BINARY",
1612 "BOTH",
1613 "COLLATE",
1614 "CONCURRENTLY",
1615 "DO",
1616 "FREEZE",
1617 "ILIKE",
1618 "INITIALLY",
1619 "ISNULL",
1620 "LATERAL",
1621 "LEADING",
1622 "LOCALTIME",
1623 "LOCALTIMESTAMP",
1624 "NOTNULL",
1625 "ONLY",
1626 "OVERLAPS",
1627 "PLACING",
1628 "SIMILAR",
1629 "SYMMETRIC",
1630 "TABLESAMPLE",
1631 "TRAILING",
1632 "VARIADIC",
1633 "VERBOSE",
1634 ]
1635 .iter()
1636 .copied(),
1637 );
1638 }
1639 Some(DialectType::BigQuery) => {
1640 words.extend(
1641 [
1642 "ASSERT_ROWS_MODIFIED",
1643 "COLLATE",
1644 "CONTAINS",
1645 "CUBE",
1646 "DEFINE",
1647 "ENUM",
1648 "EXTRACT",
1649 "FOLLOWING",
1650 "GROUPING",
1651 "GROUPS",
1652 "HASH",
1653 "IGNORE",
1654 "LATERAL",
1655 "LOOKUP",
1656 "MERGE",
1657 "NEW",
1658 "NO",
1659 "NULLS",
1660 "OF",
1661 "OVER",
1662 "PARTITION",
1663 "PRECEDING",
1664 "PROTO",
1665 "RANGE",
1666 "RECURSIVE",
1667 "RESPECT",
1668 "ROLLUP",
1669 "STRUCT",
1670 "TABLESAMPLE",
1671 "TREAT",
1672 "UNBOUNDED",
1673 "WITHIN",
1674 ]
1675 .iter()
1676 .copied(),
1677 );
1678 }
1679 Some(DialectType::Snowflake) => {
1680 words.extend(
1681 [
1682 "ACCOUNT",
1683 "BOTH",
1684 "CONNECT",
1685 "FOLLOWING",
1686 "ILIKE",
1687 "INCREMENT",
1688 "ISSUE",
1689 "LATERAL",
1690 "LEADING",
1691 "LOCALTIME",
1692 "LOCALTIMESTAMP",
1693 "MINUS",
1694 "QUALIFY",
1695 "REGEXP",
1696 "RLIKE",
1697 "SOME",
1698 "START",
1699 "TABLESAMPLE",
1700 "TOP",
1701 "TRAILING",
1702 "TRY_CAST",
1703 ]
1704 .iter()
1705 .copied(),
1706 );
1707 }
1708 Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
1709 words.extend(
1710 [
1711 "BACKUP",
1712 "BREAK",
1713 "BROWSE",
1714 "BULK",
1715 "CASCADE",
1716 "CHECKPOINT",
1717 "CLOSE",
1718 "CLUSTERED",
1719 "COALESCE",
1720 "COMPUTE",
1721 "CONTAINS",
1722 "CONTAINSTABLE",
1723 "CONTINUE",
1724 "CONVERT",
1725 "DBCC",
1726 "DEALLOCATE",
1727 "DENY",
1728 "DISK",
1729 "DISTRIBUTED",
1730 "DUMP",
1731 "ERRLVL",
1732 "EXEC",
1733 "EXECUTE",
1734 "EXIT",
1735 "EXTERNAL",
1736 "FILE",
1737 "FILLFACTOR",
1738 "FREETEXT",
1739 "FREETEXTTABLE",
1740 "FUNCTION",
1741 "GOTO",
1742 "HOLDLOCK",
1743 "IDENTITY",
1744 "IDENTITYCOL",
1745 "IDENTITY_INSERT",
1746 "KILL",
1747 "LINENO",
1748 "MERGE",
1749 "NONCLUSTERED",
1750 "NULLIF",
1751 "OF",
1752 "OFF",
1753 "OFFSETS",
1754 "OPEN",
1755 "OPENDATASOURCE",
1756 "OPENQUERY",
1757 "OPENROWSET",
1758 "OPENXML",
1759 "OVER",
1760 "PERCENT",
1761 "PIVOT",
1762 "PLAN",
1763 "PRINT",
1764 "PROC",
1765 "PROCEDURE",
1766 "PUBLIC",
1767 "RAISERROR",
1768 "READ",
1769 "READTEXT",
1770 "RECONFIGURE",
1771 "REPLICATION",
1772 "RESTORE",
1773 "RESTRICT",
1774 "REVERT",
1775 "ROWCOUNT",
1776 "ROWGUIDCOL",
1777 "RULE",
1778 "SAVE",
1779 "SECURITYAUDIT",
1780 "SEMANTICKEYPHRASETABLE",
1781 "SEMANTICSIMILARITYDETAILSTABLE",
1782 "SEMANTICSIMILARITYTABLE",
1783 "SETUSER",
1784 "SHUTDOWN",
1785 "STATISTICS",
1786 "SYSTEM_USER",
1787 "TEXTSIZE",
1788 "TOP",
1789 "TRAN",
1790 "TRANSACTION",
1791 "TRIGGER",
1792 "TSEQUAL",
1793 "UNPIVOT",
1794 "UPDATETEXT",
1795 "WAITFOR",
1796 "WRITETEXT",
1797 ]
1798 .iter()
1799 .copied(),
1800 );
1801 }
1802 Some(DialectType::ClickHouse) => {
1803 words.extend(
1804 [
1805 "ANTI",
1806 "ARRAY",
1807 "ASOF",
1808 "FINAL",
1809 "FORMAT",
1810 "GLOBAL",
1811 "INF",
1812 "KILL",
1813 "MATERIALIZED",
1814 "NAN",
1815 "PREWHERE",
1816 "SAMPLE",
1817 "SEMI",
1818 "SETTINGS",
1819 "TOP",
1820 ]
1821 .iter()
1822 .copied(),
1823 );
1824 }
1825 Some(DialectType::DuckDB) => {
1826 words.extend(
1827 [
1828 "ANALYSE",
1829 "ANALYZE",
1830 "ARRAY",
1831 "BOTH",
1832 "LATERAL",
1833 "LEADING",
1834 "LOCALTIME",
1835 "LOCALTIMESTAMP",
1836 "PLACING",
1837 "QUALIFY",
1838 "SIMILAR",
1839 "TABLESAMPLE",
1840 "TRAILING",
1841 ]
1842 .iter()
1843 .copied(),
1844 );
1845 }
1846 Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
1847 words.extend(
1848 [
1849 "BOTH",
1850 "CLUSTER",
1851 "DISTRIBUTE",
1852 "EXCHANGE",
1853 "EXTENDED",
1854 "FUNCTION",
1855 "LATERAL",
1856 "LEADING",
1857 "MACRO",
1858 "OVER",
1859 "PARTITION",
1860 "PERCENT",
1861 "RANGE",
1862 "READS",
1863 "REDUCE",
1864 "REGEXP",
1865 "REVOKE",
1866 "RLIKE",
1867 "ROLLUP",
1868 "SEMI",
1869 "SORT",
1870 "TABLESAMPLE",
1871 "TRAILING",
1872 "TRANSFORM",
1873 "UNBOUNDED",
1874 "UNIQUEJOIN",
1875 ]
1876 .iter()
1877 .copied(),
1878 );
1879 }
1880 Some(DialectType::Trino) | Some(DialectType::Presto) | Some(DialectType::Athena) => {
1881 words.extend(
1882 [
1883 "CUBE",
1884 "DEALLOCATE",
1885 "DESCRIBE",
1886 "EXECUTE",
1887 "EXTRACT",
1888 "GROUPING",
1889 "LATERAL",
1890 "LOCALTIME",
1891 "LOCALTIMESTAMP",
1892 "NORMALIZE",
1893 "PREPARE",
1894 "ROLLUP",
1895 "SOME",
1896 "TABLESAMPLE",
1897 "UESCAPE",
1898 "UNNEST",
1899 ]
1900 .iter()
1901 .copied(),
1902 );
1903 }
1904 Some(DialectType::Oracle) => {
1905 words.extend(
1906 [
1907 "ACCESS",
1908 "AUDIT",
1909 "CLUSTER",
1910 "COMMENT",
1911 "COMPRESS",
1912 "CONNECT",
1913 "EXCLUSIVE",
1914 "FILE",
1915 "IDENTIFIED",
1916 "IMMEDIATE",
1917 "INCREMENT",
1918 "INITIAL",
1919 "LEVEL",
1920 "LOCK",
1921 "LONG",
1922 "MAXEXTENTS",
1923 "MINUS",
1924 "MODE",
1925 "NOAUDIT",
1926 "NOCOMPRESS",
1927 "NOWAIT",
1928 "NUMBER",
1929 "OF",
1930 "OFFLINE",
1931 "ONLINE",
1932 "PCTFREE",
1933 "PRIOR",
1934 "RAW",
1935 "RENAME",
1936 "RESOURCE",
1937 "REVOKE",
1938 "SHARE",
1939 "SIZE",
1940 "START",
1941 "SUCCESSFUL",
1942 "SYNONYM",
1943 "SYSDATE",
1944 "TRIGGER",
1945 "UID",
1946 "VALIDATE",
1947 "VARCHAR2",
1948 "WHENEVER",
1949 ]
1950 .iter()
1951 .copied(),
1952 );
1953 }
1954 Some(DialectType::Redshift) => {
1955 words.extend(
1956 [
1957 "AZ64",
1958 "BZIP2",
1959 "DELTA",
1960 "DELTA32K",
1961 "DISTSTYLE",
1962 "ENCODE",
1963 "GZIP",
1964 "ILIKE",
1965 "LIMIT",
1966 "LUNS",
1967 "LZO",
1968 "LZOP",
1969 "MOSTLY13",
1970 "MOSTLY32",
1971 "MOSTLY8",
1972 "RAW",
1973 "SIMILAR",
1974 "SNAPSHOT",
1975 "SORTKEY",
1976 "SYSDATE",
1977 "TOP",
1978 "ZSTD",
1979 ]
1980 .iter()
1981 .copied(),
1982 );
1983 }
1984 _ => {
1985 words.extend(
1987 [
1988 "ANALYZE",
1989 "ARRAY",
1990 "BOTH",
1991 "CUBE",
1992 "GROUPING",
1993 "LATERAL",
1994 "LEADING",
1995 "LOCALTIME",
1996 "LOCALTIMESTAMP",
1997 "OVER",
1998 "PARTITION",
1999 "QUALIFY",
2000 "RANGE",
2001 "ROLLUP",
2002 "SIMILAR",
2003 "SOME",
2004 "TABLESAMPLE",
2005 "TRAILING",
2006 ]
2007 .iter()
2008 .copied(),
2009 );
2010 }
2011 }
2012
2013 words
2014}
2015
2016fn needs_quoting(name: &str, reserved_words: &HashSet<&str>) -> bool {
2024 if name.is_empty() {
2025 return false;
2026 }
2027
2028 if name.as_bytes()[0].is_ascii_digit() {
2030 return true;
2031 }
2032
2033 if !name.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'_') {
2035 return true;
2036 }
2037
2038 let upper = name.to_uppercase();
2040 reserved_words.contains(upper.as_str())
2041}
2042
2043fn maybe_quote(id: &mut Identifier, reserved_words: &HashSet<&str>) {
2045 if id.quoted || id.name.is_empty() || id.name == "*" {
2048 return;
2049 }
2050 if needs_quoting(&id.name, reserved_words) {
2051 id.quoted = true;
2052 }
2053}
2054
2055fn quote_identifiers_recursive(expr: &mut Expression, reserved_words: &HashSet<&str>) {
2057 match expr {
2058 Expression::Identifier(id) => {
2060 maybe_quote(id, reserved_words);
2061 }
2062
2063 Expression::Column(col) => {
2064 maybe_quote(&mut col.name, reserved_words);
2065 if let Some(ref mut table) = col.table {
2066 maybe_quote(table, reserved_words);
2067 }
2068 }
2069
2070 Expression::Table(table_ref) => {
2071 maybe_quote(&mut table_ref.name, reserved_words);
2072 if let Some(ref mut schema) = table_ref.schema {
2073 maybe_quote(schema, reserved_words);
2074 }
2075 if let Some(ref mut catalog) = table_ref.catalog {
2076 maybe_quote(catalog, reserved_words);
2077 }
2078 if let Some(ref mut alias) = table_ref.alias {
2079 maybe_quote(alias, reserved_words);
2080 }
2081 for ca in &mut table_ref.column_aliases {
2082 maybe_quote(ca, reserved_words);
2083 }
2084 for p in &mut table_ref.partitions {
2085 maybe_quote(p, reserved_words);
2086 }
2087 for h in &mut table_ref.hints {
2089 quote_identifiers_recursive(h, reserved_words);
2090 }
2091 if let Some(ref mut ver) = table_ref.version {
2092 quote_identifiers_recursive(&mut ver.this, reserved_words);
2093 if let Some(ref mut e) = ver.expression {
2094 quote_identifiers_recursive(e, reserved_words);
2095 }
2096 }
2097 }
2098
2099 Expression::Star(star) => {
2100 if let Some(ref mut table) = star.table {
2101 maybe_quote(table, reserved_words);
2102 }
2103 if let Some(ref mut except_ids) = star.except {
2104 for id in except_ids {
2105 maybe_quote(id, reserved_words);
2106 }
2107 }
2108 if let Some(ref mut replace_aliases) = star.replace {
2109 for alias in replace_aliases {
2110 maybe_quote(&mut alias.alias, reserved_words);
2111 quote_identifiers_recursive(&mut alias.this, reserved_words);
2112 }
2113 }
2114 if let Some(ref mut rename_pairs) = star.rename {
2115 for (from, to) in rename_pairs {
2116 maybe_quote(from, reserved_words);
2117 maybe_quote(to, reserved_words);
2118 }
2119 }
2120 }
2121
2122 Expression::Alias(alias) => {
2124 maybe_quote(&mut alias.alias, reserved_words);
2125 for ca in &mut alias.column_aliases {
2126 maybe_quote(ca, reserved_words);
2127 }
2128 quote_identifiers_recursive(&mut alias.this, reserved_words);
2129 }
2130
2131 Expression::Select(select) => {
2133 for e in &mut select.expressions {
2134 quote_identifiers_recursive(e, reserved_words);
2135 }
2136 if let Some(ref mut from) = select.from {
2137 for e in &mut from.expressions {
2138 quote_identifiers_recursive(e, reserved_words);
2139 }
2140 }
2141 for join in &mut select.joins {
2142 quote_join(join, reserved_words);
2143 }
2144 for lv in &mut select.lateral_views {
2145 quote_lateral_view(lv, reserved_words);
2146 }
2147 if let Some(ref mut prewhere) = select.prewhere {
2148 quote_identifiers_recursive(prewhere, reserved_words);
2149 }
2150 if let Some(ref mut wh) = select.where_clause {
2151 quote_identifiers_recursive(&mut wh.this, reserved_words);
2152 }
2153 if let Some(ref mut gb) = select.group_by {
2154 for e in &mut gb.expressions {
2155 quote_identifiers_recursive(e, reserved_words);
2156 }
2157 }
2158 if let Some(ref mut hv) = select.having {
2159 quote_identifiers_recursive(&mut hv.this, reserved_words);
2160 }
2161 if let Some(ref mut q) = select.qualify {
2162 quote_identifiers_recursive(&mut q.this, reserved_words);
2163 }
2164 if let Some(ref mut ob) = select.order_by {
2165 for o in &mut ob.expressions {
2166 quote_identifiers_recursive(&mut o.this, reserved_words);
2167 }
2168 }
2169 if let Some(ref mut lim) = select.limit {
2170 quote_identifiers_recursive(&mut lim.this, reserved_words);
2171 }
2172 if let Some(ref mut off) = select.offset {
2173 quote_identifiers_recursive(&mut off.this, reserved_words);
2174 }
2175 if let Some(ref mut with) = select.with {
2176 quote_with(with, reserved_words);
2177 }
2178 if let Some(ref mut windows) = select.windows {
2179 for nw in windows {
2180 maybe_quote(&mut nw.name, reserved_words);
2181 quote_over(&mut nw.spec, reserved_words);
2182 }
2183 }
2184 if let Some(ref mut distinct_on) = select.distinct_on {
2185 for e in distinct_on {
2186 quote_identifiers_recursive(e, reserved_words);
2187 }
2188 }
2189 if let Some(ref mut limit_by) = select.limit_by {
2190 for e in limit_by {
2191 quote_identifiers_recursive(e, reserved_words);
2192 }
2193 }
2194 if let Some(ref mut settings) = select.settings {
2195 for e in settings {
2196 quote_identifiers_recursive(e, reserved_words);
2197 }
2198 }
2199 if let Some(ref mut format) = select.format {
2200 quote_identifiers_recursive(format, reserved_words);
2201 }
2202 }
2203
2204 Expression::Union(u) => {
2206 quote_identifiers_recursive(&mut u.left, reserved_words);
2207 quote_identifiers_recursive(&mut u.right, reserved_words);
2208 if let Some(ref mut with) = u.with {
2209 quote_with(with, reserved_words);
2210 }
2211 if let Some(ref mut ob) = u.order_by {
2212 for o in &mut ob.expressions {
2213 quote_identifiers_recursive(&mut o.this, reserved_words);
2214 }
2215 }
2216 if let Some(ref mut lim) = u.limit {
2217 quote_identifiers_recursive(lim, reserved_words);
2218 }
2219 if let Some(ref mut off) = u.offset {
2220 quote_identifiers_recursive(off, reserved_words);
2221 }
2222 }
2223 Expression::Intersect(i) => {
2224 quote_identifiers_recursive(&mut i.left, reserved_words);
2225 quote_identifiers_recursive(&mut i.right, reserved_words);
2226 if let Some(ref mut with) = i.with {
2227 quote_with(with, reserved_words);
2228 }
2229 if let Some(ref mut ob) = i.order_by {
2230 for o in &mut ob.expressions {
2231 quote_identifiers_recursive(&mut o.this, reserved_words);
2232 }
2233 }
2234 if let Some(limit) = &mut i.limit {
2235 quote_identifiers_recursive(limit, reserved_words);
2236 }
2237 if let Some(offset) = &mut i.offset {
2238 quote_identifiers_recursive(offset, reserved_words);
2239 }
2240 }
2241 Expression::Except(e) => {
2242 quote_identifiers_recursive(&mut e.left, reserved_words);
2243 quote_identifiers_recursive(&mut e.right, reserved_words);
2244 if let Some(ref mut with) = e.with {
2245 quote_with(with, reserved_words);
2246 }
2247 if let Some(ref mut ob) = e.order_by {
2248 for o in &mut ob.expressions {
2249 quote_identifiers_recursive(&mut o.this, reserved_words);
2250 }
2251 }
2252 if let Some(limit) = &mut e.limit {
2253 quote_identifiers_recursive(limit, reserved_words);
2254 }
2255 if let Some(offset) = &mut e.offset {
2256 quote_identifiers_recursive(offset, reserved_words);
2257 }
2258 }
2259
2260 Expression::Subquery(sq) => {
2262 quote_identifiers_recursive(&mut sq.this, reserved_words);
2263 if let Some(ref mut alias) = sq.alias {
2264 maybe_quote(alias, reserved_words);
2265 }
2266 for ca in &mut sq.column_aliases {
2267 maybe_quote(ca, reserved_words);
2268 }
2269 if let Some(ref mut ob) = sq.order_by {
2270 for o in &mut ob.expressions {
2271 quote_identifiers_recursive(&mut o.this, reserved_words);
2272 }
2273 }
2274 }
2275
2276 Expression::Insert(ins) => {
2278 quote_table_ref(&mut ins.table, reserved_words);
2279 for c in &mut ins.columns {
2280 maybe_quote(c, reserved_words);
2281 }
2282 for row in &mut ins.values {
2283 for e in row {
2284 quote_identifiers_recursive(e, reserved_words);
2285 }
2286 }
2287 if let Some(ref mut q) = ins.query {
2288 quote_identifiers_recursive(q, reserved_words);
2289 }
2290 for (id, val) in &mut ins.partition {
2291 maybe_quote(id, reserved_words);
2292 if let Some(ref mut v) = val {
2293 quote_identifiers_recursive(v, reserved_words);
2294 }
2295 }
2296 for e in &mut ins.returning {
2297 quote_identifiers_recursive(e, reserved_words);
2298 }
2299 if let Some(ref mut on_conflict) = ins.on_conflict {
2300 quote_identifiers_recursive(on_conflict, reserved_words);
2301 }
2302 if let Some(ref mut with) = ins.with {
2303 quote_with(with, reserved_words);
2304 }
2305 if let Some(ref mut alias) = ins.alias {
2306 maybe_quote(alias, reserved_words);
2307 }
2308 if let Some(ref mut src_alias) = ins.source_alias {
2309 maybe_quote(src_alias, reserved_words);
2310 }
2311 }
2312
2313 Expression::Update(upd) => {
2314 quote_table_ref(&mut upd.table, reserved_words);
2315 for tr in &mut upd.extra_tables {
2316 quote_table_ref(tr, reserved_words);
2317 }
2318 for join in &mut upd.table_joins {
2319 quote_join(join, reserved_words);
2320 }
2321 for (id, val) in &mut upd.set {
2322 maybe_quote(id, reserved_words);
2323 quote_identifiers_recursive(val, reserved_words);
2324 }
2325 if let Some(ref mut from) = upd.from_clause {
2326 for e in &mut from.expressions {
2327 quote_identifiers_recursive(e, reserved_words);
2328 }
2329 }
2330 for join in &mut upd.from_joins {
2331 quote_join(join, reserved_words);
2332 }
2333 if let Some(ref mut wh) = upd.where_clause {
2334 quote_identifiers_recursive(&mut wh.this, reserved_words);
2335 }
2336 for e in &mut upd.returning {
2337 quote_identifiers_recursive(e, reserved_words);
2338 }
2339 if let Some(ref mut with) = upd.with {
2340 quote_with(with, reserved_words);
2341 }
2342 }
2343
2344 Expression::Delete(del) => {
2345 quote_table_ref(&mut del.table, reserved_words);
2346 if let Some(ref mut alias) = del.alias {
2347 maybe_quote(alias, reserved_words);
2348 }
2349 for tr in &mut del.using {
2350 quote_table_ref(tr, reserved_words);
2351 }
2352 if let Some(ref mut wh) = del.where_clause {
2353 quote_identifiers_recursive(&mut wh.this, reserved_words);
2354 }
2355 if let Some(ref mut with) = del.with {
2356 quote_with(with, reserved_words);
2357 }
2358 }
2359
2360 Expression::And(bin)
2362 | Expression::Or(bin)
2363 | Expression::Eq(bin)
2364 | Expression::Neq(bin)
2365 | Expression::Lt(bin)
2366 | Expression::Lte(bin)
2367 | Expression::Gt(bin)
2368 | Expression::Gte(bin)
2369 | Expression::Add(bin)
2370 | Expression::Sub(bin)
2371 | Expression::Mul(bin)
2372 | Expression::Div(bin)
2373 | Expression::Mod(bin)
2374 | Expression::BitwiseAnd(bin)
2375 | Expression::BitwiseOr(bin)
2376 | Expression::BitwiseXor(bin)
2377 | Expression::Concat(bin)
2378 | Expression::Adjacent(bin)
2379 | Expression::TsMatch(bin)
2380 | Expression::PropertyEQ(bin)
2381 | Expression::ArrayContainsAll(bin)
2382 | Expression::ArrayContainedBy(bin)
2383 | Expression::ArrayOverlaps(bin)
2384 | Expression::JSONBContainsAllTopKeys(bin)
2385 | Expression::JSONBContainsAnyTopKeys(bin)
2386 | Expression::JSONBDeleteAtPath(bin)
2387 | Expression::ExtendsLeft(bin)
2388 | Expression::ExtendsRight(bin)
2389 | Expression::Is(bin)
2390 | Expression::NullSafeEq(bin)
2391 | Expression::NullSafeNeq(bin)
2392 | Expression::Glob(bin)
2393 | Expression::Match(bin)
2394 | Expression::MemberOf(bin)
2395 | Expression::BitwiseLeftShift(bin)
2396 | Expression::BitwiseRightShift(bin) => {
2397 quote_identifiers_recursive(&mut bin.left, reserved_words);
2398 quote_identifiers_recursive(&mut bin.right, reserved_words);
2399 }
2400
2401 Expression::Like(like) | Expression::ILike(like) => {
2403 quote_identifiers_recursive(&mut like.left, reserved_words);
2404 quote_identifiers_recursive(&mut like.right, reserved_words);
2405 if let Some(ref mut esc) = like.escape {
2406 quote_identifiers_recursive(esc, reserved_words);
2407 }
2408 }
2409
2410 Expression::Not(un) | Expression::Neg(un) | Expression::BitwiseNot(un) => {
2412 quote_identifiers_recursive(&mut un.this, reserved_words);
2413 }
2414
2415 Expression::In(in_expr) => {
2417 quote_identifiers_recursive(&mut in_expr.this, reserved_words);
2418 for e in &mut in_expr.expressions {
2419 quote_identifiers_recursive(e, reserved_words);
2420 }
2421 if let Some(ref mut q) = in_expr.query {
2422 quote_identifiers_recursive(q, reserved_words);
2423 }
2424 if let Some(ref mut un) = in_expr.unnest {
2425 quote_identifiers_recursive(un, reserved_words);
2426 }
2427 }
2428
2429 Expression::Between(bw) => {
2430 quote_identifiers_recursive(&mut bw.this, reserved_words);
2431 quote_identifiers_recursive(&mut bw.low, reserved_words);
2432 quote_identifiers_recursive(&mut bw.high, reserved_words);
2433 }
2434
2435 Expression::IsNull(is_null) => {
2436 quote_identifiers_recursive(&mut is_null.this, reserved_words);
2437 }
2438
2439 Expression::IsTrue(is_tf) | Expression::IsFalse(is_tf) => {
2440 quote_identifiers_recursive(&mut is_tf.this, reserved_words);
2441 }
2442
2443 Expression::Exists(ex) => {
2444 quote_identifiers_recursive(&mut ex.this, reserved_words);
2445 }
2446
2447 Expression::Function(func) => {
2449 for arg in &mut func.args {
2450 quote_identifiers_recursive(arg, reserved_words);
2451 }
2452 }
2453
2454 Expression::AggregateFunction(agg) => {
2455 for arg in &mut agg.args {
2456 quote_identifiers_recursive(arg, reserved_words);
2457 }
2458 if let Some(ref mut filter) = agg.filter {
2459 quote_identifiers_recursive(filter, reserved_words);
2460 }
2461 for o in &mut agg.order_by {
2462 quote_identifiers_recursive(&mut o.this, reserved_words);
2463 }
2464 }
2465
2466 Expression::WindowFunction(wf) => {
2467 quote_identifiers_recursive(&mut wf.this, reserved_words);
2468 quote_over(&mut wf.over, reserved_words);
2469 }
2470
2471 Expression::Case(case) => {
2473 if let Some(ref mut operand) = case.operand {
2474 quote_identifiers_recursive(operand, reserved_words);
2475 }
2476 for (when, then) in &mut case.whens {
2477 quote_identifiers_recursive(when, reserved_words);
2478 quote_identifiers_recursive(then, reserved_words);
2479 }
2480 if let Some(ref mut else_) = case.else_ {
2481 quote_identifiers_recursive(else_, reserved_words);
2482 }
2483 }
2484
2485 Expression::Cast(cast) | Expression::TryCast(cast) | Expression::SafeCast(cast) => {
2487 quote_identifiers_recursive(&mut cast.this, reserved_words);
2488 if let Some(ref mut fmt) = cast.format {
2489 quote_identifiers_recursive(fmt, reserved_words);
2490 }
2491 }
2492
2493 Expression::Paren(paren) => {
2495 quote_identifiers_recursive(&mut paren.this, reserved_words);
2496 }
2497
2498 Expression::Annotated(ann) => {
2499 quote_identifiers_recursive(&mut ann.this, reserved_words);
2500 }
2501
2502 Expression::With(with) => {
2504 quote_with(with, reserved_words);
2505 }
2506
2507 Expression::Cte(cte) => {
2508 maybe_quote(&mut cte.alias, reserved_words);
2509 for c in &mut cte.columns {
2510 maybe_quote(c, reserved_words);
2511 }
2512 quote_identifiers_recursive(&mut cte.this, reserved_words);
2513 }
2514
2515 Expression::From(from) => {
2517 for e in &mut from.expressions {
2518 quote_identifiers_recursive(e, reserved_words);
2519 }
2520 }
2521
2522 Expression::Join(join) => {
2523 quote_join(join, reserved_words);
2524 }
2525
2526 Expression::JoinedTable(jt) => {
2527 quote_identifiers_recursive(&mut jt.left, reserved_words);
2528 for join in &mut jt.joins {
2529 quote_join(join, reserved_words);
2530 }
2531 if let Some(ref mut alias) = jt.alias {
2532 maybe_quote(alias, reserved_words);
2533 }
2534 }
2535
2536 Expression::Where(wh) => {
2537 quote_identifiers_recursive(&mut wh.this, reserved_words);
2538 }
2539
2540 Expression::GroupBy(gb) => {
2541 for e in &mut gb.expressions {
2542 quote_identifiers_recursive(e, reserved_words);
2543 }
2544 }
2545
2546 Expression::Having(hv) => {
2547 quote_identifiers_recursive(&mut hv.this, reserved_words);
2548 }
2549
2550 Expression::OrderBy(ob) => {
2551 for o in &mut ob.expressions {
2552 quote_identifiers_recursive(&mut o.this, reserved_words);
2553 }
2554 }
2555
2556 Expression::Ordered(ord) => {
2557 quote_identifiers_recursive(&mut ord.this, reserved_words);
2558 }
2559
2560 Expression::Limit(lim) => {
2561 quote_identifiers_recursive(&mut lim.this, reserved_words);
2562 }
2563
2564 Expression::Offset(off) => {
2565 quote_identifiers_recursive(&mut off.this, reserved_words);
2566 }
2567 Expression::Fetch(fetch) => {
2568 if let Some(count) = &mut fetch.count {
2569 quote_identifiers_recursive(count, reserved_words);
2570 }
2571 }
2572
2573 Expression::Qualify(q) => {
2574 quote_identifiers_recursive(&mut q.this, reserved_words);
2575 }
2576
2577 Expression::Window(ws) => {
2578 for e in &mut ws.partition_by {
2579 quote_identifiers_recursive(e, reserved_words);
2580 }
2581 for o in &mut ws.order_by {
2582 quote_identifiers_recursive(&mut o.this, reserved_words);
2583 }
2584 }
2585
2586 Expression::Over(over) => {
2587 quote_over(over, reserved_words);
2588 }
2589
2590 Expression::WithinGroup(wg) => {
2591 quote_identifiers_recursive(&mut wg.this, reserved_words);
2592 for o in &mut wg.order_by {
2593 quote_identifiers_recursive(&mut o.this, reserved_words);
2594 }
2595 }
2596
2597 Expression::Pivot(piv) => {
2599 quote_identifiers_recursive(&mut piv.this, reserved_words);
2600 for e in &mut piv.expressions {
2601 quote_identifiers_recursive(e, reserved_words);
2602 }
2603 for f in &mut piv.fields {
2604 quote_identifiers_recursive(f, reserved_words);
2605 }
2606 if let Some(ref mut alias) = piv.alias {
2607 maybe_quote(alias, reserved_words);
2608 }
2609 }
2610
2611 Expression::Unpivot(unpiv) => {
2612 quote_identifiers_recursive(&mut unpiv.this, reserved_words);
2613 maybe_quote(&mut unpiv.value_column, reserved_words);
2614 maybe_quote(&mut unpiv.name_column, reserved_words);
2615 for e in &mut unpiv.columns {
2616 quote_identifiers_recursive(e, reserved_words);
2617 }
2618 if let Some(ref mut alias) = unpiv.alias {
2619 maybe_quote(alias, reserved_words);
2620 }
2621 }
2622
2623 Expression::Values(vals) => {
2625 for tuple in &mut vals.expressions {
2626 for e in &mut tuple.expressions {
2627 quote_identifiers_recursive(e, reserved_words);
2628 }
2629 }
2630 if let Some(ref mut alias) = vals.alias {
2631 maybe_quote(alias, reserved_words);
2632 }
2633 for ca in &mut vals.column_aliases {
2634 maybe_quote(ca, reserved_words);
2635 }
2636 }
2637
2638 Expression::Array(arr) => {
2640 for e in &mut arr.expressions {
2641 quote_identifiers_recursive(e, reserved_words);
2642 }
2643 }
2644
2645 Expression::Struct(st) => {
2646 for (_name, e) in &mut st.fields {
2647 quote_identifiers_recursive(e, reserved_words);
2648 }
2649 }
2650
2651 Expression::Tuple(tup) => {
2652 for e in &mut tup.expressions {
2653 quote_identifiers_recursive(e, reserved_words);
2654 }
2655 }
2656
2657 Expression::Subscript(sub) => {
2659 quote_identifiers_recursive(&mut sub.this, reserved_words);
2660 quote_identifiers_recursive(&mut sub.index, reserved_words);
2661 }
2662
2663 Expression::Dot(dot) => {
2664 quote_identifiers_recursive(&mut dot.this, reserved_words);
2665 maybe_quote(&mut dot.field, reserved_words);
2666 }
2667
2668 Expression::ScopeResolution(sr) => {
2669 if let Some(ref mut this) = sr.this {
2670 quote_identifiers_recursive(this, reserved_words);
2671 }
2672 quote_identifiers_recursive(&mut sr.expression, reserved_words);
2673 }
2674
2675 Expression::Lateral(lat) => {
2677 quote_identifiers_recursive(&mut lat.this, reserved_words);
2678 }
2680
2681 Expression::DPipe(dpipe) => {
2683 quote_identifiers_recursive(&mut dpipe.this, reserved_words);
2684 quote_identifiers_recursive(&mut dpipe.expression, reserved_words);
2685 }
2686
2687 Expression::Merge(merge) => {
2689 quote_identifiers_recursive(&mut merge.this, reserved_words);
2690 quote_identifiers_recursive(&mut merge.using, reserved_words);
2691 if let Some(ref mut on) = merge.on {
2692 quote_identifiers_recursive(on, reserved_words);
2693 }
2694 if let Some(ref mut whens) = merge.whens {
2695 quote_identifiers_recursive(whens, reserved_words);
2696 }
2697 if let Some(ref mut with) = merge.with_ {
2698 quote_identifiers_recursive(with, reserved_words);
2699 }
2700 if let Some(ref mut ret) = merge.returning {
2701 quote_identifiers_recursive(ret, reserved_words);
2702 }
2703 }
2704
2705 Expression::LateralView(lv) => {
2707 quote_lateral_view(lv, reserved_words);
2708 }
2709
2710 Expression::Anonymous(anon) => {
2712 quote_identifiers_recursive(&mut anon.this, reserved_words);
2713 for e in &mut anon.expressions {
2714 quote_identifiers_recursive(e, reserved_words);
2715 }
2716 }
2717
2718 Expression::Filter(filter) => {
2720 quote_identifiers_recursive(&mut filter.this, reserved_words);
2721 quote_identifiers_recursive(&mut filter.expression, reserved_words);
2722 }
2723
2724 Expression::Returning(ret) => {
2726 for e in &mut ret.expressions {
2727 quote_identifiers_recursive(e, reserved_words);
2728 }
2729 }
2730
2731 Expression::BracedWildcard(inner) => {
2733 quote_identifiers_recursive(inner, reserved_words);
2734 }
2735
2736 Expression::ReturnStmt(inner) => {
2738 quote_identifiers_recursive(inner, reserved_words);
2739 }
2740
2741 Expression::Literal(_)
2743 | Expression::Boolean(_)
2744 | Expression::Null(_)
2745 | Expression::DataType(_)
2746 | Expression::Raw(_)
2747 | Expression::Placeholder(_)
2748 | Expression::CurrentDate(_)
2749 | Expression::CurrentTime(_)
2750 | Expression::CurrentTimestamp(_)
2751 | Expression::CurrentTimestampLTZ(_)
2752 | Expression::SessionUser(_)
2753 | Expression::RowNumber(_)
2754 | Expression::Rank(_)
2755 | Expression::DenseRank(_)
2756 | Expression::PercentRank(_)
2757 | Expression::CumeDist(_)
2758 | Expression::Random(_)
2759 | Expression::Pi(_)
2760 | Expression::JSONPathRoot(_) => {
2761 }
2763
2764 _ => {}
2768 }
2769}
2770
2771fn quote_join(join: &mut Join, reserved_words: &HashSet<&str>) {
2773 quote_identifiers_recursive(&mut join.this, reserved_words);
2774 if let Some(ref mut on) = join.on {
2775 quote_identifiers_recursive(on, reserved_words);
2776 }
2777 for id in &mut join.using {
2778 maybe_quote(id, reserved_words);
2779 }
2780 if let Some(ref mut mc) = join.match_condition {
2781 quote_identifiers_recursive(mc, reserved_words);
2782 }
2783 for piv in &mut join.pivots {
2784 quote_identifiers_recursive(piv, reserved_words);
2785 }
2786}
2787
2788fn quote_with(with: &mut With, reserved_words: &HashSet<&str>) {
2790 for cte in &mut with.ctes {
2791 maybe_quote(&mut cte.alias, reserved_words);
2792 for c in &mut cte.columns {
2793 maybe_quote(c, reserved_words);
2794 }
2795 for k in &mut cte.key_expressions {
2796 maybe_quote(k, reserved_words);
2797 }
2798 quote_identifiers_recursive(&mut cte.this, reserved_words);
2799 }
2800}
2801
2802fn quote_over(over: &mut Over, reserved_words: &HashSet<&str>) {
2804 if let Some(ref mut wn) = over.window_name {
2805 maybe_quote(wn, reserved_words);
2806 }
2807 for e in &mut over.partition_by {
2808 quote_identifiers_recursive(e, reserved_words);
2809 }
2810 for o in &mut over.order_by {
2811 quote_identifiers_recursive(&mut o.this, reserved_words);
2812 }
2813 if let Some(ref mut alias) = over.alias {
2814 maybe_quote(alias, reserved_words);
2815 }
2816}
2817
2818fn quote_table_ref(table_ref: &mut TableRef, reserved_words: &HashSet<&str>) {
2820 maybe_quote(&mut table_ref.name, reserved_words);
2821 if let Some(ref mut schema) = table_ref.schema {
2822 maybe_quote(schema, reserved_words);
2823 }
2824 if let Some(ref mut catalog) = table_ref.catalog {
2825 maybe_quote(catalog, reserved_words);
2826 }
2827 if let Some(ref mut alias) = table_ref.alias {
2828 maybe_quote(alias, reserved_words);
2829 }
2830 for ca in &mut table_ref.column_aliases {
2831 maybe_quote(ca, reserved_words);
2832 }
2833 for p in &mut table_ref.partitions {
2834 maybe_quote(p, reserved_words);
2835 }
2836 for h in &mut table_ref.hints {
2837 quote_identifiers_recursive(h, reserved_words);
2838 }
2839}
2840
2841fn quote_lateral_view(lv: &mut LateralView, reserved_words: &HashSet<&str>) {
2843 quote_identifiers_recursive(&mut lv.this, reserved_words);
2844 if let Some(ref mut ta) = lv.table_alias {
2845 maybe_quote(ta, reserved_words);
2846 }
2847 for ca in &mut lv.column_aliases {
2848 maybe_quote(ca, reserved_words);
2849 }
2850}
2851
2852pub fn quote_identifiers(expression: Expression, dialect: Option<DialectType>) -> Expression {
2863 let reserved_words = get_reserved_words(dialect);
2864 let mut result = expression;
2865 quote_identifiers_recursive(&mut result, &reserved_words);
2866 result
2867}
2868
2869pub fn pushdown_cte_alias_columns(_scope: &Scope) {
2874 }
2877
2878fn pushdown_cte_alias_columns_with(with: &mut With) {
2879 for cte in &mut with.ctes {
2880 if cte.columns.is_empty() {
2881 continue;
2882 }
2883
2884 if let Expression::Select(select) = &mut cte.this {
2885 let mut next_expressions = Vec::with_capacity(select.expressions.len());
2886
2887 for (i, projection) in select.expressions.iter().enumerate() {
2888 let Some(alias_name) = cte.columns.get(i) else {
2889 next_expressions.push(projection.clone());
2890 continue;
2891 };
2892
2893 match projection {
2894 Expression::Alias(existing) => {
2895 let mut aliased = existing.clone();
2896 aliased.alias = alias_name.clone();
2897 next_expressions.push(Expression::Alias(aliased));
2898 }
2899 _ => {
2900 next_expressions.push(create_alias(projection.clone(), &alias_name.name));
2901 }
2902 }
2903 }
2904
2905 select.expressions = next_expressions;
2906 }
2907 }
2908}
2909
2910fn get_scope_columns(scope: &Scope) -> Vec<ColumnRef> {
2916 let mut columns = Vec::new();
2917 collect_columns(&scope.expression, &mut columns);
2918 columns
2919}
2920
2921#[derive(Debug, Clone)]
2923struct ColumnRef {
2924 table: Option<String>,
2925 name: String,
2926}
2927
2928fn collect_columns(expr: &Expression, columns: &mut Vec<ColumnRef>) {
2930 match expr {
2931 Expression::Column(col) => {
2932 columns.push(ColumnRef {
2933 table: col.table.as_ref().map(|t| t.name.clone()),
2934 name: col.name.name.clone(),
2935 });
2936 }
2937 Expression::Select(select) => {
2938 for e in &select.expressions {
2939 collect_columns(e, columns);
2940 }
2941 if let Some(from) = &select.from {
2942 for e in &from.expressions {
2943 collect_columns(e, columns);
2944 }
2945 }
2946 if let Some(where_clause) = &select.where_clause {
2947 collect_columns(&where_clause.this, columns);
2948 }
2949 if let Some(group_by) = &select.group_by {
2950 for e in &group_by.expressions {
2951 collect_columns(e, columns);
2952 }
2953 }
2954 if let Some(having) = &select.having {
2955 collect_columns(&having.this, columns);
2956 }
2957 if let Some(order_by) = &select.order_by {
2958 for o in &order_by.expressions {
2959 collect_columns(&o.this, columns);
2960 }
2961 }
2962 for join in &select.joins {
2963 collect_columns(&join.this, columns);
2964 if let Some(on) = &join.on {
2965 collect_columns(on, columns);
2966 }
2967 }
2968 }
2969 Expression::Alias(alias) => {
2970 collect_columns(&alias.this, columns);
2971 }
2972 Expression::Function(func) => {
2973 for arg in &func.args {
2974 collect_columns(arg, columns);
2975 }
2976 }
2977 Expression::AggregateFunction(agg) => {
2978 for arg in &agg.args {
2979 collect_columns(arg, columns);
2980 }
2981 }
2982 Expression::And(bin)
2983 | Expression::Or(bin)
2984 | Expression::Eq(bin)
2985 | Expression::Neq(bin)
2986 | Expression::Lt(bin)
2987 | Expression::Lte(bin)
2988 | Expression::Gt(bin)
2989 | Expression::Gte(bin)
2990 | Expression::Add(bin)
2991 | Expression::Sub(bin)
2992 | Expression::Mul(bin)
2993 | Expression::Div(bin) => {
2994 collect_columns(&bin.left, columns);
2995 collect_columns(&bin.right, columns);
2996 }
2997 Expression::Not(unary) | Expression::Neg(unary) => {
2998 collect_columns(&unary.this, columns);
2999 }
3000 Expression::Paren(paren) => {
3001 collect_columns(&paren.this, columns);
3002 }
3003 Expression::Case(case) => {
3004 if let Some(operand) = &case.operand {
3005 collect_columns(operand, columns);
3006 }
3007 for (when, then) in &case.whens {
3008 collect_columns(when, columns);
3009 collect_columns(then, columns);
3010 }
3011 if let Some(else_) = &case.else_ {
3012 collect_columns(else_, columns);
3013 }
3014 }
3015 Expression::Cast(cast) => {
3016 collect_columns(&cast.this, columns);
3017 }
3018 Expression::In(in_expr) => {
3019 collect_columns(&in_expr.this, columns);
3020 for e in &in_expr.expressions {
3021 collect_columns(e, columns);
3022 }
3023 if let Some(query) = &in_expr.query {
3024 collect_columns(query, columns);
3025 }
3026 }
3027 Expression::Between(between) => {
3028 collect_columns(&between.this, columns);
3029 collect_columns(&between.low, columns);
3030 collect_columns(&between.high, columns);
3031 }
3032 Expression::Subquery(subquery) => {
3033 collect_columns(&subquery.this, columns);
3034 }
3035 _ => {}
3036 }
3037}
3038
3039fn get_unqualified_columns(scope: &Scope) -> Vec<ColumnRef> {
3041 get_scope_columns(scope)
3042 .into_iter()
3043 .filter(|c| c.table.is_none())
3044 .collect()
3045}
3046
3047fn get_external_columns(scope: &Scope) -> Vec<ColumnRef> {
3049 let source_names: HashSet<_> = scope.sources.keys().cloned().collect();
3050
3051 get_scope_columns(scope)
3052 .into_iter()
3053 .filter(|c| {
3054 if let Some(table) = &c.table {
3055 !source_names.contains(table)
3056 } else {
3057 false
3058 }
3059 })
3060 .collect()
3061}
3062
3063fn is_correlated_subquery(scope: &Scope) -> bool {
3065 scope.can_be_correlated && !get_external_columns(scope).is_empty()
3066}
3067
3068fn is_star_column(col: &Column) -> bool {
3070 col.name.name == "*"
3071}
3072
3073fn create_qualified_column(name: &str, table: Option<&str>) -> Expression {
3075 Expression::boxed_column(Column {
3076 name: Identifier::new(name),
3077 table: table.map(Identifier::new),
3078 join_mark: false,
3079 trailing_comments: vec![],
3080 span: None,
3081 inferred_type: None,
3082 })
3083}
3084
3085fn create_alias(expr: Expression, alias_name: &str) -> Expression {
3087 Expression::Alias(Box::new(Alias {
3088 this: expr,
3089 alias: Identifier::new(alias_name),
3090 column_aliases: vec![],
3091 alias_explicit_as: false,
3092 alias_keyword: None,
3093 pre_alias_comments: vec![],
3094 trailing_comments: vec![],
3095 inferred_type: None,
3096 }))
3097}
3098
3099fn get_output_name(expr: &Expression) -> Option<String> {
3101 match expr {
3102 Expression::Column(col) => Some(col.name.name.clone()),
3103 Expression::Alias(alias) => Some(alias.alias.name.clone()),
3104 Expression::Identifier(id) => Some(id.name.clone()),
3105 _ => None,
3106 }
3107}
3108
3109#[cfg(test)]
3110mod tests {
3111 use super::*;
3112 use crate::expressions::DataType;
3113 use crate::generator::Generator;
3114 use crate::parser::Parser;
3115 use crate::scope::build_scope;
3116 use crate::{MappingSchema, Schema};
3117
3118 fn gen(expr: &Expression) -> String {
3119 Generator::new().generate(expr).unwrap()
3120 }
3121
3122 fn parse(sql: &str) -> Expression {
3123 Parser::parse_sql(sql).expect("Failed to parse")[0].clone()
3124 }
3125
3126 #[test]
3127 fn test_qualify_columns_options() {
3128 let options = QualifyColumnsOptions::new()
3129 .with_expand_alias_refs(true)
3130 .with_expand_stars(false)
3131 .with_dialect(DialectType::PostgreSQL)
3132 .with_allow_partial(true);
3133
3134 assert!(options.expand_alias_refs);
3135 assert!(!options.expand_stars);
3136 assert_eq!(options.dialect, Some(DialectType::PostgreSQL));
3137 assert!(options.allow_partial_qualification);
3138 }
3139
3140 #[test]
3141 fn test_qualify_cte_chain_uses_lexically_selected_sources() {
3142 let mut schema = MappingSchema::new();
3143 schema
3144 .add_table("raw_orders", &[("amount".into(), DataType::Text)], None)
3145 .unwrap();
3146 let sql = "WITH transformed AS (SELECT CAST(amount AS INT) AS amount FROM raw_orders), final AS (SELECT amount FROM transformed) SELECT amount FROM final";
3147 let qualified =
3148 qualify_columns(parse(sql), &schema, &QualifyColumnsOptions::new()).unwrap();
3149 assert_eq!(gen(&qualified), "WITH transformed AS (SELECT CAST(raw_orders.amount AS INT) AS amount FROM raw_orders), final AS (SELECT transformed.amount AS amount FROM transformed) SELECT final.amount AS amount FROM final");
3150 }
3151
3152 #[test]
3153 fn test_qualify_does_not_capture_unrelated_schema_tables() {
3154 let mut schema = MappingSchema::new();
3155 schema
3156 .add_table("unrelated", &[("n".into(), DataType::Text)], None)
3157 .unwrap();
3158 for sql in ["SELECT n FROM missing", "SELECT unrelated.n FROM missing"] {
3159 assert!(
3160 qualify_columns(parse(sql), &schema, &QualifyColumnsOptions::new()).is_err(),
3161 "{sql}"
3162 );
3163 }
3164 let sql = "SELECT n FROM missing";
3165 let qualified = qualify_columns(
3166 parse(sql),
3167 &schema,
3168 &QualifyColumnsOptions::new().with_allow_partial(true),
3169 )
3170 .unwrap();
3171 assert_eq!(gen(&qualified), "SELECT n AS n FROM missing");
3172 assert!(matches!(
3173 qualify_columns(
3174 parse(
3175 "WITH a AS (SELECT 1 AS n), b AS (SELECT 2 AS n) SELECT n FROM a CROSS JOIN b"
3176 ),
3177 &schema,
3178 &QualifyColumnsOptions::new()
3179 ),
3180 Err(QualifyColumnsError::AmbiguousColumn(_))
3181 ));
3182 }
3183
3184 #[test]
3185 fn test_qualify_correlated_cte_alias_and_nested_shadowing() {
3186 let schema = MappingSchema::new();
3187 for (sql, expected) in [
3188 ("WITH a AS (SELECT 1 AS n) SELECT (SELECT n) AS result FROM a AS outer_a", "(SELECT outer_a.n AS n)"),
3189 ("WITH a AS (SELECT 1 AS n) SELECT (SELECT (SELECT n)) AS result FROM a AS outer_a", "(SELECT outer_a.n AS n)"),
3190 ("WITH a AS (SELECT 1 AS n) SELECT (WITH b AS (SELECT n) SELECT n FROM b) AS result FROM a AS outer_a", "WITH b AS (SELECT outer_a.n AS n) SELECT b.n AS n FROM b"),
3191 ("WITH a AS (SELECT 1 AS n) SELECT (WITH a AS (SELECT 2 AS m) SELECT m FROM a) AS result FROM a", "SELECT a.m AS m FROM a"),
3192 ("WITH a AS (SELECT 1 AS n) SELECT b.n FROM a JOIN LATERAL (SELECT a.n) AS b ON TRUE", "SELECT a.n AS n"),
3193 ("WITH a AS (SELECT 1 AS n) SELECT b.n FROM a, LATERAL (SELECT a.n) AS b", "SELECT a.n AS n"),
3194 ] {
3195 let qualified = qualify_columns(parse(sql), &schema, &QualifyColumnsOptions::new())
3196 .unwrap_or_else(|error| panic!("{sql}: {error}"));
3197 assert!(gen(&qualified).contains(expected), "{}", gen(&qualified));
3198 }
3199 }
3200
3201 #[test]
3202 fn test_get_scope_columns() {
3203 let expr = parse("SELECT a, b FROM t WHERE c = 1");
3204 let scope = build_scope(&expr);
3205 let columns = get_scope_columns(&scope);
3206
3207 assert!(columns.iter().any(|c| c.name == "a"));
3208 assert!(columns.iter().any(|c| c.name == "b"));
3209 assert!(columns.iter().any(|c| c.name == "c"));
3210 }
3211
3212 #[test]
3213 fn test_get_unqualified_columns() {
3214 let expr = parse("SELECT t.a, b FROM t");
3215 let scope = build_scope(&expr);
3216 let unqualified = get_unqualified_columns(&scope);
3217
3218 assert!(unqualified.iter().any(|c| c.name == "b"));
3220 assert!(!unqualified.iter().any(|c| c.name == "a"));
3221 }
3222
3223 #[test]
3224 fn test_is_star_column() {
3225 let col = Column {
3226 name: Identifier::new("*"),
3227 table: Some(Identifier::new("t")),
3228 join_mark: false,
3229 trailing_comments: vec![],
3230 span: None,
3231 inferred_type: None,
3232 };
3233 assert!(is_star_column(&col));
3234
3235 let col2 = Column {
3236 name: Identifier::new("id"),
3237 table: None,
3238 join_mark: false,
3239 trailing_comments: vec![],
3240 span: None,
3241 inferred_type: None,
3242 };
3243 assert!(!is_star_column(&col2));
3244 }
3245
3246 #[test]
3247 fn test_create_qualified_column() {
3248 let expr = create_qualified_column("id", Some("users"));
3249 let sql = gen(&expr);
3250 assert!(sql.contains("users"));
3251 assert!(sql.contains("id"));
3252 }
3253
3254 #[test]
3255 fn test_create_alias() {
3256 let col = Expression::boxed_column(Column {
3257 name: Identifier::new("value"),
3258 table: None,
3259 join_mark: false,
3260 trailing_comments: vec![],
3261 span: None,
3262 inferred_type: None,
3263 });
3264 let aliased = create_alias(col, "total");
3265 let sql = gen(&aliased);
3266 assert!(sql.contains("AS") || sql.contains("total"));
3267 }
3268
3269 #[test]
3270 fn test_qualify_columns_normalizes_struct_field_access_issue_408() {
3271 let struct_type = DataType::Struct {
3272 fields: vec![crate::expressions::StructField::new(
3273 "field_value".into(),
3274 DataType::Text,
3275 )],
3276 nested: true,
3277 };
3278 let mut schema = MappingSchema::with_dialect(DialectType::DuckDB);
3279 schema
3280 .add_table(
3281 "source_table",
3282 &[
3283 ("composite_value".into(), struct_type.clone()),
3284 (
3285 "nested_items".into(),
3286 DataType::Array {
3287 element_type: Box::new(struct_type),
3288 dimension: None,
3289 },
3290 ),
3291 ],
3292 None,
3293 )
3294 .expect("schema setup");
3295
3296 let cases = [
3297 (
3298 "SELECT composite_value.field_value AS output_value FROM source_table",
3299 "source_table",
3300 "composite_value",
3301 ),
3302 (
3303 "SELECT item.field_value AS output_value FROM source_table s \
3304 CROSS JOIN UNNEST(s.nested_items) AS expanded(item)",
3305 "expanded",
3306 "item",
3307 ),
3308 ];
3309
3310 for (sql, expected_table, expected_column) in cases {
3311 let qualified = qualify_columns(
3312 parse(sql),
3313 &schema,
3314 &QualifyColumnsOptions::new().with_dialect(DialectType::DuckDB),
3315 )
3316 .unwrap_or_else(|error| panic!("qualification failed for {sql:?}: {error}"));
3317 let Expression::Select(select) = qualified else {
3318 panic!("expected SELECT");
3319 };
3320 let Expression::Alias(alias) = &select.expressions[0] else {
3321 panic!("expected aliased projection");
3322 };
3323 let Expression::Dot(dot) = &alias.this else {
3324 panic!("expected normalized Dot, got {:?}", alias.this);
3325 };
3326 let Expression::Column(base) = &dot.this else {
3327 panic!("expected column-backed Dot");
3328 };
3329
3330 assert_eq!(
3331 base.table.as_ref().map(|table| table.name.as_str()),
3332 Some(expected_table)
3333 );
3334 assert_eq!(base.name.name, expected_column);
3335 assert_eq!(dot.field.name, "field_value");
3336 }
3337 }
3338
3339 #[test]
3340 fn test_validate_qualify_columns_success() {
3341 let expr = parse("SELECT t.a, t.b FROM t");
3343 let result = validate_qualify_columns(&expr);
3344 let _ = result;
3347 }
3348
3349 #[test]
3350 fn test_collect_columns_nested() {
3351 let expr = parse("SELECT a + b, c FROM t WHERE d > 0 GROUP BY e HAVING f = 1");
3352 let mut columns = Vec::new();
3353 collect_columns(&expr, &mut columns);
3354
3355 let names: Vec<_> = columns.iter().map(|c| c.name.as_str()).collect();
3356 assert!(names.contains(&"a"));
3357 assert!(names.contains(&"b"));
3358 assert!(names.contains(&"c"));
3359 assert!(names.contains(&"d"));
3360 assert!(names.contains(&"e"));
3361 assert!(names.contains(&"f"));
3362 }
3363
3364 #[test]
3365 fn test_collect_columns_in_case() {
3366 let expr = parse("SELECT CASE WHEN a = 1 THEN b ELSE c END FROM t");
3367 let mut columns = Vec::new();
3368 collect_columns(&expr, &mut columns);
3369
3370 let names: Vec<_> = columns.iter().map(|c| c.name.as_str()).collect();
3371 assert!(names.contains(&"a"));
3372 assert!(names.contains(&"b"));
3373 assert!(names.contains(&"c"));
3374 }
3375
3376 #[test]
3377 fn test_collect_columns_in_subquery() {
3378 let expr = parse("SELECT a FROM t WHERE b IN (SELECT c FROM s)");
3379 let mut columns = Vec::new();
3380 collect_columns(&expr, &mut columns);
3381
3382 let names: Vec<_> = columns.iter().map(|c| c.name.as_str()).collect();
3383 assert!(names.contains(&"a"));
3384 assert!(names.contains(&"b"));
3385 assert!(names.contains(&"c"));
3386 }
3387
3388 #[test]
3389 fn test_qualify_outputs_basic() {
3390 let expr = parse("SELECT a, b + c FROM t");
3391 let scope = build_scope(&expr);
3392 let result = qualify_outputs(&scope);
3393 assert!(result.is_ok());
3394 }
3395
3396 #[test]
3397 fn test_qualify_columns_expands_star_with_schema() {
3398 let expr = parse("SELECT * FROM users");
3399
3400 let mut schema = MappingSchema::new();
3401 schema
3402 .add_table(
3403 "users",
3404 &[
3405 (
3406 "id".to_string(),
3407 DataType::Int {
3408 length: None,
3409 integer_spelling: false,
3410 },
3411 ),
3412 ("name".to_string(), DataType::Text),
3413 ("email".to_string(), DataType::Text),
3414 ],
3415 None,
3416 )
3417 .expect("schema setup");
3418
3419 let result =
3420 qualify_columns(expr, &schema, &QualifyColumnsOptions::new()).expect("qualify");
3421 let sql = gen(&result);
3422
3423 assert!(!sql.contains("SELECT *"));
3424 assert!(sql.contains("users.id"));
3425 assert!(sql.contains("users.name"));
3426 assert!(sql.contains("users.email"));
3427 }
3428
3429 #[test]
3430 fn test_expand_stars_applies_except_replace_rename() {
3431 let mut schema = MappingSchema::new();
3432 schema
3433 .add_table(
3434 "t",
3435 &[
3436 ("a".to_string(), DataType::BigInt { length: None }),
3437 ("b".to_string(), DataType::BigInt { length: None }),
3438 ("c".to_string(), DataType::BigInt { length: None }),
3439 ],
3440 None,
3441 )
3442 .expect("schema setup");
3443
3444 for (dialect, sql, expected) in [
3445 (
3446 DialectType::Snowflake,
3447 "SELECT * EXCLUDE (b) FROM t",
3448 "SELECT t.a AS a, t.c AS c FROM t",
3449 ),
3450 (
3451 DialectType::BigQuery,
3452 "SELECT * EXCEPT (b) FROM t",
3453 "SELECT t.a AS a, t.c AS c FROM t",
3454 ),
3455 (
3456 DialectType::Snowflake,
3457 "SELECT t.* EXCLUDE (b) FROM t",
3458 "SELECT t.a AS a, t.c AS c FROM t",
3459 ),
3460 (
3461 DialectType::BigQuery,
3462 "SELECT * EXCEPT (t.b) FROM t",
3463 "SELECT t.a AS a, t.c AS c FROM t",
3464 ),
3465 (
3466 DialectType::BigQuery,
3467 "SELECT * REPLACE (b * 2 AS b) FROM t",
3468 "SELECT t.a AS a, t.b * 2 AS b, t.c AS c FROM t",
3469 ),
3470 (
3471 DialectType::Snowflake,
3472 "SELECT * RENAME (b AS d) FROM t",
3473 "SELECT t.a AS a, t.b AS d, t.c AS c FROM t",
3474 ),
3475 ] {
3476 let expr = crate::dialects::Dialect::get(dialect)
3477 .parse(sql)
3478 .expect("parse")
3479 .remove(0);
3480 let result =
3481 qualify_columns(expr, &schema, &QualifyColumnsOptions::new()).expect("qualify");
3482 assert_eq!(gen(&result), expected, "{sql}");
3483 }
3484 }
3485
3486 #[test]
3487 fn test_qualify_columns_expands_group_by_positions() {
3488 let expr = parse("SELECT a, b FROM t GROUP BY 1, 2");
3489
3490 let mut schema = MappingSchema::new();
3491 schema
3492 .add_table(
3493 "t",
3494 &[
3495 (
3496 "a".to_string(),
3497 DataType::Int {
3498 length: None,
3499 integer_spelling: false,
3500 },
3501 ),
3502 (
3503 "b".to_string(),
3504 DataType::Int {
3505 length: None,
3506 integer_spelling: false,
3507 },
3508 ),
3509 ],
3510 None,
3511 )
3512 .expect("schema setup");
3513
3514 let result =
3515 qualify_columns(expr, &schema, &QualifyColumnsOptions::new()).expect("qualify");
3516 let sql = gen(&result);
3517
3518 assert!(!sql.contains("GROUP BY 1"));
3519 assert!(!sql.contains("GROUP BY 2"));
3520 assert!(sql.contains("GROUP BY"));
3521 assert!(sql.contains("t.a"));
3522 assert!(sql.contains("t.b"));
3523 }
3524
3525 #[test]
3530 fn test_expand_using_simple() {
3531 let expr = parse("SELECT x.b FROM x JOIN y USING (b)");
3533
3534 let mut schema = MappingSchema::new();
3535 schema
3536 .add_table(
3537 "x",
3538 &[
3539 ("a".to_string(), DataType::BigInt { length: None }),
3540 ("b".to_string(), DataType::BigInt { length: None }),
3541 ],
3542 None,
3543 )
3544 .expect("schema setup");
3545 schema
3546 .add_table(
3547 "y",
3548 &[
3549 ("b".to_string(), DataType::BigInt { length: None }),
3550 ("c".to_string(), DataType::BigInt { length: None }),
3551 ],
3552 None,
3553 )
3554 .expect("schema setup");
3555
3556 let result =
3557 qualify_columns(expr, &schema, &QualifyColumnsOptions::new()).expect("qualify");
3558 let sql = gen(&result);
3559
3560 assert!(
3562 !sql.contains("USING"),
3563 "USING should be replaced with ON: {sql}"
3564 );
3565 assert!(
3566 sql.contains("ON x.b = y.b"),
3567 "ON condition should be x.b = y.b: {sql}"
3568 );
3569 assert!(sql.contains("SELECT x.b"), "SELECT should keep x.b: {sql}");
3571 }
3572
3573 #[test]
3574 fn test_expand_using_unqualified_coalesce() {
3575 let expr = parse("SELECT b FROM x JOIN y USING(b)");
3577
3578 let mut schema = MappingSchema::new();
3579 schema
3580 .add_table(
3581 "x",
3582 &[
3583 ("a".to_string(), DataType::BigInt { length: None }),
3584 ("b".to_string(), DataType::BigInt { length: None }),
3585 ],
3586 None,
3587 )
3588 .expect("schema setup");
3589 schema
3590 .add_table(
3591 "y",
3592 &[
3593 ("b".to_string(), DataType::BigInt { length: None }),
3594 ("c".to_string(), DataType::BigInt { length: None }),
3595 ],
3596 None,
3597 )
3598 .expect("schema setup");
3599
3600 let result =
3601 qualify_columns(expr, &schema, &QualifyColumnsOptions::new()).expect("qualify");
3602 let sql = gen(&result);
3603
3604 assert!(
3605 sql.contains("COALESCE(x.b, y.b)"),
3606 "Unqualified USING column should become COALESCE: {sql}"
3607 );
3608 assert!(
3609 sql.contains("AS b"),
3610 "COALESCE should be aliased as 'b': {sql}"
3611 );
3612 assert!(
3613 sql.contains("ON x.b = y.b"),
3614 "ON condition should be generated: {sql}"
3615 );
3616 }
3617
3618 #[test]
3619 fn test_expand_using_with_where() {
3620 let expr = parse("SELECT b FROM x JOIN y USING(b) WHERE b = 1");
3622
3623 let mut schema = MappingSchema::new();
3624 schema
3625 .add_table(
3626 "x",
3627 &[("b".to_string(), DataType::BigInt { length: None })],
3628 None,
3629 )
3630 .expect("schema setup");
3631 schema
3632 .add_table(
3633 "y",
3634 &[("b".to_string(), DataType::BigInt { length: None })],
3635 None,
3636 )
3637 .expect("schema setup");
3638
3639 let result =
3640 qualify_columns(expr, &schema, &QualifyColumnsOptions::new()).expect("qualify");
3641 let sql = gen(&result);
3642
3643 assert!(
3644 sql.contains("WHERE COALESCE(x.b, y.b)"),
3645 "WHERE should use COALESCE for USING column: {sql}"
3646 );
3647 }
3648
3649 #[test]
3650 fn test_expand_using_multi_join() {
3651 let expr = parse("SELECT b FROM x JOIN y USING(b) JOIN z USING(b)");
3653
3654 let mut schema = MappingSchema::new();
3655 for table in &["x", "y", "z"] {
3656 schema
3657 .add_table(
3658 table,
3659 &[("b".to_string(), DataType::BigInt { length: None })],
3660 None,
3661 )
3662 .expect("schema setup");
3663 }
3664
3665 let result =
3666 qualify_columns(expr, &schema, &QualifyColumnsOptions::new()).expect("qualify");
3667 let sql = gen(&result);
3668
3669 assert!(
3671 sql.contains("COALESCE(x.b, y.b, z.b)"),
3672 "Should have 3-table COALESCE: {sql}"
3673 );
3674 assert!(
3676 sql.contains("ON x.b = y.b"),
3677 "First join ON condition: {sql}"
3678 );
3679 }
3680
3681 #[test]
3682 fn test_expand_using_multi_column() {
3683 let expr = parse("SELECT b, c FROM y JOIN z USING(b, c)");
3685
3686 let mut schema = MappingSchema::new();
3687 schema
3688 .add_table(
3689 "y",
3690 &[
3691 ("b".to_string(), DataType::BigInt { length: None }),
3692 ("c".to_string(), DataType::BigInt { length: None }),
3693 ],
3694 None,
3695 )
3696 .expect("schema setup");
3697 schema
3698 .add_table(
3699 "z",
3700 &[
3701 ("b".to_string(), DataType::BigInt { length: None }),
3702 ("c".to_string(), DataType::BigInt { length: None }),
3703 ],
3704 None,
3705 )
3706 .expect("schema setup");
3707
3708 let result =
3709 qualify_columns(expr, &schema, &QualifyColumnsOptions::new()).expect("qualify");
3710 let sql = gen(&result);
3711
3712 assert!(
3713 sql.contains("COALESCE(y.b, z.b)"),
3714 "column 'b' should get COALESCE: {sql}"
3715 );
3716 assert!(
3717 sql.contains("COALESCE(y.c, z.c)"),
3718 "column 'c' should get COALESCE: {sql}"
3719 );
3720 assert!(
3722 sql.contains("y.b = z.b") && sql.contains("y.c = z.c"),
3723 "ON should have both equality conditions: {sql}"
3724 );
3725 }
3726
3727 #[test]
3728 fn test_expand_using_star() {
3729 let expr = parse("SELECT * FROM x JOIN y USING(b)");
3731
3732 let mut schema = MappingSchema::new();
3733 schema
3734 .add_table(
3735 "x",
3736 &[
3737 ("a".to_string(), DataType::BigInt { length: None }),
3738 ("b".to_string(), DataType::BigInt { length: None }),
3739 ],
3740 None,
3741 )
3742 .expect("schema setup");
3743 schema
3744 .add_table(
3745 "y",
3746 &[
3747 ("b".to_string(), DataType::BigInt { length: None }),
3748 ("c".to_string(), DataType::BigInt { length: None }),
3749 ],
3750 None,
3751 )
3752 .expect("schema setup");
3753
3754 let result =
3755 qualify_columns(expr, &schema, &QualifyColumnsOptions::new()).expect("qualify");
3756 let sql = gen(&result);
3757
3758 assert!(
3760 sql.contains("COALESCE(x.b, y.b) AS b"),
3761 "USING column should be COALESCE in star expansion: {sql}"
3762 );
3763 assert!(sql.contains("x.a"), "non-USING column a from x: {sql}");
3765 assert!(sql.contains("y.c"), "non-USING column c from y: {sql}");
3766 let coalesce_count = sql.matches("COALESCE").count();
3768 assert_eq!(
3769 coalesce_count, 1,
3770 "b should appear only once as COALESCE: {sql}"
3771 );
3772 }
3773
3774 #[test]
3775 fn test_expand_using_table_star() {
3776 let expr = parse("SELECT x.* FROM x JOIN y USING(b)");
3778
3779 let mut schema = MappingSchema::new();
3780 schema
3781 .add_table(
3782 "x",
3783 &[
3784 ("a".to_string(), DataType::BigInt { length: None }),
3785 ("b".to_string(), DataType::BigInt { length: None }),
3786 ],
3787 None,
3788 )
3789 .expect("schema setup");
3790 schema
3791 .add_table(
3792 "y",
3793 &[
3794 ("b".to_string(), DataType::BigInt { length: None }),
3795 ("c".to_string(), DataType::BigInt { length: None }),
3796 ],
3797 None,
3798 )
3799 .expect("schema setup");
3800
3801 let result =
3802 qualify_columns(expr, &schema, &QualifyColumnsOptions::new()).expect("qualify");
3803 let sql = gen(&result);
3804
3805 assert!(
3807 sql.contains("COALESCE(x.b, y.b)"),
3808 "USING column from x.* should become COALESCE: {sql}"
3809 );
3810 assert!(sql.contains("x.a"), "non-USING column a: {sql}");
3811 }
3812
3813 #[test]
3814 fn test_expand_natural_join_with_derived_table() {
3815 let expr = parse(
3816 "SELECT shared_key AS output_key FROM source_table \
3817 NATURAL JOIN (SELECT shared_key FROM source_table) AS derived",
3818 );
3819
3820 let mut schema = MappingSchema::new();
3821 schema
3822 .add_table(
3823 "source_table",
3824 &[(
3825 "shared_key".to_string(),
3826 DataType::VarChar {
3827 length: None,
3828 parenthesized_length: false,
3829 },
3830 )],
3831 None,
3832 )
3833 .expect("schema setup");
3834
3835 let result = qualify_columns(
3836 expr,
3837 &schema,
3838 &QualifyColumnsOptions::new().with_dialect(DialectType::DuckDB),
3839 )
3840 .expect("qualify");
3841 let sql = gen(&result);
3842
3843 assert!(!sql.contains("NATURAL"), "NATURAL should expand: {sql}");
3844 assert!(
3845 sql.contains("ON source_table.shared_key = derived.shared_key"),
3846 "common column should become an equality condition: {sql}"
3847 );
3848 assert!(
3849 sql.contains("COALESCE(source_table.shared_key, derived.shared_key) AS output_key"),
3850 "merged projection should preserve both sources: {sql}"
3851 );
3852 }
3853
3854 #[test]
3855 fn test_expand_natural_join_star_and_chained_sources() {
3856 let expr = parse("SELECT * FROM x NATURAL JOIN y NATURAL JOIN z");
3857
3858 let mut schema = MappingSchema::new();
3859 schema
3860 .add_table(
3861 "x",
3862 &[
3863 ("a".to_string(), DataType::BigInt { length: None }),
3864 ("shared".to_string(), DataType::BigInt { length: None }),
3865 ],
3866 None,
3867 )
3868 .expect("schema setup");
3869 schema
3870 .add_table(
3871 "y",
3872 &[
3873 ("shared".to_string(), DataType::BigInt { length: None }),
3874 ("b".to_string(), DataType::BigInt { length: None }),
3875 ],
3876 None,
3877 )
3878 .expect("schema setup");
3879 schema
3880 .add_table(
3881 "z",
3882 &[
3883 ("shared".to_string(), DataType::BigInt { length: None }),
3884 ("c".to_string(), DataType::BigInt { length: None }),
3885 ],
3886 None,
3887 )
3888 .expect("schema setup");
3889
3890 let result =
3891 qualify_columns(expr, &schema, &QualifyColumnsOptions::new()).expect("qualify");
3892 let sql = gen(&result);
3893
3894 assert!(!sql.contains("NATURAL"), "both joins should expand: {sql}");
3895 assert!(sql.contains("ON x.shared = y.shared"), "first join: {sql}");
3896 assert!(sql.contains("ON x.shared = z.shared"), "second join: {sql}");
3897 assert_eq!(
3898 sql.matches("COALESCE(x.shared, y.shared, z.shared) AS shared")
3899 .count(),
3900 1,
3901 "merged star column should be emitted once: {sql}"
3902 );
3903 assert!(sql.contains("x.a"), "left-only column should remain: {sql}");
3904 assert!(
3905 sql.contains("y.b"),
3906 "middle-only column should remain: {sql}"
3907 );
3908 assert!(
3909 sql.contains("z.c"),
3910 "right-only column should remain: {sql}"
3911 );
3912 }
3913
3914 #[test]
3915 fn test_expand_natural_outer_join_kinds() {
3916 let mut schema = MappingSchema::new();
3917 for table in ["x", "y"] {
3918 schema
3919 .add_table(
3920 table,
3921 &[("shared".to_string(), DataType::BigInt { length: None })],
3922 None,
3923 )
3924 .expect("schema setup");
3925 }
3926
3927 for (input_kind, output_kind) in [
3928 ("NATURAL LEFT JOIN", "LEFT JOIN"),
3929 ("NATURAL RIGHT JOIN", "RIGHT JOIN"),
3930 ("NATURAL FULL JOIN", "FULL JOIN"),
3931 ] {
3932 let expr = parse(&format!("SELECT shared FROM x {input_kind} y"));
3933 let result =
3934 qualify_columns(expr, &schema, &QualifyColumnsOptions::new()).expect("qualify");
3935 let sql = gen(&result);
3936
3937 assert!(
3938 !sql.contains("NATURAL"),
3939 "{input_kind} should expand: {sql}"
3940 );
3941 assert!(sql.contains(output_kind), "join kind should remain: {sql}");
3942 assert!(
3943 sql.contains("ON x.shared = y.shared"),
3944 "join condition should be derived: {sql}"
3945 );
3946 }
3947 }
3948
3949 #[test]
3950 fn test_preserve_natural_join_without_known_common_columns() {
3951 let mut no_common_schema = MappingSchema::new();
3952 no_common_schema
3953 .add_table(
3954 "x",
3955 &[("a".to_string(), DataType::BigInt { length: None })],
3956 None,
3957 )
3958 .expect("schema setup");
3959 no_common_schema
3960 .add_table(
3961 "y",
3962 &[("b".to_string(), DataType::BigInt { length: None })],
3963 None,
3964 )
3965 .expect("schema setup");
3966
3967 let no_common = qualify_columns(
3968 parse("SELECT * FROM x NATURAL JOIN y"),
3969 &no_common_schema,
3970 &QualifyColumnsOptions::new(),
3971 )
3972 .expect("qualify");
3973 assert!(
3974 gen(&no_common).contains("NATURAL JOIN"),
3975 "join without common columns should remain NATURAL"
3976 );
3977
3978 let mut partial_schema = MappingSchema::new();
3979 partial_schema
3980 .add_table(
3981 "x",
3982 &[("a".to_string(), DataType::BigInt { length: None })],
3983 None,
3984 )
3985 .expect("schema setup");
3986 let unknown_right = qualify_columns(
3987 parse("SELECT * FROM x NATURAL JOIN unknown_table"),
3988 &partial_schema,
3989 &QualifyColumnsOptions::new().with_allow_partial(true),
3990 )
3991 .expect("partial qualification");
3992 assert!(
3993 gen(&unknown_right).contains("NATURAL JOIN"),
3994 "join with an unknown schema should remain NATURAL"
3995 );
3996
3997 let unknown_left = qualify_columns(
3998 parse("SELECT * FROM unknown_table NATURAL JOIN x"),
3999 &partial_schema,
4000 &QualifyColumnsOptions::new().with_allow_partial(true),
4001 )
4002 .expect("partial qualification");
4003 assert!(
4004 gen(&unknown_left).contains("NATURAL JOIN"),
4005 "join with an unknown left schema should remain NATURAL"
4006 );
4007
4008 let partially_known_chain = qualify_columns(
4009 parse("SELECT * FROM x NATURAL JOIN unknown_table NATURAL JOIN x AS x2"),
4010 &partial_schema,
4011 &QualifyColumnsOptions::new().with_allow_partial(true),
4012 )
4013 .expect("partial qualification");
4014 assert_eq!(
4015 gen(&partially_known_chain).matches("NATURAL JOIN").count(),
4016 2,
4017 "an unknown earlier source should prevent later inferred join keys"
4018 );
4019 }
4020
4021 #[test]
4022 fn test_qualify_columns_qualified_table_name() {
4023 let expr = parse("SELECT a FROM raw.t1");
4024
4025 let mut schema = MappingSchema::new();
4026 schema
4027 .add_table(
4028 "raw.t1",
4029 &[("a".to_string(), DataType::BigInt { length: None })],
4030 None,
4031 )
4032 .expect("schema setup");
4033
4034 let result =
4035 qualify_columns(expr, &schema, &QualifyColumnsOptions::new()).expect("qualify");
4036 let sql = gen(&result);
4037
4038 assert!(
4039 sql.contains("t1.a"),
4040 "column should be qualified with table name: {sql}"
4041 );
4042
4043 let expr = parse("SELECT MAX(a) FROM raw.t1");
4045 let result =
4046 qualify_columns(expr, &schema, &QualifyColumnsOptions::new()).expect("qualify");
4047 let sql = gen(&result);
4048 assert!(
4049 sql.contains("t1.a"),
4050 "column in function should be qualified with table name: {sql}"
4051 );
4052
4053 let expr = parse("SELECT ABS(a) FROM raw.t1");
4055 let result =
4056 qualify_columns(expr, &schema, &QualifyColumnsOptions::new()).expect("qualify");
4057 let sql = gen(&result);
4058 assert!(
4059 sql.contains("t1.a"),
4060 "column in function should be qualified with table name: {sql}"
4061 );
4062 }
4063
4064 #[test]
4065 fn test_qualify_columns_count_star() {
4066 let expr = parse("SELECT COUNT(*) FROM t1");
4068
4069 let mut schema = MappingSchema::new();
4070 schema
4071 .add_table(
4072 "t1",
4073 &[("id".to_string(), DataType::BigInt { length: None })],
4074 None,
4075 )
4076 .expect("schema setup");
4077
4078 let result =
4079 qualify_columns(expr, &schema, &QualifyColumnsOptions::new()).expect("qualify");
4080 let sql = gen(&result);
4081
4082 assert!(
4083 sql.contains("COUNT(*)"),
4084 "COUNT(*) should be preserved: {sql}"
4085 );
4086 }
4087
4088 #[test]
4089 fn test_qualify_columns_correlated_scalar_subquery() {
4090 let expr =
4091 parse("SELECT id, (SELECT AVG(val) FROM t2 WHERE t2.id = t1.id) AS avg_val FROM t1");
4092
4093 let mut schema = MappingSchema::new();
4094 schema
4095 .add_table(
4096 "t1",
4097 &[("id".to_string(), DataType::BigInt { length: None })],
4098 None,
4099 )
4100 .expect("schema setup");
4101 schema
4102 .add_table(
4103 "t2",
4104 &[
4105 ("id".to_string(), DataType::BigInt { length: None }),
4106 ("val".to_string(), DataType::BigInt { length: None }),
4107 ],
4108 None,
4109 )
4110 .expect("schema setup");
4111
4112 let result =
4113 qualify_columns(expr, &schema, &QualifyColumnsOptions::new()).expect("qualify");
4114 let sql = gen(&result);
4115
4116 assert!(
4117 sql.contains("t1.id"),
4118 "outer column should be qualified: {sql}"
4119 );
4120 assert!(
4121 sql.contains("t2.id"),
4122 "inner column should be qualified: {sql}"
4123 );
4124 }
4125
4126 #[test]
4127 fn test_qualify_columns_correlated_scalar_subquery_unqualified() {
4128 let expr =
4129 parse("SELECT t1_id, (SELECT AVG(val) FROM t2 WHERE t2_id = t1_id) AS avg_val FROM t1");
4130
4131 let mut schema = MappingSchema::new();
4132 schema
4133 .add_table(
4134 "t1",
4135 &[("t1_id".to_string(), DataType::BigInt { length: None })],
4136 None,
4137 )
4138 .expect("schema setup");
4139 schema
4140 .add_table(
4141 "t2",
4142 &[
4143 ("t2_id".to_string(), DataType::BigInt { length: None }),
4144 ("val".to_string(), DataType::BigInt { length: None }),
4145 ],
4146 None,
4147 )
4148 .expect("schema setup");
4149
4150 let result =
4151 qualify_columns(expr, &schema, &QualifyColumnsOptions::new()).expect("qualify");
4152 let sql = gen(&result);
4153
4154 assert!(
4155 sql.contains("t1.t1_id"),
4156 "outer column should be qualified: {sql}"
4157 );
4158 assert!(
4159 sql.contains("t2.t2_id"),
4160 "inner column should be qualified: {sql}"
4161 );
4162 assert!(
4164 sql.contains("= t1.t1_id"),
4165 "correlated column should be qualified: {sql}"
4166 );
4167 }
4168
4169 #[test]
4170 fn test_qualify_columns_correlated_exists_subquery() {
4171 let expr = parse(
4172 "SELECT o_orderpriority FROM orders \
4173 WHERE EXISTS (SELECT * FROM lineitem WHERE l_orderkey = o_orderkey)",
4174 );
4175
4176 let mut schema = MappingSchema::new();
4177 schema
4178 .add_table(
4179 "orders",
4180 &[
4181 ("o_orderpriority".to_string(), DataType::Text),
4182 ("o_orderkey".to_string(), DataType::BigInt { length: None }),
4183 ],
4184 None,
4185 )
4186 .expect("schema setup");
4187 schema
4188 .add_table(
4189 "lineitem",
4190 &[("l_orderkey".to_string(), DataType::BigInt { length: None })],
4191 None,
4192 )
4193 .expect("schema setup");
4194
4195 let result =
4196 qualify_columns(expr, &schema, &QualifyColumnsOptions::new()).expect("qualify");
4197 let sql = gen(&result);
4198
4199 assert!(
4200 sql.contains("orders.o_orderpriority"),
4201 "outer column should be qualified: {sql}"
4202 );
4203 assert!(
4204 sql.contains("lineitem.l_orderkey"),
4205 "inner column should be qualified: {sql}"
4206 );
4207 assert!(
4208 sql.contains("orders.o_orderkey"),
4209 "correlated outer column should be qualified: {sql}"
4210 );
4211 }
4212
4213 #[test]
4214 fn test_qualify_columns_rejects_unknown_table() {
4215 let expr = parse("SELECT id FROM t1 WHERE nonexistent.col = 1");
4216
4217 let mut schema = MappingSchema::new();
4218 schema
4219 .add_table(
4220 "t1",
4221 &[("id".to_string(), DataType::BigInt { length: None })],
4222 None,
4223 )
4224 .expect("schema setup");
4225
4226 let result = qualify_columns(expr, &schema, &QualifyColumnsOptions::new());
4227 assert!(
4228 result.is_err(),
4229 "should reject reference to table not in scope or schema"
4230 );
4231 }
4232
4233 #[test]
4238 fn test_needs_quoting_reserved_word() {
4239 let reserved = get_reserved_words(None);
4240 assert!(needs_quoting("select", &reserved));
4241 assert!(needs_quoting("SELECT", &reserved));
4242 assert!(needs_quoting("from", &reserved));
4243 assert!(needs_quoting("WHERE", &reserved));
4244 assert!(needs_quoting("join", &reserved));
4245 assert!(needs_quoting("table", &reserved));
4246 }
4247
4248 #[test]
4249 fn test_needs_quoting_normal_identifiers() {
4250 let reserved = get_reserved_words(None);
4251 assert!(!needs_quoting("foo", &reserved));
4252 assert!(!needs_quoting("my_column", &reserved));
4253 assert!(!needs_quoting("col1", &reserved));
4254 assert!(!needs_quoting("A", &reserved));
4255 assert!(!needs_quoting("_hidden", &reserved));
4256 }
4257
4258 #[test]
4259 fn test_needs_quoting_special_characters() {
4260 let reserved = get_reserved_words(None);
4261 assert!(needs_quoting("my column", &reserved)); assert!(needs_quoting("my-column", &reserved)); assert!(needs_quoting("my.column", &reserved)); assert!(needs_quoting("col@name", &reserved)); assert!(needs_quoting("col#name", &reserved)); }
4267
4268 #[test]
4269 fn test_needs_quoting_starts_with_digit() {
4270 let reserved = get_reserved_words(None);
4271 assert!(needs_quoting("1col", &reserved));
4272 assert!(needs_quoting("123", &reserved));
4273 assert!(needs_quoting("0_start", &reserved));
4274 }
4275
4276 #[test]
4277 fn test_needs_quoting_empty() {
4278 let reserved = get_reserved_words(None);
4279 assert!(!needs_quoting("", &reserved));
4280 }
4281
4282 #[test]
4283 fn test_maybe_quote_sets_quoted_flag() {
4284 let reserved = get_reserved_words(None);
4285 let mut id = Identifier::new("select");
4286 assert!(!id.quoted);
4287 maybe_quote(&mut id, &reserved);
4288 assert!(id.quoted);
4289 }
4290
4291 #[test]
4292 fn test_maybe_quote_skips_already_quoted() {
4293 let reserved = get_reserved_words(None);
4294 let mut id = Identifier::quoted("myname");
4295 assert!(id.quoted);
4296 maybe_quote(&mut id, &reserved);
4297 assert!(id.quoted); assert_eq!(id.name, "myname"); }
4300
4301 #[test]
4302 fn test_maybe_quote_skips_star() {
4303 let reserved = get_reserved_words(None);
4304 let mut id = Identifier::new("*");
4305 maybe_quote(&mut id, &reserved);
4306 assert!(!id.quoted); }
4308
4309 #[test]
4310 fn test_maybe_quote_skips_normal() {
4311 let reserved = get_reserved_words(None);
4312 let mut id = Identifier::new("normal_col");
4313 maybe_quote(&mut id, &reserved);
4314 assert!(!id.quoted);
4315 }
4316
4317 #[test]
4318 fn test_quote_identifiers_column_with_reserved_name() {
4319 let expr = Expression::boxed_column(Column {
4321 name: Identifier::new("select"),
4322 table: None,
4323 join_mark: false,
4324 trailing_comments: vec![],
4325 span: None,
4326 inferred_type: None,
4327 });
4328 let result = quote_identifiers(expr, None);
4329 if let Expression::Column(col) = &result {
4330 assert!(col.name.quoted, "Column named 'select' should be quoted");
4331 } else {
4332 panic!("Expected Column expression");
4333 }
4334 }
4335
4336 #[test]
4337 fn test_quote_identifiers_column_with_special_chars() {
4338 let expr = Expression::boxed_column(Column {
4339 name: Identifier::new("my column"),
4340 table: None,
4341 join_mark: false,
4342 trailing_comments: vec![],
4343 span: None,
4344 inferred_type: None,
4345 });
4346 let result = quote_identifiers(expr, None);
4347 if let Expression::Column(col) = &result {
4348 assert!(col.name.quoted, "Column with space should be quoted");
4349 } else {
4350 panic!("Expected Column expression");
4351 }
4352 }
4353
4354 #[test]
4355 fn test_quote_identifiers_preserves_normal_column() {
4356 let expr = Expression::boxed_column(Column {
4357 name: Identifier::new("normal_col"),
4358 table: Some(Identifier::new("my_table")),
4359 join_mark: false,
4360 trailing_comments: vec![],
4361 span: None,
4362 inferred_type: None,
4363 });
4364 let result = quote_identifiers(expr, None);
4365 if let Expression::Column(col) = &result {
4366 assert!(!col.name.quoted, "Normal column should not be quoted");
4367 assert!(
4368 !col.table.as_ref().unwrap().quoted,
4369 "Normal table should not be quoted"
4370 );
4371 } else {
4372 panic!("Expected Column expression");
4373 }
4374 }
4375
4376 #[test]
4377 fn test_quote_identifiers_table_ref_reserved() {
4378 let expr = Expression::Table(Box::new(TableRef::new("select")));
4379 let result = quote_identifiers(expr, None);
4380 if let Expression::Table(tr) = &result {
4381 assert!(tr.name.quoted, "Table named 'select' should be quoted");
4382 } else {
4383 panic!("Expected Table expression");
4384 }
4385 }
4386
4387 #[test]
4388 fn test_quote_identifiers_table_ref_schema_and_alias() {
4389 let mut tr = TableRef::new("my_table");
4390 tr.schema = Some(Identifier::new("from"));
4391 tr.alias = Some(Identifier::new("t"));
4392 let expr = Expression::Table(Box::new(tr));
4393 let result = quote_identifiers(expr, None);
4394 if let Expression::Table(tr) = &result {
4395 assert!(!tr.name.quoted, "Normal table name should not be quoted");
4396 assert!(
4397 tr.schema.as_ref().unwrap().quoted,
4398 "Schema named 'from' should be quoted"
4399 );
4400 assert!(
4401 !tr.alias.as_ref().unwrap().quoted,
4402 "Normal alias should not be quoted"
4403 );
4404 } else {
4405 panic!("Expected Table expression");
4406 }
4407 }
4408
4409 #[test]
4410 fn test_quote_identifiers_identifier_node() {
4411 let expr = Expression::Identifier(Identifier::new("order"));
4412 let result = quote_identifiers(expr, None);
4413 if let Expression::Identifier(id) = &result {
4414 assert!(id.quoted, "Identifier named 'order' should be quoted");
4415 } else {
4416 panic!("Expected Identifier expression");
4417 }
4418 }
4419
4420 #[test]
4421 fn test_quote_identifiers_alias() {
4422 let inner = Expression::boxed_column(Column {
4423 name: Identifier::new("val"),
4424 table: None,
4425 join_mark: false,
4426 trailing_comments: vec![],
4427 span: None,
4428 inferred_type: None,
4429 });
4430 let expr = Expression::Alias(Box::new(Alias {
4431 this: inner,
4432 alias: Identifier::new("select"),
4433 column_aliases: vec![Identifier::new("from")],
4434 alias_explicit_as: false,
4435 alias_keyword: None,
4436 pre_alias_comments: vec![],
4437 trailing_comments: vec![],
4438 inferred_type: None,
4439 }));
4440 let result = quote_identifiers(expr, None);
4441 if let Expression::Alias(alias) = &result {
4442 assert!(alias.alias.quoted, "Alias named 'select' should be quoted");
4443 assert!(
4444 alias.column_aliases[0].quoted,
4445 "Column alias named 'from' should be quoted"
4446 );
4447 if let Expression::Column(col) = &alias.this {
4449 assert!(!col.name.quoted);
4450 }
4451 } else {
4452 panic!("Expected Alias expression");
4453 }
4454 }
4455
4456 #[test]
4457 fn test_quote_identifiers_select_recursive() {
4458 let expr = parse("SELECT a, b FROM t WHERE c = 1");
4460 let result = quote_identifiers(expr, None);
4461 let sql = gen(&result);
4463 assert!(sql.contains("a"));
4465 assert!(sql.contains("b"));
4466 assert!(sql.contains("t"));
4467 }
4468
4469 #[test]
4470 fn test_quote_identifiers_digit_start() {
4471 let expr = Expression::boxed_column(Column {
4472 name: Identifier::new("1col"),
4473 table: None,
4474 join_mark: false,
4475 trailing_comments: vec![],
4476 span: None,
4477 inferred_type: None,
4478 });
4479 let result = quote_identifiers(expr, None);
4480 if let Expression::Column(col) = &result {
4481 assert!(
4482 col.name.quoted,
4483 "Column starting with digit should be quoted"
4484 );
4485 } else {
4486 panic!("Expected Column expression");
4487 }
4488 }
4489
4490 #[test]
4491 fn test_quote_identifiers_with_mysql_dialect() {
4492 let reserved = get_reserved_words(Some(DialectType::MySQL));
4493 assert!(needs_quoting("KILL", &reserved));
4495 assert!(needs_quoting("FORCE", &reserved));
4497 }
4498
4499 #[test]
4500 fn test_quote_identifiers_with_postgresql_dialect() {
4501 let reserved = get_reserved_words(Some(DialectType::PostgreSQL));
4502 assert!(needs_quoting("ILIKE", &reserved));
4504 assert!(needs_quoting("VERBOSE", &reserved));
4506 }
4507
4508 #[test]
4509 fn test_quote_identifiers_with_bigquery_dialect() {
4510 let reserved = get_reserved_words(Some(DialectType::BigQuery));
4511 assert!(needs_quoting("STRUCT", &reserved));
4513 assert!(needs_quoting("PROTO", &reserved));
4515 }
4516
4517 #[test]
4518 fn test_quote_identifiers_case_insensitive_reserved() {
4519 let reserved = get_reserved_words(None);
4520 assert!(needs_quoting("Select", &reserved));
4521 assert!(needs_quoting("sElEcT", &reserved));
4522 assert!(needs_quoting("FROM", &reserved));
4523 assert!(needs_quoting("from", &reserved));
4524 }
4525
4526 #[test]
4527 fn test_quote_identifiers_join_using() {
4528 let mut join = crate::expressions::Join {
4530 this: Expression::Table(Box::new(TableRef::new("other"))),
4531 on: None,
4532 using: vec![Identifier::new("key"), Identifier::new("value")],
4533 kind: crate::expressions::JoinKind::Inner,
4534 use_inner_keyword: false,
4535 use_outer_keyword: false,
4536 deferred_condition: false,
4537 join_hint: None,
4538 match_condition: None,
4539 pivots: vec![],
4540 comments: vec![],
4541 nesting_group: 0,
4542 directed: false,
4543 };
4544 let reserved = get_reserved_words(None);
4545 quote_join(&mut join, &reserved);
4546 assert!(
4548 join.using[0].quoted,
4549 "USING identifier 'key' should be quoted"
4550 );
4551 assert!(
4552 !join.using[1].quoted,
4553 "USING identifier 'value' should not be quoted"
4554 );
4555 }
4556
4557 #[test]
4558 fn test_quote_identifiers_cte() {
4559 let mut cte = crate::expressions::Cte {
4561 alias: Identifier::new("select"),
4562 this: Expression::boxed_column(Column {
4563 name: Identifier::new("x"),
4564 table: None,
4565 join_mark: false,
4566 trailing_comments: vec![],
4567 span: None,
4568 inferred_type: None,
4569 }),
4570 columns: vec![Identifier::new("from"), Identifier::new("normal")],
4571 materialized: None,
4572 key_expressions: vec![],
4573 alias_first: false,
4574 comments: Vec::new(),
4575 };
4576 let reserved = get_reserved_words(None);
4577 maybe_quote(&mut cte.alias, &reserved);
4578 for c in &mut cte.columns {
4579 maybe_quote(c, &reserved);
4580 }
4581 assert!(cte.alias.quoted, "CTE alias 'select' should be quoted");
4582 assert!(cte.columns[0].quoted, "CTE column 'from' should be quoted");
4583 assert!(
4584 !cte.columns[1].quoted,
4585 "CTE column 'normal' should not be quoted"
4586 );
4587 }
4588
4589 #[test]
4590 fn test_quote_identifiers_binary_ops_recurse() {
4591 let expr = Expression::Add(Box::new(crate::expressions::BinaryOp::new(
4594 Expression::boxed_column(Column {
4595 name: Identifier::new("select"),
4596 table: None,
4597 join_mark: false,
4598 trailing_comments: vec![],
4599 span: None,
4600 inferred_type: None,
4601 }),
4602 Expression::boxed_column(Column {
4603 name: Identifier::new("normal"),
4604 table: None,
4605 join_mark: false,
4606 trailing_comments: vec![],
4607 span: None,
4608 inferred_type: None,
4609 }),
4610 )));
4611 let result = quote_identifiers(expr, None);
4612 if let Expression::Add(bin) = &result {
4613 if let Expression::Column(left) = &bin.left {
4614 assert!(
4615 left.name.quoted,
4616 "'select' column should be quoted in binary op"
4617 );
4618 }
4619 if let Expression::Column(right) = &bin.right {
4620 assert!(!right.name.quoted, "'normal' column should not be quoted");
4621 }
4622 } else {
4623 panic!("Expected Add expression");
4624 }
4625 }
4626
4627 #[test]
4628 fn test_quote_identifiers_already_quoted_preserved() {
4629 let expr = Expression::boxed_column(Column {
4631 name: Identifier::quoted("normal_name"),
4632 table: None,
4633 join_mark: false,
4634 trailing_comments: vec![],
4635 span: None,
4636 inferred_type: None,
4637 });
4638 let result = quote_identifiers(expr, None);
4639 if let Expression::Column(col) = &result {
4640 assert!(
4641 col.name.quoted,
4642 "Already-quoted identifier should remain quoted"
4643 );
4644 } else {
4645 panic!("Expected Column expression");
4646 }
4647 }
4648
4649 #[test]
4650 fn test_quote_identifiers_full_parsed_query() {
4651 let mut select = crate::expressions::Select::new();
4654 select.expressions.push(Expression::boxed_column(Column {
4655 name: Identifier::new("order"),
4656 table: Some(Identifier::new("t")),
4657 join_mark: false,
4658 trailing_comments: vec![],
4659 span: None,
4660 inferred_type: None,
4661 }));
4662 select.from = Some(crate::expressions::From {
4663 expressions: vec![Expression::Table(Box::new(TableRef::new("t")))],
4664 });
4665 let expr = Expression::Select(Box::new(select));
4666
4667 let result = quote_identifiers(expr, None);
4668 if let Expression::Select(sel) = &result {
4669 if let Expression::Column(col) = &sel.expressions[0] {
4670 assert!(col.name.quoted, "Column named 'order' should be quoted");
4671 assert!(
4672 !col.table.as_ref().unwrap().quoted,
4673 "Table 't' should not be quoted"
4674 );
4675 } else {
4676 panic!("Expected Column in SELECT list");
4677 }
4678 } else {
4679 panic!("Expected Select expression");
4680 }
4681 }
4682
4683 #[test]
4684 fn test_get_reserved_words_all_dialects() {
4685 let dialects = [
4687 None,
4688 Some(DialectType::Generic),
4689 Some(DialectType::MySQL),
4690 Some(DialectType::PostgreSQL),
4691 Some(DialectType::BigQuery),
4692 Some(DialectType::Snowflake),
4693 Some(DialectType::TSQL),
4694 Some(DialectType::ClickHouse),
4695 Some(DialectType::DuckDB),
4696 Some(DialectType::Hive),
4697 Some(DialectType::Spark),
4698 Some(DialectType::Trino),
4699 Some(DialectType::Oracle),
4700 Some(DialectType::Redshift),
4701 ];
4702 for dialect in &dialects {
4703 let words = get_reserved_words(*dialect);
4704 assert!(
4706 words.contains("SELECT"),
4707 "All dialects should have SELECT as reserved"
4708 );
4709 assert!(
4710 words.contains("FROM"),
4711 "All dialects should have FROM as reserved"
4712 );
4713 }
4714 }
4715}