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:
David Storch
2016-06-07 18:34:30 -04:00
parent 0f744edcde
commit 8ca23764d4
7 changed files with 262 additions and 12 deletions

View File

@@ -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

View 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));
})();

View File

@@ -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 =

View File

@@ -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.

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);