Files
MultiforaDB/tests/spec/operators
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
..

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
mongod --port 27099 --dbpath <dir>
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.