Skip to main content

uqa_sql/catalog/events/definition/
dependents.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Bound rule and trigger dependencies observed at each catalog read boundary.
8use super::lookup::EventLookupContext;
9use crate::{
10    catalog::events::{PreparedRuleColumnDrop, RuleColumnDependency, StoredRule},
11    SQLError,
12};
13use std::collections::BTreeMap;
14use uqa_core::RelationIdentity;
15pub struct EventColumnDependencies {
16    pub triggers: Vec<String>,
17    pub rules: Vec<(RelationIdentity, String)>,
18}
19impl EventLookupContext<'_> {
20    pub fn prepare_rule_column_drop(
21        &self,
22        table: &str,
23        column: &str,
24    ) -> Result<PreparedRuleColumnDrop, String> {
25        let relation = RelationIdentity::from_legacy_name(table)?;
26        let dependency = RuleColumnDependency {
27            relation: relation.clone(),
28            column: column.to_string(),
29        };
30        let mut rules = self.registry.read_rules().clone();
31        let mut rebind = std::collections::BTreeSet::new();
32        for (event_relation, entries) in &mut rules {
33            for (name, rule) in entries {
34                let dependencies = rule.dependencies.as_ref().ok_or_else(|| {
35                    format!(
36                        "rule `{}` on `{}` has no bound dependency state",
37                        rule.definition.name,
38                        event_relation.qualified_name()
39                    )
40                })?;
41                if dependencies.columns.contains(&dependency) {
42                    return Err(format!(
43                        "cannot drop column {column} of table {table} because rule {} on {} depends on it",
44                        rule.definition.name,
45                        event_relation.qualified_name()
46                    ));
47                }
48                if event_relation != &relation && !dependencies.relations.contains(&relation) {
49                    continue;
50                }
51                crate::binding::stored_columns::remove_rule_source_column_aliases(
52                    self.analysis.columns,
53                    &mut rule.definition,
54                    &dependency,
55                )
56                .map_err(|error| {
57                    format!(
58                        "reshape rule `{}` source aliases before column drop: {error}",
59                        rule.definition.name
60                    )
61                })?;
62                rebind.insert((event_relation.clone(), name.clone()));
63            }
64        }
65        Ok(PreparedRuleColumnDrop { rules, rebind })
66    }
67
68    pub fn column_event_dependencies(
69        &self,
70        table: &str,
71        column: &str,
72    ) -> Result<EventColumnDependencies, SQLError> {
73        let relation = RelationIdentity::from_legacy_name(table).map_err(|error| {
74            SQLError::Internal(format!("decode trigger relation `{table}`: {error}"))
75        })?;
76        let dependent_triggers = self
77            .registry
78            .read_triggers()
79            .get(&relation)
80            .into_iter()
81            .flat_map(BTreeMap::values)
82            .filter(|trigger| {
83                trigger
84                    .definition
85                    .update_columns
86                    .iter()
87                    .any(|name| name == column)
88                    || trigger.definition.when.as_ref().is_some_and(|condition| {
89                        crate::schema::dependencies::schema_expr_references_column(
90                            condition, column,
91                        )
92                    })
93            })
94            .map(|trigger| trigger.definition.name.clone())
95            .collect::<Vec<_>>();
96        let rules = self.registry.read_rules();
97        let dependent_rules = dependent_rules_for_column(&rules, &relation, column)?;
98        drop(rules);
99        Ok(EventColumnDependencies {
100            triggers: dependent_triggers,
101            rules: dependent_rules,
102        })
103    }
104}
105
106fn dependent_rules_for_column(
107    rules: &BTreeMap<RelationIdentity, BTreeMap<String, StoredRule>>,
108    relation: &RelationIdentity,
109    column: &str,
110) -> Result<Vec<(RelationIdentity, String)>, SQLError> {
111    let dependency = RuleColumnDependency {
112        relation: relation.clone(),
113        column: column.to_string(),
114    };
115    let mut dependent = Vec::new();
116    for (event_relation, entries) in rules {
117        for rule in entries.values() {
118            let dependencies = rule.dependencies.as_ref().ok_or_else(|| {
119                SQLError::Internal(format!(
120                    "rule `{}` on `{}` has no bound dependency state",
121                    rule.definition.name,
122                    event_relation.qualified_name()
123                ))
124            })?;
125            if dependencies.columns.contains(&dependency) {
126                dependent.push((event_relation.clone(), rule.definition.name.clone()));
127            }
128        }
129    }
130    Ok(dependent)
131}