Skip to main content

commonware_storage/qmdb/immutable/operation/
mod.rs

1//! Operations for immutable authenticated databases.
2//!
3//! This module provides the [Operation] type for databases that only support
4//! adding new keyed values (no updates or deletions).
5//!
6//! The operation type is generic over the value encoding, which determines
7//! whether operations are fixed-size or variable-size on disk.
8
9pub(crate) mod fixed;
10pub(crate) mod variable;
11
12use crate::{
13    merkle::{Family, Location},
14    qmdb::{
15        any::ValueEncoding,
16        operation::{Key, Operation as OperationTrait},
17    },
18};
19use commonware_codec::Encode;
20use commonware_formatting::hex;
21use core::fmt::Display;
22
23// Context byte prefixes for identifying the operation type.
24pub(crate) const SET_CONTEXT: u8 = 0;
25pub(crate) const COMMIT_CONTEXT: u8 = 1;
26
27/// An operation applied to an immutable authenticated database.
28///
29/// Unlike mutable database operations, immutable operations only support
30/// setting new values and committing - no updates or deletions.
31#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
32pub enum Operation<F: Family, K: Key, V: ValueEncoding> {
33    /// Set a key to a value. The key must not already exist.
34    Set(K, V::Value),
35
36    /// Commit with optional metadata and the inactivity floor location.
37    /// Operations before the floor are declared inactive by the application.
38    Commit(Option<V::Value>, Location<F>),
39}
40
41impl<F: Family, K: Key, V: ValueEncoding> Operation<F, K, V> {
42    /// If this is an operation involving a key, returns the key. Otherwise, returns None.
43    pub const fn key(&self) -> Option<&K> {
44        match self {
45            Self::Set(key, _) => Some(key),
46            Self::Commit(_, _) => None,
47        }
48    }
49
50    /// Returns true if this is a commit operation.
51    pub const fn is_commit(&self) -> bool {
52        matches!(self, Self::Commit(_, _))
53    }
54
55    /// Returns the inactivity floor location if this is a commit operation.
56    pub const fn has_floor(&self) -> Option<Location<F>> {
57        match self {
58            Self::Commit(_, loc) => Some(*loc),
59            Self::Set(_, _) => None,
60        }
61    }
62}
63
64impl<F: Family, K: Key, V: ValueEncoding> OperationTrait<F> for Operation<F, K, V> {
65    type Key = K;
66
67    fn key(&self) -> Option<&Self::Key> {
68        self.key()
69    }
70
71    fn is_delete(&self) -> bool {
72        // Immutable databases don't support deletion
73        false
74    }
75
76    fn is_update(&self) -> bool {
77        matches!(self, Self::Set(_, _))
78    }
79
80    fn has_floor(&self) -> Option<Location<F>> {
81        self.has_floor()
82    }
83}
84
85impl<F: Family, K: Key, V: ValueEncoding> Display for Operation<F, K, V>
86where
87    V::Value: Encode,
88{
89    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90        match self {
91            Self::Set(key, value) => {
92                write!(f, "[key:{} value:{}]", hex(key), hex(&value.encode()))
93            }
94            Self::Commit(value, floor) => {
95                if let Some(value) = value {
96                    write!(f, "[commit {} floor:{}]", hex(&value.encode()), **floor)
97                } else {
98                    write!(f, "[commit floor:{}]", **floor)
99                }
100            }
101        }
102    }
103}
104
105#[cfg(feature = "arbitrary")]
106impl<F: Family, K: Key, V: ValueEncoding> arbitrary::Arbitrary<'_> for Operation<F, K, V>
107where
108    K: for<'a> arbitrary::Arbitrary<'a>,
109    V::Value: for<'a> arbitrary::Arbitrary<'a>,
110{
111    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
112        let choice = u.int_in_range(0..=1)?;
113        match choice {
114            0 => {
115                let key = K::arbitrary(u)?;
116                let value = V::Value::arbitrary(u)?;
117                Ok(Self::Set(key, value))
118            }
119            1 => {
120                let metadata = Option::<V::Value>::arbitrary(u)?;
121                let max_loc = F::MAX_LEAVES;
122                let floor = u.int_in_range(0..=*max_loc)?;
123                Ok(Self::Commit(metadata, Location::new(floor)))
124            }
125            _ => unreachable!(),
126        }
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133    use crate::{merkle::mmr, qmdb::any::value::VariableEncoding};
134    use commonware_codec::Encode;
135    use commonware_utils::sequence::U64;
136
137    type VarOp = Operation<mmr::Family, U64, VariableEncoding<U64>>;
138
139    #[test]
140    fn test_operation_key() {
141        let key = U64::new(1234);
142        let value = U64::new(56789);
143
144        let set_op = VarOp::Set(key.clone(), value.clone());
145        assert_eq!(&key, set_op.key().unwrap());
146
147        let commit_op = VarOp::Commit(Some(value), Location::new(0));
148        assert_eq!(None, commit_op.key());
149
150        let commit_op_none = VarOp::Commit(None, Location::new(0));
151        assert_eq!(None, commit_op_none.key());
152    }
153
154    #[test]
155    fn test_operation_is_commit() {
156        let key = U64::new(1234);
157        let value = U64::new(56789);
158
159        let set_op = VarOp::Set(key, value.clone());
160        assert!(!set_op.is_commit());
161
162        let commit_op = VarOp::Commit(Some(value), Location::new(0));
163        assert!(commit_op.is_commit());
164
165        let commit_op_none = VarOp::Commit(None, Location::new(0));
166        assert!(commit_op_none.is_commit());
167    }
168
169    #[test]
170    fn test_operation_has_floor() {
171        let key = U64::new(1234);
172        let value = U64::new(56789);
173
174        let set_op = VarOp::Set(key, value.clone());
175        assert_eq!(
176            <VarOp as OperationTrait<mmr::Family>>::has_floor(&set_op),
177            None
178        );
179
180        let commit_op = VarOp::Commit(Some(value), Location::new(42));
181        assert_eq!(
182            <VarOp as OperationTrait<mmr::Family>>::has_floor(&commit_op),
183            Some(Location::new(42))
184        );
185
186        let commit_op_none = VarOp::Commit(None, Location::new(0));
187        assert_eq!(
188            <VarOp as OperationTrait<mmr::Family>>::has_floor(&commit_op_none),
189            Some(Location::new(0))
190        );
191    }
192
193    #[test]
194    fn test_operation_display() {
195        let key = U64::new(1234);
196        let value = U64::new(56789);
197
198        let set_op = VarOp::Set(key.clone(), value.clone());
199        assert_eq!(
200            format!("{set_op}"),
201            format!("[key:{} value:{}]", hex(&key), hex(&value.encode()))
202        );
203
204        let commit_op = VarOp::Commit(Some(value.clone()), Location::new(10));
205        assert_eq!(
206            format!("{commit_op}"),
207            format!("[commit {} floor:10]", hex(&value.encode()))
208        );
209
210        let commit_op = VarOp::Commit(None, Location::new(0));
211        assert_eq!(format!("{commit_op}"), "[commit floor:0]");
212    }
213}