tests/spec: record Tier 2's spec -- the stages that rewrite a document
24 cases for `$addFields`/`$set`, `$unset`, `$replaceRoot`, `$unwind` and
`$project`'s computed fields, recorded from mongod 8.3.7 before any of them is
written. The file starts at 0 pass / 24 fail, which is the honest number for
five stages this server does not have.
Eight things the recording settled, none of them guessable from the manual:
$addFields whose expression is missing the field is not added at all
$addFields: {"n.z": 1} sets the nested path, keeps siblings
$replaceRoot of missing or non-document error 40228
$unwind of an empty array or missing the document is dropped
$unwind of a non-array the document is kept whole
$unwind path without a $ error 28818
includeArrayIndex 0-based
$project: {n: {x: 1}} narrows it; a document without
`n` keeps only `_id`
That last one is the `query.project` gap recorded during M2 as broken rather
than unimplemented -- a nested inclusion currently reads as falsy and returns
the whole document minus the field. It now has a measured expectation to be
fixed against, in the corpus, rather than a note in a commit message.
Worth stating before the implementation: the design review expected Tier 2 to
need a per-stage iterator, on the grounds that `$unwind` is 1->N and "there is
no way to express that in a window over the input". That was true of the window
as it stood, and stopped being true when `$project` was made to rebuild the
stream -- a stage that rebuilds can emit any number of documents it likes. So
Tier 2 looks like five stages rather than a rewrite. The corpus is what will
say whether that holds.
This commit is contained in:
@@ -64,6 +64,7 @@ accumulators in:
|
|||||||
```
|
```
|
||||||
group-accumulators.json 19 pass 0 fail 0 skip
|
group-accumulators.json 19 pass 0 fail 0 skip
|
||||||
expressions.json 27 pass 0 fail 0 skip
|
expressions.json 27 pass 0 fail 0 skip
|
||||||
|
document-stages.json 0 pass 24 fail 0 skip
|
||||||
```
|
```
|
||||||
|
|
||||||
`group-accumulators` found its first real disagreement on the way to 18:
|
`group-accumulators` found its first real disagreement on the way to 18:
|
||||||
@@ -72,7 +73,27 @@ that counted documents rather than numbers would have passed every test
|
|||||||
anybody would think to write by hand.
|
anybody would think to write by hand.
|
||||||
|
|
||||||
`expressions.json` was recorded before the evaluator was written and read
|
`expressions.json` was recorded before the evaluator was written and read
|
||||||
1 pass / 26 fail against it; it is green now. Expressions are exercised through
|
1 pass / 26 fail against it; it is green now. `document-stages.json` is the
|
||||||
|
next tier's spec, recorded and not implemented -- `$addFields`/`$set`,
|
||||||
|
`$unset`, `$replaceRoot`, `$unwind` and `$project`'s computed fields, none of
|
||||||
|
which this server has, so it starts at zero.
|
||||||
|
|
||||||
|
What recording *that* settled:
|
||||||
|
|
||||||
|
| | mongod |
|
||||||
|
|---|---|
|
||||||
|
| `$addFields` whose expression is missing | the field is not added at all |
|
||||||
|
| `$addFields: {"n.z": 1}` | sets the nested path, keeps its siblings |
|
||||||
|
| `$replaceRoot` of a missing path or a non-document | error 40228 |
|
||||||
|
| `$unwind` of an empty array or a missing field | the document is dropped |
|
||||||
|
| `$unwind` of a non-array | the document is kept whole |
|
||||||
|
| `$unwind` path without a `$` | error 28818 |
|
||||||
|
| `includeArrayIndex` | 0-based |
|
||||||
|
| `$project: {n: {x: 1}}` | narrows the subdocument, and a document without `n` keeps only `_id` |
|
||||||
|
|
||||||
|
That last row is the `query.project` gap recorded during M2 -- a nested
|
||||||
|
inclusion currently reads as falsy and returns the whole document minus the
|
||||||
|
field. It now has a measured expectation to be fixed against. Expressions are exercised through
|
||||||
`$group`, because `_id` and the accumulator arguments are the only expression
|
`$group`, because `_id` and the accumulator arguments are the only expression
|
||||||
positions that exist until `$addFields` and `$project`'s computed fields land.
|
positions that exist until `$addFields` and `$project`'s computed fields land.
|
||||||
|
|
||||||
|
|||||||
1245
tests/spec/aggregate/document-stages.json
Normal file
1245
tests/spec/aggregate/document-stages.json
Normal file
File diff suppressed because it is too large
Load Diff
112
tests/spec/aggregate/sources/document-stages.json
Normal file
112
tests/spec/aggregate/sources/document-stages.json
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
{
|
||||||
|
"_comment": [
|
||||||
|
"Inputs only. Expectations are measured -- see record.js.",
|
||||||
|
"The stages that rewrite a document rather than select or reorder one:",
|
||||||
|
"$addFields/$set, $unset, $replaceRoot, $unwind, and $project's computed",
|
||||||
|
"fields. A $sort at the end wherever the order is not already fixed."
|
||||||
|
],
|
||||||
|
"documents": [
|
||||||
|
{ "_id": 1, "a": 1, "n": { "x": 10, "y": 20 }, "t": [1, 2, 3] },
|
||||||
|
{ "_id": 2, "a": 2, "n": { "x": 30 }, "t": [] },
|
||||||
|
{ "_id": 3, "a": 3, "t": "not an array" },
|
||||||
|
{ "_id": 4, "a": 4, "n": { "x": 40, "y": 50 } }
|
||||||
|
],
|
||||||
|
"cases": [
|
||||||
|
{
|
||||||
|
"description": "$addFields adds a computed field",
|
||||||
|
"pipeline": [{ "$addFields": { "b": { "$multiply": ["$a", 10] } } }, { "$sort": { "_id": 1 } }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "$addFields overwrites an existing field in place",
|
||||||
|
"pipeline": [{ "$addFields": { "a": { "$add": ["$a", 100] } } }, { "$sort": { "_id": 1 } }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "$addFields with a field whose expression is missing",
|
||||||
|
"pipeline": [{ "$addFields": { "b": "$nope" } }, { "$sort": { "_id": 1 } }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "$addFields on a dotted path",
|
||||||
|
"pipeline": [{ "$addFields": { "n.z": 1 } }, { "$sort": { "_id": 1 } }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "$set is $addFields",
|
||||||
|
"pipeline": [{ "$set": { "b": { "$add": ["$a", 1] } } }, { "$sort": { "_id": 1 } }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "$unset a top-level field",
|
||||||
|
"pipeline": [{ "$unset": "a" }, { "$sort": { "_id": 1 } }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "$unset several fields",
|
||||||
|
"pipeline": [{ "$unset": ["a", "t"] }, { "$sort": { "_id": 1 } }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "$unset a nested path",
|
||||||
|
"pipeline": [{ "$unset": "n.y" }, { "$sort": { "_id": 1 } }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "$replaceRoot with a subdocument",
|
||||||
|
"pipeline": [{ "$match": { "n": { "$exists": true } } }, { "$replaceRoot": { "newRoot": "$n" } }, { "$sort": { "x": 1 } }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "$replaceRoot with a built document",
|
||||||
|
"pipeline": [{ "$replaceRoot": { "newRoot": { "id": "$_id", "double": { "$multiply": ["$a", 2] } } } }, { "$sort": { "id": 1 } }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "$replaceRoot with a missing path is an error",
|
||||||
|
"pipeline": [{ "$replaceRoot": { "newRoot": "$n" } }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "$replaceRoot with a non-document is an error",
|
||||||
|
"pipeline": [{ "$replaceRoot": { "newRoot": "$a" } }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "$unwind an array field",
|
||||||
|
"pipeline": [{ "$match": { "_id": 1 } }, { "$unwind": "$t" }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "$unwind drops empty arrays and missing fields",
|
||||||
|
"pipeline": [{ "$unwind": "$t" }, { "$sort": { "_id": 1, "t": 1 } }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "$unwind a field that is not an array keeps it whole",
|
||||||
|
"pipeline": [{ "$match": { "_id": 3 } }, { "$unwind": "$t" }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "$unwind in its document form",
|
||||||
|
"pipeline": [{ "$unwind": { "path": "$t" } }, { "$sort": { "_id": 1, "t": 1 } }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "$unwind preserving null and empty arrays",
|
||||||
|
"pipeline": [{ "$unwind": { "path": "$t", "preserveNullAndEmptyArrays": true } }, { "$sort": { "_id": 1, "t": 1 } }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "$unwind with an index field",
|
||||||
|
"pipeline": [{ "$match": { "_id": 1 } }, { "$unwind": { "path": "$t", "includeArrayIndex": "i" } }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "$unwind of a path without a $ is an error",
|
||||||
|
"pipeline": [{ "$unwind": "t" }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "$project computes a field",
|
||||||
|
"pipeline": [{ "$project": { "b": { "$add": ["$a", 1] } } }, { "$sort": { "_id": 1 } }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "$project renames with a path",
|
||||||
|
"pipeline": [{ "$project": { "value": "$a" } }, { "$sort": { "_id": 1 } }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "$project a nested inclusion",
|
||||||
|
"pipeline": [{ "$project": { "n": { "x": 1 } } }, { "$sort": { "_id": 1 } }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "$addFields then $match sees the new field",
|
||||||
|
"pipeline": [{ "$addFields": { "b": { "$multiply": ["$a", 10] } } }, { "$match": { "b": { "$gt": 20 } } }, { "$sort": { "_id": 1 } }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "$unwind then $group counts the elements",
|
||||||
|
"pipeline": [{ "$unwind": "$t" }, { "$group": { "_id": null, "n": { "$sum": 1 } } }]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user