reddb_server/storage/index/
mod.rs1pub mod bloom_segment;
24pub mod heavy_hitters;
25pub mod registry;
26pub mod stats;
27pub mod tid_bitmap;
28pub mod zone_map;
29pub mod zone_map_persist;
30
31pub use bloom_segment::{BloomSegment, BloomSegmentBuilder, HasBloom};
32pub use heavy_hitters::HeavyHitters;
33pub use registry::{IndexRegistry, IndexScope, SharedIndex};
34pub use stats::{IndexKind, IndexStats};
35pub use zone_map::{ZoneDecision, ZoneMap, ZonePredicate};
36
37use std::fmt;
38
39#[derive(Debug, Clone)]
41pub enum IndexError {
42 InvalidKey(String),
44 InvalidValue(String),
46 ReadOnly,
48 Full,
50 Storage(String),
52}
53
54impl fmt::Display for IndexError {
55 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56 match self {
57 IndexError::InvalidKey(m) => write!(f, "invalid key: {m}"),
58 IndexError::InvalidValue(m) => write!(f, "invalid value: {m}"),
59 IndexError::ReadOnly => write!(f, "index is read-only"),
60 IndexError::Full => write!(f, "index capacity exceeded"),
61 IndexError::Storage(m) => write!(f, "storage error: {m}"),
62 }
63 }
64}
65
66impl std::error::Error for IndexError {}
67
68pub trait IndexBase: Send + Sync {
72 fn name(&self) -> &str;
74
75 fn kind(&self) -> IndexKind;
77
78 fn stats(&self) -> IndexStats;
80
81 fn bloom(&self) -> Option<&crate::storage::primitives::BloomFilter> {
84 None
85 }
86
87 fn definitely_absent(&self, key_bytes: &[u8]) -> bool {
95 self.bloom()
96 .map(|b| !b.contains(key_bytes))
97 .unwrap_or(false)
98 }
99}
100
101pub trait PointIndex<K: ?Sized, V>: IndexBase {
104 fn insert(&mut self, key: &K, value: V) -> Result<(), IndexError>;
106
107 fn remove(&mut self, key: &K) -> Result<usize, IndexError>;
109
110 fn lookup(&self, key: &K) -> Vec<V>;
112
113 fn contains(&self, key: &K) -> bool {
115 !self.lookup(key).is_empty()
116 }
117}
118
119pub trait RangeIndex<K: ?Sized, V>: PointIndex<K, V> {
121 fn range(&self, start: Option<&K>, end: Option<&K>) -> Vec<(Vec<u8>, V)>;
124
125 fn distinct_keys(&self) -> usize;
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132 use crate::storage::primitives::BloomFilter;
133 use std::collections::BTreeMap;
134
135 struct TestBTree {
137 name: String,
138 map: BTreeMap<Vec<u8>, Vec<u64>>,
139 bloom: BloomFilter,
140 }
141
142 impl TestBTree {
143 fn new(name: &str) -> Self {
144 Self {
145 name: name.to_string(),
146 map: BTreeMap::new(),
147 bloom: BloomFilter::with_capacity(1024, 0.01),
148 }
149 }
150 }
151
152 impl IndexBase for TestBTree {
153 fn name(&self) -> &str {
154 &self.name
155 }
156 fn kind(&self) -> IndexKind {
157 IndexKind::BTree
158 }
159 fn stats(&self) -> IndexStats {
160 IndexStats {
161 entries: self.map.values().map(|v| v.len()).sum(),
162 distinct_keys: self.map.len(),
163 approx_bytes: 0,
164 kind: IndexKind::BTree,
165 has_bloom: true,
166 index_correlation: 0.0,
167 }
168 }
169 fn bloom(&self) -> Option<&BloomFilter> {
170 Some(&self.bloom)
171 }
172 }
173
174 impl PointIndex<[u8], u64> for TestBTree {
175 fn insert(&mut self, key: &[u8], value: u64) -> Result<(), IndexError> {
176 self.bloom.insert(key);
177 self.map.entry(key.to_vec()).or_default().push(value);
178 Ok(())
179 }
180 fn remove(&mut self, key: &[u8]) -> Result<usize, IndexError> {
181 Ok(self.map.remove(key).map(|v| v.len()).unwrap_or(0))
182 }
183 fn lookup(&self, key: &[u8]) -> Vec<u64> {
184 self.map.get(key).cloned().unwrap_or_default()
185 }
186 }
187
188 impl RangeIndex<[u8], u64> for TestBTree {
189 fn range(&self, start: Option<&[u8]>, end: Option<&[u8]>) -> Vec<(Vec<u8>, u64)> {
190 use std::ops::Bound;
191 let lo = start.map(Bound::Included).unwrap_or(Bound::Unbounded);
192 let hi = end.map(Bound::Excluded).unwrap_or(Bound::Unbounded);
193 self.map
194 .range::<[u8], _>((lo, hi))
195 .flat_map(|(k, vs)| vs.iter().map(move |v| (k.clone(), *v)))
196 .collect()
197 }
198 fn distinct_keys(&self) -> usize {
199 self.map.len()
200 }
201 }
202
203 #[test]
204 fn point_index_roundtrip() {
205 let mut idx = TestBTree::new("test");
206 idx.insert(b"alpha", 1).unwrap();
207 idx.insert(b"alpha", 2).unwrap();
208 idx.insert(b"beta", 3).unwrap();
209
210 assert_eq!(idx.lookup(b"alpha"), vec![1, 2]);
211 assert!(idx.contains(b"beta"));
212 assert!(!idx.contains(b"gamma"));
213 }
214
215 #[test]
216 fn bloom_prunes_absent_keys() {
217 let mut idx = TestBTree::new("test");
218 idx.insert(b"alpha", 1).unwrap();
219 assert!(!idx.definitely_absent(b"alpha"));
221 assert!(idx.lookup(b"not-there").is_empty());
224 }
225
226 #[test]
227 fn range_iteration() {
228 let mut idx = TestBTree::new("test");
229 for (i, k) in [b"a", b"b", b"c", b"d"].iter().enumerate() {
230 idx.insert(*k, i as u64).unwrap();
231 }
232 let out = idx.range(Some(b"b"), Some(b"d"));
233 let keys: Vec<&[u8]> = out.iter().map(|(k, _)| k.as_slice()).collect();
234 assert_eq!(keys, vec![b"b".as_slice(), b"c".as_slice()]);
235 }
236
237 #[test]
238 fn stats_surface_cardinality() {
239 let mut idx = TestBTree::new("test");
240 idx.insert(b"a", 1).unwrap();
241 idx.insert(b"a", 2).unwrap();
242 idx.insert(b"b", 3).unwrap();
243 let s = idx.stats();
244 assert_eq!(s.entries, 3);
245 assert_eq!(s.distinct_keys, 2);
246 assert!(s.has_bloom);
247 }
248}