1use std::{
5 path::{Path, PathBuf},
6 str::FromStr,
7 sync::{Arc, LazyLock},
8};
9
10use crate::Config;
11use crate::db::migration::v0_22_1::Migration0_22_0_0_22_1;
12use crate::db::migration::v0_26_0::Migration0_25_3_0_26_0;
13use crate::db::migration::v0_31_0::Migration0_30_5_0_31_0;
14use anyhow::Context as _;
15use anyhow::bail;
16use itertools::Itertools;
17use multimap::MultiMap;
18use semver::Version;
19use tracing::debug;
20
21use super::void_migration::MigrationVoid;
22
23pub(super) trait MigrationOperation {
26 fn new(from: Version, to: Version) -> Self
27 where
28 Self: Sized;
29 fn from(&self) -> &Version;
31 fn to(&self) -> &Version;
33 fn pre_checks(&self, chain_data_path: &Path) -> anyhow::Result<()> {
37 let old_db = self.old_db_path(chain_data_path);
38 anyhow::ensure!(
39 old_db.is_dir(),
40 "source database {} does not exist",
41 old_db.display()
42 );
43 let new_db = self.new_db_path(chain_data_path);
44 anyhow::ensure!(
45 !new_db.exists(),
46 "target database {} already exists",
47 new_db.display()
48 );
49 let temp_db = self.temporary_db_path(chain_data_path);
50 if temp_db.exists() {
51 tracing::info!("Removing old temporary database {}", temp_db.display());
52 std::fs::remove_dir_all(&temp_db)?;
53 }
54 Ok(())
55 }
56 fn migrate_core(&self, chain_data_path: &Path, config: &Config) -> anyhow::Result<PathBuf>;
61 fn migrate(&self, chain_data_path: &Path, config: &Config) -> anyhow::Result<()> {
62 self.pre_checks(chain_data_path)?;
63 let migrated_db = self.migrate_core(chain_data_path, config)?;
64 self.post_checks(chain_data_path)?;
65
66 let new_db = self.new_db_path(chain_data_path);
67 debug!(
68 "Renaming database {} to {}",
69 migrated_db.display(),
70 new_db.display()
71 );
72 std::fs::rename(migrated_db, new_db)?;
73
74 let old_db = self.old_db_path(chain_data_path);
75 debug!("Deleting database {}", old_db.display());
76 std::fs::remove_dir_all(old_db)?;
77
78 Ok(())
79 }
80 fn post_checks(&self, chain_data_path: &Path) -> anyhow::Result<()> {
83 let temp_db_path = self.temporary_db_path(chain_data_path);
84 anyhow::ensure!(
85 temp_db_path.exists(),
86 "temp db {} does not exist",
87 temp_db_path.display()
88 );
89 Ok(())
90 }
91}
92
93pub trait MigrationOperationExt {
94 fn old_db_path(&self, chain_data_path: &Path) -> PathBuf;
95
96 fn new_db_path(&self, chain_data_path: &Path) -> PathBuf;
97
98 fn temporary_db_name(&self) -> String;
99
100 fn temporary_db_path(&self, chain_data_path: &Path) -> PathBuf;
101}
102
103impl<T: ?Sized + MigrationOperation> MigrationOperationExt for T {
104 fn old_db_path(&self, chain_data_path: &Path) -> PathBuf {
105 chain_data_path.join(self.from().to_string())
106 }
107
108 fn new_db_path(&self, chain_data_path: &Path) -> PathBuf {
109 chain_data_path.join(self.to().to_string())
110 }
111
112 fn temporary_db_name(&self) -> String {
113 format!("migration_{}_{}", self.from(), self.to()).replace('.', "_")
114 }
115
116 fn temporary_db_path(&self, chain_data_path: &Path) -> PathBuf {
117 chain_data_path.join(self.temporary_db_name())
118 }
119}
120
121type Migrator = Arc<dyn MigrationOperation + Send + Sync>;
129type MigrationsMap = MultiMap<Version, (Version, Migrator)>;
130
131macro_rules! create_migrations {
135 ($($from:literal -> $to:literal @ $migration:tt),* $(,)?) => {
136pub(super) static MIGRATIONS: LazyLock<MigrationsMap> = LazyLock::new(|| {
137 MigrationsMap::from_iter(
138 [
139 $((
140 Version::from_str($from).unwrap(),
141 (
142 Version::from_str($to).unwrap(),
143 Arc::new($migration::new(
144 $from.parse().expect("invalid <from> version"),
145 $to.parse().expect("invalid <to> version")))
146 as _,
147 )),
148 )*
149 ]
150 .iter()
151 .cloned(),
152 )
153});
154}}
155
156create_migrations!(
157 "0.22.0" -> "0.22.1" @ Migration0_22_0_0_22_1,
158 "0.25.3" -> "0.26.0" @ Migration0_25_3_0_26_0,
159 "0.30.5" -> "0.31.0" @ Migration0_30_5_0_31_0,
160);
161
162pub(super) fn create_migration_chain(
166 start: &Version,
167 goal: &Version,
168) -> anyhow::Result<Vec<Arc<dyn MigrationOperation + Send + Sync>>> {
169 create_migration_chain_from_migrations(start, goal, &MIGRATIONS, |from, to| {
170 Arc::new(MigrationVoid::new(from.clone(), to.clone()))
171 })
172}
173
174fn create_migration_chain_from_migrations(
176 start: &Version,
177 goal: &Version,
178 migrations_map: &MigrationsMap,
179 void_migration: impl Fn(&Version, &Version) -> Arc<dyn MigrationOperation + Send + Sync>,
180) -> anyhow::Result<Vec<Arc<dyn MigrationOperation + Send + Sync>>> {
181 let sorted_from_versions = migrations_map.keys().sorted().collect_vec();
182 let result = pathfinding::directed::bfs::bfs(
183 start,
184 |from| {
185 if let Some(migrations) = migrations_map.get_vec(from) {
186 migrations.iter().map(|(to, _)| to).cloned().collect()
187 } else if let Some(&next) =
188 sorted_from_versions.get(sorted_from_versions.partition_point(|&i| i <= from))
189 {
190 vec![next.clone()]
192 } else if goal > from {
193 vec![goal.clone()]
195 } else {
196 vec![]
198 }
199 },
200 |to| to == goal,
201 )
202 .with_context(|| format!("No migration path found from version {start} to {goal}"))?
203 .iter()
204 .tuple_windows()
205 .map(|(from, to)| {
206 migrations_map
207 .get_vec(from)
208 .map(|v| {
209 v.iter()
210 .find_map(|(version, migration)| {
211 if version == to {
212 Some(migration.clone())
213 } else {
214 None
215 }
216 })
217 .expect("Migration must exist")
218 })
219 .unwrap_or_else(|| void_migration(from, to))
220 })
221 .collect_vec();
222
223 if result.is_empty() {
224 bail!(
225 "No migration path found from version {start} to {goal}",
226 start = start,
227 goal = goal
228 );
229 }
230
231 Ok(result)
232}
233
234#[cfg(test)]
235mod tests {
236 use std::fs;
237
238 use tempfile::TempDir;
239
240 use super::*;
241 use crate::utils::version::FOREST_VERSION;
242
243 #[test]
244 fn test_possible_to_migrate_to_current_version() {
245 let earliest_version = MIGRATIONS
248 .iter_all()
249 .map(|(from, _)| from)
250 .min()
251 .expect("At least one migration must exist");
252 let current_version = &FOREST_VERSION;
253
254 let migrations = create_migration_chain(earliest_version, current_version).unwrap();
255 assert!(!migrations.is_empty());
256 }
257
258 #[test]
259 fn test_ensure_migration_possible_from_anywhere_to_latest() {
260 let current_version = &FOREST_VERSION;
263
264 for (from, _) in MIGRATIONS.iter_all() {
265 let migrations = create_migration_chain(from, current_version).unwrap();
266 assert!(!migrations.is_empty());
267 }
268 }
269
270 #[test]
271 fn test_ensure_migration_not_possible_if_higher_than_latest() {
272 let current_version = &FOREST_VERSION;
275
276 let higher_version = Version::new(
277 current_version.major,
278 current_version.minor,
279 current_version.patch + 1,
280 );
281 let migrations = create_migration_chain(&higher_version, current_version);
282 assert!(migrations.is_err());
283 }
284
285 #[test]
286 fn test_migration_down_not_possible() {
287 let current_version = &*FOREST_VERSION;
290
291 for (from, _) in MIGRATIONS.iter_all() {
292 let migrations = create_migration_chain(current_version, from);
293 assert!(migrations.is_err());
294 }
295 }
296
297 #[derive(Debug, Clone)]
298 struct EmptyMigration {
299 from: Version,
300 to: Version,
301 }
302
303 impl MigrationOperation for EmptyMigration {
304 fn pre_checks(&self, _chain_data_path: &Path) -> anyhow::Result<()> {
305 Ok(())
306 }
307
308 fn migrate_core(
309 &self,
310 _chain_data_path: &Path,
311 _config: &Config,
312 ) -> anyhow::Result<PathBuf> {
313 Ok("".into())
314 }
315
316 fn post_checks(&self, _chain_data_path: &Path) -> anyhow::Result<()> {
317 Ok(())
318 }
319
320 fn new(from: Version, to: Version) -> Self
321 where
322 Self: Sized,
323 {
324 Self { from, to }
325 }
326
327 fn from(&self) -> &Version {
328 &self.from
329 }
330
331 fn to(&self) -> &Version {
332 &self.to
333 }
334 }
335
336 fn map_empty_migration(
337 (from, to): (Version, Version),
338 ) -> (
339 Version,
340 (Version, Arc<dyn MigrationOperation + Send + Sync>),
341 ) {
342 (
343 from.clone(),
344 (to.clone(), Arc::new(EmptyMigration::new(from, to)) as _),
345 )
346 }
347
348 #[test]
349 fn test_migration_should_use_shortest_path() {
350 let migrations = MigrationsMap::from_iter(
351 [
352 (Version::new(0, 1, 0), Version::new(0, 2, 0)),
353 (Version::new(0, 2, 0), Version::new(0, 3, 0)),
354 (Version::new(0, 1, 0), Version::new(0, 3, 0)),
355 ]
356 .into_iter()
357 .map(map_empty_migration),
358 );
359
360 let migrations = create_migration_chain_from_migrations(
361 &Version::new(0, 1, 0),
362 &Version::new(0, 3, 0),
363 &migrations,
364 |_, _| unimplemented!("void migration"),
365 )
366 .unwrap();
367
368 assert_eq!(1, migrations.len());
370 assert_eq!(&Version::new(0, 1, 0), migrations[0].from());
371 assert_eq!(&Version::new(0, 3, 0), migrations[0].to());
372 }
373
374 #[test]
375 fn test_migration_complex_path() {
376 let migrations = MigrationsMap::from_iter(
377 [
378 (Version::new(0, 1, 0), Version::new(0, 2, 0)),
379 (Version::new(0, 2, 0), Version::new(0, 3, 0)),
380 (Version::new(0, 1, 0), Version::new(0, 3, 0)),
381 (Version::new(0, 3, 0), Version::new(0, 3, 1)),
382 ]
383 .into_iter()
384 .map(map_empty_migration),
385 );
386
387 let migrations = create_migration_chain_from_migrations(
388 &Version::new(0, 1, 0),
389 &Version::new(0, 3, 1),
390 &migrations,
391 |_, _| unimplemented!("void migration"),
392 )
393 .unwrap();
394
395 assert_eq!(2, migrations.len());
397 assert_eq!(&Version::new(0, 1, 0), migrations[0].from());
398 assert_eq!(&Version::new(0, 3, 0), migrations[0].to());
399 assert_eq!(&Version::new(0, 3, 0), migrations[1].from());
400 assert_eq!(&Version::new(0, 3, 1), migrations[1].to());
401 }
402
403 #[test]
404 fn test_void_migration() {
405 let migrations = MigrationsMap::from_iter(
406 [
407 (Version::new(0, 12, 1), Version::new(0, 13, 0)),
408 (Version::new(0, 15, 2), Version::new(0, 16, 0)),
409 ]
410 .into_iter()
411 .map(map_empty_migration),
412 );
413
414 let start = Version::new(0, 12, 0);
415 let goal = Version::new(1, 0, 0);
416 let migrations =
417 create_migration_chain_from_migrations(&start, &goal, &migrations, |from, to| {
418 Arc::new(EmptyMigration::new(from.clone(), to.clone()))
419 })
420 .unwrap();
421
422 assert_eq!(5, migrations.len());
424 for (a, b) in migrations.iter().zip(migrations.iter().skip(1)) {
425 assert_eq!(a.to(), b.from());
426 }
427 assert_eq!(&start, migrations[0].from());
428 assert_eq!(&Version::new(0, 12, 1), migrations[1].from());
429 assert_eq!(&Version::new(0, 13, 0), migrations[2].from());
430 assert_eq!(&Version::new(0, 15, 2), migrations[3].from());
431 assert_eq!(&Version::new(0, 16, 0), migrations[4].from());
432 assert_eq!(&goal, migrations[4].to());
433 }
434
435 #[test]
436 fn test_same_distance_paths_should_yield_any() {
437 let migrations = MigrationsMap::from_iter(
438 [
439 (Version::new(0, 1, 0), Version::new(0, 2, 0)),
440 (Version::new(0, 2, 0), Version::new(0, 4, 0)),
441 (Version::new(0, 1, 0), Version::new(0, 3, 0)),
442 (Version::new(0, 3, 0), Version::new(0, 4, 0)),
443 ]
444 .into_iter()
445 .map(map_empty_migration),
446 );
447
448 let migrations = create_migration_chain_from_migrations(
449 &Version::new(0, 1, 0),
450 &Version::new(0, 4, 0),
451 &migrations,
452 |_, _| unimplemented!("void migration"),
453 )
454 .unwrap();
455
456 assert_eq!(2, migrations.len());
461 if migrations[0].to() == &Version::new(0, 2, 0) {
462 assert_eq!(&Version::new(0, 1, 0), migrations[0].from());
463 assert_eq!(&Version::new(0, 2, 0), migrations[0].to());
464 assert_eq!(&Version::new(0, 2, 0), migrations[1].from());
465 assert_eq!(&Version::new(0, 4, 0), migrations[1].to());
466 } else {
467 assert_eq!(&Version::new(0, 1, 0), migrations[0].from());
468 assert_eq!(&Version::new(0, 3, 0), migrations[0].to());
469 assert_eq!(&Version::new(0, 3, 0), migrations[1].from());
470 assert_eq!(&Version::new(0, 4, 0), migrations[1].to());
471 }
472 }
473
474 struct SimpleMigration0_1_0_0_2_0 {
475 from: Version,
476 to: Version,
477 }
478
479 impl MigrationOperation for SimpleMigration0_1_0_0_2_0 {
480 fn migrate_core(
481 &self,
482 chain_data_path: &Path,
483 _config: &Config,
484 ) -> anyhow::Result<PathBuf> {
485 let temp_db_path = self.temporary_db_path(chain_data_path);
486 fs::create_dir(&temp_db_path).unwrap();
487 Ok(temp_db_path)
488 }
489
490 fn new(from: Version, to: Version) -> Self
491 where
492 Self: Sized,
493 {
494 Self { from, to }
495 }
496
497 fn from(&self) -> &Version {
498 &self.from
499 }
500
501 fn to(&self) -> &Version {
502 &self.to
503 }
504 }
505
506 #[test]
507 fn test_migration_map_migration() {
508 let from = Version::new(0, 1, 0);
509 let to = Version::new(0, 2, 0);
510 let migration = Arc::new(SimpleMigration0_1_0_0_2_0::new(from, to));
511
512 let temp_dir = TempDir::new().unwrap();
513
514 assert!(migration.pre_checks(temp_dir.path()).is_err());
515 fs::create_dir(temp_dir.path().join("0.1.0")).unwrap();
516 assert!(migration.pre_checks(temp_dir.path()).is_ok());
517
518 migration
519 .migrate(temp_dir.path(), &Config::default())
520 .unwrap();
521 assert!(temp_dir.path().join("0.2.0").exists());
522
523 assert!(migration.post_checks(temp_dir.path()).is_err());
524 fs::create_dir(temp_dir.path().join("migration_0_1_0_0_2_0")).unwrap();
525 assert!(migration.post_checks(temp_dir.path()).is_ok());
526 }
527}