summaryrefslogtreecommitdiff
path: root/src/mongo/db/pipeline
diff options
context:
space:
mode:
authorBetty Shen <betty.shen@mongodb.com>2020-12-03 11:11:36 -0500
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-12-08 16:51:43 +0000
commit387db376255427ec3bae648f7529d9d6e56dd64e (patch)
tree717f1b1fc17a8b2b834f407960fe2c0380214be8 /src/mongo/db/pipeline
parent4dfb0381b925cdf96290c9ca8b05543ab0f8ef04 (diff)
downloadmongo-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/SConscript3
-rw-r--r--src/mongo/db/pipeline/expression.h15
-rw-r--r--src/mongo/db/pipeline/expression_context.cpp2
-rw-r--r--src/mongo/db/pipeline/expression_context.h6
-rw-r--r--src/mongo/db/pipeline/expression_test_api_version.cpp97
-rw-r--r--src/mongo/db/pipeline/expression_test_api_version.h64
-rw-r--r--src/mongo/db/pipeline/expression_test_api_version_test.cpp123
-rw-r--r--src/mongo/db/pipeline/expression_visitor.h2
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;