SERVER-110930 Handle tables existing but not tracked in the catalog when creating them (#41519)

GitOrigin-RevId: 0582b23087cd789c10064cafce68d11837427801
This commit is contained in:
Thomas Goyne 2025-09-23 11:42:09 -07:00 committed by MongoDB Bot
parent 3c5a9c861d
commit cf41a80d0f
9 changed files with 344 additions and 240 deletions

View File

@ -1588,42 +1588,33 @@ Status CollectionImpl::prepareForIndexBuild(OperationContext* opCtx,
"spec"_attr = spec->infoObj());
}
// Add this index to the metadata. This requires building a new idxIdent mapping which includes
// the new index ident. This must be done before createIndex() as that validates that the index
// is in the metadata.
{
BSONObjBuilder indexIdents;
indexIdents.append(imd.nameStringData(), ident);
auto ii = _indexCatalog->getIndexIterator(IndexCatalog::InclusionPolicy::kAll);
while (ii->more()) {
auto entry = ii->next();
if (entry->getIdent() == ident) {
return Status(
ErrorCodes::ObjectAlreadyExists,
fmt::format(
"Attempting to create index '{}' with ident '{}' that is already in use",
spec->indexName(),
ident));
}
indexIdents.append(entry->descriptor()->indexName(), entry->getIdent());
}
auto metadata = _copyMetadataForWrite(opCtx);
metadata->insertIndex(std::move(imd));
durable_catalog::putMetaData(
opCtx, getCatalogId(), *metadata, MDBCatalog::get(opCtx), indexIdents.obj());
_metadata = std::move(metadata);
}
auto status = durable_catalog::createIndex(
opCtx, getCatalogId(), ns(), getCollectionOptions(), spec->toIndexConfig(), ident);
if (status.isOK()) {
_indexCatalog->createIndexEntry(
opCtx, this, IndexDescriptor{*spec}, CreateIndexEntryFlags::kNone);
if (!status.isOK()) {
return status;
}
// Add this index to the metadata. This requires building a new idxIdent mapping which includes
// the new index ident. This must be done before createIndexEntry() as that validates that the
// index is in the metadata, but after createIndex() as that function validates that the ident
// is not already present.
BSONObjBuilder indexIdents;
indexIdents.append(imd.nameStringData(), ident);
auto ii = _indexCatalog->getIndexIterator(IndexCatalog::InclusionPolicy::kAll);
while (ii->more()) {
auto entry = ii->next();
indexIdents.append(entry->descriptor()->indexName(), entry->getIdent());
}
auto metadata = _copyMetadataForWrite(opCtx);
metadata->insertIndex(std::move(imd));
durable_catalog::putMetaData(
opCtx, getCatalogId(), *metadata, MDBCatalog::get(opCtx), indexIdents.obj());
_metadata = std::move(metadata);
_indexCatalog->createIndexEntry(
opCtx, this, IndexDescriptor{*spec}, CreateIndexEntryFlags::kNone);
return status;
}

View File

