summaryrefslogtreecommitdiff
path: root/jstests/noPassthrough/agg_group.js
blob: 654de425e79f1af141585e624a159c8f2c90db69 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
// Tests that $group pushdown to SBE feature works in a sharded environment for some special
// scenarios.
//
// Notes:
// - In a sharded environment, the mongos splits a $group stage into two different stages. One is a
// merge $group stage at the mongos-side which does the global aggregation and the other is a $group
// stage at the shard-side which does the partial aggregation.
// - All aggregation features are tested by aggregation test suites under a sharded environment
// through passthrough tests. So, this test suite focuses on some special scenarios like for
// example, $group is pushed down to SBE at the shard-side and some accumulators may return the
// partial aggregation results in a special format to the mongos.
//
// @tags: [requires_sharding]
(function() {
'use strict';

load("jstests/libs/analyze_plan.js");

const st = new ShardingTest({config: 1, shards: 1});

// This database name can provide multiple similar test cases with a good separate namespace and
// each test case may create a separate collection for its own dataset.
const db = st.getDB(jsTestName());
const dbAtShard = st.shard0.getDB(jsTestName());

// Makes sure that the test db is sharded and the data is stored into the only shard.
assert.commandWorked(st.s0.adminCommand({enableSharding: db.getName()}));
st.ensurePrimaryShard(db.getName(), st.shard0.shardName);

let assertShardedGroupResultsMatch = (coll, pipeline) => {
    // Turns to the classic engine at the shard before figuring out its result.
    assert.commandWorked(dbAtShard.adminCommand(
        {setParameter: 1, internalQueryFrameworkControl: "forceClassicEngine"}));

    // Collects the classic engine's result as the expected result, executing the pipeline at the
    // mongos.
    const classicalRes =
        coll.runCommand({aggregate: coll.getName(), pipeline: pipeline, cursor: {}})
            .cursor.firstBatch;

    // Turns to the SBE engine at the shard.
    assert.commandWorked(
        dbAtShard.adminCommand({setParameter: 1, internalQueryFrameworkControl: "trySbeEngine"}));

    // Verifies that the SBE engine's results are same as the expected results, executing the
    // pipeline at the mongos.
    const sbeRes = coll.runCommand({aggregate: coll.getName(), pipeline: pipeline, cursor: {}})
                       .cursor.firstBatch;

    assert.sameMembers(sbeRes, classicalRes);
};

let prepareCollection = coll => {
    coll.drop();

    // Makes sure that the collection is sharded.
    assert.commandWorked(st.s0.adminCommand({shardCollection: coll.getFullName(), key: {_id: 1}}));

    return coll;
};

// A test case for a sharded $sum

let coll = prepareCollection(db.partial_sum);

// Prepares data for the 'NumberLong' sum result to overflow, when the shard sends back the partial
// sum as a doc with 'subTotal' and 'subTotalError' fields to the mongos. All data go to the only
// shard and so overflow will happen.
assert.commandWorked(
    coll.insert([{a: 1, b: NumberLong("9223372036854775807")}, {a: 2, b: NumberLong("10")}]));

assertShardedGroupResultsMatch(coll, [{$group: {_id: "$a", s: {$sum: "$b"}}}]);
assertShardedGroupResultsMatch(coll, [{$group: {_id: null, s: {$sum: "$b"}}}]);

// Test cases for a sharded $stdDevPop and $stdDevSamp

coll = prepareCollection(db.partial_std_dev);
assert.commandWorked(coll.insert([
    {"item": "a", "price": 10},
    {"item": "b", "price": 20},
    {"item": "a", "price": 5},
    {"item": "b", "price": 10},
    {"item": "c", "price": 5},
]));
assertShardedGroupResultsMatch(coll, [{$group: {_id: "$item", sd: {$stdDevSamp: "$price"}}}]);
assertShardedGroupResultsMatch(coll, [{$group: {_id: "$item", sd: {$stdDevSamp: "$missing"}}}]);
assertShardedGroupResultsMatch(coll, [{$group: {_id: "$item", sd: {$stdDevPop: "$price"}}}]);
assertShardedGroupResultsMatch(coll, [{$group: {_id: "$item", sd: {$stdDevPop: "$missing"}}}]);

// A test case for a sharded $avg

coll = prepareCollection(db.partial_avg);

// Prepares dataset so that each group has different numeric data types for price field which
// will excercise different code paths in generated SBE plan stages.
// Prices for group "a" are all decimals.
assert.commandWorked(coll.insert(
    [{item: "a", price: NumberDecimal("10.7")}, {item: "a", price: NumberDecimal("20.3")}]));
// Prices for group "b" are one decimal and one non-decimal.
assert.commandWorked(
    coll.insert([{item: "b", price: NumberDecimal("3.7")}, {item: "b", price: 2.3}]));
// Prices for group "c" are all non-decimals.
assert.commandWorked(coll.insert([{item: "c", price: 3}, {item: "b", price: 1}]));

// Verifies that SBE group pushdown with $avg works in a sharded environment.
assertShardedGroupResultsMatch(coll, [{$group: {_id: "$item", a: {$avg: "$price"}}}]);

// Verifies that SBE group pushdown with sharded $avg works for missing data.
assertShardedGroupResultsMatch(coll, [{$group: {_id: "$item", a: {$avg: "$missing"}}}]);

st.stop();
}());