safe_migrate/analysis/
graph.rs1use crate::ast::identifiers::ObjectId;
3
4#[derive(Debug, Clone, PartialEq)]
5pub enum DependencyKind {
6 ForeignKey {
7 constraint_name: Option<String>,
8 from_columns: Vec<String>,
9 to_columns: Vec<String>,
10 from_generation: u64,
11 },
12 ViewDependency {
13 view_generation: u64,
14 },
15 IndexOnRelation {
16 using_method: Option<String>,
17 has_predicate: bool,
18 is_concurrent: bool,
19 is_unique: bool,
20 },
21 RenameTo,
22 PartitionOf,
23 SequenceOwnedBy {
24 column: String,
25 },
26 ColumnGeneratedFrom {
27 column: String,
28 depends_on_column: String,
29 },
30 TriggerOnTable {
31 trigger_id: ObjectId,
32 function_id: ObjectId,
33 },
34 PublicationIncludes {
35 publication_name: String,
36 },
37}
38
39#[derive(Debug, Clone, PartialEq)]
40pub struct DependencyEdge {
41 pub dependent: ObjectId,
42 pub referenced: ObjectId,
43 pub kind: DependencyKind,
44}
45
46impl DependencyEdge {
47 pub fn new(dependent: ObjectId, referenced: ObjectId, kind: DependencyKind) -> Self {
48 Self {
49 dependent,
50 referenced,
51 kind,
52 }
53 }
54}
55
56#[derive(Debug, Clone, Default)]
57pub struct DependencyGraph {
58 pub edges: Vec<DependencyEdge>,
59}
60
61impl DependencyGraph {
62 pub fn new() -> Self {
63 Self::default()
64 }
65
66 pub fn is_referenced_by_view(&self, id: &ObjectId) -> Vec<&ObjectId> {
68 let target = self.resolve_rename(id);
69 self.edges
70 .iter()
71 .filter(|e| {
72 matches!(e.kind, DependencyKind::ViewDependency { .. })
73 && (self.resolve_rename(&e.referenced) == target || &e.referenced == id)
74 })
75 .map(|e| self.resolve_rename(&e.dependent))
76 .collect()
77 }
78
79 pub fn is_referenced_by_fk(&self, id: &ObjectId) -> Vec<(&ObjectId, u64)> {
80 let target = self.resolve_rename(id);
81 self.edges
82 .iter()
83 .filter_map(|e| {
84 if let DependencyKind::ForeignKey {
85 from_generation, ..
86 } = &e.kind
87 && (self.resolve_rename(&e.referenced) == target || &e.referenced == id)
88 {
89 Some((self.resolve_rename(&e.dependent), *from_generation))
90 } else {
91 None
92 }
93 })
94 .collect()
95 }
96
97 pub fn is_referenced_by_index(&self, id: &ObjectId) -> Vec<&ObjectId> {
98 let target = self.resolve_rename(id);
99 self.edges
100 .iter()
101 .filter(|e| {
102 matches!(e.kind, DependencyKind::IndexOnRelation { .. })
103 && (self.resolve_rename(&e.referenced) == target || &e.referenced == id)
104 })
105 .map(|e| self.resolve_rename(&e.dependent))
106 .collect()
107 }
108
109 pub fn partitions_of(&self, id: &ObjectId) -> Vec<&ObjectId> {
110 let target = self.resolve_rename(id);
111 self.edges
112 .iter()
113 .filter(|e| {
114 matches!(e.kind, DependencyKind::PartitionOf)
115 && (self.resolve_rename(&e.referenced) == target || &e.referenced == id)
116 })
117 .map(|e| self.resolve_rename(&e.dependent))
118 .collect()
119 }
120
121 pub fn resolve_rename<'a>(&'a self, id: &'a ObjectId) -> &'a ObjectId {
122 let mut current = id;
123 loop {
124 match self
125 .edges
126 .iter()
127 .find(|e| matches!(e.kind, DependencyKind::RenameTo) && &e.dependent == current)
128 {
129 Some(edge) => current = &edge.referenced,
130 None => return current,
131 }
132 }
133 }
134
135 pub fn check_partition_cycle(&self, parent: &ObjectId, child: &ObjectId) -> bool {
137 let resolved_parent = self.resolve_rename(parent);
138 let resolved_child = self.resolve_rename(child);
139 if resolved_parent == resolved_child {
140 return true;
141 }
142
143 let mut current_parent = resolved_parent;
144 loop {
145 let maybe_edge = self.edges.iter().find(|e| {
146 matches!(e.kind, DependencyKind::PartitionOf)
147 && self.resolve_rename(&e.dependent) == current_parent
148 });
149 if let Some(edge) = maybe_edge {
150 let p = self.resolve_rename(&edge.referenced);
151 if p == resolved_child {
152 return true;
153 }
154 current_parent = p;
155 } else {
156 break;
157 }
158 }
159 false
160 }
161
162 pub fn propagate_rename(&mut self, old_id: &ObjectId, new_id: &ObjectId) {
163 for edge in &mut self.edges {
164 if matches!(edge.kind, DependencyKind::RenameTo) {
165 continue;
166 }
167 if edge.dependent == *old_id {
168 edge.dependent = new_id.clone();
169 }
170 if edge.referenced == *old_id {
171 edge.referenced = new_id.clone();
172 }
173 if let DependencyKind::TriggerOnTable {
174 trigger_id,
175 function_id,
176 } = &mut edge.kind
177 {
178 if *trigger_id == *old_id {
179 *trigger_id = new_id.clone();
180 }
181 if *function_id == *old_id {
182 *function_id = new_id.clone();
183 }
184 }
185 }
186 }
187
188 pub fn triggers_on(&self, table_id: &ObjectId) -> Vec<&DependencyEdge> {
189 self.edges
190 .iter()
191 .filter(|e| {
192 matches!(e.kind, DependencyKind::TriggerOnTable { .. }) && &e.referenced == table_id
193 })
194 .collect()
195 }
196
197 pub fn triggers_for_function(&self, function_id: &ObjectId) -> Vec<&DependencyEdge> {
198 let normalize = |id: &ObjectId| -> ObjectId {
199 let name = if let Some(idx) = id.name.find('(') {
200 format!("{}()", &id.name[..idx])
201 } else {
202 id.name.clone()
203 };
204 ObjectId {
205 schema: id.schema.clone(),
206 name,
207 inferred_schema: id.inferred_schema,
208 }
209 };
210 let target_id = normalize(function_id);
211 self.edges
212 .iter()
213 .filter(|e| {
214 if let DependencyKind::TriggerOnTable {
215 function_id: fid, ..
216 } = &e.kind
217 {
218 normalize(fid) == target_id
219 } else {
220 false
221 }
222 })
223 .collect()
224 }
225}