uqa_storage/mvcc/commit/
requirements.rs1use std::sync::Arc;
10
11use sha2::{Digest, Sha256};
12use uqa_core::memory::BudgetedVec;
13use uqa_core::CancellationToken;
14
15use crate::mvcc::key::RecordKey;
16use crate::mvcc::{CommitSequence, VersionError, VersionResult};
17use crate::read_control::StorageReadControl;
18
19use super::{hash_bytes, PreparedRecordCommit};
20
21#[derive(Clone)]
22pub(in crate::mvcc) struct RecordRequirement {
23 pub(in crate::mvcc) key: RecordKey,
24 pub(in crate::mvcc) expected: Option<CommitSequence>,
25}
26
27impl PreparedRecordCommit {
28 pub(in crate::mvcc) fn with_requirements(
29 mut self,
30 requirements: &[RecordRequirement],
31 control: &StorageReadControl,
32 ) -> VersionResult<Self> {
33 if requirements.is_empty() {
34 return Ok(self);
35 }
36 let mut owned = BudgetedVec::new(control.memory());
37 owned.reserve(requirements.len())?;
38 let mut digest = Sha256::new();
39 digest.update(b"UQA prepared record requirements 1");
40 digest.update(self.fingerprint);
41 digest.update((requirements.len() as u64).to_be_bytes());
42 for requirement in requirements {
43 control.cancellation().check()?;
44 digest.update((requirement.key.bytes().len() as u64).to_be_bytes());
45 hash_bytes(&mut digest, requirement.key.bytes(), control)?;
46 digest.update([u8::from(requirement.expected.is_some())]);
47 digest.update(
48 requirement
49 .expected
50 .map_or(0, CommitSequence::as_u64)
51 .to_be_bytes(),
52 );
53 owned.push(requirement.clone())?;
54 }
55 self.fingerprint = digest.finalize().into();
56 self.requirements = Some(Arc::new(owned));
57 Ok(self)
58 }
59
60 pub(crate) fn has_requirements(&self) -> bool {
61 self.requirements.is_some()
62 }
63
64 pub fn required_keys(&self) -> impl Iterator<Item = &[u8]> {
66 self.requirements.iter().flat_map(|requirements| {
67 requirements
68 .iter()
69 .map(|requirement| requirement.key.bytes())
70 })
71 }
72
73 pub(crate) fn validate_requirements(
74 &self,
75 cancellation: &CancellationToken,
76 mut head_revision: impl FnMut(&[u8]) -> VersionResult<Option<CommitSequence>>,
77 ) -> VersionResult<()> {
78 cancellation.check()?;
79 if let Some(requirements) = &self.requirements {
80 for (index, requirement) in requirements.iter().enumerate() {
81 cancellation.check()?;
82 let actual = head_revision(requirement.key.bytes())?;
83 if requirement.expected != actual {
84 return Err(VersionError::ReadConflict {
85 dependency: index,
86 expected: requirement.expected,
87 actual,
88 });
89 }
90 }
91 }
92 Ok(())
93 }
94}