summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMax Hirschhorn <max.hirschhorn@mongodb.com>2016-05-08 14:16:24 -0400
committerMax Hirschhorn <max.hirschhorn@mongodb.com>2016-05-08 14:16:24 -0400
commit3a0d6ee6a2b6f82c5775380b7184501916338331 (patch)
treeeeb564a87bbd7b285392499cbfa01d179ff67d52
parent65222c4b5146cc6b8930e784149e5aade132a8fc (diff)
downloadmongo-3a0d6ee6a2b6f82c5775380b7184501916338331.tar.gz
SERVER-22726 Store path-level multikey information in the KVCatalog.
-rw-r--r--src/mongo/db/catalog/collection_catalog_entry.h27
-rw-r--r--src/mongo/db/catalog/index_catalog_entry.cpp5
-rw-r--r--src/mongo/db/storage/SConscript1
-rw-r--r--src/mongo/db/storage/bson_collection_catalog_entry.cpp93
-rw-r--r--src/mongo/db/storage/bson_collection_catalog_entry.h11
-rw-r--r--src/mongo/db/storage/kv/SConscript15
-rw-r--r--src/mongo/db/storage/kv/kv_collection_catalog_entry.cpp62
-rw-r--r--src/mongo/db/storage/kv/kv_collection_catalog_entry.h2
-rw-r--r--src/mongo/db/storage/kv/kv_collection_catalog_entry_test.cpp321
-rw-r--r--src/mongo/db/storage/mmap_v1/catalog/namespace_details_collection_entry.cpp15
-rw-r--r--src/mongo/db/storage/mmap_v1/catalog/namespace_details_collection_entry.h6
11 files changed, 538 insertions, 20 deletions
diff --git a/src/mongo/db/catalog/collection_catalog_entry.h b/src/mongo/db/catalog/collection_catalog_entry.h
index a31843b3cce..dd6a1f506f1 100644
--- a/src/mongo/db/catalog/collection_catalog_entry.h
+++ b/src/mongo/db/catalog/collection_catalog_entry.h
@@ -32,6 +32,7 @@
#include "mongo/base/string_data.h"
#include "mongo/db/catalog/collection_options.h"
+#include "mongo/db/index/multikey_paths.h"
#include "mongo/db/namespace_string.h"
#include "mongo/db/record_id.h"
@@ -63,11 +64,33 @@ public:
virtual BSONObj getIndexSpec(OperationContext* txn, StringData idxName) const = 0;
- virtual bool isIndexMultikey(OperationContext* txn, StringData indexName) const = 0;
+ /**
+ * Returns true if the index identified by 'indexName' is multikey, and returns false otherwise.
+ *
+ * If the 'multikeyPaths' pointer is non-null, then it must point to an empty vector. If this
+ * index supports tracking path-level multikey information, then this function sets
+ * 'multikeyPaths' as the path components that cause this index to be multikey.
+ *
+ * In particular, if this function returns false and the index supports tracking path-level
+ * multikey information, then 'multikeyPaths' is initialized as a vector with size equal to the
+ * number of elements in the index key pattern of empty sets.
+ */
+ virtual bool isIndexMultikey(OperationContext* txn,
+ StringData indexName,
+ MultikeyPaths* multikeyPaths) const = 0;
+ /**
+ * Sets the index identified by 'indexName' to be multikey.
+ *
+ * If 'multikeyPaths' is non-empty, then it must be a vector with size equal to the number of
+ * elements in the index key pattern. Additionally, at least one path component of the indexed
+ * fields must cause this index to be multikey.
+ *
+ * This function returns true if the index metadata has changed, and returns false otherwise.
+ */
virtual bool setIndexIsMultikey(OperationContext* txn,
StringData indexName,
- bool multikey = true) = 0;
+ const MultikeyPaths& multikeyPaths) = 0;
virtual RecordId getIndexHead(OperationContext* txn, StringData indexName) const = 0;
diff --git a/src/mongo/db/catalog/index_catalog_entry.cpp b/src/mongo/db/catalog/index_catalog_entry.cpp
index 239cb7e4fc0..65b48166249 100644
--- a/src/mongo/db/catalog/index_catalog_entry.cpp
+++ b/src/mongo/db/catalog/index_catalog_entry.cpp
@@ -220,7 +220,8 @@ void IndexCatalogEntry::setMultikey(OperationContext* txn) {
WriteUnitOfWork wuow(txn);
- if (_collection->setIndexIsMultikey(txn, _descriptor->indexName())) {
+ // TODO SERVER-22726: Propagate multikey paths computed during index key generation.
+ if (_collection->setIndexIsMultikey(txn, _descriptor->indexName(), MultikeyPaths{})) {
if (_infoCache) {
LOG(1) << _ns << ": clearing plan cache - index " << _descriptor->keyPattern()
<< " set to multi key.";
@@ -245,7 +246,7 @@ RecordId IndexCatalogEntry::_catalogHead(OperationContext* txn) const {
}
bool IndexCatalogEntry::_catalogIsMultikey(OperationContext* txn) const {
- return _collection->isIndexMultikey(txn, _descriptor->indexName());
+ return _collection->isIndexMultikey(txn, _descriptor->indexName(), nullptr);
}
// ------------------
diff --git a/src/mongo/db/storage/SConscript b/src/mongo/db/storage/SConscript
index eae01a14119..c2b6ae9e5e4 100644
--- a/src/mongo/db/storage/SConscript
+++ b/src/mongo/db/storage/SConscript
@@ -48,6 +48,7 @@ env.Library(
],
LIBDEPS=[
'$BUILD_DIR/mongo/db/catalog/collection_options',
+ '$BUILD_DIR/mongo/db/common',
'$BUILD_DIR/mongo/db/service_context',
],
)
diff --git a/src/mongo/db/storage/bson_collection_catalog_entry.cpp b/src/mongo/db/storage/bson_collection_catalog_entry.cpp
index 9a07ed8295e..d5a0c7da6cc 100644
--- a/src/mongo/db/storage/bson_collection_catalog_entry.cpp
+++ b/src/mongo/db/storage/bson_collection_catalog_entry.cpp
@@ -30,8 +30,75 @@
#include "mongo/db/storage/bson_collection_catalog_entry.h"
+#include <algorithm>
+
+#include "mongo/db/field_ref.h"
+
namespace mongo {
+namespace {
+
+// An index will fail to get created if the size in bytes of its key pattern is greater than 2048.
+// We use that value to represent the largest number of path components we could ever possibly
+// expect to see in an indexed field.
+const size_t kMaxKeyPatternPathLength = 2048;
+char multikeyPathsEncodedAsBytes[kMaxKeyPatternPathLength];
+
+/**
+ * Encodes 'multikeyPaths' as binary data and appends it to 'bob'.
+ *
+ * For example, consider the index {'a.b': 1, 'a.c': 1} where the paths "a" and "a.b" cause it to be
+ * multikey. The object {'a.b': HexData('0101'), 'a.c': HexData('0100')} would then be appended to
+ * 'bob'.
+ */
+void appendMultikeyPathsAsBytes(BSONObj keyPattern,
+ const MultikeyPaths& multikeyPaths,
+ BSONObjBuilder* bob) {
+ size_t i = 0;
+ for (const auto keyElem : keyPattern) {
+ StringData keyName = keyElem.fieldNameStringData();
+ size_t numParts = FieldRef{keyName}.numParts();
+ invariant(numParts > 0);
+ invariant(numParts <= kMaxKeyPatternPathLength);
+
+ std::fill_n(multikeyPathsEncodedAsBytes, numParts, 0);
+ for (const auto multikeyComponent : multikeyPaths[i]) {
+ multikeyPathsEncodedAsBytes[multikeyComponent] = 1;
+ }
+ bob->appendBinData(keyName, numParts, BinDataGeneral, &multikeyPathsEncodedAsBytes[0]);
+
+ ++i;
+ }
+}
+
+/**
+ * Parses the path-level multikey information encoded as binary data from 'multikeyPathsObj' and
+ * sets 'multikeyPaths' as that value.
+ *
+ * For example, consider the index {'a.b': 1, 'a.c': 1} where the paths "a" and "a.b" cause it to be
+ * multikey. The binary data {'a.b': HexData('0101'), 'a.c': HexData('0100')} would then be parsed
+ * into std::vector<std::set<size_t>>{{0U, 1U}, {0U}}.
+ */
+void parseMultikeyPathsFromBytes(BSONObj multikeyPathsObj, MultikeyPaths* multikeyPaths) {
+ invariant(multikeyPaths);
+ for (auto elem : multikeyPathsObj) {
+ std::set<size_t> multikeyComponents;
+ int len;
+ const char* data = elem.binData(len);
+ invariant(len > 0);
+ invariant(static_cast<size_t>(len) <= kMaxKeyPatternPathLength);
+
+ for (int i = 0; i < len; ++i) {
+ if (data[i]) {
+ multikeyComponents.insert(i);
+ }
+ }
+ multikeyPaths->push_back(multikeyComponents);
+ }
+}
+
+} // namespace
+
BSONCollectionCatalogEntry::BSONCollectionCatalogEntry(StringData ns)
: CollectionCatalogEntry(ns) {}
@@ -77,11 +144,17 @@ void BSONCollectionCatalogEntry::getAllIndexes(OperationContext* txn,
}
bool BSONCollectionCatalogEntry::isIndexMultikey(OperationContext* txn,
- StringData indexName) const {
+ StringData indexName,
+ MultikeyPaths* multikeyPaths) const {
MetaData md = _getMetaData(txn);
int offset = md.findIndexOffset(indexName);
invariant(offset >= 0);
+
+ if (multikeyPaths && !md.indexes[offset].multikeyPaths.empty()) {
+ *multikeyPaths = md.indexes[offset].multikeyPaths;
+ }
+
return md.indexes[offset].multikey;
}
@@ -160,10 +233,19 @@ BSONObj BSONCollectionCatalogEntry::MetaData::toBSON() const {
sub.append("spec", indexes[i].spec);
sub.appendBool("ready", indexes[i].ready);
sub.appendBool("multikey", indexes[i].multikey);
+
+ if (!indexes[i].multikeyPaths.empty()) {
+ BSONObjBuilder subMultikeyPaths(sub.subobjStart("multikeyPaths"));
+ appendMultikeyPathsAsBytes(indexes[i].spec.getObjectField("key"),
+ indexes[i].multikeyPaths,
+ &subMultikeyPaths);
+ subMultikeyPaths.doneFast();
+ }
+
sub.append("head", static_cast<long long>(indexes[i].head.repr()));
- sub.done();
+ sub.doneFast();
}
- arr.done();
+ arr.doneFast();
}
return b.obj();
}
@@ -189,6 +271,11 @@ void BSONCollectionCatalogEntry::MetaData::parse(const BSONObj& obj) {
imd.head = RecordId(idx["head_a"].Int(), idx["head_b"].Int());
}
imd.multikey = idx["multikey"].trueValue();
+
+ if (auto multikeyPathsElem = idx["multikeyPaths"]) {
+ parseMultikeyPathsFromBytes(multikeyPathsElem.Obj(), &imd.multikeyPaths);
+ }
+
indexes.push_back(imd);
}
}
diff --git a/src/mongo/db/storage/bson_collection_catalog_entry.h b/src/mongo/db/storage/bson_collection_catalog_entry.h
index 179c64591db..83c2238fc17 100644
--- a/src/mongo/db/storage/bson_collection_catalog_entry.h
+++ b/src/mongo/db/storage/bson_collection_catalog_entry.h
@@ -34,6 +34,7 @@
#include <vector>
#include "mongo/db/catalog/collection_catalog_entry.h"
+#include "mongo/db/index/multikey_paths.h"
namespace mongo {
@@ -57,7 +58,9 @@ public:
virtual void getAllIndexes(OperationContext* txn, std::vector<std::string>* names) const;
- virtual bool isIndexMultikey(OperationContext* txn, StringData indexName) const;
+ virtual bool isIndexMultikey(OperationContext* txn,
+ StringData indexName,
+ MultikeyPaths* multikeyPaths) const;
virtual RecordId getIndexHead(OperationContext* txn, StringData indexName) const;
@@ -80,6 +83,12 @@ public:
bool ready;
RecordId head;
bool multikey;
+
+ // If non-empty, 'multikeyPaths' is a vector with size equal to the number of elements in
+ // the index key pattern. Each element in the vector is an ordered set of positions
+ // (starting at 0) into the corresponding indexed field that represent what prefixes of the
+ // indexed field cause the index to be multikey.
+ MultikeyPaths multikeyPaths;
};
struct MetaData {
diff --git a/src/mongo/db/storage/kv/SConscript b/src/mongo/db/storage/kv/SConscript
index 2d4a63498fa..c4756f9a5da 100644
--- a/src/mongo/db/storage/kv/SConscript
+++ b/src/mongo/db/storage/kv/SConscript
@@ -9,6 +9,7 @@ env.Library(
LIBDEPS=[
'$BUILD_DIR/mongo/db/concurrency/lock_manager',
'$BUILD_DIR/mongo/db/index/index_descriptor',
+ '$BUILD_DIR/mongo/db/index_names',
'$BUILD_DIR/mongo/db/namespace_string',
'$BUILD_DIR/mongo/db/storage/bson_collection_catalog_entry',
],
@@ -106,8 +107,22 @@ env.CppUnitTest(
],
LIBDEPS=[
'kv_engine_mock',
+ '$BUILD_DIR/mongo/db/catalog/collection_options',
'$BUILD_DIR/mongo/db/namespace_string',
+ '$BUILD_DIR/mongo/db/storage/devnull/storage_devnull_core',
+ '$BUILD_DIR/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_record_store',
+ ]
+ )
+
+env.CppUnitTest(
+ target='kv_collection_catalog_entry_test',
+ source=[
+ 'kv_collection_catalog_entry_test.cpp',
+ ],
+ LIBDEPS=[
+ 'kv_engine_mock',
'$BUILD_DIR/mongo/db/catalog/collection_options',
+ '$BUILD_DIR/mongo/db/namespace_string',
'$BUILD_DIR/mongo/db/storage/devnull/storage_devnull_core',
'$BUILD_DIR/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_record_store',
]
diff --git a/src/mongo/db/storage/kv/kv_collection_catalog_entry.cpp b/src/mongo/db/storage/kv/kv_collection_catalog_entry.cpp
index 38a9d2b3b09..10a853ced75 100644
--- a/src/mongo/db/storage/kv/kv_collection_catalog_entry.cpp
+++ b/src/mongo/db/storage/kv/kv_collection_catalog_entry.cpp
@@ -38,6 +38,15 @@ namespace mongo {
using std::string;
+namespace {
+
+bool indexTypeSupportsPathLevelMultikeyTracking(StringData accessMethod) {
+ // TODO SERVER-23906: Enable path-level multikey tracking for new 2dsphere indexes.
+ return accessMethod == IndexNames::BTREE;
+}
+
+} // namespace
+
class KVCollectionCatalogEntry::AddIndexChange : public RecoveryUnit::Change {
public:
AddIndexChange(OperationContext* opCtx, KVCollectionCatalogEntry* cce, StringData ident)
@@ -84,14 +93,53 @@ KVCollectionCatalogEntry::~KVCollectionCatalogEntry() {}
bool KVCollectionCatalogEntry::setIndexIsMultikey(OperationContext* txn,
StringData indexName,
- bool multikey) {
+ const MultikeyPaths& multikeyPaths) {
MetaData md = _getMetaData(txn);
int offset = md.findIndexOffset(indexName);
invariant(offset >= 0);
- if (md.indexes[offset].multikey == multikey)
- return false;
- md.indexes[offset].multikey = multikey;
+
+ const bool tracksPathLevelMultikeyInfo = !md.indexes[offset].multikeyPaths.empty();
+ if (!tracksPathLevelMultikeyInfo) {
+ invariant(multikeyPaths.empty());
+
+ if (md.indexes[offset].multikey) {
+ // The index is already set as multikey and we aren't tracking path-level multikey
+ // information for it. We return false to indicate that the index metadata is unchanged.
+ return false;
+ }
+ }
+
+ md.indexes[offset].multikey = true;
+
+ if (tracksPathLevelMultikeyInfo && !multikeyPaths.empty()) {
+ invariant(multikeyPaths.size() == md.indexes[offset].multikeyPaths.size());
+
+ bool newPathIsMultikey = false;
+ bool somePathIsMultikey = false;
+
+ // Store new path components that cause this index to be multikey in catalog's index
+ // metadata.
+ for (size_t i = 0; i < multikeyPaths.size(); ++i) {
+ std::set<size_t>& indexMultikeyComponents = md.indexes[offset].multikeyPaths[i];
+ for (const auto multikeyComponent : multikeyPaths[i]) {
+ auto result = indexMultikeyComponents.insert(multikeyComponent);
+ newPathIsMultikey = newPathIsMultikey || result.second;
+ somePathIsMultikey = true;
+ }
+ }
+
+ // If all of the sets in the multikey paths vector were empty, then no component of any
+ // indexed field caused the index to be multikey. setIndexIsMultikey() therefore shouldn't
+ // have been called.
+ invariant(somePathIsMultikey);
+
+ if (!newPathIsMultikey) {
+ // We return false to indicate that the index metadata is unchanged.
+ return false;
+ }
+ }
+
_catalog->putMetaData(txn, ns().toString(), md);
return true;
}
@@ -125,7 +173,11 @@ Status KVCollectionCatalogEntry::removeIndex(OperationContext* txn, StringData i
Status KVCollectionCatalogEntry::prepareForIndexBuild(OperationContext* txn,
const IndexDescriptor* spec) {
MetaData md = _getMetaData(txn);
- md.indexes.push_back(IndexMetaData(spec->infoObj(), false, RecordId(), false));
+ IndexMetaData imd(spec->infoObj(), false, RecordId(), false);
+ if (indexTypeSupportsPathLevelMultikeyTracking(spec->getAccessMethodName())) {
+ imd.multikeyPaths = MultikeyPaths{static_cast<size_t>(spec->keyPattern().nFields())};
+ }
+ md.indexes.push_back(imd);
_catalog->putMetaData(txn, ns().toString(), md);
string ident = _catalog->getIndexIdent(txn, ns().ns(), spec->indexName());
diff --git a/src/mongo/db/storage/kv/kv_collection_catalog_entry.h b/src/mongo/db/storage/kv/kv_collection_catalog_entry.h
index 3a3704e82d5..fb6c884c485 100644
--- a/src/mongo/db/storage/kv/kv_collection_catalog_entry.h
+++ b/src/mongo/db/storage/kv/kv_collection_catalog_entry.h
@@ -52,7 +52,7 @@ public:
bool setIndexIsMultikey(OperationContext* txn,
StringData indexName,
- bool multikey = true) final;
+ const MultikeyPaths& multikeyPaths) final;
void setIndexHead(OperationContext* txn, StringData indexName, const RecordId& newHead) final;
diff --git a/src/mongo/db/storage/kv/kv_collection_catalog_entry_test.cpp b/src/mongo/db/storage/kv/kv_collection_catalog_entry_test.cpp
new file mode 100644
index 00000000000..57729826aa4
--- /dev/null
+++ b/src/mongo/db/storage/kv/kv_collection_catalog_entry_test.cpp
@@ -0,0 +1,321 @@
+/**
+ * Copyright (C) 2016 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include <iostream>
+#include <string>
+
+#include "mongo/db/catalog/collection_catalog_entry.h"
+#include "mongo/db/catalog/database_catalog_entry.h"
+#include "mongo/db/operation_context_noop.h"
+#include "mongo/db/index/index_descriptor.h"
+#include "mongo/db/index/multikey_paths.h"
+#include "mongo/db/storage/devnull/devnull_kv_engine.h"
+#include "mongo/db/storage/kv/kv_engine.h"
+#include "mongo/db/storage/kv/kv_storage_engine.h"
+#include "mongo/unittest/death_test.h"
+#include "mongo/unittest/unittest.h"
+#include "mongo/util/mongoutils/str.h"
+
+namespace mongo {
+namespace {
+
+class KVCollectionCatalogEntryTest : public unittest::Test {
+public:
+ KVCollectionCatalogEntryTest()
+ : _nss("unittests.kv_collection_catalog_entry"), _storageEngine(new DevNullKVEngine()) {
+ _storageEngine.finishInit();
+ }
+
+ ~KVCollectionCatalogEntryTest() {
+ _storageEngine.cleanShutdown();
+ }
+
+ std::unique_ptr<OperationContext> newOperationContext() {
+ return stdx::make_unique<OperationContextNoop>(_storageEngine.newRecoveryUnit());
+ }
+
+ void setUp() final {
+ auto opCtx = newOperationContext();
+ DatabaseCatalogEntry* dbEntry =
+ _storageEngine.getDatabaseCatalogEntry(opCtx.get(), _nss.db());
+
+ {
+ WriteUnitOfWork wuow(opCtx.get());
+ const bool allocateDefaultSpace = true;
+ ASSERT_OK(dbEntry->createCollection(
+ opCtx.get(), _nss.ns(), CollectionOptions(), allocateDefaultSpace));
+ wuow.commit();
+ }
+ }
+
+ CollectionCatalogEntry* getCollectionCatalogEntry() {
+ auto opCtx = newOperationContext();
+ DatabaseCatalogEntry* dbEntry =
+ _storageEngine.getDatabaseCatalogEntry(opCtx.get(), _nss.db());
+ return dbEntry->getCollectionCatalogEntry(_nss.ns());
+ }
+
+ std::string createIndex(BSONObj keyPattern, std::string indexType = IndexNames::BTREE) {
+ auto opCtx = newOperationContext();
+ std::string indexName = "idx" + std::to_string(numIndexesCreated);
+
+ IndexDescriptor desc(
+ nullptr,
+ indexType,
+ BSON("v" << 1 << "key" << keyPattern << "name" << indexName << "ns" << _nss.ns()));
+
+ {
+ WriteUnitOfWork wuow(opCtx.get());
+ ASSERT_OK(getCollectionCatalogEntry()->prepareForIndexBuild(opCtx.get(), &desc));
+ wuow.commit();
+ }
+
+ ++numIndexesCreated;
+ return indexName;
+ }
+
+ void assertMultikeyPathsAreEqual(const MultikeyPaths& actual, const MultikeyPaths& expected) {
+ bool match = (expected == actual);
+ if (!match) {
+ FAIL(str::stream() << "Expected: " << dumpMultikeyPaths(expected) << ", "
+ << "Actual: " << dumpMultikeyPaths(actual));
+ }
+ ASSERT(match);
+ }
+
+private:
+ std::string dumpMultikeyPaths(const MultikeyPaths& multikeyPaths) {
+ std::stringstream ss;
+
+ ss << "[ ";
+ for (const auto multikeyComponents : multikeyPaths) {
+ ss << "[ ";
+ for (const auto multikeyComponent : multikeyComponents) {
+ ss << multikeyComponent << " ";
+ }
+ ss << "] ";
+ }
+ ss << "]";
+
+ return ss.str();
+ }
+
+ const NamespaceString _nss;
+ KVStorageEngine _storageEngine;
+ size_t numIndexesCreated = 0;
+};
+
+TEST_F(KVCollectionCatalogEntryTest, MultikeyPathsForBtreeIndexInitializedToVectorOfEmptySets) {
+ std::string indexName = createIndex(BSON("a" << 1 << "b" << 1));
+ CollectionCatalogEntry* collEntry = getCollectionCatalogEntry();
+
+ auto opCtx = newOperationContext();
+ {
+ MultikeyPaths multikeyPaths;
+ ASSERT(!collEntry->isIndexMultikey(opCtx.get(), indexName, &multikeyPaths));
+ assertMultikeyPathsAreEqual(multikeyPaths, {std::set<size_t>{}, std::set<size_t>{}});
+ }
+}
+
+TEST_F(KVCollectionCatalogEntryTest, CanSetIndividualPathComponentOfBtreeIndexAsMultikey) {
+ std::string indexName = createIndex(BSON("a" << 1 << "b" << 1));
+ CollectionCatalogEntry* collEntry = getCollectionCatalogEntry();
+
+ auto opCtx = newOperationContext();
+ ASSERT(collEntry->setIndexIsMultikey(opCtx.get(), indexName, {std::set<size_t>{}, {0U}}));
+
+ {
+ MultikeyPaths multikeyPaths;
+ ASSERT(collEntry->isIndexMultikey(opCtx.get(), indexName, &multikeyPaths));
+ assertMultikeyPathsAreEqual(multikeyPaths, {std::set<size_t>{}, {0U}});
+ }
+}
+
+TEST_F(KVCollectionCatalogEntryTest, MultikeyPathsAccumulateOnDifferentFields) {
+ std::string indexName = createIndex(BSON("a" << 1 << "b" << 1));
+ CollectionCatalogEntry* collEntry = getCollectionCatalogEntry();
+
+ auto opCtx = newOperationContext();
+ ASSERT(collEntry->setIndexIsMultikey(opCtx.get(), indexName, {std::set<size_t>{}, {0U}}));
+
+ {
+ MultikeyPaths multikeyPaths;
+ ASSERT(collEntry->isIndexMultikey(opCtx.get(), indexName, &multikeyPaths));
+ assertMultikeyPathsAreEqual(multikeyPaths, {std::set<size_t>{}, {0U}});
+ }
+
+ ASSERT(collEntry->setIndexIsMultikey(opCtx.get(), indexName, {{0U}, std::set<size_t>{}}));
+
+ {
+ MultikeyPaths multikeyPaths;
+ ASSERT(collEntry->isIndexMultikey(opCtx.get(), indexName, &multikeyPaths));
+ assertMultikeyPathsAreEqual(multikeyPaths, {{0U}, {0U}});
+ }
+}
+
+TEST_F(KVCollectionCatalogEntryTest, MultikeyPathsAccumulateOnDifferentComponentsOfTheSameField) {
+ std::string indexName = createIndex(BSON("a.b" << 1));
+ CollectionCatalogEntry* collEntry = getCollectionCatalogEntry();
+
+ auto opCtx = newOperationContext();
+ ASSERT(collEntry->setIndexIsMultikey(opCtx.get(), indexName, {{0U}}));
+
+ {
+ MultikeyPaths multikeyPaths;
+ ASSERT(collEntry->isIndexMultikey(opCtx.get(), indexName, &multikeyPaths));
+ assertMultikeyPathsAreEqual(multikeyPaths, {{0U}});
+ }
+
+ ASSERT(collEntry->setIndexIsMultikey(opCtx.get(), indexName, {{1U}}));
+
+ {
+ MultikeyPaths multikeyPaths;
+ ASSERT(collEntry->isIndexMultikey(opCtx.get(), indexName, &multikeyPaths));
+ assertMultikeyPathsAreEqual(multikeyPaths, {{0U, 1U}});
+ }
+}
+
+TEST_F(KVCollectionCatalogEntryTest, NoOpWhenSpecifiedPathComponentsAlreadySetAsMultikey) {
+ std::string indexName = createIndex(BSON("a" << 1));
+ CollectionCatalogEntry* collEntry = getCollectionCatalogEntry();
+
+ auto opCtx = newOperationContext();
+ ASSERT(collEntry->setIndexIsMultikey(opCtx.get(), indexName, {{0U}}));
+
+ {
+ MultikeyPaths multikeyPaths;
+ ASSERT(collEntry->isIndexMultikey(opCtx.get(), indexName, &multikeyPaths));
+ assertMultikeyPathsAreEqual(multikeyPaths, {{0U}});
+ }
+
+ ASSERT(!collEntry->setIndexIsMultikey(opCtx.get(), indexName, {{0U}}));
+
+ {
+ MultikeyPaths multikeyPaths;
+ ASSERT(collEntry->isIndexMultikey(opCtx.get(), indexName, &multikeyPaths));
+ assertMultikeyPathsAreEqual(multikeyPaths, {{0U}});
+ }
+}
+
+TEST_F(KVCollectionCatalogEntryTest, CanSetMultipleFieldsAndComponentsAsMultikey) {
+ std::string indexName = createIndex(BSON("a.b.c" << 1 << "a.b.d" << 1));
+ CollectionCatalogEntry* collEntry = getCollectionCatalogEntry();
+
+ auto opCtx = newOperationContext();
+ ASSERT(collEntry->setIndexIsMultikey(opCtx.get(), indexName, {{0U, 1U}, {0U, 1U}}));
+
+ {
+ MultikeyPaths multikeyPaths;
+ ASSERT(collEntry->isIndexMultikey(opCtx.get(), indexName, &multikeyPaths));
+ assertMultikeyPathsAreEqual(multikeyPaths, {{0U, 1U}, {0U, 1U}});
+ }
+}
+
+DEATH_TEST_F(KVCollectionCatalogEntryTest,
+ AtLeastOnePathComponentMustCauseIndexToBeMultikey,
+ "Invariant failure somePathIsMultikey") {
+ std::string indexName = createIndex(BSON("a" << 1 << "b" << 1));
+ CollectionCatalogEntry* collEntry = getCollectionCatalogEntry();
+
+ auto opCtx = newOperationContext();
+ collEntry->setIndexIsMultikey(opCtx.get(), indexName, {std::set<size_t>{}, std::set<size_t>{}});
+}
+
+TEST_F(KVCollectionCatalogEntryTest, PathLevelMultikeyTrackingIsNotSupportedByAllIndexTypes) {
+ std::string indexTypes[] = {IndexNames::GEO_2D,
+ IndexNames::GEO_HAYSTACK,
+ IndexNames::GEO_2DSPHERE,
+ IndexNames::TEXT,
+ IndexNames::HASHED};
+
+ for (auto&& indexType : indexTypes) {
+ std::string indexName = createIndex(BSON("a" << indexType << "b" << 1), indexType);
+ CollectionCatalogEntry* collEntry = getCollectionCatalogEntry();
+
+ auto opCtx = newOperationContext();
+ {
+ MultikeyPaths multikeyPaths;
+ ASSERT(!collEntry->isIndexMultikey(opCtx.get(), indexName, &multikeyPaths));
+ ASSERT(multikeyPaths.empty());
+ }
+ }
+}
+
+TEST_F(KVCollectionCatalogEntryTest, CanSetEntireTextIndexAsMultikey) {
+ std::string indexType = IndexNames::TEXT;
+ std::string indexName = createIndex(BSON("a" << indexType << "b" << 1), indexType);
+ CollectionCatalogEntry* collEntry = getCollectionCatalogEntry();
+
+ auto opCtx = newOperationContext();
+ ASSERT(collEntry->setIndexIsMultikey(opCtx.get(), indexName, MultikeyPaths{}));
+
+ {
+ MultikeyPaths multikeyPaths;
+ ASSERT(collEntry->isIndexMultikey(opCtx.get(), indexName, &multikeyPaths));
+ ASSERT(multikeyPaths.empty());
+ }
+}
+
+TEST_F(KVCollectionCatalogEntryTest, NoOpWhenEntireIndexAlreadySetAsMultikey) {
+ std::string indexType = IndexNames::TEXT;
+ std::string indexName = createIndex(BSON("a" << indexType << "b" << 1), indexType);
+ CollectionCatalogEntry* collEntry = getCollectionCatalogEntry();
+
+ auto opCtx = newOperationContext();
+ ASSERT(collEntry->setIndexIsMultikey(opCtx.get(), indexName, MultikeyPaths{}));
+
+ {
+ MultikeyPaths multikeyPaths;
+ ASSERT(collEntry->isIndexMultikey(opCtx.get(), indexName, &multikeyPaths));
+ ASSERT(multikeyPaths.empty());
+ }
+
+ ASSERT(!collEntry->setIndexIsMultikey(opCtx.get(), indexName, MultikeyPaths{}));
+
+ {
+ MultikeyPaths multikeyPaths;
+ ASSERT(collEntry->isIndexMultikey(opCtx.get(), indexName, &multikeyPaths));
+ ASSERT(multikeyPaths.empty());
+ }
+}
+
+DEATH_TEST_F(KVCollectionCatalogEntryTest,
+ CannotSetIndividualPathComponentsOfTextIndexAsMultikey,
+ "Invariant failure multikeyPaths.empty()") {
+ std::string indexType = IndexNames::TEXT;
+ std::string indexName = createIndex(BSON("a" << indexType << "b" << 1), indexType);
+ CollectionCatalogEntry* collEntry = getCollectionCatalogEntry();
+
+ auto opCtx = newOperationContext();
+ collEntry->setIndexIsMultikey(opCtx.get(), indexName, {{0U}, {0U}});
+}
+
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/db/storage/mmap_v1/catalog/namespace_details_collection_entry.cpp b/src/mongo/db/storage/mmap_v1/catalog/namespace_details_collection_entry.cpp
index a1c6cf5eb4f..cc5e57e0868 100644
--- a/src/mongo/db/storage/mmap_v1/catalog/namespace_details_collection_entry.cpp
+++ b/src/mongo/db/storage/mmap_v1/catalog/namespace_details_collection_entry.cpp
@@ -104,7 +104,10 @@ void NamespaceDetailsCollectionCatalogEntry::getAllIndexes(OperationContext* txn
}
bool NamespaceDetailsCollectionCatalogEntry::isIndexMultikey(OperationContext* txn,
- StringData idxName) const {
+ StringData idxName,
+ MultikeyPaths* multikeyPaths) const {
+ // TODO SERVER-22727: Populate 'multikeyPaths' with path components that cause 'idxName' to be
+ // multikey.
int idxNo = _findIndexNumber(txn, idxName);
invariant(idxNo >= 0);
return isIndexMultikey(idxNo);
@@ -114,11 +117,13 @@ bool NamespaceDetailsCollectionCatalogEntry::isIndexMultikey(int idxNo) const {
return (_details->multiKeyIndexBits & (((unsigned long long)1) << idxNo)) != 0;
}
-bool NamespaceDetailsCollectionCatalogEntry::setIndexIsMultikey(OperationContext* txn,
- StringData indexName,
- bool multikey) {
+bool NamespaceDetailsCollectionCatalogEntry::setIndexIsMultikey(
+ OperationContext* txn, StringData indexName, const MultikeyPaths& multikeyPaths) {
+ // TODO SERVER-22727: Store new path components from 'multikeyPaths' that cause 'indexName' to
+ // be multikey.
int idxNo = _findIndexNumber(txn, indexName);
invariant(idxNo >= 0);
+ const bool multikey = true;
return setIndexIsMultikey(txn, idxNo, multikey);
}
@@ -284,6 +289,8 @@ Status NamespaceDetailsCollectionCatalogEntry::prepareForIndexBuild(OperationCon
// 3) indexes entry in .ns file and system.namespaces
_db->createNamespaceForIndex(txn, desc->indexNamespace());
+ // TODO SERVER-22727: Create an entry for path-level multikey info when creating the new index.
+
return Status::OK();
}
diff --git a/src/mongo/db/storage/mmap_v1/catalog/namespace_details_collection_entry.h b/src/mongo/db/storage/mmap_v1/catalog/namespace_details_collection_entry.h
index 89760adf99a..0f8940be756 100644
--- a/src/mongo/db/storage/mmap_v1/catalog/namespace_details_collection_entry.h
+++ b/src/mongo/db/storage/mmap_v1/catalog/namespace_details_collection_entry.h
@@ -67,13 +67,15 @@ public:
BSONObj getIndexSpec(OperationContext* txn, StringData idxName) const final;
- bool isIndexMultikey(OperationContext* txn, StringData indexName) const final;
+ bool isIndexMultikey(OperationContext* txn,
+ StringData indexName,
+ MultikeyPaths* multikeyPaths) const final;
bool isIndexMultikey(int idxNo) const;
bool setIndexIsMultikey(OperationContext* txn, int idxNo, bool multikey = true);
bool setIndexIsMultikey(OperationContext* txn,
StringData indexName,
- bool multikey = true) final;
+ const MultikeyPaths& multikeyPaths) final;
RecordId getIndexHead(OperationContext* txn, StringData indexName) const final;