Skip to main content

uqa_sql/catalog/events/
renames.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Rewrite bound relation identities in rule actions, conditions, and event catalogs.
8use super::{RuleCatalog, StoredRule, TriggerCatalog};
9use crate::{ast::Statement, catalog::stored_ast::StoredAstVisitor, SQLError};
10use std::collections::BTreeSet;
11use uqa_core::RelationIdentity;
12pub fn rewrite_stored_statement_relation(
13    statement: &mut Statement,
14    from: &RelationIdentity,
15    to: &RelationIdentity,
16) -> Result<bool, SQLError> {
17    let from = from.qualified_name();
18    let to = to.qualified_name();
19    let mut changed = false;
20    let mut rewrite = |reference: &mut String| {
21        if reference == &from {
22            reference.clone_from(&to);
23            changed = true;
24        }
25        Ok(())
26    };
27    let mut ignore_routine = |_: &mut String,
28                              _: Option<&mut Option<crate::ast::FunctionBinding>>|
29     -> Result<(), SQLError> { Ok(()) };
30    StoredAstVisitor {
31        source: None,
32        merge: None,
33        expression: None,
34        projection: None,
35        ty: None,
36        relation: &mut rewrite,
37        routine: &mut ignore_routine,
38    }
39    .bind_statement(statement)?;
40    Ok(changed)
41}
42
43pub fn rewrite_stored_rule_relation(
44    rule: &mut StoredRule,
45    from: &RelationIdentity,
46    to: &RelationIdentity,
47) -> Result<bool, SQLError> {
48    let dependencies = rule.dependencies.as_mut().ok_or_else(|| {
49        SQLError::Internal(format!(
50            "rule `{}` has no bound dependency state",
51            rule.definition.name
52        ))
53    })?;
54    let mut changed = false;
55    if dependencies.relations.remove(from) {
56        dependencies.relations.insert(to.clone());
57        changed = true;
58    }
59    let renamed_columns = dependencies
60        .columns
61        .iter()
62        .filter(|dependency| &dependency.relation == from)
63        .cloned()
64        .collect::<Vec<_>>();
65    for mut dependency in renamed_columns {
66        dependencies.columns.remove(&dependency);
67        dependency.relation = to.clone();
68        dependencies.columns.insert(dependency);
69        changed = true;
70    }
71    for action in &mut rule.definition.actions {
72        changed |= rewrite_stored_statement_relation(action, from, to)?;
73    }
74    if let Some(condition) = &mut rule.definition.condition {
75        let from_name = from.qualified_name();
76        let to_name = to.qualified_name();
77        let mut rewrite = |reference: &mut String| {
78            if reference == &from_name {
79                reference.clone_from(&to_name);
80                changed = true;
81            }
82            Ok(())
83        };
84        let mut ignore_routine = |_: &mut String,
85                                  _: Option<&mut Option<crate::ast::FunctionBinding>>|
86         -> Result<(), SQLError> { Ok(()) };
87        StoredAstVisitor {
88            source: None,
89            merge: None,
90            expression: None,
91            projection: None,
92            ty: None,
93            relation: &mut rewrite,
94            routine: &mut ignore_routine,
95        }
96        .bind_expr(condition, &BTreeSet::new())?;
97    }
98    if let Some(plan) = &mut rule.condition_plan {
99        for subquery in &mut plan.subqueries {
100            crate::binding::view_dependencies::bind_query_plan_relations(
101                subquery,
102                &BTreeSet::new(),
103                &mut |reference| -> Result<String, SQLError> {
104                    let identity =
105                        RelationIdentity::from_legacy_name(reference).map_err(|error| {
106                            SQLError::Internal(format!(
107                                "decode stored rule relation `{reference}`: {error}"
108                            ))
109                        })?;
110                    if &identity == from {
111                        changed = true;
112                        Ok(to.qualified_name())
113                    } else {
114                        Ok(reference.to_string())
115                    }
116                },
117            )?;
118        }
119    }
120    Ok(changed)
121}
122
123pub fn renamed_relation_events(
124    triggers: &TriggerCatalog,
125    rules: &RuleCatalog,
126    from: &RelationIdentity,
127    to: &RelationIdentity,
128) -> Result<Option<(TriggerCatalog, RuleCatalog)>, String> {
129    let from_name = from.qualified_name();
130    let to_name = to.qualified_name();
131    let referenced_by_trigger = triggers.values().any(|entries| {
132        entries.values().any(|trigger| {
133            trigger.definition.referenced_table.as_deref() == Some(from_name.as_str())
134        })
135    });
136    let mut referenced_by_rule = false;
137    for (event_relation, entries) in rules {
138        for rule in entries.values() {
139            let dependencies = rule.dependencies.as_ref().ok_or_else(|| {
140                format!(
141                    "rule `{}` on `{}` has no bound dependency state",
142                    rule.definition.name,
143                    event_relation.qualified_name()
144                )
145            })?;
146            referenced_by_rule |= dependencies.relations.contains(from);
147        }
148    }
149    if !triggers.contains_key(from)
150        && !rules.contains_key(from)
151        && !referenced_by_trigger
152        && !referenced_by_rule
153    {
154        return Ok(None);
155    }
156    let mut next_triggers = triggers.clone();
157    let mut next_rules = rules.clone();
158    if let Some(mut entries) = next_triggers.remove(from) {
159        for trigger in entries.values_mut() {
160            trigger.definition.table.clone_from(&to_name);
161        }
162        next_triggers.insert(to.clone(), entries);
163    }
164    for entries in next_triggers.values_mut() {
165        for trigger in entries.values_mut() {
166            if trigger.definition.referenced_table.as_deref() == Some(from_name.as_str()) {
167                trigger.definition.referenced_table = Some(to_name.clone());
168            }
169        }
170    }
171    if let Some(mut entries) = next_rules.remove(from) {
172        for rule in entries.values_mut() {
173            rule.definition.table = to.qualified_name();
174        }
175        next_rules.insert(to.clone(), entries);
176    }
177    for entries in next_rules.values_mut() {
178        for rule in entries.values_mut() {
179            rewrite_stored_rule_relation(rule, from, to).map_err(|error| {
180                format!(
181                    "rewrite rule `{}` relation dependency: {error}",
182                    rule.definition.name
183                )
184            })?;
185        }
186    }
187    Ok(Some((next_triggers, next_rules)))
188}