tests/spec: a recorded corpus for partial and hashed indexes

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.
This commit is contained in:
A.Shakhmatov
2026-08-10 22:29:08 +03:00
parent 21002528be
commit 879a6bb07b
7 changed files with 609 additions and 2 deletions

View File

@@ -45,8 +45,9 @@ function opt(name, dflt) {
const VERBOSE = !!opt('verbose', false);
// The corpus. Defaults to the pinned crud suite; `--suite-dir` points the same
// runner at another one, which is how `tests/spec/aggregate/`,
// `tests/spec/positional/` and `tests/spec/operators/` are run -- each exists
// because the pinned suite has a hole where a whole feature should be. Sharing
// `tests/spec/positional/`, `tests/spec/operators/` and `tests/spec/indexes/`
// are run -- each exists because the pinned suite has a hole where a whole
// feature should be. Sharing
// the runner rather than writing a second one is the point: the entity model,
// the matchers, the skip accounting and `expectEvents` all come for free, and a
// second runner would drift from this one exactly where it mattered.