Files
mongo/jstests/multiVersion/genericSetFCVUsage/collection_validator_feature_compatibility_version.js
Felipe Farinon 61ac8b0aec SERVER-115004 Add remaining FCV tests for the new syntax on map/reduce/filter (#45419)
Co-authored-by: Gil Alon <gil.alon@mongodb.com>
GitOrigin-RevId: f395bea66bd346c6ecaaae05641e2753790eb4f2
2025-12-19 11:32:55 +00:00

346 lines
14 KiB
JavaScript

/**
* Test that mongod will not allow creation of collection validators using new query features when
* the feature compatibility version is older than the latest version.
*
* We restart mongod during the test and expect it to have the same data after restarting.
* @tags: [requires_persistence]
*/
const testName = "collection_validator_feature_compatibility_version";
const dbpath = MongoRunner.dataPath + testName;
// An array of feature flags that must be enabled to run feature flag tests.
const featureFlagsToEnable = ["featureFlagExposeArrayIndexInMapFilterReduce"];
// These arrays should be populated with
//
// { validator: { ... }, nonMatchingDocument: { ... } }
//
// objects that use query features in new versions of mongod. Note that this also
// includes new aggregation expressions able to be used with the $expr match expression. This
// test ensures that a collection validator accepts the new query feature when the feature
// compatibility version is the latest version, and rejects it when the feature compatibility
// version is the last version.
const testCasesLastContinuous = [
//
// Populate with any new expressions.
//
];
const testCasesLastContinuousWithFeatureFlags = [
// TODO(SERVER-115778): Move arrayIndexAs/as/valueAs queries to 'testCasesLastStable' when 8.3 becomes last continuous.
// TODO(SERVER-90514): Remove arrayIndexAs/as/valueAs queries when feature flag is removed.
{
validator: {
$expr: {
$eq: [
{
$map: {
input: "$a",
arrayIndexAs: "i",
in: "$$i",
},
},
[0, 1, 2],
],
},
},
nonMatchingDocument: {a: [0, 0]},
},
{
validator: {
$expr: {
$eq: [
{
$reduce: {
input: "$a",
arrayIndexAs: "i",
initialValue: 0,
in: {$add: ["$$value", "$$i"]},
},
},
0,
],
},
},
nonMatchingDocument: {a: [0, 1]},
},
{
validator: {
$expr: {
$eq: [
{
$reduce: {
input: "$a",
initialValue: 0,
in: {$add: ["$$value", "$$IDX"]},
},
},
0,
],
},
},
nonMatchingDocument: {a: [0, 1]},
},
{
validator: {
$expr: {
$eq: [
{
$reduce: {
input: "$a",
as: "elem",
valueAs: "acc",
initialValue: 0,
in: {$add: ["$$acc", "$$elem"]},
},
},
0,
],
},
},
nonMatchingDocument: {a: [0, 1]},
},
{
validator: {
$expr: {
$eq: [
{
$filter: {
input: "$a",
arrayIndexAs: "i",
cond: {$eq: ["$$i", 1]},
},
},
[1, 2, 3],
],
},
},
nonMatchingDocument: {a: [0, 0]},
},
];
const testCasesLastStable = testCasesLastContinuous.concat([]);
const testCasesLastStableWithFeatureFlags = testCasesLastContinuousWithFeatureFlags.concat([]);
// Tests Feature Compatibility Version behavior of the validator of a collection by executing test
// cases 'testCases' and using a previous stable version 'lastVersion' of mongod. 'lastVersion' can
// have values "last-lts" and "last-continuous".
function testCollectionValidatorFCVBehavior(lastVersion, testCases, featureFlags = []) {
if (testCases.length === 0) {
jsTest.log.info("Skipping setup for tests against " + lastVersion + " since there are none");
return;
}
let conn = MongoRunner.runMongod({dbpath: dbpath, binVersion: "latest"});
assert.neq(null, conn, "mongod was unable to start up");
let testDB = conn.getDB(testName);
for (let i = 0; i < featureFlags.length; i++) {
const command = {"getParameter": 1};
command[featureFlags[i]] = 1;
const featureEnabled = assert.commandWorked(testDB.adminCommand(command))[featureFlags[i]].value;
if (!featureEnabled) {
jsTest.log.info("Skipping test because the " + featureFlags[i] + " feature flag is disabled");
MongoRunner.stopMongod(conn);
return;
}
}
let adminDB = conn.getDB("admin");
// Explicitly set the feature compatibility version to the latest version.
assert.commandWorked(adminDB.runCommand({setFeatureCompatibilityVersion: latestFCV, confirm: true}));
testCases.forEach(function (test, i) {
// Create a collection with a validator using new 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 new 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 the last version.
assert.commandWorked(
adminDB.runCommand({setFeatureCompatibilityVersion: binVersionToFCV(lastVersion), confirm: true}),
);
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 new query features should
// fail while feature compatibility version is the last version.
let res = testDB.createCollection("other", {validator: test.validator});
assert.commandFailed(
res,
"Expected *not* to be able to create collection with validator " + tojson(test.validator),
);
// Trying to update a collection with a validator using new query features should also
// fail.
res = testDB.runCommand({collMod: coll.getName(), validator: test.validator});
assert.commandFailed(res, `Expected to be able to create collection with validator ${tojson(test.validator)}`);
});
MongoRunner.stopMongod(conn);
// Versions of mongod 4.2 and later are able to start up with a collection validator that's
// considered invalid. However, any writes to the collection will fail.
conn = MongoRunner.runMongod({dbpath: dbpath, binVersion: lastVersion, noCleanData: true});
assert.neq(null, conn, lastVersion + " mongod was unable to start up with invalid validator");
testDB = conn.getDB(testName);
// Check that writes fail to all collections with validators using new query features.
testCases.forEach(function (test, i) {
const coll = testDB["coll" + i];
const res = assert.writeError(coll.insert(test.nonMatchingDocument));
assert.neq(
res.getWriteError(),
ErrorCodes.DocumentValidationFailure,
`Expected validator ${tojson(test.validator)} to not be able to execute new query features`,
);
});
MongoRunner.stopMongod(conn);
// Starting up the latest version of mongod should succeed, even though the feature
// compatibility version is still set to the last version.
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 shouldn't be able to execute the query with new features.
testCases.forEach(function (test, i) {
const coll = testDB["coll" + i];
const res = assert.writeError(coll.insert(test.nonMatchingDocument));
assert.neq(
res.getWriteError(),
ErrorCodes.DocumentValidationFailure,
`Expected validator ${tojson(test.validator)} to not be able to execute new query features`,
);
// Remove the validator.
assert.commandWorked(testDB.runCommand({collMod: coll.getName(), validator: {}}));
});
MongoRunner.stopMongod(conn);
// Now, we should be able to start up the last version of mongod.
conn = MongoRunner.runMongod({dbpath: dbpath, binVersion: lastVersion, noCleanData: true});
assert.neq(
null,
conn,
`version ${MongoRunner.getBinVersionFor(lastVersion)} of mongod failed to start, even` +
" after we removed the validator using new query features",
);
MongoRunner.stopMongod(conn);
// The rest of the test uses the latest version of mongod.
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 the latest version.
assert.commandWorked(adminDB.runCommand({setFeatureCompatibilityVersion: latestFCV, confirm: true}));
testCases.forEach(function (test, i) {
const coll = testDB["coll2" + i];
// Now we should be able to create a collection with a validator using new 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 new 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 the last version and then restart with
// internalValidateFeaturesAsPrimary=false.
assert.commandWorked(
adminDB.runCommand({setFeatureCompatibilityVersion: binVersionToFCV(lastVersion), confirm: true}),
);
MongoRunner.stopMongod(conn);
// TODO SERVER-115604 investigate usage of internalValidateFeaturesAsPrimary, and confirm if we
// should remove this or fix query validation.
/*conn = MongoRunner.runMongod({
dbpath: dbpath,
binVersion: "latest",
noCleanData: true,
setParameter: "internalValidateFeaturesAsPrimary=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 the last version, we should still
// be able to add a validator using new query features, because
// internalValidateFeaturesAsPrimary 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 new 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);*/
}
testCollectionValidatorFCVBehavior("last-lts", testCasesLastStable);
testCollectionValidatorFCVBehavior("last-lts", testCasesLastStableWithFeatureFlags, featureFlagsToEnable);
testCollectionValidatorFCVBehavior("last-continuous", testCasesLastContinuous);
testCollectionValidatorFCVBehavior("last-continuous", testCasesLastContinuousWithFeatureFlags, featureFlagsToEnable);