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::{Floored, 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 into_key(self) -> Option<Self::Key> {
72        match self {
73            Self::Set(key, _) => Some(key),
74            Self::Commit(_, _) => None,
75        }
76    }
77
78    fn is_delete(&self) -> bool {
79        // Immutable databases don't support deletion
80        false
81    }
82
83    fn is_update(&self) -> bool {
84        matches!(self, Self::Set(_, _))
85    }
86}
87
88impl<F: Family, K: Key, V: ValueEncoding> Floored<F> for Operation<F, K, V> {
89    fn has_floor(&self) -> Option<Location<F>> {
90        self.has_floor()
91    }
92}
93
94impl<F: Family, K: Key, V: ValueEncoding> Display for Operation<F, K, V>
95where
96    V::Value: Encode,
97{
98    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99        match self {
100            Self::Set(key, value) => {
101                write!(f, "[key:{} value:{}]", hex(key), hex(&value.encode()))
102            }
103            Self::Commit(value, floor) => {
104                if let Some(value) = value {
105                    write!(f, "[commit {} floor:{}]", hex(&value.encode()), **floor)
106                } else {
107                    write!(f, "[commit floor:{}]", **floor)
108                }
109            }
110        }
111    }
112}
113
114#[cfg(feature = "arbitrary")]
115impl<F: Family, K: Key, V: ValueEncoding> arbitrary::Arbitrary<'_> for Operation<F, K, V>
116where
117    K: for<'a> arbitrary::Arbitrary<'a>,
118    V::Value: for<'a> arbitrary::Arbitrary<'a>,
119{
120    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
121        let choice = u.int_in_range(0..=1)?;
122        match choice {
123            0 => {
124                let key = K::arbitrary(u)?;
125                let value = V::Value::arbitrary(u)?;
126                Ok(Self::Set(key, value))
127            }
128            1 => {
129                let metadata = Option::<V::Value>::arbitrary(u)?;
130                let max_loc = F::MAX_LEAVES;
131                let floor = u.int_in_range(0..=*max_loc)?;
132                Ok(Self::Commit(metadata, Location::new(floor)))
133            }
134            _ => unreachable!(),
135        }
136    }
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142    use crate::{merkle::mmr, qmdb::any::value::VariableEncoding};
143    use commonware_codec::Encode;
144    use commonware_utils::sequence::U64;
145
146    type VarOp = Operation<mmr::Family, U64, VariableEncoding<U64>>;
147
148    #[test]
149    fn test_operation_key() {
150        let key = U64::new(1234);
151        let value = U64::new(56789);
152
153        let set_op = VarOp::Set(key.clone(), value.clone());
154        assert_eq!(&key, set_op.key().unwrap());
155
156        let commit_op = VarOp::Commit(Some(value), Location::new(0));
157        assert_eq!(None, commit_op.key());
158
159        let commit_op_none = VarOp::Commit(None, Location::new(0));
160        assert_eq!(None, commit_op_none.key());
161    }
162
163    #[test]
164    fn test_operation_is_commit() {
165        let key = U64::new(1234);
166        let value = U64::new(56789);
167
168        let set_op = VarOp::Set(key, value.clone());
169        assert!(!set_op.is_commit());
170
171        let commit_op = VarOp::Commit(Some(value), Location::new(0));
172        assert!(commit_op.is_commit());
173
174        let commit_op_none = VarOp::Commit(None, Location::new(0));
175        assert!(commit_op_none.is_commit());
176    }
177
178    #[test]
179    fn test_operation_has_floor() {
180        let key = U64::new(1234);
181        let value = U64::new(56789);
182
183        let set_op = VarOp::Set(key, value.clone());
184        assert_eq!(<VarOp as Floored<mmr::Family>>::has_floor(&set_op), None);
185
186        let commit_op = VarOp::Commit(Some(value), Location::new(42));
187        assert_eq!(
188            <VarOp as Floored<mmr::Family>>::has_floor(&commit_op),
189            Some(Location::new(42))
190        );
191
192        let commit_op_none = VarOp::Commit(None, Location::new(0));
193        assert_eq!(
194            <VarOp as Floored<mmr::Family>>::has_floor(&commit_op_none),
195            Some(Location::new(0))
196        );
197    }
198
199    #[test]
200    fn test_operation_display() {
201        let key = U64::new(1234);
202        let value = U64::new(56789);
203
204        let set_op = VarOp::Set(key.clone(), value.clone());
205        assert_eq!(
206            format!("{set_op}"),
207            format!("[key:{} value:{}]", hex(&key), hex(&value.encode()))
208        );
209
210        let commit_op = VarOp::Commit(Some(value.clone()), Location::new(10));
211        assert_eq!(
212            format!("{commit_op}"),
213            format!("[commit {} floor:10]", hex(&value.encode()))
214        );
215
216        let commit_op = VarOp::Commit(None, Location::new(0));
217        assert_eq!(format!("{commit_op}"), "[commit floor:0]");
218    }
219}