uqa_storage/mvcc/commit/
reclamation.rs1use sha2::{Digest, Sha256};
10use uqa_core::CancellationToken;
11
12use crate::mvcc::{CommittedRecordSnapshot, RecordWrite, VersionError, VersionResult};
13use crate::read_control::StorageReadControl;
14
15use super::PreparedRecordCommit;
16
17impl PreparedRecordCommit {
18 pub fn new_at_snapshot(
20 writes: &[RecordWrite<'_>],
21 snapshot: &dyn CommittedRecordSnapshot,
22 control: &StorageReadControl,
23 ) -> VersionResult<Self> {
24 Ok(Self::new(writes, control)?.with_reclamation_epoch(snapshot.reclamation_epoch()))
25 }
26
27 pub(in crate::mvcc) fn with_reclamation_epoch(mut self, epoch: Option<u64>) -> Self {
28 if let Some(epoch) = epoch {
29 let mut digest = Sha256::new();
30 digest.update(b"UQA prepared reclamation epoch 1");
31 digest.update(self.fingerprint);
32 digest.update(epoch.to_be_bytes());
33 self.fingerprint = digest.finalize().into();
34 }
35 self.reclamation_epoch = epoch;
36 self
37 }
38
39 pub fn validate_reclamation_epoch(
41 &self,
42 prefix: &[u8],
43 minimum: u64,
44 current: u64,
45 cancellation: &CancellationToken,
46 ) -> VersionResult<()> {
47 cancellation.check()?;
48 if prefix.is_empty() || minimum == 0 || minimum > current {
49 return Err(VersionError::InvalidEncoding(
50 "invalid tombstone reclamation domain",
51 ));
52 }
53 if self
54 .reclamation_epoch
55 .is_some_and(|epoch| epoch >= minimum && epoch <= current)
56 {
57 return Ok(());
58 }
59 let absent = self
60 .records()
61 .iter()
62 .filter(|write| write.expected().is_none())
63 .map(super::PreparedRecordWrite::key)
64 .chain(self.requirements.iter().flat_map(|requirements| {
65 requirements
66 .iter()
67 .filter(|requirement| requirement.expected.is_none())
68 .map(|requirement| requirement.key.bytes())
69 }));
70 for key in absent {
71 cancellation.check()?;
72 if key.starts_with(prefix) {
73 return Err(VersionError::ReclaimedObservation {
74 observed: self.reclamation_epoch,
75 minimum,
76 current,
77 });
78 }
79 }
80 Ok(())
81 }
82}