Implemented a nice migration library.
This commit is contained in:
1 parent
3dc963cdea
commit
4a86c4250a
2 files changed
+69
-29
No files matched your search
+1
-1
@@ -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);
|
||||
}
|
||||
+68
-28
@@ -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)
|
||||
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(());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user