plan/spec: hashed indexes are done, the implication test is next
`tests/spec/indexes/` is 42/42, so all four recorded corpora are green:
positional 51, operators 125, indexes 42, aggregate 70.
Records what implementing the row taught, including the three things the
design review got wrong. Two were already noted when the corpus was
recorded ($in is allowed; a differing filter is 86); the third is new and
cheaper than the review's version: hashed needs no flags bit, because it
belongs to a key *component* and the per-component direction byte was
already there holding 0 or 1.
Also fixes a corpus case that did not measure what it said. "equality
across numeric types" sent an int32, because a source is plain JSON and
`5.0` is `5` after JSON.parse -- it was a second copy of the case above
it. Reading sources as EJSON was tried and reverted, and the recorder now
says why: EJSON's wrapper namespace collides with the query operators
these sources are made of, so `{"$regex": "x"}` became a BSONRegExp,
`structuredClone` flattened it to `{pattern, options}`, and "a filter
using $regex is refused" silently became a filter mongod accepts. The
cross-type property is a unit test instead, which is where it belongs --
it is about how this server hashes, and mongod's hash is a different
function, so a corpus could only ever check the answer.
The case is renamed to what it does measure rather than deleted: two
documents sharing a value is still the read a hashed index exists for.
Verified: 256/256 unit tests in ReleaseFast and ReleaseSafe, 87/87 fuzz,
all four corpora 0 fail, pinned scorecard unchanged at 228/63/196, the
full e2e matrix and crash-fuzz green.
This commit was merged in pull request #13.
This commit is contained in:
@@ -35,13 +35,21 @@ 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 partial
|
||||
indexes have since been driven green:
|
||||
Recorded against mongod 8.3.7 at 3/39 -- red by construction -- and both
|
||||
halves have since been driven green:
|
||||
|
||||
```
|
||||
hashed.json 0 pass 18 fail 0 skip
|
||||
hashed.json 18 pass 0 fail 0 skip
|
||||
partial.json 24 pass 0 fail 0 skip
|
||||
```
|
||||
|
||||
@@ -50,6 +58,11 @@ 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 recording it settled
|
||||
|
||||
Two of the review's own guesses were wrong, which is why it was recorded
|
||||
@@ -78,3 +91,17 @@ And what it confirmed:
|
||||
| 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) |
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -68,6 +68,15 @@ async function main() {
|
||||
.filter((f) => !ONLY || f === ONLY || f === ONLY + '.json')
|
||||
.sort();
|
||||
for (const file of sources) {
|
||||
// Plain JSON, deliberately, and the cost is 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 and no source here can ask a
|
||||
// cross-type question. Reading sources as EJSON instead 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` below then flattens it to
|
||||
// `{pattern, options}`, and "a filter using $regex is refused"
|
||||
// silently became a filter mongod accepts.
|
||||
const src = JSON.parse(fs.readFileSync(path.join(SRC_DIR, file), 'utf8'));
|
||||
const name = path.basename(file, '.json');
|
||||
const out = await record(client, name, src);
|
||||
|
||||
@@ -56,10 +56,20 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "equality across numeric types",
|
||||
"description": "equality against a value two documents share",
|
||||
"_comment": [
|
||||
"This was 'equality across numeric types' and could not be: a source",
|
||||
"is plain JSON, so 5.0 reaches the driver as an int32 and the case was",
|
||||
"a second copy of the one above it. The cross-type property belongs to",
|
||||
"how this server hashes anyway -- it hashes the *encoded* value, which",
|
||||
"normalizes every numeric type -- and mongod's hash is a different",
|
||||
"function, so a corpus could only ever check the answer. It is a unit",
|
||||
"test instead: 'a hashed component answers equality across numeric",
|
||||
"types and nothing else' in src/index.zig."
|
||||
],
|
||||
"ops": [
|
||||
{ "name": "createIndex", "arguments": { "keys": { "a": "hashed" } } },
|
||||
{ "name": "find", "arguments": { "filter": { "a": 5.0 }, "sort": { "_id": 1 } } }
|
||||
{ "name": "find", "arguments": { "filter": { "a": 5 }, "sort": { "_id": 1 } } }
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user