diff options
author | Charlie Swanson <charlie.swanson@mongodb.com> | 2019-05-21 17:22:25 -0400 |
---|---|---|
committer | Charlie Swanson <charlie.swanson@mongodb.com> | 2019-05-22 16:29:09 -0400 |
commit | 5594eeb9c42f5a28ebb20c8fcce87a2a1a01f6a5 (patch) | |
tree | 8453d01043b1bc67161a4ee684c69e637e989645 | |
parent | 76cf536d476b50994c75dd16ec5c7caca23759a5 (diff) | |
download | mongo-5594eeb9c42f5a28ebb20c8fcce87a2a1a01f6a5.tar.gz |
SERVER-41257 Disallow persisting new 4.2 expressions in FCV 4.0
7 files changed, 653 insertions, 4 deletions
diff --git a/jstests/multiVersion/collection_validator_feature_compatibility_version.js b/jstests/multiVersion/collection_validator_feature_compatibility_version.js new file mode 100644 index 00000000000..dbb25b1127a --- /dev/null +++ b/jstests/multiVersion/collection_validator_feature_compatibility_version.js @@ -0,0 +1,205 @@ +/** + * Test that mongod will not allow creation of collection validators using 4.2 query features when + * the feature compatibility version is older than 4.2. + * + * TODO SERVER-41273: Remove FCV 4.0 validation during the 4.3 development cycle. + * + * We restart mongod during the test and expect it to have the same data after restarting. + * @tags: [requires_persistence] + */ + +(function() { + "use strict"; + + const testName = "collection_validator_feature_compatibility_version"; + const dbpath = MongoRunner.dataPath + testName; + + // In order to avoid restarting the server for each test case, we declare all the test cases up + // front, and test them all at once. + const testCases = [ + {validator: {$expr: {$eq: [{$round: "$a"}, 4]}}, nonMatchingDocument: {a: 5.2}}, + {validator: {$expr: {$eq: [{$trunc: ["$a", 2]}, 4.1]}}, nonMatchingDocument: {a: 4.23}}, + { + validator: {$expr: {$regexMatch: {input: "$a", regex: /sentinel/}}}, + nonMatchingDocument: {a: "no dice"} + }, + ]; + + let conn = MongoRunner.runMongod({dbpath: dbpath, binVersion: "latest"}); + assert.neq(null, conn, "mongod was unable to start up"); + + let testDB = conn.getDB(testName); + + let adminDB = conn.getDB("admin"); + + // Explicitly set feature compatibility version 4.2. + assert.commandWorked(adminDB.runCommand({setFeatureCompatibilityVersion: "4.2"})); + + testCases.forEach(function(test, i) { + // Create a collection with a validator using 4.2 query features. + const coll = testDB["coll" + i]; + assert.commandWorked( + testDB.createCollection(coll.getName(), {validator: test.validator}), + `Expected to be able to create collection with validator ${tojson(test.validator)}`); + + // The validator should cause this insert to fail. + assert.writeErrorWithCode( + coll.insert(test.nonMatchingDocument), + ErrorCodes.DocumentValidationFailure, + `Expected document ${tojson(test.nonMatchingDocument)} to fail validation for ` + + `collection with validator ${tojson(test.validator)}`); + + // Set a validator using 4.2 query features on an existing collection. + coll.drop(); + assert.commandWorked(testDB.createCollection(coll.getName())); + assert.commandWorked( + testDB.runCommand({collMod: coll.getName(), validator: test.validator}), + `Expected to be able to modify collection validator to be ${tojson(test.validator)}`); + + // Another failing update. + assert.writeErrorWithCode( + coll.insert(test.nonMatchingDocument), + ErrorCodes.DocumentValidationFailure, + `Expected document ${tojson(test.nonMatchingDocument)} to fail validation for ` + + `collection with validator ${tojson(test.validator)}`); + }); + + // Set the feature compatibility version to 4.0. + assert.commandWorked(adminDB.runCommand({setFeatureCompatibilityVersion: "4.0"})); + + testCases.forEach(function(test, i) { + // The validator is already in place, so it should still cause this insert to fail. + const coll = testDB["coll" + i]; + assert.writeErrorWithCode( + coll.insert(test.nonMatchingDocument), + ErrorCodes.DocumentValidationFailure, + `Expected document ${tojson(test.nonMatchingDocument)} to fail validation for ` + + `collection with validator ${tojson(test.validator)}`); + + // Trying to create a new collection with a validator using 4.2 query features should fail + // while feature compatibility version is 4.0. + let res = testDB.createCollection("other", {validator: test.validator}); + assert.commandFailedWithCode( + res, + ErrorCodes.QueryFeatureNotAllowed, + 'Expected *not* to be able to create collection with validator ' + + tojson(test.validator)); + assert( + res.errmsg.match(/feature compatibility version/), + `Expected error message from createCollection with validator ` + + `${tojson(test.validator)} to reference 'feature compatibility version' but got: ` + + res.errmsg); + + // Trying to update a collection with a validator using 4.2 query features should also fail. + res = testDB.runCommand({collMod: coll.getName(), validator: test.validator}); + assert.commandFailedWithCode( + res, + ErrorCodes.QueryFeatureNotAllowed, + `Expected to be able to create collection with validator ${tojson(test.validator)}`); + assert( + res.errmsg.match(/feature compatibility version/), + `Expected error message from createCollection with validator ` + + `${tojson(test.validator)} to reference 'feature compatibility version' but got: ` + + res.errmsg); + }); + + MongoRunner.stopMongod(conn); + + // If we try to start up a 4.0 mongod, it will fail, because it will not be able to parse + // the validator using 4.2 query features. + conn = MongoRunner.runMongod({dbpath: dbpath, binVersion: "4.0", noCleanData: true}); + assert.eq( + null, conn, "mongod 4.0 started, even with a validator using 4.2 query features in place."); + + // Starting up a 4.2 mongod, however, should succeed, even though the feature compatibility + // version is still set to 4.0. + conn = MongoRunner.runMongod({dbpath: dbpath, binVersion: "latest", noCleanData: true}); + assert.neq(null, conn, "mongod was unable to start up"); + + adminDB = conn.getDB("admin"); + testDB = conn.getDB(testName); + + // And the validator should still work. + testCases.forEach(function(test, i) { + const coll = testDB["coll" + i]; + assert.writeErrorWithCode( + coll.insert(test.nonMatchingDocument), + ErrorCodes.DocumentValidationFailure, + `Expected document ${tojson(test.nonMatchingDocument)} to fail validation for ` + + `collection with validator ${tojson(test.validator)}`); + + // Remove the validator. + assert.commandWorked(testDB.runCommand({collMod: coll.getName(), validator: {}})); + }); + + MongoRunner.stopMongod(conn); + + // Now, we should be able to start up a 4.0 mongod. + conn = MongoRunner.runMongod({dbpath: dbpath, binVersion: "4.0", noCleanData: true}); + assert.neq( + null, + conn, + "mongod 4.0 failed to start, even after we removed the validator using 4.2 query features"); + + MongoRunner.stopMongod(conn); + + // The rest of the test uses mongod 4.2. + conn = MongoRunner.runMongod({dbpath: dbpath, binVersion: "latest", noCleanData: true}); + assert.neq(null, conn, "mongod was unable to start up"); + + adminDB = conn.getDB("admin"); + testDB = conn.getDB(testName); + + // Set the feature compatibility version back to 4.2. + assert.commandWorked(adminDB.runCommand({setFeatureCompatibilityVersion: "4.2"})); + + testCases.forEach(function(test, i) { + const coll = testDB["coll2" + i]; + + // Now we should be able to create a collection with a validator using 4.2 query features + // again. + assert.commandWorked( + testDB.createCollection(coll.getName(), {validator: test.validator}), + `Expected to be able to create collection with validator ${tojson(test.validator)}`); + + // And we should be able to modify a collection to have a validator using 4.2 query + // features. + assert.commandWorked( + testDB.runCommand({collMod: coll.getName(), validator: test.validator}), + `Expected to be able to modify collection validator to be ${tojson(test.validator)}`); + }); + + // Set the feature compatibility version to 4.0 and then restart with + // internalValidateFeaturesAsMaster=false. + assert.commandWorked(adminDB.runCommand({setFeatureCompatibilityVersion: "4.0"})); + 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); + + testCases.forEach(function(test, i) { + const coll = testDB["coll3" + i]; + // Even though the feature compatibility version is 4.0, we should still be able to add a + // validator using 4.2 query features, because internalValidateFeaturesAsMaster is false. + assert.commandWorked( + testDB.createCollection(coll.getName(), {validator: test.validator}), + `Expected to be able to create collection with validator ${tojson(test.validator)}`); + + // We should also be able to modify a collection to have a validator using 4.2 query + // features. + coll.drop(); + assert.commandWorked(testDB.createCollection(coll.getName())); + assert.commandWorked( + testDB.runCommand({collMod: coll.getName(), validator: test.validator}), + `Expected to be able to modify collection validator to be ${tojson(test.validator)}`); + }); + + MongoRunner.stopMongod(conn); + +}()); 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..c8d9eb54d12 --- /dev/null +++ b/jstests/multiVersion/view_definition_feature_compatibility_version.js @@ -0,0 +1,196 @@ +/** + * Test that mongod will not allow creation of a view using 4.2 aggregation features when the + * feature compatibility version is older than 4.2. + * + * TODO SERVER-41273: Remove FCV 4.0 validation during the 4.3 development cycle. + * + * 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; + + // In order to avoid restarting the server for each test case, we declare all the test cases up + // front, and test them all at once. + const pipelinesWithNewFeatures = [ + [{$addFields: {x: {$round: 4.57}}}], + [{$addFields: {x: {$trunc: [4.57, 1]}}}], + [{$addFields: {x: {$regexFind: {input: "string", regex: /st/}}}}], + [{$addFields: {x: {$regexFindAll: {input: "string", regex: /st/}}}}], + [{$addFields: {x: {$regexMatch: {input: "string", regex: /st/}}}}], + [{$facet: {pipe1: [{$addFields: {x: {$round: 4.57}}}]}}], + [{ + $facet: { + pipe1: [{$addFields: {x: {$round: 4.57}}}], + pipe2: [{$addFields: {newThing: {$regexMatch: {input: "string", regex: /st/}}}}] + } + }], + [{$lookup: {from: 'x', pipeline: [{$addFields: {x: {$round: 4.57}}}], as: 'results'}}], + [{ + $graphLookup: { + from: 'x', + startWith: ["$_id"], + connectFromField: "target_id", + connectToField: "_id", + restrictSearchWithMatch: {$expr: {$eq: [4, {$round: "$x"}]}}, + as: 'results' + } + }], + [{ + $lookup: { + from: 'x', + pipeline: [{$facet: {pipe1: [{$addFields: {x: {$round: 4.57}}}]}}], + as: 'results' + } + }], + ]; + + 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 4.2. + assert.commandWorked(testDB.adminCommand({setFeatureCompatibilityVersion: "4.2"})); + + // Test that we are able to create a new view with any of the new features. + pipelinesWithNewFeatures.forEach( + (pipe, i) => assert.commandWorked( + testDB.createView("firstView" + i, "coll", pipe), + `Expected to be able to create view with pipeline ${tojson(pipe)} while in FCV 4.2`)); + + // Test that we are able to create a new view with any of the new features. + pipelinesWithNewFeatures.forEach(function(pipe, i) { + assert(testDB["firstView" + i].drop(), `Drop of view with pipeline ${tojson(pipe)} failed`); + assert.commandWorked(testDB.createView("firstView" + i, "coll", [])); + assert.commandWorked( + testDB.runCommand({collMod: "firstView" + i, viewOn: "coll", pipeline: pipe}), + `Expected to be able to modify view to use pipeline ${tojson(pipe)} while in FCV 4.2`); + }); + + // Create an empty view which we will attempt to update to use 4.0 query features under + // feature compatibility mode 4.0. + assert.commandWorked(testDB.createView("emptyView", "coll", [])); + + // Set the feature compatibility version to 4.0. + assert.commandWorked(testDB.adminCommand({setFeatureCompatibilityVersion: "4.0"})); + + // Read against an existing view using 4.2 query features should not fail. + pipelinesWithNewFeatures.forEach( + (pipe, i) => assert.commandWorked(testDB.runCommand({find: "firstView" + i}), + `Failed to query view with pipeline ${tojson(pipe)}`)); + + // Trying to create a new view using 4.2 query features should fail. + pipelinesWithNewFeatures.forEach( + (pipe, i) => assert.commandFailedWithCode( + testDB.createView("view_fail" + i, "coll", pipe), + ErrorCodes.QueryFeatureNotAllowed, + `Expected *not* to be able to create view with pipeline ${tojson(pipe)} while in FCV 4.0`)); + + // Trying to update existing view to use 4.2 query features should also fail. + pipelinesWithNewFeatures.forEach( + (pipe, i) => assert.commandFailedWithCode( + testDB.runCommand({collMod: "emptyView", viewOn: "coll", pipeline: pipe}), + ErrorCodes.QueryFeatureNotAllowed, + `Expected *not* to be able to modify view to use pipeline ${tojson(pipe)} while in FCV + 4.0`)); + + MongoRunner.stopMongod(conn); + + // Starting up a 4.0 mongod with 4.2 query features will succeed. + conn = MongoRunner.runMongod({dbpath: dbpath, binVersion: "4.0", noCleanData: true}); + assert.neq(null, conn, "mongod 4.0 was unable to start up"); + testDB = conn.getDB(testName); + + // Reads will fail against views with 4.2 query features when running a 4.0 binary. + // Not checking the code returned on failure as it is not uniform across the various + // 'pipeline' arguments tested. + pipelinesWithNewFeatures.forEach( + (pipe, i) => assert.commandFailed( + testDB.runCommand({find: "firstView" + i}), + `Expected read against view with pipeline ${tojson(pipe)} to fail on 4.0 binary`)); + + // Test that a read against a view that does not contain 4.2 query features succeeds. + assert.commandWorked(testDB.runCommand({find: "emptyView"})); + + MongoRunner.stopMongod(conn); + + // Starting up a 4.2 mongod should succeed, even though the feature compatibility version is + // still set to 4.0. + conn = MongoRunner.runMongod({dbpath: dbpath, binVersion: "latest", noCleanData: true}); + assert.neq(null, conn, "mongod was unable to start up"); + testDB = conn.getDB(testName); + + // Read against an existing view using 4.2 query features should not fail. + pipelinesWithNewFeatures.forEach( + (pipe, i) => assert.commandWorked(testDB.runCommand({find: "firstView" + i}), + `Failed to query view with pipeline ${tojson(pipe)}`)); + + // Set the feature compatibility version back to 4.2. + assert.commandWorked(testDB.adminCommand({setFeatureCompatibilityVersion: "4.2"})); + + pipelinesWithNewFeatures.forEach(function(pipe, i) { + assert.commandWorked(testDB.runCommand({find: "firstView" + i}), + `Failed to query view with pipeline ${tojson(pipe)}`); + // Test that we are able to create a new view with any of the new features. + assert.commandWorked( + testDB.createView("secondView" + i, "coll", pipe), + `Expected to be able to create view with pipeline ${tojson(pipe)} while in FCV 4.2`); + + // Test that we are able to update an existing view to use any of the new features. + assert(testDB["secondView" + i].drop(), + `Drop of view with pipeline ${tojson(pipe)} failed`); + assert.commandWorked(testDB.createView("secondView" + i, "coll", [])); + assert.commandWorked( + testDB.runCommand({collMod: "secondView" + i, viewOn: "coll", pipeline: pipe}), + `Expected to be able to modify view to use pipeline ${tojson(pipe)} while in FCV 4.2`); + }); + + // Set the feature compatibility version to 4.0 and then restart with + // internalValidateFeaturesAsMaster=false. + assert.commandWorked(testDB.adminCommand({setFeatureCompatibilityVersion: "4.0"})); + 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); + + pipelinesWithNewFeatures.forEach(function(pipe, i) { + // Even though the feature compatibility version is 4.0, we should still be able to create a + // view using 4.2 query features, because internalValidateFeaturesAsMaster is false. + assert.commandWorked( + testDB.createView("thirdView" + i, "coll", pipe), + `Expected to be able to create view with pipeline ${tojson(pipe)} while in FCV 4.0 ` + + `with internalValidateFeaturesAsMaster=false`); + + // We should also be able to modify a view to use 4.2 query features. + assert(testDB["thirdView" + i].drop(), `Drop of view with pipeline ${tojson(pipe)} failed`); + assert.commandWorked(testDB.createView("thirdView" + i, "coll", [])); + assert.commandWorked( + testDB.runCommand({collMod: "thirdView" + i, viewOn: "coll", pipeline: pipe}), + `Expected to be able to modify view to use pipeline ${tojson(pipe)} while in FCV 4.0 ` + + `with internalValidateFeaturesAsMaster=false`); + }); + + MongoRunner.stopMongod(conn); + + // Starting up a 4.0 mongod with 4.2 query features. + conn = MongoRunner.runMongod({dbpath: dbpath, binVersion: "4.0", noCleanData: true}); + assert.neq(null, conn, "mongod 4.0 was unable to start up"); + testDB = conn.getDB(testName); + + // Existing views with 4.2 query features can be dropped. + pipelinesWithNewFeatures.forEach( + (pipe, i) => assert(testDB["firstView" + i].drop(), + `Drop of view with pipeline ${tojson(pipe)} failed`)); + assert(testDB.system.views.drop(), "Drop of system.views collection failed"); + + MongoRunner.stopMongod(conn); +}()); diff --git a/jstests/replsets/collection_validator_initial_sync_with_feature_compatibility_version.js b/jstests/replsets/collection_validator_initial_sync_with_feature_compatibility_version.js new file mode 100644 index 00000000000..6a9101c3586 --- /dev/null +++ b/jstests/replsets/collection_validator_initial_sync_with_feature_compatibility_version.js @@ -0,0 +1,91 @@ +/** + * Test that a new replica set member can successfully sync a collection with a validator using 4.2 + * aggregation features, even when the replica set was downgraded to feature compatibility version + * 4.0. + * + * TODO SERVER-41273: Remove FCV 4.0 validation during the 4.3 development cycle. + * + * 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] + */ + +load("jstests/replsets/rslib.js"); + +(function() { + "use strict"; + const testName = "collection_validator_initial_sync_with_feature_compatibility"; + + function testValidator(validator, nonMatchingDocument) { + // + // 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 4.2. + // + assert.commandWorked(primary.adminCommand({setFeatureCompatibilityVersion: "4.2"})); + + // + // Create a collection with a validator using 4.2 query features. + // + assert.commandWorked(testDB.createCollection("coll", {validator: validator})); + + // + // Downgrade the replica set to feature compatibility version 4.0. + // + assert.commandWorked(primary.adminCommand({setFeatureCompatibilityVersion: "4.0"})); + + // + // 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 validator synced to the new member by attempting to insert a document + // that does not validate and checking that the insert fails. + // + let secondaryDB = secondary.getDB("test"); + assert.writeError(secondaryDB.coll.insert(nonMatchingDocument), + ErrorCodes.DocumentValidationFailure); + + // + // Verify that, even though the existing validator still works, it is not possible to create + // a new validator using 4.2 query features because of feature compatibility version 4.0. + // + assert.commandFailedWithCode( + secondaryDB.runCommand({collMod: "coll", validator: validator}), + ErrorCodes.QueryFeatureNotAllowed); + + MongoRunner.stopMongod(secondary); + } + + // Ban the use of expressions that were introduced or had their parsing modified in 4.2. + testValidator({$expr: {$eq: [{$round: "$a"}, 4]}}, {a: 5.2}); + testValidator({$expr: {$eq: [{$trunc: ["$a", 2]}, 4.1]}}, {a: 4.23}); + testValidator({$expr: {$regexMatch: {input: "$a", regex: /sentinel/}}}, {a: "no dice"}); +}()); diff --git a/jstests/replsets/view_definition_initial_sync_with_feature_compatibility_version.js b/jstests/replsets/view_definition_initial_sync_with_feature_compatibility_version.js new file mode 100644 index 00000000000..ad5ef00391d --- /dev/null +++ b/jstests/replsets/view_definition_initial_sync_with_feature_compatibility_version.js @@ -0,0 +1,116 @@ +/** + * Test that a new replica set member can successfully sync a collection with a view using 4.2 + * aggregation features, even when the replica set was downgraded to feature compatibility version + * 4.0. + * + * TODO SERVER-41273: Remove FCV 4.0 validation during the 4.3 development cycle. + * + * 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] + */ + +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 4.2. + // + assert.commandWorked(primary.adminCommand({setFeatureCompatibilityVersion: "4.2"})); + + // + // Create a view using 4.2 query features. + // + assert.commandWorked(testDB.createView("view1", "coll", pipeline)); + + // + // Downgrade the replica set to feature compatibility version 4.0. + // + assert.commandWorked(primary.adminCommand({setFeatureCompatibilityVersion: "4.0"})); + + // + // 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 4.2 query features exists, it is not possible to + // create a new view using 4.2 query features because of feature compatibility version 4.0. + // + assert.commandFailedWithCode(secondaryDB.createView("view2", "coll", pipeline), + ErrorCodes.QueryFeatureNotAllowed); + + MongoRunner.stopMongod(secondary); + } + + testView([{$addFields: {x: {$round: 4.57}}}]); + testView([{$addFields: {x: {$trunc: [4.57, 1]}}}]); + testView([{$addFields: {x: {$regexFind: {input: "string", regex: /st/}}}}]); + testView([{$addFields: {x: {$regexFindAll: {input: "string", regex: /st/}}}}]); + testView([{$addFields: {x: {$regexMatch: {input: "string", regex: /st/}}}}]); + testView([{$facet: {pipe1: [{$addFields: {x: {$round: 4.57}}}]}}]); + testView([{ + $facet: { + pipe1: [{$addFields: {x: {$round: 4.57}}}], + pipe2: [{$addFields: {newThing: {$regexMatch: {input: "string", regex: /st/}}}}] + } + }]); + testView( + [{$lookup: {from: 'x', pipeline: [{$addFields: {x: {$round: 4.57}}}], as: 'results'}}]); + testView([{ + $graphLookup: { + from: 'x', + startWith: ["$_id"], + connectFromField: "target_id", + connectToField: "_id", + restrictSearchWithMatch: {$expr: {$eq: [4, {$round: "$x"}]}}, + as: 'results' + } + }]); + testView([{ + $lookup: { + from: 'x', + pipeline: [{$facet: {pipe1: [{$addFields: {x: {$round: 4.57}}}]}}], + as: 'results' + } + }]); +}()); diff --git a/src/mongo/db/pipeline/expression.cpp b/src/mongo/db/pipeline/expression.cpp index 0e8984a81fa..d6068398342 100644 --- a/src/mongo/db/pipeline/expression.cpp +++ b/src/mongo/db/pipeline/expression.cpp @@ -4924,7 +4924,10 @@ Value ExpressionRound::evaluate(const Document& root) const { root, _children, getOpName(), Decimal128::kRoundTiesToEven, &std::round); } -REGISTER_EXPRESSION(round, ExpressionRound::parse); +REGISTER_EXPRESSION_WITH_MIN_VERSION( + round, + ExpressionRound::parse, + ServerGlobalParams::FeatureCompatibility::Version::kFullyUpgradedTo42); const char* ExpressionRound::getOpName() const { return "$round"; } @@ -4934,6 +4937,30 @@ Value ExpressionTrunc::evaluate(const Document& root) const { root, _children, getOpName(), Decimal128::kRoundTowardZero, &std::trunc); } +intrusive_ptr<Expression> ExpressionTrunc::parse(const intrusive_ptr<ExpressionContext>& expCtx, + BSONElement elem, + const VariablesParseState& vps) { + // In version 4.2 we added new arguments. In all previous versions the expression existed but + // only supported a single argument. + const bool newArgumentsAllowed = + (!expCtx->maxFeatureCompatibilityVersion || + (*expCtx->maxFeatureCompatibilityVersion >= + ServerGlobalParams::FeatureCompatibility::Version::kFullyUpgradedTo42)); + uassert( + ErrorCodes::QueryFeatureNotAllowed, + // TODO SERVER-31968 we would like to include the current version and the required minimum + // version in this error message, but using FeatureCompatibilityVersion::toString() would + // introduce a dependency cycle. + str::stream() + << elem.fieldNameStringData() + << " with >1 argument is not allowed in the current feature compatibility version. See " + << feature_compatibility_version_documentation::kCompatibilityLink + << " for more information.", + // Allow non-arrays since they will be rejected anyway by the parser below. + elem.type() != BSONType::Array || elem.Array().size() <= 1 || newArgumentsAllowed); + return ExpressionRangedArity<ExpressionTrunc, 1, 2>::parse(expCtx, elem, vps); +} + REGISTER_EXPRESSION(trunc, ExpressionTrunc::parse); const char* ExpressionTrunc::getOpName() const { return "$trunc"; @@ -6015,7 +6042,10 @@ void ExpressionRegex::_doAddDependencies(DepsTracker* deps) const { /* -------------------------- ExpressionRegexFind ------------------------------ */ -REGISTER_EXPRESSION(regexFind, ExpressionRegexFind::parse); +REGISTER_EXPRESSION_WITH_MIN_VERSION( + regexFind, + ExpressionRegexFind::parse, + ServerGlobalParams::FeatureCompatibility::Version::kFullyUpgradedTo42); boost::intrusive_ptr<Expression> ExpressionRegexFind::parse( const boost::intrusive_ptr<ExpressionContext>& expCtx, BSONElement expr, @@ -6036,7 +6066,10 @@ Value ExpressionRegexFind::evaluate(const Document& root) const { /* -------------------------- ExpressionRegexFindAll ------------------------------ */ -REGISTER_EXPRESSION(regexFindAll, ExpressionRegexFindAll::parse); +REGISTER_EXPRESSION_WITH_MIN_VERSION( + regexFindAll, + ExpressionRegexFindAll::parse, + ServerGlobalParams::FeatureCompatibility::Version::kFullyUpgradedTo42); boost::intrusive_ptr<Expression> ExpressionRegexFindAll::parse( const boost::intrusive_ptr<ExpressionContext>& expCtx, BSONElement expr, @@ -6097,7 +6130,10 @@ Value ExpressionRegexFindAll::evaluate(const Document& root) const { /* -------------------------- ExpressionRegexMatch ------------------------------ */ -REGISTER_EXPRESSION(regexMatch, ExpressionRegexMatch::parse); +REGISTER_EXPRESSION_WITH_MIN_VERSION( + regexMatch, + ExpressionRegexMatch::parse, + ServerGlobalParams::FeatureCompatibility::Version::kFullyUpgradedTo42); boost::intrusive_ptr<Expression> ExpressionRegexMatch::parse( const boost::intrusive_ptr<ExpressionContext>& expCtx, BSONElement expr, diff --git a/src/mongo/db/pipeline/expression.h b/src/mongo/db/pipeline/expression.h index 7033ee717d1..7ec1700afb3 100644 --- a/src/mongo/db/pipeline/expression.h +++ b/src/mongo/db/pipeline/expression.h @@ -2325,6 +2325,10 @@ public: explicit ExpressionTrunc(const boost::intrusive_ptr<ExpressionContext>& expCtx) : ExpressionRangedArity<ExpressionTrunc, 1, 2>(expCtx) {} + static boost::intrusive_ptr<Expression> parse( + const boost::intrusive_ptr<ExpressionContext>& expCtx, + BSONElement elem, + const VariablesParseState& vps); Value evaluate(const Document& root) const final; const char* getOpName() const final; diff --git a/src/mongo/db/pipeline/expression_context.cpp b/src/mongo/db/pipeline/expression_context.cpp index 0054e0de98c..9f65c12669f 100644 --- a/src/mongo/db/pipeline/expression_context.cpp +++ b/src/mongo/db/pipeline/expression_context.cpp @@ -160,6 +160,7 @@ intrusive_ptr<ExpressionContext> ExpressionContext::copyWith( expCtx->inMongos = inMongos; expCtx->allowDiskUse = allowDiskUse; expCtx->bypassDocumentValidation = bypassDocumentValidation; + expCtx->maxFeatureCompatibilityVersion = maxFeatureCompatibilityVersion; expCtx->subPipelineDepth = subPipelineDepth; expCtx->tempDir = tempDir; |