1use crate::dialects::DialectType;
13use crate::expressions::{Expression, Identifier, TableRef};
14#[cfg(feature = "generate")]
15use crate::generator::Generator;
16use crate::schema::{normalize_name, Schema};
17use crate::scope::{Scope, SourceInfo};
18use crate::traversal::ExpressionWalk;
19use std::collections::{HashMap, HashSet};
20use thiserror::Error;
21
22#[derive(Debug, Error, Clone)]
24pub enum ResolverError {
25 #[error("Unknown table: {0}")]
26 UnknownTable(String),
27
28 #[error("Ambiguous column: {column} appears in multiple sources: {sources}")]
29 AmbiguousColumn { column: String, sources: String },
30
31 #[error("Column not found: {0}")]
32 ColumnNotFound(String),
33
34 #[error("Unknown set operation: {0}")]
35 UnknownSetOperation(String),
36
37 #[error("Invalid set operation: {0}")]
38 InvalidSetOperation(String),
39}
40
41pub type ResolverResult<T> = Result<T, ResolverError>;
43
44pub struct Resolver<'a> {
49 pub scope: &'a Scope,
51 schema: &'a dyn Schema,
53 pub dialect: Option<DialectType>,
55 infer_schema: bool,
57 source_columns_cache: HashMap<String, Vec<String>>,
59 unambiguous_columns_cache: Option<HashMap<String, String>>,
61 all_columns_cache: Option<HashSet<String>>,
63}
64
65impl<'a> Resolver<'a> {
66 pub fn new(scope: &'a Scope, schema: &'a dyn Schema, infer_schema: bool) -> Self {
68 Self {
69 scope,
70 schema,
71 dialect: schema.dialect(),
72 infer_schema,
73 source_columns_cache: HashMap::new(),
74 unambiguous_columns_cache: None,
75 all_columns_cache: None,
76 }
77 }
78
79 pub fn get_table(&mut self, column_name: &str) -> Option<String> {
83 let table_name = self.get_table_name_from_sources(column_name, None);
85
86 if table_name.is_some() {
88 return table_name;
89 }
90
91 if self.infer_schema {
94 let sources_without_schema: Vec<_> = self
95 .get_all_source_columns()
96 .iter()
97 .filter(|(_, columns)| columns.is_empty() || columns.contains(&"*".to_string()))
98 .map(|(name, _)| name.clone())
99 .collect();
100
101 if sources_without_schema.len() == 1 {
102 return Some(sources_without_schema[0].clone());
103 }
104 }
105
106 None
107 }
108
109 pub fn get_table_identifier(&mut self, column_name: &str) -> Option<Identifier> {
111 self.get_table(column_name).map(Identifier::new)
112 }
113
114 pub fn table_exists_in_schema(&self, table_name: &str) -> bool {
117 self.schema.column_names(table_name).is_ok()
118 }
119
120 pub fn find_column_in_outer_schema_tables(&self, column_name: &str) -> Option<String> {
125 let tables = self.schema.find_tables_for_column(column_name);
126 let outer_tables: Vec<String> = tables
128 .into_iter()
129 .filter(|t| !self.scope.sources.contains_key(t))
130 .collect();
131 if outer_tables.len() == 1 {
133 Some(outer_tables.into_iter().next().unwrap())
134 } else {
135 None
136 }
137 }
138
139 pub fn all_columns(&mut self) -> &HashSet<String> {
141 if self.all_columns_cache.is_none() {
142 let mut all = HashSet::new();
143 for columns in self.get_all_source_columns().values() {
144 all.extend(columns.iter().cloned());
145 }
146 self.all_columns_cache = Some(all);
147 }
148 self.all_columns_cache
149 .as_ref()
150 .expect("cache populated above")
151 }
152
153 pub fn get_source_columns(&mut self, source_name: &str) -> ResolverResult<Vec<String>> {
157 if let Some(columns) = self.source_columns_cache.get(source_name) {
159 return Ok(columns.clone());
160 }
161
162 let source_info = self
164 .scope
165 .sources
166 .get(source_name)
167 .ok_or_else(|| ResolverError::UnknownTable(source_name.to_string()))?;
168
169 let columns = self.extract_columns_from_source(source_info)?;
170
171 self.source_columns_cache
173 .insert(source_name.to_string(), columns.clone());
174
175 Ok(columns)
176 }
177
178 fn extract_columns_from_source(&self, source_info: &SourceInfo) -> ResolverResult<Vec<String>> {
180 self.get_source_columns_for_expression(&source_info.expression)
181 }
182
183 fn get_source_columns_for_expression(
184 &self,
185 expression: &Expression,
186 ) -> ResolverResult<Vec<String>> {
187 let columns = match expression {
188 Expression::Table(table) => {
189 let table_name = qualified_table_name(table);
193 match self.schema.column_names(&table_name) {
194 Ok(cols) => cols,
195 Err(_) => Vec::new(), }
197 }
198 Expression::Subquery(subquery) => {
199 self.get_named_selects(&subquery.this)
201 }
202 Expression::Select(select) => {
203 self.get_select_column_names(select)
205 }
206 Expression::Union(union) => {
207 self.get_source_columns_from_set_op(&Expression::Union(union.clone()))?
209 }
210 Expression::Intersect(intersect) => {
211 self.get_source_columns_from_set_op(&Expression::Intersect(intersect.clone()))?
212 }
213 Expression::Except(except) => {
214 self.get_source_columns_from_set_op(&Expression::Except(except.clone()))?
215 }
216 Expression::Cte(cte) => {
217 if !cte.columns.is_empty() {
218 cte.columns.iter().map(|c| c.name.clone()).collect()
219 } else {
220 self.get_named_selects(&cte.this)
221 }
222 }
223 Expression::Pivot(pivot) => self.get_pivot_output_columns(pivot),
224 Expression::Unpivot(unpivot) => self.get_unpivot_output_columns(unpivot),
225 Expression::Alias(alias) if matches!(&alias.this, Expression::Unnest(_)) => {
226 alias_output_columns(alias)
227 }
228 Expression::Alias(alias) => {
229 let columns = self.get_source_columns_for_expression(&alias.this)?;
230 apply_alias_columns(columns, &alias.column_aliases)
231 }
232 Expression::Unnest(unnest) => unnest_output_columns(unnest),
233 Expression::Lateral(lateral) => lateral_output_columns(lateral),
234 Expression::LateralView(lateral_view) => lateral_view_output_columns(lateral_view),
235 Expression::Paren(paren) => self.get_source_columns_for_expression(&paren.this)?,
236 _ => Vec::new(),
237 };
238
239 Ok(columns)
240 }
241
242 fn get_named_selects(&self, expr: &Expression) -> Vec<String> {
244 if let Ok(identifiers) = crate::set_operation::query_output_identifiers(expr, self.dialect)
245 {
246 return identifiers
247 .into_iter()
248 .map(|identifier| identifier.name)
249 .collect();
250 }
251
252 match expr {
253 Expression::Select(select) => self.get_select_column_names(select),
254 Expression::Union(union) => {
255 self.get_named_selects(&union.left)
257 }
258 Expression::Intersect(intersect) => self.get_named_selects(&intersect.left),
259 Expression::Except(except) => self.get_named_selects(&except.left),
260 Expression::Subquery(subquery) => self.get_named_selects(&subquery.this),
261 Expression::Alias(alias) => {
262 let columns = self.get_named_selects(&alias.this);
263 apply_alias_columns(columns, &alias.column_aliases)
264 }
265 Expression::Paren(paren) => self.get_named_selects(&paren.this),
266 _ => Vec::new(),
267 }
268 }
269
270 fn get_select_column_names(&self, select: &crate::expressions::Select) -> Vec<String> {
272 select
273 .expressions
274 .iter()
275 .filter_map(|expr| self.get_expression_alias(expr))
276 .collect()
277 }
278
279 fn get_expression_alias(&self, expr: &Expression) -> Option<String> {
281 match expr {
282 Expression::Alias(alias) => Some(alias.alias.name.clone()),
283 Expression::Column(col) => Some(col.name.name.clone()),
284 Expression::Star(_) => Some("*".to_string()),
285 Expression::Identifier(id) => Some(id.name.clone()),
286 _ => None,
287 }
288 }
289
290 fn get_pivot_output_columns(&self, pivot: &crate::expressions::Pivot) -> Vec<String> {
291 if pivot.unpivot {
292 return self.get_pivot_unpivot_output_columns(pivot);
293 }
294
295 let pre_columns = self.get_source_output_columns(&pivot.this);
296 if pre_columns.is_empty() || pre_columns.iter().any(|column| column == "*") {
297 return Vec::new();
298 }
299
300 let excluded = pivot_excluded_source_columns(pivot, self.dialect);
301 let generated = pivot_generated_output_columns(pivot, self.dialect);
302 if excluded.is_empty() || generated.is_empty() {
303 return Vec::new();
304 }
305
306 let mut columns: Vec<String> = pre_columns
307 .into_iter()
308 .filter(|column| !excluded.contains(&normalize_column_name(column, self.dialect)))
309 .collect();
310 columns.extend(generated);
311 apply_alias_columns(columns, &pivot.alias_columns)
312 }
313
314 fn get_pivot_unpivot_output_columns(&self, pivot: &crate::expressions::Pivot) -> Vec<String> {
315 let pre_columns = self.get_source_output_columns(&pivot.this);
316 if pre_columns.is_empty() || pre_columns.iter().any(|column| column == "*") {
317 return Vec::new();
318 }
319
320 let input_columns: HashSet<String> = pivot
321 .expressions
322 .iter()
323 .flat_map(expression_column_names)
324 .map(|column| normalize_column_name(&column, self.dialect))
325 .collect();
326 let mut columns: Vec<String> = pre_columns
327 .into_iter()
328 .filter(|column| !input_columns.contains(&normalize_column_name(column, self.dialect)))
329 .collect();
330
331 if let Some(Expression::UnpivotColumns(unpivot_columns)) = pivot.into.as_deref() {
332 if let Some(name) = expression_name(&unpivot_columns.this) {
333 columns.push(name);
334 }
335 for value_column in &unpivot_columns.expressions {
336 if let Some(name) = expression_name(value_column) {
337 columns.push(name);
338 }
339 }
340 }
341
342 apply_alias_columns(columns, &pivot.alias_columns)
343 }
344
345 fn get_unpivot_output_columns(&self, unpivot: &crate::expressions::Unpivot) -> Vec<String> {
346 let pre_columns = self.get_source_output_columns(&unpivot.this);
347 if pre_columns.is_empty() || pre_columns.iter().any(|column| column == "*") {
348 return Vec::new();
349 }
350
351 let input_columns: HashSet<String> = unpivot
352 .columns
353 .iter()
354 .flat_map(expression_column_names)
355 .map(|column| normalize_column_name(&column, self.dialect))
356 .collect();
357 let mut columns: Vec<String> = pre_columns
358 .into_iter()
359 .filter(|column| !input_columns.contains(&normalize_column_name(column, self.dialect)))
360 .collect();
361 columns.push(unpivot.name_column.name.clone());
362 columns.push(unpivot.value_column.name.clone());
363 columns.extend(
364 unpivot
365 .extra_value_columns
366 .iter()
367 .map(|column| column.name.clone()),
368 );
369 apply_alias_columns(columns, &unpivot.alias_columns)
370 }
371
372 fn get_source_output_columns(&self, source: &Expression) -> Vec<String> {
373 match source {
374 Expression::Table(table) => {
375 if table.schema.is_none() && table.catalog.is_none() {
376 if let Some(source) = self.scope.cte_sources.get(&table.name.name) {
377 return self.extract_columns_from_source(source).unwrap_or_default();
378 }
379 }
380
381 let table_name = qualified_table_name(table);
382 self.schema.column_names(&table_name).unwrap_or_default()
383 }
384 Expression::Subquery(subquery) => self.get_named_selects(&subquery.this),
385 Expression::Select(select) => self.get_select_column_names(select),
386 Expression::Union(_) | Expression::Intersect(_) | Expression::Except(_) => self
387 .get_source_columns_from_set_op(source)
388 .unwrap_or_default(),
389 Expression::Alias(alias) if matches!(&alias.this, Expression::Unnest(_)) => {
390 alias_output_columns(alias)
391 }
392 Expression::Alias(alias) => {
393 let columns = self.get_source_output_columns(&alias.this);
394 apply_alias_columns(columns, &alias.column_aliases)
395 }
396 Expression::Unnest(unnest) => unnest_output_columns(unnest),
397 Expression::Lateral(lateral) => lateral_output_columns(lateral),
398 Expression::LateralView(lateral_view) => lateral_view_output_columns(lateral_view),
399 Expression::Cte(cte) => {
400 if cte.columns.is_empty() {
401 self.get_named_selects(&cte.this)
402 } else {
403 cte.columns
404 .iter()
405 .map(|column| column.name.clone())
406 .collect()
407 }
408 }
409 Expression::Paren(paren) => self.get_source_output_columns(&paren.this),
410 _ => Vec::new(),
411 }
412 }
413
414 pub fn get_source_columns_from_set_op(
416 &self,
417 expression: &Expression,
418 ) -> ResolverResult<Vec<String>> {
419 if matches!(
420 expression,
421 Expression::Union(_) | Expression::Intersect(_) | Expression::Except(_)
422 ) {
423 match crate::set_operation::set_operation_layout(expression, self.dialect) {
424 Ok(Some(layout)) => {
425 return Ok(layout
426 .outputs
427 .into_iter()
428 .map(|output| output.identifier.name)
429 .collect())
430 }
431 Ok(None) => {}
432 Err(error) if error.is_indeterminate() => {}
433 Err(error) => return Err(ResolverError::InvalidSetOperation(error.to_string())),
434 }
435 }
436
437 match expression {
438 Expression::Select(select) => Ok(self.get_select_column_names(select)),
439 Expression::Subquery(subquery) => {
440 if matches!(
441 &subquery.this,
442 Expression::Union(_) | Expression::Intersect(_) | Expression::Except(_)
443 ) {
444 self.get_source_columns_from_set_op(&subquery.this)
445 } else {
446 Ok(self.get_named_selects(&subquery.this))
447 }
448 }
449 Expression::Alias(alias) => {
450 let columns = self.get_source_columns_from_set_op(&alias.this)?;
451 Ok(apply_alias_columns(columns, &alias.column_aliases))
452 }
453 Expression::Paren(paren) => self.get_source_columns_from_set_op(&paren.this),
454 Expression::Union(union) => {
455 self.get_source_columns_from_set_op(&union.left)
457 }
458 Expression::Intersect(intersect) => {
459 self.get_source_columns_from_set_op(&intersect.left)
460 }
461 Expression::Except(except) => self.get_source_columns_from_set_op(&except.left),
462 _ => Err(ResolverError::UnknownSetOperation(format!(
463 "{:?}",
464 expression
465 ))),
466 }
467 }
468
469 fn get_all_source_columns(&mut self) -> HashMap<String, Vec<String>> {
471 let source_names: Vec<_> = self.scope.sources.keys().cloned().collect();
472
473 let mut result = HashMap::new();
474 for source_name in source_names {
475 if let Ok(columns) = self.get_source_columns(&source_name) {
476 result.insert(source_name, columns);
477 }
478 }
479 result
480 }
481
482 fn get_table_name_from_sources(
484 &mut self,
485 column_name: &str,
486 source_columns: Option<&HashMap<String, Vec<String>>>,
487 ) -> Option<String> {
488 let normalized_column_name = normalize_column_name(column_name, self.dialect);
489 let unambiguous = match source_columns {
490 Some(cols) => self.compute_unambiguous_columns(cols),
491 None => {
492 if self.unambiguous_columns_cache.is_none() {
493 let all_source_columns = self.get_all_source_columns();
494 self.unambiguous_columns_cache =
495 Some(self.compute_unambiguous_columns(&all_source_columns));
496 }
497 self.unambiguous_columns_cache
498 .clone()
499 .expect("cache populated above")
500 }
501 };
502
503 unambiguous.get(&normalized_column_name).cloned()
504 }
505
506 fn compute_unambiguous_columns(
510 &self,
511 source_columns: &HashMap<String, Vec<String>>,
512 ) -> HashMap<String, String> {
513 if source_columns.is_empty() {
514 return HashMap::new();
515 }
516
517 let mut column_to_sources: HashMap<String, Vec<String>> = HashMap::new();
518
519 for (source_name, columns) in source_columns {
520 for column in columns {
521 column_to_sources
522 .entry(normalize_column_name(column, self.dialect))
523 .or_default()
524 .push(source_name.clone());
525 }
526 }
527
528 column_to_sources
530 .into_iter()
531 .filter(|(_, sources)| sources.len() == 1)
532 .map(|(column, sources)| (column, sources.into_iter().next().unwrap()))
533 .collect()
534 }
535
536 pub fn is_ambiguous(&mut self, column_name: &str) -> bool {
538 let normalized_column_name = normalize_column_name(column_name, self.dialect);
539 let all_source_columns = self.get_all_source_columns();
540 let sources_with_column: Vec<_> = all_source_columns
541 .iter()
542 .filter(|(_, columns)| {
543 columns.iter().any(|column| {
544 normalize_column_name(column, self.dialect) == normalized_column_name
545 })
546 })
547 .map(|(name, _)| name.clone())
548 .collect();
549
550 sources_with_column.len() > 1
551 }
552
553 pub fn sources_for_column(&mut self, column_name: &str) -> Vec<String> {
555 let normalized_column_name = normalize_column_name(column_name, self.dialect);
556 let all_source_columns = self.get_all_source_columns();
557 all_source_columns
558 .iter()
559 .filter(|(_, columns)| {
560 columns.iter().any(|column| {
561 normalize_column_name(column, self.dialect) == normalized_column_name
562 })
563 })
564 .map(|(name, _)| name.clone())
565 .collect()
566 }
567
568 pub fn disambiguate_in_join_context(
573 &mut self,
574 column_name: &str,
575 available_sources: &[String],
576 ) -> Option<String> {
577 let normalized_column_name = normalize_column_name(column_name, self.dialect);
578 let mut matching_sources = Vec::new();
579
580 for source_name in available_sources {
581 if let Ok(columns) = self.get_source_columns(source_name) {
582 if columns.iter().any(|column| {
583 normalize_column_name(column, self.dialect) == normalized_column_name
584 }) {
585 matching_sources.push(source_name.clone());
586 }
587 }
588 }
589
590 if matching_sources.len() == 1 {
591 Some(matching_sources.remove(0))
592 } else {
593 None
594 }
595 }
596}
597
598fn normalize_column_name(name: &str, dialect: Option<DialectType>) -> String {
599 normalize_name(name, dialect, false, true)
600}
601
602fn apply_alias_columns(mut columns: Vec<String>, alias_columns: &[Identifier]) -> Vec<String> {
603 for (idx, alias) in alias_columns.iter().enumerate() {
604 if let Some(column) = columns.get_mut(idx) {
605 *column = alias.name.clone();
606 }
607 }
608 columns
609}
610
611fn unnest_output_columns(unnest: &crate::expressions::UnnestFunc) -> Vec<String> {
612 unnest
613 .alias
614 .iter()
615 .map(|alias| alias.name.clone())
616 .chain(unnest.offset_alias.iter().map(|alias| alias.name.clone()))
617 .collect()
618}
619
620fn alias_output_columns(alias: &crate::expressions::Alias) -> Vec<String> {
621 if alias.column_aliases.is_empty() {
622 vec![alias.alias.name.clone()]
623 } else {
624 alias
625 .column_aliases
626 .iter()
627 .map(|column| column.name.clone())
628 .collect()
629 }
630}
631
632fn lateral_output_columns(lateral: &crate::expressions::Lateral) -> Vec<String> {
633 if lateral.column_aliases.is_empty() {
634 default_virtual_output_columns(&lateral.this)
635 } else {
636 lateral.column_aliases.clone()
637 }
638}
639
640fn lateral_view_output_columns(lateral_view: &crate::expressions::LateralView) -> Vec<String> {
641 lateral_view
642 .column_aliases
643 .iter()
644 .map(|column| column.name.clone())
645 .collect()
646}
647
648fn default_virtual_output_columns(expression: &Expression) -> Vec<String> {
649 match expression {
650 Expression::Unnest(unnest) => unnest_output_columns(unnest),
651 Expression::Alias(alias) if matches!(&alias.this, Expression::Unnest(_)) => {
652 alias_output_columns(alias)
653 }
654 Expression::Function(function) if function.name.eq_ignore_ascii_case("FLATTEN") => {
655 ["seq", "key", "path", "index", "value", "this"]
656 .into_iter()
657 .map(String::from)
658 .collect()
659 }
660 _ => Vec::new(),
661 }
662}
663
664fn pivot_excluded_source_columns(
665 pivot: &crate::expressions::Pivot,
666 dialect: Option<DialectType>,
667) -> HashSet<String> {
668 pivot
669 .fields
670 .iter()
671 .chain(pivot.expressions.iter())
672 .chain(pivot.using.iter())
673 .flat_map(expression_column_names)
674 .map(|column| normalize_column_name(&column, dialect))
675 .collect()
676}
677
678fn pivot_generated_output_columns(
679 pivot: &crate::expressions::Pivot,
680 _dialect: Option<DialectType>,
681) -> Vec<String> {
682 let fields = pivot_field_output_names(pivot);
683 let aggregations = if pivot.using.is_empty() {
684 &pivot.expressions
685 } else {
686 &pivot.using
687 };
688
689 if fields.is_empty() || aggregations.is_empty() {
690 return Vec::new();
691 }
692
693 let needs_suffix = aggregations.len() > 1;
694 let mut outputs = Vec::new();
695 for field in fields {
696 for aggregation in aggregations {
697 if let Some(suffix) = pivot_aggregation_output_suffix(aggregation, needs_suffix) {
698 outputs.push(format!("{field}_{suffix}"));
699 } else {
700 outputs.push(field.clone());
701 }
702 }
703 }
704 outputs
705}
706
707fn pivot_field_output_names(pivot: &crate::expressions::Pivot) -> Vec<String> {
708 pivot
709 .fields
710 .iter()
711 .filter_map(|field| match field {
712 Expression::In(in_expr) => Some(
713 in_expr
714 .expressions
715 .iter()
716 .filter_map(expression_name)
717 .collect::<Vec<_>>(),
718 ),
719 _ => None,
720 })
721 .flatten()
722 .collect()
723}
724
725fn pivot_aggregation_output_suffix(expr: &Expression, needs_suffix: bool) -> Option<String> {
726 match expr {
727 Expression::Alias(alias) => Some(alias.alias.name.clone()),
728 _ if needs_suffix => pivot_generated_aggregation_suffix(expr),
729 _ => None,
730 }
731}
732
733#[cfg(feature = "generate")]
734fn pivot_generated_aggregation_suffix(expr: &Expression) -> Option<String> {
735 Generator::sql(expr).ok().map(|sql| sql.to_lowercase())
736}
737
738#[cfg(not(feature = "generate"))]
739fn pivot_generated_aggregation_suffix(expr: &Expression) -> Option<String> {
740 expression_name(expr).or_else(|| Some(expr.variant_name().to_string()))
741}
742
743fn expression_name(expr: &Expression) -> Option<String> {
744 match expr {
745 Expression::PivotAlias(alias) => expression_name(&alias.alias),
746 Expression::Alias(alias) => Some(alias.alias.name.clone()),
747 Expression::Identifier(identifier) => Some(identifier.name.clone()),
748 Expression::Column(column) => Some(column.name.name.clone()),
749 Expression::Literal(literal) => Some(literal.value_str().to_string()),
750 Expression::Var(var) => Some(var.this.clone()),
751 Expression::Tuple(tuple) => tuple.expressions.first().and_then(expression_name),
752 _ => None,
753 }
754}
755
756fn expression_column_names(expr: &Expression) -> Vec<String> {
757 expr.find_all(|node| matches!(node, Expression::Column(_)))
758 .into_iter()
759 .filter_map(|node| match node {
760 Expression::Column(column) => Some(column.name.name.clone()),
761 _ => None,
762 })
763 .collect()
764}
765
766pub fn resolve_column(
770 scope: &Scope,
771 schema: &dyn Schema,
772 column_name: &str,
773 infer_schema: bool,
774) -> Option<String> {
775 let mut resolver = Resolver::new(scope, schema, infer_schema);
776 resolver.get_table(column_name)
777}
778
779pub fn is_column_ambiguous(scope: &Scope, schema: &dyn Schema, column_name: &str) -> bool {
781 let mut resolver = Resolver::new(scope, schema, true);
782 resolver.is_ambiguous(column_name)
783}
784
785fn qualified_table_name(table: &TableRef) -> String {
787 let mut parts = Vec::new();
788 if let Some(catalog) = &table.catalog {
789 parts.push(catalog.name.clone());
790 }
791 if let Some(schema) = &table.schema {
792 parts.push(schema.name.clone());
793 }
794 parts.push(table.name.name.clone());
795 parts.join(".")
796}
797
798#[cfg(test)]
799mod tests {
800 use super::*;
801 use crate::dialects::Dialect;
802 use crate::expressions::DataType;
803 use crate::parser::Parser;
804 use crate::schema::MappingSchema;
805 use crate::scope::build_scope;
806
807 fn create_test_schema() -> MappingSchema {
808 let mut schema = MappingSchema::new();
809 schema
811 .add_table(
812 "users",
813 &[
814 (
815 "id".to_string(),
816 DataType::Int {
817 length: None,
818 integer_spelling: false,
819 },
820 ),
821 ("name".to_string(), DataType::Text),
822 ("email".to_string(), DataType::Text),
823 ],
824 None,
825 )
826 .unwrap();
827 schema
828 .add_table(
829 "orders",
830 &[
831 (
832 "id".to_string(),
833 DataType::Int {
834 length: None,
835 integer_spelling: false,
836 },
837 ),
838 (
839 "user_id".to_string(),
840 DataType::Int {
841 length: None,
842 integer_spelling: false,
843 },
844 ),
845 (
846 "amount".to_string(),
847 DataType::Double {
848 precision: None,
849 scale: None,
850 },
851 ),
852 ],
853 None,
854 )
855 .unwrap();
856 schema
857 }
858
859 #[test]
860 fn test_resolver_basic() {
861 let ast = Parser::parse_sql("SELECT id, name FROM users").expect("Failed to parse");
862 let scope = build_scope(&ast[0]);
863 let schema = create_test_schema();
864 let mut resolver = Resolver::new(&scope, &schema, true);
865
866 let table = resolver.get_table("name");
868 assert_eq!(table, Some("users".to_string()));
869 }
870
871 #[test]
872 fn test_resolver_ambiguous_column() {
873 let ast =
874 Parser::parse_sql("SELECT id FROM users JOIN orders ON users.id = orders.user_id")
875 .expect("Failed to parse");
876 let scope = build_scope(&ast[0]);
877 let schema = create_test_schema();
878 let mut resolver = Resolver::new(&scope, &schema, true);
879
880 assert!(resolver.is_ambiguous("id"));
882
883 assert!(!resolver.is_ambiguous("name"));
885
886 assert!(!resolver.is_ambiguous("amount"));
888 }
889
890 #[test]
891 fn test_resolver_unambiguous_column() {
892 let ast = Parser::parse_sql(
893 "SELECT name, amount FROM users JOIN orders ON users.id = orders.user_id",
894 )
895 .expect("Failed to parse");
896 let scope = build_scope(&ast[0]);
897 let schema = create_test_schema();
898 let mut resolver = Resolver::new(&scope, &schema, true);
899
900 let table = resolver.get_table("name");
902 assert_eq!(table, Some("users".to_string()));
903
904 let table = resolver.get_table("amount");
906 assert_eq!(table, Some("orders".to_string()));
907 }
908
909 #[test]
910 fn test_resolver_with_alias() {
911 let ast = Parser::parse_sql("SELECT u.id FROM users AS u").expect("Failed to parse");
912 let scope = build_scope(&ast[0]);
913 let schema = create_test_schema();
914 let _resolver = Resolver::new(&scope, &schema, true);
915
916 assert!(scope.sources.contains_key("u"));
918 }
919
920 #[test]
921 fn test_sources_for_column() {
922 let ast = Parser::parse_sql("SELECT * FROM users JOIN orders ON users.id = orders.user_id")
923 .expect("Failed to parse");
924 let scope = build_scope(&ast[0]);
925 let schema = create_test_schema();
926 let mut resolver = Resolver::new(&scope, &schema, true);
927
928 let sources = resolver.sources_for_column("id");
930 assert!(sources.contains(&"users".to_string()));
931 assert!(sources.contains(&"orders".to_string()));
932
933 let sources = resolver.sources_for_column("email");
935 assert_eq!(sources, vec!["users".to_string()]);
936 }
937
938 #[test]
939 fn test_all_columns() {
940 let ast = Parser::parse_sql("SELECT * FROM users").expect("Failed to parse");
941 let scope = build_scope(&ast[0]);
942 let schema = create_test_schema();
943 let mut resolver = Resolver::new(&scope, &schema, true);
944
945 let all = resolver.all_columns();
946 assert!(all.contains("id"));
947 assert!(all.contains("name"));
948 assert!(all.contains("email"));
949 }
950
951 #[test]
952 fn test_resolver_cte_projected_alias_column() {
953 let ast = Parser::parse_sql(
954 "WITH my_cte AS (SELECT id AS emp_id FROM users) SELECT emp_id FROM my_cte",
955 )
956 .expect("Failed to parse");
957 let scope = build_scope(&ast[0]);
958 let schema = create_test_schema();
959 let mut resolver = Resolver::new(&scope, &schema, true);
960
961 let table = resolver.get_table("emp_id");
962 assert_eq!(table, Some("my_cte".to_string()));
963 }
964
965 #[test]
966 fn test_resolver_uses_name_aligned_set_operation_outputs() {
967 for dialect_type in [DialectType::DuckDB, DialectType::Snowflake] {
968 let expr = Dialect::get(dialect_type)
969 .parse(
970 "SELECT combined.right_value FROM (\
971 SELECT 1 AS left_value UNION ALL BY NAME \
972 SELECT 2 AS right_value\
973 ) AS combined",
974 )
975 .expect("parse")
976 .remove(0);
977 let scope = build_scope(&expr);
978 let schema = MappingSchema::with_dialect(dialect_type);
979 let mut resolver = Resolver::new(&scope, &schema, true);
980
981 assert_eq!(
982 resolver.get_table("right_value"),
983 Some("combined".to_string()),
984 "right-only output was not exposed for {dialect_type:?}"
985 );
986 }
987
988 let dialect_type = DialectType::BigQuery;
989 let expr = Dialect::get(dialect_type)
990 .parse(
991 "SELECT combined.c FROM (\
992 SELECT 1 AS a, 2 AS b FULL OUTER UNION ALL BY NAME \
993 SELECT 3 AS b, 4 AS c\
994 ) AS combined",
995 )
996 .expect("parse")
997 .remove(0);
998 let scope = build_scope(&expr);
999 let schema = MappingSchema::with_dialect(dialect_type);
1000 let mut resolver = Resolver::new(&scope, &schema, true);
1001 assert_eq!(resolver.get_table("c"), Some("combined".to_string()));
1002 }
1003
1004 #[test]
1005 fn test_resolve_column_helper() {
1006 let ast = Parser::parse_sql("SELECT name FROM users").expect("Failed to parse");
1007 let scope = build_scope(&ast[0]);
1008 let schema = create_test_schema();
1009
1010 let table = resolve_column(&scope, &schema, "name", true);
1011 assert_eq!(table, Some("users".to_string()));
1012 }
1013
1014 #[test]
1015 fn test_resolver_bigquery_mixed_case_column_names() {
1016 let dialect = Dialect::get(DialectType::BigQuery);
1017 let expr = dialect
1018 .parse("SELECT Name AS name FROM teams")
1019 .unwrap()
1020 .into_iter()
1021 .next()
1022 .expect("expected one expression");
1023 let scope = build_scope(&expr);
1024
1025 let mut schema = MappingSchema::with_dialect(DialectType::BigQuery);
1026 schema
1027 .add_table(
1028 "teams",
1029 &[("Name".into(), DataType::String { length: None })],
1030 None,
1031 )
1032 .expect("schema setup");
1033
1034 let mut resolver = Resolver::new(&scope, &schema, true);
1035 let table = resolver.get_table("Name");
1036 assert_eq!(table, Some("teams".to_string()));
1037
1038 let table = resolver.get_table("name");
1039 assert_eq!(table, Some("teams".to_string()));
1040 }
1041}