use crate::util::interning::InternedString;
use crate::CargoResult;
use rusqlite::types::{FromSql, FromSqlError, ToSql, ToSqlOutput};
use rusqlite::{Connection, TransactionBehavior};
impl FromSql for InternedString {
fn column_result(value: rusqlite::types::ValueRef<'_>) -> Result<Self, FromSqlError> {
value.as_str().map(InternedString::new)
}
}
impl ToSql for InternedString {
fn to_sql(&self) -> Result<ToSqlOutput<'_>, rusqlite::Error> {
Ok(ToSqlOutput::from(self.as_str()))
}
}
pub type Migration = Box<dyn Fn(&Connection) -> CargoResult<()>>;
pub fn basic_migration(stmt: &'static str) -> Migration {
Box::new(|conn| {
conn.execute(stmt, [])?;
Ok(())
})
}
pub fn migrate(conn: &mut Connection, migrations: &[Migration]) -> CargoResult<()> {
let tx = conn.transaction_with_behavior(TransactionBehavior::Exclusive)?;
let user_version = tx.query_row("SELECT user_version FROM pragma_user_version", [], |row| {
row.get(0)
})?;
if user_version < migrations.len() {
for migration in &migrations[user_version..] {
migration(&tx)?;
}
tx.pragma_update(None, "user_version", &migrations.len())?;
}
tx.commit()?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn migrate_twice() -> CargoResult<()> {
let mut conn = Connection::open_in_memory()?;
let mut migrations = vec![basic_migration("CREATE TABLE foo (a, b, c)")];
migrate(&mut conn, &migrations)?;
conn.execute("INSERT INTO foo VALUES (1,2,3)", [])?;
migrations.push(basic_migration("ALTER TABLE foo ADD COLUMN d"));
migrate(&mut conn, &migrations)?;
conn.execute("INSERT INTO foo VALUES (1,2,3,4)", [])?;
Ok(())
}
}