M3's last row, and the first of the four corpora here whose subject nothing in
the repository tested at all: the pinned suite is crud and aggregate, and
`e2e5.js`/`e2e6.js` write neither a partial nor a hashed spec.
42 cases in two files, recorded red at 3/39. The three that pass are the reads
a partial index does not change -- this server indexes every document, so a
query still finds everything, which is exactly why the review called this a
smaller fire than `arrayFilters`.
A case here is a *sequence* rather than one operation: create an index, insert
against it, read back, list it. So the recorder walks a case's operations in
order and stops at the first that throws, which is what a client would see,
and drops the collection between cases because an index outlives a
`deleteMany`.
Two of the design review's own guesses were wrong, which is the argument for
recording rather than reasoning:
- **`$in` in a partial filter is allowed.** The review grouped it with `$ne`
and `$regex`, which are 67.
- **Same key, different filter, no explicit name is IndexKeySpecsConflict
(86)**, not the 67 the review assumed.
What it confirmed, and what the implementation now has to satisfy: a unique
partial index constrains only the documents its filter selects; a document
*leaving* the filter frees the value it held; a document *entering* it must
take a value nothing inside holds, or the update is E11000. `sparse` and
`partialFilterExpression` may not be combined (67). `expireAfterSeconds` and a
filter may, and `listIndexes` reports the filter before the expiry. Two hashed
components is 31303, `unique` on a hashed index is 16764, and an array at a
hashed path is 16766 *at insert time* rather than at creation.
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.
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 |