velesdb_core/collection/graph/
range_index.rs1#![allow(clippy::cast_precision_loss)]
9
10use super::helpers::{make_label_prop_key, safe_bitmap_id, PostcardPersistence};
11use roaring::RoaringBitmap;
12use serde::{Deserialize, Serialize};
13use std::collections::{BTreeMap, HashMap};
14use std::ops::Bound;
15
16#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
20pub(crate) enum OrderedValue {
21 Null,
23 Integer(i64),
25 Float(OrderedFloat),
27 String(String),
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
33pub struct OrderedFloat(pub f64);
34
35impl PartialOrd for OrderedFloat {
36 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
37 Some(self.cmp(other))
38 }
39}
40
41impl Eq for OrderedFloat {}
42
43impl Ord for OrderedFloat {
44 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
45 self.0.total_cmp(&other.0)
48 }
49}
50
51impl PartialOrd for OrderedValue {
52 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
53 Some(self.cmp(other))
54 }
55}
56
57impl Ord for OrderedValue {
58 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
59 match (self, other) {
60 (Self::Null, Self::Null) => std::cmp::Ordering::Equal,
61 (Self::Null, _) | (Self::Integer(_) | Self::Float(_), Self::String(_)) => {
62 std::cmp::Ordering::Less
63 }
64 (_, Self::Null) | (Self::String(_), Self::Integer(_) | Self::Float(_)) => {
65 std::cmp::Ordering::Greater
66 }
67 (Self::Integer(a), Self::Integer(b)) => a.cmp(b),
68 (Self::Float(a), Self::Float(b)) => a.cmp(b),
69 (Self::Integer(a), Self::Float(b)) => OrderedFloat(*a as f64).cmp(b),
70 (Self::Float(a), Self::Integer(b)) => a.cmp(&OrderedFloat(*b as f64)),
71 (Self::String(a), Self::String(b)) => a.cmp(b),
72 }
73 }
74}
75
76impl OrderedValue {
77 #[must_use]
79 pub fn from_json(value: &serde_json::Value) -> Option<Self> {
80 match value {
81 serde_json::Value::Null => Some(Self::Null),
82 serde_json::Value::Number(n) => {
83 if let Some(i) = n.as_i64() {
84 Some(Self::Integer(i))
85 } else {
86 n.as_f64().map(|f| Self::Float(OrderedFloat(f)))
87 }
88 }
89 serde_json::Value::String(s) => Some(Self::String(s.clone())),
90 _ => None, }
92 }
93}
94
95pub const RANGE_INDEX_VERSION: u32 = 1;
98
99#[derive(Debug, Default, Serialize, Deserialize)]
115pub struct RangeIndex {
116 #[serde(default = "default_range_version")]
118 version: u32,
119 indexes: HashMap<(String, String), BTreeMap<OrderedValue, RoaringBitmap>>,
121}
122
123fn default_range_version() -> u32 {
124 RANGE_INDEX_VERSION
125}
126
127impl RangeIndex {
128 #[must_use]
130 pub fn new() -> Self {
131 Self::default()
132 }
133
134 pub fn create_index(&mut self, label: &str, property: &str) {
136 self.indexes
137 .entry(make_label_prop_key(label, property))
138 .or_default();
139 }
140
141 #[must_use]
143 pub fn has_index(&self, label: &str, property: &str) -> bool {
144 self.indexes
145 .contains_key(&make_label_prop_key(label, property))
146 }
147
148 pub fn insert(
153 &mut self,
154 label: &str,
155 property: &str,
156 value: &serde_json::Value,
157 node_id: u64,
158 ) -> bool {
159 let Some(safe_id) = safe_bitmap_id(node_id) else {
160 return false;
161 };
162
163 let key = make_label_prop_key(label, property);
164 if let Some(btree) = self.indexes.get_mut(&key) {
165 if let Some(ordered) = OrderedValue::from_json(value) {
166 btree.entry(ordered).or_default().insert(safe_id);
167 return true;
168 }
169 }
170 false
171 }
172
173 pub fn remove(
177 &mut self,
178 label: &str,
179 property: &str,
180 value: &serde_json::Value,
181 node_id: u64,
182 ) -> bool {
183 let Some(safe_id) = safe_bitmap_id(node_id) else {
184 return false;
185 };
186
187 let key = make_label_prop_key(label, property);
188 if let Some(btree) = self.indexes.get_mut(&key) {
189 if let Some(ordered) = OrderedValue::from_json(value) {
190 if let Some(bitmap) = btree.get_mut(&ordered) {
191 let removed = bitmap.remove(safe_id);
192 if bitmap.is_empty() {
193 btree.remove(&ordered);
194 }
195 return removed;
196 }
197 }
198 }
199 false
200 }
201
202 #[must_use]
204 pub fn range_greater_than(
205 &self,
206 label: &str,
207 property: &str,
208 threshold: &serde_json::Value,
209 ) -> RoaringBitmap {
210 self.range_query(
211 label,
212 property,
213 Bound::Excluded(threshold),
214 Bound::Unbounded,
215 )
216 }
217
218 #[must_use]
220 pub fn range_greater_or_equal(
221 &self,
222 label: &str,
223 property: &str,
224 threshold: &serde_json::Value,
225 ) -> RoaringBitmap {
226 self.range_query(
227 label,
228 property,
229 Bound::Included(threshold),
230 Bound::Unbounded,
231 )
232 }
233
234 #[must_use]
236 pub fn range_less_than(
237 &self,
238 label: &str,
239 property: &str,
240 threshold: &serde_json::Value,
241 ) -> RoaringBitmap {
242 self.range_query(
243 label,
244 property,
245 Bound::Unbounded,
246 Bound::Excluded(threshold),
247 )
248 }
249
250 #[must_use]
252 pub fn range_less_or_equal(
253 &self,
254 label: &str,
255 property: &str,
256 threshold: &serde_json::Value,
257 ) -> RoaringBitmap {
258 self.range_query(
259 label,
260 property,
261 Bound::Unbounded,
262 Bound::Included(threshold),
263 )
264 }
265
266 #[must_use]
268 pub fn range_between(
269 &self,
270 label: &str,
271 property: &str,
272 start: &serde_json::Value,
273 end: &serde_json::Value,
274 ) -> RoaringBitmap {
275 self.range_query(
276 label,
277 property,
278 Bound::Included(start),
279 Bound::Included(end),
280 )
281 }
282
283 fn convert_bound(bound: Bound<&serde_json::Value>) -> Option<Bound<OrderedValue>> {
287 match bound {
288 Bound::Included(v) => OrderedValue::from_json(v).map(Bound::Included),
289 Bound::Excluded(v) => OrderedValue::from_json(v).map(Bound::Excluded),
290 Bound::Unbounded => Some(Bound::Unbounded),
291 }
292 }
293
294 fn range_query(
296 &self,
297 label: &str,
298 property: &str,
299 start: Bound<&serde_json::Value>,
300 end: Bound<&serde_json::Value>,
301 ) -> RoaringBitmap {
302 let mut result = RoaringBitmap::new();
303
304 let key = make_label_prop_key(label, property);
305 let Some(btree) = self.indexes.get(&key) else {
306 return result;
307 };
308
309 let (Some(start_ord), Some(end_ord)) =
310 (Self::convert_bound(start), Self::convert_bound(end))
311 else {
312 return result;
313 };
314
315 for bitmap in btree.range((start_ord, end_ord)).map(|(_k, v)| v) {
317 result |= bitmap;
318 }
319
320 result
321 }
322
323 pub fn drop_index(&mut self, label: &str, property: &str) -> bool {
325 self.indexes
326 .remove(&make_label_prop_key(label, property))
327 .is_some()
328 }
329
330 pub fn clear(&mut self) {
332 self.indexes.clear();
333 }
334
335 #[must_use]
337 pub fn memory_usage(&self) -> usize {
338 let mut total = std::mem::size_of::<Self>();
339 for ((label, prop), btree) in &self.indexes {
340 total += label.len() + prop.len();
341 for bitmap in btree.values() {
342 total += std::mem::size_of::<OrderedValue>();
343 total += bitmap.serialized_size();
344 }
345 }
346 total
347 }
348
349 #[must_use]
351 pub fn indexed_properties(&self) -> Vec<(String, String)> {
352 self.indexes.keys().cloned().collect()
353 }
354}
355
356impl PostcardPersistence for RangeIndex {}
357
358impl RangeIndex {
362 pub fn to_bytes(&self) -> Result<Vec<u8>, postcard::Error> {
367 <Self as PostcardPersistence>::to_bytes(self)
368 }
369
370 pub fn from_bytes(bytes: &[u8]) -> Result<Self, postcard::Error> {
375 <Self as PostcardPersistence>::from_bytes(bytes)
376 }
377
378 pub fn save_to_file(&self, path: &std::path::Path) -> std::io::Result<()> {
383 <Self as PostcardPersistence>::save_to_file(self, path)
384 }
385
386 pub fn load_from_file(path: &std::path::Path) -> std::io::Result<Self> {
391 <Self as PostcardPersistence>::load_from_file(path)
392 }
393}