pub struct FilterEvaluator { /* private fields */ }Expand description
A validated Filter paired with the canonical evaluator.
Build one with FilterEvaluator::new; the filter is walked once at
construction to enforce MAX_FILTER_DEPTH and MAX_IN_VALUES. After
that, FilterEvaluator::evaluate is infallible and may be called per
row inside a search loop without revalidation.
§Examples
use iqdb_filter::FilterEvaluator;
use iqdb_types::{Filter, Metadata, Value};
let evaluator = FilterEvaluator::new(Filter::eq("year", Value::Int(2026)))?;
let meta: Metadata =
[("year".to_string(), Value::Int(2026))].into_iter().collect();
assert!(evaluator.evaluate(Some(&meta)));
assert!(!evaluator.evaluate(None));Implementations§
Source§impl FilterEvaluator
impl FilterEvaluator
Sourcepub fn new(filter: Filter) -> Result<Self>
pub fn new(filter: Filter) -> Result<Self>
Validates filter and wraps it for evaluation.
The validation walk is iterative — an explicit work-list, not
recursion — so new itself cannot stack-overflow on a pathological
input. It returns IqdbError::InvalidFilter when:
- the filter’s nested-boolean depth exceeds
MAX_FILTER_DEPTH; or - any
Filter::Innode carries more thanMAX_IN_VALUESvalues.
§Errors
Returns IqdbError::InvalidFilter for filters that violate either
cap. The variant carries no extra context — callers that need to
distinguish “too deep” from “In too wide” can re-walk the filter
themselves or pre-validate against the public consts.
§Examples
use iqdb_filter::FilterEvaluator;
use iqdb_types::{Filter, IqdbError, Value};
// Accepted: a small, well-formed filter.
let ok = FilterEvaluator::new(Filter::eq("k", Value::Int(1)));
assert!(ok.is_ok());
// Rejected: an oversized `In`.
let huge = vec![Value::Int(0); iqdb_filter::MAX_IN_VALUES + 1];
let err = FilterEvaluator::new(Filter::is_in("tag", huge)).unwrap_err();
assert_eq!(err, IqdbError::InvalidFilter);Sourcepub fn evaluate(&self, metadata: Option<&Metadata>) -> bool
pub fn evaluate(&self, metadata: Option<&Metadata>) -> bool
Evaluates the validated filter against metadata.
None means the record has no metadata at all — distinct from an
empty Metadata only at the API boundary; semantically every leaf
over None and every leaf over an empty Metadata evaluates to
false (and Not over either evaluates to true).
This call is infallible: validation happened in
FilterEvaluator::new, so the recursive descent is bounded by
MAX_FILTER_DEPTH and cannot stack-overflow.
§Examples
use iqdb_filter::FilterEvaluator;
use iqdb_types::{Filter, Metadata, Value};
let evaluator =
FilterEvaluator::new(Filter::not(Filter::eq("author", Value::String("ada".into()))))?;
// No metadata → leaf is false → Not flips it to true. This is the
// documented "records without this field" idiom.
assert!(evaluator.evaluate(None));
let meta: Metadata = [(
"author".to_string(),
Value::String("ada".into()),
)]
.into_iter()
.collect();
assert!(!evaluator.evaluate(Some(&meta)));Sourcepub fn prefilter<'a, K, I>(
&'a self,
candidates: I,
) -> impl Iterator<Item = K> + 'a
pub fn prefilter<'a, K, I>( &'a self, candidates: I, ) -> impl Iterator<Item = K> + 'a
Pre-filter a stream of candidates: yield the key of each candidate whose metadata matches, before any distance is computed.
This is the FilterStrategy::PreFilter
shape — reduce the candidate set first, then score only the survivors.
It is the pattern an exact index uses to skip the distance computation
for rows the predicate already rejects.
The adapter is lazy and allocation-free: it borrows each candidate’s
metadata and forwards the key untouched. key is whatever a caller uses
to identify a row — a storage index, a iqdb_types::VectorId, a tuple.
§Examples
use iqdb_filter::FilterEvaluator;
use iqdb_types::{Filter, Metadata, Value};
let evaluator = FilterEvaluator::new(Filter::gt("year", Value::Int(2000)))?;
let m2026: Metadata = [("year".to_string(), Value::Int(2026))].into_iter().collect();
let m1999: Metadata = [("year".to_string(), Value::Int(1999))].into_iter().collect();
let rows = [(0_usize, Some(&m2026)), (1, Some(&m1999)), (2, None)];
let kept: Vec<usize> = evaluator.prefilter(rows).collect();
assert_eq!(kept, [0]); // only the 2026 row survivesSourcepub fn postfilter<'a, H, I>(&'a self, scored: I) -> impl Iterator<Item = H> + 'a
pub fn postfilter<'a, H, I>(&'a self, scored: I) -> impl Iterator<Item = H> + 'a
Post-filter a stream of already-scored results: yield each hit whose metadata matches, after the distance scan has ranked candidates.
This is the FilterStrategy::PostFilter
shape — score everything, then drop the hits the predicate rejects. It
shares the per-row test with prefilter; the
difference is purely where in the pipeline it runs. Because it is lazy,
a caller refilling a top-k result set can chain .take(k) and stop as
soon as k survivors are found.
§Examples
use iqdb_filter::FilterEvaluator;
use iqdb_types::{Filter, Metadata, Value};
let evaluator = FilterEvaluator::new(Filter::eq("lang", Value::String("rust".into())))?;
let rust: Metadata = [("lang".to_string(), Value::String("rust".into()))]
.into_iter()
.collect();
let go: Metadata = [("lang".to_string(), Value::String("go".into()))]
.into_iter()
.collect();
// Hits arrive sorted by distance; keep the first matching one.
let scored = [("hit-a", Some(&go)), ("hit-b", Some(&rust))];
let best: Vec<&str> = evaluator.postfilter(scored).take(1).collect();
assert_eq!(best, ["hit-b"]);Sourcepub fn filter(&self) -> &Filter
pub fn filter(&self) -> &Filter
Borrows the inner validated filter.
Useful for adapters that want to introspect the predicate (for logging, pushdown, or statistics) without rebuilding it.
§Examples
use iqdb_filter::FilterEvaluator;
use iqdb_types::{Filter, Value};
let evaluator = FilterEvaluator::new(Filter::eq("k", Value::Int(1)))?;
assert!(matches!(evaluator.filter(), Filter::Eq { .. }));Trait Implementations§
Source§impl Clone for FilterEvaluator
impl Clone for FilterEvaluator
Source§fn clone(&self) -> FilterEvaluator
fn clone(&self) -> FilterEvaluator
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more