We store $setWindowFields output functions in a StringMap, which has an undefined iteration order. This can cause the field order to vary from one execution to the next. This can lead to surprising results, as illustrated in the following reproduction script:
foo = db.getSiblingDB("repro"); foo.bar.drop(); foo.bar.insert({ "obj" : { "obj" : { } } }); foo.bar.insert({});for (let i=0; i<100; i++) { res = foo.bar.aggregate([ { "$setWindowFields" : { "output" : { "obj.num" : { "$first" : 1 }, "obj.obj" : { "$first" : 2 } } } }, {"$sortByCount" : "$obj" } ]); printjson(res.toArray()); }
The output from the above script oscillates between:
[ { "_id" : { "obj" : 2, "num" : 1 }, "count" : 2 } ]
and
[{"_id": {"obj":2,"num":1},"count":1},{"_id":{"num":1,"obj":2},"count":1}]
depending on how the field order comes out.
Converting the above StringMap to a std::map fixes the oscillation issue, but it's unclear if we should do more to enforce a specific field order.
This was first spotted by the fuzzer in BF-29818.