Implemented a nice migration library.
This commit is contained in:
1 parent
3dc963cdea
commit
4a86c4250a
2 files changed
+70
-30
No files matched your search
+1
-1
@@ -315,7 +315,7 @@ fn main() {
|
|||||||
set_logger(&m);
|
set_logger(&m);
|
||||||
let mut conn = get_connection(&m);
|
let mut conn = get_connection(&m);
|
||||||
let migrations: Vec<String> = Vec::new();
|
let migrations: Vec<String> = Vec::new();
|
||||||
migrate(&mut conn, & migrations);
|
migrate(&mut conn, & migrations).unwrap();
|
||||||
let mut logic = ProCommandLineLogic::new();
|
let mut logic = ProCommandLineLogic::new();
|
||||||
process_command_line(&conn, &m, &mut logic);
|
process_command_line(&conn, &m, &mut logic);
|
||||||
}
|
}
|
||||||
+69
-29
@@ -1,40 +1,70 @@
|
|||||||
|
use std::time::Instant;
|
||||||
use rusqlite::{Connection, Transaction};
|
use rusqlite::{Connection, Transaction};
|
||||||
|
|
||||||
use sha2::{Digest, Sha256};
|
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 = "
|
const INITIAL_MIGRATION: &str = "
|
||||||
CREATE TABLE IF NOT EXISTS shitty_migrations (
|
CREATE TABLE IF NOT EXISTS shitty_migrations (
|
||||||
id INTEGER PRIMARY KEY,
|
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";
|
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>) {
|
pub fn migrate(conn: &mut Connection, migrations: &Vec<String>) -> Result<(), Error> {
|
||||||
let tx = conn.transaction().unwrap();
|
let tx = conn.transaction().map_err(|e| Error::Sql(e))?;
|
||||||
tx.execute(INITIAL_MIGRATION, ()).unwrap();
|
tx.execute(INITIAL_MIGRATION, ()).map_err(|e| Error::Sql(e))?;
|
||||||
let id = get_id_of_new_migration(&tx);
|
let id = get_id_of_new_migration(&tx).map_err(|e| Error::Sql(e))?;
|
||||||
|
|
||||||
for (i, migration) in migrations.iter().enumerate() {
|
for (i, migration) in migrations.iter().enumerate() {
|
||||||
if i < id {
|
if i < id {
|
||||||
check_migration(&tx, i, migration);
|
check_migration(&tx, i, migration)?;
|
||||||
} else {
|
} 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);
|
let hash = compute_migration_sha256(migration);
|
||||||
tx.execute(migration, ()).unwrap();
|
|
||||||
|
|
||||||
let q = "INSERT INTO shitty_migrations(id, sha256) VALUES (?1, ?2)";
|
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 {
|
fn compute_migration_sha256(migration: &String) -> String {
|
||||||
@@ -44,11 +74,19 @@ fn compute_migration_sha256(migration: &String) -> String {
|
|||||||
return format!("{:x}", ret);
|
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 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);
|
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)]
|
#[cfg(test)]
|
||||||
@@ -70,9 +108,10 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn basic() {
|
fn basic() {
|
||||||
let mut conn = Connection::open_in_memory().unwrap();
|
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();
|
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 q = "SELECT COUNT(*) FROM people;";
|
||||||
let c = tx.query_row(q, (), |x| x.get::<usize, usize>(0)).unwrap();
|
let c = tx.query_row(q, (), |x| x.get::<usize, usize>(0)).unwrap();
|
||||||
@@ -80,27 +119,28 @@ mod test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn idempotency() {
|
fn idempotency() -> Result<(), Error> {
|
||||||
let mut conn = Connection::open_in_memory().unwrap();
|
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();
|
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 q = "SELECT COUNT(*) FROM people;";
|
||||||
let c = tx.query_row(q, (), |x| x.get::<usize, usize>(0)).unwrap();
|
let c = tx.query_row(q, (), |x| x.get::<usize, usize>(0)).unwrap();
|
||||||
assert_eq!(c, c);
|
assert_eq!(c, c);
|
||||||
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[ignore]
|
|
||||||
#[test]
|
#[test]
|
||||||
fn broken_migration() {
|
fn broken_migration() -> Result<(), Error> {
|
||||||
let mut conn = Connection::open_in_memory().unwrap();
|
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_2());
|
let ret = migrate(&mut conn, &create_basic_migrations_2());
|
||||||
|
assert!(ret.is_err());
|
||||||
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user