# 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 ``` ```sh mongod --port 27099 --dbpath 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. ## Where it stands Recorded against mongod 8.3.7, run before any of it was implemented: ``` hashed.json 0 pass 18 fail 0 skip partial.json 3 pass 21 fail 0 skip ``` Red by construction. The three that pass are the reads a partial index does not change: this server builds an index over every document, so a query still finds everything, which is the whole reason the review called the partial gap smaller than the `arrayFilters` one. ## 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 |