Implemented a nice migration library.

This commit is contained in:
didier amyot committed 2022-12-07 10:43:05 -05:00
1 parent 3dc963cdea
commit 4a86c4250a
2 files changed
+70 -30

No files matched your search

+1 -1
View File
@@ -315,7 +315,7 @@ fn main() {
set_logger(&m);
let mut conn = get_connection(&m);
let migrations: Vec<String> = Vec::new();
migrate(&mut conn, & migrations);
migrate(&mut conn, & migrations).unwrap();
let mut logic = ProCommandLineLogic::new();
process_command_line(&conn, &m, &mut logic);
}
+69 -29
View File
@@ -1,40 +1,70 @@
use std::time::Instant;
use rusqlite::{Connection, Transaction};
use sha2::{Digest, Sha256};
#[derive(Debug)]
pub enum IntegrityError {
MigrationCheckFailed {
migration_id: usize,
migration_definition: String,
migration_sha256: String,
existing_migration_sha256: String,
},
}
#[derive(Debug)]
pub enum Error {
Sql(rusqlite::Error),
Integrity(IntegrityError),
}
const INITIAL_MIGRATION: &str = "
CREATE TABLE IF NOT EXISTS shitty_migrations (
id INTEGER PRIMARY KEY,
sha256 BLOB(2)
id INTEGER PRIMARY KEY,
creation_timestamp DATETIME DEFAULT current_timestamp NOT NULL,
duration REAL DEFAULT 0 NOT NULL,
sha256 BLOB(2) NOT NULL
);
";
fn get_id_of_new_migration(tx: &Transaction) -> usize {
fn get_id_of_new_migration(tx: &Transaction) -> Result<usize, rusqlite::Error> {
let q = "SELECT COALESCE(MAX(id) + 1, 0) FROM shitty_migrations";
return tx.query_row(q, (), |row| row.get::<usize, usize>(0)).unwrap();
return tx.query_row(q, (), |row| row.get::<usize, usize>(0));
}
pub fn migrate(conn: &mut Connection, migrations: &Vec<String>) {
let tx = conn.transaction().unwrap();
tx.execute(INITIAL_MIGRATION, ()).unwrap();
let id = get_id_of_new_migration(&tx);
pub fn migrate(conn: &mut Connection, migrations: &Vec<String>) -> Result<(), Error> {
let tx = conn.transaction().map_err(|e| Error::Sql(e))?;
tx.execute(INITIAL_MIGRATION, ()).map_err(|e| Error::Sql(e))?;
let id = get_id_of_new_migration(&tx).map_err(|e| Error::Sql(e))?;
for (i, migration) in migrations.iter().enumerate() {
if i < id {
check_migration(&tx, i, migration);
check_migration(&tx, i, migration)?;
} else {
apply_migration(&tx, i, migration);
apply_migration(&tx, i, migration)?;
}
}
tx.commit().unwrap();
tx.commit().map_err(|e| Error::Sql(e))?;
return Ok(());
}
fn apply_migration(tx: &Transaction, id: usize, migration: &String) {
fn apply_migration(tx: &Transaction, id: usize, migration: &String) -> Result<(), Error> {
let hash = compute_migration_sha256(migration);
tx.execute(migration, ()).unwrap();
let q = "INSERT INTO shitty_migrations(id, sha256) VALUES (?1, ?2)";
tx.execute(q, (&id, &hash)).unwrap();
tx.execute(q, (&id, &hash)).map_err(|e| Error::Sql(e))?;
// let migration_id = tx.last_insert_rowid();
let now = Instant::now();
tx.execute(migration, ()).map_err(|e| Error::Sql(e))?;
let elapsed_time = now.elapsed();
let q = "UPDATE shitty_migrations SET duration = ?1 WHERE id = ?2";
tx.execute(q, (elapsed_time.as_secs_f32(), &id)).map_err(|e| Error::Sql(e))?;
return Ok(());
}
fn compute_migration_sha256(migration: &String) -> String {
@@ -44,11 +74,19 @@ fn compute_migration_sha256(migration: &String) -> String {
return format!("{:x}", ret);
}
fn check_migration(tx: &Transaction, id: usize, migration: &String) {
fn check_migration(tx: &Transaction, id: usize, migration: &String) -> Result<(), Error> {
let q = "SELECT sha256 FROM shitty_migrations WHERE id = ?1";
let res = tx.query_row(q, (&id, ), |row| row.get::<usize, String>(0)).unwrap();
let res = tx.query_row(q, (&id, ), |row| row.get::<usize, String>(0)).map_err(|e| Error::Sql(e))?;
let expected = compute_migration_sha256(migration);
assert_eq!(&res, &expected);
if !res.eq(&expected) {
return Err(Error::Integrity(IntegrityError::MigrationCheckFailed {
migration_definition: migration.clone(),
migration_id: id,
migration_sha256: expected,
existing_migration_sha256: res,
}));
}
return Ok(());
}
#[cfg(test)]
@@ -70,9 +108,10 @@ mod test {
#[test]
fn basic() {
let mut conn = Connection::open_in_memory().unwrap();
migrate(&mut conn, &create_basic_migrations());
let ret = migrate(&mut conn, &create_basic_migrations());
assert!(ret.is_ok());
let tx = conn.transaction().unwrap();
assert_eq!(get_id_of_new_migration(&tx), 2);
assert_eq!(get_id_of_new_migration(&tx).unwrap(), 2);
let q = "SELECT COUNT(*) FROM people;";
let c = tx.query_row(q, (), |x| x.get::<usize, usize>(0)).unwrap();
@@ -80,27 +119,28 @@ mod test {
}
#[test]
fn idempotency() {
fn idempotency() -> Result<(), Error> {
let mut conn = Connection::open_in_memory().unwrap();
migrate(&mut conn, &create_basic_migrations());
migrate(&mut conn, &create_basic_migrations());
migrate(&mut conn, &create_basic_migrations())?;
migrate(&mut conn, &create_basic_migrations())?;
let tx = conn.transaction().unwrap();
assert_eq!(get_id_of_new_migration(&tx), 2);
assert_eq!(get_id_of_new_migration(&tx).unwrap(), 2);
let q = "SELECT COUNT(*) FROM people;";
let c = tx.query_row(q, (), |x| x.get::<usize, usize>(0)).unwrap();
assert_eq!(c, c);
return Ok(());
}
#[ignore]
#[test]
fn broken_migration() {
fn broken_migration() -> Result<(), Error> {
let mut conn = Connection::open_in_memory().unwrap();
migrate(&mut conn, &create_basic_migrations());
migrate(&mut conn, &create_basic_migrations_2());
migrate(&mut conn, &create_basic_migrations())?;
let ret = migrate(&mut conn, &create_basic_migrations_2());
assert!(ret.is_err());
return Ok(());
}
}