diff options
-rw-r--r-- | src/mongo/db/repl/databases_cloner_test.cpp | 4 | ||||
-rw-r--r-- | src/mongo/db/repl/initial_syncer_test.cpp | 40 | ||||
-rw-r--r-- | src/mongo/db/repl/storage_interface.h | 10 | ||||
-rw-r--r-- | src/mongo/db/repl/storage_interface_impl.cpp | 93 | ||||
-rw-r--r-- | src/mongo/db/repl/storage_interface_impl.h | 10 | ||||
-rw-r--r-- | src/mongo/db/repl/storage_interface_impl_test.cpp | 228 | ||||
-rw-r--r-- | src/mongo/db/repl/storage_interface_mock.h | 22 |
7 files changed, 291 insertions, 116 deletions
diff --git a/src/mongo/db/repl/databases_cloner_test.cpp b/src/mongo/db/repl/databases_cloner_test.cpp index 369b8a27c38..56abaa0c4bd 100644 --- a/src/mongo/db/repl/databases_cloner_test.cpp +++ b/src/mongo/db/repl/databases_cloner_test.cpp @@ -146,14 +146,14 @@ protected: return Status::OK(); }; _storageInterface.insertDocumentFn = [this](OperationContext* opCtx, - const NamespaceString& nss, + const NamespaceStringOrUUID& nsOrUUID, const TimestampedBSONObj& doc, long long term) { ++_storageInterfaceWorkDone.documentsInsertedCount; return Status::OK(); }; _storageInterface.insertDocumentsFn = [this](OperationContext* opCtx, - const NamespaceString& nss, + const NamespaceStringOrUUID& nsOrUUID, const std::vector<InsertStatement>& ops) { _storageInterfaceWorkDone.insertedOplogEntries = true; ++_storageInterfaceWorkDone.oplogEntriesInserted; diff --git a/src/mongo/db/repl/initial_syncer_test.cpp b/src/mongo/db/repl/initial_syncer_test.cpp index 26735134ade..c4c8e26d72c 100644 --- a/src/mongo/db/repl/initial_syncer_test.cpp +++ b/src/mongo/db/repl/initial_syncer_test.cpp @@ -261,7 +261,7 @@ protected: return Status::OK(); }; _storageInterface->insertDocumentFn = [this](OperationContext* opCtx, - const NamespaceString& nss, + const NamespaceStringOrUUID& nsOrUUID, const TimestampedBSONObj& doc, long long term) { LockGuard lock(_storageInterfaceWorkDoneMutex); @@ -269,7 +269,7 @@ protected: return Status::OK(); }; _storageInterface->insertDocumentsFn = [this](OperationContext* opCtx, - const NamespaceString& nss, + const NamespaceStringOrUUID& nsOrUUID, const std::vector<InsertStatement>& ops) { LockGuard lock(_storageInterfaceWorkDoneMutex); _storageInterfaceWorkDone.insertedOplogEntries = true; @@ -2396,11 +2396,12 @@ TEST_F( TimestampedBSONObj insertDocumentDoc; long long insertDocumentTerm; _storageInterface->insertDocumentFn = - [&insertDocumentDoc, &insertDocumentNss, &insertDocumentTerm](OperationContext*, - const NamespaceString& nss, - const TimestampedBSONObj& doc, - long long term) { - insertDocumentNss = nss; + [&insertDocumentDoc, &insertDocumentNss, &insertDocumentTerm]( + OperationContext*, + const NamespaceStringOrUUID& nsOrUUID, + const TimestampedBSONObj& doc, + long long term) { + insertDocumentNss = *nsOrUUID.nss(); insertDocumentDoc = doc; insertDocumentTerm = term; return Status(ErrorCodes::OperationFailed, "failed to insert oplog entry"); @@ -2462,19 +2463,18 @@ TEST_F( NamespaceString insertDocumentNss; TimestampedBSONObj insertDocumentDoc; long long insertDocumentTerm; - _storageInterface->insertDocumentFn = [initialSyncer, - &insertDocumentDoc, - &insertDocumentNss, - &insertDocumentTerm](OperationContext*, - const NamespaceString& nss, - const TimestampedBSONObj& doc, - long long term) { - insertDocumentNss = nss; - insertDocumentDoc = doc; - insertDocumentTerm = term; - initialSyncer->shutdown().transitional_ignore(); - return Status::OK(); - }; + _storageInterface->insertDocumentFn = + [initialSyncer, &insertDocumentDoc, &insertDocumentNss, &insertDocumentTerm]( + OperationContext*, + const NamespaceStringOrUUID& nsOrUUID, + const TimestampedBSONObj& doc, + long long term) { + insertDocumentNss = *nsOrUUID.nss(); + insertDocumentDoc = doc; + insertDocumentTerm = term; + initialSyncer->shutdown().transitional_ignore(); + return Status::OK(); + }; _syncSourceSelector->setChooseNewSyncSourceResult_forTest(HostAndPort("localhost", 12345)); ASSERT_OK(initialSyncer->startup(opCtx.get(), maxAttempts)); diff --git a/src/mongo/db/repl/storage_interface.h b/src/mongo/db/repl/storage_interface.h index 9f517d96971..be79c228483 100644 --- a/src/mongo/db/repl/storage_interface.h +++ b/src/mongo/db/repl/storage_interface.h @@ -125,7 +125,7 @@ public: * an error is returned. */ virtual Status insertDocument(OperationContext* opCtx, - const NamespaceString& nss, + const NamespaceStringOrUUID& nsOrUUID, const TimestampedBSONObj& doc, long long term) = 0; @@ -135,7 +135,7 @@ public: * It is an error to call this function with an empty set of documents. */ virtual Status insertDocuments(OperationContext* opCtx, - const NamespaceString& nss, + const NamespaceStringOrUUID& nsOrUUID, const std::vector<InsertStatement>& docs) = 0; /** @@ -277,7 +277,7 @@ public: * Not supported on collections with a default collation. */ virtual StatusWith<BSONObj> findById(OperationContext* opCtx, - const NamespaceString& nss, + const NamespaceStringOrUUID& nsOrUUID, const BSONElement& idKey) = 0; /** @@ -287,7 +287,7 @@ public: * Not supported on collections with a default collation. */ virtual StatusWith<BSONObj> deleteById(OperationContext* opCtx, - const NamespaceString& nss, + const NamespaceStringOrUUID& nsOrUUID, const BSONElement& idKey) = 0; /** @@ -299,7 +299,7 @@ public: * applied. */ virtual Status upsertById(OperationContext* opCtx, - const NamespaceString& nss, + const NamespaceStringOrUUID& nsOrUUID, const BSONElement& idKey, const BSONObj& update) = 0; diff --git a/src/mongo/db/repl/storage_interface_impl.cpp b/src/mongo/db/repl/storage_interface_impl.cpp index d816ee05ea6..58a6eaf21f2 100644 --- a/src/mongo/db/repl/storage_interface_impl.cpp +++ b/src/mongo/db/repl/storage_interface_impl.cpp @@ -277,10 +277,10 @@ StorageInterfaceImpl::createCollectionForBulkLoading( } Status StorageInterfaceImpl::insertDocument(OperationContext* opCtx, - const NamespaceString& nss, + const NamespaceStringOrUUID& nsOrUUID, const TimestampedBSONObj& doc, long long term) { - return insertDocuments(opCtx, nss, {InsertStatement(doc.obj, doc.timestamp, term)}); + return insertDocuments(opCtx, nsOrUUID, {InsertStatement(doc.obj, doc.timestamp, term)}); } namespace { @@ -291,30 +291,32 @@ namespace { */ template <typename AutoGetCollectionType> StatusWith<Collection*> getCollection(const AutoGetCollectionType& autoGetCollection, - const NamespaceString& nss, + const NamespaceStringOrUUID& nsOrUUID, const std::string& message) { if (!autoGetCollection.getDb()) { + StringData dbName = nsOrUUID.nss() ? nsOrUUID.nss()->db() : nsOrUUID.dbname(); return {ErrorCodes::NamespaceNotFound, - str::stream() << "Database [" << nss.db() << "] not found. " << message}; + str::stream() << "Database [" << dbName << "] not found. " << message}; } auto collection = autoGetCollection.getCollection(); if (!collection) { return {ErrorCodes::NamespaceNotFound, - str::stream() << "Collection [" << nss.ns() << "] not found. " << message}; + str::stream() << "Collection [" << nsOrUUID.toString() << "] not found. " + << message}; } return collection; } Status insertDocumentsSingleBatch(OperationContext* opCtx, - const NamespaceString& nss, + const NamespaceStringOrUUID& nsOrUUID, std::vector<InsertStatement>::const_iterator begin, std::vector<InsertStatement>::const_iterator end) { - AutoGetCollection autoColl(opCtx, nss, MODE_IX); + AutoGetCollection autoColl(opCtx, nsOrUUID, MODE_IX); auto collectionResult = - getCollection(autoColl, nss, "The collection must exist before inserting documents."); + getCollection(autoColl, nsOrUUID, "The collection must exist before inserting documents."); if (!collectionResult.isOK()) { return collectionResult.getStatus(); } @@ -334,11 +336,11 @@ Status insertDocumentsSingleBatch(OperationContext* opCtx, } // namespace Status StorageInterfaceImpl::insertDocuments(OperationContext* opCtx, - const NamespaceString& nss, + const NamespaceStringOrUUID& nsOrUUID, const std::vector<InsertStatement>& docs) { if (docs.size() > 1U) { try { - if (insertDocumentsSingleBatch(opCtx, nss, docs.cbegin(), docs.cend()).isOK()) { + if (insertDocumentsSingleBatch(opCtx, nsOrUUID, docs.cbegin(), docs.cend()).isOK()) { return Status::OK(); } } catch (...) { @@ -349,9 +351,9 @@ Status StorageInterfaceImpl::insertDocuments(OperationContext* opCtx, // Try to insert the batch one-at-a-time because the batch failed all-at-once inserting. for (auto it = docs.cbegin(); it != docs.cend(); ++it) { - auto status = - writeConflictRetry(opCtx, "StorageInterfaceImpl::insertDocuments", nss.ns(), [&] { - auto status = insertDocumentsSingleBatch(opCtx, nss, it, it + 1); + auto status = writeConflictRetry( + opCtx, "StorageInterfaceImpl::insertDocuments", nsOrUUID.toString(), [&] { + auto status = insertDocumentsSingleBatch(opCtx, nsOrUUID, it, it + 1); if (!status.isOK()) { return status; } @@ -555,7 +557,7 @@ DeleteStageParams makeDeleteStageParamsForDeleteDocuments() { enum class FindDeleteMode { kFind, kDelete }; StatusWith<std::vector<BSONObj>> _findOrDeleteDocuments( OperationContext* opCtx, - const NamespaceString& nss, + const NamespaceStringOrUUID& nsOrUUID, boost::optional<StringData> indexName, StorageInterface::ScanDirection scanDirection, const BSONObj& startKey, @@ -567,15 +569,15 @@ StatusWith<std::vector<BSONObj>> _findOrDeleteDocuments( auto opStr = isFind ? "StorageInterfaceImpl::find" : "StorageInterfaceImpl::delete"; - return writeConflictRetry(opCtx, opStr, nss.ns(), [&] { + return writeConflictRetry(opCtx, opStr, nsOrUUID.toString(), [&] { // We need to explicitly use this in a few places to help the type inference. Use a // shorthand. using Result = StatusWith<std::vector<BSONObj>>; auto collectionAccessMode = isFind ? MODE_IS : MODE_IX; - AutoGetCollection autoColl(opCtx, nss, collectionAccessMode); + AutoGetCollection autoColl(opCtx, nsOrUUID, collectionAccessMode); auto collectionResult = getCollection( - autoColl, nss, str::stream() << "Unable to proceed with " << opStr << "."); + autoColl, nsOrUUID, str::stream() << "Unable to proceed with " << opStr << "."); if (!collectionResult.isOK()) { return Result(collectionResult.getStatus()); } @@ -598,7 +600,7 @@ StatusWith<std::vector<BSONObj>> _findOrDeleteDocuments( // Use collection scan. planExecutor = isFind ? InternalPlanner::collectionScan( - opCtx, nss.ns(), collection, PlanExecutor::NO_YIELD, direction) + opCtx, nsOrUUID.toString(), collection, PlanExecutor::NO_YIELD, direction) : InternalPlanner::deleteWithCollectionScan( opCtx, collection, @@ -614,14 +616,15 @@ StatusWith<std::vector<BSONObj>> _findOrDeleteDocuments( indexCatalog->findIndexByName(opCtx, *indexName, includeUnfinishedIndexes); if (!indexDescriptor) { return Result(ErrorCodes::IndexNotFound, - str::stream() << "Index not found, ns:" << nss.ns() << ", index: " + str::stream() << "Index not found, ns:" << nsOrUUID.toString() + << ", index: " << *indexName); } if (indexDescriptor->isPartial()) { return Result(ErrorCodes::IndexOptionsConflict, str::stream() << "Partial index is not allowed for this operation, ns:" - << nss.ns() + << nsOrUUID.toString() << ", index: " << *indexName); } @@ -674,12 +677,12 @@ StatusWith<std::vector<BSONObj>> _findOrDeleteDocuments( } StatusWith<BSONObj> _findOrDeleteById(OperationContext* opCtx, - const NamespaceString& nss, + const NamespaceStringOrUUID& nsOrUUID, const BSONElement& idKey, FindDeleteMode mode) { auto wrappedIdKey = idKey.wrap(""); auto result = _findOrDeleteDocuments(opCtx, - nss, + nsOrUUID, kIdIndexName, StorageInterface::ScanDirection::kForward, wrappedIdKey, @@ -692,7 +695,9 @@ StatusWith<BSONObj> _findOrDeleteById(OperationContext* opCtx, } const auto& docs = result.getValue(); if (docs.empty()) { - return {ErrorCodes::NoSuchKey, str::stream() << "No document found with _id: " << idKey}; + return {ErrorCodes::NoSuchKey, + str::stream() << "No document found with _id: " << redact(idKey) << " in namespace " + << nsOrUUID.toString()}; } return docs.front(); @@ -764,15 +769,15 @@ StatusWith<BSONObj> StorageInterfaceImpl::findSingleton(OperationContext* opCtx, } StatusWith<BSONObj> StorageInterfaceImpl::findById(OperationContext* opCtx, - const NamespaceString& nss, + const NamespaceStringOrUUID& nsOrUUID, const BSONElement& idKey) { - return _findOrDeleteById(opCtx, nss, idKey, FindDeleteMode::kFind); + return _findOrDeleteById(opCtx, nsOrUUID, idKey, FindDeleteMode::kFind); } StatusWith<BSONObj> StorageInterfaceImpl::deleteById(OperationContext* opCtx, - const NamespaceString& nss, + const NamespaceStringOrUUID& nsOrUUID, const BSONElement& idKey) { - return _findOrDeleteById(opCtx, nss, idKey, FindDeleteMode::kDelete); + return _findOrDeleteById(opCtx, nsOrUUID, idKey, FindDeleteMode::kDelete); } namespace { @@ -843,7 +848,7 @@ Status _updateWithQuery(OperationContext* opCtx, } // namespace Status StorageInterfaceImpl::upsertById(OperationContext* opCtx, - const NamespaceString& nss, + const NamespaceStringOrUUID& nsOrUUID, const BSONElement& idKey, const BSONObj& update) { // Validate and construct an _id query for UpdateResult. @@ -854,15 +859,24 @@ Status StorageInterfaceImpl::upsertById(OperationContext* opCtx, } auto query = queryResult.getValue(); - UpdateRequest request(nss); - request.setQuery(query); - request.setUpdates(update); - request.setUpsert(true); - invariant(!request.isMulti()); // This follows from using an exact _id query. - invariant(!request.shouldReturnAnyDocs()); - invariant(PlanExecutor::NO_YIELD == request.getYieldPolicy()); + return writeConflictRetry(opCtx, "StorageInterfaceImpl::upsertById", nsOrUUID.toString(), [&] { + AutoGetCollection autoColl(opCtx, nsOrUUID, MODE_IX); + auto collectionResult = getCollection(autoColl, nsOrUUID, "Unable to update document."); + if (!collectionResult.isOK()) { + return collectionResult.getStatus(); + } + auto collection = collectionResult.getValue(); + + // We can create an UpdateRequest now that the collection's namespace has been resolved, in + // the event it was specified as a UUID. + UpdateRequest request(collection->ns()); + request.setQuery(query); + request.setUpdates(update); + request.setUpsert(true); + invariant(!request.isMulti()); // This follows from using an exact _id query. + invariant(!request.shouldReturnAnyDocs()); + invariant(PlanExecutor::NO_YIELD == request.getYieldPolicy()); - return writeConflictRetry(opCtx, "StorageInterfaceImpl::upsertById", nss.ns(), [&] { // ParsedUpdate needs to be inside the write conflict retry loop because it contains // the UpdateDriver whose state may be modified while we are applying the update. ParsedUpdate parsedUpdate(opCtx, &request); @@ -871,13 +885,6 @@ Status StorageInterfaceImpl::upsertById(OperationContext* opCtx, return parsedUpdateStatus; } - AutoGetCollection autoColl(opCtx, nss, MODE_IX); - auto collectionResult = getCollection(autoColl, nss, "Unable to update document."); - if (!collectionResult.isOK()) { - return collectionResult.getStatus(); - } - auto collection = collectionResult.getValue(); - // We're using the ID hack to perform the update so we have to disallow collections // without an _id index. auto descriptor = collection->getIndexCatalog()->findIdIndex(opCtx); diff --git a/src/mongo/db/repl/storage_interface_impl.h b/src/mongo/db/repl/storage_interface_impl.h index 6a9b6c07ee6..761875dd0bf 100644 --- a/src/mongo/db/repl/storage_interface_impl.h +++ b/src/mongo/db/repl/storage_interface_impl.h @@ -66,12 +66,12 @@ public: const std::vector<BSONObj>& secondaryIndexSpecs) override; Status insertDocument(OperationContext* opCtx, - const NamespaceString& nss, + const NamespaceStringOrUUID& nsOrUUID, const TimestampedBSONObj& doc, long long term) override; Status insertDocuments(OperationContext* opCtx, - const NamespaceString& nss, + const NamespaceStringOrUUID& nsOrUUID, const std::vector<InsertStatement>& docs) override; Status dropReplicatedDatabases(OperationContext* opCtx) override; @@ -127,15 +127,15 @@ public: const TimestampedBSONObj& update) override; StatusWith<BSONObj> findById(OperationContext* opCtx, - const NamespaceString& nss, + const NamespaceStringOrUUID& nsOrUUID, const BSONElement& idKey) override; StatusWith<BSONObj> deleteById(OperationContext* opCtx, - const NamespaceString& nss, + const NamespaceStringOrUUID& nsOrUUID, const BSONElement& idKey) override; Status upsertById(OperationContext* opCtx, - const NamespaceString& nss, + const NamespaceStringOrUUID& nsOrUUID, const BSONElement& idKey, const BSONObj& update) override; diff --git a/src/mongo/db/repl/storage_interface_impl_test.cpp b/src/mongo/db/repl/storage_interface_impl_test.cpp index ddacb478aa6..588ae856a2e 100644 --- a/src/mongo/db/repl/storage_interface_impl_test.cpp +++ b/src/mongo/db/repl/storage_interface_impl_test.cpp @@ -401,9 +401,12 @@ TEST_F(StorageInterfaceImplTest, SnapshotSupported) { TEST_F(StorageInterfaceImplTest, InsertDocumentsReturnsOKWhenNoOperationsAreGiven) { auto opCtx = getOperationContext(); auto nss = makeNamespace(_agent); - createCollection(opCtx, nss); + auto options = generateOptionsWithUuid(); + createCollection(opCtx, nss, options); + StorageInterfaceImpl storage; ASSERT_OK(storage.insertDocuments(opCtx, nss, {})); + ASSERT_OK(storage.insertDocuments(opCtx, {nss.db().toString(), *options.uuid}, {})); } TEST_F(StorageInterfaceImplTest, @@ -412,7 +415,8 @@ TEST_F(StorageInterfaceImplTest, // fail. auto opCtx = getOperationContext(); auto nss = makeNamespace(_agent); - createCollection(opCtx, nss); + auto options = generateOptionsWithUuid(); + createCollection(opCtx, nss, options); // Non-oplog collection will enforce mandatory _id field requirement on insertion. StorageInterfaceImpl storage; @@ -420,6 +424,11 @@ TEST_F(StorageInterfaceImplTest, auto status = storage.insertDocuments(opCtx, nss, transformInserts({op})); ASSERT_EQUALS(ErrorCodes::InternalError, status); ASSERT_STRING_CONTAINS(status.reason(), "Collection::insertDocument got document without _id"); + + // Again, but specify the collection with its UUID. + ASSERT_EQ(ErrorCodes::InternalError, + storage.insertDocuments( + opCtx, {nss.db().toString(), *options.uuid}, transformInserts({op}))); } TEST_F(StorageInterfaceImplTest, @@ -475,6 +484,30 @@ TEST_F(StorageInterfaceImplTest, InsertDocumentsSavesOperationsReturnsOpTimeOfLa ASSERT_EQUALS(ErrorCodes::CollectionIsEmpty, iter->next().getStatus()); } +TEST_F(StorageInterfaceImplTest, InsertDocumentsSavesOperationsWhenCollSpecifiedWithUUID) { + // This is exactly like the test InsertDocumentsSavesOperationsReturnsOpTimeOfLastOperation, but + // with the UUID specified instead of the namespace string. + auto opCtx = getOperationContext(); + auto nss = makeNamespace(_agent); + auto options = createOplogCollectionOptions(); + createCollection(opCtx, nss, options); + + // Insert operations using storage interface. Ensure optime return is consistent with last + // operation inserted. + StorageInterfaceImpl storage; + auto op1 = makeOplogEntry({Timestamp(Seconds(1), 0), 1LL}); + auto op2 = makeOplogEntry({Timestamp(Seconds(1), 0), 1LL}); + ASSERT_OK(storage.insertDocuments( + opCtx, {nss.db().toString(), *options.uuid}, transformInserts({op1, op2}))); + + // Check contents of oplog. OplogInterface iterates over oplog collection in reverse. + repl::OplogInterfaceLocal oplog(opCtx, nss.ns()); + auto iter = oplog.makeIterator(); + ASSERT_BSONOBJ_EQ(op2.obj, unittest::assertGet(iter->next()).first); + ASSERT_BSONOBJ_EQ(op1.obj, unittest::assertGet(iter->next()).first); + ASSERT_EQUALS(ErrorCodes::CollectionIsEmpty, iter->next().getStatus()); +} + TEST_F(StorageInterfaceImplTest, InsertDocumentsReturnsNamespaceNotFoundIfOplogCollectionDoesNotExist) { auto op = makeOplogEntry({Timestamp(Seconds(1), 0), 1LL}); @@ -486,6 +519,16 @@ TEST_F(StorageInterfaceImplTest, ASSERT_STRING_CONTAINS(status.reason(), "The collection must exist before inserting documents"); } +TEST_F(StorageInterfaceImplTest, InsertDocumentThrowsNamespaceNotFoundIfOplogUUIDNotInCatalog) { + auto op = makeOplogEntry({Timestamp(Seconds(1), 0), 1LL}); + auto opCtx = getOperationContext(); + StorageInterfaceImpl storage; + ASSERT_THROWS_CODE( + storage.insertDocuments(opCtx, {"local", UUID::gen()}, transformInserts({op})).ignore(), + DBException, + ErrorCodes::NamespaceNotFound); +} + TEST_F(StorageInterfaceImplTest, InsertMissingDocWorksOnExistingCappedCollection) { auto opCtx = getOperationContext(); StorageInterfaceImpl storage; @@ -500,6 +543,23 @@ TEST_F(StorageInterfaceImplTest, InsertMissingDocWorksOnExistingCappedCollection ASSERT_TRUE(autoColl.getCollection()); } +TEST_F(StorageInterfaceImplTest, InsertDocWorksWithExistingCappedCollectionSpecifiedByUUID) { + auto opCtx = getOperationContext(); + auto nss = makeNamespace(_agent); + auto options = generateOptionsWithUuid(); + options.capped = true; + options.cappedSize = 1024 * 1024; + createCollection(opCtx, nss, options); + + StorageInterfaceImpl storage; + ASSERT_OK(storage.insertDocument(opCtx, + {nss.db().toString(), *options.uuid}, + {BSON("_id" << 1), Timestamp(1)}, + OpTime::kUninitializedTerm)); + AutoGetCollectionForReadCommand autoColl(opCtx, nss); + ASSERT_TRUE(autoColl.getCollection()); +} + TEST_F(StorageInterfaceImplTest, InsertMissingDocWorksOnExistingCollection) { auto opCtx = getOperationContext(); StorageInterfaceImpl storage; @@ -1843,6 +1903,18 @@ TEST_F(StorageInterfaceImplTest, UpdateSingletonUpdatesDocumentWhenCollectionIsN _assertDocumentsInCollectionEquals(opCtx, nss, {BSON("_id" << 0 << "x" << 1)}); } +TEST_F(StorageInterfaceImplTest, FindByIdThrowsIfUUIDNotInCatalog) { + auto op = makeOplogEntry({Timestamp(Seconds(1), 0), 1LL}); + auto opCtx = getOperationContext(); + auto obj = BSON("_id" + << "kyle"); + StorageInterfaceImpl storage; + ASSERT_THROWS_CODE( + storage.findById(opCtx, {"local", UUID::gen()}, obj["_id"]).getStatus().ignore(), + DBException, + ErrorCodes::NamespaceNotFound); +} + TEST_F(StorageInterfaceImplTest, FindByIdReturnsNamespaceNotFoundWhenDatabaseDoesNotExist) { auto opCtx = getOperationContext(); StorageInterfaceImpl storage; @@ -1863,9 +1935,11 @@ TEST_F(StorageInterfaceImplTest, FindByIdReturnsNoSuchKeyWhenCollectionIsEmpty) TEST_F(StorageInterfaceImplTest, FindByIdReturnsNoSuchKeyWhenDocumentIsNotFound) { auto opCtx = getOperationContext(); - StorageInterfaceImpl storage; auto nss = makeNamespace(_agent); - ASSERT_OK(storage.createCollection(opCtx, nss, generateOptionsWithUuid())); + auto options = generateOptionsWithUuid(); + + StorageInterfaceImpl storage; + ASSERT_OK(storage.createCollection(opCtx, nss, options)); auto doc1 = BSON("_id" << 0 << "x" << 0); auto doc2 = BSON("_id" << 1 << "x" << 1); auto doc3 = BSON("_id" << 2 << "x" << 2); @@ -1873,14 +1947,20 @@ TEST_F(StorageInterfaceImplTest, FindByIdReturnsNoSuchKeyWhenDocumentIsNotFound) nss, {{doc1, Timestamp(0), OpTime::kUninitializedTerm}, {doc3, Timestamp(0), OpTime::kUninitializedTerm}})); + ASSERT_EQUALS(ErrorCodes::NoSuchKey, storage.findById(opCtx, nss, doc2["_id"]).getStatus()); + ASSERT_EQUALS( + ErrorCodes::NoSuchKey, + storage.findById(opCtx, {nss.db().toString(), *options.uuid}, doc2["_id"]).getStatus()); } TEST_F(StorageInterfaceImplTest, FindByIdReturnsDocumentWhenDocumentExists) { auto opCtx = getOperationContext(); - StorageInterfaceImpl storage; auto nss = makeNamespace(_agent); - ASSERT_OK(storage.createCollection(opCtx, nss, generateOptionsWithUuid())); + auto options = generateOptionsWithUuid(); + + StorageInterfaceImpl storage; + ASSERT_OK(storage.createCollection(opCtx, nss, options)); auto doc1 = BSON("_id" << 0 << "x" << 0); auto doc2 = BSON("_id" << 1 << "x" << 1); auto doc3 = BSON("_id" << 2 << "x" << 2); @@ -1889,7 +1969,23 @@ TEST_F(StorageInterfaceImplTest, FindByIdReturnsDocumentWhenDocumentExists) { {{doc1, Timestamp(0), OpTime::kUninitializedTerm}, {doc2, Timestamp(0), OpTime::kUninitializedTerm}, {doc3, Timestamp(0), OpTime::kUninitializedTerm}})); + ASSERT_BSONOBJ_EQ(doc2, unittest::assertGet(storage.findById(opCtx, nss, doc2["_id"]))); + ASSERT_BSONOBJ_EQ(doc2, + unittest::assertGet(storage.findById( + opCtx, {nss.db().toString(), *options.uuid}, doc2["_id"]))); +} + +TEST_F(StorageInterfaceImplTest, DeleteByIdThrowsIfUUIDNotInCatalog) { + auto op = makeOplogEntry({Timestamp(Seconds(1), 0), 1LL}); + auto opCtx = getOperationContext(); + auto obj = BSON("_id" + << "kyle"); + StorageInterfaceImpl storage; + ASSERT_THROWS_CODE( + storage.deleteById(opCtx, {"local", UUID::gen()}, obj["_id"]).getStatus().ignore(), + DBException, + ErrorCodes::NamespaceNotFound); } TEST_F(StorageInterfaceImplTest, DeleteByIdReturnsNamespaceNotFoundWhenDatabaseDoesNotExist) { @@ -1913,9 +2009,11 @@ TEST_F(StorageInterfaceImplTest, DeleteByIdReturnsNoSuchKeyWhenCollectionIsEmpty TEST_F(StorageInterfaceImplTest, DeleteByIdReturnsNoSuchKeyWhenDocumentIsNotFound) { auto opCtx = getOperationContext(); - StorageInterfaceImpl storage; auto nss = makeNamespace(_agent); - ASSERT_OK(storage.createCollection(opCtx, nss, generateOptionsWithUuid())); + auto options = generateOptionsWithUuid(); + + StorageInterfaceImpl storage; + ASSERT_OK(storage.createCollection(opCtx, nss, options)); auto doc1 = BSON("_id" << 0 << "x" << 0); auto doc2 = BSON("_id" << 1 << "x" << 1); auto doc3 = BSON("_id" << 2 << "x" << 2); @@ -1924,14 +2022,19 @@ TEST_F(StorageInterfaceImplTest, DeleteByIdReturnsNoSuchKeyWhenDocumentIsNotFoun {{doc1, Timestamp(0), OpTime::kUninitializedTerm}, {doc3, Timestamp(0), OpTime::kUninitializedTerm}})); ASSERT_EQUALS(ErrorCodes::NoSuchKey, storage.deleteById(opCtx, nss, doc2["_id"]).getStatus()); + ASSERT_EQUALS( + ErrorCodes::NoSuchKey, + storage.deleteById(opCtx, {nss.db().toString(), *options.uuid}, doc2["_id"]).getStatus()); _assertDocumentsInCollectionEquals(opCtx, nss, {doc1, doc3}); } TEST_F(StorageInterfaceImplTest, DeleteByIdReturnsDocumentWhenDocumentExists) { auto opCtx = getOperationContext(); - StorageInterfaceImpl storage; auto nss = makeNamespace(_agent); - ASSERT_OK(storage.createCollection(opCtx, nss, generateOptionsWithUuid())); + auto options = generateOptionsWithUuid(); + + StorageInterfaceImpl storage; + ASSERT_OK(storage.createCollection(opCtx, nss, options)); auto doc1 = BSON("_id" << 0 << "x" << 0); auto doc2 = BSON("_id" << 1 << "x" << 1); auto doc3 = BSON("_id" << 2 << "x" << 2); @@ -1940,8 +2043,23 @@ TEST_F(StorageInterfaceImplTest, DeleteByIdReturnsDocumentWhenDocumentExists) { {{doc1, Timestamp(0), OpTime::kUninitializedTerm}, {doc2, Timestamp(0), OpTime::kUninitializedTerm}, {doc3, Timestamp(0), OpTime::kUninitializedTerm}})); + ASSERT_BSONOBJ_EQ(doc2, unittest::assertGet(storage.deleteById(opCtx, nss, doc2["_id"]))); _assertDocumentsInCollectionEquals(opCtx, nss, {doc1, doc3}); + + ASSERT_BSONOBJ_EQ(doc1, unittest::assertGet(storage.deleteById(opCtx, nss, doc1["_id"]))); + _assertDocumentsInCollectionEquals(opCtx, nss, {doc3}); +} + +TEST_F(StorageInterfaceImplTest, UpsertByIdThrowsIfUUIDNotInCatalog) { + auto op = makeOplogEntry({Timestamp(Seconds(1), 0), 1LL}); + auto opCtx = getOperationContext(); + auto obj = BSON("_id" + << "kyle"); + StorageInterfaceImpl storage; + ASSERT_THROWS_CODE(storage.upsertById(opCtx, {"local", UUID::gen()}, obj["_id"], obj).ignore(), + DBException, + ErrorCodes::NamespaceNotFound); } TEST_F(StorageInterfaceImplTest, @@ -1971,9 +2089,11 @@ TEST_F(StorageInterfaceImplTest, TEST_F(StorageInterfaceImplTest, UpsertSingleDocumentReplacesExistingDocumentInCollection) { auto opCtx = getOperationContext(); - StorageInterfaceImpl storage; auto nss = makeNamespace(_agent); - ASSERT_OK(storage.createCollection(opCtx, nss, generateOptionsWithUuid())); + auto options = generateOptionsWithUuid(); + + StorageInterfaceImpl storage; + ASSERT_OK(storage.createCollection(opCtx, nss, options)); auto originalDoc = BSON("_id" << 1 << "x" << 1); ASSERT_OK(storage.insertDocuments( @@ -1984,19 +2104,29 @@ TEST_F(StorageInterfaceImplTest, UpsertSingleDocumentReplacesExistingDocumentInC {BSON("_id" << 2 << "x" << 2), Timestamp(2), OpTime::kUninitializedTerm}})); ASSERT_OK(storage.upsertById(opCtx, nss, originalDoc["_id"], BSON("x" << 100))); - _assertDocumentsInCollectionEquals(opCtx, nss, {BSON("_id" << 0 << "x" << 0), BSON("_id" << 1 << "x" << 100), BSON("_id" << 2 << "x" << 2)}); + + // Again, but specify the collection's UUID. + ASSERT_OK(storage.upsertById( + opCtx, {nss.db().toString(), *options.uuid}, originalDoc["_id"], BSON("x" << 200))); + _assertDocumentsInCollectionEquals(opCtx, + nss, + {BSON("_id" << 0 << "x" << 0), + BSON("_id" << 1 << "x" << 200), + BSON("_id" << 2 << "x" << 2)}); } TEST_F(StorageInterfaceImplTest, UpsertSingleDocumentInsertsNewDocumentInCollectionIfIdIsNotFound) { auto opCtx = getOperationContext(); - StorageInterfaceImpl storage; auto nss = makeNamespace(_agent); - ASSERT_OK(storage.createCollection(opCtx, nss, generateOptionsWithUuid())); + auto options = generateOptionsWithUuid(); + + StorageInterfaceImpl storage; + ASSERT_OK(storage.createCollection(opCtx, nss, options)); ASSERT_OK(storage.insertDocuments( opCtx, @@ -2013,6 +2143,17 @@ TEST_F(StorageInterfaceImplTest, UpsertSingleDocumentInsertsNewDocumentInCollect {BSON("_id" << 0 << "x" << 0), BSON("_id" << 2 << "x" << 2), BSON("_id" << 1 << "x" << 100)}); + + ASSERT_OK(storage.upsertById(opCtx, + {nss.db().toString(), *options.uuid}, + BSON("" << 3).firstElement(), + BSON("x" << 300))); + _assertDocumentsInCollectionEquals(opCtx, + nss, + {BSON("_id" << 0 << "x" << 0), + BSON("_id" << 2 << "x" << 2), + BSON("_id" << 1 << "x" << 100), + BSON("_id" << 3 << "x" << 300)}); } TEST_F(StorageInterfaceImplTest, @@ -2023,8 +2164,9 @@ TEST_F(StorageInterfaceImplTest, ASSERT_FALSE(nss.isLegalClientSystemNS()); auto opCtx = getOperationContext(); + auto options = generateOptionsWithUuid(); StorageInterfaceImpl storage; - ASSERT_OK(storage.createCollection(opCtx, nss, generateOptionsWithUuid())); + ASSERT_OK(storage.createCollection(opCtx, nss, options)); auto originalDoc = BSON("_id" << 1 << "x" << 1); ASSERT_OK(storage.insertDocuments( @@ -2035,25 +2177,40 @@ TEST_F(StorageInterfaceImplTest, {BSON("_id" << 2 << "x" << 2), Timestamp(2), OpTime::kUninitializedTerm}})); ASSERT_OK(storage.upsertById(opCtx, nss, originalDoc["_id"], BSON("x" << 100))); - _assertDocumentsInCollectionEquals(opCtx, nss, {BSON("_id" << 0 << "x" << 0), BSON("_id" << 1 << "x" << 100), BSON("_id" << 2 << "x" << 2)}); + + ASSERT_OK(storage.upsertById( + opCtx, {nss.db().toString(), *options.uuid}, originalDoc["_id"], BSON("x" << 200))); + _assertDocumentsInCollectionEquals(opCtx, + nss, + {BSON("_id" << 0 << "x" << 0), + BSON("_id" << 1 << "x" << 200), + BSON("_id" << 2 << "x" << 2)}); } TEST_F(StorageInterfaceImplTest, UpsertSingleDocumentReturnsFailedToParseOnNonSimpleIdQuery) { auto opCtx = getOperationContext(); - StorageInterfaceImpl storage; auto nss = makeNamespace(_agent); - ASSERT_OK(storage.createCollection(opCtx, nss, generateOptionsWithUuid())); + auto options = generateOptionsWithUuid(); + + StorageInterfaceImpl storage; + ASSERT_OK(storage.createCollection(opCtx, nss, options)); auto status = storage.upsertById( opCtx, nss, BSON("" << BSON("$gt" << 3)).firstElement(), BSON("x" << 100)); ASSERT_EQUALS(ErrorCodes::InvalidIdField, status); ASSERT_STRING_CONTAINS(status.reason(), "Unable to update document with a non-simple _id query:"); + + ASSERT_EQ(storage.upsertById(opCtx, + {nss.db().toString(), *options.uuid}, + BSON("" << BSON("$gt" << 3)).firstElement(), + BSON("x" << 100)), + ErrorCodes::InvalidIdField); } TEST_F(StorageInterfaceImplTest, @@ -2071,24 +2228,35 @@ TEST_F(StorageInterfaceImplTest, ASSERT_EQUALS(ErrorCodes::IndexNotFound, status); ASSERT_STRING_CONTAINS(status.reason(), "Unable to update document in a collection without an _id index."); + + ASSERT_EQ(storage.upsertById(opCtx, {nss.db().toString(), *options.uuid}, doc["_id"], doc), + ErrorCodes::IndexNotFound); } TEST_F(StorageInterfaceImplTest, UpsertSingleDocumentReturnsFailedToParseWhenUpdateDocumentContainsUnknownOperator) { auto opCtx = getOperationContext(); - StorageInterfaceImpl storage; auto nss = makeNamespace(_agent); - ASSERT_OK(storage.createCollection(opCtx, nss, generateOptionsWithUuid())); + auto options = generateOptionsWithUuid(); - ASSERT_THROWS_CODE_AND_WHAT(storage - .upsertById(opCtx, - nss, - BSON("" << 1).firstElement(), - BSON("$unknownUpdateOp" << BSON("x" << 1000))) - .transitional_ignore(), - AssertionException, - ErrorCodes::FailedToParse, - "Unknown modifier: $unknownUpdateOp"); + StorageInterfaceImpl storage; + ASSERT_OK(storage.createCollection(opCtx, nss, options)); + + auto unknownUpdateOp = BSON("$unknownUpdateOp" << BSON("x" << 1000)); + ASSERT_THROWS_CODE_AND_WHAT( + storage.upsertById(opCtx, nss, BSON("" << 1).firstElement(), unknownUpdateOp).ignore(), + AssertionException, + ErrorCodes::FailedToParse, + "Unknown modifier: $unknownUpdateOp"); + + ASSERT_THROWS_CODE(storage + .upsertById(opCtx, + {nss.db().toString(), *options.uuid}, + BSON("" << 1).firstElement(), + unknownUpdateOp) + .ignore(), + DBException, + ErrorCodes::FailedToParse); } TEST_F(StorageInterfaceImplTest, DeleteByFilterReturnsNamespaceNotFoundWhenDatabaseDoesNotExist) { diff --git a/src/mongo/db/repl/storage_interface_mock.h b/src/mongo/db/repl/storage_interface_mock.h index cd02935179f..b2b3d22f276 100644 --- a/src/mongo/db/repl/storage_interface_mock.h +++ b/src/mongo/db/repl/storage_interface_mock.h @@ -91,11 +91,11 @@ public: const BSONObj idIndexSpec, const std::vector<BSONObj>& secondaryIndexSpecs)>; using InsertDocumentFn = stdx::function<Status(OperationContext* opCtx, - const NamespaceString& nss, + const NamespaceStringOrUUID& nsOrUUID, const TimestampedBSONObj& doc, long long term)>; using InsertDocumentsFn = stdx::function<Status(OperationContext* opCtx, - const NamespaceString& nss, + const NamespaceStringOrUUID& nsOrUUID, const std::vector<InsertStatement>& docs)>; using DropUserDatabasesFn = stdx::function<Status(OperationContext* opCtx)>; using CreateOplogFn = @@ -141,16 +141,16 @@ public: }; Status insertDocument(OperationContext* opCtx, - const NamespaceString& nss, + const NamespaceStringOrUUID& nsOrUUID, const TimestampedBSONObj& doc, long long term) override { - return insertDocumentFn(opCtx, nss, doc, term); + return insertDocumentFn(opCtx, nsOrUUID, doc, term); }; Status insertDocuments(OperationContext* opCtx, - const NamespaceString& nss, + const NamespaceStringOrUUID& nsOrUUID, const std::vector<InsertStatement>& docs) override { - return insertDocumentsFn(opCtx, nss, docs); + return insertDocumentsFn(opCtx, nsOrUUID, docs); } Status dropReplicatedDatabases(OperationContext* opCtx) override { @@ -238,19 +238,19 @@ public: } StatusWith<BSONObj> findById(OperationContext* opCtx, - const NamespaceString& nss, + const NamespaceStringOrUUID&, const BSONElement& idKey) override { return Status{ErrorCodes::IllegalOperation, "findById not implemented."}; } StatusWith<BSONObj> deleteById(OperationContext* opCtx, - const NamespaceString& nss, + const NamespaceStringOrUUID&, const BSONElement& idKey) override { return Status{ErrorCodes::IllegalOperation, "deleteById not implemented."}; } Status upsertById(OperationContext* opCtx, - const NamespaceString& nss, + const NamespaceStringOrUUID& nsOrUUID, const BSONElement& idKey, const BSONObj& update) override { return Status{ErrorCodes::IllegalOperation, "upsertById not implemented."}; @@ -315,13 +315,13 @@ public: return Status{ErrorCodes::IllegalOperation, "CreateCollectionForBulkFn not implemented."}; }; InsertDocumentFn insertDocumentFn = [](OperationContext* opCtx, - const NamespaceString& nss, + const NamespaceStringOrUUID& nsOrUUID, const TimestampedBSONObj& doc, long long term) { return Status{ErrorCodes::IllegalOperation, "InsertDocumentFn not implemented."}; }; InsertDocumentsFn insertDocumentsFn = [](OperationContext* opCtx, - const NamespaceString& nss, + const NamespaceStringOrUUID& nsOrUUID, const std::vector<InsertStatement>& docs) { return Status{ErrorCodes::IllegalOperation, "InsertDocumentsFn not implemented."}; }; |