@ -82,88 +82,6 @@ std::shared_ptr<CatalogEntryMetaData> parseMetaData(const BSONElement& mdElement
}
return md;
}
// Ensures that only one catalog entry has the same ident as 'expectedCatalogEntry'. This check is
// potentially expensive, as it iterates over all catalog entries, and should only be used after an
// 'ObjectAlreadyExists' error occurs after the first attempt to persist a new collection in
// storage. Such cases are expected to be rare.
Status validateNoIdentConflictInCatalog(OperationContext* opCtx,
const MDBCatalog::EntryIdentifier& expectedCatalogEntry,
MDBCatalog* mdbCatalog) {
std::vector<MDBCatalog::EntryIdentifier> entriesWithIdent;
auto cursor = mdbCatalog->getCursor(opCtx);
while (auto record = cursor->next()) {
BSONObj obj = record->data.releaseToBson();
if (feature_document_util::isFeatureDocument(obj)) {
// Skip over the version document because it doesn't correspond to a collection.
continue;
}
auto entryIdent = obj["ident"].String();
if (entryIdent == expectedCatalogEntry.ident &&
expectedCatalogEntry.catalogId != record->id) {
return Status(
ErrorCodes::ObjectAlreadyExists,
fmt::format("Could not create collection {} with ident {} because the ident was "
"already in use by another table recorded in the catalog",
expectedCatalogEntry.nss.toStringForErrorMsg(),
expectedCatalogEntry.ident));
}
}
return Status::OK();
}
// Retries creating new collection's table on disk after the first attempt returns
// 'ObjectAlreadyExists'. This can happen if idents are replicated, the initial create attempt was
// rolled back, and the same operation gets applied again. In which case, the ident may correspond
// to a table still on disk.
StatusWith<std::unique_ptr<RecordStore>> retryCreateCollectionIfObjectAlreadyExists(
OperationContext* opCtx,
const MDBCatalog::EntryIdentifier& catalogEntry,
const boost::optional<UUID>& uuid,
const RecordStore::Options& recordStoreOptions,
const Status& originalFailure,
MDBCatalog* mdbCatalog) {
// First, validate the ident doesn't appear in multiple catalog entries. This should never
// happen unless there is manual oplog work or applyOps intervention which has caused
// corruption.
Status validateStatus = validateNoIdentConflictInCatalog(opCtx, catalogEntry, mdbCatalog);
if (!validateStatus.isOK()) {
return validateStatus;
}
// Rollback only guarantees the first state of two-phase table drop. If the initial create for
// the table was rolled back, it can still exist in the storage engine. The ident is likely in
// the drop-pending reaper, so we must remove it before trying to create the same collection
// again.
auto storageEngine = opCtx->getServiceContext()->getStorageEngine();
const auto& ident = catalogEntry.ident;
auto dropStatus = storageEngine->immediatelyCompletePendingDrop(opCtx, ident);
if (!dropStatus.isOK()) {
LOGV2(10526201,
"Attempted to drop and recreate a collection ident which already existed, but failed "
"the drop",
"ident"_attr = ident,
"uuid"_attr = uuid,
"nss"_attr = catalogEntry.nss.toStringForErrorMsg(),
"dropResult"_attr = dropStatus,
"originalCreateFailure"_attr = originalFailure);
return originalFailure;
}
auto createResult =
mdbCatalog->createRecordStoreForEntry(opCtx, catalogEntry, uuid, recordStoreOptions);
LOGV2(10526200,
"Attempted to drop and recreate a collection ident which already existed",
"ident"_attr = ident,
"uuid"_attr = uuid,
"nss"_attr = catalogEntry.nss.toStringForErrorMsg(),
"dropResult"_attr = dropStatus,
"createResult"_attr = createResult.getStatus());
return createResult;
}
} // namespace
namespace internal {
@ -185,12 +103,12 @@ CatalogEntryMetaData createMetaDataForNewCollection(const NamespaceString& nss,
BSONObj buildRawMDBCatalogEntry(const std::string& ident,
const BSONObj& idxIdent,
const CatalogEntryMetaData& md,
const std::string& ns) {
const NamespaceString& nss) {
BSONObjBuilder b;
b.append("ident", ident);
b.append("idxIdent", idxIdent);
b.append("md", md.toBSON());
b.append("ns", ns);
b.append("ns", NamespaceStringUtil::serializeForCatalog(nss));
return b.obj();
}
} // namespace internal
@ -286,6 +204,77 @@ void putMetaData(OperationContext* opCtx,
mdbCatalog->putUpdatedEntry(opCtx, catalogId, b.obj());
}
namespace {
/**
* Creates the underlying storage for a collection or index, handling cases where the ident is
* transiently in use but can be safely dropped. The actual creation is performed by the `create`
* callback argument, which should attempt to create the record store or SDI and return whatever
* error that produces. The `identExists` callback should scan the catalog for the ident and check
* if it's already present. This callback is only invoked after an ident conflict has been detected,
* so it can perform checks which are too expensive for the happy path.
*/
Status createStorage(OperationContext* opCtx,
StringData ident,
function_ref<Status()> create,
function_ref<bool()> identExists) {
auto& ru = *shard_role_details::getRecoveryUnit(opCtx);
auto storageEngine = opCtx->getServiceContext()->getStorageEngine();
// Rolling back table creation performs a two-phase drop, so this ident may be pending drop. If
// it is we'll need to complete the drop before we can proceed. If it isn't, this is a no-op.
if (auto status = storageEngine->immediatelyCompletePendingDrop(opCtx, ident); !status.isOK()) {
LOGV2(11093000,
"Ident being created was drop-pending and could not be dropped immediately",
"ident"_attr = ident,
"error"_attr = status);
return status;
}
Status status = create();
if (status == ErrorCodes::ObjectAlreadyExists) {
// The ident is already in use (and wasn't drop pending). Check if the ident is already
// found in the catalog, which would mean that we've either hit a bug or an admin user did
// something invalid with oplog editing or applyOps. This is an expensive check, so we do it
// only after optimistically trying the creation the first time.
if (identExists()) {
LOGV2(11093001,
"Ident being created is already in the catalog and so cannot be dropped",
"ident"_attr = ident);
return status;
}
// The ident isn't in the catalog and isn't drop-pending, so we can safely drop it, which is
// also what would happen if we were to reload the catalog. This can happen if a table is
// created while a checkpoint is in progress, as DDL operations being non-transactional
// means that the table *might* be included in the checkpoint despite not existing at the
// checkpoint's timestamp.
auto dropStatus = storageEngine->getEngine()->dropIdent(ru, ident, true);
if (!dropStatus.isOK()) {
LOGV2(11093002,
"Ident being created is known to the storage engine but not in the catalog, but "
"could not be dropped",
"ident"_attr = ident,
"error"_attr = dropStatus);
return dropStatus;
}
// Try again after dropping the existing table. This is expected to always work.
status = create();
}
if (!status.isOK()) {
return status;
}
ru.onRollback([ident = std::string(ident)](OperationContext* opCtx) {
auto storageEngine = opCtx->getServiceContext()->getStorageEngine();
storageEngine->addDropPendingIdent(Timestamp::min(), std::make_shared<Ident>(ident));
});
return status;
}
} // namespace
StatusWith<std::unique_ptr<RecordStore>> createCollection(
OperationContext* opCtx,
const RecordId& catalogId,
@ -297,30 +286,33 @@ StatusWith<std::unique_ptr<RecordStore>> createCollection(
invariant(nss.coll().size() > 0);
auto recordStoreOptions = getRecordStoreOptions(nss, collectionOptions);
durable_catalog::CatalogEntryMetaData md =
internal::createMetaDataForNewCollection(nss, collectionOptions);
const auto ns = NamespaceStringUtil::serializeForCatalog(nss);
auto mdbCatalogEntryObj =
internal::buildRawMDBCatalogEntry(ident, BSONObj() /* idxIdent */, md, ns);
auto swCatalogEntry = mdbCatalog->addEntry(opCtx, ident, nss, mdbCatalogEntryObj, catalogId);
if (!swCatalogEntry.isOK()) {
return swCatalogEntry.getStatus();
}
const auto& catalogEntry = swCatalogEntry.getValue();
invariant(catalogEntry.catalogId == catalogId);
auto createResult = mdbCatalog->createRecordStoreForEntry(
opCtx, catalogEntry, md.options.uuid, recordStoreOptions);
if (createResult.getStatus() == ErrorCodes::ObjectAlreadyExists) {
createResult = retryCreateCollectionIfObjectAlreadyExists(opCtx,
catalogEntry,
md.options.uuid,
recordStoreOptions,
createResult.getStatus(),
mdbCatalog);
auto engine = opCtx->getServiceContext()->getStorageEngine()->getEngine();
auto& provider = rss::ReplicatedStorageService::get(opCtx).getPersistenceProvider();
auto status = createStorage(
opCtx,
ident,
[&] { return engine->createRecordStore(provider, nss, ident, recordStoreOptions); },
[&] { return mdbCatalog->hasCollectionIdent(opCtx, ident); });
if (!status.isOK()) {
return status;
}
return createResult;
// Update the catalog only after successfully creating the record store. In between attempts at
// creating the record store we check if the catalog already contains the ident we're trying to
// create, and that's easier to do if we haven't already added our entry containing the ident.
auto mdbCatalogEntryObj =
internal::buildRawMDBCatalogEntry(ident, BSONObj() /* idxIdent */, md, nss);
status = mdbCatalog->addEntry(opCtx, ident, nss, mdbCatalogEntryObj, catalogId).getStatus();
if (!status.isOK()) {
return status;
}
auto rs = engine->getRecordStore(opCtx, nss, ident, recordStoreOptions, collectionOptions.uuid);
invariant(rs);
return rs;
}
Status createIndex(OperationContext* opCtx,
@ -329,51 +321,27 @@ Status createIndex(OperationContext* opCtx,
const CollectionOptions& collectionOptions,
const IndexConfig& indexConfig,
StringData ident) {
auto& ru = *shard_role_details::getRecoveryUnit(opCtx);
auto storageEngine = opCtx->getServiceContext()->getStorageEngine();
auto kvEngine = storageEngine->getEngine();
auto& provider = rss::ReplicatedStorageService::get(opCtx).getPersistenceProvider();
invariant(collectionOptions.uuid);
bool replicateLocalCatalogIdentifiers = shouldReplicateLocalCatalogIdentifiers(
rss::ReplicatedStorageService::get(opCtx).getPersistenceProvider());
if (replicateLocalCatalogIdentifiers) {
// If a previous attempt at creating this index was rolled back, the ident may still be drop
// pending. Complete that drop before creating the index if so.
if (Status status = storageEngine->immediatelyCompletePendingDrop(opCtx, ident);
!status.isOK()) {
LOGV2(10526400,
"Index ident was drop pending and required completing the drop",
"ident"_attr = ident,
"error"_attr = status);
return status;
}
}
auto mdbCatalog = MDBCatalog::get(opCtx);
auto& provider = rss::ReplicatedStorageService::get(opCtx).getPersistenceProvider();
auto& ru = *shard_role_details::getRecoveryUnit(opCtx);
auto engine = opCtx->getServiceContext()->getStorageEngine()->getEngine();
Status status = kvEngine->createSortedDataInterface(
provider,
ru,
nss,
*collectionOptions.uuid,
return createStorage(
opCtx,
ident,
indexConfig,
collectionOptions.indexOptionDefaults.getStorageEngine());
if (status.isOK()) {
if (replicateLocalCatalogIdentifiers) {
ru.onRollback([storageEngine, ident = std::string(ident), &ru](OperationContext*) {
storageEngine->addDropPendingIdent(Timestamp::min(),
std::make_shared<Ident>(ident));
});
} else {
ru.onRollback([kvEngine, ident = std::string(ident), &ru](OperationContext*) {
// Intentionally ignoring failure.
kvEngine->dropIdent(ru, ident, false).ignore();
});
}
}
return status;
[&] {
return engine->createSortedDataInterface(
provider,
ru,
nss,
*collectionOptions.uuid,
ident,
indexConfig,
collectionOptions.indexOptionDefaults.getStorageEngine());
},
[&] { return mdbCatalog->hasIndexIdent(opCtx, ident); });
}
StatusWith<ImportResult> importCollection(OperationContext* opCtx,

View File

@ -196,7 +196,7 @@ durable_catalog::CatalogEntryMetaData createMetaDataForNewCollection(
BSONObj buildRawMDBCatalogEntry(const std::string& ident,
const BSONObj& idxIdent,
const durable_catalog::CatalogEntryMetaData& md,
const std::string& ns);
const NamespaceString& nss);
} // namespace internal

View File

@ -118,25 +118,35 @@ public:
CatalogTestFixture::setUp();
_nss = NamespaceString::createNamespaceString_forTest("unittests.durable_catalog");
_collectionUUID = createCollection(_nss, CollectionOptions()).uuid;
_collection = createCollection(_nss, CollectionOptions());
}
NamespaceString ns() {
return _nss;
}
UUID uuid() const {
return _collection->uuid;
}
RecordId catalogId() const {
return _collection->catalogId;
}
StorageEngine* storageEngine() {
return operationContext()->getServiceContext()->getStorageEngine();
}
MDBCatalog* getMDBCatalog() {
return operationContext()->getServiceContext()->getStorageEngine()->getMDBCatalog();
return storageEngine()->getMDBCatalog();
}
std::string generateNewCollectionIdent(const NamespaceString& nss) {
auto storageEngine = operationContext()->getServiceContext()->getStorageEngine();
return storageEngine->generateNewCollectionIdent(nss.dbName());
return storageEngine()->generateNewCollectionIdent(nss.dbName());
}
std::string generateNewIndexIdent(const NamespaceString& nss) {
auto storageEngine = operationContext()->getServiceContext()->getStorageEngine();
return storageEngine->generateNewIndexIdent(nss.dbName());
return storageEngine()->generateNewIndexIdent(nss.dbName());
}
CollectionPtr getCollection() {
@ -144,11 +154,11 @@ public:
// it's controlled by the test. The initialization is therefore safe.
return CollectionPtr::CollectionPtr_UNSAFE(
CollectionCatalog::get(operationContext())
->lookupCollectionByUUID(operationContext(), *_collectionUUID));
->lookupCollectionByUUID(operationContext(), uuid()));
}
CollectionWriter getCollectionWriter() {
return CollectionWriter(operationContext(), *_collectionUUID);
return CollectionWriter(operationContext(), uuid());
}
struct CollectionCatalogIdAndUUID {
@ -189,13 +199,9 @@ public:
return CollectionCatalogIdAndUUID{catalogId, *options.uuid};
}
IndexCatalogEntry* createIndex(BSONObj keyPattern,
std::string indexType = IndexNames::BTREE,
bool twoPhase = false) {
Lock::DBLock dbLk(operationContext(), _nss.dbName(), MODE_IX);
Lock::CollectionLock collLk(operationContext(), _nss, MODE_X);
std::string indexName = "idx" + std::to_string(_numIndexesCreated);
IndexDescriptor indexDescriptor(BSONObj keyPattern,
const std::string& indexType = IndexNames::BTREE) {
std::string indexName = fmt::format("idx_{}", _numIndexesCreated++);
// Make sure we have a valid IndexSpec for the type requested
IndexSpec spec;
spec.version(1).name(indexName).addKeys(keyPattern);
@ -205,7 +211,16 @@ public:
spec.textDefaultLanguage("swedish");
}
auto desc = IndexDescriptor(indexType, spec.toBSON());
return IndexDescriptor(indexType, spec.toBSON());
}
IndexCatalogEntry* createIndex(BSONObj keyPattern,
std::string indexType = IndexNames::BTREE,
bool twoPhase = false) {
Lock::DBLock dbLk(operationContext(), _nss.dbName(), MODE_IX);
Lock::CollectionLock collLk(operationContext(), _nss, MODE_X);
auto desc = indexDescriptor(keyPattern, indexType);
IndexCatalogEntry* entry = nullptr;
auto collWriter = getCollectionWriter();
@ -217,13 +232,13 @@ public:
operationContext(), &desc, generateNewIndexIdent(_nss), buildUUID));
entry = collWriter.getWritableCollection(operationContext())
->getIndexCatalog()
->getWritableEntryByName(
operationContext(), indexName, IndexCatalog::InclusionPolicy::kAll);
->getWritableEntryByName(operationContext(),
desc.indexName(),
IndexCatalog::InclusionPolicy::kAll);
ASSERT(entry);
wuow.commit();
}
++_numIndexesCreated;
return entry;
}
@ -257,7 +272,7 @@ private:
size_t _numIndexesCreated = 0;
boost::optional<UUID> _collectionUUID;
boost::optional<CollectionCatalogIdAndUUID> _collection;
};
class ImportCollectionTest : public DurableCatalogTest {
@ -310,7 +325,7 @@ protected:
wuow.commit();
auto engine = operationContext()->getServiceContext()->getStorageEngine()->getEngine();
auto engine = storageEngine()->getEngine();
engine->checkpoint();
storageMetadata =
@ -1017,7 +1032,7 @@ TEST_F(DurableCatalogTest, ScanForCatalogEntryByNssBasic) {
ASSERT(catalogEntryDoesNotExist == boost::none);
}
TEST_F(DurableCatalogTest, CreateCollectionSucceedsWithExistingIdent) {
TEST_F(DurableCatalogTest, CreateCollectionSucceedsWithDropPendingIdent) {
auto opCtx = operationContext();
auto mdbCatalog = getMDBCatalog();
const auto catalogId = mdbCatalog->reserveCatalogId(operationContext());
@ -1061,6 +1076,41 @@ TEST_F(DurableCatalogTest, CreateCollectionSucceedsWithExistingIdent) {
ASSERT_EQUALS(ident, parsedEntry.ident);
}
TEST_F(DurableCatalogTest, CreateCollectionSucceedsWithIdentNotInCatalog) {
auto opCtx = operationContext();
auto mdbCatalog = getMDBCatalog();
const auto catalogId = mdbCatalog->reserveCatalogId(operationContext());
auto storageEngine = opCtx->getServiceContext()->getStorageEngine();
const NamespaceString nss = NamespaceString::createNamespaceString_forTest("test.coll");
const auto ident = storageEngine->generateNewCollectionIdent(nss.dbName());
// Create the collection but remove the catalog entry for it. This simulates the case where the
// table is present in a checkpoint at a timestamp before when the collection was created.
{
auto collection = acquireCollectionForWrite(opCtx, nss);
WriteUnitOfWork wuow(opCtx);
unittest::assertGet(durable_catalog::createCollection(
opCtx, catalogId, nss, ident, CollectionOptions{.uuid = UUID::gen()}, mdbCatalog));
ASSERT_OK(mdbCatalog->removeEntry(opCtx, catalogId));
wuow.commit();
}
ASSERT_FALSE(mdbCatalog->getEntry_forTest(catalogId));
// Creating the collection again should drop and recreate the table
{
auto collection = acquireCollectionForWrite(opCtx, nss);
WriteUnitOfWork wuow(opCtx);
auto recordStore = unittest::assertGet(durable_catalog::createCollection(
opCtx, catalogId, nss, ident, CollectionOptions{.uuid = UUID::gen()}, mdbCatalog));
wuow.commit();
}
auto parsedEntry = mdbCatalog->getEntry(catalogId);
ASSERT_EQUALS(catalogId, parsedEntry.catalogId);
ASSERT_EQUALS(nss, parsedEntry.nss);
ASSERT_EQUALS(ident, parsedEntry.ident);
}
TEST_F(DurableCatalogTest, CreateCollectionRemovesPriorDocumentsAfterRecreate) {
auto opCtx = operationContext();
auto mdbCatalog = getMDBCatalog();
@ -1202,5 +1252,92 @@ TEST_F(DurableCatalogTest, CreateCollectionWithCatalogIdentifierSucceedsAfterRol
ASSERT_EQUALS(ident, parsedEntry.ident);
}
TEST_F(DurableCatalogTest, RollingBackCreateIndexAddsIdentToReaper) {
const auto ident = generateNewIndexIdent(ns());
ASSERT_EQUALS(0U, storageEngine()->getNumDropPendingIdents());
{
WriteUnitOfWork wuow(operationContext());
ASSERT_OK(durable_catalog::createIndex(operationContext(),
catalogId(),
ns(),
{.uuid = uuid()},
indexDescriptor(BSON("a" << 1)).toIndexConfig(),
ident));
}
ASSERT_EQUALS(1U, storageEngine()->getNumDropPendingIdents());
}
TEST_F(DurableCatalogTest, CreateIndexRemovesIdentFromDropPending) {
const auto ident = generateNewIndexIdent(ns());
ASSERT_EQUALS(0U, storageEngine()->getNumDropPendingIdents());
{
WriteUnitOfWork wuow(operationContext());
ASSERT_OK(durable_catalog::createIndex(operationContext(),
catalogId(),
ns(),
{.uuid = uuid()},
indexDescriptor(BSON("a" << 1)).toIndexConfig(),
ident));
}
ASSERT_EQUALS(1U, storageEngine()->getNumDropPendingIdents());
{
WriteUnitOfWork wuow(operationContext());
ASSERT_OK(durable_catalog::createIndex(operationContext(),
catalogId(),
ns(),
{.uuid = uuid()},
indexDescriptor(BSON("a" << 1)).toIndexConfig(),
ident));
wuow.commit();
}
ASSERT_EQUALS(0U, storageEngine()->getNumDropPendingIdents());
}
TEST_F(DurableCatalogTest, CreateIndexSucceedsIfIdentExistsButIsNotInCatalog) {
const auto ident = generateNewIndexIdent(ns());
{
WriteUnitOfWork wuow(operationContext());
ASSERT_OK(durable_catalog::createIndex(operationContext(),
catalogId(),
ns(),
{.uuid = uuid()},
indexDescriptor(BSON("a" << 1)).toIndexConfig(),
ident));
wuow.commit();
}
{
WriteUnitOfWork wuow(operationContext());
ASSERT_OK(durable_catalog::createIndex(operationContext(),
catalogId(),
ns(),
{.uuid = uuid()},
indexDescriptor(BSON("a" << 1)).toIndexConfig(),
ident));
wuow.commit();
}
}
TEST_F(DurableCatalogTest, CreateIndexFailsIfIdentExistsAndIsInCatalog) {
auto entry = createIndex(BSON("a" << 1));
WriteUnitOfWork wuow(operationContext());
ASSERT_EQUALS(ErrorCodes::ObjectAlreadyExists,
durable_catalog::createIndex(operationContext(),
catalogId(),
ns(),
{.uuid = uuid()},
indexDescriptor(BSON("a" << 1)).toIndexConfig(),
entry->getIdent()));
}
} // namespace
} // namespace mongo

View File

@ -141,7 +141,7 @@ protected:
const auto ident = ident::generateNewCollectionIdent(nss.dbName(), false, false);
auto md = durable_catalog::internal::createMetaDataForNewCollection(nss, collectionOptions);
auto rawMDBCatalogEntry = durable_catalog::internal::buildRawMDBCatalogEntry(
ident, BSONObj() /* idxIdent */, md, NamespaceStringUtil::serializeForCatalog(nss));
ident, BSONObj() /* idxIdent */, md, nss);
// An 'orphaned' entry is one without a RecordStore explicitly created to back it.
auto swEntry = mdbCatalog->addEntry(

View File

@ -34,7 +34,6 @@
#include "mongo/db/namespace_string.h"
#include "mongo/db/operation_context.h"
#include "mongo/db/record_id.h"
#include "mongo/db/rss/replicated_storage_service.h"
#include "mongo/db/storage/feature_document_util.h"
#include "mongo/db/storage/kv/kv_engine.h"
#include "mongo/db/storage/record_store.h"
@ -168,11 +167,7 @@ std::vector<std::string> MDBCatalog::getAllIdents(OperationContext* opCtx) const
BSONElement e = obj["idxIdent"];
if (!e.isABSONObj())
continue;
BSONObj idxIdent = e.Obj();
BSONObjIterator sub(idxIdent);
while (sub.more()) {
BSONElement e = sub.next();
for (auto&& e : e.Obj()) {
v.push_back(e.String());
}
}
@ -201,6 +196,32 @@ std::vector<std::string> MDBCatalog::getIndexIdents(OperationContext* opCtx,
return _getIndexIdents(obj);
}
bool MDBCatalog::hasCollectionIdent(OperationContext* opCtx, StringData ident) const {
auto cursor = _rs->getCursor(opCtx, *shard_role_details::getRecoveryUnit(opCtx));
while (auto record = cursor->next()) {
BSONObj obj = record->data.releaseToBson();
if (obj["ident"].valueStringDataSafe() == ident) {
return true;
}
}
return false;
}
bool MDBCatalog::hasIndexIdent(OperationContext* opCtx, StringData ident) const {
auto cursor = _rs->getCursor(opCtx, *shard_role_details::getRecoveryUnit(opCtx));
while (auto record = cursor->next()) {
BSONObj obj = record->data.releaseToBson();
BSONElement e = obj["idxIdent"];
if (!e.isABSONObj())
continue;
for (auto&& e : e.Obj()) {
if (e.valueStringDataSafe() == ident)
return true;
}
}
return false;
}
std::unique_ptr<SeekableRecordCursor> MDBCatalog::getCursor(OperationContext* opCtx,
bool forward) const {
if (!_rs) {
@ -222,6 +243,7 @@ StatusWith<MDBCatalog::EntryIdentifier> MDBCatalog::addEntry(OperationContext* o
Timestamp());
if (!res.isOK())
return res.getStatus();
invariant(res.getValue() == catalogId);
stdx::lock_guard<stdx::mutex> lk(_catalogIdToEntryMapLock);
invariant(_catalogIdToEntryMap.find(res.getValue()) == _catalogIdToEntryMap.end());
@ -238,29 +260,6 @@ StatusWith<MDBCatalog::EntryIdentifier> MDBCatalog::addEntry(OperationContext* o
return {{res.getValue(), ident, nss}};
}
StatusWith<std::unique_ptr<RecordStore>> MDBCatalog::createRecordStoreForEntry(
OperationContext* opCtx,
const MDBCatalog::EntryIdentifier& entry,
const boost::optional<UUID>& uuid,
const RecordStore::Options& recordStoreOptions) {
auto& provider = rss::ReplicatedStorageService::get(opCtx).getPersistenceProvider();
Status status =
_engine->createRecordStore(provider, entry.nss, entry.ident, recordStoreOptions);
if (!status.isOK()) {
return status;
}
auto& ru = *shard_role_details::getRecoveryUnit(opCtx);
ru.onRollback([&ru, catalog = this, ident = entry.ident](OperationContext*) {
// Intentionally ignoring failure
catalog->_engine->dropIdent(ru, ident, /*identHasSizeInfo=*/true).ignore();
});
auto rs = _engine->getRecordStore(opCtx, entry.nss, entry.ident, recordStoreOptions, uuid);
invariant(rs);
return rs;
}
StatusWith<std::pair<RecordId, std::unique_ptr<RecordStore>>> MDBCatalog::importCatalogEntry(
OperationContext* opCtx,
const NamespaceString& nss,

View File

@ -29,6 +29,7 @@
#pragma once
#include "mongo/base/string_data.h"
#include "mongo/bson/bsonobj.h"
#include "mongo/db/namespace_string.h"
#include "mongo/db/operation_context.h"
@ -129,6 +130,16 @@ public:
std::vector<std::string> getIndexIdents(OperationContext* opCtx, const RecordId& catalogId);
/**
* Checks if any collection tracked in the catalog is using the given ident.
*/
bool hasCollectionIdent(OperationContext* opCtx, StringData ident) const;
/**
* Checks if any index tracked in the catalog is using the given ident.
*/
bool hasIndexIdent(OperationContext* opCtx, StringData ident) const;
std::unique_ptr<SeekableRecordCursor> getCursor(OperationContext* opCtx,
bool forward = true) const;
@ -141,14 +152,6 @@ public:
const NamespaceString& nss,
const BSONObj& catalogEntryObj,
const RecordId& catalogId);
/**
* Creates a new record store to back the catalog entry.
*/
StatusWith<std::unique_ptr<RecordStore>> createRecordStoreForEntry(
OperationContext* opCtx,
const MDBCatalog::EntryIdentifier& entry,
const boost::optional<UUID>& uuid,
const RecordStore::Options& recordStoreOptions);
StatusWith<std::pair<RecordId, std::unique_ptr<RecordStore>>> importCatalogEntry(
OperationContext* opCtx,

View File

@ -126,8 +126,8 @@ TEST_F(MDBCatalogTest, BuildCatalogEntryObjAndNsEquivalence) {
auto expectedNs = NamespaceStringUtil::serializeForCatalog(expectedNss);
durable_catalog::CatalogEntryMetaData md =
durable_catalog::internal::createMetaDataForNewCollection(expectedNss, optionsWithUUID);
auto expected =
durable_catalog::internal::buildRawMDBCatalogEntry("test-Ident", BSONObj(), md, expectedNs);
auto expected = durable_catalog::internal::buildRawMDBCatalogEntry(
"test-Ident", BSONObj(), md, expectedNss);
// Build with MDBCatalog function and compare
NamespaceString nss;

View File

@ -202,6 +202,12 @@ StatusWith<std::string> WiredTigerIndex::generateCreateString(const std::string&
ss << ",key_format=u";
ss << ",value_format=u";
// By default, WiredTiger silently ignores a create table command if the specified ident already
// exists - even if the existing table has a different configuration.
//
// Enable the 'exclusive' flag so WiredTiger table creation fails if an ident already exists.
ss << ",exclusive=true";
// Index metadata
ss << generateAppMetadataString(config);
if (isLogged) {