lib_migrations_core/
store.rs

1use crate::Result;
2
3/// Record of an applied migration
4#[derive(Debug, Clone, PartialEq, Eq)]
5pub struct MigrationRecord {
6    pub version: u64,
7    pub name: String,
8    pub applied_at: u64, // Unix timestamp
9}
10
11/// Storage backend for tracking applied migrations.
12///
13/// Implement this trait to store migration state in your preferred backend:
14/// - SQLite, PostgreSQL, MySQL
15/// - JSON/YAML file
16/// - Redis, etcd
17/// - In-memory (for testing)
18///
19/// The store is responsible for:
20/// - Initializing any required schema/structure
21/// - Recording when migrations are applied/rolled back
22/// - Querying which migrations have been applied
23pub trait MigrationStore {
24    /// Initialize the store (create tables, files, etc.)
25    fn init(&mut self) -> Result<()>;
26
27    /// Get all applied migrations, sorted by version ascending
28    fn applied(&self) -> Result<Vec<MigrationRecord>>;
29
30    /// Check if a specific version has been applied
31    fn is_applied(&self, version: u64) -> Result<bool> {
32        Ok(self.applied()?.iter().any(|r| r.version == version))
33    }
34
35    /// Get the highest applied version (0 if none)
36    fn current_version(&self) -> Result<u64> {
37        Ok(self.applied()?.last().map(|r| r.version).unwrap_or(0))
38    }
39
40    /// Record a migration as applied
41    fn mark_applied(&mut self, version: u64, name: &str) -> Result<()>;
42
43    /// Record a migration as rolled back (remove from applied)
44    fn mark_rolled_back(&mut self, version: u64) -> Result<()>;
45}
46
47/// In-memory store for testing
48#[derive(Debug, Default)]
49pub struct MemoryStore {
50    records: Vec<MigrationRecord>,
51}
52
53impl MemoryStore {
54    pub fn new() -> Self {
55        Self::default()
56    }
57}
58
59impl MigrationStore for MemoryStore {
60    fn init(&mut self) -> Result<()> {
61        Ok(())
62    }
63
64    fn applied(&self) -> Result<Vec<MigrationRecord>> {
65        let mut records = self.records.clone();
66        records.sort_by_key(|r| r.version);
67        Ok(records)
68    }
69
70    fn mark_applied(&mut self, version: u64, name: &str) -> Result<()> {
71        let now = std::time::SystemTime::now()
72            .duration_since(std::time::UNIX_EPOCH)
73            .map(|d| d.as_secs())
74            .unwrap_or(0);
75
76        self.records.push(MigrationRecord {
77            version,
78            name: name.to_string(),
79            applied_at: now,
80        });
81        Ok(())
82    }
83
84    fn mark_rolled_back(&mut self, version: u64) -> Result<()> {
85        self.records.retain(|r| r.version != version);
86        Ok(())
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn test_memory_store() {
96        let mut store = MemoryStore::new();
97        store.init().unwrap();
98
99        assert_eq!(store.current_version().unwrap(), 0);
100        assert!(!store.is_applied(1).unwrap());
101
102        store.mark_applied(1, "first").unwrap();
103        assert!(store.is_applied(1).unwrap());
104        assert_eq!(store.current_version().unwrap(), 1);
105
106        store.mark_applied(2, "second").unwrap();
107        assert_eq!(store.current_version().unwrap(), 2);
108
109        let applied = store.applied().unwrap();
110        assert_eq!(applied.len(), 2);
111        assert_eq!(applied[0].version, 1);
112        assert_eq!(applied[1].version, 2);
113
114        store.mark_rolled_back(2).unwrap();
115        assert_eq!(store.current_version().unwrap(), 1);
116        assert!(!store.is_applied(2).unwrap());
117    }
118}