// Tests seekExact on reverse cursor when it hits something with dup keys. Doesn't make sense
    // for unique indexes.
    TEST(SortedDataInterface, SeekExact_HitWithDups_Reverse) {
        auto harnessHelper = newHarnessHelper();
        auto opCtx = harnessHelper->newOperationContext();
        auto sorted = harnessHelper->newSortedDataInterface(false, {
            {key1, loc1},
            {key2, loc1},
            {key2, loc2},
            {key3, loc1},
        });

        auto cursor = sorted->newCursor(opCtx.get(), false);

        ASSERT_EQ(cursor->seekExact(key2), IndexKeyEntry(key2, loc2));
        ASSERT_EQ(cursor->next(), IndexKeyEntry(key2, loc1));
        ASSERT_EQ(cursor->next(), IndexKeyEntry(key1, loc1));
        ASSERT_EQ(cursor->next(), boost::none);
    }
    // Tests seekExact when it hits something.
    void testSeekExact_Hit(bool unique, bool forward) {
        auto harnessHelper = newHarnessHelper();
        auto opCtx = harnessHelper->newOperationContext();
        auto sorted = harnessHelper->newSortedDataInterface(unique, {
            {key1, loc1},
            {key2, loc1},
            {key3, loc1},
        });

        auto cursor = sorted->newCursor(opCtx.get(), forward);

        ASSERT_EQ(cursor->seekExact(key2), IndexKeyEntry(key2, loc1));

        // Make sure iterating works. We may consider loosening this requirement if it is a hardship
        // for some storage engines.
        ASSERT_EQ(cursor->next(), IndexKeyEntry(forward ? key3 : key1, loc1));
        ASSERT_EQ(cursor->next(), boost::none);
    }
    // Ensure that restore lands as close as possible to original position, even if data inserted
    // while saved.
    void testSaveAndRestorePositionSeesNewInserts(bool forward, bool unique) {
        auto harnessHelper = newHarnessHelper();
        auto opCtx = harnessHelper->newOperationContext();
        auto sorted = harnessHelper->newSortedDataInterface(unique, {
            {key1, loc1},
            {key3, loc1},
        });

        auto cursor = sorted->newCursor(opCtx.get(), forward);
        const auto seekPoint = forward ? key1 : key3;

        ASSERT_EQ(cursor->seek(seekPoint, true), IndexKeyEntry(seekPoint, loc1));

        cursor->savePositioned();
        insertToIndex(opCtx, sorted, {{key2, loc1}});
        cursor->restore(opCtx.get());

        ASSERT_EQ(cursor->next(), IndexKeyEntry(key2, loc1));
    }
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, 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}});
    }
}
    // Make sure we restore to a RecordId at or ahead of save point if same key on reverse cursor.
    void testSaveAndRestorePositionConsidersRecordId_Reverse(bool unique) {
        auto harnessHelper = newHarnessHelper();
        auto opCtx = harnessHelper->newOperationContext();
        auto sorted = harnessHelper->newSortedDataInterface(unique, {
            {key0, loc1},
            {key1, loc1},
            {key2, loc2},
        });

        auto cursor = sorted->newCursor(opCtx.get(), false);

        ASSERT_EQ(cursor->seek(key2, true), IndexKeyEntry(key2, loc2));

        cursor->savePositioned();
        removeFromIndex(opCtx, sorted, {{key2, loc2}});
        insertToIndex(opCtx, sorted, {{key2, loc1}});
        cursor->restore(opCtx.get());

        ASSERT_EQ(cursor->next(), IndexKeyEntry(key2, loc1));

        cursor->savePositioned();
        removeFromIndex(opCtx, sorted, {{key2, loc1}});
        insertToIndex(opCtx, sorted, {{key2, loc2}});
        cursor->restore(opCtx.get());

        ASSERT_EQ(cursor->next(), IndexKeyEntry(key1, loc1));

        cursor->savePositioned();
        removeFromIndex(opCtx, sorted, {{key1, loc1}});
        cursor->restore(opCtx.get());

        cursor->savePositioned();
        insertToIndex(opCtx, sorted, {{key1, loc1}});
        cursor->restore(opCtx.get()); // Lands at same point as initial save.

        // Advances from restore point since restore didn't move position.
        ASSERT_EQ(cursor->next(), IndexKeyEntry(key0, loc1));
    }
    // Ensure that SaveUnpositioned allows later use of the cursor.
    TEST(SortedDataInterface, SaveUnpositionedAndRestore) {
        auto harnessHelper = newHarnessHelper();
        auto opCtx = harnessHelper->newOperationContext();
        auto sorted = harnessHelper->newSortedDataInterface(false, {
            {key1, loc1},
            {key2, loc1},
            {key3, loc1},
        });

        auto cursor = sorted->newCursor(opCtx.get());

        ASSERT_EQ(cursor->seek(key2, true), IndexKeyEntry(key2, loc1));

        cursor->saveUnpositioned();
        removeFromIndex(opCtx, sorted, {{key2, loc1}});
        cursor->restore(opCtx.get());

        ASSERT_EQ(cursor->seek(key1, true), IndexKeyEntry(key1, loc1));

        cursor->saveUnpositioned();
        cursor->restore(opCtx.get());

        ASSERT_EQ(cursor->seek(key3, true), IndexKeyEntry(key3, loc1));
    }
    // Ensure that repeated restores lands as close as possible to original position, even if data
    // inserted while saved and the current position removed in a way that temporarily makes the
    // cursor EOF.
    void testSaveAndRestorePositionSeesNewInsertsAfterEOF(bool forward, bool unique) {
        auto harnessHelper = newHarnessHelper();
        auto opCtx = harnessHelper->newOperationContext();
        auto sorted = harnessHelper->newSortedDataInterface(false, {
            {key1, loc1},
        });

        auto cursor = sorted->newCursor(opCtx.get(), forward);

        ASSERT_EQ(cursor->seek(key1, true), IndexKeyEntry(key1, loc1));
        // next() would return EOF now.

        cursor->savePositioned();
        removeFromIndex(opCtx, sorted, {{key1, loc1}});
        cursor->restore(opCtx.get());
        // The restore may have seeked to EOF.

        auto insertPoint = forward ? key2 : key0;
        cursor->savePositioned(); // Should still save key1 as "current position".
        insertToIndex(opCtx, sorted, {{insertPoint, loc1}});
        cursor->restore(opCtx.get());

        ASSERT_EQ(cursor->next(), IndexKeyEntry(insertPoint, loc1));
    }
    // Ensure that repeated restores lands as close as possible to original position, even if data
    // inserted while saved and the current position removed.
    void testSaveAndRestorePositionSeesNewInsertsAfterRemove(bool forward, bool unique) {
        auto harnessHelper = newHarnessHelper();
        auto opCtx = harnessHelper->newOperationContext();
        auto sorted = harnessHelper->newSortedDataInterface(unique, {
            {key1, loc1},
            {key3, loc1},
        });

        auto cursor = sorted->newCursor(opCtx.get(), forward);
        const auto seekPoint = forward ? key1 : key3;

        ASSERT_EQ(cursor->seek(seekPoint, true), IndexKeyEntry(seekPoint, loc1));

        cursor->savePositioned();
        removeFromIndex(opCtx, sorted, {{key1, loc1}});
        cursor->restore(opCtx.get());
        // The restore may have seeked since it can't return to the saved position.

        cursor->savePositioned(); // Should still save originally saved key as "current position".
        insertToIndex(opCtx, sorted, {{key2, loc1}});
        cursor->restore(opCtx.get());

        ASSERT_EQ(cursor->next(), IndexKeyEntry(key2, loc1));
    }
    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,
             CannotOmitPathLevelMultikeyInfoWithBtreeIndex,
             "Invariant failure !multikeyPaths.empty()") {
    std::string indexName = createIndex(BSON("a" << 1 << "b" << 1));
    CollectionCatalogEntry* collEntry = getCollectionCatalogEntry();

    auto opCtx = newOperationContext();
    collEntry->setIndexIsMultikey(opCtx.get(), indexName, MultikeyPaths{});
}

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, PathLevelMultikeyTrackingIsSupportedBy2dsphereIndexes) {
    std::string indexType = IndexNames::GEO_2DSPHERE;