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 IndexEdge {
23 pub index_id: ObjectId,
24 pub relation_id: ObjectId,
25 pub using_method: Option<String>,
26 pub has_predicate: bool,
27 pub is_concurrent: bool,
28}
29
30#[derive(Debug, Clone, PartialEq)]
31pub struct RenameEdge {
32 pub from: ObjectId,
33 pub to: ObjectId,
34}
35
36#[derive(Debug, Clone, PartialEq)]
37pub struct PartitionEdge {
38 pub parent: ObjectId,
39 pub child: ObjectId,
40}
41
42#[derive(Debug, Clone, PartialEq)]
43pub struct SequenceEdge {
44 pub sequence_id: ObjectId,
45 pub table_id: ObjectId,
46 pub column: String,
47}
48
49#[derive(Debug, Clone, Default)]
50pub struct DependencyGraph {
51 pub foreign_keys: Vec<FkEdge>,
52 pub views: Vec<ViewEdge>,
53 pub indexes: Vec<IndexEdge>,
54 pub renames: Vec<RenameEdge>,
55 pub partitions: Vec<PartitionEdge>,
56 pub sequences: Vec<SequenceEdge>,
57}
58
59impl DependencyGraph {
60 pub fn new() -> Self {
61 Self::default()
62 }
63
64 pub fn is_referenced_by_view(&self, id: &ObjectId) -> Vec<&ObjectId> {
66 let target = self.resolve_rename(id);
67 self.views
68 .iter()
69 .filter(|v| {
70 v.depends_on
71 .iter()
72 .any(|dep| self.resolve_rename(dep) == target || dep == id)
73 })
74 .map(|v| self.resolve_rename(&v.view_id))
75 .collect()
76 }
77
78 pub fn is_referenced_by_fk(&self, id: &ObjectId) -> Vec<(&ObjectId, u64)> {
79 let target = self.resolve_rename(id);
80 self.foreign_keys
81 .iter()
82 .filter(|fk| self.resolve_rename(&fk.to_table) == target || &fk.to_table == id)
83 .map(|fk| (self.resolve_rename(&fk.from_table), fk.from_generation))
84 .collect()
85 }
86
87 pub fn is_referenced_by_index(&self, id: &ObjectId) -> Vec<&ObjectId> {
88 let target = self.resolve_rename(id);
89 self.indexes
90 .iter()
91 .filter(|ix| self.resolve_rename(&ix.relation_id) == target || &ix.relation_id == id)
92 .map(|ix| self.resolve_rename(&ix.index_id))
93 .collect()
94 }
95
96 pub fn partitions_of(&self, id: &ObjectId) -> Vec<&ObjectId> {
97 let target = self.resolve_rename(id);
98 self.partitions
99 .iter()
100 .filter(|p| self.resolve_rename(&p.parent) == target || &p.parent == id)
101 .map(|p| self.resolve_rename(&p.child))
102 .collect()
103 }
104
105 pub fn resolve_rename<'a>(&'a self, id: &'a ObjectId) -> &'a ObjectId {
106 let mut current = id;
107 loop {
108 match self.renames.iter().find(|r| &r.from == current) {
109 Some(edge) => current = &edge.to,
110 None => return current,
111 }
112 }
113 }
114
115 pub fn check_partition_cycle(&self, parent: &ObjectId, child: &ObjectId) -> bool {
117 let resolved_parent = self.resolve_rename(parent);
118 let resolved_child = self.resolve_rename(child);
119 if resolved_parent == resolved_child {
120 return true;
121 }
122
123 let mut current_parent = resolved_parent;
124 loop {
125 let maybe_edge = self
126 .partitions
127 .iter()
128 .find(|p| self.resolve_rename(&p.child) == current_parent);
129 if let Some(edge) = maybe_edge {
130 let p = self.resolve_rename(&edge.parent);
131 if p == resolved_child {
132 return true;
133 }
134 current_parent = p;
135 } else {
136 break;
137 }
138 }
139 false
140 }
141}