Skip to main content

dodb_core/
transaction.rs

1use std::collections::{BTreeMap, BTreeSet};
2use std::fmt;
3
4use crate::{DocumentKey, Lsn, ObservedState, Revision};
5
6#[derive(Clone, Debug, Eq, PartialEq)]
7pub enum WriteIntent {
8    Put(Vec<u8>),
9    Delete,
10}
11
12/// A point-key predicate evaluated at the transaction commit serialization
13/// point.
14#[derive(Clone, Debug, Eq, PartialEq)]
15pub enum TransactionCondition {
16    RevisionEquals {
17        key: DocumentKey,
18        expected_revision: Revision,
19    },
20    Exists {
21        key: DocumentKey,
22    },
23    NotExists {
24        key: DocumentKey,
25    },
26}
27
28impl TransactionCondition {
29    pub fn key(&self) -> &DocumentKey {
30        match self {
31            Self::RevisionEquals { key, .. } | Self::Exists { key } | Self::NotExists { key } => {
32                key
33            }
34        }
35    }
36
37    pub fn expectation(&self) -> ConditionExpectation {
38        match self {
39            Self::RevisionEquals {
40                expected_revision, ..
41            } => ConditionExpectation::RevisionEquals(*expected_revision),
42            Self::Exists { .. } => ConditionExpectation::Exists,
43            Self::NotExists { .. } => ConditionExpectation::NotExists,
44        }
45    }
46}
47
48/// The expected part of a structured transaction conflict.
49#[derive(Clone, Copy, Debug, Eq, PartialEq)]
50pub enum ConditionExpectation {
51    RevisionEquals(Revision),
52    Exists,
53    NotExists,
54}
55
56/// A mutation applied atomically with all other mutations in a request.
57#[derive(Clone, Debug, Eq, PartialEq)]
58pub enum TransactionMutation {
59    Put { key: DocumentKey, value: Vec<u8> },
60    Delete { key: DocumentKey },
61}
62
63impl TransactionMutation {
64    pub fn key(&self) -> &DocumentKey {
65        match self {
66            Self::Put { key, .. } | Self::Delete { key } => key,
67        }
68    }
69}
70
71/// The single-shot semantic primitive for an optimistic atomic write.
72#[derive(Clone, Debug, Default, Eq, PartialEq)]
73pub struct TransactionRequest {
74    pub conditions: Vec<TransactionCondition>,
75    pub mutations: Vec<TransactionMutation>,
76}
77
78impl TransactionRequest {
79    pub fn new(conditions: Vec<TransactionCondition>, mutations: Vec<TransactionMutation>) -> Self {
80        Self {
81            conditions,
82            mutations,
83        }
84    }
85
86    /// Rejects ambiguous requests before any storage preparation takes place.
87    pub fn validate(&self) -> crate::Result<()> {
88        if self.conditions.is_empty() && self.mutations.is_empty() {
89            return Err(crate::Error::invalid_request(
90                "a transaction must contain at least one condition or mutation",
91            ));
92        }
93
94        let mut mutation_keys = BTreeSet::new();
95        for mutation in &self.mutations {
96            if !mutation_keys.insert(mutation.key().clone()) {
97                return Err(crate::Error::invalid_request(
98                    "a transaction contains multiple mutations for one key",
99                ));
100            }
101        }
102
103        let mut condition_keys = BTreeSet::new();
104        for condition in &self.conditions {
105            if !condition_keys.insert(condition.key().clone()) {
106                return Err(crate::Error::invalid_request(
107                    "a transaction contains multiple conditions for one key",
108                ));
109            }
110        }
111        Ok(())
112    }
113}
114
115/// The committed identity returned for one successful logical transaction.
116///
117/// Condition-only transactions successfully validate their read predicates
118/// without creating a logical commit, so their commit identity is `None`.
119#[derive(Clone, Copy, Debug, Eq, PartialEq)]
120pub struct TransactionResult {
121    pub commit_lsn: Option<Lsn>,
122}
123
124/// Structured expected-vs-actual information for an optimistic conflict.
125#[derive(Clone, Debug, Eq, PartialEq)]
126pub struct TransactionConflict {
127    pub key: DocumentKey,
128    pub expected: ConditionExpectation,
129    pub actual: ObservedState,
130}
131
132impl fmt::Display for TransactionConflict {
133    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
134        write!(
135            formatter,
136            "key {:?}: expected {:?}, actual {:?}",
137            self.key, self.expected, self.actual
138        )
139    }
140}
141
142#[derive(Clone, Debug, Default, Eq, PartialEq)]
143pub struct ReadSet(BTreeMap<DocumentKey, Revision>);
144
145impl ReadSet {
146    pub fn record(&mut self, key: DocumentKey, revision: Revision) {
147        self.0.entry(key).or_insert(revision);
148    }
149
150    pub fn get(&self, key: &DocumentKey) -> Option<Revision> {
151        self.0.get(key).copied()
152    }
153
154    pub fn iter(&self) -> impl Iterator<Item = (&DocumentKey, &Revision)> {
155        self.0.iter()
156    }
157
158    pub fn len(&self) -> usize {
159        self.0.len()
160    }
161
162    pub fn is_empty(&self) -> bool {
163        self.0.is_empty()
164    }
165}
166
167#[derive(Clone, Debug, Default, Eq, PartialEq)]
168pub struct WriteSet(BTreeMap<DocumentKey, WriteIntent>);
169
170impl WriteSet {
171    pub fn put(&mut self, key: DocumentKey, value: impl Into<Vec<u8>>) {
172        self.0.insert(key, WriteIntent::Put(value.into()));
173    }
174
175    pub fn delete(&mut self, key: DocumentKey) {
176        self.0.insert(key, WriteIntent::Delete);
177    }
178
179    pub fn get(&self, key: &DocumentKey) -> Option<&WriteIntent> {
180        self.0.get(key)
181    }
182
183    pub fn iter(&self) -> impl Iterator<Item = (&DocumentKey, &WriteIntent)> {
184        self.0.iter()
185    }
186
187    pub fn len(&self) -> usize {
188        self.0.len()
189    }
190
191    pub fn is_empty(&self) -> bool {
192        self.0.is_empty()
193    }
194}