Property (equality) indexes
An equality index makes MATCH (n:Label {field: value}) a direct lookup
instead of a full scan of every node with that label. It is the right tool for
selective property filters — WHERE-style equality on an id, email, city,
status, or any scalar field — once a label holds more than a few thousand nodes.
Only scalar values are indexable (Int, Float, Str, Bool). List- and
map-valued properties are skipped: a list has no single equality key.
Declaring an index
Via a schema file (idempotent — re-applying is a no-op):
{ "indexes": [["Person", "city"], ["Company", "status"]] }
mushroomdb schema apply mydb.db schema.json
# → created index:Person.city
Or from the embedded API:
#![allow(unused)]
fn main() {
db.enable_index("Person", "city")?;
// db.disable_index("Person", "city")?;
// db.is_index_enabled("Person", "city");
}
Over HTTP, apply a schema through POST /query / the schema endpoint the same
way you declare rules or fulltext fields.
Semantics
- Maintained incrementally. Every insert,
SET, and delete updates the index; you never rebuild it by hand. - Rebuilt on open. The declaration is persisted in the WAL (and the snapshot baseline); the postings are rebuilt from live data when the store opens — so an index survives a snapshot and reopen with no format migration.
- Used automatically. The planner rewrites a single non-
idequality in a MATCH property map to an indexed lookup. If the field is not indexed the query still runs, just as a scan — declaring an index never changes results, only speed. - Which query shapes. The fast path covers:
- A single non-
idequality in the inline property map:MATCH (n:Person {city: 'austin'})orMATCH (n:Person {city: $c}). - A
WHERE-clause single equality on the scan variable:MATCH (n:Person) WHERE n.city = 'austin'orWHERE n.city = $c. The equality is folded into anIndexScanat plan time. - Compound equalities (two or more equalities on the same scan variable):
MATCH (n:Person) WHERE n.city = 'austin' AND n.age = 30or equivalently as an inline mapMATCH (n:Person {city: 'austin', age: 30}). The planner emits anIndexIntersectop; the executor resolves each field against the property index, two-pointer-intersects the resulting sorted node-ID lists, then post-filters any fields that have no declared index. If no fields are indexed the query falls back to a full label scan — results are always correct; only the speed benefit depends on indexes being declared. CompoundWHEREpredicates with non-equality residuals (AND n.score > 10) fold the equality terms and leave the rest as a trailing filter. - Eligibility rules: the fold applies when the scan node has no prior
Expandbinding it (conservative — cross-expand pushdown is not yet supported). Theidpseudo-property is always resolved via the identity map and is excluded from index intersection.
- A single non-
Relationship to the vector index
A property index answers equality; the vector index answers nearness. It is not
declared — it is built by a VectorSimilar rule over
its two sides. Its shape, the MUSHROOMDB_HNSW_PARAMS override, its memory per
indexed vector and the recall its gates assert are documented under
Vector index parameters.
From 0.6.6 both kinds of VectorSimilar rule take their candidates from it: an
approximate: true rule in one beam pass at k, an approximate: false rule by
widening the beam — doubling from ef_search (400 by default) up to a ceiling of
4,096 — and then re-scoring every candidate exactly, which is why its scores are
exact and its candidate set carries a committed recall floor of 0.98.
For an exact rule the index only ever narrows the candidates when a beam comes
back full with its worst hit below min, which is the one case that proves what
the beam left out. A beam that comes back short of its own width (layer 0 need
not be one connected component), or that reaches the ceiling with its worst
hit still at or above min, falls back to every vector on that side — so a dense
cluster above min costs a scan and never costs recall. The same path serves an
All([VectorSimilar, …]) rule, whose candidates are that set intersected with
the other conjuncts’ index lookups. MUSHROOMDB_VECTOR_SCAN=1 restores the
O(n²) full scan for a caller who needs every pair above min provably found.
Query-time find_similar is a different path from rule derivation: brute
find_similar is exact GEMM, HNSW is still the approximate path, and
exact=True (or a where= predicate) forces GEMM.
Relationship to fulltext
Use a property index for exact scalar equality (city = 'austin'). Use a
fulltext index for tokenized text search (BM25, stemming, phrase
and prefix queries) over free-text fields.