use roaring::RoaringBitmap;
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum PrefilterSource {
Array,
GraphHop,
Bitemporal,
SpatialBbox,
Fts,
}
pub struct PrefilterInput {
pub source: PrefilterSource,
pub matched_ids: RoaringBitmap,
}
pub fn compose_prefilters(inputs: &[PrefilterInput], union: bool) -> RoaringBitmap {
if inputs.is_empty() {
return RoaringBitmap::new();
}
if union {
inputs
.iter()
.fold(RoaringBitmap::new(), |acc, inp| &acc | &inp.matched_ids)
} else {
let mut result = inputs[0].matched_ids.clone();
for inp in &inputs[1..] {
result &= &inp.matched_ids;
}
result
}
}
pub fn semimask_from_prefilter(input: &PrefilterInput) -> RoaringBitmap {
input.matched_ids.clone()
}
#[cfg(test)]
mod tests {
use super::*;
fn make_input(source: PrefilterSource, ids: impl IntoIterator<Item = u32>) -> PrefilterInput {
let mut bitmap = RoaringBitmap::new();
for id in ids {
bitmap.insert(id);
}
PrefilterInput {
source,
matched_ids: bitmap,
}
}
#[test]
fn and_composition_is_intersection() {
let a = make_input(PrefilterSource::Fts, [1, 2, 3, 4]);
let b = make_input(PrefilterSource::SpatialBbox, [2, 4, 6]);
let result = compose_prefilters(&[a, b], false);
let expected: RoaringBitmap = [2u32, 4].iter().copied().collect();
assert_eq!(result, expected);
}
#[test]
fn or_composition_is_union() {
let a = make_input(PrefilterSource::Fts, [1, 2, 3]);
let b = make_input(PrefilterSource::GraphHop, [3, 4, 5]);
let result = compose_prefilters(&[a, b], true);
let expected: RoaringBitmap = [1u32, 2, 3, 4, 5].iter().copied().collect();
assert_eq!(result, expected);
}
#[test]
fn empty_inputs_returns_empty_bitmap() {
let result = compose_prefilters(&[], false);
assert!(result.is_empty());
let result_union = compose_prefilters(&[], true);
assert!(result_union.is_empty());
}
#[test]
fn semimask_from_prefilter_returns_same_bitmap() {
let ids = [10u32, 20, 30];
let input = make_input(PrefilterSource::Array, ids);
let semimask = semimask_from_prefilter(&input);
assert_eq!(semimask, input.matched_ids);
}
#[test]
fn single_input_and_returns_same_bitmap() {
let a = make_input(PrefilterSource::Bitemporal, [7, 14, 21]);
let result = compose_prefilters(&[a], false);
let expected: RoaringBitmap = [7u32, 14, 21].iter().copied().collect();
assert_eq!(result, expected);
}
#[test]
fn disjoint_and_composition_is_empty() {
let a = make_input(PrefilterSource::Fts, [1, 2]);
let b = make_input(PrefilterSource::SpatialBbox, [3, 4]);
let result = compose_prefilters(&[a, b], false);
assert!(result.is_empty());
}
}