|
|
@ -1,55 +1,76 @@ |
|
|
|
|
|
|
|
use log::debug; |
|
|
|
use rusqlite::NO_PARAMS; |
|
|
|
use rusqlite::NO_PARAMS; |
|
|
|
use rusqlite::{params, Connection}; |
|
|
|
use rusqlite::{params, Connection}; |
|
|
|
use std::collections::BTreeMap; |
|
|
|
use std::collections::BTreeMap; |
|
|
|
use std::ops::Bound::{Excluded, Unbounded}; |
|
|
|
use std::ops::Bound::{Excluded, Unbounded}; |
|
|
|
use thiserror::Error; |
|
|
|
use thiserror::Error; |
|
|
|
use log::debug; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Error)] |
|
|
|
#[derive(Debug, Error)] |
|
|
|
pub enum MigrationError { |
|
|
|
pub enum MigrationError { |
|
|
|
#[error("sql error: {0}")] |
|
|
|
#[error("sql error: {0}")] |
|
|
|
SqlError(#[from] rusqlite::Error), |
|
|
|
SqlError(#[from] rusqlite::Error), |
|
|
|
|
|
|
|
#[error("migration version {0} up failed, sql error: {1}")] |
|
|
|
|
|
|
|
MigrationUpFailed(MigrationVersion, rusqlite::Error), |
|
|
|
|
|
|
|
#[error("migration version {0} down failed, sql error: {1}")] |
|
|
|
|
|
|
|
MigrationDownFailed(MigrationVersion, rusqlite::Error), |
|
|
|
#[error("database version {0} too new to migrate")] |
|
|
|
#[error("database version {0} too new to migrate")] |
|
|
|
VersionTooNew(MigrationVersion), |
|
|
|
VersionTooNew(MigrationVersion), |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
pub type MigrationResult<T> = Result<T, MigrationError>; |
|
|
|
pub type MigrationResult<T> = Result<T, MigrationError>; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub type MigrationVersion = u32; |
|
|
|
|
|
|
|
|
|
|
|
pub trait Migration { |
|
|
|
pub trait Migration { |
|
|
|
|
|
|
|
fn version(&self) -> MigrationVersion; |
|
|
|
fn up(&self, conn: &Connection) -> MigrationResult<()>; |
|
|
|
fn up(&self, conn: &Connection) -> MigrationResult<()>; |
|
|
|
fn down(&self, conn: &Connection) -> MigrationResult<()>; |
|
|
|
fn down(&self, conn: &Connection) -> MigrationResult<()>; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
pub struct SimpleMigration { |
|
|
|
pub struct SimpleMigration { |
|
|
|
|
|
|
|
pub version: MigrationVersion, |
|
|
|
pub up_sql: String, |
|
|
|
pub up_sql: String, |
|
|
|
pub down_sql: String, |
|
|
|
pub down_sql: String, |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
impl SimpleMigration { |
|
|
|
impl SimpleMigration { |
|
|
|
pub fn new<T1: ToString, T2: ToString>(up_sql: T1, down_sql: T2) -> Self { |
|
|
|
pub fn new<T1: ToString, T2: ToString>( |
|
|
|
|
|
|
|
version: MigrationVersion, |
|
|
|
|
|
|
|
up_sql: T1, |
|
|
|
|
|
|
|
down_sql: T2, |
|
|
|
|
|
|
|
) -> Self { |
|
|
|
Self { |
|
|
|
Self { |
|
|
|
|
|
|
|
version, |
|
|
|
up_sql: up_sql.to_string(), |
|
|
|
up_sql: up_sql.to_string(), |
|
|
|
down_sql: down_sql.to_string(), |
|
|
|
down_sql: down_sql.to_string(), |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
pub fn new_box<T1: ToString, T2: ToString>(up_sql: T1, down_sql: T2) -> Box<dyn Migration> { |
|
|
|
pub fn new_box<T1: ToString, T2: ToString>( |
|
|
|
Box::new(Self::new(up_sql, down_sql)) |
|
|
|
version: MigrationVersion, |
|
|
|
|
|
|
|
up_sql: T1, |
|
|
|
|
|
|
|
down_sql: T2, |
|
|
|
|
|
|
|
) -> Box<dyn Migration> { |
|
|
|
|
|
|
|
Box::new(Self::new(version, up_sql, down_sql)) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
impl Migration for SimpleMigration { |
|
|
|
impl Migration for SimpleMigration { |
|
|
|
|
|
|
|
fn version(&self) -> MigrationVersion { |
|
|
|
|
|
|
|
self.version |
|
|
|
|
|
|
|
} |
|
|
|
fn up(&self, conn: &Connection) -> MigrationResult<()> { |
|
|
|
fn up(&self, conn: &Connection) -> MigrationResult<()> { |
|
|
|
conn.execute(&self.up_sql, NO_PARAMS)?; |
|
|
|
conn.execute_batch(&self.up_sql) |
|
|
|
|
|
|
|
.map_err(|sql_err| MigrationError::MigrationUpFailed(self.version, sql_err))?; |
|
|
|
Ok(()) |
|
|
|
Ok(()) |
|
|
|
} |
|
|
|
} |
|
|
|
fn down(&self, conn: &Connection) -> MigrationResult<()> { |
|
|
|
fn down(&self, conn: &Connection) -> MigrationResult<()> { |
|
|
|
conn.execute(&self.down_sql, NO_PARAMS)?; |
|
|
|
conn.execute_batch(&self.down_sql) |
|
|
|
|
|
|
|
.map_err(|sql_err| MigrationError::MigrationDownFailed(self.version, sql_err))?; |
|
|
|
Ok(()) |
|
|
|
Ok(()) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
pub type MigrationVersion = u32; |
|
|
|
|
|
|
|
pub const NO_MIGRATIONS: MigrationVersion = 0; |
|
|
|
pub const NO_MIGRATIONS: MigrationVersion = 0; |
|
|
|
|
|
|
|
|
|
|
|
pub fn get_db_version(conn: &Connection) -> MigrationResult<MigrationVersion> { |
|
|
|
pub fn get_db_version(conn: &Connection) -> MigrationResult<MigrationVersion> { |
|
|
@ -77,12 +98,15 @@ pub fn set_db_version(conn: &Connection, version: MigrationVersion) -> Migration |
|
|
|
CREATE TABLE IF NOT EXISTS db_version ( |
|
|
|
CREATE TABLE IF NOT EXISTS db_version ( |
|
|
|
id INTEGER PRIMARY KEY, |
|
|
|
id INTEGER PRIMARY KEY, |
|
|
|
version INTEGER |
|
|
|
version INTEGER |
|
|
|
);", NO_PARAMS)?; |
|
|
|
);", |
|
|
|
|
|
|
|
NO_PARAMS, |
|
|
|
|
|
|
|
)?; |
|
|
|
conn.execute( |
|
|
|
conn.execute( |
|
|
|
" |
|
|
|
" |
|
|
|
INSERT OR REPLACE INTO db_version (id, version) |
|
|
|
INSERT OR REPLACE INTO db_version (id, version) |
|
|
|
VALUES (1, ?1);", |
|
|
|
VALUES (1, ?1);", |
|
|
|
params![version])?; |
|
|
|
params![version], |
|
|
|
|
|
|
|
)?; |
|
|
|
Ok(()) |
|
|
|
Ok(()) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -97,8 +121,9 @@ impl Migrations { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
pub fn add(&mut self, version: MigrationVersion, migration: Box<dyn Migration>) { |
|
|
|
pub fn add(&mut self, migration: Box<dyn Migration>) { |
|
|
|
self.migrations.insert(version, migration); |
|
|
|
assert!(migration.version() != NO_MIGRATIONS, "migration has bad vesion"); |
|
|
|
|
|
|
|
self.migrations.insert(migration.version(), migration); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
pub fn apply(&self, conn: &mut Connection) -> MigrationResult<()> { |
|
|
|
pub fn apply(&self, conn: &mut Connection) -> MigrationResult<()> { |
|
|
@ -106,16 +131,17 @@ impl Migrations { |
|
|
|
if db_version != 0 && !self.migrations.contains_key(&db_version) { |
|
|
|
if db_version != 0 && !self.migrations.contains_key(&db_version) { |
|
|
|
return Err(MigrationError::VersionTooNew(db_version)); |
|
|
|
return Err(MigrationError::VersionTooNew(db_version)); |
|
|
|
} |
|
|
|
} |
|
|
|
let mig_range = self.migrations.range( |
|
|
|
let mig_range = self.migrations.range((Excluded(db_version), Unbounded)); |
|
|
|
(Excluded(db_version), Unbounded)); |
|
|
|
|
|
|
|
let mut trans = conn.transaction()?; |
|
|
|
let mut trans = conn.transaction()?; |
|
|
|
let mut last_ver: MigrationVersion = 0; |
|
|
|
let mut last_ver: MigrationVersion = NO_MIGRATIONS; |
|
|
|
for (ver, mig) in mig_range { |
|
|
|
for (ver, mig) in mig_range { |
|
|
|
debug!("applying migration version {}", ver); |
|
|
|
debug!("applying migration version {}", ver); |
|
|
|
mig.up(&mut trans)?; |
|
|
|
mig.up(&mut trans)?; |
|
|
|
last_ver = *ver; |
|
|
|
last_ver = *ver; |
|
|
|
} |
|
|
|
} |
|
|
|
set_db_version(&trans, last_ver)?; |
|
|
|
if last_ver != NO_MIGRATIONS { |
|
|
|
|
|
|
|
set_db_version(&trans, last_ver)?; |
|
|
|
|
|
|
|
} |
|
|
|
trans.commit()?; |
|
|
|
trans.commit()?; |
|
|
|
Ok(()) |
|
|
|
Ok(()) |
|
|
|
} |
|
|
|
} |
|
|
|