mirror of https://github.com/mongodb/mongo
SERVER-110930 Handle tables existing but not tracked in the catalog when creating them (#41519)
GitOrigin-RevId: 0582b23087cd789c10064cafce68d11837427801
This commit is contained in:
parent
3c5a9c861d
commit
cf41a80d0f
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue