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}