1use crate::dialects::DialectType;
9use crate::error::{ColumnResolutionReason, ColumnResolutionTarget};
10use crate::expressions::{DataType, Expression, Identifier, JoinKind, NamedWindow, Select, With};
11#[cfg(feature = "generate")]
12use crate::generator::Generator;
13use crate::optimizer::annotate_types::annotate_types;
14use crate::optimizer::qualify_schema_aware_expression;
15use crate::schema::{normalize_name, Schema};
16use crate::scope::{
17 build_scope, find_all_in_scope, Scope, ScopeType, SourceInfo as ScopeSourceInfo, SourceKind,
18};
19use crate::{Error, Result};
20use serde::{Deserialize, Serialize};
21use std::collections::{HashMap, HashSet};
22
23#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
25#[serde(rename_all = "camelCase")]
26pub struct QueryOutput {
27 pub columns: Vec<OutputColumn>,
29 pub ordinal_complete: bool,
31}
32
33#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
35#[serde(tag = "kind", rename_all = "snake_case")]
36pub enum OutputColumn {
37 Named {
39 name: String,
40 ordinal: Option<usize>,
42 },
43 Unnamed {
45 ordinal: Option<usize>,
47 },
48 Wildcard {
50 qualifier: Option<String>,
52 #[serde(rename = "startOrdinal")]
54 start_ordinal: Option<usize>,
55 },
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
60#[serde(rename_all = "snake_case")]
61pub enum SetOperator {
62 Union,
63 Intersect,
64 Except,
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
69#[serde(rename_all = "camelCase")]
70pub struct SetBranch {
71 pub operator: SetOperator,
73 pub ordinal: usize,
75 pub all: bool,
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct LineageNode {
82 pub name: String,
84 pub expression: Expression,
86 pub source: Expression,
88 pub downstream: Vec<LineageNode>,
90 pub source_name: String,
92 pub source_kind: SourceKind,
94 #[serde(default, skip_serializing_if = "Option::is_none")]
96 pub source_alias: Option<String>,
97 #[serde(default, skip_serializing_if = "Option::is_none")]
99 pub set_branch: Option<SetBranch>,
100 pub reference_node_name: String,
102}
103
104impl LineageNode {
105 pub fn new(name: impl Into<String>, expression: Expression, source: Expression) -> Self {
107 Self {
108 name: name.into(),
109 expression,
110 source,
111 downstream: Vec::new(),
112 source_name: String::new(),
113 source_kind: SourceKind::Unknown,
114 source_alias: None,
115 set_branch: None,
116 reference_node_name: String::new(),
117 }
118 }
119
120 pub fn walk(&self) -> LineageWalker<'_> {
122 LineageWalker { stack: vec![self] }
123 }
124
125 pub fn downstream_names(&self) -> Vec<String> {
127 self.downstream.iter().map(|n| n.name.clone()).collect()
128 }
129}
130
131fn source_kind_for_scope_context(
132 scope: &Scope,
133 source_name: &str,
134 reference_node_name: &str,
135) -> SourceKind {
136 source_kind_for_scope_context_with_type(
137 scope,
138 scope.scope_type,
139 source_name,
140 reference_node_name,
141 )
142}
143
144fn source_kind_for_scope_context_with_type(
145 scope: &Scope,
146 scope_type: ScopeType,
147 source_name: &str,
148 reference_node_name: &str,
149) -> SourceKind {
150 if source_name.is_empty() && reference_node_name.is_empty() {
151 return SourceKind::Root;
152 }
153 if let Some(source_info) = scope.sources.get(source_name) {
154 return source_info.kind;
155 }
156 if scope.cte_sources.contains_key(source_name) {
157 return SourceKind::Cte;
158 }
159 match scope_type {
160 ScopeType::Cte => SourceKind::Cte,
161 ScopeType::DerivedTable => SourceKind::DerivedTable,
162 ScopeType::Udtf => SourceKind::Virtual,
163 _ => SourceKind::Unknown,
164 }
165}
166
167fn apply_scope_context(
168 node: &mut LineageNode,
169 scope: &Scope,
170 source_name: &str,
171 reference_node_name: &str,
172) {
173 node.source_name = source_name.to_string();
174 node.reference_node_name = reference_node_name.to_string();
175 node.source_kind = source_kind_for_scope_context(scope, source_name, reference_node_name);
176}
177
178fn apply_scope_context_with_type(
179 node: &mut LineageNode,
180 scope: &Scope,
181 scope_type: ScopeType,
182 source_name: &str,
183 reference_node_name: &str,
184) {
185 node.source_name = source_name.to_string();
186 node.reference_node_name = reference_node_name.to_string();
187 node.source_kind = source_kind_for_scope_context_with_type(
188 scope,
189 scope_type,
190 source_name,
191 reference_node_name,
192 );
193}
194
195pub struct LineageWalker<'a> {
197 stack: Vec<&'a LineageNode>,
198}
199
200impl<'a> Iterator for LineageWalker<'a> {
201 type Item = &'a LineageNode;
202
203 fn next(&mut self) -> Option<Self::Item> {
204 if let Some(node) = self.stack.pop() {
205 for child in node.downstream.iter().rev() {
207 self.stack.push(child);
208 }
209 Some(node)
210 } else {
211 None
212 }
213 }
214}
215
216enum ColumnRef<'a> {
222 Name(&'a str),
223 Index(usize),
224}
225
226pub fn lineage(
252 column: &str,
253 sql: &Expression,
254 dialect: Option<DialectType>,
255 trim_selects: bool,
256) -> Result<LineageNode> {
257 let prepared = prepare_lineage_expression(sql, None, dialect, false)?;
258 lineage_from_column_ref(ColumnRef::Name(column), &prepared, dialect, trim_selects)
259}
260
261pub fn lineage_at(
263 ordinal: usize,
264 sql: &Expression,
265 dialect: Option<DialectType>,
266 trim_selects: bool,
267) -> Result<LineageNode> {
268 let prepared = prepare_lineage_expression(sql, None, dialect, false)?;
269 lineage_from_column_ref(ColumnRef::Index(ordinal), &prepared, dialect, trim_selects)
270}
271
272pub fn lineage_with_schema(
288 column: &str,
289 sql: &Expression,
290 schema: Option<&dyn Schema>,
291 dialect: Option<DialectType>,
292 trim_selects: bool,
293) -> Result<LineageNode> {
294 let prepared = prepare_lineage_expression(sql, schema, dialect, true)?;
295 lineage_from_column_ref(ColumnRef::Name(column), &prepared, dialect, trim_selects)
296}
297
298pub fn lineage_at_with_schema(
300 ordinal: usize,
301 sql: &Expression,
302 schema: Option<&dyn Schema>,
303 dialect: Option<DialectType>,
304 trim_selects: bool,
305) -> Result<LineageNode> {
306 let prepared = prepare_lineage_expression(sql, schema, dialect, true)?;
307 lineage_from_column_ref(ColumnRef::Index(ordinal), &prepared, dialect, trim_selects)
308}
309
310pub fn output_columns(sql: &Expression, dialect: Option<DialectType>) -> Result<QueryOutput> {
312 let prepared = prepare_lineage_expression(sql, None, dialect, false)?;
313 query_output_from_expression(&prepared, dialect)
314}
315
316pub fn output_columns_with_schema(
318 sql: &Expression,
319 schema: Option<&dyn Schema>,
320 dialect: Option<DialectType>,
321) -> Result<QueryOutput> {
322 let prepared = prepare_lineage_expression(sql, schema, dialect, true)?;
323 query_output_from_expression(&prepared, dialect)
324}
325
326fn prepare_lineage_expression(
327 sql: &Expression,
328 schema: Option<&dyn Schema>,
329 dialect: Option<DialectType>,
330 schema_aware: bool,
331) -> Result<Expression> {
332 let normalized = lineage_normalized_expression(sql);
333 let mut prepared = if schema_aware {
334 if let Some(schema) = schema {
335 qualify_schema_aware_expression(normalized.clone(), schema, dialect).map_err(
336 |error| {
337 Error::internal(format!("Lineage qualification failed with schema: {error}"))
338 },
339 )?
340 } else {
341 normalized
342 }
343 } else {
344 normalized
345 };
346
347 if schema_aware {
348 annotate_types(&mut prepared, schema, dialect);
349 expand_cte_stars(&mut prepared, schema);
350 } else if has_lineage_with_clause(&prepared) {
351 expand_cte_stars(&mut prepared, None);
352 }
353
354 Ok(prepared)
355}
356
357fn lineage_from_column_ref(
358 column: ColumnRef<'_>,
359 sql: &Expression,
360 dialect: Option<DialectType>,
361 trim_selects: bool,
362) -> Result<LineageNode> {
363 let scope = build_scope(sql);
364 to_node(column, scope, dialect, "", "", "", trim_selects)
365}
366
367#[cfg(feature = "generate")]
368pub(crate) fn lineage_by_index_from_expression(
369 column_index: usize,
370 sql: &Expression,
371 dialect: Option<DialectType>,
372 trim_selects: bool,
373) -> Result<LineageNode> {
374 let prepared = prepare_lineage_expression(sql, None, dialect, false)?;
375 lineage_from_column_ref(
376 ColumnRef::Index(column_index),
377 &prepared,
378 dialect,
379 trim_selects,
380 )
381}
382
383fn lineage_normalized_expression(sql: &Expression) -> Expression {
384 match sql {
385 Expression::Prepare(prepare) => lineage_normalized_expression(&prepare.statement),
386 Expression::CreateTable(create) => create
387 .as_select
388 .as_ref()
389 .map(|query| attach_with_to_query(query.clone(), create.with_cte.clone()))
390 .unwrap_or_else(|| sql.clone()),
391 Expression::CreateView(create) => lineage_normalized_expression(&create.query),
392 Expression::Insert(insert) => insert
393 .query
394 .as_ref()
395 .map(|query| attach_with_to_query(query.clone(), insert.with.clone()))
396 .unwrap_or_else(|| sql.clone()),
397 _ => sql.clone(),
398 }
399}
400
401fn attach_with_to_query(
402 mut query: Expression,
403 with: Option<crate::expressions::With>,
404) -> Expression {
405 if let Some(with) = with {
406 attach_with_to_query_mut(&mut query, with);
407 }
408 query
409}
410
411fn attach_with_to_query_mut(query: &mut Expression, with: crate::expressions::With) {
412 match query {
413 Expression::Select(select) => {
414 if select.with.is_none() {
415 select.with = Some(with);
416 }
417 }
418 Expression::Union(union) => {
419 if union.with.is_none() {
420 union.with = Some(with);
421 }
422 }
423 Expression::Intersect(intersect) => {
424 if intersect.with.is_none() {
425 intersect.with = Some(with);
426 }
427 }
428 Expression::Except(except) => {
429 if except.with.is_none() {
430 except.with = Some(with);
431 }
432 }
433 Expression::Paren(paren) => attach_with_to_query_mut(&mut paren.this, with),
434 _ => {}
435 }
436}
437
438fn has_lineage_with_clause(expr: &Expression) -> bool {
439 match expr {
440 Expression::Select(select) => select.with.is_some(),
441 Expression::Union(union) => {
442 union.with.is_some()
443 || has_lineage_with_clause(&union.left)
444 || has_lineage_with_clause(&union.right)
445 }
446 Expression::Intersect(intersect) => {
447 intersect.with.is_some()
448 || has_lineage_with_clause(&intersect.left)
449 || has_lineage_with_clause(&intersect.right)
450 }
451 Expression::Except(except) => {
452 except.with.is_some()
453 || has_lineage_with_clause(&except.left)
454 || has_lineage_with_clause(&except.right)
455 }
456 Expression::Paren(paren) => has_lineage_with_clause(&paren.this),
457 _ => false,
458 }
459}
460
461fn normalize_cte_name(ident: &Identifier) -> String {
471 if ident.quoted {
472 ident.name.clone()
473 } else {
474 ident.name.to_lowercase()
475 }
476}
477
478pub fn expand_cte_stars(expr: &mut Expression, schema: Option<&dyn Schema>) {
490 if let Expression::Prepare(prepare) = expr {
491 expand_cte_stars(&mut prepare.statement, schema);
492 return;
493 }
494
495 let resolved_cte_columns = {
496 let with = match query_with_mut(expr) {
497 Some(with) => with,
498 None => return,
499 };
500 let is_recursive_with = with.recursive;
501 let mut resolved_cte_columns: HashMap<String, Vec<String>> = HashMap::new();
502
503 for cte in &mut with.ctes {
504 let cte_name = normalize_cte_name(&cte.alias);
505 let explicit_columns = (!cte.columns.is_empty())
506 .then(|| cte.columns.iter().map(|c| c.name.clone()).collect());
507
508 if is_recursive_with && query_references_source(&cte.this, &cte_name) {
513 if let Some(columns) = explicit_columns {
514 resolved_cte_columns.insert(cte_name, columns);
515 }
516 continue;
517 }
518
519 let implicit_columns =
523 rewrite_stars_in_query(&mut cte.this, &resolved_cte_columns, schema);
524 if let Some(columns) = explicit_columns.or(implicit_columns) {
525 resolved_cte_columns.insert(cte_name, columns);
526 }
527 }
528
529 resolved_cte_columns
530 };
531
532 rewrite_stars_in_query(expr, &resolved_cte_columns, schema);
536}
537
538fn query_with_mut(expr: &mut Expression) -> Option<&mut With> {
540 let mut current = expr;
541 loop {
542 match current {
543 Expression::Select(select) => return select.with.as_mut(),
544 Expression::Union(union) => return union.with.as_mut(),
545 Expression::Intersect(intersect) => return intersect.with.as_mut(),
546 Expression::Except(except) => return except.with.as_mut(),
547 Expression::Paren(p) => current = &mut p.this,
548 Expression::Subquery(subquery) => current = &mut subquery.this,
549 _ => return None,
550 }
551 }
552}
553
554fn query_references_source(expr: &Expression, source_name: &str) -> bool {
559 let mut stack = vec![expr];
560
561 while let Some(current) = stack.pop() {
562 match current {
563 Expression::Select(select) => {
564 if get_select_sources(select)
565 .iter()
566 .any(|source| source.normalized == source_name)
567 {
568 return true;
569 }
570 }
571 Expression::Union(union) => {
572 stack.push(&union.right);
573 stack.push(&union.left);
574 }
575 Expression::Intersect(intersect) => {
576 stack.push(&intersect.right);
577 stack.push(&intersect.left);
578 }
579 Expression::Except(except) => {
580 stack.push(&except.right);
581 stack.push(&except.left);
582 }
583 Expression::Paren(paren) => stack.push(&paren.this),
584 Expression::Subquery(subquery) => stack.push(&subquery.this),
585 _ => {}
586 }
587 }
588
589 false
590}
591
592fn rewrite_stars_in_query(
599 expr: &mut Expression,
600 resolved_ctes: &HashMap<String, Vec<String>>,
601 schema: Option<&dyn Schema>,
602) -> Option<Vec<String>> {
603 let mut leftmost_columns = None;
604 let mut stack = vec![expr];
605
606 while let Some(current) = stack.pop() {
607 match current {
608 Expression::Select(select) => {
609 let columns = rewrite_stars_in_select(select, resolved_ctes, schema);
610 if leftmost_columns.is_none() {
611 leftmost_columns = Some(columns);
612 }
613 }
614 Expression::Union(union) => {
615 stack.push(&mut union.right);
616 stack.push(&mut union.left);
617 }
618 Expression::Intersect(intersect) => {
619 stack.push(&mut intersect.right);
620 stack.push(&mut intersect.left);
621 }
622 Expression::Except(except) => {
623 stack.push(&mut except.right);
624 stack.push(&mut except.left);
625 }
626 Expression::Paren(paren) => stack.push(&mut paren.this),
627 Expression::Subquery(subquery) => stack.push(&mut subquery.this),
628 _ => {}
629 }
630 }
631
632 leftmost_columns
633}
634
635fn rewrite_stars_in_select(
639 select: &mut Select,
640 resolved_ctes: &HashMap<String, Vec<String>>,
641 schema: Option<&dyn Schema>,
642) -> Vec<String> {
643 let has_star = select
648 .expressions
649 .iter()
650 .any(|e| matches!(e, Expression::Star(_)));
651 let has_qualified_star = select
652 .expressions
653 .iter()
654 .any(|e| matches!(e, Expression::Column(c) if c.name.name == "*"));
655
656 if !has_star && !has_qualified_star {
657 return select
659 .expressions
660 .iter()
661 .filter_map(get_expression_output_name)
662 .collect();
663 }
664
665 let sources = get_select_sources(select);
666 let mut new_expressions = Vec::new();
667 let mut result_columns = Vec::new();
668
669 for expr in &select.expressions {
670 match expr {
671 Expression::Star(star) => {
672 let qual = star.table.as_ref();
673 if let Some(expanded) =
674 expand_star_from_sources(qual, &sources, resolved_ctes, schema)
675 {
676 for (src_alias, col_name) in &expanded {
677 let table_id = Identifier::new(src_alias);
678 new_expressions.push(make_column_expr(col_name, Some(&table_id)));
679 result_columns.push(col_name.clone());
680 }
681 } else {
682 new_expressions.push(expr.clone());
683 result_columns.push("*".to_string());
684 }
685 }
686 Expression::Column(c) if c.name.name == "*" => {
687 let qual = c.table.as_ref();
688 if let Some(expanded) =
689 expand_star_from_sources(qual, &sources, resolved_ctes, schema)
690 {
691 for (_src_alias, col_name) in &expanded {
692 new_expressions.push(make_column_expr(col_name, c.table.as_ref()));
694 result_columns.push(col_name.clone());
695 }
696 } else {
697 new_expressions.push(expr.clone());
698 result_columns.push("*".to_string());
699 }
700 }
701 _ => {
702 new_expressions.push(expr.clone());
703 if let Some(name) = get_expression_output_name(expr) {
704 result_columns.push(name);
705 }
706 }
707 }
708 }
709
710 select.expressions = new_expressions;
711 result_columns
712}
713
714fn expand_star_from_sources(
719 qualifier: Option<&Identifier>,
720 sources: &[SourceInfo],
721 resolved_ctes: &HashMap<String, Vec<String>>,
722 schema: Option<&dyn Schema>,
723) -> Option<Vec<(String, String)>> {
724 let mut expanded = Vec::new();
725
726 if let Some(qual) = qualifier {
727 let qual_normalized = normalize_cte_name(qual);
729 for src in sources {
730 if src.normalized == qual_normalized || src.alias.to_lowercase() == qual_normalized {
731 if let Some(cols) = resolved_ctes.get(&src.normalized) {
733 expanded.extend(cols.iter().map(|c| (src.alias.clone(), c.clone())));
734 return Some(expanded);
735 }
736 if let Some(cols) = lookup_schema_columns(schema, &src.fq_name) {
738 expanded.extend(cols.into_iter().map(|c| (src.alias.clone(), c)));
739 return Some(expanded);
740 }
741 }
742 }
743 None
744 } else {
745 let mut any_expanded = false;
751 for src in sources {
752 if let Some(cols) = resolved_ctes.get(&src.normalized) {
753 expanded.extend(cols.iter().map(|c| (src.alias.clone(), c.clone())));
754 any_expanded = true;
755 } else if let Some(cols) = lookup_schema_columns(schema, &src.fq_name) {
756 expanded.extend(cols.into_iter().map(|c| (src.alias.clone(), c)));
757 any_expanded = true;
758 } else {
759 return None;
760 }
761 }
762 if any_expanded {
763 Some(expanded)
764 } else {
765 None
766 }
767 }
768}
769
770fn lookup_schema_columns(schema: Option<&dyn Schema>, fq_name: &str) -> Option<Vec<String>> {
772 let schema = schema?;
773 if fq_name.is_empty() {
774 return None;
775 }
776 schema
777 .column_names(fq_name)
778 .ok()
779 .filter(|cols| !cols.is_empty() && !cols.contains(&"*".to_string()))
780}
781
782fn make_column_expr(name: &str, table: Option<&Identifier>) -> Expression {
784 Expression::Column(Box::new(crate::expressions::Column {
785 name: Identifier::new(name),
786 table: table.cloned(),
787 join_mark: false,
788 trailing_comments: Vec::new(),
789 span: None,
790 inferred_type: None,
791 }))
792}
793
794fn get_expression_output_name(expr: &Expression) -> Option<String> {
796 match expr {
797 Expression::Alias(a) => Some(a.alias.name.clone()),
798 Expression::Column(c) => Some(c.name.name.clone()),
799 Expression::Identifier(id) => Some(id.name.clone()),
800 Expression::Star(_) => Some("*".to_string()),
801 _ => None,
802 }
803}
804
805struct SourceInfo {
807 alias: String,
808 quoted: bool,
815 normalized: String,
817 fq_name: String,
819}
820
821fn get_select_sources(select: &Select) -> Vec<SourceInfo> {
824 let mut sources = Vec::new();
825
826 fn extract_source(expr: &Expression) -> Option<SourceInfo> {
827 fn virtual_source_info(alias: &Identifier) -> SourceInfo {
828 SourceInfo {
829 alias: alias.name.clone(),
830 quoted: alias.quoted,
831 normalized: normalize_cte_name(alias),
832 fq_name: alias.name.clone(),
833 }
834 }
835
836 fn named_virtual_source_info(alias: &str) -> SourceInfo {
837 SourceInfo {
838 alias: alias.to_string(),
839 quoted: false,
840 normalized: alias.to_lowercase(),
841 fq_name: alias.to_string(),
842 }
843 }
844
845 match expr {
846 Expression::Table(t) => {
847 let normalized = normalize_cte_name(&t.name);
848 let alias = t
849 .alias
850 .as_ref()
851 .map(|a| a.name.clone())
852 .unwrap_or_else(|| t.name.name.clone());
853 let mut parts = Vec::new();
854 if let Some(catalog) = &t.catalog {
855 parts.push(catalog.name.clone());
856 }
857 if let Some(schema) = &t.schema {
858 parts.push(schema.name.clone());
859 }
860 parts.push(t.name.name.clone());
861 let fq_name = parts.join(".");
862 Some(SourceInfo {
863 alias,
864 quoted: t.name.quoted,
865 normalized,
866 fq_name,
867 })
868 }
869 Expression::Subquery(s) => {
870 let alias_identifier = s.alias.as_ref()?;
871 let alias = alias_identifier.name.clone();
872 let normalized = alias.to_lowercase();
873 let fq_name = alias.clone();
874 Some(SourceInfo {
875 alias,
876 quoted: alias_identifier.quoted,
877 normalized,
878 fq_name,
879 })
880 }
881 Expression::Unnest(u) => u.alias.as_ref().map(virtual_source_info),
882 Expression::Alias(a) if matches!(&a.this, Expression::Unnest(_)) => {
883 Some(virtual_source_info(&a.alias))
884 }
885 Expression::Alias(a) if is_query_like_relation(&a.this) => {
886 Some(virtual_source_info(&a.alias))
887 }
888 Expression::Lateral(lateral) => lateral.alias.as_deref().map(named_virtual_source_info),
889 Expression::LateralView(lateral_view) => lateral_view
890 .table_alias
891 .as_ref()
892 .or_else(|| lateral_view.column_aliases.first())
893 .map(virtual_source_info),
894 Expression::Pivot(pivot) => {
895 let alias = pivot_lineage_source_name(
896 &pivot.this,
897 pivot.alias.as_ref().map(|alias| alias.name.as_str()),
898 );
899 Some(SourceInfo {
900 alias: alias.clone(),
901 quoted: false,
902 normalized: alias.to_lowercase(),
903 fq_name: alias,
904 })
905 }
906 Expression::Unpivot(unpivot) => {
907 let alias = pivot_lineage_source_name(
908 &unpivot.this,
909 unpivot.alias.as_ref().map(|alias| alias.name.as_str()),
910 );
911 Some(SourceInfo {
912 alias: alias.clone(),
913 quoted: false,
914 normalized: alias.to_lowercase(),
915 fq_name: alias,
916 })
917 }
918 Expression::Paren(p) => extract_source(&p.this),
919 _ => None,
920 }
921 }
922
923 if let Some(from) = &select.from {
924 for expr in &from.expressions {
925 if let Some(info) = extract_source(expr) {
926 sources.push(info);
927 }
928 }
929 }
930 for join in &select.joins {
931 if is_semi_or_anti_join_kind(join.kind) {
932 continue;
933 }
934 if let Some(info) = extract_source(&join.this) {
935 sources.push(info);
936 }
937 }
938 for lateral_view in &select.lateral_views {
939 if let Some(info) = extract_source(&Expression::LateralView(Box::new(lateral_view.clone())))
940 {
941 sources.push(info);
942 }
943 }
944 sources
945}
946
947fn pivot_lineage_source_name(source: &Expression, explicit_alias: Option<&str>) -> String {
948 if let Some(alias) = explicit_alias {
949 return alias.to_string();
950 }
951
952 match source {
953 Expression::Table(table) => table
954 .alias
955 .as_ref()
956 .map(|alias| alias.name.clone())
957 .unwrap_or_else(|| table.name.name.clone()),
958 Expression::Subquery(subquery) => subquery
959 .alias
960 .as_ref()
961 .map(|alias| alias.name.clone())
962 .unwrap_or_else(|| "_0".to_string()),
963 Expression::Paren(paren) => pivot_lineage_source_name(&paren.this, explicit_alias),
964 _ => "_0".to_string(),
965 }
966}
967
968pub fn get_source_tables(node: &LineageNode) -> HashSet<String> {
970 let mut tables = HashSet::new();
971 collect_source_tables(node, &mut tables);
972 tables
973}
974
975pub fn collect_source_tables(node: &LineageNode, tables: &mut HashSet<String>) {
977 if let Expression::Table(table) = &node.source {
978 tables.insert(table.name.name.clone());
979 }
980 for child in &node.downstream {
981 collect_source_tables(child, tables);
982 }
983}
984
985const MAX_LINEAGE_DEPTH: usize = 64;
992
993#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
994struct ScopeId(usize);
995
996struct IndexedScope {
997 scope: Scope,
998 subquery_scopes: Vec<ScopeId>,
999 derived_table_scopes: Vec<ScopeId>,
1000 cte_scopes: Vec<ScopeId>,
1001 union_scopes: Vec<ScopeId>,
1002}
1003
1004struct LineageScopeContext {
1005 scopes: Vec<IndexedScope>,
1006 conservative: bool,
1008}
1009
1010pub(crate) struct ScopedLineage {
1015 context: LineageScopeContext,
1016 root: ScopeId,
1017 ctes: Vec<ScopeId>,
1018 dialect: Option<DialectType>,
1019}
1020
1021impl ScopedLineage {
1022 pub(crate) fn new(scope: Scope, inherited_ctes: &[Scope], dialect: DialectType) -> Self {
1023 let visible_ctes = scope.cte_sources.clone();
1024 let (mut context, root) = LineageScopeContext::from_scope(scope);
1025 context.conservative = true;
1026 let mut ctes = context.indexed(root).cte_scopes.clone();
1027 for cte_scope in inherited_ctes {
1028 if let Expression::Cte(cte) = &cte_scope.expression {
1029 if visible_ctes
1030 .get(&cte.alias.name)
1031 .is_some_and(|source| source.expression == cte_scope.expression)
1032 && !ctes
1033 .iter()
1034 .any(|id| context.scope(*id).expression == cte_scope.expression)
1035 {
1036 ctes.push(context.insert_scope(cte_scope.clone()));
1037 }
1038 }
1039 }
1040 Self {
1041 context,
1042 root,
1043 ctes,
1044 dialect: Some(dialect),
1045 }
1046 }
1047
1048 pub(crate) fn column(&self, source: &str, column: &str) -> LineageNode {
1049 let mut node = LineageNode::new(
1050 column,
1051 Expression::qualified_column(source, column),
1052 self.context.scope(self.root).expression.clone(),
1053 );
1054 resolve_qualified_column(
1055 &mut node,
1056 &self.context,
1057 self.root,
1058 self.dialect,
1059 source,
1060 column,
1061 column,
1062 false,
1063 &self.ctes,
1064 0,
1065 );
1066 node
1067 }
1068
1069 pub(crate) fn output(&self, ordinal: usize) -> Result<LineageNode> {
1070 to_node_inner(
1071 ColumnRef::Index(ordinal),
1072 &self.context,
1073 self.root,
1074 self.dialect,
1075 "",
1076 "",
1077 "",
1078 false,
1079 &self.ctes,
1080 0,
1081 )
1082 }
1083
1084 pub(crate) fn output_names(&self) -> Vec<String> {
1085 crate::ast_transforms::get_output_column_names_for_dialect(
1086 effective_scope_expression(&self.context.scope(self.root).expression),
1087 self.dialect,
1088 )
1089 }
1090
1091 pub(crate) fn subquery_output(&self, index: usize) -> Result<LineageNode> {
1092 let scope_id = *self
1093 .context
1094 .indexed(self.root)
1095 .subquery_scopes
1096 .get(index)
1097 .ok_or_else(|| Error::internal("missing scalar subquery scope"))?;
1098 to_node_inner(
1099 ColumnRef::Index(0),
1100 &self.context,
1101 scope_id,
1102 self.dialect,
1103 "",
1104 "",
1105 "",
1106 false,
1107 &self.ctes,
1108 0,
1109 )
1110 }
1111}
1112
1113impl LineageScopeContext {
1114 fn from_scope(scope: Scope) -> (Self, ScopeId) {
1115 let mut context = Self {
1116 scopes: Vec::new(),
1117 conservative: false,
1118 };
1119 let root = context.insert_scope(scope);
1120 (context, root)
1121 }
1122
1123 fn insert_scope(&mut self, mut scope: Scope) -> ScopeId {
1124 let subquery_scopes = std::mem::take(&mut scope.subquery_scopes)
1125 .into_iter()
1126 .map(|child| self.insert_scope(child))
1127 .collect();
1128 let derived_table_scopes = std::mem::take(&mut scope.derived_table_scopes)
1129 .into_iter()
1130 .map(|child| self.insert_scope(child))
1131 .collect();
1132 let cte_scopes = std::mem::take(&mut scope.cte_scopes)
1133 .into_iter()
1134 .map(|child| self.insert_scope(child))
1135 .collect();
1136 let union_scopes = std::mem::take(&mut scope.union_scopes)
1137 .into_iter()
1138 .map(|child| self.insert_scope(child))
1139 .collect();
1140
1141 let id = ScopeId(self.scopes.len());
1142 self.scopes.push(IndexedScope {
1143 scope,
1144 subquery_scopes,
1145 derived_table_scopes,
1146 cte_scopes,
1147 union_scopes,
1148 });
1149 id
1150 }
1151
1152 fn indexed(&self, id: ScopeId) -> &IndexedScope {
1153 &self.scopes[id.0]
1154 }
1155
1156 fn scope(&self, id: ScopeId) -> &Scope {
1157 &self.indexed(id).scope
1158 }
1159}
1160
1161fn to_node(
1163 column: ColumnRef<'_>,
1164 scope: Scope,
1165 dialect: Option<DialectType>,
1166 scope_name: &str,
1167 source_name: &str,
1168 reference_node_name: &str,
1169 trim_selects: bool,
1170) -> Result<LineageNode> {
1171 let (context, scope_id) = LineageScopeContext::from_scope(scope);
1172 to_node_inner(
1173 column,
1174 &context,
1175 scope_id,
1176 dialect,
1177 scope_name,
1178 source_name,
1179 reference_node_name,
1180 trim_selects,
1181 &[],
1182 0,
1183 )
1184}
1185
1186fn to_node_inner(
1187 column: ColumnRef<'_>,
1188 context: &LineageScopeContext,
1189 scope_id: ScopeId,
1190 dialect: Option<DialectType>,
1191 scope_name: &str,
1192 source_name: &str,
1193 reference_node_name: &str,
1194 trim_selects: bool,
1195 ancestor_cte_scopes: &[ScopeId],
1196 depth: usize,
1197) -> Result<LineageNode> {
1198 if depth > MAX_LINEAGE_DEPTH {
1199 return Err(Error::internal(format!(
1200 "lineage recursion depth exceeded (>{MAX_LINEAGE_DEPTH}) — possible circular CTE reference for scope '{scope_name}'"
1201 )));
1202 }
1203 let scope = context.scope(scope_id);
1204 let scope_expr = &scope.expression;
1205
1206 let mut all_cte_scopes = context.indexed(scope_id).cte_scopes.clone();
1208 all_cte_scopes.extend_from_slice(ancestor_cte_scopes);
1209 let descendant_cte_scopes = descendant_cte_scope_ids(&all_cte_scopes, scope_id);
1210
1211 let effective_expr = effective_scope_expression(scope_expr);
1214
1215 if matches!(
1217 effective_expr,
1218 Expression::Union(_) | Expression::Intersect(_) | Expression::Except(_)
1219 ) {
1220 return handle_set_operation(
1221 &column,
1222 context,
1223 scope_id,
1224 effective_expr,
1225 matches!(scope_expr, Expression::Cte(_)).then_some(ScopeType::Root),
1226 dialect,
1227 scope_name,
1228 source_name,
1229 reference_node_name,
1230 trim_selects,
1231 &descendant_cte_scopes,
1232 depth,
1233 );
1234 }
1235
1236 let select_expr = find_select_expr(effective_expr, &column, dialect)?;
1238 let column_name = resolve_column_name(&column, &select_expr);
1239
1240 let node_source = if trim_selects {
1242 trim_source(effective_expr, &select_expr)
1243 } else {
1244 effective_expr.clone()
1245 };
1246
1247 let mut node = LineageNode::new(&column_name, select_expr.clone(), node_source);
1249 apply_scope_context(&mut node, scope, source_name, reference_node_name);
1250
1251 if let Expression::Star(star) = &select_expr {
1253 let star_table = star
1254 .table
1255 .as_ref()
1256 .map(|identifier| identifier.name.as_str());
1257 for (name, source_info) in &scope.sources {
1258 if let Some(star_table) = star_table {
1259 let table_matches = name.eq_ignore_ascii_case(star_table)
1260 || source_info
1261 .alias
1262 .as_deref()
1263 .is_some_and(|alias| alias.eq_ignore_ascii_case(star_table))
1264 || matches!(
1265 &source_info.expression,
1266 Expression::Table(table_ref)
1267 if table_name_from_table_ref(table_ref).eq_ignore_ascii_case(star_table)
1268 );
1269 if !table_matches {
1270 continue;
1271 }
1272 }
1273
1274 let mut child = LineageNode::new(
1275 format!("{}.*", name),
1276 Expression::Star(crate::expressions::Star {
1277 table: star.table.clone(),
1278 except: None,
1279 replace: None,
1280 rename: None,
1281 trailing_comments: vec![],
1282 span: None,
1283 }),
1284 source_info.expression.clone(),
1285 );
1286 apply_source_info_context(&mut child, name, source_info);
1287 node.downstream.push(child);
1288 }
1289 return Ok(node);
1290 }
1291
1292 for query in query_expressions_in_scope(&select_expr) {
1294 for &sq_scope_id in &context.indexed(scope_id).subquery_scopes {
1295 if context.scope(sq_scope_id).expression == *query {
1296 if let Ok(child) = to_node_inner(
1297 ColumnRef::Index(0),
1298 context,
1299 sq_scope_id,
1300 dialect,
1301 &column_name,
1302 "",
1303 "",
1304 trim_selects,
1305 &descendant_cte_scopes,
1306 depth + 1,
1307 ) {
1308 node.downstream.push(child);
1309 }
1310 break;
1311 }
1312 }
1313 }
1314
1315 let col_refs = find_column_refs_in_expr_with_select(&select_expr, effective_expr, dialect);
1317 for col_ref in col_refs {
1318 let col_name = &col_ref.column;
1319 if let Some(ref table_id) = col_ref.table {
1320 let tbl = &table_id.name;
1321 resolve_qualified_column(
1322 &mut node,
1323 context,
1324 scope_id,
1325 dialect,
1326 tbl,
1327 col_name,
1328 &column_name,
1329 trim_selects,
1330 &all_cte_scopes,
1331 depth,
1332 );
1333 } else {
1334 if let Some(alias_expr) =
1335 find_prior_select_alias_expr(effective_expr, &select_expr, col_name, dialect)
1336 {
1337 for alias_ref in
1338 find_column_refs_in_expr_with_select(&alias_expr, effective_expr, dialect)
1339 {
1340 if let Some(ref table_id) = alias_ref.table {
1341 resolve_qualified_column(
1342 &mut node,
1343 context,
1344 scope_id,
1345 dialect,
1346 &table_id.name,
1347 &alias_ref.column,
1348 &column_name,
1349 trim_selects,
1350 &all_cte_scopes,
1351 depth,
1352 );
1353 } else {
1354 resolve_unqualified_column(
1355 &mut node,
1356 context,
1357 scope_id,
1358 dialect,
1359 &alias_ref.column,
1360 &column_name,
1361 trim_selects,
1362 &all_cte_scopes,
1363 depth,
1364 );
1365 }
1366 }
1367 continue;
1368 }
1369
1370 resolve_unqualified_column(
1371 &mut node,
1372 context,
1373 scope_id,
1374 dialect,
1375 col_name,
1376 &column_name,
1377 trim_selects,
1378 &all_cte_scopes,
1379 depth,
1380 );
1381 }
1382 }
1383
1384 Ok(node)
1385}
1386
1387fn descendant_cte_scope_ids(all_cte_scopes: &[ScopeId], current_scope: ScopeId) -> Vec<ScopeId> {
1388 all_cte_scopes
1389 .iter()
1390 .copied()
1391 .filter(|scope| *scope != current_scope)
1392 .collect()
1393}
1394
1395fn effective_scope_expression(expr: &Expression) -> &Expression {
1396 match expr {
1397 Expression::Cte(cte) => effective_scope_expression(&cte.this),
1398 Expression::Subquery(subquery) => effective_scope_expression(&subquery.this),
1399 Expression::Paren(paren) => effective_scope_expression(&paren.this),
1400 other => other,
1401 }
1402}
1403
1404fn query_expressions_in_scope(expr: &Expression) -> Vec<&Expression> {
1405 let mut queries = Vec::new();
1406 let mut seen = HashSet::new();
1407
1408 for node in find_all_in_scope(
1409 expr,
1410 |node| {
1411 matches!(
1412 node,
1413 Expression::Subquery(subquery) if subquery.alias.is_none()
1414 ) || matches!(
1415 node,
1416 Expression::Exists(_) | Expression::In(_) | Expression::Any(_) | Expression::All(_)
1417 )
1418 },
1419 false,
1420 ) {
1421 let query = match node {
1422 Expression::Subquery(subquery) if subquery.alias.is_none() => Some(&subquery.this),
1423 Expression::Exists(exists) => Some(&exists.this),
1424 Expression::In(in_expr) => in_expr.query.as_ref(),
1425 Expression::Any(quantified) | Expression::All(quantified) => Some(&quantified.subquery),
1426 _ => None,
1427 };
1428
1429 if let Some(query) = query {
1430 let key = query as *const Expression as usize;
1431 if seen.insert(key) {
1432 queries.push(query);
1433 }
1434 }
1435 }
1436
1437 queries
1438}
1439
1440fn set_branch_metadata(expression: &Expression, ordinal: usize) -> SetBranch {
1445 match expression {
1446 Expression::Union(union) => SetBranch {
1447 operator: SetOperator::Union,
1448 ordinal,
1449 all: union.all,
1450 },
1451 Expression::Intersect(intersect) => SetBranch {
1452 operator: SetOperator::Intersect,
1453 ordinal,
1454 all: intersect.all,
1455 },
1456 Expression::Except(except) => SetBranch {
1457 operator: SetOperator::Except,
1458 ordinal,
1459 all: except.all,
1460 },
1461 _ => unreachable!("set-operation metadata requires a set operation"),
1462 }
1463}
1464
1465fn handle_set_operation(
1466 column: &ColumnRef<'_>,
1467 context: &LineageScopeContext,
1468 scope_id: ScopeId,
1469 scope_expr: &Expression,
1470 scope_type_override: Option<ScopeType>,
1471 dialect: Option<DialectType>,
1472 scope_name: &str,
1473 source_name: &str,
1474 reference_node_name: &str,
1475 trim_selects: bool,
1476 ancestor_cte_scopes: &[ScopeId],
1477 depth: usize,
1478) -> Result<LineageNode> {
1479 let scope = context.scope(scope_id);
1480 let trace_wildcard_by_name =
1481 matches!(column, ColumnRef::Name(name) if normalize_column_name(name, dialect) == "*");
1482
1483 let aligned_layout = match crate::set_operation::set_operation_layout(scope_expr, dialect) {
1484 Ok(layout) => layout,
1485 Err(error) if error.is_indeterminate() => None,
1486 Err(error) => return Err(Error::invalid_input(error.to_string())),
1487 };
1488
1489 let col_index = match column {
1491 ColumnRef::Name(_) if trace_wildcard_by_name => 0,
1492 ColumnRef::Name(name) => column_to_index(scope_expr, name, dialect)?,
1493 ColumnRef::Index(i) => *i,
1494 };
1495
1496 if aligned_layout
1497 .as_ref()
1498 .is_some_and(|layout| col_index >= layout.outputs.len())
1499 {
1500 return Err(ordinal_resolution_error(
1501 col_index,
1502 ColumnResolutionReason::NotFound,
1503 ));
1504 }
1505
1506 let col_name = match column {
1507 ColumnRef::Name(name) => name.to_string(),
1508 ColumnRef::Index(_) => aligned_layout
1509 .as_ref()
1510 .and_then(|layout| layout.outputs.get(col_index))
1511 .map(|output| output.identifier.name.clone())
1512 .unwrap_or_else(|| format!("_{col_index}")),
1513 };
1514
1515 let mut node = LineageNode::new(&col_name, scope_expr.clone(), scope_expr.clone());
1516 if let Some(scope_type) = scope_type_override {
1517 apply_scope_context_with_type(
1518 &mut node,
1519 scope,
1520 scope_type,
1521 source_name,
1522 reference_node_name,
1523 );
1524 } else {
1525 apply_scope_context(&mut node, scope, source_name, reference_node_name);
1526 }
1527
1528 let mut resolution_failure = None;
1529
1530 for (branch_ordinal, &branch_scope_id) in
1533 context.indexed(scope_id).union_scopes.iter().enumerate()
1534 {
1535 let branch_column = if trace_wildcard_by_name {
1536 match column {
1537 ColumnRef::Name(name) => ColumnRef::Name(name),
1538 ColumnRef::Index(_) => unreachable!("wildcard tracing is name-based"),
1539 }
1540 } else if let Some(layout) = &aligned_layout {
1541 let output = &layout.outputs[col_index];
1542 let branch_index = if branch_ordinal == 0 {
1543 output.left_ordinal
1544 } else {
1545 output.right_ordinal
1546 };
1547 let Some(branch_index) = branch_index else {
1548 continue;
1551 };
1552 ColumnRef::Index(branch_index)
1553 } else {
1554 ColumnRef::Index(col_index)
1555 };
1556
1557 match to_node_inner(
1558 branch_column,
1559 context,
1560 branch_scope_id,
1561 dialect,
1562 scope_name,
1563 "",
1564 "",
1565 trim_selects,
1566 ancestor_cte_scopes,
1567 depth + 1,
1568 ) {
1569 Ok(mut child) => {
1570 child.set_branch = Some(set_branch_metadata(scope_expr, branch_ordinal));
1571 node.downstream.push(child);
1572 }
1573 Err(Error::ColumnResolution { reason, .. }) => {
1574 resolution_failure = Some(merge_resolution_reason(resolution_failure, reason));
1575 }
1576 Err(error) => return Err(error),
1577 }
1578 }
1579
1580 if node.downstream.is_empty() {
1581 if let Some(reason) = resolution_failure {
1582 let target = match column {
1583 ColumnRef::Name(name) => ColumnResolutionTarget::Name {
1584 name: name.to_string(),
1585 },
1586 ColumnRef::Index(ordinal) => ColumnResolutionTarget::Ordinal { ordinal: *ordinal },
1587 };
1588 return Err(column_resolution_error(target, reason));
1589 }
1590 }
1591
1592 Ok(node)
1593}
1594
1595fn merge_resolution_reason(
1596 current: Option<ColumnResolutionReason>,
1597 candidate: ColumnResolutionReason,
1598) -> ColumnResolutionReason {
1599 match (current, candidate) {
1600 (Some(ColumnResolutionReason::Ambiguous), _) | (_, ColumnResolutionReason::Ambiguous) => {
1601 ColumnResolutionReason::Ambiguous
1602 }
1603 (Some(ColumnResolutionReason::Indeterminate), _)
1604 | (_, ColumnResolutionReason::Indeterminate) => ColumnResolutionReason::Indeterminate,
1605 _ => ColumnResolutionReason::NotFound,
1606 }
1607}
1608
1609fn resolve_qualified_column(
1614 node: &mut LineageNode,
1615 context: &LineageScopeContext,
1616 scope_id: ScopeId,
1617 dialect: Option<DialectType>,
1618 table: &str,
1619 col_name: &str,
1620 parent_name: &str,
1621 trim_selects: bool,
1622 all_cte_scopes: &[ScopeId],
1623 depth: usize,
1624) {
1625 let scope = context.scope(scope_id);
1626 let resolved_cte_name = resolve_cte_alias(scope, table);
1629 let effective_table = resolved_cte_name.as_deref().unwrap_or(table);
1630
1631 if let Some(source_info) = scope
1632 .sources
1633 .get(table)
1634 .or_else(|| scope.sources.get(effective_table))
1635 {
1636 match &source_info.expression {
1637 Expression::Pivot(pivot) => {
1638 if attach_pivot_dependencies(
1639 node,
1640 context,
1641 scope_id,
1642 dialect,
1643 pivot,
1644 col_name,
1645 trim_selects,
1646 all_cte_scopes,
1647 depth,
1648 ) {
1649 return;
1650 }
1651 }
1652 Expression::Unpivot(unpivot) => {
1653 if attach_unpivot_dependencies(
1654 node,
1655 context,
1656 scope_id,
1657 dialect,
1658 unpivot,
1659 col_name,
1660 trim_selects,
1661 all_cte_scopes,
1662 depth,
1663 ) {
1664 return;
1665 }
1666 }
1667 _ => {}
1668 }
1669 }
1670
1671 let is_cte = scope.cte_sources.contains_key(effective_table)
1674 || all_cte_scopes.iter().any(
1675 |scope_id| matches!(&context.scope(*scope_id).expression, Expression::Cte(cte) if cte.alias.name == effective_table),
1676 );
1677 if is_cte {
1678 if let Some(child_scope_id) =
1679 find_child_scope_in(context, all_cte_scopes, scope_id, effective_table)
1680 {
1681 if let Ok(child) = to_node_inner(
1682 ColumnRef::Name(col_name),
1683 context,
1684 child_scope_id,
1685 dialect,
1686 parent_name,
1687 effective_table,
1688 parent_name,
1689 trim_selects,
1690 all_cte_scopes,
1691 depth + 1,
1692 ) {
1693 node.downstream.push(child);
1694 return;
1695 }
1696 }
1697
1698 if let Some(source_info) = scope
1699 .sources
1700 .get(table)
1701 .or_else(|| scope.sources.get(effective_table))
1702 .filter(|source_info| source_info.kind == SourceKind::Cte)
1703 {
1704 node.downstream.push(make_table_column_node_from_source(
1705 effective_table,
1706 col_name,
1707 source_info,
1708 ));
1709 return;
1710 }
1711 }
1712
1713 if let Some(source_info) = scope.sources.get(table) {
1715 if source_info.is_scope {
1716 if let Some(child_scope_id) = find_child_scope(context, scope_id, table) {
1717 if let Ok(child) = to_node_inner(
1718 ColumnRef::Name(col_name),
1719 context,
1720 child_scope_id,
1721 dialect,
1722 parent_name,
1723 table,
1724 parent_name,
1725 trim_selects,
1726 all_cte_scopes,
1727 depth + 1,
1728 ) {
1729 node.downstream.push(child);
1730 return;
1731 }
1732 }
1733 }
1734 }
1735
1736 if let Some(source_info) = scope.sources.get(table) {
1739 if !source_info.is_scope {
1740 let mut child = make_table_column_node_from_source(table, col_name, source_info);
1741 if source_info.kind == SourceKind::Virtual {
1742 attach_virtual_source_dependencies(
1743 &mut child,
1744 context,
1745 scope_id,
1746 dialect,
1747 table,
1748 &source_info.expression,
1749 trim_selects,
1750 all_cte_scopes,
1751 depth,
1752 );
1753 }
1754 node.downstream.push(child);
1755 return;
1756 }
1757 }
1758
1759 let mut child = make_table_column_node(table, col_name);
1761 if context.conservative {
1762 child.source_kind = SourceKind::Unknown;
1763 }
1764 node.downstream.push(child);
1765}
1766
1767fn attach_pivot_dependencies(
1768 node: &mut LineageNode,
1769 context: &LineageScopeContext,
1770 scope_id: ScopeId,
1771 dialect: Option<DialectType>,
1772 pivot: &crate::expressions::Pivot,
1773 col_name: &str,
1774 trim_selects: bool,
1775 all_cte_scopes: &[ScopeId],
1776 depth: usize,
1777) -> bool {
1778 if pivot.unpivot {
1779 return false;
1780 }
1781
1782 let scope = context.scope(scope_id);
1783 let mapping = pivot_lineage_column_mapping(pivot, scope, dialect);
1784 let Some(input_columns) = mapping.get(&normalize_column_name(col_name, dialect)) else {
1785 if pivot_implicit_source_column(pivot, col_name) {
1786 let col_ref = SimpleColumnRef {
1787 table: None,
1788 column: col_name.to_string(),
1789 };
1790 attach_pivot_input_column(
1791 node,
1792 context,
1793 scope_id,
1794 dialect,
1795 &pivot.this,
1796 &col_ref,
1797 trim_selects,
1798 all_cte_scopes,
1799 depth,
1800 );
1801 return true;
1802 }
1803 return false;
1804 };
1805
1806 for col_ref in input_columns {
1807 attach_pivot_input_column(
1808 node,
1809 context,
1810 scope_id,
1811 dialect,
1812 &pivot.this,
1813 col_ref,
1814 trim_selects,
1815 all_cte_scopes,
1816 depth,
1817 );
1818 }
1819 true
1820}
1821
1822fn attach_unpivot_dependencies(
1823 node: &mut LineageNode,
1824 context: &LineageScopeContext,
1825 scope_id: ScopeId,
1826 dialect: Option<DialectType>,
1827 unpivot: &crate::expressions::Unpivot,
1828 col_name: &str,
1829 trim_selects: bool,
1830 all_cte_scopes: &[ScopeId],
1831 depth: usize,
1832) -> bool {
1833 let mapping = unpivot_column_mapping(unpivot, dialect);
1834 let Some(input_columns) = mapping.get(&normalize_column_name(col_name, dialect)) else {
1835 return false;
1836 };
1837
1838 for col_ref in input_columns {
1839 attach_pivot_input_column(
1840 node,
1841 context,
1842 scope_id,
1843 dialect,
1844 &unpivot.this,
1845 col_ref,
1846 trim_selects,
1847 all_cte_scopes,
1848 depth,
1849 );
1850 }
1851 true
1852}
1853
1854fn pivot_column_mapping(
1855 pivot: &crate::expressions::Pivot,
1856 dialect: Option<DialectType>,
1857) -> HashMap<String, Vec<SimpleColumnRef>> {
1858 let aggregations = pivot_aggregation_expressions(pivot);
1859 let output_columns = pivot_generated_output_columns(pivot, dialect);
1860 if aggregations.is_empty() || output_columns.is_empty() {
1861 return HashMap::new();
1862 }
1863
1864 let mut mapping = HashMap::new();
1865 for (agg_index, agg) in aggregations.iter().enumerate() {
1866 let input_columns = find_column_refs_in_expr(agg, dialect);
1867 if input_columns.is_empty() {
1868 continue;
1869 }
1870 for col_index in (agg_index..output_columns.len()).step_by(aggregations.len()) {
1871 mapping.insert(
1872 normalize_column_name(&output_columns[col_index], dialect),
1873 input_columns.clone(),
1874 );
1875 }
1876 }
1877 mapping
1878}
1879
1880fn pivot_lineage_column_mapping(
1881 pivot: &crate::expressions::Pivot,
1882 scope: &Scope,
1883 dialect: Option<DialectType>,
1884) -> HashMap<String, Vec<SimpleColumnRef>> {
1885 let mut mapping = pivot_column_mapping(pivot, dialect);
1886 let Some(pre_pivot_columns) = pre_pivot_output_columns(&pivot.this, scope) else {
1887 return mapping;
1888 };
1889
1890 let output_columns = pivot_output_columns(pivot, &pre_pivot_columns, dialect);
1891 if output_columns.is_empty() {
1892 return mapping;
1893 }
1894
1895 let base_mapping = mapping.clone();
1896 for (post_name, pre_name) in output_columns {
1897 let normalized_pre = normalize_column_name(&pre_name, dialect);
1898 let normalized_post = normalize_column_name(&post_name, dialect);
1899
1900 if let Some(input_columns) = base_mapping.get(&normalized_pre) {
1901 mapping.insert(normalized_post, input_columns.clone());
1902 } else {
1903 mapping.insert(
1904 normalized_post,
1905 vec![SimpleColumnRef {
1906 table: None,
1907 column: pre_name,
1908 }],
1909 );
1910 }
1911 }
1912
1913 mapping
1914}
1915
1916fn pre_pivot_output_columns(source: &Expression, scope: &Scope) -> Option<Vec<String>> {
1917 match source {
1918 Expression::Subquery(subquery) => known_output_columns(&subquery.this),
1919 Expression::Table(table) if table.schema.is_none() && table.catalog.is_none() => scope
1920 .cte_sources
1921 .get(&table.name.name)
1922 .and_then(|source| known_output_columns(&source.expression)),
1923 Expression::Paren(paren) => pre_pivot_output_columns(&paren.this, scope),
1924 _ => None,
1925 }
1926}
1927
1928fn known_output_columns(expression: &Expression) -> Option<Vec<String>> {
1929 let expression = match expression {
1930 Expression::Cte(cte) => &cte.this,
1931 Expression::Subquery(subquery) => &subquery.this,
1932 other => other,
1933 };
1934 let columns = crate::ast_transforms::get_output_column_names(expression);
1935 if columns.is_empty() || columns.iter().any(|column| column == "*") {
1936 None
1937 } else {
1938 Some(columns)
1939 }
1940}
1941
1942fn pivot_output_columns(
1943 pivot: &crate::expressions::Pivot,
1944 pre_pivot_columns: &[String],
1945 dialect: Option<DialectType>,
1946) -> Vec<(String, String)> {
1947 let generated_outputs = pivot_generated_output_columns(pivot, dialect);
1948 let excluded = pivot_excluded_source_columns(pivot, dialect);
1949
1950 if excluded.is_empty() || generated_outputs.is_empty() {
1951 return Vec::new();
1952 }
1953
1954 let mut pre_rename: Vec<String> = pre_pivot_columns
1955 .iter()
1956 .filter(|column| !excluded.contains(&normalize_column_name(column, dialect)))
1957 .cloned()
1958 .collect();
1959 pre_rename.extend(generated_outputs);
1960
1961 let post_rename = if pivot.alias_columns.is_empty() {
1962 pre_rename.clone()
1963 } else {
1964 let mut names: Vec<String> = pivot
1965 .alias_columns
1966 .iter()
1967 .map(|column| column.name.clone())
1968 .collect();
1969 names.extend(pre_rename.iter().skip(names.len()).cloned());
1970 names
1971 };
1972
1973 post_rename.into_iter().zip(pre_rename).collect()
1974}
1975
1976fn pivot_excluded_source_columns(
1977 pivot: &crate::expressions::Pivot,
1978 dialect: Option<DialectType>,
1979) -> HashSet<String> {
1980 pivot
1981 .fields
1982 .iter()
1983 .chain(pivot.expressions.iter())
1984 .chain(pivot.using.iter())
1985 .flat_map(|expr| find_column_refs_in_expr(expr, dialect))
1986 .map(|column| normalize_column_name(&column.column, dialect))
1987 .collect()
1988}
1989
1990fn pivot_generated_output_columns(
1991 pivot: &crate::expressions::Pivot,
1992 _dialect: Option<DialectType>,
1993) -> Vec<String> {
1994 let fields = pivot_field_output_names(pivot);
1995 if fields.is_empty() {
1996 return Vec::new();
1997 }
1998
1999 let aggregations = pivot_aggregation_expressions(pivot);
2000 if aggregations.is_empty() {
2001 return Vec::new();
2002 }
2003
2004 let needs_suffix = aggregations.len() > 1;
2005 let mut outputs = Vec::new();
2006 for field in fields {
2007 for aggregation in aggregations {
2008 if let Some(suffix) = pivot_aggregation_output_suffix(aggregation, needs_suffix) {
2009 outputs.push(format!("{field}_{suffix}"));
2010 } else {
2011 outputs.push(field.clone());
2012 }
2013 }
2014 }
2015 outputs
2016}
2017
2018fn pivot_aggregation_expressions(pivot: &crate::expressions::Pivot) -> &[Expression] {
2019 if pivot.using.is_empty() {
2020 &pivot.expressions
2021 } else {
2022 &pivot.using
2023 }
2024}
2025
2026fn pivot_aggregation_output_suffix(expr: &Expression, needs_suffix: bool) -> Option<String> {
2027 match expr {
2028 Expression::Alias(alias) => Some(alias.alias.name.clone()),
2029 _ if needs_suffix => pivot_generated_aggregation_suffix(expr),
2030 _ => None,
2031 }
2032}
2033
2034#[cfg(feature = "generate")]
2035fn pivot_generated_aggregation_suffix(expr: &Expression) -> Option<String> {
2036 Generator::sql(expr).ok().map(|sql| sql.to_lowercase())
2037}
2038
2039#[cfg(not(feature = "generate"))]
2040fn pivot_generated_aggregation_suffix(expr: &Expression) -> Option<String> {
2041 pivot_expr_output_name(expr).or_else(|| Some(expr.variant_name().to_string()))
2042}
2043
2044fn pivot_field_output_names(pivot: &crate::expressions::Pivot) -> Vec<String> {
2045 let mut names = Vec::new();
2046 for field in &pivot.fields {
2047 if let Expression::In(in_expr) = field {
2048 for expr in &in_expr.expressions {
2049 if let Some(name) = pivot_expr_output_name(expr) {
2050 names.push(name);
2051 }
2052 }
2053 }
2054 }
2055 names
2056}
2057
2058fn pivot_expr_output_name(expr: &Expression) -> Option<String> {
2059 match expr {
2060 Expression::PivotAlias(alias) => pivot_expr_output_name(&alias.alias),
2061 Expression::Alias(alias) => Some(alias.alias.name.clone()),
2062 Expression::Identifier(identifier) => Some(identifier.name.clone()),
2063 Expression::Column(column) => Some(column.name.name.clone()),
2064 Expression::Literal(literal) => Some(literal.value_str().to_string()),
2065 Expression::Var(var) => Some(var.this.clone()),
2066 Expression::Tuple(tuple) => tuple.expressions.first().and_then(pivot_expr_output_name),
2067 _ => None,
2068 }
2069}
2070
2071fn pivot_implicit_source_column(pivot: &crate::expressions::Pivot, col_name: &str) -> bool {
2072 let pivot_columns: HashSet<String> = pivot
2073 .fields
2074 .iter()
2075 .filter_map(|field| match field {
2076 Expression::In(in_expr) => Some(&in_expr.this),
2077 _ => None,
2078 })
2079 .flat_map(|expr| find_column_refs_in_expr(expr, None))
2080 .map(|col| col.column.to_lowercase())
2081 .collect();
2082 let aggregation_columns: HashSet<String> = pivot
2083 .expressions
2084 .iter()
2085 .flat_map(|expr| find_column_refs_in_expr(expr, None))
2086 .map(|col| col.column.to_lowercase())
2087 .collect();
2088
2089 let normalized = col_name.to_lowercase();
2090 !pivot_columns.contains(&normalized) && !aggregation_columns.contains(&normalized)
2091}
2092
2093fn unpivot_column_mapping(
2094 unpivot: &crate::expressions::Unpivot,
2095 dialect: Option<DialectType>,
2096) -> HashMap<String, Vec<SimpleColumnRef>> {
2097 let value_columns: Vec<String> = std::iter::once(unpivot.value_column.name.clone())
2098 .chain(
2099 unpivot
2100 .extra_value_columns
2101 .iter()
2102 .map(|column| column.name.clone()),
2103 )
2104 .collect();
2105 let mut all_input_columns = Vec::new();
2106 let mut value_input_columns: Vec<Vec<SimpleColumnRef>> = vec![Vec::new(); value_columns.len()];
2107
2108 for entry in &unpivot.columns {
2109 let columns = unpivot_entry_columns(entry);
2110 all_input_columns.extend(columns.clone());
2111 if columns.len() == value_columns.len() {
2112 for (idx, col_ref) in columns.into_iter().enumerate() {
2113 value_input_columns[idx].push(col_ref);
2114 }
2115 } else {
2116 for inputs in &mut value_input_columns {
2117 inputs.extend(columns.clone());
2118 }
2119 }
2120 }
2121
2122 let mut mapping = HashMap::new();
2123 mapping.insert(
2124 normalize_column_name(&unpivot.name_column.name, dialect),
2125 all_input_columns.clone(),
2126 );
2127 for (idx, value_column) in value_columns.iter().enumerate() {
2128 mapping.insert(
2129 normalize_column_name(value_column, dialect),
2130 value_input_columns.get(idx).cloned().unwrap_or_default(),
2131 );
2132 }
2133 mapping
2134}
2135
2136fn unpivot_entry_columns(expr: &Expression) -> Vec<SimpleColumnRef> {
2137 match expr {
2138 Expression::PivotAlias(alias) => unpivot_entry_columns(&alias.this),
2139 Expression::Tuple(tuple) => tuple
2140 .expressions
2141 .iter()
2142 .flat_map(unpivot_entry_columns)
2143 .collect(),
2144 Expression::Column(column) => vec![SimpleColumnRef {
2145 table: column.table.clone(),
2146 column: column.name.name.clone(),
2147 }],
2148 Expression::Identifier(identifier) => vec![SimpleColumnRef {
2149 table: None,
2150 column: identifier.name.clone(),
2151 }],
2152 _ => find_column_refs_in_expr(expr, None),
2153 }
2154}
2155
2156fn attach_pivot_input_column(
2157 node: &mut LineageNode,
2158 context: &LineageScopeContext,
2159 scope_id: ScopeId,
2160 dialect: Option<DialectType>,
2161 source_expr: &Expression,
2162 col_ref: &SimpleColumnRef,
2163 trim_selects: bool,
2164 all_cte_scopes: &[ScopeId],
2165 depth: usize,
2166) {
2167 let scope = context.scope(scope_id);
2168 match source_expr {
2169 Expression::Table(table) => {
2170 let table_name = col_ref
2171 .table
2172 .as_ref()
2173 .map(|identifier| identifier.name.as_str())
2174 .unwrap_or(table.name.name.as_str());
2175 if scope.cte_sources.contains_key(table_name) {
2176 resolve_qualified_column(
2177 node,
2178 context,
2179 scope_id,
2180 dialect,
2181 table_name,
2182 &col_ref.column,
2183 &node.name.clone(),
2184 trim_selects,
2185 all_cte_scopes,
2186 depth + 1,
2187 );
2188 } else {
2189 let mut source = ScopeSourceInfo::new(
2190 Expression::Table(Box::new(table.as_ref().clone())),
2191 false,
2192 SourceKind::Table,
2193 );
2194 if let Some(alias) = &table.alias {
2195 source = source.with_alias(alias.name.clone());
2196 }
2197 let source_key = table
2198 .alias
2199 .as_ref()
2200 .map(|alias| alias.name.as_str())
2201 .unwrap_or(table.name.name.as_str());
2202 node.downstream.push(make_table_column_node_from_source(
2203 source_key,
2204 &col_ref.column,
2205 &source,
2206 ));
2207 }
2208 }
2209 Expression::Subquery(subquery) => {
2210 let Some(source_scope_id) =
2211 find_derived_scope_for_query(context, scope_id, &subquery.this)
2212 else {
2213 return;
2214 };
2215 let child = if let Some(table) = &col_ref.table {
2216 let mut child_node = LineageNode::new(
2217 &col_ref.column,
2218 subquery.this.clone(),
2219 subquery.this.clone(),
2220 );
2221 resolve_qualified_column(
2222 &mut child_node,
2223 context,
2224 source_scope_id,
2225 dialect,
2226 &table.name,
2227 &col_ref.column,
2228 &node.name.clone(),
2229 trim_selects,
2230 all_cte_scopes,
2231 depth + 1,
2232 );
2233 Ok(child_node)
2234 } else {
2235 to_node_inner(
2236 ColumnRef::Name(&col_ref.column),
2237 context,
2238 source_scope_id,
2239 dialect,
2240 "",
2241 "",
2242 "",
2243 trim_selects,
2244 all_cte_scopes,
2245 depth + 1,
2246 )
2247 };
2248 if let Ok(child) = child {
2249 node.downstream.push(child);
2250 }
2251 }
2252 Expression::Paren(paren) => attach_pivot_input_column(
2253 node,
2254 context,
2255 scope_id,
2256 dialect,
2257 &paren.this,
2258 col_ref,
2259 trim_selects,
2260 all_cte_scopes,
2261 depth,
2262 ),
2263 _ => {
2264 if let Some(table) = &col_ref.table {
2265 resolve_qualified_column(
2266 node,
2267 context,
2268 scope_id,
2269 dialect,
2270 &table.name,
2271 &col_ref.column,
2272 &node.name.clone(),
2273 trim_selects,
2274 all_cte_scopes,
2275 depth + 1,
2276 );
2277 } else {
2278 node.downstream
2279 .push(make_table_column_node("_", &col_ref.column));
2280 }
2281 }
2282 }
2283}
2284
2285fn resolve_cte_alias(scope: &Scope, name: &str) -> Option<String> {
2291 if scope.cte_sources.contains_key(name) {
2293 return None;
2294 }
2295 if let Some(source_info) = scope.sources.get(name) {
2297 if source_info.is_scope {
2298 if let Expression::Cte(cte) = &source_info.expression {
2299 let cte_name = &cte.alias.name;
2300 if scope.cte_sources.contains_key(cte_name) {
2301 return Some(cte_name.clone());
2302 }
2303 }
2304 }
2305 }
2306 None
2307}
2308
2309fn resolve_unqualified_column(
2310 node: &mut LineageNode,
2311 context: &LineageScopeContext,
2312 scope_id: ScopeId,
2313 dialect: Option<DialectType>,
2314 col_name: &str,
2315 parent_name: &str,
2316 trim_selects: bool,
2317 all_cte_scopes: &[ScopeId],
2318 depth: usize,
2319) {
2320 let scope = context.scope(scope_id);
2321 let from_source_names = source_names_from_from_join(scope);
2325
2326 if let Some(tbl) = unique_virtual_source_for_column(scope, &from_source_names, col_name) {
2327 resolve_qualified_column(
2328 node,
2329 context,
2330 scope_id,
2331 dialect,
2332 &tbl,
2333 col_name,
2334 parent_name,
2335 trim_selects,
2336 all_cte_scopes,
2337 depth,
2338 );
2339 return;
2340 }
2341
2342 if from_source_names.len() == 1 {
2343 let tbl = &from_source_names[0];
2344 resolve_qualified_column(
2345 node,
2346 context,
2347 scope_id,
2348 dialect,
2349 tbl,
2350 col_name,
2351 parent_name,
2352 trim_selects,
2353 all_cte_scopes,
2354 depth,
2355 );
2356 return;
2357 }
2358
2359 let child = LineageNode::new(
2361 col_name.to_string(),
2362 Expression::Column(Box::new(crate::expressions::Column {
2363 name: crate::expressions::Identifier::new(col_name.to_string()),
2364 table: None,
2365 join_mark: false,
2366 trailing_comments: vec![],
2367 span: None,
2368 inferred_type: None,
2369 })),
2370 node.source.clone(),
2371 );
2372 node.downstream.push(child);
2373}
2374
2375fn unique_virtual_source_for_column(
2376 scope: &Scope,
2377 source_names: &[String],
2378 col_name: &str,
2379) -> Option<String> {
2380 let mut matches = source_names.iter().filter_map(|source_name| {
2381 let source = scope.sources.get(source_name)?;
2382 if source.kind == SourceKind::Virtual
2383 && virtual_source_output_columns(source)
2384 .any(|column| column.eq_ignore_ascii_case(col_name))
2385 {
2386 Some(source_name.clone())
2387 } else {
2388 None
2389 }
2390 });
2391
2392 let first = matches.next()?;
2393 if matches.next().is_none() {
2394 Some(first)
2395 } else {
2396 None
2397 }
2398}
2399
2400fn virtual_source_output_columns(
2401 source_info: &ScopeSourceInfo,
2402) -> Box<dyn Iterator<Item = String> + '_> {
2403 match &source_info.expression {
2404 Expression::Unnest(unnest) => Box::new(unnest_output_columns(unnest)),
2405 Expression::Alias(alias) if matches!(&alias.this, Expression::Unnest(_)) => {
2406 Box::new(alias_output_columns(alias))
2407 }
2408 Expression::Lateral(lateral) => Box::new(lateral_output_columns(lateral)),
2409 Expression::LateralView(lateral_view) => {
2410 Box::new(lateral_view_output_columns(lateral_view))
2411 }
2412 _ => Box::new(source_info.alias.clone().into_iter()),
2413 }
2414}
2415
2416fn unnest_output_types(unnest: &crate::expressions::UnnestFunc) -> Vec<DataType> {
2417 let element_type = |expression: &Expression| match expression.inferred_type() {
2418 Some(DataType::Array { element_type, .. }) => (**element_type).clone(),
2419 _ => DataType::Unknown,
2420 };
2421
2422 let mut types = vec![unnest
2423 .inferred_type
2424 .clone()
2425 .unwrap_or_else(|| element_type(&unnest.this))];
2426 types.extend(unnest.expressions.iter().map(element_type));
2427 if unnest.with_ordinality || unnest.offset_alias.is_some() {
2428 types.push(DataType::BigInt { length: None });
2429 }
2430 types
2431}
2432
2433fn virtual_source_column_type(source_info: &ScopeSourceInfo, column: &str) -> Option<DataType> {
2434 let find_type = |names: Vec<String>, types: Vec<DataType>| {
2435 names
2436 .iter()
2437 .position(|name| name.eq_ignore_ascii_case(column))
2438 .and_then(|index| types.get(index).cloned())
2439 };
2440
2441 match &source_info.expression {
2442 Expression::Unnest(unnest) => find_type(
2443 unnest_output_columns(unnest).collect(),
2444 unnest_output_types(unnest),
2445 ),
2446 Expression::Alias(alias) => match &alias.this {
2447 Expression::Unnest(unnest) => find_type(
2448 alias_output_columns(alias).collect(),
2449 unnest_output_types(unnest),
2450 ),
2451 _ => None,
2452 },
2453 Expression::Lateral(lateral) => match lateral.this.as_ref() {
2454 Expression::Unnest(unnest) => find_type(
2455 lateral_output_columns(lateral).collect(),
2456 unnest_output_types(unnest),
2457 ),
2458 _ => None,
2459 },
2460 _ => None,
2461 }
2462}
2463
2464fn unnest_output_columns(
2465 unnest: &crate::expressions::UnnestFunc,
2466) -> impl Iterator<Item = String> + '_ {
2467 unnest
2468 .alias
2469 .iter()
2470 .map(|alias| alias.name.clone())
2471 .chain(unnest.offset_alias.iter().map(|alias| alias.name.clone()))
2472}
2473
2474fn alias_output_columns(
2475 alias: &crate::expressions::Alias,
2476) -> Box<dyn Iterator<Item = String> + '_> {
2477 if alias.column_aliases.is_empty() {
2478 Box::new(std::iter::once(alias.alias.name.clone()))
2479 } else {
2480 Box::new(
2481 alias
2482 .column_aliases
2483 .iter()
2484 .map(|column| column.name.clone()),
2485 )
2486 }
2487}
2488
2489fn lateral_output_columns(
2490 lateral: &crate::expressions::Lateral,
2491) -> Box<dyn Iterator<Item = String> + '_> {
2492 if lateral.column_aliases.is_empty() {
2493 default_virtual_output_columns(&lateral.this)
2494 } else {
2495 Box::new(lateral.column_aliases.iter().cloned())
2496 }
2497}
2498
2499fn lateral_view_output_columns(
2500 lateral_view: &crate::expressions::LateralView,
2501) -> Box<dyn Iterator<Item = String> + '_> {
2502 Box::new(
2503 lateral_view
2504 .column_aliases
2505 .iter()
2506 .map(|column| column.name.clone()),
2507 )
2508}
2509
2510fn default_virtual_output_columns(expr: &Expression) -> Box<dyn Iterator<Item = String> + '_> {
2511 match expr {
2512 Expression::Unnest(unnest) => Box::new(unnest_output_columns(unnest)),
2513 Expression::Alias(alias) if matches!(&alias.this, Expression::Unnest(_)) => {
2514 alias_output_columns(alias)
2515 }
2516 Expression::Function(function) if function.name.eq_ignore_ascii_case("FLATTEN") => {
2517 Box::new(
2518 ["seq", "key", "path", "index", "value", "this"]
2519 .into_iter()
2520 .map(String::from),
2521 )
2522 }
2523 _ => Box::new(std::iter::empty()),
2524 }
2525}
2526
2527fn attach_virtual_source_dependencies(
2528 node: &mut LineageNode,
2529 context: &LineageScopeContext,
2530 scope_id: ScopeId,
2531 dialect: Option<DialectType>,
2532 source_alias: &str,
2533 source_expr: &Expression,
2534 trim_selects: bool,
2535 all_cte_scopes: &[ScopeId],
2536 depth: usize,
2537) {
2538 let scope = context.scope(scope_id);
2539 let parent_name = node.name.clone();
2540 let mut seen = HashSet::new();
2541 for col_ref in find_column_refs_in_expr(source_expr, dialect) {
2542 let key = (
2543 col_ref.table.as_ref().map(|t| t.name.clone()),
2544 col_ref.column.clone(),
2545 );
2546 if !seen.insert(key) {
2547 continue;
2548 }
2549
2550 if let Some(table_id) = col_ref.table {
2551 let table = table_id.name;
2552 if table == source_alias {
2553 continue;
2554 }
2555 resolve_qualified_column(
2556 node,
2557 context,
2558 scope_id,
2559 dialect,
2560 &table,
2561 &col_ref.column,
2562 &parent_name,
2563 trim_selects,
2564 all_cte_scopes,
2565 depth + 1,
2566 );
2567 } else {
2568 let non_virtual_sources = non_virtual_source_names_from_from_join(scope);
2569 if non_virtual_sources.len() == 1 {
2570 resolve_qualified_column(
2571 node,
2572 context,
2573 scope_id,
2574 dialect,
2575 &non_virtual_sources[0],
2576 &col_ref.column,
2577 &parent_name,
2578 trim_selects,
2579 all_cte_scopes,
2580 depth + 1,
2581 );
2582 }
2583 }
2584 }
2585}
2586
2587fn source_names_from_from_join(scope: &Scope) -> Vec<String> {
2588 fn source_name(expr: &Expression) -> Option<String> {
2589 match expr {
2590 Expression::Table(table) => Some(
2591 table
2592 .alias
2593 .as_ref()
2594 .map(|a| a.name.clone())
2595 .unwrap_or_else(|| table.name.name.clone()),
2596 ),
2597 Expression::Subquery(subquery) => {
2598 subquery.alias.as_ref().map(|alias| alias.name.clone())
2599 }
2600 Expression::Unnest(unnest) => unnest.alias.as_ref().map(|alias| alias.name.clone()),
2601 Expression::Alias(alias) if matches!(&alias.this, Expression::Unnest(_)) => {
2602 Some(alias.alias.name.clone())
2603 }
2604 Expression::Alias(alias) if is_query_like_relation(&alias.this) => {
2605 Some(alias.alias.name.clone())
2606 }
2607 Expression::Lateral(lateral) => lateral.alias.clone(),
2608 Expression::LateralView(lateral_view) => lateral_view
2609 .table_alias
2610 .as_ref()
2611 .or_else(|| lateral_view.column_aliases.first())
2612 .map(|alias| alias.name.clone()),
2613 Expression::Pivot(pivot) => Some(pivot_lineage_source_name(
2614 &pivot.this,
2615 pivot.alias.as_ref().map(|alias| alias.name.as_str()),
2616 )),
2617 Expression::Unpivot(unpivot) => Some(pivot_lineage_source_name(
2618 &unpivot.this,
2619 unpivot.alias.as_ref().map(|alias| alias.name.as_str()),
2620 )),
2621 Expression::Paren(paren) => source_name(&paren.this),
2622 _ => None,
2623 }
2624 }
2625
2626 let effective_expr = match &scope.expression {
2627 Expression::Cte(cte) => &cte.this,
2628 expr => expr,
2629 };
2630
2631 let mut names = Vec::new();
2632 let mut seen = std::collections::HashSet::new();
2633
2634 if let Expression::Select(select) = effective_expr {
2635 if let Some(from) = &select.from {
2636 for expr in &from.expressions {
2637 if let Some(name) = source_name(expr) {
2638 if !name.is_empty() && seen.insert(name.clone()) {
2639 names.push(name);
2640 }
2641 }
2642 }
2643 }
2644 for join in &select.joins {
2645 if is_semi_or_anti_join_kind(join.kind) {
2646 continue;
2647 }
2648 if let Some(name) = source_name(&join.this) {
2649 if !name.is_empty() && seen.insert(name.clone()) {
2650 names.push(name);
2651 }
2652 }
2653 }
2654 for lateral_view in &select.lateral_views {
2655 if let Some(name) =
2656 source_name(&Expression::LateralView(Box::new(lateral_view.clone())))
2657 {
2658 if !name.is_empty() && seen.insert(name.clone()) {
2659 names.push(name);
2660 }
2661 }
2662 }
2663 }
2664
2665 names
2666}
2667
2668fn is_semi_or_anti_join_kind(kind: JoinKind) -> bool {
2669 matches!(
2670 kind,
2671 JoinKind::Semi
2672 | JoinKind::Anti
2673 | JoinKind::LeftSemi
2674 | JoinKind::LeftAnti
2675 | JoinKind::RightSemi
2676 | JoinKind::RightAnti
2677 )
2678}
2679
2680fn is_query_like_relation(expr: &Expression) -> bool {
2681 match expr {
2682 Expression::Select(_)
2683 | Expression::Subquery(_)
2684 | Expression::Union(_)
2685 | Expression::Intersect(_)
2686 | Expression::Except(_) => true,
2687 Expression::Paren(paren) => is_query_like_relation(&paren.this),
2688 _ => false,
2689 }
2690}
2691
2692fn derived_source_query(expr: &Expression) -> Option<&Expression> {
2693 match expr {
2694 Expression::Subquery(subquery) => Some(&subquery.this),
2695 Expression::Alias(alias) if is_query_like_relation(&alias.this) => Some(&alias.this),
2696 Expression::Select(_)
2697 | Expression::Union(_)
2698 | Expression::Intersect(_)
2699 | Expression::Except(_) => Some(expr),
2700 Expression::Paren(paren) => derived_source_query(&paren.this),
2701 _ => None,
2702 }
2703}
2704
2705fn expressions_equivalent_after_wrappers(left: &Expression, right: &Expression) -> bool {
2706 left == right || effective_scope_expression(left) == effective_scope_expression(right)
2707}
2708
2709fn non_virtual_source_names_from_from_join(scope: &Scope) -> Vec<String> {
2710 source_names_from_from_join(scope)
2711 .into_iter()
2712 .filter(|name| {
2713 !matches!(
2714 scope.sources.get(name).map(|source| source.kind),
2715 Some(SourceKind::Virtual)
2716 )
2717 })
2718 .collect()
2719}
2720
2721#[derive(Debug, Clone)]
2726struct OutputLayoutEntry {
2727 column: OutputColumn,
2728 projection_index: usize,
2729}
2730
2731fn query_output_from_expression(
2732 expression: &Expression,
2733 dialect: Option<DialectType>,
2734) -> Result<QueryOutput> {
2735 match crate::set_operation::set_operation_layout(expression, dialect) {
2736 Ok(Some(layout)) => {
2737 return Ok(QueryOutput {
2738 columns: layout
2739 .outputs
2740 .into_iter()
2741 .enumerate()
2742 .map(|(ordinal, output)| OutputColumn::Named {
2743 name: output.identifier.name,
2744 ordinal: Some(ordinal),
2745 })
2746 .collect(),
2747 ordinal_complete: true,
2748 })
2749 }
2750 Ok(None) => {}
2751 Err(error) if error.is_indeterminate() => {}
2752 Err(error) => return Err(Error::invalid_input(error.to_string())),
2753 }
2754
2755 let select = leftmost_output_select(expression).ok_or_else(|| {
2756 Error::invalid_input("output_columns requires a SELECT or set-operation query")
2757 })?;
2758 let entries = output_layout(select);
2759 let ordinal_complete = !entries
2760 .iter()
2761 .any(|entry| matches!(entry.column, OutputColumn::Wildcard { .. }));
2762
2763 Ok(QueryOutput {
2764 columns: entries.into_iter().map(|entry| entry.column).collect(),
2765 ordinal_complete,
2766 })
2767}
2768
2769fn leftmost_output_select(expression: &Expression) -> Option<&Select> {
2770 match expression {
2771 Expression::Select(select) => Some(select),
2772 Expression::Union(set_op) => leftmost_output_select(&set_op.left),
2773 Expression::Intersect(set_op) => leftmost_output_select(&set_op.left),
2774 Expression::Except(set_op) => leftmost_output_select(&set_op.left),
2775 Expression::Subquery(subquery) => leftmost_output_select(&subquery.this),
2776 Expression::Cte(cte) => leftmost_output_select(&cte.this),
2777 Expression::Paren(paren) => leftmost_output_select(&paren.this),
2778 _ => None,
2779 }
2780}
2781
2782fn output_layout(select: &Select) -> Vec<OutputLayoutEntry> {
2783 let mut entries = Vec::new();
2784 let mut next_ordinal = Some(0usize);
2785
2786 for (projection_index, projection) in select.expressions.iter().enumerate() {
2787 let projection = unwrap_output_annotation(projection);
2788
2789 if let Some(qualifier) = output_wildcard_qualifier(projection) {
2790 entries.push(OutputLayoutEntry {
2791 column: OutputColumn::Wildcard {
2792 qualifier,
2793 start_ordinal: next_ordinal,
2794 },
2795 projection_index,
2796 });
2797 next_ordinal = None;
2798 continue;
2799 }
2800
2801 if let Expression::Aliases(aliases) = projection {
2802 if !aliases.expressions.is_empty() {
2803 for alias in &aliases.expressions {
2804 let ordinal = take_output_ordinal(&mut next_ordinal);
2805 let column = get_alias_or_name(alias)
2806 .map(|name| OutputColumn::Named { name, ordinal })
2807 .unwrap_or(OutputColumn::Unnamed { ordinal });
2808 entries.push(OutputLayoutEntry {
2809 column,
2810 projection_index,
2811 });
2812 }
2813 continue;
2814 }
2815 }
2816
2817 let ordinal = take_output_ordinal(&mut next_ordinal);
2818 let column = get_alias_or_name(projection)
2819 .map(|name| OutputColumn::Named { name, ordinal })
2820 .unwrap_or(OutputColumn::Unnamed { ordinal });
2821 entries.push(OutputLayoutEntry {
2822 column,
2823 projection_index,
2824 });
2825 }
2826
2827 entries
2828}
2829
2830fn take_output_ordinal(next_ordinal: &mut Option<usize>) -> Option<usize> {
2831 let ordinal = *next_ordinal;
2832 if let Some(value) = ordinal {
2833 *next_ordinal = Some(value + 1);
2834 }
2835 ordinal
2836}
2837
2838fn unwrap_output_annotation(mut expression: &Expression) -> &Expression {
2839 while let Expression::Annotated(annotated) = expression {
2840 expression = &annotated.this;
2841 }
2842 expression
2843}
2844
2845fn output_wildcard_qualifier(expression: &Expression) -> Option<Option<String>> {
2847 match expression {
2848 Expression::Star(star) => Some(star.table.as_ref().map(|table| table.name.clone())),
2849 Expression::Column(column) if column.name.name == "*" => {
2850 Some(column.table.as_ref().map(|table| table.name.clone()))
2851 }
2852 _ => None,
2853 }
2854}
2855
2856fn column_resolution_error(
2857 target: ColumnResolutionTarget,
2858 reason: ColumnResolutionReason,
2859) -> Error {
2860 Error::column_resolution(target, reason)
2861}
2862
2863fn name_resolution_error(name: &str, reason: ColumnResolutionReason) -> Error {
2864 column_resolution_error(
2865 ColumnResolutionTarget::Name {
2866 name: name.to_string(),
2867 },
2868 reason,
2869 )
2870}
2871
2872fn ordinal_resolution_error(ordinal: usize, reason: ColumnResolutionReason) -> Error {
2873 column_resolution_error(ColumnResolutionTarget::Ordinal { ordinal }, reason)
2874}
2875
2876fn find_select_expr_by_name(
2877 select: &Select,
2878 name: &str,
2879 dialect: Option<DialectType>,
2880) -> Result<Expression> {
2881 let normalized_name = normalize_column_name(name, dialect);
2882 let layout = output_layout(select);
2883 let mut matches = Vec::new();
2884
2885 for entry in &layout {
2886 let is_match = match &entry.column {
2887 OutputColumn::Named {
2888 name: output_name, ..
2889 } => normalize_column_name(output_name, dialect) == normalized_name,
2890 OutputColumn::Wildcard { .. } => normalized_name == "*",
2891 OutputColumn::Unnamed { .. } => false,
2892 };
2893 if is_match {
2894 matches.push(entry.projection_index);
2895 }
2896 }
2897 match matches.as_slice() {
2898 [projection_index] => return Ok(select.expressions[*projection_index].clone()),
2899 [_, ..] => {
2900 return Err(name_resolution_error(
2901 name,
2902 ColumnResolutionReason::Ambiguous,
2903 ))
2904 }
2905 [] => {}
2906 }
2907
2908 if let Some(expression) = synthesize_star_passthrough_expr(select, name) {
2909 return Ok(expression);
2910 }
2911
2912 let reason = if layout
2913 .iter()
2914 .any(|entry| matches!(entry.column, OutputColumn::Wildcard { .. }))
2915 {
2916 ColumnResolutionReason::Indeterminate
2917 } else {
2918 ColumnResolutionReason::NotFound
2919 };
2920 Err(name_resolution_error(name, reason))
2921}
2922
2923fn find_select_expr_by_ordinal(select: &Select, ordinal: usize) -> Result<Expression> {
2924 let layout = output_layout(select);
2925
2926 for entry in &layout {
2927 match &entry.column {
2928 OutputColumn::Named {
2929 ordinal: Some(candidate),
2930 ..
2931 }
2932 | OutputColumn::Unnamed {
2933 ordinal: Some(candidate),
2934 } if *candidate == ordinal => {
2935 return Ok(select.expressions[entry.projection_index].clone())
2936 }
2937 OutputColumn::Wildcard { start_ordinal, .. } => {
2938 if match start_ordinal {
2939 Some(start) => ordinal >= *start,
2940 None => true,
2941 } {
2942 return Err(ordinal_resolution_error(
2943 ordinal,
2944 ColumnResolutionReason::Indeterminate,
2945 ));
2946 }
2947 }
2948 _ => {}
2949 }
2950 }
2951
2952 Err(ordinal_resolution_error(
2953 ordinal,
2954 ColumnResolutionReason::NotFound,
2955 ))
2956}
2957
2958fn output_name_to_ordinal(
2959 expression: &Expression,
2960 name: &str,
2961 dialect: Option<DialectType>,
2962) -> Result<usize> {
2963 match crate::set_operation::set_operation_layout(expression, dialect) {
2964 Ok(Some(layout)) => {
2965 let lookup = Identifier::new(name);
2966 let lookup_key = crate::set_operation::identifier_key(&lookup, dialect);
2967 let matches: Vec<_> = layout
2968 .outputs
2969 .iter()
2970 .enumerate()
2971 .filter(|(_, output)| {
2972 (output.identifier.quoted && output.identifier.name == name)
2973 || crate::set_operation::identifier_key(&output.identifier, dialect)
2974 == lookup_key
2975 })
2976 .map(|(ordinal, _)| ordinal)
2977 .collect();
2978
2979 return match matches.as_slice() {
2980 [ordinal] => Ok(*ordinal),
2981 [_, ..] => Err(name_resolution_error(
2982 name,
2983 ColumnResolutionReason::Ambiguous,
2984 )),
2985 [] => Err(name_resolution_error(
2986 name,
2987 ColumnResolutionReason::NotFound,
2988 )),
2989 };
2990 }
2991 Ok(None) => {}
2992 Err(error) if error.is_indeterminate() => {}
2993 Err(error) => return Err(Error::invalid_input(error.to_string())),
2994 }
2995
2996 let select = leftmost_output_select(expression).ok_or_else(|| {
2997 Error::invalid_input("column resolution requires a SELECT or set-operation query")
2998 })?;
2999 let normalized_name = normalize_column_name(name, dialect);
3000 let layout = output_layout(select);
3001 let mut matches = Vec::new();
3002
3003 for entry in &layout {
3004 if let OutputColumn::Named {
3005 name: output_name,
3006 ordinal,
3007 } = &entry.column
3008 {
3009 if normalize_column_name(output_name, dialect) == normalized_name {
3010 matches.push(*ordinal);
3011 }
3012 }
3013 }
3014
3015 if matches.len() > 1 {
3016 return Err(name_resolution_error(
3017 name,
3018 ColumnResolutionReason::Ambiguous,
3019 ));
3020 }
3021 if let Some(ordinal) = matches.into_iter().next() {
3022 return ordinal
3023 .ok_or_else(|| name_resolution_error(name, ColumnResolutionReason::Indeterminate));
3024 }
3025
3026 let reason = if layout
3027 .iter()
3028 .any(|entry| matches!(entry.column, OutputColumn::Wildcard { .. }))
3029 {
3030 ColumnResolutionReason::Indeterminate
3031 } else {
3032 ColumnResolutionReason::NotFound
3033 };
3034 Err(name_resolution_error(name, reason))
3035}
3036
3037fn get_alias_or_name(expr: &Expression) -> Option<String> {
3039 match expr {
3040 Expression::Alias(alias) => Some(alias.alias.name.clone()),
3041 Expression::Column(col) => Some(col.name.name.clone()),
3042 Expression::Identifier(id) => Some(id.name.clone()),
3043 Expression::Star(_) => Some("*".to_string()),
3044 Expression::Annotated(a) => get_alias_or_name(&a.this),
3047 _ => None,
3048 }
3049}
3050
3051fn find_prior_select_alias_expr(
3052 scope_expr: &Expression,
3053 target_expr: &Expression,
3054 alias_name: &str,
3055 dialect: Option<DialectType>,
3056) -> Option<Expression> {
3057 let Expression::Select(select) = scope_expr else {
3058 return None;
3059 };
3060
3061 let normalized_alias = normalize_column_name(alias_name, dialect);
3062 for expr in &select.expressions {
3063 if expr == target_expr {
3064 return None;
3065 }
3066
3067 if let Expression::Alias(alias) = expr {
3068 if normalize_column_name(&alias.alias.name, dialect) == normalized_alias {
3069 return Some(alias.this.clone());
3070 }
3071 }
3072 }
3073
3074 None
3075}
3076
3077fn resolve_column_name(column: &ColumnRef<'_>, select_expr: &Expression) -> String {
3079 match column {
3080 ColumnRef::Name(n) => n.to_string(),
3081 ColumnRef::Index(_) => get_alias_or_name(select_expr).unwrap_or_else(|| "?".to_string()),
3082 }
3083}
3084
3085fn find_select_expr(
3087 scope_expr: &Expression,
3088 column: &ColumnRef<'_>,
3089 dialect: Option<DialectType>,
3090) -> Result<Expression> {
3091 if let Expression::Select(ref select) = scope_expr {
3092 match column {
3093 ColumnRef::Name(name) => find_select_expr_by_name(select, name, dialect),
3094 ColumnRef::Index(ordinal) => find_select_expr_by_ordinal(select, *ordinal),
3095 }
3096 } else {
3097 Err(Error::invalid_input(
3098 "column resolution requires a SELECT expression",
3099 ))
3100 }
3101}
3102
3103fn synthesize_star_passthrough_expr(select: &Select, name: &str) -> Option<Expression> {
3104 let sources = get_select_sources(select);
3105 if sources.is_empty() {
3106 return None;
3107 }
3108
3109 let mut candidate_aliases = Vec::new();
3110 let mut seen = HashSet::new();
3111
3112 for expr in &select.expressions {
3113 let aliases = match star_passthrough_source_aliases(expr, &sources) {
3114 StarPassthroughSources::None => continue,
3115 StarPassthroughSources::Ambiguous => return None,
3116 StarPassthroughSources::Aliases(aliases) => aliases,
3117 };
3118
3119 for alias in aliases {
3120 if seen.insert(alias.clone()) {
3121 candidate_aliases.push(alias);
3122 }
3123 }
3124 }
3125
3126 match candidate_aliases.as_slice() {
3127 [alias] => {
3128 let table = Identifier::new(alias.clone());
3129 Some(make_column_expr(name, Some(&table)))
3130 }
3131 _ => None,
3132 }
3133}
3134
3135enum StarPassthroughSources {
3136 None,
3137 Ambiguous,
3138 Aliases(Vec<String>),
3139}
3140
3141fn star_passthrough_source_aliases(
3142 expr: &Expression,
3143 sources: &[SourceInfo],
3144) -> StarPassthroughSources {
3145 match expr {
3146 Expression::Star(star) => star_source_aliases(star.table.as_ref(), sources),
3147 Expression::Column(column) if column.name.name == "*" => {
3148 star_source_aliases(column.table.as_ref(), sources)
3149 }
3150 Expression::Annotated(annotated) => {
3151 star_passthrough_source_aliases(&annotated.this, sources)
3152 }
3153 _ => StarPassthroughSources::None,
3154 }
3155}
3156
3157fn star_source_aliases(
3158 qualifier: Option<&Identifier>,
3159 sources: &[SourceInfo],
3160) -> StarPassthroughSources {
3161 if let Some(qualifier) = qualifier {
3162 let mut aliases = Vec::new();
3163
3164 for source in sources {
3165 if source_matches_star_qualifier(source, qualifier) {
3166 aliases.push(source.alias.clone());
3167 }
3168 }
3169
3170 return match aliases.len() {
3171 0 => StarPassthroughSources::None,
3172 1 => StarPassthroughSources::Aliases(aliases),
3173 _ => StarPassthroughSources::Ambiguous,
3174 };
3175 }
3176
3177 match sources {
3178 [source] if source.quoted => StarPassthroughSources::None,
3182 [source] => StarPassthroughSources::Aliases(vec![source.alias.clone()]),
3183 [] => StarPassthroughSources::None,
3184 _ => StarPassthroughSources::Ambiguous,
3185 }
3186}
3187
3188fn source_matches_star_qualifier(source: &SourceInfo, qualifier: &Identifier) -> bool {
3189 if source.normalized == normalize_cte_name(qualifier) {
3190 return true;
3191 }
3192
3193 if qualifier.quoted {
3194 source.alias == qualifier.name
3195 } else {
3196 source.alias.eq_ignore_ascii_case(&qualifier.name)
3197 }
3198}
3199
3200fn column_to_index(
3202 set_op_expr: &Expression,
3203 name: &str,
3204 dialect: Option<DialectType>,
3205) -> Result<usize> {
3206 output_name_to_ordinal(set_op_expr, name, dialect)
3207}
3208
3209fn normalize_column_name(name: &str, dialect: Option<DialectType>) -> String {
3210 normalize_name(name, dialect, false, true)
3211}
3212
3213fn trim_source(select_expr: &Expression, target_expr: &Expression) -> Expression {
3215 if let Expression::Select(select) = select_expr {
3216 let mut trimmed = select.as_ref().clone();
3217 trimmed.expressions = vec![target_expr.clone()];
3218 Expression::Select(Box::new(trimmed))
3219 } else {
3220 select_expr.clone()
3221 }
3222}
3223
3224fn find_child_scope(
3226 context: &LineageScopeContext,
3227 scope_id: ScopeId,
3228 source_name: &str,
3229) -> Option<ScopeId> {
3230 let indexed = context.indexed(scope_id);
3231 let scope = &indexed.scope;
3232
3233 if scope.cte_sources.contains_key(source_name) {
3235 for &cte_scope_id in &indexed.cte_scopes {
3236 let cte_scope = context.scope(cte_scope_id);
3237 if let Expression::Cte(cte) = &cte_scope.expression {
3238 if cte.alias.name == source_name {
3239 return Some(cte_scope_id);
3240 }
3241 }
3242 }
3243 }
3244
3245 if let Some(source_info) = scope.sources.get(source_name) {
3247 if source_info.is_scope && !scope.cte_sources.contains_key(source_name) {
3248 if let Some(query) = derived_source_query(&source_info.expression) {
3249 for &dt_scope_id in &indexed.derived_table_scopes {
3250 let dt_scope = context.scope(dt_scope_id);
3251 if expressions_equivalent_after_wrappers(&dt_scope.expression, query) {
3252 return Some(dt_scope_id);
3253 }
3254 }
3255 }
3256 }
3257 }
3258
3259 None
3260}
3261
3262fn find_child_scope_in(
3266 context: &LineageScopeContext,
3267 all_cte_scopes: &[ScopeId],
3268 scope_id: ScopeId,
3269 source_name: &str,
3270) -> Option<ScopeId> {
3271 let indexed = context.indexed(scope_id);
3272 let scope = &indexed.scope;
3273
3274 for &cte_scope_id in &indexed.cte_scopes {
3276 let cte_scope = context.scope(cte_scope_id);
3277 if let Expression::Cte(cte) = &cte_scope.expression {
3278 if cte.alias.name == source_name {
3279 return Some(cte_scope_id);
3280 }
3281 }
3282 }
3283
3284 for &cte_scope_id in all_cte_scopes {
3286 let cte_scope = context.scope(cte_scope_id);
3287 if let Expression::Cte(cte) = &cte_scope.expression {
3288 if cte.alias.name == source_name {
3289 return Some(cte_scope_id);
3290 }
3291 }
3292 }
3293
3294 if let Some(source_info) = scope.sources.get(source_name) {
3296 if source_info.is_scope {
3297 if let Some(query) = derived_source_query(&source_info.expression) {
3298 for &dt_scope_id in &indexed.derived_table_scopes {
3299 let dt_scope = context.scope(dt_scope_id);
3300 if expressions_equivalent_after_wrappers(&dt_scope.expression, query) {
3301 return Some(dt_scope_id);
3302 }
3303 }
3304 }
3305 }
3306 }
3307
3308 None
3309}
3310
3311fn find_derived_scope_for_query(
3312 context: &LineageScopeContext,
3313 scope_id: ScopeId,
3314 query: &Expression,
3315) -> Option<ScopeId> {
3316 context
3317 .indexed(scope_id)
3318 .derived_table_scopes
3319 .iter()
3320 .copied()
3321 .find(|derived_scope_id| {
3322 expressions_equivalent_after_wrappers(
3323 &context.scope(*derived_scope_id).expression,
3324 query,
3325 )
3326 })
3327}
3328
3329fn make_table_column_node(table: &str, column: &str) -> LineageNode {
3331 let mut node = LineageNode::new(
3332 format!("{}.{}", table, column),
3333 Expression::Column(Box::new(crate::expressions::Column {
3334 name: crate::expressions::Identifier::new(column.to_string()),
3335 table: Some(crate::expressions::Identifier::new(table.to_string())),
3336 join_mark: false,
3337 trailing_comments: vec![],
3338 span: None,
3339 inferred_type: None,
3340 })),
3341 Expression::Table(Box::new(crate::expressions::TableRef::new(table))),
3342 );
3343 node.source_name = table.to_string();
3344 node.source_kind = SourceKind::Table;
3345 node
3346}
3347
3348fn table_name_from_table_ref(table_ref: &crate::expressions::TableRef) -> String {
3349 let mut parts: Vec<String> = Vec::new();
3350 if let Some(catalog) = &table_ref.catalog {
3351 parts.push(catalog.name.clone());
3352 }
3353 if let Some(schema) = &table_ref.schema {
3354 parts.push(schema.name.clone());
3355 }
3356 parts.push(table_ref.name.name.clone());
3357 parts.join(".")
3358}
3359
3360fn apply_source_info_context(
3361 node: &mut LineageNode,
3362 source_key: &str,
3363 source_info: &ScopeSourceInfo,
3364) {
3365 node.source_kind = source_info.kind;
3366 node.source_name =
3367 source_info
3368 .lineage_name
3369 .clone()
3370 .unwrap_or_else(|| match &source_info.expression {
3371 Expression::Table(table_ref) => table_name_from_table_ref(table_ref),
3372 _ => source_key.to_string(),
3373 });
3374 node.source_alias = source_info.alias.clone();
3375}
3376
3377fn make_table_column_node_from_source(
3378 source_key: &str,
3379 column: &str,
3380 source_info: &ScopeSourceInfo,
3381) -> LineageNode {
3382 let lineage_name = source_info.lineage_name.as_deref().unwrap_or(source_key);
3383 let inferred_type = (source_info.kind == SourceKind::Virtual)
3384 .then(|| virtual_source_column_type(source_info, column))
3385 .flatten();
3386 let mut node = LineageNode::new(
3387 format!("{}.{}", lineage_name, column),
3388 Expression::Column(Box::new(crate::expressions::Column {
3389 name: crate::expressions::Identifier::new(column.to_string()),
3390 table: Some(crate::expressions::Identifier::new(
3391 lineage_name.to_string(),
3392 )),
3393 join_mark: false,
3394 trailing_comments: vec![],
3395 span: None,
3396 inferred_type,
3397 })),
3398 source_info.expression.clone(),
3399 );
3400
3401 apply_source_info_context(&mut node, source_key, source_info);
3402
3403 node
3404}
3405
3406#[derive(Debug, Clone)]
3408struct SimpleColumnRef {
3409 table: Option<crate::expressions::Identifier>,
3410 column: String,
3411}
3412
3413fn find_column_refs_in_expr(
3415 expr: &Expression,
3416 dialect: Option<DialectType>,
3417) -> Vec<SimpleColumnRef> {
3418 let mut refs = Vec::new();
3419 collect_column_refs(expr, dialect, &mut refs, None);
3420 refs
3421}
3422
3423fn find_column_refs_in_expr_with_select(
3424 expr: &Expression,
3425 select_expr: &Expression,
3426 dialect: Option<DialectType>,
3427) -> Vec<SimpleColumnRef> {
3428 let named_windows = match select_expr {
3429 Expression::Select(select) => select.windows.as_deref(),
3430 _ => None,
3431 };
3432 let mut refs = Vec::new();
3433 collect_column_refs(expr, dialect, &mut refs, named_windows);
3434 refs
3435}
3436
3437fn is_bigquery_safe_namespace_receiver(expr: &Expression) -> bool {
3438 match expr {
3439 Expression::Column(col) => {
3440 col.table.is_none() && !col.name.quoted && col.name.name.eq_ignore_ascii_case("SAFE")
3441 }
3442 Expression::Identifier(id) => !id.quoted && id.name.eq_ignore_ascii_case("SAFE"),
3443 _ => false,
3444 }
3445}
3446
3447fn collect_column_refs(
3448 expr: &Expression,
3449 dialect: Option<DialectType>,
3450 refs: &mut Vec<SimpleColumnRef>,
3451 named_windows: Option<&[NamedWindow]>,
3452) {
3453 let mut stack: Vec<&Expression> = vec![expr];
3454
3455 while let Some(current) = stack.pop() {
3456 match current {
3457 Expression::Column(col) => {
3459 refs.push(SimpleColumnRef {
3460 table: col.table.clone(),
3461 column: col.name.name.clone(),
3462 });
3463 }
3464
3465 Expression::Subquery(_) | Expression::Exists(_) => {}
3467
3468 Expression::And(op)
3470 | Expression::Or(op)
3471 | Expression::Eq(op)
3472 | Expression::Neq(op)
3473 | Expression::Lt(op)
3474 | Expression::Lte(op)
3475 | Expression::Gt(op)
3476 | Expression::Gte(op)
3477 | Expression::Add(op)
3478 | Expression::Sub(op)
3479 | Expression::Mul(op)
3480 | Expression::Div(op)
3481 | Expression::Mod(op)
3482 | Expression::BitwiseAnd(op)
3483 | Expression::BitwiseOr(op)
3484 | Expression::BitwiseXor(op)
3485 | Expression::BitwiseLeftShift(op)
3486 | Expression::BitwiseRightShift(op)
3487 | Expression::Concat(op)
3488 | Expression::Adjacent(op)
3489 | Expression::TsMatch(op)
3490 | Expression::PropertyEQ(op)
3491 | Expression::ArrayContainsAll(op)
3492 | Expression::ArrayContainedBy(op)
3493 | Expression::ArrayOverlaps(op)
3494 | Expression::JSONBContainsAllTopKeys(op)
3495 | Expression::JSONBContainsAnyTopKeys(op)
3496 | Expression::JSONBDeleteAtPath(op)
3497 | Expression::ExtendsLeft(op)
3498 | Expression::ExtendsRight(op)
3499 | Expression::Is(op)
3500 | Expression::MemberOf(op)
3501 | Expression::NullSafeEq(op)
3502 | Expression::NullSafeNeq(op)
3503 | Expression::Glob(op)
3504 | Expression::Match(op) => {
3505 stack.push(&op.left);
3506 stack.push(&op.right);
3507 }
3508
3509 Expression::Not(u) | Expression::Neg(u) | Expression::BitwiseNot(u) => {
3511 stack.push(&u.this);
3512 }
3513
3514 Expression::Upper(f)
3516 | Expression::Lower(f)
3517 | Expression::Length(f)
3518 | Expression::LTrim(f)
3519 | Expression::RTrim(f)
3520 | Expression::Reverse(f)
3521 | Expression::Abs(f)
3522 | Expression::Sqrt(f)
3523 | Expression::Cbrt(f)
3524 | Expression::Ln(f)
3525 | Expression::Exp(f)
3526 | Expression::Sign(f)
3527 | Expression::Date(f)
3528 | Expression::Time(f)
3529 | Expression::DateFromUnixDate(f)
3530 | Expression::UnixDate(f)
3531 | Expression::UnixSeconds(f)
3532 | Expression::UnixMillis(f)
3533 | Expression::UnixMicros(f)
3534 | Expression::TimeStrToDate(f)
3535 | Expression::DateToDi(f)
3536 | Expression::DiToDate(f)
3537 | Expression::TsOrDiToDi(f)
3538 | Expression::TsOrDsToDatetime(f)
3539 | Expression::TsOrDsToTimestamp(f)
3540 | Expression::YearOfWeek(f)
3541 | Expression::YearOfWeekIso(f)
3542 | Expression::Initcap(f)
3543 | Expression::Ascii(f)
3544 | Expression::Chr(f)
3545 | Expression::Soundex(f)
3546 | Expression::ByteLength(f)
3547 | Expression::Hex(f)
3548 | Expression::LowerHex(f)
3549 | Expression::Unicode(f)
3550 | Expression::Radians(f)
3551 | Expression::Degrees(f)
3552 | Expression::Sin(f)
3553 | Expression::Cos(f)
3554 | Expression::Tan(f)
3555 | Expression::Asin(f)
3556 | Expression::Acos(f)
3557 | Expression::Atan(f)
3558 | Expression::IsNan(f)
3559 | Expression::IsInf(f)
3560 | Expression::ArrayLength(f)
3561 | Expression::ArraySize(f)
3562 | Expression::Cardinality(f)
3563 | Expression::ArrayReverse(f)
3564 | Expression::ArrayDistinct(f)
3565 | Expression::ArrayFlatten(f)
3566 | Expression::ArrayCompact(f)
3567 | Expression::Explode(f)
3568 | Expression::ExplodeOuter(f)
3569 | Expression::ToArray(f)
3570 | Expression::MapFromEntries(f)
3571 | Expression::MapKeys(f)
3572 | Expression::MapValues(f)
3573 | Expression::JsonArrayLength(f)
3574 | Expression::JsonKeys(f)
3575 | Expression::JsonType(f)
3576 | Expression::ParseJson(f)
3577 | Expression::ToJson(f)
3578 | Expression::Typeof(f)
3579 | Expression::BitwiseCount(f)
3580 | Expression::Year(f)
3581 | Expression::Month(f)
3582 | Expression::Day(f)
3583 | Expression::Hour(f)
3584 | Expression::Minute(f)
3585 | Expression::Second(f)
3586 | Expression::DayOfWeek(f)
3587 | Expression::DayOfWeekIso(f)
3588 | Expression::DayOfMonth(f)
3589 | Expression::DayOfYear(f)
3590 | Expression::WeekOfYear(f)
3591 | Expression::Quarter(f)
3592 | Expression::Epoch(f)
3593 | Expression::EpochMs(f)
3594 | Expression::TimeStrToUnix(f)
3595 | Expression::SHA(f)
3596 | Expression::SHA1Digest(f)
3597 | Expression::TimeToUnix(f)
3598 | Expression::JSONBool(f)
3599 | Expression::Int64(f)
3600 | Expression::MD5NumberLower64(f)
3601 | Expression::MD5NumberUpper64(f)
3602 | Expression::DateStrToDate(f)
3603 | Expression::DateToDateStr(f) => {
3604 stack.push(&f.this);
3605 }
3606
3607 Expression::Power(f)
3609 | Expression::NullIf(f)
3610 | Expression::IfNull(f)
3611 | Expression::Nvl(f)
3612 | Expression::UnixToTimeStr(f)
3613 | Expression::Contains(f)
3614 | Expression::StartsWith(f)
3615 | Expression::EndsWith(f)
3616 | Expression::Levenshtein(f)
3617 | Expression::ModFunc(f)
3618 | Expression::Atan2(f)
3619 | Expression::IntDiv(f)
3620 | Expression::AddMonths(f)
3621 | Expression::MonthsBetween(f)
3622 | Expression::NextDay(f)
3623 | Expression::ArrayContains(f)
3624 | Expression::ArrayPosition(f)
3625 | Expression::ArrayAppend(f)
3626 | Expression::ArrayPrepend(f)
3627 | Expression::ArrayUnion(f)
3628 | Expression::ArrayExcept(f)
3629 | Expression::ArrayRemove(f)
3630 | Expression::StarMap(f)
3631 | Expression::MapFromArrays(f)
3632 | Expression::MapContainsKey(f)
3633 | Expression::ElementAt(f)
3634 | Expression::JsonMergePatch(f)
3635 | Expression::JSONBContains(f)
3636 | Expression::JSONBExtract(f) => {
3637 stack.push(&f.this);
3638 stack.push(&f.expression);
3639 }
3640
3641 Expression::Greatest(f)
3643 | Expression::Least(f)
3644 | Expression::Coalesce(f)
3645 | Expression::ArrayConcat(f)
3646 | Expression::ArrayIntersect(f)
3647 | Expression::ArrayZip(f)
3648 | Expression::MapConcat(f)
3649 | Expression::JsonArray(f) => {
3650 for e in &f.expressions {
3651 stack.push(e);
3652 }
3653 }
3654
3655 Expression::Sum(f)
3657 | Expression::Avg(f)
3658 | Expression::Min(f)
3659 | Expression::Max(f)
3660 | Expression::ArrayAgg(f)
3661 | Expression::CountIf(f)
3662 | Expression::Stddev(f)
3663 | Expression::StddevPop(f)
3664 | Expression::StddevSamp(f)
3665 | Expression::Variance(f)
3666 | Expression::VarPop(f)
3667 | Expression::VarSamp(f)
3668 | Expression::Median(f)
3669 | Expression::Mode(f)
3670 | Expression::First(f)
3671 | Expression::Last(f)
3672 | Expression::AnyValue(f)
3673 | Expression::ApproxDistinct(f)
3674 | Expression::ApproxCountDistinct(f)
3675 | Expression::LogicalAnd(f)
3676 | Expression::LogicalOr(f)
3677 | Expression::Skewness(f)
3678 | Expression::ArrayConcatAgg(f)
3679 | Expression::ArrayUniqueAgg(f)
3680 | Expression::BoolXorAgg(f)
3681 | Expression::BitwiseAndAgg(f)
3682 | Expression::BitwiseOrAgg(f)
3683 | Expression::BitwiseXorAgg(f) => {
3684 stack.push(&f.this);
3685 if let Some(ref filter) = f.filter {
3686 stack.push(filter);
3687 }
3688 if let Some((ref expr, _)) = f.having_max {
3689 stack.push(expr);
3690 }
3691 if let Some(ref limit) = f.limit {
3692 stack.push(limit);
3693 }
3694 }
3695
3696 Expression::Function(func) => {
3698 for arg in &func.args {
3699 stack.push(arg);
3700 }
3701 }
3702 Expression::AggregateFunction(func) => {
3703 for arg in &func.args {
3704 stack.push(arg);
3705 }
3706 if let Some(ref filter) = func.filter {
3707 stack.push(filter);
3708 }
3709 if let Some(ref limit) = func.limit {
3710 stack.push(limit);
3711 }
3712 }
3713
3714 Expression::WindowFunction(wf) => {
3716 stack.push(&wf.this);
3717 for e in &wf.over.partition_by {
3718 stack.push(e);
3719 }
3720 for e in &wf.over.order_by {
3721 stack.push(&e.this);
3722 }
3723 if let Some(keep) = &wf.keep {
3724 for e in &keep.order_by {
3725 stack.push(&e.this);
3726 }
3727 }
3728 if let (Some(window_name), Some(named_windows)) =
3729 (&wf.over.window_name, named_windows)
3730 {
3731 for named_window in named_windows {
3732 if named_window
3733 .name
3734 .name
3735 .eq_ignore_ascii_case(&window_name.name)
3736 {
3737 for e in &named_window.spec.partition_by {
3738 stack.push(e);
3739 }
3740 for e in &named_window.spec.order_by {
3741 stack.push(&e.this);
3742 }
3743 }
3744 }
3745 }
3746 }
3747
3748 Expression::Alias(a) => {
3750 stack.push(&a.this);
3751 }
3752 Expression::Cast(c) | Expression::TryCast(c) | Expression::SafeCast(c) => {
3753 stack.push(&c.this);
3754 if let Some(ref fmt) = c.format {
3755 stack.push(fmt);
3756 }
3757 if let Some(ref def) = c.default {
3758 stack.push(def);
3759 }
3760 }
3761 Expression::Paren(p) => {
3762 stack.push(&p.this);
3763 }
3764 Expression::Annotated(a) => {
3765 stack.push(&a.this);
3766 }
3767 Expression::Case(case) => {
3768 if let Some(ref operand) = case.operand {
3769 stack.push(operand);
3770 }
3771 for (cond, result) in &case.whens {
3772 stack.push(cond);
3773 stack.push(result);
3774 }
3775 if let Some(ref else_expr) = case.else_ {
3776 stack.push(else_expr);
3777 }
3778 }
3779 Expression::Collation(c) => {
3780 stack.push(&c.this);
3781 }
3782 Expression::In(i) => {
3783 stack.push(&i.this);
3784 for e in &i.expressions {
3785 stack.push(e);
3786 }
3787 if let Some(ref q) = i.query {
3788 stack.push(q);
3789 }
3790 if let Some(ref u) = i.unnest {
3791 stack.push(u);
3792 }
3793 }
3794 Expression::Between(b) => {
3795 stack.push(&b.this);
3796 stack.push(&b.low);
3797 stack.push(&b.high);
3798 }
3799 Expression::IsNull(n) => {
3800 stack.push(&n.this);
3801 }
3802 Expression::IsTrue(t) | Expression::IsFalse(t) => {
3803 stack.push(&t.this);
3804 }
3805 Expression::IsJson(j) => {
3806 stack.push(&j.this);
3807 }
3808 Expression::Like(l) | Expression::ILike(l) => {
3809 stack.push(&l.left);
3810 stack.push(&l.right);
3811 if let Some(ref esc) = l.escape {
3812 stack.push(esc);
3813 }
3814 }
3815 Expression::SimilarTo(s) => {
3816 stack.push(&s.this);
3817 stack.push(&s.pattern);
3818 if let Some(ref esc) = s.escape {
3819 stack.push(esc);
3820 }
3821 }
3822 Expression::Ordered(o) => {
3823 stack.push(&o.this);
3824 }
3825 Expression::Array(a) => {
3826 for e in &a.expressions {
3827 stack.push(e);
3828 }
3829 }
3830 Expression::Tuple(t) => {
3831 for e in &t.expressions {
3832 stack.push(e);
3833 }
3834 }
3835 Expression::Struct(s) => {
3836 for (_, e) in &s.fields {
3837 stack.push(e);
3838 }
3839 }
3840 Expression::Subscript(s) => {
3841 stack.push(&s.this);
3842 stack.push(&s.index);
3843 }
3844 Expression::Dot(d) => {
3845 stack.push(&d.this);
3846 }
3847 Expression::MethodCall(m) => {
3848 if !matches!(dialect, Some(DialectType::BigQuery))
3849 || !is_bigquery_safe_namespace_receiver(&m.this)
3850 {
3851 stack.push(&m.this);
3852 }
3853 for arg in &m.args {
3854 stack.push(arg);
3855 }
3856 }
3857 Expression::ArraySlice(s) => {
3858 stack.push(&s.this);
3859 if let Some(ref start) = s.start {
3860 stack.push(start);
3861 }
3862 if let Some(ref end) = s.end {
3863 stack.push(end);
3864 }
3865 }
3866 Expression::Lambda(l) => {
3867 stack.push(&l.body);
3868 }
3869 Expression::NamedArgument(n) => {
3870 stack.push(&n.value);
3871 }
3872 Expression::Lateral(l) => {
3873 stack.push(&l.this);
3874 if let Some(ref view) = l.view {
3875 stack.push(view);
3876 }
3877 if let Some(ref outer) = l.outer {
3878 stack.push(outer);
3879 }
3880 if let Some(ref ordinality) = l.ordinality {
3881 stack.push(ordinality);
3882 }
3883 }
3884 Expression::LateralView(lv) => {
3885 stack.push(&lv.this);
3886 }
3887 Expression::TryCatch(t) => {
3888 for stmt in &t.try_body {
3889 stack.push(stmt);
3890 }
3891 if let Some(catch_body) = &t.catch_body {
3892 for stmt in catch_body {
3893 stack.push(stmt);
3894 }
3895 }
3896 }
3897 Expression::BracedWildcard(e) | Expression::ReturnStmt(e) => {
3898 stack.push(e);
3899 }
3900
3901 Expression::Substring(f) => {
3903 stack.push(&f.this);
3904 stack.push(&f.start);
3905 if let Some(ref len) = f.length {
3906 stack.push(len);
3907 }
3908 }
3909 Expression::Trim(f) => {
3910 stack.push(&f.this);
3911 if let Some(ref chars) = f.characters {
3912 stack.push(chars);
3913 }
3914 }
3915 Expression::Replace(f) => {
3916 stack.push(&f.this);
3917 stack.push(&f.old);
3918 stack.push(&f.new);
3919 }
3920 Expression::IfFunc(f) => {
3921 stack.push(&f.condition);
3922 stack.push(&f.true_value);
3923 if let Some(ref fv) = f.false_value {
3924 stack.push(fv);
3925 }
3926 }
3927 Expression::Nvl2(f) => {
3928 stack.push(&f.this);
3929 stack.push(&f.true_value);
3930 stack.push(&f.false_value);
3931 }
3932 Expression::ConcatWs(f) => {
3933 stack.push(&f.separator);
3934 for e in &f.expressions {
3935 stack.push(e);
3936 }
3937 }
3938 Expression::Count(f) => {
3939 if let Some(ref this) = f.this {
3940 stack.push(this);
3941 }
3942 if let Some(ref filter) = f.filter {
3943 stack.push(filter);
3944 }
3945 }
3946 Expression::GroupConcat(f) => {
3947 stack.push(&f.this);
3948 if let Some(ref sep) = f.separator {
3949 stack.push(sep);
3950 }
3951 if let Some(ref filter) = f.filter {
3952 stack.push(filter);
3953 }
3954 }
3955 Expression::StringAgg(f) => {
3956 stack.push(&f.this);
3957 if let Some(ref sep) = f.separator {
3958 stack.push(sep);
3959 }
3960 if let Some(ref filter) = f.filter {
3961 stack.push(filter);
3962 }
3963 if let Some(ref limit) = f.limit {
3964 stack.push(limit);
3965 }
3966 }
3967 Expression::ListAgg(f) => {
3968 stack.push(&f.this);
3969 if let Some(ref sep) = f.separator {
3970 stack.push(sep);
3971 }
3972 if let Some(ref filter) = f.filter {
3973 stack.push(filter);
3974 }
3975 }
3976 Expression::SumIf(f) => {
3977 stack.push(&f.this);
3978 stack.push(&f.condition);
3979 if let Some(ref filter) = f.filter {
3980 stack.push(filter);
3981 }
3982 }
3983 Expression::DateAdd(f) | Expression::DateSub(f) => {
3984 stack.push(&f.this);
3985 stack.push(&f.interval);
3986 }
3987 Expression::DateDiff(f) => {
3988 stack.push(&f.this);
3989 stack.push(&f.expression);
3990 }
3991 Expression::DateTrunc(f) | Expression::TimestampTrunc(f) => {
3992 stack.push(&f.this);
3993 }
3994 Expression::Extract(f) => {
3995 stack.push(&f.this);
3996 }
3997 Expression::Round(f) => {
3998 stack.push(&f.this);
3999 if let Some(ref d) = f.decimals {
4000 stack.push(d);
4001 }
4002 }
4003 Expression::Floor(f) => {
4004 stack.push(&f.this);
4005 if let Some(ref s) = f.scale {
4006 stack.push(s);
4007 }
4008 if let Some(ref t) = f.to {
4009 stack.push(t);
4010 }
4011 }
4012 Expression::Ceil(f) => {
4013 stack.push(&f.this);
4014 if let Some(ref d) = f.decimals {
4015 stack.push(d);
4016 }
4017 if let Some(ref t) = f.to {
4018 stack.push(t);
4019 }
4020 }
4021 Expression::Log(f) => {
4022 stack.push(&f.this);
4023 if let Some(ref b) = f.base {
4024 stack.push(b);
4025 }
4026 }
4027 Expression::AtTimeZone(f) => {
4028 stack.push(&f.this);
4029 stack.push(&f.zone);
4030 }
4031 Expression::Lead(f) | Expression::Lag(f) => {
4032 stack.push(&f.this);
4033 if let Some(ref off) = f.offset {
4034 stack.push(off);
4035 }
4036 if let Some(ref def) = f.default {
4037 stack.push(def);
4038 }
4039 }
4040 Expression::FirstValue(f) | Expression::LastValue(f) => {
4041 stack.push(&f.this);
4042 }
4043 Expression::NthValue(f) => {
4044 stack.push(&f.this);
4045 stack.push(&f.offset);
4046 }
4047 Expression::Position(f) => {
4048 stack.push(&f.substring);
4049 stack.push(&f.string);
4050 if let Some(ref start) = f.start {
4051 stack.push(start);
4052 }
4053 }
4054 Expression::Decode(f) => {
4055 stack.push(&f.this);
4056 for (search, result) in &f.search_results {
4057 stack.push(search);
4058 stack.push(result);
4059 }
4060 if let Some(ref def) = f.default {
4061 stack.push(def);
4062 }
4063 }
4064 Expression::CharFunc(f) => {
4065 for arg in &f.args {
4066 stack.push(arg);
4067 }
4068 }
4069 Expression::ArraySort(f) => {
4070 stack.push(&f.this);
4071 if let Some(ref cmp) = f.comparator {
4072 stack.push(cmp);
4073 }
4074 }
4075 Expression::ArrayJoin(f) | Expression::ArrayToString(f) => {
4076 stack.push(&f.this);
4077 stack.push(&f.separator);
4078 if let Some(ref nr) = f.null_replacement {
4079 stack.push(nr);
4080 }
4081 }
4082 Expression::ArrayFilter(f) => {
4083 stack.push(&f.this);
4084 stack.push(&f.filter);
4085 }
4086 Expression::ArrayTransform(f) => {
4087 stack.push(&f.this);
4088 stack.push(&f.transform);
4089 }
4090 Expression::Sequence(f)
4091 | Expression::Generate(f)
4092 | Expression::ExplodingGenerateSeries(f) => {
4093 stack.push(&f.start);
4094 stack.push(&f.stop);
4095 if let Some(ref step) = f.step {
4096 stack.push(step);
4097 }
4098 }
4099 Expression::JsonExtract(f)
4100 | Expression::JsonExtractScalar(f)
4101 | Expression::JsonQuery(f)
4102 | Expression::JsonValue(f) => {
4103 stack.push(&f.this);
4104 stack.push(&f.path);
4105 }
4106 Expression::JsonExtractPath(f) | Expression::JsonRemove(f) => {
4107 stack.push(&f.this);
4108 for p in &f.paths {
4109 stack.push(p);
4110 }
4111 }
4112 Expression::JsonObject(f) => {
4113 for (k, v) in &f.pairs {
4114 stack.push(k);
4115 stack.push(v);
4116 }
4117 }
4118 Expression::JsonSet(f) | Expression::JsonInsert(f) => {
4119 stack.push(&f.this);
4120 for (path, val) in &f.path_values {
4121 stack.push(path);
4122 stack.push(val);
4123 }
4124 }
4125 Expression::Overlay(f) => {
4126 stack.push(&f.this);
4127 stack.push(&f.replacement);
4128 stack.push(&f.from);
4129 if let Some(ref len) = f.length {
4130 stack.push(len);
4131 }
4132 }
4133 Expression::Convert(f) => {
4134 stack.push(&f.this);
4135 if let Some(ref style) = f.style {
4136 stack.push(style);
4137 }
4138 }
4139 Expression::ApproxPercentile(f) => {
4140 stack.push(&f.this);
4141 stack.push(&f.percentile);
4142 if let Some(ref acc) = f.accuracy {
4143 stack.push(acc);
4144 }
4145 if let Some(ref filter) = f.filter {
4146 stack.push(filter);
4147 }
4148 }
4149 Expression::Percentile(f)
4150 | Expression::PercentileCont(f)
4151 | Expression::PercentileDisc(f) => {
4152 stack.push(&f.this);
4153 stack.push(&f.percentile);
4154 if let Some(ref filter) = f.filter {
4155 stack.push(filter);
4156 }
4157 }
4158 Expression::WithinGroup(f) => {
4159 stack.push(&f.this);
4160 for e in &f.order_by {
4161 stack.push(&e.this);
4162 }
4163 }
4164 Expression::Left(f) | Expression::Right(f) => {
4165 stack.push(&f.this);
4166 stack.push(&f.length);
4167 }
4168 Expression::Repeat(f) => {
4169 stack.push(&f.this);
4170 stack.push(&f.times);
4171 }
4172 Expression::Lpad(f) | Expression::Rpad(f) => {
4173 stack.push(&f.this);
4174 stack.push(&f.length);
4175 if let Some(ref fill) = f.fill {
4176 stack.push(fill);
4177 }
4178 }
4179 Expression::Split(f) => {
4180 stack.push(&f.this);
4181 stack.push(&f.delimiter);
4182 }
4183 Expression::RegexpLike(f) => {
4184 stack.push(&f.this);
4185 stack.push(&f.pattern);
4186 if let Some(ref flags) = f.flags {
4187 stack.push(flags);
4188 }
4189 }
4190 Expression::RegexpReplace(f) => {
4191 stack.push(&f.this);
4192 stack.push(&f.pattern);
4193 stack.push(&f.replacement);
4194 if let Some(ref flags) = f.flags {
4195 stack.push(flags);
4196 }
4197 }
4198 Expression::RegexpExtract(f) => {
4199 stack.push(&f.this);
4200 stack.push(&f.pattern);
4201 if let Some(ref group) = f.group {
4202 stack.push(group);
4203 }
4204 }
4205 Expression::ToDate(f) => {
4206 stack.push(&f.this);
4207 if let Some(ref fmt) = f.format {
4208 stack.push(fmt);
4209 }
4210 }
4211 Expression::ToTimestamp(f) => {
4212 stack.push(&f.this);
4213 if let Some(ref fmt) = f.format {
4214 stack.push(fmt);
4215 }
4216 }
4217 Expression::DateFormat(f) | Expression::FormatDate(f) => {
4218 stack.push(&f.this);
4219 stack.push(&f.format);
4220 }
4221 Expression::LastDay(f) => {
4222 stack.push(&f.this);
4223 }
4224 Expression::FromUnixtime(f) => {
4225 stack.push(&f.this);
4226 if let Some(ref fmt) = f.format {
4227 stack.push(fmt);
4228 }
4229 }
4230 Expression::UnixTimestamp(f) => {
4231 if let Some(ref this) = f.this {
4232 stack.push(this);
4233 }
4234 if let Some(ref fmt) = f.format {
4235 stack.push(fmt);
4236 }
4237 }
4238 Expression::MakeDate(f) => {
4239 stack.push(&f.year);
4240 stack.push(&f.month);
4241 stack.push(&f.day);
4242 }
4243 Expression::MakeTimestamp(f) => {
4244 stack.push(&f.year);
4245 stack.push(&f.month);
4246 stack.push(&f.day);
4247 stack.push(&f.hour);
4248 stack.push(&f.minute);
4249 stack.push(&f.second);
4250 if let Some(ref tz) = f.timezone {
4251 stack.push(tz);
4252 }
4253 }
4254 Expression::TruncFunc(f) => {
4255 stack.push(&f.this);
4256 if let Some(ref d) = f.decimals {
4257 stack.push(d);
4258 }
4259 }
4260 Expression::ArrayFunc(f) => {
4261 for e in &f.expressions {
4262 stack.push(e);
4263 }
4264 }
4265 Expression::Unnest(f) => {
4266 stack.push(&f.this);
4267 for e in &f.expressions {
4268 stack.push(e);
4269 }
4270 }
4271 Expression::StructFunc(f) => {
4272 for (_, e) in &f.fields {
4273 stack.push(e);
4274 }
4275 }
4276 Expression::StructExtract(f) => {
4277 stack.push(&f.this);
4278 }
4279 Expression::NamedStruct(f) => {
4280 for (k, v) in &f.pairs {
4281 stack.push(k);
4282 stack.push(v);
4283 }
4284 }
4285 Expression::MapFunc(f) => {
4286 for k in &f.keys {
4287 stack.push(k);
4288 }
4289 for v in &f.values {
4290 stack.push(v);
4291 }
4292 }
4293 Expression::TransformKeys(f) | Expression::TransformValues(f) => {
4294 stack.push(&f.this);
4295 stack.push(&f.transform);
4296 }
4297 Expression::JsonArrayAgg(f) => {
4298 stack.push(&f.this);
4299 if let Some(ref filter) = f.filter {
4300 stack.push(filter);
4301 }
4302 }
4303 Expression::JsonObjectAgg(f) => {
4304 stack.push(&f.key);
4305 stack.push(&f.value);
4306 if let Some(ref filter) = f.filter {
4307 stack.push(filter);
4308 }
4309 }
4310 Expression::NTile(f) => {
4311 if let Some(ref n) = f.num_buckets {
4312 stack.push(n);
4313 }
4314 }
4315 Expression::Rand(f) => {
4316 if let Some(ref s) = f.seed {
4317 stack.push(s);
4318 }
4319 if let Some(ref lo) = f.lower {
4320 stack.push(lo);
4321 }
4322 if let Some(ref hi) = f.upper {
4323 stack.push(hi);
4324 }
4325 }
4326 Expression::Any(q) | Expression::All(q) => {
4327 stack.push(&q.this);
4328 stack.push(&q.subquery);
4329 }
4330 Expression::Overlaps(o) => {
4331 if let Some(ref this) = o.this {
4332 stack.push(this);
4333 }
4334 if let Some(ref expr) = o.expression {
4335 stack.push(expr);
4336 }
4337 if let Some(ref ls) = o.left_start {
4338 stack.push(ls);
4339 }
4340 if let Some(ref le) = o.left_end {
4341 stack.push(le);
4342 }
4343 if let Some(ref rs) = o.right_start {
4344 stack.push(rs);
4345 }
4346 if let Some(ref re) = o.right_end {
4347 stack.push(re);
4348 }
4349 }
4350 Expression::Interval(i) => {
4351 if let Some(ref this) = i.this {
4352 stack.push(this);
4353 }
4354 }
4355 Expression::TimeStrToTime(f) => {
4356 stack.push(&f.this);
4357 if let Some(ref zone) = f.zone {
4358 stack.push(zone);
4359 }
4360 }
4361 Expression::JSONBExtractScalar(f) => {
4362 stack.push(&f.this);
4363 stack.push(&f.expression);
4364 if let Some(ref jt) = f.json_type {
4365 stack.push(jt);
4366 }
4367 }
4368 Expression::JSONExtract(f) => {
4369 stack.push(&f.this);
4370 stack.push(&f.expression);
4371 for e in &f.expressions {
4372 stack.push(e);
4373 }
4374 if let Some(ref option) = f.option {
4375 stack.push(option);
4376 }
4377 if let Some(ref on_condition) = f.on_condition {
4378 stack.push(on_condition);
4379 }
4380 }
4381
4382 _ => {}
4387 }
4388 }
4389}
4390
4391#[cfg(test)]
4396mod tests {
4397 use super::*;
4398 use crate::dialects::{Dialect, DialectType};
4399 use crate::expressions::DataType;
4400 use crate::optimizer::annotate_types::annotate_types;
4401 use crate::parse_one;
4402 use crate::schema::{MappingSchema, Schema};
4403
4404 fn parse(sql: &str) -> Expression {
4405 let dialect = Dialect::get(DialectType::Generic);
4406 let ast = dialect.parse(sql).unwrap();
4407 ast.into_iter().next().unwrap()
4408 }
4409
4410 fn parse_dialect(sql: &str, dialect_type: DialectType) -> Expression {
4411 let dialect = Dialect::get(dialect_type);
4412 let ast = dialect.parse(sql).unwrap();
4413 ast.into_iter().next().unwrap()
4414 }
4415
4416 fn lineage_names(node: &LineageNode) -> Vec<String> {
4417 node.walk().map(|n| n.name.clone()).collect()
4418 }
4419
4420 fn assert_lineage_contains(node: &LineageNode, expected: &str) {
4421 let names = lineage_names(node);
4422 assert!(
4423 names.iter().any(|name| name == expected),
4424 "expected {expected} in lineage, got {names:?}"
4425 );
4426 }
4427
4428 const ISSUE_368_SQL: &str = "with
4429base as (
4430 select 1 as col_a
4431),
4432literal_branch as (
4433 select 2 as col_a
4434),
4435unioned as (
4436 select * from base
4437 union all
4438 select * from literal_branch
4439)
4440select col_a from unioned";
4441
4442 #[test]
4443 fn test_simple_lineage() {
4444 let expr = parse("SELECT a FROM t");
4445 let node = lineage("a", &expr, None, false).unwrap();
4446
4447 assert_eq!(node.name, "a");
4448 assert!(!node.downstream.is_empty(), "Should have downstream nodes");
4449 let names = node.downstream_names();
4451 assert!(
4452 names.iter().any(|n| n == "t.a"),
4453 "Expected t.a in downstream, got: {:?}",
4454 names
4455 );
4456 }
4457
4458 #[test]
4459 fn test_lineage_walk() {
4460 let root = LineageNode {
4461 name: "col_a".to_string(),
4462 expression: Expression::Null(crate::expressions::Null),
4463 source: Expression::Null(crate::expressions::Null),
4464 downstream: vec![LineageNode::new(
4465 "t.a",
4466 Expression::Null(crate::expressions::Null),
4467 Expression::Null(crate::expressions::Null),
4468 )],
4469 source_name: String::new(),
4470 source_kind: SourceKind::Unknown,
4471 source_alias: None,
4472 set_branch: None,
4473 reference_node_name: String::new(),
4474 };
4475
4476 let names: Vec<_> = root.walk().map(|n| n.name.clone()).collect();
4477 assert_eq!(names.len(), 2);
4478 assert_eq!(names[0], "col_a");
4479 assert_eq!(names[1], "t.a");
4480 }
4481
4482 #[test]
4483 fn test_aliased_column() {
4484 let expr = parse("SELECT a + 1 AS b FROM t");
4485 let node = lineage("b", &expr, None, false).unwrap();
4486
4487 assert_eq!(node.name, "b");
4488 let all_names: Vec<_> = node.walk().map(|n| n.name.clone()).collect();
4490 assert!(
4491 all_names.iter().any(|n| n.contains("a")),
4492 "Expected to trace to column a, got: {:?}",
4493 all_names
4494 );
4495 }
4496
4497 #[test]
4498 fn test_qualified_column() {
4499 let expr = parse("SELECT t.a FROM t");
4500 let node = lineage("a", &expr, None, false).unwrap();
4501
4502 assert_eq!(node.name, "a");
4503 let names = node.downstream_names();
4504 assert!(
4505 names.iter().any(|n| n == "t.a"),
4506 "Expected t.a, got: {:?}",
4507 names
4508 );
4509 }
4510
4511 #[test]
4512 fn test_unqualified_column() {
4513 let expr = parse("SELECT a FROM t");
4514 let node = lineage("a", &expr, None, false).unwrap();
4515
4516 let names = node.downstream_names();
4518 assert!(
4519 names.iter().any(|n| n == "t.a"),
4520 "Expected t.a, got: {:?}",
4521 names
4522 );
4523 }
4524
4525 #[test]
4526 fn test_lineage_with_schema_qualifies_root_expression_issue_40() {
4527 let query = "SELECT name FROM users";
4528 let dialect = Dialect::get(DialectType::BigQuery);
4529 let expr = dialect
4530 .parse(query)
4531 .unwrap()
4532 .into_iter()
4533 .next()
4534 .expect("expected one expression");
4535
4536 let mut schema = MappingSchema::with_dialect(DialectType::BigQuery);
4537 schema
4538 .add_table("users", &[("name".into(), DataType::Text)], None)
4539 .expect("schema setup");
4540
4541 let node_without_schema = lineage("name", &expr, Some(DialectType::BigQuery), false)
4542 .expect("lineage without schema");
4543 let mut expr_without = node_without_schema.expression.clone();
4544 annotate_types(
4545 &mut expr_without,
4546 Some(&schema),
4547 Some(DialectType::BigQuery),
4548 );
4549 assert_eq!(
4550 expr_without.inferred_type(),
4551 None,
4552 "Expected unresolved root type without schema-aware lineage qualification"
4553 );
4554
4555 let node_with_schema = lineage_with_schema(
4556 "name",
4557 &expr,
4558 Some(&schema),
4559 Some(DialectType::BigQuery),
4560 false,
4561 )
4562 .expect("lineage with schema");
4563 let mut expr_with = node_with_schema.expression.clone();
4564 annotate_types(&mut expr_with, Some(&schema), Some(DialectType::BigQuery));
4565
4566 assert_eq!(expr_with.inferred_type(), Some(&DataType::Text));
4567 }
4568
4569 #[test]
4570 fn test_lineage_with_schema_tolerates_partial_schema_for_known_column() {
4571 let expr = parse_dialect("SELECT order_id, amount FROM t", DialectType::DuckDB);
4572 let mut schema = MappingSchema::with_dialect(DialectType::DuckDB);
4573 schema
4574 .add_table(
4575 "t",
4576 &[("amount".into(), DataType::BigInt { length: None })],
4577 None,
4578 )
4579 .expect("schema setup");
4580
4581 let node = lineage_with_schema(
4582 "amount",
4583 &expr,
4584 Some(&schema),
4585 Some(DialectType::DuckDB),
4586 false,
4587 )
4588 .expect("lineage_with_schema should tolerate unrelated unknown columns");
4589
4590 assert_lineage_contains(&node, "t.amount");
4591 }
4592
4593 #[test]
4594 fn test_lineage_with_schema_tolerates_partial_schema_for_unknown_column() {
4595 let expr = parse_dialect("SELECT order_id, amount FROM t", DialectType::DuckDB);
4596 let mut schema = MappingSchema::with_dialect(DialectType::DuckDB);
4597 schema
4598 .add_table(
4599 "t",
4600 &[("amount".into(), DataType::BigInt { length: None })],
4601 None,
4602 )
4603 .expect("schema setup");
4604
4605 let node = lineage_with_schema(
4606 "order_id",
4607 &expr,
4608 Some(&schema),
4609 Some(DialectType::DuckDB),
4610 false,
4611 )
4612 .expect("lineage_with_schema should keep unknown selected columns");
4613
4614 assert_lineage_contains(&node, "t.order_id");
4615 }
4616
4617 #[test]
4618 fn test_lineage_with_schema_tolerates_partial_schema_for_join_conditions() {
4619 let expr = parse_dialect(
4620 "SELECT a.order_id, b.amount FROM t a JOIN u b ON a.id = b.id",
4621 DialectType::DuckDB,
4622 );
4623 let mut schema = MappingSchema::with_dialect(DialectType::DuckDB);
4624 schema
4625 .add_table(
4626 "t",
4627 &[("order_id".into(), DataType::BigInt { length: None })],
4628 None,
4629 )
4630 .expect("schema setup");
4631 schema
4632 .add_table(
4633 "u",
4634 &[("amount".into(), DataType::BigInt { length: None })],
4635 None,
4636 )
4637 .expect("schema setup");
4638
4639 let node = lineage_with_schema(
4640 "amount",
4641 &expr,
4642 Some(&schema),
4643 Some(DialectType::DuckDB),
4644 false,
4645 )
4646 .expect("lineage_with_schema should tolerate unknown join keys");
4647
4648 assert_lineage_contains(&node, "b.amount");
4649 }
4650
4651 #[test]
4652 fn test_lineage_with_schema_correlated_scalar_subquery() {
4653 let query = "SELECT id, (SELECT AVG(val) FROM t2 WHERE t2.id = t1.id) AS avg_val FROM t1";
4654 let dialect = Dialect::get(DialectType::BigQuery);
4655 let expr = dialect
4656 .parse(query)
4657 .unwrap()
4658 .into_iter()
4659 .next()
4660 .expect("expected one expression");
4661
4662 let mut schema = MappingSchema::with_dialect(DialectType::BigQuery);
4663 schema
4664 .add_table(
4665 "t1",
4666 &[("id".into(), DataType::BigInt { length: None })],
4667 None,
4668 )
4669 .expect("schema setup");
4670 schema
4671 .add_table(
4672 "t2",
4673 &[
4674 ("id".into(), DataType::BigInt { length: None }),
4675 ("val".into(), DataType::BigInt { length: None }),
4676 ],
4677 None,
4678 )
4679 .expect("schema setup");
4680
4681 let node = lineage_with_schema(
4682 "id",
4683 &expr,
4684 Some(&schema),
4685 Some(DialectType::BigQuery),
4686 false,
4687 )
4688 .expect("lineage_with_schema should handle correlated scalar subqueries");
4689
4690 assert_eq!(node.name, "id");
4691 }
4692
4693 #[test]
4694 fn test_lineage_with_schema_join_using() {
4695 let query = "SELECT a FROM t1 JOIN t2 USING(a)";
4696 let dialect = Dialect::get(DialectType::BigQuery);
4697 let expr = dialect
4698 .parse(query)
4699 .unwrap()
4700 .into_iter()
4701 .next()
4702 .expect("expected one expression");
4703
4704 let mut schema = MappingSchema::with_dialect(DialectType::BigQuery);
4705 schema
4706 .add_table(
4707 "t1",
4708 &[("a".into(), DataType::BigInt { length: None })],
4709 None,
4710 )
4711 .expect("schema setup");
4712 schema
4713 .add_table(
4714 "t2",
4715 &[("a".into(), DataType::BigInt { length: None })],
4716 None,
4717 )
4718 .expect("schema setup");
4719
4720 let node = lineage_with_schema(
4721 "a",
4722 &expr,
4723 Some(&schema),
4724 Some(DialectType::BigQuery),
4725 false,
4726 )
4727 .expect("lineage_with_schema should handle JOIN USING");
4728
4729 assert_eq!(node.name, "a");
4730 }
4731
4732 #[test]
4733 fn test_lineage_with_schema_natural_join_merged_column() {
4734 let query = "SELECT shared_key AS output_key FROM source_table \
4735 NATURAL JOIN (SELECT shared_key FROM source_table) derived";
4736 let dialect = Dialect::get(DialectType::DuckDB);
4737 let expr = dialect
4738 .parse(query)
4739 .unwrap()
4740 .into_iter()
4741 .next()
4742 .expect("expected one expression");
4743
4744 let mut schema = MappingSchema::with_dialect(DialectType::DuckDB);
4745 schema
4746 .add_table(
4747 "source_table",
4748 &[(
4749 "shared_key".into(),
4750 DataType::VarChar {
4751 length: None,
4752 parenthesized_length: false,
4753 },
4754 )],
4755 None,
4756 )
4757 .expect("schema setup");
4758
4759 let node = lineage_with_schema(
4760 "output_key",
4761 &expr,
4762 Some(&schema),
4763 Some(DialectType::DuckDB),
4764 false,
4765 )
4766 .expect("lineage_with_schema should resolve a NATURAL JOIN merged column");
4767
4768 let terminal_nodes: Vec<_> = node
4769 .walk()
4770 .filter(|candidate| candidate.downstream.is_empty())
4771 .collect();
4772 assert_eq!(
4773 terminal_nodes.len(),
4774 2,
4775 "both NATURAL JOIN inputs should remain visible: {node:#?}"
4776 );
4777 assert!(terminal_nodes.iter().all(|candidate| {
4778 candidate.source_kind == SourceKind::Table
4779 && candidate.source_name == "source_table"
4780 && candidate.name.ends_with(".shared_key")
4781 }));
4782 }
4783
4784 #[test]
4785 fn test_lineage_with_schema_propagates_type_through_anonymous_derived_table() {
4786 let expr = parse_dialect(
4787 "SELECT source_value FROM (SELECT source_value FROM source_table)",
4788 DialectType::DuckDB,
4789 );
4790 let mut schema = MappingSchema::with_dialect(DialectType::DuckDB);
4791 let expected = DataType::VarChar {
4792 length: None,
4793 parenthesized_length: false,
4794 };
4795 schema
4796 .add_table(
4797 "source_table",
4798 &[("source_value".into(), expected.clone())],
4799 None,
4800 )
4801 .expect("schema setup");
4802
4803 let node = lineage_with_schema(
4804 "source_value",
4805 &expr,
4806 Some(&schema),
4807 Some(DialectType::DuckDB),
4808 false,
4809 )
4810 .expect("lineage_with_schema should resolve an anonymous derived table");
4811
4812 assert_eq!(node.expression.inferred_type(), Some(&expected));
4813 let terminal_nodes: Vec<_> = node
4814 .walk()
4815 .filter(|candidate| candidate.downstream.is_empty())
4816 .collect();
4817 assert_eq!(terminal_nodes.len(), 1, "{node:#?}");
4818 assert_eq!(terminal_nodes[0].source_kind, SourceKind::Table);
4819 assert_eq!(terminal_nodes[0].source_name, "source_table");
4820 assert_eq!(terminal_nodes[0].name, "source_table.source_value");
4821 }
4822
4823 #[test]
4824 fn test_issue_412_duckdb_date_trunc_preserves_timestamp_lineage_type() {
4825 let expr = parse_dialect(
4826 "SELECT DATE_TRUNC('month', event_timestamp) AS truncated_timestamp \
4827 FROM source_table",
4828 DialectType::DuckDB,
4829 );
4830 let mut schema = MappingSchema::with_dialect(DialectType::DuckDB);
4831 let timestamp = DataType::Timestamp {
4832 precision: None,
4833 timezone: false,
4834 };
4835 schema
4836 .add_table(
4837 "source_table",
4838 &[("event_timestamp".into(), timestamp.clone())],
4839 None,
4840 )
4841 .expect("schema setup");
4842
4843 let node =
4844 lineage_at_with_schema(0, &expr, Some(&schema), Some(DialectType::DuckDB), false)
4845 .expect("schema-aware lineage");
4846
4847 assert_eq!(node.expression.inferred_type(), Some(×tamp));
4848 let Expression::Alias(alias) = &node.expression else {
4849 panic!("expected aliased DATE_TRUNC projection");
4850 };
4851 assert_eq!(alias.this.inferred_type(), Some(×tamp));
4852 let Expression::Function(function) = &alias.this else {
4853 panic!("expected generic DATE_TRUNC function");
4854 };
4855 assert_eq!(function.args[1].inferred_type(), Some(×tamp));
4856 }
4857
4858 #[test]
4859 fn test_lineage_with_schema_qualified_table_name() {
4860 let query = "SELECT a FROM raw.t1";
4861 let dialect = Dialect::get(DialectType::BigQuery);
4862 let expr = dialect
4863 .parse(query)
4864 .unwrap()
4865 .into_iter()
4866 .next()
4867 .expect("expected one expression");
4868
4869 let mut schema = MappingSchema::with_dialect(DialectType::BigQuery);
4870 schema
4871 .add_table(
4872 "raw.t1",
4873 &[("a".into(), DataType::BigInt { length: None })],
4874 None,
4875 )
4876 .expect("schema setup");
4877
4878 let node = lineage_with_schema(
4879 "a",
4880 &expr,
4881 Some(&schema),
4882 Some(DialectType::BigQuery),
4883 false,
4884 )
4885 .expect("lineage_with_schema should handle dotted schema.table names");
4886
4887 assert_eq!(node.name, "a");
4888 }
4889
4890 #[test]
4891 fn test_lineage_with_schema_none_matches_lineage() {
4892 let expr = parse("SELECT a FROM t");
4893 let baseline = lineage("a", &expr, None, false).expect("lineage baseline");
4894 let with_none =
4895 lineage_with_schema("a", &expr, None, None, false).expect("lineage_with_schema");
4896
4897 assert_eq!(with_none.name, baseline.name);
4898 assert_eq!(with_none.downstream_names(), baseline.downstream_names());
4899 }
4900
4901 #[test]
4902 fn test_lineage_with_schema_bigquery_mixed_case_column_names_issue_60() {
4903 let dialect = Dialect::get(DialectType::BigQuery);
4904 let expr = dialect
4905 .parse("SELECT Name AS name FROM teams")
4906 .unwrap()
4907 .into_iter()
4908 .next()
4909 .expect("expected one expression");
4910
4911 let mut schema = MappingSchema::with_dialect(DialectType::BigQuery);
4912 schema
4913 .add_table(
4914 "teams",
4915 &[("Name".into(), DataType::String { length: None })],
4916 None,
4917 )
4918 .expect("schema setup");
4919
4920 let node = lineage_with_schema(
4921 "name",
4922 &expr,
4923 Some(&schema),
4924 Some(DialectType::BigQuery),
4925 false,
4926 )
4927 .expect("lineage_with_schema should resolve mixed-case BigQuery columns");
4928
4929 let names = node.downstream_names();
4930 assert!(
4931 names.iter().any(|n| n == "teams.Name"),
4932 "Expected teams.Name in downstream, got: {:?}",
4933 names
4934 );
4935 }
4936
4937 #[test]
4938 fn test_lineage_bigquery_mixed_case_alias_lookup() {
4939 let dialect = Dialect::get(DialectType::BigQuery);
4940 let expr = dialect
4941 .parse("SELECT Name AS Name FROM teams")
4942 .unwrap()
4943 .into_iter()
4944 .next()
4945 .expect("expected one expression");
4946
4947 let node = lineage("name", &expr, Some(DialectType::BigQuery), false)
4948 .expect("lineage should resolve mixed-case aliases in BigQuery");
4949
4950 assert_eq!(node.name, "name");
4951 }
4952
4953 #[test]
4954 fn test_lineage_bigquery_unnest_alias_source_issue_209() {
4955 let expr = parse_one(
4956 r#"
4957SELECT date_val AS week_start
4958FROM UNNEST(GENERATE_DATE_ARRAY('2024-01-01', '2024-12-31', INTERVAL 1 WEEK)) AS date_val
4959"#,
4960 DialectType::BigQuery,
4961 )
4962 .expect("parse");
4963
4964 let node = lineage("week_start", &expr, Some(DialectType::BigQuery), false)
4965 .expect("lineage should resolve UNNEST alias as a source");
4966 let child = node
4967 .downstream
4968 .first()
4969 .expect("week_start should have downstream lineage");
4970
4971 assert_eq!(child.name, "_0.date_val");
4972 assert_eq!(child.source_name, "_0");
4973 assert_eq!(child.source_kind, SourceKind::Virtual);
4974 assert_eq!(child.source_alias.as_deref(), Some("date_val"));
4975
4976 let Expression::Column(column) = &child.expression else {
4977 panic!(
4978 "expected downstream column expression, got {:?}",
4979 child.expression
4980 );
4981 };
4982 assert_eq!(column.name.name, "date_val");
4983 assert_eq!(
4984 column.table.as_ref().map(|table| table.name.as_str()),
4985 Some("_0")
4986 );
4987 assert!(
4988 matches!(&child.source, Expression::Alias(alias) if matches!(&alias.this, Expression::Unnest(_)) && alias.alias.name == "date_val"),
4989 "expected UNNEST source expression, got {:?}",
4990 child.source
4991 );
4992 }
4993
4994 #[test]
4995 fn test_lineage_real_table_named_like_unnest_alias_is_not_virtual() {
4996 let expr =
4997 parse_one("SELECT date_val.id FROM date_val", DialectType::BigQuery).expect("parse");
4998
4999 let node = lineage("id", &expr, Some(DialectType::BigQuery), false).expect("lineage");
5000 let child = node.downstream.first().expect("id should have lineage");
5001
5002 assert_eq!(child.name, "date_val.id");
5003 assert_eq!(child.source_name, "date_val");
5004 assert_eq!(child.source_kind, SourceKind::Table);
5005 assert_eq!(child.source_alias, None);
5006 }
5007
5008 #[test]
5009 fn test_lineage_multiple_bigquery_unnest_sources_get_stable_virtual_names() {
5010 let expr = parse_one(
5011 r#"
5012SELECT a.a AS first_value, b.b AS second_value
5013FROM UNNEST(GENERATE_ARRAY(1, 2)) AS a
5014JOIN UNNEST(GENERATE_ARRAY(3, 4)) AS b ON TRUE
5015"#,
5016 DialectType::BigQuery,
5017 )
5018 .expect("parse");
5019
5020 let first =
5021 lineage("first_value", &expr, Some(DialectType::BigQuery), false).expect("lineage");
5022 let second =
5023 lineage("second_value", &expr, Some(DialectType::BigQuery), false).expect("lineage");
5024
5025 let first_child = first.downstream.first().expect("first source");
5026 let second_child = second.downstream.first().expect("second source");
5027
5028 assert_eq!(first_child.name, "_0.a");
5029 assert_eq!(first_child.source_name, "_0");
5030 assert_eq!(first_child.source_alias.as_deref(), Some("a"));
5031 assert_eq!(first_child.source_kind, SourceKind::Virtual);
5032
5033 assert_eq!(second_child.name, "_1.b");
5034 assert_eq!(second_child.source_name, "_1");
5035 assert_eq!(second_child.source_alias.as_deref(), Some("b"));
5036 assert_eq!(second_child.source_kind, SourceKind::Virtual);
5037 }
5038
5039 #[test]
5040 fn test_lineage_table_backed_unnest_points_to_real_source_column() {
5041 let expr = parse_one(
5042 r#"
5043SELECT item.item AS item
5044FROM t JOIN UNNEST(t.items) AS item ON TRUE
5045"#,
5046 DialectType::BigQuery,
5047 )
5048 .expect("parse");
5049
5050 let node = lineage("item", &expr, Some(DialectType::BigQuery), false).expect("lineage");
5051 let virtual_child = node.downstream.first().expect("virtual item source");
5052 assert_eq!(virtual_child.name, "_0.item");
5053 assert_eq!(virtual_child.source_kind, SourceKind::Virtual);
5054
5055 let real_child = virtual_child
5056 .downstream
5057 .first()
5058 .expect("UNNEST(t.items) should depend on t.items");
5059 assert_eq!(real_child.name, "t.items");
5060 assert_eq!(real_child.source_name, "t");
5061 assert_eq!(real_child.source_kind, SourceKind::Table);
5062 }
5063
5064 #[test]
5065 fn test_lineage_table_backed_unnest_unqualified_column_resolves_to_virtual_source() {
5066 let expr = parse_one(
5067 r#"
5068SELECT item AS item
5069FROM t JOIN UNNEST(t.items) AS item ON TRUE
5070"#,
5071 DialectType::BigQuery,
5072 )
5073 .expect("parse");
5074
5075 let node = lineage("item", &expr, Some(DialectType::BigQuery), false).expect("lineage");
5076 let virtual_child = node.downstream.first().expect("virtual item source");
5077 assert_eq!(virtual_child.name, "_0.item");
5078 assert_eq!(virtual_child.source_name, "_0");
5079 assert_eq!(virtual_child.source_kind, SourceKind::Virtual);
5080 assert_eq!(virtual_child.source_alias.as_deref(), Some("item"));
5081
5082 let real_child = virtual_child
5083 .downstream
5084 .first()
5085 .expect("UNNEST(t.items) should depend on t.items");
5086 assert_eq!(real_child.name, "t.items");
5087 assert_eq!(real_child.source_name, "t");
5088 assert_eq!(real_child.source_kind, SourceKind::Table);
5089 }
5090
5091 #[test]
5092 fn test_lineage_unnest_alias_columns_resolve_to_virtual_sources_across_dialects() {
5093 let cases = [
5094 (
5095 DialectType::PostgreSQL,
5096 "SELECT x AS out FROM t CROSS JOIN LATERAL UNNEST(items) AS u(x)",
5097 ),
5098 (
5099 DialectType::Presto,
5100 "SELECT x AS out FROM t CROSS JOIN UNNEST(items) AS u(x)",
5101 ),
5102 (
5103 DialectType::Trino,
5104 "SELECT x AS out FROM t CROSS JOIN UNNEST(items) AS u(x)",
5105 ),
5106 ];
5107
5108 for (dialect, sql) in cases {
5109 let expr = parse_one(sql, dialect).unwrap_or_else(|e| panic!("parse {dialect:?}: {e}"));
5110 let node = lineage("out", &expr, Some(dialect), false)
5111 .unwrap_or_else(|e| panic!("lineage {dialect:?}: {e}"));
5112 let virtual_child = node
5113 .downstream
5114 .first()
5115 .unwrap_or_else(|| panic!("expected virtual child for {dialect:?}"));
5116
5117 assert_eq!(
5118 virtual_child.name, "_0.x",
5119 "unexpected virtual child for {dialect:?}"
5120 );
5121 assert_eq!(virtual_child.source_name, "_0");
5122 assert_eq!(virtual_child.source_kind, SourceKind::Virtual);
5123 assert_eq!(virtual_child.source_alias.as_deref(), Some("u"));
5124
5125 let real_child = virtual_child
5126 .downstream
5127 .first()
5128 .unwrap_or_else(|| panic!("expected table dependency for {dialect:?}"));
5129 assert_eq!(real_child.name, "t.items");
5130 assert_eq!(real_child.source_kind, SourceKind::Table);
5131 }
5132 }
5133
5134 #[test]
5135 fn test_lineage_with_schema_propagates_unnest_element_type() {
5136 let expr = parse_dialect(
5137 "SELECT u.tag FROM events AS e, UNNEST(e.tags) AS u(tag)",
5138 DialectType::DuckDB,
5139 );
5140 let mut schema = MappingSchema::with_dialect(DialectType::DuckDB);
5141 schema
5142 .add_table(
5143 "events",
5144 &[(
5145 "tags".into(),
5146 DataType::Array {
5147 element_type: Box::new(DataType::VarChar {
5148 length: None,
5149 parenthesized_length: false,
5150 }),
5151 dimension: None,
5152 },
5153 )],
5154 None,
5155 )
5156 .expect("schema setup");
5157
5158 let node = lineage_with_schema(
5159 "tag",
5160 &expr,
5161 Some(&schema),
5162 Some(DialectType::DuckDB),
5163 false,
5164 )
5165 .expect("lineage_with_schema");
5166 let expected = DataType::VarChar {
5167 length: None,
5168 parenthesized_length: false,
5169 };
5170
5171 assert_eq!(node.expression.inferred_type(), Some(&expected));
5172 let virtual_child = node
5173 .downstream
5174 .iter()
5175 .find(|child| child.source_kind == SourceKind::Virtual)
5176 .expect("virtual UNNEST output");
5177 assert_eq!(virtual_child.expression.inferred_type(), Some(&expected));
5178 }
5179
5180 #[test]
5181 fn test_lineage_with_schema_resolves_struct_fields_issue_408() {
5182 let struct_type = DataType::Struct {
5183 fields: vec![crate::expressions::StructField::new(
5184 "field_value".into(),
5185 DataType::Text,
5186 )],
5187 nested: true,
5188 };
5189 let mut schema = MappingSchema::with_dialect(DialectType::DuckDB);
5190 schema
5191 .add_table(
5192 "source_table",
5193 &[
5194 ("composite_value".into(), struct_type.clone()),
5195 (
5196 "nested_items".into(),
5197 DataType::Array {
5198 element_type: Box::new(struct_type),
5199 dimension: None,
5200 },
5201 ),
5202 ],
5203 None,
5204 )
5205 .expect("schema setup");
5206
5207 let direct = parse_dialect(
5208 "SELECT composite_value.field_value AS output_value FROM source_table",
5209 DialectType::DuckDB,
5210 );
5211 let direct_node = lineage_with_schema(
5212 "output_value",
5213 &direct,
5214 Some(&schema),
5215 Some(DialectType::DuckDB),
5216 false,
5217 )
5218 .expect("direct struct lineage");
5219 assert_eq!(
5220 direct_node.expression.inferred_type(),
5221 Some(&DataType::Text)
5222 );
5223 assert_lineage_contains(&direct_node, "source_table.composite_value");
5224
5225 let unnested = parse_dialect(
5226 "SELECT item.field_value AS output_value FROM source_table s \
5227 CROSS JOIN UNNEST(s.nested_items) AS expanded(item)",
5228 DialectType::DuckDB,
5229 );
5230 let unnest_node = lineage_with_schema(
5231 "output_value",
5232 &unnested,
5233 Some(&schema),
5234 Some(DialectType::DuckDB),
5235 false,
5236 )
5237 .expect("UNNEST struct lineage");
5238 assert_eq!(
5239 unnest_node.expression.inferred_type(),
5240 Some(&DataType::Text)
5241 );
5242 assert!(
5243 unnest_node.walk().any(|node| {
5244 node.source_kind == SourceKind::Table
5245 && node.source_name == "source_table"
5246 && node.name.ends_with(".nested_items")
5247 }),
5248 "expected physical nested_items lineage, got {:?}",
5249 lineage_names(&unnest_node)
5250 );
5251 }
5252
5253 #[test]
5254 fn test_lineage_lateral_view_columns_resolve_to_virtual_sources() {
5255 let cases = [
5256 (
5257 DialectType::Spark,
5258 "SELECT x AS out FROM t LATERAL VIEW EXPLODE(items) u AS x",
5259 ),
5260 (
5261 DialectType::Hive,
5262 "SELECT x AS out FROM t LATERAL VIEW EXPLODE(items) u AS x",
5263 ),
5264 ];
5265
5266 for (dialect, sql) in cases {
5267 let expr = parse_one(sql, dialect).unwrap_or_else(|e| panic!("parse {dialect:?}: {e}"));
5268 let node = lineage("out", &expr, Some(dialect), false)
5269 .unwrap_or_else(|e| panic!("lineage {dialect:?}: {e}"));
5270 let virtual_child = node
5271 .downstream
5272 .first()
5273 .unwrap_or_else(|| panic!("expected virtual child for {dialect:?}"));
5274
5275 assert_eq!(virtual_child.name, "_0.x");
5276 assert_eq!(virtual_child.source_name, "_0");
5277 assert_eq!(virtual_child.source_kind, SourceKind::Virtual);
5278 assert_eq!(virtual_child.source_alias.as_deref(), Some("u"));
5279
5280 let real_child = virtual_child
5281 .downstream
5282 .first()
5283 .unwrap_or_else(|| panic!("expected table dependency for {dialect:?}"));
5284 assert_eq!(real_child.name, "t.items");
5285 assert_eq!(real_child.source_kind, SourceKind::Table);
5286 }
5287 }
5288
5289 #[test]
5290 fn test_lineage_snowflake_lateral_flatten_is_virtual_source() {
5291 let expr = parse_one(
5292 "SELECT f.value AS value FROM raw_events, LATERAL FLATTEN(INPUT => payload:items) AS f",
5293 DialectType::Snowflake,
5294 )
5295 .expect("parse");
5296
5297 let node = lineage("value", &expr, Some(DialectType::Snowflake), false).expect("lineage");
5298 let virtual_child = node.downstream.first().expect("virtual flatten source");
5299 assert_eq!(virtual_child.name, "_0.value");
5300 assert_eq!(virtual_child.source_name, "_0");
5301 assert_eq!(virtual_child.source_kind, SourceKind::Virtual);
5302 assert_eq!(virtual_child.source_alias.as_deref(), Some("f"));
5303
5304 let real_child = virtual_child
5305 .downstream
5306 .first()
5307 .expect("FLATTEN input should depend on raw_events.payload");
5308 assert_eq!(real_child.name, "raw_events.payload");
5309 assert_eq!(real_child.source_kind, SourceKind::Table);
5310 }
5311
5312 #[test]
5313 fn test_lineage_with_schema_snowflake_datediff_date_part_issue_61() {
5314 let expr = parse_one(
5315 "SELECT DATEDIFF(day, date_utc, CURRENT_DATE()) AS recency FROM fact.some_daily_metrics",
5316 DialectType::Snowflake,
5317 )
5318 .expect("parse");
5319
5320 let mut schema = MappingSchema::with_dialect(DialectType::Snowflake);
5321 schema
5322 .add_table(
5323 "fact.some_daily_metrics",
5324 &[("date_utc".to_string(), DataType::Date)],
5325 None,
5326 )
5327 .expect("schema setup");
5328
5329 let node = lineage_with_schema(
5330 "recency",
5331 &expr,
5332 Some(&schema),
5333 Some(DialectType::Snowflake),
5334 false,
5335 )
5336 .expect("lineage_with_schema should not treat date part as a column");
5337
5338 let names = node.downstream_names();
5339 assert!(
5340 names.iter().any(|n| n == "some_daily_metrics.date_utc"),
5341 "Expected some_daily_metrics.date_utc in downstream, got: {:?}",
5342 names
5343 );
5344 assert!(
5345 !names.iter().any(|n| n.ends_with(".day") || n == "day"),
5346 "Did not expect date part to appear as lineage column, got: {:?}",
5347 names
5348 );
5349 }
5350
5351 #[test]
5352 fn test_snowflake_datediff_parses_to_typed_ast() {
5353 let expr = parse_one(
5354 "SELECT DATEDIFF(day, date_utc, CURRENT_DATE()) AS recency FROM fact.some_daily_metrics",
5355 DialectType::Snowflake,
5356 )
5357 .expect("parse");
5358
5359 match expr {
5360 Expression::Select(select) => match &select.expressions[0] {
5361 Expression::Alias(alias) => match &alias.this {
5362 Expression::DateDiff(f) => {
5363 assert_eq!(f.unit, Some(crate::expressions::IntervalUnit::Day));
5364 }
5365 other => panic!("expected DateDiff, got {other:?}"),
5366 },
5367 other => panic!("expected Alias, got {other:?}"),
5368 },
5369 other => panic!("expected Select, got {other:?}"),
5370 }
5371 }
5372
5373 #[test]
5374 fn test_lineage_with_schema_snowflake_dateadd_date_part_issue_followup() {
5375 let expr = parse_one(
5376 "SELECT DATEADD(day, 1, date_utc) AS next_day FROM fact.some_daily_metrics",
5377 DialectType::Snowflake,
5378 )
5379 .expect("parse");
5380
5381 let mut schema = MappingSchema::with_dialect(DialectType::Snowflake);
5382 schema
5383 .add_table(
5384 "fact.some_daily_metrics",
5385 &[("date_utc".to_string(), DataType::Date)],
5386 None,
5387 )
5388 .expect("schema setup");
5389
5390 let node = lineage_with_schema(
5391 "next_day",
5392 &expr,
5393 Some(&schema),
5394 Some(DialectType::Snowflake),
5395 false,
5396 )
5397 .expect("lineage_with_schema should not treat DATEADD date part as a column");
5398
5399 let names = node.downstream_names();
5400 assert!(
5401 names.iter().any(|n| n == "some_daily_metrics.date_utc"),
5402 "Expected some_daily_metrics.date_utc in downstream, got: {:?}",
5403 names
5404 );
5405 assert!(
5406 !names.iter().any(|n| n.ends_with(".day") || n == "day"),
5407 "Did not expect date part to appear as lineage column, got: {:?}",
5408 names
5409 );
5410 }
5411
5412 #[test]
5413 fn test_lineage_with_schema_snowflake_date_part_identifier_issue_followup() {
5414 let expr = parse_one(
5415 "SELECT DATE_PART(day, date_utc) AS day_part FROM fact.some_daily_metrics",
5416 DialectType::Snowflake,
5417 )
5418 .expect("parse");
5419
5420 let mut schema = MappingSchema::with_dialect(DialectType::Snowflake);
5421 schema
5422 .add_table(
5423 "fact.some_daily_metrics",
5424 &[("date_utc".to_string(), DataType::Date)],
5425 None,
5426 )
5427 .expect("schema setup");
5428
5429 let node = lineage_with_schema(
5430 "day_part",
5431 &expr,
5432 Some(&schema),
5433 Some(DialectType::Snowflake),
5434 false,
5435 )
5436 .expect("lineage_with_schema should not treat DATE_PART identifier as a column");
5437
5438 let names = node.downstream_names();
5439 assert!(
5440 names.iter().any(|n| n == "some_daily_metrics.date_utc"),
5441 "Expected some_daily_metrics.date_utc in downstream, got: {:?}",
5442 names
5443 );
5444 assert!(
5445 !names.iter().any(|n| n.ends_with(".day") || n == "day"),
5446 "Did not expect date part to appear as lineage column, got: {:?}",
5447 names
5448 );
5449 }
5450
5451 #[test]
5452 fn test_lineage_with_schema_snowflake_date_part_string_literal_control() {
5453 let expr = parse_one(
5454 "SELECT DATE_PART('day', date_utc) AS day_part FROM fact.some_daily_metrics",
5455 DialectType::Snowflake,
5456 )
5457 .expect("parse");
5458
5459 let mut schema = MappingSchema::with_dialect(DialectType::Snowflake);
5460 schema
5461 .add_table(
5462 "fact.some_daily_metrics",
5463 &[("date_utc".to_string(), DataType::Date)],
5464 None,
5465 )
5466 .expect("schema setup");
5467
5468 let node = lineage_with_schema(
5469 "day_part",
5470 &expr,
5471 Some(&schema),
5472 Some(DialectType::Snowflake),
5473 false,
5474 )
5475 .expect("quoted DATE_PART should continue to work");
5476
5477 let names = node.downstream_names();
5478 assert!(
5479 names.iter().any(|n| n == "some_daily_metrics.date_utc"),
5480 "Expected some_daily_metrics.date_utc in downstream, got: {:?}",
5481 names
5482 );
5483 }
5484
5485 #[test]
5486 fn test_snowflake_dateadd_date_part_identifier_stays_generic_function() {
5487 let expr = parse_one(
5488 "SELECT DATEADD(day, 1, date_utc) AS next_day FROM fact.some_daily_metrics",
5489 DialectType::Snowflake,
5490 )
5491 .expect("parse");
5492
5493 match expr {
5494 Expression::Select(select) => match &select.expressions[0] {
5495 Expression::Alias(alias) => match &alias.this {
5496 Expression::Function(f) => {
5497 assert_eq!(f.name.to_uppercase(), "DATEADD");
5498 assert!(matches!(&f.args[0], Expression::Var(v) if v.this == "day"));
5499 }
5500 other => panic!("expected generic DATEADD function, got {other:?}"),
5501 },
5502 other => panic!("expected Alias, got {other:?}"),
5503 },
5504 other => panic!("expected Select, got {other:?}"),
5505 }
5506 }
5507
5508 #[test]
5509 fn test_snowflake_date_part_identifier_stays_generic_function_with_var_arg() {
5510 let expr = parse_one(
5511 "SELECT DATE_PART(day, date_utc) AS day_part FROM fact.some_daily_metrics",
5512 DialectType::Snowflake,
5513 )
5514 .expect("parse");
5515
5516 match expr {
5517 Expression::Select(select) => match &select.expressions[0] {
5518 Expression::Alias(alias) => match &alias.this {
5519 Expression::Function(f) => {
5520 assert_eq!(f.name.to_uppercase(), "DATE_PART");
5521 assert!(matches!(&f.args[0], Expression::Var(v) if v.this == "day"));
5522 }
5523 other => panic!("expected generic DATE_PART function, got {other:?}"),
5524 },
5525 other => panic!("expected Alias, got {other:?}"),
5526 },
5527 other => panic!("expected Select, got {other:?}"),
5528 }
5529 }
5530
5531 #[test]
5532 fn test_snowflake_date_part_string_literal_stays_generic_function() {
5533 let expr = parse_one(
5534 "SELECT DATE_PART('day', date_utc) AS day_part FROM fact.some_daily_metrics",
5535 DialectType::Snowflake,
5536 )
5537 .expect("parse");
5538
5539 match expr {
5540 Expression::Select(select) => match &select.expressions[0] {
5541 Expression::Alias(alias) => match &alias.this {
5542 Expression::Function(f) => {
5543 assert_eq!(f.name.to_uppercase(), "DATE_PART");
5544 }
5545 other => panic!("expected generic DATE_PART function, got {other:?}"),
5546 },
5547 other => panic!("expected Alias, got {other:?}"),
5548 },
5549 other => panic!("expected Select, got {other:?}"),
5550 }
5551 }
5552
5553 #[test]
5554 fn test_lineage_join() {
5555 let expr = parse("SELECT t.a, s.b FROM t JOIN s ON t.id = s.id");
5556
5557 let node_a = lineage("a", &expr, None, false).unwrap();
5558 let names_a = node_a.downstream_names();
5559 assert!(
5560 names_a.iter().any(|n| n == "t.a"),
5561 "Expected t.a, got: {:?}",
5562 names_a
5563 );
5564
5565 let node_b = lineage("b", &expr, None, false).unwrap();
5566 let names_b = node_b.downstream_names();
5567 assert!(
5568 names_b.iter().any(|n| n == "s.b"),
5569 "Expected s.b, got: {:?}",
5570 names_b
5571 );
5572 }
5573
5574 #[test]
5575 fn test_lineage_alias_leaf_has_resolved_source_name() {
5576 let expr = parse("SELECT t1.col1 FROM table1 t1 JOIN table2 t2 ON t1.id = t2.id");
5577 let node = lineage("col1", &expr, None, false).unwrap();
5578
5579 let names = node.downstream_names();
5581 assert!(
5582 names.iter().any(|n| n == "t1.col1"),
5583 "Expected aliased column edge t1.col1, got: {:?}",
5584 names
5585 );
5586
5587 let leaf = node
5589 .downstream
5590 .iter()
5591 .find(|n| n.name == "t1.col1")
5592 .expect("Expected t1.col1 leaf");
5593 assert_eq!(leaf.source_name, "table1");
5594 match &leaf.source {
5595 Expression::Table(table) => assert_eq!(table.name.name, "table1"),
5596 _ => panic!("Expected leaf source to be a table expression"),
5597 }
5598 }
5599
5600 #[test]
5601 fn test_lineage_derived_table() {
5602 let expr = parse("SELECT x.a FROM (SELECT a FROM t) AS x");
5603 let node = lineage("a", &expr, None, false).unwrap();
5604
5605 assert_eq!(node.name, "a");
5606 let all_names: Vec<_> = node.walk().map(|n| n.name.clone()).collect();
5608 assert!(
5609 all_names.iter().any(|n| n == "t.a"),
5610 "Expected to trace through derived table to t.a, got: {:?}",
5611 all_names
5612 );
5613 }
5614
5615 #[test]
5616 fn test_lineage_cte() {
5617 let expr = parse("WITH cte AS (SELECT a FROM t) SELECT a FROM cte");
5618 let node = lineage("a", &expr, None, false).unwrap();
5619
5620 assert_eq!(node.name, "a");
5621 let all_names: Vec<_> = node.walk().map(|n| n.name.clone()).collect();
5622 assert!(
5623 all_names.iter().any(|n| n == "t.a"),
5624 "Expected to trace through CTE to t.a, got: {:?}",
5625 all_names
5626 );
5627 }
5628
5629 #[test]
5630 fn test_lineage_union() {
5631 let expr = parse("SELECT a FROM t1 UNION SELECT a FROM t2");
5632 let node = lineage("a", &expr, None, false).unwrap();
5633
5634 assert_eq!(node.name, "a");
5635 assert_eq!(
5637 node.downstream.len(),
5638 2,
5639 "Expected 2 branches for UNION, got {}",
5640 node.downstream.len()
5641 );
5642 }
5643
5644 #[test]
5645 fn test_lineage_cte_union() {
5646 let expr = parse("WITH cte AS (SELECT a FROM t1 UNION SELECT a FROM t2) SELECT a FROM cte");
5647 let node = lineage("a", &expr, None, false).unwrap();
5648
5649 let all_names: Vec<_> = node.walk().map(|n| n.name.clone()).collect();
5651 assert!(
5652 all_names.len() >= 3,
5653 "Expected at least 3 nodes for CTE with UNION, got: {:?}",
5654 all_names
5655 );
5656 }
5657
5658 #[test]
5659 fn test_lineage_star() {
5660 let expr = parse("SELECT * FROM t");
5661 let node = lineage("*", &expr, None, false).unwrap();
5662
5663 assert_eq!(node.name, "*");
5664 assert!(
5666 !node.downstream.is_empty(),
5667 "Star should produce downstream nodes"
5668 );
5669 }
5670
5671 #[test]
5672 fn test_lineage_subquery_in_select() {
5673 let expr = parse("SELECT (SELECT MAX(b) FROM s) AS x FROM t");
5674 let node = lineage("x", &expr, None, false).unwrap();
5675
5676 assert_eq!(node.name, "x");
5677 let all_names: Vec<_> = node.walk().map(|n| n.name.clone()).collect();
5679 assert!(
5680 all_names.len() >= 2,
5681 "Expected tracing into scalar subquery, got: {:?}",
5682 all_names
5683 );
5684 }
5685
5686 #[test]
5687 fn test_lineage_multiple_columns() {
5688 let expr = parse("SELECT a, b FROM t");
5689
5690 let node_a = lineage("a", &expr, None, false).unwrap();
5691 let node_b = lineage("b", &expr, None, false).unwrap();
5692
5693 assert_eq!(node_a.name, "a");
5694 assert_eq!(node_b.name, "b");
5695
5696 let names_a = node_a.downstream_names();
5698 let names_b = node_b.downstream_names();
5699 assert!(names_a.iter().any(|n| n == "t.a"));
5700 assert!(names_b.iter().any(|n| n == "t.b"));
5701 }
5702
5703 #[test]
5704 fn test_get_source_tables() {
5705 let expr = parse("SELECT t.a, s.b FROM t JOIN s ON t.id = s.id");
5706 let node = lineage("a", &expr, None, false).unwrap();
5707
5708 let tables = get_source_tables(&node);
5709 assert!(
5710 tables.contains("t"),
5711 "Expected source table 't', got: {:?}",
5712 tables
5713 );
5714 }
5715
5716 #[test]
5717 fn test_lineage_column_not_found() {
5718 let expr = parse("SELECT a FROM t");
5719 let result = lineage("nonexistent", &expr, None, false);
5720 assert!(result.is_err());
5721 }
5722
5723 #[test]
5724 fn test_lineage_nested_cte() {
5725 let expr = parse(
5726 "WITH cte1 AS (SELECT a FROM t), \
5727 cte2 AS (SELECT a FROM cte1) \
5728 SELECT a FROM cte2",
5729 );
5730 let node = lineage("a", &expr, None, false).unwrap();
5731
5732 let all_names: Vec<_> = node.walk().map(|n| n.name.clone()).collect();
5734 assert!(
5735 all_names.len() >= 3,
5736 "Expected to trace through nested CTEs, got: {:?}",
5737 all_names
5738 );
5739 }
5740
5741 #[test]
5742 fn test_lineage_deeply_nested_cte_reaches_base_table() {
5743 let expr = parse(
5744 "WITH outer_cte AS (\
5745 WITH middle_cte AS (\
5746 WITH inner_cte AS (SELECT x AS col FROM base_table) \
5747 SELECT col FROM inner_cte\
5748 ) SELECT col FROM middle_cte\
5749 ) SELECT col FROM outer_cte",
5750 );
5751 let node = lineage("col", &expr, None, false).unwrap();
5752
5753 assert_lineage_contains(&node, "base_table.x");
5754 for cte_name in ["outer_cte", "middle_cte", "inner_cte"] {
5755 assert!(
5756 node.walk().any(|child| child.source_name == cte_name),
5757 "expected lineage to include CTE {cte_name}, got {:?}",
5758 lineage_names(&node)
5759 );
5760 }
5761 }
5762
5763 #[test]
5764 fn test_lineage_reused_nested_cte_traces_each_reference() {
5765 let expr = parse(
5766 "WITH shared AS (\
5767 WITH nested AS (SELECT x AS col FROM base_table) \
5768 SELECT col FROM nested\
5769 ) \
5770 SELECT s0.col + s1.col + s2.col AS total \
5771 FROM shared AS s0 \
5772 CROSS JOIN shared AS s1 \
5773 CROSS JOIN shared AS s2",
5774 );
5775 let node = lineage("total", &expr, None, false).unwrap();
5776
5777 let base_references = node
5778 .walk()
5779 .filter(|child| child.name == "base_table.x")
5780 .count();
5781 assert_eq!(
5782 base_references,
5783 3,
5784 "each shared CTE reference should reach base_table.x: {:?}",
5785 lineage_names(&node)
5786 );
5787 }
5788
5789 #[test]
5790 fn test_trim_selects_true() {
5791 let expr = parse("SELECT a, b, c FROM t");
5792 let node = lineage("a", &expr, None, true).unwrap();
5793
5794 if let Expression::Select(select) = &node.source {
5796 assert_eq!(
5797 select.expressions.len(),
5798 1,
5799 "Trimmed source should have 1 expression, got {}",
5800 select.expressions.len()
5801 );
5802 } else {
5803 panic!("Expected Select source");
5804 }
5805 }
5806
5807 #[test]
5808 fn test_trim_selects_false() {
5809 let expr = parse("SELECT a, b, c FROM t");
5810 let node = lineage("a", &expr, None, false).unwrap();
5811
5812 if let Expression::Select(select) = &node.source {
5814 assert_eq!(
5815 select.expressions.len(),
5816 3,
5817 "Untrimmed source should have 3 expressions"
5818 );
5819 } else {
5820 panic!("Expected Select source");
5821 }
5822 }
5823
5824 #[test]
5825 fn test_lineage_expression_in_select() {
5826 let expr = parse("SELECT a + b AS c FROM t");
5827 let node = lineage("c", &expr, None, false).unwrap();
5828
5829 let all_names: Vec<_> = node.walk().map(|n| n.name.clone()).collect();
5831 assert!(
5832 all_names.len() >= 3,
5833 "Expected to trace a + b to both columns, got: {:?}",
5834 all_names
5835 );
5836 }
5837
5838 #[test]
5839 fn test_set_operation_by_index() {
5840 let expr = parse("SELECT a FROM t1 UNION SELECT b FROM t2");
5841
5842 let node = lineage("a", &expr, None, false).unwrap();
5844
5845 assert_eq!(node.downstream.len(), 2);
5847 }
5848
5849 #[test]
5850 fn test_issue_384_output_columns_preserve_unknown_positions() {
5851 let expr = parse("SELECT a, 1, *, tail FROM unknown_source");
5852 let output = output_columns(&expr, None).expect("output columns");
5853
5854 assert!(!output.ordinal_complete);
5855 assert_eq!(
5856 output.columns,
5857 vec![
5858 OutputColumn::Named {
5859 name: "a".to_string(),
5860 ordinal: Some(0),
5861 },
5862 OutputColumn::Unnamed { ordinal: Some(1) },
5863 OutputColumn::Wildcard {
5864 qualifier: None,
5865 start_ordinal: Some(2),
5866 },
5867 OutputColumn::Named {
5868 name: "tail".to_string(),
5869 ordinal: None,
5870 },
5871 ]
5872 );
5873 }
5874
5875 #[test]
5876 fn test_issue_384_output_columns_use_leftmost_set_operation_branch() {
5877 let expr = parse(
5878 "SELECT * FROM unknown_source UNION ALL \
5879 SELECT known_first AS x, known_second AS y FROM known_source",
5880 );
5881 let output = output_columns(&expr, None).expect("output columns");
5882
5883 assert!(!output.ordinal_complete);
5884 assert_eq!(
5885 output.columns,
5886 vec![OutputColumn::Wildcard {
5887 qualifier: None,
5888 start_ordinal: Some(0),
5889 }]
5890 );
5891 }
5892
5893 #[test]
5894 fn test_issue_384_schema_expands_output_wildcard() {
5895 let expr = parse("SELECT * FROM unknown_source");
5896 let mut schema = MappingSchema::new();
5897 schema
5898 .add_table(
5899 "unknown_source",
5900 &[
5901 ("first_col".to_string(), DataType::Text),
5902 ("second_col".to_string(), DataType::Text),
5903 ],
5904 None,
5905 )
5906 .expect("schema setup");
5907
5908 let output = output_columns_with_schema(&expr, Some(&schema), None)
5909 .expect("schema-aware output columns");
5910 assert!(output.ordinal_complete);
5911 assert_eq!(
5912 output.columns,
5913 vec![
5914 OutputColumn::Named {
5915 name: "first_col".to_string(),
5916 ordinal: Some(0),
5917 },
5918 OutputColumn::Named {
5919 name: "second_col".to_string(),
5920 ordinal: Some(1),
5921 },
5922 ]
5923 );
5924 }
5925
5926 #[test]
5927 fn test_issue_411_duckdb_and_snowflake_union_by_name_layout_and_lineage() {
5928 let sql = "SELECT 1 AS left_value UNION ALL BY NAME SELECT 2 AS right_value";
5929
5930 for dialect in [DialectType::DuckDB, DialectType::Snowflake] {
5931 let expr = parse_dialect(sql, dialect);
5932 let output = output_columns(&expr, Some(dialect)).expect("output columns");
5933 assert_eq!(
5934 output.columns,
5935 vec![
5936 OutputColumn::Named {
5937 name: "left_value".to_string(),
5938 ordinal: Some(0),
5939 },
5940 OutputColumn::Named {
5941 name: "right_value".to_string(),
5942 ordinal: Some(1),
5943 },
5944 ],
5945 "unexpected output layout for {dialect:?}"
5946 );
5947
5948 let left = lineage_at(0, &expr, Some(dialect), false).expect("left lineage");
5949 assert_eq!(
5950 left.downstream
5951 .iter()
5952 .map(|node| node.name.as_str())
5953 .collect::<Vec<_>>(),
5954 vec!["left_value"]
5955 );
5956 assert_eq!(
5957 left.downstream[0].set_branch,
5958 Some(SetBranch {
5959 operator: SetOperator::Union,
5960 ordinal: 0,
5961 all: true,
5962 })
5963 );
5964
5965 let right = lineage_at(1, &expr, Some(dialect), false).expect("right lineage");
5966 assert_eq!(
5967 right
5968 .downstream
5969 .iter()
5970 .map(|node| node.name.as_str())
5971 .collect::<Vec<_>>(),
5972 vec!["right_value"]
5973 );
5974 assert_eq!(
5975 right.downstream[0].set_branch,
5976 Some(SetBranch {
5977 operator: SetOperator::Union,
5978 ordinal: 1,
5979 all: true,
5980 })
5981 );
5982 }
5983 }
5984
5985 #[test]
5986 fn test_issue_411_bigquery_strict_by_name_reorders_lineage() {
5987 let dialect = DialectType::BigQuery;
5988 let expr = parse_dialect(
5989 "SELECT 1 AS a, 2 AS b UNION ALL BY NAME SELECT 3 AS b, 4 AS a",
5990 dialect,
5991 );
5992 let output = output_columns(&expr, Some(dialect)).expect("output columns");
5993 assert_eq!(
5994 output.columns,
5995 vec![
5996 OutputColumn::Named {
5997 name: "a".to_string(),
5998 ordinal: Some(0),
5999 },
6000 OutputColumn::Named {
6001 name: "b".to_string(),
6002 ordinal: Some(1),
6003 },
6004 ]
6005 );
6006
6007 for (ordinal, expected_name) in [(0, "a"), (1, "b")] {
6008 let node = lineage_at(ordinal, &expr, Some(dialect), false).expect("lineage");
6009 assert_eq!(
6010 node.downstream
6011 .iter()
6012 .map(|child| child.name.as_str())
6013 .collect::<Vec<_>>(),
6014 vec![expected_name, expected_name]
6015 );
6016 }
6017 }
6018
6019 #[test]
6020 fn test_issue_411_bigquery_by_name_modes_and_explicit_order() {
6021 let dialect = DialectType::BigQuery;
6022 for (sql, expected) in [
6023 (
6024 "SELECT 1 AS a, 2 AS b INNER UNION ALL BY NAME SELECT 3 AS b, 4 AS c",
6025 vec!["b"],
6026 ),
6027 (
6028 "SELECT 1 AS a, 2 AS b LEFT OUTER UNION ALL BY NAME SELECT 3 AS b, 4 AS c",
6029 vec!["a", "b"],
6030 ),
6031 (
6032 "SELECT 1 AS a, 2 AS b FULL OUTER UNION ALL BY NAME SELECT 3 AS b, 4 AS c",
6033 vec!["a", "b", "c"],
6034 ),
6035 (
6036 "SELECT 1 AS a, 2 AS b FULL OUTER UNION ALL BY NAME ON (c, a) SELECT 3 AS b, 4 AS c",
6037 vec!["c", "a"],
6038 ),
6039 (
6040 "SELECT 1 AS a, 2 AS b INTERSECT DISTINCT BY NAME SELECT 3 AS b, 4 AS a",
6041 vec!["a", "b"],
6042 ),
6043 (
6044 "SELECT 1 AS a, 2 AS b EXCEPT DISTINCT BY NAME SELECT 3 AS b, 4 AS a",
6045 vec!["a", "b"],
6046 ),
6047 ] {
6048 let expr = parse_dialect(sql, dialect);
6049 let output = output_columns(&expr, Some(dialect)).expect("output columns");
6050 assert_eq!(
6051 output
6052 .columns
6053 .iter()
6054 .filter_map(|column| match column {
6055 OutputColumn::Named { name, .. } => Some(name.as_str()),
6056 _ => None,
6057 })
6058 .collect::<Vec<_>>(),
6059 expected,
6060 "unexpected output layout for {sql}"
6061 );
6062 }
6063 }
6064
6065 #[test]
6066 fn test_issue_411_name_matching_uses_dialect_identifier_rules() {
6067 let sql = "SELECT 1 AS \"lower\" UNION ALL BY NAME SELECT 2 AS lower";
6068
6069 let duckdb = parse_dialect(sql, DialectType::DuckDB);
6070 assert_eq!(
6071 output_columns(&duckdb, Some(DialectType::DuckDB))
6072 .expect("DuckDB output")
6073 .columns
6074 .len(),
6075 1,
6076 "DuckDB quoted identifiers are case-insensitive"
6077 );
6078
6079 let snowflake = parse_dialect(sql, DialectType::Snowflake);
6080 assert_eq!(
6081 output_columns(&snowflake, Some(DialectType::Snowflake))
6082 .expect("Snowflake output")
6083 .columns
6084 .len(),
6085 2,
6086 "Snowflake quoted lowercase and unquoted uppercase identifiers differ"
6087 );
6088 }
6089
6090 #[test]
6091 fn test_issue_383_lineage_at_traces_resolvable_set_operation_branch() {
6092 let expr = parse(
6093 "SELECT * FROM unknown_source UNION ALL \
6094 SELECT known_first AS x, known_second AS y FROM known_source",
6095 );
6096 let node = lineage_at(1, &expr, None, false).expect("ordinal lineage");
6097
6098 assert_lineage_contains(&node, "known_source.known_second");
6099 assert_eq!(node.downstream.len(), 1);
6100 assert_eq!(
6101 node.downstream[0].set_branch,
6102 Some(SetBranch {
6103 operator: SetOperator::Union,
6104 ordinal: 1,
6105 all: true,
6106 })
6107 );
6108
6109 let expr = parse(
6110 "SELECT known_first, known_second FROM known_source EXCEPT \
6111 SELECT * FROM unknown_source",
6112 );
6113 let node = lineage_at(1, &expr, None, false).expect("left-only ordinal lineage");
6114
6115 assert_lineage_contains(&node, "known_source.known_second");
6116 assert_eq!(node.downstream.len(), 1);
6117 assert_eq!(
6118 node.downstream[0].set_branch,
6119 Some(SetBranch {
6120 operator: SetOperator::Except,
6121 ordinal: 0,
6122 all: false,
6123 })
6124 );
6125 }
6126
6127 #[test]
6128 fn test_issue_383_unresolved_wildcard_does_not_shift_ordinal() {
6129 let expr = parse(
6130 "SELECT *, tail FROM unknown_source UNION ALL \
6131 SELECT known_first, known_second FROM known_source",
6132 );
6133 let node = lineage_at(1, &expr, None, false).expect("partial ordinal lineage");
6134 let names = lineage_names(&node);
6135
6136 assert!(names.iter().any(|name| name == "known_source.known_second"));
6137 assert!(!names
6138 .iter()
6139 .any(|name| name.ends_with(".tail") || name == "tail"));
6140 }
6141
6142 #[test]
6143 fn test_issue_383_lineage_at_ignores_branch_output_names() {
6144 let expr = parse(
6145 "SELECT left_value AS left_name FROM left_source UNION ALL \
6146 SELECT right_value AS right_name FROM right_source",
6147 );
6148 let node = lineage_at(0, &expr, None, false).expect("ordinal lineage");
6149
6150 assert_lineage_contains(&node, "left_source.left_value");
6151 assert_lineage_contains(&node, "right_source.right_value");
6152 }
6153
6154 #[test]
6155 fn test_issue_383_lineage_at_supports_all_set_operations() {
6156 for (sql_operator, operator, all) in [
6157 ("UNION ALL", SetOperator::Union, true),
6158 ("INTERSECT", SetOperator::Intersect, false),
6159 ("EXCEPT", SetOperator::Except, false),
6160 ] {
6161 let expr = parse(&format!(
6162 "SELECT left_value AS left_name FROM left_source {sql_operator} \
6163 SELECT right_value AS right_name FROM right_source"
6164 ));
6165 let node = lineage_at(0, &expr, None, false).expect("ordinal lineage");
6166 assert_eq!(
6167 node.downstream.len(),
6168 2,
6169 "expected both branches for {sql_operator}"
6170 );
6171 assert_eq!(
6172 node.downstream
6173 .iter()
6174 .map(|child| child.set_branch)
6175 .collect::<Vec<_>>(),
6176 vec![
6177 Some(SetBranch {
6178 operator,
6179 ordinal: 0,
6180 all,
6181 }),
6182 Some(SetBranch {
6183 operator,
6184 ordinal: 1,
6185 all,
6186 }),
6187 ]
6188 );
6189 }
6190 }
6191
6192 #[test]
6193 fn test_issue_383_lineage_at_with_schema_expands_wildcard() {
6194 let expr = parse(
6195 "SELECT * FROM unknown_source UNION ALL \
6196 SELECT known_first, known_second FROM known_source",
6197 );
6198 let mut schema = MappingSchema::new();
6199 schema
6200 .add_table(
6201 "unknown_source",
6202 &[
6203 ("first_col".to_string(), DataType::Text),
6204 ("second_col".to_string(), DataType::Text),
6205 ],
6206 None,
6207 )
6208 .expect("schema setup");
6209 schema
6210 .add_table(
6211 "known_source",
6212 &[
6213 ("known_first".to_string(), DataType::Text),
6214 ("known_second".to_string(), DataType::Text),
6215 ],
6216 None,
6217 )
6218 .expect("schema setup");
6219
6220 let node = lineage_at_with_schema(1, &expr, Some(&schema), None, false)
6221 .expect("schema-aware ordinal lineage");
6222 assert_lineage_contains(&node, "unknown_source.second_col");
6223 assert_lineage_contains(&node, "known_source.known_second");
6224 }
6225
6226 #[test]
6227 fn test_issue_385_structured_lineage_resolution_errors() {
6228 let out_of_range = lineage_at(1, &parse("SELECT a FROM t"), None, false)
6229 .expect_err("ordinal should be out of range");
6230 assert!(matches!(
6231 out_of_range,
6232 Error::ColumnResolution {
6233 target: ColumnResolutionTarget::Ordinal { ordinal: 1 },
6234 reason: ColumnResolutionReason::NotFound,
6235 }
6236 ));
6237
6238 let indeterminate = lineage(
6239 "tail",
6240 &parse(
6241 "SELECT *, tail FROM unknown_source UNION ALL \
6242 SELECT known_first, known_second FROM known_source",
6243 ),
6244 None,
6245 false,
6246 )
6247 .expect_err("tail ordinal should be indeterminate");
6248 assert!(matches!(
6249 indeterminate,
6250 Error::ColumnResolution {
6251 target: ColumnResolutionTarget::Name { ref name },
6252 reason: ColumnResolutionReason::Indeterminate,
6253 } if name == "tail"
6254 ));
6255
6256 let ambiguous = lineage(
6257 "duplicate",
6258 &parse("SELECT a AS duplicate, b AS duplicate FROM t"),
6259 None,
6260 false,
6261 )
6262 .expect_err("duplicate output name should be ambiguous");
6263 assert!(matches!(
6264 ambiguous,
6265 Error::ColumnResolution {
6266 target: ColumnResolutionTarget::Name { ref name },
6267 reason: ColumnResolutionReason::Ambiguous,
6268 } if name == "duplicate"
6269 ));
6270 }
6271
6272 fn print_node(node: &LineageNode, indent: usize) {
6275 let pad = " ".repeat(indent);
6276 println!(
6277 "{pad}name={:?} source_name={:?}",
6278 node.name, node.source_name
6279 );
6280 for child in &node.downstream {
6281 print_node(child, indent + 1);
6282 }
6283 }
6284
6285 #[test]
6286 fn test_issue18_repro() {
6287 let query = "SELECT UPPER(name) as upper_name FROM users";
6289 println!("Query: {query}\n");
6290
6291 let dialect = crate::dialects::Dialect::get(DialectType::BigQuery);
6292 let exprs = dialect.parse(query).unwrap();
6293 let expr = &exprs[0];
6294
6295 let node = lineage("upper_name", expr, Some(DialectType::BigQuery), false).unwrap();
6296 println!("lineage(\"upper_name\"):");
6297 print_node(&node, 1);
6298
6299 let names = node.downstream_names();
6300 assert!(
6301 names.iter().any(|n| n == "users.name"),
6302 "Expected users.name in downstream, got: {:?}",
6303 names
6304 );
6305 }
6306
6307 #[test]
6308 fn test_lineage_bigquery_safe_namespace_issue207() {
6309 let query = r#"
6310WITH import_cte AS (
6311 SELECT timestamp, data, operation
6312 FROM `project`.`dataset`.`source_table`
6313),
6314transform_cte AS (
6315 SELECT
6316 timestamp,
6317 SAFE.PARSE_JSON(data) AS json_data
6318 FROM import_cte
6319)
6320SELECT json_data FROM transform_cte
6321"#;
6322 let expr = parse_one(query, DialectType::BigQuery).expect("parse");
6323 let node = lineage("json_data", &expr, Some(DialectType::BigQuery), false)
6324 .expect("lineage should resolve SAFE.PARSE_JSON arguments");
6325 let names: Vec<_> = node.walk().map(|n| n.name.clone()).collect();
6326
6327 assert!(
6328 names.iter().any(|name| name == "source_table.data"),
6329 "expected source_table.data in lineage, got {names:?}"
6330 );
6331 assert!(
6332 !names
6333 .iter()
6334 .any(|name| name.eq_ignore_ascii_case("import_cte.safe")),
6335 "did not expect SAFE namespace receiver in lineage, got {names:?}"
6336 );
6337 }
6338
6339 #[test]
6340 fn test_lineage_bigquery_safe_namespace_method_call_guard() {
6341 let expr = parse("SELECT SAFE.PARSE_JSON(data) AS json_data FROM t");
6342 let node = lineage("json_data", &expr, Some(DialectType::BigQuery), false)
6343 .expect("lineage should resolve SAFE.PARSE_JSON arguments");
6344 let names: Vec<_> = node.walk().map(|n| n.name.clone()).collect();
6345
6346 assert!(
6347 names.iter().any(|name| name == "t.data"),
6348 "expected t.data in lineage, got {names:?}"
6349 );
6350 assert!(
6351 !names.iter().any(|name| name.eq_ignore_ascii_case("t.safe")),
6352 "did not expect SAFE namespace receiver in lineage, got {names:?}"
6353 );
6354 }
6355
6356 #[test]
6357 fn test_lineage_method_call_receiver_control() {
6358 let expr = parse("SELECT obj.METHOD(arg) AS out FROM t");
6359 let node = lineage("out", &expr, None, false).expect("lineage");
6360 let names: Vec<_> = node.walk().map(|n| n.name.clone()).collect();
6361
6362 assert!(
6363 names.iter().any(|name| name == "t.obj"),
6364 "expected ordinary method receiver to remain in lineage, got {names:?}"
6365 );
6366 assert!(
6367 names.iter().any(|name| name == "t.arg"),
6368 "expected method argument in lineage, got {names:?}"
6369 );
6370 }
6371
6372 #[test]
6373 fn test_lineage_upper_function() {
6374 let expr = parse("SELECT UPPER(name) AS upper_name FROM users");
6375 let node = lineage("upper_name", &expr, None, false).unwrap();
6376
6377 let names = node.downstream_names();
6378 assert!(
6379 names.iter().any(|n| n == "users.name"),
6380 "Expected users.name in downstream, got: {:?}",
6381 names
6382 );
6383 }
6384
6385 #[test]
6386 fn test_lineage_round_function() {
6387 let expr = parse("SELECT ROUND(price, 2) AS rounded FROM products");
6388 let node = lineage("rounded", &expr, None, false).unwrap();
6389
6390 let names = node.downstream_names();
6391 assert!(
6392 names.iter().any(|n| n == "products.price"),
6393 "Expected products.price in downstream, got: {:?}",
6394 names
6395 );
6396 }
6397
6398 #[test]
6399 fn test_lineage_coalesce_function() {
6400 let expr = parse("SELECT COALESCE(a, b) AS val FROM t");
6401 let node = lineage("val", &expr, None, false).unwrap();
6402
6403 let names = node.downstream_names();
6404 assert!(
6405 names.iter().any(|n| n == "t.a"),
6406 "Expected t.a in downstream, got: {:?}",
6407 names
6408 );
6409 assert!(
6410 names.iter().any(|n| n == "t.b"),
6411 "Expected t.b in downstream, got: {:?}",
6412 names
6413 );
6414 }
6415
6416 #[test]
6417 fn test_lineage_count_function() {
6418 let expr = parse("SELECT COUNT(id) AS cnt FROM t");
6419 let node = lineage("cnt", &expr, None, false).unwrap();
6420
6421 let names = node.downstream_names();
6422 assert!(
6423 names.iter().any(|n| n == "t.id"),
6424 "Expected t.id in downstream, got: {:?}",
6425 names
6426 );
6427 }
6428
6429 #[test]
6430 fn test_lineage_sum_function() {
6431 let expr = parse("SELECT SUM(amount) AS total FROM t");
6432 let node = lineage("total", &expr, None, false).unwrap();
6433
6434 let names = node.downstream_names();
6435 assert!(
6436 names.iter().any(|n| n == "t.amount"),
6437 "Expected t.amount in downstream, got: {:?}",
6438 names
6439 );
6440 }
6441
6442 #[test]
6443 fn test_lineage_case_with_nested_functions() {
6444 let expr =
6445 parse("SELECT CASE WHEN x > 0 THEN UPPER(name) ELSE LOWER(name) END AS result FROM t");
6446 let node = lineage("result", &expr, None, false).unwrap();
6447
6448 let names = node.downstream_names();
6449 assert!(
6450 names.iter().any(|n| n == "t.x"),
6451 "Expected t.x in downstream, got: {:?}",
6452 names
6453 );
6454 assert!(
6455 names.iter().any(|n| n == "t.name"),
6456 "Expected t.name in downstream, got: {:?}",
6457 names
6458 );
6459 }
6460
6461 #[test]
6462 fn test_lineage_substring_function() {
6463 let expr = parse("SELECT SUBSTRING(name, 1, 3) AS short FROM t");
6464 let node = lineage("short", &expr, None, false).unwrap();
6465
6466 let names = node.downstream_names();
6467 assert!(
6468 names.iter().any(|n| n == "t.name"),
6469 "Expected t.name in downstream, got: {:?}",
6470 names
6471 );
6472 }
6473
6474 #[test]
6477 fn test_lineage_cte_select_star() {
6478 let expr = parse("WITH y AS (SELECT * FROM x) SELECT a FROM y");
6482 let node = lineage("a", &expr, None, false).unwrap();
6483
6484 assert_eq!(node.name, "a");
6485 assert!(
6488 !node.downstream.is_empty(),
6489 "Expected downstream nodes tracing through CTE, got none"
6490 );
6491 }
6492
6493 #[test]
6494 fn test_lineage_schema_less_cte_star_passthrough_resolves_base_column() {
6495 let expr = parse("WITH c AS (SELECT * FROM t) SELECT c.x FROM c");
6496 let node = lineage("x", &expr, None, false).unwrap();
6497
6498 let all_names: Vec<_> = node.walk().map(|n| n.name.clone()).collect();
6499 assert!(
6500 all_names.iter().any(|name| name == "t.x"),
6501 "Expected schema-less CTE star passthrough to reach t.x, got: {:?}",
6502 all_names
6503 );
6504
6505 let cte_node = node
6506 .walk()
6507 .find(|child| child.source_kind == SourceKind::Cte && child.source_name == "c")
6508 .expect("expected CTE hop with source_name c");
6509 assert_eq!(cte_node.source_kind, SourceKind::Cte);
6510 assert_eq!(cte_node.source_name, "c");
6511 }
6512
6513 #[test]
6514 fn test_lineage_schema_less_cte_star_passthrough_with_aggregation() {
6515 let expr = parse(
6516 "WITH c AS (SELECT * FROM t) \
6517 SELECT SUM(c.x) AS s FROM c GROUP BY 1",
6518 );
6519 let node = lineage("s", &expr, None, false).unwrap();
6520
6521 let all_names: Vec<_> = node.walk().map(|n| n.name.clone()).collect();
6522 assert!(
6523 all_names.iter().any(|name| name == "t.x"),
6524 "Expected aggregate over CTE star passthrough to reach t.x, got: {:?}",
6525 all_names
6526 );
6527 }
6528
6529 #[test]
6530 fn test_lineage_schema_less_cte_star_passthrough_with_join_and_alias() {
6531 let expr = parse(
6532 "WITH a AS (SELECT * FROM t1), b AS (SELECT * FROM t2) \
6533 SELECT SUM(b.x) AS s FROM a LEFT JOIN b ON b.id = a.id GROUP BY a.k",
6534 );
6535 let node = lineage("s", &expr, None, false).unwrap();
6536
6537 let all_names: Vec<_> = node.walk().map(|n| n.name.clone()).collect();
6538 assert!(
6539 all_names.iter().any(|name| name == "t2.x"),
6540 "Expected joined CTE star passthrough to reach t2.x, got: {:?}",
6541 all_names
6542 );
6543 }
6544
6545 #[test]
6546 fn test_lineage_schema_less_chained_cte_star_passthrough() {
6547 let expr = parse(
6548 "WITH c1 AS (SELECT * FROM t), \
6549 c2 AS (SELECT * FROM c1), \
6550 c3 AS (SELECT * FROM c2) \
6551 SELECT c3.x FROM c3",
6552 );
6553 let node = lineage("x", &expr, None, false).unwrap();
6554
6555 let all_names: Vec<_> = node.walk().map(|n| n.name.clone()).collect();
6556 assert!(
6557 all_names.iter().any(|name| name == "t.x"),
6558 "Expected chained CTE star passthrough to reach t.x, got: {:?}",
6559 all_names
6560 );
6561 }
6562
6563 #[test]
6564 fn test_lineage_schema_less_unqualified_star_with_multiple_sources_does_not_guess() {
6565 let expr = parse("SELECT * FROM t1 JOIN t2 ON t1.id = t2.id");
6566 let result = lineage("x", &expr, None, false);
6567
6568 assert!(
6569 result.is_err(),
6570 "Unqualified star over multiple sources should remain ambiguous, got: {:?}",
6571 result
6572 );
6573 }
6574
6575 #[test]
6576 fn test_lineage_cte_select_star_renamed_column() {
6577 let expr =
6580 parse("WITH renamed AS (SELECT id AS customer_id FROM source) SELECT * FROM renamed");
6581 let node = lineage("customer_id", &expr, None, false).unwrap();
6582
6583 assert_eq!(node.name, "customer_id");
6584 let all_names: Vec<_> = node.walk().map(|n| n.name.clone()).collect();
6586 assert!(
6587 all_names.len() >= 2,
6588 "Expected at least 2 nodes (customer_id → source), got: {:?}",
6589 all_names
6590 );
6591 }
6592
6593 #[test]
6594 fn test_lineage_cte_select_star_multiple_columns() {
6595 let expr = parse("WITH cte AS (SELECT a, b, c FROM t) SELECT * FROM cte");
6597
6598 for col in &["a", "b", "c"] {
6599 let node = lineage(col, &expr, None, false).unwrap();
6600 assert_eq!(node.name, *col);
6601 let all_names: Vec<_> = node.walk().map(|n| n.name.clone()).collect();
6603 assert!(
6604 all_names.len() >= 2,
6605 "Expected at least 2 nodes for column {}, got: {:?}",
6606 col,
6607 all_names
6608 );
6609 }
6610 }
6611
6612 #[test]
6613 fn test_lineage_nested_cte_select_star() {
6614 let expr = parse(
6616 "WITH cte1 AS (SELECT a FROM t), \
6617 cte2 AS (SELECT * FROM cte1) \
6618 SELECT * FROM cte2",
6619 );
6620 let node = lineage("a", &expr, None, false).unwrap();
6621
6622 assert_eq!(node.name, "a");
6623 let all_names: Vec<_> = node.walk().map(|n| n.name.clone()).collect();
6624 assert!(
6625 all_names.len() >= 3,
6626 "Expected at least 3 nodes (a → cte2 → cte1 → t.a), got: {:?}",
6627 all_names
6628 );
6629 }
6630
6631 #[test]
6632 fn test_lineage_three_level_nested_cte_star() {
6633 let expr = parse(
6635 "WITH cte1 AS (SELECT x FROM t), \
6636 cte2 AS (SELECT * FROM cte1), \
6637 cte3 AS (SELECT * FROM cte2) \
6638 SELECT * FROM cte3",
6639 );
6640 let node = lineage("x", &expr, None, false).unwrap();
6641
6642 assert_eq!(node.name, "x");
6643 let all_names: Vec<_> = node.walk().map(|n| n.name.clone()).collect();
6644 assert!(
6645 all_names.len() >= 4,
6646 "Expected at least 4 nodes through 3-level CTE chain, got: {:?}",
6647 all_names
6648 );
6649 }
6650
6651 #[test]
6652 fn test_lineage_cte_union_star() {
6653 let expr = parse(
6655 "WITH cte AS (SELECT a, b FROM t1 UNION ALL SELECT a, b FROM t2) \
6656 SELECT * FROM cte",
6657 );
6658 let node = lineage("a", &expr, None, false).unwrap();
6659
6660 assert_eq!(node.name, "a");
6661 let all_names: Vec<_> = node.walk().map(|n| n.name.clone()).collect();
6662 assert!(
6663 all_names.len() >= 2,
6664 "Expected at least 2 nodes for CTE union star, got: {:?}",
6665 all_names
6666 );
6667 }
6668
6669 #[test]
6670 fn test_issue_368_expand_cte_stars_rewrites_every_union_arm() {
6671 let mut expr = parse_one(ISSUE_368_SQL, DialectType::BigQuery).unwrap();
6672
6673 expand_cte_stars(&mut expr, None);
6674
6675 assert_eq!(
6676 crate::generate(&expr, DialectType::BigQuery).unwrap(),
6677 "WITH base AS (SELECT 1 AS col_a), literal_branch AS (SELECT 2 AS col_a), \
6678 unioned AS (SELECT base.col_a FROM base UNION ALL \
6679 SELECT literal_branch.col_a FROM literal_branch) \
6680 SELECT col_a FROM unioned"
6681 );
6682 }
6683
6684 #[test]
6685 fn test_issue_368_lineage_resolves_non_leftmost_union_star() {
6686 let expr = parse_one(ISSUE_368_SQL, DialectType::BigQuery).unwrap();
6687
6688 let node = lineage("col_a", &expr, Some(DialectType::BigQuery), false).unwrap();
6689 let names = lineage_names(&node);
6690
6691 assert!(
6692 node.walk().any(|child| {
6693 child.name == "col_a"
6694 && child.source_name == "literal_branch"
6695 && child.source_kind == SourceKind::Cte
6696 }),
6697 "expected the right UNION branch to resolve to literal_branch.col_a, got {node:#?}"
6698 );
6699 assert!(
6700 !names
6701 .iter()
6702 .any(|name| name == "*" || name == "literal_branch.*"),
6703 "did not expect an unresolved right-branch star, got {names:?}"
6704 );
6705 }
6706
6707 #[test]
6708 fn test_expand_cte_stars_rewrites_all_set_operation_kinds() {
6709 for operator in ["UNION ALL", "INTERSECT", "EXCEPT"] {
6710 let mut expr = parse(&format!(
6711 "WITH left_cte AS (SELECT 1 AS col_a), \
6712 right_cte AS (SELECT 2 AS col_a), \
6713 combined AS (SELECT * FROM left_cte {operator} SELECT * FROM right_cte) \
6714 SELECT col_a FROM combined"
6715 ));
6716
6717 expand_cte_stars(&mut expr, None);
6718
6719 let sql = crate::generate(&expr, DialectType::Generic).unwrap();
6720 assert!(
6721 sql.contains("SELECT left_cte.col_a FROM left_cte"),
6722 "expected left arm expansion for {operator}, got {sql}"
6723 );
6724 assert!(
6725 sql.contains("SELECT right_cte.col_a FROM right_cte"),
6726 "expected right arm expansion for {operator}, got {sql}"
6727 );
6728 }
6729 }
6730
6731 #[test]
6732 fn test_expand_cte_stars_rewrites_nested_parenthesized_set_operations() {
6733 let mut expr = parse(
6734 "WITH a AS (SELECT 1 AS x), \
6735 b AS (SELECT 2 AS x), \
6736 c AS (SELECT 3 AS x), \
6737 combined AS ((SELECT * FROM a UNION ALL SELECT * FROM b) \
6738 UNION ALL SELECT * FROM c) \
6739 SELECT x FROM combined",
6740 );
6741
6742 expand_cte_stars(&mut expr, None);
6743
6744 let sql = crate::generate(&expr, DialectType::Generic).unwrap();
6745 for source in ["a", "b", "c"] {
6746 assert!(
6747 sql.contains(&format!("SELECT {source}.x FROM {source}")),
6748 "expected nested arm {source} to be expanded, got {sql}"
6749 );
6750 }
6751 }
6752
6753 #[test]
6754 fn test_expand_cte_stars_rewrites_root_set_operation() {
6755 let mut expr = parse(
6756 "WITH a AS (SELECT 1 AS x), b AS (SELECT 2 AS x) \
6757 SELECT * FROM a UNION ALL SELECT * FROM b",
6758 );
6759
6760 expand_cte_stars(&mut expr, None);
6761
6762 let sql = crate::generate(&expr, DialectType::Generic).unwrap();
6763 assert!(
6764 sql.contains("SELECT a.x FROM a UNION ALL SELECT b.x FROM b"),
6765 "expected both root UNION arms to be expanded, got {sql}"
6766 );
6767 }
6768
6769 #[test]
6770 fn test_expand_cte_stars_preserves_leftmost_output_names() {
6771 let mut expr = parse(
6772 "WITH a AS (SELECT 1 AS left_name), \
6773 b AS (SELECT 2 AS right_name), \
6774 combined AS (SELECT * FROM a UNION ALL SELECT * FROM b) \
6775 SELECT * FROM combined",
6776 );
6777
6778 expand_cte_stars(&mut expr, None);
6779
6780 let sql = crate::generate(&expr, DialectType::Generic).unwrap();
6781 assert!(
6782 sql.ends_with("SELECT combined.left_name FROM combined"),
6783 "expected the set operation output name to come from the left arm, got {sql}"
6784 );
6785 assert!(
6786 sql.contains("SELECT b.right_name FROM b"),
6787 "expected the differently named right arm to still be expanded, got {sql}"
6788 );
6789 }
6790
6791 #[test]
6792 fn test_expand_cte_stars_rewrites_body_with_explicit_cte_columns() {
6793 let mut expr = parse(
6794 "WITH a AS (SELECT 1 AS x), \
6795 b AS (SELECT 2 AS x), \
6796 combined(output_name) AS (SELECT * FROM a UNION ALL SELECT * FROM b) \
6797 SELECT * FROM combined",
6798 );
6799
6800 expand_cte_stars(&mut expr, None);
6801
6802 let sql = crate::generate(&expr, DialectType::Generic).unwrap();
6803 assert!(
6804 sql.contains("SELECT a.x FROM a UNION ALL SELECT b.x FROM b"),
6805 "expected explicit aliases not to suppress body expansion, got {sql}"
6806 );
6807 assert!(
6808 sql.ends_with("SELECT combined.output_name FROM combined"),
6809 "expected the explicit CTE output name to override the body name, got {sql}"
6810 );
6811 }
6812
6813 #[test]
6814 fn test_expand_cte_stars_keeps_recursive_self_reference_conservative() {
6815 let mut expr = parse(
6816 "WITH RECURSIVE r(x) AS (\
6817 SELECT 1 AS x UNION ALL SELECT * FROM r\
6818 ) SELECT * FROM r",
6819 );
6820
6821 expand_cte_stars(&mut expr, None);
6822
6823 let sql = crate::generate(&expr, DialectType::Generic).unwrap();
6824 assert!(
6825 sql.contains("UNION ALL SELECT * FROM r"),
6826 "expected the recursive body star to remain untouched, got {sql}"
6827 );
6828 assert!(
6829 sql.ends_with("SELECT r.x FROM r"),
6830 "expected the explicit recursive CTE column to expand the outer star, got {sql}"
6831 );
6832 }
6833
6834 #[test]
6835 fn test_expand_cte_stars_preserves_genuinely_unresolved_branch_star() {
6836 let mut expr = parse(
6837 "WITH known AS (SELECT 1 AS x), \
6838 combined AS (SELECT * FROM known UNION ALL SELECT * FROM missing) \
6839 SELECT * FROM combined",
6840 );
6841
6842 expand_cte_stars(&mut expr, None);
6843
6844 let sql = crate::generate(&expr, DialectType::Generic).unwrap();
6845 assert!(
6846 sql.contains("SELECT known.x FROM known UNION ALL SELECT * FROM missing"),
6847 "expected only the resolvable branch to expand, got {sql}"
6848 );
6849 assert!(
6850 sql.ends_with("SELECT combined.x FROM combined"),
6851 "expected the leftmost output name to remain usable, got {sql}"
6852 );
6853 }
6854
6855 #[test]
6856 fn test_lineage_cte_star_unknown_table() {
6857 let expr = parse(
6860 "WITH cte AS (SELECT * FROM unknown_table) \
6861 SELECT * FROM cte",
6862 );
6863 let _result = lineage("x", &expr, None, false);
6866 }
6867
6868 #[test]
6869 fn test_lineage_cte_explicit_columns() {
6870 let expr = parse(
6872 "WITH cte(x, y) AS (SELECT a, b FROM t) \
6873 SELECT * FROM cte",
6874 );
6875 let node = lineage("x", &expr, None, false).unwrap();
6876 assert_eq!(node.name, "x");
6877 }
6878
6879 #[test]
6880 fn test_lineage_cte_qualified_star() {
6881 let expr = parse(
6883 "WITH cte AS (SELECT a, b FROM t) \
6884 SELECT cte.* FROM cte",
6885 );
6886 for col in &["a", "b"] {
6887 let node = lineage(col, &expr, None, false).unwrap();
6888 assert_eq!(node.name, *col);
6889 let all_names: Vec<_> = node.walk().map(|n| n.name.clone()).collect();
6890 assert!(
6891 all_names.len() >= 2,
6892 "Expected at least 2 nodes for qualified star column {}, got: {:?}",
6893 col,
6894 all_names
6895 );
6896 }
6897 }
6898
6899 #[test]
6900 fn test_lineage_subquery_select_star() {
6901 let expr = parse("SELECT x FROM (SELECT * FROM table_a)");
6904 let node = lineage("x", &expr, None, false).unwrap();
6905
6906 assert_eq!(node.name, "x");
6907 assert!(
6908 !node.downstream.is_empty(),
6909 "Expected downstream nodes for subquery with SELECT *, got none"
6910 );
6911 }
6912
6913 #[test]
6914 fn test_lineage_cte_star_with_schema_external_table() {
6915 let sql = r#"WITH orders AS (SELECT * FROM stg_orders)
6917SELECT * FROM orders"#;
6918 let expr = parse(sql);
6919
6920 let mut schema = MappingSchema::new();
6921 let cols = vec![
6922 ("order_id".to_string(), DataType::Unknown),
6923 ("customer_id".to_string(), DataType::Unknown),
6924 ("amount".to_string(), DataType::Unknown),
6925 ];
6926 schema.add_table("stg_orders", &cols, None).unwrap();
6927
6928 let node =
6929 lineage_with_schema("order_id", &expr, Some(&schema as &dyn Schema), None, false)
6930 .unwrap();
6931 assert_eq!(node.name, "order_id");
6932 }
6933
6934 #[test]
6935 fn test_lineage_cte_star_with_schema_three_part_name() {
6936 let sql = r#"WITH orders AS (SELECT * FROM "db"."schema"."stg_orders")
6938SELECT * FROM orders"#;
6939 let expr = parse(sql);
6940
6941 let mut schema = MappingSchema::new();
6942 let cols = vec![
6943 ("order_id".to_string(), DataType::Unknown),
6944 ("customer_id".to_string(), DataType::Unknown),
6945 ];
6946 schema
6947 .add_table("db.schema.stg_orders", &cols, None)
6948 .unwrap();
6949
6950 let node = lineage_with_schema(
6951 "customer_id",
6952 &expr,
6953 Some(&schema as &dyn Schema),
6954 None,
6955 false,
6956 )
6957 .unwrap();
6958 assert_eq!(node.name, "customer_id");
6959 }
6960
6961 #[test]
6962 fn test_lineage_cte_star_with_schema_nested() {
6963 let sql = r#"WITH
6966 raw AS (SELECT * FROM external_table),
6967 enriched AS (SELECT * FROM raw)
6968 SELECT * FROM enriched"#;
6969 let expr = parse(sql);
6970
6971 let mut schema = MappingSchema::new();
6972 let cols = vec![
6973 ("id".to_string(), DataType::Unknown),
6974 ("name".to_string(), DataType::Unknown),
6975 ];
6976 schema.add_table("external_table", &cols, None).unwrap();
6977
6978 let node =
6979 lineage_with_schema("name", &expr, Some(&schema as &dyn Schema), None, false).unwrap();
6980 assert_eq!(node.name, "name");
6981 }
6982
6983 #[test]
6984 fn test_lineage_cte_qualified_star_with_schema() {
6985 let sql = r#"WITH
6988 orders AS (SELECT * FROM stg_orders),
6989 enriched AS (
6990 SELECT orders.*, 'extra' AS extra
6991 FROM orders
6992 )
6993 SELECT * FROM enriched"#;
6994 let expr = parse(sql);
6995
6996 let mut schema = MappingSchema::new();
6997 let cols = vec![
6998 ("order_id".to_string(), DataType::Unknown),
6999 ("total".to_string(), DataType::Unknown),
7000 ];
7001 schema.add_table("stg_orders", &cols, None).unwrap();
7002
7003 let node =
7004 lineage_with_schema("order_id", &expr, Some(&schema as &dyn Schema), None, false)
7005 .unwrap();
7006 assert_eq!(node.name, "order_id");
7007
7008 let extra =
7010 lineage_with_schema("extra", &expr, Some(&schema as &dyn Schema), None, false).unwrap();
7011 assert_eq!(extra.name, "extra");
7012 }
7013
7014 #[test]
7015 fn test_lineage_cte_star_without_schema_still_works() {
7016 let sql = r#"WITH
7018 cte1 AS (SELECT id, name FROM raw_table),
7019 cte2 AS (SELECT * FROM cte1)
7020 SELECT * FROM cte2"#;
7021 let expr = parse(sql);
7022
7023 let node = lineage("id", &expr, None, false).unwrap();
7025 assert_eq!(node.name, "id");
7026 }
7027
7028 #[test]
7029 fn test_lineage_nested_cte_star_with_join_and_schema() {
7030 let sql = r#"WITH
7033base_orders AS (
7034 SELECT * FROM stg_orders
7035),
7036with_payments AS (
7037 SELECT
7038 base_orders.*,
7039 p.amount
7040 FROM base_orders
7041 LEFT JOIN stg_payments p ON base_orders.order_id = p.order_id
7042),
7043final_cte AS (
7044 SELECT * FROM with_payments
7045)
7046SELECT * FROM final_cte"#;
7047 let expr = parse(sql);
7048
7049 let mut schema = MappingSchema::new();
7050 let order_cols = vec![
7051 (
7052 "order_id".to_string(),
7053 crate::expressions::DataType::Unknown,
7054 ),
7055 (
7056 "customer_id".to_string(),
7057 crate::expressions::DataType::Unknown,
7058 ),
7059 ("status".to_string(), crate::expressions::DataType::Unknown),
7060 ];
7061 let pay_cols = vec![
7062 (
7063 "payment_id".to_string(),
7064 crate::expressions::DataType::Unknown,
7065 ),
7066 (
7067 "order_id".to_string(),
7068 crate::expressions::DataType::Unknown,
7069 ),
7070 ("amount".to_string(), crate::expressions::DataType::Unknown),
7071 ];
7072 schema.add_table("stg_orders", &order_cols, None).unwrap();
7073 schema.add_table("stg_payments", &pay_cols, None).unwrap();
7074
7075 let node =
7077 lineage_with_schema("order_id", &expr, Some(&schema as &dyn Schema), None, false)
7078 .unwrap();
7079 let all_names: Vec<_> = node.walk().map(|n| n.name.clone()).collect();
7080
7081 let has_table_qualified = all_names
7083 .iter()
7084 .any(|n| n.contains('.') && n.contains("order_id"));
7085 assert!(
7086 has_table_qualified,
7087 "Expected table-qualified leaf like 'stg_orders.order_id', got: {:?}",
7088 all_names
7089 );
7090
7091 let node = lineage_with_schema("amount", &expr, Some(&schema as &dyn Schema), None, false)
7093 .unwrap();
7094 let all_names: Vec<_> = node.walk().map(|n| n.name.clone()).collect();
7095
7096 let has_table_qualified = all_names
7097 .iter()
7098 .any(|n| n.contains('.') && n.contains("amount"));
7099 assert!(
7100 has_table_qualified,
7101 "Expected table-qualified leaf like 'stg_payments.amount', got: {:?}",
7102 all_names
7103 );
7104 }
7105
7106 #[test]
7107 fn test_lineage_cte_alias_resolution() {
7108 let sql = r#"WITH import_stg_items AS (
7110 SELECT item_id, name, status FROM stg_items
7111)
7112SELECT base.item_id, base.status
7113FROM import_stg_items AS base"#;
7114 let expr = parse(sql);
7115
7116 let node = lineage("item_id", &expr, None, false).unwrap();
7117 let all_names: Vec<_> = node.walk().map(|n| n.name.clone()).collect();
7118 assert!(
7120 all_names.iter().any(|n| n == "stg_items.item_id"),
7121 "Expected leaf 'stg_items.item_id', got: {:?}",
7122 all_names
7123 );
7124 }
7125
7126 #[test]
7127 fn test_lineage_cte_alias_with_schema_and_star() {
7128 let sql = r#"WITH import_stg AS (
7130 SELECT * FROM stg_items
7131)
7132SELECT base.item_id, base.status
7133FROM import_stg AS base"#;
7134 let expr = parse(sql);
7135
7136 let mut schema = MappingSchema::new();
7137 schema
7138 .add_table(
7139 "stg_items",
7140 &[
7141 ("item_id".to_string(), DataType::Unknown),
7142 ("name".to_string(), DataType::Unknown),
7143 ("status".to_string(), DataType::Unknown),
7144 ],
7145 None,
7146 )
7147 .unwrap();
7148
7149 let node = lineage_with_schema("item_id", &expr, Some(&schema as &dyn Schema), None, false)
7150 .unwrap();
7151 let all_names: Vec<_> = node.walk().map(|n| n.name.clone()).collect();
7152 assert!(
7153 all_names.iter().any(|n| n == "stg_items.item_id"),
7154 "Expected leaf 'stg_items.item_id', got: {:?}",
7155 all_names
7156 );
7157 }
7158
7159 #[test]
7160 fn test_lineage_cte_alias_with_join() {
7161 let sql = r#"WITH
7163 import_users AS (SELECT id, name FROM users),
7164 import_orders AS (SELECT id, user_id, amount FROM orders)
7165SELECT u.name, o.amount
7166FROM import_users AS u
7167LEFT JOIN import_orders AS o ON u.id = o.user_id"#;
7168 let expr = parse(sql);
7169
7170 let node = lineage("name", &expr, None, false).unwrap();
7171 let all_names: Vec<_> = node.walk().map(|n| n.name.clone()).collect();
7172 assert!(
7173 all_names.iter().any(|n| n == "users.name"),
7174 "Expected leaf 'users.name', got: {:?}",
7175 all_names
7176 );
7177
7178 let node = lineage("amount", &expr, None, false).unwrap();
7179 let all_names: Vec<_> = node.walk().map(|n| n.name.clone()).collect();
7180 assert!(
7181 all_names.iter().any(|n| n == "orders.amount"),
7182 "Expected leaf 'orders.amount', got: {:?}",
7183 all_names
7184 );
7185 }
7186
7187 #[test]
7192 fn test_lineage_unquoted_cte_case_insensitive() {
7193 let expr = parse("WITH MyCte AS (SELECT id AS col FROM source) SELECT * FROM MYCTE");
7196 let node = lineage("col", &expr, None, false).unwrap();
7197 assert_eq!(node.name, "col");
7198 assert!(
7199 !node.downstream.is_empty(),
7200 "Unquoted CTE should resolve case-insensitively"
7201 );
7202 }
7203
7204 #[test]
7205 fn test_lineage_quoted_cte_case_preserved() {
7206 let expr = parse(r#"WITH "MyCte" AS (SELECT id AS col FROM source) SELECT * FROM "MyCte""#);
7208 let node = lineage("col", &expr, None, false).unwrap();
7209 assert_eq!(node.name, "col");
7210 assert!(
7211 !node.downstream.is_empty(),
7212 "Quoted CTE with matching case should resolve"
7213 );
7214 }
7215
7216 #[test]
7217 fn test_lineage_quoted_cte_case_mismatch_no_expansion() {
7218 let expr = parse(r#"WITH "MyCte" AS (SELECT id AS col FROM source) SELECT * FROM "mycte""#);
7222 let result = lineage("col", &expr, None, false);
7225 assert!(
7226 result.is_err(),
7227 "Quoted CTE with case mismatch should not expand star: {:?}",
7228 result
7229 );
7230 }
7231
7232 #[test]
7233 fn test_lineage_mixed_quoted_unquoted_cte() {
7234 let expr = parse(
7236 r#"WITH unquoted AS (SELECT 1 AS a FROM t), "Quoted" AS (SELECT a FROM unquoted) SELECT * FROM "Quoted""#,
7237 );
7238 let node = lineage("a", &expr, None, false).unwrap();
7239 assert_eq!(node.name, "a");
7240 assert!(
7241 !node.downstream.is_empty(),
7242 "Mixed quoted/unquoted CTE chain should resolve"
7243 );
7244 }
7245
7246 #[test]
7262 fn test_lineage_quoted_cte_case_mismatch_non_star_known_bug() {
7263 let expr = parse(r#"WITH "MyCte" AS (SELECT 1 AS col) SELECT col FROM "mycte""#);
7274 let node = lineage("col", &expr, None, false).unwrap();
7275 assert!(!node.downstream.is_empty());
7276 let child = &node.downstream[0];
7277 assert_eq!(
7279 child.source_name, "MyCte",
7280 "Known bug: quoted CTE case mismatch should NOT resolve, but currently does. \
7281 If this fails, the bug may be fixed — update to assert source_name != \"MyCte\""
7282 );
7283 }
7284
7285 #[test]
7286 fn test_lineage_quoted_cte_case_mismatch_qualified_col_known_bug() {
7287 let expr = parse(r#"WITH "MyCte" AS (SELECT 1 AS col) SELECT "mycte".col FROM "mycte""#);
7294 let node = lineage("col", &expr, None, false).unwrap();
7295 assert!(!node.downstream.is_empty());
7296 let child = &node.downstream[0];
7297 assert_eq!(
7299 child.source_name, "MyCte",
7300 "Known bug: quoted CTE case mismatch should NOT resolve, but currently does. \
7301 If this fails, the bug may be fixed — update to assert source_name != \"MyCte\""
7302 );
7303 }
7304
7305 #[test]
7306 fn test_lineage_recursive_cte_terminates_at_base_case() {
7307 let expr = parse_dialect(
7308 "WITH RECURSIVE nums AS (\
7309 SELECT 1 AS n \
7310 UNION ALL \
7311 SELECT n + 1 FROM nums WHERE n < 5\
7312 ) SELECT n FROM nums",
7313 DialectType::DuckDB,
7314 );
7315 let node = lineage("n", &expr, Some(DialectType::DuckDB), false).unwrap();
7316 let names = lineage_names(&node);
7317
7318 assert!(
7319 names.len() <= 12,
7320 "recursive CTE lineage should not unroll repeatedly, got {names:?}"
7321 );
7322 assert!(
7323 node.walk()
7324 .any(|child| child.source_kind == SourceKind::Cte && child.source_name == "nums"),
7325 "expected recursive source to be marked as a CTE, got {names:?}"
7326 );
7327 }
7328
7329 #[test]
7330 fn test_lineage_window_partition_and_order_columns() {
7331 let expr = parse(
7332 "WITH c AS (SELECT user_id, ts FROM events) \
7333 SELECT ROW_NUMBER() OVER (PARTITION BY c.user_id ORDER BY c.ts) AS out FROM c",
7334 );
7335 let node = lineage("out", &expr, None, false).unwrap();
7336
7337 assert_lineage_contains(&node, "events.user_id");
7338 assert_lineage_contains(&node, "events.ts");
7339 }
7340
7341 #[test]
7342 fn test_lineage_window_aggregate_order_column() {
7343 let expr = parse(
7344 "WITH c AS (SELECT amount, d FROM txns) \
7345 SELECT SUM(c.amount) OVER (ORDER BY c.d) AS running FROM c",
7346 );
7347 let node = lineage("running", &expr, None, false).unwrap();
7348
7349 assert_lineage_contains(&node, "txns.amount");
7350 assert_lineage_contains(&node, "txns.d");
7351 }
7352
7353 #[test]
7354 fn test_lineage_named_window_columns() {
7355 let expr = parse(
7356 "SELECT ROW_NUMBER() OVER w AS out \
7357 FROM events \
7358 WINDOW w AS (PARTITION BY user_id ORDER BY ts)",
7359 );
7360 let node = lineage("out", &expr, None, false).unwrap();
7361
7362 assert_lineage_contains(&node, "events.user_id");
7363 assert_lineage_contains(&node, "events.ts");
7364 }
7365
7366 #[test]
7367 fn test_lineage_within_group_order_column() {
7368 let expr =
7369 parse("SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY amount) AS p FROM txns");
7370 let node = lineage("p", &expr, None, false).unwrap();
7371
7372 assert_lineage_contains(&node, "txns.amount");
7373 }
7374
7375 #[test]
7376 fn test_lineage_query_wrappers_resolve_inner_select() {
7377 for sql in [
7378 "CREATE TABLE tgt AS SELECT x FROM src",
7379 "CREATE VIEW v AS SELECT x FROM src",
7380 "INSERT INTO tgt SELECT x FROM src",
7381 ] {
7382 let expr = parse(sql);
7383 let node = lineage("x", &expr, None, false).unwrap();
7384 assert_lineage_contains(&node, "src.x");
7385 }
7386 }
7387
7388 #[test]
7389 fn test_lineage_scalar_subquery_through_cte_reaches_base_table() {
7390 let expr = parse(
7391 "WITH c AS (SELECT x FROM t) \
7392 SELECT (SELECT SUM(x) FROM c) AS s FROM c LIMIT 1",
7393 );
7394 let node = lineage("s", &expr, None, false).unwrap();
7395
7396 assert_lineage_contains(&node, "t.x");
7397 assert!(
7398 node.walk()
7399 .any(|child| child.source_kind == SourceKind::Cte && child.source_name == "c"),
7400 "expected scalar subquery CTE hop in lineage, got {:?}",
7401 lineage_names(&node)
7402 );
7403 }
7404
7405 #[test]
7406 fn test_lineage_scalar_subqueries_inside_expression_wrappers() {
7407 for sql in [
7408 "WITH c AS (SELECT a, b FROM t) \
7409 SELECT CASE WHEN c.a > 0 THEN c.b ELSE (SELECT MAX(z) FROM o) END AS r FROM c",
7410 "WITH c AS (SELECT a FROM t) \
7411 SELECT COALESCE(c.a, (SELECT MAX(z) FROM o)) AS r FROM c",
7412 "WITH c AS (SELECT a FROM t) \
7413 SELECT CAST((SELECT MAX(z) FROM o) AS INT) + c.a AS r FROM c",
7414 "WITH c AS (SELECT a FROM t) \
7415 SELECT CASE WHEN c.a BETWEEN 0 AND (SELECT MAX(z) FROM o) THEN c.a END AS r FROM c",
7416 ] {
7417 let expr = parse_dialect(sql, DialectType::DuckDB);
7418 let node = lineage("r", &expr, Some(DialectType::DuckDB), false)
7419 .unwrap_or_else(|error| panic!("lineage failed for {sql}: {error}"));
7420
7421 assert_lineage_contains(&node, "o.z");
7422 assert_lineage_contains(&node, "t.a");
7423 }
7424 }
7425
7426 #[test]
7427 fn test_lineage_nested_set_operation_inside_derived_table() {
7428 let expr = parse_dialect(
7429 "SELECT v FROM ((SELECT v FROM t1 UNION ALL SELECT v FROM t2) \
7430 UNION ALL SELECT v FROM t3) u",
7431 DialectType::DuckDB,
7432 );
7433 let node = lineage("v", &expr, Some(DialectType::DuckDB), false).unwrap();
7434
7435 assert_lineage_contains(&node, "t1.v");
7436 assert_lineage_contains(&node, "t2.v");
7437 assert_lineage_contains(&node, "t3.v");
7438 }
7439
7440 #[test]
7441 fn test_lineage_select_alias_reference_resolves_to_alias_source() {
7442 let expr = parse_dialect(
7443 "WITH c AS (SELECT x FROM t) SELECT c.x AS a, a + 1 AS b FROM c",
7444 DialectType::DuckDB,
7445 );
7446 let node = lineage("b", &expr, Some(DialectType::DuckDB), false).unwrap();
7447
7448 assert_lineage_contains(&node, "t.x");
7449 }
7450
7451 #[test]
7452 fn test_lineage_pivot_output_resolves_aggregation_input() {
7453 let expr = parse_dialect(
7454 "SELECT * FROM (SELECT region, q, amt FROM sales) \
7455 PIVOT(SUM(amt) FOR q IN ('Q1' AS q1))",
7456 DialectType::DuckDB,
7457 );
7458 let node = lineage("q1", &expr, Some(DialectType::DuckDB), false).unwrap();
7459
7460 assert_lineage_contains(&node, "sales.amt");
7461 }
7462
7463 #[test]
7464 fn test_lineage_pivot_multi_aggregate_and_alias_columns() {
7465 let multi = parse_dialect(
7466 "SELECT * FROM (SELECT category, value, price FROM t) \
7467 PIVOT(SUM(value) AS value_sum, MAX(price) FOR category IN ('a' AS cat_a, 'b'))",
7468 DialectType::DuckDB,
7469 );
7470 let value_sum =
7471 lineage("cat_a_value_sum", &multi, Some(DialectType::DuckDB), false).unwrap();
7472 assert_lineage_contains(&value_sum, "t.value");
7473
7474 let max_price =
7475 lineage("cat_a_max(price)", &multi, Some(DialectType::DuckDB), false).unwrap();
7476 assert_lineage_contains(&max_price, "t.price");
7477
7478 let aliased = parse_dialect(
7479 "SELECT * FROM (SELECT region, q, amt FROM sales) \
7480 PIVOT(SUM(amt) FOR q IN ('Q1')) AS p(region2, p1)",
7481 DialectType::DuckDB,
7482 );
7483 let region = lineage("region2", &aliased, Some(DialectType::DuckDB), false).unwrap();
7484 assert_lineage_contains(®ion, "sales.region");
7485
7486 let pivot_value = lineage("p1", &aliased, Some(DialectType::DuckDB), false).unwrap();
7487 assert_lineage_contains(&pivot_value, "sales.amt");
7488 }
7489
7490 #[test]
7491 fn test_lineage_pivot_through_cte_resolves_aggregation_input() {
7492 let expr = parse_dialect(
7493 "WITH src AS (SELECT region, q, amt FROM sales) \
7494 SELECT q1 FROM src PIVOT(SUM(amt) FOR q IN ('Q1' AS q1))",
7495 DialectType::DuckDB,
7496 );
7497 let node = lineage("q1", &expr, Some(DialectType::DuckDB), false).unwrap();
7498
7499 assert_lineage_contains(&node, "sales.amt");
7500 }
7501
7502 #[test]
7503 fn test_lineage_unpivot_value_resolves_input_columns() {
7504 let expr = parse_dialect(
7505 "SELECT name, val FROM t UNPIVOT(val FOR col IN (a, b, c))",
7506 DialectType::DuckDB,
7507 );
7508 let node = lineage("val", &expr, Some(DialectType::DuckDB), false).unwrap();
7509
7510 assert_lineage_contains(&node, "t.a");
7511 assert_lineage_contains(&node, "t.b");
7512 assert_lineage_contains(&node, "t.c");
7513 }
7514
7515 #[test]
7516 fn test_lineage_unpivot_multi_value_columns_resolve_positionally() {
7517 let expr = parse_dialect(
7518 "SELECT first_half_sales, second_half_sales, semester \
7519 FROM produce \
7520 UNPIVOT((first_half_sales, second_half_sales) \
7521 FOR semester IN ((q1, q2) AS 'semester_1', (q3, q4) AS 'semester_2'))",
7522 DialectType::BigQuery,
7523 );
7524
7525 let first = lineage(
7526 "first_half_sales",
7527 &expr,
7528 Some(DialectType::BigQuery),
7529 false,
7530 )
7531 .unwrap();
7532 assert_lineage_contains(&first, "produce.q1");
7533 assert_lineage_contains(&first, "produce.q3");
7534
7535 let second = lineage(
7536 "second_half_sales",
7537 &expr,
7538 Some(DialectType::BigQuery),
7539 false,
7540 )
7541 .unwrap();
7542 assert_lineage_contains(&second, "produce.q2");
7543 assert_lineage_contains(&second, "produce.q4");
7544 }
7545
7546 #[test]
7547 fn test_lineage_top_level_union_over_ctes_reaches_base_tables() {
7548 let expr = parse(
7549 "WITH a AS (SELECT x FROM t1), b AS (SELECT x FROM t2) \
7550 SELECT x FROM a UNION SELECT x FROM b",
7551 );
7552 let node = lineage("x", &expr, None, false).unwrap();
7553
7554 assert_lineage_contains(&node, "t1.x");
7555 assert_lineage_contains(&node, "t2.x");
7556 for cte_name in ["a", "b"] {
7557 assert!(
7558 node.walk().any(|child| child.source_name == cte_name),
7559 "expected set-operation lineage to retain CTE source {cte_name}: {:?}",
7560 lineage_names(&node)
7561 );
7562 }
7563 }
7564
7565 #[test]
7566 fn test_lineage_star_excludes_semi_join_rhs_source() {
7567 let expr = parse_dialect(
7568 "SELECT * FROM orders LEFT SEMI JOIN customers ON orders.customer_id = customers.id",
7569 DialectType::DuckDB,
7570 );
7571 let node = lineage("customer_id", &expr, Some(DialectType::DuckDB), false).unwrap();
7572
7573 assert_lineage_contains(&node, "orders.customer_id");
7574 }
7575
7576 #[test]
7583 #[ignore = "requires derived table star expansion (separate issue)"]
7584 fn test_node_name_doesnt_contain_comment() {
7585 let expr = parse("SELECT * FROM (SELECT x /* c */ FROM t1) AS t2");
7586 let node = lineage("x", &expr, None, false).unwrap();
7587
7588 assert_eq!(node.name, "x");
7589 assert!(!node.downstream.is_empty());
7590 }
7591
7592 #[test]
7596 fn test_comment_before_first_column_in_cte() {
7597 let sql_with_comment = "with t as (select 1 as a) select\n -- comment\n a from t";
7598 let sql_without_comment = "with t as (select 1 as a) select a from t";
7599
7600 let expr_ok = parse(sql_without_comment);
7602 let node_ok = lineage("a", &expr_ok, None, false).expect("without comment should succeed");
7603
7604 let expr_comment = parse(sql_with_comment);
7606 let node_comment = lineage("a", &expr_comment, None, false)
7607 .expect("with comment before first column should succeed");
7608
7609 assert_eq!(node_ok.name, node_comment.name, "node names should match");
7610 assert_eq!(
7611 node_ok.downstream_names(),
7612 node_comment.downstream_names(),
7613 "downstream lineage should be identical with or without comment"
7614 );
7615 }
7616
7617 #[test]
7619 fn test_block_comment_before_first_column() {
7620 let sql = "with t as (select 1 as a) select /* section */ a from t";
7621 let expr = parse(sql);
7622 let node = lineage("a", &expr, None, false)
7623 .expect("block comment before first column should succeed");
7624 assert_eq!(node.name, "a");
7625 assert!(
7626 !node.downstream.is_empty(),
7627 "should have downstream lineage"
7628 );
7629 }
7630
7631 #[test]
7633 fn test_comment_before_first_column_second_col_ok() {
7634 let sql = "with t as (select 1 as a, 2 as b) select\n -- comment\n a, b from t";
7635 let expr = parse(sql);
7636
7637 let node_a =
7638 lineage("a", &expr, None, false).expect("column a with comment should succeed");
7639 assert_eq!(node_a.name, "a");
7640
7641 let node_b =
7642 lineage("b", &expr, None, false).expect("column b with comment should succeed");
7643 assert_eq!(node_b.name, "b");
7644 }
7645
7646 #[test]
7648 fn test_comment_before_aliased_column() {
7649 let sql = "with t as (select 1 as x) select\n -- renamed\n x as y from t";
7650 let expr = parse(sql);
7651 let node =
7652 lineage("y", &expr, None, false).expect("aliased column with comment should succeed");
7653 assert_eq!(node.name, "y");
7654 assert!(
7655 !node.downstream.is_empty(),
7656 "aliased column should have downstream lineage"
7657 );
7658 }
7659}