Commit Graph

4 Commits

Author SHA1 Message Date
A.Shakhmatov
98fde82946 plan/spec: pipeline-style updates are done
M3's row and §6 updated: the operator corpus is 125 cases across seven files,
all green, and the pipeline work also gave `aggregate` the `$replaceWith`
stage it never had -- the compiler is shared between the two.

Full matrix at this commit: 249/249 unit tests in ReleaseFast and ReleaseSafe,
83/83 fuzz, operators 125/0, positional 51/0, aggregation 70/0, pinned crud
228/63/196, e2e and crash-fuzz green.
2026-08-10 22:01:11 +03:00
A.Shakhmatov
e344a073a1 tests/spec: record the pipeline-style updates
An update document may be an *array* of aggregation stages instead of a
document of operators. The pinned crud corpus has five cases, one per command,
and not one of them is a refusal -- so which stages are allowed, what happens
to `_id`, and what an upsert does are all unmeasured there.

23 cases into the existing operator corpus rather than an eighth directory:
this is another shape of update document, and it shares the recorder, the
masking and the README.

Recorded red, 0/23. What it settled:

  - **`_id` always survives**, through `$replaceRoot: {newRoot: "$t"}` and
    through `$project: {_id: 0}` alike. A stage that sets it to a *different*
    value is ImmutableField (66); restating the same one is fine.
  - `$match`, `$group`, `$sort` and `$unwind` are real stages refused
    specifically here: InvalidOptions (72), "$X is not allowed to be used
    within an update". A name that is no stage at all is 40324, and two
    stages packed into one array element is 40323.
  - `arrayFilters` beside a pipeline is FailedToParse (9).
  - an upsert runs the pipeline over the document the filter implies.

Three shapes were authored and removed: `{b: 1, $set: {...}}`, an empty
pipeline, and a pipeline holding a non-document. The driver rejects all three
before they reach a server, so there is nothing to record.
2026-08-10 21:48:55 +03:00
A.Shakhmatov
44788ae681 plan/spec: the operator corpus goes green
102/102. PLAN's M3 row now names both corpora as the gate, because neither the
pinned crud suite nor e2e3/e2e4 can see this work: the eight operators are
barely in the pinned corpus and `$push`'s modifiers are not in it at all.

§6 records what the corpus found that was nobody's operator -- an upsert never
reporting the `_id` it generated -- and the three things left open on purpose:
`$bit`, a multi-field `$sort` key inside `$push`, and the 64-path bound on the
conflict check.

Full matrix at this commit: 247/247 unit tests in ReleaseFast and ReleaseSafe,
83/83 fuzz, operators 102/0, positional 51/0, aggregation 70/0, pinned crud
218/73/196 unmoved, e2e and crash-fuzz green.
2026-08-10 21:33:45 +03:00
A.Shakhmatov
eaf8d7c677 tests/spec: a recorded corpus for the update operators
PLAN §3 lists eight 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.

It also found the thing this directory exists for: `$push`'s `$slice`,
`$position` and `$sort` are **silently ignored**. `{$each: [3, 4], $slice: -3}`
appends both values, slices nothing, and answers ok: 1 with modifiedCount: 1.
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.

103 cases in six files, 18 pass / 84 fail -- red by construction, like
`tests/spec/aggregate/expressions.json` at 1/26 and the positional corpus at
15/36. Inputs authored in `sources/`, every expectation measured.

Two things here cannot be recorded as values, and both become a `$$type`
assertion rather than being left out: a `$currentDate` field is whatever the
clock said (named per case in `volatile`, so a real stored date can still be
pinned one day), and an upsert that inserts gets a generated ObjectId (that
one automatic -- no source authors an ObjectId). Everything else is compared
exactly. Files are canonical extended JSON, which the runner already parses
that way: `$mul` overflowing an int32 produces an int64, and writing
`4000000000` as a bare number would not have said so.

What recording it settled, none of it guessable:

  - `$mul` of a missing field writes **0**, not the operand; of a non-numeric
    field, or by one, TypeMismatch (14).
  - `$min`/`$max` are not numeric operators. They compare in BSON canonical
    order, so `$min: {s: 5}` on `s: "b"` writes 5, and a missing field is
    always written.
  - two operators writing one field is **ConflictingUpdateOperators (40)** --
    `$min`+`$max`, `$set`+`$inc`, `$setOnInsert`+`$set`. A whole error class
    this server does not have.
  - `$addToSet` compares documents whole, **field order included**:
    `{a:1,b:2}` and `{b:2,a:1}` are two values. But `2` and `2.0` are one.
  - `$push` modifiers are only modifiers when `$each` is there: `{$slice: 1}`
    alone is a value to push. With it, the order is position, then sort the
    whole array, then slice.
  - `$currentDate` with `false` still writes a date.
  - `$setOnInsert` may write `_id` on an insert, where `$set` may not.
  - an unknown modifier is FailedToParse (9), not BadValue.

One case was authored and then removed: `{b: 1, $set: {c: 1}}` never reaches a
server -- the driver rejects it -- so there was no answer to record and the
case would have asserted nothing.
2026-08-10 21:02:51 +03:00