diff options
author | Betty Shen <betty.shen@mongodb.com> | 2020-12-03 11:11:36 -0500 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2020-12-08 16:51:43 +0000 |
commit | 387db376255427ec3bae648f7529d9d6e56dd64e (patch) | |
tree | 717f1b1fc17a8b2b834f407960fe2c0380214be8 /src/mongo/db/pipeline | |
parent | 4dfb0381b925cdf96290c9ca8b05543ab0f8ef04 (diff) | |
download | mongo-387db376255427ec3bae648f7529d9d6e56dd64e.tar.gz |
SERVER-51617 Add test expression for API version testing
Diffstat (limited to 'src/mongo/db/pipeline')
-rw-r--r-- | src/mongo/db/pipeline/SConscript | 3 | ||||
-rw-r--r-- | src/mongo/db/pipeline/expression.h | 15 | ||||
-rw-r--r-- | src/mongo/db/pipeline/expression_context.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/pipeline/expression_context.h | 6 | ||||
-rw-r--r-- | src/mongo/db/pipeline/expression_test_api_version.cpp | 97 | ||||
-rw-r--r-- | src/mongo/db/pipeline/expression_test_api_version.h | 64 | ||||
-rw-r--r-- | src/mongo/db/pipeline/expression_test_api_version_test.cpp | 123 | ||||
-rw-r--r-- | src/mongo/db/pipeline/expression_visitor.h | 2 |
8 files changed, 312 insertions, 0 deletions
diff --git a/src/mongo/db/pipeline/SConscript b/src/mongo/db/pipeline/SConscript index 41c30aecb02..9a5adb2ce1f 100644 --- a/src/mongo/db/pipeline/SConscript +++ b/src/mongo/db/pipeline/SConscript @@ -70,12 +70,14 @@ env.Library( 'expression_context.cpp', 'expression_function.cpp', 'expression_js_emit.cpp', + 'expression_test_api_version.cpp', 'expression_trigonometric.cpp', 'javascript_execution.cpp', 'make_js_function.cpp', 'variables.cpp', ], LIBDEPS=[ + '$BUILD_DIR/mongo/db/commands/test_commands_enabled', '$BUILD_DIR/mongo/db/exec/document_value/document_value', '$BUILD_DIR/mongo/db/query/collation/collator_factory_interface', '$BUILD_DIR/mongo/db/query/datetime/date_time_support', @@ -389,6 +391,7 @@ env.CppUnitTest( 'expression_or_test.cpp', 'expression_replace_test.cpp', 'expression_test.cpp', + 'expression_test_api_version_test.cpp', 'expression_trigonometric_test.cpp', 'expression_trim_test.cpp', 'expression_walker_test.cpp', diff --git a/src/mongo/db/pipeline/expression.h b/src/mongo/db/pipeline/expression.h index cbcf74dd618..647f7a24527 100644 --- a/src/mongo/db/pipeline/expression.h +++ b/src/mongo/db/pipeline/expression.h @@ -41,6 +41,7 @@ #include <vector> #include "mongo/base/init.h" +#include "mongo/db/commands/test_commands_enabled.h" #include "mongo/db/exec/document_value/document.h" #include "mongo/db/exec/document_value/value.h" #include "mongo/db/pipeline/dependencies.h" @@ -91,6 +92,20 @@ class DocumentSource; return Status::OK(); \ } +/** + * Registers a Parser only if test commands are enabled. Use this if your expression is only used + * for testing purposes. + */ +#define REGISTER_TEST_EXPRESSION(key, parser) \ + MONGO_INITIALIZER_GENERAL( \ + addToExpressionParserMap_##key, ("EndStartupOptionHandling"), ("expressionParserMap")) \ + (InitializerContext*) { \ + if (getTestCommandsEnabled()) { \ + Expression::registerExpression("$" #key, (parser), boost::none); \ + } \ + return Status::OK(); \ + } + class Expression : public RefCountable { public: using Parser = std::function<boost::intrusive_ptr<Expression>( diff --git a/src/mongo/db/pipeline/expression_context.cpp b/src/mongo/db/pipeline/expression_context.cpp index a27d8ca64b1..0ca09f7a27c 100644 --- a/src/mongo/db/pipeline/expression_context.cpp +++ b/src/mongo/db/pipeline/expression_context.cpp @@ -125,6 +125,8 @@ ExpressionContext::ExpressionContext( } if (letParameters) variables.seedVariablesWithLetParameters(this, *letParameters); + if (opCtx) + apiParameters = APIParameters::get(opCtx); } ExpressionContext::ExpressionContext( diff --git a/src/mongo/db/pipeline/expression_context.h b/src/mongo/db/pipeline/expression_context.h index c6a20a28ab9..721cf6ffe01 100644 --- a/src/mongo/db/pipeline/expression_context.h +++ b/src/mongo/db/pipeline/expression_context.h @@ -37,6 +37,7 @@ #include "mongo/base/string_data.h" #include "mongo/bson/bsonobj.h" +#include "mongo/db/api_parameters.h" #include "mongo/db/exec/document_value/document_comparator.h" #include "mongo/db/exec/document_value/value_comparator.h" #include "mongo/db/namespace_string.h" @@ -363,6 +364,11 @@ public: // construction. const bool mayDbProfile = true; + // API Parameters pulled from OperationContext upon object creation. + // This may become stale if OperationContext changes after object creation. + // Expressions should reach APIParameters with this variable instead of using the decorator. + APIParameters apiParameters; + protected: static const int kInterruptCheckPeriod = 128; diff --git a/src/mongo/db/pipeline/expression_test_api_version.cpp b/src/mongo/db/pipeline/expression_test_api_version.cpp new file mode 100644 index 00000000000..c501d8b9292 --- /dev/null +++ b/src/mongo/db/pipeline/expression_test_api_version.cpp @@ -0,0 +1,97 @@ +/** + * Copyright (C) 2020-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/platform/basic.h" + +#include "mongo/db/api_parameters.h" +#include "mongo/db/pipeline/expression_test_api_version.h" + +namespace mongo { + +REGISTER_TEST_EXPRESSION(_testApiVersion, ExpressionTestApiVersion::parse); + +ExpressionTestApiVersion::ExpressionTestApiVersion(ExpressionContext* const expCtx, + bool unstable, + bool deprecated) + : Expression(expCtx), _unstable(unstable), _deprecated(deprecated) {} + +boost::intrusive_ptr<Expression> ExpressionTestApiVersion::parse(ExpressionContext* const expCtx, + BSONElement expr, + const VariablesParseState& vps) { + uassert(5161700, + "$_testApiVersion only supports an object as its argument", + expr.type() == BSONType::Object); + + const BSONObj params = expr.embeddedObject(); + uassert(5161701, + "$_testApiVersion only accepts an object with a single field.", + params.nFields() == 1); + + bool unstableField = false; + bool deprecatedField = false; + + auto field = params.firstElementFieldNameStringData(); + if (field == kUnstableField) { + uassert(5161702, "unstable must be a boolean", params.firstElement().isBoolean()); + unstableField = params.firstElement().boolean(); + } else if (field == kDeprecatedField) { + uassert(5161703, "deprecated must be a boolean", params.firstElement().isBoolean()); + deprecatedField = params.firstElement().boolean(); + } else { + uasserted(5161704, + str::stream() << field << " is not a valid argument for $_testApiVersion"); + } + + return new ExpressionTestApiVersion(expCtx, unstableField, deprecatedField); +} + +Value ExpressionTestApiVersion::serialize(bool explain) const { + return Value(Document{{"$_testApiVersion", + Document{{"unstable", _unstable ? Value(_unstable) : Value()}, + {"deprecated", _deprecated ? Value(_deprecated) : Value()}}}}); +} + +Value ExpressionTestApiVersion::evaluate(const Document& root, Variables* variables) const { + APIParameters apiParams = getExpressionContext()->apiParameters; + + if (apiParams.getAPIStrict() && _unstable) { + uasserted(ErrorCodes::APIStrictError, + "Provided apiStrict is true with an unstable command."); + } + + if (apiParams.getAPIDeprecationErrors() && _deprecated) { + uasserted(ErrorCodes::APIDeprecationError, + "Provided apiDeprecatedErrors is true with a deprecated command."); + } + + return Value(1); +} + +void ExpressionTestApiVersion::_doAddDependencies(DepsTracker* deps) const {} + +} // namespace mongo
\ No newline at end of file diff --git a/src/mongo/db/pipeline/expression_test_api_version.h b/src/mongo/db/pipeline/expression_test_api_version.h new file mode 100644 index 00000000000..ede9a8378d4 --- /dev/null +++ b/src/mongo/db/pipeline/expression_test_api_version.h @@ -0,0 +1,64 @@ +/** + * Copyright (C) 2020-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" + +namespace mongo { +/** + * This expression will be used to validate that versioning code is working as expected. + * $_testApiVersion should only take one parameter, either {unstable: true} or {deprecated: true}. + * If no error is thrown, this expression will return an integer value. + */ +class ExpressionTestApiVersion final : public Expression { +public: + static constexpr auto kUnstableField = "unstable"; + static constexpr auto kDeprecatedField = "deprecated"; + + static boost::intrusive_ptr<Expression> parse(ExpressionContext* const expCtx, + BSONElement expr, + const VariablesParseState& vps); + + Value evaluate(const Document& root, Variables* variables) const final; + + Value serialize(bool explain) const final; + + void acceptVisitor(ExpressionVisitor* visitor) final { + return visitor->visit(this); + } + +private: + ExpressionTestApiVersion(ExpressionContext* const expCtx, bool unstable, bool deprecated); + void _doAddDependencies(DepsTracker* deps) const final override; + + bool _unstable; + bool _deprecated; +}; +} // namespace mongo diff --git a/src/mongo/db/pipeline/expression_test_api_version_test.cpp b/src/mongo/db/pipeline/expression_test_api_version_test.cpp new file mode 100644 index 00000000000..5dc4b93493e --- /dev/null +++ b/src/mongo/db/pipeline/expression_test_api_version_test.cpp @@ -0,0 +1,123 @@ +/** + * Copyright (C) 2020-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/exec/document_value/document_value_test_util.h" +#include "mongo/db/pipeline/aggregation_context_fixture.h" +#include "mongo/db/pipeline/expression_context_for_test.h" +#include "mongo/platform/basic.h" + +#include "mongo/db/pipeline/expression_test_api_version.h" +#include "mongo/unittest/unittest.h" + +namespace mongo { +namespace { + +using TestApiVersion = AggregationContextFixture; + +TEST_F(TestApiVersion, UnstableAcceptsBooleanValue) { + auto expCtx = getExpCtx(); + boost::intrusive_ptr<Expression> expression = ExpressionTestApiVersion::parse( + expCtx.get(), + BSON("$_testApiVersion" << BSON("unstable" << true)).firstElement(), + expCtx->variablesParseState); + + ASSERT_VALUE_EQ(Value(DOC("$_testApiVersion" << DOC("unstable" << true))), + expression->serialize(false)); +} + +TEST_F(TestApiVersion, UnstableDoesNotAcceptNumericValue) { + auto expCtx = getExpCtx(); + + ASSERT_THROWS_CODE(ExpressionTestApiVersion::parse( + expCtx.get(), + BSON("$_testApiVersion" << BSON("unstable" << 1)).firstElement(), + expCtx->variablesParseState), + AssertionException, + 5161702); +} + +TEST_F(TestApiVersion, DeprecatedAcceptsBooleanValue) { + auto expCtx = getExpCtx(); + boost::intrusive_ptr<Expression> expression = ExpressionTestApiVersion::parse( + expCtx.get(), + BSON("$_testApiVersion" << BSON("deprecated" << true)).firstElement(), + expCtx->variablesParseState); + + ASSERT_VALUE_EQ(Value(DOC("$_testApiVersion" << DOC("deprecated" << true))), + expression->serialize(false)); +} + +TEST_F(TestApiVersion, DeprecatedDoesNotAcceptNumericValue) { + auto expCtx = getExpCtx(); + + ASSERT_THROWS_CODE(ExpressionTestApiVersion::parse( + expCtx.get(), + BSON("$_testApiVersion" << BSON("deprecated" << 1)).firstElement(), + expCtx->variablesParseState), + AssertionException, + 5161703); +} + +TEST_F(TestApiVersion, DoesNotAcceptInvalidParameter) { + auto expCtx = getExpCtx(); + + ASSERT_THROWS_CODE( + ExpressionTestApiVersion::parse( + expCtx.get(), + BSON("$_testApiVersion" << BSON("invalidParameter" << true)).firstElement(), + expCtx->variablesParseState), + AssertionException, + 5161704); +} + +TEST_F(TestApiVersion, OnlyTakesOneParameter) { + auto expCtx = getExpCtx(); + + ASSERT_THROWS_CODE( + ExpressionTestApiVersion::parse( + expCtx.get(), + BSON("$_testApiVersion" << BSON("deprecated" << true << "unstable" << true)) + .firstElement(), + expCtx->variablesParseState), + AssertionException, + 5161701); +} + +TEST_F(TestApiVersion, DoesNotAcceptEmptyDocument) { + auto expCtx = getExpCtx(); + + ASSERT_THROWS_CODE( + ExpressionTestApiVersion::parse(expCtx.get(), + BSON("$_testApiVersion" << BSONObj()).firstElement(), + expCtx->variablesParseState), + AssertionException, + 5161701); +} +} // namespace +} // namespace mongo
\ No newline at end of file diff --git a/src/mongo/db/pipeline/expression_visitor.h b/src/mongo/db/pipeline/expression_visitor.h index de4b32908e3..51225289b26 100644 --- a/src/mongo/db/pipeline/expression_visitor.h +++ b/src/mongo/db/pipeline/expression_visitor.h @@ -117,6 +117,7 @@ class ExpressionBinarySize; class ExpressionStrLenCP; class ExpressionSubtract; class ExpressionSwitch; +class ExpressionTestApiVersion; class ExpressionToLower; class ExpressionToUpper; class ExpressionTrim; @@ -185,6 +186,7 @@ public: virtual void visit(ExpressionAllElementsTrue*) = 0; virtual void visit(ExpressionAnd*) = 0; virtual void visit(ExpressionAnyElementTrue*) = 0; + virtual void visit(ExpressionTestApiVersion*) = 0; virtual void visit(ExpressionArray*) = 0; virtual void visit(ExpressionArrayElemAt*) = 0; virtual void visit(ExpressionFirst*) = 0; |