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