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.
123 lines
4.9 KiB
JSON
123 lines
4.9 KiB
JSON
{
|
|
"_comment": [
|
|
"Inputs only. Expectations are measured -- see record.js.",
|
|
"`$push`'s modifiers: `$each`, `$slice`, `$position`, `$sort`. This server",
|
|
"already implements `$push` and `$each` and *silently ignores* the other",
|
|
"three -- `{$each: [3, 4], $slice: -3}` appended and did not slice, and",
|
|
"answered ok: 1. That is the same class of wrong answer the positional",
|
|
"operators were: the client asked for one thing and was told it got it."
|
|
],
|
|
"documents": [{ "_id": 1, "t": [1, 2] }],
|
|
"cases": [
|
|
{
|
|
"description": "$slice keeps the last n",
|
|
"operation": "updateOne",
|
|
"arguments": { "filter": {}, "update": { "$push": { "t": { "$each": [3, 4], "$slice": -3 } } } }
|
|
},
|
|
{
|
|
"description": "$slice keeps the first n",
|
|
"operation": "updateOne",
|
|
"arguments": { "filter": {}, "update": { "$push": { "t": { "$each": [3, 4], "$slice": 3 } } } }
|
|
},
|
|
{
|
|
"description": "$slice of zero empties the array",
|
|
"operation": "updateOne",
|
|
"arguments": { "filter": {}, "update": { "$push": { "t": { "$each": [3], "$slice": 0 } } } }
|
|
},
|
|
{
|
|
"description": "$slice larger than the array keeps all of it",
|
|
"operation": "updateOne",
|
|
"arguments": { "filter": {}, "update": { "$push": { "t": { "$each": [3], "$slice": 10 } } } }
|
|
},
|
|
{
|
|
"description": "$slice with an empty $each truncates without adding",
|
|
"operation": "updateOne",
|
|
"arguments": { "filter": {}, "update": { "$push": { "t": { "$each": [], "$slice": 1 } } } }
|
|
},
|
|
{
|
|
"description": "$position inserts at the front",
|
|
"operation": "updateOne",
|
|
"arguments": { "filter": {}, "update": { "$push": { "t": { "$each": [9], "$position": 0 } } } }
|
|
},
|
|
{
|
|
"description": "$position inserts in the middle",
|
|
"operation": "updateOne",
|
|
"arguments": { "filter": {}, "update": { "$push": { "t": { "$each": [9], "$position": 1 } } } }
|
|
},
|
|
{
|
|
"description": "$position past the end appends",
|
|
"operation": "updateOne",
|
|
"arguments": { "filter": {}, "update": { "$push": { "t": { "$each": [9], "$position": 99 } } } }
|
|
},
|
|
{
|
|
"description": "$position counted from the end",
|
|
"operation": "updateOne",
|
|
"arguments": { "filter": {}, "update": { "$push": { "t": { "$each": [9], "$position": -1 } } } }
|
|
},
|
|
{
|
|
"description": "$sort ascending over scalars",
|
|
"documents": [{ "_id": 1, "t": [3, 1] }],
|
|
"operation": "updateOne",
|
|
"arguments": { "filter": {}, "update": { "$push": { "t": { "$each": [2], "$sort": 1 } } } }
|
|
},
|
|
{
|
|
"description": "$sort descending over scalars",
|
|
"documents": [{ "_id": 1, "t": [3, 1] }],
|
|
"operation": "updateOne",
|
|
"arguments": { "filter": {}, "update": { "$push": { "t": { "$each": [2], "$sort": -1 } } } }
|
|
},
|
|
{
|
|
"description": "$sort on a field of the elements",
|
|
"documents": [{ "_id": 1, "t": [{ "a": 3 }, { "a": 1 }] }],
|
|
"operation": "updateOne",
|
|
"arguments": { "filter": {}, "update": { "$push": { "t": { "$each": [{ "a": 2 }], "$sort": { "a": 1 } } } } }
|
|
},
|
|
{
|
|
"description": "$sort and $slice together",
|
|
"documents": [{ "_id": 1, "t": [3, 1] }],
|
|
"operation": "updateOne",
|
|
"arguments": { "filter": {}, "update": { "$push": { "t": { "$each": [2], "$sort": 1, "$slice": 2 } } } }
|
|
},
|
|
{
|
|
"description": "$position and $slice together",
|
|
"operation": "updateOne",
|
|
"arguments": { "filter": {}, "update": { "$push": { "t": { "$each": [9], "$position": 0, "$slice": 2 } } } }
|
|
},
|
|
{
|
|
"description": "$slice without $each",
|
|
"operation": "updateOne",
|
|
"arguments": { "filter": {}, "update": { "$push": { "t": { "$slice": 1 } } } }
|
|
},
|
|
{
|
|
"description": "$each that is not an array",
|
|
"operation": "updateOne",
|
|
"arguments": { "filter": {}, "update": { "$push": { "t": { "$each": 3 } } } }
|
|
},
|
|
{
|
|
"description": "$slice that is not a number",
|
|
"operation": "updateOne",
|
|
"arguments": { "filter": {}, "update": { "$push": { "t": { "$each": [3], "$slice": "x" } } } }
|
|
},
|
|
{
|
|
"description": "$position that is not a number",
|
|
"operation": "updateOne",
|
|
"arguments": { "filter": {}, "update": { "$push": { "t": { "$each": [3], "$position": "x" } } } }
|
|
},
|
|
{
|
|
"description": "an unknown modifier beside $each",
|
|
"operation": "updateOne",
|
|
"arguments": { "filter": {}, "update": { "$push": { "t": { "$each": [3], "$bogus": 1 } } } }
|
|
},
|
|
{
|
|
"description": "a document operand that is not modifiers at all is a value",
|
|
"operation": "updateOne",
|
|
"arguments": { "filter": {}, "update": { "$push": { "t": { "a": 1 } } } }
|
|
},
|
|
{
|
|
"description": "$push $each onto a missing field",
|
|
"operation": "updateOne",
|
|
"arguments": { "filter": {}, "update": { "$push": { "gone": { "$each": [1, 2] } } } }
|
|
}
|
|
]
|
|
}
|