# The update-operator corpus PLAN §3 lists eight update operators for M3 — `$setOnInsert`, `$addToSet`, `$mul`, `$min`, `$max`, `$pop`, `$pullAll`, `$currentDate` — and the pinned crud corpus says almost nothing about any of them. A probe running the identical update against mongod 8.3.7 and this server found: - all eight answering `bad update`, code 2, one message for every question; - `$push`'s `$slice`, `$position` and `$sort` **silently ignored**. `{$each: [3, 4], $slice: -3}` appended both values, sliced nothing, and answered `ok: 1` with `modifiedCount: 1`. The second is the reason this directory exists rather than a list of TODOs. A missing operator is an error the client can see; a modifier that is parsed, accepted and then dropped is the same class of wrong answer the positional operators were — the client asked for one thing and was told it got it. ## The one rule **Inputs are authored here; expectations are measured against a real mongod.** ``` tests/spec/operators/ 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 node tests/spec/operators/record.js --mongod-port 27099 node tests/spec/run.js --suite-dir tests/spec/operators ``` Same discipline as `tests/spec/positional/` and `tests/spec/aggregate/`, and for the same reason: a corpus written end to end here can encode our own bugs as expectations and then agree with us forever. ## What cannot be recorded as a value Two things in this corpus are not predictable, and both are replaced by a `$$type` assertion rather than left out: - a `$currentDate` field is whatever the clock said. The case names it in `volatile`, per case, so a corpus that one day wants to pin a real stored date still can. - an upsert that inserts gets a generated ObjectId. That one is automatic: no source file authors an ObjectId, so the rule is unambiguous. Everything else about the document — which fields exist, in what order, holding what — is still compared exactly. A case that dropped a field still fails. Files are written with canonical extended JSON (`relaxed: false`), which the runner already parses that way. It is verbose and it is exact: `$mul` overflowing an int32 produces an int64, and a corpus that wrote `4000000000` as a bare number would not have said so. ## Where it stands Recorded against mongod 8.3.7, run against the server before any of it was implemented: ``` array-ops.json 2 pass 24 fail 0 skip current-date.json 3 pass 8 fail 0 skip modifiers.json 7 pass 7 fail 0 skip numeric.json 0 pass 21 fail 0 skip push-modifiers.json 6 pass 15 fail 0 skip set-on-insert.json 0 pass 9 fail 0 skip ``` Red by construction. The 18 that pass are the shapes this server already answers the way mongod does — mostly refusals that happen to agree, plus the `$push` cases with no modifier on them. ## What recording it settled None of this is guessable, and several rows contradict the obvious reading: | | mongod | |---|---| | `$mul` of a missing field | writes **0**, not the operand | | `$mul` of a non-numeric field, or by one | TypeMismatch (14) | | `$min`/`$max` across types | compares in BSON canonical order, so `$min: {s: 5}` on `s: "b"` writes 5 | | `$min`/`$max` of a missing field | always writes | | two operators writing one field | **ConflictingUpdateOperators (40)** — `$min`+`$max`, `$set`+`$inc`, `$setOnInsert`+`$set` | | `$addToSet` of a document | compares whole, **field order included**: `{a:1,b:2}` and `{b:2,a:1}` are two values | | `$addToSet` of `2` and `2.0` | one value | | `$pop` of an empty or missing field | no-op, not an error | | `$pop` with an argument that is not ±1 | FailedToParse (9); on a non-array field, TypeMismatch (14) | | `$pullAll` with a non-array argument | BadValue (2) | | `$push` modifiers without `$each` | **not modifiers at all** — `{$slice: 1}` is pushed as a value | | `$push` modifier order | insert at `$position`, then `$sort` the whole array, then `$slice` | | `$position` negative | counted from the end | | `$currentDate` with `false` | still writes a date; the boolean's value is ignored | | `$currentDate` with anything but a bool or `{$type: date\|timestamp}` | BadValue (2) | | `$setOnInsert` writing `_id` on an insert | **allowed**, unlike `$set` | | an unknown modifier | FailedToParse (9), not BadValue | One case was authored and then removed: `{b: 1, $set: {c: 1}}` is rejected by the driver before it reaches a server, so there is no server answer to record and the case would have asserted nothing.