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
//! # 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.
//! - [`estimate_selectivity`] — a best-effort, structural estimate of the
//! fraction of records a validated filter passes, in `[0.0, 1.0]`.
//! - [`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 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!;