tests/spec: an aggregation corpus, recorded from mongod
M2.5's gate, built before the milestone it gates -- the same order that put
`expectEvents` before the free list in M1 and Tier 0 before everything in M2.
`mongodb/specifications` has no aggregation suite, which is amendment A6's
central finding, so this milestone has to bring its own. The hazard in a corpus
we author is obvious and fatal: it can encode our own bugs as expectations and
then agree with us forever. So the split is enforced by the tooling.
`sources/*.json` holds documents and pipelines and nothing else; `record.js`
asks a real mongod 8.3.7 what each pipeline answers and writes the unified-
format file from the reply. Inputs authored, expectations measured -- the
discipline that corrected three assumptions in M1's session work and every
error code in M2, where the alternative would have shipped both times.
No second runner. `run.js --suite-dir` points the existing one somewhere else,
so the entity model, the matchers, the skip accounting and `expectEvents` come
for free; a second runner would drift from the first exactly where it mattered.
`--scorecard` is refused with `--suite-dir`, because `scorecard.txt` is the crud
corpus's record and the milestones are compared against it -- writing it from an
unrelated run would replace that record silently.
Errors record the code and not the message: message text is mongod's to change
between releases. Group pipelines end in a `$sort`, because group output order
is unspecified and a case depending on it would fail for the wrong reason on
either server.
The first source covers `$group`: nine accumulators including the edge cases
that decide an implementation -- `$avg` over a group whose values are not
numbers, `$min` of a field no document has, `$push` skipping a missing field,
`$first`/`$last` against input order, grouping on an array, a compound `_id`.
Where it starts, run against the M2 tip:
group-accumulators.json 9 pass 10 fail 0 skip
The nine include the four refusals M2 added, which answer with mongod's own
codes -- so the corpus already confirms that half. The ten are the milestone.
The crud corpus is unchanged at 201/90/196.
This commit is contained in:
807
tests/spec/aggregate/group-accumulators.json
Normal file
807
tests/spec/aggregate/group-accumulators.json
Normal file
@@ -0,0 +1,807 @@
|
||||
{
|
||||
"description": "group-accumulators",
|
||||
"schemaVersion": "1.4",
|
||||
"createEntities": [
|
||||
{
|
||||
"client": {
|
||||
"id": "client0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"database": {
|
||||
"id": "database0",
|
||||
"client": "client0",
|
||||
"databaseName": "aggregate-corpus"
|
||||
}
|
||||
},
|
||||
{
|
||||
"collection": {
|
||||
"id": "collection0",
|
||||
"database": "database0",
|
||||
"collectionName": "coll"
|
||||
}
|
||||
}
|
||||
],
|
||||
"initialData": [
|
||||
{
|
||||
"collectionName": "coll",
|
||||
"databaseName": "aggregate-corpus",
|
||||
"documents": [
|
||||
{
|
||||
"_id": 1,
|
||||
"g": "a",
|
||||
"x": 10,
|
||||
"s": "p",
|
||||
"t": [
|
||||
1,
|
||||
2
|
||||
]
|
||||
},
|
||||
{
|
||||
"_id": 2,
|
||||
"g": "a",
|
||||
"x": 20,
|
||||
"s": "q",
|
||||
"t": [
|
||||
2,
|
||||
3
|
||||
]
|
||||
},
|
||||
{
|
||||
"_id": 3,
|
||||
"g": "b",
|
||||
"x": 30,
|
||||
"s": "p",
|
||||
"t": []
|
||||
},
|
||||
{
|
||||
"_id": 4,
|
||||
"g": "b",
|
||||
"x": 7,
|
||||
"s": "r"
|
||||
},
|
||||
{
|
||||
"_id": 5,
|
||||
"g": "b",
|
||||
"x": "not a number",
|
||||
"s": "p",
|
||||
"t": [
|
||||
4
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"tests": [
|
||||
{
|
||||
"description": "$sum over a field path",
|
||||
"operations": [
|
||||
{
|
||||
"object": "collection0",
|
||||
"name": "aggregate",
|
||||
"arguments": {
|
||||
"pipeline": [
|
||||
{
|
||||
"$group": {
|
||||
"_id": "$g",
|
||||
"v": {
|
||||
"$sum": "$x"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$sort": {
|
||||
"_id": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"expectResult": [
|
||||
{
|
||||
"_id": "a",
|
||||
"v": 30
|
||||
},
|
||||
{
|
||||
"_id": "b",
|
||||
"v": 37
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "$sum counts with a constant",
|
||||
"operations": [
|
||||
{
|
||||
"object": "collection0",
|
||||
"name": "aggregate",
|
||||
"arguments": {
|
||||
"pipeline": [
|
||||
{
|
||||
"$group": {
|
||||
"_id": "$g",
|
||||
"v": {
|
||||
"$sum": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$sort": {
|
||||
"_id": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"expectResult": [
|
||||
{
|
||||
"_id": "a",
|
||||
"v": 2
|
||||
},
|
||||
{
|
||||
"_id": "b",
|
||||
"v": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "$avg ignores non-numeric values",
|
||||
"operations": [
|
||||
{
|
||||
"object": "collection0",
|
||||
"name": "aggregate",
|
||||
"arguments": {
|
||||
"pipeline": [
|
||||
{
|
||||
"$group": {
|
||||
"_id": "$g",
|
||||
"v": {
|
||||
"$avg": "$x"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$sort": {
|
||||
"_id": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"expectResult": [
|
||||
{
|
||||
"_id": "a",
|
||||
"v": 15
|
||||
},
|
||||
{
|
||||
"_id": "b",
|
||||
"v": 18.5
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "$avg of a group with no numeric value at all",
|
||||
"operations": [
|
||||
{
|
||||
"object": "collection0",
|
||||
"name": "aggregate",
|
||||
"arguments": {
|
||||
"pipeline": [
|
||||
{
|
||||
"$group": {
|
||||
"_id": "$s",
|
||||
"v": {
|
||||
"$avg": "$missing"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$sort": {
|
||||
"_id": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"expectResult": [
|
||||
{
|
||||
"_id": "p",
|
||||
"v": null
|
||||
},
|
||||
{
|
||||
"_id": "q",
|
||||
"v": null
|
||||
},
|
||||
{
|
||||
"_id": "r",
|
||||
"v": null
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "$min and $max compare across types",
|
||||
"operations": [
|
||||
{
|
||||
"object": "collection0",
|
||||
"name": "aggregate",
|
||||
"arguments": {
|
||||
"pipeline": [
|
||||
{
|
||||
"$group": {
|
||||
"_id": "$g",
|
||||
"lo": {
|
||||
"$min": "$x"
|
||||
},
|
||||
"hi": {
|
||||
"$max": "$x"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$sort": {
|
||||
"_id": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"expectResult": [
|
||||
{
|
||||
"_id": "a",
|
||||
"lo": 10,
|
||||
"hi": 20
|
||||
},
|
||||
{
|
||||
"_id": "b",
|
||||
"lo": 7,
|
||||
"hi": "not a number"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "$min of a field no document has",
|
||||
"operations": [
|
||||
{
|
||||
"object": "collection0",
|
||||
"name": "aggregate",
|
||||
"arguments": {
|
||||
"pipeline": [
|
||||
{
|
||||
"$group": {
|
||||
"_id": "$g",
|
||||
"v": {
|
||||
"$min": "$missing"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$sort": {
|
||||
"_id": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"expectResult": [
|
||||
{
|
||||
"_id": "a",
|
||||
"v": null
|
||||
},
|
||||
{
|
||||
"_id": "b",
|
||||
"v": null
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "$first and $last follow the input order",
|
||||
"operations": [
|
||||
{
|
||||
"object": "collection0",
|
||||
"name": "aggregate",
|
||||
"arguments": {
|
||||
"pipeline": [
|
||||
{
|
||||
"$sort": {
|
||||
"_id": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"$group": {
|
||||
"_id": "$g",
|
||||
"f": {
|
||||
"$first": "$x"
|
||||
},
|
||||
"l": {
|
||||
"$last": "$x"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$sort": {
|
||||
"_id": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"expectResult": [
|
||||
{
|
||||
"_id": "a",
|
||||
"f": 10,
|
||||
"l": 20
|
||||
},
|
||||
{
|
||||
"_id": "b",
|
||||
"f": 30,
|
||||
"l": "not a number"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "$push keeps duplicates and order",
|
||||
"operations": [
|
||||
{
|
||||
"object": "collection0",
|
||||
"name": "aggregate",
|
||||
"arguments": {
|
||||
"pipeline": [
|
||||
{
|
||||
"$sort": {
|
||||
"_id": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"$group": {
|
||||
"_id": "$s",
|
||||
"v": {
|
||||
"$push": "$g"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$sort": {
|
||||
"_id": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"expectResult": [
|
||||
{
|
||||
"_id": "p",
|
||||
"v": [
|
||||
"a",
|
||||
"b",
|
||||
"b"
|
||||
]
|
||||
},
|
||||
{
|
||||
"_id": "q",
|
||||
"v": [
|
||||
"a"
|
||||
]
|
||||
},
|
||||
{
|
||||
"_id": "r",
|
||||
"v": [
|
||||
"b"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "$push of a missing field skips it",
|
||||
"operations": [
|
||||
{
|
||||
"object": "collection0",
|
||||
"name": "aggregate",
|
||||
"arguments": {
|
||||
"pipeline": [
|
||||
{
|
||||
"$sort": {
|
||||
"_id": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"$group": {
|
||||
"_id": "$g",
|
||||
"v": {
|
||||
"$push": "$t"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$sort": {
|
||||
"_id": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"expectResult": [
|
||||
{
|
||||
"_id": "a",
|
||||
"v": [
|
||||
[
|
||||
1,
|
||||
2
|
||||
],
|
||||
[
|
||||
2,
|
||||
3
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"_id": "b",
|
||||
"v": [
|
||||
[],
|
||||
[
|
||||
4
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "$addToSet drops duplicates",
|
||||
"operations": [
|
||||
{
|
||||
"object": "collection0",
|
||||
"name": "aggregate",
|
||||
"arguments": {
|
||||
"pipeline": [
|
||||
{
|
||||
"$group": {
|
||||
"_id": "$s",
|
||||
"v": {
|
||||
"$addToSet": "$g"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$sort": {
|
||||
"_id": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"expectResult": [
|
||||
{
|
||||
"_id": "p",
|
||||
"v": [
|
||||
"a",
|
||||
"b"
|
||||
]
|
||||
},
|
||||
{
|
||||
"_id": "q",
|
||||
"v": [
|
||||
"a"
|
||||
]
|
||||
},
|
||||
{
|
||||
"_id": "r",
|
||||
"v": [
|
||||
"b"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "$count is the number of documents in the group",
|
||||
"operations": [
|
||||
{
|
||||
"object": "collection0",
|
||||
"name": "aggregate",
|
||||
"arguments": {
|
||||
"pipeline": [
|
||||
{
|
||||
"$group": {
|
||||
"_id": "$g",
|
||||
"v": {
|
||||
"$count": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$sort": {
|
||||
"_id": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"expectResult": [
|
||||
{
|
||||
"_id": "a",
|
||||
"v": 2
|
||||
},
|
||||
{
|
||||
"_id": "b",
|
||||
"v": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "a compound _id groups on every field",
|
||||
"operations": [
|
||||
{
|
||||
"object": "collection0",
|
||||
"name": "aggregate",
|
||||
"arguments": {
|
||||
"pipeline": [
|
||||
{
|
||||
"$group": {
|
||||
"_id": {
|
||||
"g": "$g",
|
||||
"s": "$s"
|
||||
},
|
||||
"n": {
|
||||
"$sum": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$sort": {
|
||||
"_id.g": 1,
|
||||
"_id.s": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"expectResult": [
|
||||
{
|
||||
"_id": {
|
||||
"g": "a",
|
||||
"s": "p"
|
||||
},
|
||||
"n": 1
|
||||
},
|
||||
{
|
||||
"_id": {
|
||||
"g": "a",
|
||||
"s": "q"
|
||||
},
|
||||
"n": 1
|
||||
},
|
||||
{
|
||||
"_id": {
|
||||
"g": "b",
|
||||
"s": "p"
|
||||
},
|
||||
"n": 2
|
||||
},
|
||||
{
|
||||
"_id": {
|
||||
"g": "b",
|
||||
"s": "r"
|
||||
},
|
||||
"n": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "a constant _id puts everything in one group",
|
||||
"operations": [
|
||||
{
|
||||
"object": "collection0",
|
||||
"name": "aggregate",
|
||||
"arguments": {
|
||||
"pipeline": [
|
||||
{
|
||||
"$group": {
|
||||
"_id": null,
|
||||
"n": {
|
||||
"$sum": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"expectResult": [
|
||||
{
|
||||
"_id": null,
|
||||
"n": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "an _id path that no document has",
|
||||
"operations": [
|
||||
{
|
||||
"object": "collection0",
|
||||
"name": "aggregate",
|
||||
"arguments": {
|
||||
"pipeline": [
|
||||
{
|
||||
"$group": {
|
||||
"_id": "$missing",
|
||||
"n": {
|
||||
"$sum": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"expectResult": [
|
||||
{
|
||||
"_id": null,
|
||||
"n": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "grouping on an array field",
|
||||
"operations": [
|
||||
{
|
||||
"object": "collection0",
|
||||
"name": "aggregate",
|
||||
"arguments": {
|
||||
"pipeline": [
|
||||
{
|
||||
"$group": {
|
||||
"_id": "$t",
|
||||
"n": {
|
||||
"$sum": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$sort": {
|
||||
"_id": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"expectResult": [
|
||||
{
|
||||
"_id": null,
|
||||
"n": 1
|
||||
},
|
||||
{
|
||||
"_id": [],
|
||||
"n": 1
|
||||
},
|
||||
{
|
||||
"_id": [
|
||||
1,
|
||||
2
|
||||
],
|
||||
"n": 1
|
||||
},
|
||||
{
|
||||
"_id": [
|
||||
2,
|
||||
3
|
||||
],
|
||||
"n": 1
|
||||
},
|
||||
{
|
||||
"_id": [
|
||||
4
|
||||
],
|
||||
"n": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "an unknown accumulator is refused",
|
||||
"operations": [
|
||||
{
|
||||
"object": "collection0",
|
||||
"name": "aggregate",
|
||||
"arguments": {
|
||||
"pipeline": [
|
||||
{
|
||||
"$group": {
|
||||
"_id": "$g",
|
||||
"v": {
|
||||
"$bogusAcc": "$x"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"expectError": {
|
||||
"isError": true,
|
||||
"errorCode": 15952
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "an accumulator that is not a document is refused",
|
||||
"operations": [
|
||||
{
|
||||
"object": "collection0",
|
||||
"name": "aggregate",
|
||||
"arguments": {
|
||||
"pipeline": [
|
||||
{
|
||||
"$group": {
|
||||
"_id": "$g",
|
||||
"v": "$x"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"expectError": {
|
||||
"isError": true,
|
||||
"errorCode": 40234
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "two accumulators in one field are refused",
|
||||
"operations": [
|
||||
{
|
||||
"object": "collection0",
|
||||
"name": "aggregate",
|
||||
"arguments": {
|
||||
"pipeline": [
|
||||
{
|
||||
"$group": {
|
||||
"_id": "$g",
|
||||
"v": {
|
||||
"$sum": "$x",
|
||||
"$max": "$x"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"expectError": {
|
||||
"isError": true,
|
||||
"errorCode": 40238
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "a $group without _id is refused",
|
||||
"operations": [
|
||||
{
|
||||
"object": "collection0",
|
||||
"name": "aggregate",
|
||||
"arguments": {
|
||||
"pipeline": [
|
||||
{
|
||||
"$group": {
|
||||
"n": {
|
||||
"$sum": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"expectError": {
|
||||
"isError": true,
|
||||
"errorCode": 15955
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user