Skip to main content

uqa_sql/semantics/
source_shape.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Source qualifiers, outer-join nullability, and subquery dependencies.
8
9use super::from_qualifier_set;
10use crate::plan::SourcePlan;
11use crate::ScalarExpr;
12use std::collections::BTreeSet;
13
14/// Qualifiers whose rows can be synthesized as NULLs by an outer join cannot
15/// receive an arbitrary WHERE predicate before that join. A predicate such as
16/// `right.id IS NULL` accepts the synthesized row; pushing it into the right
17/// scan first can remove a real match, manufacture a NULL-extended row, and
18/// turn a non-result into a result. Keep predicates on these qualifiers above
19/// the outer join unless a separate rewrite has first reduced it to an inner
20/// join.
21pub fn outer_join_nullable_qualifiers(from: &SourcePlan) -> BTreeSet<String> {
22    let SourcePlan::Join {
23        left,
24        right,
25        kind,
26        alias,
27        ..
28    } = from
29    else {
30        return BTreeSet::new();
31    };
32    let left_nullable = outer_join_nullable_qualifiers(left);
33    let right_nullable = outer_join_nullable_qualifiers(right);
34    if let Some(alias) = alias {
35        let nullable = matches!(
36            kind,
37            crate::ast::JoinKind::Left | crate::ast::JoinKind::Right | crate::ast::JoinKind::Full
38        ) || !left_nullable.is_empty()
39            || !right_nullable.is_empty();
40        return if nullable {
41            BTreeSet::from([alias.clone()])
42        } else {
43            BTreeSet::new()
44        };
45    }
46    let mut nullable = left_nullable;
47    nullable.extend(right_nullable);
48    match kind {
49        crate::ast::JoinKind::Left => nullable.extend(from_qualifier_set(right)),
50        crate::ast::JoinKind::Right => nullable.extend(from_qualifier_set(left)),
51        crate::ast::JoinKind::Full => {
52            nullable.extend(from_qualifier_set(left));
53            nullable.extend(from_qualifier_set(right));
54        }
55        crate::ast::JoinKind::Inner | crate::ast::JoinKind::Cross => {}
56    }
57    nullable
58}
59
60pub fn collect_from_qualifiers(from: &SourcePlan, out: &mut Vec<String>) {
61    match from {
62        SourcePlan::Join {
63            left, right, alias, ..
64        } => {
65            if let Some(alias) = alias {
66                out.push(alias.clone());
67            } else {
68                collect_from_qualifiers(left, out);
69                collect_from_qualifiers(right, out);
70            }
71        }
72        SourcePlan::Table { .. }
73        | SourcePlan::Values { .. }
74        | SourcePlan::Function { .. }
75        | SourcePlan::FunctionGroup { .. }
76        | SourcePlan::Subquery { .. } => {
77            if let Some(qualifier) = from.visible_qualifier() {
78                out.push(qualifier.to_string());
79            }
80        }
81    }
82}
83
84pub fn collect_subquery_ids(expression: &ScalarExpr, output: &mut BTreeSet<usize>) {
85    match expression {
86        ScalarExpr::ScalarSubquery(id) | ScalarExpr::Exists { subquery: id, .. } => {
87            output.insert(*id);
88        }
89        ScalarExpr::InSubquery { expr, subquery, .. } => {
90            collect_subquery_ids(expr, output);
91            output.insert(*subquery);
92        }
93        ScalarExpr::Array(items)
94        | ScalarExpr::Row(items)
95        | ScalarExpr::CompositeRow { items, .. }
96        | ScalarExpr::And(items)
97        | ScalarExpr::Or(items) => {
98            for item in items {
99                collect_subquery_ids(item, output);
100            }
101        }
102        ScalarExpr::Func {
103            args,
104            order_by,
105            filter,
106            ..
107        } => {
108            for argument in args {
109                collect_subquery_ids(argument, output);
110            }
111            for order in order_by {
112                collect_subquery_ids(&order.expr, output);
113            }
114            if let Some(filter) = filter {
115                collect_subquery_ids(filter, output);
116            }
117        }
118        ScalarExpr::Binary { lhs, rhs, .. } => {
119            collect_subquery_ids(lhs, output);
120            collect_subquery_ids(rhs, output);
121        }
122        ScalarExpr::Not(inner)
123        | ScalarExpr::UnaryMinus(inner)
124        | ScalarExpr::IsNull { expr: inner, .. }
125        | ScalarExpr::Cast { expr: inner, .. } => collect_subquery_ids(inner, output),
126        ScalarExpr::Between { expr, low, high } => {
127            collect_subquery_ids(expr, output);
128            collect_subquery_ids(low, output);
129            collect_subquery_ids(high, output);
130        }
131        ScalarExpr::InList { expr, list, .. } => {
132            collect_subquery_ids(expr, output);
133            for item in list {
134                collect_subquery_ids(item, output);
135            }
136        }
137        ScalarExpr::WindowCall {
138            args, spec, filter, ..
139        } => {
140            for argument in args {
141                collect_subquery_ids(argument, output);
142            }
143            if let Some(filter) = filter {
144                collect_subquery_ids(filter, output);
145            }
146            for partition in &spec.partition_by {
147                collect_subquery_ids(partition, output);
148            }
149            for order in &spec.order_by {
150                collect_subquery_ids(&order.expr, output);
151            }
152            if let Some(frame) = &spec.frame {
153                collect_frame_bound_subquery_ids(&frame.start, output);
154                collect_frame_bound_subquery_ids(&frame.end, output);
155            }
156        }
157        ScalarExpr::Case {
158            base,
159            when,
160            else_branch,
161        } => {
162            if let Some(base) = base {
163                collect_subquery_ids(base, output);
164            }
165            for (condition, result) in when {
166                collect_subquery_ids(condition, output);
167                collect_subquery_ids(result, output);
168            }
169            if let Some(branch) = else_branch {
170                collect_subquery_ids(branch, output);
171            }
172        }
173        ScalarExpr::Default
174        | ScalarExpr::Star
175        | ScalarExpr::QualifiedStar(_)
176        | ScalarExpr::Column(_)
177        | ScalarExpr::Position(_)
178        | ScalarExpr::InternalColumn(_)
179        | ScalarExpr::QualifiedColumn { .. }
180        | ScalarExpr::Literal(_)
181        | ScalarExpr::TypedLiteral { .. }
182        | ScalarExpr::Param(_) => {}
183    }
184}
185
186pub fn collect_frame_bound_subquery_ids(
187    bound: &crate::ScalarFrameBound,
188    output: &mut BTreeSet<usize>,
189) {
190    match bound {
191        crate::ScalarFrameBound::Preceding(expression)
192        | crate::ScalarFrameBound::Following(expression) => {
193            collect_subquery_ids(expression, output);
194        }
195        crate::ScalarFrameBound::UnboundedPreceding
196        | crate::ScalarFrameBound::UnboundedFollowing
197        | crate::ScalarFrameBound::CurrentRow => {}
198    }
199}