use crate::db::connection::Connection;
use crate::db::DbError;
use std::collections::BTreeSet;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Migration {
pub version: u64,
pub sql: &'static str,
}
pub fn apply(connection: &Connection, migrations: &[Migration]) -> Result<(), DbError> {
validate_unique_versions(migrations)?;
connection.execute_batch(
"CREATE TABLE IF NOT EXISTS __ic_sqlite_migrations (
version INTEGER PRIMARY KEY NOT NULL
)",
)?;
for migration in migrations {
let version = sqlite_version(migration.version)?;
let exists = connection.query_scalar::<i64>(
&format!(
"SELECT EXISTS(SELECT 1 FROM __ic_sqlite_migrations WHERE version = {version})"
),
crate::params![],
)?;
if exists != 0 {
continue;
}
connection.execute_batch(migration.sql)?;
connection.execute_batch(&format!(
"INSERT INTO __ic_sqlite_migrations(version) VALUES ({version})"
))?;
}
Ok(())
}
fn validate_unique_versions(migrations: &[Migration]) -> Result<(), DbError> {
let mut seen = BTreeSet::new();
for migration in migrations {
if !seen.insert(migration.version) {
return Err(DbError::DuplicateMigrationVersion(migration.version));
}
}
Ok(())
}
fn sqlite_version(version: u64) -> Result<i64, DbError> {
i64::try_from(version).map_err(|_| DbError::MigrationVersionOutOfRange(version))
}