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::And(items)
96        | ScalarExpr::Or(items) => {
97            for item in items {
98                collect_subquery_ids(item, output);
99            }
100        }
101        ScalarExpr::Func {
102            args,
103            order_by,
104            filter,
105            ..
106        } => {
107            for argument in args {
108                collect_subquery_ids(argument, output);
109            }
110            for order in order_by {
111                collect_subquery_ids(&order.expr, output);
112            }
113            if let Some(filter) = filter {
114                collect_subquery_ids(filter, output);
115            }
116        }
117        ScalarExpr::Binary { lhs, rhs, .. } => {
118            collect_subquery_ids(lhs, output);
119            collect_subquery_ids(rhs, output);
120        }
121        ScalarExpr::Not(inner)
122        | ScalarExpr::UnaryMinus(inner)
123        | ScalarExpr::IsNull { expr: inner, .. }
124        | ScalarExpr::Cast { expr: inner, .. } => collect_subquery_ids(inner, output),
125        ScalarExpr::Between { expr, low, high } => {
126            collect_subquery_ids(expr, output);
127            collect_subquery_ids(low, output);
128            collect_subquery_ids(high, output);
129        }
130        ScalarExpr::InList { expr, list, .. } => {
131            collect_subquery_ids(expr, output);
132            for item in list {
133                collect_subquery_ids(item, output);
134            }
135        }
136        ScalarExpr::WindowCall { args, spec, .. } => {
137            for argument in args {
138                collect_subquery_ids(argument, output);
139            }
140            for partition in &spec.partition_by {
141                collect_subquery_ids(partition, output);
142            }
143            for order in &spec.order_by {
144                collect_subquery_ids(&order.expr, output);
145            }
146            if let Some(frame) = &spec.frame {
147                collect_frame_bound_subquery_ids(&frame.start, output);
148                collect_frame_bound_subquery_ids(&frame.end, output);
149            }
150        }
151        ScalarExpr::Case {
152            base,
153            when,
154            else_branch,
155        } => {
156            if let Some(base) = base {
157                collect_subquery_ids(base, output);
158            }
159            for (condition, result) in when {
160                collect_subquery_ids(condition, output);
161                collect_subquery_ids(result, output);
162            }
163            if let Some(branch) = else_branch {
164                collect_subquery_ids(branch, output);
165            }
166        }
167        ScalarExpr::Default
168        | ScalarExpr::Star
169        | ScalarExpr::QualifiedStar(_)
170        | ScalarExpr::Column(_)
171        | ScalarExpr::Position(_)
172        | ScalarExpr::InternalColumn(_)
173        | ScalarExpr::QualifiedColumn { .. }
174        | ScalarExpr::Literal(_)
175        | ScalarExpr::TypedLiteral { .. }
176        | ScalarExpr::Param(_) => {}
177    }
178}
179
180pub fn collect_frame_bound_subquery_ids(
181    bound: &crate::ScalarFrameBound,
182    output: &mut BTreeSet<usize>,
183) {
184    match bound {
185        crate::ScalarFrameBound::Preceding(expression)
186        | crate::ScalarFrameBound::Following(expression) => {
187            collect_subquery_ids(expression, output);
188        }
189        crate::ScalarFrameBound::UnboundedPreceding
190        | crate::ScalarFrameBound::UnboundedFollowing
191        | crate::ScalarFrameBound::CurrentRow => {}
192    }
193}