drizzle_migrations/sqlite/
serializer.rs1use super::collection::SQLiteDDL;
7use super::ddl::SqliteEntity;
8use super::snapshot::SQLiteSnapshot;
9use std::path::Path;
10
11#[derive(Debug, Clone)]
13pub struct SerializerError {
14 pub message: String,
15 pub path: Option<String>,
16}
17
18impl std::fmt::Display for SerializerError {
19 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20 if let Some(path) = &self.path {
21 write!(f, "Serialization error in '{}': {}", path, self.message)
22 } else {
23 write!(f, "Serialization error: {}", self.message)
24 }
25 }
26}
27
28impl std::error::Error for SerializerError {}
29
30pub type SerializerResult<T> = Result<T, SerializerError>;
32
33#[derive(Debug, Clone)]
35pub struct PreparedSnapshots {
36 pub ddl_prev: SQLiteDDL,
38 pub ddl_cur: SQLiteDDL,
40 pub snapshot: SQLiteSnapshot,
42 pub snapshot_prev: SQLiteSnapshot,
44}
45
46pub fn load_snapshot(path: &Path) -> SerializerResult<SQLiteSnapshot> {
54 let contents = std::fs::read_to_string(path).map_err(|e| SerializerError {
55 message: format!("Failed to read snapshot file: {e}"),
56 path: Some(path.display().to_string()),
57 })?;
58
59 if let Ok(snapshot) = serde_json::from_str::<SQLiteSnapshot>(&contents) {
61 return Ok(snapshot);
62 }
63
64 if let Ok(legacy) = serde_json::from_str::<serde_json::Value>(&contents) {
67 let upgraded = crate::upgrade::upgrade_to_latest(legacy, drizzle_types::Dialect::SQLite);
68 if let Ok(snapshot) = serde_json::from_value::<SQLiteSnapshot>(upgraded) {
69 return Ok(snapshot);
70 }
71 }
72
73 Err(SerializerError {
74 message:
75 "Failed to parse snapshot as a v7 document or upgrade it from the legacy v5/v6 format"
76 .to_string(),
77 path: Some(path.display().to_string()),
78 })
79}
80
81pub fn save_snapshot(snapshot: &SQLiteSnapshot, path: &Path) -> SerializerResult<()> {
88 if let Some(parent) = path.parent() {
90 std::fs::create_dir_all(parent).map_err(|e| SerializerError {
91 message: format!("Failed to create directory: {e}"),
92 path: Some(parent.display().to_string()),
93 })?;
94 }
95
96 let json = serde_json::to_string_pretty(snapshot).map_err(|e| SerializerError {
97 message: format!("Failed to serialize snapshot: {e}"),
98 path: Some(path.display().to_string()),
99 })?;
100
101 std::fs::write(path, json).map_err(|e| SerializerError {
102 message: format!("Failed to write snapshot file: {e}"),
103 path: Some(path.display().to_string()),
104 })?;
105
106 Ok(())
107}
108
109pub fn load_latest_snapshot(drizzle_folder: &Path) -> SerializerResult<Option<SQLiteSnapshot>> {
116 let snapshots = find_snapshot_files(drizzle_folder)?;
117 snapshots.last().map(|path| load_snapshot(path)).transpose()
118}
119
120pub fn find_snapshot_files(drizzle_folder: &Path) -> SerializerResult<Vec<std::path::PathBuf>> {
126 if !drizzle_folder.exists() {
127 return Ok(Vec::new());
128 }
129
130 let mut snapshots = Vec::new();
131
132 let entries = std::fs::read_dir(drizzle_folder).map_err(|e| SerializerError {
133 message: format!("Failed to read migrations folder: {e}"),
134 path: Some(drizzle_folder.display().to_string()),
135 })?;
136
137 for entry in entries.flatten() {
138 let path = entry.path();
139 if !entry.file_type().is_ok_and(|t| t.is_dir()) {
140 continue;
141 }
142
143 let snapshot_path = path.join("snapshot.json");
144 if snapshot_path.exists() {
145 snapshots.push(snapshot_path);
146 }
147 }
148
149 snapshots.sort();
151
152 Ok(snapshots)
153}
154
155pub fn prepare_snapshots(
162 drizzle_folder: &Path,
163 current_ddl: SQLiteDDL,
164) -> SerializerResult<PreparedSnapshots> {
165 let snapshot_prev = load_latest_snapshot(drizzle_folder)?.unwrap_or_else(SQLiteSnapshot::new);
167
168 let ddl_prev = SQLiteDDL::from_entities(snapshot_prev.ddl.clone());
170
171 let mut snapshot = SQLiteSnapshot::with_prev_ids(vec![snapshot_prev.id.clone()]);
173 snapshot.ddl = current_ddl.to_entities();
174
175 Ok(PreparedSnapshots {
176 ddl_prev,
177 ddl_cur: current_ddl,
178 snapshot,
179 snapshot_prev,
180 })
181}
182
183#[must_use]
185pub fn empty_snapshot() -> SQLiteSnapshot {
186 SQLiteSnapshot::new()
187}
188
189#[must_use]
191pub fn ddl_from_entities(entities: Vec<SqliteEntity>) -> SQLiteDDL {
192 SQLiteDDL::from_entities(entities)
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198 use std::io::Write;
199 use tempfile::TempDir;
200
201 #[test]
202 fn test_empty_snapshot() {
203 let snapshot = empty_snapshot();
204 assert!(snapshot.ddl.is_empty());
205 assert_eq!(snapshot.version, "7");
206 }
207
208 #[test]
209 fn test_save_and_load_snapshot() {
210 let temp_dir = TempDir::new().unwrap();
211 let snapshot_path = temp_dir.path().join("test_snapshot.json");
212
213 let snapshot = SQLiteSnapshot::new();
214 save_snapshot(&snapshot, &snapshot_path).unwrap();
215
216 let loaded = load_snapshot(&snapshot_path).unwrap();
217 assert_eq!(loaded.version, snapshot.version);
218 assert_eq!(loaded.id, snapshot.id);
219 }
220
221 #[test]
222 fn test_find_snapshot_files() {
223 let temp_dir = TempDir::new().unwrap();
224 let mig1 = temp_dir.path().join("0001_first");
225 let mig2 = temp_dir.path().join("0002_second");
226 std::fs::create_dir_all(&mig1).unwrap();
227 std::fs::create_dir_all(&mig2).unwrap();
228
229 let mut f1 = std::fs::File::create(mig1.join("snapshot.json")).unwrap();
231 f1.write_all(b"{}").unwrap();
232
233 let mut f2 = std::fs::File::create(mig2.join("snapshot.json")).unwrap();
234 f2.write_all(b"{}").unwrap();
235
236 let snapshots = find_snapshot_files(temp_dir.path()).unwrap();
237 assert_eq!(snapshots.len(), 2);
238 }
239}