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) => {
1060 has_star = true;
1061 if let Some(table) = &star.table {
1062 let table_name = &table.name;
1063 if !ordered_sources.contains(table_name) {
1064 return Err(QualifyColumnsError::UnknownTable(table_name.clone()));
1065 }
1066 if let Ok(columns) = resolver.get_source_columns(table_name) {
1067 if columns.contains(&"*".to_string()) || columns.is_empty() {
1068 return Ok(());
1069 }
1070 for col_name in &columns {
1071 let normalized = normalize_column_name(col_name, resolver.dialect);
1072 if coalesced_columns.contains(&normalized) {
1073 continue;
1074 }
1075 if let Some(tables) = column_tables.get(&normalized) {
1076 if tables.contains(table_name) {
1077 coalesced_columns.insert(normalized);
1078 let coalesce = make_coalesce(col_name, tables);
1079 new_selections.push(Expression::Alias(Box::new(Alias {
1080 this: coalesce,
1081 alias: Identifier::new(col_name),
1082 column_aliases: vec![],
1083 alias_explicit_as: false,
1084 alias_keyword: None,
1085 pre_alias_comments: vec![],
1086 trailing_comments: vec![],
1087 inferred_type: None,
1088 })));
1089 continue;
1090 }
1091 }
1092 new_selections
1093 .push(create_qualified_column(col_name, Some(table_name)));
1094 }
1095 }
1096 } else {
1097 for source_name in &ordered_sources {
1098 if let Ok(columns) = resolver.get_source_columns(source_name) {
1099 if columns.contains(&"*".to_string()) || columns.is_empty() {
1100 return Ok(());
1101 }
1102 for col_name in &columns {
1103 let normalized = normalize_column_name(col_name, resolver.dialect);
1104 if coalesced_columns.contains(&normalized) {
1105 continue;
1107 }
1108 if let Some(tables) = column_tables.get(&normalized) {
1109 if tables.contains(source_name) {
1110 coalesced_columns.insert(normalized);
1112 let coalesce = make_coalesce(col_name, tables);
1113 new_selections.push(Expression::Alias(Box::new(Alias {
1114 this: coalesce,
1115 alias: Identifier::new(col_name),
1116 column_aliases: vec![],
1117 alias_explicit_as: false,
1118 alias_keyword: None,
1119 pre_alias_comments: vec![],
1120 trailing_comments: vec![],
1121 inferred_type: None,
1122 })));
1123 continue;
1124 }
1125 }
1126 new_selections
1127 .push(create_qualified_column(col_name, Some(source_name)));
1128 }
1129 }
1130 }
1131 }
1132 }
1133 Expression::Column(col) if is_star_column(col) => {
1134 has_star = true;
1135 if let Some(table) = &col.table {
1136 let table_name = &table.name;
1137 if !ordered_sources.contains(table_name) {
1138 return Err(QualifyColumnsError::UnknownTable(table_name.clone()));
1139 }
1140 if let Ok(columns) = resolver.get_source_columns(table_name) {
1141 if columns.contains(&"*".to_string()) || columns.is_empty() {
1142 return Ok(());
1143 }
1144 for col_name in &columns {
1145 let normalized = normalize_column_name(col_name, resolver.dialect);
1146 if coalesced_columns.contains(&normalized) {
1147 continue;
1148 }
1149 if let Some(tables) = column_tables.get(&normalized) {
1150 if tables.contains(table_name) {
1151 coalesced_columns.insert(normalized);
1152 let coalesce = make_coalesce(col_name, tables);
1153 new_selections.push(Expression::Alias(Box::new(Alias {
1154 this: coalesce,
1155 alias: Identifier::new(col_name),
1156 column_aliases: vec![],
1157 alias_explicit_as: false,
1158 alias_keyword: None,
1159 pre_alias_comments: vec![],
1160 trailing_comments: vec![],
1161 inferred_type: None,
1162 })));
1163 continue;
1164 }
1165 }
1166 new_selections
1167 .push(create_qualified_column(col_name, Some(table_name)));
1168 }
1169 }
1170 }
1171 }
1172 _ => new_selections.push(expr.clone()),
1173 }
1174 }
1175
1176 if has_star {
1177 select.expressions = new_selections;
1178 }
1179
1180 Ok(())
1181}
1182
1183pub fn qualify_outputs(scope: &Scope) -> QualifyColumnsResult<()> {
1190 if let Expression::Select(mut select) = scope.expression.clone() {
1191 qualify_outputs_select(&mut select)?;
1192 }
1193 Ok(())
1194}
1195
1196fn qualify_outputs_select(select: &mut Select) -> QualifyColumnsResult<()> {
1197 let mut new_selections: Vec<Expression> = Vec::new();
1198
1199 for (i, expr) in select.expressions.iter().enumerate() {
1200 match expr {
1201 Expression::Alias(_) => new_selections.push(expr.clone()),
1202 Expression::Column(col) => {
1203 new_selections.push(create_alias(expr.clone(), &col.name.name));
1204 }
1205 Expression::Star(_) => new_selections.push(expr.clone()),
1206 _ => {
1207 let alias_name = get_output_name(expr).unwrap_or_else(|| format!("_col_{}", i));
1208 new_selections.push(create_alias(expr.clone(), &alias_name));
1209 }
1210 }
1211 }
1212
1213 select.expressions = new_selections;
1214 Ok(())
1215}
1216
1217fn qualify_columns_in_expression(
1218 expr: &mut Expression,
1219 scope: &Scope,
1220 resolver: &mut Resolver,
1221 allow_partial: bool,
1222) -> QualifyColumnsResult<()> {
1223 let first_error: RefCell<Option<QualifyColumnsError>> = RefCell::new(None);
1224 let resolver_cell: RefCell<&mut Resolver> = RefCell::new(resolver);
1225
1226 let transformed = transform_in_scope(expr.clone(), |node| {
1227 if first_error.borrow().is_some() {
1228 return Ok(node);
1229 }
1230
1231 match node {
1232 Expression::Column(mut col) => {
1233 if let Err(err) = qualify_single_column(
1234 &mut col,
1235 scope,
1236 &mut resolver_cell.borrow_mut(),
1237 allow_partial,
1238 ) {
1239 *first_error.borrow_mut() = Some(err);
1240 }
1241 Ok(Expression::Column(col))
1242 }
1243 _ => Ok(node),
1244 }
1245 })
1246 .map_err(|err: crate::Error| QualifyColumnsError::CannotAutoJoin(err.to_string()))?;
1247
1248 if let Some(err) = first_error.into_inner() {
1249 return Err(err);
1250 }
1251
1252 *expr = transformed;
1253 Ok(())
1254}
1255
1256fn qualify_single_column(
1257 col: &mut Column,
1258 scope: &Scope,
1259 resolver: &mut Resolver,
1260 allow_partial: bool,
1261) -> QualifyColumnsResult<()> {
1262 if is_star_column(col) {
1263 return Ok(());
1264 }
1265
1266 if let Some(table) = &col.table {
1267 let table_name = &table.name;
1268 let source_name = scope.sources.keys().find(|source| {
1269 normalize_name(source, resolver.dialect, true, true)
1270 == normalize_name(table_name, resolver.dialect, true, true)
1271 });
1272 let Some(source_name) = source_name else {
1273 if resolver.outer_source_exists(table_name) {
1274 return Ok(());
1275 }
1276 return Err(QualifyColumnsError::UnknownTable(table_name.clone()));
1277 };
1278
1279 if let Ok(source_columns) = resolver.get_source_columns(source_name) {
1280 let normalized_column_name = normalize_column_name(&col.name.name, resolver.dialect);
1281 if !allow_partial
1282 && !source_columns.is_empty()
1283 && !source_columns.iter().any(|column| {
1284 normalize_column_name(column, resolver.dialect) == normalized_column_name
1285 })
1286 && !source_columns.contains(&"*".to_string())
1287 {
1288 return Err(QualifyColumnsError::UnknownColumn(col.name.name.clone()));
1289 }
1290 }
1291 return Ok(());
1292 }
1293
1294 if resolver.is_ambiguous(&col.name.name) {
1295 if allow_partial {
1296 return Ok(());
1297 }
1298 return Err(QualifyColumnsError::AmbiguousColumn(col.name.name.clone()));
1299 }
1300 if let Some(table_name) = resolver.get_table(&col.name.name) {
1301 col.table = Some(Identifier::new(table_name));
1302 return Ok(());
1303 }
1304
1305 if let Some(outer_table) = resolver.find_column_in_outer_scopes(&col.name.name) {
1306 col.table = Some(Identifier::new(outer_table));
1307 return Ok(());
1308 }
1309
1310 if !allow_partial {
1311 return Err(QualifyColumnsError::UnknownColumn(col.name.name.clone()));
1312 }
1313
1314 Ok(())
1315}
1316
1317fn normalize_column_name(name: &str, dialect: Option<DialectType>) -> String {
1318 normalize_name(name, dialect, false, true)
1319}
1320
1321fn replace_alias_refs_in_expression(
1322 expr: &mut Expression,
1323 alias_to_expression: &HashMap<String, (Expression, usize)>,
1324 literal_index: bool,
1325) {
1326 let transformed: crate::Result<_> = transform_in_scope(expr.clone(), |node| match node {
1327 Expression::Column(col) if col.table.is_none() => {
1328 if let Some((alias_expr, index)) = alias_to_expression.get(&col.name.name) {
1329 if literal_index && matches!(alias_expr, Expression::Literal(_)) {
1330 return Ok(Expression::number(*index as i64));
1331 }
1332 return Ok(Expression::Paren(Box::new(Paren {
1333 this: alias_expr.clone(),
1334 trailing_comments: vec![],
1335 })));
1336 }
1337 Ok(Expression::Column(col))
1338 }
1339 other => Ok(other),
1340 });
1341
1342 if let Ok(next) = transformed {
1343 *expr = next;
1344 }
1345}
1346
1347fn positional_reference(expr: &Expression) -> Option<usize> {
1348 match expr {
1349 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
1350 let Literal::Number(value) = lit.as_ref() else {
1351 unreachable!()
1352 };
1353 value.parse::<usize>().ok()
1354 }
1355 _ => None,
1356 }
1357}
1358
1359fn select_expression_at_position(
1360 projections: &[Expression],
1361 index: usize,
1362) -> QualifyColumnsResult<Expression> {
1363 if index == 0 || index > projections.len() {
1364 return Err(QualifyColumnsError::UnknownOutputColumn(index.to_string()));
1365 }
1366
1367 let projection = projections[index - 1].clone();
1368 Ok(match projection {
1369 Expression::Alias(alias) => alias.this.clone(),
1370 other => other,
1371 })
1372}
1373
1374fn get_reserved_words(dialect: Option<DialectType>) -> HashSet<&'static str> {
1377 let mut words: HashSet<&'static str> = [
1379 "ADD",
1381 "ALL",
1382 "ALTER",
1383 "AND",
1384 "ANY",
1385 "AS",
1386 "ASC",
1387 "BETWEEN",
1388 "BY",
1389 "CASE",
1390 "CAST",
1391 "CHECK",
1392 "COLUMN",
1393 "CONSTRAINT",
1394 "CREATE",
1395 "CROSS",
1396 "CURRENT",
1397 "CURRENT_DATE",
1398 "CURRENT_TIME",
1399 "CURRENT_TIMESTAMP",
1400 "CURRENT_USER",
1401 "DATABASE",
1402 "DEFAULT",
1403 "DELETE",
1404 "DESC",
1405 "DISTINCT",
1406 "DROP",
1407 "ELSE",
1408 "END",
1409 "ESCAPE",
1410 "EXCEPT",
1411 "EXISTS",
1412 "FALSE",
1413 "FETCH",
1414 "FOR",
1415 "FOREIGN",
1416 "FROM",
1417 "FULL",
1418 "GRANT",
1419 "GROUP",
1420 "HAVING",
1421 "IF",
1422 "IN",
1423 "INDEX",
1424 "INNER",
1425 "INSERT",
1426 "INTERSECT",
1427 "INTO",
1428 "IS",
1429 "JOIN",
1430 "KEY",
1431 "LEFT",
1432 "LIKE",
1433 "LIMIT",
1434 "NATURAL",
1435 "NOT",
1436 "NULL",
1437 "OFFSET",
1438 "ON",
1439 "OR",
1440 "ORDER",
1441 "OUTER",
1442 "PRIMARY",
1443 "REFERENCES",
1444 "REPLACE",
1445 "RETURNING",
1446 "RIGHT",
1447 "ROLLBACK",
1448 "ROW",
1449 "ROWS",
1450 "SELECT",
1451 "SESSION_USER",
1452 "SET",
1453 "SOME",
1454 "TABLE",
1455 "THEN",
1456 "TO",
1457 "TRUE",
1458 "TRUNCATE",
1459 "UNION",
1460 "UNIQUE",
1461 "UPDATE",
1462 "USING",
1463 "VALUES",
1464 "VIEW",
1465 "WHEN",
1466 "WHERE",
1467 "WINDOW",
1468 "WITH",
1469 ]
1470 .iter()
1471 .copied()
1472 .collect();
1473
1474 match dialect {
1476 Some(DialectType::MySQL) => {
1477 words.extend(
1478 [
1479 "ANALYZE",
1480 "BOTH",
1481 "CHANGE",
1482 "CONDITION",
1483 "DATABASES",
1484 "DAY_HOUR",
1485 "DAY_MICROSECOND",
1486 "DAY_MINUTE",
1487 "DAY_SECOND",
1488 "DELAYED",
1489 "DETERMINISTIC",
1490 "DIV",
1491 "DUAL",
1492 "EACH",
1493 "ELSEIF",
1494 "ENCLOSED",
1495 "EXPLAIN",
1496 "FLOAT4",
1497 "FLOAT8",
1498 "FORCE",
1499 "HOUR_MICROSECOND",
1500 "HOUR_MINUTE",
1501 "HOUR_SECOND",
1502 "IGNORE",
1503 "INFILE",
1504 "INT1",
1505 "INT2",
1506 "INT3",
1507 "INT4",
1508 "INT8",
1509 "ITERATE",
1510 "KEYS",
1511 "KILL",
1512 "LEADING",
1513 "LEAVE",
1514 "LINES",
1515 "LOAD",
1516 "LOCK",
1517 "LONG",
1518 "LONGBLOB",
1519 "LONGTEXT",
1520 "LOOP",
1521 "LOW_PRIORITY",
1522 "MATCH",
1523 "MEDIUMBLOB",
1524 "MEDIUMINT",
1525 "MEDIUMTEXT",
1526 "MINUTE_MICROSECOND",
1527 "MINUTE_SECOND",
1528 "MOD",
1529 "MODIFIES",
1530 "NO_WRITE_TO_BINLOG",
1531 "OPTIMIZE",
1532 "OPTIONALLY",
1533 "OUT",
1534 "OUTFILE",
1535 "PURGE",
1536 "READS",
1537 "REGEXP",
1538 "RELEASE",
1539 "RENAME",
1540 "REPEAT",
1541 "REQUIRE",
1542 "RESIGNAL",
1543 "RETURN",
1544 "REVOKE",
1545 "RLIKE",
1546 "SCHEMA",
1547 "SCHEMAS",
1548 "SECOND_MICROSECOND",
1549 "SENSITIVE",
1550 "SEPARATOR",
1551 "SHOW",
1552 "SIGNAL",
1553 "SPATIAL",
1554 "SQL",
1555 "SQLEXCEPTION",
1556 "SQLSTATE",
1557 "SQLWARNING",
1558 "SQL_BIG_RESULT",
1559 "SQL_CALC_FOUND_ROWS",
1560 "SQL_SMALL_RESULT",
1561 "SSL",
1562 "STARTING",
1563 "STRAIGHT_JOIN",
1564 "TERMINATED",
1565 "TINYBLOB",
1566 "TINYINT",
1567 "TINYTEXT",
1568 "TRAILING",
1569 "TRIGGER",
1570 "UNDO",
1571 "UNLOCK",
1572 "UNSIGNED",
1573 "USAGE",
1574 "UTC_DATE",
1575 "UTC_TIME",
1576 "UTC_TIMESTAMP",
1577 "VARBINARY",
1578 "VARCHARACTER",
1579 "WHILE",
1580 "WRITE",
1581 "XOR",
1582 "YEAR_MONTH",
1583 "ZEROFILL",
1584 ]
1585 .iter()
1586 .copied(),
1587 );
1588 }
1589 Some(DialectType::PostgreSQL) | Some(DialectType::CockroachDB) => {
1590 words.extend(
1591 [
1592 "ANALYSE",
1593 "ANALYZE",
1594 "ARRAY",
1595 "AUTHORIZATION",
1596 "BINARY",
1597 "BOTH",
1598 "COLLATE",
1599 "CONCURRENTLY",
1600 "DO",
1601 "FREEZE",
1602 "ILIKE",
1603 "INITIALLY",
1604 "ISNULL",
1605 "LATERAL",
1606 "LEADING",
1607 "LOCALTIME",
1608 "LOCALTIMESTAMP",
1609 "NOTNULL",
1610 "ONLY",
1611 "OVERLAPS",
1612 "PLACING",
1613 "SIMILAR",
1614 "SYMMETRIC",
1615 "TABLESAMPLE",
1616 "TRAILING",
1617 "VARIADIC",
1618 "VERBOSE",
1619 ]
1620 .iter()
1621 .copied(),
1622 );
1623 }
1624 Some(DialectType::BigQuery) => {
1625 words.extend(
1626 [
1627 "ASSERT_ROWS_MODIFIED",
1628 "COLLATE",
1629 "CONTAINS",
1630 "CUBE",
1631 "DEFINE",
1632 "ENUM",
1633 "EXTRACT",
1634 "FOLLOWING",
1635 "GROUPING",
1636 "GROUPS",
1637 "HASH",
1638 "IGNORE",
1639 "LATERAL",
1640 "LOOKUP",
1641 "MERGE",
1642 "NEW",
1643 "NO",
1644 "NULLS",
1645 "OF",
1646 "OVER",
1647 "PARTITION",
1648 "PRECEDING",
1649 "PROTO",
1650 "RANGE",
1651 "RECURSIVE",
1652 "RESPECT",
1653 "ROLLUP",
1654 "STRUCT",
1655 "TABLESAMPLE",
1656 "TREAT",
1657 "UNBOUNDED",
1658 "WITHIN",
1659 ]
1660 .iter()
1661 .copied(),
1662 );
1663 }
1664 Some(DialectType::Snowflake) => {
1665 words.extend(
1666 [
1667 "ACCOUNT",
1668 "BOTH",
1669 "CONNECT",
1670 "FOLLOWING",
1671 "ILIKE",
1672 "INCREMENT",
1673 "ISSUE",
1674 "LATERAL",
1675 "LEADING",
1676 "LOCALTIME",
1677 "LOCALTIMESTAMP",
1678 "MINUS",
1679 "QUALIFY",
1680 "REGEXP",
1681 "RLIKE",
1682 "SOME",
1683 "START",
1684 "TABLESAMPLE",
1685 "TOP",
1686 "TRAILING",
1687 "TRY_CAST",
1688 ]
1689 .iter()
1690 .copied(),
1691 );
1692 }
1693 Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
1694 words.extend(
1695 [
1696 "BACKUP",
1697 "BREAK",
1698 "BROWSE",
1699 "BULK",
1700 "CASCADE",
1701 "CHECKPOINT",
1702 "CLOSE",
1703 "CLUSTERED",
1704 "COALESCE",
1705 "COMPUTE",
1706 "CONTAINS",
1707 "CONTAINSTABLE",
1708 "CONTINUE",
1709 "CONVERT",
1710 "DBCC",
1711 "DEALLOCATE",
1712 "DENY",
1713 "DISK",
1714 "DISTRIBUTED",
1715 "DUMP",
1716 "ERRLVL",
1717 "EXEC",
1718 "EXECUTE",
1719 "EXIT",
1720 "EXTERNAL",
1721 "FILE",
1722 "FILLFACTOR",
1723 "FREETEXT",
1724 "FREETEXTTABLE",
1725 "FUNCTION",
1726 "GOTO",
1727 "HOLDLOCK",
1728 "IDENTITY",
1729 "IDENTITYCOL",
1730 "IDENTITY_INSERT",
1731 "KILL",
1732 "LINENO",
1733 "MERGE",
1734 "NONCLUSTERED",
1735 "NULLIF",
1736 "OF",
1737 "OFF",
1738 "OFFSETS",
1739 "OPEN",
1740 "OPENDATASOURCE",
1741 "OPENQUERY",
1742 "OPENROWSET",
1743 "OPENXML",
1744 "OVER",
1745 "PERCENT",
1746 "PIVOT",
1747 "PLAN",
1748 "PRINT",
1749 "PROC",
1750 "PROCEDURE",
1751 "PUBLIC",
1752 "RAISERROR",
1753 "READ",
1754 "READTEXT",
1755 "RECONFIGURE",
1756 "REPLICATION",
1757 "RESTORE",
1758 "RESTRICT",
1759 "REVERT",
1760 "ROWCOUNT",
1761 "ROWGUIDCOL",
1762 "RULE",
1763 "SAVE",
1764 "SECURITYAUDIT",
1765 "SEMANTICKEYPHRASETABLE",
1766 "SEMANTICSIMILARITYDETAILSTABLE",
1767 "SEMANTICSIMILARITYTABLE",
1768 "SETUSER",
1769 "SHUTDOWN",
1770 "STATISTICS",
1771 "SYSTEM_USER",
1772 "TEXTSIZE",
1773 "TOP",
1774 "TRAN",
1775 "TRANSACTION",
1776 "TRIGGER",
1777 "TSEQUAL",
1778 "UNPIVOT",
1779 "UPDATETEXT",
1780 "WAITFOR",
1781 "WRITETEXT",
1782 ]
1783 .iter()
1784 .copied(),
1785 );
1786 }
1787 Some(DialectType::ClickHouse) => {
1788 words.extend(
1789 [
1790 "ANTI",
1791 "ARRAY",
1792 "ASOF",
1793 "FINAL",
1794 "FORMAT",
1795 "GLOBAL",
1796 "INF",
1797 "KILL",
1798 "MATERIALIZED",
1799 "NAN",
1800 "PREWHERE",
1801 "SAMPLE",
1802 "SEMI",
1803 "SETTINGS",
1804 "TOP",
1805 ]
1806 .iter()
1807 .copied(),
1808 );
1809 }
1810 Some(DialectType::DuckDB) => {
1811 words.extend(
1812 [
1813 "ANALYSE",
1814 "ANALYZE",
1815 "ARRAY",
1816 "BOTH",
1817 "LATERAL",
1818 "LEADING",
1819 "LOCALTIME",
1820 "LOCALTIMESTAMP",
1821 "PLACING",
1822 "QUALIFY",
1823 "SIMILAR",
1824 "TABLESAMPLE",
1825 "TRAILING",
1826 ]
1827 .iter()
1828 .copied(),
1829 );
1830 }
1831 Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
1832 words.extend(
1833 [
1834 "BOTH",
1835 "CLUSTER",
1836 "DISTRIBUTE",
1837 "EXCHANGE",
1838 "EXTENDED",
1839 "FUNCTION",
1840 "LATERAL",
1841 "LEADING",
1842 "MACRO",
1843 "OVER",
1844 "PARTITION",
1845 "PERCENT",
1846 "RANGE",
1847 "READS",
1848 "REDUCE",
1849 "REGEXP",
1850 "REVOKE",
1851 "RLIKE",
1852 "ROLLUP",
1853 "SEMI",
1854 "SORT",
1855 "TABLESAMPLE",
1856 "TRAILING",
1857 "TRANSFORM",
1858 "UNBOUNDED",
1859 "UNIQUEJOIN",
1860 ]
1861 .iter()
1862 .copied(),
1863 );
1864 }
1865 Some(DialectType::Trino) | Some(DialectType::Presto) | Some(DialectType::Athena) => {
1866 words.extend(
1867 [
1868 "CUBE",
1869 "DEALLOCATE",
1870 "DESCRIBE",
1871 "EXECUTE",
1872 "EXTRACT",
1873 "GROUPING",
1874 "LATERAL",
1875 "LOCALTIME",
1876 "LOCALTIMESTAMP",
1877 "NORMALIZE",
1878 "PREPARE",
1879 "ROLLUP",
1880 "SOME",
1881 "TABLESAMPLE",
1882 "UESCAPE",
1883 "UNNEST",
1884 ]
1885 .iter()
1886 .copied(),
1887 );
1888 }
1889 Some(DialectType::Oracle) => {
1890 words.extend(
1891 [
1892 "ACCESS",
1893 "AUDIT",
1894 "CLUSTER",
1895 "COMMENT",
1896 "COMPRESS",
1897 "CONNECT",
1898 "EXCLUSIVE",
1899 "FILE",
1900 "IDENTIFIED",
1901 "IMMEDIATE",
1902 "INCREMENT",
1903 "INITIAL",
1904 "LEVEL",
1905 "LOCK",
1906 "LONG",
1907 "MAXEXTENTS",
1908 "MINUS",
1909 "MODE",
1910 "NOAUDIT",
1911 "NOCOMPRESS",
1912 "NOWAIT",
1913 "NUMBER",
1914 "OF",
1915 "OFFLINE",
1916 "ONLINE",
1917 "PCTFREE",
1918 "PRIOR",
1919 "RAW",
1920 "RENAME",
1921 "RESOURCE",
1922 "REVOKE",
1923 "SHARE",
1924 "SIZE",
1925 "START",
1926 "SUCCESSFUL",
1927 "SYNONYM",
1928 "SYSDATE",
1929 "TRIGGER",
1930 "UID",
1931 "VALIDATE",
1932 "VARCHAR2",
1933 "WHENEVER",
1934 ]
1935 .iter()
1936 .copied(),
1937 );
1938 }
1939 Some(DialectType::Redshift) => {
1940 words.extend(
1941 [
1942 "AZ64",
1943 "BZIP2",
1944 "DELTA",
1945 "DELTA32K",
1946 "DISTSTYLE",
1947 "ENCODE",
1948 "GZIP",
1949 "ILIKE",
1950 "LIMIT",
1951 "LUNS",
1952 "LZO",
1953 "LZOP",
1954 "MOSTLY13",
1955 "MOSTLY32",
1956 "MOSTLY8",
1957 "RAW",
1958 "SIMILAR",
1959 "SNAPSHOT",
1960 "SORTKEY",
1961 "SYSDATE",
1962 "TOP",
1963 "ZSTD",
1964 ]
1965 .iter()
1966 .copied(),
1967 );
1968 }
1969 _ => {
1970 words.extend(
1972 [
1973 "ANALYZE",
1974 "ARRAY",
1975 "BOTH",
1976 "CUBE",
1977 "GROUPING",
1978 "LATERAL",
1979 "LEADING",
1980 "LOCALTIME",
1981 "LOCALTIMESTAMP",
1982 "OVER",
1983 "PARTITION",
1984 "QUALIFY",
1985 "RANGE",
1986 "ROLLUP",
1987 "SIMILAR",
1988 "SOME",
1989 "TABLESAMPLE",
1990 "TRAILING",
1991 ]
1992 .iter()
1993 .copied(),
1994 );
1995 }
1996 }
1997
1998 words
1999}
2000
2001fn needs_quoting(name: &str, reserved_words: &HashSet<&str>) -> bool {
2009 if name.is_empty() {
2010 return false;
2011 }
2012
2013 if name.as_bytes()[0].is_ascii_digit() {
2015 return true;
2016 }
2017
2018 if !name.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'_') {
2020 return true;
2021 }
2022
2023 let upper = name.to_uppercase();
2025 reserved_words.contains(upper.as_str())
2026}
2027
2028fn maybe_quote(id: &mut Identifier, reserved_words: &HashSet<&str>) {
2030 if id.quoted || id.name.is_empty() || id.name == "*" {
2033 return;
2034 }
2035 if needs_quoting(&id.name, reserved_words) {
2036 id.quoted = true;
2037 }
2038}
2039
2040fn quote_identifiers_recursive(expr: &mut Expression, reserved_words: &HashSet<&str>) {
2042 match expr {
2043 Expression::Identifier(id) => {
2045 maybe_quote(id, reserved_words);
2046 }
2047
2048 Expression::Column(col) => {
2049 maybe_quote(&mut col.name, reserved_words);
2050 if let Some(ref mut table) = col.table {
2051 maybe_quote(table, reserved_words);
2052 }
2053 }
2054
2055 Expression::Table(table_ref) => {
2056 maybe_quote(&mut table_ref.name, reserved_words);
2057 if let Some(ref mut schema) = table_ref.schema {
2058 maybe_quote(schema, reserved_words);
2059 }
2060 if let Some(ref mut catalog) = table_ref.catalog {
2061 maybe_quote(catalog, reserved_words);
2062 }
2063 if let Some(ref mut alias) = table_ref.alias {
2064 maybe_quote(alias, reserved_words);
2065 }
2066 for ca in &mut table_ref.column_aliases {
2067 maybe_quote(ca, reserved_words);
2068 }
2069 for p in &mut table_ref.partitions {
2070 maybe_quote(p, reserved_words);
2071 }
2072 for h in &mut table_ref.hints {
2074 quote_identifiers_recursive(h, reserved_words);
2075 }
2076 if let Some(ref mut ver) = table_ref.version {
2077 quote_identifiers_recursive(&mut ver.this, reserved_words);
2078 if let Some(ref mut e) = ver.expression {
2079 quote_identifiers_recursive(e, reserved_words);
2080 }
2081 }
2082 }
2083
2084 Expression::Star(star) => {
2085 if let Some(ref mut table) = star.table {
2086 maybe_quote(table, reserved_words);
2087 }
2088 if let Some(ref mut except_ids) = star.except {
2089 for id in except_ids {
2090 maybe_quote(id, reserved_words);
2091 }
2092 }
2093 if let Some(ref mut replace_aliases) = star.replace {
2094 for alias in replace_aliases {
2095 maybe_quote(&mut alias.alias, reserved_words);
2096 quote_identifiers_recursive(&mut alias.this, reserved_words);
2097 }
2098 }
2099 if let Some(ref mut rename_pairs) = star.rename {
2100 for (from, to) in rename_pairs {
2101 maybe_quote(from, reserved_words);
2102 maybe_quote(to, reserved_words);
2103 }
2104 }
2105 }
2106
2107 Expression::Alias(alias) => {
2109 maybe_quote(&mut alias.alias, reserved_words);
2110 for ca in &mut alias.column_aliases {
2111 maybe_quote(ca, reserved_words);
2112 }
2113 quote_identifiers_recursive(&mut alias.this, reserved_words);
2114 }
2115
2116 Expression::Select(select) => {
2118 for e in &mut select.expressions {
2119 quote_identifiers_recursive(e, reserved_words);
2120 }
2121 if let Some(ref mut from) = select.from {
2122 for e in &mut from.expressions {
2123 quote_identifiers_recursive(e, reserved_words);
2124 }
2125 }
2126 for join in &mut select.joins {
2127 quote_join(join, reserved_words);
2128 }
2129 for lv in &mut select.lateral_views {
2130 quote_lateral_view(lv, reserved_words);
2131 }
2132 if let Some(ref mut prewhere) = select.prewhere {
2133 quote_identifiers_recursive(prewhere, reserved_words);
2134 }
2135 if let Some(ref mut wh) = select.where_clause {
2136 quote_identifiers_recursive(&mut wh.this, reserved_words);
2137 }
2138 if let Some(ref mut gb) = select.group_by {
2139 for e in &mut gb.expressions {
2140 quote_identifiers_recursive(e, reserved_words);
2141 }
2142 }
2143 if let Some(ref mut hv) = select.having {
2144 quote_identifiers_recursive(&mut hv.this, reserved_words);
2145 }
2146 if let Some(ref mut q) = select.qualify {
2147 quote_identifiers_recursive(&mut q.this, reserved_words);
2148 }
2149 if let Some(ref mut ob) = select.order_by {
2150 for o in &mut ob.expressions {
2151 quote_identifiers_recursive(&mut o.this, reserved_words);
2152 }
2153 }
2154 if let Some(ref mut lim) = select.limit {
2155 quote_identifiers_recursive(&mut lim.this, reserved_words);
2156 }
2157 if let Some(ref mut off) = select.offset {
2158 quote_identifiers_recursive(&mut off.this, reserved_words);
2159 }
2160 if let Some(ref mut with) = select.with {
2161 quote_with(with, reserved_words);
2162 }
2163 if let Some(ref mut windows) = select.windows {
2164 for nw in windows {
2165 maybe_quote(&mut nw.name, reserved_words);
2166 quote_over(&mut nw.spec, reserved_words);
2167 }
2168 }
2169 if let Some(ref mut distinct_on) = select.distinct_on {
2170 for e in distinct_on {
2171 quote_identifiers_recursive(e, reserved_words);
2172 }
2173 }
2174 if let Some(ref mut limit_by) = select.limit_by {
2175 for e in limit_by {
2176 quote_identifiers_recursive(e, reserved_words);
2177 }
2178 }
2179 if let Some(ref mut settings) = select.settings {
2180 for e in settings {
2181 quote_identifiers_recursive(e, reserved_words);
2182 }
2183 }
2184 if let Some(ref mut format) = select.format {
2185 quote_identifiers_recursive(format, reserved_words);
2186 }
2187 }
2188
2189 Expression::Union(u) => {
2191 quote_identifiers_recursive(&mut u.left, reserved_words);
2192 quote_identifiers_recursive(&mut u.right, reserved_words);
2193 if let Some(ref mut with) = u.with {
2194 quote_with(with, reserved_words);
2195 }
2196 if let Some(ref mut ob) = u.order_by {
2197 for o in &mut ob.expressions {
2198 quote_identifiers_recursive(&mut o.this, reserved_words);
2199 }
2200 }
2201 if let Some(ref mut lim) = u.limit {
2202 quote_identifiers_recursive(lim, reserved_words);
2203 }
2204 if let Some(ref mut off) = u.offset {
2205 quote_identifiers_recursive(off, reserved_words);
2206 }
2207 }
2208 Expression::Intersect(i) => {
2209 quote_identifiers_recursive(&mut i.left, reserved_words);
2210 quote_identifiers_recursive(&mut i.right, reserved_words);
2211 if let Some(ref mut with) = i.with {
2212 quote_with(with, reserved_words);
2213 }
2214 if let Some(ref mut ob) = i.order_by {
2215 for o in &mut ob.expressions {
2216 quote_identifiers_recursive(&mut o.this, reserved_words);
2217 }
2218 }
2219 if let Some(limit) = &mut i.limit {
2220 quote_identifiers_recursive(limit, reserved_words);
2221 }
2222 if let Some(offset) = &mut i.offset {
2223 quote_identifiers_recursive(offset, reserved_words);
2224 }
2225 }
2226 Expression::Except(e) => {
2227 quote_identifiers_recursive(&mut e.left, reserved_words);
2228 quote_identifiers_recursive(&mut e.right, reserved_words);
2229 if let Some(ref mut with) = e.with {
2230 quote_with(with, reserved_words);
2231 }
2232 if let Some(ref mut ob) = e.order_by {
2233 for o in &mut ob.expressions {
2234 quote_identifiers_recursive(&mut o.this, reserved_words);
2235 }
2236 }
2237 if let Some(limit) = &mut e.limit {
2238 quote_identifiers_recursive(limit, reserved_words);
2239 }
2240 if let Some(offset) = &mut e.offset {
2241 quote_identifiers_recursive(offset, reserved_words);
2242 }
2243 }
2244
2245 Expression::Subquery(sq) => {
2247 quote_identifiers_recursive(&mut sq.this, reserved_words);
2248 if let Some(ref mut alias) = sq.alias {
2249 maybe_quote(alias, reserved_words);
2250 }
2251 for ca in &mut sq.column_aliases {
2252 maybe_quote(ca, reserved_words);
2253 }
2254 if let Some(ref mut ob) = sq.order_by {
2255 for o in &mut ob.expressions {
2256 quote_identifiers_recursive(&mut o.this, reserved_words);
2257 }
2258 }
2259 }
2260
2261 Expression::Insert(ins) => {
2263 quote_table_ref(&mut ins.table, reserved_words);
2264 for c in &mut ins.columns {
2265 maybe_quote(c, reserved_words);
2266 }
2267 for row in &mut ins.values {
2268 for e in row {
2269 quote_identifiers_recursive(e, reserved_words);
2270 }
2271 }
2272 if let Some(ref mut q) = ins.query {
2273 quote_identifiers_recursive(q, reserved_words);
2274 }
2275 for (id, val) in &mut ins.partition {
2276 maybe_quote(id, reserved_words);
2277 if let Some(ref mut v) = val {
2278 quote_identifiers_recursive(v, reserved_words);
2279 }
2280 }
2281 for e in &mut ins.returning {
2282 quote_identifiers_recursive(e, reserved_words);
2283 }
2284 if let Some(ref mut on_conflict) = ins.on_conflict {
2285 quote_identifiers_recursive(on_conflict, reserved_words);
2286 }
2287 if let Some(ref mut with) = ins.with {
2288 quote_with(with, reserved_words);
2289 }
2290 if let Some(ref mut alias) = ins.alias {
2291 maybe_quote(alias, reserved_words);
2292 }
2293 if let Some(ref mut src_alias) = ins.source_alias {
2294 maybe_quote(src_alias, reserved_words);
2295 }
2296 }
2297
2298 Expression::Update(upd) => {
2299 quote_table_ref(&mut upd.table, reserved_words);
2300 for tr in &mut upd.extra_tables {
2301 quote_table_ref(tr, reserved_words);
2302 }
2303 for join in &mut upd.table_joins {
2304 quote_join(join, reserved_words);
2305 }
2306 for (id, val) in &mut upd.set {
2307 maybe_quote(id, reserved_words);
2308 quote_identifiers_recursive(val, reserved_words);
2309 }
2310 if let Some(ref mut from) = upd.from_clause {
2311 for e in &mut from.expressions {
2312 quote_identifiers_recursive(e, reserved_words);
2313 }
2314 }
2315 for join in &mut upd.from_joins {
2316 quote_join(join, reserved_words);
2317 }
2318 if let Some(ref mut wh) = upd.where_clause {
2319 quote_identifiers_recursive(&mut wh.this, reserved_words);
2320 }
2321 for e in &mut upd.returning {
2322 quote_identifiers_recursive(e, reserved_words);
2323 }
2324 if let Some(ref mut with) = upd.with {
2325 quote_with(with, reserved_words);
2326 }
2327 }
2328
2329 Expression::Delete(del) => {
2330 quote_table_ref(&mut del.table, reserved_words);
2331 if let Some(ref mut alias) = del.alias {
2332 maybe_quote(alias, reserved_words);
2333 }
2334 for tr in &mut del.using {
2335 quote_table_ref(tr, reserved_words);
2336 }
2337 if let Some(ref mut wh) = del.where_clause {
2338 quote_identifiers_recursive(&mut wh.this, reserved_words);
2339 }
2340 if let Some(ref mut with) = del.with {
2341 quote_with(with, reserved_words);
2342 }
2343 }
2344
2345 Expression::And(bin)
2347 | Expression::Or(bin)
2348 | Expression::Eq(bin)
2349 | Expression::Neq(bin)
2350 | Expression::Lt(bin)
2351 | Expression::Lte(bin)
2352 | Expression::Gt(bin)
2353 | Expression::Gte(bin)
2354 | Expression::Add(bin)
2355 | Expression::Sub(bin)
2356 | Expression::Mul(bin)
2357 | Expression::Div(bin)
2358 | Expression::Mod(bin)
2359 | Expression::BitwiseAnd(bin)
2360 | Expression::BitwiseOr(bin)
2361 | Expression::BitwiseXor(bin)
2362 | Expression::Concat(bin)
2363 | Expression::Adjacent(bin)
2364 | Expression::TsMatch(bin)
2365 | Expression::PropertyEQ(bin)
2366 | Expression::ArrayContainsAll(bin)
2367 | Expression::ArrayContainedBy(bin)
2368 | Expression::ArrayOverlaps(bin)
2369 | Expression::JSONBContainsAllTopKeys(bin)
2370 | Expression::JSONBContainsAnyTopKeys(bin)
2371 | Expression::JSONBDeleteAtPath(bin)
2372 | Expression::ExtendsLeft(bin)
2373 | Expression::ExtendsRight(bin)
2374 | Expression::Is(bin)
2375 | Expression::NullSafeEq(bin)
2376 | Expression::NullSafeNeq(bin)
2377 | Expression::Glob(bin)
2378 | Expression::Match(bin)
2379 | Expression::MemberOf(bin)
2380 | Expression::BitwiseLeftShift(bin)
2381 | Expression::BitwiseRightShift(bin) => {
2382 quote_identifiers_recursive(&mut bin.left, reserved_words);
2383 quote_identifiers_recursive(&mut bin.right, reserved_words);
2384 }
2385
2386 Expression::Like(like) | Expression::ILike(like) => {
2388 quote_identifiers_recursive(&mut like.left, reserved_words);
2389 quote_identifiers_recursive(&mut like.right, reserved_words);
2390 if let Some(ref mut esc) = like.escape {
2391 quote_identifiers_recursive(esc, reserved_words);
2392 }
2393 }
2394
2395 Expression::Not(un) | Expression::Neg(un) | Expression::BitwiseNot(un) => {
2397 quote_identifiers_recursive(&mut un.this, reserved_words);
2398 }
2399
2400 Expression::In(in_expr) => {
2402 quote_identifiers_recursive(&mut in_expr.this, reserved_words);
2403 for e in &mut in_expr.expressions {
2404 quote_identifiers_recursive(e, reserved_words);
2405 }
2406 if let Some(ref mut q) = in_expr.query {
2407 quote_identifiers_recursive(q, reserved_words);
2408 }
2409 if let Some(ref mut un) = in_expr.unnest {
2410 quote_identifiers_recursive(un, reserved_words);
2411 }
2412 }
2413
2414 Expression::Between(bw) => {
2415 quote_identifiers_recursive(&mut bw.this, reserved_words);
2416 quote_identifiers_recursive(&mut bw.low, reserved_words);
2417 quote_identifiers_recursive(&mut bw.high, reserved_words);
2418 }
2419
2420 Expression::IsNull(is_null) => {
2421 quote_identifiers_recursive(&mut is_null.this, reserved_words);
2422 }
2423
2424 Expression::IsTrue(is_tf) | Expression::IsFalse(is_tf) => {
2425 quote_identifiers_recursive(&mut is_tf.this, reserved_words);
2426 }
2427
2428 Expression::Exists(ex) => {
2429 quote_identifiers_recursive(&mut ex.this, reserved_words);
2430 }
2431
2432 Expression::Function(func) => {
2434 for arg in &mut func.args {
2435 quote_identifiers_recursive(arg, reserved_words);
2436 }
2437 }
2438
2439 Expression::AggregateFunction(agg) => {
2440 for arg in &mut agg.args {
2441 quote_identifiers_recursive(arg, reserved_words);
2442 }
2443 if let Some(ref mut filter) = agg.filter {
2444 quote_identifiers_recursive(filter, reserved_words);
2445 }
2446 for o in &mut agg.order_by {
2447 quote_identifiers_recursive(&mut o.this, reserved_words);
2448 }
2449 }
2450
2451 Expression::WindowFunction(wf) => {
2452 quote_identifiers_recursive(&mut wf.this, reserved_words);
2453 quote_over(&mut wf.over, reserved_words);
2454 }
2455
2456 Expression::Case(case) => {
2458 if let Some(ref mut operand) = case.operand {
2459 quote_identifiers_recursive(operand, reserved_words);
2460 }
2461 for (when, then) in &mut case.whens {
2462 quote_identifiers_recursive(when, reserved_words);
2463 quote_identifiers_recursive(then, reserved_words);
2464 }
2465 if let Some(ref mut else_) = case.else_ {
2466 quote_identifiers_recursive(else_, reserved_words);
2467 }
2468 }
2469
2470 Expression::Cast(cast) | Expression::TryCast(cast) | Expression::SafeCast(cast) => {
2472 quote_identifiers_recursive(&mut cast.this, reserved_words);
2473 if let Some(ref mut fmt) = cast.format {
2474 quote_identifiers_recursive(fmt, reserved_words);
2475 }
2476 }
2477
2478 Expression::Paren(paren) => {
2480 quote_identifiers_recursive(&mut paren.this, reserved_words);
2481 }
2482
2483 Expression::Annotated(ann) => {
2484 quote_identifiers_recursive(&mut ann.this, reserved_words);
2485 }
2486
2487 Expression::With(with) => {
2489 quote_with(with, reserved_words);
2490 }
2491
2492 Expression::Cte(cte) => {
2493 maybe_quote(&mut cte.alias, reserved_words);
2494 for c in &mut cte.columns {
2495 maybe_quote(c, reserved_words);
2496 }
2497 quote_identifiers_recursive(&mut cte.this, reserved_words);
2498 }
2499
2500 Expression::From(from) => {
2502 for e in &mut from.expressions {
2503 quote_identifiers_recursive(e, reserved_words);
2504 }
2505 }
2506
2507 Expression::Join(join) => {
2508 quote_join(join, reserved_words);
2509 }
2510
2511 Expression::JoinedTable(jt) => {
2512 quote_identifiers_recursive(&mut jt.left, reserved_words);
2513 for join in &mut jt.joins {
2514 quote_join(join, reserved_words);
2515 }
2516 if let Some(ref mut alias) = jt.alias {
2517 maybe_quote(alias, reserved_words);
2518 }
2519 }
2520
2521 Expression::Where(wh) => {
2522 quote_identifiers_recursive(&mut wh.this, reserved_words);
2523 }
2524
2525 Expression::GroupBy(gb) => {
2526 for e in &mut gb.expressions {
2527 quote_identifiers_recursive(e, reserved_words);
2528 }
2529 }
2530
2531 Expression::Having(hv) => {
2532 quote_identifiers_recursive(&mut hv.this, reserved_words);
2533 }
2534
2535 Expression::OrderBy(ob) => {
2536 for o in &mut ob.expressions {
2537 quote_identifiers_recursive(&mut o.this, reserved_words);
2538 }
2539 }
2540
2541 Expression::Ordered(ord) => {
2542 quote_identifiers_recursive(&mut ord.this, reserved_words);
2543 }
2544
2545 Expression::Limit(lim) => {
2546 quote_identifiers_recursive(&mut lim.this, reserved_words);
2547 }
2548
2549 Expression::Offset(off) => {
2550 quote_identifiers_recursive(&mut off.this, reserved_words);
2551 }
2552 Expression::Fetch(fetch) => {
2553 if let Some(count) = &mut fetch.count {
2554 quote_identifiers_recursive(count, reserved_words);
2555 }
2556 }
2557
2558 Expression::Qualify(q) => {
2559 quote_identifiers_recursive(&mut q.this, reserved_words);
2560 }
2561
2562 Expression::Window(ws) => {
2563 for e in &mut ws.partition_by {
2564 quote_identifiers_recursive(e, reserved_words);
2565 }
2566 for o in &mut ws.order_by {
2567 quote_identifiers_recursive(&mut o.this, reserved_words);
2568 }
2569 }
2570
2571 Expression::Over(over) => {
2572 quote_over(over, reserved_words);
2573 }
2574
2575 Expression::WithinGroup(wg) => {
2576 quote_identifiers_recursive(&mut wg.this, reserved_words);
2577 for o in &mut wg.order_by {
2578 quote_identifiers_recursive(&mut o.this, reserved_words);
2579 }
2580 }
2581
2582 Expression::Pivot(piv) => {
2584 quote_identifiers_recursive(&mut piv.this, reserved_words);
2585 for e in &mut piv.expressions {
2586 quote_identifiers_recursive(e, reserved_words);
2587 }
2588 for f in &mut piv.fields {
2589 quote_identifiers_recursive(f, reserved_words);
2590 }
2591 if let Some(ref mut alias) = piv.alias {
2592 maybe_quote(alias, reserved_words);
2593 }
2594 }
2595
2596 Expression::Unpivot(unpiv) => {
2597 quote_identifiers_recursive(&mut unpiv.this, reserved_words);
2598 maybe_quote(&mut unpiv.value_column, reserved_words);
2599 maybe_quote(&mut unpiv.name_column, reserved_words);
2600 for e in &mut unpiv.columns {
2601 quote_identifiers_recursive(e, reserved_words);
2602 }
2603 if let Some(ref mut alias) = unpiv.alias {
2604 maybe_quote(alias, reserved_words);
2605 }
2606 }
2607
2608 Expression::Values(vals) => {
2610 for tuple in &mut vals.expressions {
2611 for e in &mut tuple.expressions {
2612 quote_identifiers_recursive(e, reserved_words);
2613 }
2614 }
2615 if let Some(ref mut alias) = vals.alias {
2616 maybe_quote(alias, reserved_words);
2617 }
2618 for ca in &mut vals.column_aliases {
2619 maybe_quote(ca, reserved_words);
2620 }
2621 }
2622
2623 Expression::Array(arr) => {
2625 for e in &mut arr.expressions {
2626 quote_identifiers_recursive(e, reserved_words);
2627 }
2628 }
2629
2630 Expression::Struct(st) => {
2631 for (_name, e) in &mut st.fields {
2632 quote_identifiers_recursive(e, reserved_words);
2633 }
2634 }
2635
2636 Expression::Tuple(tup) => {
2637 for e in &mut tup.expressions {
2638 quote_identifiers_recursive(e, reserved_words);
2639 }
2640 }
2641
2642 Expression::Subscript(sub) => {
2644 quote_identifiers_recursive(&mut sub.this, reserved_words);
2645 quote_identifiers_recursive(&mut sub.index, reserved_words);
2646 }
2647
2648 Expression::Dot(dot) => {
2649 quote_identifiers_recursive(&mut dot.this, reserved_words);
2650 maybe_quote(&mut dot.field, reserved_words);
2651 }
2652
2653 Expression::ScopeResolution(sr) => {
2654 if let Some(ref mut this) = sr.this {
2655 quote_identifiers_recursive(this, reserved_words);
2656 }
2657 quote_identifiers_recursive(&mut sr.expression, reserved_words);
2658 }
2659
2660 Expression::Lateral(lat) => {
2662 quote_identifiers_recursive(&mut lat.this, reserved_words);
2663 }
2665
2666 Expression::DPipe(dpipe) => {
2668 quote_identifiers_recursive(&mut dpipe.this, reserved_words);
2669 quote_identifiers_recursive(&mut dpipe.expression, reserved_words);
2670 }
2671
2672 Expression::Merge(merge) => {
2674 quote_identifiers_recursive(&mut merge.this, reserved_words);
2675 quote_identifiers_recursive(&mut merge.using, reserved_words);
2676 if let Some(ref mut on) = merge.on {
2677 quote_identifiers_recursive(on, reserved_words);
2678 }
2679 if let Some(ref mut whens) = merge.whens {
2680 quote_identifiers_recursive(whens, reserved_words);
2681 }
2682 if let Some(ref mut with) = merge.with_ {
2683 quote_identifiers_recursive(with, reserved_words);
2684 }
2685 if let Some(ref mut ret) = merge.returning {
2686 quote_identifiers_recursive(ret, reserved_words);
2687 }
2688 }
2689
2690 Expression::LateralView(lv) => {
2692 quote_lateral_view(lv, reserved_words);
2693 }
2694
2695 Expression::Anonymous(anon) => {
2697 quote_identifiers_recursive(&mut anon.this, reserved_words);
2698 for e in &mut anon.expressions {
2699 quote_identifiers_recursive(e, reserved_words);
2700 }
2701 }
2702
2703 Expression::Filter(filter) => {
2705 quote_identifiers_recursive(&mut filter.this, reserved_words);
2706 quote_identifiers_recursive(&mut filter.expression, reserved_words);
2707 }
2708
2709 Expression::Returning(ret) => {
2711 for e in &mut ret.expressions {
2712 quote_identifiers_recursive(e, reserved_words);
2713 }
2714 }
2715
2716 Expression::BracedWildcard(inner) => {
2718 quote_identifiers_recursive(inner, reserved_words);
2719 }
2720
2721 Expression::ReturnStmt(inner) => {
2723 quote_identifiers_recursive(inner, reserved_words);
2724 }
2725
2726 Expression::Literal(_)
2728 | Expression::Boolean(_)
2729 | Expression::Null(_)
2730 | Expression::DataType(_)
2731 | Expression::Raw(_)
2732 | Expression::Placeholder(_)
2733 | Expression::CurrentDate(_)
2734 | Expression::CurrentTime(_)
2735 | Expression::CurrentTimestamp(_)
2736 | Expression::CurrentTimestampLTZ(_)
2737 | Expression::SessionUser(_)
2738 | Expression::RowNumber(_)
2739 | Expression::Rank(_)
2740 | Expression::DenseRank(_)
2741 | Expression::PercentRank(_)
2742 | Expression::CumeDist(_)
2743 | Expression::Random(_)
2744 | Expression::Pi(_)
2745 | Expression::JSONPathRoot(_) => {
2746 }
2748
2749 _ => {}
2753 }
2754}
2755
2756fn quote_join(join: &mut Join, reserved_words: &HashSet<&str>) {
2758 quote_identifiers_recursive(&mut join.this, reserved_words);
2759 if let Some(ref mut on) = join.on {
2760 quote_identifiers_recursive(on, reserved_words);
2761 }
2762 for id in &mut join.using {
2763 maybe_quote(id, reserved_words);
2764 }
2765 if let Some(ref mut mc) = join.match_condition {
2766 quote_identifiers_recursive(mc, reserved_words);
2767 }
2768 for piv in &mut join.pivots {
2769 quote_identifiers_recursive(piv, reserved_words);
2770 }
2771}
2772
2773fn quote_with(with: &mut With, reserved_words: &HashSet<&str>) {
2775 for cte in &mut with.ctes {
2776 maybe_quote(&mut cte.alias, reserved_words);
2777 for c in &mut cte.columns {
2778 maybe_quote(c, reserved_words);
2779 }
2780 for k in &mut cte.key_expressions {
2781 maybe_quote(k, reserved_words);
2782 }
2783 quote_identifiers_recursive(&mut cte.this, reserved_words);
2784 }
2785}
2786
2787fn quote_over(over: &mut Over, reserved_words: &HashSet<&str>) {
2789 if let Some(ref mut wn) = over.window_name {
2790 maybe_quote(wn, reserved_words);
2791 }
2792 for e in &mut over.partition_by {
2793 quote_identifiers_recursive(e, reserved_words);
2794 }
2795 for o in &mut over.order_by {
2796 quote_identifiers_recursive(&mut o.this, reserved_words);
2797 }
2798 if let Some(ref mut alias) = over.alias {
2799 maybe_quote(alias, reserved_words);
2800 }
2801}
2802
2803fn quote_table_ref(table_ref: &mut TableRef, reserved_words: &HashSet<&str>) {
2805 maybe_quote(&mut table_ref.name, reserved_words);
2806 if let Some(ref mut schema) = table_ref.schema {
2807 maybe_quote(schema, reserved_words);
2808 }
2809 if let Some(ref mut catalog) = table_ref.catalog {
2810 maybe_quote(catalog, reserved_words);
2811 }
2812 if let Some(ref mut alias) = table_ref.alias {
2813 maybe_quote(alias, reserved_words);
2814 }
2815 for ca in &mut table_ref.column_aliases {
2816 maybe_quote(ca, reserved_words);
2817 }
2818 for p in &mut table_ref.partitions {
2819 maybe_quote(p, reserved_words);
2820 }
2821 for h in &mut table_ref.hints {
2822 quote_identifiers_recursive(h, reserved_words);
2823 }
2824}
2825
2826fn quote_lateral_view(lv: &mut LateralView, reserved_words: &HashSet<&str>) {
2828 quote_identifiers_recursive(&mut lv.this, reserved_words);
2829 if let Some(ref mut ta) = lv.table_alias {
2830 maybe_quote(ta, reserved_words);
2831 }
2832 for ca in &mut lv.column_aliases {
2833 maybe_quote(ca, reserved_words);
2834 }
2835}
2836
2837pub fn quote_identifiers(expression: Expression, dialect: Option<DialectType>) -> Expression {
2848 let reserved_words = get_reserved_words(dialect);
2849 let mut result = expression;
2850 quote_identifiers_recursive(&mut result, &reserved_words);
2851 result
2852}
2853
2854pub fn pushdown_cte_alias_columns(_scope: &Scope) {
2859 }
2862
2863fn pushdown_cte_alias_columns_with(with: &mut With) {
2864 for cte in &mut with.ctes {
2865 if cte.columns.is_empty() {
2866 continue;
2867 }
2868
2869 if let Expression::Select(select) = &mut cte.this {
2870 let mut next_expressions = Vec::with_capacity(select.expressions.len());
2871
2872 for (i, projection) in select.expressions.iter().enumerate() {
2873 let Some(alias_name) = cte.columns.get(i) else {
2874 next_expressions.push(projection.clone());
2875 continue;
2876 };
2877
2878 match projection {
2879 Expression::Alias(existing) => {
2880 let mut aliased = existing.clone();
2881 aliased.alias = alias_name.clone();
2882 next_expressions.push(Expression::Alias(aliased));
2883 }
2884 _ => {
2885 next_expressions.push(create_alias(projection.clone(), &alias_name.name));
2886 }
2887 }
2888 }
2889
2890 select.expressions = next_expressions;
2891 }
2892 }
2893}
2894
2895fn get_scope_columns(scope: &Scope) -> Vec<ColumnRef> {
2901 let mut columns = Vec::new();
2902 collect_columns(&scope.expression, &mut columns);
2903 columns
2904}
2905
2906#[derive(Debug, Clone)]
2908struct ColumnRef {
2909 table: Option<String>,
2910 name: String,
2911}
2912
2913fn collect_columns(expr: &Expression, columns: &mut Vec<ColumnRef>) {
2915 match expr {
2916 Expression::Column(col) => {
2917 columns.push(ColumnRef {
2918 table: col.table.as_ref().map(|t| t.name.clone()),
2919 name: col.name.name.clone(),
2920 });
2921 }
2922 Expression::Select(select) => {
2923 for e in &select.expressions {
2924 collect_columns(e, columns);
2925 }
2926 if let Some(from) = &select.from {
2927 for e in &from.expressions {
2928 collect_columns(e, columns);
2929 }
2930 }
2931 if let Some(where_clause) = &select.where_clause {
2932 collect_columns(&where_clause.this, columns);
2933 }
2934 if let Some(group_by) = &select.group_by {
2935 for e in &group_by.expressions {
2936 collect_columns(e, columns);
2937 }
2938 }
2939 if let Some(having) = &select.having {
2940 collect_columns(&having.this, columns);
2941 }
2942 if let Some(order_by) = &select.order_by {
2943 for o in &order_by.expressions {
2944 collect_columns(&o.this, columns);
2945 }
2946 }
2947 for join in &select.joins {
2948 collect_columns(&join.this, columns);
2949 if let Some(on) = &join.on {
2950 collect_columns(on, columns);
2951 }
2952 }
2953 }
2954 Expression::Alias(alias) => {
2955 collect_columns(&alias.this, columns);
2956 }
2957 Expression::Function(func) => {
2958 for arg in &func.args {
2959 collect_columns(arg, columns);
2960 }
2961 }
2962 Expression::AggregateFunction(agg) => {
2963 for arg in &agg.args {
2964 collect_columns(arg, columns);
2965 }
2966 }
2967 Expression::And(bin)
2968 | Expression::Or(bin)
2969 | Expression::Eq(bin)
2970 | Expression::Neq(bin)
2971 | Expression::Lt(bin)
2972 | Expression::Lte(bin)
2973 | Expression::Gt(bin)
2974 | Expression::Gte(bin)
2975 | Expression::Add(bin)
2976 | Expression::Sub(bin)
2977 | Expression::Mul(bin)
2978 | Expression::Div(bin) => {
2979 collect_columns(&bin.left, columns);
2980 collect_columns(&bin.right, columns);
2981 }
2982 Expression::Not(unary) | Expression::Neg(unary) => {
2983 collect_columns(&unary.this, columns);
2984 }
2985 Expression::Paren(paren) => {
2986 collect_columns(&paren.this, columns);
2987 }
2988 Expression::Case(case) => {
2989 if let Some(operand) = &case.operand {
2990 collect_columns(operand, columns);
2991 }
2992 for (when, then) in &case.whens {
2993 collect_columns(when, columns);
2994 collect_columns(then, columns);
2995 }
2996 if let Some(else_) = &case.else_ {
2997 collect_columns(else_, columns);
2998 }
2999 }
3000 Expression::Cast(cast) => {
3001 collect_columns(&cast.this, columns);
3002 }
3003 Expression::In(in_expr) => {
3004 collect_columns(&in_expr.this, columns);
3005 for e in &in_expr.expressions {
3006 collect_columns(e, columns);
3007 }
3008 if let Some(query) = &in_expr.query {
3009 collect_columns(query, columns);
3010 }
3011 }
3012 Expression::Between(between) => {
3013 collect_columns(&between.this, columns);
3014 collect_columns(&between.low, columns);
3015 collect_columns(&between.high, columns);
3016 }
3017 Expression::Subquery(subquery) => {
3018 collect_columns(&subquery.this, columns);
3019 }
3020 _ => {}
3021 }
3022}
3023
3024fn get_unqualified_columns(scope: &Scope) -> Vec<ColumnRef> {
3026 get_scope_columns(scope)
3027 .into_iter()
3028 .filter(|c| c.table.is_none())
3029 .collect()
3030}
3031
3032fn get_external_columns(scope: &Scope) -> Vec<ColumnRef> {
3034 let source_names: HashSet<_> = scope.sources.keys().cloned().collect();
3035
3036 get_scope_columns(scope)
3037 .into_iter()
3038 .filter(|c| {
3039 if let Some(table) = &c.table {
3040 !source_names.contains(table)
3041 } else {
3042 false
3043 }
3044 })
3045 .collect()
3046}
3047
3048fn is_correlated_subquery(scope: &Scope) -> bool {
3050 scope.can_be_correlated && !get_external_columns(scope).is_empty()
3051}
3052
3053fn is_star_column(col: &Column) -> bool {
3055 col.name.name == "*"
3056}
3057
3058fn create_qualified_column(name: &str, table: Option<&str>) -> Expression {
3060 Expression::boxed_column(Column {
3061 name: Identifier::new(name),
3062 table: table.map(Identifier::new),
3063 join_mark: false,
3064 trailing_comments: vec![],
3065 span: None,
3066 inferred_type: None,
3067 })
3068}
3069
3070fn create_alias(expr: Expression, alias_name: &str) -> Expression {
3072 Expression::Alias(Box::new(Alias {
3073 this: expr,
3074 alias: Identifier::new(alias_name),
3075 column_aliases: vec![],
3076 alias_explicit_as: false,
3077 alias_keyword: None,
3078 pre_alias_comments: vec![],
3079 trailing_comments: vec![],
3080 inferred_type: None,
3081 }))
3082}
3083
3084fn get_output_name(expr: &Expression) -> Option<String> {
3086 match expr {
3087 Expression::Column(col) => Some(col.name.name.clone()),
3088 Expression::Alias(alias) => Some(alias.alias.name.clone()),
3089 Expression::Identifier(id) => Some(id.name.clone()),
3090 _ => None,
3091 }
3092}
3093
3094#[cfg(test)]
3095mod tests {
3096 use super::*;
3097 use crate::expressions::DataType;
3098 use crate::generator::Generator;
3099 use crate::parser::Parser;
3100 use crate::scope::build_scope;
3101 use crate::{MappingSchema, Schema};
3102
3103 fn gen(expr: &Expression) -> String {
3104 Generator::new().generate(expr).unwrap()
3105 }
3106
3107 fn parse(sql: &str) -> Expression {
3108 Parser::parse_sql(sql).expect("Failed to parse")[0].clone()
3109 }
3110
3111 #[test]
3112 fn test_qualify_columns_options() {
3113 let options = QualifyColumnsOptions::new()
3114 .with_expand_alias_refs(true)
3115 .with_expand_stars(false)
3116 .with_dialect(DialectType::PostgreSQL)
3117 .with_allow_partial(true);
3118
3119 assert!(options.expand_alias_refs);
3120 assert!(!options.expand_stars);
3121 assert_eq!(options.dialect, Some(DialectType::PostgreSQL));
3122 assert!(options.allow_partial_qualification);
3123 }
3124
3125 #[test]
3126 fn test_qualify_cte_chain_uses_lexically_selected_sources() {
3127 let mut schema = MappingSchema::new();
3128 schema
3129 .add_table("raw_orders", &[("amount".into(), DataType::Text)], None)
3130 .unwrap();
3131 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";
3132 let qualified =
3133 qualify_columns(parse(sql), &schema, &QualifyColumnsOptions::new()).unwrap();
3134 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");
3135 }
3136
3137 #[test]
3138 fn test_qualify_does_not_capture_unrelated_schema_tables() {
3139 let mut schema = MappingSchema::new();
3140 schema
3141 .add_table("unrelated", &[("n".into(), DataType::Text)], None)
3142 .unwrap();
3143 for sql in ["SELECT n FROM missing", "SELECT unrelated.n FROM missing"] {
3144 assert!(
3145 qualify_columns(parse(sql), &schema, &QualifyColumnsOptions::new()).is_err(),
3146 "{sql}"
3147 );
3148 }
3149 let sql = "SELECT n FROM missing";
3150 let qualified = qualify_columns(
3151 parse(sql),
3152 &schema,
3153 &QualifyColumnsOptions::new().with_allow_partial(true),
3154 )
3155 .unwrap();
3156 assert_eq!(gen(&qualified), "SELECT n AS n FROM missing");
3157 assert!(matches!(
3158 qualify_columns(
3159 parse(
3160 "WITH a AS (SELECT 1 AS n), b AS (SELECT 2 AS n) SELECT n FROM a CROSS JOIN b"
3161 ),
3162 &schema,
3163 &QualifyColumnsOptions::new()
3164 ),
3165 Err(QualifyColumnsError::AmbiguousColumn(_))
3166 ));
3167 }
3168
3169 #[test]
3170 fn test_qualify_correlated_cte_alias_and_nested_shadowing() {
3171 let schema = MappingSchema::new();
3172 for (sql, expected) in [
3173 ("WITH a AS (SELECT 1 AS n) SELECT (SELECT n) AS result FROM a AS outer_a", "(SELECT outer_a.n AS n)"),
3174 ("WITH a AS (SELECT 1 AS n) SELECT (SELECT (SELECT n)) AS result FROM a AS outer_a", "(SELECT outer_a.n AS n)"),
3175 ("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"),
3176 ("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"),
3177 ("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"),
3178 ("WITH a AS (SELECT 1 AS n) SELECT b.n FROM a, LATERAL (SELECT a.n) AS b", "SELECT a.n AS n"),
3179 ] {
3180 let qualified = qualify_columns(parse(sql), &schema, &QualifyColumnsOptions::new())
3181 .unwrap_or_else(|error| panic!("{sql}: {error}"));
3182 assert!(gen(&qualified).contains(expected), "{}", gen(&qualified));
3183 }
3184 }
3185
3186 #[test]
3187 fn test_get_scope_columns() {
3188 let expr = parse("SELECT a, b FROM t WHERE c = 1");
3189 let scope = build_scope(&expr);
3190 let columns = get_scope_columns(&scope);
3191
3192 assert!(columns.iter().any(|c| c.name == "a"));
3193 assert!(columns.iter().any(|c| c.name == "b"));
3194 assert!(columns.iter().any(|c| c.name == "c"));
3195 }
3196
3197 #[test]
3198 fn test_get_unqualified_columns() {
3199 let expr = parse("SELECT t.a, b FROM t");
3200 let scope = build_scope(&expr);
3201 let unqualified = get_unqualified_columns(&scope);
3202
3203 assert!(unqualified.iter().any(|c| c.name == "b"));
3205 assert!(!unqualified.iter().any(|c| c.name == "a"));
3206 }
3207
3208 #[test]
3209 fn test_is_star_column() {
3210 let col = Column {
3211 name: Identifier::new("*"),
3212 table: Some(Identifier::new("t")),
3213 join_mark: false,
3214 trailing_comments: vec![],
3215 span: None,
3216 inferred_type: None,
3217 };
3218 assert!(is_star_column(&col));
3219
3220 let col2 = Column {
3221 name: Identifier::new("id"),
3222 table: None,
3223 join_mark: false,
3224 trailing_comments: vec![],
3225 span: None,
3226 inferred_type: None,
3227 };
3228 assert!(!is_star_column(&col2));
3229 }
3230
3231 #[test]
3232 fn test_create_qualified_column() {
3233 let expr = create_qualified_column("id", Some("users"));
3234 let sql = gen(&expr);
3235 assert!(sql.contains("users"));
3236 assert!(sql.contains("id"));
3237 }
3238
3239 #[test]
3240 fn test_create_alias() {
3241 let col = Expression::boxed_column(Column {
3242 name: Identifier::new("value"),
3243 table: None,
3244 join_mark: false,
3245 trailing_comments: vec![],
3246 span: None,
3247 inferred_type: None,
3248 });
3249 let aliased = create_alias(col, "total");
3250 let sql = gen(&aliased);
3251 assert!(sql.contains("AS") || sql.contains("total"));
3252 }
3253
3254 #[test]
3255 fn test_qualify_columns_normalizes_struct_field_access_issue_408() {
3256 let struct_type = DataType::Struct {
3257 fields: vec![crate::expressions::StructField::new(
3258 "field_value".into(),
3259 DataType::Text,
3260 )],
3261 nested: true,
3262 };
3263 let mut schema = MappingSchema::with_dialect(DialectType::DuckDB);
3264 schema
3265 .add_table(
3266 "source_table",
3267 &[
3268 ("composite_value".into(), struct_type.clone()),
3269 (
3270 "nested_items".into(),
3271 DataType::Array {
3272 element_type: Box::new(struct_type),
3273 dimension: None,
3274 },
3275 ),
3276 ],
3277 None,
3278 )
3279 .expect("schema setup");
3280
3281 let cases = [
3282 (
3283 "SELECT composite_value.field_value AS output_value FROM source_table",
3284 "source_table",
3285 "composite_value",
3286 ),
3287 (
3288 "SELECT item.field_value AS output_value FROM source_table s \
3289 CROSS JOIN UNNEST(s.nested_items) AS expanded(item)",
3290 "expanded",
3291 "item",
3292 ),
3293 ];
3294
3295 for (sql, expected_table, expected_column) in cases {
3296 let qualified = qualify_columns(
3297 parse(sql),
3298 &schema,
3299 &QualifyColumnsOptions::new().with_dialect(DialectType::DuckDB),
3300 )
3301 .unwrap_or_else(|error| panic!("qualification failed for {sql:?}: {error}"));
3302 let Expression::Select(select) = qualified else {
3303 panic!("expected SELECT");
3304 };
3305 let Expression::Alias(alias) = &select.expressions[0] else {
3306 panic!("expected aliased projection");
3307 };
3308 let Expression::Dot(dot) = &alias.this else {
3309 panic!("expected normalized Dot, got {:?}", alias.this);
3310 };
3311 let Expression::Column(base) = &dot.this else {
3312 panic!("expected column-backed Dot");
3313 };
3314
3315 assert_eq!(
3316 base.table.as_ref().map(|table| table.name.as_str()),
3317 Some(expected_table)
3318 );
3319 assert_eq!(base.name.name, expected_column);
3320 assert_eq!(dot.field.name, "field_value");
3321 }
3322 }
3323
3324 #[test]
3325 fn test_validate_qualify_columns_success() {
3326 let expr = parse("SELECT t.a, t.b FROM t");
3328 let result = validate_qualify_columns(&expr);
3329 let _ = result;
3332 }
3333
3334 #[test]
3335 fn test_collect_columns_nested() {
3336 let expr = parse("SELECT a + b, c FROM t WHERE d > 0 GROUP BY e HAVING f = 1");
3337 let mut columns = Vec::new();
3338 collect_columns(&expr, &mut columns);
3339
3340 let names: Vec<_> = columns.iter().map(|c| c.name.as_str()).collect();
3341 assert!(names.contains(&"a"));
3342 assert!(names.contains(&"b"));
3343 assert!(names.contains(&"c"));
3344 assert!(names.contains(&"d"));
3345 assert!(names.contains(&"e"));
3346 assert!(names.contains(&"f"));
3347 }
3348
3349 #[test]
3350 fn test_collect_columns_in_case() {
3351 let expr = parse("SELECT CASE WHEN a = 1 THEN b ELSE c END FROM t");
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 }
3360
3361 #[test]
3362 fn test_collect_columns_in_subquery() {
3363 let expr = parse("SELECT a FROM t WHERE b IN (SELECT c FROM s)");
3364 let mut columns = Vec::new();
3365 collect_columns(&expr, &mut columns);
3366
3367 let names: Vec<_> = columns.iter().map(|c| c.name.as_str()).collect();
3368 assert!(names.contains(&"a"));
3369 assert!(names.contains(&"b"));
3370 assert!(names.contains(&"c"));
3371 }
3372
3373 #[test]
3374 fn test_qualify_outputs_basic() {
3375 let expr = parse("SELECT a, b + c FROM t");
3376 let scope = build_scope(&expr);
3377 let result = qualify_outputs(&scope);
3378 assert!(result.is_ok());
3379 }
3380
3381 #[test]
3382 fn test_qualify_columns_expands_star_with_schema() {
3383 let expr = parse("SELECT * FROM users");
3384
3385 let mut schema = MappingSchema::new();
3386 schema
3387 .add_table(
3388 "users",
3389 &[
3390 (
3391 "id".to_string(),
3392 DataType::Int {
3393 length: None,
3394 integer_spelling: false,
3395 },
3396 ),
3397 ("name".to_string(), DataType::Text),
3398 ("email".to_string(), DataType::Text),
3399 ],
3400 None,
3401 )
3402 .expect("schema setup");
3403
3404 let result =
3405 qualify_columns(expr, &schema, &QualifyColumnsOptions::new()).expect("qualify");
3406 let sql = gen(&result);
3407
3408 assert!(!sql.contains("SELECT *"));
3409 assert!(sql.contains("users.id"));
3410 assert!(sql.contains("users.name"));
3411 assert!(sql.contains("users.email"));
3412 }
3413
3414 #[test]
3415 fn test_qualify_columns_expands_group_by_positions() {
3416 let expr = parse("SELECT a, b FROM t GROUP BY 1, 2");
3417
3418 let mut schema = MappingSchema::new();
3419 schema
3420 .add_table(
3421 "t",
3422 &[
3423 (
3424 "a".to_string(),
3425 DataType::Int {
3426 length: None,
3427 integer_spelling: false,
3428 },
3429 ),
3430 (
3431 "b".to_string(),
3432 DataType::Int {
3433 length: None,
3434 integer_spelling: false,
3435 },
3436 ),
3437 ],
3438 None,
3439 )
3440 .expect("schema setup");
3441
3442 let result =
3443 qualify_columns(expr, &schema, &QualifyColumnsOptions::new()).expect("qualify");
3444 let sql = gen(&result);
3445
3446 assert!(!sql.contains("GROUP BY 1"));
3447 assert!(!sql.contains("GROUP BY 2"));
3448 assert!(sql.contains("GROUP BY"));
3449 assert!(sql.contains("t.a"));
3450 assert!(sql.contains("t.b"));
3451 }
3452
3453 #[test]
3458 fn test_expand_using_simple() {
3459 let expr = parse("SELECT x.b FROM x JOIN y USING (b)");
3461
3462 let mut schema = MappingSchema::new();
3463 schema
3464 .add_table(
3465 "x",
3466 &[
3467 ("a".to_string(), DataType::BigInt { length: None }),
3468 ("b".to_string(), DataType::BigInt { length: None }),
3469 ],
3470 None,
3471 )
3472 .expect("schema setup");
3473 schema
3474 .add_table(
3475 "y",
3476 &[
3477 ("b".to_string(), DataType::BigInt { length: None }),
3478 ("c".to_string(), DataType::BigInt { length: None }),
3479 ],
3480 None,
3481 )
3482 .expect("schema setup");
3483
3484 let result =
3485 qualify_columns(expr, &schema, &QualifyColumnsOptions::new()).expect("qualify");
3486 let sql = gen(&result);
3487
3488 assert!(
3490 !sql.contains("USING"),
3491 "USING should be replaced with ON: {sql}"
3492 );
3493 assert!(
3494 sql.contains("ON x.b = y.b"),
3495 "ON condition should be x.b = y.b: {sql}"
3496 );
3497 assert!(sql.contains("SELECT x.b"), "SELECT should keep x.b: {sql}");
3499 }
3500
3501 #[test]
3502 fn test_expand_using_unqualified_coalesce() {
3503 let expr = parse("SELECT b FROM x JOIN y USING(b)");
3505
3506 let mut schema = MappingSchema::new();
3507 schema
3508 .add_table(
3509 "x",
3510 &[
3511 ("a".to_string(), DataType::BigInt { length: None }),
3512 ("b".to_string(), DataType::BigInt { length: None }),
3513 ],
3514 None,
3515 )
3516 .expect("schema setup");
3517 schema
3518 .add_table(
3519 "y",
3520 &[
3521 ("b".to_string(), DataType::BigInt { length: None }),
3522 ("c".to_string(), DataType::BigInt { length: None }),
3523 ],
3524 None,
3525 )
3526 .expect("schema setup");
3527
3528 let result =
3529 qualify_columns(expr, &schema, &QualifyColumnsOptions::new()).expect("qualify");
3530 let sql = gen(&result);
3531
3532 assert!(
3533 sql.contains("COALESCE(x.b, y.b)"),
3534 "Unqualified USING column should become COALESCE: {sql}"
3535 );
3536 assert!(
3537 sql.contains("AS b"),
3538 "COALESCE should be aliased as 'b': {sql}"
3539 );
3540 assert!(
3541 sql.contains("ON x.b = y.b"),
3542 "ON condition should be generated: {sql}"
3543 );
3544 }
3545
3546 #[test]
3547 fn test_expand_using_with_where() {
3548 let expr = parse("SELECT b FROM x JOIN y USING(b) WHERE b = 1");
3550
3551 let mut schema = MappingSchema::new();
3552 schema
3553 .add_table(
3554 "x",
3555 &[("b".to_string(), DataType::BigInt { length: None })],
3556 None,
3557 )
3558 .expect("schema setup");
3559 schema
3560 .add_table(
3561 "y",
3562 &[("b".to_string(), DataType::BigInt { length: None })],
3563 None,
3564 )
3565 .expect("schema setup");
3566
3567 let result =
3568 qualify_columns(expr, &schema, &QualifyColumnsOptions::new()).expect("qualify");
3569 let sql = gen(&result);
3570
3571 assert!(
3572 sql.contains("WHERE COALESCE(x.b, y.b)"),
3573 "WHERE should use COALESCE for USING column: {sql}"
3574 );
3575 }
3576
3577 #[test]
3578 fn test_expand_using_multi_join() {
3579 let expr = parse("SELECT b FROM x JOIN y USING(b) JOIN z USING(b)");
3581
3582 let mut schema = MappingSchema::new();
3583 for table in &["x", "y", "z"] {
3584 schema
3585 .add_table(
3586 table,
3587 &[("b".to_string(), DataType::BigInt { length: None })],
3588 None,
3589 )
3590 .expect("schema setup");
3591 }
3592
3593 let result =
3594 qualify_columns(expr, &schema, &QualifyColumnsOptions::new()).expect("qualify");
3595 let sql = gen(&result);
3596
3597 assert!(
3599 sql.contains("COALESCE(x.b, y.b, z.b)"),
3600 "Should have 3-table COALESCE: {sql}"
3601 );
3602 assert!(
3604 sql.contains("ON x.b = y.b"),
3605 "First join ON condition: {sql}"
3606 );
3607 }
3608
3609 #[test]
3610 fn test_expand_using_multi_column() {
3611 let expr = parse("SELECT b, c FROM y JOIN z USING(b, c)");
3613
3614 let mut schema = MappingSchema::new();
3615 schema
3616 .add_table(
3617 "y",
3618 &[
3619 ("b".to_string(), DataType::BigInt { length: None }),
3620 ("c".to_string(), DataType::BigInt { length: None }),
3621 ],
3622 None,
3623 )
3624 .expect("schema setup");
3625 schema
3626 .add_table(
3627 "z",
3628 &[
3629 ("b".to_string(), DataType::BigInt { length: None }),
3630 ("c".to_string(), DataType::BigInt { length: None }),
3631 ],
3632 None,
3633 )
3634 .expect("schema setup");
3635
3636 let result =
3637 qualify_columns(expr, &schema, &QualifyColumnsOptions::new()).expect("qualify");
3638 let sql = gen(&result);
3639
3640 assert!(
3641 sql.contains("COALESCE(y.b, z.b)"),
3642 "column 'b' should get COALESCE: {sql}"
3643 );
3644 assert!(
3645 sql.contains("COALESCE(y.c, z.c)"),
3646 "column 'c' should get COALESCE: {sql}"
3647 );
3648 assert!(
3650 sql.contains("y.b = z.b") && sql.contains("y.c = z.c"),
3651 "ON should have both equality conditions: {sql}"
3652 );
3653 }
3654
3655 #[test]
3656 fn test_expand_using_star() {
3657 let expr = parse("SELECT * FROM x JOIN y USING(b)");
3659
3660 let mut schema = MappingSchema::new();
3661 schema
3662 .add_table(
3663 "x",
3664 &[
3665 ("a".to_string(), DataType::BigInt { length: None }),
3666 ("b".to_string(), DataType::BigInt { length: None }),
3667 ],
3668 None,
3669 )
3670 .expect("schema setup");
3671 schema
3672 .add_table(
3673 "y",
3674 &[
3675 ("b".to_string(), DataType::BigInt { length: None }),
3676 ("c".to_string(), DataType::BigInt { length: None }),
3677 ],
3678 None,
3679 )
3680 .expect("schema setup");
3681
3682 let result =
3683 qualify_columns(expr, &schema, &QualifyColumnsOptions::new()).expect("qualify");
3684 let sql = gen(&result);
3685
3686 assert!(
3688 sql.contains("COALESCE(x.b, y.b) AS b"),
3689 "USING column should be COALESCE in star expansion: {sql}"
3690 );
3691 assert!(sql.contains("x.a"), "non-USING column a from x: {sql}");
3693 assert!(sql.contains("y.c"), "non-USING column c from y: {sql}");
3694 let coalesce_count = sql.matches("COALESCE").count();
3696 assert_eq!(
3697 coalesce_count, 1,
3698 "b should appear only once as COALESCE: {sql}"
3699 );
3700 }
3701
3702 #[test]
3703 fn test_expand_using_table_star() {
3704 let expr = parse("SELECT x.* FROM x JOIN y USING(b)");
3706
3707 let mut schema = MappingSchema::new();
3708 schema
3709 .add_table(
3710 "x",
3711 &[
3712 ("a".to_string(), DataType::BigInt { length: None }),
3713 ("b".to_string(), DataType::BigInt { length: None }),
3714 ],
3715 None,
3716 )
3717 .expect("schema setup");
3718 schema
3719 .add_table(
3720 "y",
3721 &[
3722 ("b".to_string(), DataType::BigInt { length: None }),
3723 ("c".to_string(), DataType::BigInt { length: None }),
3724 ],
3725 None,
3726 )
3727 .expect("schema setup");
3728
3729 let result =
3730 qualify_columns(expr, &schema, &QualifyColumnsOptions::new()).expect("qualify");
3731 let sql = gen(&result);
3732
3733 assert!(
3735 sql.contains("COALESCE(x.b, y.b)"),
3736 "USING column from x.* should become COALESCE: {sql}"
3737 );
3738 assert!(sql.contains("x.a"), "non-USING column a: {sql}");
3739 }
3740
3741 #[test]
3742 fn test_expand_natural_join_with_derived_table() {
3743 let expr = parse(
3744 "SELECT shared_key AS output_key FROM source_table \
3745 NATURAL JOIN (SELECT shared_key FROM source_table) AS derived",
3746 );
3747
3748 let mut schema = MappingSchema::new();
3749 schema
3750 .add_table(
3751 "source_table",
3752 &[(
3753 "shared_key".to_string(),
3754 DataType::VarChar {
3755 length: None,
3756 parenthesized_length: false,
3757 },
3758 )],
3759 None,
3760 )
3761 .expect("schema setup");
3762
3763 let result = qualify_columns(
3764 expr,
3765 &schema,
3766 &QualifyColumnsOptions::new().with_dialect(DialectType::DuckDB),
3767 )
3768 .expect("qualify");
3769 let sql = gen(&result);
3770
3771 assert!(!sql.contains("NATURAL"), "NATURAL should expand: {sql}");
3772 assert!(
3773 sql.contains("ON source_table.shared_key = derived.shared_key"),
3774 "common column should become an equality condition: {sql}"
3775 );
3776 assert!(
3777 sql.contains("COALESCE(source_table.shared_key, derived.shared_key) AS output_key"),
3778 "merged projection should preserve both sources: {sql}"
3779 );
3780 }
3781
3782 #[test]
3783 fn test_expand_natural_join_star_and_chained_sources() {
3784 let expr = parse("SELECT * FROM x NATURAL JOIN y NATURAL JOIN z");
3785
3786 let mut schema = MappingSchema::new();
3787 schema
3788 .add_table(
3789 "x",
3790 &[
3791 ("a".to_string(), DataType::BigInt { length: None }),
3792 ("shared".to_string(), DataType::BigInt { length: None }),
3793 ],
3794 None,
3795 )
3796 .expect("schema setup");
3797 schema
3798 .add_table(
3799 "y",
3800 &[
3801 ("shared".to_string(), DataType::BigInt { length: None }),
3802 ("b".to_string(), DataType::BigInt { length: None }),
3803 ],
3804 None,
3805 )
3806 .expect("schema setup");
3807 schema
3808 .add_table(
3809 "z",
3810 &[
3811 ("shared".to_string(), DataType::BigInt { length: None }),
3812 ("c".to_string(), DataType::BigInt { length: None }),
3813 ],
3814 None,
3815 )
3816 .expect("schema setup");
3817
3818 let result =
3819 qualify_columns(expr, &schema, &QualifyColumnsOptions::new()).expect("qualify");
3820 let sql = gen(&result);
3821
3822 assert!(!sql.contains("NATURAL"), "both joins should expand: {sql}");
3823 assert!(sql.contains("ON x.shared = y.shared"), "first join: {sql}");
3824 assert!(sql.contains("ON x.shared = z.shared"), "second join: {sql}");
3825 assert_eq!(
3826 sql.matches("COALESCE(x.shared, y.shared, z.shared) AS shared")
3827 .count(),
3828 1,
3829 "merged star column should be emitted once: {sql}"
3830 );
3831 assert!(sql.contains("x.a"), "left-only column should remain: {sql}");
3832 assert!(
3833 sql.contains("y.b"),
3834 "middle-only column should remain: {sql}"
3835 );
3836 assert!(
3837 sql.contains("z.c"),
3838 "right-only column should remain: {sql}"
3839 );
3840 }
3841
3842 #[test]
3843 fn test_expand_natural_outer_join_kinds() {
3844 let mut schema = MappingSchema::new();
3845 for table in ["x", "y"] {
3846 schema
3847 .add_table(
3848 table,
3849 &[("shared".to_string(), DataType::BigInt { length: None })],
3850 None,
3851 )
3852 .expect("schema setup");
3853 }
3854
3855 for (input_kind, output_kind) in [
3856 ("NATURAL LEFT JOIN", "LEFT JOIN"),
3857 ("NATURAL RIGHT JOIN", "RIGHT JOIN"),
3858 ("NATURAL FULL JOIN", "FULL JOIN"),
3859 ] {
3860 let expr = parse(&format!("SELECT shared FROM x {input_kind} y"));
3861 let result =
3862 qualify_columns(expr, &schema, &QualifyColumnsOptions::new()).expect("qualify");
3863 let sql = gen(&result);
3864
3865 assert!(
3866 !sql.contains("NATURAL"),
3867 "{input_kind} should expand: {sql}"
3868 );
3869 assert!(sql.contains(output_kind), "join kind should remain: {sql}");
3870 assert!(
3871 sql.contains("ON x.shared = y.shared"),
3872 "join condition should be derived: {sql}"
3873 );
3874 }
3875 }
3876
3877 #[test]
3878 fn test_preserve_natural_join_without_known_common_columns() {
3879 let mut no_common_schema = MappingSchema::new();
3880 no_common_schema
3881 .add_table(
3882 "x",
3883 &[("a".to_string(), DataType::BigInt { length: None })],
3884 None,
3885 )
3886 .expect("schema setup");
3887 no_common_schema
3888 .add_table(
3889 "y",
3890 &[("b".to_string(), DataType::BigInt { length: None })],
3891 None,
3892 )
3893 .expect("schema setup");
3894
3895 let no_common = qualify_columns(
3896 parse("SELECT * FROM x NATURAL JOIN y"),
3897 &no_common_schema,
3898 &QualifyColumnsOptions::new(),
3899 )
3900 .expect("qualify");
3901 assert!(
3902 gen(&no_common).contains("NATURAL JOIN"),
3903 "join without common columns should remain NATURAL"
3904 );
3905
3906 let mut partial_schema = MappingSchema::new();
3907 partial_schema
3908 .add_table(
3909 "x",
3910 &[("a".to_string(), DataType::BigInt { length: None })],
3911 None,
3912 )
3913 .expect("schema setup");
3914 let unknown_right = qualify_columns(
3915 parse("SELECT * FROM x NATURAL JOIN unknown_table"),
3916 &partial_schema,
3917 &QualifyColumnsOptions::new().with_allow_partial(true),
3918 )
3919 .expect("partial qualification");
3920 assert!(
3921 gen(&unknown_right).contains("NATURAL JOIN"),
3922 "join with an unknown schema should remain NATURAL"
3923 );
3924
3925 let unknown_left = qualify_columns(
3926 parse("SELECT * FROM unknown_table NATURAL JOIN x"),
3927 &partial_schema,
3928 &QualifyColumnsOptions::new().with_allow_partial(true),
3929 )
3930 .expect("partial qualification");
3931 assert!(
3932 gen(&unknown_left).contains("NATURAL JOIN"),
3933 "join with an unknown left schema should remain NATURAL"
3934 );
3935
3936 let partially_known_chain = qualify_columns(
3937 parse("SELECT * FROM x NATURAL JOIN unknown_table NATURAL JOIN x AS x2"),
3938 &partial_schema,
3939 &QualifyColumnsOptions::new().with_allow_partial(true),
3940 )
3941 .expect("partial qualification");
3942 assert_eq!(
3943 gen(&partially_known_chain).matches("NATURAL JOIN").count(),
3944 2,
3945 "an unknown earlier source should prevent later inferred join keys"
3946 );
3947 }
3948
3949 #[test]
3950 fn test_qualify_columns_qualified_table_name() {
3951 let expr = parse("SELECT a FROM raw.t1");
3952
3953 let mut schema = MappingSchema::new();
3954 schema
3955 .add_table(
3956 "raw.t1",
3957 &[("a".to_string(), DataType::BigInt { length: None })],
3958 None,
3959 )
3960 .expect("schema setup");
3961
3962 let result =
3963 qualify_columns(expr, &schema, &QualifyColumnsOptions::new()).expect("qualify");
3964 let sql = gen(&result);
3965
3966 assert!(
3967 sql.contains("t1.a"),
3968 "column should be qualified with table name: {sql}"
3969 );
3970
3971 let expr = parse("SELECT MAX(a) FROM raw.t1");
3973 let result =
3974 qualify_columns(expr, &schema, &QualifyColumnsOptions::new()).expect("qualify");
3975 let sql = gen(&result);
3976 assert!(
3977 sql.contains("t1.a"),
3978 "column in function should be qualified with table name: {sql}"
3979 );
3980
3981 let expr = parse("SELECT ABS(a) FROM raw.t1");
3983 let result =
3984 qualify_columns(expr, &schema, &QualifyColumnsOptions::new()).expect("qualify");
3985 let sql = gen(&result);
3986 assert!(
3987 sql.contains("t1.a"),
3988 "column in function should be qualified with table name: {sql}"
3989 );
3990 }
3991
3992 #[test]
3993 fn test_qualify_columns_count_star() {
3994 let expr = parse("SELECT COUNT(*) FROM t1");
3996
3997 let mut schema = MappingSchema::new();
3998 schema
3999 .add_table(
4000 "t1",
4001 &[("id".to_string(), DataType::BigInt { length: None })],
4002 None,
4003 )
4004 .expect("schema setup");
4005
4006 let result =
4007 qualify_columns(expr, &schema, &QualifyColumnsOptions::new()).expect("qualify");
4008 let sql = gen(&result);
4009
4010 assert!(
4011 sql.contains("COUNT(*)"),
4012 "COUNT(*) should be preserved: {sql}"
4013 );
4014 }
4015
4016 #[test]
4017 fn test_qualify_columns_correlated_scalar_subquery() {
4018 let expr =
4019 parse("SELECT id, (SELECT AVG(val) FROM t2 WHERE t2.id = t1.id) AS avg_val FROM t1");
4020
4021 let mut schema = MappingSchema::new();
4022 schema
4023 .add_table(
4024 "t1",
4025 &[("id".to_string(), DataType::BigInt { length: None })],
4026 None,
4027 )
4028 .expect("schema setup");
4029 schema
4030 .add_table(
4031 "t2",
4032 &[
4033 ("id".to_string(), DataType::BigInt { length: None }),
4034 ("val".to_string(), DataType::BigInt { length: None }),
4035 ],
4036 None,
4037 )
4038 .expect("schema setup");
4039
4040 let result =
4041 qualify_columns(expr, &schema, &QualifyColumnsOptions::new()).expect("qualify");
4042 let sql = gen(&result);
4043
4044 assert!(
4045 sql.contains("t1.id"),
4046 "outer column should be qualified: {sql}"
4047 );
4048 assert!(
4049 sql.contains("t2.id"),
4050 "inner column should be qualified: {sql}"
4051 );
4052 }
4053
4054 #[test]
4055 fn test_qualify_columns_correlated_scalar_subquery_unqualified() {
4056 let expr =
4057 parse("SELECT t1_id, (SELECT AVG(val) FROM t2 WHERE t2_id = t1_id) AS avg_val FROM t1");
4058
4059 let mut schema = MappingSchema::new();
4060 schema
4061 .add_table(
4062 "t1",
4063 &[("t1_id".to_string(), DataType::BigInt { length: None })],
4064 None,
4065 )
4066 .expect("schema setup");
4067 schema
4068 .add_table(
4069 "t2",
4070 &[
4071 ("t2_id".to_string(), DataType::BigInt { length: None }),
4072 ("val".to_string(), DataType::BigInt { length: None }),
4073 ],
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("t1.t1_id"),
4084 "outer column should be qualified: {sql}"
4085 );
4086 assert!(
4087 sql.contains("t2.t2_id"),
4088 "inner column should be qualified: {sql}"
4089 );
4090 assert!(
4092 sql.contains("= t1.t1_id"),
4093 "correlated column should be qualified: {sql}"
4094 );
4095 }
4096
4097 #[test]
4098 fn test_qualify_columns_correlated_exists_subquery() {
4099 let expr = parse(
4100 "SELECT o_orderpriority FROM orders \
4101 WHERE EXISTS (SELECT * FROM lineitem WHERE l_orderkey = o_orderkey)",
4102 );
4103
4104 let mut schema = MappingSchema::new();
4105 schema
4106 .add_table(
4107 "orders",
4108 &[
4109 ("o_orderpriority".to_string(), DataType::Text),
4110 ("o_orderkey".to_string(), DataType::BigInt { length: None }),
4111 ],
4112 None,
4113 )
4114 .expect("schema setup");
4115 schema
4116 .add_table(
4117 "lineitem",
4118 &[("l_orderkey".to_string(), DataType::BigInt { length: None })],
4119 None,
4120 )
4121 .expect("schema setup");
4122
4123 let result =
4124 qualify_columns(expr, &schema, &QualifyColumnsOptions::new()).expect("qualify");
4125 let sql = gen(&result);
4126
4127 assert!(
4128 sql.contains("orders.o_orderpriority"),
4129 "outer column should be qualified: {sql}"
4130 );
4131 assert!(
4132 sql.contains("lineitem.l_orderkey"),
4133 "inner column should be qualified: {sql}"
4134 );
4135 assert!(
4136 sql.contains("orders.o_orderkey"),
4137 "correlated outer column should be qualified: {sql}"
4138 );
4139 }
4140
4141 #[test]
4142 fn test_qualify_columns_rejects_unknown_table() {
4143 let expr = parse("SELECT id FROM t1 WHERE nonexistent.col = 1");
4144
4145 let mut schema = MappingSchema::new();
4146 schema
4147 .add_table(
4148 "t1",
4149 &[("id".to_string(), DataType::BigInt { length: None })],
4150 None,
4151 )
4152 .expect("schema setup");
4153
4154 let result = qualify_columns(expr, &schema, &QualifyColumnsOptions::new());
4155 assert!(
4156 result.is_err(),
4157 "should reject reference to table not in scope or schema"
4158 );
4159 }
4160
4161 #[test]
4166 fn test_needs_quoting_reserved_word() {
4167 let reserved = get_reserved_words(None);
4168 assert!(needs_quoting("select", &reserved));
4169 assert!(needs_quoting("SELECT", &reserved));
4170 assert!(needs_quoting("from", &reserved));
4171 assert!(needs_quoting("WHERE", &reserved));
4172 assert!(needs_quoting("join", &reserved));
4173 assert!(needs_quoting("table", &reserved));
4174 }
4175
4176 #[test]
4177 fn test_needs_quoting_normal_identifiers() {
4178 let reserved = get_reserved_words(None);
4179 assert!(!needs_quoting("foo", &reserved));
4180 assert!(!needs_quoting("my_column", &reserved));
4181 assert!(!needs_quoting("col1", &reserved));
4182 assert!(!needs_quoting("A", &reserved));
4183 assert!(!needs_quoting("_hidden", &reserved));
4184 }
4185
4186 #[test]
4187 fn test_needs_quoting_special_characters() {
4188 let reserved = get_reserved_words(None);
4189 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)); }
4195
4196 #[test]
4197 fn test_needs_quoting_starts_with_digit() {
4198 let reserved = get_reserved_words(None);
4199 assert!(needs_quoting("1col", &reserved));
4200 assert!(needs_quoting("123", &reserved));
4201 assert!(needs_quoting("0_start", &reserved));
4202 }
4203
4204 #[test]
4205 fn test_needs_quoting_empty() {
4206 let reserved = get_reserved_words(None);
4207 assert!(!needs_quoting("", &reserved));
4208 }
4209
4210 #[test]
4211 fn test_maybe_quote_sets_quoted_flag() {
4212 let reserved = get_reserved_words(None);
4213 let mut id = Identifier::new("select");
4214 assert!(!id.quoted);
4215 maybe_quote(&mut id, &reserved);
4216 assert!(id.quoted);
4217 }
4218
4219 #[test]
4220 fn test_maybe_quote_skips_already_quoted() {
4221 let reserved = get_reserved_words(None);
4222 let mut id = Identifier::quoted("myname");
4223 assert!(id.quoted);
4224 maybe_quote(&mut id, &reserved);
4225 assert!(id.quoted); assert_eq!(id.name, "myname"); }
4228
4229 #[test]
4230 fn test_maybe_quote_skips_star() {
4231 let reserved = get_reserved_words(None);
4232 let mut id = Identifier::new("*");
4233 maybe_quote(&mut id, &reserved);
4234 assert!(!id.quoted); }
4236
4237 #[test]
4238 fn test_maybe_quote_skips_normal() {
4239 let reserved = get_reserved_words(None);
4240 let mut id = Identifier::new("normal_col");
4241 maybe_quote(&mut id, &reserved);
4242 assert!(!id.quoted);
4243 }
4244
4245 #[test]
4246 fn test_quote_identifiers_column_with_reserved_name() {
4247 let expr = Expression::boxed_column(Column {
4249 name: Identifier::new("select"),
4250 table: None,
4251 join_mark: false,
4252 trailing_comments: vec![],
4253 span: None,
4254 inferred_type: None,
4255 });
4256 let result = quote_identifiers(expr, None);
4257 if let Expression::Column(col) = &result {
4258 assert!(col.name.quoted, "Column named 'select' should be quoted");
4259 } else {
4260 panic!("Expected Column expression");
4261 }
4262 }
4263
4264 #[test]
4265 fn test_quote_identifiers_column_with_special_chars() {
4266 let expr = Expression::boxed_column(Column {
4267 name: Identifier::new("my column"),
4268 table: None,
4269 join_mark: false,
4270 trailing_comments: vec![],
4271 span: None,
4272 inferred_type: None,
4273 });
4274 let result = quote_identifiers(expr, None);
4275 if let Expression::Column(col) = &result {
4276 assert!(col.name.quoted, "Column with space should be quoted");
4277 } else {
4278 panic!("Expected Column expression");
4279 }
4280 }
4281
4282 #[test]
4283 fn test_quote_identifiers_preserves_normal_column() {
4284 let expr = Expression::boxed_column(Column {
4285 name: Identifier::new("normal_col"),
4286 table: Some(Identifier::new("my_table")),
4287 join_mark: false,
4288 trailing_comments: vec![],
4289 span: None,
4290 inferred_type: None,
4291 });
4292 let result = quote_identifiers(expr, None);
4293 if let Expression::Column(col) = &result {
4294 assert!(!col.name.quoted, "Normal column should not be quoted");
4295 assert!(
4296 !col.table.as_ref().unwrap().quoted,
4297 "Normal table should not be quoted"
4298 );
4299 } else {
4300 panic!("Expected Column expression");
4301 }
4302 }
4303
4304 #[test]
4305 fn test_quote_identifiers_table_ref_reserved() {
4306 let expr = Expression::Table(Box::new(TableRef::new("select")));
4307 let result = quote_identifiers(expr, None);
4308 if let Expression::Table(tr) = &result {
4309 assert!(tr.name.quoted, "Table named 'select' should be quoted");
4310 } else {
4311 panic!("Expected Table expression");
4312 }
4313 }
4314
4315 #[test]
4316 fn test_quote_identifiers_table_ref_schema_and_alias() {
4317 let mut tr = TableRef::new("my_table");
4318 tr.schema = Some(Identifier::new("from"));
4319 tr.alias = Some(Identifier::new("t"));
4320 let expr = Expression::Table(Box::new(tr));
4321 let result = quote_identifiers(expr, None);
4322 if let Expression::Table(tr) = &result {
4323 assert!(!tr.name.quoted, "Normal table name should not be quoted");
4324 assert!(
4325 tr.schema.as_ref().unwrap().quoted,
4326 "Schema named 'from' should be quoted"
4327 );
4328 assert!(
4329 !tr.alias.as_ref().unwrap().quoted,
4330 "Normal alias should not be quoted"
4331 );
4332 } else {
4333 panic!("Expected Table expression");
4334 }
4335 }
4336
4337 #[test]
4338 fn test_quote_identifiers_identifier_node() {
4339 let expr = Expression::Identifier(Identifier::new("order"));
4340 let result = quote_identifiers(expr, None);
4341 if let Expression::Identifier(id) = &result {
4342 assert!(id.quoted, "Identifier named 'order' should be quoted");
4343 } else {
4344 panic!("Expected Identifier expression");
4345 }
4346 }
4347
4348 #[test]
4349 fn test_quote_identifiers_alias() {
4350 let inner = Expression::boxed_column(Column {
4351 name: Identifier::new("val"),
4352 table: None,
4353 join_mark: false,
4354 trailing_comments: vec![],
4355 span: None,
4356 inferred_type: None,
4357 });
4358 let expr = Expression::Alias(Box::new(Alias {
4359 this: inner,
4360 alias: Identifier::new("select"),
4361 column_aliases: vec![Identifier::new("from")],
4362 alias_explicit_as: false,
4363 alias_keyword: None,
4364 pre_alias_comments: vec![],
4365 trailing_comments: vec![],
4366 inferred_type: None,
4367 }));
4368 let result = quote_identifiers(expr, None);
4369 if let Expression::Alias(alias) = &result {
4370 assert!(alias.alias.quoted, "Alias named 'select' should be quoted");
4371 assert!(
4372 alias.column_aliases[0].quoted,
4373 "Column alias named 'from' should be quoted"
4374 );
4375 if let Expression::Column(col) = &alias.this {
4377 assert!(!col.name.quoted);
4378 }
4379 } else {
4380 panic!("Expected Alias expression");
4381 }
4382 }
4383
4384 #[test]
4385 fn test_quote_identifiers_select_recursive() {
4386 let expr = parse("SELECT a, b FROM t WHERE c = 1");
4388 let result = quote_identifiers(expr, None);
4389 let sql = gen(&result);
4391 assert!(sql.contains("a"));
4393 assert!(sql.contains("b"));
4394 assert!(sql.contains("t"));
4395 }
4396
4397 #[test]
4398 fn test_quote_identifiers_digit_start() {
4399 let expr = Expression::boxed_column(Column {
4400 name: Identifier::new("1col"),
4401 table: None,
4402 join_mark: false,
4403 trailing_comments: vec![],
4404 span: None,
4405 inferred_type: None,
4406 });
4407 let result = quote_identifiers(expr, None);
4408 if let Expression::Column(col) = &result {
4409 assert!(
4410 col.name.quoted,
4411 "Column starting with digit should be quoted"
4412 );
4413 } else {
4414 panic!("Expected Column expression");
4415 }
4416 }
4417
4418 #[test]
4419 fn test_quote_identifiers_with_mysql_dialect() {
4420 let reserved = get_reserved_words(Some(DialectType::MySQL));
4421 assert!(needs_quoting("KILL", &reserved));
4423 assert!(needs_quoting("FORCE", &reserved));
4425 }
4426
4427 #[test]
4428 fn test_quote_identifiers_with_postgresql_dialect() {
4429 let reserved = get_reserved_words(Some(DialectType::PostgreSQL));
4430 assert!(needs_quoting("ILIKE", &reserved));
4432 assert!(needs_quoting("VERBOSE", &reserved));
4434 }
4435
4436 #[test]
4437 fn test_quote_identifiers_with_bigquery_dialect() {
4438 let reserved = get_reserved_words(Some(DialectType::BigQuery));
4439 assert!(needs_quoting("STRUCT", &reserved));
4441 assert!(needs_quoting("PROTO", &reserved));
4443 }
4444
4445 #[test]
4446 fn test_quote_identifiers_case_insensitive_reserved() {
4447 let reserved = get_reserved_words(None);
4448 assert!(needs_quoting("Select", &reserved));
4449 assert!(needs_quoting("sElEcT", &reserved));
4450 assert!(needs_quoting("FROM", &reserved));
4451 assert!(needs_quoting("from", &reserved));
4452 }
4453
4454 #[test]
4455 fn test_quote_identifiers_join_using() {
4456 let mut join = crate::expressions::Join {
4458 this: Expression::Table(Box::new(TableRef::new("other"))),
4459 on: None,
4460 using: vec![Identifier::new("key"), Identifier::new("value")],
4461 kind: crate::expressions::JoinKind::Inner,
4462 use_inner_keyword: false,
4463 use_outer_keyword: false,
4464 deferred_condition: false,
4465 join_hint: None,
4466 match_condition: None,
4467 pivots: vec![],
4468 comments: vec![],
4469 nesting_group: 0,
4470 directed: false,
4471 };
4472 let reserved = get_reserved_words(None);
4473 quote_join(&mut join, &reserved);
4474 assert!(
4476 join.using[0].quoted,
4477 "USING identifier 'key' should be quoted"
4478 );
4479 assert!(
4480 !join.using[1].quoted,
4481 "USING identifier 'value' should not be quoted"
4482 );
4483 }
4484
4485 #[test]
4486 fn test_quote_identifiers_cte() {
4487 let mut cte = crate::expressions::Cte {
4489 alias: Identifier::new("select"),
4490 this: Expression::boxed_column(Column {
4491 name: Identifier::new("x"),
4492 table: None,
4493 join_mark: false,
4494 trailing_comments: vec![],
4495 span: None,
4496 inferred_type: None,
4497 }),
4498 columns: vec![Identifier::new("from"), Identifier::new("normal")],
4499 materialized: None,
4500 key_expressions: vec![],
4501 alias_first: false,
4502 comments: Vec::new(),
4503 };
4504 let reserved = get_reserved_words(None);
4505 maybe_quote(&mut cte.alias, &reserved);
4506 for c in &mut cte.columns {
4507 maybe_quote(c, &reserved);
4508 }
4509 assert!(cte.alias.quoted, "CTE alias 'select' should be quoted");
4510 assert!(cte.columns[0].quoted, "CTE column 'from' should be quoted");
4511 assert!(
4512 !cte.columns[1].quoted,
4513 "CTE column 'normal' should not be quoted"
4514 );
4515 }
4516
4517 #[test]
4518 fn test_quote_identifiers_binary_ops_recurse() {
4519 let expr = Expression::Add(Box::new(crate::expressions::BinaryOp::new(
4522 Expression::boxed_column(Column {
4523 name: Identifier::new("select"),
4524 table: None,
4525 join_mark: false,
4526 trailing_comments: vec![],
4527 span: None,
4528 inferred_type: None,
4529 }),
4530 Expression::boxed_column(Column {
4531 name: Identifier::new("normal"),
4532 table: None,
4533 join_mark: false,
4534 trailing_comments: vec![],
4535 span: None,
4536 inferred_type: None,
4537 }),
4538 )));
4539 let result = quote_identifiers(expr, None);
4540 if let Expression::Add(bin) = &result {
4541 if let Expression::Column(left) = &bin.left {
4542 assert!(
4543 left.name.quoted,
4544 "'select' column should be quoted in binary op"
4545 );
4546 }
4547 if let Expression::Column(right) = &bin.right {
4548 assert!(!right.name.quoted, "'normal' column should not be quoted");
4549 }
4550 } else {
4551 panic!("Expected Add expression");
4552 }
4553 }
4554
4555 #[test]
4556 fn test_quote_identifiers_already_quoted_preserved() {
4557 let expr = Expression::boxed_column(Column {
4559 name: Identifier::quoted("normal_name"),
4560 table: None,
4561 join_mark: false,
4562 trailing_comments: vec![],
4563 span: None,
4564 inferred_type: None,
4565 });
4566 let result = quote_identifiers(expr, None);
4567 if let Expression::Column(col) = &result {
4568 assert!(
4569 col.name.quoted,
4570 "Already-quoted identifier should remain quoted"
4571 );
4572 } else {
4573 panic!("Expected Column expression");
4574 }
4575 }
4576
4577 #[test]
4578 fn test_quote_identifiers_full_parsed_query() {
4579 let mut select = crate::expressions::Select::new();
4582 select.expressions.push(Expression::boxed_column(Column {
4583 name: Identifier::new("order"),
4584 table: Some(Identifier::new("t")),
4585 join_mark: false,
4586 trailing_comments: vec![],
4587 span: None,
4588 inferred_type: None,
4589 }));
4590 select.from = Some(crate::expressions::From {
4591 expressions: vec![Expression::Table(Box::new(TableRef::new("t")))],
4592 });
4593 let expr = Expression::Select(Box::new(select));
4594
4595 let result = quote_identifiers(expr, None);
4596 if let Expression::Select(sel) = &result {
4597 if let Expression::Column(col) = &sel.expressions[0] {
4598 assert!(col.name.quoted, "Column named 'order' should be quoted");
4599 assert!(
4600 !col.table.as_ref().unwrap().quoted,
4601 "Table 't' should not be quoted"
4602 );
4603 } else {
4604 panic!("Expected Column in SELECT list");
4605 }
4606 } else {
4607 panic!("Expected Select expression");
4608 }
4609 }
4610
4611 #[test]
4612 fn test_get_reserved_words_all_dialects() {
4613 let dialects = [
4615 None,
4616 Some(DialectType::Generic),
4617 Some(DialectType::MySQL),
4618 Some(DialectType::PostgreSQL),
4619 Some(DialectType::BigQuery),
4620 Some(DialectType::Snowflake),
4621 Some(DialectType::TSQL),
4622 Some(DialectType::ClickHouse),
4623 Some(DialectType::DuckDB),
4624 Some(DialectType::Hive),
4625 Some(DialectType::Spark),
4626 Some(DialectType::Trino),
4627 Some(DialectType::Oracle),
4628 Some(DialectType::Redshift),
4629 ];
4630 for dialect in &dialects {
4631 let words = get_reserved_words(*dialect);
4632 assert!(
4634 words.contains("SELECT"),
4635 "All dialects should have SELECT as reserved"
4636 );
4637 assert!(
4638 words.contains("FROM"),
4639 "All dialects should have FROM as reserved"
4640 );
4641 }
4642 }
4643}