1use std::io::Cursor;
4
5use ahash::HashMap;
6use parking_lot::RwLock;
7use roaring::RoaringBitmap;
8use serde::{Deserialize, Serialize};
9
10use mentedb_core::error::{MenteError, MenteResult};
11use mentedb_core::types::MemoryId;
12
13pub struct BitmapIndex {
18 inner: RwLock<BitmapInner>,
19}
20
21struct BitmapInner {
22 tag_bitmaps: HashMap<String, RoaringBitmap>,
24 id_to_offset: HashMap<MemoryId, u32>,
26 offset_to_id: Vec<MemoryId>,
28}
29
30impl BitmapIndex {
31 pub fn new() -> Self {
32 Self {
33 inner: RwLock::new(BitmapInner {
34 tag_bitmaps: HashMap::default(),
35 id_to_offset: HashMap::default(),
36 offset_to_id: Vec::new(),
37 }),
38 }
39 }
40
41 fn ensure_offset(inner: &mut BitmapInner, id: MemoryId) -> u32 {
43 if let Some(&offset) = inner.id_to_offset.get(&id) {
44 return offset;
45 }
46 let offset = inner.offset_to_id.len() as u32;
47 inner.id_to_offset.insert(id, offset);
48 inner.offset_to_id.push(id);
49 offset
50 }
51
52 pub fn add_tag(&self, id: MemoryId, tag: &str) {
54 let mut inner = self.inner.write();
55 let offset = Self::ensure_offset(&mut inner, id);
56 inner
57 .tag_bitmaps
58 .entry(tag.to_string())
59 .or_default()
60 .insert(offset);
61 }
62
63 pub fn remove_tag(&self, id: MemoryId, tag: &str) {
65 let mut inner = self.inner.write();
66 if let Some(&offset) = inner.id_to_offset.get(&id)
67 && let Some(bm) = inner.tag_bitmaps.get_mut(tag)
68 {
69 bm.remove(offset);
70 }
71 }
72
73 pub fn query_tag(&self, tag: &str) -> Vec<MemoryId> {
75 let inner = self.inner.read();
76 match inner.tag_bitmaps.get(tag) {
77 Some(bm) => bm
78 .iter()
79 .filter_map(|offset| inner.offset_to_id.get(offset as usize).copied())
80 .collect(),
81 None => Vec::new(),
82 }
83 }
84
85 pub fn query_tags_and(&self, tags: &[&str]) -> Vec<MemoryId> {
87 let inner = self.inner.read();
88 if tags.is_empty() {
89 return Vec::new();
90 }
91
92 let mut result: Option<RoaringBitmap> = None;
93 for tag in tags {
94 match inner.tag_bitmaps.get(*tag) {
95 Some(bm) => {
96 result = Some(match result {
97 Some(r) => r & bm,
98 None => bm.clone(),
99 });
100 }
101 None => return Vec::new(),
102 }
103 }
104
105 match result {
106 Some(bm) => bm
107 .iter()
108 .filter_map(|offset| inner.offset_to_id.get(offset as usize).copied())
109 .collect(),
110 None => Vec::new(),
111 }
112 }
113
114 pub fn query_tags_or(&self, tags: &[&str]) -> Vec<MemoryId> {
116 let inner = self.inner.read();
117 if tags.is_empty() {
118 return Vec::new();
119 }
120
121 let mut result = RoaringBitmap::new();
122 for tag in tags {
123 if let Some(bm) = inner.tag_bitmaps.get(*tag) {
124 result |= bm;
125 }
126 }
127
128 result
129 .iter()
130 .filter_map(|offset| inner.offset_to_id.get(offset as usize).copied())
131 .collect()
132 }
133
134 pub fn remove_all(&self, id: MemoryId) {
136 let mut inner = self.inner.write();
137 if let Some(&offset) = inner.id_to_offset.get(&id) {
138 for bm in inner.tag_bitmaps.values_mut() {
139 bm.remove(offset);
140 }
141 }
142 }
143}
144
145#[derive(Serialize, Deserialize)]
147struct BitmapSnapshot {
148 tag_bitmaps: Vec<(String, Vec<u8>)>,
150 id_to_offset: Vec<(MemoryId, u32)>,
151 offset_to_id: Vec<MemoryId>,
152}
153
154impl BitmapIndex {
155 pub fn save(&self, path: &std::path::Path) -> MenteResult<()> {
157 let inner = self.inner.read();
158 let mut tag_bitmaps = Vec::new();
159 for (tag, bm) in &inner.tag_bitmaps {
160 let mut buf = Vec::new();
161 bm.serialize_into(&mut buf)
162 .map_err(|e| MenteError::Serialization(e.to_string()))?;
163 tag_bitmaps.push((tag.clone(), buf));
164 }
165 let snapshot = BitmapSnapshot {
166 tag_bitmaps,
167 id_to_offset: inner.id_to_offset.iter().map(|(&k, &v)| (k, v)).collect(),
168 offset_to_id: inner.offset_to_id.clone(),
169 };
170 let data =
171 serde_json::to_vec(&snapshot).map_err(|e| MenteError::Serialization(e.to_string()))?;
172 std::fs::write(path, data)?;
173 Ok(())
174 }
175
176 pub fn load(path: &std::path::Path) -> MenteResult<Self> {
178 let data = std::fs::read(path)?;
179 let snapshot: BitmapSnapshot =
180 serde_json::from_slice(&data).map_err(|e| MenteError::Serialization(e.to_string()))?;
181
182 let mut tag_bitmaps = HashMap::default();
183 for (tag, bytes) in snapshot.tag_bitmaps {
184 let bm = RoaringBitmap::deserialize_from(&mut Cursor::new(bytes))
185 .map_err(|e| MenteError::Serialization(e.to_string()))?;
186 tag_bitmaps.insert(tag, bm);
187 }
188
189 let mut id_to_offset = HashMap::default();
190 for (id, offset) in snapshot.id_to_offset {
191 id_to_offset.insert(id, offset);
192 }
193
194 Ok(Self {
195 inner: RwLock::new(BitmapInner {
196 tag_bitmaps,
197 id_to_offset,
198 offset_to_id: snapshot.offset_to_id,
199 }),
200 })
201 }
202}
203
204impl Default for BitmapIndex {
205 fn default() -> Self {
206 Self::new()
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213 use uuid::Uuid;
214
215 #[test]
216 fn test_add_and_query_tag() {
217 let idx = BitmapIndex::new();
218 let id = Uuid::new_v4();
219 idx.add_tag(id, "important");
220
221 let results = idx.query_tag("important");
222 assert_eq!(results, vec![id]);
223 }
224
225 #[test]
226 fn test_query_empty_tag() {
227 let idx = BitmapIndex::new();
228 assert!(idx.query_tag("nonexistent").is_empty());
229 }
230
231 #[test]
232 fn test_remove_tag() {
233 let idx = BitmapIndex::new();
234 let id = Uuid::new_v4();
235 idx.add_tag(id, "foo");
236 idx.remove_tag(id, "foo");
237
238 assert!(idx.query_tag("foo").is_empty());
239 }
240
241 #[test]
242 fn test_tags_and() {
243 let idx = BitmapIndex::new();
244 let a = Uuid::new_v4();
245 let b = Uuid::new_v4();
246
247 idx.add_tag(a, "x");
248 idx.add_tag(a, "y");
249 idx.add_tag(b, "x");
250
251 let both = idx.query_tags_and(&["x", "y"]);
252 assert_eq!(both, vec![a]);
253 }
254
255 #[test]
256 fn test_tags_or() {
257 let idx = BitmapIndex::new();
258 let a = Uuid::new_v4();
259 let b = Uuid::new_v4();
260
261 idx.add_tag(a, "x");
262 idx.add_tag(b, "y");
263
264 let either = idx.query_tags_or(&["x", "y"]);
265 assert_eq!(either.len(), 2);
266 }
267
268 #[test]
269 fn test_remove_all() {
270 let idx = BitmapIndex::new();
271 let id = Uuid::new_v4();
272 idx.add_tag(id, "a");
273 idx.add_tag(id, "b");
274 idx.remove_all(id);
275
276 assert!(idx.query_tag("a").is_empty());
277 assert!(idx.query_tag("b").is_empty());
278 }
279}