Skip to main content

polyglot_sql/
query_analysis.rs

1//! Compact query analysis facts.
2//!
3//! This module intentionally builds on the existing parser, scope builder, type
4//! annotator, and lineage implementation. It is a convenience API: callers that
5//! need the full AST or full lineage graph should continue using those lower
6//! level APIs directly.
7
8use 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/// Options for [`analyze_query`].
26#[derive(Debug, Clone, Serialize, Deserialize, Default)]
27#[serde(rename_all = "camelCase", default, deny_unknown_fields)]
28pub struct AnalyzeQueryOptions {
29    /// Per-call limits used for parsing and rendering analysis facts.
30    pub complexity_guard: Option<ComplexityGuardOptions>,
31    /// SQL dialect used for parsing and dialect-aware rendering.
32    pub dialect: DialectType,
33    /// Optional validation schema used for qualification and type annotation.
34    pub schema: Option<ValidationSchema>,
35}
36
37/// Compact facts about a query's output shape and data dependencies.
38#[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    /// Clause-specific uses, separate from output projection lineage.
50    #[serde(default)]
51    pub column_uses: Vec<ColumnUseFact>,
52}
53
54/// A half-open range in the original SQL, measured in Unicode characters.
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
56pub struct QuerySourceSpan {
57    pub start: usize,
58    pub end: usize,
59}
60
61/// The syntactic role of a column-containing expression.
62#[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/// One resolved dependency of an original column occurrence. Several terminal
79/// dependencies can share a span (for example, a reference to a computed CTE
80/// column). Spans identify the use, not the upstream column's definition.
81#[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/// One containing expression, with references in occurrence order. Paths are
91/// deterministic within an analysis, not persistent IDs across SQL edits.
92#[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    /// Dialect-rendered SQL; not necessarily the original source substring.
99    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/// Top-level query shape.
106#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
107#[serde(rename_all = "snake_case")]
108pub enum QueryShape {
109    Select,
110    SetOperation,
111}
112
113/// Compact fact about one output projection.
114#[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/// Compact fact about a function-like projection transform.
131#[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/// Compact fact about one top-level CTE definition.
140#[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/// Compact fact about one original star projection.
150#[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/// Compact fact about an upstream column reference.
159#[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/// Compact fact about a relation visible in the root scope.
172#[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/// Compact fact about a set operation.
185#[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/// Compact facts for one immediate set-operation branch.
196#[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/// Semantic contribution of one set-operation branch.
205#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
206#[serde(rename_all = "snake_case")]
207pub enum SetOperationBranchRole {
208    Value,
209    Filter,
210}
211
212/// High-level kind of transformation performed by a projection.
213#[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/// Confidence level for a compact upstream column reference.
225#[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/// Conservative nullability classification for one output projection.
234#[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
242/// Analyze a single SELECT or set-operation query.
243pub 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            // Analysis is not validation. Incomplete schemas and unresolved
281            // lexical references must still yield conservative usage facts.
282            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    // Keep each CTE's definition environment, rather than resolving it in the
485    // environment of a later consumer that may shadow the same names.
486    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
923/// Return one representative projection for each result ordinal together with
924/// whether any immediate name-aligned branch contributes a synthetic NULL.
925fn 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    // Inference is optional metadata: cycles and excessive dependency depth
1463    // must yield Unknown, not recurse indefinitely or claim non-nullability.
1464    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                // An open source can also provide this column. Do not pick a
1671                // known source just because the other source lacks metadata.
1672                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}