- Canonical evaluator — one implementation of
Filtersemantics, shared by every metadata-aware index so query results never diverge - Validate once, evaluate many —
FilterEvaluator::newchecks the filter (depth,Incardinality) a single time;evaluateis then infallible and runs per-row inside the search loop - Closed-world semantics — a leaf over an absent field is
false, type mismatches arefalse,NaNorderings arefalse, andNotof afalseleaf istrue(the "records without this field" idiom) - DoS-hardened — iterative validation that can't stack-overflow, with bounded depth and
Inwidth; the library never panics on adversarial input - Scan helpers —
prefilter/postfilterapply the evaluator as lazy, allocation-free iterator adapters over a stream of(key, metadata)pairs - Strategy selection — a selectivity estimate drives an automatic
PreFilter/PostFilterchoice, with a tunable threshold - Inverted index — an opt-in, per-field
MetadataIndexresolves selectiveEq/Inpredicates to a candidate set (a superset of true matches) and backs a sharper, count-based selectivity estimate - First-party only — depends solely on
iqdb-types, so it is unblocked today
Installation
[]
= "0.5"
Quick start
Build an evaluator once, then test it against each record's metadata:
use FilterEvaluator;
use ;
// published == true AND year > 2000
let filter = and;
let evaluator = new.expect;
let meta: Metadata =
.into_iter
.collect;
assert!;
assert!; // no metadata -> every leaf is false
The Not / absent-field idiom selects records that lack a field, or carry it with a non-matching value:
use FilterEvaluator;
use ;
// "records that are not authored by ada" — including records with no author.
let evaluator =
new
.expect;
assert!;
Validation rejects pathological filters up front — bounded by the public caps:
use ;
use ;
// An `In` set wider than the cap is refused before it can slow a query.
let huge = vec!;
let err = new.unwrap_err;
assert_eq!;
Apply a strategy with the scan helpers, or let the selectivity estimate pick one:
use ;
use ;
let evaluator = new
.expect;
// `prefilter` keeps the keys of matching candidates, lazily, before scoring.
let rust: Metadata =
.into_iter
.collect;
let go: Metadata =
.into_iter
.collect;
let rows = ;
let kept: = evaluator.prefilter.collect;
assert_eq!;
// An equality predicate is narrow, so the selector recommends pre-filtering.
assert_eq!;
For repeated queries, build an opt-in MetadataIndex so a selective predicate resolves to a candidate set instead of scanning every row:
use ;
use ;
let rows = ;
// Index only the `lang` field.
let index = build;
let evaluator = new
.expect;
// `candidates` returns a superset of true matches; confirm with `evaluate`.
let mut hits: = match index.candidates ;
hits.sort_unstable;
assert_eq!;
Errors
FilterEvaluator::new returns iqdb_types::Result; the only failure is
IqdbError::InvalidFilter, returned when a filter exceeds MAX_FILTER_DEPTH
nesting or carries an In node wider than MAX_IN_VALUES. After a filter is
validated, evaluate is infallible and never panics — including on records
with no metadata, type mismatches, and NaN values.
Status
v0.5.0 — API frozen, hardening complete. The full surface
— the canonical FilterEvaluator (validate-on-construction, infallible
allocation-free per-row evaluate), the prefilter / postfilter scan helpers,
estimate_selectivity + the selector (choose_strategy / StrategySelector),
and the opt-in per-field MetadataIndex — is now committed: only additive
changes until 2.0. It is exercised by unit, integration, and property tests, a
consumer-simulation suite that builds a filtered top-k searcher on the public
API alone, and fuzz targets that drive the no-panic and superset contracts over
unbounded input; all verified across the CI matrix (Linux, macOS, Windows) on
stable and the 1.87 MSRV. Only InFilter pushdown into graph traversal remains
to build — it is additive and gated on the first approximate-index consumer (see
the ROADMAP). The full surface is
documented in docs/API.md.
Where It Fits
iqdb-filter sits just above the types crate and is consumed by the index layer:
iqdb-types— theFilter,Metadata, andValuetypes it evaluatesiqdb-flat/iqdb-hnsw/iqdb-ivf— delegate here for metadata filteringiqdb— exposes filtered search to users
Its only first-party dependency is iqdb-types, so it is unblocked today.
Standards
Built to the iQDB Rust standard. See REPS.md (Rust Efficiency & Performance Standards) and dev/DIRECTIVES.md for the engineering law and the definition of done. Before a PR: cargo fmt --all, cargo clippy --all-targets --all-features -- -D warnings, and cargo test --all-features must be clean.