velesdb_memory/migration/orchestrate.rs
1//! The operator's one command, end to end (#1762, PR C3).
2//!
3//! `execute`, `validate_destination` and `switch_over` each acquire the lock,
4//! do one phase's work, and release — independently provable, independently
5//! refusable. What they cannot do alone is RESUME as a chain: after the
6//! switch's first rename the source path is vacant, and a re-run that blindly
7//! started from the diagnosis would fail on the absent directory before
8//! reaching the one function that knows how to continue. This module reads
9//! the journal FIRST and enters the chain where the journal says — which is
10//! what makes "re-run the same command" a true recovery instruction.
11
12use std::path::Path;
13
14use super::diagnosis::TargetContract;
15use super::execute::{execute, journal_workspace, ExecuteOutcome};
16use super::query_error;
17use super::state::{MigrationState, Phase};
18use super::switchover::{switch_over, SwitchOutcome};
19use super::validate::{validate_destination, ValidationOutcome};
20use crate::embedder::Embedder;
21
22/// What one `migrate` run did. The early stages are `None` exactly when the
23/// journal routed past them — reporting a rebuild that did not run would be
24/// misreporting, and with the source already archived it could not have run.
25#[derive(Debug)]
26pub struct MigrateOutcome {
27 /// The rebuild, when this run performed it.
28 pub executed: Option<ExecuteOutcome>,
29 /// The validation, when this run performed it.
30 pub validated: Option<ValidationOutcome>,
31 /// The switch — every completed run ends here.
32 pub switched: SwitchOutcome,
33}
34
35/// Rebuild, validate and switch — entering wherever the journal stands.
36///
37/// A journal already at [`Phase::DestinationValidated`] or beyond routes
38/// straight to the switch: the earlier stages are journalled as done, and
39/// after the first rename the source path no longer exists for them to run
40/// against.
41///
42/// # Errors
43/// Returns [`crate::MemoryError`] from whichever stage refuses; each stage
44/// names itself, and re-running the same command resumes from the journal.
45pub fn migrate(
46 store: &Path,
47 scratch_parent: &Path,
48 target: &TargetContract,
49 destination: &Path,
50 embedder: &dyn Embedder,
51 batch: usize,
52) -> Result<MigrateOutcome, crate::MemoryError> {
53 let (executed, validated) = if past_validation(destination)? {
54 (None, None)
55 } else {
56 let executed = execute(store, scratch_parent, target, destination, embedder, batch)?;
57 let validated = validate_destination(store, destination, target, batch)?;
58 (Some(executed), Some(validated))
59 };
60 let switched = switch_over(store, destination)?;
61 Ok(MigrateOutcome {
62 executed,
63 validated,
64 switched,
65 })
66}
67
68/// Whether the journal says validation already happened.
69fn past_validation(destination: &Path) -> Result<bool, crate::MemoryError> {
70 let workspace = journal_workspace(destination)?;
71 let state = MigrationState::read(&workspace).map_err(query_error)?;
72 Ok(state.is_some_and(|state| state.phase >= Phase::DestinationValidated))
73}