mixtape_tools/sqlite/migration/
mod.rs1mod add;
24pub mod export;
25mod get;
26pub mod import;
27mod list;
28mod remove;
29mod run;
30pub mod types;
31
32pub use add::AddMigrationTool;
33pub use export::ExportMigrationsTool;
34pub use get::GetMigrationTool;
35pub use import::ImportMigrationsTool;
36pub use list::ListMigrationsTool;
37pub use remove::RemoveMigrationTool;
38pub use run::RunMigrationsTool;
39pub use types::{Migration, MigrationStatusFilter};
40
41use crate::sqlite::error::SqliteToolError;
42use chrono::Utc;
43use rusqlite::Connection;
44use sha2::{Digest, Sha256};
45
46pub const MIGRATIONS_TABLE: &str = "_schema_migrations";
48
49pub fn ensure_migrations_table(conn: &Connection) -> Result<(), SqliteToolError> {
51 conn.execute_batch(&format!(
52 r#"
53 CREATE TABLE IF NOT EXISTS {MIGRATIONS_TABLE} (
54 version TEXT PRIMARY KEY,
55 name TEXT NOT NULL,
56 sql TEXT NOT NULL,
57 applied_at TEXT,
58 checksum TEXT NOT NULL
59 );
60 "#
61 ))?;
62 Ok(())
63}
64
65pub fn generate_version() -> String {
69 Utc::now().format("%Y%m%d_%H%M%S_%6f").to_string()
70}
71
72pub fn compute_checksum(sql: &str) -> String {
74 let mut hasher = Sha256::new();
75 hasher.update(sql.as_bytes());
76 let result = hasher.finalize();
77 hex::encode(result)
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83
84 #[test]
85 fn test_generate_version_format() {
86 let version = generate_version();
87 assert_eq!(version.len(), 22);
89 assert_eq!(&version[8..9], "_");
90 assert_eq!(&version[15..16], "_");
91 }
92
93 #[test]
94 fn test_generate_version_uniqueness() {
95 let v1 = generate_version();
96 std::thread::sleep(std::time::Duration::from_micros(10));
97 let v2 = generate_version();
98 assert_ne!(v1, v2);
99 }
100
101 #[test]
102 fn test_compute_checksum() {
103 let sql = "CREATE TABLE users (id INTEGER PRIMARY KEY);";
104 let checksum = compute_checksum(sql);
105 assert_eq!(checksum.len(), 64);
107
108 assert_eq!(checksum, compute_checksum(sql));
110
111 let other = compute_checksum("CREATE TABLE posts (id INTEGER PRIMARY KEY);");
113 assert_ne!(checksum, other);
114 }
115
116 #[test]
117 fn test_ensure_migrations_table() {
118 let conn = Connection::open_in_memory().unwrap();
119 ensure_migrations_table(&conn).unwrap();
120
121 let count: i64 = conn
123 .query_row(
124 "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name=?",
125 [MIGRATIONS_TABLE],
126 |row| row.get(0),
127 )
128 .unwrap();
129 assert_eq!(count, 1);
130
131 ensure_migrations_table(&conn).unwrap();
133 }
134}