diff options
-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.cpp | 25 | ||||
-rw-r--r-- | src/mongo/db/pipeline/SConscript | 3 | ||||
-rw-r--r-- | src/mongo/db/pipeline/expression_function.cpp | 112 | ||||
-rw-r--r-- | src/mongo/db/pipeline/expression_function.h | 79 | ||||
-rw-r--r-- | src/mongo/db/pipeline/expression_javascript_test.cpp | 88 | ||||
-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.h | 4 |
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; |