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_utils::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<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.
37    Commit(Option<V::Value>),
38}
39
40impl<K: Key, V: ValueEncoding> Operation<K, V> {
41    /// If this is an operation involving a key, returns the key. Otherwise, returns None.
42    pub const fn key(&self) -> Option<&K> {
43        match self {
44            Self::Set(key, _) => Some(key),
45            Self::Commit(_) => None,
46        }
47    }
48
49    /// Returns true if this is a commit operation.
50    pub const fn is_commit(&self) -> bool {
51        matches!(self, Self::Commit(_))
52    }
53}
54
55impl<F: Family, K: Key, V: ValueEncoding> OperationTrait<F> for Operation<K, V> {
56    type Key = K;
57
58    fn key(&self) -> Option<&Self::Key> {
59        self.key()
60    }
61
62    fn is_delete(&self) -> bool {
63        // Immutable databases don't support deletion
64        false
65    }
66
67    fn is_update(&self) -> bool {
68        matches!(self, Self::Set(_, _))
69    }
70
71    fn has_floor(&self) -> Option<Location<F>> {
72        // Immutable databases don't have inactivity floors
73        None
74    }
75}
76
77impl<K: Key, V: ValueEncoding> Display for Operation<K, V>
78where
79    V::Value: Encode,
80{
81    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82        match self {
83            Self::Set(key, value) => {
84                write!(f, "[key:{} value:{}]", hex(key), hex(&value.encode()))
85            }
86            Self::Commit(value) => {
87                if let Some(value) = value {
88                    write!(f, "[commit {}]", hex(&value.encode()))
89                } else {
90                    write!(f, "[commit]")
91                }
92            }
93        }
94    }
95}
96
97#[cfg(feature = "arbitrary")]
98impl<K: Key, V: ValueEncoding> arbitrary::Arbitrary<'_> for Operation<K, V>
99where
100    K: for<'a> arbitrary::Arbitrary<'a>,
101    V::Value: for<'a> arbitrary::Arbitrary<'a>,
102{
103    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
104        let choice = u.int_in_range(0..=1)?;
105        match choice {
106            0 => {
107                let key = K::arbitrary(u)?;
108                let value = V::Value::arbitrary(u)?;
109                Ok(Self::Set(key, value))
110            }
111            1 => Ok(Self::Commit(Option::<V::Value>::arbitrary(u)?)),
112            _ => unreachable!(),
113        }
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120    use crate::qmdb::any::value::VariableEncoding;
121    use commonware_codec::Encode;
122    use commonware_utils::sequence::U64;
123
124    type VarOp = Operation<U64, VariableEncoding<U64>>;
125
126    #[test]
127    fn test_operation_key() {
128        let key = U64::new(1234);
129        let value = U64::new(56789);
130
131        let set_op = VarOp::Set(key.clone(), value.clone());
132        assert_eq!(&key, set_op.key().unwrap());
133
134        let commit_op = VarOp::Commit(Some(value));
135        assert_eq!(None, commit_op.key());
136
137        let commit_op_none = VarOp::Commit(None);
138        assert_eq!(None, commit_op_none.key());
139    }
140
141    #[test]
142    fn test_operation_is_commit() {
143        let key = U64::new(1234);
144        let value = U64::new(56789);
145
146        let set_op = VarOp::Set(key, value.clone());
147        assert!(!set_op.is_commit());
148
149        let commit_op = VarOp::Commit(Some(value));
150        assert!(commit_op.is_commit());
151
152        let commit_op_none = VarOp::Commit(None);
153        assert!(commit_op_none.is_commit());
154    }
155
156    #[test]
157    fn test_operation_display() {
158        let key = U64::new(1234);
159        let value = U64::new(56789);
160
161        let set_op = VarOp::Set(key.clone(), value.clone());
162        assert_eq!(
163            format!("{set_op}"),
164            format!("[key:{} value:{}]", hex(&key), hex(&value.encode()))
165        );
166
167        let commit_op = VarOp::Commit(Some(value.clone()));
168        assert_eq!(
169            format!("{commit_op}"),
170            format!("[commit {}]", hex(&value.encode()))
171        );
172
173        let commit_op = VarOp::Commit(None);
174        assert_eq!(format!("{commit_op}"), "[commit]");
175    }
176}