Skip to main content

uqa_sql/catalog/events/definition/
rewrites.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Rebind event definitions and stored dependencies after routine and column changes.
8use super::EventAnalysisContext;
9use crate::{
10    ast::{Expr, FunctionBinding},
11    catalog::{
12        events::{
13            PreparedRuleColumnDrop, RuleCatalog, RuleColumnDependency, StoredRule, TriggerCatalog,
14        },
15        resolution::RelationLookupMode,
16    },
17    plpgsql::{ResolvedVariable, VariableResolver},
18    SQLError,
19};
20use std::collections::BTreeMap;
21use uqa_core::RelationIdentity;
22pub fn rewrite_trigger_routine_references(
23    next_triggers: &mut TriggerCatalog,
24    target: &FunctionBinding,
25    new_name: &str,
26) -> Result<bool, SQLError> {
27    let mut triggers_changed = false;
28    for trigger in next_triggers.values_mut().flat_map(BTreeMap::values_mut) {
29        let invokes_target = match (trigger.function_object_id, target.object_id) {
30            (Some(stored), Some(target)) => stored == target,
31            (None, None) => {
32                target.argument_types.is_empty() && trigger.definition.function == target.name
33            }
34            _ => false,
35        };
36        if invokes_target {
37            trigger.definition.function = new_name.to_string();
38            triggers_changed = true;
39        }
40        if let Some(condition) = &mut trigger.definition.when {
41            triggers_changed |= crate::catalog::stored_ast::rewrite_expression_routine_identity(
42                condition, target, new_name,
43            )?;
44        }
45    }
46
47    Ok(triggers_changed)
48}
49impl EventAnalysisContext<'_> {
50    pub fn rewrite_rule_routine_references(
51        &self,
52        next_rules: &mut RuleCatalog,
53        target: &FunctionBinding,
54        new_name: &str,
55    ) -> Result<bool, SQLError> {
56        let mut rules_changed = false;
57        for (event_relation, entries) in next_rules {
58            for rule in entries.values_mut() {
59                let references_target = rule
60                    .dependencies
61                    .as_ref()
62                    .ok_or_else(|| {
63                        SQLError::Internal(format!(
64                            "rule `{}` on `{}` has no bound dependency state",
65                            rule.definition.name,
66                            event_relation.qualified_name()
67                        ))
68                    })?
69                    .routines
70                    .iter()
71                    .any(
72                        |dependency| match (dependency.object_id, target.object_id) {
73                            (Some(dependency), Some(target)) => dependency == target,
74                            (None, None) => {
75                                dependency.name == target.name
76                                    && dependency.argument_types == target.argument_types
77                            }
78                            _ => false,
79                        },
80                    );
81                if !references_target {
82                    continue;
83                }
84                if let Some(condition) = &mut rule.definition.condition {
85                    crate::catalog::stored_ast::rewrite_expression_routine_identity(
86                        condition, target, new_name,
87                    )?;
88                }
89                for action in &mut rule.definition.actions {
90                    crate::catalog::stored_ast::rewrite_statement_routine_identity(
91                        action, target, new_name,
92                    )?;
93                }
94                let (validated_relation, condition_plan, condition_binding, dependencies) = self
95                    .validate_rule_definition(
96                        &mut rule.definition,
97                        RelationLookupMode::Bound,
98                        None,
99                        None,
100                    )?;
101                if &validated_relation != event_relation {
102                    return Err(SQLError::Internal(format!(
103                        "rewritten rule `{}` moved from `{}` to `{}`",
104                        rule.definition.name,
105                        event_relation.qualified_name(),
106                        validated_relation.qualified_name()
107                    )));
108                }
109                rule.condition_plan = condition_plan;
110                rule.condition_binding = condition_binding;
111                rule.dependencies = Some(dependencies);
112                rules_changed = true;
113            }
114        }
115
116        Ok(rules_changed)
117    }
118    pub fn renamed_event_column(
119        &self,
120        triggers: &TriggerCatalog,
121        rules: &RuleCatalog,
122        relation: &RelationIdentity,
123        from: &str,
124        to: &str,
125    ) -> Result<Option<(TriggerCatalog, RuleCatalog)>, String> {
126        let dependency = RuleColumnDependency {
127            relation: relation.clone(),
128            column: from.to_string(),
129        };
130        let referenced_by_rule = rules.values().any(|entries| {
131            entries.values().any(|rule| {
132                rule.dependencies
133                    .as_ref()
134                    .is_some_and(|dependencies| dependencies.columns.contains(&dependency))
135            })
136        });
137        if !triggers.contains_key(relation) && !rules.contains_key(relation) && !referenced_by_rule
138        {
139            return Ok(None);
140        }
141        let mut next_triggers = triggers.clone();
142        let mut next_rules = rules.clone();
143        if let Some(entries) = next_triggers.get_mut(relation) {
144            for trigger in entries.values_mut() {
145                for column in &mut trigger.definition.update_columns {
146                    if column == from {
147                        *column = to.to_string();
148                    }
149                }
150                if let Some(condition) = trigger.definition.when.as_mut() {
151                    crate::schema::dependencies::rewrites::rename_schema_expr_column(
152                        condition, from, to,
153                    )?;
154                }
155            }
156        }
157        self.rewrite_rule_catalog_column(&mut next_rules, &dependency, relation, from, to)?;
158
159        Ok(Some((next_triggers, next_rules)))
160    }
161    pub fn rewrite_rule_catalog_column(
162        &self,
163        rules: &mut BTreeMap<RelationIdentity, BTreeMap<String, StoredRule>>,
164        dependency: &RuleColumnDependency,
165        relation: &RelationIdentity,
166        from: &str,
167        to: &str,
168    ) -> Result<(), String> {
169        for (event_relation, entries) in rules {
170            for rule in entries.values_mut() {
171                let dependencies = rule.dependencies.as_ref().ok_or_else(|| {
172                    format!(
173                        "rule `{}` on `{}` has no bound dependency state",
174                        rule.definition.name,
175                        event_relation.qualified_name()
176                    )
177                })?;
178                if dependencies.columns.contains(dependency) {
179                    self.rewrite_stored_rule_column(rule, event_relation, relation, from, to)?;
180                }
181            }
182        }
183        Ok(())
184    }
185
186    fn rewrite_stored_rule_column(
187        &self,
188        rule: &mut StoredRule,
189        event_relation: &RelationIdentity,
190        relation: &RelationIdentity,
191        from: &str,
192        to: &str,
193    ) -> Result<(), String> {
194        if event_relation == relation {
195            self.rewrite_rule_event_row_column(rule, from, to)?;
196        }
197        crate::binding::stored_columns::rewrite_rule_column_references(
198            self.columns,
199            &mut rule.definition,
200            relation,
201            from,
202            to,
203        )
204        .map_err(|error| {
205            format!(
206                "rewrite rule `{}` column dependency: {error}",
207                rule.definition.name
208            )
209        })?;
210        let (validated_relation, condition_plan, condition_binding, dependencies) = self
211            .validate_rule_definition(&mut rule.definition, RelationLookupMode::Bound, None, None)
212            .map_err(|error| {
213                format!(
214                    "rebind rule `{}` after column rename: {error}",
215                    rule.definition.name
216                )
217            })?;
218        if validated_relation != *event_relation {
219            return Err(format!(
220                "rule `{}` changed event relation while rebinding column rename",
221                rule.definition.name
222            ));
223        }
224        rule.condition_plan = condition_plan;
225        rule.condition_binding = condition_binding;
226        rule.dependencies = Some(dependencies);
227        Ok(())
228    }
229
230    fn rewrite_rule_event_row_column(
231        &self,
232        rule: &mut StoredRule,
233        from: &str,
234        to: &str,
235    ) -> Result<(), String> {
236        if let Some(condition) = rule.definition.condition.as_mut() {
237            *condition = crate::semantics::rules::action_binding::bind_rule_expr_scoped(
238                condition,
239                &mut RuleColumnResolver {
240                    from,
241                    to: Some(to),
242                    referenced: false,
243                },
244                &std::collections::BTreeSet::new(),
245            )
246            .map_err(|error| format!("rename rule condition column: {error}"))?;
247        }
248        for action in &mut rule.definition.actions {
249            let action_columns = self
250                .rule_action_target_columns(action)
251                .map_err(|error| format!("read rule action columns during rename: {error}"))?;
252            *action = crate::semantics::rules::action_binding::bind_rule_action(
253                self.sources,
254                action,
255                &action_columns,
256                &mut RuleColumnResolver {
257                    from,
258                    to: Some(to),
259                    referenced: false,
260                },
261            )
262            .map_err(|error| format!("rename rule event column: {error}"))?;
263        }
264        Ok(())
265    }
266    pub fn rebind_rule_column_drop(
267        &self,
268        prepared: &mut PreparedRuleColumnDrop,
269    ) -> Result<(), String> {
270        for (event_relation, name) in &prepared.rebind {
271            let rule = prepared
272                .rules
273                .get_mut(event_relation)
274                .and_then(|entries| entries.get_mut(name))
275                .ok_or_else(|| {
276                    format!(
277                        "rule `{name}` on `{}` disappeared while dropping a column",
278                        event_relation.qualified_name()
279                    )
280                })?;
281            let (validated_relation, condition_plan, condition_binding, dependencies) = self
282                .validate_rule_definition(
283                    &mut rule.definition,
284                    RelationLookupMode::Bound,
285                    None,
286                    None,
287                )
288                .map_err(|error| {
289                    format!(
290                        "rebind rule `{}` after column drop: {error}",
291                        rule.definition.name
292                    )
293                })?;
294            if validated_relation != *event_relation {
295                return Err(format!(
296                    "rule `{}` changed event relation while rebinding column drop",
297                    rule.definition.name
298                ));
299            }
300            rule.condition_plan = condition_plan;
301            rule.condition_binding = condition_binding;
302            rule.dependencies = Some(dependencies);
303        }
304
305        Ok(())
306    }
307}
308
309struct RuleColumnResolver<'a> {
310    from: &'a str,
311    to: Option<&'a str>,
312    referenced: bool,
313}
314
315impl VariableResolver for RuleColumnResolver<'_> {
316    fn resolve_name(&mut self, _name: &str) -> Result<Option<ResolvedVariable>, SQLError> {
317        Ok(None)
318    }
319
320    fn resolve_qualified(
321        &mut self,
322        _qualifier: &str,
323        _column: &str,
324    ) -> Result<Option<ResolvedVariable>, SQLError> {
325        Ok(None)
326    }
327
328    fn resolve_param(&mut self, _index: usize) -> Result<Option<ResolvedVariable>, SQLError> {
329        Ok(None)
330    }
331
332    fn rewrite_qualified(
333        &mut self,
334        qualifier: &str,
335        column: &str,
336    ) -> Result<Option<Expr>, SQLError> {
337        if (qualifier.eq_ignore_ascii_case("old") || qualifier.eq_ignore_ascii_case("new"))
338            && column == self.from
339        {
340            self.referenced = true;
341            if let Some(to) = self.to {
342                return Ok(Some(Expr::qualified_column(qualifier, to)));
343            }
344        }
345        Ok(None)
346    }
347}