Status addKey(const BSONObj& key, const RecordId& loc) { // inserts should be in ascending (key, RecordId) order. if (key.objsize() >= TempKeyMaxSize) { return Status(ErrorCodes::KeyTooLong, "key too big"); } invariant(loc.isNormal()); invariant(!hasFieldNames(key)); if (!_data->empty()) { // Compare specified key with last inserted key, ignoring its RecordId int cmp = _comparator.compare(IndexKeyEntry(key, RecordId()), *_last); if (cmp < 0 || (_dupsAllowed && cmp == 0 && loc < _last->loc)) { return Status(ErrorCodes::InternalError, "expected ascending (key, RecordId) order in bulk builder"); } else if (!_dupsAllowed && cmp == 0 && loc != _last->loc) { return dupKeyError(key); } } BSONObj owned = key.getOwned(); _last = _data->insert(_data->end(), IndexKeyEntry(owned, loc)); *_currentKeySize += key.objsize(); return Status::OK(); }
void testSetEndPosition_Restore_Reverse(bool unique) { auto harnessHelper = newHarnessHelper(); auto opCtx = harnessHelper->newOperationContext(); auto sorted = harnessHelper->newSortedDataInterface( unique, { {key1, loc1}, {key2, loc1}, {key3, loc1}, {key4, loc1}, }); auto cursor = sorted->newCursor(opCtx.get(), false); cursor->setEndPosition(key2, false); // Should never see key1 or key2. ASSERT_EQ(cursor->seek(key4, true), IndexKeyEntry(key4, loc1)); cursor->save(); cursor->restore(); ASSERT_EQ(cursor->next(), IndexKeyEntry(key3, loc1)); cursor->save(); removeFromIndex(opCtx, sorted, { {key2, loc1}, {key3, loc1}, }); cursor->restore(); ASSERT_EQ(cursor->next(), boost::none); }
void testSetEndPosition_RestoreEndCursor_Reverse(bool unique) { auto harnessHelper = newHarnessHelper(); auto opCtx = harnessHelper->newOperationContext(); auto sorted = harnessHelper->newSortedDataInterface(unique, { {key1, loc1}, {key4, loc1}, }); auto cursor = sorted->newCursor(opCtx.get(), false); cursor->setEndPosition(key3, true); ASSERT_EQ(cursor->seek(key4, true), IndexKeyEntry(key4, loc1)); cursor->saveUnpositioned(); insertToIndex(opCtx, sorted, { {key2, loc1}, // in range {key3, loc1}, // out of range }); cursor->restore(); // must restore end cursor even with saveUnpositioned(). ASSERT_EQ(cursor->seek(key4, true), IndexKeyEntry(key4, loc1)); ASSERT_EQ(cursor->next(), IndexKeyEntry(key3, loc1)); ASSERT_EQ(cursor->next(), boost::none); }
void testSetEndPosition_Next_Reverse(bool unique, bool inclusive) { auto harnessHelper = newHarnessHelper(); auto opCtx = harnessHelper->newOperationContext(); auto sorted = harnessHelper->newSortedDataInterface( unique, { {key1, loc1}, {key2, loc1}, {key3, loc1}, {key4, loc1}, {key5, loc1}, }); // Dup key on end point. Illegal for unique indexes. if (!unique) insertToIndex(opCtx, sorted, {{key3, loc2}}); auto cursor = sorted->newCursor(opCtx.get(), false); cursor->setEndPosition(key3, inclusive); ASSERT_EQ(cursor->seek(key5, true), IndexKeyEntry(key5, loc1)); ASSERT_EQ(cursor->next(), IndexKeyEntry(key4, loc1)); if (inclusive) { if (!unique) ASSERT_EQ(cursor->next(), IndexKeyEntry(key3, loc2)); ASSERT_EQ(cursor->next(), IndexKeyEntry(key3, loc1)); } ASSERT_EQ(cursor->next(), boost::none); ASSERT_EQ(cursor->next(), boost::none); // don't resurrect. }
/** * Constructs an IndexKeyEntry from a slice containing the bytes of a BSONObject followed * by the bytes of a RecordId */ static IndexKeyEntry makeIndexKeyEntry(const WT_ITEM *keyCols) { const char* data = static_cast<const char*>( keyCols->data ); BSONObj key( data ); if ( keyCols->size == static_cast<size_t>( key.objsize() ) ) { // in unique mode return IndexKeyEntry( key, RecordId() ); } invariant( keyCols->size == key.objsize() + sizeof(RecordIdRepr) ); return IndexKeyEntry( key, getRecordIdAt(data + key.objsize())); }
/** * Constructs an IndexKeyEntry from a slice containing the bytes of a BSONObject followed * by the bytes of a RecordId */ static IndexKeyEntry makeIndexKeyEntry(const WT_ITEM *keyCols) { const char* data = reinterpret_cast<const char*>( keyCols->data ); BSONObj key( data ); if ( keyCols->size == static_cast<size_t>( key.objsize() ) ) { // in unique mode return IndexKeyEntry( key, RecordId() ); } invariant( keyCols->size == key.objsize() + sizeof(RecordId) ); RecordId loc = reinterpret_cast<const RecordId*>( data + key.objsize() )[0]; return IndexKeyEntry( key, loc ); }
Status addKey(const BSONObj& key, const DiskLoc& loc) { // inserts should be in ascending (key, DiskLoc) order. if ( key.objsize() >= TempKeyMaxSize ) { return Status(ErrorCodes::KeyTooLong, "key too big"); } invariant(!loc.isNull()); invariant(loc.isValid()); invariant(!hasFieldNames(key)); if (!_data->empty()) { if (key < _last->key || (_dupsAllowed && key == _last->key && loc < _last->loc)) { return Status(ErrorCodes::InternalError, "expected ascending (key, DiskLoc) order in bulk builder"); } else if (!_dupsAllowed && key == _last->key && loc != _last->loc) { return dupKeyError(key); } } BSONObj owned = key.getOwned(); _last = _data->insert(_data->end(), IndexKeyEntry(owned, loc)); *_currentKeySize += key.objsize(); return Status::OK(); }
virtual Status insert(OperationContext* txn, const BSONObj& key, const DiskLoc& loc, bool dupsAllowed) { invariant(!loc.isNull()); invariant(loc.isValid()); invariant(!hasFieldNames(key)); if ( key.objsize() >= TempKeyMaxSize ) { string msg = mongoutils::str::stream() << "Heap1Btree::insert: key too large to index, failing " << ' ' << key.objsize() << ' ' << key; return Status(ErrorCodes::KeyTooLong, msg); } // TODO optimization: save the iterator from the dup-check to speed up insert if (!dupsAllowed && isDup(*_data, key, loc)) return dupKeyError(key); BSONObj owned = key.getOwned(); if ( _data->insert(IndexKeyEntry(owned, loc)).second ) { _currentKeySize += key.objsize(); Heap1RecoveryUnit::notifyIndexInsert( txn, this, owned, loc ); } return Status::OK(); }
void testSetEndPosition_Seek_Reverse(bool unique, bool inclusive) { auto harnessHelper = newHarnessHelper(); auto opCtx = harnessHelper->newOperationContext(); auto sorted = harnessHelper->newSortedDataInterface(unique, { {key1, loc1}, {key2, loc1}, // No key3 {key4, loc1}, }); auto cursor = sorted->newCursor(opCtx.get(), false); cursor->setEndPosition(key2, inclusive); // Directly seeking past end is considered out of range. ASSERT_EQ(cursor->seek(key1, true), boost::none); ASSERT_EQ(cursor->seekExact(key1), boost::none); // Seeking to key2 directly or indirectly is only returned if endPosition is inclusive. auto maybeKey2 = inclusive ? boost::make_optional(IndexKeyEntry(key2, loc1)) : boost::none; // direct ASSERT_EQ(cursor->seek(key2, true), maybeKey2); ASSERT_EQ(cursor->seekExact(key2), maybeKey2); // indirect ASSERT_EQ(cursor->seek(key3, true), maybeKey2); cursor->saveUnpositioned(); removeFromIndex(opCtx, sorted, {{key2, loc1}}); cursor->restore(); ASSERT_EQ(cursor->seek(key3, true), boost::none); ASSERT_EQ(cursor->seek(key2, true), boost::none); }
void testSetEndPosition_Empty_Reverse(bool unique, bool inclusive) { auto harnessHelper = newHarnessHelper(); auto opCtx = harnessHelper->newOperationContext(); auto sorted = harnessHelper->newSortedDataInterface(unique, { {key1, loc1}, {key2, loc1}, {key3, loc1}, }); auto cursor = sorted->newCursor(opCtx.get(), false); cursor->setEndPosition(BSONObj(), inclusive); ASSERT_EQ(cursor->seek(key3, true), IndexKeyEntry(key3, loc1)); ASSERT_EQ(cursor->next(), IndexKeyEntry(key2, loc1)); ASSERT_EQ(cursor->next(), IndexKeyEntry(key1, loc1)); ASSERT_EQ(cursor->next(), boost::none); }
// 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); }
bool isDup(const IndexSet& data, const BSONObj& key, RecordId loc) { const IndexSet::const_iterator it = data.find(IndexKeyEntry(key, RecordId())); if (it == data.end()) return false; // Not a dup if the entry is for the same loc. return it->loc != loc; }
Status IndexAccessMethod::update(OperationContext* opCtx, const UpdateTicket& ticket, int64_t* numInserted, int64_t* numDeleted) { invariant(numInserted); invariant(numDeleted); *numInserted = 0; *numDeleted = 0; if (!ticket._isValid) { return Status(ErrorCodes::InternalError, "Invalid UpdateTicket in update"); } if (ticket.oldKeys.size() + ticket.added.size() - ticket.removed.size() > 1 || isMultikeyFromPaths(ticket.newMultikeyPaths)) { _btreeState->setMultikey(opCtx, ticket.newMultikeyPaths); } for (size_t i = 0; i < ticket.removed.size(); ++i) { _newInterface->unindex(opCtx, ticket.removed[i], ticket.loc, ticket.dupsAllowed); IndexKeyEntry indexEntry = IndexKeyEntry(ticket.removed[i], ticket.loc); } for (size_t i = 0; i < ticket.added.size(); ++i) { Status status = _newInterface->insert(opCtx, ticket.added[i], ticket.loc, ticket.dupsAllowed); if (!status.isOK()) { if (status.code() == ErrorCodes::KeyTooLong && ignoreKeyTooLong(opCtx)) { // Ignore. IndexKeyEntry indexEntry = IndexKeyEntry(ticket.added[i], ticket.loc); continue; } return status; } IndexKeyEntry indexEntry = IndexKeyEntry(ticket.added[i], ticket.loc); } *numInserted = ticket.added.size(); *numDeleted = ticket.removed.size(); return Status::OK(); }
// 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)); }
bool isDup(const IndexSet& data, const BSONObj& key) { IndexSet::const_iterator it = data.find(IndexKeyEntry(key, RecordId())); if (it == data.end()) return false; ++it; if (it == data.end()) return false; return it->key.woCompare(key, BSONObj(), false) == 0; }
virtual bool locate(const BSONObj& keyRaw, const DiskLoc& loc) { const BSONObj key = stripFieldNames(keyRaw); _it = _data.lower_bound(IndexKeyEntry(key, loc)); // lower_bound is >= key if ( _it == _data.end() ) { return false; } if ( _it->key != key ) { return false; } return _it->loc == loc; }
// 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)); }
StatusWith<SpecialFormatInserted> addKey(const BSONObj& key, const RecordId& loc) { // inserts should be in ascending (key, RecordId) order. invariant(loc.isValid()); invariant(!hasFieldNames(key)); if (!_data->empty()) { // Compare specified key with last inserted key, ignoring its RecordId int cmp = _comparator.compare(IndexKeyEntry(key, RecordId()), *_last); if (cmp < 0 || (_dupsAllowed && cmp == 0 && loc < _last->loc)) { return Status(ErrorCodes::InternalError, "expected ascending (key, RecordId) order in bulk builder"); } else if (!_dupsAllowed && cmp == 0 && loc != _last->loc) { return buildDupKeyErrorStatus(key, _collectionNamespace, _indexName, _keyPattern); } } BSONObj owned = key.getOwned(); _last = _data->insert(_data->end(), IndexKeyEntry(owned, loc)); *_currentKeySize += key.objsize(); return StatusWith<SpecialFormatInserted>(SpecialFormatInserted::NoSpecialFormatInserted); }
virtual bool unindex(OperationContext* txn, const BSONObj& key, const DiskLoc& loc) { invariant(!loc.isNull()); invariant(loc.isValid()); invariant(!hasFieldNames(key)); const size_t numDeleted = _data->erase(IndexKeyEntry(key, loc)); invariant(numDeleted <= 1); if ( numDeleted == 1 ) { _currentKeySize -= key.objsize(); Heap1RecoveryUnit::notifyIndexRemove( txn, this, key, loc ); } return numDeleted == 1; }
// 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)); }
virtual void customLocate(const BSONObj& keyBegin, int keyBeginLen, bool afterKey, const vector<const BSONElement*>& keyEnd, const vector<bool>& keyEndInclusive) { // makeQueryObject handles stripping of fieldnames for us. _it = lower_bound(IndexKeyEntry(IndexEntryComparison::makeQueryObject( keyBegin, keyBeginLen, afterKey, keyEnd, keyEndInclusive, -1), // reverse DiskLoc())); }
void IndexAccessMethod::removeOneKey(OperationContext* opCtx, const BSONObj& key, const RecordId& loc, bool dupsAllowed) { try { _newInterface->unindex(opCtx, key, loc, dupsAllowed); IndexKeyEntry indexEntry = IndexKeyEntry(key, loc); } catch (AssertionException& e) { log() << "Assertion failure: _unindex failed " << _descriptor->indexNamespace(); log() << "Assertion failure: _unindex failed: " << redact(e) << " key:" << key.toString() << " dl:" << loc; logContext(); } }
void locate(const BSONObj& key, const RecordId& loc) { _isEOF = false; const auto query = IndexKeyEntry(key, loc); _it = _data.lower_bound(query); if (_forward) { if (_it == _data.end()) _isEOF = true; } else { // lower_bound lands us on or after query. Reverse cursors must be on or before. if (_it == _data.end() || _data.value_comp().compare(*_it, query) > 0) advance(); // sets _isEOF if there is nothing more to return. } if (atOrPastEndPointAfterSeeking()) _isEOF = true; }
// Tests seekExact when it doesn't hit the query. void testSeekExact_Miss(bool unique, bool forward) { auto harnessHelper = newHarnessHelper(); auto opCtx = harnessHelper->newOperationContext(); auto sorted = harnessHelper->newSortedDataInterface(unique, { {key1, loc1}, // No key2. {key3, loc1}, }); auto cursor = sorted->newCursor(opCtx.get(), forward); ASSERT_EQ(cursor->seekExact(key2), boost::none); // Not testing iteration since the cursors position following a failed seekExact is // undefined. However, you must be able to seek somewhere else. ASSERT_EQ(cursor->seekExact(key1), IndexKeyEntry(key1, loc1)); }
// Insert multiple keys and try to iterate through all of them // using a reverse cursor while calling savePosition() and // restorePosition() in succession. TEST( SortedDataInterface, SaveAndRestorePositionWhileIterateCursorReversed ) { const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT( sorted->isEmpty( opCtx.get() ) ); } int nToInsert = 10; for ( int i = 0; i < nToInsert; i++ ) { const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); BSONObj key = BSON( "" << i ); RecordId loc( 42, i * 2 ); ASSERT_OK( sorted->insert( opCtx.get(), key, loc, true ) ); uow.commit(); } } { const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( nToInsert, sorted->numEntries( opCtx.get() ) ); } { const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get(), false) ); int i = nToInsert - 1; for (auto entry = cursor->seek(maxKey, true); entry; i--, entry = cursor->next()) { ASSERT_GTE(i, 0); ASSERT_EQ(entry, IndexKeyEntry(BSON( "" << i), RecordId(42, i * 2))); cursor->savePositioned(); cursor->restore( opCtx.get() ); } ASSERT( !cursor->next() ); ASSERT_EQ(i, -1); } }
// Insert the same key multiple times and try to iterate through each // occurrence using a forward cursor while calling savePosition() and // restorePosition() in succession. Verify that the RecordId is saved // as part of the current position of the cursor. TEST( SortedDataInterface, SaveAndRestorePositionWhileIterateCursorWithDupKeys ) { const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT( sorted->isEmpty( opCtx.get() ) ); } int nToInsert = 10; for ( int i = 0; i < nToInsert; i++ ) { const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); RecordId loc( 42, i * 2 ); ASSERT_OK( sorted->insert( opCtx.get(), key1, loc, true /* allow duplicates */ ) ); uow.commit(); } } { const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( nToInsert, sorted->numEntries( opCtx.get() ) ); } { const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get()) ); int i = 0; for (auto entry = cursor->seek(minKey, true); entry; i++, entry = cursor->next()) { ASSERT_LT(i, nToInsert); ASSERT_EQ(entry, IndexKeyEntry(key1, RecordId(42, i * 2))); cursor->savePositioned(); cursor->restore( opCtx.get() ); } ASSERT( !cursor->next() ); ASSERT_EQ(i, nToInsert); } }
Status addKey(const BSONObj& key, const DiskLoc& loc) { // inserts should be in ascending order. if ( key.objsize() >= TempKeyMaxSize ) { return Status(ErrorCodes::KeyTooLong, "key too big"); } invariant(!loc.isNull()); invariant(loc.isValid()); invariant(!hasFieldNames(key)); // TODO optimization: dup check can assume dup is only possible with last inserted key // and avoid the log(n) lookup. if (!_dupsAllowed && isDup(*_data, key, loc)) return dupKeyError(key); BSONObj owned = key.getOwned(); _data->insert(_data->end(), IndexKeyEntry(owned, loc)); *_currentKeySize += key.objsize(); return Status::OK(); }