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.
94 lines
3.8 KiB
JSON
94 lines
3.8 KiB
JSON
{
|
|
"_comment": [
|
|
"Inputs only. Expectations are measured -- see record.js.",
|
|
"Every pipeline ends in a $sort, because $group's output order is",
|
|
"unspecified and a corpus that depended on it would fail for the wrong",
|
|
"reason on either server."
|
|
],
|
|
"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] }
|
|
],
|
|
"cases": [
|
|
{
|
|
"description": "$sum over a field path",
|
|
"pipeline": [{ "$group": { "_id": "$g", "v": { "$sum": "$x" } } }, { "$sort": { "_id": 1 } }]
|
|
},
|
|
{
|
|
"description": "$sum counts with a constant",
|
|
"pipeline": [{ "$group": { "_id": "$g", "v": { "$sum": 1 } } }, { "$sort": { "_id": 1 } }]
|
|
},
|
|
{
|
|
"description": "$avg ignores non-numeric values",
|
|
"pipeline": [{ "$group": { "_id": "$g", "v": { "$avg": "$x" } } }, { "$sort": { "_id": 1 } }]
|
|
},
|
|
{
|
|
"description": "$avg of a group with no numeric value at all",
|
|
"pipeline": [{ "$group": { "_id": "$s", "v": { "$avg": "$missing" } } }, { "$sort": { "_id": 1 } }]
|
|
},
|
|
{
|
|
"description": "$min and $max compare across types",
|
|
"pipeline": [{ "$group": { "_id": "$g", "lo": { "$min": "$x" }, "hi": { "$max": "$x" } } }, { "$sort": { "_id": 1 } }]
|
|
},
|
|
{
|
|
"description": "$min of a field no document has",
|
|
"pipeline": [{ "$group": { "_id": "$g", "v": { "$min": "$missing" } } }, { "$sort": { "_id": 1 } }]
|
|
},
|
|
{
|
|
"description": "$first and $last follow the input order",
|
|
"pipeline": [{ "$sort": { "_id": 1 } }, { "$group": { "_id": "$g", "f": { "$first": "$x" }, "l": { "$last": "$x" } } }, { "$sort": { "_id": 1 } }]
|
|
},
|
|
{
|
|
"description": "$push keeps duplicates and order",
|
|
"pipeline": [{ "$sort": { "_id": 1 } }, { "$group": { "_id": "$s", "v": { "$push": "$g" } } }, { "$sort": { "_id": 1 } }]
|
|
},
|
|
{
|
|
"description": "$push of a missing field skips it",
|
|
"pipeline": [{ "$sort": { "_id": 1 } }, { "$group": { "_id": "$g", "v": { "$push": "$t" } } }, { "$sort": { "_id": 1 } }]
|
|
},
|
|
{
|
|
"description": "$addToSet drops duplicates",
|
|
"pipeline": [{ "$group": { "_id": "$s", "v": { "$addToSet": "$g" } } }, { "$sort": { "_id": 1 } }]
|
|
},
|
|
{
|
|
"description": "$count is the number of documents in the group",
|
|
"pipeline": [{ "$group": { "_id": "$g", "v": { "$count": {} } } }, { "$sort": { "_id": 1 } }]
|
|
},
|
|
{
|
|
"description": "a compound _id groups on every field",
|
|
"pipeline": [{ "$group": { "_id": { "g": "$g", "s": "$s" }, "n": { "$sum": 1 } } }, { "$sort": { "_id.g": 1, "_id.s": 1 } }]
|
|
},
|
|
{
|
|
"description": "a constant _id puts everything in one group",
|
|
"pipeline": [{ "$group": { "_id": null, "n": { "$sum": 1 } } }]
|
|
},
|
|
{
|
|
"description": "an _id path that no document has",
|
|
"pipeline": [{ "$group": { "_id": "$missing", "n": { "$sum": 1 } } }]
|
|
},
|
|
{
|
|
"description": "grouping on an array field",
|
|
"pipeline": [{ "$group": { "_id": "$t", "n": { "$sum": 1 } } }, { "$sort": { "_id": 1 } }]
|
|
},
|
|
{
|
|
"description": "an unknown accumulator is refused",
|
|
"pipeline": [{ "$group": { "_id": "$g", "v": { "$bogusAcc": "$x" } } }]
|
|
},
|
|
{
|
|
"description": "an accumulator that is not a document is refused",
|
|
"pipeline": [{ "$group": { "_id": "$g", "v": "$x" } }]
|
|
},
|
|
{
|
|
"description": "two accumulators in one field are refused",
|
|
"pipeline": [{ "$group": { "_id": "$g", "v": { "$sum": "$x", "$max": "$x" } } }]
|
|
},
|
|
{
|
|
"description": "a $group without _id is refused",
|
|
"pipeline": [{ "$group": { "n": { "$sum": 1 } } }]
|
|
}
|
|
]
|
|
}
|