Skip to main content

uqa_storage/mvcc/
restore.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Caller-retained identities distinguish one explicit backup restoration from an ordinary reopen.
8
9use super::{DatabaseId, VersionError, VersionResult};
10
11/// One restoration of a closed, consistent backup into a new database incarnation. Persist this request outside the database before calling a provider's restore entry point, and reuse it only to resolve that same operation after an error. A separate restoration, including another copy of the same backup, requires a fresh target identity.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct DatabaseRestore {
14    source: DatabaseId,
15    target: DatabaseId,
16}
17
18impl DatabaseRestore {
19    /// Allocate the new incarnation without opening or changing a database.
20    pub fn new(source: DatabaseId) -> VersionResult<Self> {
21        let mut target = [0; 16];
22        getrandom::fill(&mut target).map_err(|error| {
23            crate::StorageBackendError::Other(format!(
24                "allocate database restore identity: {error}"
25            ))
26        })?;
27        Self::from_identities(source, DatabaseId::from_bytes(target))
28    }
29
30    /// Reconstitute a previously retained request. `target` must be unique to this restoration and must differ from the backup's incarnation; callers normally obtain it from `new`.
31    pub fn from_identities(source: DatabaseId, target: DatabaseId) -> VersionResult<Self> {
32        if source == target {
33            return Err(VersionError::InvalidRestoreIdentity);
34        }
35        Ok(Self { source, target })
36    }
37
38    pub const fn source(self) -> DatabaseId {
39        self.source
40    }
41
42    pub const fn target(self) -> DatabaseId {
43        self.target
44    }
45
46    /// A provider must inspect this before changing any restore state. Seeing the target means the atomic transition already completed; retries must preserve all subsequent transactions.
47    pub fn needs_restore(self, current: DatabaseId) -> VersionResult<bool> {
48        if current == self.source {
49            Ok(true)
50        } else if current == self.target {
51            Ok(false)
52        } else {
53            Err(VersionError::WrongDatabase)
54        }
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61
62    #[test]
63    fn restored_history_requests_distinguish_source_completion_and_foreign_databases() {
64        let source = DatabaseId::from_bytes([1; 16]);
65        let target = DatabaseId::from_bytes([2; 16]);
66        let request = DatabaseRestore::from_identities(source, target).unwrap();
67        assert!(request.needs_restore(source).unwrap());
68        assert!(!request.needs_restore(target).unwrap());
69        assert!(matches!(
70            request.needs_restore(DatabaseId::from_bytes([3; 16])),
71            Err(VersionError::WrongDatabase)
72        ));
73        assert!(matches!(
74            DatabaseRestore::from_identities(source, source),
75            Err(VersionError::InvalidRestoreIdentity)
76        ));
77        let generated = DatabaseRestore::new(source).unwrap();
78        assert_eq!(generated.source(), source);
79        assert_ne!(generated.target(), source);
80        assert_eq!(
81            DatabaseRestore::from_identities(generated.source(), generated.target()).unwrap(),
82            generated
83        );
84    }
85}