summaryrefslogtreecommitdiff
path: root/src/mongo
diff options
context:
space:
mode:
authorBernard Gorman <bernard.gorman@gmail.com>2020-08-29 23:48:59 +0100
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-11-20 01:54:55 +0000
commit9870937b91b88348e619580f1050965b1006e33d (patch)
tree317b8564e315f8290a01477e4190ac56ea7d9722 /src/mongo
parent6f7f808583de3c432458d0adc2413d3a4022c126 (diff)
downloadmongo-9870937b91b88348e619580f1050965b1006e33d.tar.gz
SERVER-47812 Secondaries persist wildcard multikeypaths out of order
(cherry picked from commit bd320bc2d10cff75756a2c95986cc81ec8a5e7c7) (cherry picked from commit 48089c01bcccc193b1d8dd3c50ae5cb3e072ebed)
Diffstat (limited to 'src/mongo')
-rw-r--r--src/mongo/db/catalog/index_catalog.h1
-rw-r--r--src/mongo/db/catalog/index_catalog_entry.h4
-rw-r--r--src/mongo/db/catalog/index_catalog_entry_impl.cpp40
-rw-r--r--src/mongo/db/catalog/index_catalog_entry_impl.h11
-rw-r--r--src/mongo/db/catalog/index_catalog_impl.cpp5
-rw-r--r--src/mongo/db/catalog/index_catalog_impl.h1
-rw-r--r--src/mongo/db/catalog/index_catalog_noop.h1
-rw-r--r--src/mongo/db/catalog/multi_index_block.cpp34
-rw-r--r--src/mongo/db/index/index_access_method.cpp149
-rw-r--r--src/mongo/db/index/index_access_method.h42
-rw-r--r--src/mongo/db/index/index_build_interceptor.cpp2
-rw-r--r--src/mongo/db/multi_key_path_tracker.cpp14
-rw-r--r--src/mongo/db/multi_key_path_tracker.h7
-rw-r--r--src/mongo/db/repl/storage_interface.h1
-rw-r--r--src/mongo/db/repl/storage_interface_impl.cpp3
-rw-r--r--src/mongo/db/repl/storage_interface_impl.h1
-rw-r--r--src/mongo/db/repl/storage_interface_impl_test.cpp10
-rw-r--r--src/mongo/db/repl/storage_interface_mock.h1
-rw-r--r--src/mongo/db/repl/sync_tail.cpp8
-rw-r--r--src/mongo/dbtests/storage_timestamp_tests.cpp193
-rw-r--r--src/mongo/dbtests/validate_tests.cpp4
21 files changed, 408 insertions, 124 deletions
diff --git a/src/mongo/db/catalog/index_catalog.h b/src/mongo/db/catalog/index_catalog.h
index 9cc22948090..eb2dfc2dc41 100644
--- a/src/mongo/db/catalog/index_catalog.h
+++ b/src/mongo/db/catalog/index_catalog.h
@@ -443,6 +443,7 @@ public:
*/
virtual void setMultikeyPaths(OperationContext* const opCtx,
const IndexDescriptor* const desc,
+ const std::vector<BSONObj>& multikeyMetadataKeys,
const MultikeyPaths& multikeyPaths) = 0;
// ----- data modifiers ------
diff --git a/src/mongo/db/catalog/index_catalog_entry.h b/src/mongo/db/catalog/index_catalog_entry.h
index 822957dcc61..8c22cfb53b5 100644
--- a/src/mongo/db/catalog/index_catalog_entry.h
+++ b/src/mongo/db/catalog/index_catalog_entry.h
@@ -124,7 +124,9 @@ public:
* namespace, index name, and multikey paths on the OperationContext rather than set the index
* as multikey here.
*/
- virtual void setMultikey(OperationContext* const opCtx, const MultikeyPaths& multikeyPaths) = 0;
+ virtual void setMultikey(OperationContext* const opCtx,
+ const std::vector<BSONObj>& multikeyMetadataKeys,
+ const MultikeyPaths& multikeyPaths) = 0;
// if this ready is ready for queries
virtual bool isReady(OperationContext* const opCtx) const = 0;
diff --git a/src/mongo/db/catalog/index_catalog_entry_impl.cpp b/src/mongo/db/catalog/index_catalog_entry_impl.cpp
index 2c51dbae331..325689f9060 100644
--- a/src/mongo/db/catalog/index_catalog_entry_impl.cpp
+++ b/src/mongo/db/catalog/index_catalog_entry_impl.cpp
@@ -75,7 +75,7 @@ IndexCatalogEntryImpl::IndexCatalogEntryImpl(OperationContext* const opCtx,
{
stdx::lock_guard<Latch> lk(_indexMultikeyPathsMutex);
_isMultikey.store(_catalogIsMultikey(opCtx, &_indexMultikeyPaths));
- _indexTracksPathLevelMultikeyInfo = !_indexMultikeyPaths.empty();
+ _indexTracksMultikeyPathsInCatalog = !_indexMultikeyPaths.empty();
}
const BSONObj& collation = _descriptor->collation();
@@ -199,14 +199,19 @@ void IndexCatalogEntryImpl::setIsReady(bool newIsReady) {
}
void IndexCatalogEntryImpl::setMultikey(OperationContext* opCtx,
+ const std::vector<BSONObj>& multikeyMetadataKeys,
const MultikeyPaths& multikeyPaths) {
- if (!_indexTracksPathLevelMultikeyInfo && isMultikey(opCtx)) {
- // If the index is already set as multikey and we don't have any path-level information to
- // update, then there's nothing more for us to do.
+ // An index can either track path-level multikey information in the catalog or as metadata keys
+ // in the index itself, but not both.
+ invariant(!(_indexTracksMultikeyPathsInCatalog && multikeyMetadataKeys.size() > 0));
+ // If the index is already set as multikey and we don't have any path-level information to
+ // update, then there's nothing more for us to do.
+ bool hasNoPathLevelInfo = (!_indexTracksMultikeyPathsInCatalog && multikeyMetadataKeys.empty());
+ if (hasNoPathLevelInfo && _isMultikey.load()) {
return;
}
- if (_indexTracksPathLevelMultikeyInfo) {
+ if (_indexTracksMultikeyPathsInCatalog) {
stdx::lock_guard<Latch> lk(_indexMultikeyPathsMutex);
invariant(multikeyPaths.size() == _indexMultikeyPaths.size());
@@ -230,7 +235,7 @@ void IndexCatalogEntryImpl::setMultikey(OperationContext* opCtx,
}
}
- MultikeyPaths paths = _indexTracksPathLevelMultikeyInfo ? multikeyPaths : MultikeyPaths{};
+ MultikeyPaths paths = _indexTracksMultikeyPathsInCatalog ? multikeyPaths : MultikeyPaths{};
// On a primary, we can simply assign this write the same timestamp as the index creation,
// insert, or update that caused this index to become multikey. This is because if two
@@ -253,14 +258,21 @@ void IndexCatalogEntryImpl::setMultikey(OperationContext* opCtx,
// it multikey, that write will be marked as "isTrackingMultikeyPathInfo" on the applier's
// OperationContext and we can safely defer that write to the end of the batch.
if (MultikeyPathTracker::get(opCtx).isTrackingMultikeyPathInfo()) {
- MultikeyPathInfo info;
- info.nss = _ns;
- info.indexName = _descriptor->indexName();
- info.multikeyPaths = paths;
- MultikeyPathTracker::get(opCtx).addMultikeyPathInfo(info);
+ MultikeyPathTracker::get(opCtx).addMultikeyPathInfo(
+ {_ns, _descriptor->indexName(), multikeyMetadataKeys, std::move(paths)});
return;
}
+ // If multikeyMetadataKeys is non-empty, we must insert these keys into the index itself. We do
+ // not have to account for potential dupes, since all metadata keys are indexed against a single
+ // RecordId. An attempt to write a duplicate key will therefore be ignored.
+ if (!multikeyMetadataKeys.empty()) {
+ static const auto kMultikeyMetadataKeyId =
+ RecordId{RecordId::ReservedId::kWildcardMultikeyMetadataId};
+ uassertStatusOK(accessMethod()->insertKeys(
+ opCtx, multikeyMetadataKeys, kMultikeyMetadataKeyId, {}, {}, nullptr));
+ }
+
// It's possible that the index type (e.g. ascending/descending index) supports tracking
// path-level multikey information, but this particular index doesn't.
// CollectionCatalogEntry::setIndexIsMultikey() requires that we discard the path-level
@@ -274,7 +286,7 @@ void IndexCatalogEntryImpl::setMultikey(OperationContext* opCtx,
auto onMultikeyCommitFn = [this, multikeyPaths](bool indexMetadataHasChanged) {
_isMultikey.store(true);
- if (_indexTracksPathLevelMultikeyInfo) {
+ if (_indexTracksMultikeyPathsInCatalog) {
stdx::lock_guard<Latch> lk(_indexMultikeyPathsMutex);
for (size_t i = 0; i < multikeyPaths.size(); ++i) {
_indexMultikeyPaths[i].insert(multikeyPaths[i].begin(), multikeyPaths[i].end());
@@ -345,8 +357,8 @@ void IndexCatalogEntryImpl::setMultikey(OperationContext* opCtx,
// previous write in the transaction.
if (opCtx->inMultiDocumentTransaction()) {
invariant(txnParticipant);
- txnParticipant.addUncommittedMultikeyPathInfo(
- MultikeyPathInfo{_ns, _descriptor->indexName(), std::move(paths)});
+ txnParticipant.addUncommittedMultikeyPathInfo(MultikeyPathInfo{
+ _ns, _descriptor->indexName(), multikeyMetadataKeys, std::move(paths)});
}
}
diff --git a/src/mongo/db/catalog/index_catalog_entry_impl.h b/src/mongo/db/catalog/index_catalog_entry_impl.h
index 9698975bc8b..88a5d666888 100644
--- a/src/mongo/db/catalog/index_catalog_entry_impl.h
+++ b/src/mongo/db/catalog/index_catalog_entry_impl.h
@@ -152,7 +152,9 @@ public:
* namespace, index name, and multikey paths on the OperationContext rather than set the index
* as multikey here.
*/
- void setMultikey(OperationContext* opCtx, const MultikeyPaths& multikeyPaths) final;
+ void setMultikey(OperationContext* opCtx,
+ const std::vector<BSONObj>& multikeyMetadataKeys,
+ const MultikeyPaths& multikeyPaths) final;
// if this ready is ready for queries
bool isReady(OperationContext* opCtx) const final;
@@ -210,10 +212,9 @@ private:
Ordering _ordering; // TODO: this might be b-tree specific
bool _isReady; // cache of NamespaceDetails info
- // Set to true if this index supports path-level multikey tracking.
- // '_indexTracksPathLevelMultikeyInfo' is effectively const after IndexCatalogEntry::init() is
- // called.
- bool _indexTracksPathLevelMultikeyInfo = false;
+ // Set to true if this index can track path-level multikey information in the catalog. This
+ // member is effectively const after IndexCatalogEntry::init() is called.
+ bool _indexTracksMultikeyPathsInCatalog = false;
// Set to true if this index is multikey. '_isMultikey' serves as a cache of the information
// stored in the NamespaceDetails or DurableCatalog.
diff --git a/src/mongo/db/catalog/index_catalog_impl.cpp b/src/mongo/db/catalog/index_catalog_impl.cpp
index 266fac5b46b..96eb1ddd835 100644
--- a/src/mongo/db/catalog/index_catalog_impl.cpp
+++ b/src/mongo/db/catalog/index_catalog_impl.cpp
@@ -1059,13 +1059,14 @@ MultikeyPaths IndexCatalogImpl::getMultikeyPaths(OperationContext* opCtx,
void IndexCatalogImpl::setMultikeyPaths(OperationContext* const opCtx,
const IndexDescriptor* desc,
+ const std::vector<BSONObj>& multikeyMetadataKeys,
const MultikeyPaths& multikeyPaths) {
IndexCatalogEntry* entry = _readyIndexes.find(desc);
if (!entry) {
entry = _buildingIndexes.find(desc);
}
invariant(entry);
- entry->setMultikey(opCtx, multikeyPaths);
+ entry->setMultikey(opCtx, multikeyMetadataKeys, multikeyPaths);
};
// ---------------------------
@@ -1313,7 +1314,7 @@ Status IndexCatalogImpl::_indexKeys(OperationContext* opCtx,
}
} else {
int64_t numInserted;
- status = index->accessMethod()->insertKeys(
+ status = index->accessMethod()->insertKeysAndUpdateMultikeyPaths(
opCtx,
keys,
{multikeyMetadataKeys.begin(), multikeyMetadataKeys.end()},
diff --git a/src/mongo/db/catalog/index_catalog_impl.h b/src/mongo/db/catalog/index_catalog_impl.h
index 1439832ab96..be525335b16 100644
--- a/src/mongo/db/catalog/index_catalog_impl.h
+++ b/src/mongo/db/catalog/index_catalog_impl.h
@@ -250,6 +250,7 @@ public:
void setMultikeyPaths(OperationContext* const opCtx,
const IndexDescriptor* desc,
+ const std::vector<BSONObj>& multikeyMetadataKeys,
const MultikeyPaths& multikeyPaths) override;
// --- these probably become private?
diff --git a/src/mongo/db/catalog/index_catalog_noop.h b/src/mongo/db/catalog/index_catalog_noop.h
index 9f9bb23c27b..95414148792 100644
--- a/src/mongo/db/catalog/index_catalog_noop.h
+++ b/src/mongo/db/catalog/index_catalog_noop.h
@@ -184,6 +184,7 @@ public:
void setMultikeyPaths(OperationContext* const opCtx,
const IndexDescriptor* const desc,
+ const std::vector<BSONObj>& multikeyMetadataKeys,
const MultikeyPaths& multikeyPaths) override {}
Status indexRecords(OperationContext* const opCtx,
diff --git a/src/mongo/db/catalog/multi_index_block.cpp b/src/mongo/db/catalog/multi_index_block.cpp
index cb902dec15b..27545c0c070 100644
--- a/src/mongo/db/catalog/multi_index_block.cpp
+++ b/src/mongo/db/catalog/multi_index_block.cpp
@@ -846,37 +846,47 @@ Status MultiIndexBlock::commit(OperationContext* opCtx,
onCreateEach(_indexes[i].block->getSpec());
// Do this before calling success(), which unsets the interceptor pointer on the index
- // catalog entry.
+ // catalog entry. The interceptor will write multikey metadata keys into the index during
+ // IndexBuildInterceptor::sideWrite, so we only need to pass the cached MultikeyPaths into
+ // IndexCatalogEntry::setMultikey here.
auto interceptor = _indexes[i].block->getEntry()->indexBuildInterceptor();
if (interceptor) {
auto multikeyPaths = interceptor->getMultikeyPaths();
if (multikeyPaths) {
- _indexes[i].block->getEntry()->setMultikey(opCtx, multikeyPaths.get());
+ _indexes[i].block->getEntry()->setMultikey(opCtx, {}, multikeyPaths.get());
}
}
_indexes[i].block->success(opCtx, collection);
- // The bulk builder will track multikey information itself. Non-bulk builders re-use the
- // code path that a typical insert/update uses. State is altered on the non-bulk build
- // path to accumulate the multikey information on the `MultikeyPathTracker`.
+ // The bulk builder will track multikey information itself, and will write cached multikey
+ // metadata keys into the index just before committing. We therefore only need to pass the
+ // MultikeyPaths into IndexCatalogEntry::setMultikey here.
if (_indexes[i].bulk) {
const auto& bulkBuilder = _indexes[i].bulk;
if (bulkBuilder->isMultikey()) {
- _indexes[i].block->getEntry()->setMultikey(opCtx, bulkBuilder->getMultikeyPaths());
+ _indexes[i].block->getEntry()->setMultikey(
+ opCtx, {}, bulkBuilder->getMultikeyPaths());
}
} else {
- auto multikeyPaths =
- boost::optional<MultikeyPaths>(MultikeyPathTracker::get(opCtx).getMultikeyPathInfo(
- collection->ns(), _indexes[i].block->getIndexName()));
- if (multikeyPaths) {
+ // Non-bulk builders re-use the code path that a typical insert/update uses. State is
+ // altered on the non-bulk build path to accumulate the multikey information on the
+ // MultikeyPathTracker.
+ auto multikeyPaths = MultikeyPathTracker::get(opCtx).getMultikeyPathInfo(
+ collection->ns(), _indexes[i].block->getIndexName());
+ auto multikeyMetadataKeys = MultikeyPathTracker::get(opCtx).getMultikeyMetadataKeys(
+ collection->ns(), _indexes[i].block->getIndexName());
+ if (multikeyPaths || multikeyMetadataKeys) {
// Upon reaching this point, multikeyPaths must either have at least one (possibly
// empty) element unless it is of an index with a type that doesn't support tracking
// multikeyPaths via the multikeyPaths array or has "special" multikey semantics.
- invariant(!multikeyPaths.get().empty() ||
+ invariant(multikeyMetadataKeys || !multikeyPaths.get().empty() ||
!IndexBuildInterceptor::typeCanFastpathMultikeyUpdates(
_indexes[i].block->getEntry()->descriptor()->getIndexType()));
- _indexes[i].block->getEntry()->setMultikey(opCtx, *multikeyPaths);
+ _indexes[i].block->getEntry()->setMultikey(
+ opCtx,
+ multikeyMetadataKeys.value_or(std::vector<BSONObj>{}),
+ multikeyPaths.value_or(MultikeyPaths{{}}));
}
}
}
diff --git a/src/mongo/db/index/index_access_method.cpp b/src/mongo/db/index/index_access_method.cpp
index 706218c164d..b9024a5fd94 100644
--- a/src/mongo/db/index/index_access_method.cpp
+++ b/src/mongo/db/index/index_access_method.cpp
@@ -200,20 +200,45 @@ Status AbstractIndexAccessMethod::insert(OperationContext* opCtx,
&multikeyMetadataKeys,
&multikeyPaths);
- return insertKeys(opCtx,
- {keys.begin(), keys.end()},
- {multikeyMetadataKeys.begin(), multikeyMetadataKeys.end()},
- multikeyPaths,
- loc,
- options,
- std::move(onDuplicateKey),
- numInserted);
+ return insertKeysAndUpdateMultikeyPaths(
+ opCtx,
+ {keys.begin(), keys.end()},
+ {multikeyMetadataKeys.begin(), multikeyMetadataKeys.end()},
+ multikeyPaths,
+ loc,
+ options,
+ std::move(onDuplicateKey),
+ numInserted);
+}
+
+Status AbstractIndexAccessMethod::insertKeysAndUpdateMultikeyPaths(
+ OperationContext* opCtx,
+ const std::vector<BSONObj>& keys,
+ const std::vector<BSONObj>& multikeyMetadataKeys,
+ const MultikeyPaths& multikeyPaths,
+ const RecordId& loc,
+ const InsertDeleteOptions& options,
+ KeyHandlerFn&& onDuplicateKey,
+ int64_t* numInserted) {
+ // Insert the specified data keys into the index.
+ auto status = insertKeys(opCtx, keys, loc, options, std::move(onDuplicateKey), numInserted);
+ if (!status.isOK()) {
+ return status;
+ }
+ // If these keys should cause the index to become multikey, pass them into the catalog.
+ if (shouldMarkIndexAsMultikey(keys, multikeyMetadataKeys, multikeyPaths)) {
+ _btreeState->setMultikey(opCtx, multikeyMetadataKeys, multikeyPaths);
+ }
+ // If we have some multikey metadata keys, they should have been added while marking the index
+ // as multikey in the catalog. Add them to the count of keys inserted for completeness.
+ if (numInserted && !multikeyMetadataKeys.empty()) {
+ *numInserted += multikeyMetadataKeys.size();
+ }
+ return Status::OK();
}
Status AbstractIndexAccessMethod::insertKeys(OperationContext* opCtx,
const vector<BSONObj>& keys,
- const vector<BSONObj>& multikeyMetadataKeys,
- const MultikeyPaths& multikeyPaths,
const RecordId& loc,
const InsertDeleteOptions& options,
KeyHandlerFn&& onDuplicateKey,
@@ -222,47 +247,36 @@ Status AbstractIndexAccessMethod::insertKeys(OperationContext* opCtx,
if (numInserted) {
*numInserted = 0;
}
- // Add all new data keys, and all new multikey metadata keys, into the index. When iterating
- // over the data keys, each of them should point to the doc's RecordId. When iterating over
- // the multikey metadata keys, they should point to the reserved 'kMultikeyMetadataKeyId'.
+ // Add all new data keys into the index with the specified RecordId.
bool checkIndexKeySize = shouldCheckIndexKeySize(opCtx);
- for (const auto keyVec : {&keys, &multikeyMetadataKeys}) {
- const auto& recordId = (keyVec == &keys ? loc : kMultikeyMetadataKeyId);
- for (const auto& key : *keyVec) {
- Status status = checkIndexKeySize ? checkKeySize(key) : Status::OK();
- if (status.isOK()) {
- bool unique = _descriptor->unique();
- StatusWith<SpecialFormatInserted> ret =
- _newInterface->insert(opCtx, key, recordId, !unique /* dupsAllowed */);
- status = ret.getStatus();
-
- // When duplicates are encountered and allowed, retry with dupsAllowed. Call
- // onDuplicateKey() with the inserted duplicate key.
- if (ErrorCodes::DuplicateKey == status.code() && options.dupsAllowed) {
- invariant(unique);
- ret = _newInterface->insert(opCtx, key, recordId, true /* dupsAllowed */);
- status = ret.getStatus();
+ for (const auto& key : keys) {
+ Status status = checkIndexKeySize ? checkKeySize(key) : Status::OK();
+ if (status.isOK()) {
+ bool unique = _descriptor->unique();
+ StatusWith<SpecialFormatInserted> ret =
+ _newInterface->insert(opCtx, key, loc, !unique /* dupsAllowed */);
+ status = ret.getStatus();
- if (status.isOK() && onDuplicateKey)
- status = onDuplicateKey(key);
- }
+ // When duplicates are encountered and allowed, retry with dupsAllowed. Call
+ // onDuplicateKey() with the inserted duplicate key.
+ if (ErrorCodes::DuplicateKey == status.code() && options.dupsAllowed) {
+ invariant(unique);
+ ret = _newInterface->insert(opCtx, key, loc, true /* dupsAllowed */);
+ status = ret.getStatus();
- if (status.isOK() && ret.getValue() == SpecialFormatInserted::LongTypeBitsInserted)
- DurableCatalog::get(opCtx)->setIndexKeyStringWithLongTypeBitsExistsOnDisk(
- opCtx);
- }
- if (isFatalError(opCtx, status, key)) {
- return status;
+ if (status.isOK() && onDuplicateKey)
+ status = onDuplicateKey(key);
}
+
+ if (status.isOK() && ret.getValue() == SpecialFormatInserted::LongTypeBitsInserted)
+ DurableCatalog::get(opCtx)->setIndexKeyStringWithLongTypeBitsExistsOnDisk(opCtx);
+ }
+ if (isFatalError(opCtx, status, key)) {
+ return status;
}
}
-
if (numInserted) {
- *numInserted = keys.size() + multikeyMetadataKeys.size();
- }
-
- if (shouldMarkIndexAsMultikey(keys, multikeyMetadataKeys, multikeyPaths)) {
- _btreeState->setMultikey(opCtx, multikeyPaths);
+ *numInserted = keys.size();
}
return Status::OK();
}
@@ -488,37 +502,36 @@ Status AbstractIndexAccessMethod::update(OperationContext* opCtx,
bool checkIndexKeySize = shouldCheckIndexKeySize(opCtx);
- // Add all new data keys, and all new multikey metadata keys, into the index. When iterating
- // over the data keys, each of them should point to the doc's RecordId. When iterating over
- // the multikey metadata keys, they should point to the reserved 'kMultikeyMetadataKeyId'.
- const auto newMultikeyMetadataKeys = asVector(ticket.newMultikeyMetadataKeys);
- for (const auto keySet : {&ticket.added, &newMultikeyMetadataKeys}) {
- const auto& recordId = (keySet == &ticket.added ? ticket.loc : kMultikeyMetadataKeyId);
- for (const auto& key : *keySet) {
- Status status = checkIndexKeySize ? checkKeySize(key) : Status::OK();
- if (status.isOK()) {
- StatusWith<SpecialFormatInserted> ret =
- _newInterface->insert(opCtx, key, recordId, ticket.dupsAllowed);
- status = ret.getStatus();
- if (status.isOK() && ret.getValue() == SpecialFormatInserted::LongTypeBitsInserted)
- DurableCatalog::get(opCtx)->setIndexKeyStringWithLongTypeBitsExistsOnDisk(
- opCtx);
- }
- if (isFatalError(opCtx, status, key)) {
- return status;
- }
+ // Add all new data keys into the index with the specified RecordId.
+ for (const auto& key : ticket.added) {
+ Status status = checkIndexKeySize ? checkKeySize(key) : Status::OK();
+ if (status.isOK()) {
+ StatusWith<SpecialFormatInserted> ret =
+ _newInterface->insert(opCtx, key, ticket.loc, ticket.dupsAllowed);
+ status = ret.getStatus();
+ if (status.isOK() && ret.getValue() == SpecialFormatInserted::LongTypeBitsInserted)
+ DurableCatalog::get(opCtx)->setIndexKeyStringWithLongTypeBitsExistsOnDisk(opCtx);
+ }
+ if (isFatalError(opCtx, status, key)) {
+ return status;
}
}
+ // If these keys should cause the index to become multikey, pass them into the catalog.
if (shouldMarkIndexAsMultikey(
{ticket.newKeys.begin(), ticket.newKeys.end()},
{ticket.newMultikeyMetadataKeys.begin(), ticket.newMultikeyMetadataKeys.end()},
ticket.newMultikeyPaths)) {
- _btreeState->setMultikey(opCtx, ticket.newMultikeyPaths);
+ _btreeState->setMultikey(
+ opCtx,
+ {ticket.newMultikeyMetadataKeys.begin(), ticket.newMultikeyMetadataKeys.end()},
+ ticket.newMultikeyPaths);
}
+ // If we have some multikey metadata keys, they should have been added while marking the index
+ // as multikey in the catalog. Add them to the count of keys inserted for completeness.
+ *numInserted = ticket.added.size() + ticket.newMultikeyMetadataKeys.size();
*numDeleted = ticket.removed.size();
- *numInserted = ticket.added.size();
return Status::OK();
}
@@ -750,8 +763,10 @@ Status AbstractIndexAccessMethod::commitBulk(OperationContext* opCtx,
return Status::OK();
}
-void AbstractIndexAccessMethod::setIndexIsMultikey(OperationContext* opCtx, MultikeyPaths paths) {
- _btreeState->setMultikey(opCtx, paths);
+void AbstractIndexAccessMethod::setIndexIsMultikey(OperationContext* opCtx,
+ std::vector<BSONObj> multikeyMetadataKeys,
+ MultikeyPaths paths) {
+ _btreeState->setMultikey(opCtx, multikeyMetadataKeys, paths);
}
void AbstractIndexAccessMethod::getKeys(const BSONObj& obj,
diff --git a/src/mongo/db/index/index_access_method.h b/src/mongo/db/index/index_access_method.h
index 5171c7ffef4..e4d150ab60d 100644
--- a/src/mongo/db/index/index_access_method.h
+++ b/src/mongo/db/index/index_access_method.h
@@ -97,15 +97,26 @@ public:
/**
* Inserts the specified keys into the index, and determines whether these keys should cause the
* index to become multikey. If so, this method also handles the task of marking the index as
- * multikey in the catalog, and sets the path-level multikey information if applicable. The
- * 'numInserted' output parameter, if non-nullptr, will be reset to the number of keys inserted
- * by this function call, or to zero in the case of either a non-OK return Status or an empty
- * 'keys' argument.
+ * multikey in the catalog, and sets the path-level multikey information if applicable.
+ */
+ virtual Status insertKeysAndUpdateMultikeyPaths(
+ OperationContext* opCtx,
+ const std::vector<BSONObj>& keys,
+ const std::vector<BSONObj>& multikeyMetadataKeys,
+ const MultikeyPaths& multikeyPaths,
+ const RecordId& loc,
+ const InsertDeleteOptions& options,
+ KeyHandlerFn&& onDuplicateKey,
+ int64_t* numInserted) = 0;
+
+ /**
+ * Inserts the specified keys into the index. Does not attempt to determine whether the
+ * insertion of these keys should cause the index to become multikey. The 'numInserted' output
+ * parameter, if non-nullptr, will be reset to the number of keys inserted by this function
+ * call, or to zero in the case of either a non-OK return Status or an empty 'keys' argument.
*/
virtual Status insertKeys(OperationContext* opCtx,
const std::vector<BSONObj>& keys,
- const std::vector<BSONObj>& multikeyMetadataKeys,
- const MultikeyPaths& multikeyPaths,
const RecordId& loc,
const InsertDeleteOptions& options,
KeyHandlerFn&& onDuplicateKey,
@@ -216,7 +227,9 @@ public:
/**
* Sets this index as multikey with the provided paths.
*/
- virtual void setIndexIsMultikey(OperationContext* opCtx, MultikeyPaths paths) = 0;
+ virtual void setIndexIsMultikey(OperationContext* opCtx,
+ std::vector<BSONObj> multikeyMetadataKeys,
+ MultikeyPaths paths) = 0;
//
// Bulk operations support
@@ -461,13 +474,20 @@ public:
Status insertKeys(OperationContext* opCtx,
const std::vector<BSONObj>& keys,
- const std::vector<BSONObj>& multikeyMetadataKeys,
- const MultikeyPaths& multikeyPaths,
const RecordId& loc,
const InsertDeleteOptions& options,
KeyHandlerFn&& onDuplicateKey,
int64_t* numInserted) final;
+ Status insertKeysAndUpdateMultikeyPaths(OperationContext* opCtx,
+ const std::vector<BSONObj>& keys,
+ const std::vector<BSONObj>& multikeyMetadataKeys,
+ const MultikeyPaths& multikeyPaths,
+ const RecordId& loc,
+ const InsertDeleteOptions& options,
+ KeyHandlerFn&& onDuplicateKey,
+ int64_t* numInserted) final;
+
Status removeKeys(OperationContext* opCtx,
const std::vector<BSONObj>& keys,
const RecordId& loc,
@@ -511,7 +531,9 @@ public:
Status compact(OperationContext* opCtx) final;
- void setIndexIsMultikey(OperationContext* opCtx, MultikeyPaths paths) final;
+ void setIndexIsMultikey(OperationContext* opCtx,
+ std::vector<BSONObj> multikeyMetadataKeys,
+ MultikeyPaths paths) final;
std::unique_ptr<BulkBuilder> initiateBulk(size_t maxMemoryUsageBytes) final;
diff --git a/src/mongo/db/index/index_build_interceptor.cpp b/src/mongo/db/index/index_build_interceptor.cpp
index b7eb5b336b5..8b9b1c504d3 100644
--- a/src/mongo/db/index/index_build_interceptor.cpp
+++ b/src/mongo/db/index/index_build_interceptor.cpp
@@ -276,7 +276,7 @@ Status IndexBuildInterceptor::_applyWrite(OperationContext* opCtx,
auto accessMethod = _indexCatalogEntry->accessMethod();
if (opType == Op::kInsert) {
int64_t numInserted;
- auto status = accessMethod->insertKeys(
+ auto status = accessMethod->insertKeysAndUpdateMultikeyPaths(
opCtx,
{keySet.begin(), keySet.end()},
{},
diff --git a/src/mongo/db/multi_key_path_tracker.cpp b/src/mongo/db/multi_key_path_tracker.cpp
index 003a600e6bf..a80a5e48194 100644
--- a/src/mongo/db/multi_key_path_tracker.cpp
+++ b/src/mongo/db/multi_key_path_tracker.cpp
@@ -87,6 +87,9 @@ void MultikeyPathTracker::addMultikeyPathInfo(MultikeyPathInfo info) {
}
mergeMultikeyPaths(&existingChanges.multikeyPaths, info.multikeyPaths);
+ existingChanges.multikeyMetadataKeys.insert(existingChanges.multikeyMetadataKeys.end(),
+ info.multikeyMetadataKeys.begin(),
+ info.multikeyMetadataKeys.end());
return;
}
@@ -109,6 +112,17 @@ const boost::optional<MultikeyPaths> MultikeyPathTracker::getMultikeyPathInfo(
return boost::none;
}
+const boost::optional<std::vector<BSONObj>> MultikeyPathTracker::getMultikeyMetadataKeys(
+ const NamespaceString& nss, const std::string& indexName) {
+ for (const auto& multikeyPathInfo : _multikeyPathInfo) {
+ if (multikeyPathInfo.nss == nss && multikeyPathInfo.indexName == indexName) {
+ return multikeyPathInfo.multikeyMetadataKeys;
+ }
+ }
+
+ return boost::none;
+}
+
void MultikeyPathTracker::startTrackingMultikeyPathInfo() {
_trackMultikeyPathInfo = true;
}
diff --git a/src/mongo/db/multi_key_path_tracker.h b/src/mongo/db/multi_key_path_tracker.h
index b155bc1fe58..0849a812c8a 100644
--- a/src/mongo/db/multi_key_path_tracker.h
+++ b/src/mongo/db/multi_key_path_tracker.h
@@ -41,6 +41,7 @@ namespace mongo {
struct MultikeyPathInfo {
NamespaceString nss;
std::string indexName;
+ std::vector<BSONObj> multikeyMetadataKeys;
MultikeyPaths multikeyPaths;
};
@@ -91,6 +92,12 @@ public:
const std::string& indexName);
/**
+ * Returns the multikey metadata keys for the given inputs, or boost::none if none exist.
+ */
+ const boost::optional<std::vector<BSONObj>> getMultikeyMetadataKeys(
+ const NamespaceString& nss, const std::string& indexName);
+
+ /**
* Specifies that we should track multikey path information on this MultikeyPathTracker. This is
* only expected to be called during oplog application on secondaries. We cannot simply check
* 'canAcceptWritesFor' because background index builds use their own OperationContext and
diff --git a/src/mongo/db/repl/storage_interface.h b/src/mongo/db/repl/storage_interface.h
index ca6354a9a7a..426c6fbcb03 100644
--- a/src/mongo/db/repl/storage_interface.h
+++ b/src/mongo/db/repl/storage_interface.h
@@ -186,6 +186,7 @@ public:
virtual Status setIndexIsMultikey(OperationContext* opCtx,
const NamespaceString& nss,
const std::string& indexName,
+ const std::vector<BSONObj>& multikeyMetadataKeys,
const MultikeyPaths& paths,
Timestamp ts) = 0;
/**
diff --git a/src/mongo/db/repl/storage_interface_impl.cpp b/src/mongo/db/repl/storage_interface_impl.cpp
index 693f2199c70..e0527d461bb 100644
--- a/src/mongo/db/repl/storage_interface_impl.cpp
+++ b/src/mongo/db/repl/storage_interface_impl.cpp
@@ -522,6 +522,7 @@ Status StorageInterfaceImpl::renameCollection(OperationContext* opCtx,
Status StorageInterfaceImpl::setIndexIsMultikey(OperationContext* opCtx,
const NamespaceString& nss,
const std::string& indexName,
+ const std::vector<BSONObj>& multikeyMetadataKeys,
const MultikeyPaths& paths,
Timestamp ts) {
if (ts.isNull()) {
@@ -552,7 +553,7 @@ Status StorageInterfaceImpl::setIndexIsMultikey(OperationContext* opCtx,
str::stream() << "Could not find index " << indexName << " in "
<< nss.ns() << " to set to multikey.");
}
- collection->getIndexCatalog()->setMultikeyPaths(opCtx, idx, paths);
+ collection->getIndexCatalog()->setMultikeyPaths(opCtx, idx, multikeyMetadataKeys, paths);
wunit.commit();
return Status::OK();
});
diff --git a/src/mongo/db/repl/storage_interface_impl.h b/src/mongo/db/repl/storage_interface_impl.h
index 61eee68dea1..21827a8b0e8 100644
--- a/src/mongo/db/repl/storage_interface_impl.h
+++ b/src/mongo/db/repl/storage_interface_impl.h
@@ -96,6 +96,7 @@ public:
Status setIndexIsMultikey(OperationContext* opCtx,
const NamespaceString& nss,
const std::string& indexName,
+ const std::vector<BSONObj>& multikeyMetadataKeys,
const MultikeyPaths& paths,
Timestamp ts) override;
diff --git a/src/mongo/db/repl/storage_interface_impl_test.cpp b/src/mongo/db/repl/storage_interface_impl_test.cpp
index 8ade17d6efa..f4ef3dc1bd7 100644
--- a/src/mongo/db/repl/storage_interface_impl_test.cpp
+++ b/src/mongo/db/repl/storage_interface_impl_test.cpp
@@ -2607,7 +2607,7 @@ TEST_F(StorageInterfaceImplTest, SetIndexIsMultikeyReturnsNamespaceNotFoundForMi
StorageInterfaceImpl storage;
auto nss = makeNamespace(_agent);
ASSERT_EQUALS(ErrorCodes::NamespaceNotFound,
- storage.setIndexIsMultikey(opCtx, nss, "foo", {}, Timestamp(3, 3)));
+ storage.setIndexIsMultikey(opCtx, nss, "foo", {}, {}, Timestamp(3, 3)));
}
TEST_F(StorageInterfaceImplTest, SetIndexIsMultikeyReturnsNamespaceNotFoundForMissingCollection) {
@@ -2617,7 +2617,7 @@ TEST_F(StorageInterfaceImplTest, SetIndexIsMultikeyReturnsNamespaceNotFoundForMi
NamespaceString wrongColl(nss.db(), "wrongColl"_sd);
ASSERT_OK(storage.createCollection(opCtx, nss, CollectionOptions()));
ASSERT_EQUALS(ErrorCodes::NamespaceNotFound,
- storage.setIndexIsMultikey(opCtx, wrongColl, "foo", {}, Timestamp(3, 3)));
+ storage.setIndexIsMultikey(opCtx, wrongColl, "foo", {}, {}, Timestamp(3, 3)));
}
TEST_F(StorageInterfaceImplTest, SetIndexIsMultikeyReturnsIndexNotFoundForMissingIndex) {
@@ -2626,7 +2626,7 @@ TEST_F(StorageInterfaceImplTest, SetIndexIsMultikeyReturnsIndexNotFoundForMissin
auto nss = makeNamespace(_agent);
ASSERT_OK(storage.createCollection(opCtx, nss, CollectionOptions()));
ASSERT_EQUALS(ErrorCodes::IndexNotFound,
- storage.setIndexIsMultikey(opCtx, nss, "foo", {}, Timestamp(3, 3)));
+ storage.setIndexIsMultikey(opCtx, nss, "foo", {}, {}, Timestamp(3, 3)));
}
TEST_F(StorageInterfaceImplTest, SetIndexIsMultikeyReturnsInvalidOptionsForNullTimestamp) {
@@ -2635,7 +2635,7 @@ TEST_F(StorageInterfaceImplTest, SetIndexIsMultikeyReturnsInvalidOptionsForNullT
auto nss = makeNamespace(_agent);
ASSERT_OK(storage.createCollection(opCtx, nss, CollectionOptions()));
ASSERT_EQUALS(ErrorCodes::InvalidOptions,
- storage.setIndexIsMultikey(opCtx, nss, "foo", {}, Timestamp()));
+ storage.setIndexIsMultikey(opCtx, nss, "foo", {}, {}, Timestamp()));
}
TEST_F(StorageInterfaceImplTest, SetIndexIsMultikeySucceeds) {
@@ -2650,7 +2650,7 @@ TEST_F(StorageInterfaceImplTest, SetIndexIsMultikeySucceeds) {
ASSERT_EQUALS(_createIndexOnEmptyCollection(opCtx, nss, indexSpec), 2);
MultikeyPaths paths = {{1}};
- ASSERT_OK(storage.setIndexIsMultikey(opCtx, nss, indexName, paths, Timestamp(3, 3)));
+ ASSERT_OK(storage.setIndexIsMultikey(opCtx, nss, indexName, {}, paths, Timestamp(3, 3)));
AutoGetCollectionForReadCommand autoColl(opCtx, nss);
ASSERT_TRUE(autoColl.getCollection());
auto indexCatalog = autoColl.getCollection()->getIndexCatalog();
diff --git a/src/mongo/db/repl/storage_interface_mock.h b/src/mongo/db/repl/storage_interface_mock.h
index 68811f01bab..6b48bfcbb15 100644
--- a/src/mongo/db/repl/storage_interface_mock.h
+++ b/src/mongo/db/repl/storage_interface_mock.h
@@ -193,6 +193,7 @@ public:
Status setIndexIsMultikey(OperationContext* opCtx,
const NamespaceString& nss,
const std::string& indexName,
+ const std::vector<BSONObj>& multikeyMetadataKeys,
const MultikeyPaths& paths,
Timestamp ts) override {
diff --git a/src/mongo/db/repl/sync_tail.cpp b/src/mongo/db/repl/sync_tail.cpp
index bdcccdfd384..1a72e087b1e 100644
--- a/src/mongo/db/repl/sync_tail.cpp
+++ b/src/mongo/db/repl/sync_tail.cpp
@@ -1438,8 +1438,12 @@ StatusWith<OpTime> SyncTail::multiApply(OperationContext* opCtx, MultiApplier::O
// the first timestamp in the batch since we do not have enough information to find out
// the timestamp of the first write that set the given multikey path.
fassert(50686,
- _storageInterface->setIndexIsMultikey(
- opCtx, info.nss, info.indexName, info.multikeyPaths, firstTimeInBatch));
+ _storageInterface->setIndexIsMultikey(opCtx,
+ info.nss,
+ info.indexName,
+ info.multikeyMetadataKeys,
+ info.multikeyPaths,
+ firstTimeInBatch));
}
}
diff --git a/src/mongo/dbtests/storage_timestamp_tests.cpp b/src/mongo/dbtests/storage_timestamp_tests.cpp
index e59ea345044..218547ceb1e 100644
--- a/src/mongo/dbtests/storage_timestamp_tests.cpp
+++ b/src/mongo/dbtests/storage_timestamp_tests.cpp
@@ -48,6 +48,7 @@
#include "mongo/db/global_settings.h"
#include "mongo/db/index/index_build_interceptor.h"
#include "mongo/db/index/index_descriptor.h"
+#include "mongo/db/index/wildcard_access_method.h"
#include "mongo/db/index_builds_coordinator.h"
#include "mongo/db/logical_clock.h"
#include "mongo/db/multi_key_path_tracker.h"
@@ -1323,6 +1324,196 @@ public:
}
};
+class SecondarySetWildcardIndexMultikeyOnInsert : public StorageTimestampTest {
+
+public:
+ void run() {
+ // Pretend to be a secondary.
+ repl::UnreplicatedWritesBlock uwb(_opCtx);
+
+ NamespaceString nss("unittests.SecondarySetWildcardIndexMultikeyOnInsert");
+ reset(nss);
+ UUID uuid = UUID::gen();
+ {
+ AutoGetCollection autoColl(_opCtx, nss, LockMode::MODE_IX);
+ uuid = *autoColl.getCollection()->uuid();
+ }
+ auto indexName = "a_1";
+ auto indexSpec = BSON("name" << indexName << "key" << BSON("$**" << 1) << "v"
+ << static_cast<int>(kIndexVersion));
+ ASSERT_OK(dbtests::createIndexFromSpec(_opCtx, nss.ns(), indexSpec));
+
+ _coordinatorMock->alwaysAllowWrites(false);
+
+ const LogicalTime pastTime = _clock->reserveTicks(1);
+ const LogicalTime insertTime0 = _clock->reserveTicks(1);
+ const LogicalTime insertTime1 = _clock->reserveTicks(1);
+ const LogicalTime insertTime2 = _clock->reserveTicks(1);
+
+ BSONObj doc0 = BSON("_id" << 0 << "a" << 3);
+ BSONObj doc1 = BSON("_id" << 1 << "a" << BSON_ARRAY(1 << 2));
+ BSONObj doc2 = BSON("_id" << 2 << "a" << BSON_ARRAY(1 << 2));
+ auto op0 = repl::OplogEntry(
+ BSON("ts" << insertTime0.asTimestamp() << "t" << 1LL << "v" << 2 << "op"
+ << "i"
+ << "ns" << nss.ns() << "ui" << uuid << "wall" << Date_t() << "o" << doc0));
+ auto op1 = repl::OplogEntry(
+ BSON("ts" << insertTime1.asTimestamp() << "t" << 1LL << "v" << 2 << "op"
+ << "i"
+ << "ns" << nss.ns() << "ui" << uuid << "wall" << Date_t() << "o" << doc1));
+ auto op2 = repl::OplogEntry(
+ BSON("ts" << insertTime2.asTimestamp() << "t" << 1LL << "v" << 2 << "op"
+ << "i"
+ << "ns" << nss.ns() << "ui" << uuid << "wall" << Date_t() << "o" << doc2));
+
+ std::vector<repl::OplogEntry> ops = {op0, op1, op2};
+
+ DoNothingOplogApplierObserver observer;
+ auto storageInterface = repl::StorageInterface::get(_opCtx);
+ auto writerPool = repl::OplogApplier::makeWriterPool();
+ repl::OplogApplierImpl oplogApplier(
+ nullptr, // task executor. not required for multiApply().
+ nullptr, // oplog buffer. not required for multiApply().
+ &observer,
+ _coordinatorMock,
+ _consistencyMarkers,
+ storageInterface,
+ repl::OplogApplier::Options(repl::OplogApplication::Mode::kRecovering),
+ writerPool.get());
+
+ uassertStatusOK(oplogApplier.multiApply(_opCtx, ops));
+
+ AutoGetCollectionForRead autoColl(_opCtx, nss);
+ auto wildcardIndexDescriptor =
+ autoColl.getCollection()->getIndexCatalog()->findIndexByName(_opCtx, indexName);
+ const IndexAccessMethod* wildcardIndexAccessMethod = autoColl.getCollection()
+ ->getIndexCatalog()
+ ->getEntry(wildcardIndexDescriptor)
+ ->accessMethod();
+ {
+ // Verify that the index is not multikey prior to the earliest insert timestamp.
+ OneOffRead oor(_opCtx, pastTime.asTimestamp());
+ const WildcardAccessMethod* wam =
+ dynamic_cast<const WildcardAccessMethod*>(wildcardIndexAccessMethod);
+ MultikeyMetadataAccessStats stats;
+ std::set<FieldRef> paths = wam->getMultikeyPathSet(_opCtx, &stats);
+ ASSERT_EQUALS(0UL, paths.size());
+ }
+ {
+ // Oplog application conservatively uses the first optime in the batch, insertTime0, as
+ // the point at which the index became multikey, despite the fact that the earliest op
+ // which caused the index to become multikey did not occur until insertTime1. This works
+ // because if we construct a query plan that incorrectly believes a particular path to
+ // be multikey, the plan will still be correct (if possibly sub-optimal). Conversely, if
+ // we were to construct a query plan that incorrectly believes a path is NOT multikey,
+ // it could produce incorrect results.
+ OneOffRead oor(_opCtx, insertTime0.asTimestamp());
+ const WildcardAccessMethod* wam =
+ dynamic_cast<const WildcardAccessMethod*>(wildcardIndexAccessMethod);
+ MultikeyMetadataAccessStats stats;
+ std::set<FieldRef> paths = wam->getMultikeyPathSet(_opCtx, &stats);
+ ASSERT_EQUALS(1UL, paths.size());
+ ASSERT_EQUALS("a", paths.begin()->dottedField());
+ }
+ }
+};
+
+class SecondarySetWildcardIndexMultikeyOnUpdate : public StorageTimestampTest {
+
+public:
+ void run() {
+ // Pretend to be a secondary.
+ repl::UnreplicatedWritesBlock uwb(_opCtx);
+
+ NamespaceString nss("unittests.SecondarySetWildcardIndexMultikeyOnUpdate");
+ reset(nss);
+ UUID uuid = UUID::gen();
+ {
+ AutoGetCollection autoColl(_opCtx, nss, LockMode::MODE_IX);
+ uuid = *autoColl.getCollection()->uuid();
+ }
+ auto indexName = "a_1";
+ auto indexSpec = BSON("name" << indexName << "key" << BSON("$**" << 1) << "v"
+ << static_cast<int>(kIndexVersion));
+ ASSERT_OK(dbtests::createIndexFromSpec(_opCtx, nss.ns(), indexSpec));
+
+ _coordinatorMock->alwaysAllowWrites(false);
+
+ const LogicalTime pastTime = _clock->reserveTicks(1);
+ const LogicalTime insertTime0 = _clock->reserveTicks(1);
+ const LogicalTime updateTime1 = _clock->reserveTicks(1);
+ const LogicalTime updateTime2 = _clock->reserveTicks(1);
+
+ BSONObj doc0 = BSON("_id" << 0 << "a" << 3);
+ BSONObj doc1 = BSON("$v" << 1 << "$set" << BSON("a" << BSON_ARRAY(1 << 2)));
+ BSONObj doc2 = BSON("$v" << 1 << "$set" << BSON("a" << BSON_ARRAY(1 << 2)));
+ auto op0 = repl::OplogEntry(
+ BSON("ts" << insertTime0.asTimestamp() << "t" << 1LL << "v" << 2 << "op"
+ << "i"
+ << "ns" << nss.ns() << "ui" << uuid << "wall" << Date_t() << "o" << doc0));
+ auto op1 = repl::OplogEntry(
+ BSON("ts" << updateTime1.asTimestamp() << "t" << 1LL << "v" << 2 << "op"
+ << "u"
+ << "ns" << nss.ns() << "ui" << uuid << "wall" << Date_t() << "o" << doc1
+ << "o2" << BSON("_id" << 0)));
+ auto op2 = repl::OplogEntry(
+ BSON("ts" << updateTime2.asTimestamp() << "t" << 1LL << "v" << 2 << "op"
+ << "u"
+ << "ns" << nss.ns() << "ui" << uuid << "wall" << Date_t() << "o" << doc2
+ << "o2" << BSON("_id" << 0)));
+
+ std::vector<repl::OplogEntry> ops = {op0, op1, op2};
+
+ DoNothingOplogApplierObserver observer;
+ auto storageInterface = repl::StorageInterface::get(_opCtx);
+ auto writerPool = repl::OplogApplier::makeWriterPool();
+ repl::OplogApplierImpl oplogApplier(
+ nullptr, // task executor. not required for multiApply().
+ nullptr, // oplog buffer. not required for multiApply().
+ &observer,
+ _coordinatorMock,
+ _consistencyMarkers,
+ storageInterface,
+ repl::OplogApplier::Options(repl::OplogApplication::Mode::kRecovering),
+ writerPool.get());
+
+ uassertStatusOK(oplogApplier.multiApply(_opCtx, ops));
+
+ AutoGetCollectionForRead autoColl(_opCtx, nss);
+ auto wildcardIndexDescriptor =
+ autoColl.getCollection()->getIndexCatalog()->findIndexByName(_opCtx, indexName);
+ const IndexAccessMethod* wildcardIndexAccessMethod = autoColl.getCollection()
+ ->getIndexCatalog()
+ ->getEntry(wildcardIndexDescriptor)
+ ->accessMethod();
+ {
+ // Verify that the index is not multikey prior to the earliest insert timestamp.
+ OneOffRead oor(_opCtx, pastTime.asTimestamp());
+ const WildcardAccessMethod* wam =
+ dynamic_cast<const WildcardAccessMethod*>(wildcardIndexAccessMethod);
+ MultikeyMetadataAccessStats stats;
+ std::set<FieldRef> paths = wam->getMultikeyPathSet(_opCtx, &stats);
+ ASSERT_EQUALS(0UL, paths.size());
+ }
+ {
+ // Oplog application conservatively uses the first optime in the batch, insertTime0, as
+ // the point at which the index became multikey, despite the fact that the earliest op
+ // which caused the index to become multikey did not occur until updateTime1. This works
+ // because if we construct a query plan that incorrectly believes a particular path to
+ // be multikey, the plan will still be correct (if possibly sub-optimal). Conversely, if
+ // we were to construct a query plan that incorrectly believes a path is NOT multikey,
+ // it could produce incorrect results.
+ OneOffRead oor(_opCtx, insertTime0.asTimestamp());
+ const WildcardAccessMethod* wam =
+ dynamic_cast<const WildcardAccessMethod*>(wildcardIndexAccessMethod);
+ MultikeyMetadataAccessStats stats;
+ std::set<FieldRef> paths = wam->getMultikeyPathSet(_opCtx, &stats);
+ ASSERT_EQUALS(1UL, paths.size());
+ ASSERT_EQUALS("a", paths.begin()->dottedField());
+ }
+ }
+};
+
class InitialSyncSetIndexMultikeyOnInsert : public StorageTimestampTest {
public:
@@ -3432,6 +3623,8 @@ public:
add<SecondaryCreateCollectionBetweenInserts>();
add<PrimaryCreateCollectionInApplyOps>();
add<SecondarySetIndexMultikeyOnInsert>();
+ add<SecondarySetWildcardIndexMultikeyOnInsert>();
+ add<SecondarySetWildcardIndexMultikeyOnUpdate>();
add<InitialSyncSetIndexMultikeyOnInsert>();
add<PrimarySetIndexMultikeyOnInsert>();
add<PrimarySetIndexMultikeyOnInsertUnreplicated>();
diff --git a/src/mongo/dbtests/validate_tests.cpp b/src/mongo/dbtests/validate_tests.cpp
index 8968eb7ec71..d27fe28a291 100644
--- a/src/mongo/dbtests/validate_tests.cpp
+++ b/src/mongo/dbtests/validate_tests.cpp
@@ -1609,8 +1609,6 @@ public:
auto insertStatus = iam->insertKeys(
&_opCtx,
{keys.begin(), keys.end()},
- {},
- MultikeyPaths{},
swRecordId.getValue(),
options,
[this, &interceptor](const BSONObj& duplicateKey) {
@@ -1648,8 +1646,6 @@ public:
auto insertStatus = iam->insertKeys(
&_opCtx,
{keys.begin(), keys.end()},
- {},
- MultikeyPaths{},
swRecordId.getValue(),
options,
[this, &interceptor](const BSONObj& duplicateKey) {