Skip to content
You are viewing the corvid 0.4.1 release snapshot — frozen at the 0.4.1 engine release.Current documentation

Filters

Filters are pure predicates: Predicate trees built with the field(path) fluent API and evaluated against documents. They compose into queries, delete_where, page_where, and the C ABI’s predicate family.

use corvid::{field, Value};
// Comparisons
field("category").eq(Value::Text("blog".into()));
field("category").ne(Value::Text("draft".into()));
field("score").lt(Value::Int(5));
field("score").le(Value::Int(5));
field("score").gt(Value::Int(5));
field("score").ge(Value::Int(5));
// Membership and ranges
field("tag").is_in([Value::Text("a".into()), Value::Text("b".into())]);
field("score").between(Value::Int(1), Value::Int(10)); // inclusive both ends
// Text
field("title").starts_with("intro");
field("body").contains("rust");
// Presence
field("email").exists();
// Geo (haversine kilometres)
field("loc").within_km(51.5, -0.13, 25.0);
# let _ = ();
# use corvid::{field, Value};
let p = field("category").eq(Value::Text("blog".into()))
.and(field("score").ge(Value::Int(3)))
.or(field("pinned").eq(Value::Bool(true)));
let p = !field("draft").eq(Value::Bool(true)); // Not
# let _ = p;

and/or/not build the tree; De Morgan identities hold and nesting is arbitrary. Multiple .filter(...) calls on one query intersect like and.

  • Missing path ⇒ false for every predicate except exists() — including ne: “not equal to X” never matches a document that lacks the field (pinned by conformance: Ne on a MISSING path is FALSE). Use exists() composed with or when you want missing values to pass.
  • Ordered comparisons across non-comparable kinds ⇒ false. Numbers compare numerically across Int/Float (exact to 2^53); text compares lexicographically by UTF-8 bytes; bools, bytes, containers and vectors do not participate in </>/<=/>=.
  • eq matches per value kind, with numeric interop: field("n").eq(Value::Float(2.0)) matches Int(2).
  • ne is the complement of eq evaluated over a present value — it is true only for a document that carries the field with a non-matching value (including any value against a NaN filter); the missing-path rule above applies first, so a document without the field still yields false.
  • NaN matches nothing, not even NaN — a NaN filter value selects the empty set under eq and ordered ops, and every present value under ne (documents missing the field still miss — see above). (This is the predicate rule; storage equality — CAS, unique constraints — treats NaN as equal to NaN. See equality semantics.)
  • is_in is an OR over eq against each element; an empty list matches nothing. between(lo, hi) is lo <= v && v <= hi, inclusive; a degenerate lo > hi range simply matches nothing.
  • starts_with / contains are byte-level text predicates — false on non-text values and missing paths, case-sensitive.
  • within_km(lat, lon, km) resolves the path as a geo point ([lat, lon] array or {lat, lon} map), false on non-points.

Filters never require an index, but each predicate family has a sub-linear path when one exists:

PredicateServiced by
eq / ne* / lt / le / gt / gescalar index
eq prefix + trailing range across fieldscompound index
is_inscalar index (union of windows, capped)
betweenscalar index (range window)
starts_withscalar index (text prefix scan)
within_kmgeo index
top-level or of index-serviceable disjunctsindex union

*ne is not serviced (an anti-scan is not sub-linear) — it verifies on the scan path.

The builder probes every serviceable index (each capped) and drives the query on the smallest candidate set, then verifies every candidate against the exact predicate — so results are identical with or without indexes, and unselective predicates fall back to the bounded streaming scan. explain() reports which happened (IndexedWindow vs Scan).

Next: the query builder.