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.
80 lines
3.3 KiB
Markdown
80 lines
3.3 KiB
Markdown
# 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 <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 |
|