forest/db/migration/
db_migration.rs1use std::path::PathBuf;
5
6use tracing::info;
7
8use crate::{
9 Config,
10 cli_shared::chain_path,
11 db::{
12 db_mode::{DbMode, get_latest_versioned_database},
13 migration::migration_map::create_migration_chain,
14 },
15 utils::version::FOREST_VERSION,
16};
17
18pub struct DbMigration {
20 config: Config,
22}
23
24impl DbMigration {
25 pub fn new(config: &Config) -> Self {
26 Self {
27 config: config.clone(),
28 }
29 }
30
31 pub fn chain_data_path(&self) -> PathBuf {
32 chain_path(&self.config)
33 }
34
35 pub fn is_migration_required(&self) -> anyhow::Result<bool> {
38 if !self.chain_data_path().exists() {
40 return Ok(false);
41 }
42
43 if let DbMode::Current = DbMode::read() {
46 let current_db = get_latest_versioned_database(&self.chain_data_path())?
47 .unwrap_or_else(|| FOREST_VERSION.clone());
48 Ok(current_db < *FOREST_VERSION)
49 } else {
50 Ok(false)
51 }
52 }
53
54 pub fn migrate(&self) -> anyhow::Result<()> {
60 if !self.is_migration_required()? {
61 info!("No database migration required");
62 return Ok(());
63 }
64
65 let latest_db_version = get_latest_versioned_database(&self.chain_data_path())?
66 .unwrap_or_else(|| FOREST_VERSION.clone());
67
68 let target_db_version = &FOREST_VERSION;
69
70 let migrations = create_migration_chain(&latest_db_version, target_db_version)?;
71
72 for migration in migrations {
73 info!(
74 "Migrating database from version {} to {}",
75 migration.from(),
76 migration.to()
77 );
78 let start = std::time::Instant::now();
79 migration.migrate(&self.chain_data_path(), &self.config)?;
80 info!(
81 "Successfully migrated from version {} to {}, took {}",
82 migration.from(),
83 migration.to(),
84 humantime::format_duration(std::time::Instant::now() - start),
85 );
86 }
87
88 Ok(())
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95 use crate::db::db_mode::FOREST_DB_DEV_MODE;
96
97 #[test]
98 fn test_migration_not_required_no_chain_path() {
99 let temp_dir = tempfile::tempdir().unwrap();
100 let mut config = Config::default();
101 config.client.data_dir = temp_dir.path().join("azathoth");
102 let db_migration = DbMigration::new(&config);
103 assert!(!db_migration.is_migration_required().unwrap());
104 }
105
106 #[test]
107 fn test_migration_not_required_no_databases() {
108 let temp_dir = tempfile::tempdir().unwrap();
109 let mut config = Config::default();
110 temp_dir.path().clone_into(&mut config.client.data_dir);
111 let db_migration = DbMigration::new(&config);
112 assert!(!db_migration.is_migration_required().unwrap());
113 }
114
115 #[test]
116 fn test_migration_not_required_under_non_current_mode() {
117 let temp_dir = tempfile::tempdir().unwrap();
118 let mut config = Config::default();
119 temp_dir.path().clone_into(&mut config.client.data_dir);
120
121 let db_dir = temp_dir.path().join("mainnet/0.1.0");
122 std::fs::create_dir_all(&db_dir).unwrap();
123 let db_migration = DbMigration::new(&config);
124
125 unsafe { std::env::set_var(FOREST_DB_DEV_MODE, "latest") };
126 assert!(!db_migration.is_migration_required().unwrap());
127
128 std::fs::remove_dir(db_dir).unwrap();
129 std::fs::create_dir_all(temp_dir.path().join("mainnet/cthulhu")).unwrap();
130
131 unsafe { std::env::set_var(FOREST_DB_DEV_MODE, "cthulhu") };
132 assert!(!db_migration.is_migration_required().unwrap());
133 }
134
135 #[test]
136 fn test_migration_required_current_mode() {
137 let temp_dir = tempfile::tempdir().unwrap();
138 let mut config = Config::default();
139 temp_dir.path().clone_into(&mut config.client.data_dir);
140
141 let db_dir = temp_dir.path().join("mainnet/0.1.0");
142 std::fs::create_dir_all(db_dir).unwrap();
143 let db_migration = DbMigration::new(&config);
144
145 unsafe { std::env::set_var(FOREST_DB_DEV_MODE, "current") };
146 assert!(db_migration.is_migration_required().unwrap());
147 unsafe { std::env::remove_var(FOREST_DB_DEV_MODE) };
148 assert!(db_migration.is_migration_required().unwrap());
149 }
150}