use std::path::PathBuf;
use tracing::info;
use crate::{
cli_shared::chain_path,
db::{
db_mode::{get_latest_versioned_database, DbMode},
migration::migration_map::create_migration_chain,
},
utils::version::FOREST_VERSION,
Config,
};
pub struct DbMigration {
config: Config,
}
impl DbMigration {
pub fn new(config: &Config) -> Self {
Self {
config: config.clone(),
}
}
pub fn chain_data_path(&self) -> PathBuf {
chain_path(&self.config)
}
pub fn is_migration_required(&self) -> anyhow::Result<bool> {
if !self.chain_data_path().exists() {
return Ok(false);
}
if let DbMode::Current = DbMode::read() {
let current_db = get_latest_versioned_database(&self.chain_data_path())?
.unwrap_or_else(|| FOREST_VERSION.clone());
Ok(current_db < *FOREST_VERSION)
} else {
Ok(false)
}
}
pub fn migrate(&self) -> anyhow::Result<()> {
if !self.is_migration_required()? {
info!("No database migration required");
return Ok(());
}
let latest_db_version = get_latest_versioned_database(&self.chain_data_path())?
.unwrap_or_else(|| FOREST_VERSION.clone());
info!(
"Migrating database from version {} to {}",
latest_db_version, *FOREST_VERSION
);
let target_db_version = &FOREST_VERSION;
let migrations = create_migration_chain(&latest_db_version, target_db_version)?;
for migration in migrations {
migration.migrate(&self.chain_data_path(), &self.config)?;
}
info!(
"Migration to version {} complete",
target_db_version.to_string()
);
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::db::db_mode::FOREST_DB_DEV_MODE;
use super::*;
#[test]
fn test_migration_not_required_no_chain_path() {
let temp_dir = tempfile::tempdir().unwrap();
let mut config = Config::default();
config.client.data_dir = temp_dir.path().join("azathoth");
let db_migration = DbMigration::new(&config);
assert!(!db_migration.is_migration_required().unwrap());
}
#[test]
fn test_migration_not_required_no_databases() {
let temp_dir = tempfile::tempdir().unwrap();
let mut config = Config::default();
temp_dir.path().clone_into(&mut config.client.data_dir);
let db_migration = DbMigration::new(&config);
assert!(!db_migration.is_migration_required().unwrap());
}
#[test]
fn test_migration_not_required_under_non_current_mode() {
let temp_dir = tempfile::tempdir().unwrap();
let mut config = Config::default();
temp_dir.path().clone_into(&mut config.client.data_dir);
let db_dir = temp_dir.path().join("mainnet/0.1.0");
std::fs::create_dir_all(&db_dir).unwrap();
let db_migration = DbMigration::new(&config);
std::env::set_var(FOREST_DB_DEV_MODE, "latest");
assert!(!db_migration.is_migration_required().unwrap());
std::fs::remove_dir(db_dir).unwrap();
std::fs::create_dir_all(temp_dir.path().join("mainnet/cthulhu")).unwrap();
std::env::set_var(FOREST_DB_DEV_MODE, "cthulhu");
assert!(!db_migration.is_migration_required().unwrap());
}
#[test]
fn test_migration_required_current_mode() {
let temp_dir = tempfile::tempdir().unwrap();
let mut config = Config::default();
temp_dir.path().clone_into(&mut config.client.data_dir);
let db_dir = temp_dir.path().join("mainnet/0.1.0");
std::fs::create_dir_all(db_dir).unwrap();
let db_migration = DbMigration::new(&config);
std::env::set_var(FOREST_DB_DEV_MODE, "current");
assert!(db_migration.is_migration_required().unwrap());
std::env::remove_var(FOREST_DB_DEV_MODE);
assert!(db_migration.is_migration_required().unwrap());
}
}