1use serde::{Deserialize, Serialize};
11use std::collections::HashMap;
12
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
17pub struct SchemaDiff {
18 pub tables_added: Vec<String>,
20
21 pub tables_removed: Vec<String>,
23
24 pub tables_modified: HashMap<String, TableDiff>,
26
27 pub views_added: Vec<String>,
29
30 pub views_removed: Vec<String>,
32
33 pub views_modified: HashMap<String, ViewDiff>,
35
36 pub functions_added: Vec<String>,
38
39 pub functions_removed: Vec<String>,
41
42 pub functions_modified: HashMap<String, FunctionDiff>,
44
45 pub types_added: Vec<String>,
47
48 pub types_removed: Vec<String>,
50
51 pub types_modified: HashMap<String, TypeDiff>,
53}
54
55impl SchemaDiff {
56 pub fn is_empty(&self) -> bool {
58 self.tables_added.is_empty()
59 && self.tables_removed.is_empty()
60 && self.tables_modified.is_empty()
61 && self.views_added.is_empty()
62 && self.views_removed.is_empty()
63 && self.views_modified.is_empty()
64 && self.functions_added.is_empty()
65 && self.functions_removed.is_empty()
66 && self.functions_modified.is_empty()
67 && self.types_added.is_empty()
68 && self.types_removed.is_empty()
69 && self.types_modified.is_empty()
70 }
71
72 pub fn change_count(&self) -> usize {
74 self.tables_added.len()
75 + self.tables_removed.len()
76 + self.tables_modified.len()
77 + self.views_added.len()
78 + self.views_removed.len()
79 + self.views_modified.len()
80 + self.functions_added.len()
81 + self.functions_removed.len()
82 + self.functions_modified.len()
83 + self.types_added.len()
84 + self.types_removed.len()
85 + self.types_modified.len()
86 }
87}
88
89#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
91pub struct TableDiff {
92 pub columns_added: Vec<String>,
94
95 pub columns_removed: Vec<String>,
97
98 pub columns_modified: HashMap<String, ColumnDiff>,
100
101 pub primary_key_changed: bool,
103
104 pub foreign_keys_added: Vec<String>,
106
107 pub foreign_keys_removed: Vec<String>,
109
110 pub unique_constraints_added: Vec<String>,
112
113 pub unique_constraints_removed: Vec<String>,
115
116 pub indexes_added: Vec<String>,
118
119 pub indexes_removed: Vec<String>,
121
122 pub check_constraints_added: Vec<String>,
124
125 pub check_constraints_removed: Vec<String>,
127}
128
129#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
131pub struct ColumnDiff {
132 pub data_type_changed: bool,
134
135 pub old_data_type: Option<String>,
137
138 pub new_data_type: Option<String>,
140
141 pub nullability_changed: bool,
143
144 pub default_changed: bool,
146
147 pub old_default: Option<String>,
149
150 pub new_default: Option<String>,
152}
153
154#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
156pub struct ViewDiff {
157 pub definition_changed: bool,
159}
160
161#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
163pub struct FunctionDiff {
164 pub signature_changed: bool,
166
167 pub body_changed: bool,
169
170 pub return_type_changed: bool,
172}
173
174#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
176pub struct TypeDiff {
177 pub definition_changed: bool,
179}
180
181pub trait DiffCalculator: Send + Sync {
187 fn calculate_diff(&self, from: &crate::snapshot::SchemaSnapshot, to: &crate::snapshot::SchemaSnapshot) -> SchemaDiff;
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194
195 #[test]
196 fn test_empty_diff() {
197 let diff = SchemaDiff {
198 tables_added: vec![],
199 tables_removed: vec![],
200 tables_modified: HashMap::new(),
201 views_added: vec![],
202 views_removed: vec![],
203 views_modified: HashMap::new(),
204 functions_added: vec![],
205 functions_removed: vec![],
206 functions_modified: HashMap::new(),
207 types_added: vec![],
208 types_removed: vec![],
209 types_modified: HashMap::new(),
210 };
211 assert!(diff.is_empty());
212 assert_eq!(diff.change_count(), 0);
213 }
214
215 #[test]
216 fn test_diff_with_changes() {
217 let mut diff = SchemaDiff {
218 tables_added: vec!["users".to_string()],
219 tables_removed: vec!["old_table".to_string()],
220 tables_modified: HashMap::new(),
221 views_added: vec![],
222 views_removed: vec![],
223 views_modified: HashMap::new(),
224 functions_added: vec![],
225 functions_removed: vec![],
226 functions_modified: HashMap::new(),
227 types_added: vec![],
228 types_removed: vec![],
229 types_modified: HashMap::new(),
230 };
231 assert!(!diff.is_empty());
232 assert_eq!(diff.change_count(), 2);
233
234 let mut table_diff = TableDiff {
235 columns_added: vec!["email".to_string()],
236 columns_removed: vec![],
237 columns_modified: HashMap::new(),
238 primary_key_changed: false,
239 foreign_keys_added: vec![],
240 foreign_keys_removed: vec![],
241 unique_constraints_added: vec![],
242 unique_constraints_removed: vec![],
243 indexes_added: vec![],
244 indexes_removed: vec![],
245 check_constraints_added: vec![],
246 check_constraints_removed: vec![],
247 };
248 diff.tables_modified.insert("posts".to_string(), table_diff);
249 assert_eq!(diff.change_count(), 3);
250 }
251
252 #[test]
253 fn test_diff_serialization() {
254 let diff = SchemaDiff {
255 tables_added: vec!["users".to_string()],
256 tables_removed: vec![],
257 tables_modified: HashMap::new(),
258 views_added: vec![],
259 views_removed: vec![],
260 views_modified: HashMap::new(),
261 functions_added: vec![],
262 functions_removed: vec![],
263 functions_modified: HashMap::new(),
264 types_added: vec![],
265 types_removed: vec![],
266 types_modified: HashMap::new(),
267 };
268
269 let json = serde_json::to_string(&diff).unwrap();
270 let deserialized: SchemaDiff = serde_json::from_str(&json).unwrap();
271 assert_eq!(diff, deserialized);
272 }
273}