Scorecard 204 -> 218 pass, 87 -> 73 fail: the fourteen `arrayFilters` cases across five files, which is the whole of what the two implementation commits were expected to move and nothing else. Positional corpus 51/51. The three divergences measured on the way are written into PLAN §6 rather than left in commit messages: `$` with two predicates on one array that no element satisfies together, an array filter with a top-level `$and`/`$or`, and a literal index into a scalar element -- the last being the one place the positional walk and the plain indexed path now answer differently, which is worth a commit of its own and needs its own measurements first. The design review gets an outcome note, since two of its guesses were wrong and the corpus is where that was settled.
122 lines
5.4 KiB
Markdown
122 lines
5.4 KiB
Markdown
# The positional-update corpus
|
|
|
|
MongoDB has three ways to say "descend into this array" in an update path:
|
|
|
|
| | since | pinned crud corpus |
|
|
|---|---|---|
|
|
| `$[<identifier>]` with `arrayFilters` | 3.6 | 4 files, 14 cases |
|
|
| `$[]` all-positional | 3.6 | **nothing** |
|
|
| `$` positional | ancient | **nothing** |
|
|
|
|
All three shared one code path in this engine, and that path overwrote the
|
|
array it was supposed to walk — `y: [{b: 3}, {b: 1}]` became
|
|
`y: {"$[i]": {"b": 2}}`, every element discarded, `ok: 1`. Only the
|
|
`$[<identifier>]` third of it was externally visible, so a fix measured
|
|
against the pinned corpus alone would have gone green while two of the three
|
|
still destroyed data. That is `docs/M3_ARRAYFILTERS_DESIGN_REVIEW.md` §3, and
|
|
this directory is its consequence.
|
|
|
|
## The one rule
|
|
|
|
**Inputs are authored here; expectations are measured against a real mongod.**
|
|
|
|
The same rule as `tests/spec/aggregate/`, for the same reason: a corpus we
|
|
write end to end can encode our own bugs as expectations and will then agree
|
|
with us forever. It is not a hypothetical risk here — the design review's own
|
|
guesses about `$[]`, about missing paths and about upserts were all wrong
|
|
before mongod was asked.
|
|
|
|
`sources/*.json` holds documents and operations and nothing else. `record.js`
|
|
asks mongod 8.3.7 what each one answers.
|
|
|
|
## Running it
|
|
|
|
```sh
|
|
node tests/spec/run.js --suite-dir tests/spec/positional
|
|
```
|
|
|
|
The same runner as the crud corpus, pointed elsewhere — the entity model, the
|
|
matchers, `outcome` verification and the skip accounting come for free.
|
|
`--scorecard` is refused with `--suite-dir`: `tests/spec/scorecard.txt` is the
|
|
crud corpus's record and the milestones are compared against it.
|
|
|
|
## Re-recording
|
|
|
|
```sh
|
|
mongod --port 27099 --dbpath /tmp/mongo-corpus &
|
|
node tests/spec/positional/record.js --mongod-port 27099
|
|
```
|
|
|
|
Writes `<name>.json` for every `sources/<name>.json`. The generated files are
|
|
committed: they *are* the corpus, and regenerating them is how a disagreement
|
|
with mongod gets re-measured rather than argued about.
|
|
|
|
Three things to know when adding cases:
|
|
|
|
- **Every case records its `outcome`, including the refusals.** A refusal that
|
|
left the document mangled looks identical to a clean one in `expectError`
|
|
alone, and a mangled document is the entire reason this corpus exists.
|
|
- **Errors record the code, not the message.** Message text is mongod's to
|
|
change between releases. Several of these messages also embed a
|
|
shell-syntax rendering of the offending element, which no formatter in this
|
|
server produces.
|
|
- **A case needing its own documents reseeds through operations**
|
|
(`deleteMany` + `insertMany`), not `initialData`: the unified format's
|
|
`initialData` is per file and the runner seeds it once per test. This keeps
|
|
one file per operator rather than one file per document shape.
|
|
|
|
Every construct here is 3.6 or older, so nothing depends on a server newer
|
|
than the 4.4 this one reports. A case that did should be left out rather than
|
|
annotated — recording it from mongod 8.x and judging it against a 4.4 answer
|
|
measures the version gap, not the engine.
|
|
|
|
## Where it stands
|
|
|
|
Recorded against mongod 8.3.7. Green:
|
|
|
|
```
|
|
filtered.json 27 pass 0 fail 0 skip
|
|
all-positional.json 12 pass 0 fail 0 skip
|
|
first-positional.json 12 pass 0 fail 0 skip
|
|
```
|
|
|
|
It was recorded red — 15 pass / 36 fail against the positional refusal — and
|
|
driven green by the two implementation commits, the same shape
|
|
`tests/spec/aggregate/expressions.json` had at 1 pass / 26 fail before the
|
|
expression evaluator existed. The 15 that passed then were the refusals where
|
|
this server already agreed with mongod, which is the only part of a red gate
|
|
that is worth anything: it says the corpus is measuring the server and not
|
|
the harness.
|
|
|
|
Three answers here still differ from mongod and are green only because no
|
|
case covers them; they are written down in PLAN §6 rather than papered over —
|
|
`$` with two disagreeing predicates on one array, an array filter with a
|
|
top-level `$and`/`$or`, and a literal index into a scalar element.
|
|
|
|
## What recording it settled
|
|
|
|
None of this is guessable, and the first row contradicts what the review
|
|
assumed:
|
|
|
|
| | mongod |
|
|
|---|---|
|
|
| `y.$[i].c.$[i].d`, one identifier reused at two levels | **accepted** — not a duplicate-identifier error |
|
|
| `$[]` over an empty array | no-op, `modifiedCount: 0` |
|
|
| `$[]` where one element is not a document | error **28**, where the other path failures are 2 |
|
|
| `$[]` or `$[i]` where the path is missing, or is not an array | error 2 — a positional segment never *creates* |
|
|
| an upsert that would insert | error 2; upsert gets no special case |
|
|
| `$` when the query never touched the array | error 2 |
|
|
| `$` where several elements match | only the first is written |
|
|
| `$`, `$[]` or `$[i]` in first position | error 2 |
|
|
| `$` twice in one path | error 2 |
|
|
| `arrayFilters` alongside a replacement | ignored, no error |
|
|
| an array filter the update never uses | error 9 |
|
|
| a filter with no top-level field, two of them, or a duplicate identifier | error 9 |
|
|
|
|
The 2-versus-9 split is *nearly* "9 is the `arrayFilters` array judged on its
|
|
own, 2 is anything needing the update or the stored document" — and it is not
|
|
a clean rule: an identifier that is not a lowercase alphanumeric name carries
|
|
the same `Error parsing array filter` prefix as the 9s and is a 2. Recorded as
|
|
measured rather than tidied, because deriving the codes from the rule would
|
|
get that row wrong.
|