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                crate::catalog::events::synchronize_rule_sql_text(&mut rule.definition)?;
95                let (validated_relation, condition_plan, condition_binding, dependencies) = self
96                    .validate_rule_definition(
97                        &mut rule.definition,
98                        RelationLookupMode::Bound,
99                        None,
100                        None,
101                    )?;
102                if &validated_relation != event_relation {
103                    return Err(SQLError::Internal(format!(
104                        "rewritten rule `{}` moved from `{}` to `{}`",
105                        rule.definition.name,
106                        event_relation.qualified_name(),
107                        validated_relation.qualified_name()
108                    )));
109                }
110                rule.condition_plan = condition_plan;
111                rule.condition_binding = condition_binding;
112                rule.dependencies = Some(dependencies);
113                rules_changed = true;
114            }
115        }
116
117        Ok(rules_changed)
118    }
119    pub fn renamed_event_column(
120        &self,
121        triggers: &TriggerCatalog,
122        rules: &RuleCatalog,
123        relation: &RelationIdentity,
124        from: &str,
125        to: &str,
126    ) -> Result<Option<(TriggerCatalog, RuleCatalog)>, String> {
127        let dependency = RuleColumnDependency {
128            relation: relation.clone(),
129            column: from.to_string(),
130        };
131        let referenced_by_rule = rules.values().any(|entries| {
132            entries.values().any(|rule| {
133                rule.dependencies
134                    .as_ref()
135                    .is_some_and(|dependencies| dependencies.columns.contains(&dependency))
136            })
137        });
138        if !triggers.contains_key(relation) && !rules.contains_key(relation) && !referenced_by_rule
139        {
140            return Ok(None);
141        }
142        let mut next_triggers = triggers.clone();
143        let mut next_rules = rules.clone();
144        if let Some(entries) = next_triggers.get_mut(relation) {
145            for trigger in entries.values_mut() {
146                for column in &mut trigger.definition.update_columns {
147                    if column == from {
148                        *column = to.to_string();
149                    }
150                }
151                if let Some(condition) = trigger.definition.when.as_mut() {
152                    crate::schema::dependencies::rewrites::rename_schema_expr_column(
153                        condition, from, to,
154                    )?;
155                }
156            }
157        }
158        self.rewrite_rule_catalog_column(&mut next_rules, &dependency, relation, from, to)?;
159
160        Ok(Some((next_triggers, next_rules)))
161    }
162    pub fn rewrite_rule_catalog_column(
163        &self,
164        rules: &mut BTreeMap<RelationIdentity, BTreeMap<String, StoredRule>>,
165        dependency: &RuleColumnDependency,
166        relation: &RelationIdentity,
167        from: &str,
168        to: &str,
169    ) -> Result<(), String> {
170        for (event_relation, entries) in rules {
171            for rule in entries.values_mut() {
172                let dependencies = rule.dependencies.as_ref().ok_or_else(|| {
173                    format!(
174                        "rule `{}` on `{}` has no bound dependency state",
175                        rule.definition.name,
176                        event_relation.qualified_name()
177                    )
178                })?;
179                if dependencies.columns.contains(dependency) {
180                    self.rewrite_stored_rule_column(rule, event_relation, relation, from, to)?;
181                }
182            }
183        }
184        Ok(())
185    }
186
187    fn rewrite_stored_rule_column(
188        &self,
189        rule: &mut StoredRule,
190        event_relation: &RelationIdentity,
191        relation: &RelationIdentity,
192        from: &str,
193        to: &str,
194    ) -> Result<(), String> {
195        if event_relation == relation {
196            self.rewrite_rule_event_row_column(rule, from, to)?;
197        }
198        crate::binding::stored_columns::rewrite_rule_column_references(
199            self.columns,
200            &mut rule.definition,
201            relation,
202            from,
203            to,
204        )
205        .map_err(|error| {
206            format!(
207                "rewrite rule `{}` column dependency: {error}",
208                rule.definition.name
209            )
210        })?;
211        let (validated_relation, condition_plan, condition_binding, dependencies) = self
212            .validate_rule_definition(&mut rule.definition, RelationLookupMode::Bound, None, None)
213            .map_err(|error| {
214                format!(
215                    "rebind rule `{}` after column rename: {error}",
216                    rule.definition.name
217                )
218            })?;
219        if validated_relation != *event_relation {
220            return Err(format!(
221                "rule `{}` changed event relation while rebinding column rename",
222                rule.definition.name
223            ));
224        }
225        rule.condition_plan = condition_plan;
226        rule.condition_binding = condition_binding;
227        rule.dependencies = Some(dependencies);
228        Ok(())
229    }
230
231    fn rewrite_rule_event_row_column(
232        &self,
233        rule: &mut StoredRule,
234        from: &str,
235        to: &str,
236    ) -> Result<(), String> {
237        if let Some(condition) = rule.definition.condition.as_mut() {
238            *condition = crate::semantics::rules::action_binding::bind_rule_expr_scoped(
239                condition,
240                &mut RuleColumnResolver {
241                    from,
242                    to: Some(to),
243                    referenced: false,
244                },
245                &std::collections::BTreeSet::new(),
246            )
247            .map_err(|error| format!("rename rule condition column: {error}"))?;
248        }
249        for action in &mut rule.definition.actions {
250            let action_columns = self
251                .rule_action_target_columns(action)
252                .map_err(|error| format!("read rule action columns during rename: {error}"))?;
253            *action = crate::semantics::rules::action_binding::bind_rule_action(
254                self.sources,
255                action,
256                &action_columns,
257                &mut RuleColumnResolver {
258                    from,
259                    to: Some(to),
260                    referenced: false,
261                },
262            )
263            .map_err(|error| format!("rename rule event column: {error}"))?;
264        }
265        Ok(())
266    }
267    pub fn rebind_rule_column_drop(
268        &self,
269        prepared: &mut PreparedRuleColumnDrop,
270    ) -> Result<(), String> {
271        for (event_relation, name) in &prepared.rebind {
272            let rule = prepared
273                .rules
274                .get_mut(event_relation)
275                .and_then(|entries| entries.get_mut(name))
276                .ok_or_else(|| {
277                    format!(
278                        "rule `{name}` on `{}` disappeared while dropping a column",
279                        event_relation.qualified_name()
280                    )
281                })?;
282            let (validated_relation, condition_plan, condition_binding, dependencies) = self
283                .validate_rule_definition(
284                    &mut rule.definition,
285                    RelationLookupMode::Bound,
286                    None,
287                    None,
288                )
289                .map_err(|error| {
290                    format!(
291                        "rebind rule `{}` after column drop: {error}",
292                        rule.definition.name
293                    )
294                })?;
295            if validated_relation != *event_relation {
296                return Err(format!(
297                    "rule `{}` changed event relation while rebinding column drop",
298                    rule.definition.name
299                ));
300            }
301            rule.condition_plan = condition_plan;
302            rule.condition_binding = condition_binding;
303            rule.dependencies = Some(dependencies);
304        }
305
306        Ok(())
307    }
308}
309
310struct RuleColumnResolver<'a> {
311    from: &'a str,
312    to: Option<&'a str>,
313    referenced: bool,
314}
315
316impl VariableResolver for RuleColumnResolver<'_> {
317    fn resolve_name(&mut self, _name: &str) -> Result<Option<ResolvedVariable>, SQLError> {
318        Ok(None)
319    }
320
321    fn resolve_qualified(
322        &mut self,
323        _qualifier: &str,
324        _column: &str,
325    ) -> Result<Option<ResolvedVariable>, SQLError> {
326        Ok(None)
327    }
328
329    fn resolve_param(&mut self, _index: usize) -> Result<Option<ResolvedVariable>, SQLError> {
330        Ok(None)
331    }
332
333    fn rewrite_qualified(
334        &mut self,
335        qualifier: &str,
336        column: &str,
337    ) -> Result<Option<Expr>, SQLError> {
338        if (qualifier.eq_ignore_ascii_case("old") || qualifier.eq_ignore_ascii_case("new"))
339            && column == self.from
340        {
341            self.referenced = true;
342            if let Some(to) = self.to {
343                return Ok(Some(Expr::qualified_column(qualifier, to)));
344            }
345        }
346        Ok(None)
347    }
348}