1use crate::ast_transforms::get_output_column_names_for_dialect;
9use crate::dialects::{Dialect, DialectType};
10use crate::expressions::{DataType, Expression, JoinKind, TableRef, With};
11use crate::lineage::{lineage_by_index_from_expression, LineageNode};
12use crate::optimizer::annotate_types::annotate_types;
13use crate::optimizer::qualify_schema_aware_expression;
14use crate::schema::{MappingSchema, Schema};
15use crate::scope::{build_scope, Scope, SourceInfo, SourceKind};
16use crate::traversal::{contains_aggregate, ExpressionWalk};
17use crate::validation::{mapping_schema_from_validation_schema_with_dialect, ValidationSchema};
18use crate::{parse_one, Error, Result};
19use serde::{Deserialize, Serialize};
20use std::collections::{HashMap, HashSet};
21
22mod column_uses;
23
24#[derive(Debug, Clone, Serialize, Deserialize, Default)]
26#[serde(rename_all = "camelCase", default)]
27pub struct AnalyzeQueryOptions {
28 pub dialect: DialectType,
30 pub schema: Option<ValidationSchema>,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
36#[serde(rename_all = "camelCase")]
37pub struct QueryAnalysis {
38 pub shape: QueryShape,
39 pub ctes: Vec<String>,
40 pub cte_facts: Vec<CteFact>,
41 pub projections: Vec<ProjectionFact>,
42 pub relations: Vec<RelationFact>,
43 pub base_tables: Vec<RelationFact>,
44 pub star_projections: Vec<StarProjectionFact>,
45 pub set_operations: Vec<SetOperationFact>,
46 #[serde(default)]
48 pub column_uses: Vec<ColumnUseFact>,
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
53pub struct QuerySourceSpan {
54 pub start: usize,
55 pub end: usize,
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
60#[serde(rename_all = "snake_case")]
61pub enum ColumnUseContext {
62 Join,
63 Filter,
64 Group,
65 Having,
66 Qualify,
67 WindowPartition,
68 WindowOrder,
69 WindowFrame,
70 Order,
71 AggregateOrder,
72 SetOperationFilter,
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
79#[serde(rename_all = "camelCase")]
80pub struct ColumnUseReferenceFact {
81 #[serde(flatten)]
82 pub reference: ColumnReferenceFact,
83 #[serde(skip_serializing_if = "Option::is_none")]
84 pub span: Option<QuerySourceSpan>,
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize)]
90#[serde(rename_all = "camelCase")]
91pub struct ColumnUseFact {
92 pub context: ColumnUseContext,
93 pub scope_path: String,
94 pub expression_path: String,
95 pub expression_sql: String,
97 #[serde(skip_serializing_if = "Option::is_none")]
98 pub span: Option<QuerySourceSpan>,
99 pub references: Vec<ColumnUseReferenceFact>,
100}
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
104#[serde(rename_all = "snake_case")]
105pub enum QueryShape {
106 Select,
107 SetOperation,
108}
109
110#[derive(Debug, Clone, Serialize, Deserialize)]
112#[serde(rename_all = "camelCase")]
113pub struct ProjectionFact {
114 pub index: usize,
115 pub name: Option<String>,
116 pub is_star: bool,
117 pub star_table: Option<String>,
118 pub transform_kind: TransformKind,
119 #[serde(skip_serializing_if = "Option::is_none")]
120 pub transform_function: Option<TransformFunctionFact>,
121 pub cast_type: Option<String>,
122 pub type_hint: Option<String>,
123 pub nullability: ProjectionNullability,
124 pub upstream: Vec<ColumnReferenceFact>,
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize)]
129#[serde(rename_all = "camelCase")]
130pub struct TransformFunctionFact {
131 pub name: String,
132 pub literal_args: Vec<String>,
133 pub column_args: Vec<ColumnReferenceFact>,
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize)]
138#[serde(rename_all = "camelCase")]
139pub struct CteFact {
140 pub name: String,
141 pub columns: Vec<String>,
142 pub body_sql: String,
143 pub output_columns: Vec<String>,
144}
145
146#[derive(Debug, Clone, Serialize, Deserialize)]
148#[serde(rename_all = "camelCase")]
149pub struct StarProjectionFact {
150 pub index: usize,
151 pub table: Option<String>,
152 pub expanded_columns: Vec<String>,
153}
154
155#[derive(Debug, Clone, Serialize, Deserialize)]
157#[serde(rename_all = "camelCase")]
158pub struct ColumnReferenceFact {
159 pub source_name: Option<String>,
160 pub source_alias: Option<String>,
161 pub source_kind: SourceKind,
162 pub table: Option<String>,
163 pub column: String,
164 pub unqualified: bool,
165 pub confidence: ReferenceConfidence,
166}
167
168#[derive(Debug, Clone, Serialize, Deserialize)]
170#[serde(rename_all = "camelCase")]
171pub struct RelationFact {
172 pub name: String,
173 pub alias: Option<String>,
174 pub kind: SourceKind,
175 pub columns: Vec<String>,
176 pub catalog: Option<String>,
177 pub schema: Option<String>,
178 pub table: Option<String>,
179}
180
181#[derive(Debug, Clone, Serialize, Deserialize)]
183#[serde(rename_all = "camelCase")]
184pub struct SetOperationFact {
185 pub kind: String,
186 pub all: bool,
187 pub distinct: bool,
188 pub output_columns: Vec<String>,
189 pub branches: Vec<SetOperationBranchFact>,
190}
191
192#[derive(Debug, Clone, Serialize, Deserialize)]
194#[serde(rename_all = "camelCase")]
195pub struct SetOperationBranchFact {
196 pub index: usize,
197 pub role: SetOperationBranchRole,
198 pub projections: Vec<ProjectionFact>,
199}
200
201#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
203#[serde(rename_all = "snake_case")]
204pub enum SetOperationBranchRole {
205 Value,
206 Filter,
207}
208
209#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
211#[serde(rename_all = "snake_case")]
212pub enum TransformKind {
213 Direct,
214 Cast,
215 Aggregation,
216 Constant,
217 Expression,
218 Star,
219}
220
221#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
223#[serde(rename_all = "snake_case")]
224pub enum ReferenceConfidence {
225 Resolved,
226 Ambiguous,
227 Unknown,
228}
229
230#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
232#[serde(rename_all = "snake_case")]
233pub enum ProjectionNullability {
234 NonNull,
235 Nullable,
236 Unknown,
237}
238
239pub fn analyze_query(sql: &str, options: AnalyzeQueryOptions) -> Result<QueryAnalysis> {
241 let mut expression = parse_one(sql, options.dialect)?;
242 expression = effective_query(expression);
243 ensure_query(&expression)?;
244 let original_expression = expression.clone();
245
246 let mapping_schema = options
247 .schema
248 .as_ref()
249 .map(|schema| analysis_mapping_schema(schema, options.dialect));
250 let schema_info = options.schema.as_ref().map(AnalysisSchemaInfo::from_schema);
251 let cte_facts = top_level_cte_facts(&original_expression, options.dialect)?;
252 let star_projections = star_projection_facts(
253 &original_expression,
254 mapping_schema.as_ref(),
255 options.dialect,
256 );
257
258 if let Some(schema) = mapping_schema.as_ref() {
259 use crate::optimizer::qualify_columns::QualifyColumnsError;
260 expression = match qualify_schema_aware_expression(
261 expression.clone(),
262 schema,
263 Some(options.dialect),
264 ) {
265 Ok(qualified) => qualified,
266 Err(
269 QualifyColumnsError::UnknownTable(_)
270 | QualifyColumnsError::UnknownColumn(_)
271 | QualifyColumnsError::AmbiguousColumn(_)
272 | QualifyColumnsError::ColumnNotResolved { .. },
273 ) => expression,
274 Err(error) => {
275 return Err(Error::internal(format!(
276 "query analysis qualification failed: {error}"
277 )))
278 }
279 };
280 }
281
282 crate::lineage::expand_cte_stars(
283 &mut expression,
284 mapping_schema.as_ref().map(|schema| schema as &dyn Schema),
285 );
286 annotate_types(
287 &mut expression,
288 mapping_schema.as_ref().map(|schema| schema as &dyn Schema),
289 Some(options.dialect),
290 );
291
292 let scope = build_scope(&expression);
293 let nullability_context = NullabilityContext {
294 schema: schema_info.as_ref(),
295 nullable_sources: nullable_source_names(&expression),
296 };
297 let shape = if is_set_operation(&expression) {
298 QueryShape::SetOperation
299 } else {
300 QueryShape::Select
301 };
302
303 Ok(QueryAnalysis {
304 shape,
305 ctes: collect_cte_names(&expression),
306 cte_facts,
307 projections: projection_facts_for_query(
308 &expression,
309 &scope,
310 options.dialect,
311 &nullability_context,
312 ),
313 relations: relation_facts(&scope, mapping_schema.as_ref(), options.dialect),
314 base_tables: base_table_facts(&scope, mapping_schema.as_ref(), options.dialect),
315 star_projections,
316 set_operations: set_operation_facts(&expression, &scope, options.dialect),
317 column_uses: column_uses::collect(
318 &original_expression,
319 &scope,
320 mapping_schema.as_ref(),
321 options.dialect,
322 ),
323 })
324}
325
326fn analysis_mapping_schema(schema: &ValidationSchema, dialect: DialectType) -> MappingSchema {
327 mapping_schema_from_validation_schema_with_dialect(schema, dialect)
328}
329
330fn validation_table_names(table: &crate::validation::SchemaTable) -> Vec<String> {
331 let mut names = Vec::new();
332
333 names.push(table.name.to_ascii_lowercase());
334 if let Some(schema_name) = &table.schema {
335 names.push(format!(
336 "{}.{}",
337 schema_name.to_ascii_lowercase(),
338 table.name.to_ascii_lowercase()
339 ));
340 }
341 for alias in &table.aliases {
342 names.push(alias.to_ascii_lowercase());
343 }
344
345 names.sort();
346 names.dedup();
347 names
348}
349
350#[derive(Debug, Clone)]
351struct AnalysisColumnInfo {
352 nullable: Option<bool>,
353 primary_key: bool,
354}
355
356#[derive(Debug, Clone)]
357struct AnalysisSchemaInfo {
358 columns: HashMap<(String, String), AnalysisColumnInfo>,
359}
360
361impl AnalysisSchemaInfo {
362 fn from_schema(schema: &ValidationSchema) -> Self {
363 let mut columns = HashMap::new();
364
365 for table in &schema.tables {
366 let table_names = validation_table_names(table);
367 let primary_keys: HashSet<String> = table
368 .primary_key
369 .iter()
370 .map(|column| column.to_ascii_lowercase())
371 .collect();
372
373 for column in &table.columns {
374 let info = AnalysisColumnInfo {
375 nullable: column.nullable,
376 primary_key: column.primary_key
377 || primary_keys.contains(&column.name.to_ascii_lowercase()),
378 };
379
380 for table_name in &table_names {
381 columns.insert(
382 (
383 normalize_lookup_name(table_name),
384 normalize_lookup_name(&column.name),
385 ),
386 info.clone(),
387 );
388 }
389 }
390 }
391
392 Self { columns }
393 }
394
395 fn column(&self, table: &str, column: &str) -> Option<&AnalysisColumnInfo> {
396 self.columns
397 .get(&(normalize_lookup_name(table), normalize_lookup_name(column)))
398 }
399}
400
401struct NullabilityContext<'a> {
402 schema: Option<&'a AnalysisSchemaInfo>,
403 nullable_sources: HashSet<String>,
404}
405
406fn top_level_cte_facts(expression: &Expression, dialect: DialectType) -> Result<Vec<CteFact>> {
407 let Some(with_clause) = with_clause(expression) else {
408 return Ok(Vec::new());
409 };
410
411 with_clause
412 .ctes
413 .iter()
414 .map(|cte| {
415 Ok(CteFact {
416 name: cte.alias.name.clone(),
417 columns: cte
418 .columns
419 .iter()
420 .map(|column| column.name.clone())
421 .collect(),
422 body_sql: Dialect::get(dialect).generate(&cte.this)?,
423 output_columns: get_output_column_names_for_dialect(&cte.this, Some(dialect)),
424 })
425 })
426 .collect()
427}
428
429fn star_projection_facts(
430 expression: &Expression,
431 mapping_schema: Option<&MappingSchema>,
432 dialect: DialectType,
433) -> Vec<StarProjectionFact> {
434 let scope = build_scope(expression);
435 let ordered_sources = ordered_source_names_for_query(expression);
436
437 select_expressions_for_query(expression)
438 .iter()
439 .enumerate()
440 .filter_map(|(index, projection)| {
441 let inner = unwrap_projection_alias(projection);
442 if !projection_is_star(inner) {
443 return None;
444 }
445
446 let table = projection_star_table(inner);
447 let expanded_columns = expanded_star_columns(
448 table.as_deref(),
449 &scope,
450 &ordered_sources,
451 mapping_schema,
452 dialect,
453 );
454
455 Some(StarProjectionFact {
456 index,
457 table,
458 expanded_columns,
459 })
460 })
461 .collect()
462}
463
464fn expanded_star_columns(
465 star_table: Option<&str>,
466 scope: &Scope,
467 ordered_sources: &[String],
468 mapping_schema: Option<&MappingSchema>,
469 dialect: DialectType,
470) -> Vec<String> {
471 let mut columns = Vec::new();
472 let mut source_names: Vec<String> = if ordered_sources.is_empty() {
473 let mut names: Vec<_> = scope.sources.keys().cloned().collect();
474 names.sort();
475 names
476 } else {
477 ordered_sources.to_vec()
478 };
479
480 source_names.dedup();
481
482 for source_name in source_names {
483 let Some(source) = scope.sources.get(&source_name) else {
484 continue;
485 };
486
487 if let Some(star_table) = star_table {
488 let matches = source_name.eq_ignore_ascii_case(star_table)
489 || source
490 .alias
491 .as_deref()
492 .is_some_and(|alias| alias.eq_ignore_ascii_case(star_table))
493 || source_table_name(source)
494 .is_some_and(|table| table.eq_ignore_ascii_case(star_table));
495
496 if !matches {
497 continue;
498 }
499 }
500
501 columns.extend(source_columns(source, mapping_schema, dialect));
502 }
503
504 columns
505}
506
507fn ordered_source_names_for_query(expression: &Expression) -> Vec<String> {
508 match expression {
509 Expression::Select(select) => ordered_source_names_for_select(select),
510 Expression::Union(union) => ordered_source_names_for_query(&union.left),
511 Expression::Intersect(intersect) => ordered_source_names_for_query(&intersect.left),
512 Expression::Except(except) => ordered_source_names_for_query(&except.left),
513 Expression::Subquery(subquery) => ordered_source_names_for_query(&subquery.this),
514 _ => Vec::new(),
515 }
516}
517
518fn ordered_source_names_for_select(select: &crate::expressions::Select) -> Vec<String> {
519 let mut sources = Vec::new();
520
521 if let Some(from) = &select.from {
522 for expression in &from.expressions {
523 if let Some(source_name) = expression_source_name(expression) {
524 sources.push(source_name);
525 }
526 }
527 }
528
529 for join in &select.joins {
530 if let Some(source_name) = expression_source_name(&join.this) {
531 sources.push(source_name);
532 }
533 }
534
535 sources
536}
537
538fn nullable_source_names(expression: &Expression) -> HashSet<String> {
539 match expression {
540 Expression::Select(select) => nullable_source_names_for_select(select),
541 Expression::Union(union) => nullable_source_names(&union.left),
542 Expression::Intersect(intersect) => nullable_source_names(&intersect.left),
543 Expression::Except(except) => nullable_source_names(&except.left),
544 Expression::Subquery(subquery) => nullable_source_names(&subquery.this),
545 _ => HashSet::new(),
546 }
547}
548
549fn nullable_source_names_for_select(select: &crate::expressions::Select) -> HashSet<String> {
550 let mut nullable = HashSet::new();
551 let mut left_sources = Vec::new();
552
553 if let Some(from) = &select.from {
554 for expression in &from.expressions {
555 if let Some(source_name) = expression_source_name(expression) {
556 left_sources.push(source_name);
557 }
558 }
559 }
560
561 for join in &select.joins {
562 let right_source = expression_source_name(&join.this);
563
564 if join_nullable_left(join.kind) {
565 for source_name in &left_sources {
566 nullable.insert(normalize_lookup_name(source_name));
567 }
568 }
569
570 if join_nullable_right(join.kind) {
571 if let Some(source_name) = &right_source {
572 nullable.insert(normalize_lookup_name(source_name));
573 }
574 }
575
576 if let Some(source_name) = right_source {
577 left_sources.push(source_name);
578 }
579 }
580
581 nullable
582}
583
584fn join_nullable_left(kind: JoinKind) -> bool {
585 matches!(
586 kind,
587 JoinKind::Right
588 | JoinKind::NaturalRight
589 | JoinKind::AsOfRight
590 | JoinKind::Full
591 | JoinKind::NaturalFull
592 | JoinKind::Outer
593 )
594}
595
596fn join_nullable_right(kind: JoinKind) -> bool {
597 matches!(
598 kind,
599 JoinKind::Left
600 | JoinKind::NaturalLeft
601 | JoinKind::AsOfLeft
602 | JoinKind::LeftLateral
603 | JoinKind::OuterApply
604 | JoinKind::LeftArray
605 | JoinKind::Full
606 | JoinKind::NaturalFull
607 | JoinKind::Outer
608 )
609}
610
611fn expression_source_name(expression: &Expression) -> Option<String> {
612 match expression {
613 Expression::Table(table) => table
614 .alias
615 .as_ref()
616 .map(|alias| alias.name.clone())
617 .or_else(|| Some(table.name.name.clone())),
618 Expression::Subquery(subquery) => subquery.alias.as_ref().map(|alias| alias.name.clone()),
619 Expression::Alias(alias) => Some(alias.alias.name.clone()),
620 Expression::Cte(cte) => Some(cte.alias.name.clone()),
621 _ => None,
622 }
623}
624
625fn normalize_lookup_name(name: &str) -> String {
626 name.to_ascii_lowercase()
627}
628
629fn effective_query(expression: Expression) -> Expression {
630 match expression {
631 Expression::Prepare(prepare) => prepare.statement,
632 Expression::Subquery(subquery) if subquery.alias.is_none() => subquery.this,
633 other => other,
634 }
635}
636
637fn ensure_query(expression: &Expression) -> Result<()> {
638 if matches!(
639 expression,
640 Expression::Select(_)
641 | Expression::Union(_)
642 | Expression::Intersect(_)
643 | Expression::Except(_)
644 ) {
645 Ok(())
646 } else {
647 Err(Error::internal(
648 "analyze_query requires a SELECT or set operation query",
649 ))
650 }
651}
652
653fn is_set_operation(expression: &Expression) -> bool {
654 matches!(
655 expression,
656 Expression::Union(_) | Expression::Intersect(_) | Expression::Except(_)
657 )
658}
659
660fn collect_cte_names(expression: &Expression) -> Vec<String> {
661 let mut names = Vec::new();
662 let mut seen = HashSet::new();
663 collect_cte_names_inner(expression, &mut names, &mut seen);
664 names
665}
666
667fn collect_cte_names_inner(
668 expression: &Expression,
669 names: &mut Vec<String>,
670 seen: &mut HashSet<String>,
671) {
672 if let Some(with_clause) = with_clause(expression) {
673 collect_with_names(with_clause, names, seen);
674 }
675
676 match expression {
677 Expression::Union(union) => {
678 collect_cte_names_inner(&union.left, names, seen);
679 collect_cte_names_inner(&union.right, names, seen);
680 }
681 Expression::Intersect(intersect) => {
682 collect_cte_names_inner(&intersect.left, names, seen);
683 collect_cte_names_inner(&intersect.right, names, seen);
684 }
685 Expression::Except(except) => {
686 collect_cte_names_inner(&except.left, names, seen);
687 collect_cte_names_inner(&except.right, names, seen);
688 }
689 Expression::Subquery(subquery) => collect_cte_names_inner(&subquery.this, names, seen),
690 _ => {}
691 }
692}
693
694fn collect_with_names(with_clause: &With, names: &mut Vec<String>, seen: &mut HashSet<String>) {
695 for cte in &with_clause.ctes {
696 if seen.insert(cte.alias.name.clone()) {
697 names.push(cte.alias.name.clone());
698 }
699 collect_cte_names_inner(&cte.this, names, seen);
700 }
701}
702
703fn with_clause(expression: &Expression) -> Option<&With> {
704 match expression {
705 Expression::Select(select) => select.with.as_ref(),
706 Expression::Union(union) => union.with.as_ref(),
707 Expression::Intersect(intersect) => intersect.with.as_ref(),
708 Expression::Except(except) => except.with.as_ref(),
709 _ => None,
710 }
711}
712
713fn projection_facts_for_query(
714 expression: &Expression,
715 scope: &Scope,
716 dialect: DialectType,
717 nullability_context: &NullabilityContext<'_>,
718) -> Vec<ProjectionFact> {
719 let expressions = projection_sources_for_query(expression, dialect);
720 let names = get_output_column_names_for_dialect(expression, Some(dialect));
721
722 expressions
723 .iter()
724 .enumerate()
725 .map(|(index, (projection, null_padded))| {
726 let mut fact = projection_fact(
727 index,
728 names
729 .get(index)
730 .cloned()
731 .or_else(|| projection_name(projection)),
732 projection,
733 expression,
734 scope,
735 dialect,
736 nullability_context,
737 );
738 if *null_padded {
739 fact.nullability = ProjectionNullability::Nullable;
740 }
741 fact
742 })
743 .collect()
744}
745
746fn projection_sources_for_query(
749 expression: &Expression,
750 dialect: DialectType,
751) -> Vec<(&Expression, bool)> {
752 match crate::set_operation::set_operation_layout(expression, Some(dialect)) {
753 Ok(Some(layout)) => layout
754 .outputs
755 .iter()
756 .filter_map(|output| {
757 let null_padded = output.left_ordinal.is_none() || output.right_ordinal.is_none();
758 output
759 .left_ordinal
760 .and_then(|ordinal| {
761 projection_source_for_ordinal(
762 set_operation_left(expression)?,
763 ordinal,
764 dialect,
765 )
766 })
767 .or_else(|| {
768 output.right_ordinal.and_then(|ordinal| {
769 projection_source_for_ordinal(
770 set_operation_right(expression)?,
771 ordinal,
772 dialect,
773 )
774 })
775 })
776 .map(|(projection, nested_null_padded)| {
777 (projection, null_padded || nested_null_padded)
778 })
779 })
780 .collect(),
781 _ => select_expressions_for_query(expression)
782 .into_iter()
783 .map(|projection| (projection, false))
784 .collect(),
785 }
786}
787
788fn projection_source_for_ordinal(
789 expression: &Expression,
790 ordinal: usize,
791 dialect: DialectType,
792) -> Option<(&Expression, bool)> {
793 match crate::set_operation::set_operation_layout(expression, Some(dialect)) {
794 Ok(Some(layout)) => {
795 let output = layout.outputs.get(ordinal)?;
796 let null_padded = output.left_ordinal.is_none() || output.right_ordinal.is_none();
797 output
798 .left_ordinal
799 .and_then(|child_ordinal| {
800 projection_source_for_ordinal(
801 set_operation_left(expression)?,
802 child_ordinal,
803 dialect,
804 )
805 })
806 .or_else(|| {
807 output.right_ordinal.and_then(|child_ordinal| {
808 projection_source_for_ordinal(
809 set_operation_right(expression)?,
810 child_ordinal,
811 dialect,
812 )
813 })
814 })
815 .map(|(projection, nested_null_padded)| {
816 (projection, null_padded || nested_null_padded)
817 })
818 }
819 _ => match expression {
820 Expression::Select(select) => select
821 .expressions
822 .get(ordinal)
823 .map(|projection| (projection, false)),
824 Expression::Union(union) => {
825 projection_source_for_ordinal(&union.left, ordinal, dialect)
826 }
827 Expression::Intersect(intersect) => {
828 projection_source_for_ordinal(&intersect.left, ordinal, dialect)
829 }
830 Expression::Except(except) => {
831 projection_source_for_ordinal(&except.left, ordinal, dialect)
832 }
833 Expression::Subquery(subquery) => {
834 projection_source_for_ordinal(&subquery.this, ordinal, dialect)
835 }
836 Expression::Paren(paren) => {
837 projection_source_for_ordinal(&paren.this, ordinal, dialect)
838 }
839 _ => None,
840 },
841 }
842}
843
844fn set_operation_left(expression: &Expression) -> Option<&Expression> {
845 match expression {
846 Expression::Union(set_op) => Some(&set_op.left),
847 Expression::Intersect(set_op) => Some(&set_op.left),
848 Expression::Except(set_op) => Some(&set_op.left),
849 _ => None,
850 }
851}
852
853fn set_operation_right(expression: &Expression) -> Option<&Expression> {
854 match expression {
855 Expression::Union(set_op) => Some(&set_op.right),
856 Expression::Intersect(set_op) => Some(&set_op.right),
857 Expression::Except(set_op) => Some(&set_op.right),
858 _ => None,
859 }
860}
861
862fn select_expressions_for_query(expression: &Expression) -> Vec<&Expression> {
863 match expression {
864 Expression::Select(select) => select.expressions.iter().collect(),
865 Expression::Union(union) => select_expressions_for_query(&union.left),
866 Expression::Intersect(intersect) => select_expressions_for_query(&intersect.left),
867 Expression::Except(except) => select_expressions_for_query(&except.left),
868 Expression::Subquery(subquery) => select_expressions_for_query(&subquery.this),
869 _ => Vec::new(),
870 }
871}
872
873fn projection_fact(
874 index: usize,
875 name: Option<String>,
876 projection: &Expression,
877 query: &Expression,
878 scope: &Scope,
879 dialect: DialectType,
880 nullability_context: &NullabilityContext<'_>,
881) -> ProjectionFact {
882 let inner = unwrap_projection_alias(projection);
883 let is_star = projection_is_star(inner);
884 let upstream = lineage_by_index_from_expression(index, query, Some(dialect), false)
885 .map(|node| terminal_references_from_lineage(&node))
886 .ok()
887 .filter(|refs| !refs.is_empty())
888 .unwrap_or_else(|| fallback_column_references(inner, scope));
889
890 ProjectionFact {
891 index,
892 name,
893 is_star,
894 star_table: projection_star_table(inner),
895 transform_kind: transform_kind(inner),
896 transform_function: transform_function_fact(inner, scope, dialect),
897 cast_type: cast_type(inner, dialect),
898 type_hint: projection
899 .inferred_type()
900 .or_else(|| inner.inferred_type())
901 .and_then(|data_type| render_data_type(data_type, dialect)),
902 nullability: projection_nullability(inner, scope, nullability_context),
903 upstream,
904 }
905}
906
907fn transform_function_fact(
908 expression: &Expression,
909 scope: &Scope,
910 dialect: DialectType,
911) -> Option<TransformFunctionFact> {
912 let mut matches = expression
913 .find_all(|candidate| transform_function_fact_for_node(candidate, scope, dialect).is_some())
914 .into_iter();
915
916 let first = matches.next()?;
917 if matches.next().is_some() {
918 return None;
919 }
920
921 transform_function_fact_for_node(first, scope, dialect)
922}
923
924fn transform_function_fact_for_node(
925 expression: &Expression,
926 scope: &Scope,
927 dialect: DialectType,
928) -> Option<TransformFunctionFact> {
929 match expression {
930 Expression::Function(function) => Some(transform_function_from_args(
931 &function.name,
932 &function.args,
933 scope,
934 dialect,
935 )),
936 Expression::AggregateFunction(function) => Some(transform_function_from_args(
937 &function.name,
938 &function.args,
939 scope,
940 dialect,
941 )),
942 Expression::DateTrunc(function) => Some(transform_function_from_parts(
943 "DATE_TRUNC",
944 vec![datetime_field_name(&function.unit)],
945 vec![&function.this],
946 scope,
947 dialect,
948 )),
949 Expression::TimestampTrunc(function) => Some(transform_function_from_parts(
950 "TIMESTAMP_TRUNC",
951 vec![datetime_field_name(&function.unit)],
952 vec![&function.this],
953 scope,
954 dialect,
955 )),
956 Expression::TimeTrunc(function) => {
957 let mut args = vec![function.this.as_ref()];
958 if let Some(zone) = function.zone.as_deref() {
959 args.push(zone);
960 }
961 Some(transform_function_from_parts(
962 "TIME_TRUNC",
963 vec![function.unit.clone()],
964 args,
965 scope,
966 dialect,
967 ))
968 }
969 Expression::Extract(function) => Some(transform_function_from_parts(
970 "EXTRACT",
971 vec![datetime_field_name(&function.field)],
972 vec![&function.this],
973 scope,
974 dialect,
975 )),
976 Expression::DateAdd(function) => Some(transform_function_from_parts(
977 "DATE_ADD",
978 Vec::new(),
979 vec![&function.this, &function.interval],
980 scope,
981 dialect,
982 )),
983 Expression::DateSub(function) => Some(transform_function_from_parts(
984 "DATE_SUB",
985 Vec::new(),
986 vec![&function.this, &function.interval],
987 scope,
988 dialect,
989 )),
990 Expression::DateDiff(function) => Some(transform_function_from_parts(
991 "DATE_DIFF",
992 Vec::new(),
993 vec![&function.this, &function.expression],
994 scope,
995 dialect,
996 )),
997 _ => None,
998 }
999}
1000
1001fn transform_function_from_args(
1002 name: &str,
1003 args: &[Expression],
1004 scope: &Scope,
1005 dialect: DialectType,
1006) -> TransformFunctionFact {
1007 let literal_args = args
1008 .iter()
1009 .filter_map(|arg| literal_argument(arg, dialect))
1010 .collect();
1011 transform_function_from_parts(name, literal_args, args.iter().collect(), scope, dialect)
1012}
1013
1014fn transform_function_from_parts(
1015 name: &str,
1016 literal_args: Vec<String>,
1017 args: Vec<&Expression>,
1018 scope: &Scope,
1019 _dialect: DialectType,
1020) -> TransformFunctionFact {
1021 let column_args = dedupe_column_refs(
1022 args.into_iter()
1023 .flat_map(|arg| fallback_column_references(arg, scope))
1024 .collect(),
1025 );
1026
1027 TransformFunctionFact {
1028 name: name.to_string(),
1029 literal_args,
1030 column_args,
1031 }
1032}
1033
1034fn literal_argument(expression: &Expression, dialect: DialectType) -> Option<String> {
1035 match expression {
1036 Expression::Literal(literal) => Some(literal.value_str().to_string()),
1037 Expression::Boolean(boolean) => Some(boolean.value.to_string()),
1038 Expression::Null(_) => Some("NULL".to_string()),
1039 Expression::Identifier(identifier) => Some(identifier.name.clone()),
1040 Expression::Var(var) => Some(var.this.clone()),
1041 Expression::DataType(data_type) => render_data_type(data_type, dialect),
1042 _ => None,
1043 }
1044}
1045
1046fn datetime_field_name(field: &crate::expressions::DateTimeField) -> String {
1047 match field {
1048 crate::expressions::DateTimeField::Year => "year".to_string(),
1049 crate::expressions::DateTimeField::Month => "month".to_string(),
1050 crate::expressions::DateTimeField::Day => "day".to_string(),
1051 crate::expressions::DateTimeField::Hour => "hour".to_string(),
1052 crate::expressions::DateTimeField::Minute => "minute".to_string(),
1053 crate::expressions::DateTimeField::Second => "second".to_string(),
1054 crate::expressions::DateTimeField::Millisecond => "millisecond".to_string(),
1055 crate::expressions::DateTimeField::Microsecond => "microsecond".to_string(),
1056 crate::expressions::DateTimeField::DayOfWeek => "day_of_week".to_string(),
1057 crate::expressions::DateTimeField::DayOfYear => "day_of_year".to_string(),
1058 crate::expressions::DateTimeField::Week => "week".to_string(),
1059 crate::expressions::DateTimeField::WeekWithModifier(modifier) => {
1060 format!("week({modifier})")
1061 }
1062 crate::expressions::DateTimeField::Quarter => "quarter".to_string(),
1063 crate::expressions::DateTimeField::Epoch => "epoch".to_string(),
1064 crate::expressions::DateTimeField::Timezone => "timezone".to_string(),
1065 crate::expressions::DateTimeField::TimezoneHour => "timezone_hour".to_string(),
1066 crate::expressions::DateTimeField::TimezoneMinute => "timezone_minute".to_string(),
1067 crate::expressions::DateTimeField::Date => "date".to_string(),
1068 crate::expressions::DateTimeField::Time => "time".to_string(),
1069 crate::expressions::DateTimeField::Custom(name) => name.clone(),
1070 }
1071}
1072
1073fn unwrap_projection_alias(expression: &Expression) -> &Expression {
1074 match expression {
1075 Expression::Alias(alias) => unwrap_projection_alias(&alias.this),
1076 Expression::Annotated(annotated) => unwrap_projection_alias(&annotated.this),
1077 Expression::Paren(paren) => unwrap_projection_alias(&paren.this),
1078 _ => expression,
1079 }
1080}
1081
1082fn projection_name(expression: &Expression) -> Option<String> {
1083 match expression {
1084 Expression::Alias(alias) => Some(alias.alias.name.clone()),
1085 Expression::Column(column) => Some(column.name.name.clone()),
1086 Expression::Identifier(identifier) => Some(identifier.name.clone()),
1087 Expression::Star(_) => Some("*".to_string()),
1088 Expression::Annotated(annotated) => projection_name(&annotated.this),
1089 _ => None,
1090 }
1091}
1092
1093fn projection_is_star(expression: &Expression) -> bool {
1094 matches!(expression, Expression::Star(_))
1095 || matches!(expression, Expression::Column(column) if column.name.name == "*")
1096}
1097
1098fn projection_star_table(expression: &Expression) -> Option<String> {
1099 match expression {
1100 Expression::Star(star) => star
1101 .table
1102 .as_ref()
1103 .map(|identifier| identifier.name.clone()),
1104 Expression::Column(column) if column.name.name == "*" => column
1105 .table
1106 .as_ref()
1107 .map(|identifier| identifier.name.clone()),
1108 _ => None,
1109 }
1110}
1111
1112fn transform_kind(expression: &Expression) -> TransformKind {
1113 if projection_is_star(expression) {
1114 TransformKind::Star
1115 } else if is_cast_expression(expression) {
1116 TransformKind::Cast
1117 } else if contains_aggregate(expression) {
1118 TransformKind::Aggregation
1119 } else if matches!(
1120 expression,
1121 Expression::Column(_) | Expression::Identifier(_)
1122 ) {
1123 TransformKind::Direct
1124 } else if is_simple_constant(expression) {
1125 TransformKind::Constant
1126 } else {
1127 TransformKind::Expression
1128 }
1129}
1130
1131fn is_cast_expression(expression: &Expression) -> bool {
1132 matches!(
1133 expression,
1134 Expression::Cast(_) | Expression::TryCast(_) | Expression::SafeCast(_)
1135 )
1136}
1137
1138fn cast_type(expression: &Expression, dialect: DialectType) -> Option<String> {
1139 match expression {
1140 Expression::Cast(cast) | Expression::TryCast(cast) | Expression::SafeCast(cast) => {
1141 render_data_type(&cast.to, dialect)
1142 }
1143 _ => None,
1144 }
1145}
1146
1147fn render_data_type(data_type: &DataType, dialect: DialectType) -> Option<String> {
1148 Dialect::get(dialect)
1149 .generate(&Expression::DataType(data_type.clone()))
1150 .ok()
1151}
1152
1153fn is_simple_constant(expression: &Expression) -> bool {
1154 match expression {
1155 Expression::Literal(_) | Expression::Boolean(_) | Expression::Null(_) => true,
1156 Expression::Cast(cast) | Expression::TryCast(cast) | Expression::SafeCast(cast) => {
1157 is_simple_constant(&cast.this)
1158 }
1159 Expression::Neg(unary) | Expression::BitwiseNot(unary) => is_simple_constant(&unary.this),
1160 _ => false,
1161 }
1162}
1163
1164fn projection_nullability(
1165 expression: &Expression,
1166 scope: &Scope,
1167 context: &NullabilityContext<'_>,
1168) -> ProjectionNullability {
1169 match expression {
1170 Expression::Alias(alias) => projection_nullability(&alias.this, scope, context),
1171 Expression::Annotated(annotated) => projection_nullability(&annotated.this, scope, context),
1172 Expression::Paren(paren) => projection_nullability(&paren.this, scope, context),
1173 Expression::Literal(_) | Expression::Boolean(_) => ProjectionNullability::NonNull,
1174 Expression::Null(_) => ProjectionNullability::Nullable,
1175 Expression::Count(_) | Expression::CountIf(_) => ProjectionNullability::NonNull,
1176 Expression::Cast(cast) => projection_nullability(&cast.this, scope, context),
1177 Expression::TryCast(_) | Expression::SafeCast(_) => ProjectionNullability::Unknown,
1178 Expression::Column(column) => column_nullability(
1179 &column.name.name,
1180 column.table.as_ref().map(|table| table.name.as_str()),
1181 scope,
1182 context,
1183 ),
1184 Expression::Identifier(identifier) => {
1185 column_nullability(&identifier.name, None, scope, context)
1186 }
1187 Expression::Coalesce(func) => coalesce_nullability(&func.expressions, scope, context),
1188 _ => ProjectionNullability::Unknown,
1189 }
1190}
1191
1192fn column_nullability(
1193 column_name: &str,
1194 source_name: Option<&str>,
1195 scope: &Scope,
1196 context: &NullabilityContext<'_>,
1197) -> ProjectionNullability {
1198 let resolved_source_name = source_name
1199 .map(str::to_string)
1200 .or_else(|| single_scope_source_name(scope));
1201
1202 if let Some(source_name) = &resolved_source_name {
1203 if context
1204 .nullable_sources
1205 .contains(&normalize_lookup_name(source_name))
1206 {
1207 return ProjectionNullability::Nullable;
1208 }
1209 }
1210
1211 let Some(schema) = context.schema else {
1212 return ProjectionNullability::Unknown;
1213 };
1214
1215 let table_name = resolved_source_name
1216 .as_ref()
1217 .and_then(|name| scope.sources.get(name).and_then(source_table_name))
1218 .or(resolved_source_name);
1219
1220 let Some(table_name) = table_name else {
1221 return ProjectionNullability::Unknown;
1222 };
1223
1224 match schema.column(&table_name, column_name) {
1225 Some(info) if info.primary_key || info.nullable == Some(false) => {
1226 ProjectionNullability::NonNull
1227 }
1228 Some(info) if info.nullable == Some(true) => ProjectionNullability::Nullable,
1229 Some(_) | None => ProjectionNullability::Unknown,
1230 }
1231}
1232
1233fn single_scope_source_name(scope: &Scope) -> Option<String> {
1234 if scope.sources.len() == 1 {
1235 scope.sources.keys().next().cloned()
1236 } else {
1237 None
1238 }
1239}
1240
1241fn coalesce_nullability(
1242 expressions: &[Expression],
1243 scope: &Scope,
1244 context: &NullabilityContext<'_>,
1245) -> ProjectionNullability {
1246 if expressions.is_empty() {
1247 return ProjectionNullability::Unknown;
1248 }
1249
1250 let mut all_nullable = true;
1251
1252 for expression in expressions {
1253 match projection_nullability(unwrap_projection_alias(expression), scope, context) {
1254 ProjectionNullability::NonNull => return ProjectionNullability::NonNull,
1255 ProjectionNullability::Nullable => {}
1256 ProjectionNullability::Unknown => all_nullable = false,
1257 }
1258 }
1259
1260 if all_nullable {
1261 ProjectionNullability::Nullable
1262 } else {
1263 ProjectionNullability::Unknown
1264 }
1265}
1266
1267fn terminal_references_from_lineage(node: &LineageNode) -> Vec<ColumnReferenceFact> {
1268 let mut refs = Vec::new();
1269 collect_terminal_references(node, &mut refs);
1270 dedupe_column_refs(refs)
1271}
1272
1273fn collect_terminal_references(node: &LineageNode, refs: &mut Vec<ColumnReferenceFact>) {
1274 if node.downstream.is_empty() {
1275 if let Some(reference) = column_reference_from_lineage_node(node) {
1276 refs.push(reference);
1277 }
1278 return;
1279 }
1280
1281 for child in &node.downstream {
1282 collect_terminal_references(child, refs);
1283 }
1284}
1285
1286fn column_reference_from_lineage_node(node: &LineageNode) -> Option<ColumnReferenceFact> {
1287 match &node.expression {
1288 Expression::Column(column) => {
1289 let source_name = non_empty_string(node.source_name.clone());
1290 let table =
1291 lineage_node_table(node).or_else(|| column.table.as_ref().map(|t| t.name.clone()));
1292 let confidence = if node.source_kind == SourceKind::Unknown && source_name.is_none() {
1293 ReferenceConfidence::Unknown
1294 } else {
1295 ReferenceConfidence::Resolved
1296 };
1297 Some(ColumnReferenceFact {
1298 source_name,
1299 source_alias: node.source_alias.clone(),
1300 source_kind: node.source_kind,
1301 table,
1302 column: column.name.name.clone(),
1303 unqualified: column.table.is_none(),
1304 confidence,
1305 })
1306 }
1307 Expression::Star(_) => Some(ColumnReferenceFact {
1308 source_name: non_empty_string(node.source_name.clone()),
1309 source_alias: node.source_alias.clone(),
1310 source_kind: node.source_kind,
1311 table: lineage_node_table(node),
1312 column: "*".to_string(),
1313 unqualified: true,
1314 confidence: if node.source_kind == SourceKind::Unknown {
1315 ReferenceConfidence::Unknown
1316 } else {
1317 ReferenceConfidence::Resolved
1318 },
1319 }),
1320 _ => None,
1321 }
1322}
1323
1324fn lineage_node_table(node: &LineageNode) -> Option<String> {
1325 match &node.source {
1326 Expression::Table(table) => Some(table_name(table)),
1327 _ => None,
1328 }
1329}
1330
1331fn fallback_column_references(expression: &Expression, scope: &Scope) -> Vec<ColumnReferenceFact> {
1332 let mut refs = Vec::new();
1333 let source_count = scope.sources.len();
1334 let single_source = if source_count == 1 {
1335 scope.sources.iter().next()
1336 } else {
1337 None
1338 };
1339
1340 for column_expr in expression.find_all(|candidate| matches!(candidate, Expression::Column(_))) {
1341 if let Expression::Column(column) = column_expr {
1342 if column.name.name == "*" {
1343 continue;
1344 }
1345 let source = column
1346 .table
1347 .as_ref()
1348 .and_then(|table| scope.sources.get(&table.name));
1349 let (source_name, source_alias, source_kind, table, confidence) =
1350 if let Some(table_identifier) = &column.table {
1351 if let Some(source) = source {
1352 (
1353 Some(table_identifier.name.clone()),
1354 source.alias.clone(),
1355 source.kind,
1356 source_table_name(source)
1357 .or_else(|| Some(table_identifier.name.clone())),
1358 ReferenceConfidence::Resolved,
1359 )
1360 } else {
1361 (
1362 Some(table_identifier.name.clone()),
1363 None,
1364 SourceKind::Unknown,
1365 Some(table_identifier.name.clone()),
1366 ReferenceConfidence::Unknown,
1367 )
1368 }
1369 } else if let Some((name, source)) = single_source {
1370 (
1371 Some(name.clone()),
1372 source.alias.clone(),
1373 source.kind,
1374 source_table_name(source).or_else(|| Some(name.clone())),
1375 ReferenceConfidence::Resolved,
1376 )
1377 } else if source_count > 1 {
1378 (
1379 None,
1380 None,
1381 SourceKind::Unknown,
1382 None,
1383 ReferenceConfidence::Ambiguous,
1384 )
1385 } else {
1386 (
1387 None,
1388 None,
1389 SourceKind::Unknown,
1390 None,
1391 ReferenceConfidence::Unknown,
1392 )
1393 };
1394
1395 refs.push(ColumnReferenceFact {
1396 source_name,
1397 source_alias,
1398 source_kind,
1399 table,
1400 column: column.name.name.clone(),
1401 unqualified: column.table.is_none(),
1402 confidence,
1403 });
1404 }
1405 }
1406
1407 dedupe_column_refs(refs)
1408}
1409
1410fn dedupe_column_refs(refs: Vec<ColumnReferenceFact>) -> Vec<ColumnReferenceFact> {
1411 let mut seen = HashSet::new();
1412 let mut deduped = Vec::new();
1413
1414 for reference in refs {
1415 let key = (
1416 reference.source_name.clone(),
1417 reference.source_alias.clone(),
1418 reference.table.clone(),
1419 reference.column.clone(),
1420 format!("{:?}", reference.source_kind),
1421 reference.unqualified,
1422 format!("{:?}", reference.confidence),
1423 );
1424 if seen.insert(key) {
1425 deduped.push(reference);
1426 }
1427 }
1428
1429 deduped
1430}
1431
1432fn relation_facts(
1433 scope: &Scope,
1434 mapping_schema: Option<&crate::schema::MappingSchema>,
1435 dialect: DialectType,
1436) -> Vec<RelationFact> {
1437 let mut relations = Vec::new();
1438 let mut seen = HashSet::new();
1439 collect_relation_facts(scope, mapping_schema, dialect, &mut seen, &mut relations);
1440
1441 relations.sort_by(|left, right| {
1442 left.name
1443 .cmp(&right.name)
1444 .then_with(|| left.alias.cmp(&right.alias))
1445 });
1446 relations
1447}
1448
1449fn collect_relation_facts(
1450 scope: &Scope,
1451 mapping_schema: Option<&crate::schema::MappingSchema>,
1452 dialect: DialectType,
1453 seen: &mut HashSet<String>,
1454 relations: &mut Vec<RelationFact>,
1455) {
1456 for relation in scope.sources.iter().map(|(source_name, source)| {
1457 let identity = source_table_identity(source);
1458 RelationFact {
1459 name: source
1460 .lineage_name
1461 .clone()
1462 .or_else(|| identity.as_ref().map(|identity| identity.name.clone()))
1463 .unwrap_or_else(|| source_name.clone()),
1464 alias: source.alias.clone().or_else(|| source_alias(source)),
1465 kind: source.kind,
1466 columns: source_columns(source, mapping_schema, dialect),
1467 catalog: identity
1468 .as_ref()
1469 .and_then(|identity| identity.catalog.clone()),
1470 schema: identity
1471 .as_ref()
1472 .and_then(|identity| identity.schema.clone()),
1473 table: identity
1474 .as_ref()
1475 .and_then(|identity| identity.table.clone()),
1476 }
1477 }) {
1478 let key = format!("{:?}|{}|{:?}", relation.kind, relation.name, relation.alias);
1479 if seen.insert(key) {
1480 relations.push(relation);
1481 }
1482 }
1483
1484 for branch_scope in &scope.union_scopes {
1485 collect_relation_facts(branch_scope, mapping_schema, dialect, seen, relations);
1486 }
1487}
1488
1489fn base_table_facts(
1490 scope: &Scope,
1491 mapping_schema: Option<&crate::schema::MappingSchema>,
1492 dialect: DialectType,
1493) -> Vec<RelationFact> {
1494 let mut relations = Vec::new();
1495 let mut seen = HashSet::new();
1496
1497 collect_base_table_facts(scope, mapping_schema, dialect, &mut seen, &mut relations);
1498
1499 relations.sort_by(|left, right| left.name.cmp(&right.name));
1500 relations
1501}
1502
1503fn collect_base_table_facts(
1504 scope: &Scope,
1505 mapping_schema: Option<&crate::schema::MappingSchema>,
1506 dialect: DialectType,
1507 seen: &mut HashSet<String>,
1508 relations: &mut Vec<RelationFact>,
1509) {
1510 for source in scope.sources.values() {
1511 if source.kind != SourceKind::Table {
1512 continue;
1513 }
1514
1515 let Some(identity) = source_table_identity(source) else {
1516 continue;
1517 };
1518
1519 if seen.insert(identity.name.clone()) {
1520 relations.push(RelationFact {
1521 name: identity.name,
1522 alias: source.alias.clone().or_else(|| source_alias(source)),
1523 kind: SourceKind::Table,
1524 columns: source_columns(source, mapping_schema, dialect),
1525 catalog: identity.catalog,
1526 schema: identity.schema,
1527 table: identity.table,
1528 });
1529 }
1530 }
1531
1532 for child_scope in scope
1533 .cte_scopes
1534 .iter()
1535 .chain(scope.union_scopes.iter())
1536 .chain(scope.table_scopes.iter())
1537 .chain(scope.derived_table_scopes.iter())
1538 .chain(scope.subquery_scopes.iter())
1539 {
1540 collect_base_table_facts(child_scope, mapping_schema, dialect, seen, relations);
1541 }
1542}
1543
1544fn source_columns(
1545 source: &SourceInfo,
1546 mapping_schema: Option<&crate::schema::MappingSchema>,
1547 dialect: DialectType,
1548) -> Vec<String> {
1549 match &source.expression {
1550 Expression::Table(table) => mapping_schema
1551 .and_then(|schema| schema.column_names(&table_name(table)).ok())
1552 .unwrap_or_default(),
1553 Expression::Select(_)
1554 | Expression::Union(_)
1555 | Expression::Intersect(_)
1556 | Expression::Except(_) => {
1557 get_output_column_names_for_dialect(&source.expression, Some(dialect))
1558 }
1559 Expression::Subquery(subquery) => {
1560 get_output_column_names_for_dialect(&subquery.this, Some(dialect))
1561 }
1562 Expression::Cte(cte) if !cte.columns.is_empty() => cte
1563 .columns
1564 .iter()
1565 .map(|column| column.name.clone())
1566 .collect(),
1567 Expression::Cte(cte) => get_output_column_names_for_dialect(&cte.this, Some(dialect)),
1568 _ => Vec::new(),
1569 }
1570}
1571
1572fn source_table_name(source: &SourceInfo) -> Option<String> {
1573 source_table_identity(source).map(|identity| identity.name)
1574}
1575
1576fn source_alias(source: &SourceInfo) -> Option<String> {
1577 match &source.expression {
1578 Expression::Table(table) => table.alias.as_ref().map(|alias| alias.name.clone()),
1579 Expression::Subquery(subquery) => subquery.alias.as_ref().map(|alias| alias.name.clone()),
1580 _ => None,
1581 }
1582}
1583
1584fn table_name(table: &TableRef) -> String {
1585 let mut parts = Vec::new();
1586 if let Some(catalog) = &table.catalog {
1587 parts.push(catalog.name.clone());
1588 }
1589 if let Some(schema) = &table.schema {
1590 parts.push(schema.name.clone());
1591 }
1592 parts.push(table.name.name.clone());
1593 parts.join(".")
1594}
1595
1596#[derive(Debug, Clone)]
1597struct RelationIdentity {
1598 name: String,
1599 catalog: Option<String>,
1600 schema: Option<String>,
1601 table: Option<String>,
1602}
1603
1604fn source_table_identity(source: &SourceInfo) -> Option<RelationIdentity> {
1605 match &source.expression {
1606 Expression::Table(table) => Some(table_identity(table)),
1607 _ => None,
1608 }
1609}
1610
1611fn table_identity(table: &TableRef) -> RelationIdentity {
1612 RelationIdentity {
1613 name: table_name(table),
1614 catalog: table.catalog.as_ref().map(|catalog| catalog.name.clone()),
1615 schema: table.schema.as_ref().map(|schema| schema.name.clone()),
1616 table: Some(table.name.name.clone()),
1617 }
1618}
1619
1620fn set_operation_facts(
1621 expression: &Expression,
1622 scope: &Scope,
1623 dialect: DialectType,
1624) -> Vec<SetOperationFact> {
1625 let mut facts = Vec::new();
1626 collect_set_operation_facts(expression, scope, dialect, &mut facts);
1627 facts
1628}
1629
1630fn collect_set_operation_facts(
1631 expression: &Expression,
1632 scope: &Scope,
1633 dialect: DialectType,
1634 facts: &mut Vec<SetOperationFact>,
1635) {
1636 match expression {
1637 Expression::Union(union) => {
1638 facts.push(SetOperationFact {
1639 kind: "union".to_string(),
1640 all: union.all,
1641 distinct: union.distinct,
1642 output_columns: get_output_column_names_for_dialect(expression, Some(dialect)),
1643 branches: set_operation_branches(
1644 &union.left,
1645 &union.right,
1646 scope,
1647 dialect,
1648 SetOperationBranchRole::Value,
1649 ),
1650 });
1651 collect_set_operation_facts(&union.left, scope, dialect, facts);
1652 collect_set_operation_facts(&union.right, scope, dialect, facts);
1653 }
1654 Expression::Intersect(intersect) => {
1655 facts.push(SetOperationFact {
1656 kind: "intersect".to_string(),
1657 all: intersect.all,
1658 distinct: intersect.distinct,
1659 output_columns: get_output_column_names_for_dialect(expression, Some(dialect)),
1660 branches: set_operation_branches(
1661 &intersect.left,
1662 &intersect.right,
1663 scope,
1664 dialect,
1665 SetOperationBranchRole::Filter,
1666 ),
1667 });
1668 collect_set_operation_facts(&intersect.left, scope, dialect, facts);
1669 collect_set_operation_facts(&intersect.right, scope, dialect, facts);
1670 }
1671 Expression::Except(except) => {
1672 facts.push(SetOperationFact {
1673 kind: "except".to_string(),
1674 all: except.all,
1675 distinct: except.distinct,
1676 output_columns: get_output_column_names_for_dialect(expression, Some(dialect)),
1677 branches: set_operation_branches(
1678 &except.left,
1679 &except.right,
1680 scope,
1681 dialect,
1682 SetOperationBranchRole::Filter,
1683 ),
1684 });
1685 collect_set_operation_facts(&except.left, scope, dialect, facts);
1686 collect_set_operation_facts(&except.right, scope, dialect, facts);
1687 }
1688 Expression::Subquery(subquery) => {
1689 collect_set_operation_facts(&subquery.this, scope, dialect, facts);
1690 }
1691 _ => {}
1692 }
1693}
1694
1695fn set_operation_branches(
1696 left: &Expression,
1697 right: &Expression,
1698 scope: &Scope,
1699 dialect: DialectType,
1700 right_role: SetOperationBranchRole,
1701) -> Vec<SetOperationBranchFact> {
1702 vec![
1703 SetOperationBranchFact {
1704 index: 0,
1705 role: SetOperationBranchRole::Value,
1706 projections: projection_facts_for_branch(left, scope, dialect),
1707 },
1708 SetOperationBranchFact {
1709 index: 1,
1710 role: right_role,
1711 projections: projection_facts_for_branch(right, scope, dialect),
1712 },
1713 ]
1714}
1715
1716fn projection_facts_for_branch(
1717 expression: &Expression,
1718 root_scope: &Scope,
1719 dialect: DialectType,
1720) -> Vec<ProjectionFact> {
1721 let branch_scope = build_scope(expression);
1722 let scope = if branch_scope.sources.is_empty() {
1723 root_scope
1724 } else {
1725 &branch_scope
1726 };
1727 let nullability_context = NullabilityContext {
1728 schema: None,
1729 nullable_sources: nullable_source_names(expression),
1730 };
1731 projection_facts_for_query(expression, scope, dialect, &nullability_context)
1732}
1733
1734fn non_empty_string(value: String) -> Option<String> {
1735 if value.is_empty() {
1736 None
1737 } else {
1738 Some(value)
1739 }
1740}