summaryrefslogtreecommitdiff
path: root/jstests/aggregation/accumulators/accumulator_js.js
blob: 2942ef641ddf4c063adfd7ce1dea86d3e36d5543 (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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
// Test the behavior of user-defined (Javascript) accumulators.
(function() {
"use strict";

load('jstests/aggregation/extras/utils.js');

db.accumulator_js.drop();

for (const word of ["hello", "world", "world", "hello", "hi"]) {
    db.accumulator_js.insert({word: word, val: 1});
}

const command = {
    aggregate: 'accumulator_js',
    cursor: {},
    pipeline: [{
        $group: {
            _id: "$word",
            wordCount: {
                $accumulator: {
                    init: function() {
                        return 0;
                    },
                    accumulateArgs: ["$val"],
                    accumulate: function(state, val) {
                        return state + val;
                    },
                    merge: function(state1, state2) {
                        return state1 + state2;
                    },
                    finalize: function(state) {
                        return state;
                    }
                }
            }
        }
    }],
};

const expectedResults = [
    {_id: "hello", wordCount: 2},
    {_id: "world", wordCount: 2},
    {_id: "hi", wordCount: 1},
];

let res = assert.commandWorked(db.runCommand(command));
assert(resultsEq(res.cursor.firstBatch, expectedResults), res.cursor);

// Test that the functions can be passed as strings.
{
    const accumulatorSpec = command.pipeline[0].$group.wordCount.$accumulator;
    accumulatorSpec.init = accumulatorSpec.init.toString();
    accumulatorSpec.accumulate = accumulatorSpec.accumulate.toString();
    accumulatorSpec.merge = accumulatorSpec.merge.toString();
    accumulatorSpec.finalize = accumulatorSpec.finalize.toString();
}
res = assert.commandWorked(db.runCommand(command));
assert(resultsEq(res.cursor.firstBatch, expectedResults), res.cursor);

// Test that finalize is optional.
delete command.pipeline[0].$group.wordCount.$accumulator.finalize;
res = assert.commandWorked(db.runCommand(command));
assert(resultsEq(res.cursor.firstBatch, expectedResults), res.cursor);

// Test a finalizer other than the identity function. Finalizers are useful when the intermediate
// state needs to be a different format from the final result.
res = assert.commandWorked(db.runCommand(Object.merge(command, {
    pipeline: [{
        $group: {
            _id: 1,
            avgWordLen: {
                $accumulator: {
                    init: function() {
                        return {count: 0, sum: 0};
                    },
                    accumulateArgs: [{$strLenCP: "$word"}],
                    accumulate: function({count, sum}, wordLen) {
                        return {count: count + 1, sum: sum + wordLen};
                    },
                    merge: function(s1, s2) {
                        return {count: s1.count + s2.count, sum: s1.sum + s2.sum};
                    },
                    finalize: function({count, sum}) {
                        return sum / count;
                    },
                    lang: 'js',
                }
            },
        }
    }],
})));
assert(resultsEq(res.cursor.firstBatch, [{_id: 1, avgWordLen: 22 / 5}]), res.cursor);

// Test that a null word is considered a valid value.
assert.commandWorked(db.accumulator_js.insert({word: null, val: 1}));
expectedResults.push({_id: null, wordCount: 1});
res = assert.commandWorked(db.runCommand(command));
assert(resultsEq(res.cursor.firstBatch, expectedResults), res.cursor);

// Test that missing fields become JS null.
// This is similar to how most other agg operators work.
// TODO SERVER-45450 is this a problem for mapreduce?
assert(db.accumulator_js.drop());
assert.commandWorked(db.accumulator_js.insert({sentinel: 1}));
command.pipeline = [{
    $group: {
        _id: 1,
        value: {
            $accumulator: {
                init: function() {
                    return [];
                },
                accumulateArgs: ["$no_such_field"],
                accumulate: function(state, value) {
                    return state.concat([value]);
                },
                merge: function(s1, s2) {
                    return s1.concat(s2);
                },
                lang: 'js',
            }
        }
    }
}];
res = assert.commandWorked(db.runCommand(command));
assert(resultsEq(res.cursor.firstBatch, [{_id: 1, value: [null]}]), res.cursor);
})();