summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--jstests/aggregation/expressions/expression_function.js (renamed from jstests/aggregation/expressions/internal_js.js)64
-rw-r--r--src/mongo/db/commands/mr_common.cpp25
-rw-r--r--src/mongo/db/pipeline/SConscript3
-rw-r--r--src/mongo/db/pipeline/expression_function.cpp112
-rw-r--r--src/mongo/db/pipeline/expression_function.h79
-rw-r--r--src/mongo/db/pipeline/expression_javascript_test.cpp88
-rw-r--r--src/mongo/db/pipeline/expression_js_emit.cpp (renamed from src/mongo/db/pipeline/expression_javascript.cpp)65
-rw-r--r--src/mongo/db/pipeline/expression_js_emit.h (renamed from src/mongo/db/pipeline/expression_javascript.h)41
-rw-r--r--src/mongo/db/pipeline/expression_visitor.h4
9 files changed, 316 insertions, 165 deletions
diff --git a/jstests/aggregation/expressions/internal_js.js b/jstests/aggregation/expressions/expression_function.js
index 63442710195..27b38bc9148 100644
--- a/jstests/aggregation/expressions/internal_js.js
+++ b/jstests/aggregation/expressions/expression_function.js
@@ -1,10 +1,10 @@
-// Tests basic functionality of the $_internalJs expression.
+// Tests basic functionality of the $function expression.
(function() {
"use strict";
load('jstests/aggregation/extras/utils.js');
-const coll = db.internal_js;
+const coll = db.expression_function;
coll.drop();
function f_finalize(first, second) {
@@ -18,9 +18,10 @@ for (let i = 0; i < 5; i++) {
let pipeline = [{
$project: {
newValue: {
- $_internalJs: {
+ $function: {
args: ["$value", -1],
- eval: f_finalize,
+ body: f_finalize,
+ lang: "js",
},
},
_id: 0,
@@ -32,14 +33,14 @@ assert(resultsEq(results,
[{newValue: -1}, {newValue: 0}, {newValue: 1}, {newValue: 2}, {newValue: 3}]),
results);
-// Test that the 'eval' function accepts a string argument.
-pipeline[0].$project.newValue.$_internalJs.eval = f_finalize.toString();
+// Test that the 'body' function accepts a string argument.
+pipeline[0].$project.newValue.$function.body = f_finalize.toString();
results = coll.aggregate(pipeline, {cursor: {}}).toArray();
assert(resultsEq(results,
[{newValue: -1}, {newValue: 0}, {newValue: 1}, {newValue: 2}, {newValue: 3}]),
results);
-// Test that internalJs can take an expression that evaluates to an array for the 'args' parameter.
+// Test that function can take an expression that evaluates to an array for the 'args' parameter.
coll.drop();
for (let i = 0; i < 5; i++) {
assert.commandWorked(coll.insert({values: [i, i * 2]}));
@@ -47,9 +48,10 @@ for (let i = 0; i < 5; i++) {
pipeline = [{
$project: {
newValue: {
- $_internalJs: {
+ $function: {
args: "$values",
- eval: f_finalize,
+ body: f_finalize,
+ lang: "js",
},
},
_id: 0,
@@ -65,9 +67,10 @@ assert(resultsEq(results,
pipeline = [{
$project: {
newValue: {
- $_internalJs: {
+ $function: {
'args': 'must evaluate to an array',
- 'eval': f_finalize,
+ 'body': f_finalize,
+ 'lang': 'js',
},
},
_id: 0,
@@ -79,9 +82,10 @@ assert.commandFailedWithCode(
pipeline = [{
$project: {
newValue: {
- $_internalJs: {
+ $function: {
'args': [1, 3],
- 'eval': 'this is not a valid function!',
+ 'body': 'this is not a valid function!',
+ 'lang': 'js',
},
},
_id: 0,
@@ -91,13 +95,43 @@ assert.commandFailedWithCode(
db.runCommand({aggregate: coll.getName(), pipeline: pipeline, cursor: {}}),
ErrorCodes.JSInterpreterFailure);
+pipeline = [{
+ $project: {
+ newValue: {
+ $function: {
+ 'args': [1, 3],
+ 'body': f_finalize,
+ },
+ },
+ _id: 0,
+ }
+}];
+assert.commandFailedWithCode(
+ db.runCommand({aggregate: coll.getName(), pipeline: pipeline, cursor: {}}), 31418);
+
+pipeline = [{
+ $project: {
+ newValue: {
+ $function: {
+ 'args': [1, 3],
+ 'body': f_finalize,
+ 'lang': 'not js!',
+ },
+ },
+ _id: 0,
+ }
+}];
+assert.commandFailedWithCode(
+ db.runCommand({aggregate: coll.getName(), pipeline: pipeline, cursor: {}}), 31419);
+
// Test that we fail if the 'args' field is not an array.
pipeline = [{
$project: {
newValue: {
- $_internalJs: {
+ $function: {
'args': "A string!",
- 'eval': f_finalize,
+ 'body': f_finalize,
+ 'lang': 'js',
},
},
_id: 0,
diff --git a/src/mongo/db/commands/mr_common.cpp b/src/mongo/db/commands/mr_common.cpp
index fcdf998e125..098c702f408 100644
--- a/src/mongo/db/commands/mr_common.cpp
+++ b/src/mongo/db/commands/mr_common.cpp
@@ -52,7 +52,8 @@
#include "mongo/db/pipeline/document_source_single_document_transformation.h"
#include "mongo/db/pipeline/document_source_sort.h"
#include "mongo/db/pipeline/document_source_unwind.h"
-#include "mongo/db/pipeline/expression_javascript.h"
+#include "mongo/db/pipeline/expression_function.h"
+#include "mongo/db/pipeline/expression_js_emit.h"
#include "mongo/db/query/util/make_data_structure.h"
#include "mongo/util/intrusive_counter.h"
#include "mongo/util/log.h"
@@ -139,14 +140,15 @@ auto translateReduce(boost::intrusive_ptr<ExpressionContext> expCtx, std::string
auto translateFinalize(boost::intrusive_ptr<ExpressionContext> expCtx,
MapReduceJavascriptCodeOrNull codeObj) {
return codeObj.getCode().map([&](auto&& code) {
- auto jsExpression = ExpressionInternalJs::create(
+ auto jsExpression = ExpressionFunction::create(
expCtx,
ExpressionArray::create(
expCtx,
make_vector<boost::intrusive_ptr<Expression>>(
ExpressionFieldPath::parse(expCtx, "$_id", expCtx->variablesParseState),
ExpressionFieldPath::parse(expCtx, "$value", expCtx->variablesParseState))),
- code);
+ code,
+ ExpressionFunction::kJavaScript);
auto node = std::make_unique<projection_executor::InclusionNode>(
ProjectionPolicies{ProjectionPolicies::DefaultIdPolicy::kIncludeId});
node->addProjectionForPath(FieldPath{"_id"s});
@@ -187,23 +189,24 @@ auto translateOutReduce(boost::intrusive_ptr<ExpressionContext> expCtx,
// Because of communication for sharding, $merge must hold on to a serializable BSON object
// at the moment so we reparse here. Note that the reduce function signature expects 2
// arguments, the first being the key and the second being the array of values to reduce.
- auto reduceObj = BSON("args" << BSON_ARRAY("$_id" << BSON_ARRAY("$value"
- << "$$new.value"))
- << "eval" << reduceCode);
+ auto reduceObj =
+ BSON("args" << BSON_ARRAY("$_id" << BSON_ARRAY("$value"
+ << "$$new.value"))
+ << "body" << reduceCode << "lang" << ExpressionFunction::kJavaScript);
- auto reduceSpec =
- BSON(DocumentSourceProject::kStageName
- << BSON("value" << BSON(ExpressionInternalJs::kExpressionName << reduceObj)));
+ auto reduceSpec = BSON(DocumentSourceProject::kStageName << BSON(
+ "value" << BSON(ExpressionFunction::kExpressionName << reduceObj)));
auto pipelineSpec = boost::make_optional(std::vector<BSONObj>{reduceSpec});
// Build finalize $project stage if given.
if (finalizeCode && finalizeCode->hasCode()) {
auto finalizeObj = BSON("args" << BSON_ARRAY("$_id"
<< "$value")
- << "eval" << finalizeCode->getCode().get());
+ << "body" << finalizeCode->getCode().get() << "lang"
+ << ExpressionFunction::kJavaScript);
auto finalizeSpec =
BSON(DocumentSourceProject::kStageName
- << BSON("value" << BSON(ExpressionInternalJs::kExpressionName << finalizeObj)));
+ << BSON("value" << BSON(ExpressionFunction::kExpressionName << finalizeObj)));
pipelineSpec->emplace_back(std::move(finalizeSpec));
}
diff --git a/src/mongo/db/pipeline/SConscript b/src/mongo/db/pipeline/SConscript
index fa27da05155..45dceed8cf2 100644
--- a/src/mongo/db/pipeline/SConscript
+++ b/src/mongo/db/pipeline/SConscript
@@ -103,7 +103,8 @@ env.Library(
env.Library(
target='expression_javascript',
source=[
- 'expression_javascript.cpp'
+ 'expression_function.cpp',
+ 'expression_js_emit.cpp'
],
LIBDEPS=[
'$BUILD_DIR/mongo/scripting/scripting_common',
diff --git a/src/mongo/db/pipeline/expression_function.cpp b/src/mongo/db/pipeline/expression_function.cpp
new file mode 100644
index 00000000000..399a8847e03
--- /dev/null
+++ b/src/mongo/db/pipeline/expression_function.cpp
@@ -0,0 +1,112 @@
+/**
+ * Copyright (C) 2019-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.
+ */
+
+#include "mongo/db/pipeline/expression_function.h"
+
+namespace mongo {
+
+REGISTER_EXPRESSION_WITH_MIN_VERSION(
+ function,
+ ExpressionFunction::parse,
+ ServerGlobalParams::FeatureCompatibility::Version::kFullyUpgradedTo44);
+
+ExpressionFunction::ExpressionFunction(const boost::intrusive_ptr<ExpressionContext>& expCtx,
+ boost::intrusive_ptr<Expression> passedArgs,
+ std::string funcSource,
+ std::string lang)
+ : Expression(expCtx, {std::move(passedArgs)}),
+ _passedArgs(_children[0]),
+ _funcSource(std::move(funcSource)),
+ _lang(std::move(lang)) {}
+
+Value ExpressionFunction::serialize(bool explain) const {
+ return Value(Document{{kExpressionName,
+ Document{{"body", _funcSource},
+ {"args", _passedArgs->serialize(explain)},
+ {"lang", _lang}}}});
+}
+
+void ExpressionFunction::_doAddDependencies(mongo::DepsTracker* deps) const {
+ _children[0]->addDependencies(deps);
+}
+
+boost::intrusive_ptr<Expression> ExpressionFunction::parse(
+ const boost::intrusive_ptr<ExpressionContext>& expCtx,
+ BSONElement expr,
+ const VariablesParseState& vps) {
+
+ uassert(31260,
+ str::stream() << kExpressionName
+ << " requires an object as an argument, found: " << expr.type(),
+ expr.type() == BSONType::Object);
+
+ BSONElement bodyField = expr["body"];
+
+ uassert(31261, "The body function must be specified.", bodyField);
+
+ boost::intrusive_ptr<Expression> bodyExpr = Expression::parseOperand(expCtx, bodyField, vps);
+
+ auto bodyConst = dynamic_cast<ExpressionConstant*>(bodyExpr.get());
+ uassert(31432, "The body function must be a constant expression", bodyConst);
+
+ auto bodyValue = bodyConst->getValue();
+ uassert(31262,
+ "The body function must evaluate to type string or code",
+ bodyValue.getType() == BSONType::String || bodyValue.getType() == BSONType::Code);
+
+ BSONElement argsField = expr["args"];
+ uassert(31263, "The args field must be specified.", argsField);
+ boost::intrusive_ptr<Expression> argsExpr = parseOperand(expCtx, argsField, vps);
+
+ BSONElement langField = expr["lang"];
+ uassert(31418, "The lang field must be specified.", langField);
+ uassert(31419,
+ "Currently the only supported language specifier is 'js'.",
+ langField.type() == BSONType::String && langField.str() == kJavaScript);
+
+ return new ExpressionFunction(expCtx, argsExpr, bodyValue.coerceToString(), langField.str());
+}
+
+Value ExpressionFunction::evaluate(const Document& root, Variables* variables) const {
+ auto jsExec = getExpressionContext()->getJsExecWithScope();
+
+ ScriptingFunction func = jsExec->getScope()->createFunction(_funcSource.c_str());
+ uassert(31265, "The body function did not evaluate", func);
+
+ auto argValue = _passedArgs->evaluate(root, variables);
+ uassert(31266, "The args field must be of type array", argValue.getType() == BSONType::Array);
+
+ int argNum = 0;
+ BSONObjBuilder bob;
+ for (const auto& arg : argValue.getArray()) {
+ arg.addToBsonObj(&bob, "arg" + std::to_string(argNum++));
+ }
+ return jsExec->callFunction(func, bob.done(), {});
+};
+} // namespace mongo \ No newline at end of file
diff --git a/src/mongo/db/pipeline/expression_function.h b/src/mongo/db/pipeline/expression_function.h
new file mode 100644
index 00000000000..3d8d5e2df7b
--- /dev/null
+++ b/src/mongo/db/pipeline/expression_function.h
@@ -0,0 +1,79 @@
+/**
+ * Copyright (C) 2019-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.
+ */
+
+#pragma once
+
+#include "mongo/db/pipeline/expression.h"
+#include "mongo/db/pipeline/javascript_execution.h"
+
+namespace mongo {
+/**
+ * This expression takes a function, an array of arguments to pass to it, and the language
+ * specifier (currently limited to JavaScript). It returns the return value of the function with
+ * the given arguments.
+ */
+class ExpressionFunction final : public Expression {
+public:
+ static boost::intrusive_ptr<Expression> parse(
+ const boost::intrusive_ptr<ExpressionContext>& expCtx,
+ BSONElement expr,
+ const VariablesParseState& vps);
+
+ static boost::intrusive_ptr<ExpressionFunction> create(
+ const boost::intrusive_ptr<ExpressionContext>& expCtx,
+ boost::intrusive_ptr<Expression> passedArgs,
+ std::string funcSourceString,
+ std::string lang) {
+ return new ExpressionFunction{
+ expCtx, passedArgs, std::move(funcSourceString), std::move(lang)};
+ }
+
+ Value evaluate(const Document& root, Variables* variables) const final;
+
+ Value serialize(bool explain) const final;
+
+ void acceptVisitor(ExpressionVisitor* visitor) final {
+ return visitor->visit(this);
+ }
+
+ static constexpr auto kExpressionName = "$function"_sd;
+ static constexpr auto kJavaScript = "js";
+
+private:
+ ExpressionFunction(const boost::intrusive_ptr<ExpressionContext>& expCtx,
+ boost::intrusive_ptr<Expression> passedArgs,
+ std::string funcSourceString,
+ std::string lang);
+ void _doAddDependencies(DepsTracker* deps) const final override;
+
+ const boost::intrusive_ptr<Expression>& _passedArgs;
+ std::string _funcSource;
+ std::string _lang;
+};
+} // namespace mongo \ No newline at end of file
diff --git a/src/mongo/db/pipeline/expression_javascript_test.cpp b/src/mongo/db/pipeline/expression_javascript_test.cpp
index 43539f7bf75..f11d4c0e7ef 100644
--- a/src/mongo/db/pipeline/expression_javascript_test.cpp
+++ b/src/mongo/db/pipeline/expression_javascript_test.cpp
@@ -27,12 +27,13 @@
* it in the license file.
*/
-#include "mongo/db/pipeline/expression_javascript.h"
+#include "mongo/db/pipeline/expression_js_emit.h"
#include "mongo/db/commands/test_commands_enabled.h"
#include "mongo/db/exec/document_value/document.h"
#include "mongo/db/exec/document_value/document_value_test_util.h"
#include "mongo/db/pipeline/expression_context_for_test.h"
+#include "mongo/db/pipeline/expression_function.h"
#include "mongo/db/pipeline/process_interface/non_shardsvr_process_interface.h"
#include "mongo/db/query/query_knobs_gen.h"
#include "mongo/db/service_context_d_test_fixture.h"
@@ -82,80 +83,105 @@ void MapReduceFixture::tearDown() {
ServiceContextMongoDTest::tearDown();
}
-TEST_F(MapReduceFixture, ExpressionInternalJsProducesExpectedResult) {
- auto bsonExpr = BSON("expr" << BSON("eval"
+TEST_F(MapReduceFixture, ExpressionFunctionProducesExpectedResult) {
+ auto bsonExpr = BSON("expr" << BSON("body"
<< "function(first, second) {return first + second;};"
- << "args" << BSON_ARRAY("$a" << 4)));
+ << "args" << BSON_ARRAY("$a" << 4) << "lang"
+ << ExpressionFunction::kJavaScript));
- auto expr = ExpressionInternalJs::parse(getExpCtx(), bsonExpr.firstElement(), getVPS());
+ auto expr = ExpressionFunction::parse(getExpCtx(), bsonExpr.firstElement(), getVPS());
Value result = expr->evaluate(Document{BSON("a" << 2)}, getVariables());
ASSERT_VALUE_EQ(result, Value(6));
bsonExpr =
- BSON("expr" << BSON("eval"
+ BSON("expr" << BSON("body"
<< "function(first, second, third) {return first + second + third;};"
- << "args" << BSON_ARRAY(1 << 2 << 4)));
- expr = ExpressionInternalJs::parse(getExpCtx(), bsonExpr.firstElement(), getVPS());
+ << "args" << BSON_ARRAY(1 << 2 << 4) << "lang"
+ << ExpressionFunction::kJavaScript));
+ expr = ExpressionFunction::parse(getExpCtx(), bsonExpr.firstElement(), getVPS());
result = expr->evaluate(Document{BSONObj{}}, getVariables());
ASSERT_VALUE_EQ(result, Value(7));
- bsonExpr = BSON("expr" << BSON("eval"
+ bsonExpr = BSON("expr" << BSON("body"
<< "function(first) {return first;};"
- << "args" << BSON_ARRAY(1)));
- expr = ExpressionInternalJs::parse(getExpCtx(), bsonExpr.firstElement(), getVPS());
+ << "args" << BSON_ARRAY(1) << "lang"
+ << ExpressionFunction::kJavaScript));
+ expr = ExpressionFunction::parse(getExpCtx(), bsonExpr.firstElement(), getVPS());
result = expr->evaluate(Document{BSONObj{}}, getVariables());
ASSERT_VALUE_EQ(result, Value(1));
}
-TEST_F(MapReduceFixture, ExpressionInternalJsFailsIfArgsDoesNotEvaluateToArray) {
- auto bsonExpr = BSON("expr" << BSON("eval"
+TEST_F(MapReduceFixture, ExpressionFunctionFailsIfArgsDoesNotEvaluateToArray) {
+ auto bsonExpr = BSON("expr" << BSON("body"
<< "function(first, second) {return first + second;};"
- << "args" << BSON("a" << 1)));
- auto expr = ExpressionInternalJs::parse(getExpCtx(), bsonExpr.firstElement(), getVPS());
+ << "args" << BSON("a" << 1) << "lang"
+ << ExpressionFunction::kJavaScript));
+ auto expr = ExpressionFunction::parse(getExpCtx(), bsonExpr.firstElement(), getVPS());
ASSERT_THROWS_CODE(expr->evaluate({}, getVariables()), AssertionException, 31266);
}
-TEST_F(MapReduceFixture, ExpressionInternalJsFailsWithInvalidFunction) {
- auto bsonExpr = BSON("expr" << BSON("eval"
+TEST_F(MapReduceFixture, ExpressionFunctionFailsWithInvalidFunction) {
+ auto bsonExpr = BSON("expr" << BSON("body"
<< "INVALID"
- << "args" << BSON_ARRAY(1 << 2)));
- auto expr = ExpressionInternalJs::parse(getExpCtx(), bsonExpr.firstElement(), getVPS());
+ << "args" << BSON_ARRAY(1 << 2) << "lang"
+ << ExpressionFunction::kJavaScript));
+ auto expr = ExpressionFunction::parse(getExpCtx(), bsonExpr.firstElement(), getVPS());
ASSERT_THROWS_CODE(
expr->evaluate({}, getVariables()), AssertionException, ErrorCodes::JSInterpreterFailure);
}
-TEST_F(MapReduceFixture, ExpressionInternalJsFailsIfArgumentIsNotObject) {
+TEST_F(MapReduceFixture, ExpressionFunctionFailsIfArgumentIsNotObject) {
auto bsonExpr = BSON("expr" << 1);
- ASSERT_THROWS_CODE(ExpressionInternalJs::parse(getExpCtx(), bsonExpr.firstElement(), getVPS()),
+ ASSERT_THROWS_CODE(ExpressionFunction::parse(getExpCtx(), bsonExpr.firstElement(), getVPS()),
AssertionException,
31260);
}
-TEST_F(MapReduceFixture, ExpressionInternalJsFailsIfEvalNotSpecified) {
- auto bsonExpr = BSON("expr" << BSON("args" << BSON_ARRAY(1 << 2)));
- ASSERT_THROWS_CODE(ExpressionInternalJs::parse(getExpCtx(), bsonExpr.firstElement(), getVPS()),
+TEST_F(MapReduceFixture, ExpressionFunctionFailsIfBodyNotSpecified) {
+ auto bsonExpr = BSON(
+ "expr" << BSON("args" << BSON_ARRAY(1 << 2) << "lang" << ExpressionFunction::kJavaScript));
+ ASSERT_THROWS_CODE(ExpressionFunction::parse(getExpCtx(), bsonExpr.firstElement(), getVPS()),
AssertionException,
31261);
}
-TEST_F(MapReduceFixture, ExpressionInternalJsFailsIfEvalIsNotCorrectType) {
- auto bsonExpr = BSON("expr" << BSON("eval" << BSONObj() << "args" << BSON_ARRAY(1 << 2)));
- ASSERT_THROWS_CODE(ExpressionInternalJs::parse(getExpCtx(), bsonExpr.firstElement(), getVPS()),
+TEST_F(MapReduceFixture, ExpressionFunctionFailsIfBodyIsNotConstantExpression) {
+ auto bsonExpr = BSON("expr" << BSON("body" << BSONObj() << "args" << BSON_ARRAY(1 << 2)
+ << "lang" << ExpressionFunction::kJavaScript));
+ ASSERT_THROWS_CODE(ExpressionFunction::parse(getExpCtx(), bsonExpr.firstElement(), getVPS()),
+ AssertionException,
+ 31432);
+}
+
+TEST_F(MapReduceFixture, ExpressionFunctionFailsIfBodyIsNotCorrectType) {
+ auto bsonExpr = BSON("expr" << BSON("body" << 1 << "args" << BSON_ARRAY(1 << 2) << "lang"
+ << ExpressionFunction::kJavaScript));
+ ASSERT_THROWS_CODE(ExpressionFunction::parse(getExpCtx(), bsonExpr.firstElement(), getVPS()),
AssertionException,
31262);
}
-TEST_F(MapReduceFixture, ExpressionInternalJsFailsIfArgsIsNotSpecified) {
- auto bsonExpr = BSON("expr" << BSON("eval"
- << "function(first) {return first;};"));
- ASSERT_THROWS_CODE(ExpressionInternalJs::parse(getExpCtx(), bsonExpr.firstElement(), getVPS()),
+TEST_F(MapReduceFixture, ExpressionFunctionFailsIfArgsIsNotSpecified) {
+ auto bsonExpr = BSON("expr" << BSON("body"
+ << "function(first) {return first;};"
+ << "lang" << ExpressionFunction::kJavaScript));
+ ASSERT_THROWS_CODE(ExpressionFunction::parse(getExpCtx(), bsonExpr.firstElement(), getVPS()),
AssertionException,
31263);
}
+TEST_F(MapReduceFixture, ExpressionFunctionFailsIfLangIsNotSpecified) {
+ auto bsonExpr = BSON("expr" << BSON("body"
+ << "function(first) {return first;};"
+ << "args" << BSON_ARRAY(1 << 2)));
+ ASSERT_THROWS_CODE(ExpressionFunction::parse(getExpCtx(), bsonExpr.firstElement(), getVPS()),
+ AssertionException,
+ 31418);
+}
+
TEST_F(MapReduceFixture, ExpressionInternalJsEmitProducesExpectedResult) {
auto bsonExpr = BSON("expr" << BSON("this"
<< "$$ROOT"
diff --git a/src/mongo/db/pipeline/expression_javascript.cpp b/src/mongo/db/pipeline/expression_js_emit.cpp
index 4e3f489e7f2..4072ecee993 100644
--- a/src/mongo/db/pipeline/expression_javascript.cpp
+++ b/src/mongo/db/pipeline/expression_js_emit.cpp
@@ -30,15 +30,13 @@
#include "mongo/platform/basic.h"
#include "mongo/db/auth/authorization_session.h"
-#include "mongo/db/pipeline/expression_javascript.h"
+#include "mongo/db/pipeline/expression_js_emit.h"
#include "mongo/db/pipeline/make_js_function.h"
#include "mongo/db/query/query_knobs_gen.h"
namespace mongo {
REGISTER_EXPRESSION(_internalJsEmit, ExpressionInternalJsEmit::parse);
-
-REGISTER_EXPRESSION(_internalJs, ExpressionInternalJs::parse);
namespace {
/**
@@ -129,65 +127,4 @@ Value ExpressionInternalJsEmit::evaluate(const Document& root, Variables* variab
_emitState.reset();
return returnValue;
}
-
-ExpressionInternalJs::ExpressionInternalJs(const boost::intrusive_ptr<ExpressionContext>& expCtx,
- boost::intrusive_ptr<Expression> passedArgs,
- std::string funcSource)
- : Expression(expCtx, {std::move(passedArgs)}),
- _passedArgs(_children[0]),
- _funcSource(std::move(funcSource)) {}
-
-Value ExpressionInternalJs::serialize(bool explain) const {
- return Value(
- Document{{kExpressionName,
- Document{{"eval", _funcSource}, {"args", _passedArgs->serialize(explain)}}}});
-}
-
-void ExpressionInternalJs::_doAddDependencies(mongo::DepsTracker* deps) const {
- _children[0]->addDependencies(deps);
-}
-
-boost::intrusive_ptr<Expression> ExpressionInternalJs::parse(
- const boost::intrusive_ptr<ExpressionContext>& expCtx,
- BSONElement expr,
- const VariablesParseState& vps) {
-
- uassert(31260,
- str::stream() << kExpressionName
- << " requires an object as an argument, found: " << expr.type(),
- expr.type() == BSONType::Object);
-
- BSONElement evalField = expr["eval"];
-
- uassert(31261, "The eval function must be specified.", evalField);
- uassert(31262,
- "The eval function must be of type string or code",
- evalField.type() == BSONType::String || evalField.type() == BSONType::Code);
-
- BSONElement argsField = expr["args"];
- uassert(31263, "The args field must be specified.", argsField);
- boost::intrusive_ptr<Expression> argsExpr = parseOperand(expCtx, argsField, vps);
-
- return new ExpressionInternalJs(expCtx, argsExpr, evalField._asCode());
-}
-
-Value ExpressionInternalJs::evaluate(const Document& root, Variables* variables) const {
- auto& expCtx = getExpressionContext();
-
- auto jsExec = expCtx->getJsExecWithScope();
-
- ScriptingFunction func = jsExec->getScope()->createFunction(_funcSource.c_str());
- uassert(31265, "The eval function did not evaluate", func);
-
- auto argExpressions = _passedArgs->evaluate(root, variables);
- uassert(
- 31266, "The args field must be of type array", argExpressions.getType() == BSONType::Array);
-
- int argNum = 0;
- BSONObjBuilder bob;
- for (const auto& arg : argExpressions.getArray()) {
- arg.addToBsonObj(&bob, "arg" + std::to_string(argNum++));
- }
- return jsExec->callFunction(func, bob.done(), {});
-}
} // namespace mongo
diff --git a/src/mongo/db/pipeline/expression_javascript.h b/src/mongo/db/pipeline/expression_js_emit.h
index 4368b4985af..b81c73baa75 100644
--- a/src/mongo/db/pipeline/expression_javascript.h
+++ b/src/mongo/db/pipeline/expression_js_emit.h
@@ -32,10 +32,6 @@
#include "mongo/db/pipeline/expression.h"
#include "mongo/db/pipeline/javascript_execution.h"
-/**
- * This file contains all expressions which make use of JavaScript execution and depend on the JS
- * engine to operate.
- */
namespace mongo {
/**
@@ -99,41 +95,4 @@ private:
std::string _funcSource;
};
-/**
- * This expression takes a Javascript function and an array of arguments to pass to it. It returns
- * the return value of the Javascript function with the given arguments.
- */
-class ExpressionInternalJs final : public Expression {
-public:
- static boost::intrusive_ptr<Expression> parse(
- const boost::intrusive_ptr<ExpressionContext>& expCtx,
- BSONElement expr,
- const VariablesParseState& vps);
-
- static boost::intrusive_ptr<ExpressionInternalJs> create(
- const boost::intrusive_ptr<ExpressionContext>& expCtx,
- boost::intrusive_ptr<Expression> passedArgs,
- std::string funcSourceString) {
- return new ExpressionInternalJs{expCtx, passedArgs, std::move(funcSourceString)};
- }
-
- Value evaluate(const Document& root, Variables* variables) const final;
-
- Value serialize(bool explain) const final;
-
- void acceptVisitor(ExpressionVisitor* visitor) final {
- return visitor->visit(this);
- }
-
- static constexpr auto kExpressionName = "$_internalJs"_sd;
-
-private:
- ExpressionInternalJs(const boost::intrusive_ptr<ExpressionContext>& expCtx,
- boost::intrusive_ptr<Expression> passedArgs,
- std::string funcSourceString);
- void _doAddDependencies(DepsTracker* deps) const final override;
-
- const boost::intrusive_ptr<Expression>& _passedArgs;
- std::string _funcSource;
-};
} // namespace mongo
diff --git a/src/mongo/db/pipeline/expression_visitor.h b/src/mongo/db/pipeline/expression_visitor.h
index 04100467daf..326c8e2e6ce 100644
--- a/src/mongo/db/pipeline/expression_visitor.h
+++ b/src/mongo/db/pipeline/expression_visitor.h
@@ -147,7 +147,7 @@ class ExpressionInternalFindSlice;
class ExpressionInternalFindPositional;
class ExpressionInternalFindElemMatch;
class ExpressionInternalJsEmit;
-class ExpressionInternalJs;
+class ExpressionFunction;
class ExpressionDegreesToRadians;
class ExpressionRadiansToDegrees;
@@ -293,7 +293,7 @@ public:
virtual void visit(ExpressionFromAccumulator<AccumulatorMergeObjects>*) = 0;
virtual void visit(ExpressionTests::Testable*) = 0;
virtual void visit(ExpressionInternalJsEmit*) = 0;
- virtual void visit(ExpressionInternalJs*) = 0;
+ virtual void visit(ExpressionFunction*) = 0;
virtual void visit(ExpressionInternalFindSlice*) = 0;
virtual void visit(ExpressionInternalFindPositional*) = 0;
virtual void visit(ExpressionInternalFindElemMatch*) = 0;