safe_migrate/analysis/
graph.rs1use crate::ast::identifiers::ObjectId;
3
4#[derive(Debug, Clone, PartialEq)]
5pub struct FkEdge {
6 pub constraint_name: Option<String>,
7 pub from_table: ObjectId,
8 pub from_columns: Vec<String>,
9 pub to_table: ObjectId,
10 pub to_columns: Vec<String>,
11 pub from_generation: u64,
12}
13
14#[derive(Debug, Clone, PartialEq)]
15pub struct ViewEdge {
16 pub view_id: ObjectId,
17 pub depends_on: Vec<ObjectId>,
18 pub view_generation: u64,
19}
20
21#[derive(Debug, Clone, PartialEq)]
22pub struct ColumnDependencyEdge {
23 pub table_id: ObjectId,
24 pub column: String,
25 pub depends_on_table: ObjectId,
26 pub depends_on_column: String,
27}
28
29#[derive(Debug, Clone, PartialEq)]
30pub struct IndexEdge {
31 pub index_id: ObjectId,
32 pub relation_id: ObjectId,
33 pub using_method: Option<String>,
34 pub has_predicate: bool,
35 pub is_concurrent: bool,
36 pub is_unique: bool,
37}
38
39#[derive(Debug, Clone, PartialEq)]
40pub struct RenameEdge {
41 pub from: ObjectId,
42 pub to: ObjectId,
43}
44
45#[derive(Debug, Clone, PartialEq)]
46pub struct PartitionEdge {
47 pub parent: ObjectId,
48 pub child: ObjectId,
49}
50
51#[derive(Debug, Clone, PartialEq)]
52pub struct SequenceEdge {
53 pub sequence_id: ObjectId,
54 pub table_id: ObjectId,
55 pub column: String,
56}
57
58#[derive(Debug, Clone, PartialEq)]
59pub struct TriggerEdge {
60 pub trigger_id: ObjectId,
61 pub table_id: ObjectId,
62 pub function_id: ObjectId,
63}
64
65#[derive(Debug, Clone, PartialEq)]
66pub struct PublicationEdge {
67 pub publication_name: String,
68 pub table_id: ObjectId,
69}
70
71#[derive(Debug, Clone, Default)]
72pub struct DependencyGraph {
73 pub foreign_keys: Vec<FkEdge>,
74 pub views: Vec<ViewEdge>,
75 pub indexes: Vec<IndexEdge>,
76 pub renames: Vec<RenameEdge>,
77 pub partitions: Vec<PartitionEdge>,
78 pub sequences: Vec<SequenceEdge>,
79 pub column_dependencies: Vec<ColumnDependencyEdge>,
80 pub trigger_dependencies: Vec<TriggerEdge>,
81 pub publication_dependencies: Vec<PublicationEdge>,
82}
83
84impl DependencyGraph {
85 pub fn new() -> Self {
86 Self::default()
87 }
88
89 pub fn is_referenced_by_view(&self, id: &ObjectId) -> Vec<&ObjectId> {
91 let target = self.resolve_rename(id);
92 self.views
93 .iter()
94 .filter(|v| {
95 v.depends_on
96 .iter()
97 .any(|dep| self.resolve_rename(dep) == target || dep == id)
98 })
99 .map(|v| self.resolve_rename(&v.view_id))
100 .collect()
101 }
102
103 pub fn is_referenced_by_fk(&self, id: &ObjectId) -> Vec<(&ObjectId, u64)> {
104 let target = self.resolve_rename(id);
105 self.foreign_keys
106 .iter()
107 .filter(|fk| self.resolve_rename(&fk.to_table) == target || &fk.to_table == id)
108 .map(|fk| (self.resolve_rename(&fk.from_table), fk.from_generation))
109 .collect()
110 }
111
112 pub fn is_referenced_by_index(&self, id: &ObjectId) -> Vec<&ObjectId> {
113 let target = self.resolve_rename(id);
114 self.indexes
115 .iter()
116 .filter(|ix| self.resolve_rename(&ix.relation_id) == target || &ix.relation_id == id)
117 .map(|ix| self.resolve_rename(&ix.index_id))
118 .collect()
119 }
120
121 pub fn partitions_of(&self, id: &ObjectId) -> Vec<&ObjectId> {
122 let target = self.resolve_rename(id);
123 self.partitions
124 .iter()
125 .filter(|p| self.resolve_rename(&p.parent) == target || &p.parent == id)
126 .map(|p| self.resolve_rename(&p.child))
127 .collect()
128 }
129
130 pub fn resolve_rename<'a>(&'a self, id: &'a ObjectId) -> &'a ObjectId {
131 let mut current = id;
132 loop {
133 match self.renames.iter().find(|r| &r.from == current) {
134 Some(edge) => current = &edge.to,
135 None => return current,
136 }
137 }
138 }
139
140 pub fn check_partition_cycle(&self, parent: &ObjectId, child: &ObjectId) -> bool {
142 let resolved_parent = self.resolve_rename(parent);
143 let resolved_child = self.resolve_rename(child);
144 if resolved_parent == resolved_child {
145 return true;
146 }
147
148 let mut current_parent = resolved_parent;
149 loop {
150 let maybe_edge = self
151 .partitions
152 .iter()
153 .find(|p| self.resolve_rename(&p.child) == current_parent);
154 if let Some(edge) = maybe_edge {
155 let p = self.resolve_rename(&edge.parent);
156 if p == resolved_child {
157 return true;
158 }
159 current_parent = p;
160 } else {
161 break;
162 }
163 }
164 false
165 }
166
167 pub fn propagate_rename(&mut self, old_id: &ObjectId, new_id: &ObjectId) {
168 for idx in &mut self.indexes {
169 if idx.index_id == *old_id {
170 idx.index_id = new_id.clone();
171 }
172 if idx.relation_id == *old_id {
173 idx.relation_id = new_id.clone();
174 }
175 }
176 for view in &mut self.views {
177 if view.view_id == *old_id {
178 view.view_id = new_id.clone();
179 }
180 view.depends_on.iter_mut().for_each(|dep| {
181 if *dep == *old_id {
182 *dep = new_id.clone();
183 }
184 });
185 }
186 for fk in &mut self.foreign_keys {
187 if fk.from_table == *old_id {
188 fk.from_table = new_id.clone();
189 }
190 if fk.to_table == *old_id {
191 fk.to_table = new_id.clone();
192 }
193 }
194 for part in &mut self.partitions {
195 if part.parent == *old_id {
196 part.parent = new_id.clone();
197 }
198 if part.child == *old_id {
199 part.child = new_id.clone();
200 }
201 }
202 for seq in &mut self.sequences {
203 if seq.sequence_id == *old_id {
204 seq.sequence_id = new_id.clone();
205 }
206 if seq.table_id == *old_id {
207 seq.table_id = new_id.clone();
208 }
209 }
210 for col_dep in &mut self.column_dependencies {
211 if col_dep.table_id == *old_id {
212 col_dep.table_id = new_id.clone();
213 }
214 if col_dep.depends_on_table == *old_id {
215 col_dep.depends_on_table = new_id.clone();
216 }
217 }
218 for trg in &mut self.trigger_dependencies {
219 if trg.table_id == *old_id {
220 trg.table_id = new_id.clone();
221 }
222 if trg.trigger_id == *old_id {
223 trg.trigger_id = new_id.clone();
224 }
225 if trg.function_id == *old_id {
226 trg.function_id = new_id.clone();
227 }
228 }
229 for publ in &mut self.publication_dependencies {
230 if publ.table_id == *old_id {
231 publ.table_id = new_id.clone();
232 }
233 }
234 }
235
236 pub fn triggers_on(&self, table_id: &ObjectId) -> Vec<&TriggerEdge> {
237 self.trigger_dependencies
238 .iter()
239 .filter(|t| &t.table_id == table_id)
240 .collect()
241 }
242
243 pub fn triggers_for_function(&self, function_id: &ObjectId) -> Vec<&TriggerEdge> {
244 let normalize = |id: &ObjectId| -> ObjectId {
245 let name = if let Some(idx) = id.name.find('(') {
246 format!("{}()", &id.name[..idx])
247 } else {
248 id.name.clone()
249 };
250 ObjectId {
251 schema: id.schema.clone(),
252 name,
253 inferred_schema: id.inferred_schema,
254 }
255 };
256 let target_id = normalize(function_id);
257 self.trigger_dependencies
258 .iter()
259 .filter(|t| normalize(&t.function_id) == target_id)
260 .collect()
261 }
262}