1use crate::{AzothError, ProjectionStore, Result};
56use rusqlite::Connection;
57use std::sync::Arc;
58
59pub trait Migration: Send + Sync {
63 fn version(&self) -> u32;
67
68 fn name(&self) -> &str;
70
71 fn up(&self, conn: &Connection) -> Result<()>;
73
74 fn down(&self, _conn: &Connection) -> Result<()> {
78 Err(AzothError::InvalidState(format!(
79 "Migration '{}' does not support rollback",
80 self.name()
81 )))
82 }
83
84 fn verify(&self, _conn: &Connection) -> Result<()> {
86 Ok(())
87 }
88}
89
90pub struct MigrationManager {
94 migrations: Vec<Box<dyn Migration>>,
95}
96
97impl MigrationManager {
98 pub fn new() -> Self {
100 Self {
101 migrations: Vec::new(),
102 }
103 }
104
105 pub fn add(&mut self, migration: Box<dyn Migration>) {
109 self.migrations.push(migration);
110 }
111
112 pub fn add_all(&mut self, migrations: Vec<Box<dyn Migration>>) {
114 for m in migrations {
115 self.add(m);
116 }
117 }
118
119 pub fn run(&self, projection: &Arc<crate::SqliteProjectionStore>) -> Result<()> {
123 let current_version = projection.schema_version()?;
125
126 let mut sorted: Vec<_> = self.migrations.iter().collect();
128 sorted.sort_by_key(|m| m.version());
129
130 let pending: Vec<_> = sorted
132 .into_iter()
133 .filter(|m| m.version() > current_version)
134 .collect();
135
136 if pending.is_empty() {
137 tracing::info!("No pending migrations");
138 return Ok(());
139 }
140
141 tracing::info!("Running {} pending migrations", pending.len());
142
143 for migration in pending {
145 self.run_single(projection, &**migration)?;
146 }
147
148 Ok(())
149 }
150
151 fn run_single(
153 &self,
154 projection: &Arc<crate::SqliteProjectionStore>,
155 migration: &dyn Migration,
156 ) -> Result<()> {
157 tracing::info!(
158 "Applying migration v{}: {}",
159 migration.version(),
160 migration.name()
161 );
162
163 {
165 let conn = projection.conn().lock().unwrap();
166 migration.up(&conn)?;
167 }
168
169 projection.migrate(migration.version())?;
171
172 tracing::info!("Migration v{} complete", migration.version());
173 Ok(())
174 }
175
176 pub fn rollback_last(&self, projection: &Arc<crate::SqliteProjectionStore>) -> Result<()> {
180 let current_version = projection.schema_version()?;
181
182 if current_version <= 1 {
183 return Err(AzothError::InvalidState(
184 "Cannot rollback base schema".into(),
185 ));
186 }
187
188 let migration = self
190 .migrations
191 .iter()
192 .find(|m| m.version() == current_version)
193 .ok_or_else(|| {
194 AzothError::InvalidState(format!(
195 "No migration found for version {}",
196 current_version
197 ))
198 })?;
199
200 tracing::warn!(
201 "Rolling back migration v{}: {}",
202 migration.version(),
203 migration.name()
204 );
205
206 {
208 let conn = projection.conn().lock().unwrap();
209 migration.down(&conn)?;
210 }
211
212 projection.migrate(current_version - 1)?;
214
215 Ok(())
216 }
217
218 pub fn list(&self) -> Vec<MigrationInfo> {
220 let mut sorted: Vec<_> = self.migrations.iter().collect();
221 sorted.sort_by_key(|m| m.version());
222
223 sorted
224 .into_iter()
225 .map(|m| MigrationInfo {
226 version: m.version(),
227 name: m.name().to_string(),
228 })
229 .collect()
230 }
231
232 pub fn pending(
234 &self,
235 projection: &Arc<crate::SqliteProjectionStore>,
236 ) -> Result<Vec<MigrationInfo>> {
237 let current_version = projection.schema_version()?;
238
239 let mut sorted: Vec<_> = self.migrations.iter().collect();
240 sorted.sort_by_key(|m| m.version());
241
242 Ok(sorted
243 .into_iter()
244 .filter(|m| m.version() > current_version)
245 .map(|m| MigrationInfo {
246 version: m.version(),
247 name: m.name().to_string(),
248 })
249 .collect())
250 }
251}
252
253impl Default for MigrationManager {
254 fn default() -> Self {
255 Self::new()
256 }
257}
258
259#[derive(Debug, Clone)]
261pub struct MigrationInfo {
262 pub version: u32,
263 pub name: String,
264}
265
266#[macro_export]
289macro_rules! migration {
290 (
291 $name:ident,
292 version: $version:expr,
293 name: $migration_name:expr,
294 up: |$up_conn:ident| $up_body:block
295 $(, down: |$down_conn:ident| $down_body:block)?
296 ) => {
297 struct $name;
298
299 impl $crate::Migration for $name {
300 fn version(&self) -> u32 {
301 $version
302 }
303
304 fn name(&self) -> &str {
305 $migration_name
306 }
307
308 fn up(&self, $up_conn: &rusqlite::Connection) -> $crate::Result<()> {
309 $up_body
310 }
311
312 $(
313 fn down(&self, $down_conn: &rusqlite::Connection) -> $crate::Result<()> {
314 $down_body
315 }
316 )?
317 }
318 };
319}
320
321#[cfg(test)]
322mod tests {
323 use super::*;
324
325 struct TestMigration;
326
327 impl Migration for TestMigration {
328 fn version(&self) -> u32 {
329 2
330 }
331
332 fn name(&self) -> &str {
333 "test_migration"
334 }
335
336 fn up(&self, _conn: &Connection) -> Result<()> {
337 Ok(())
338 }
339 }
340
341 #[test]
342 fn test_migration_manager() {
343 let mut manager = MigrationManager::new();
344 manager.add(Box::new(TestMigration));
345
346 let migrations = manager.list();
347 assert_eq!(migrations.len(), 1);
348 assert_eq!(migrations[0].version, 2);
349 assert_eq!(migrations[0].name, "test_migration");
350 }
351}