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.
This commit is contained in:
153
tests/spec/operators/sources/array-ops.json
Normal file
153
tests/spec/operators/sources/array-ops.json
Normal file
@@ -0,0 +1,153 @@
|
||||
{
|
||||
"_comment": [
|
||||
"Inputs only. Expectations are measured -- see record.js.",
|
||||
"`$addToSet`, `$pop` and `$pullAll`: the three array operators that are",
|
||||
"neither `$push` nor `$pull`. Each has a shape that decides whether it",
|
||||
"wrote at all -- a set that already held the value, a pop of an empty",
|
||||
"array, a pullAll matching nothing -- and `modifiedCount` is the only",
|
||||
"place that shows, which is why every case records an outcome too."
|
||||
],
|
||||
"documents": [{ "_id": 1, "t": [1, 2, 3] }],
|
||||
"cases": [
|
||||
{
|
||||
"description": "$addToSet appends a value the array does not hold",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$addToSet": { "t": 4 } } }
|
||||
},
|
||||
{
|
||||
"description": "$addToSet on a value already there writes nothing",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$addToSet": { "t": 2 } } }
|
||||
},
|
||||
{
|
||||
"description": "$addToSet on a missing field creates the array",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$addToSet": { "gone": 1 } } }
|
||||
},
|
||||
{
|
||||
"description": "$addToSet $each adds only what is missing",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$addToSet": { "t": { "$each": [2, 4, 5] } } } }
|
||||
},
|
||||
{
|
||||
"description": "$addToSet $each with duplicates inside it",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$addToSet": { "t": { "$each": [7, 7] } } } }
|
||||
},
|
||||
{
|
||||
"description": "$addToSet compares documents whole",
|
||||
"documents": [{ "_id": 1, "t": [{ "a": 1 }] }],
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$addToSet": { "t": { "a": 1 } } } }
|
||||
},
|
||||
{
|
||||
"description": "$addToSet distinguishes documents by field order",
|
||||
"documents": [{ "_id": 1, "t": [{ "a": 1, "b": 2 }] }],
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$addToSet": { "t": { "b": 2, "a": 1 } } } }
|
||||
},
|
||||
{
|
||||
"description": "$addToSet treats an int and an equal double as one value",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$addToSet": { "t": 2.0 } } }
|
||||
},
|
||||
{
|
||||
"description": "$addToSet on a non-array field",
|
||||
"documents": [{ "_id": 1, "t": 5 }],
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$addToSet": { "t": 1 } } }
|
||||
},
|
||||
{
|
||||
"description": "$pop removes the last element",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$pop": { "t": 1 } } }
|
||||
},
|
||||
{
|
||||
"description": "$pop with -1 removes the first",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$pop": { "t": -1 } } }
|
||||
},
|
||||
{
|
||||
"description": "$pop of an empty array writes nothing",
|
||||
"documents": [{ "_id": 1, "t": [] }],
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$pop": { "t": 1 } } }
|
||||
},
|
||||
{
|
||||
"description": "$pop of a missing field writes nothing",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$pop": { "gone": 1 } } }
|
||||
},
|
||||
{
|
||||
"description": "$pop of a one-element array leaves an empty one",
|
||||
"documents": [{ "_id": 1, "t": [9] }],
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$pop": { "t": 1 } } }
|
||||
},
|
||||
{
|
||||
"description": "$pop with an argument that is neither 1 nor -1",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$pop": { "t": 2 } } }
|
||||
},
|
||||
{
|
||||
"description": "$pop with 1.0, which is 1",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$pop": { "t": 1.0 } } }
|
||||
},
|
||||
{
|
||||
"description": "$pop of a non-array field",
|
||||
"documents": [{ "_id": 1, "t": 5 }],
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$pop": { "t": 1 } } }
|
||||
},
|
||||
{
|
||||
"description": "$pullAll removes every copy of every listed value",
|
||||
"documents": [{ "_id": 1, "t": [1, 2, 3, 2] }],
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$pullAll": { "t": [2] } } }
|
||||
},
|
||||
{
|
||||
"description": "$pullAll with several values",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$pullAll": { "t": [1, 3] } } }
|
||||
},
|
||||
{
|
||||
"description": "$pullAll matching nothing writes nothing",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$pullAll": { "t": [9] } } }
|
||||
},
|
||||
{
|
||||
"description": "$pullAll compares documents whole, not by predicate",
|
||||
"documents": [{ "_id": 1, "t": [{ "a": 1 }, { "a": 2 }] }],
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$pullAll": { "t": [{ "a": 1 }] } } }
|
||||
},
|
||||
{
|
||||
"description": "$pullAll on a missing field writes nothing",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$pullAll": { "gone": [1] } } }
|
||||
},
|
||||
{
|
||||
"description": "$pullAll with an argument that is not an array",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$pullAll": { "t": 1 } } }
|
||||
},
|
||||
{
|
||||
"description": "$pullAll emptying the array",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$pullAll": { "t": [1, 2, 3] } } }
|
||||
},
|
||||
{
|
||||
"description": "$addToSet through a positional segment",
|
||||
"documents": [{ "_id": 1, "y": [{ "t": [1] }, { "t": [2] }] }],
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$addToSet": { "y.$[].t": 1 } } }
|
||||
},
|
||||
{
|
||||
"description": "$pop down a dotted path",
|
||||
"documents": [{ "_id": 1, "n": { "t": [1, 2] } }],
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$pop": { "n.t": -1 } } }
|
||||
}
|
||||
]
|
||||
}
|
||||
81
tests/spec/operators/sources/current-date.json
Normal file
81
tests/spec/operators/sources/current-date.json
Normal file
@@ -0,0 +1,81 @@
|
||||
{
|
||||
"_comment": [
|
||||
"Inputs only. Expectations are measured -- see record.js.",
|
||||
"`$currentDate`, the one update operator whose result cannot be recorded",
|
||||
"as a value: it is whatever the clock said. `volatile` names the fields",
|
||||
"whose recorded value is replaced by a `$$type` assertion, so the case",
|
||||
"still pins the type, the position and every other field of the document",
|
||||
"-- everything except the number nobody can predict."
|
||||
],
|
||||
"documents": [{ "_id": 1, "a": 1 }],
|
||||
"cases": [
|
||||
{
|
||||
"description": "$currentDate with true writes a date",
|
||||
"volatile": ["d"],
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$currentDate": { "d": true } } }
|
||||
},
|
||||
{
|
||||
"description": "$currentDate with an explicit date type",
|
||||
"volatile": ["d"],
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$currentDate": { "d": { "$type": "date" } } } }
|
||||
},
|
||||
{
|
||||
"description": "$currentDate with the timestamp type",
|
||||
"volatile": ["d"],
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$currentDate": { "d": { "$type": "timestamp" } } } }
|
||||
},
|
||||
{
|
||||
"description": "$currentDate overwrites a field that is already there",
|
||||
"volatile": ["a"],
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$currentDate": { "a": true } } }
|
||||
},
|
||||
{
|
||||
"description": "$currentDate down a dotted path",
|
||||
"volatile": ["n.d"],
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$currentDate": { "n.d": true } } }
|
||||
},
|
||||
{
|
||||
"description": "$currentDate beside another operator",
|
||||
"volatile": ["d"],
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$currentDate": { "d": true }, "$inc": { "a": 1 } } }
|
||||
},
|
||||
{
|
||||
"description": "$currentDate with false, which is still a date",
|
||||
"volatile": ["d"],
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$currentDate": { "d": false } } }
|
||||
},
|
||||
{
|
||||
"description": "$currentDate with an unknown type name",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$currentDate": { "d": { "$type": "nope" } } } }
|
||||
},
|
||||
{
|
||||
"description": "$currentDate with a document that is not $type",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$currentDate": { "d": { "a": 1 } } } }
|
||||
},
|
||||
{
|
||||
"description": "$currentDate with a number",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$currentDate": { "d": 1 } } }
|
||||
},
|
||||
{
|
||||
"description": "$currentDate on an upsert that inserts",
|
||||
"documents": [],
|
||||
"volatile": ["d"],
|
||||
"operation": "updateOne",
|
||||
"arguments": {
|
||||
"filter": { "k": 1 },
|
||||
"update": { "$currentDate": { "d": true } },
|
||||
"upsert": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
88
tests/spec/operators/sources/modifiers.json
Normal file
88
tests/spec/operators/sources/modifiers.json
Normal file
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"_comment": [
|
||||
"Inputs only. Expectations are measured -- see record.js.",
|
||||
"The operator table itself, rather than any one operator: what an update",
|
||||
"document may be, and what happens to a name that is not in the table.",
|
||||
"This server answers `bad update` with code 2 to every one of these, which",
|
||||
"is one message covering several different questions.",
|
||||
"A `{b: 1, $set: {...}}` case was authored and then removed: the driver",
|
||||
"rejects it before it reaches a server, so there is no server answer to",
|
||||
"record and a case with no `errorCode` would assert nothing."
|
||||
],
|
||||
"documents": [{ "_id": 1, "a": 1, "t": [1, 2] }],
|
||||
"cases": [
|
||||
{
|
||||
"description": "an unknown modifier",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$bogus": { "a": 1 } } }
|
||||
},
|
||||
{
|
||||
"description": "an unknown modifier beside a known one",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$set": { "b": 1 }, "$bogus": { "a": 1 } } }
|
||||
},
|
||||
{
|
||||
"description": "a known modifier whose argument is not a document",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$set": 1 } }
|
||||
},
|
||||
{
|
||||
"description": "an empty argument to a known modifier",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$set": {} } }
|
||||
},
|
||||
{
|
||||
"description": "a data field after an operator",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$set": { "c": 1 }, "b": 1 } }
|
||||
},
|
||||
{
|
||||
"description": "two operators writing the same field",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$set": { "a": 2 }, "$inc": { "a": 1 } } }
|
||||
},
|
||||
{
|
||||
"description": "$inc on a non-numeric field",
|
||||
"documents": [{ "_id": 1, "a": "x" }],
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$inc": { "a": 1 } } }
|
||||
},
|
||||
{
|
||||
"description": "$inc by a non-numeric operand",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$inc": { "a": "x" } } }
|
||||
},
|
||||
{
|
||||
"description": "$push onto a non-array field",
|
||||
"documents": [{ "_id": 1, "t": 5 }],
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$push": { "t": 1 } } }
|
||||
},
|
||||
{
|
||||
"description": "$pull on a non-array field",
|
||||
"documents": [{ "_id": 1, "t": 5 }],
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$pull": { "t": 1 } } }
|
||||
},
|
||||
{
|
||||
"description": "$unset of a field that is not there",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$unset": { "gone": "" } } }
|
||||
},
|
||||
{
|
||||
"description": "$rename onto a field that already exists",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$rename": { "a": "t" } } }
|
||||
},
|
||||
{
|
||||
"description": "$rename of a field that is not there",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$rename": { "gone": "b" } } }
|
||||
},
|
||||
{
|
||||
"description": "an update that writes nothing still reports a match",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$set": { "a": 1 } } }
|
||||
}
|
||||
]
|
||||
}
|
||||
122
tests/spec/operators/sources/numeric.json
Normal file
122
tests/spec/operators/sources/numeric.json
Normal file
@@ -0,0 +1,122 @@
|
||||
{
|
||||
"_comment": [
|
||||
"Inputs only. Expectations are measured -- see record.js.",
|
||||
"`$mul`, `$min` and `$max`. The two comparison operators are not numeric",
|
||||
"at all -- they compare in BSON canonical order, which is why a string",
|
||||
"field and a number operand have a defined answer -- and the cases here",
|
||||
"are shaped to say which of the three rules each one follows."
|
||||
],
|
||||
"documents": [{ "_id": 1, "a": 5, "s": "b", "d": 2.5 }],
|
||||
"cases": [
|
||||
{
|
||||
"description": "$mul multiplies an int by an int",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$mul": { "a": 2 } } }
|
||||
},
|
||||
{
|
||||
"description": "$mul of a missing field",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$mul": { "gone": 5 } } }
|
||||
},
|
||||
{
|
||||
"description": "$mul mixes int and double",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$mul": { "d": 2 } } }
|
||||
},
|
||||
{
|
||||
"description": "$mul by zero",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$mul": { "a": 0 } } }
|
||||
},
|
||||
{
|
||||
"description": "$mul overflowing an int32",
|
||||
"documents": [{ "_id": 1, "a": 2000000000 }],
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$mul": { "a": 2 } } }
|
||||
},
|
||||
{
|
||||
"description": "$mul of a non-numeric field",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$mul": { "s": 2 } } }
|
||||
},
|
||||
{
|
||||
"description": "$mul by a non-numeric operand",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$mul": { "a": "x" } } }
|
||||
},
|
||||
{
|
||||
"description": "$mul down a dotted path",
|
||||
"documents": [{ "_id": 1, "n": { "a": 3 } }],
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$mul": { "n.a": 4 } } }
|
||||
},
|
||||
{
|
||||
"description": "$min writes when the operand is lower",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$min": { "a": 3 } } }
|
||||
},
|
||||
{
|
||||
"description": "$min leaves the field when the operand is higher",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$min": { "a": 7 } } }
|
||||
},
|
||||
{
|
||||
"description": "$min of a missing field always writes",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$min": { "gone": 7 } } }
|
||||
},
|
||||
{
|
||||
"description": "$min against a field of another type",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$min": { "s": 5 } } }
|
||||
},
|
||||
{
|
||||
"description": "$min compares an int and an equal double",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$min": { "a": 5.0 } } }
|
||||
},
|
||||
{
|
||||
"description": "$max writes when the operand is higher",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$max": { "a": 7 } } }
|
||||
},
|
||||
{
|
||||
"description": "$max leaves the field when the operand is lower",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$max": { "a": 3 } } }
|
||||
},
|
||||
{
|
||||
"description": "$max of a missing field always writes",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$max": { "gone": 7 } } }
|
||||
},
|
||||
{
|
||||
"description": "$max against a field of another type",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$max": { "s": 5 } } }
|
||||
},
|
||||
{
|
||||
"description": "$max against a null field",
|
||||
"documents": [{ "_id": 1, "a": null }],
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$max": { "a": 1 } } }
|
||||
},
|
||||
{
|
||||
"description": "$min and $max on the same field in one update",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$min": { "a": 3 }, "$max": { "a": 9 } } }
|
||||
},
|
||||
{
|
||||
"description": "$min through an array index",
|
||||
"documents": [{ "_id": 1, "t": [5, 5] }],
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$min": { "t.0": 1 } } }
|
||||
},
|
||||
{
|
||||
"description": "$mul through a positional segment",
|
||||
"documents": [{ "_id": 1, "t": [{ "a": 2 }, { "a": 3 }] }],
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": {}, "update": { "$mul": { "t.$[].a": 10 } } }
|
||||
}
|
||||
]
|
||||
}
|
||||
122
tests/spec/operators/sources/push-modifiers.json
Normal file
122
tests/spec/operators/sources/push-modifiers.json
Normal file
@@ -0,0 +1,122 @@
|
||||
{
|
||||
"_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] } } } }
|
||||
}
|
||||
]
|
||||
}
|
||||
99
tests/spec/operators/sources/set-on-insert.json
Normal file
99
tests/spec/operators/sources/set-on-insert.json
Normal file
@@ -0,0 +1,99 @@
|
||||
{
|
||||
"_comment": [
|
||||
"Inputs only. Expectations are measured -- see record.js.",
|
||||
"`$setOnInsert`, the only update operator whose meaning depends on which",
|
||||
"branch of an upsert ran. Every case here is therefore a pair: the same",
|
||||
"update against a document that exists and against one that does not."
|
||||
],
|
||||
"documents": [{ "_id": 1, "k": 1, "a": 100 }],
|
||||
"cases": [
|
||||
{
|
||||
"description": "$setOnInsert writes when the upsert inserts",
|
||||
"documents": [],
|
||||
"operation": "updateOne",
|
||||
"arguments": {
|
||||
"filter": { "k": 1 },
|
||||
"update": { "$setOnInsert": { "a": 1 }, "$set": { "b": 2 } },
|
||||
"upsert": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "$setOnInsert is ignored when the upsert updates",
|
||||
"operation": "updateOne",
|
||||
"arguments": {
|
||||
"filter": { "k": 1 },
|
||||
"update": { "$setOnInsert": { "a": 1 }, "$set": { "b": 2 } },
|
||||
"upsert": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "$setOnInsert alone on a plain update is a no-op",
|
||||
"operation": "updateOne",
|
||||
"arguments": {
|
||||
"filter": { "k": 1 },
|
||||
"update": { "$setOnInsert": { "a": 1 } }
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "$setOnInsert alone on an insert is the whole document",
|
||||
"documents": [],
|
||||
"operation": "updateOne",
|
||||
"arguments": {
|
||||
"filter": { "k": 7 },
|
||||
"update": { "$setOnInsert": { "a": 1 } },
|
||||
"upsert": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "$setOnInsert writes a dotted path on insert",
|
||||
"documents": [],
|
||||
"operation": "updateOne",
|
||||
"arguments": {
|
||||
"filter": { "k": 1 },
|
||||
"update": { "$setOnInsert": { "x.y": 5 } },
|
||||
"upsert": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "$setOnInsert may not touch _id",
|
||||
"documents": [],
|
||||
"operation": "updateOne",
|
||||
"arguments": {
|
||||
"filter": { "k": 1 },
|
||||
"update": { "$setOnInsert": { "_id": 9 } },
|
||||
"upsert": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "$setOnInsert and $set naming the same field on an insert",
|
||||
"documents": [],
|
||||
"operation": "updateOne",
|
||||
"arguments": {
|
||||
"filter": { "k": 1 },
|
||||
"update": { "$setOnInsert": { "a": 1 }, "$set": { "a": 2 } },
|
||||
"upsert": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "findOneAndUpdate reports the document $setOnInsert built",
|
||||
"documents": [],
|
||||
"operation": "findOneAndUpdate",
|
||||
"arguments": {
|
||||
"filter": { "k": 3 },
|
||||
"update": { "$setOnInsert": { "a": 1 } },
|
||||
"upsert": true,
|
||||
"returnDocument": "after"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "updateMany with $setOnInsert on an upsert that inserts",
|
||||
"documents": [],
|
||||
"operation": "updateMany",
|
||||
"arguments": {
|
||||
"filter": { "k": 1 },
|
||||
"update": { "$setOnInsert": { "a": 1 } },
|
||||
"upsert": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user