summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--jstests/change_streams/change_stream_ban_from_views.js6
-rw-r--r--jstests/multiVersion/view_definition_feature_compatibility_version.js139
-rw-r--r--jstests/noPassthrough/view_definition_feature_compatibility_version.js72
-rw-r--r--jstests/replsets/view_definition_initial_sync_with_feature_compatibility.js89
-rw-r--r--src/mongo/db/pipeline/document_source_facet.h4
-rw-r--r--src/mongo/db/views/view_catalog.cpp73
-rw-r--r--src/mongo/db/views/view_catalog_test.cpp126
7 files changed, 502 insertions, 7 deletions
diff --git a/jstests/change_streams/change_stream_ban_from_views.js b/jstests/change_streams/change_stream_ban_from_views.js
index bba7433c6c6..b2789e72155 100644
--- a/jstests/change_streams/change_stream_ban_from_views.js
+++ b/jstests/change_streams/change_stream_ban_from_views.js
@@ -22,9 +22,11 @@
// Verify that we cannot create a view using a pipeline which begins with $changeStream.
assert.commandFailedWithCode(
- db.runCommand({create: csViewName, viewOn: collName, pipeline: csPipe}), 40255);
+ db.runCommand({create: csViewName, viewOn: collName, pipeline: csPipe}),
+ ErrorCodes.OptionNotSupportedOnView);
// We also cannot update an existing view to use a $changeStream pipeline.
assert.commandFailedWithCode(
- db.runCommand({collMod: normalViewName, viewOn: collName, pipeline: csPipe}), 40255);
+ db.runCommand({collMod: normalViewName, viewOn: collName, pipeline: csPipe}),
+ ErrorCodes.OptionNotSupportedOnView);
})();
diff --git a/jstests/multiVersion/view_definition_feature_compatibility_version.js b/jstests/multiVersion/view_definition_feature_compatibility_version.js
new file mode 100644
index 00000000000..5f85a28430a
--- /dev/null
+++ b/jstests/multiVersion/view_definition_feature_compatibility_version.js
@@ -0,0 +1,139 @@
+/**
+ * Test that mongod will not allow creation of a view using 3.6 query features when the feature
+ * compatibility version is older than 3.6.
+ *
+ * We restart mongod during the test and expect it to have the same data after restarting.
+ * @tags: [requires_persistence]
+ */
+
+(function() {
+ "use strict";
+
+ const testName = "view_definition_feature_compatibility_version_multiversion";
+ const dbpath = MongoRunner.dataPath + testName;
+
+ /**
+ * Tests the correct behavior of view creation and modification using 3.6 query features with
+ * different binary versions and feature compatibility versions.
+ *
+ * TODO SERVER-31588: Remove FCV 3.4 validation during the 3.7 development cycle.
+ */
+ function testView(pipeline) {
+ resetDbpath(dbpath);
+
+ let conn = MongoRunner.runMongod({dbpath: dbpath, binVersion: "latest"});
+ assert.neq(null, conn, "mongod was unable to start up");
+ let testDB = conn.getDB(testName);
+
+ // Explicitly set feature compatibility version 3.6.
+ assert.commandWorked(testDB.adminCommand({setFeatureCompatibilityVersion: "3.6"}));
+
+ // Create a view using a 3.6 query feature.
+ assert.commandWorked(testDB.createView("view1", "coll", pipeline));
+
+ // Update an existing view to use a 3.6 query feature.
+ assert(testDB.view1.drop(), "Drop of view failed");
+ assert.commandWorked(testDB.createView("view1", "coll", []));
+ assert.commandWorked(
+ testDB.runCommand({collMod: "view1", viewOn: "coll", pipeline: pipeline}));
+
+ // Create an empty view which we will attempt to update to use 3.6 query features under
+ // feature compatibility mode 3.4.
+ assert.commandWorked(testDB.createView("view2", "coll", []));
+
+ // Set the feature compatibility version to 3.4.
+ assert.commandWorked(testDB.adminCommand({setFeatureCompatibilityVersion: "3.4"}));
+
+ // Read against an existing view using 3.6 query features should not fail.
+ assert.commandWorked(testDB.runCommand({find: "view1"}));
+
+ // Trying to create a new view using 3.6 query features should fail.
+ assert.commandFailedWithCode(testDB.createView("view_fail", "coll", pipeline),
+ ErrorCodes.QueryFeatureNotAllowed);
+
+ // Trying to update existing view to use 3.6 query features should also fail.
+ assert.commandFailedWithCode(
+ testDB.runCommand({collMod: "view2", viewOn: "coll", pipeline: pipeline}),
+ ErrorCodes.QueryFeatureNotAllowed);
+
+ MongoRunner.stopMongod(conn);
+
+ // Starting up a 3.4 mongod with 3.6 query features will succeed.
+ conn = MongoRunner.runMongod({dbpath: dbpath, binVersion: "3.4", noCleanData: true});
+ assert.neq(null, conn, "mongod 3.4 was unable to start up");
+ testDB = conn.getDB(testName);
+
+ // Reads will fail against views with 3.6 query features when running a 3.4 binary.
+ // Not checking the code returned on failure as it is not uniform across the various
+ // 'pipeline' arguments tested.
+ assert.commandFailed(testDB.runCommand({find: "view1"}));
+
+ // A read against a view that does not contain 3.6 query features succeeds.
+ assert.commandWorked(testDB.runCommand({find: "view2"}));
+
+ MongoRunner.stopMongod(conn);
+
+ // Starting up a 3.6 mongod should succeed, even though the feature compatibility version is
+ // still set to 3.4.
+ conn = MongoRunner.runMongod({dbpath: dbpath, binVersion: "latest", noCleanData: true});
+ assert.neq(null, conn, "mongod was unable to start up");
+ testDB = conn.getDB(testName);
+
+ // A read against a view with 3.6 query features should now succeed.
+ assert.commandWorked(testDB.runCommand({find: "view1"}));
+
+ // Set the feature compatibility version back to 3.6.
+ assert.commandWorked(testDB.adminCommand({setFeatureCompatibilityVersion: "3.6"}));
+
+ // Now we should be able to create a view using 3.6 query features again.
+ assert.commandWorked(testDB.createView("view3", "coll", pipeline));
+
+ // And we should be able to modify a view to use 3.6 query features.
+ assert(testDB.view3.drop(), "Drop of view failed");
+ assert.commandWorked(testDB.createView("view3", "coll", []));
+ assert.commandWorked(
+ testDB.runCommand({collMod: "view3", viewOn: "coll", pipeline: pipeline}));
+
+ // Set the feature compatibility version to 3.4 and then restart with
+ // internalValidateFeaturesAsMaster=false.
+ assert.commandWorked(testDB.adminCommand({setFeatureCompatibilityVersion: "3.4"}));
+ MongoRunner.stopMongod(conn);
+ conn = MongoRunner.runMongod({
+ dbpath: dbpath,
+ binVersion: "latest",
+ noCleanData: true,
+ setParameter: "internalValidateFeaturesAsMaster=false"
+ });
+ assert.neq(null, conn, "mongod was unable to start up");
+ testDB = conn.getDB(testName);
+
+ // Even though the feature compatibility version is 3.4, we should still be able to create a
+ // view using 3.6 query features, because internalValidateFeaturesAsMaster is false.
+ assert.commandWorked(testDB.createView("view4", "coll", pipeline));
+
+ // We should also be able to modify a view to use 3.6 query features.
+ assert(testDB.view4.drop(), "Drop of view failed");
+ assert.commandWorked(testDB.createView("view4", "coll", []));
+ assert.commandWorked(
+ testDB.runCommand({collMod: "view4", viewOn: "coll", pipeline: pipeline}));
+
+ MongoRunner.stopMongod(conn);
+
+ // Starting up a 3.4 mongod with 3.6 query features.
+ conn = MongoRunner.runMongod({dbpath: dbpath, binVersion: "3.4", noCleanData: true});
+ assert.neq(null, conn, "mongod 3.4 was unable to start up");
+ testDB = conn.getDB(testName);
+
+ // Existing views with 3.6 query features can be dropped.
+ assert(testDB.view1.drop(), "Drop of view failed");
+ assert(testDB.system.views.drop(), "Drop of system.views collection failed");
+
+ MongoRunner.stopMongod(conn);
+ }
+
+ testView([{$match: {$expr: {$eq: ["$x", "$y"]}}}]);
+ testView([{$match: {$jsonSchema: {properties: {foo: {type: "string"}}}}}]);
+ testView([{$facet: {field: [{$match: {$jsonSchema: {properties: {foo: {type: "string"}}}}}]}}]);
+ testView([{$facet: {field: [{$match: {$expr: {$eq: ["$x", "$y"]}}}]}}]);
+ testView([{$lookup: {from: "foreign", as: "as", pipeline: []}}]);
+}());
diff --git a/jstests/noPassthrough/view_definition_feature_compatibility_version.js b/jstests/noPassthrough/view_definition_feature_compatibility_version.js
new file mode 100644
index 00000000000..50ce10f0688
--- /dev/null
+++ b/jstests/noPassthrough/view_definition_feature_compatibility_version.js
@@ -0,0 +1,72 @@
+// Test that query and aggregation syntax introduced in 3.6 is restricted from view definitions when
+// the featureCompatibilityVersion is 3.4.
+// TODO SERVER-31588: Remove FCV 3.4 validation during the 3.7 development cycle.
+
+(function() {
+ "use strict";
+ const conn = MongoRunner.runMongod({});
+ assert.neq(null, conn, "mongod was unable to start up");
+
+ const testDB = conn.getDB("view_definition_feature_compatibility_version");
+ assert.commandWorked(testDB.dropDatabase());
+
+ const adminDB = conn.getDB("admin");
+
+ function pipelineValidForViewUnderFCV_36(pipeline) {
+ // Set featureCompatibilityVersion to 3.6.
+ assert.commandWorked(adminDB.runCommand({setFeatureCompatibilityVersion: "3.6"}));
+
+ // Confirm view creation with 'pipeline' succeeds.
+ assert.commandWorked(testDB.createView("view", "test", pipeline));
+ assert(testDB.view.drop());
+
+ // Confirm view collMod with 'pipeline' succeeds.
+ assert.commandWorked(testDB.createView("view", "test", []));
+ assert.commandWorked(
+ testDB.runCommand({collMod: "view", viewOn: "test", pipeline: pipeline}));
+ assert(testDB.view.drop());
+ }
+
+ function pipelineNotValidForViewUnderFCV_34(pipeline) {
+ // Set featureCompatibilityVersion to 3.4.
+ assert.commandWorked(adminDB.runCommand({setFeatureCompatibilityVersion: "3.4"}));
+
+ // Confirm view creation with 'pipeline' fails.
+ assert.commandFailedWithCode(testDB.createView("view", "test", pipeline),
+ ErrorCodes.QueryFeatureNotAllowed);
+
+ // Confirm view collMod with 'pipeline' fails.
+ assert.commandWorked(testDB.createView("view", "test", []));
+ assert.commandFailedWithCode(
+ testDB.runCommand({collMod: "view", viewOn: "test", pipeline: pipeline}),
+ ErrorCodes.QueryFeatureNotAllowed);
+ assert(testDB.view.drop());
+ }
+
+ // $lookup with 'pipeline' syntax.
+ let pipeline = [{$lookup: {from: "test", as: "as", pipeline: []}}];
+ pipelineValidForViewUnderFCV_36(pipeline);
+ pipelineNotValidForViewUnderFCV_34(pipeline);
+
+ // $match with $jsonSchema.
+ pipeline = [{$match: {$jsonSchema: {"required": ["x"]}}}];
+ pipelineValidForViewUnderFCV_36(pipeline);
+ pipelineNotValidForViewUnderFCV_34(pipeline);
+
+ // $match with $expr.
+ pipeline = [{$match: {$expr: {$eq: ["$x", "$y"]}}}];
+ pipelineValidForViewUnderFCV_36(pipeline);
+ pipelineNotValidForViewUnderFCV_34(pipeline);
+
+ // $facet with a subpipeline containing a $match with $jsonSchema.
+ pipeline = [{$facet: {output: [{$match: {$jsonSchema: {"required": ["x"]}}}]}}];
+ pipelineValidForViewUnderFCV_36(pipeline);
+ pipelineNotValidForViewUnderFCV_34(pipeline);
+
+ // $facet with a subpipeline containing a $match with $expr.
+ pipeline = [{$facet: {output: [{$match: {$expr: {$eq: ["$x", "$y"]}}}]}}];
+ pipelineValidForViewUnderFCV_36(pipeline);
+ pipelineNotValidForViewUnderFCV_34(pipeline);
+
+ MongoRunner.stopMongod(conn);
+}());
diff --git a/jstests/replsets/view_definition_initial_sync_with_feature_compatibility.js b/jstests/replsets/view_definition_initial_sync_with_feature_compatibility.js
new file mode 100644
index 00000000000..16b9091b34e
--- /dev/null
+++ b/jstests/replsets/view_definition_initial_sync_with_feature_compatibility.js
@@ -0,0 +1,89 @@
+/**
+ * Test that a new replica set member can successfully sync a collection with a view using 3.6 query
+ * features, even when the replica set was downgraded to feature compatibility version 3.4.
+ *
+ * We restart the secondary as part of this test with the expectation that it still has the same
+ * data after the restart.
+ * @tags: [requires_persistence]
+ *
+ * TODO SERVER-31588: Remove FCV 3.4 validation during the 3.7 development cycle.
+ */
+
+load("jstests/replsets/rslib.js");
+
+(function() {
+ "use strict";
+ const testName = "view_definition_initial_sync_with_feature_compatibility";
+
+ function testView(pipeline) {
+ //
+ // Create a single-node replica set.
+ //
+ let replTest = new ReplSetTest({name: testName, nodes: 1});
+
+ replTest.startSet();
+ replTest.initiate();
+
+ let primary = replTest.getPrimary();
+ let testDB = primary.getDB("test");
+
+ //
+ // Explicitly set the replica set to feature compatibility version 3.6.
+ //
+ assert.commandWorked(primary.adminCommand({setFeatureCompatibilityVersion: "3.6"}));
+
+ //
+ // Create a view using 3.6 query features.
+ //
+ assert.commandWorked(testDB.createView("view1", "coll", pipeline));
+
+ //
+ // Downgrade the replica set to feature compatibility version 3.4.
+ //
+ assert.commandWorked(primary.adminCommand({setFeatureCompatibilityVersion: "3.4"}));
+
+ //
+ // Add a new member to the replica set.
+ //
+ let secondaryDBPath = MongoRunner.dataPath + testName + "_secondary";
+ resetDbpath(secondaryDBPath);
+ let secondary = replTest.add({dbpath: secondaryDBPath});
+ replTest.reInitiate(secondary);
+ reconnect(primary);
+ reconnect(secondary);
+
+ //
+ // Once the new member completes its initial sync, stop it, remove it from the replica
+ // set, and start it back up as an individual instance.
+ //
+ replTest.waitForState(secondary, [ReplSetTest.State.PRIMARY, ReplSetTest.State.SECONDARY]);
+
+ replTest.stopSet(undefined /* send default signal */,
+ true /* don't clear data directory */);
+
+ secondary = MongoRunner.runMongod({dbpath: secondaryDBPath, noCleanData: true});
+ assert.neq(null, secondary, "mongod was unable to start up");
+
+ //
+ // Verify that the view synced to the new member.
+ //
+ let secondaryDB = secondary.getDB("test");
+ assert.eq(secondaryDB.system.views.findOne({_id: "test.view1"}, {_id: 1}),
+ {_id: "test.view1"});
+
+ //
+ // Verify that, even though a view using 3.6 query features exists, it is not possible to
+ // create a new view using 3.6 query features because of feature compatibility version 3.4.
+ //
+ assert.commandFailedWithCode(secondaryDB.createView("view2", "coll", pipeline),
+ ErrorCodes.QueryFeatureNotAllowed);
+
+ MongoRunner.stopMongod(secondary);
+ }
+
+ testView([{$match: {$expr: {$eq: ["$x", "$y"]}}}]);
+ testView([{$match: {$jsonSchema: {properties: {foo: {type: "string"}}}}}]);
+ testView([{$facet: {field: [{$match: {$jsonSchema: {properties: {foo: {type: "string"}}}}}]}}]);
+ testView([{$facet: {field: [{$match: {$expr: {$eq: ["$x", "$y"]}}}]}}]);
+ testView([{$lookup: {from: "foreign", as: "as", pipeline: []}}]);
+}()); \ No newline at end of file
diff --git a/src/mongo/db/pipeline/document_source_facet.h b/src/mongo/db/pipeline/document_source_facet.h
index 85eb2f0eb69..ff7c12f4ae1 100644
--- a/src/mongo/db/pipeline/document_source_facet.h
+++ b/src/mongo/db/pipeline/document_source_facet.h
@@ -130,6 +130,10 @@ public:
return {this};
}
+ const std::vector<FacetPipeline>& getFacetPipelines() const {
+ return _facets;
+ }
+
// The following are overridden just to forward calls to sub-pipelines.
void addInvolvedCollections(std::vector<NamespaceString>* collections) const final;
void doInjectMongoProcessInterface(std::shared_ptr<MongoProcessInterface>) final;
diff --git a/src/mongo/db/views/view_catalog.cpp b/src/mongo/db/views/view_catalog.cpp
index 67410c7a045..ec654514a1b 100644
--- a/src/mongo/db/views/view_catalog.cpp
+++ b/src/mongo/db/views/view_catalog.cpp
@@ -38,10 +38,14 @@
#include "mongo/base/status_with.h"
#include "mongo/base/string_data.h"
#include "mongo/bson/util/builder.h"
+#include "mongo/db/commands/feature_compatibility_version_command_parser.h"
#include "mongo/db/namespace_string.h"
#include "mongo/db/operation_context.h"
#include "mongo/db/pipeline/aggregation_request.h"
#include "mongo/db/pipeline/document_source.h"
+#include "mongo/db/pipeline/document_source_facet.h"
+#include "mongo/db/pipeline/document_source_lookup.h"
+#include "mongo/db/pipeline/document_source_match.h"
#include "mongo/db/pipeline/expression_context.h"
#include "mongo/db/pipeline/lite_parsed_pipeline.h"
#include "mongo/db/pipeline/pipeline.h"
@@ -64,6 +68,62 @@ StatusWith<std::unique_ptr<CollatorInterface>> parseCollator(OperationContext* o
}
return CollatorFactoryInterface::get(opCtx->getServiceContext())->makeFromBSON(collationSpec);
}
+
+// TODO SERVER-31588: Remove FCV 3.4 validation during the 3.7 development cycle.
+Status validInViewUnder34FeatureCompatibility(const boost::intrusive_ptr<ExpressionContext>& expCtx,
+ const Pipeline& pipeline) {
+ const auto& sourceList = pipeline.getSources();
+ // Confirm that the view pipeline does not contain elements that require 3.6 feature
+ // compatibility.
+ for (auto&& source : sourceList) {
+ if (auto matchStage = dynamic_cast<DocumentSourceMatch*>(source.get())) {
+ auto query = matchStage->getQuery();
+ MatchExpressionParser::AllowedFeatureSet allowedFeatures =
+ Pipeline::kAllowedMatcherFeatures &
+ ~MatchExpressionParser::AllowedFeatures::kJSONSchema &
+ ~MatchExpressionParser::AllowedFeatures::kExpr;
+
+ auto statusWithMatcher = MatchExpressionParser::parse(
+ query, expCtx, ExtensionsCallbackNoop(), allowedFeatures);
+
+ if (!statusWithMatcher.isOK()) {
+ if (statusWithMatcher.getStatus().code() == ErrorCodes::QueryFeatureNotAllowed) {
+ return {statusWithMatcher.getStatus().code(),
+ str::stream()
+ << "featureCompatibility version '3.6' is required to create "
+ "a view containing new features. See "
+ << feature_compatibility_version::kDochubLink
+ << "; "
+ << statusWithMatcher.getStatus().reason()};
+ }
+
+ uasserted(ErrorCodes::InternalError,
+ str::stream()
+ << "Unexpected error on validation for 3.4 feature compatibility: "
+ << statusWithMatcher.getStatus().toString());
+ }
+ } else if (auto lookupStage = dynamic_cast<DocumentSourceLookUp*>(source.get())) {
+ if (lookupStage->wasConstructedWithPipelineSyntax()) {
+ return {ErrorCodes::QueryFeatureNotAllowed,
+ str::stream() << "featureCompatibility version '3.6' is required to create "
+ "a view containing "
+ "a $lookup stage with 'pipeline' syntax. See "
+ << feature_compatibility_version::kDochubLink};
+ }
+ } else if (auto facetStage = dynamic_cast<DocumentSourceFacet*>(source.get())) {
+ for (auto&& facetSubPipe : facetStage->getFacetPipelines()) {
+ auto status =
+ validInViewUnder34FeatureCompatibility(expCtx, *facetSubPipe.pipeline);
+ if (!status.isOK()) {
+ return status;
+ }
+ }
+ }
+ }
+
+ return Status::OK();
+}
+
} // namespace
Status ViewCatalog::reloadIfNeeded(OperationContext* opCtx) {
@@ -172,7 +232,7 @@ Status ViewCatalog::_upsertIntoGraph(OperationContext* opCtx, const ViewDefiniti
// will also return the set of involved namespaces.
auto pipelineStatus = _validatePipeline_inlock(opCtx, viewDef);
if (!pipelineStatus.isOK()) {
- uassert(40255,
+ uassert(pipelineStatus.getStatus().code(),
str::stream() << "Invalid pipeline for view " << viewDef.name().ns() << "; "
<< pipelineStatus.getStatus().reason(),
!needsValidation);
@@ -224,7 +284,6 @@ Status ViewCatalog::_upsertIntoGraph(OperationContext* opCtx, const ViewDefiniti
StatusWith<stdx::unordered_set<NamespaceString>> ViewCatalog::_validatePipeline_inlock(
OperationContext* opCtx, const ViewDefinition& viewDef) const {
- // Make a LiteParsedPipeline to determine the namespaces referenced by this pipeline.
AggregationRequest request(viewDef.viewOn(), viewDef.pipeline());
const LiteParsedPipeline liteParsedPipeline(request);
const auto involvedNamespaces = liteParsedPipeline.getInvolvedNamespaces();
@@ -234,7 +293,7 @@ StatusWith<stdx::unordered_set<NamespaceString>> ViewCatalog::_validatePipeline_
// collection and a pipeline, but in this case we don't need this map to be accurate since
// we will not be evaluating the pipeline.
StringMap<ExpressionContext::ResolvedNamespace> resolvedNamespaces;
- for (auto&& nss : liteParsedPipeline.getInvolvedNamespaces()) {
+ for (auto&& nss : involvedNamespaces) {
resolvedNamespaces[nss.coll()] = {nss, {}};
}
boost::intrusive_ptr<ExpressionContext> expCtx =
@@ -254,6 +313,14 @@ StatusWith<stdx::unordered_set<NamespaceString>> ViewCatalog::_validatePipeline_
"$changeStream cannot be used in a view definition"};
}
+ if (serverGlobalParams.featureCompatibility.validateFeaturesAsMaster.load() &&
+ !serverGlobalParams.featureCompatibility.isFullyUpgradedTo36()) {
+ auto status = validInViewUnder34FeatureCompatibility(expCtx, *pipelineStatus.getValue());
+ if (!status.isOK()) {
+ return status;
+ }
+ }
+
return std::move(involvedNamespaces);
}
diff --git a/src/mongo/db/views/view_catalog_test.cpp b/src/mongo/db/views/view_catalog_test.cpp
index 849d6d1845a..192e79a1255 100644
--- a/src/mongo/db/views/view_catalog_test.cpp
+++ b/src/mongo/db/views/view_catalog_test.cpp
@@ -179,6 +179,128 @@ TEST_F(ViewCatalogFixture, CreateViewOnDifferentDatabase) {
viewCatalog.createView(opCtx.get(), viewName, viewOn, emptyPipeline, emptyCollation));
}
+// TODO SERVER-31588: Remove FCV 3.4 validation during the 3.7 development cycle.
+TEST_F(ViewCatalogFixture, CreateViewWith36FeaturesSucceedsUnder36FCV) {
+ EnsureFCV ensureFCV(EnsureFCV::Version::k36);
+
+ const NamespaceString viewOn("db.coll");
+
+ ASSERT_OK(viewCatalog.createView(opCtx.get(),
+ NamespaceString("db.view1"),
+ viewOn,
+ BSON_ARRAY(BSON("$match" << BSON("$expr" << 1))),
+ emptyCollation));
+
+ ASSERT_OK(viewCatalog.createView(
+ opCtx.get(),
+ NamespaceString("db.view2"),
+ viewOn,
+ BSON_ARRAY(BSON("$match" << BSON("$jsonSchema" << BSON("required" << BSON_ARRAY("x"))))),
+ emptyCollation));
+
+ ASSERT_OK(viewCatalog.createView(
+ opCtx.get(),
+ NamespaceString("db.view3"),
+ viewOn,
+ BSON_ARRAY(
+ BSON("$facet" << BSON("output" << BSON_ARRAY(BSON("$match" << BSON("$expr" << 1)))))),
+ emptyCollation));
+
+ ASSERT_OK(viewCatalog.createView(
+ opCtx.get(),
+ NamespaceString("db.view4"),
+ viewOn,
+ BSON_ARRAY(BSON(
+ "$facet" << BSON(
+ "output" << BSON_ARRAY(BSON(
+ "$match" << BSON("$jsonSchema" << BSON("required" << BSON_ARRAY("x")))))))),
+ emptyCollation));
+
+ ASSERT_OK(viewCatalog.createView(opCtx.get(),
+ NamespaceString("db.view5"),
+ viewOn,
+ BSON_ARRAY(BSON("$lookup" << BSON("from"
+ << "fcoll"
+ << "as"
+ << "as"
+ << "pipeline"
+ << BSONArray()))),
+ emptyCollation));
+}
+
+// TODO SERVER-31588: Remove FCV 3.4 validation during the 3.7 development cycle.
+TEST_F(ViewCatalogFixture, CreateViewWith36FeaturesFailsUnder34FCV) {
+ EnsureFCV ensureFCV(EnsureFCV::Version::k34);
+
+ const NamespaceString viewName("db.view");
+ const NamespaceString viewOn("db.coll");
+
+ ASSERT_THROWS_CODE(viewCatalog
+ .createView(opCtx.get(),
+ viewName,
+ viewOn,
+ BSON_ARRAY(BSON("$match" << BSON("$expr" << 1))),
+ emptyCollation)
+ .ignore(),
+ AssertionException,
+ ErrorCodes::QueryFeatureNotAllowed);
+
+ ASSERT_THROWS_CODE(
+ viewCatalog
+ .createView(opCtx.get(),
+ viewName,
+ viewOn,
+ BSON_ARRAY(BSON("$match" << BSON("$jsonSchema"
+ << BSON("required" << BSON_ARRAY("x"))))),
+ emptyCollation)
+ .ignore(),
+ AssertionException,
+ ErrorCodes::QueryFeatureNotAllowed);
+
+ ASSERT_THROWS_CODE(
+ viewCatalog
+ .createView(opCtx.get(),
+ viewName,
+ viewOn,
+ BSON_ARRAY(BSON("$facet" << BSON("output" << BSON_ARRAY(BSON(
+ "$match" << BSON("$expr" << 1)))))),
+ emptyCollation)
+ .ignore(),
+ AssertionException,
+ ErrorCodes::QueryFeatureNotAllowed);
+
+ ASSERT_THROWS_CODE(
+ viewCatalog
+ .createView(
+ opCtx.get(),
+ viewName,
+ viewOn,
+ BSON_ARRAY(BSON(
+ "$facet" << BSON("output" << BSON_ARRAY(BSON(
+ "$match" << BSON("$jsonSchema" << BSON(
+ "required" << BSON_ARRAY("x")))))))),
+ emptyCollation)
+ .ignore(),
+ AssertionException,
+ ErrorCodes::QueryFeatureNotAllowed);
+
+ ASSERT_THROWS_CODE(viewCatalog
+ .createView(opCtx.get(),
+ viewName,
+ viewOn,
+ BSON_ARRAY(BSON("$lookup" << BSON("from"
+ << "fcoll"
+ << "as"
+ << "as"
+ << "pipeline"
+ << BSONArray()))),
+ emptyCollation)
+ .ignore(),
+ AssertionException,
+ ErrorCodes::QueryFeatureNotAllowed);
+}
+
+
TEST_F(ViewCatalogFixture, CreateViewWithPipelineFailsOnInvalidStageName) {
const NamespaceString viewName("db.view");
const NamespaceString viewOn("db.coll");
@@ -204,7 +326,7 @@ TEST_F(ReplViewCatalogFixture, CreateViewWithPipelineFailsOnIneligibleStage) {
viewCatalog.createView(opCtx.get(), viewName, viewOn, invalidPipeline, emptyCollation)
.ignore(),
AssertionException,
- 40255);
+ ErrorCodes::OptionNotSupportedOnView);
}
TEST_F(ViewCatalogFixture, CreateViewOnInvalidCollectionName) {
@@ -383,7 +505,7 @@ TEST_F(ReplViewCatalogFixture, ModifyViewWithPipelineFailsOnIneligibleStage) {
ASSERT_THROWS_CODE(
viewCatalog.modifyView(opCtx.get(), viewName, viewOn, invalidPipeline).ignore(),
AssertionException,
- 40255);
+ ErrorCodes::OptionNotSupportedOnView);
}
TEST_F(ViewCatalogFixture, LookupMissingView) {