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.
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
# 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
|
||||
What an update *document* may be, measured. PLAN §3 lists eight operators for
|
||||
M3 — `$setOnInsert`, `$addToSet`, `$mul`, `$min`, `$max`, `$pop`, `$pullAll`,
|
||||
`$currentDate` — and pipeline-style updates beside them; the pinned crud
|
||||
corpus says almost nothing about any of it. 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;
|
||||
@@ -62,6 +63,7 @@ Recorded against mongod 8.3.7. Green:
|
||||
|
||||
```
|
||||
array-ops.json 26 pass 0 fail 0 skip
|
||||
pipeline.json 23 pass 0 fail 0 skip
|
||||
current-date.json 11 pass 0 fail 0 skip
|
||||
modifiers.json 14 pass 0 fail 0 skip
|
||||
numeric.json 21 pass 0 fail 0 skip
|
||||
@@ -103,7 +105,13 @@ None of this is guessable, and several rows contradict the obvious reading:
|
||||
| `$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 |
|
||||
| a pipeline update's `_id` | **always survives**, through `$replaceRoot` and through `$project: {_id: 0}` |
|
||||
| a stage changing `_id` to something else | ImmutableField (66) |
|
||||
| `$match`, `$group`, `$sort`, `$unwind` in an update | InvalidOptions (72) — real stages, refused *here* |
|
||||
| a name that is no stage at all | 40324; two stages in one array element, 40323 |
|
||||
| `arrayFilters` beside a pipeline | FailedToParse (9) |
|
||||
|
||||
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.
|
||||
Three cases were authored and then removed, all for the same reason: the
|
||||
driver rejects `{b: 1, $set: {c: 1}}`, an empty pipeline and a pipeline
|
||||
holding a non-document before any of them reaches a server, so there is no
|
||||
server answer to record and the cases would have asserted nothing.
|
||||
|
||||
1
tests/spec/operators/pipeline.json
Normal file
1
tests/spec/operators/pipeline.json
Normal file
File diff suppressed because one or more lines are too long
163
tests/spec/operators/sources/pipeline.json
Normal file
163
tests/spec/operators/sources/pipeline.json
Normal file
@@ -0,0 +1,163 @@
|
||||
{
|
||||
"_comment": [
|
||||
"Inputs only. Expectations are measured -- see record.js.",
|
||||
"Pipeline-style updates: an update document that is an *array* of",
|
||||
"aggregation stages rather than a document of operators. The pinned crud",
|
||||
"corpus has five cases, one per command, and none of them is a refusal --",
|
||||
"so which stages are allowed, what happens to `_id`, and what an upsert",
|
||||
"does are all unmeasured there.",
|
||||
"Two shapes were authored and removed: an empty pipeline and an array",
|
||||
"holding a non-document. The driver rejects both before they reach a",
|
||||
"server ('Update document requires atomic operators'), so there is no",
|
||||
"server answer to record."
|
||||
],
|
||||
"documents": [
|
||||
{ "_id": 1, "x": 1, "y": 1, "t": { "u": { "v": 1 } } },
|
||||
{ "_id": 2, "x": 2, "y": 1 }
|
||||
],
|
||||
"cases": [
|
||||
{
|
||||
"description": "$replaceRoot keeps the _id it was given",
|
||||
"operation": "updateOne",
|
||||
"arguments": {
|
||||
"filter": { "_id": 1 },
|
||||
"update": [{ "$replaceRoot": { "newRoot": "$t" } }]
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "$replaceWith is the same stage under its short name",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": { "_id": 1 }, "update": [{ "$replaceWith": "$t" }] }
|
||||
},
|
||||
{
|
||||
"description": "$project keeps the _id it did not mention",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": { "_id": 1 }, "update": [{ "$project": { "x": 1 } }] }
|
||||
},
|
||||
{
|
||||
"description": "$project excluding _id keeps it anyway",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": { "_id": 1 }, "update": [{ "$project": { "_id": 0, "x": 1 } }] }
|
||||
},
|
||||
{
|
||||
"description": "$addFields adds to the document",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": { "_id": 1 }, "update": [{ "$addFields": { "foo": 1 } }] }
|
||||
},
|
||||
{
|
||||
"description": "$set is $addFields under its other name",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": { "_id": 1 }, "update": [{ "$set": { "foo": "$x" } }] }
|
||||
},
|
||||
{
|
||||
"description": "$unset of one field",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": { "_id": 1 }, "update": [{ "$unset": "y" }] }
|
||||
},
|
||||
{
|
||||
"description": "$unset of several fields",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": { "_id": 1 }, "update": [{ "$unset": ["y", "t"] }] }
|
||||
},
|
||||
{
|
||||
"description": "the stages run in order, each on the last one's output",
|
||||
"operation": "updateOne",
|
||||
"arguments": {
|
||||
"filter": { "_id": 1 },
|
||||
"update": [{ "$replaceRoot": { "newRoot": "$t" } }, { "$addFields": { "foo": 1 } }]
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "an expression reads the document the stage was handed",
|
||||
"operation": "updateOne",
|
||||
"arguments": {
|
||||
"filter": { "_id": 1 },
|
||||
"update": [{ "$addFields": { "z": { "$add": ["$x", "$y"] } } }]
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "a stage may restate the _id it already has",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": { "_id": 1 }, "update": [{ "$addFields": { "_id": 1 } }] }
|
||||
},
|
||||
{
|
||||
"description": "a stage may not change the _id",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": { "_id": 1 }, "update": [{ "$addFields": { "_id": 9 } }] }
|
||||
},
|
||||
{
|
||||
"description": "updateMany runs the pipeline on every match",
|
||||
"operation": "updateMany",
|
||||
"arguments": {
|
||||
"filter": {},
|
||||
"update": [{ "$project": { "x": 1 } }, { "$addFields": { "foo": 1 } }]
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "findOneAndUpdate with a pipeline",
|
||||
"operation": "findOneAndUpdate",
|
||||
"arguments": {
|
||||
"filter": { "_id": 1 },
|
||||
"update": [{ "$project": { "x": 1 } }, { "$addFields": { "foo": 1 } }],
|
||||
"returnDocument": "after"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "an upsert runs the pipeline on the document the filter implies",
|
||||
"documents": [],
|
||||
"operation": "updateOne",
|
||||
"arguments": {
|
||||
"filter": { "k": 1 },
|
||||
"update": [{ "$addFields": { "a": 1 } }],
|
||||
"upsert": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "a pipeline that writes nothing new still reports a match",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": { "_id": 1 }, "update": [{ "$addFields": { "x": 1 } }] }
|
||||
},
|
||||
{
|
||||
"description": "$match is not allowed inside an update",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": { "_id": 1 }, "update": [{ "$match": { "x": 1 } }] }
|
||||
},
|
||||
{
|
||||
"description": "$group is not allowed inside an update",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": { "_id": 1 }, "update": [{ "$group": { "_id": null } }] }
|
||||
},
|
||||
{
|
||||
"description": "$sort is not allowed inside an update",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": { "_id": 1 }, "update": [{ "$sort": { "x": 1 } }] }
|
||||
},
|
||||
{
|
||||
"description": "$unwind is not allowed inside an update",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": { "_id": 1 }, "update": [{ "$unwind": "$t" }] }
|
||||
},
|
||||
{
|
||||
"description": "a stage name that is not a stage at all",
|
||||
"operation": "updateOne",
|
||||
"arguments": { "filter": { "_id": 1 }, "update": [{ "$bogus": {} }] }
|
||||
},
|
||||
{
|
||||
"description": "two stages packed into one array element",
|
||||
"operation": "updateOne",
|
||||
"arguments": {
|
||||
"filter": { "_id": 1 },
|
||||
"update": [{ "$addFields": { "a": 1 }, "$unset": "y" }]
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "arrayFilters alongside a pipeline",
|
||||
"operation": "updateOne",
|
||||
"arguments": {
|
||||
"filter": { "_id": 1 },
|
||||
"update": [{ "$addFields": { "a": 1 } }],
|
||||
"arrayFilters": [{ "i.b": 1 }]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user