summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--jstests/aggregation/bugs/server14691.js52
-rw-r--r--jstests/aggregation/testshard1.js4
-rw-r--r--src/mongo/db/pipeline/accumulator_avg.cpp2
-rw-r--r--src/mongo/db/pipeline/accumulator_test.cpp2
4 files changed, 58 insertions, 2 deletions
diff --git a/jstests/aggregation/bugs/server14691.js b/jstests/aggregation/bugs/server14691.js
new file mode 100644
index 00000000000..0ba010ac41a
--- /dev/null
+++ b/jstests/aggregation/bugs/server14691.js
@@ -0,0 +1,52 @@
+// SERVER-14691: $avg aggregator should return null when it receives no input.
+(function() {
+ 'use strict';
+
+ var coll = db.accumulate_avg_sum_null;
+
+ // Test the $avg aggregator.
+ coll.drop();
+
+ // Null cases.
+ assert.writeOK(coll.insert({a: 1, b: 2, c: 'string', d: null}));
+
+ // Missing field.
+ var pipeline = [{$group: {_id: '$a', avg: {$avg: '$missing'}}}];
+ assert.eq(coll.aggregate(pipeline).toArray(), [{_id: 1, avg: null}]);
+
+ // Non-numeric field.
+ pipeline = [{$group: {_id: '$a', avg: {$avg: '$c'}}}];
+ assert.eq(coll.aggregate(pipeline).toArray(), [{_id: 1, avg: null}]);
+
+ // Field with value of null.
+ pipeline = [{$group: {_id: '$a', avg: {$avg: '$d'}}}];
+ assert.eq(coll.aggregate(pipeline).toArray(), [{_id: 1, avg: null}]);
+
+ // All three.
+ coll.insert({a: 1, d: 'string'});
+ coll.insert({a: 1});
+ pipeline = [{$group: {_id: '$a', avg: {$avg: '$d'}}}];
+ assert.eq(coll.aggregate(pipeline).toArray(), [{_id: 1, avg: null}]);
+
+ // Non-null cases.
+ coll.drop();
+ assert.writeOK(coll.insert({a: 1, b: 2}));
+ pipeline = [{$group: {_id: '$a', avg: {$avg: '$b'}}}];
+
+ // One field.
+ assert.eq(coll.aggregate(pipeline).toArray(), [{_id: 1, avg: 2}]);
+
+ // Two fields.
+ assert.writeOK(coll.insert({a: 1, b: 4}));
+ assert.eq(coll.aggregate(pipeline).toArray(), [{_id: 1, avg: 3}]);
+
+ // Average of zero should still work.
+ assert.writeOK(coll.insert({a: 1, b: -6}));
+ assert.eq(coll.aggregate(pipeline).toArray(), [{_id: 1, avg: 0}]);
+
+ // Missing, null, or non-numeric fields should not error or affect the average.
+ assert.writeOK(coll.insert({a: 1}));
+ assert.writeOK(coll.insert({a: 1, b: 'string'}));
+ assert.writeOK(coll.insert({a: 1, b: null}));
+ assert.eq(coll.aggregate(pipeline).toArray(), [{_id: 1, avg: 0}]);
+}());
diff --git a/jstests/aggregation/testshard1.js b/jstests/aggregation/testshard1.js
index 81f27e51830..71271ca0af5 100644
--- a/jstests/aggregation/testshard1.js
+++ b/jstests/aggregation/testshard1.js
@@ -129,6 +129,10 @@ jsTestLog('sum of an arithmetic progression S(n) = (n/2)(a(1) + a(n));');
assert.eq(a2[0].total, (nItems/2)*(1 + nItems),
'agg sharded test counter sum failed');
+jsTestLog('A group combining all documents into one, averaging a null field.');
+assert.eq(aggregateOrdered(db.ts1, [{$group: {_id: null, avg: {$avg: "$missing"}}}]),
+ [{_id: null, avg: null}]);
+
jsTestLog('an initial group starts the group in the shards, and combines them in mongos');
var a3 = aggregateOrdered(db.ts1, [
{ $group: {
diff --git a/src/mongo/db/pipeline/accumulator_avg.cpp b/src/mongo/db/pipeline/accumulator_avg.cpp
index cf2d5c9b677..06dd585bdf7 100644
--- a/src/mongo/db/pipeline/accumulator_avg.cpp
+++ b/src/mongo/db/pipeline/accumulator_avg.cpp
@@ -72,7 +72,7 @@ intrusive_ptr<Accumulator> AccumulatorAvg::create() {
Value AccumulatorAvg::getValue(bool toBeMerged) const {
if (!toBeMerged) {
if (_count == 0)
- return Value(0.0);
+ return Value(BSONNULL);
return Value(_total / static_cast<double>(_count));
} else {
diff --git a/src/mongo/db/pipeline/accumulator_test.cpp b/src/mongo/db/pipeline/accumulator_test.cpp
index e3e6bd04d87..9bef088316d 100644
--- a/src/mongo/db/pipeline/accumulator_test.cpp
+++ b/src/mongo/db/pipeline/accumulator_test.cpp
@@ -84,7 +84,7 @@ class None : public Base {
public:
void run() {
createAccumulator();
- ASSERT_EQUALS(0, accumulator()->getValue(false).getDouble());
+ ASSERT_EQUALS(Value(BSONNULL), accumulator()->getValue(false));
}
};