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