-
Type: Task
-
Resolution: Fixed
-
Priority: Minor - P4
-
Affects Version/s: None
The $accumulator operator lets you define a custom accumulator using Javascript: https://www.mongodb.com/docs/v6.0/reference/operator/aggregation/accumulator/
Part of the contract between the user and the server is that the server is free to decide the order and grouping when it calls init()/accumulate()/merge(), and so the user is responsible for making sure these functions are insensitive to order and grouping.
We do allude to this, because we document the conditions when merge() is called: https://www.mongodb.com/docs/v6.0/reference/operator/aggregation/accumulator/#merge-two-states-with--merge. But maybe we should be more explicit about the assumptions the server makes about the user's init()/accumulate()/merge() functions.
For example, here's an example of a bad, grouping-sensitive $accumulator:
{$accumulator: { init: function () {return "a";}, accumulate: function(state, arg) {return state + arg;}, accumulateArgs: ["b"], merge: function(state1, state2) {return state1 + state2;} lang: "js" }}
This accumulator is bad because it gives you a different answer depending on how the server chooses to do the grouping:
// It can group this way: accumulate(accumulate(init(), "b"), "b") = ("a" + "b") + "b" = "abb" // or this way instead: merge(init(), accumulate(accumulate(init(), "b"), "b")) = "a" + (("a" + "b") + "b") = "aabb"
If you think something precise would be useful, I think this captures it:
// merge() is associative and commutative merge(state1, merge(state2, state3)) == merge(merge(state1, state2), state3) merge(state1, state2) == merge(state2, state1) // init() is an identity merge(init(), state) == state merge(state, init()) == state // accumulate() and merge() are related accumulate(state, value) == merge(state, accumulate(init(), value))