SERVER-23761 set NR feature bit for collation
This will ensure downgrade fails after creating collections or indices with a non-simple collation.
This commit is contained in:
@@ -19,7 +19,17 @@
|
||||
foundIndex = true;
|
||||
// We assume that the key pattern is unique, even though indices with different
|
||||
// collations but the same key pattern are allowed.
|
||||
assert.eq(indexSpecs[i].collation, collation);
|
||||
if (collation.locale === "simple") {
|
||||
// The simple collation is not explicitly stored in the catalog, so we expect
|
||||
// the "collation" field to be absent.
|
||||
assert(!indexSpecs[i].hasOwnProperty("collation"),
|
||||
"Expected the simple collation in: " + tojson(indexSpecs[i]));
|
||||
} else {
|
||||
assert.eq(indexSpecs[i].collation,
|
||||
collation,
|
||||
"Expected collation " + tojson(collation) + " in: " +
|
||||
tojson(indexSpecs[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(foundIndex, "index with key pattern " + tojson(keyPattern) + " not found");
|
||||
@@ -48,7 +58,7 @@
|
||||
assert.commandWorked(db.createCollection("collation", {collation: {locale: "simple"}}));
|
||||
var collectionInfos = db.getCollectionInfos({name: "collation"});
|
||||
assert.eq(collectionInfos.length, 1);
|
||||
assert.eq(collectionInfos[0].options.collation, {locale: "simple"});
|
||||
assert(!collectionInfos[0].options.hasOwnProperty("collation"));
|
||||
coll.drop();
|
||||
|
||||
// Ensure that we populate all collation-related fields when we create a collection with a valid
|
||||
|
||||
216
jstests/multiVersion/collation_downgrade.js
Normal file
216
jstests/multiVersion/collation_downgrade.js
Normal file
@@ -0,0 +1,216 @@
|
||||
/**
|
||||
* Test downgrade behavior for collation:
|
||||
* - The server should fail to start up on downgrade to 3.2 or earlier after creating a collection
|
||||
* or index with a non-simple collation.
|
||||
* - The server should successfully start up on downgrade to 3.2 or earlier after creating a
|
||||
* collection or index that omits the collation.
|
||||
* - The server should successfully start up on downgrade to 3.2 or earlier after creating a
|
||||
* collection or index that explicitly specifies the simple collation with {locale: "simple"}.
|
||||
*/
|
||||
(function() {
|
||||
"use strict";
|
||||
|
||||
var dbpath = MongoRunner.dataPath + "collation_downgrade";
|
||||
resetDbpath(dbpath);
|
||||
|
||||
var defaultOptions = {
|
||||
dbpath: dbpath,
|
||||
noCleanData: true,
|
||||
// We explicitly set the storage engine as part of the options because not all versions
|
||||
// being tested automatically detect it from the storage.bson file.
|
||||
storageEngine: jsTest.options().storageEngine || "wiredTiger",
|
||||
};
|
||||
|
||||
// TODO SERVER-23761: also prevent users from downgrading on MMAP.
|
||||
if (defaultOptions.storageEngine === "mmapv1") {
|
||||
print("Skipping test on mmapv1 storage engine");
|
||||
return;
|
||||
}
|
||||
|
||||
// Whenever we start "latest", we use the "enableBSON1_1" server parameter to force indices
|
||||
// created with the wiredTiger storage engine to use KeyString V0. Otherwise, downgrade will
|
||||
// fail due to creating KeyString V1 indices rather than exercising the code which prevents
|
||||
// downgrading in the presence of non-simple collations.
|
||||
var latestOptions =
|
||||
Object.extend({binVersion: "latest", setParameter: "enableBSON1_1=false"}, defaultOptions);
|
||||
|
||||
var downgradeVersion = "3.2";
|
||||
var downgradeOptions = Object.extend({binVersion: downgradeVersion}, defaultOptions);
|
||||
|
||||
//
|
||||
// Test that downgrade is possible after creating collections or indexes with the simple
|
||||
// collation.
|
||||
//
|
||||
|
||||
// Create a collection with a simple collation on the latest version.
|
||||
var conn = MongoRunner.runMongod(latestOptions);
|
||||
assert.neq(null, conn, "mongod was unable to start up with options: " + tojson(latestOptions));
|
||||
|
||||
var testDB = conn.getDB("test");
|
||||
testDB.dropDatabase();
|
||||
assert.commandWorked(testDB.createCollection("simplecollator"));
|
||||
|
||||
// We should be able to downgrade, since the collection has no collation.
|
||||
MongoRunner.stopMongod(conn);
|
||||
conn = MongoRunner.runMongod(downgradeOptions);
|
||||
assert.neq(null,
|
||||
conn,
|
||||
"mongod should have been able to downgrade to the latest version of " +
|
||||
downgradeVersion + "; options: " + tojson(downgradeOptions));
|
||||
|
||||
// Start latest again. This time create a collection which specifies the simple collation
|
||||
// explicitly.
|
||||
MongoRunner.stopMongod(conn);
|
||||
conn = MongoRunner.runMongod(latestOptions);
|
||||
assert.neq(null, conn, "mongod was unable to start up with options: " + tojson(latestOptions));
|
||||
testDB = conn.getDB("test");
|
||||
testDB.dropDatabase();
|
||||
assert.commandWorked(
|
||||
testDB.createCollection("simplecollator", {collation: {locale: "simple"}}));
|
||||
|
||||
// Again, we should be able to downgrade.
|
||||
MongoRunner.stopMongod(conn);
|
||||
conn = MongoRunner.runMongod(downgradeOptions);
|
||||
assert.neq(null,
|
||||
conn,
|
||||
"mongod should have been able to downgrade to the latest version of " +
|
||||
downgradeVersion + "; options: " + tojson(downgradeOptions));
|
||||
|
||||
// Start latest and create an index with a simple collation.
|
||||
MongoRunner.stopMongod(conn);
|
||||
conn = MongoRunner.runMongod(latestOptions);
|
||||
assert.neq(null, conn, "mongod was unable to start up with options: " + tojson(latestOptions));
|
||||
testDB = conn.getDB("test");
|
||||
testDB.dropDatabase();
|
||||
assert.commandWorked(testDB.simplecollation.createIndex({a: 1}));
|
||||
|
||||
// Ensure we can downgrade.
|
||||
MongoRunner.stopMongod(conn);
|
||||
conn = MongoRunner.runMongod(downgradeOptions);
|
||||
assert.neq(null,
|
||||
conn,
|
||||
"mongod should have been able to downgrade to the latest version of " +
|
||||
downgradeVersion + "; options: " + tojson(downgradeOptions));
|
||||
|
||||
// Start latest and create an index which specifies the simple collation explicitly.
|
||||
MongoRunner.stopMongod(conn);
|
||||
conn = MongoRunner.runMongod(latestOptions);
|
||||
assert.neq(null, conn, "mongod was unable to start up with options: " + tojson(latestOptions));
|
||||
testDB = conn.getDB("test");
|
||||
testDB.dropDatabase();
|
||||
assert.commandWorked(
|
||||
testDB.simplecollation.createIndex({a: 1}, {collation: {locale: "simple"}}));
|
||||
|
||||
// Ensure we can downgrade.
|
||||
MongoRunner.stopMongod(conn);
|
||||
conn = MongoRunner.runMongod(downgradeOptions);
|
||||
assert.neq(null,
|
||||
conn,
|
||||
"mongod should have been able to downgrade to the latest version of " +
|
||||
downgradeVersion + "; options: " + tojson(downgradeOptions));
|
||||
|
||||
//
|
||||
// Test that the server fails to start up on downgrade after creating collections or indexes
|
||||
// with a non-simple collation.
|
||||
//
|
||||
|
||||
// Start latest and create a collection with a non-simple collation.
|
||||
MongoRunner.stopMongod(conn);
|
||||
conn = MongoRunner.runMongod(latestOptions);
|
||||
assert.neq(null, conn, "mongod was unable to start up with options: " + tojson(latestOptions));
|
||||
testDB = conn.getDB("test");
|
||||
testDB.dropDatabase();
|
||||
assert.commandWorked(testDB.createCollection("simplecollator"));
|
||||
assert.commandWorked(testDB.createCollection("nonsimple", {collation: {locale: "fr"}}));
|
||||
|
||||
// Downgrade should fail.
|
||||
MongoRunner.stopMongod(conn);
|
||||
conn = MongoRunner.runMongod(downgradeOptions);
|
||||
assert.eq(null,
|
||||
conn,
|
||||
"mongod shouldn't have been able to downgrade to the latest version of " +
|
||||
downgradeVersion + "; options: " + tojson(downgradeOptions));
|
||||
|
||||
// Start latest and drop the collection with the non-simple collation.
|
||||
MongoRunner.stopMongod(conn);
|
||||
conn = MongoRunner.runMongod(latestOptions);
|
||||
assert.neq(null, conn, "mongod was unable to start up with options: " + tojson(latestOptions));
|
||||
testDB = conn.getDB("test");
|
||||
testDB.nonsimple.drop();
|
||||
|
||||
// Now downgrade should succeed.
|
||||
MongoRunner.stopMongod(conn);
|
||||
conn = MongoRunner.runMongod(downgradeOptions);
|
||||
assert.neq(null,
|
||||
conn,
|
||||
"mongod should have been able to downgrade to the latest version of " +
|
||||
downgradeVersion + "; options: " + tojson(downgradeOptions));
|
||||
|
||||
// Start latest and create an index with a non-simple collation.
|
||||
MongoRunner.stopMongod(conn);
|
||||
conn = MongoRunner.runMongod(latestOptions);
|
||||
assert.neq(null, conn, "mongod was unable to start up with options: " + tojson(latestOptions));
|
||||
testDB = conn.getDB("test");
|
||||
testDB.dropDatabase();
|
||||
assert.commandWorked(testDB.simplecollator.createIndex({a: 1}));
|
||||
assert.commandWorked(testDB.nonsimple.createIndex({a: 1}, {collation: {locale: "fr"}}));
|
||||
|
||||
// Downgrade should fail.
|
||||
MongoRunner.stopMongod(conn);
|
||||
conn = MongoRunner.runMongod(downgradeOptions);
|
||||
assert.eq(null,
|
||||
conn,
|
||||
"mongod shouldn't have been able to downgrade to the latest version of " +
|
||||
downgradeVersion + "; options: " + tojson(downgradeOptions));
|
||||
|
||||
// Start latest and drop the index with the non-simple collation.
|
||||
MongoRunner.stopMongod(conn);
|
||||
conn = MongoRunner.runMongod(latestOptions);
|
||||
assert.neq(null, conn, "mongod was unable to start up with options: " + tojson(latestOptions));
|
||||
testDB = conn.getDB("test");
|
||||
assert.commandWorked(testDB.nonsimple.dropIndex({a: 1}));
|
||||
|
||||
// Now downgrade should succeed.
|
||||
MongoRunner.stopMongod(conn);
|
||||
conn = MongoRunner.runMongod(downgradeOptions);
|
||||
assert.neq(null,
|
||||
conn,
|
||||
"mongod should have been able to downgrade to the latest version of " +
|
||||
downgradeVersion + "; options: " + tojson(downgradeOptions));
|
||||
|
||||
//
|
||||
// Test that downgrade to 3.2.1 and 3.0 always fail. These versions do not have the code capable
|
||||
// of unsetting the collation feature bit if all indexes/collections with the collation have
|
||||
// been dropped.
|
||||
//
|
||||
|
||||
// Start latest. Create an index with a non-simple collation but then drop it.
|
||||
MongoRunner.stopMongod(conn);
|
||||
conn = MongoRunner.runMongod(latestOptions);
|
||||
assert.neq(null, conn, "mongod was unable to start up with options: " + tojson(latestOptions));
|
||||
testDB = conn.getDB("test");
|
||||
assert.commandWorked(testDB.nonsimple.createIndex({a: 1}, {collation: {locale: "fr"}}));
|
||||
assert.commandWorked(testDB.nonsimple.dropIndex({a: 1}));
|
||||
|
||||
var downgradeAlwaysFailsVersion = "3.2.1";
|
||||
var downgradeAlwaysFailsOptions =
|
||||
Object.extend({binVersion: downgradeAlwaysFailsVersion}, defaultOptions);
|
||||
|
||||
// Downgrade should fail.
|
||||
MongoRunner.stopMongod(conn);
|
||||
conn = MongoRunner.runMongod(downgradeAlwaysFailsOptions);
|
||||
assert.eq(null,
|
||||
conn,
|
||||
"mongod shouldn't have been able to downgrade to " + downgradeAlwaysFailsVersion +
|
||||
"; options: " + tojson(downgradeAlwaysFailsOptions));
|
||||
|
||||
// Downgrade to 3.0 should also fail.
|
||||
downgradeAlwaysFailsVersion = "3.0";
|
||||
downgradeAlwaysFailsOptions =
|
||||
Object.extend({binVersion: downgradeAlwaysFailsVersion}, defaultOptions);
|
||||
conn = MongoRunner.runMongod(downgradeAlwaysFailsOptions);
|
||||
assert.eq(null,
|
||||
conn,
|
||||
"mongod shouldn't have been able to downgrade to " + downgradeAlwaysFailsVersion +
|
||||
"; options: " + tojson(downgradeAlwaysFailsOptions));
|
||||
})();
|
||||
@@ -635,11 +635,13 @@ Status userCreateNS(OperationContext* txn,
|
||||
// all options that the user omitted.
|
||||
//
|
||||
// If the collator factory returned a null collator (representing the "simple" collation),
|
||||
// we can't use the collation serializer. In this case, we simply set the collation option
|
||||
// to the original user BSON.
|
||||
// we can't use the collation serializer. In this case, we simply unset the "collation" from
|
||||
// the collection options. This ensures that collections created on versions which do not
|
||||
// support the collation feature have the same format for representing the simple collation
|
||||
// as collections created on this version.
|
||||
collectionOptions.collation = collator.getValue()
|
||||
? CollationSerializer::specToBSON(collator.getValue()->getSpec())
|
||||
: collectionOptions.collation;
|
||||
: BSONObj();
|
||||
}
|
||||
|
||||
status =
|
||||
|
||||
@@ -1320,12 +1320,13 @@ StatusWith<BSONObj> IndexCatalog::_fixIndexSpec(OperationContext* txn,
|
||||
// all options that the user omitted.
|
||||
//
|
||||
// If the collator factory returned a null collator (representing the "simple" collation),
|
||||
// we can't use the collation serializer. In this case, we simply set the collation option
|
||||
// to the original user BSON.
|
||||
b.append("collation",
|
||||
collator.getValue()
|
||||
? CollationSerializer::specToBSON(collator.getValue()->getSpec())
|
||||
: collationElt.Obj());
|
||||
// we can't use the collation serializer. In this case, we simply omit the "collation" from
|
||||
// the index spec. This ensures that indices with the simple collation built on versions
|
||||
// which do not support the collation feature have the same format for representing the
|
||||
// simple collation as indices built on this version.
|
||||
if (collator.getValue()) {
|
||||
b.append("collation", CollationSerializer::specToBSON(collator.getValue()->getSpec()));
|
||||
}
|
||||
} else if (collection->getDefaultCollator()) {
|
||||
// The user did not specify an explicit collation for this index and the collection has a
|
||||
// default collator. In this case, the index inherits the collection default.
|
||||
|
||||
@@ -60,7 +60,10 @@ public:
|
||||
* The next feature added to this enumeration should use the current value of 'kNextFeatureBit',
|
||||
* and 'kNextFeatureBit' should be changed to the next largest power of two.
|
||||
*/
|
||||
enum class NonRepairableFeature : std::uint64_t { kNextFeatureBit = 1 << 0 };
|
||||
enum class NonRepairableFeature : std::uint64_t {
|
||||
kCollation = 1 << 0,
|
||||
kNextFeatureBit = 1 << 1
|
||||
};
|
||||
|
||||
using NonRepairableFeatureMask = std::underlying_type<NonRepairableFeature>::type;
|
||||
|
||||
|
||||
@@ -183,6 +183,15 @@ Status KVCollectionCatalogEntry::prepareForIndexBuild(OperationContext* txn,
|
||||
}
|
||||
imd.multikeyPaths = MultikeyPaths{static_cast<size_t>(spec->keyPattern().nFields())};
|
||||
}
|
||||
|
||||
// Mark collation feature as in use if the index has a non-simple collation.
|
||||
if (imd.spec["collation"]) {
|
||||
const auto feature = KVCatalog::FeatureTracker::NonRepairableFeature::kCollation;
|
||||
if (!_catalog->getFeatureTracker()->isNonRepairableFeatureInUse(txn, feature)) {
|
||||
_catalog->getFeatureTracker()->markNonRepairableFeatureAsInUse(txn, feature);
|
||||
}
|
||||
}
|
||||
|
||||
md.indexes.push_back(imd);
|
||||
_catalog->putMetaData(txn, ns().toString(), md);
|
||||
|
||||
|
||||
@@ -220,6 +220,15 @@ Status KVDatabaseCatalogEntry::createCollection(OperationContext* txn,
|
||||
RecordStore* rs = _engine->getEngine()->getRecordStore(txn, ns, ident, options);
|
||||
invariant(rs);
|
||||
|
||||
// Mark collation feature as in use if the collection has a non-simple default collation.
|
||||
if (!options.collation.isEmpty()) {
|
||||
const auto feature = KVCatalog::FeatureTracker::NonRepairableFeature::kCollation;
|
||||
if (_engine->getCatalog()->getFeatureTracker()->isNonRepairableFeatureInUse(txn, feature)) {
|
||||
_engine->getCatalog()->getFeatureTracker()->markNonRepairableFeatureAsInUse(txn,
|
||||
feature);
|
||||
}
|
||||
}
|
||||
|
||||
txn->recoveryUnit()->registerChange(new AddCollectionChange(txn, this, ns, ident, true));
|
||||
_collections[ns.toString()] =
|
||||
new KVCollectionCatalogEntry(_engine->getEngine(), _engine->getCatalog(), ns, ident, rs);
|
||||
|
||||
Reference in New Issue
Block a user