diff options
author | Geert Bosch <geert@mongodb.com> | 2017-03-02 14:07:32 -0500 |
---|---|---|
committer | Geert Bosch <geert@mongodb.com> | 2017-03-08 10:07:17 -0500 |
commit | 78052771af75f9c6fdff10796c82a38167274a6f (patch) | |
tree | 7fcfa87c45775dc86c86898501b2e06541ecd21b /src | |
parent | 54f3fff857ac66be0cdca5a4ddce9f87a0d80e6a (diff) | |
download | mongo-78052771af75f9c6fdff10796c82a38167274a6f.tar.gz |
SERVER-27987 Create and persist UUIDs for newly created collections
Diffstat (limited to 'src')
23 files changed, 357 insertions, 98 deletions
diff --git a/src/mongo/bson/bsonelement.h b/src/mongo/bson/bsonelement.h index 111cbad442f..3120907b589 100644 --- a/src/mongo/bson/bsonelement.h +++ b/src/mongo/bson/bsonelement.h @@ -579,6 +579,17 @@ public: return Timestamp(); } + const std::array<unsigned char, 16> uuid() const { + int len = 0; + const char* data = nullptr; + if (type() == BinData && binDataType() == BinDataType::newUUID) + data = binData(len); + uassert(00000, "uuid must be a 16-byte binary field with UUID (4) subtype", len == 16); + std::array<unsigned char, 16> result; + memcpy(&result, data, len); + return result; + } + Date_t timestampTime() const { unsigned long long t = ConstDataView(value() + 4).read<LittleEndian<unsigned int>>(); return Date_t::fromMillisSinceEpoch(t * 1000); diff --git a/src/mongo/db/catalog/SConscript b/src/mongo/db/catalog/SConscript index a15aa150388..df7d91124c2 100644 --- a/src/mongo/db/catalog/SConscript +++ b/src/mongo/db/catalog/SConscript @@ -4,7 +4,17 @@ Import("env") env = env.Clone() -env.Library('collection_options', ['collection_options.cpp'], LIBDEPS=['$BUILD_DIR/mongo/base']) +env.Library( + target='collection_options', + source=[ + 'collection_options.cpp', + 'collection_uuid.cpp', + ], + LIBDEPS=[ + '$BUILD_DIR/mongo/base', + '$BUILD_DIR/mongo/db/server_parameters', + ], +) env.CppUnitTest('collection_options_test', ['collection_options_test.cpp'], LIBDEPS=['collection_options']) diff --git a/src/mongo/db/catalog/collection_options.cpp b/src/mongo/db/catalog/collection_options.cpp index 0a576d3c5d2..0f5152d138e 100644 --- a/src/mongo/db/catalog/collection_options.cpp +++ b/src/mongo/db/catalog/collection_options.cpp @@ -95,32 +95,6 @@ const std::set<StringData> collectionOptionsWhitelist{ } // namespace -void CollectionOptions::reset() { - capped = false; - cappedSize = 0; - cappedMaxDocs = 0; - initialNumExtents = 0; - initialExtentSizes.clear(); - autoIndexId = DEFAULT; - // For compatibility with previous versions if the user sets no flags, - // we set Flag_UsePowerOf2Sizes in case the user downgrades. - flags = Flag_UsePowerOf2Sizes; - flagsSet = false; - temp = false; - storageEngine = BSONObj(); - indexOptionDefaults = BSONObj(); - validator = BSONObj(); - validationLevel = ""; - validationAction = ""; - collation = BSONObj(); - viewOn = ""; - pipeline = BSONObj(); -} - -bool CollectionOptions::isValid() const { - return validate().isOK(); -} - bool CollectionOptions::isView() const { return !viewOn.empty(); } @@ -129,8 +103,8 @@ Status CollectionOptions::validate() const { return CollectionOptions().parse(toBSON()); } -Status CollectionOptions::parse(const BSONObj& options) { - reset(); +Status CollectionOptions::parse(const BSONObj& options, ParseKind kind) { + *this = {}; // Versions 2.4 and earlier of the server store "create" inside the collection metadata when the // user issues an explicit collection creation command. These versions also wrote any @@ -151,7 +125,13 @@ Status CollectionOptions::parse(const BSONObj& options) { BSONElement e = i.next(); StringData fieldName = e.fieldName(); - if (fieldName == "capped") { + if (fieldName == "uuid" && kind == parseForStorage) { + try { + uuid.emplace(e); + } catch (const UserException& ex) { + return ex.toStatus(); + } + } else if (fieldName == "capped") { capped = e.trueValue(); } else if (fieldName == "size") { if (!e.isNumber()) { @@ -282,6 +262,11 @@ Status CollectionOptions::parse(const BSONObj& options) { BSONObj CollectionOptions::toBSON() const { BSONObjBuilder b; + + if (uuid) { + b.appendElements(uuid->toBSON()); + } + if (capped) { b.appendBool("capped", true); b.appendNumber("size", cappedSize); diff --git a/src/mongo/db/catalog/collection_options.h b/src/mongo/db/catalog/collection_options.h index 8f8867bcf27..50703874e22 100644 --- a/src/mongo/db/catalog/collection_options.h +++ b/src/mongo/db/catalog/collection_options.h @@ -30,27 +30,26 @@ #include <string> +#include <boost/optional.hpp> + #include "mongo/base/status.h" +#include "mongo/db/catalog/collection_uuid.h" #include "mongo/db/jsobj.h" namespace mongo { struct CollectionOptions { - CollectionOptions() { - reset(); - } - - void reset(); - /** - * Returns true if collection options validates successfully. + * Returns true if the options indicate the namespace is a view. */ - bool isValid() const; + bool isView() const; /** - * Returns true if the options indicate the namespace is a view. + * The 'uuid' member is a collection property stored in the catalog with user-settable options, + * but is not valid for the user to specify as collection option. So, parsing commands must + * reject the 'uuid' property, but parsing stored options must accept it. */ - bool isView() const; + enum ParseKind { parseForCommand, parseForStorage }; /** * Confirms that collection options can be converted to BSON and back without errors. @@ -60,7 +59,7 @@ struct CollectionOptions { /** * Parses the "options" subfield of the collection info object. */ - Status parse(const BSONObj& obj); + Status parse(const BSONObj& obj, ParseKind kind = parseForCommand); BSONObj toBSON() const; @@ -72,15 +71,18 @@ struct CollectionOptions { // ---- - bool capped; - long long cappedSize; - long long cappedMaxDocs; + // Collection UUID. Will exist if featureCompatibilityVersion >= 3.6. + boost::optional<CollectionUUID> uuid; + + bool capped = false; + long long cappedSize = 0; + long long cappedMaxDocs = 0; - // following 2 are mutually exclusive, can only have one set - long long initialNumExtents; + // (MMAPv1) The following 2 are mutually exclusive, can only have one set. + long long initialNumExtents = 0; std::vector<long long> initialExtentSizes; - // behavior of _id index creation when collection created + // The behavior of _id index creation when collection created void setNoIdIndex() { autoIndexId = NO; } @@ -88,17 +90,17 @@ struct CollectionOptions { DEFAULT, // currently yes for most collections, NO for some system ones YES, // create _id index NO // do not create _id index - } autoIndexId; + } autoIndexId = DEFAULT; // user flags enum UserFlags { Flag_UsePowerOf2Sizes = 1 << 0, Flag_NoPadding = 1 << 1, }; - int flags; // a bitvector of UserFlags - bool flagsSet; + int flags = Flag_UsePowerOf2Sizes; // a bitvector of UserFlags + bool flagsSet = false; - bool temp; + bool temp = false; // Storage engine collection options. Always owned or empty. BSONObj storageEngine; diff --git a/src/mongo/db/catalog/collection_options_test.cpp b/src/mongo/db/catalog/collection_options_test.cpp index 71f6b28fa29..cb8cef38182 100644 --- a/src/mongo/db/catalog/collection_options_test.cpp +++ b/src/mongo/db/catalog/collection_options_test.cpp @@ -57,14 +57,6 @@ TEST(CollectionOptions, SimpleRoundTrip) { checkRoundTrip(options); } -TEST(CollectionOptions, IsValid) { - CollectionOptions options; - ASSERT_TRUE(options.isValid()); - - options.storageEngine = fromjson("{storageEngine1: 1}"); - ASSERT_FALSE(options.isValid()); -} - TEST(CollectionOptions, Validate) { CollectionOptions options; ASSERT_OK(options.validate()); @@ -84,9 +76,9 @@ TEST(CollectionOptions, Validator) { options.validator = fromjson("{b: 1}"); ASSERT_BSONOBJ_EQ(options.toBSON()["validator"].Obj(), fromjson("{b: 1}")); - options.reset(); - ASSERT_BSONOBJ_EQ(options.validator, BSONObj()); - ASSERT(!options.toBSON()["validator"]); + CollectionOptions defaultOptions; + ASSERT_BSONOBJ_EQ(defaultOptions.validator, BSONObj()); + ASSERT(!defaultOptions.toBSON()["validator"]); } TEST(CollectionOptions, ErrorBadSize) { @@ -171,9 +163,8 @@ TEST(CollectionOptions, ResetStorageEngineField) { ASSERT_OK(opts.parse(fromjson("{storageEngine: {storageEngine1: {x: 1}}}"))); checkRoundTrip(opts); - opts.reset(); - - ASSERT_TRUE(opts.storageEngine.isEmpty()); + CollectionOptions defaultOpts; + ASSERT_TRUE(defaultOpts.storageEngine.isEmpty()); } TEST(CollectionOptions, ModifyStorageEngineField) { @@ -211,7 +202,6 @@ TEST(CollectionOptions, CollationFieldParsesCorrectly) { CollectionOptions options; ASSERT_OK(options.parse(fromjson("{collation: {locale: 'en'}}"))); ASSERT_BSONOBJ_EQ(options.collation, fromjson("{locale: 'en'}")); - ASSERT_TRUE(options.isValid()); ASSERT_OK(options.validate()); } @@ -224,10 +214,9 @@ TEST(CollectionOptions, ParsedCollationObjShouldBeOwned) { TEST(CollectionOptions, ResetClearsCollationField) { CollectionOptions options; + ASSERT_TRUE(options.collation.isEmpty()); ASSERT_OK(options.parse(fromjson("{collation: {locale: 'en'}}"))); ASSERT_FALSE(options.collation.isEmpty()); - options.reset(); - ASSERT_TRUE(options.collation.isEmpty()); } TEST(CollectionOptions, CollationFieldLeftEmptyWhenOmitted) { @@ -325,4 +314,20 @@ TEST(CollectionOptions, WriteConcernWhitelistedOptionIgnored) { auto status = options.parse(fromjson("{writeConcern: 1}")); ASSERT_OK(status); } + +TEST(CollectionOptions, ParseUUID) { + CollectionOptions options; + CollectionUUID uuid = CollectionUUID::generateSecureRandomUUID(); + + // Check required parse failures + ASSERT_FALSE(options.uuid); + ASSERT_NOT_OK(options.parse(uuid.toBSON())); + ASSERT_NOT_OK(options.parse(BSON("uuid" << 1))); + ASSERT_NOT_OK(options.parse(BSON("uuid" << 1), CollectionOptions::parseForStorage)); + ASSERT_FALSE(options.uuid); + + // Check successful parse and roundtrip. + ASSERT_OK(options.parse(uuid.toBSON(), CollectionOptions::parseForStorage)); + ASSERT(options.uuid.get() == uuid); +} } // namespace mongo diff --git a/src/mongo/db/catalog/collection_uuid.cpp b/src/mongo/db/catalog/collection_uuid.cpp new file mode 100644 index 00000000000..dfe9327705a --- /dev/null +++ b/src/mongo/db/catalog/collection_uuid.cpp @@ -0,0 +1,62 @@ +/** + * Copyright (C) 2017 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "collection_uuid.h" + +#include "mongo/db/server_parameters.h" +#include "mongo/platform/random.h" +#include "mongo/stdx/mutex.h" + +namespace mongo { + +bool enableCollectionUUIDs = false; +ExportedServerParameter<bool, ServerParameterType::kStartupOnly> enableCollectionUUIDsParameter( + ServerParameterSet::getGlobal(), "enableCollectionUUIDs", &enableCollectionUUIDs); + +namespace { +stdx::mutex uuidGenMutex; +auto uuidGen = SecureRandom::create(); +} // namespace + +// static +CollectionUUID CollectionUUID::generateSecureRandomUUID() { + stdx::unique_lock<stdx::mutex> lock(uuidGenMutex); + int64_t randomWords[2] = {uuidGen->nextInt64(), uuidGen->nextInt64()}; + UUID randomBytes; + memcpy(&randomBytes, randomWords, sizeof(randomBytes)); + // Set version in high 4 bits of byte 6 and variant in high 2 bits of byte 8, see RFC 4122, + // section 4.1.1, 4.1.2 and 4.1.3. + randomBytes[6] &= 0x0f; + randomBytes[6] |= 0x40; // v4 + randomBytes[8] &= 0x3f; + randomBytes[8] |= 0x80; // Randomly assigned + return CollectionUUID{randomBytes}; +} +} // namespace mongo diff --git a/src/mongo/db/catalog/collection_uuid.h b/src/mongo/db/catalog/collection_uuid.h new file mode 100644 index 00000000000..23c6deba1d9 --- /dev/null +++ b/src/mongo/db/catalog/collection_uuid.h @@ -0,0 +1,83 @@ +/** + * Copyright (C) 2017 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +#include <string> + +#include "mongo/db/jsobj.h" + +namespace mongo { + +extern bool enableCollectionUUIDs; // TODO(SERVER-27993) Replace based on upgrade/downgrade state. + +/** + * A CollectionUUID is a 128-bit unique identifier, per RFC 4122, v4. for a database collection. + * Newly created collections are assigned a new randomly generated CollectionUUID. In a replica-set + * or a sharded cluster, all nodes will use the same UUID for a given collection. The UUID stays + * with the collection until it is dropped, so even across renames. A copied collection must have + * own new new unique UUID though. + */ +class CollectionUUID { +public: + using UUID = std::array<unsigned char, 16>; + CollectionUUID() = delete; + CollectionUUID(const CollectionUUID& other) = default; + + inline bool operator==(const CollectionUUID& rhs) const { + return !memcmp(&_uuid, &rhs._uuid, sizeof(_uuid)); + } + + inline bool operator!=(const CollectionUUID& rhs) const { + return !(*this == rhs); + } + + /** + * Parse a UUID from the given element. Caller must validate the input. + */ + CollectionUUID(BSONElement from) : _uuid(from.uuid()) {} + + /** + * Generate a new random v4 UUID per RFC 4122. + */ + static CollectionUUID generateSecureRandomUUID(); + + /** + * Return a BSON object of the form { uuid: BinData(4, "...") }. + */ + BSONObj toBSON() const { + BSONObjBuilder builder; + builder.appendBinData("uuid", sizeof(UUID), BinDataType::newUUID, &_uuid); + return builder.obj(); + } + +private: + CollectionUUID(const UUID& uuid) : _uuid(uuid) {} + UUID _uuid; // UUID in network byte order +}; +} diff --git a/src/mongo/db/catalog/create_collection.cpp b/src/mongo/db/catalog/create_collection.cpp index 2007aba76d6..ec963bdf0a8 100644 --- a/src/mongo/db/catalog/create_collection.cpp +++ b/src/mongo/db/catalog/create_collection.cpp @@ -86,7 +86,13 @@ Status createCollection(OperationContext* opCtx, // Create collection. const bool createDefaultIndexes = true; - status = userCreateNS(opCtx, ctx.db(), nss.ns(), options, createDefaultIndexes, idIndex); + status = userCreateNS(opCtx, + ctx.db(), + nss.ns(), + options, + CollectionOptions::parseForCommand, + createDefaultIndexes, + idIndex); if (!status.isOK()) { return status; } diff --git a/src/mongo/db/catalog/database.cpp b/src/mongo/db/catalog/database.cpp index b6c11f4c51f..1f68f3256ee 100644 --- a/src/mongo/db/catalog/database.cpp +++ b/src/mongo/db/catalog/database.cpp @@ -640,6 +640,7 @@ Status userCreateNS(OperationContext* opCtx, Database* db, StringData ns, BSONObj options, + CollectionOptions::ParseKind parseKind, bool createDefaultIndexes, const BSONObj& idIndex) { invariant(db); @@ -660,7 +661,7 @@ Status userCreateNS(OperationContext* opCtx, str::stream() << "a view '" << ns.toString() << "' already exists"); CollectionOptions collectionOptions; - Status status = collectionOptions.parse(options); + Status status = collectionOptions.parse(options, parseKind); if (!status.isOK()) return status; @@ -704,8 +705,11 @@ Status userCreateNS(OperationContext* opCtx, } if (collectionOptions.isView()) { + invariant(parseKind == CollectionOptions::parseForCommand); uassertStatusOK(db->createView(opCtx, ns, collectionOptions)); } else { + if (enableCollectionUUIDs && !collectionOptions.uuid) + collectionOptions.uuid.emplace(CollectionUUID::generateSecureRandomUUID()); invariant( db->createCollection(opCtx, ns, collectionOptions, createDefaultIndexes, idIndex)); } diff --git a/src/mongo/db/catalog/database.h b/src/mongo/db/catalog/database.h index de8e65aa3ce..a41d9dcdffb 100644 --- a/src/mongo/db/catalog/database.h +++ b/src/mongo/db/catalog/database.h @@ -267,6 +267,7 @@ Status userCreateNS(OperationContext* opCtx, Database* db, StringData ns, BSONObj options, + CollectionOptions::ParseKind parseKind = CollectionOptions::parseForCommand, bool createDefaultIndexes = true, const BSONObj& idIndex = BSONObj()); diff --git a/src/mongo/db/cloner.cpp b/src/mongo/db/cloner.cpp index 439d3ac574c..20455ad7c22 100644 --- a/src/mongo/db/cloner.cpp +++ b/src/mongo/db/cloner.cpp @@ -170,11 +170,13 @@ struct Cloner::Fun { opCtx->checkForInterrupt(); WriteUnitOfWork wunit(opCtx); + const bool createDefaultIndexes = true; Status s = userCreateNS(opCtx, db, to_collection.toString(), from_options, - true, + CollectionOptions::parseForCommand, + createDefaultIndexes, fixIndexSpec(to_collection.db().toString(), from_id_index)); verify(s.isOK()); wunit.commit(); @@ -390,12 +392,14 @@ void Cloner::copyIndexes(OperationContext* opCtx, opCtx->checkForInterrupt(); WriteUnitOfWork wunit(opCtx); + const bool createDefaultIndexes = true; Status s = userCreateNS( opCtx, db, to_collection.toString(), from_opts, - true, + CollectionOptions::parseForCommand, + createDefaultIndexes, fixIndexSpec(to_collection.db().toString(), getIdIndexSpec(from_indexes))); invariant(s.isOK()); collection = db->getCollection(to_collection); @@ -490,7 +494,14 @@ bool Cloner::copyCollection(OperationContext* opCtx, opCtx->checkForInterrupt(); WriteUnitOfWork wunit(opCtx); - Status status = userCreateNS(opCtx, db, ns, options, true, idIndexSpec); + const bool createDefaultIndexes = true; + Status status = userCreateNS(opCtx, + db, + ns, + options, + CollectionOptions::parseForCommand, + createDefaultIndexes, + idIndexSpec); if (!status.isOK()) { errmsg = status.toString(); // abort write unit of work @@ -528,7 +539,8 @@ StatusWith<std::vector<BSONObj>> Cloner::filterCollectionsForClone( BSONElement collectionOptions = collection["options"]; if (collectionOptions.isABSONObj()) { - auto parseOptionsStatus = CollectionOptions().parse(collectionOptions.Obj()); + auto parseOptionsStatus = CollectionOptions().parse(collectionOptions.Obj(), + CollectionOptions::parseForCommand); if (!parseOptionsStatus.isOK()) { return parseOptionsStatus; } @@ -578,12 +590,14 @@ Status Cloner::createCollectionsForDb( opCtx->checkForInterrupt(); WriteUnitOfWork wunit(opCtx); + const bool createDefaultIndexes = true; Status createStatus = userCreateNS(opCtx, db, nss.ns(), options, - true, + CollectionOptions::parseForCommand, + createDefaultIndexes, fixIndexSpec(nss.db().toString(), params.idIndexSpec)); if (!createStatus.isOK()) { return createStatus; diff --git a/src/mongo/db/commands/list_collections.cpp b/src/mongo/db/commands/list_collections.cpp index 68a6337f548..a0f998fe346 100644 --- a/src/mongo/db/commands/list_collections.cpp +++ b/src/mongo/db/commands/list_collections.cpp @@ -163,10 +163,18 @@ BSONObj buildCollectionBson(OperationContext* opCtx, const Collection* collectio b.append("type", "collection"); CollectionOptions options = collection->getCatalogEntry()->getCollectionOptions(opCtx); + + // While the UUID is stored as a collection option, from the user's perspective it is an + // unsettable read-only property, so put it in the 'info' section. + auto uuid = options.uuid; + options.uuid.reset(); b.append("options", options.toBSON()); - BSONObj info = BSON("readOnly" << storageGlobalParams.readOnly); - b.append("info", info); + BSONObjBuilder infoBuilder; + infoBuilder.append("readOnly", storageGlobalParams.readOnly); + if (uuid) + infoBuilder.appendElements(uuid->toBSON()); + b.append("info", infoBuilder.obj()); auto idIndex = collection->getIndexCatalog()->findIdIndex(opCtx); if (idIndex) { diff --git a/src/mongo/db/db.h b/src/mongo/db/db.h index 180ad9847aa..e94bb34ccb2 100644 --- a/src/mongo/db/db.h +++ b/src/mongo/db/db.h @@ -1,3 +1,4 @@ + /** * Copyright (C) 2008 10gen Inc. * diff --git a/src/mongo/db/repl/collection_cloner_test.cpp b/src/mongo/db/repl/collection_cloner_test.cpp index 440b8dee232..aac4d047372 100644 --- a/src/mongo/db/repl/collection_cloner_test.cpp +++ b/src/mongo/db/repl/collection_cloner_test.cpp @@ -73,7 +73,7 @@ protected: void CollectionClonerTest::setUp() { BaseClonerTest::setUp(); - options.reset(); + options = {}; collectionCloner.reset(nullptr); collectionCloner = stdx::make_unique<CollectionCloner>( &getExecutor(), @@ -101,7 +101,7 @@ void CollectionClonerTest::tearDown() { BaseClonerTest::tearDown(); // Executor may still invoke collection cloner's callback before shutting down. collectionCloner.reset(nullptr); - options.reset(); + options = {}; } BaseCloner* CollectionClonerTest::getCloner() const { @@ -320,7 +320,7 @@ TEST_F(CollectionClonerTest, } TEST_F(CollectionClonerTest, DoNotCreateIDIndexIfAutoIndexIdUsed) { - options.reset(); + options = {}; options.autoIndexId = CollectionOptions::NO; collectionCloner.reset(new CollectionCloner( &getExecutor(), diff --git a/src/mongo/db/repl/database_cloner.cpp b/src/mongo/db/repl/database_cloner.cpp index ba3f52ed048..f7b4cbbc3f7 100644 --- a/src/mongo/db/repl/database_cloner.cpp +++ b/src/mongo/db/repl/database_cloner.cpp @@ -58,6 +58,8 @@ using UniqueLock = stdx::unique_lock<stdx::mutex>; const char* kNameFieldName = "name"; const char* kOptionsFieldName = "options"; +const char* kInfoFieldName = "info"; +const char* kUUIDFieldName = "uuid"; // The number of attempts for the listCollections commands. MONGO_EXPORT_SERVER_PARAMETER(numInitialSyncListCollectionsAttempts, int, 3); @@ -341,11 +343,26 @@ void DatabaseCloner::_listCollectionsCallback(const StatusWith<Fetcher::QueryRes } const BSONObj optionsObj = optionsElement.Obj(); CollectionOptions options; - Status parseStatus = options.parse(optionsObj); + Status parseStatus = options.parse(optionsObj, CollectionOptions::parseForCommand); if (!parseStatus.isOK()) { _finishCallback_inlock(lk, parseStatus); return; } + + BSONElement infoElement = info.getField(kInfoFieldName); + if (infoElement.isABSONObj()) { + BSONElement uuidElement = infoElement[kUUIDFieldName]; + if (!uuidElement.eoo()) { + try { + options.uuid.emplace(uuidElement); + } catch (const UserException& ex) { + _finishCallback_inlock(lk, ex.toStatus()); + return; + } + } + } + // TODO(SERVER-27994): Ensure UUID present when FCV >= "3.6". + seen.insert(collectionName); _collectionNamespaces.emplace_back(_dbname, collectionName); diff --git a/src/mongo/db/repl/rs_rollback.cpp b/src/mongo/db/repl/rs_rollback.cpp index dbfb3a51284..8859ea6fba7 100644 --- a/src/mongo/db/repl/rs_rollback.cpp +++ b/src/mongo/db/repl/rs_rollback.cpp @@ -480,12 +480,13 @@ void syncFixUp(OperationContext* opCtx, << typeName(optionsField.type())); } - auto status = options.parse(optionsField.Obj()); + auto status = options.parse(optionsField.Obj(), CollectionOptions::parseForCommand); if (!status.isOK()) { throw RSFatalException(str::stream() << "Failed to parse options " << info << ": " << status.toString()); } + // TODO(SERVER-27992): Set options.uuid. } else { // Use default options. } diff --git a/src/mongo/db/repl/storage_interface_impl_test.cpp b/src/mongo/db/repl/storage_interface_impl_test.cpp index 2ee2cbf2259..cd8bdd98686 100644 --- a/src/mongo/db/repl/storage_interface_impl_test.cpp +++ b/src/mongo/db/repl/storage_interface_impl_test.cpp @@ -576,7 +576,7 @@ TEST_F(StorageInterfaceImplWithReplCoordTest, CreateCollectionThatAlreadyExistsF NamespaceString nss("test.system.indexes"); createCollection(opCtx, nss); - const CollectionOptions opts; + const CollectionOptions opts{}; const std::vector<BSONObj> indexes; const auto status = storage.createCollectionForBulkLoading(nss, opts, makeIdIndexSpec(nss), indexes); diff --git a/src/mongo/db/s/migration_destination_manager.cpp b/src/mongo/db/s/migration_destination_manager.cpp index cb46f30b056..470afdd6d02 100644 --- a/src/mongo/db/s/migration_destination_manager.cpp +++ b/src/mongo/db/s/migration_destination_manager.cpp @@ -509,7 +509,14 @@ void MigrationDestinationManager::_migrateDriver(OperationContext* opCtx, } WriteUnitOfWork wuow(opCtx); - Status status = userCreateNS(opCtx, db, _nss.ns(), options, true, idIndexSpec); + const bool createDefaultIndexes = true; + Status status = userCreateNS(opCtx, + db, + _nss.ns(), + options, + CollectionOptions::parseForCommand, + createDefaultIndexes, + idIndexSpec); if (!status.isOK()) { warning() << "failed to create collection [" << _nss << "] " << " with options " << options << ": " << redact(status); diff --git a/src/mongo/db/storage/bson_collection_catalog_entry.cpp b/src/mongo/db/storage/bson_collection_catalog_entry.cpp index f7c3eb3d2e0..9bd57ee6941 100644 --- a/src/mongo/db/storage/bson_collection_catalog_entry.cpp +++ b/src/mongo/db/storage/bson_collection_catalog_entry.cpp @@ -260,7 +260,7 @@ void BSONCollectionCatalogEntry::MetaData::parse(const BSONObj& obj) { ns = obj["ns"].valuestrsafe(); if (obj["options"].isABSONObj()) { - options.parse(obj["options"].Obj()); + options.parse(obj["options"].Obj(), CollectionOptions::parseForStorage); } BSONElement indexList = obj["indexes"]; diff --git a/src/mongo/db/storage/mmap_v1/mmap_v1_database_catalog_entry.cpp b/src/mongo/db/storage/mmap_v1/mmap_v1_database_catalog_entry.cpp index c88ed7545c4..d92f89ca2e3 100644 --- a/src/mongo/db/storage/mmap_v1/mmap_v1_database_catalog_entry.cpp +++ b/src/mongo/db/storage/mmap_v1/mmap_v1_database_catalog_entry.cpp @@ -906,7 +906,8 @@ CollectionOptions MMAPV1DatabaseCatalogEntry::getCollectionOptions(OperationCont invariant(rs->findRecord(opCtx, rid, &data)); if (data.releaseToBson()["options"].isABSONObj()) { - Status status = options.parse(data.releaseToBson()["options"].Obj()); + Status status = options.parse(data.releaseToBson()["options"].Obj(), + CollectionOptions::parseForStorage); fassert(18523, status); } return options; diff --git a/src/mongo/db/storage/mmap_v1/repair_database.cpp b/src/mongo/db/storage/mmap_v1/repair_database.cpp index c321cd5513c..90198cfd214 100644 --- a/src/mongo/db/storage/mmap_v1/repair_database.cpp +++ b/src/mongo/db/storage/mmap_v1/repair_database.cpp @@ -361,7 +361,8 @@ Status MMAPV1Engine::repairDatabase(OperationContext* opCtx, CollectionOptions options; if (obj["options"].isABSONObj()) { - Status status = options.parse(obj["options"].Obj()); + Status status = + options.parse(obj["options"].Obj(), CollectionOptions::parseForCommand); if (!status.isOK()) return status; } diff --git a/src/mongo/dbtests/querytests.cpp b/src/mongo/dbtests/querytests.cpp index b2b5495573e..83765c6ecd4 100644 --- a/src/mongo/dbtests/querytests.cpp +++ b/src/mongo/dbtests/querytests.cpp @@ -1318,6 +1318,7 @@ public: ctx.db(), ns(), fromjson("{ capped : true, size : 2000, max: 10000 }"), + CollectionOptions::parseForCommand, false) .isOK()); wunit.commit(); diff --git a/src/mongo/dbtests/rollbacktests.cpp b/src/mongo/dbtests/rollbacktests.cpp index 27982080d36..d9e70651178 100644 --- a/src/mongo/dbtests/rollbacktests.cpp +++ b/src/mongo/dbtests/rollbacktests.cpp @@ -72,7 +72,8 @@ void createCollection(OperationContext* opCtx, const NamespaceString& nss) { { WriteUnitOfWork uow(opCtx); ASSERT(!collectionExists(&ctx, nss.ns())); - ASSERT_OK(userCreateNS(opCtx, ctx.db(), nss.ns(), BSONObj(), false)); + ASSERT_OK(userCreateNS( + opCtx, ctx.db(), nss.ns(), BSONObj(), CollectionOptions::parseForCommand, false)); ASSERT(collectionExists(&ctx, nss.ns())); uow.commit(); } @@ -161,7 +162,8 @@ public: WriteUnitOfWork uow(&opCtx); ASSERT(!collectionExists(&ctx, ns)); auto options = capped ? BSON("capped" << true << "size" << 1000) : BSONObj(); - ASSERT_OK(userCreateNS(&opCtx, ctx.db(), ns, options, defaultIndexes)); + ASSERT_OK(userCreateNS( + &opCtx, ctx.db(), ns, options, CollectionOptions::parseForCommand, defaultIndexes)); ASSERT(collectionExists(&ctx, ns)); if (!rollback) { uow.commit(); @@ -192,7 +194,8 @@ public: WriteUnitOfWork uow(&opCtx); ASSERT(!collectionExists(&ctx, ns)); auto options = capped ? BSON("capped" << true << "size" << 1000) : BSONObj(); - ASSERT_OK(userCreateNS(&opCtx, ctx.db(), ns, options, defaultIndexes)); + ASSERT_OK(userCreateNS( + &opCtx, ctx.db(), ns, options, CollectionOptions::parseForCommand, defaultIndexes)); uow.commit(); } ASSERT(collectionExists(&ctx, ns)); @@ -236,7 +239,12 @@ public: WriteUnitOfWork uow(&opCtx); ASSERT(!collectionExists(&ctx, source.ns())); ASSERT(!collectionExists(&ctx, target.ns())); - ASSERT_OK(userCreateNS(&opCtx, ctx.db(), source.ns(), BSONObj(), defaultIndexes)); + ASSERT_OK(userCreateNS(&opCtx, + ctx.db(), + source.ns(), + BSONObj(), + CollectionOptions::parseForCommand, + defaultIndexes)); uow.commit(); } ASSERT(collectionExists(&ctx, source.ns())); @@ -288,8 +296,18 @@ public: WriteUnitOfWork uow(&opCtx); ASSERT(!collectionExists(&ctx, source.ns())); ASSERT(!collectionExists(&ctx, target.ns())); - ASSERT_OK(userCreateNS(&opCtx, ctx.db(), source.ns(), BSONObj(), defaultIndexes)); - ASSERT_OK(userCreateNS(&opCtx, ctx.db(), target.ns(), BSONObj(), defaultIndexes)); + ASSERT_OK(userCreateNS(&opCtx, + ctx.db(), + source.ns(), + BSONObj(), + CollectionOptions::parseForCommand, + defaultIndexes)); + ASSERT_OK(userCreateNS(&opCtx, + ctx.db(), + target.ns(), + BSONObj(), + CollectionOptions::parseForCommand, + defaultIndexes)); insertRecord(&opCtx, source, sourceDoc); insertRecord(&opCtx, target, targetDoc); @@ -348,7 +366,12 @@ public: { WriteUnitOfWork uow(&opCtx); ASSERT(!collectionExists(&ctx, nss.ns())); - ASSERT_OK(userCreateNS(&opCtx, ctx.db(), nss.ns(), BSONObj(), defaultIndexes)); + ASSERT_OK(userCreateNS(&opCtx, + ctx.db(), + nss.ns(), + BSONObj(), + CollectionOptions::parseForCommand, + defaultIndexes)); insertRecord(&opCtx, nss, oldDoc); uow.commit(); } @@ -361,7 +384,12 @@ public: WriteUnitOfWork uow(&opCtx); ASSERT_OK(ctx.db()->dropCollection(&opCtx, nss.ns())); ASSERT(!collectionExists(&ctx, nss.ns())); - ASSERT_OK(userCreateNS(&opCtx, ctx.db(), nss.ns(), BSONObj(), defaultIndexes)); + ASSERT_OK(userCreateNS(&opCtx, + ctx.db(), + nss.ns(), + BSONObj(), + CollectionOptions::parseForCommand, + defaultIndexes)); ASSERT(collectionExists(&ctx, nss.ns())); insertRecord(&opCtx, nss, newDoc); assertOnlyRecord(&opCtx, nss, newDoc); @@ -398,7 +426,12 @@ public: { WriteUnitOfWork uow(&opCtx); - ASSERT_OK(userCreateNS(&opCtx, ctx.db(), nss.ns(), BSONObj(), defaultIndexes)); + ASSERT_OK(userCreateNS(&opCtx, + ctx.db(), + nss.ns(), + BSONObj(), + CollectionOptions::parseForCommand, + defaultIndexes)); ASSERT(collectionExists(&ctx, nss.ns())); insertRecord(&opCtx, nss, doc); assertOnlyRecord(&opCtx, nss, doc); @@ -434,7 +467,12 @@ public: { WriteUnitOfWork uow(&opCtx); - ASSERT_OK(userCreateNS(&opCtx, ctx.db(), nss.ns(), BSONObj(), defaultIndexes)); + ASSERT_OK(userCreateNS(&opCtx, + ctx.db(), + nss.ns(), + BSONObj(), + CollectionOptions::parseForCommand, + defaultIndexes)); ASSERT(collectionExists(&ctx, nss.ns())); insertRecord(&opCtx, nss, doc); assertOnlyRecord(&opCtx, nss, doc); @@ -698,7 +736,8 @@ public: { WriteUnitOfWork uow(&opCtx); ASSERT(!collectionExists(&ctx, nss.ns())); - ASSERT_OK(userCreateNS(&opCtx, ctx.db(), nss.ns(), BSONObj(), false)); + ASSERT_OK(userCreateNS( + &opCtx, ctx.db(), nss.ns(), BSONObj(), CollectionOptions::parseForCommand, false)); ASSERT(collectionExists(&ctx, nss.ns())); Collection* coll = ctx.db()->getCollection(ns); IndexCatalog* catalog = coll->getIndexCatalog(); |