chroma_types/
signed_rbm.rs

1use std::ops::{BitAnd, BitOr};
2
3use roaring::RoaringBitmap;
4
5/// This enum helps to delay the evaluation of set minus in metadata filtering:
6/// - `Include(rbm)` suggests the result contains the specified ids in `rbm`.
7///   For example, `<k>: {$eq: <v>}` will result in a `Include(<record ids with k=v>)`.
8/// - `Exclude(rbm)` suggests the result exludes the specified ids in `rbm`.
9///   For example, `<k>: {$ne: <v>}` will result in a `Exclude(<record ids with k=v>)`
10///
11/// Without this, we need to figure out the set of existing ids when we evaluate `$ne`, `$nin`, and `$not_contains`,
12/// but this is not always necessary and may lead to overheads. Let's consider the following where clause as an example:
13///
14/// `{$and: [{<k0>: {$gt: <v0>}}, {<k1>: {$ne: <v1>}}]}`
15///
16/// The naive way is to evaluate the `$gt` and `$ne` seperately and take the conjunction, but this requires
17/// us to know the full set of existing ids because it is needed to evaluate `$ne`.
18/// However, we can first evaluate `$gt` and then exclude the offset ids for records with metadata `k1=v1`.
19/// This behavior is captured in the `BitAnd::bitand` operator of `SignedRoaringBitmap`:
20/// - A bitand between `Include(...)` and `Exclude(...)` will always result in `Include(...)`
21/// - This process does not require the knowledge of the full domain
22/// - The domain is needed only when we have to convert an `Exclude(...)` to the actual result in the end.
23///
24/// In summary, this enum distinguishes the results that depends on the domain and those that do not:
25/// - `Include(...)` does not depend on the domain
26/// - `Exclude(...)` depends on the domain
27///    We only need to figure out the domain (i.e. the set of ids for existing records) when it is
28///    necessary to do so.
29#[derive(Clone, Debug, PartialEq)]
30pub enum SignedRoaringBitmap {
31    Include(RoaringBitmap),
32    Exclude(RoaringBitmap),
33}
34
35impl SignedRoaringBitmap {
36    pub fn empty() -> Self {
37        Self::Include(RoaringBitmap::new())
38    }
39
40    pub fn full() -> Self {
41        Self::Exclude(RoaringBitmap::new())
42    }
43
44    pub fn contains(&self, value: u32) -> bool {
45        use SignedRoaringBitmap::*;
46        match self {
47            Include(rbm) => rbm.contains(value),
48            Exclude(rbm) => !rbm.contains(value),
49        }
50    }
51
52    pub fn flip(self) -> Self {
53        use SignedRoaringBitmap::*;
54        match self {
55            Include(rbm) => Exclude(rbm),
56            Exclude(rbm) => Include(rbm),
57        }
58    }
59}
60
61impl BitAnd for SignedRoaringBitmap {
62    type Output = Self;
63
64    fn bitand(self, rhs: Self) -> Self {
65        use SignedRoaringBitmap::*;
66        match (self, rhs) {
67            (Include(lhs), Include(rhs)) => Include(lhs & rhs),
68            (Include(lhs), Exclude(rhs)) => Include(lhs - rhs),
69            (Exclude(lhs), Include(rhs)) => Include(rhs - lhs),
70            (Exclude(lhs), Exclude(rhs)) => Exclude(lhs | rhs),
71        }
72    }
73}
74
75impl BitOr for SignedRoaringBitmap {
76    type Output = Self;
77
78    fn bitor(self, rhs: Self) -> Self::Output {
79        use SignedRoaringBitmap::*;
80        match (self, rhs) {
81            (Include(lhs), Include(rhs)) => Include(lhs | rhs),
82            (Include(lhs), Exclude(rhs)) => Exclude(rhs - lhs),
83            (Exclude(lhs), Include(rhs)) => Exclude(lhs - rhs),
84            (Exclude(lhs), Exclude(rhs)) => Exclude(lhs & rhs),
85        }
86    }
87}