diff options
author | Max Hirschhorn <max.hirschhorn@mongodb.com> | 2016-05-08 14:16:24 -0400 |
---|---|---|
committer | Max Hirschhorn <max.hirschhorn@mongodb.com> | 2016-05-08 14:16:24 -0400 |
commit | 3a0d6ee6a2b6f82c5775380b7184501916338331 (patch) | |
tree | eeb564a87bbd7b285392499cbfa01d179ff67d52 | |
parent | 65222c4b5146cc6b8930e784149e5aade132a8fc (diff) | |
download | mongo-3a0d6ee6a2b6f82c5775380b7184501916338331.tar.gz |
SERVER-22726 Store path-level multikey information in the KVCatalog.
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; |