Skip to main content

mongreldb_core/index/
bitmap.rs

1//! Roaring-bitmap secondary index — `value bytes → row-id set`.
2//!
3//! Best for low-cardinality columns (equality, IN, GROUP BY). Multiple indexes
4//! intersect with cheap SIMD bitmap ops in the shared [`RowId`] space.
5
6use crate::rowid::RowId;
7use roaring::RoaringBitmap;
8use std::collections::HashMap;
9use std::sync::Arc;
10
11type BitmapLayer = HashMap<Vec<u8>, RoaringBitmap>;
12
13/// `value → row-id set`. Values are type-aware encoded bytes (lexicographically
14/// comparable), matching the encoding used for page min/max.
15#[derive(Clone)]
16pub struct BitmapIndex {
17    frozen: Arc<Vec<Arc<BitmapLayer>>>,
18    active: BitmapLayer,
19}
20
21impl Default for BitmapIndex {
22    fn default() -> Self {
23        Self::new()
24    }
25}
26
27impl BitmapIndex {
28    pub fn new() -> Self {
29        Self {
30            frozen: Arc::new(Vec::new()),
31            active: HashMap::new(),
32        }
33    }
34
35    pub fn insert(&mut self, value: Vec<u8>, row_id: RowId) {
36        // Roaring bitmaps address u32. The Phase-3 upgrade shards bitmaps by
37        // the high 32 bits to cover the full u64 row-id space; until then we
38        // require row ids < 2^32.
39        let id32 = u32::try_from(row_id.0)
40            .expect("bitmap index supports row_id < 2^32; shard-by-high-bits is a Phase-3 upgrade");
41        self.active.entry(value).or_default().insert(id32);
42    }
43
44    /// Remove `row_id` from the set for `value` across the active layer and any
45    /// sealed generations. Used by secondary-index delta maintenance when a
46    /// row is replaced or an indexed column changes.
47    ///
48    /// Returns whether the id was present in any layer.
49    pub fn remove(&mut self, value: &[u8], row_id: RowId) -> bool {
50        let Ok(id32) = u32::try_from(row_id.0) else {
51            return false;
52        };
53        let mut removed = false;
54        if let Some(bm) = self.active.get_mut(value) {
55            removed |= bm.remove(id32);
56            if bm.is_empty() {
57                self.active.remove(value);
58            }
59        }
60        // Sealed generations are shared; copy-on-write only the layers that
61        // actually contain this membership so readers keep stable snapshots.
62        let mut new_frozen: Option<Vec<Arc<BitmapLayer>>> = None;
63        for (i, layer) in self.frozen.iter().enumerate() {
64            if layer.get(value).is_some_and(|bm| bm.contains(id32)) {
65                let layers = new_frozen
66                    .get_or_insert_with(|| self.frozen.iter().cloned().collect::<Vec<_>>());
67                let mut owned = (*layers[i]).clone();
68                if let Some(bm) = owned.get_mut(value) {
69                    if bm.remove(id32) {
70                        removed = true;
71                        if bm.is_empty() {
72                            owned.remove(value);
73                        }
74                    }
75                }
76                layers[i] = Arc::new(owned);
77            }
78        }
79        if let Some(layers) = new_frozen {
80            self.frozen = Arc::new(layers);
81        }
82        removed
83    }
84
85    /// True if any layer currently associates `value` with `row_id`.
86    #[cfg(test)]
87    pub fn contains(&self, value: &[u8], row_id: RowId) -> bool {
88        let Ok(id32) = u32::try_from(row_id.0) else {
89            return false;
90        };
91        if self.active.get(value).is_some_and(|bm| bm.contains(id32)) {
92            return true;
93        }
94        self.frozen
95            .iter()
96            .any(|layer| layer.get(value).is_some_and(|bm| bm.contains(id32)))
97    }
98
99    /// The row-id set for `value` (empty if absent).
100    pub fn get(&self, value: &[u8]) -> RoaringBitmap {
101        let mut rows = self.active.get(value).cloned().unwrap_or_default();
102        for layer in self.frozen.iter() {
103            if let Some(layer_rows) = layer.get(value) {
104                rows |= layer_rows;
105            }
106        }
107        rows
108    }
109
110    /// Intersection of several sets — the workhorse of multi-condition queries.
111    pub fn intersect(sets: &[RoaringBitmap]) -> RoaringBitmap {
112        match sets {
113            [] => RoaringBitmap::new(),
114            [first, rest @ ..] => {
115                let mut acc = first.clone();
116                for s in rest {
117                    acc &= s;
118                }
119                acc
120            }
121        }
122    }
123
124    pub fn value_count(&self) -> usize {
125        self.keys().len()
126    }
127
128    /// All distinct values (keys) in this index — Phase 17.2 broadcast join.
129    pub fn keys(&self) -> Vec<Vec<u8>> {
130        let mut keys = std::collections::HashSet::new();
131        keys.extend(self.active.keys().cloned());
132        for layer in self.frozen.iter() {
133            keys.extend(layer.keys().cloned());
134        }
135        keys.into_iter().collect()
136    }
137
138    /// Snapshot `(value_bytes → serialized RoaringBitmap)` pairs for
139    /// checkpointing to `_idx/global.idx`.
140    pub fn entries(&self) -> Vec<(Vec<u8>, Vec<u8>)> {
141        self.keys()
142            .into_iter()
143            .map(|k| {
144                let v = self.get(&k);
145                let mut bytes = Vec::new();
146                v.serialize_into(&mut bytes)
147                    .expect("roaring serialize is infallible for Vec");
148                (k, bytes)
149            })
150            .collect()
151    }
152
153    /// Rebuild from a snapshot produced by [`BitmapIndex::entries`].
154    pub fn from_entries(
155        entries: Vec<(Vec<u8>, Vec<u8>)>,
156    ) -> std::result::Result<Self, &'static str> {
157        let mut active = HashMap::new();
158        for (k, bytes) in entries {
159            let bm = RoaringBitmap::deserialize_from(&bytes[..]).map_err(|_| "bad bitmap bytes")?;
160            active.insert(k, bm);
161        }
162        Ok(Self {
163            frozen: Arc::new(Vec::new()),
164            active,
165        })
166    }
167
168    pub(crate) fn seal(&mut self) {
169        if self.active.is_empty() {
170            return;
171        }
172        let active = std::mem::take(&mut self.active);
173        Arc::make_mut(&mut self.frozen).push(Arc::new(active));
174        if self.frozen.len() >= crate::MAX_READ_GENERATION_LAYERS {
175            self.consolidate();
176        }
177    }
178
179    fn consolidate(&mut self) {
180        let mut merged = HashMap::<Vec<u8>, RoaringBitmap>::new();
181        for layer in self.frozen.iter() {
182            for (key, rows) in layer.iter() {
183                *merged.entry(key.clone()).or_default() |= rows;
184            }
185        }
186        self.frozen = Arc::new(vec![Arc::new(merged)]);
187    }
188
189    #[cfg(test)]
190    pub(crate) fn frozen_layer_count(&self) -> usize {
191        self.frozen.len()
192    }
193}
194
195#[cfg(test)]
196mod tests {
197    use super::*;
198
199    #[test]
200    fn insert_get_and_intersect() {
201        let mut color = BitmapIndex::new();
202        color.insert(b"red".to_vec(), RowId(1));
203        color.insert(b"red".to_vec(), RowId(3));
204        color.insert(b"blue".to_vec(), RowId(3));
205
206        let mut region = BitmapIndex::new();
207        region.insert(b"us".to_vec(), RowId(1));
208        region.insert(b"us".to_vec(), RowId(3));
209        region.insert(b"eu".to_vec(), RowId(2));
210
211        let red = color.get(b"red");
212        let us = region.get(b"us");
213        let both = BitmapIndex::intersect(&[red, us]);
214        let ids: Vec<u32> = both.iter().collect();
215        assert_eq!(ids, vec![1, 3]);
216    }
217
218    #[test]
219    fn remove_drops_membership_across_active_and_sealed() {
220        let mut idx = BitmapIndex::new();
221        idx.insert(b"tok".to_vec(), RowId(1));
222        idx.insert(b"tok".to_vec(), RowId(2));
223        idx.seal();
224        idx.insert(b"tok".to_vec(), RowId(3));
225        assert!(idx.contains(b"tok", RowId(1)));
226        assert!(idx.remove(b"tok", RowId(1)));
227        assert!(!idx.contains(b"tok", RowId(1)));
228        assert!(idx.contains(b"tok", RowId(2)));
229        assert!(idx.contains(b"tok", RowId(3)));
230        assert!(idx.remove(b"tok", RowId(3)));
231        assert!(!idx.contains(b"tok", RowId(3)));
232        assert!(!idx.remove(b"missing", RowId(9)));
233    }
234
235    #[test]
236    fn sealed_generations_share_bitmaps_and_consolidate() {
237        let mut writer = BitmapIndex::new();
238        for id in 0..crate::MAX_READ_GENERATION_LAYERS as u64 + 2 {
239            writer.insert(b"all".to_vec(), RowId(id));
240            writer.seal();
241        }
242        assert!(writer.frozen_layer_count() < crate::MAX_READ_GENERATION_LAYERS);
243        let generation = writer.clone();
244        writer.insert(b"new".to_vec(), RowId(99));
245        assert!(generation.get(b"new").is_empty());
246        assert!(writer.get(b"new").contains(99));
247    }
248}