Files
MultiforaDB/tests/spec/indexes
A.Shakhmatov 35c1e6537c plan/spec: M3's row is closed
All five steps of the index-types review landed:
the refusal, the recorded corpus, partial indexes, hashed indexes, and
the implication test. `tests/spec/indexes/` is 48/48 and all four
recorded corpora are green -- positional 51, operators 125, indexes 48,
aggregate 70.

The review gets a fourth correction, and it is about method rather than a
fact. §4 asserted the implication rule without saying how to test it; the
answer needed no comparison of its own, because every operator the
partial-filter grammar admits is existential, so running the real matcher
against a stand-in document settles five operators at once.

What the review missed entirely was the gates. A corpus recorded from
mongod can see an implication test that says yes too readily -- documents
go missing -- but not one that never says yes, because no client can
observe which index a read used and this server has no `explain`. Both
numbers are recorded in PLAN §6 and the corpus README so the next change
to this code knows which gate is load-bearing for which direction.

Nothing else in M3 remains. PLAN §6 still carries the items this row
turned up and deliberately left: comparison operators are not
type-bracketed, a dotted path through an empty array reads as absent, a
key-pattern direction may be any non-zero number, three positional
divergences, three operator omissions, and the four older engine items.
2026-08-11 00:36:05 +03:00
..
2026-08-11 00:36:05 +03:00

The index corpus

M3's last row: partial and hashed indexes. docs/M3_INDEX_TYPES_DESIGN_REVIEW.md measured both against mongod 8.3.7 and found they were not the same kind of gap — hashed was honestly refused, partialFilterExpression was accepted and ignored, and a unique partial index therefore refused inserts mongod accepts.

It also found that no test in this repository covered that row, in any suite. The pinned corpus is crud and aggregate; tests/e2e/e2e5.js and e2e6.js test indexes and write neither a partial nor a hashed spec. So this directory exists for the same reason tests/spec/positional/ and tests/spec/operators/ do.

The one rule

Inputs are authored here; expectations are measured against a real mongod.

tests/spec/indexes/
  sources/*.json      documents + operations, authored
  record.js           runs them against mongod, writes the expectations
  *.json              generated, unified format, do not hand-edit
mongod --port 27099 --dbpath <dir>
node tests/spec/indexes/record.js --mongod-port 27099
node tests/spec/run.js --suite-dir tests/spec/indexes

Unlike the other two corpora, a case here is a sequence: create an index, insert against it, read back, list it. An index outlives a deleteMany, and every case is about which indexes exist, so the recorder drops the collection between cases and walks each case's operations in order — stopping at the first that throws, which is what a client would see.

Sources are plain JSON, not EJSON, and that costs something worth stating: a source cannot name a BSON type the JSON grammar has no syntax for, so 5.0 reaches the driver as an int32. Reading them as EJSON was tried and reverted — EJSON's wrapper namespace collides with the query operators these sources are made of. {"$regex": "x"} parses to a BSONRegExp, structuredClone flattens it to {pattern, options}, and "a filter using $regex is refused" silently became a filter mongod accepts.

Where it stands

Recorded against mongod 8.3.7 at 3/39 -- red by construction -- and both halves have since been driven green:

hashed.json    18 pass    0 fail   0 skip
partial.json   30 pass    0 fail   0 skip

The three that passed at the start were the reads a partial index does not change: this server indexed every document, so a query still found everything, which is the whole reason the review called the partial gap smaller than the arrayFilters one.

The last one to go green was not about indexes at all. find({a: null}) has to match a document with no a, and this server matched only an explicit null — with or without an index. No other test in the repository asks, and the pinned crud+aggregate scorecard did not move when it was fixed.

What this corpus cannot see

Six of partial.json's cases exist for the implication test — the rule that lets a partial index answer a read — and it is worth being exact about what they guard, because it is only half of it.

A partial index holds a subset, so reading from one the query does not imply returns too few documents, and that is an answer a result comparison catches. It was measured: forcing the implication test to always say yes takes this file to 23 pass / 7 fail, every failure reading "expected N, got N-1".

The other direction is invisible here. Forcing it to always say no — the behaviour before the test existed, where a partial index was maintained and never read — leaves this file at 30 pass / 0 fail. No client can observe which index a read used, and this server has no explain. So the corpus guards soundness and a unit test on plan() is the only thing that sees the feature work at all.

What recording it settled

Two of the review's own guesses were wrong, which is why it was recorded rather than reasoned:

mongod
$in in a partial filter allowed — the review listed it with $ne and $regex
same key, different filter, no explicit name IndexKeySpecsConflict (86), not the 67 the review assumed

And what it confirmed:

mongod
a unique partial index constrains only the documents its filter selects — two {a: 1, t: false} are fine, two {a: 9, t: true} are E11000
a document leaving the filter frees the value it held for another document to take
a document entering the filter must take a value nothing inside it holds, or the update is E11000
$regex, $ne in a filter 67
a filter that is not a document TypeMismatch (14)
sparse + partialFilterExpression 67 — may not be combined
expireAfterSeconds + a filter allowed; listIndexes reports the filter before the expiry
an empty filter allowed, and reported
two hashed components 31303
unique on a hashed index 16764
an array at a hashed path 16766, at insert time, not at creation
a key direction that is not 1, -1 or "hashed" 67
a hashed index beside an ascending one on the same field both exist
a range query or a sort over a hashed field still correct — the planner declines the index rather than misusing it

And what implementing hashed then measured, none of which the review had:

mongod
codeName for 31303 and 16764 Location31303 / Location16764 — bare location numbers with no name
codeName for {a: "bogus"} CannotCreateIndex, the named 67
16766 on an insert or update a per-document writeError beside ok: 1, so the rest of the batch lands
16766 from createIndexes a command error, over data that already holds an array
a one-element array through the path refused too — the case a value count cannot tell apart from a plain subdocument
an empty array at the path refused
an array inside a subdocument at the path fine — only the path itself matters
hashed with expireAfterSeconds, or with a partial filter both allowed
a direction that is any non-zero, non-NaN number allowed, sign taken as the direction, value echoed verbatim — this server takes only ±1 (PLAN §6)