Skip to main content

uni_query/query/df_graph/
bitmap.rs

1use bitvec::vec::BitVec;
2use fxhash::FxHashSet;
3use uni_common::core::id::{Eid, Vid};
4
5/// Density threshold: if set bits exceed this fraction of total range, use DenseBitVec.
6const DENSITY_THRESHOLD: f64 = 0.125; // 12.5%
7
8/// Build an adaptive filter (dense bitvec or sparse hashset) from raw u64 IDs.
9///
10/// Uses a density heuristic: if more than 12.5% of the range is populated,
11/// a `BitVec` is used for O(1) lookups; otherwise a `FxHashSet` is used.
12///
13/// Returns `(Option<BitVec>, Option<FxHashSet<u64>>)` — exactly one is `Some`.
14fn build_filter(ids: Vec<u64>, max_hint: usize) -> (Option<BitVec>, Option<FxHashSet<u64>>) {
15    if ids.is_empty() {
16        return (None, Some(FxHashSet::default()));
17    }
18
19    let range = max_hint.max(ids.iter().copied().max().unwrap_or(0) as usize + 1);
20    let density = ids.len() as f64 / range.max(1) as f64;
21
22    if density > DENSITY_THRESHOLD {
23        let mut bv = BitVec::repeat(false, range);
24        for &id in &ids {
25            let idx = id as usize;
26            if idx < bv.len() {
27                bv.set(idx, true);
28            }
29        }
30        (Some(bv), None)
31    } else {
32        (None, Some(ids.into_iter().collect()))
33    }
34}
35
36/// Check if a raw u64 ID passes a dense bitvec filter.
37fn bitvec_contains(bv: &BitVec, raw: u64) -> bool {
38    let idx = raw as usize;
39    idx < bv.len() && bv[idx]
40}
41
42/// Macro to define an ID filter enum with identical structure but typed `contains` method.
43macro_rules! define_id_filter {
44    (
45        $(#[$meta:meta])*
46        $filter_name:ident, $id_type:ty, $from_fn:ident
47    ) => {
48        $(#[$meta])*
49        #[derive(Debug)]
50        pub enum $filter_name {
51            /// No predicate — all IDs are allowed.
52            AllAllowed,
53            /// Dense bitvec indexed by raw ID.
54            DenseBitVec(BitVec),
55            /// Sparse hash set for low-cardinality results.
56            Sparse(FxHashSet<u64>),
57        }
58
59        impl $filter_name {
60            /// Check if an ID passes the filter.
61            pub fn contains(&self, id: $id_type) -> bool {
62                match self {
63                    Self::AllAllowed => true,
64                    Self::DenseBitVec(bv) => bitvec_contains(bv, id.as_u64()),
65                    Self::Sparse(set) => set.contains(&id.as_u64()),
66                }
67            }
68
69            /// Build a filter from a list of raw u64 IDs.
70            ///
71            /// Uses a density heuristic to choose between DenseBitVec (>12.5% density)
72            /// and Sparse HashSet.
73            pub fn $from_fn(ids: Vec<u64>, max_hint: usize) -> Self {
74                let (bv, set) = build_filter(ids, max_hint);
75                if let Some(bv) = bv {
76                    Self::DenseBitVec(bv)
77                } else {
78                    Self::Sparse(set.unwrap_or_default())
79                }
80            }
81        }
82    };
83}
84
85define_id_filter!(
86    /// Bitmap filter for edge IDs — preselects which edges pass a property predicate.
87    EidFilter, Eid, from_eids
88);
89
90define_id_filter!(
91    /// Bitmap filter for vertex IDs — preselects which target vertices pass a property predicate.
92    VidFilter, Vid, from_vids
93);
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn test_eid_all_allowed() {
101        let f = EidFilter::AllAllowed;
102        assert!(f.contains(Eid::new(0)));
103        assert!(f.contains(Eid::new(999)));
104        assert!(f.contains(Eid::new(u64::MAX - 1)));
105    }
106
107    #[test]
108    fn test_eid_dense_bitvec_contains() {
109        let f = EidFilter::from_eids(vec![1, 3, 5, 7], 8);
110        // DenseBitVec chosen: 4/8 = 50% > 12.5%
111        assert!(matches!(f, EidFilter::DenseBitVec(_)));
112        assert!(f.contains(Eid::new(1)));
113        assert!(f.contains(Eid::new(3)));
114        assert!(f.contains(Eid::new(5)));
115        assert!(f.contains(Eid::new(7)));
116        assert!(!f.contains(Eid::new(0)));
117        assert!(!f.contains(Eid::new(2)));
118        assert!(!f.contains(Eid::new(4)));
119        assert!(!f.contains(Eid::new(6)));
120    }
121
122    #[test]
123    fn test_eid_dense_bitvec_empty() {
124        let f = EidFilter::from_eids(vec![], 100);
125        // Empty set → Sparse
126        assert!(matches!(f, EidFilter::Sparse(_)));
127        assert!(!f.contains(Eid::new(0)));
128        assert!(!f.contains(Eid::new(50)));
129    }
130
131    #[test]
132    fn test_eid_hashset_contains() {
133        // Sparse set: 3 out of 1000 = 0.3% < 12.5%
134        let f = EidFilter::from_eids(vec![100, 500, 999], 1000);
135        assert!(matches!(f, EidFilter::Sparse(_)));
136        assert!(f.contains(Eid::new(100)));
137        assert!(f.contains(Eid::new(500)));
138        assert!(f.contains(Eid::new(999)));
139        assert!(!f.contains(Eid::new(0)));
140        assert!(!f.contains(Eid::new(101)));
141        assert!(!f.contains(Eid::new(998)));
142    }
143
144    #[test]
145    fn test_eid_from_eids_chooses_dense() {
146        // 20 out of 100 = 20% > 12.5% → DenseBitVec
147        let eids: Vec<u64> = (0..20).collect();
148        let f = EidFilter::from_eids(eids, 100);
149        assert!(matches!(f, EidFilter::DenseBitVec(_)));
150    }
151
152    #[test]
153    fn test_eid_from_eids_chooses_hashset() {
154        // 5 out of 10000 = 0.05% < 12.5% → Sparse
155        let f = EidFilter::from_eids(vec![10, 100, 1000, 5000, 9999], 10000);
156        assert!(matches!(f, EidFilter::Sparse(_)));
157    }
158
159    #[test]
160    fn test_eid_out_of_range_dense() {
161        // DenseBitVec with range 10, querying beyond range returns false
162        let f = EidFilter::from_eids(vec![1, 2, 3, 4, 5, 6, 7, 8], 10);
163        assert!(matches!(f, EidFilter::DenseBitVec(_)));
164        assert!(!f.contains(Eid::new(100)));
165    }
166
167    #[test]
168    fn test_vid_filter_basic() {
169        // AllAllowed
170        let f = VidFilter::AllAllowed;
171        assert!(f.contains(Vid::new(0)));
172        assert!(f.contains(Vid::new(999)));
173
174        // Dense
175        let f = VidFilter::from_vids(vec![1, 2, 3, 4], 8);
176        assert!(matches!(f, VidFilter::DenseBitVec(_)));
177        assert!(f.contains(Vid::new(1)));
178        assert!(f.contains(Vid::new(4)));
179        assert!(!f.contains(Vid::new(0)));
180        assert!(!f.contains(Vid::new(5)));
181
182        // Sparse
183        let f = VidFilter::from_vids(vec![100, 9999], 10000);
184        assert!(matches!(f, VidFilter::Sparse(_)));
185        assert!(f.contains(Vid::new(100)));
186        assert!(f.contains(Vid::new(9999)));
187        assert!(!f.contains(Vid::new(0)));
188    }
189}