- 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.4"
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.4.0 — the evaluator, strategy selection, and the inverted
index. On top of the canonical FilterEvaluator (validate-on-construction,
infallible allocation-free per-row evaluate), the crate ships the prefilter /
postfilter scan helpers, the estimate_selectivity + selector
(choose_strategy / StrategySelector), and an opt-in per-field
MetadataIndex that resolves selective Eq / In predicates to a candidate set
and backs a count-based selectivity estimate. Semantics, the superset contract,
and the new surface are pinned by unit, integration, and property tests, verified
across the CI matrix (Linux, macOS, Windows) on stable and the 1.87 MSRV. Only
InFilter pushdown into graph traversal remains deferred, until the first
approximate-index consumer lands — 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.