reddb_server/storage/index/
bloom_segment.rs1use crate::storage::primitives::split_block_bloom::SplitBlockBloom;
20
21pub use reddb_file::BloomSegmentFrameError as BloomSegmentError;
22
23pub trait HasBloom {
28 fn bloom_segment(&self) -> Option<&BloomSegment>;
30
31 fn definitely_absent(&self, key: &[u8]) -> bool {
34 self.bloom_segment()
35 .map(|b| b.definitely_absent(key))
36 .unwrap_or(false)
37 }
38}
39
40pub struct BloomSegment {
42 filter: SplitBlockBloom,
43 inserted: u32,
44}
45
46impl std::fmt::Debug for BloomSegment {
47 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48 f.debug_struct("BloomSegment")
49 .field("num_blocks", &self.filter.num_blocks())
50 .field("inserted", &self.inserted)
51 .finish()
52 }
53}
54
55impl BloomSegment {
56 pub fn with_capacity(expected: usize) -> Self {
59 Self {
60 filter: SplitBlockBloom::with_capacity(expected.max(16)),
61 inserted: 0,
62 }
63 }
64
65 pub fn insert(&mut self, key: &[u8]) {
67 self.filter.insert_bytes(key);
68 self.inserted = self.inserted.saturating_add(1);
69 }
70
71 pub fn contains(&self, key: &[u8]) -> bool {
74 self.filter.probe_bytes(key)
75 }
76
77 pub fn definitely_absent(&self, key: &[u8]) -> bool {
79 !self.filter.probe_bytes(key)
80 }
81
82 pub fn estimated_fp_rate(&self) -> f64 {
86 self.filter.fill_ratio().powi(8)
87 }
88
89 pub fn inserted_count(&self) -> u32 {
91 self.inserted
92 }
93
94 pub fn byte_size(&self) -> usize {
96 self.filter.byte_size()
97 }
98
99 pub fn union_inplace(&mut self, other: &BloomSegment) -> bool {
102 if self.filter.union_inplace(&other.filter) {
103 self.inserted = self.inserted.saturating_add(other.inserted);
104 true
105 } else {
106 false
107 }
108 }
109
110 pub fn encode(&self) -> Vec<u8> {
112 reddb_file::encode_bloom_segment_frame(self.inserted, &self.filter.to_bytes())
113 }
114
115 pub fn decode(bytes: &[u8]) -> Result<(Self, usize), BloomSegmentError> {
118 let (inserted, bloom_blob, consumed) = reddb_file::decode_bloom_segment_frame(bytes)?;
119 let filter =
120 SplitBlockBloom::from_bytes(&bloom_blob).ok_or(BloomSegmentError::LengthMismatch)?;
121 Ok((Self { filter, inserted }, consumed))
122 }
123}
124
125pub struct BloomSegmentBuilder {
127 expected: usize,
128}
129
130impl BloomSegmentBuilder {
131 pub fn new() -> Self {
132 Self { expected: 1024 }
133 }
134
135 pub fn expected(mut self, n: usize) -> Self {
136 self.expected = n;
137 self
138 }
139
140 pub fn build(self) -> BloomSegment {
141 BloomSegment::with_capacity(self.expected)
142 }
143}
144
145impl Default for BloomSegmentBuilder {
146 fn default() -> Self {
147 Self::new()
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 #[test]
156 fn insert_and_query() {
157 let mut seg = BloomSegment::with_capacity(1024);
158 seg.insert(b"alpha");
159 seg.insert(b"beta");
160
161 assert!(seg.contains(b"alpha"));
162 assert!(seg.contains(b"beta"));
163 assert!(seg.definitely_absent(b"gamma") || seg.contains(b"gamma"));
164 assert!(!seg.definitely_absent(b"alpha"));
166 assert_eq!(seg.inserted_count(), 2);
167 }
168
169 #[test]
170 fn encode_decode_roundtrip() {
171 let mut seg = BloomSegment::with_capacity(512);
172 for i in 0..100 {
173 seg.insert(format!("key{i}").as_bytes());
174 }
175
176 let bytes = seg.encode();
177 let (restored, consumed) = BloomSegment::decode(&bytes).unwrap();
178 assert_eq!(consumed, bytes.len());
179 assert_eq!(restored.inserted_count(), 100);
180 for i in 0..100 {
181 assert!(restored.contains(format!("key{i}").as_bytes()));
182 }
183 }
184
185 #[test]
186 fn decode_rejects_bad_magic() {
187 let mut bytes = BloomSegment::with_capacity(64).encode();
188 bytes[0] = 0x00;
189 assert_eq!(
190 BloomSegment::decode(&bytes).unwrap_err(),
191 BloomSegmentError::BadMagic
192 );
193 }
194
195 #[test]
196 fn decode_rejects_short_buffer() {
197 let bytes = [reddb_file::BLOOM_SEGMENT_V2_MAGIC, 0, 0, 0];
198 assert_eq!(
199 BloomSegment::decode(&bytes).unwrap_err(),
200 BloomSegmentError::TooShort
201 );
202 }
203
204 #[test]
205 fn decode_rejects_truncated_payload() {
206 let mut bytes = BloomSegment::with_capacity(64).encode();
207 bytes.truncate(bytes.len() - 1);
208 assert_eq!(
209 BloomSegment::decode(&bytes).unwrap_err(),
210 BloomSegmentError::LengthMismatch
211 );
212 }
213
214 #[test]
215 fn union_merges_populations() {
216 let mut a = BloomSegment::with_capacity(1024);
217 let mut b = BloomSegment::with_capacity(1024);
218 a.insert(b"one");
219 b.insert(b"two");
220 assert!(a.union_inplace(&b));
221 assert!(a.contains(b"one"));
222 assert!(a.contains(b"two"));
223 assert_eq!(a.inserted_count(), 2);
224 }
225
226 #[test]
227 fn union_rejects_incompatible() {
228 let mut a = BloomSegment::with_capacity(1024);
229 let b = BloomSegment::with_capacity(4096);
230 assert!(!a.union_inplace(&b));
231 }
232
233 #[test]
234 fn has_bloom_default_absent_when_none() {
235 struct NoBloom;
236 impl HasBloom for NoBloom {
237 fn bloom_segment(&self) -> Option<&BloomSegment> {
238 None
239 }
240 }
241 let x = NoBloom;
242 assert!(!x.definitely_absent(b"anything"));
243 }
244}