// 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;