1use crate::ast_transforms::get_output_column_names_for_dialect;
9use crate::dialects::{Dialect, DialectType};
10use crate::expressions::{DataType, Expression, Identifier, JoinKind, TableRef, With};
11use crate::lineage::{LineageNode, ScopedLineage};
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_with_options, ComplexityGuardOptions, Error, ParseOptions, Result};
19use serde::{Deserialize, Serialize};
20use std::cell::RefCell;
21use std::collections::{HashMap, HashSet};
22
23mod column_uses;
24
25#[derive(Debug, Clone, Serialize, Deserialize, Default)]
27#[serde(rename_all = "camelCase", default, deny_unknown_fields)]
28pub struct AnalyzeQueryOptions {
29 pub complexity_guard: Option<ComplexityGuardOptions>,
31 pub dialect: DialectType,
33 pub schema: Option<ValidationSchema>,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
39#[serde(rename_all = "camelCase")]
40pub struct QueryAnalysis {
41 pub shape: QueryShape,
42 pub ctes: Vec<String>,
43 pub cte_facts: Vec<CteFact>,
44 pub projections: Vec<ProjectionFact>,
45 pub relations: Vec<RelationFact>,
46 pub base_tables: Vec<RelationFact>,
47 pub star_projections: Vec<StarProjectionFact>,
48 pub set_operations: Vec<SetOperationFact>,
49 #[serde(default)]
51 pub column_uses: Vec<ColumnUseFact>,
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
56pub struct QuerySourceSpan {
57 pub start: usize,
58 pub end: usize,
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
63#[serde(rename_all = "snake_case")]
64pub enum ColumnUseContext {
65 Join,
66 Filter,
67 Group,
68 Having,
69 Qualify,
70 WindowPartition,
71 WindowOrder,
72 WindowFrame,
73 Order,
74 AggregateOrder,
75 SetOperationFilter,
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize)]
82#[serde(rename_all = "camelCase")]
83pub struct ColumnUseReferenceFact {
84 #[serde(flatten)]
85 pub reference: ColumnReferenceFact,
86 #[serde(skip_serializing_if = "Option::is_none")]
87 pub span: Option<QuerySourceSpan>,
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
93#[serde(rename_all = "camelCase")]
94pub struct ColumnUseFact {
95 pub context: ColumnUseContext,
96 pub scope_path: String,
97 pub expression_path: String,
98 pub expression_sql: String,
100 #[serde(skip_serializing_if = "Option::is_none")]
101 pub span: Option<QuerySourceSpan>,
102 pub references: Vec<ColumnUseReferenceFact>,
103}
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
107#[serde(rename_all = "snake_case")]
108pub enum QueryShape {
109 Select,
110 SetOperation,
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize)]
115#[serde(rename_all = "camelCase")]
116pub struct ProjectionFact {
117 pub index: usize,
118 pub name: Option<String>,
119 pub is_star: bool,
120 pub star_table: Option<String>,
121 pub transform_kind: TransformKind,
122 #[serde(skip_serializing_if = "Option::is_none")]
123 pub transform_function: Option<TransformFunctionFact>,
124 pub cast_type: Option<String>,
125 pub type_hint: Option<String>,
126 pub nullability: ProjectionNullability,
127 pub upstream: Vec<ColumnReferenceFact>,
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize)]
132#[serde(rename_all = "camelCase")]
133pub struct TransformFunctionFact {
134 pub name: String,
135 pub literal_args: Vec<String>,
136 pub column_args: Vec<ColumnReferenceFact>,
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize)]
141#[serde(rename_all = "camelCase")]
142pub struct CteFact {
143 pub name: String,
144 pub columns: Vec<String>,
145 pub body_sql: String,
146 pub output_columns: Vec<String>,
147}
148
149#[derive(Debug, Clone, Serialize, Deserialize)]
151#[serde(rename_all = "camelCase")]
152pub struct StarProjectionFact {
153 pub index: usize,
154 pub table: Option<String>,
155 pub expanded_columns: Vec<String>,
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize)]
160#[serde(rename_all = "camelCase")]
161pub struct ColumnReferenceFact {
162 pub source_name: Option<String>,
163 pub source_alias: Option<String>,
164 pub source_kind: SourceKind,
165 pub table: Option<String>,
166 pub column: String,
167 pub unqualified: bool,
168 pub confidence: ReferenceConfidence,
169}
170
171#[derive(Debug, Clone, Serialize, Deserialize)]
173#[serde(rename_all = "camelCase")]
174pub struct RelationFact {
175 pub name: String,
176 pub alias: Option<String>,
177 pub kind: SourceKind,
178 pub columns: Vec<String>,
179 pub catalog: Option<String>,
180 pub schema: Option<String>,
181 pub table: Option<String>,
182}
183
184#[derive(Debug, Clone, Serialize, Deserialize)]
186#[serde(rename_all = "camelCase")]
187pub struct SetOperationFact {
188 pub kind: String,
189 pub all: bool,
190 pub distinct: bool,
191 pub output_columns: Vec<String>,
192 pub branches: Vec<SetOperationBranchFact>,
193}
194
195#[derive(Debug, Clone, Serialize, Deserialize)]
197#[serde(rename_all = "camelCase")]
198pub struct SetOperationBranchFact {
199 pub index: usize,
200 pub role: SetOperationBranchRole,
201 pub projections: Vec<ProjectionFact>,
202}
203
204#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
206#[serde(rename_all = "snake_case")]
207pub enum SetOperationBranchRole {
208 Value,
209 Filter,
210}
211
212#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
214#[serde(rename_all = "snake_case")]
215pub enum TransformKind {
216 Direct,
217 Cast,
218 Aggregation,
219 Constant,
220 Expression,
221 Star,
222}
223
224#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
226#[serde(rename_all = "snake_case")]
227pub enum ReferenceConfidence {
228 Resolved,
229 Ambiguous,
230 Unknown,
231}
232
233#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
235#[serde(rename_all = "snake_case")]
236pub enum ProjectionNullability {
237 NonNull,
238 Nullable,
239 Unknown,
240}
241
242pub fn analyze_query(sql: &str, options: AnalyzeQueryOptions) -> Result<QueryAnalysis> {
244 let mut expression = parse_one_with_options(
245 sql,
246 options.dialect,
247 &ParseOptions {
248 complexity_guard: options.complexity_guard,
249 },
250 )?;
251 expression = effective_query(expression);
252 ensure_query(&expression)?;
253 expression = crate::binding::bind_lambdas(expression, options.dialect);
254 let original_expression = expression.clone();
255
256 let mapping_schema = options
257 .schema
258 .as_ref()
259 .map(|schema| analysis_mapping_schema(schema, options.dialect));
260 let schema_info = options.schema.as_ref().map(AnalysisSchemaInfo::from_schema);
261 let cte_facts = top_level_cte_facts(
262 &original_expression,
263 options.dialect,
264 options.complexity_guard,
265 )?;
266 let star_projections = star_projection_facts(
267 &original_expression,
268 mapping_schema.as_ref(),
269 options.dialect,
270 );
271
272 if let Some(schema) = mapping_schema.as_ref() {
273 use crate::optimizer::qualify_columns::QualifyColumnsError;
274 expression = match qualify_schema_aware_expression(
275 expression.clone(),
276 schema,
277 Some(options.dialect),
278 ) {
279 Ok(qualified) => qualified,
280 Err(
283 QualifyColumnsError::UnknownTable(_)
284 | QualifyColumnsError::UnknownColumn(_)
285 | QualifyColumnsError::AmbiguousColumn(_)
286 | QualifyColumnsError::ColumnNotResolved { .. },
287 ) => expression,
288 Err(error) => {
289 return Err(Error::internal(format!(
290 "query analysis qualification failed: {error}"
291 )))
292 }
293 };
294 }
295
296 crate::lineage::expand_cte_stars(
297 &mut expression,
298 mapping_schema.as_ref().map(|schema| schema as &dyn Schema),
299 );
300 annotate_types(
301 &mut expression,
302 mapping_schema.as_ref().map(|schema| schema as &dyn Schema),
303 Some(options.dialect),
304 );
305
306 let scope = build_scope(&expression);
307 let original_scope = build_scope(&original_expression);
308 let empty_schema = MappingSchema::with_dialect(options.dialect);
309 let mut uncertain_columns = HashMap::new();
310 column_uses::collect_uncertain_occurrences(
311 &original_scope,
312 mapping_schema.as_ref().unwrap_or(&empty_schema),
313 &mut uncertain_columns,
314 );
315 let nullability_context = NullabilityContext::new(
316 &scope,
317 schema_info.as_ref(),
318 mapping_schema.as_ref(),
319 options.dialect,
320 &uncertain_columns,
321 );
322 let shape = if is_set_operation(&expression) {
323 QueryShape::SetOperation
324 } else {
325 QueryShape::Select
326 };
327
328 Ok(QueryAnalysis {
329 shape,
330 ctes: collect_cte_names(&expression),
331 cte_facts,
332 projections: projection_facts_for_query(
333 &expression,
334 &scope,
335 options.dialect,
336 &nullability_context,
337 ),
338 relations: relation_facts(&scope, mapping_schema.as_ref(), options.dialect),
339 base_tables: base_table_facts(&scope, mapping_schema.as_ref(), options.dialect),
340 star_projections,
341 set_operations: set_operation_facts(
342 &expression,
343 &scope,
344 options.dialect,
345 &nullability_context,
346 ),
347 column_uses: column_uses::collect(
348 &original_scope,
349 &scope,
350 mapping_schema.as_ref(),
351 options.dialect,
352 &uncertain_columns,
353 options.complexity_guard,
354 ),
355 })
356}
357
358fn analysis_mapping_schema(schema: &ValidationSchema, dialect: DialectType) -> MappingSchema {
359 mapping_schema_from_validation_schema_with_dialect(schema, dialect)
360}
361
362fn validation_table_names(table: &crate::validation::SchemaTable) -> Vec<String> {
363 let mut names = Vec::new();
364
365 names.push(table.name.to_ascii_lowercase());
366 if let Some(schema_name) = &table.schema {
367 names.push(format!(
368 "{}.{}",
369 schema_name.to_ascii_lowercase(),
370 table.name.to_ascii_lowercase()
371 ));
372 }
373 for alias in &table.aliases {
374 names.push(alias.to_ascii_lowercase());
375 }
376
377 names.sort();
378 names.dedup();
379 names
380}
381
382#[derive(Debug, Clone)]
383struct AnalysisColumnInfo {
384 nullable: Option<bool>,
385 primary_key: bool,
386}
387
388#[derive(Debug, Clone)]
389struct AnalysisSchemaInfo {
390 columns: HashMap<(String, String), AnalysisColumnInfo>,
391}
392
393impl AnalysisSchemaInfo {
394 fn from_schema(schema: &ValidationSchema) -> Self {
395 let mut columns = HashMap::new();
396
397 for table in &schema.tables {
398 let table_names = validation_table_names(table);
399 let primary_keys: HashSet<String> = table
400 .primary_key
401 .iter()
402 .map(|column| column.to_ascii_lowercase())
403 .collect();
404
405 for column in &table.columns {
406 let info = AnalysisColumnInfo {
407 nullable: column.nullable,
408 primary_key: column.primary_key
409 || primary_keys.contains(&column.name.to_ascii_lowercase()),
410 };
411
412 for table_name in &table_names {
413 columns.insert(
414 (
415 normalize_lookup_name(table_name),
416 normalize_lookup_name(&column.name),
417 ),
418 info.clone(),
419 );
420 }
421 }
422 }
423
424 Self { columns }
425 }
426
427 fn column(&self, table: &str, column: &str) -> Option<&AnalysisColumnInfo> {
428 self.columns
429 .get(&(normalize_lookup_name(table), normalize_lookup_name(column)))
430 }
431}
432
433struct NullabilityContext<'a> {
434 lineage: RefCell<HashMap<*const Scope, ScopedLineage>>,
435 output_types: RefCell<crate::optimizer::set_operation_types::OutputResolver>,
436 schema: Option<&'a AnalysisSchemaInfo>,
437 mapping_schema: Option<&'a MappingSchema>,
438 empty_schema: MappingSchema,
439 dialect: DialectType,
440 scopes: Vec<NullabilityScope<'a>>,
441 scope_ids: HashMap<*const Scope, usize>,
442 outputs: RefCell<HashMap<(usize, usize), ProjectionNullability>>,
443 resolving: RefCell<HashSet<(usize, usize)>>,
444 uncertain_columns: &'a HashMap<(usize, usize), ReferenceConfidence>,
445}
446
447struct NullabilityScope<'a> {
448 scope: &'a Scope,
449 selected: Scope,
450 bindings: HashMap<String, &'a Expression>,
451 ctes: HashMap<String, usize>,
452 derived: Vec<usize>,
453 branches: Vec<usize>,
454 nullable_sources: HashSet<String>,
455}
456
457impl<'a> NullabilityContext<'a> {
458 fn new(
459 scope: &'a Scope,
460 schema: Option<&'a AnalysisSchemaInfo>,
461 mapping_schema: Option<&'a MappingSchema>,
462 dialect: DialectType,
463 uncertain_columns: &'a HashMap<(usize, usize), ReferenceConfidence>,
464 ) -> Self {
465 let mut context = Self {
466 lineage: RefCell::new(HashMap::new()),
467 output_types: RefCell::new(crate::optimizer::set_operation_types::OutputResolver::new(
468 dialect,
469 )),
470 schema,
471 mapping_schema,
472 empty_schema: MappingSchema::with_dialect(dialect),
473 dialect,
474 scopes: Vec::new(),
475 scope_ids: HashMap::new(),
476 outputs: RefCell::new(HashMap::new()),
477 resolving: RefCell::new(HashSet::new()),
478 uncertain_columns,
479 };
480 context.index_scope(scope, HashMap::new(), None);
481 context
482 }
483
484 fn index_scope(
487 &mut self,
488 scope: &'a Scope,
489 mut ctes: HashMap<String, usize>,
490 recursive_name: Option<&Identifier>,
491 ) -> usize {
492 let id = self.scopes.len();
493 self.scope_ids.insert(scope, id);
494 if let Some(name) = recursive_name {
495 ctes.insert(
496 crate::set_operation::identifier_key(name, Some(self.dialect)),
497 id,
498 );
499 }
500 let query = crate::scope::scope_query(&scope.expression);
501 let bindings: HashMap<_, _> = if let Expression::Select(select) = query {
502 select
503 .from
504 .iter()
505 .flat_map(|from| &from.expressions)
506 .chain(select.joins.iter().map(|join| &join.this))
507 .filter_map(|expression| {
508 expression_source_name(expression).map(|name| (name, expression))
509 })
510 .collect()
511 } else {
512 HashMap::new()
513 };
514 self.scopes.push(NullabilityScope {
515 scope,
516 selected: crate::scope::selected_reference_scope(scope),
517 bindings,
518 ctes: HashMap::new(),
519 derived: Vec::new(),
520 branches: Vec::new(),
521 nullable_sources: nullable_source_names(query, self.dialect),
522 });
523 let recursive = with_clause(crate::scope::scope_query(&scope.expression))
524 .is_some_and(|with| with.recursive);
525 for child in &scope.cte_scopes {
526 if let Expression::Cte(cte) = &child.expression {
527 let name = &cte.alias;
528 let child_id = self.index_scope(child, ctes.clone(), recursive.then_some(name));
529 ctes.insert(
530 crate::set_operation::identifier_key(name, Some(self.dialect)),
531 child_id,
532 );
533 }
534 }
535 self.scopes[id].ctes = ctes.clone();
536 for child in &scope.derived_table_scopes {
537 let child_id = self.index_scope(child, ctes.clone(), None);
538 self.scopes[id].derived.push(child_id);
539 }
540 for child in &scope.union_scopes {
541 let child_id = self.index_scope(child, ctes.clone(), None);
542 self.scopes[id].branches.push(child_id);
543 }
544 id
545 }
546}
547
548fn top_level_cte_facts(
549 expression: &Expression,
550 dialect: DialectType,
551 guard: Option<ComplexityGuardOptions>,
552) -> Result<Vec<CteFact>> {
553 let Some(with_clause) = with_clause(expression) else {
554 return Ok(Vec::new());
555 };
556
557 with_clause
558 .ctes
559 .iter()
560 .map(|cte| {
561 Ok(CteFact {
562 name: cte.alias.name.clone(),
563 columns: cte
564 .columns
565 .iter()
566 .map(|column| column.name.clone())
567 .collect(),
568 body_sql: Dialect::get(dialect).generate_with_guard(&cte.this, guard)?,
569 output_columns: get_output_column_names_for_dialect(&cte.this, Some(dialect)),
570 })
571 })
572 .collect()
573}
574
575fn star_projection_facts(
576 expression: &Expression,
577 mapping_schema: Option<&MappingSchema>,
578 dialect: DialectType,
579) -> Vec<StarProjectionFact> {
580 let scope = build_scope(expression);
581 let ordered_sources = ordered_source_names_for_query(expression);
582
583 select_expressions_for_query(expression)
584 .iter()
585 .enumerate()
586 .filter_map(|(index, projection)| {
587 let inner = unwrap_projection_alias(projection);
588 if !projection_is_star(inner) {
589 return None;
590 }
591
592 let table = projection_star_table(inner);
593 let expanded_columns = expanded_star_columns(
594 table.as_deref(),
595 &scope,
596 &ordered_sources,
597 mapping_schema,
598 dialect,
599 );
600
601 Some(StarProjectionFact {
602 index,
603 table,
604 expanded_columns,
605 })
606 })
607 .collect()
608}
609
610fn expanded_star_columns(
611 star_table: Option<&str>,
612 scope: &Scope,
613 ordered_sources: &[String],
614 mapping_schema: Option<&MappingSchema>,
615 dialect: DialectType,
616) -> Vec<String> {
617 let mut columns = Vec::new();
618 let mut source_names: Vec<String> = if ordered_sources.is_empty() {
619 let mut names: Vec<_> = scope.sources.keys().cloned().collect();
620 names.sort();
621 names
622 } else {
623 ordered_sources.to_vec()
624 };
625
626 source_names.dedup();
627
628 for source_name in source_names {
629 let Some(source) = scope.sources.get(&source_name) else {
630 continue;
631 };
632
633 if let Some(star_table) = star_table {
634 let matches = source_name.eq_ignore_ascii_case(star_table)
635 || source
636 .alias
637 .as_deref()
638 .is_some_and(|alias| alias.eq_ignore_ascii_case(star_table))
639 || source_table_name(source)
640 .is_some_and(|table| table.eq_ignore_ascii_case(star_table));
641
642 if !matches {
643 continue;
644 }
645 }
646
647 columns.extend(source_columns(source, mapping_schema, dialect));
648 }
649
650 columns
651}
652
653fn ordered_source_names_for_query(expression: &Expression) -> Vec<String> {
654 match expression {
655 Expression::Select(select) => ordered_source_names_for_select(select),
656 Expression::Union(union) => ordered_source_names_for_query(&union.left),
657 Expression::Intersect(intersect) => ordered_source_names_for_query(&intersect.left),
658 Expression::Except(except) => ordered_source_names_for_query(&except.left),
659 Expression::Subquery(subquery) => ordered_source_names_for_query(&subquery.this),
660 _ => Vec::new(),
661 }
662}
663
664fn ordered_source_names_for_select(select: &crate::expressions::Select) -> Vec<String> {
665 let mut sources = Vec::new();
666
667 if let Some(from) = &select.from {
668 for expression in &from.expressions {
669 if let Some(source_name) = expression_source_name(expression) {
670 sources.push(source_name);
671 }
672 }
673 }
674
675 for join in &select.joins {
676 if let Some(source_name) = expression_source_name(&join.this) {
677 sources.push(source_name);
678 }
679 }
680
681 sources
682}
683
684fn nullable_source_names(expression: &Expression, dialect: DialectType) -> HashSet<String> {
685 match expression {
686 Expression::Select(select) => nullable_source_names_for_select(select, dialect),
687 Expression::Union(union) => nullable_source_names(&union.left, dialect),
688 Expression::Intersect(intersect) => nullable_source_names(&intersect.left, dialect),
689 Expression::Except(except) => nullable_source_names(&except.left, dialect),
690 Expression::Subquery(subquery) => nullable_source_names(&subquery.this, dialect),
691 _ => HashSet::new(),
692 }
693}
694
695fn nullable_source_names_for_select(
696 select: &crate::expressions::Select,
697 dialect: DialectType,
698) -> HashSet<String> {
699 let mut nullable = HashSet::new();
700 let mut left_sources = Vec::new();
701
702 if let Some(from) = &select.from {
703 for expression in &from.expressions {
704 if let Some(identifier) = expression_source_identifier(expression) {
705 left_sources.push(crate::set_operation::identifier_key(
706 identifier,
707 Some(dialect),
708 ));
709 }
710 }
711 }
712
713 for join in &select.joins {
714 let right_source = expression_source_identifier(&join.this)
715 .map(|identifier| crate::set_operation::identifier_key(identifier, Some(dialect)));
716
717 if join_nullable_left(join.kind) {
718 for source_name in &left_sources {
719 nullable.insert(source_name.clone());
720 }
721 }
722
723 if join_nullable_right(join.kind) {
724 if let Some(source_name) = &right_source {
725 nullable.insert(source_name.clone());
726 }
727 }
728
729 if let Some(source_name) = right_source {
730 left_sources.push(source_name);
731 }
732 }
733
734 nullable
735}
736
737fn join_nullable_left(kind: JoinKind) -> bool {
738 matches!(
739 kind,
740 JoinKind::Right
741 | JoinKind::NaturalRight
742 | JoinKind::AsOfRight
743 | JoinKind::Full
744 | JoinKind::NaturalFull
745 | JoinKind::Outer
746 )
747}
748
749fn join_nullable_right(kind: JoinKind) -> bool {
750 matches!(
751 kind,
752 JoinKind::Left
753 | JoinKind::NaturalLeft
754 | JoinKind::AsOfLeft
755 | JoinKind::LeftLateral
756 | JoinKind::OuterApply
757 | JoinKind::LeftArray
758 | JoinKind::Full
759 | JoinKind::NaturalFull
760 | JoinKind::Outer
761 )
762}
763
764fn expression_source_name(expression: &Expression) -> Option<String> {
765 expression_source_identifier(expression).map(|identifier| identifier.name.clone())
766}
767
768fn expression_source_identifier(expression: &Expression) -> Option<&Identifier> {
769 match expression {
770 Expression::Table(table) => Some(table.alias.as_ref().unwrap_or(&table.name)),
771 Expression::Subquery(subquery) => subquery.alias.as_ref(),
772 Expression::Alias(alias) => Some(&alias.alias),
773 Expression::Cte(cte) => Some(&cte.alias),
774 _ => None,
775 }
776}
777
778fn normalize_lookup_name(name: &str) -> String {
779 name.to_ascii_lowercase()
780}
781
782fn effective_query(expression: Expression) -> Expression {
783 match expression {
784 Expression::Prepare(prepare) => prepare.statement,
785 Expression::Subquery(subquery) if subquery.alias.is_none() => subquery.this,
786 other => other,
787 }
788}
789
790fn ensure_query(expression: &Expression) -> Result<()> {
791 if matches!(
792 expression,
793 Expression::Select(_)
794 | Expression::Union(_)
795 | Expression::Intersect(_)
796 | Expression::Except(_)
797 ) {
798 Ok(())
799 } else {
800 Err(Error::internal(
801 "analyze_query requires a SELECT or set operation query",
802 ))
803 }
804}
805
806fn is_set_operation(expression: &Expression) -> bool {
807 matches!(
808 expression,
809 Expression::Union(_) | Expression::Intersect(_) | Expression::Except(_)
810 )
811}
812
813fn collect_cte_names(expression: &Expression) -> Vec<String> {
814 let mut names = Vec::new();
815 let mut seen = HashSet::new();
816 collect_cte_names_inner(expression, &mut names, &mut seen);
817 names
818}
819
820fn collect_cte_names_inner(
821 expression: &Expression,
822 names: &mut Vec<String>,
823 seen: &mut HashSet<String>,
824) {
825 if let Some(with_clause) = with_clause(expression) {
826 collect_with_names(with_clause, names, seen);
827 }
828
829 match expression {
830 Expression::Union(union) => {
831 collect_cte_names_inner(&union.left, names, seen);
832 collect_cte_names_inner(&union.right, names, seen);
833 }
834 Expression::Intersect(intersect) => {
835 collect_cte_names_inner(&intersect.left, names, seen);
836 collect_cte_names_inner(&intersect.right, names, seen);
837 }
838 Expression::Except(except) => {
839 collect_cte_names_inner(&except.left, names, seen);
840 collect_cte_names_inner(&except.right, names, seen);
841 }
842 Expression::Subquery(subquery) => collect_cte_names_inner(&subquery.this, names, seen),
843 _ => {}
844 }
845}
846
847fn collect_with_names(with_clause: &With, names: &mut Vec<String>, seen: &mut HashSet<String>) {
848 for cte in &with_clause.ctes {
849 if seen.insert(cte.alias.name.clone()) {
850 names.push(cte.alias.name.clone());
851 }
852 collect_cte_names_inner(&cte.this, names, seen);
853 }
854}
855
856fn with_clause(expression: &Expression) -> Option<&With> {
857 match expression {
858 Expression::Select(select) => select.with.as_ref(),
859 Expression::Union(union) => union.with.as_ref(),
860 Expression::Intersect(intersect) => intersect.with.as_ref(),
861 Expression::Except(except) => except.with.as_ref(),
862 _ => None,
863 }
864}
865
866fn projection_facts_for_query(
867 expression: &Expression,
868 scope: &Scope,
869 dialect: DialectType,
870 nullability_context: &NullabilityContext<'_>,
871) -> Vec<ProjectionFact> {
872 let expressions = projection_sources_for_query(expression, dialect);
873 let names = get_output_column_names_for_dialect(expression, Some(dialect));
874 let mut type_query = expression;
875 loop {
876 type_query = match type_query {
877 Expression::Subquery(subquery) => &subquery.this,
878 Expression::Paren(paren) => &paren.this,
879 Expression::Annotated(annotated) => &annotated.this,
880 _ => break,
881 };
882 }
883 let resolved_outputs = is_set_operation(type_query).then(|| {
884 nullability_context
885 .output_types
886 .borrow_mut()
887 .resolve(expression)
888 });
889
890 expressions
891 .iter()
892 .enumerate()
893 .map(|(index, (projection, null_padded))| {
894 let mut fact = projection_fact(
895 index,
896 names
897 .get(index)
898 .cloned()
899 .or_else(|| projection_name(projection)),
900 projection,
901 expression,
902 scope,
903 dialect,
904 nullability_context,
905 );
906 if *null_padded {
907 fact.nullability = ProjectionNullability::Nullable;
908 }
909 if let Some(outputs) = &resolved_outputs {
910 let output = outputs.get(index);
911 fact.type_hint = output
912 .and_then(|output| output.data_type())
913 .and_then(|data_type| render_data_type(data_type, dialect));
914 fact.cast_type = output
915 .and_then(|output| output.cast_type.as_ref())
916 .and_then(|data_type| render_data_type(data_type, dialect));
917 }
918 fact
919 })
920 .collect()
921}
922
923fn projection_sources_for_query(
926 expression: &Expression,
927 dialect: DialectType,
928) -> Vec<(&Expression, bool)> {
929 match expression {
930 Expression::Subquery(subquery) => {
931 return projection_sources_for_query(&subquery.this, dialect)
932 }
933 Expression::Paren(paren) => return projection_sources_for_query(&paren.this, dialect),
934 Expression::Annotated(annotated) => {
935 return projection_sources_for_query(&annotated.this, dialect)
936 }
937 _ => {}
938 }
939 match crate::set_operation::set_operation_layout(expression, Some(dialect)) {
940 Ok(Some(layout)) => layout
941 .outputs
942 .iter()
943 .filter_map(|output| {
944 let null_padded = output.left_ordinal.is_none() || output.right_ordinal.is_none();
945 output
946 .left_ordinal
947 .and_then(|ordinal| {
948 projection_source_for_ordinal(
949 set_operation_left(expression)?,
950 ordinal,
951 dialect,
952 )
953 })
954 .or_else(|| {
955 output.right_ordinal.and_then(|ordinal| {
956 projection_source_for_ordinal(
957 set_operation_right(expression)?,
958 ordinal,
959 dialect,
960 )
961 })
962 })
963 .map(|(projection, nested_null_padded)| {
964 (projection, null_padded || nested_null_padded)
965 })
966 })
967 .collect(),
968 _ => select_expressions_for_query(expression)
969 .into_iter()
970 .map(|projection| (projection, false))
971 .collect(),
972 }
973}
974
975fn projection_source_for_ordinal(
976 expression: &Expression,
977 ordinal: usize,
978 dialect: DialectType,
979) -> Option<(&Expression, bool)> {
980 match crate::set_operation::set_operation_layout(expression, Some(dialect)) {
981 Ok(Some(layout)) => {
982 let output = layout.outputs.get(ordinal)?;
983 let null_padded = output.left_ordinal.is_none() || output.right_ordinal.is_none();
984 output
985 .left_ordinal
986 .and_then(|child_ordinal| {
987 projection_source_for_ordinal(
988 set_operation_left(expression)?,
989 child_ordinal,
990 dialect,
991 )
992 })
993 .or_else(|| {
994 output.right_ordinal.and_then(|child_ordinal| {
995 projection_source_for_ordinal(
996 set_operation_right(expression)?,
997 child_ordinal,
998 dialect,
999 )
1000 })
1001 })
1002 .map(|(projection, nested_null_padded)| {
1003 (projection, null_padded || nested_null_padded)
1004 })
1005 }
1006 _ => match expression {
1007 Expression::Select(select) => select
1008 .expressions
1009 .get(ordinal)
1010 .map(|projection| (projection, false)),
1011 Expression::Union(union) => {
1012 projection_source_for_ordinal(&union.left, ordinal, dialect)
1013 }
1014 Expression::Intersect(intersect) => {
1015 projection_source_for_ordinal(&intersect.left, ordinal, dialect)
1016 }
1017 Expression::Except(except) => {
1018 projection_source_for_ordinal(&except.left, ordinal, dialect)
1019 }
1020 Expression::Subquery(subquery) => {
1021 projection_source_for_ordinal(&subquery.this, ordinal, dialect)
1022 }
1023 Expression::Paren(paren) => {
1024 projection_source_for_ordinal(&paren.this, ordinal, dialect)
1025 }
1026 _ => None,
1027 },
1028 }
1029}
1030
1031fn set_operation_left(expression: &Expression) -> Option<&Expression> {
1032 match expression {
1033 Expression::Union(set_op) => Some(&set_op.left),
1034 Expression::Intersect(set_op) => Some(&set_op.left),
1035 Expression::Except(set_op) => Some(&set_op.left),
1036 _ => None,
1037 }
1038}
1039
1040fn set_operation_right(expression: &Expression) -> Option<&Expression> {
1041 match expression {
1042 Expression::Union(set_op) => Some(&set_op.right),
1043 Expression::Intersect(set_op) => Some(&set_op.right),
1044 Expression::Except(set_op) => Some(&set_op.right),
1045 _ => None,
1046 }
1047}
1048
1049fn select_expressions_for_query(expression: &Expression) -> Vec<&Expression> {
1050 match expression {
1051 Expression::Select(select) => select.expressions.iter().collect(),
1052 Expression::Union(union) => select_expressions_for_query(&union.left),
1053 Expression::Intersect(intersect) => select_expressions_for_query(&intersect.left),
1054 Expression::Except(except) => select_expressions_for_query(&except.left),
1055 Expression::Subquery(subquery) => select_expressions_for_query(&subquery.this),
1056 _ => Vec::new(),
1057 }
1058}
1059
1060fn projection_fact(
1061 index: usize,
1062 name: Option<String>,
1063 projection: &Expression,
1064 _query: &Expression,
1065 scope: &Scope,
1066 dialect: DialectType,
1067 nullability_context: &NullabilityContext<'_>,
1068) -> ProjectionFact {
1069 let inner = unwrap_projection_alias(projection);
1070 let is_star = projection_is_star(inner);
1071 let mut lineage = nullability_context.lineage.borrow_mut();
1072 let prepared = lineage
1073 .entry(scope as *const Scope)
1074 .or_insert_with(|| ScopedLineage::new(scope.clone(), &[], dialect));
1075 let mut upstream = prepared
1076 .output(index)
1077 .map(|node| {
1078 let uncertain = lineage_uncertainty(&node, nullability_context.uncertain_columns);
1079 let mut references = terminal_references_from_lineage(&node);
1080 if let Some(confidence) = uncertain {
1081 for reference in &mut references {
1082 reference.confidence = confidence;
1083 }
1084 }
1085 references
1086 })
1087 .ok()
1088 .filter(|refs| !refs.is_empty())
1089 .unwrap_or_else(|| fallback_column_references(inner, scope));
1090 if let Some(confidence) = expression_uncertainty(inner, nullability_context.uncertain_columns) {
1091 for reference in &mut upstream {
1092 reference.confidence = confidence;
1093 }
1094 }
1095 if let Some(schema) = nullability_context.mapping_schema {
1096 for reference in &mut upstream {
1097 if let Some(table) = &reference.table {
1098 if let Ok(columns) = schema.column_names(table) {
1099 if !columns.is_empty()
1100 && !columns.iter().any(|column| {
1101 column == "*"
1102 || crate::schema::normalize_name(column, Some(dialect), false, true)
1103 == crate::schema::normalize_name(
1104 &reference.column,
1105 Some(dialect),
1106 false,
1107 true,
1108 )
1109 })
1110 {
1111 reference.confidence = ReferenceConfidence::Unknown;
1112 }
1113 }
1114 }
1115 }
1116 }
1117
1118 let mut transform_function = transform_function_fact(inner, scope, dialect);
1119 if let Some(function) = &mut transform_function {
1120 for reference in &mut function.column_args {
1121 if let Some(upstream) = upstream.iter().find(|upstream| {
1122 upstream.column == reference.column && upstream.table == reference.table
1123 }) {
1124 reference.confidence = upstream.confidence;
1125 } else if upstream
1126 .iter()
1127 .any(|upstream| upstream.confidence != ReferenceConfidence::Resolved)
1128 {
1129 reference.confidence = ReferenceConfidence::Unknown;
1130 }
1131 }
1132 }
1133 ProjectionFact {
1134 index,
1135 name,
1136 is_star,
1137 star_table: projection_star_table(inner),
1138 transform_kind: transform_kind(inner),
1139 transform_function,
1140 cast_type: cast_type(inner, dialect),
1141 type_hint: projection
1142 .inferred_type()
1143 .or_else(|| inner.inferred_type())
1144 .filter(|data_type| **data_type != DataType::Unknown)
1145 .and_then(|data_type| render_data_type(data_type, dialect)),
1146 nullability: nullability_context
1147 .scope_ids
1148 .get(&(scope as *const Scope))
1149 .map(|id| nullability_context.output(*id, index, 0))
1150 .unwrap_or(ProjectionNullability::Unknown),
1151 upstream,
1152 }
1153}
1154
1155fn expression_uncertainty(
1156 expression: &Expression,
1157 uncertain: &HashMap<(usize, usize), ReferenceConfidence>,
1158) -> Option<ReferenceConfidence> {
1159 combine_uncertainty(
1160 crate::scope::walk_in_scope(expression, false).filter_map(|node| match node {
1161 Expression::Column(column) => column
1162 .span
1163 .or(column.name.span)
1164 .and_then(|span| uncertain.get(&(span.start, span.end)))
1165 .copied(),
1166 _ => None,
1167 }),
1168 )
1169}
1170
1171fn combine_uncertainty(
1172 confidences: impl Iterator<Item = ReferenceConfidence>,
1173) -> Option<ReferenceConfidence> {
1174 confidences.fold(None, |current, confidence| {
1175 Some(
1176 if current == Some(ReferenceConfidence::Ambiguous)
1177 || confidence == ReferenceConfidence::Ambiguous
1178 {
1179 ReferenceConfidence::Ambiguous
1180 } else {
1181 confidence
1182 },
1183 )
1184 })
1185}
1186
1187fn lineage_uncertainty(
1188 node: &LineageNode,
1189 uncertain: &HashMap<(usize, usize), ReferenceConfidence>,
1190) -> Option<ReferenceConfidence> {
1191 combine_uncertainty(
1192 node.walk()
1193 .filter_map(|node| expression_uncertainty(&node.expression, uncertain)),
1194 )
1195}
1196
1197fn transform_function_fact(
1198 expression: &Expression,
1199 scope: &Scope,
1200 dialect: DialectType,
1201) -> Option<TransformFunctionFact> {
1202 let mut matches = expression
1203 .find_all(|candidate| transform_function_fact_for_node(candidate, scope, dialect).is_some())
1204 .into_iter();
1205
1206 let first = matches.next()?;
1207 if matches.next().is_some() {
1208 return None;
1209 }
1210
1211 transform_function_fact_for_node(first, scope, dialect)
1212}
1213
1214fn transform_function_fact_for_node(
1215 expression: &Expression,
1216 scope: &Scope,
1217 dialect: DialectType,
1218) -> Option<TransformFunctionFact> {
1219 match expression {
1220 Expression::Function(function) => Some(transform_function_from_args(
1221 &function.name,
1222 &function.args,
1223 scope,
1224 dialect,
1225 )),
1226 Expression::AggregateFunction(function) => Some(transform_function_from_args(
1227 &function.name,
1228 &function.args,
1229 scope,
1230 dialect,
1231 )),
1232 Expression::DateTrunc(function) => Some(transform_function_from_parts(
1233 "DATE_TRUNC",
1234 vec![datetime_field_name(&function.unit)],
1235 vec![&function.this],
1236 scope,
1237 dialect,
1238 )),
1239 Expression::TimestampTrunc(function) => Some(transform_function_from_parts(
1240 "TIMESTAMP_TRUNC",
1241 vec![datetime_field_name(&function.unit)],
1242 vec![&function.this],
1243 scope,
1244 dialect,
1245 )),
1246 Expression::TimeTrunc(function) => {
1247 let mut args = vec![function.this.as_ref()];
1248 if let Some(zone) = function.zone.as_deref() {
1249 args.push(zone);
1250 }
1251 Some(transform_function_from_parts(
1252 "TIME_TRUNC",
1253 vec![function.unit.clone()],
1254 args,
1255 scope,
1256 dialect,
1257 ))
1258 }
1259 Expression::Extract(function) => Some(transform_function_from_parts(
1260 "EXTRACT",
1261 vec![datetime_field_name(&function.field)],
1262 vec![&function.this],
1263 scope,
1264 dialect,
1265 )),
1266 Expression::DateAdd(function) => Some(transform_function_from_parts(
1267 "DATE_ADD",
1268 Vec::new(),
1269 vec![&function.this, &function.interval],
1270 scope,
1271 dialect,
1272 )),
1273 Expression::DateSub(function) => Some(transform_function_from_parts(
1274 "DATE_SUB",
1275 Vec::new(),
1276 vec![&function.this, &function.interval],
1277 scope,
1278 dialect,
1279 )),
1280 Expression::DateDiff(function) => Some(transform_function_from_parts(
1281 "DATE_DIFF",
1282 Vec::new(),
1283 vec![&function.this, &function.expression],
1284 scope,
1285 dialect,
1286 )),
1287 _ => None,
1288 }
1289}
1290
1291fn transform_function_from_args(
1292 name: &str,
1293 args: &[Expression],
1294 scope: &Scope,
1295 dialect: DialectType,
1296) -> TransformFunctionFact {
1297 let literal_args = args
1298 .iter()
1299 .filter_map(|arg| literal_argument(arg, dialect))
1300 .collect();
1301 transform_function_from_parts(name, literal_args, args.iter().collect(), scope, dialect)
1302}
1303
1304fn transform_function_from_parts(
1305 name: &str,
1306 literal_args: Vec<String>,
1307 args: Vec<&Expression>,
1308 scope: &Scope,
1309 _dialect: DialectType,
1310) -> TransformFunctionFact {
1311 let column_args = dedupe_column_refs(
1312 args.into_iter()
1313 .flat_map(|arg| fallback_column_references(arg, scope))
1314 .collect(),
1315 );
1316
1317 TransformFunctionFact {
1318 name: name.to_string(),
1319 literal_args,
1320 column_args,
1321 }
1322}
1323
1324fn literal_argument(expression: &Expression, dialect: DialectType) -> Option<String> {
1325 match expression {
1326 Expression::Literal(literal) => Some(literal.value_str().to_string()),
1327 Expression::Boolean(boolean) => Some(boolean.value.to_string()),
1328 Expression::Null(_) => Some("NULL".to_string()),
1329 Expression::Identifier(identifier) => Some(identifier.name.clone()),
1330 Expression::Var(var) => Some(var.this.clone()),
1331 Expression::DataType(data_type) => render_data_type(data_type, dialect),
1332 _ => None,
1333 }
1334}
1335
1336fn datetime_field_name(field: &crate::expressions::DateTimeField) -> String {
1337 match field {
1338 crate::expressions::DateTimeField::Year => "year".to_string(),
1339 crate::expressions::DateTimeField::Month => "month".to_string(),
1340 crate::expressions::DateTimeField::Day => "day".to_string(),
1341 crate::expressions::DateTimeField::Hour => "hour".to_string(),
1342 crate::expressions::DateTimeField::Minute => "minute".to_string(),
1343 crate::expressions::DateTimeField::Second => "second".to_string(),
1344 crate::expressions::DateTimeField::Millisecond => "millisecond".to_string(),
1345 crate::expressions::DateTimeField::Microsecond => "microsecond".to_string(),
1346 crate::expressions::DateTimeField::DayOfWeek => "day_of_week".to_string(),
1347 crate::expressions::DateTimeField::DayOfYear => "day_of_year".to_string(),
1348 crate::expressions::DateTimeField::Week => "week".to_string(),
1349 crate::expressions::DateTimeField::WeekWithModifier(modifier) => {
1350 format!("week({modifier})")
1351 }
1352 crate::expressions::DateTimeField::Quarter => "quarter".to_string(),
1353 crate::expressions::DateTimeField::Epoch => "epoch".to_string(),
1354 crate::expressions::DateTimeField::Timezone => "timezone".to_string(),
1355 crate::expressions::DateTimeField::TimezoneHour => "timezone_hour".to_string(),
1356 crate::expressions::DateTimeField::TimezoneMinute => "timezone_minute".to_string(),
1357 crate::expressions::DateTimeField::Date => "date".to_string(),
1358 crate::expressions::DateTimeField::Time => "time".to_string(),
1359 crate::expressions::DateTimeField::Custom(name) => name.clone(),
1360 }
1361}
1362
1363fn unwrap_projection_alias(expression: &Expression) -> &Expression {
1364 match expression {
1365 Expression::Alias(alias) => unwrap_projection_alias(&alias.this),
1366 Expression::Annotated(annotated) => unwrap_projection_alias(&annotated.this),
1367 Expression::Paren(paren) => unwrap_projection_alias(&paren.this),
1368 _ => expression,
1369 }
1370}
1371
1372fn projection_name(expression: &Expression) -> Option<String> {
1373 match expression {
1374 Expression::Alias(alias) => Some(alias.alias.name.clone()),
1375 Expression::Column(column) => Some(column.name.name.clone()),
1376 Expression::Identifier(identifier) => Some(identifier.name.clone()),
1377 Expression::Star(_) => Some("*".to_string()),
1378 Expression::Annotated(annotated) => projection_name(&annotated.this),
1379 _ => None,
1380 }
1381}
1382
1383fn projection_is_star(expression: &Expression) -> bool {
1384 matches!(expression, Expression::Star(_))
1385 || matches!(expression, Expression::Column(column) if column.name.name == "*")
1386}
1387
1388fn projection_star_table(expression: &Expression) -> Option<String> {
1389 match expression {
1390 Expression::Star(star) => star
1391 .table
1392 .as_ref()
1393 .map(|identifier| identifier.name.clone()),
1394 Expression::Column(column) if column.name.name == "*" => column
1395 .table
1396 .as_ref()
1397 .map(|identifier| identifier.name.clone()),
1398 _ => None,
1399 }
1400}
1401
1402fn transform_kind(expression: &Expression) -> TransformKind {
1403 if projection_is_star(expression) {
1404 TransformKind::Star
1405 } else if is_cast_expression(expression) {
1406 TransformKind::Cast
1407 } else if contains_aggregate(expression) {
1408 TransformKind::Aggregation
1409 } else if matches!(
1410 expression,
1411 Expression::Column(_) | Expression::Identifier(_)
1412 ) {
1413 TransformKind::Direct
1414 } else if is_simple_constant(expression) {
1415 TransformKind::Constant
1416 } else {
1417 TransformKind::Expression
1418 }
1419}
1420
1421fn is_cast_expression(expression: &Expression) -> bool {
1422 matches!(
1423 expression,
1424 Expression::Cast(_) | Expression::TryCast(_) | Expression::SafeCast(_)
1425 )
1426}
1427
1428fn cast_type(expression: &Expression, dialect: DialectType) -> Option<String> {
1429 match expression {
1430 Expression::Cast(cast) | Expression::TryCast(cast) | Expression::SafeCast(cast) => {
1431 render_data_type(&cast.to, dialect)
1432 }
1433 _ => None,
1434 }
1435}
1436
1437fn render_data_type(data_type: &DataType, dialect: DialectType) -> Option<String> {
1438 if dialect == DialectType::ClickHouse {
1439 return crate::generator::Generator::with_config(
1440 Dialect::get(dialect).generator_config().clone(),
1441 )
1442 .generate_type_hint(data_type)
1443 .ok();
1444 }
1445 Dialect::get(dialect)
1446 .generate(&Expression::DataType(data_type.clone()))
1447 .ok()
1448}
1449
1450fn is_simple_constant(expression: &Expression) -> bool {
1451 match expression {
1452 Expression::Literal(_) | Expression::Boolean(_) | Expression::Null(_) => true,
1453 Expression::Cast(cast) | Expression::TryCast(cast) | Expression::SafeCast(cast) => {
1454 is_simple_constant(&cast.this)
1455 }
1456 Expression::Neg(unary) | Expression::BitwiseNot(unary) => is_simple_constant(&unary.this),
1457 _ => false,
1458 }
1459}
1460
1461impl NullabilityContext<'_> {
1462 const MAX_DEPTH: usize = 128;
1465
1466 fn output(&self, scope_id: usize, ordinal: usize, depth: usize) -> ProjectionNullability {
1467 let key = (scope_id, ordinal);
1468 if let Some(value) = self.outputs.borrow().get(&key) {
1469 return *value;
1470 }
1471 if depth >= Self::MAX_DEPTH || !self.resolving.borrow_mut().insert(key) {
1472 return ProjectionNullability::Unknown;
1473 }
1474 let value = self.output_inner(scope_id, ordinal, depth + 1);
1475 self.resolving.borrow_mut().remove(&key);
1476 self.outputs.borrow_mut().insert(key, value);
1477 value
1478 }
1479
1480 fn output_inner(&self, scope_id: usize, ordinal: usize, depth: usize) -> ProjectionNullability {
1481 use ProjectionNullability::*;
1482 let frame = &self.scopes[scope_id];
1483 let query = crate::scope::scope_query(&frame.scope.expression);
1484 if let Expression::Select(select) = query {
1485 return select
1486 .expressions
1487 .get(ordinal)
1488 .map(|expression| self.expression(scope_id, expression, depth))
1489 .unwrap_or(Unknown);
1490 }
1491 if !is_set_operation(query) || frame.branches.len() != 2 {
1492 return Unknown;
1493 }
1494 let (left, right) =
1495 match crate::set_operation::set_operation_layout(query, Some(self.dialect)) {
1496 Ok(Some(layout)) => match layout.outputs.get(ordinal) {
1497 Some(output) => (output.left_ordinal, output.right_ordinal),
1498 None => return Unknown,
1499 },
1500 Ok(None) => (Some(ordinal), Some(ordinal)),
1501 Err(_) => return Unknown,
1502 };
1503 let branch = |id, ordinal: Option<usize>| {
1504 ordinal
1505 .map(|ordinal| self.output(id, ordinal, depth))
1506 .unwrap_or(Nullable)
1507 };
1508 let left = branch(frame.branches[0], left);
1509 if matches!(query, Expression::Except(_)) {
1510 return left;
1511 }
1512 let right = branch(frame.branches[1], right);
1513 if matches!(query, Expression::Intersect(_)) {
1514 return match (left, right) {
1515 (NonNull, _) | (_, NonNull) => NonNull,
1516 (Nullable, Nullable) => Nullable,
1517 _ => Unknown,
1518 };
1519 }
1520 match (left, right) {
1521 (NonNull, NonNull) => NonNull,
1522 (Nullable, _) | (_, Nullable) => Nullable,
1523 _ => Unknown,
1524 }
1525 }
1526
1527 fn expression(
1528 &self,
1529 scope_id: usize,
1530 expression: &Expression,
1531 depth: usize,
1532 ) -> ProjectionNullability {
1533 use ProjectionNullability::*;
1534 if depth >= Self::MAX_DEPTH {
1535 return Unknown;
1536 }
1537 let depth = depth + 1;
1538 match expression {
1539 Expression::Alias(alias) => self.expression(scope_id, &alias.this, depth),
1540 Expression::Annotated(annotated) => self.expression(scope_id, &annotated.this, depth),
1541 Expression::Paren(paren) => self.expression(scope_id, &paren.this, depth),
1542 Expression::Literal(_)
1543 | Expression::Boolean(_)
1544 | Expression::Count(_)
1545 | Expression::CountIf(_) => NonNull,
1546 Expression::Null(_) => Nullable,
1547 Expression::Cast(cast) => self.expression(scope_id, &cast.this, depth),
1548 Expression::Column(column) => {
1549 if column.span.or(column.name.span).is_some_and(|span| {
1550 self.uncertain_columns.contains_key(&(span.start, span.end))
1551 }) {
1552 Unknown
1553 } else {
1554 self.column(scope_id, &column.name, column.table.as_ref(), depth)
1555 }
1556 }
1557 Expression::Identifier(identifier) => self.column(scope_id, identifier, None, depth),
1558 Expression::Coalesce(func) => {
1559 let mut all_nullable = !func.expressions.is_empty();
1560 for expression in &func.expressions {
1561 match self.expression(scope_id, expression, depth) {
1562 NonNull => return NonNull,
1563 Nullable => {}
1564 Unknown => all_nullable = false,
1565 }
1566 }
1567 if all_nullable {
1568 Nullable
1569 } else {
1570 Unknown
1571 }
1572 }
1573 _ => Unknown,
1574 }
1575 }
1576
1577 fn source_scope(
1578 &self,
1579 frame: &NullabilityScope<'_>,
1580 name: &str,
1581 source: &SourceInfo,
1582 ) -> Option<usize> {
1583 if let Some(Expression::Table(table)) = frame.bindings.get(name).copied() {
1584 if table.schema.is_none() && table.catalog.is_none() {
1585 return frame
1586 .ctes
1587 .get(&crate::set_operation::identifier_key(
1588 &table.name,
1589 Some(self.dialect),
1590 ))
1591 .copied();
1592 }
1593 }
1594 if source.kind == SourceKind::DerivedTable && source.is_scope {
1595 return frame.derived.iter().copied().find(|id| {
1596 crate::scope::scope_query(&self.scopes[*id].scope.expression)
1597 == crate::scope::scope_query(&source.expression)
1598 });
1599 }
1600 None
1601 }
1602
1603 fn source_columns(
1604 &self,
1605 frame: &NullabilityScope<'_>,
1606 name: &str,
1607 source: &SourceInfo,
1608 ) -> Option<Vec<Identifier>> {
1609 if let Some(id) = self.source_scope(frame, name, source) {
1610 let expression = if matches!(
1611 frame.bindings.get(name).copied(),
1612 Some(Expression::Table(_))
1613 ) {
1614 &self.scopes[id].scope.expression
1615 } else {
1616 &source.expression
1617 };
1618 return crate::set_operation::query_output_identifiers(expression, Some(self.dialect))
1619 .ok();
1620 }
1621 if source.kind == SourceKind::Table {
1622 let mut resolver = crate::resolver::Resolver::new(
1623 &frame.selected,
1624 self.mapping_schema.unwrap_or(&self.empty_schema),
1625 false,
1626 );
1627 let columns = resolver.get_source_columns(name).ok()?;
1628 if columns.is_empty() || columns.iter().any(|name| name == "*") {
1629 return None;
1630 }
1631 Some(columns.into_iter().map(Identifier::new).collect())
1632 } else {
1633 crate::set_operation::query_output_identifiers(&source.expression, Some(self.dialect))
1634 .ok()
1635 }
1636 }
1637
1638 fn column(
1639 &self,
1640 scope_id: usize,
1641 column: &Identifier,
1642 qualifier: Option<&Identifier>,
1643 depth: usize,
1644 ) -> ProjectionNullability {
1645 use ProjectionNullability::*;
1646 let frame = &self.scopes[scope_id];
1647 let column_key = crate::set_operation::identifier_key(column, Some(self.dialect));
1648 let same_column = |name: &Identifier| {
1649 crate::set_operation::identifier_key(name, Some(self.dialect)) == column_key
1650 };
1651 let binding = if let Some(qualifier) = qualifier {
1652 let key = crate::set_operation::identifier_key(qualifier, Some(self.dialect));
1653 let mut matches = frame
1654 .bindings
1655 .iter()
1656 .filter(|(_, expression)| {
1657 expression_source_identifier(expression).is_some_and(|identifier| {
1658 crate::set_operation::identifier_key(identifier, Some(self.dialect)) == key
1659 })
1660 })
1661 .filter_map(|(name, _)| frame.selected.sources.get_key_value(name));
1662 let binding = matches.next();
1663 if matches.next().is_some() {
1664 return Unknown;
1665 }
1666 binding
1667 } else {
1668 let mut binding = None;
1669 for (name, source) in &frame.selected.sources {
1670 let Some(columns) = self.source_columns(frame, name, source) else {
1673 return Unknown;
1674 };
1675 if columns.iter().any(&same_column) {
1676 if binding.is_some() {
1677 return Unknown;
1678 }
1679 binding = Some((name, source));
1680 }
1681 }
1682 binding
1683 };
1684 let Some((name, source)) = binding else {
1685 return Unknown;
1686 };
1687 if frame
1688 .bindings
1689 .get(name)
1690 .and_then(|expression| expression_source_identifier(expression))
1691 .is_some_and(|identifier| {
1692 frame
1693 .nullable_sources
1694 .contains(&crate::set_operation::identifier_key(
1695 identifier,
1696 Some(self.dialect),
1697 ))
1698 })
1699 {
1700 return Nullable;
1701 }
1702 let target = self.source_scope(frame, name, source);
1703 if target.is_none() && source.kind == SourceKind::Table {
1704 let info = self.schema.and_then(|schema| {
1705 source_table_name(source).and_then(|table| schema.column(&table, &column.name))
1706 });
1707 return match info {
1708 Some(info) if info.primary_key || info.nullable == Some(false) => NonNull,
1709 Some(info) if info.nullable == Some(true) => Nullable,
1710 _ => Unknown,
1711 };
1712 }
1713 let Some(columns) = self.source_columns(frame, name, source) else {
1714 return Unknown;
1715 };
1716 let mut ordinals = columns
1717 .iter()
1718 .enumerate()
1719 .filter(|(_, name)| same_column(name))
1720 .map(|(ordinal, _)| ordinal);
1721 let Some(ordinal) = ordinals.next() else {
1722 return Unknown;
1723 };
1724 if ordinals.next().is_some() {
1725 return Unknown;
1726 }
1727 target
1728 .map(|id| self.output(id, ordinal, depth))
1729 .unwrap_or(Unknown)
1730 }
1731}
1732
1733fn terminal_references_from_lineage(node: &LineageNode) -> Vec<ColumnReferenceFact> {
1734 let mut refs = Vec::new();
1735 collect_terminal_references(node, &mut refs);
1736 dedupe_column_refs(refs)
1737}
1738
1739fn collect_terminal_references(node: &LineageNode, refs: &mut Vec<ColumnReferenceFact>) {
1740 if node.downstream.is_empty() {
1741 if let Some(reference) = column_reference_from_lineage_node(node) {
1742 refs.push(reference);
1743 }
1744 return;
1745 }
1746
1747 for child in &node.downstream {
1748 collect_terminal_references(child, refs);
1749 }
1750}
1751
1752fn column_reference_from_lineage_node(node: &LineageNode) -> Option<ColumnReferenceFact> {
1753 match &node.expression {
1754 Expression::Column(column) => {
1755 let source_name = non_empty_string(node.source_name.clone());
1756 let table =
1757 lineage_node_table(node).or_else(|| column.table.as_ref().map(|t| t.name.clone()));
1758 let confidence = if node.source_kind == SourceKind::Unknown && source_name.is_none() {
1759 ReferenceConfidence::Unknown
1760 } else {
1761 ReferenceConfidence::Resolved
1762 };
1763 Some(ColumnReferenceFact {
1764 source_name,
1765 source_alias: node.source_alias.clone(),
1766 source_kind: node.source_kind,
1767 table,
1768 column: column.name.name.clone(),
1769 unqualified: column.table.is_none(),
1770 confidence,
1771 })
1772 }
1773 Expression::Star(_) => Some(ColumnReferenceFact {
1774 source_name: non_empty_string(node.source_name.clone()),
1775 source_alias: node.source_alias.clone(),
1776 source_kind: node.source_kind,
1777 table: lineage_node_table(node),
1778 column: "*".to_string(),
1779 unqualified: true,
1780 confidence: if node.source_kind == SourceKind::Unknown {
1781 ReferenceConfidence::Unknown
1782 } else {
1783 ReferenceConfidence::Resolved
1784 },
1785 }),
1786 _ => None,
1787 }
1788}
1789
1790fn lineage_node_table(node: &LineageNode) -> Option<String> {
1791 match &node.source {
1792 Expression::Table(table) => Some(table_name(table)),
1793 _ => None,
1794 }
1795}
1796
1797fn fallback_column_references(expression: &Expression, scope: &Scope) -> Vec<ColumnReferenceFact> {
1798 let mut refs = Vec::new();
1799 let source_count = scope.sources.len();
1800 let single_source = if source_count == 1 {
1801 scope.sources.iter().next()
1802 } else {
1803 None
1804 };
1805
1806 for column_expr in expression.find_all(|candidate| matches!(candidate, Expression::Column(_))) {
1807 if let Expression::Column(column) = column_expr {
1808 if column.name.name == "*" {
1809 continue;
1810 }
1811 let source = column
1812 .table
1813 .as_ref()
1814 .and_then(|table| scope.sources.get(&table.name));
1815 let (source_name, source_alias, source_kind, table, confidence) =
1816 if let Some(table_identifier) = &column.table {
1817 if let Some(source) = source {
1818 (
1819 Some(table_identifier.name.clone()),
1820 source.alias.clone(),
1821 source.kind,
1822 source_table_name(source)
1823 .or_else(|| Some(table_identifier.name.clone())),
1824 ReferenceConfidence::Resolved,
1825 )
1826 } else {
1827 (
1828 Some(table_identifier.name.clone()),
1829 None,
1830 SourceKind::Unknown,
1831 Some(table_identifier.name.clone()),
1832 ReferenceConfidence::Unknown,
1833 )
1834 }
1835 } else if let Some((name, source)) = single_source {
1836 (
1837 Some(name.clone()),
1838 source.alias.clone(),
1839 source.kind,
1840 source_table_name(source).or_else(|| Some(name.clone())),
1841 ReferenceConfidence::Resolved,
1842 )
1843 } else if source_count > 1 {
1844 (
1845 None,
1846 None,
1847 SourceKind::Unknown,
1848 None,
1849 ReferenceConfidence::Ambiguous,
1850 )
1851 } else {
1852 (
1853 None,
1854 None,
1855 SourceKind::Unknown,
1856 None,
1857 ReferenceConfidence::Unknown,
1858 )
1859 };
1860
1861 refs.push(ColumnReferenceFact {
1862 source_name,
1863 source_alias,
1864 source_kind,
1865 table,
1866 column: column.name.name.clone(),
1867 unqualified: column.table.is_none(),
1868 confidence,
1869 });
1870 }
1871 }
1872
1873 dedupe_column_refs(refs)
1874}
1875
1876fn dedupe_column_refs(refs: Vec<ColumnReferenceFact>) -> Vec<ColumnReferenceFact> {
1877 let mut seen = HashSet::new();
1878 let mut deduped = Vec::new();
1879
1880 for reference in refs {
1881 let key = (
1882 reference.source_name.clone(),
1883 reference.source_alias.clone(),
1884 reference.table.clone(),
1885 reference.column.clone(),
1886 format!("{:?}", reference.source_kind),
1887 reference.unqualified,
1888 format!("{:?}", reference.confidence),
1889 );
1890 if seen.insert(key) {
1891 deduped.push(reference);
1892 }
1893 }
1894
1895 deduped
1896}
1897
1898fn relation_facts(
1899 scope: &Scope,
1900 mapping_schema: Option<&crate::schema::MappingSchema>,
1901 dialect: DialectType,
1902) -> Vec<RelationFact> {
1903 let mut relations = Vec::new();
1904 let mut seen = HashSet::new();
1905 collect_relation_facts(scope, mapping_schema, dialect, &mut seen, &mut relations);
1906
1907 relations.sort_by(|left, right| {
1908 left.name
1909 .cmp(&right.name)
1910 .then_with(|| left.alias.cmp(&right.alias))
1911 });
1912 relations
1913}
1914
1915fn collect_relation_facts(
1916 scope: &Scope,
1917 mapping_schema: Option<&crate::schema::MappingSchema>,
1918 dialect: DialectType,
1919 seen: &mut HashSet<String>,
1920 relations: &mut Vec<RelationFact>,
1921) {
1922 for relation in scope.sources.iter().map(|(source_name, source)| {
1923 let identity = source_table_identity(source);
1924 RelationFact {
1925 name: source
1926 .lineage_name
1927 .clone()
1928 .or_else(|| identity.as_ref().map(|identity| identity.name.clone()))
1929 .unwrap_or_else(|| source_name.clone()),
1930 alias: source.alias.clone().or_else(|| source_alias(source)),
1931 kind: source.kind,
1932 columns: source_columns(source, mapping_schema, dialect),
1933 catalog: identity
1934 .as_ref()
1935 .and_then(|identity| identity.catalog.clone()),
1936 schema: identity
1937 .as_ref()
1938 .and_then(|identity| identity.schema.clone()),
1939 table: identity
1940 .as_ref()
1941 .and_then(|identity| identity.table.clone()),
1942 }
1943 }) {
1944 let key = format!("{:?}|{}|{:?}", relation.kind, relation.name, relation.alias);
1945 if seen.insert(key) {
1946 relations.push(relation);
1947 }
1948 }
1949
1950 for branch_scope in &scope.union_scopes {
1951 collect_relation_facts(branch_scope, mapping_schema, dialect, seen, relations);
1952 }
1953}
1954
1955fn base_table_facts(
1956 scope: &Scope,
1957 mapping_schema: Option<&crate::schema::MappingSchema>,
1958 dialect: DialectType,
1959) -> Vec<RelationFact> {
1960 let mut relations = Vec::new();
1961 let mut seen = HashSet::new();
1962
1963 collect_base_table_facts(scope, mapping_schema, dialect, &mut seen, &mut relations);
1964
1965 relations.sort_by(|left, right| left.name.cmp(&right.name));
1966 relations
1967}
1968
1969fn collect_base_table_facts(
1970 scope: &Scope,
1971 mapping_schema: Option<&crate::schema::MappingSchema>,
1972 dialect: DialectType,
1973 seen: &mut HashSet<String>,
1974 relations: &mut Vec<RelationFact>,
1975) {
1976 for source in scope.sources.values() {
1977 if source.kind != SourceKind::Table {
1978 continue;
1979 }
1980
1981 let Some(identity) = source_table_identity(source) else {
1982 continue;
1983 };
1984
1985 if seen.insert(identity.name.clone()) {
1986 relations.push(RelationFact {
1987 name: identity.name,
1988 alias: source.alias.clone().or_else(|| source_alias(source)),
1989 kind: SourceKind::Table,
1990 columns: source_columns(source, mapping_schema, dialect),
1991 catalog: identity.catalog,
1992 schema: identity.schema,
1993 table: identity.table,
1994 });
1995 }
1996 }
1997
1998 for child_scope in scope
1999 .cte_scopes
2000 .iter()
2001 .chain(scope.union_scopes.iter())
2002 .chain(scope.table_scopes.iter())
2003 .chain(scope.derived_table_scopes.iter())
2004 .chain(scope.subquery_scopes.iter())
2005 {
2006 collect_base_table_facts(child_scope, mapping_schema, dialect, seen, relations);
2007 }
2008}
2009
2010fn source_columns(
2011 source: &SourceInfo,
2012 mapping_schema: Option<&crate::schema::MappingSchema>,
2013 dialect: DialectType,
2014) -> Vec<String> {
2015 match &source.expression {
2016 Expression::Table(table) => mapping_schema
2017 .and_then(|schema| schema.column_names(&table_name(table)).ok())
2018 .unwrap_or_default(),
2019 Expression::Select(_)
2020 | Expression::Union(_)
2021 | Expression::Intersect(_)
2022 | Expression::Except(_) => {
2023 get_output_column_names_for_dialect(&source.expression, Some(dialect))
2024 }
2025 Expression::Subquery(subquery) => {
2026 get_output_column_names_for_dialect(&subquery.this, Some(dialect))
2027 }
2028 Expression::Cte(cte) if !cte.columns.is_empty() => cte
2029 .columns
2030 .iter()
2031 .map(|column| column.name.clone())
2032 .collect(),
2033 Expression::Cte(cte) => get_output_column_names_for_dialect(&cte.this, Some(dialect)),
2034 _ => Vec::new(),
2035 }
2036}
2037
2038fn source_table_name(source: &SourceInfo) -> Option<String> {
2039 source_table_identity(source).map(|identity| identity.name)
2040}
2041
2042fn source_alias(source: &SourceInfo) -> Option<String> {
2043 match &source.expression {
2044 Expression::Table(table) => table.alias.as_ref().map(|alias| alias.name.clone()),
2045 Expression::Subquery(subquery) => subquery.alias.as_ref().map(|alias| alias.name.clone()),
2046 _ => None,
2047 }
2048}
2049
2050fn table_name(table: &TableRef) -> String {
2051 let mut parts = Vec::new();
2052 if let Some(catalog) = &table.catalog {
2053 parts.push(catalog.name.clone());
2054 }
2055 if let Some(schema) = &table.schema {
2056 parts.push(schema.name.clone());
2057 }
2058 parts.push(table.name.name.clone());
2059 parts.join(".")
2060}
2061
2062#[derive(Debug, Clone)]
2063struct RelationIdentity {
2064 name: String,
2065 catalog: Option<String>,
2066 schema: Option<String>,
2067 table: Option<String>,
2068}
2069
2070fn source_table_identity(source: &SourceInfo) -> Option<RelationIdentity> {
2071 match &source.expression {
2072 Expression::Table(table) => Some(table_identity(table)),
2073 _ => None,
2074 }
2075}
2076
2077fn table_identity(table: &TableRef) -> RelationIdentity {
2078 RelationIdentity {
2079 name: table_name(table),
2080 catalog: table.catalog.as_ref().map(|catalog| catalog.name.clone()),
2081 schema: table.schema.as_ref().map(|schema| schema.name.clone()),
2082 table: Some(table.name.name.clone()),
2083 }
2084}
2085
2086fn set_operation_facts(
2087 expression: &Expression,
2088 scope: &Scope,
2089 dialect: DialectType,
2090 nullability: &NullabilityContext<'_>,
2091) -> Vec<SetOperationFact> {
2092 let mut facts = Vec::new();
2093 collect_set_operation_facts(expression, scope, dialect, nullability, &mut facts);
2094 facts
2095}
2096
2097fn collect_set_operation_facts(
2098 expression: &Expression,
2099 scope: &Scope,
2100 dialect: DialectType,
2101 nullability: &NullabilityContext<'_>,
2102 facts: &mut Vec<SetOperationFact>,
2103) {
2104 match expression {
2105 Expression::Union(union) => {
2106 facts.push(SetOperationFact {
2107 kind: "union".to_string(),
2108 all: union.all,
2109 distinct: union.distinct,
2110 output_columns: get_output_column_names_for_dialect(expression, Some(dialect)),
2111 branches: set_operation_branches(
2112 &union.left,
2113 &union.right,
2114 scope,
2115 dialect,
2116 SetOperationBranchRole::Value,
2117 nullability,
2118 ),
2119 });
2120 collect_set_operation_facts(
2121 &union.left,
2122 scope.union_scopes.first().unwrap_or(scope),
2123 dialect,
2124 nullability,
2125 facts,
2126 );
2127 collect_set_operation_facts(
2128 &union.right,
2129 scope.union_scopes.get(1).unwrap_or(scope),
2130 dialect,
2131 nullability,
2132 facts,
2133 );
2134 }
2135 Expression::Intersect(intersect) => {
2136 facts.push(SetOperationFact {
2137 kind: "intersect".to_string(),
2138 all: intersect.all,
2139 distinct: intersect.distinct,
2140 output_columns: get_output_column_names_for_dialect(expression, Some(dialect)),
2141 branches: set_operation_branches(
2142 &intersect.left,
2143 &intersect.right,
2144 scope,
2145 dialect,
2146 SetOperationBranchRole::Filter,
2147 nullability,
2148 ),
2149 });
2150 collect_set_operation_facts(
2151 &intersect.left,
2152 scope.union_scopes.first().unwrap_or(scope),
2153 dialect,
2154 nullability,
2155 facts,
2156 );
2157 collect_set_operation_facts(
2158 &intersect.right,
2159 scope.union_scopes.get(1).unwrap_or(scope),
2160 dialect,
2161 nullability,
2162 facts,
2163 );
2164 }
2165 Expression::Except(except) => {
2166 facts.push(SetOperationFact {
2167 kind: "except".to_string(),
2168 all: except.all,
2169 distinct: except.distinct,
2170 output_columns: get_output_column_names_for_dialect(expression, Some(dialect)),
2171 branches: set_operation_branches(
2172 &except.left,
2173 &except.right,
2174 scope,
2175 dialect,
2176 SetOperationBranchRole::Filter,
2177 nullability,
2178 ),
2179 });
2180 collect_set_operation_facts(
2181 &except.left,
2182 scope.union_scopes.first().unwrap_or(scope),
2183 dialect,
2184 nullability,
2185 facts,
2186 );
2187 collect_set_operation_facts(
2188 &except.right,
2189 scope.union_scopes.get(1).unwrap_or(scope),
2190 dialect,
2191 nullability,
2192 facts,
2193 );
2194 }
2195 Expression::Subquery(subquery) => {
2196 collect_set_operation_facts(&subquery.this, scope, dialect, nullability, facts);
2197 }
2198 _ => {}
2199 }
2200}
2201
2202fn set_operation_branches(
2203 left: &Expression,
2204 right: &Expression,
2205 scope: &Scope,
2206 dialect: DialectType,
2207 right_role: SetOperationBranchRole,
2208 nullability: &NullabilityContext<'_>,
2209) -> Vec<SetOperationBranchFact> {
2210 vec![
2211 SetOperationBranchFact {
2212 index: 0,
2213 role: SetOperationBranchRole::Value,
2214 projections: projection_facts_for_query(
2215 left,
2216 scope.union_scopes.first().unwrap_or(scope),
2217 dialect,
2218 nullability,
2219 ),
2220 },
2221 SetOperationBranchFact {
2222 index: 1,
2223 role: right_role,
2224 projections: projection_facts_for_query(
2225 right,
2226 scope.union_scopes.get(1).unwrap_or(scope),
2227 dialect,
2228 nullability,
2229 ),
2230 },
2231 ]
2232}
2233
2234fn non_empty_string(value: String) -> Option<String> {
2235 if value.is_empty() {
2236 None
2237 } else {
2238 Some(value)
2239 }
2240}