Skip to main content

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}