1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
//! # iqdb-filter
//!
//! Canonical [`iqdb_types::Filter`] evaluator for the HiveDB **iqdb**
//! vector-database spine. One place that decides what `Filter` means; every
//! index that supports metadata filtering delegates to it.
//!
//! ## Why this lives outside the index crates
//!
//! Filtering used to be inlined in `iqdb-flat`. The moment a second index
//! (HNSW, IVF) starts honouring filters, two copies of the semantics would
//! drift — the `Neq(absent)` / `Not(Eq(absent))` rule is exactly the kind of
//! subtlety that splits between implementations and produces query-result
//! bugs nobody can attribute. Extracting the evaluator pins one set of
//! semantics across every consumer.
//!
//! ## Public surface
//!
//! - [`FilterEvaluator`] — `new(filter) -> Result<Self, IqdbError>` validates
//! the filter once (depth, `In` cardinality); `evaluate(metadata) -> bool`
//! is infallible on a validated filter. [`FilterEvaluator::prefilter`] and
//! [`FilterEvaluator::postfilter`] apply it as lazy, allocation-free scan
//! adapters over a stream of `(key, metadata)` pairs.
//! - [`MetadataIndex`] — an opt-in, per-field inverted index that resolves a
//! selective `Eq` / `In` predicate to a candidate key set (a superset of the
//! true matches), and backs a sharper, count-based selectivity estimate.
//! - [`estimate_selectivity`] — a best-effort, structural estimate of the
//! fraction of records a validated filter passes, in `[0.0, 1.0]`; the
//! index-backed counterpart is [`MetadataIndex::estimate_selectivity`].
//! - [`choose_strategy`] / [`StrategySelector`] — pick a concrete
//! [`FilterStrategy`] from the selectivity estimate. The free function uses
//! the [`DEFAULT_PREFILTER_THRESHOLD`]; the selector is the Tier-2 builder
//! for tuning it.
//! - [`FilterStrategy`] — vocabulary for how an index applies a filter
//! relative to its distance scan. The selector resolves `Auto` down to
//! `PreFilter` / `PostFilter`; `InFilter` waits on a graph-index consumer.
//! - [`MAX_FILTER_DEPTH`] / [`MAX_IN_VALUES`] — documented validation caps,
//! `pub const` so callers can quote them in error messages or higher-level
//! validation.
//!
//! ## Null and absent-field semantics
//!
//! The evaluator implements the **closed-world** rule pinned by
//! [`iqdb_types::Filter`]: every leaf comparison (`Eq`, `Neq`, `Lt`, `Lte`,
//! `Gt`, `Gte`, `In`) over a field absent from the record's metadata
//! evaluates to `false`. Type mismatches between a stored value and a literal
//! also evaluate to `false`. `Value::Float(NaN)` under any ordered comparison
//! evaluates to `false` (IEEE-754 unordered). `Not` over a `false` leaf is
//! `true`, which is the idiom for "records without this field, or with a
//! non-matching value."
//!
//! `Neq(absent) → false` and `Not(Eq(absent)) → true` are therefore **not**
//! interchangeable. The pair is pinned by the conformance tests in
//! `tests/conformance.rs`.
//!
//! ## DoS hardening
//!
//! Construction is the validation gate. The walk is iterative (an explicit
//! stack, not recursion), so `new` cannot itself stack-overflow on
//! adversarial input. After construction every filter is bounded by
//! [`MAX_FILTER_DEPTH`], so the recursive [`FilterEvaluator::evaluate`] hot
//! path runs with a bounded call stack.
//!
//! ## Example
//!
//! ```
//! use iqdb_filter::FilterEvaluator;
//! use iqdb_types::{Filter, Metadata, Value};
//!
//! # fn main() -> iqdb_types::Result<()> {
//! let filter = Filter::and(vec![
//! Filter::eq("published", Value::Bool(true)),
//! Filter::gt("year", Value::Int(2000)),
//! ]);
//! let evaluator = FilterEvaluator::new(filter)?;
//!
//! let meta: Metadata = [
//! ("published".to_string(), Value::Bool(true)),
//! ("year".to_string(), Value::Int(2026)),
//! ]
//! .into_iter()
//! .collect();
//!
//! assert!(evaluator.evaluate(Some(&meta)));
//! assert!(!evaluator.evaluate(None));
//! # Ok(())
//! # }
//! ```
pub use crate;
pub use crateMetadataIndex;
pub use crateestimate_selectivity;
pub use crate;
/// The version of this crate, taken from `Cargo.toml` at compile time.
///
/// Exposed so a consumer can report the exact `iqdb-filter` build it links
/// against — useful in diagnostics and version-skew checks across the iqdb
/// crate family.
///
/// # Examples
///
/// ```
/// // Carries a `major.minor.patch` SemVer core.
/// let version = iqdb_filter::VERSION;
/// assert_eq!(version.split('.').count(), 3);
/// assert!(version.split('.').all(|part| !part.is_empty()));
/// ```
pub const VERSION: &str = env!;