Skip to main content

lsm_db/
batch.rs

1//! Grouped writes.
2//!
3//! A [`Batch`] collects a sequence of puts and deletes and hands them to
4//! [`Lsm::write`](crate::Lsm::write), which applies the whole group under a
5//! single lock acquisition. That makes the group atomic with respect to
6//! concurrent readers — a reader sees either none of the batch or all of it,
7//! never a half-applied state — and amortises the per-write locking cost across
8//! the group.
9
10/// One buffered operation in a [`Batch`].
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub(crate) enum Op {
13    /// Set `key` to a value.
14    Put(Vec<u8>),
15    /// Delete `key`.
16    Delete,
17}
18
19/// An ordered group of writes applied together.
20///
21/// Operations are recorded in call order and replayed in that order when the
22/// batch is written, so a later operation on a key overrides an earlier one.
23///
24/// # Examples
25///
26/// ```
27/// use lsm_db::Batch;
28///
29/// let mut batch = Batch::new();
30/// batch.put(b"alpha", b"1");
31/// batch.put(b"beta", b"2");
32/// batch.delete(b"gamma");
33/// assert_eq!(batch.len(), 3);
34/// ```
35#[derive(Debug, Default, Clone)]
36pub struct Batch {
37    ops: Vec<(Vec<u8>, Op)>,
38}
39
40impl Batch {
41    /// Create an empty batch.
42    ///
43    /// # Examples
44    ///
45    /// ```
46    /// use lsm_db::Batch;
47    /// let batch = Batch::new();
48    /// assert!(batch.is_empty());
49    /// ```
50    #[inline]
51    #[must_use]
52    pub fn new() -> Self {
53        Batch { ops: Vec::new() }
54    }
55
56    /// Queue setting `key` to `value`.
57    ///
58    /// Both are copied into the batch, so the caller's buffers are free to be
59    /// reused immediately.
60    ///
61    /// # Examples
62    ///
63    /// ```
64    /// use lsm_db::Batch;
65    /// let mut batch = Batch::new();
66    /// batch.put(b"key", b"value");
67    /// assert_eq!(batch.len(), 1);
68    /// ```
69    pub fn put(&mut self, key: impl AsRef<[u8]>, value: impl AsRef<[u8]>) {
70        self.ops
71            .push((key.as_ref().to_vec(), Op::Put(value.as_ref().to_vec())));
72    }
73
74    /// Queue deleting `key`.
75    ///
76    /// # Examples
77    ///
78    /// ```
79    /// use lsm_db::Batch;
80    /// let mut batch = Batch::new();
81    /// batch.delete(b"key");
82    /// assert_eq!(batch.len(), 1);
83    /// ```
84    pub fn delete(&mut self, key: impl AsRef<[u8]>) {
85        self.ops.push((key.as_ref().to_vec(), Op::Delete));
86    }
87
88    /// The number of queued operations.
89    #[inline]
90    #[must_use]
91    pub fn len(&self) -> usize {
92        self.ops.len()
93    }
94
95    /// Whether the batch has no queued operations.
96    #[inline]
97    #[must_use]
98    pub fn is_empty(&self) -> bool {
99        self.ops.is_empty()
100    }
101
102    /// Consume the batch, yielding its operations in call order.
103    pub(crate) fn into_ops(self) -> Vec<(Vec<u8>, Op)> {
104        self.ops
105    }
106}
107
108#[cfg(test)]
109#[allow(clippy::unwrap_used, clippy::expect_used)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn test_new_batch_is_empty() {
115        let b = Batch::new();
116        assert!(b.is_empty());
117        assert_eq!(b.len(), 0);
118    }
119
120    #[test]
121    fn test_records_operations_in_order() {
122        let mut b = Batch::new();
123        b.put(b"a", b"1");
124        b.delete(b"b");
125        let ops = b.into_ops();
126        assert_eq!(ops[0], (b"a".to_vec(), Op::Put(b"1".to_vec())));
127        assert_eq!(ops[1], (b"b".to_vec(), Op::Delete));
128    }
129
130    #[test]
131    fn test_accepts_vec_and_slice_keys() {
132        let mut b = Batch::new();
133        b.put(vec![1u8, 2, 3], vec![4u8]);
134        b.put([9u8, 8].as_slice(), [7u8].as_slice());
135        assert_eq!(b.len(), 2);
136    }
137}