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