use super::query_options::QuantizationKind;
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum FilterRoute {
BruteForce,
SieveSubindex {
signature: String,
},
NavixGlobal,
Compass,
}
pub struct FilterRouteInputs<'a> {
pub global_selectivity: f32,
pub brute_force_threshold: f32,
pub matched_sieve_signature: Option<&'a str>,
pub compass_enabled: bool,
pub numeric_predicate_count: u8,
}
pub fn route_filter(inputs: &FilterRouteInputs<'_>) -> FilterRoute {
if inputs.global_selectivity < inputs.brute_force_threshold {
return FilterRoute::BruteForce;
}
if let Some(sig) = inputs.matched_sieve_signature {
return FilterRoute::SieveSubindex {
signature: sig.to_owned(),
};
}
if inputs.compass_enabled && inputs.numeric_predicate_count >= 3 {
return FilterRoute::Compass;
}
FilterRoute::NavixGlobal
}
pub fn pick_quantization(candidate_set_size: usize, target_recall: f32) -> QuantizationKind {
if candidate_set_size < 1_000 {
QuantizationKind::None
} else if candidate_set_size < 100_000 {
if target_recall >= 0.97 {
QuantizationKind::Sq8
} else {
QuantizationKind::Pq
}
} else if target_recall >= 0.95 {
QuantizationKind::Bbq
} else {
QuantizationKind::RaBitQ
}
}
#[cfg(test)]
mod tests {
use super::*;
fn default_inputs<'a>() -> FilterRouteInputs<'a> {
FilterRouteInputs {
global_selectivity: 0.5,
brute_force_threshold: 0.001,
matched_sieve_signature: None,
compass_enabled: false,
numeric_predicate_count: 0,
}
}
#[test]
fn brute_force_fires_below_threshold() {
let inputs = FilterRouteInputs {
global_selectivity: 0.0005,
..default_inputs()
};
assert_eq!(route_filter(&inputs), FilterRoute::BruteForce);
}
#[test]
fn brute_force_fires_exactly_at_threshold_minus_epsilon() {
let threshold = 0.001_f32;
let inputs = FilterRouteInputs {
global_selectivity: threshold - f32::EPSILON,
brute_force_threshold: threshold,
..default_inputs()
};
assert_eq!(route_filter(&inputs), FilterRoute::BruteForce);
}
#[test]
fn sieve_subindex_fires_when_signature_matched() {
let inputs = FilterRouteInputs {
global_selectivity: 0.5,
matched_sieve_signature: Some("tenant_id=42"),
..default_inputs()
};
assert_eq!(
route_filter(&inputs),
FilterRoute::SieveSubindex {
signature: "tenant_id=42".to_owned()
}
);
}
#[test]
fn sieve_takes_priority_over_compass() {
let inputs = FilterRouteInputs {
global_selectivity: 0.5,
matched_sieve_signature: Some("lang=en"),
compass_enabled: true,
numeric_predicate_count: 5,
..default_inputs()
};
assert_eq!(
route_filter(&inputs),
FilterRoute::SieveSubindex {
signature: "lang=en".to_owned()
}
);
}
#[test]
fn compass_fires_when_enabled_and_enough_predicates() {
let inputs = FilterRouteInputs {
global_selectivity: 0.5,
matched_sieve_signature: None,
compass_enabled: true,
numeric_predicate_count: 3,
..default_inputs()
};
assert_eq!(route_filter(&inputs), FilterRoute::Compass);
}
#[test]
fn compass_does_not_fire_with_fewer_than_3_predicates() {
let inputs = FilterRouteInputs {
global_selectivity: 0.5,
matched_sieve_signature: None,
compass_enabled: true,
numeric_predicate_count: 2,
..default_inputs()
};
assert_eq!(route_filter(&inputs), FilterRoute::NavixGlobal);
}
#[test]
fn compass_does_not_fire_when_disabled() {
let inputs = FilterRouteInputs {
global_selectivity: 0.5,
matched_sieve_signature: None,
compass_enabled: false,
numeric_predicate_count: 10,
..default_inputs()
};
assert_eq!(route_filter(&inputs), FilterRoute::NavixGlobal);
}
#[test]
fn navix_global_is_default_fallback() {
let inputs = default_inputs();
assert_eq!(route_filter(&inputs), FilterRoute::NavixGlobal);
}
#[test]
fn small_set_returns_none() {
assert_eq!(pick_quantization(0, 0.99), QuantizationKind::None);
assert_eq!(pick_quantization(999, 0.99), QuantizationKind::None);
}
#[test]
fn medium_set_high_recall_returns_sq8() {
assert_eq!(pick_quantization(1_000, 0.97), QuantizationKind::Sq8);
assert_eq!(pick_quantization(50_000, 0.99), QuantizationKind::Sq8);
assert_eq!(pick_quantization(99_999, 1.0), QuantizationKind::Sq8);
}
#[test]
fn medium_set_lower_recall_returns_pq() {
assert_eq!(pick_quantization(1_000, 0.96), QuantizationKind::Pq);
assert_eq!(pick_quantization(50_000, 0.90), QuantizationKind::Pq);
}
#[test]
fn large_set_high_recall_returns_bbq() {
assert_eq!(pick_quantization(100_000, 0.95), QuantizationKind::Bbq);
assert_eq!(pick_quantization(1_000_000, 0.99), QuantizationKind::Bbq);
}
#[test]
fn large_set_lower_recall_returns_rabitq() {
assert_eq!(pick_quantization(100_000, 0.94), QuantizationKind::RaBitQ);
assert_eq!(
pick_quantization(10_000_000, 0.80),
QuantizationKind::RaBitQ
);
}
#[test]
fn boundary_1000_uses_medium_path() {
assert_ne!(pick_quantization(1_000, 0.99), QuantizationKind::None);
}
#[test]
fn boundary_100000_uses_large_path() {
let result = pick_quantization(100_000, 0.95);
assert!(
result == QuantizationKind::Bbq || result == QuantizationKind::RaBitQ,
"expected BBQ or RaBitQ at boundary, got {result:?}"
);
}
}