summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--jstests/core/mr_single_reduce.js18
-rw-r--r--jstests/noPassthrough/mr_single_reduce_optimization.js29
-rw-r--r--src/mongo/db/pipeline/SConscript6
-rw-r--r--src/mongo/db/pipeline/accumulator_js_reduce.cpp5
-rw-r--r--src/mongo/db/pipeline/accumulator_js_test.cpp33
-rw-r--r--src/mongo/db/pipeline/map_reduce_options.idl42
6 files changed, 132 insertions, 1 deletions
diff --git a/jstests/core/mr_single_reduce.js b/jstests/core/mr_single_reduce.js
new file mode 100644
index 00000000000..28e66c71c72
--- /dev/null
+++ b/jstests/core/mr_single_reduce.js
@@ -0,0 +1,18 @@
+(function() {
+"use strict";
+const coll = db.bar;
+
+assert.commandWorked(coll.insert({x: 1}));
+
+const map = function() {
+ emit(0, "mapped value");
+};
+
+const reduce = function(key, values) {
+ return "reduced value";
+};
+
+const res = assert.commandWorked(
+ db.runCommand({mapReduce: 'bar', map: map, reduce: reduce, out: {inline: 1}}));
+assert.eq(res.results[0], {_id: 0, value: "reduced value"});
+}());
diff --git a/jstests/noPassthrough/mr_single_reduce_optimization.js b/jstests/noPassthrough/mr_single_reduce_optimization.js
new file mode 100644
index 00000000000..71ab924836b
--- /dev/null
+++ b/jstests/noPassthrough/mr_single_reduce_optimization.js
@@ -0,0 +1,29 @@
+(function() {
+"use strict";
+// See SERVER-68766. Verify that the reduce function is not run on a single value if the relevant
+// flag is enabled.
+
+const conn = MongoRunner.runMongod({setParameter: {mrEnableSingleReduceOptimization: true}});
+const testDB = conn.getDB('foo');
+const coll = testDB.bar;
+
+assert.commandWorked(coll.insert({x: 1}));
+
+const map = function() {
+ emit(0, "mapped value");
+};
+
+const reduce = function(key, values) {
+ return "reduced value";
+};
+
+let res = assert.commandWorked(
+ testDB.runCommand({mapReduce: 'bar', map: map, reduce: reduce, out: {inline: 1}}));
+assert.eq(res.results[0], {_id: 0, value: "mapped value"});
+assert.commandWorked(coll.insert({x: 2}));
+res = assert.commandWorked(
+ testDB.runCommand({mapReduce: 'bar', map: map, reduce: reduce, out: {inline: 1}}));
+assert.eq(res.results[0], {_id: 0, value: "reduced value"});
+
+MongoRunner.stopMongod(conn);
+}());
diff --git a/src/mongo/db/pipeline/SConscript b/src/mongo/db/pipeline/SConscript
index 440cbfc80f9..a5e6bf0234f 100644
--- a/src/mongo/db/pipeline/SConscript
+++ b/src/mongo/db/pipeline/SConscript
@@ -133,6 +133,7 @@ env.Library(
'accumulator_rank.cpp',
'accumulator_std_dev.cpp',
'accumulator_sum.cpp',
+ 'map_reduce_options.idl',
'window_function/window_bounds.cpp',
'window_function/window_function_covariance.cpp',
'window_function/window_function_count.cpp',
@@ -148,7 +149,10 @@ env.Library(
'$BUILD_DIR/mongo/util/summation',
'expression_context',
'field_path',
- ]
+ ],
+ LIBDEPS_PRIVATE=[
+ '$BUILD_DIR/mongo/idl/idl_parser',
+ ],
)
env.Library(
diff --git a/src/mongo/db/pipeline/accumulator_js_reduce.cpp b/src/mongo/db/pipeline/accumulator_js_reduce.cpp
index e7192465d7c..acd77254b9d 100644
--- a/src/mongo/db/pipeline/accumulator_js_reduce.cpp
+++ b/src/mongo/db/pipeline/accumulator_js_reduce.cpp
@@ -32,6 +32,7 @@
#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/db/pipeline/accumulator_js_reduce.h"
#include "mongo/db/pipeline/make_js_function.h"
+#include "mongo/db/pipeline/map_reduce_options_gen.h"
namespace mongo {
@@ -116,6 +117,10 @@ void AccumulatorInternalJsReduce::processInternal(const Value& input, bool mergi
Value AccumulatorInternalJsReduce::getValue(bool toBeMerged) {
if (_values.size() < 1) {
return Value{};
+ } else if (mrSingleReduceOptimizationEnabled && _values.size() == 1) {
+ // This optimization existed in the old Pre-4.4 MapReduce implementation. If the flag is
+ // set, then we should replicate the optimization. See SERVER-68766 for more details.
+ return _values[0];
}
const auto keySize = _key.getApproximateSize();
diff --git a/src/mongo/db/pipeline/accumulator_js_test.cpp b/src/mongo/db/pipeline/accumulator_js_test.cpp
index 953b4ce475a..2a44e8e967c 100644
--- a/src/mongo/db/pipeline/accumulator_js_test.cpp
+++ b/src/mongo/db/pipeline/accumulator_js_test.cpp
@@ -38,6 +38,7 @@
#include "mongo/db/pipeline/process_interface/standalone_process_interface.h"
#include "mongo/db/service_context_d_test_fixture.h"
#include "mongo/dbtests/dbtests.h"
+#include "mongo/idl/server_parameter_test_util.h"
#include "mongo/scripting/engine.h"
namespace mongo {
@@ -197,6 +198,38 @@ TEST_F(MapReduceFixture, InternalJsReduceFailsWhenEvalContainsInvalidJavascript)
}
}
+TEST_F(
+ MapReduceFixture,
+ InternalJsReduceFailsDependentOnDocumentCountWhenEvalIsInvalidJavascriptWithSingleReduceOpt) {
+ RAIIServerParameterControllerForTest flag("mrEnableSingleReduceOptimization", true);
+ std::string eval("INVALID_JAVASCRIPT");
+ // Multiple source documents should evaluate the passed in function and return an error with
+ // invalid javascript.
+ {
+ auto accum = AccumulatorInternalJsReduce::create(getExpCtx(), "INVALID_JAVASCRIPT");
+ auto input = Value(DOC("k" << Value(1) << "v" << Value(2)));
+ accum->process(input, false);
+ accum->process(input, false);
+
+ ASSERT_THROWS_CODE(accum->getValue(false), DBException, ErrorCodes::JSInterpreterFailure);
+ }
+
+ // Single source document. With the reduce optimization, we simply return this document rather
+ // than executing the JS engine at all, so no error is thrown.
+ {
+ auto accum = AccumulatorInternalJsReduce::create(getExpCtx(), "INVALID_JAVASCRIPT");
+
+ auto input = Value(DOC("k" << Value(1) << "v" << Value(2)));
+ auto expectedResult = Value(2);
+
+ accum->process(input, false);
+ Value result = accum->getValue(false);
+
+ ASSERT_VALUE_EQ(expectedResult, result);
+ ASSERT_EQUALS(expectedResult.getType(), result.getType());
+ }
+}
+
TEST_F(MapReduceFixture, InternalJsReduceFailsIfArgumentNotDocument) {
auto argument = Value(2);
assertProcessFailsWithCode<AccumulatorInternalJsReduce>(
diff --git a/src/mongo/db/pipeline/map_reduce_options.idl b/src/mongo/db/pipeline/map_reduce_options.idl
new file mode 100644
index 00000000000..55d5e133b6f
--- /dev/null
+++ b/src/mongo/db/pipeline/map_reduce_options.idl
@@ -0,0 +1,42 @@
+# Copyright (C) 2022-present MongoDB, Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the Server Side Public License, version 1,
+# as published by MongoDB, Inc.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# Server Side Public License for more details.
+#
+# You should have received a copy of the Server Side Public License
+# along with this program. If not, see
+# <http://www.mongodb.com/licensing/server-side-public-license>.
+#
+# As a special exception, the copyright holders give permission to link the
+# code of portions of this program with the OpenSSL library under certain
+# conditions as described in each individual source file and distribute
+# linked combinations including the program with the OpenSSL library. You
+# must comply with the Server Side Public License in all respects for
+# all of the code used other than as permitted herein. If you modify file(s)
+# with this exception, you may extend this exception to your version of the
+# file(s), but you are not obligated to do so. If you do not wish to do so,
+# delete this exception statement from your version. If you delete this
+# exception statement from all source files in the program, then also delete
+# it in the license file.
+#
+
+global:
+ cpp_namespace: "mongo"
+
+server_parameters:
+ mrEnableSingleReduceOptimization:
+ description: >
+ In version 4.2 and before, MongoDB MapReduce will not call the reduce function
+ for a key that has only a single value. In version 4.4 and later, the reduce
+ function is still called in order to validate the JavaScript reduce function
+ even when there is only one value. This setting will re-enable the old optimization.
+ set_at: startup
+ cpp_vartype: bool
+ cpp_varname: mrSingleReduceOptimizationEnabled
+ default: false