Skip to main content

uqa_sql/catalog/events/
dependencies.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Relation and routine dependencies of stored query plans, including mutation CTEs.
8
9mod ctes;
10use super::{RuleDependencies, RuleRoutineDependency};
11use crate::{
12    plan::{QueryPlan, RelationalPlan, SourcePlan},
13    SQLError,
14};
15use ctes::{collect_cte_relation_dependencies, collect_cte_source_routine_dependencies};
16use std::collections::BTreeSet;
17use uqa_core::RelationIdentity;
18
19pub fn collect_query_relation_dependencies(
20    query: &QueryPlan,
21    dependencies: &mut RuleDependencies,
22    inherited_ctes: &BTreeSet<String>,
23) -> Result<(), SQLError> {
24    let mut visible_ctes = inherited_ctes.clone();
25    let recursive = query.ctes.iter().any(|cte| cte.recursive).then(|| {
26        query
27            .ctes
28            .iter()
29            .map(|cte| cte.name.clone())
30            .collect::<BTreeSet<_>>()
31    });
32    for cte in &query.ctes {
33        let body_scope = recursive.as_ref().map_or_else(
34            || visible_ctes.clone(),
35            |recursive| inherited_ctes.union(recursive).cloned().collect(),
36        );
37        collect_cte_relation_dependencies(&cte.body, dependencies, &body_scope)?;
38        visible_ctes.insert(cte.name.clone());
39    }
40    match &query.root {
41        RelationalPlan::QueryBlock(block) => {
42            if let Some(source) = &block.from {
43                collect_source_relation_dependencies(source, dependencies, &visible_ctes)?;
44            }
45            for subquery in &block.subqueries {
46                collect_query_relation_dependencies(subquery, dependencies, &visible_ctes)?;
47            }
48        }
49        RelationalPlan::SetOp {
50            left,
51            right,
52            subqueries,
53            ..
54        } => {
55            collect_query_relation_dependencies(left, dependencies, &visible_ctes)?;
56            collect_query_relation_dependencies(right, dependencies, &visible_ctes)?;
57            for subquery in subqueries {
58                collect_query_relation_dependencies(subquery, dependencies, &visible_ctes)?;
59            }
60        }
61        RelationalPlan::Values { subqueries, .. } => {
62            for subquery in subqueries {
63                collect_query_relation_dependencies(subquery, dependencies, &visible_ctes)?;
64            }
65        }
66    }
67    Ok(())
68}
69
70pub fn collect_expression_routine_dependencies(
71    expression: &crate::plan::ExpressionPlan,
72    dependencies: &mut RuleDependencies,
73) {
74    let mut scalar = expression.scalar.clone();
75    crate::plan::rewrite_scalar_expression(&mut scalar, &mut |expression| {
76        if let crate::ScalarExpr::Func {
77            binding: Some(binding),
78            ..
79        } = expression
80        {
81            insert_routine_dependency(binding, dependencies);
82        }
83    });
84    for query in &expression.subqueries {
85        collect_query_routine_dependencies(query, dependencies);
86    }
87}
88
89pub fn collect_query_routine_dependencies(query: &QueryPlan, dependencies: &mut RuleDependencies) {
90    let mut scalar_plan = query.clone();
91    scalar_plan.rewrite_scalar_expressions(&mut |expression| {
92        if let crate::ScalarExpr::Func {
93            binding: Some(binding),
94            ..
95        } = expression
96        {
97            insert_routine_dependency(binding, dependencies);
98        }
99    });
100    for cte in &query.ctes {
101        collect_cte_source_routine_dependencies(&cte.body, dependencies);
102    }
103    collect_relational_source_routine_dependencies(&query.root, dependencies);
104}
105
106fn collect_query_source_routine_dependencies(
107    query: &QueryPlan,
108    dependencies: &mut RuleDependencies,
109) {
110    for cte in &query.ctes {
111        collect_cte_source_routine_dependencies(&cte.body, dependencies);
112    }
113    collect_relational_source_routine_dependencies(&query.root, dependencies);
114}
115
116fn collect_relational_source_routine_dependencies(
117    plan: &RelationalPlan,
118    dependencies: &mut RuleDependencies,
119) {
120    match plan {
121        RelationalPlan::QueryBlock(block) => {
122            if let Some(source) = &block.from {
123                collect_source_routine_dependencies(source, dependencies);
124            }
125            for subquery in &block.subqueries {
126                collect_query_source_routine_dependencies(subquery, dependencies);
127            }
128        }
129        RelationalPlan::SetOp {
130            left,
131            right,
132            subqueries,
133            ..
134        } => {
135            collect_query_source_routine_dependencies(left, dependencies);
136            collect_query_source_routine_dependencies(right, dependencies);
137            for subquery in subqueries {
138                collect_query_source_routine_dependencies(subquery, dependencies);
139            }
140        }
141        RelationalPlan::Values { subqueries, .. } => {
142            for subquery in subqueries {
143                collect_query_source_routine_dependencies(subquery, dependencies);
144            }
145        }
146    }
147}
148
149fn collect_source_routine_dependencies(source: &SourcePlan, dependencies: &mut RuleDependencies) {
150    match source {
151        SourcePlan::Table { .. } | SourcePlan::Values { .. } => {}
152        SourcePlan::Join { left, right, .. } => {
153            collect_source_routine_dependencies(left, dependencies);
154            collect_source_routine_dependencies(right, dependencies);
155        }
156        SourcePlan::Subquery { body, .. } => {
157            collect_query_source_routine_dependencies(body, dependencies);
158        }
159        SourcePlan::Function { binding, .. } => {
160            if let Some(binding) = binding {
161                insert_routine_dependency(binding, dependencies);
162            }
163        }
164        SourcePlan::FunctionGroup { functions, .. } => {
165            for function in functions {
166                if let Some(binding) = &function.binding {
167                    insert_routine_dependency(binding, dependencies);
168                }
169            }
170        }
171    }
172}
173
174fn insert_routine_dependency(
175    binding: &crate::ast::FunctionBinding,
176    dependencies: &mut RuleDependencies,
177) {
178    if !binding.builtin {
179        dependencies.routines.insert(RuleRoutineDependency {
180            object_id: binding.object_id,
181            name: binding.name.clone(),
182            argument_types: binding.argument_types.clone(),
183        });
184    }
185}
186
187fn collect_source_relation_dependencies(
188    source: &SourcePlan,
189    dependencies: &mut RuleDependencies,
190    visible_ctes: &BTreeSet<String>,
191) -> Result<(), SQLError> {
192    match source {
193        SourcePlan::Table { name, .. } => {
194            if crate::binding::view_dependencies::canonical_virtual_relation_reference(name)
195                .is_some()
196            {
197                return Ok(());
198            }
199            let (schema, relation) = RelationIdentity::parse_reference(name).map_err(|error| {
200                SQLError::Internal(format!("decode stored rule dependency `{name}`: {error}"))
201            })?;
202            if schema.is_none() && visible_ctes.contains(&relation) {
203                return Ok(());
204            }
205            let schema = schema.ok_or_else(|| {
206                SQLError::Internal(format!(
207                    "stored rule relation dependency `{name}` is not catalog-bound"
208                ))
209            })?;
210            dependencies
211                .relations
212                .insert(RelationIdentity::new(schema, relation));
213        }
214        SourcePlan::Join { left, right, .. } => {
215            collect_source_relation_dependencies(left, dependencies, visible_ctes)?;
216            collect_source_relation_dependencies(right, dependencies, visible_ctes)?;
217        }
218        SourcePlan::Subquery { body, .. } => {
219            collect_query_relation_dependencies(body, dependencies, visible_ctes)?;
220        }
221        SourcePlan::Function { relations, .. } => {
222            if let Some(relations) = relations {
223                collect_canonical_relation(&relations.left, dependencies)?;
224                collect_canonical_relation(&relations.right, dependencies)?;
225            }
226        }
227        SourcePlan::FunctionGroup { functions, .. } => {
228            for function in functions {
229                if let Some(relations) = &function.relations {
230                    collect_canonical_relation(&relations.left, dependencies)?;
231                    collect_canonical_relation(&relations.right, dependencies)?;
232                }
233            }
234        }
235        SourcePlan::Values { .. } => {}
236    }
237    Ok(())
238}
239
240fn collect_canonical_relation(
241    reference: &str,
242    dependencies: &mut RuleDependencies,
243) -> Result<(), SQLError> {
244    let relation = RelationIdentity::from_legacy_name(reference).map_err(|error| {
245        SQLError::Internal(format!(
246            "decode stored rule dependency `{reference}`: {error}"
247        ))
248    })?;
249    dependencies.relations.insert(relation);
250    Ok(())
251}