/**
* 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 .
*
* 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
#include
#include "mongo/db/catalog/index_create.h"
#include "mongo/db/client.h"
#include "mongo/db/db_raii.h"
#include "mongo/db/index/index_descriptor.h"
#include "mongo/db/index/multikey_paths.h"
#include "mongo/db/namespace_string.h"
#include "mongo/db/service_context.h"
#include "mongo/dbtests/dbtests.h"
#include "mongo/unittest/unittest.h"
#include "mongo/util/mongoutils/str.h"
namespace mongo {
namespace {
const auto kIndexVersion = IndexDescriptor::IndexVersion::kV2;
/**
* Fixture for testing correctness of multikey paths.
*
* Has helper functions for creating indexes and asserting that the multikey paths after performing
* write operations are as expected.
*/
class MultikeyPathsTest : public unittest::Test {
public:
MultikeyPathsTest() : _nss("unittests.multikey_paths") {}
void setUp() final {
AutoGetOrCreateDb autoDb(_opCtx.get(), _nss.db(), MODE_X);
Database* database = autoDb.getDb();
{
WriteUnitOfWork wuow(_opCtx.get());
ASSERT(database->createCollection(_opCtx.get(), _nss.ns()));
wuow.commit();
}
}
void tearDown() final {
AutoGetDb autoDb(_opCtx.get(), _nss.db(), MODE_X);
Database* database = autoDb.getDb();
if (database) {
WriteUnitOfWork wuow(_opCtx.get());
ASSERT_OK(database->dropCollection(_opCtx.get(), _nss.ns()));
wuow.commit();
}
}
Status createIndex(Collection* collection, BSONObj indexSpec) {
return dbtests::createIndexFromSpec(_opCtx.get(), collection->ns().ns(), indexSpec);
}
void assertMultikeyPaths(Collection* collection,
BSONObj keyPattern,
const MultikeyPaths& expectedMultikeyPaths) {
IndexCatalog* indexCatalog = collection->getIndexCatalog();
std::vector indexes;
indexCatalog->findIndexesByKeyPattern(_opCtx.get(), keyPattern, false, &indexes);
ASSERT_EQ(indexes.size(), 1U);
IndexDescriptor* desc = indexes[0];
const IndexCatalogEntry* ice = indexCatalog->getEntry(desc);
auto actualMultikeyPaths = ice->getMultikeyPaths(_opCtx.get());
if (storageEngineSupportsPathLevelMultikeyTracking()) {
ASSERT_FALSE(actualMultikeyPaths.empty());
const bool match = (expectedMultikeyPaths == actualMultikeyPaths);
if (!match) {
FAIL(str::stream() << "Expected: " << dumpMultikeyPaths(expectedMultikeyPaths)
<< ", Actual: "
<< dumpMultikeyPaths(actualMultikeyPaths));
}
ASSERT_TRUE(match);
} else {
ASSERT_TRUE(actualMultikeyPaths.empty());
}
}
protected:
const ServiceContext::UniqueOperationContext _opCtx = cc().makeOperationContext();
const NamespaceString _nss;
private:
bool storageEngineSupportsPathLevelMultikeyTracking() {
// Path-level multikey tracking is supported for all storage engines that use the KVCatalog.
// MMAPv1 is the only storage engine that does not.
//
// TODO SERVER-22727: Store path-level multikey information in MMAPv1 index catalog.
return !getGlobalServiceContext()->getGlobalStorageEngine()->isMmapV1();
}
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();
}
};
TEST_F(MultikeyPathsTest, PathsUpdatedOnIndexCreation) {
AutoGetCollection autoColl(_opCtx.get(), _nss, MODE_X);
Collection* collection = autoColl.getCollection();
invariant(collection);
{
WriteUnitOfWork wuow(_opCtx.get());
OpDebug* const nullOpDebug = nullptr;
const bool enforceQuota = true;
ASSERT_OK(collection->insertDocument(
_opCtx.get(),
InsertStatement(BSON("_id" << 0 << "a" << 5 << "b" << BSON_ARRAY(1 << 2 << 3))),
nullOpDebug,
enforceQuota));
wuow.commit();
}
BSONObj keyPattern = BSON("a" << 1 << "b" << 1);
createIndex(collection,
BSON("name"
<< "a_1_b_1"
<< "ns"
<< _nss.ns()
<< "key"
<< keyPattern
<< "v"
<< static_cast(kIndexVersion)))
.transitional_ignore();
assertMultikeyPaths(collection, keyPattern, {std::set{}, {0U}});
}
TEST_F(MultikeyPathsTest, PathsUpdatedOnIndexCreationWithMultipleDocuments) {
AutoGetCollection autoColl(_opCtx.get(), _nss, MODE_X);
Collection* collection = autoColl.getCollection();
invariant(collection);
{
WriteUnitOfWork wuow(_opCtx.get());
OpDebug* const nullOpDebug = nullptr;
const bool enforceQuota = true;
ASSERT_OK(collection->insertDocument(
_opCtx.get(),
InsertStatement(BSON("_id" << 0 << "a" << 5 << "b" << BSON_ARRAY(1 << 2 << 3))),
nullOpDebug,
enforceQuota));
ASSERT_OK(collection->insertDocument(
_opCtx.get(),
InsertStatement(BSON("_id" << 1 << "a" << BSON_ARRAY(1 << 2 << 3) << "b" << 5)),
nullOpDebug,
enforceQuota));
wuow.commit();
}
BSONObj keyPattern = BSON("a" << 1 << "b" << 1);
createIndex(collection,
BSON("name"
<< "a_1_b_1"
<< "ns"
<< _nss.ns()
<< "key"
<< keyPattern
<< "v"
<< static_cast(kIndexVersion)))
.transitional_ignore();
assertMultikeyPaths(collection, keyPattern, {{0U}, {0U}});
}
TEST_F(MultikeyPathsTest, PathsUpdatedOnDocumentInsert) {
AutoGetCollection autoColl(_opCtx.get(), _nss, MODE_X);
Collection* collection = autoColl.getCollection();
invariant(collection);
BSONObj keyPattern = BSON("a" << 1 << "b" << 1);
createIndex(collection,
BSON("name"
<< "a_1_b_1"
<< "ns"
<< _nss.ns()
<< "key"
<< keyPattern
<< "v"
<< static_cast(kIndexVersion)))
.transitional_ignore();
{
WriteUnitOfWork wuow(_opCtx.get());
OpDebug* const nullOpDebug = nullptr;
const bool enforceQuota = true;
ASSERT_OK(collection->insertDocument(
_opCtx.get(),
InsertStatement(BSON("_id" << 0 << "a" << 5 << "b" << BSON_ARRAY(1 << 2 << 3))),
nullOpDebug,
enforceQuota));
wuow.commit();
}
assertMultikeyPaths(collection, keyPattern, {std::set{}, {0U}});
{
WriteUnitOfWork wuow(_opCtx.get());
OpDebug* const nullOpDebug = nullptr;
const bool enforceQuota = true;
ASSERT_OK(collection->insertDocument(
_opCtx.get(),
InsertStatement(BSON("_id" << 1 << "a" << BSON_ARRAY(1 << 2 << 3) << "b" << 5)),
nullOpDebug,
enforceQuota));
wuow.commit();
}
assertMultikeyPaths(collection, keyPattern, {{0U}, {0U}});
}
TEST_F(MultikeyPathsTest, PathsUpdatedOnDocumentUpdate) {
AutoGetCollection autoColl(_opCtx.get(), _nss, MODE_X);
Collection* collection = autoColl.getCollection();
invariant(collection);
BSONObj keyPattern = BSON("a" << 1 << "b" << 1);
createIndex(collection,
BSON("name"
<< "a_1_b_1"
<< "ns"
<< _nss.ns()
<< "key"
<< keyPattern
<< "v"
<< static_cast(kIndexVersion)))
.transitional_ignore();
{
WriteUnitOfWork wuow(_opCtx.get());
OpDebug* const nullOpDebug = nullptr;
const bool enforceQuota = true;
ASSERT_OK(collection->insertDocument(_opCtx.get(),
InsertStatement(BSON("_id" << 0 << "a" << 5)),
nullOpDebug,
enforceQuota));
wuow.commit();
}
assertMultikeyPaths(collection, keyPattern, {std::set{}, std::set{}});
{
auto cursor = collection->getCursor(_opCtx.get());
auto record = cursor->next();
invariant(record);
auto oldDoc = collection->docFor(_opCtx.get(), record->id);
{
WriteUnitOfWork wuow(_opCtx.get());
const bool enforceQuota = true;
const bool indexesAffected = true;
OpDebug* opDebug = nullptr;
OplogUpdateEntryArgs args;
collection->updateDocument(
_opCtx.get(),
record->id,
oldDoc,
BSON("_id" << 0 << "a" << 5 << "b" << BSON_ARRAY(1 << 2 << 3)),
enforceQuota,
indexesAffected,
opDebug,
&args);
wuow.commit();
}
}
assertMultikeyPaths(collection, keyPattern, {std::set{}, {0U}});
}
TEST_F(MultikeyPathsTest, PathsNotUpdatedOnDocumentDelete) {
AutoGetCollection autoColl(_opCtx.get(), _nss, MODE_X);
Collection* collection = autoColl.getCollection();
invariant(collection);
BSONObj keyPattern = BSON("a" << 1 << "b" << 1);
createIndex(collection,
BSON("name"
<< "a_1_b_1"
<< "ns"
<< _nss.ns()
<< "key"
<< keyPattern
<< "v"
<< static_cast(kIndexVersion)))
.transitional_ignore();
{
WriteUnitOfWork wuow(_opCtx.get());
OpDebug* const nullOpDebug = nullptr;
const bool enforceQuota = true;
ASSERT_OK(collection->insertDocument(
_opCtx.get(),
InsertStatement(BSON("_id" << 0 << "a" << 5 << "b" << BSON_ARRAY(1 << 2 << 3))),
nullOpDebug,
enforceQuota));
wuow.commit();
}
assertMultikeyPaths(collection, keyPattern, {std::set{}, {0U}});
{
auto cursor = collection->getCursor(_opCtx.get());
auto record = cursor->next();
invariant(record);
{
WriteUnitOfWork wuow(_opCtx.get());
OpDebug* const nullOpDebug = nullptr;
collection->deleteDocument(_opCtx.get(), kUninitializedStmtId, record->id, nullOpDebug);
wuow.commit();
}
}
assertMultikeyPaths(collection, keyPattern, {std::set{}, {0U}});
}
TEST_F(MultikeyPathsTest, PathsUpdatedForMultipleIndexesOnDocumentInsert) {
AutoGetCollection autoColl(_opCtx.get(), _nss, MODE_X);
Collection* collection = autoColl.getCollection();
invariant(collection);
BSONObj keyPatternAB = BSON("a" << 1 << "b" << 1);
createIndex(collection,
BSON("name"
<< "a_1_b_1"
<< "ns"
<< _nss.ns()
<< "key"
<< keyPatternAB
<< "v"
<< static_cast(kIndexVersion)))
.transitional_ignore();
BSONObj keyPatternAC = BSON("a" << 1 << "c" << 1);
createIndex(collection,
BSON("name"
<< "a_1_c_1"
<< "ns"
<< _nss.ns()
<< "key"
<< keyPatternAC
<< "v"
<< static_cast(kIndexVersion)))
.transitional_ignore();
{
WriteUnitOfWork wuow(_opCtx.get());
OpDebug* const nullOpDebug = nullptr;
const bool enforceQuota = true;
ASSERT_OK(collection->insertDocument(
_opCtx.get(),
InsertStatement(
BSON("_id" << 0 << "a" << BSON_ARRAY(1 << 2 << 3) << "b" << 5 << "c" << 8)),
nullOpDebug,
enforceQuota));
wuow.commit();
}
assertMultikeyPaths(collection, keyPatternAB, {{0U}, std::set{}});
assertMultikeyPaths(collection, keyPatternAC, {{0U}, std::set{}});
}
} // namespace
} // namespace mongo