diff options
author | Mathias Stearn <mathias@10gen.com> | 2015-03-25 12:25:04 -0400 |
---|---|---|
committer | Mathias Stearn <mathias@10gen.com> | 2015-04-09 11:35:56 -0400 |
commit | db59e0f31b12966f127bf39df5674e3239c57350 (patch) | |
tree | 70699492e09cb72c3d7a0654e74107e512a990ad /src | |
parent | 8b31673665dd2a9d38e4b65ea880fc49acc0bd81 (diff) | |
download | mongo-db59e0f31b12966f127bf39df5674e3239c57350.tar.gz |
SERVER-17635 Improve SortedDataInterface::Cursor API
Major changes:
* Implementation now responsible for simple end point checking.
* No way to ask for current position. Relocating methods now return position.
* Simplified seeking methods so they have clear uses.
* Callers can use saveUnpositioned to indicate they don't care about position.
Diffstat (limited to 'src')
40 files changed, 2026 insertions, 3887 deletions
diff --git a/src/mongo/SConscript b/src/mongo/SConscript index 40bfc918512..5698fbceab4 100644 --- a/src/mongo/SConscript +++ b/src/mongo/SConscript @@ -791,7 +791,6 @@ serverOnlyFiles = [ "db/background.cpp", "db/index/hash_access_method.cpp", "db/index/haystack_access_method.cpp", "db/index/index_access_method.cpp", - "db/index/index_cursor.cpp", "db/index/s2_access_method.cpp", "db/index_builder.cpp", "db/index_legacy.cpp", diff --git a/src/mongo/db/exec/count_scan.cpp b/src/mongo/db/exec/count_scan.cpp index 13a1652f54b..5ab657704f3 100644 --- a/src/mongo/db/exec/count_scan.cpp +++ b/src/mongo/db/exec/count_scan.cpp @@ -30,7 +30,6 @@ #include "mongo/db/concurrency/write_conflict_exception.h" #include "mongo/db/exec/scoped_timer.h" -#include "mongo/db/index/index_cursor.h" #include "mongo/db/index/index_descriptor.h" namespace mongo { @@ -48,109 +47,66 @@ namespace mongo { _workingSet(workingSet), _descriptor(params.descriptor), _iam(params.descriptor->getIndexCatalog()->getIndex(params.descriptor)), - _params(params), - _hitEnd(false), _shouldDedup(params.descriptor->isMultikey(txn)), + _params(params), _commonStats(kStageType) { _specificStats.keyPattern = _params.descriptor->keyPattern(); _specificStats.indexName = _params.descriptor->indexName(); _specificStats.isMultiKey = _params.descriptor->isMultikey(txn); _specificStats.indexVersion = _params.descriptor->version(); - } - - void CountScan::initIndexCursor() { - CursorOptions cursorOptions; - cursorOptions.direction = CursorOptions::INCREASING; - - IndexCursor *cursor; - Status s = _iam->newCursor(_txn, cursorOptions, &cursor); - verify(s.isOK()); - verify(cursor); - _cursor.reset(cursor); - - // _cursor points at our start position. We move it forward until it hits a cursor - // that points at the end. - _cursor->seek(_params.startKey, !_params.startKeyInclusive); - - ++_specificStats.keysExamined; - - // Create the cursor that points at our end position. - IndexCursor* endCursor; - verify(_iam->newCursor(_txn, cursorOptions, &endCursor).isOK()); - verify(endCursor); - _endCursor.reset(endCursor); - // If the end key is inclusive we want to point *past* it since that's the end. - _endCursor->seek(_params.endKey, _params.endKeyInclusive); - - ++_specificStats.keysExamined; - - // See if we've hit the end already. - checkEnd(); + // endKey must be after startKey in index order since we only do forward scans. + dassert(_params.startKey.woCompare(_params.endKey, + Ordering::make(params.descriptor->keyPattern()), + /*compareFieldNames*/false) <= 0); } - void CountScan::checkEnd() { - if (isEOF()) { return; } - - if (_endCursor->isEOF()) { - // If the endCursor is EOF we're only done when our 'current count position' hits EOF. - _hitEnd = _cursor->isEOF(); - } - else { - // If not, we're only done when we hit the end cursor's (valid) position. - _hitEnd = _cursor->pointsAt(*_endCursor.get()); - } - } PlanStage::StageState CountScan::work(WorkingSetID* out) { ++_commonStats.works; + if (_commonStats.isEOF) return PlanStage::IS_EOF; // Adds the amount of time taken by work() to executionTimeMillis. ScopedTimer timer(&_commonStats.executionTimeMillis); - if (NULL == _cursor.get()) { - // First call to work(). Perform cursor init. - try { - initIndexCursor(); - checkEnd(); - } - catch (const WriteConflictException& wce) { - // Release our owned cursors and try again next time. - _cursor.reset(); - _endCursor.reset(); - *out = WorkingSet::INVALID_ID; - return PlanStage::NEED_YIELD; - } - ++_commonStats.needTime; - return PlanStage::NEED_TIME; - } - - if (isEOF()) { return PlanStage::IS_EOF; } + boost::optional<IndexKeyEntry> entry; + const bool needInit = !_cursor; + try { + // We don't care about the keys. + const auto kWantLoc = SortedDataInterface::Cursor::kWantLoc; - RecordId loc = _cursor->getValue(); + if (needInit) { + // First call to work(). Perform cursor init. + _cursor = _iam->newCursor(_txn); + _cursor->setEndPosition(_params.endKey, _params.endKeyInclusive); - try { - _cursor->next(); + entry = _cursor->seek(_params.startKey, _params.startKeyInclusive, kWantLoc); + } + else { + entry = _cursor->next(kWantLoc); + } } catch (const WriteConflictException& wce) { - // The cursor shouldn't have moved. - invariant(_cursor->getValue() == loc); + if (needInit) { + // Release our cursor and try again next time. + _cursor.reset(); + } *out = WorkingSet::INVALID_ID; return PlanStage::NEED_YIELD; } - checkEnd(); - ++_specificStats.keysExamined; - if (_shouldDedup) { - if (_returned.end() != _returned.find(loc)) { - ++_commonStats.needTime; - return PlanStage::NEED_TIME; - } - else { - _returned.insert(loc); - } + if (!entry) { + _commonStats.isEOF = true; + _cursor.reset(); + return PlanStage::IS_EOF; + } + + if (_shouldDedup && !_returned.insert(entry->loc).second) { + // *loc was already in _returned. + ++_commonStats.needTime; + return PlanStage::NEED_TIME; } *out = WorkingSet::INVALID_ID; @@ -159,68 +115,25 @@ namespace mongo { } bool CountScan::isEOF() { - if (NULL == _cursor.get()) { - // Have to call work() at least once. - return false; - } - - return _hitEnd || _cursor->isEOF(); + return _commonStats.isEOF; } void CountScan::saveState() { _txn = NULL; ++_commonStats.yields; - if (_hitEnd || (NULL == _cursor.get())) { return; } - - _cursor->savePosition(); - _endCursor->savePosition(); + if (_cursor) _cursor->savePositioned(); } void CountScan::restoreState(OperationContext* opCtx) { invariant(_txn == NULL); _txn = opCtx; ++_commonStats.unyields; - if (_hitEnd || (NULL == _cursor.get())) { return; } - - if (!_cursor->restorePosition( opCtx ).isOK()) { - _hitEnd = true; - return; - } - - if (_cursor->isEOF()) { - _hitEnd = true; - return; - } - - // See if we're somehow already past our end key (maybe the thing we were pointing at got - // deleted...) - int cmp = _cursor->getKey().woCompare(_params.endKey, _descriptor->keyPattern(), false); - if (cmp > 0 || (cmp == 0 && !_params.endKeyInclusive)) { - _hitEnd = true; - return; - } - - if (!_endCursor->restorePosition( opCtx ).isOK()) { - _hitEnd = true; - return; - } - - // If we were EOF when we yielded we don't always want to have _cursor run until - // EOF. New documents may have been inserted after our endKey and our end marker - // may be before them. - // - // As an example, say we're counting from 5 to 10 and the index only has keys - // for 6, 7, 8, and 9. btreeCursor will point at a 6 key at the start and the - // endCursor will be EOF. If we insert documents with keys 11 during a yield we - // need to relocate the endCursor to point at them as the "end key" of our count. - // - // If we weren't EOF our end position might have moved around. Relocate it. - _endCursor->seek(_params.endKey, _params.endKeyInclusive); + + if (_cursor) _cursor->restore(opCtx); // This can change during yielding. + // TODO this isn't sufficient. See SERVER-17678. _shouldDedup = _descriptor->isMultikey(_txn); - - checkEnd(); } void CountScan::invalidate(OperationContext* txn, const RecordId& dl, InvalidationType type) { @@ -246,7 +159,6 @@ namespace mongo { } PlanStageStats* CountScan::getStats() { - _commonStats.isEOF = isEOF(); auto_ptr<PlanStageStats> ret(new PlanStageStats(_commonStats, STAGE_COUNT_SCAN)); CountScanStats* countStats = new CountScanStats(_specificStats); diff --git a/src/mongo/db/exec/count_scan.h b/src/mongo/db/exec/count_scan.h index 54bdaf8db2f..e1b3966a22e 100644 --- a/src/mongo/db/exec/count_scan.h +++ b/src/mongo/db/exec/count_scan.h @@ -89,16 +89,6 @@ namespace mongo { static const char* kStageType; private: - /** - * Initialize the underlying IndexCursor - */ - void initIndexCursor(); - - /** - * See if we've hit the end yet. - */ - void checkEnd(); - // transactional context for read locks. Not owned by us OperationContext* _txn; @@ -109,21 +99,14 @@ namespace mongo { const IndexDescriptor* _descriptor; const IndexAccessMethod* _iam; - // Our start cursor. - boost::scoped_ptr<IndexCursor> _cursor; - - // Our end marker. - boost::scoped_ptr<IndexCursor> _endCursor; + std::unique_ptr<SortedDataInterface::Cursor> _cursor; // Could our index have duplicates? If so, we use _returned to dedup. + bool _shouldDedup; unordered_set<RecordId, RecordId::Hasher> _returned; CountScanParams _params; - bool _hitEnd; - - bool _shouldDedup; - CommonStats _commonStats; CountScanStats _specificStats; }; diff --git a/src/mongo/db/exec/distinct_scan.cpp b/src/mongo/db/exec/distinct_scan.cpp index db3eae5dceb..9a38f7d675a 100644 --- a/src/mongo/db/exec/distinct_scan.cpp +++ b/src/mongo/db/exec/distinct_scan.cpp @@ -28,11 +28,11 @@ #include "mongo/db/exec/distinct_scan.h" +#include "mongo/db/concurrency/write_conflict_exception.h" #include "mongo/db/exec/filter.h" #include "mongo/db/exec/scoped_timer.h" #include "mongo/db/exec/working_set_computed_data.h" #include "mongo/db/index/index_access_method.h" -#include "mongo/db/index/index_cursor.h" #include "mongo/db/index/index_descriptor.h" namespace mongo { @@ -48,142 +48,87 @@ namespace mongo { _workingSet(workingSet), _descriptor(params.descriptor), _iam(params.descriptor->getIndexCatalog()->getIndex(params.descriptor)), - _scanState(INITIALIZING), _params(params), + _checker(&_params.bounds, _descriptor->keyPattern(), _params.direction), _commonStats(kStageType) { + _specificStats.keyPattern = _params.descriptor->keyPattern(); _specificStats.indexName = _params.descriptor->indexName(); _specificStats.indexVersion = _params.descriptor->version(); - } - - void DistinctScan::initIndexCursor() { - // This function transitions from the initializing state to CHECKING_END. If - // the initialization fails, however, then the state transitions to HIT_END. - invariant(INITIALIZING == _scanState); - - // Create an IndexCursor over the btree we're distinct-ing over. - CursorOptions cursorOptions; - - if (1 == _params.direction) { - cursorOptions.direction = CursorOptions::INCREASING; - } - else { - cursorOptions.direction = CursorOptions::DECREASING; - } - - IndexCursor *cursor; - Status s = _iam->newCursor(_txn, cursorOptions, &cursor); - verify(s.isOK()); - verify(cursor); - _cursor.reset(cursor); - - // Create a new bounds checker. The bounds checker gets our start key and assists in - // executing the scan and staying within the required bounds. - _checker.reset(new IndexBoundsChecker(&_params.bounds, - _descriptor->keyPattern(), - _params.direction)); - - int nFields = _descriptor->keyPattern().nFields(); - // The start key is dumped into these two. - vector<const BSONElement*> key; - vector<bool> inc; - key.resize(nFields); - inc.resize(nFields); - if (_checker->getStartKey(&key, &inc)) { - _cursor->seek(key, inc); - _keyElts.resize(nFields); - _keyEltsInc.resize(nFields); - } - else { - _scanState = HIT_END; - } - // This method may throw an exception while it's doing initialization. If we've gotten - // here, then we've done all the initialization without an exception being thrown. This - // means it is safe to transition to the CHECKING_END state. In error cases, we transition - // to HIT_END, so we should not change state again here. - if (HIT_END != _scanState) { - _scanState = CHECKING_END; - } + // Set up our initial seek. If there is no valid data, just mark as EOF. + _commonStats.isEOF = !_checker.getStartSeekPoint(&_seekPoint); } PlanStage::StageState DistinctScan::work(WorkingSetID* out) { ++_commonStats.works; + if (_commonStats.isEOF) return PlanStage::IS_EOF; // Adds the amount of time taken by work() to executionTimeMillis. ScopedTimer timer(&_commonStats.executionTimeMillis); - if (INITIALIZING == _scanState) { - invariant(NULL == _cursor.get()); - initIndexCursor(); + boost::optional<IndexKeyEntry> kv; + try { + if (!_cursor) _cursor = _iam->newCursor(_txn, _params.direction == 1); + kv = _cursor->seek(_seekPoint); } - - if (CHECKING_END == _scanState) { - checkEnd(); + catch (const WriteConflictException& wce) { + *out = WorkingSet::INVALID_ID; + return PlanStage::NEED_YIELD; } - - if (isEOF()) { + + if (!kv) { _commonStats.isEOF = true; return PlanStage::IS_EOF; } - if (GETTING_NEXT == _scanState) { - // Grab the next (key, value) from the index. - BSONObj ownedKeyObj = _cursor->getKey().getOwned(); - RecordId loc = _cursor->getValue(); - - // The underlying IndexCursor points at the *next* thing we want to return. We do this - // so that if we're scanning an index looking for docs to delete we don't continually - // clobber the thing we're pointing at. + ++_specificStats.keysExamined; - // We skip to the next value of the _params.fieldNo-th field in the index key pattern. - // This is the field we're distinct-ing over. - _cursor->skip(_cursor->getKey(), - _params.fieldNo + 1, - true, - _keyElts, - _keyEltsInc); + switch (_checker.checkKey(kv->key, &_seekPoint)) { + case IndexBoundsChecker::MUST_ADVANCE: + // Try again next time. The checker has adjusted the _seekPoint. + ++_commonStats.needTime; + return PlanStage::NEED_TIME; - // On the next call to work, make sure that the cursor is still within the bounds. - _scanState = CHECKING_END; + case IndexBoundsChecker::DONE: + // There won't be a next time. + _commonStats.isEOF = true; + _cursor.reset(); + return IS_EOF; + + case IndexBoundsChecker::VALID: + // Return this key. Adjust the _seekPoint so that it is exclusive on the field we + // are using. + + if (!kv->key.isOwned()) kv->key = kv->key.getOwned(); + _seekPoint.keyPrefix = kv->key; + _seekPoint.prefixLen = _params.fieldNo + 1; + _seekPoint.prefixExclusive = true; // Package up the result for the caller. WorkingSetID id = _workingSet->allocate(); WorkingSetMember* member = _workingSet->get(id); - member->loc = loc; - member->keyData.push_back(IndexKeyDatum(_descriptor->keyPattern(), ownedKeyObj, _iam)); + member->loc = kv->loc; + member->keyData.push_back(IndexKeyDatum(_descriptor->keyPattern(), kv->key, _iam)); member->state = WorkingSetMember::LOC_AND_IDX; *out = id; ++_commonStats.advanced; return PlanStage::ADVANCED; } - - ++_commonStats.needTime; - return PlanStage::NEED_TIME; + invariant(false); } bool DistinctScan::isEOF() { - if (INITIALIZING == _scanState) { - // Have to call work() at least once. - return false; - } - - return HIT_END == _scanState || _cursor->isEOF(); + return _commonStats.isEOF; } void DistinctScan::saveState() { _txn = NULL; ++_commonStats.yields; - if (HIT_END == _scanState || INITIALIZING == _scanState) { return; } - // We save these so that we know if the cursor moves during the yield. If it moves, we have - // to make sure its ending position is valid w.r.t. our bounds. - if (!_cursor->isEOF()) { - _savedKey = _cursor->getKey().getOwned(); - _savedLoc = _cursor->getValue(); - } - _cursor->savePosition(); + // We always seek, so we don't care where the cursor is. + if (_cursor) _cursor->saveUnpositioned(); } void DistinctScan::restoreState(OperationContext* opCtx) { @@ -191,69 +136,19 @@ namespace mongo { _txn = opCtx; ++_commonStats.unyields; - if (HIT_END == _scanState || INITIALIZING == _scanState) { return; } - - // We can have a valid position before we check isEOF(), restore the position, and then be - // EOF upon restore. - if (!_cursor->restorePosition( opCtx ).isOK() || _cursor->isEOF()) { - _scanState = HIT_END; - return; - } - - if (!_savedKey.binaryEqual(_cursor->getKey()) || _savedLoc != _cursor->getValue()) { - // Our restored position might be past endKey, see if we've hit the end. - _scanState = CHECKING_END; - } + if (_cursor) _cursor->restore(opCtx); } void DistinctScan::invalidate(OperationContext* txn, const RecordId& dl, InvalidationType type) { ++_commonStats.invalidates; } - void DistinctScan::checkEnd() { - if (isEOF()) { - _commonStats.isEOF = true; - return; - } - - // Use _checker to see how things are. - IndexBoundsChecker::KeyState keyState; - keyState = _checker->checkKey(_cursor->getKey(), - &_keyEltsToUse, - &_movePastKeyElts, - &_keyElts, - &_keyEltsInc); - - if (IndexBoundsChecker::DONE == keyState) { - _scanState = HIT_END; - return; - } - - // This seems weird but it's the old definition of nscanned. - ++_specificStats.keysExamined; - - if (IndexBoundsChecker::VALID == keyState) { - _scanState = GETTING_NEXT; - return; - } - - verify(IndexBoundsChecker::MUST_ADVANCE == keyState); - _cursor->skip(_cursor->getKey(), _keyEltsToUse, _movePastKeyElts, - _keyElts, _keyEltsInc); - - // Must check underlying cursor EOF after every cursor movement. - if (_cursor->isEOF()) { - _scanState = HIT_END; - } - } - vector<PlanStage*> DistinctScan::getChildren() const { vector<PlanStage*> empty; return empty; } PlanStageStats* DistinctScan::getStats() { - _commonStats.isEOF = isEOF(); auto_ptr<PlanStageStats> ret(new PlanStageStats(_commonStats, STAGE_DISTINCT_SCAN)); ret->specific.reset(new DistinctScanStats(_specificStats)); return ret.release(); diff --git a/src/mongo/db/exec/distinct_scan.h b/src/mongo/db/exec/distinct_scan.h index 5d42c338ac7..bfc18104cea 100644 --- a/src/mongo/db/exec/distinct_scan.h +++ b/src/mongo/db/exec/distinct_scan.h @@ -41,7 +41,6 @@ namespace mongo { class IndexAccessMethod; - class IndexCursor; class IndexDescriptor; class WorkingSet; @@ -78,24 +77,6 @@ namespace mongo { */ class DistinctScan : public PlanStage { public: - /** - * Keeps track of what this distinct scan is currently doing so that it - * can do the right thing on the next call to work(). - */ - enum ScanState { - // Need to initialize the underlying index traversal machinery. - INITIALIZING, - - // Skipping keys in order to check whether we have reached the end. - CHECKING_END, - - // Retrieving the next key, and applying the filter if necessary. - GETTING_NEXT, - - // The index scan is finished. - HIT_END - }; - DistinctScan(OperationContext* txn, const DistinctParams& params, WorkingSet* workingSet); virtual ~DistinctScan() { } @@ -118,14 +99,6 @@ namespace mongo { static const char* kStageType; private: - /** - * Initialize the underlying IndexCursor - */ - void initIndexCursor(); - - /** See if the cursor is pointing at or past _endKey, if _endKey is non-empty. */ - void checkEnd(); - // transactional context for read locks. Not owned by us OperationContext* _txn; @@ -137,23 +110,13 @@ namespace mongo { const IndexAccessMethod* _iam; // owned by Collection -> IndexCatalog // The cursor we use to navigate the tree. - boost::scoped_ptr<IndexCursor> _cursor; - - // Keeps track of what work we need to do next. - ScanState _scanState; - - // For yielding. - BSONObj _savedKey; - RecordId _savedLoc; + std::unique_ptr<SortedDataInterface::Cursor> _cursor; DistinctParams _params; // _checker gives us our start key and ensures we stay in bounds. - boost::scoped_ptr<IndexBoundsChecker> _checker; - int _keyEltsToUse; - bool _movePastKeyElts; - std::vector<const BSONElement*> _keyElts; - std::vector<bool> _keyEltsInc; + IndexBoundsChecker _checker; + IndexSeekPoint _seekPoint; // Stats CommonStats _commonStats; diff --git a/src/mongo/db/exec/index_scan.cpp b/src/mongo/db/exec/index_scan.cpp index 8df75d2bdda..e8f59c07496 100644 --- a/src/mongo/db/exec/index_scan.cpp +++ b/src/mongo/db/exec/index_scan.cpp @@ -37,7 +37,6 @@ #include "mongo/db/exec/scoped_timer.h" #include "mongo/db/exec/working_set_computed_data.h" #include "mongo/db/index/index_access_method.h" -#include "mongo/db/index/index_cursor.h" #include "mongo/db/index/index_descriptor.h" #include "mongo/db/query/index_bounds_builder.h" #include "mongo/util/log.h" @@ -55,9 +54,6 @@ namespace { namespace mongo { - using std::auto_ptr; - using std::vector; - // static const char* IndexScan::kStageType = "IXSCAN"; @@ -67,16 +63,15 @@ namespace mongo { const MatchExpression* filter) : _txn(txn), _workingSet(workingSet), + _iam(params.descriptor->getIndexCatalog()->getIndex(params.descriptor)), + _keyPattern(params.descriptor->keyPattern().getOwned()), _scanState(INITIALIZING), _filter(filter), _shouldDedup(true), + _forward(params.direction == 1), _params(params), _commonStats(kStageType), - _keyEltsToUse(0), - _movePastKeyElts(false), _endKeyInclusive(false) { - _iam = _params.descriptor->getIndexCatalog()->getIndex(_params.descriptor); - _keyPattern = _params.descriptor->keyPattern().getOwned(); // We can't always access the descriptor in the call to getStats() so we pull // any info we need for stats reporting out here. @@ -86,44 +81,24 @@ namespace mongo { _specificStats.indexVersion = _params.descriptor->version(); } - void IndexScan::initIndexScan() { - // This function transitions from the initializing state to CHECKING_END. If - // the initialization fails, however, then the state transitions to HIT_END. - invariant(INITIALIZING == _scanState); - - // Perform the possibly heavy-duty initialization of the underlying index cursor. + boost::optional<IndexKeyEntry> IndexScan::initIndexScan() { if (_params.doNotDedup) { _shouldDedup = false; } else { + // TODO it is incorrect to rely on this not changing. SERVER-17678 _shouldDedup = _params.descriptor->isMultikey(_txn); } - // Set up the index cursor. - CursorOptions cursorOptions; - - if (1 == _params.direction) { - cursorOptions.direction = CursorOptions::INCREASING; - } - else { - cursorOptions.direction = CursorOptions::DECREASING; - } - - IndexCursor *cursor; - Status s = _iam->newCursor(_txn, cursorOptions, &cursor); - verify(s.isOK()); - _indexCursor.reset(cursor); + // Perform the possibly heavy-duty initialization of the underlying index cursor. + _indexCursor = _iam->newCursor(_txn, _forward); if (_params.bounds.isSimpleRange) { // Start at one key, end at another. - Status status = _indexCursor->seek(_params.bounds.startKey); - if (!status.isOK()) { - warning() << "IndexCursor seek failed: " << status.toString(); - _scanState = HIT_END; - } - if (!isEOF()) { - _specificStats.keysExamined = 1; - } + _endKey = _params.bounds.endKey; + _endKeyInclusive = _params.bounds.endKeyInclusive; + _indexCursor->setEndPosition(_endKey, _endKeyInclusive); + return _indexCursor->seek(_params.bounds.startKey, /*inclusive*/true); } else { // For single intervals, we can use an optimized scan which checks against the position @@ -136,45 +111,20 @@ namespace mongo { &startKeyInclusive, &_endKey, &_endKeyInclusive)) { - // We want to point at the start key if it's inclusive, and we want to point past - // the start key if it's exclusive. - _indexCursor->seek(startKey, !startKeyInclusive); - - IndexCursor* endCursor; - invariant(_iam->newCursor(_txn, cursorOptions, &endCursor).isOK()); - invariant(endCursor); - _endCursor.reset(endCursor); - // If the end key is inclusive, we want to point *past* it since that's the end. - _endCursor->seek(_endKey, _endKeyInclusive); + _indexCursor->setEndPosition(_endKey, _endKeyInclusive); + return _indexCursor->seek(startKey, startKeyInclusive); } else { _checker.reset(new IndexBoundsChecker(&_params.bounds, _keyPattern, _params.direction)); - int nFields = _keyPattern.nFields(); - vector<const BSONElement*> key; - vector<bool> inc; - key.resize(nFields); - inc.resize(nFields); - if (_checker->getStartKey(&key, &inc)) { - _indexCursor->seek(key, inc); - _keyElts.resize(nFields); - _keyEltsInc.resize(nFields); - } - else { - _scanState = HIT_END; - } - } - } + if (!_checker->getStartSeekPoint(&_seekPoint)) + return boost::none; - // This method may throw an exception while it's doing initialization. If we've gotten - // here, then we've done all the initialization without an exception being thrown. This - // means it is safe to transition to the CHECKING_END state. In error cases, we transition - // to HIT_END, so we should not change state again here. - if (HIT_END != _scanState) { - _scanState = CHECKING_END; + return _indexCursor->seek(_seekPoint); + } } } @@ -184,120 +134,105 @@ namespace mongo { // Adds the amount of time taken by work() to executionTimeMillis. ScopedTimer timer(&_commonStats.executionTimeMillis); - if (INITIALIZING == _scanState) { - invariant(NULL == _indexCursor.get()); - try { - initIndexScan(); - } - catch (const WriteConflictException& wce) { - // Release our owned cursors and try again next time. - _scanState = INITIALIZING; - _indexCursor.reset(); - _endCursor.reset(); - *out = WorkingSet::INVALID_ID; - return PlanStage::NEED_YIELD; + // Get the next kv pair from the index, if any. + boost::optional<IndexKeyEntry> kv; + try { + switch (_scanState) { + case INITIALIZING: kv = initIndexScan(); break; + case GETTING_NEXT: kv = _indexCursor->next(); break; + case NEED_SEEK: kv = _indexCursor->seek(_seekPoint); break; + case HIT_END: return PlanStage::IS_EOF; } } + catch (const WriteConflictException& wce) { + *out = WorkingSet::INVALID_ID; + return PlanStage::NEED_YIELD; + } - if (CHECKING_END == _scanState) { - try { - checkEnd(); + if (kv) { + // In debug mode, check that the cursor isn't lying to us. + if (kDebugBuild && !_endKey.isEmpty()) { + int cmp = kv->key.woCompare(_endKey, + Ordering::make(_params.descriptor->keyPattern()), + /*compareFieldNames*/false); + if (cmp == 0) dassert(_endKeyInclusive); + dassert(_forward ? cmp <= 0 : cmp >= 0); + } + + ++_specificStats.keysExamined; + if (_params.maxScan && _specificStats.keysExamined >= _params.maxScan) { + kv = boost::none; } - catch (const WriteConflictException& wce) { - // checkEnd only fails in ways that is safe to call again after yielding. - _scanState = CHECKING_END; - *out = WorkingSet::INVALID_ID; - return PlanStage::NEED_YIELD; + } + + if (kv && _checker) { + switch (_checker->checkKey(kv->key, &_seekPoint)) { + case IndexBoundsChecker::VALID: + break; + + case IndexBoundsChecker::DONE: + // This seems weird but it's the old definition of nscanned. + --_specificStats.keysExamined; + kv = boost::none; + break; + + case IndexBoundsChecker::MUST_ADVANCE: + _scanState = NEED_SEEK; + _commonStats.needTime++; + return PlanStage::NEED_TIME; } } - if (isEOF()) { + if (!kv) { + _scanState = HIT_END; _commonStats.isEOF = true; + _indexCursor.reset(); return PlanStage::IS_EOF; } - if (GETTING_NEXT == _scanState) { - // Grab the next (key, value) from the index. - BSONObj keyObj = _indexCursor->getKey(); - RecordId loc = _indexCursor->getValue(); - - bool filterPasses = Filter::passes(keyObj, _keyPattern, _filter); - if ( filterPasses ) { - // We must make a copy of the on-disk data since it can mutate during the execution - // of this query. - keyObj = keyObj.getOwned(); - } - - _scanState = CHECKING_END; + _scanState = GETTING_NEXT; - // Move to the next result. - // The underlying IndexCursor points at the *next* thing we want to return. We do this - // so that if we're scanning an index looking for docs to delete we don't continually - // clobber the thing we're pointing at. - try { - _indexCursor->next(); - } - catch (const WriteConflictException& wce) { - // If next throws, it leaves us at the original position. - invariant(_indexCursor->getValue() == loc); - *out = WorkingSet::INVALID_ID; - return PlanStage::NEED_YIELD; + if (_shouldDedup) { + ++_specificStats.dupsTested; + if (!_returned.insert(kv->loc).second) { + // We've seen this RecordId before. Skip it this time. + ++_specificStats.dupsDropped; + ++_commonStats.needTime; + return PlanStage::NEED_TIME; } + } - if (_shouldDedup) { - ++_specificStats.dupsTested; - if (_returned.end() != _returned.find(loc)) { - ++_specificStats.dupsDropped; - ++_commonStats.needTime; - return PlanStage::NEED_TIME; - } - else { - _returned.insert(loc); - } + if (_filter) { + if (!Filter::passes(kv->key, _keyPattern, _filter)) { + ++_commonStats.needTime; + return PlanStage::NEED_TIME; } - if (filterPasses) { - if (NULL != _filter) { - ++_specificStats.matchTested; - } - - // Fill out the WSM. - WorkingSetID id = _workingSet->allocate(); - WorkingSetMember* member = _workingSet->get(id); - member->loc = loc; - member->keyData.push_back(IndexKeyDatum(_keyPattern, keyObj, _iam)); - member->state = WorkingSetMember::LOC_AND_IDX; - - if (_params.addKeyMetadata) { - BSONObjBuilder bob; - bob.appendKeys(_keyPattern, keyObj); - member->addComputed(new IndexKeyComputedData(bob.obj())); - } - - *out = id; - ++_commonStats.advanced; - return PlanStage::ADVANCED; - } + ++_specificStats.matchTested; } + + if (!kv->key.isOwned()) kv->key = kv->key.getOwned(); - ++_commonStats.needTime; - return PlanStage::NEED_TIME; - } + // We found something to return, so fill out the WSM. + WorkingSetID id = _workingSet->allocate(); + WorkingSetMember* member = _workingSet->get(id); + member->loc = kv->loc; + member->keyData.push_back(IndexKeyDatum(_keyPattern, kv->key, _iam)); + member->state = WorkingSetMember::LOC_AND_IDX; - bool IndexScan::isEOF() { - if (INITIALIZING == _scanState) { - // Have to call work() at least once. - return false; + if (_params.addKeyMetadata) { + BSONObjBuilder bob; + bob.appendKeys(_keyPattern, kv->key); + member->addComputed(new IndexKeyComputedData(bob.obj())); } - // If there's a limit on how many keys we can scan, we may be EOF when we hit that. - if (0 != _params.maxScan) { - if (_specificStats.keysExamined >= _params.maxScan) { - return true; - } - } + *out = id; + ++_commonStats.advanced; + return PlanStage::ADVANCED; + } - return HIT_END == _scanState || _indexCursor->isEOF(); + bool IndexScan::isEOF() { + return _commonStats.isEOF; } void IndexScan::saveState() { @@ -308,17 +243,14 @@ namespace mongo { _txn = NULL; ++_commonStats.yields; + if (!_indexCursor) return; - if (HIT_END == _scanState || INITIALIZING == _scanState) { return; } - if (!_indexCursor->isEOF()) { - _savedKey = _indexCursor->getKey().getOwned(); - _savedLoc = _indexCursor->getValue(); + if (_scanState == NEED_SEEK) { + _indexCursor->saveUnpositioned(); + return; } - _indexCursor->savePosition(); - if (_endCursor) { - _endCursor->savePosition(); - } + _indexCursor->savePositioned(); } void IndexScan::restoreState(OperationContext* opCtx) { @@ -326,61 +258,7 @@ namespace mongo { _txn = opCtx; ++_commonStats.unyields; - if (HIT_END == _scanState || INITIALIZING == _scanState) { return; } - - // We can have a valid position before we check isEOF(), restore the position, and then be - // EOF upon restore. - if (!_indexCursor->restorePosition( opCtx ).isOK() || _indexCursor->isEOF()) { - _scanState = HIT_END; - return; - } - - if (_endCursor) { - // Single interval case. - if (!_endCursor->restorePosition(opCtx).isOK()) { - _scanState = HIT_END; - return; - } - - // If we were EOF when we yielded, we don't always want to have '_indexCursor' run until - // EOF. New documents may have been inserted after our end key, and our end marker may - // be before them. - // - // As an example, say we're counting from 5 to 10 and the index only has keys for 6, 7, - // 8, and 9. '_indexCursor' will point at key 6 at the start and '_endCursor' will be - // EOF. If we insert a document with key 11 during a yield, we need to relocate - // '_endCursor' to point at the new key as the end key of our scan. - _endCursor->seek(_endKey, _endKeyInclusive); - - // It is possible that the re-positioning of the end cursor above will move the end - // cursor so that it is on the other side of the scanning cursor. If this happens, the - // scan is over, so we transition to HIT_END state. - // - // Example: - // Suppose we're counting from 5 to 10 and the index has keys 6 and 15. The end - // cursor will initially point at 15. Say that the scanning cursor advances, returning - // key 6 and now points at 15. Then the index scan state is saved. While saved, - // key 11 is inserted. The end cursor will seek to point at key 11. If we didn't have - // the check below, then the scan could erroneously return key 15, which is not in the - // desired range of [5, 10]. - int cmp = _endKey.woCompare(_indexCursor->getKey(), _keyPattern); - const bool cursorPastEndKey = (_params.direction == 1 ? cmp < 0 : cmp > 0); - const bool cursorAtExclusiveEndKey = (cmp == 0 && !_endKeyInclusive); - if (cursorPastEndKey || cursorAtExclusiveEndKey) { - _scanState = HIT_END; - return; - } - } - - if (!_savedKey.binaryEqual(_indexCursor->getKey()) - || _savedLoc != _indexCursor->getValue()) { - // Our restored position isn't the same as the saved position. When we call work() - // again we want to return where we currently point, not past it. - ++_specificStats.yieldMovedCursor; - - // Our restored position might be past endKey, see if we've hit the end. - _scanState = CHECKING_END; - } + if (_indexCursor) _indexCursor->restore(opCtx); } void IndexScan::invalidate(OperationContext* txn, const RecordId& dl, InvalidationType type) { @@ -401,91 +279,13 @@ namespace mongo { } } - void IndexScan::checkEnd() { - if (isEOF()) { - _commonStats.isEOF = true; - return; - } - - if (_params.bounds.isSimpleRange) { - _scanState = GETTING_NEXT; - - // "Normal" start -> end scanning. - verify(NULL == _endCursor); - verify(NULL == _checker.get()); - - // If there is an empty endKey we will scan until we run out of index to scan over. - if (_params.bounds.endKey.isEmpty()) { return; } - - int cmp = sgn(_params.bounds.endKey.woCompare(_indexCursor->getKey(), _keyPattern)); - - if ((cmp != 0 && cmp != _params.direction) - || (cmp == 0 && !_params.bounds.endKeyInclusive)) { - _scanState = HIT_END; - } - else { - ++_specificStats.keysExamined; - } - } - else if (_endCursor) { - // We're in the single interval case, and we have a cursor pointing to the end position. - // We can check whether the scan is over by seeing if our cursor points at the same - // thing as the end cursor. - _scanState = GETTING_NEXT; - invariant(!_checker); - - if (_endCursor->pointsAt(*_indexCursor)) { - _scanState = HIT_END; - } - else { - ++_specificStats.keysExamined; - } - } - else { - verify(NULL != _indexCursor); - verify(NULL != _checker.get()); - - IndexBoundsChecker::KeyState keyState; - keyState = _checker->checkKey(_indexCursor->getKey(), - &_keyEltsToUse, - &_movePastKeyElts, - &_keyElts, - &_keyEltsInc); - - if (IndexBoundsChecker::DONE == keyState) { - _scanState = HIT_END; - return; - } - - // This seems weird but it's the old definition of nscanned. - ++_specificStats.keysExamined; - - if (IndexBoundsChecker::VALID == keyState) { - _scanState = GETTING_NEXT; - return; - } - - verify(IndexBoundsChecker::MUST_ADVANCE == keyState); - _indexCursor->skip(_indexCursor->getKey(), _keyEltsToUse, _movePastKeyElts, - _keyElts, _keyEltsInc); - - // Must check underlying cursor EOF after every cursor movement. - if (_indexCursor->isEOF()) { - _scanState = HIT_END; - return; - } - } - } - - vector<PlanStage*> IndexScan::getChildren() const { - vector<PlanStage*> empty; - return empty; + std::vector<PlanStage*> IndexScan::getChildren() const { + return {}; } PlanStageStats* IndexScan::getStats() { // WARNING: this could be called even if the collection was dropped. Do not access any // catalog information here. - _commonStats.isEOF = isEOF(); // Add a BSON representation of the filter to the stats tree, if there is one. if (NULL != _filter) { @@ -503,7 +303,7 @@ namespace mongo { _specificStats.direction = _params.direction; } - auto_ptr<PlanStageStats> ret(new PlanStageStats(_commonStats, STAGE_IXSCAN)); + std::unique_ptr<PlanStageStats> ret(new PlanStageStats(_commonStats, STAGE_IXSCAN)); ret->specific.reset(new IndexScanStats(_specificStats)); return ret.release(); } diff --git a/src/mongo/db/exec/index_scan.h b/src/mongo/db/exec/index_scan.h index db15d43a0bd..a2cfd4bad45 100644 --- a/src/mongo/db/exec/index_scan.h +++ b/src/mongo/db/exec/index_scan.h @@ -36,12 +36,13 @@ #include "mongo/db/matcher/expression.h" #include "mongo/db/query/index_bounds.h" #include "mongo/db/record_id.h" +#include "mongo/db/storage/index_entry_comparison.h" +#include "mongo/db/storage/sorted_data_interface.h" #include "mongo/platform/unordered_set.h" namespace mongo { class IndexAccessMethod; - class IndexCursor; class IndexDescriptor; class WorkingSet; @@ -84,8 +85,8 @@ namespace mongo { // Need to initialize the underlying index traversal machinery. INITIALIZING, - // Skipping keys in order to check whether we have reached the end. - CHECKING_END, + // Skipping keys as directed by the _checker. + NEED_SEEK, // Retrieving the next key, and applying the filter if necessary. GETTING_NEXT, @@ -121,23 +122,20 @@ namespace mongo { private: /** - * Initialize the underlying IndexCursor, grab information from the catalog for stats. + * Initialize the underlying index Cursor, returning first result if any. */ - void initIndexScan(); - - /** See if the cursor is pointing at or past _endKey, if _endKey is non-empty. */ - void checkEnd(); + boost::optional<IndexKeyEntry> initIndexScan(); // transactional context for read locks. Not owned by us OperationContext* _txn; - // The WorkingSet we annotate with results. Not owned by us. - WorkingSet* _workingSet; + // The WorkingSet we fill with results. Not owned by us. + WorkingSet* const _workingSet; // Index access. - const IndexAccessMethod* _iam; // owned by Collection -> IndexCatalog - boost::scoped_ptr<IndexCursor> _indexCursor; - BSONObj _keyPattern; + const IndexAccessMethod* const _iam; // owned by Collection -> IndexCatalog + std::unique_ptr<SortedDataInterface::Cursor> _indexCursor; + const BSONObj _keyPattern; // Keeps track of what work we need to do next. ScanState _scanState; @@ -145,50 +143,40 @@ namespace mongo { // Contains expressions only over fields in the index key. We assume this is built // correctly by whomever creates this class. // The filter is not owned by us. - const MatchExpression* _filter; + const MatchExpression* const _filter; // Could our index have duplicates? If so, we use _returned to dedup. bool _shouldDedup; unordered_set<RecordId, RecordId::Hasher> _returned; - // For yielding. - BSONObj _savedKey; - RecordId _savedLoc; - - IndexScanParams _params; + const bool _forward; + const IndexScanParams _params; // Stats CommonStats _commonStats; IndexScanStats _specificStats; // - // If we aren't doing a "simple" index scan, we make a decision to employ one of two - // different algorithms for determining when the index scan has reached the end: + // This class employs one of two different algorithms for determining when the index scan + // has reached the end: // // - // 1) If the index scan is not a single interval, then we use an IndexBoundsChecker to - // determine when the index scan has reached the end. In this case, _checker will be - // non-NULL (and _endCursor will be NULL). + // 1) If the index scan is not a single contiguous interval, then we use an + // IndexBoundsChecker to determine which keys to return and when to stop scanning. + // In this case, _checker will be non-NULL. // boost::scoped_ptr<IndexBoundsChecker> _checker; - int _keyEltsToUse; - bool _movePastKeyElts; - std::vector<const BSONElement*> _keyElts; - std::vector<bool> _keyEltsInc; + IndexSeekPoint _seekPoint; // - // 2) If the index scan is a single interval, then the scan can execute faster by - // checking for the end via comparison against an end cursor, rather than repeatedly - // doing BSON compares against scanned keys. In this case, _endCursor will be non-NULL - // (and _checker will be NULL). + // 2) If the index scan is a single contiguous interval, then the scan can execute faster by + // letting the index cursor tell us when it hits the end, rather than repeatedly doing + // BSON compares against scanned keys. In this case _checker will be NULL. // - // The end cursor. - boost::scoped_ptr<IndexCursor> _endCursor; - - // The key that the end cursor should point to. + // The key that the index cursor should stop on/after. BSONObj _endKey; // Is the end key included in the range? diff --git a/src/mongo/db/exec/plan_stats.h b/src/mongo/db/exec/plan_stats.h index 6e1069f1609..d823c9ee4d2 100644 --- a/src/mongo/db/exec/plan_stats.h +++ b/src/mongo/db/exec/plan_stats.h @@ -364,7 +364,6 @@ namespace mongo { IndexScanStats() : indexVersion(0), direction(1), isMultiKey(false), - yieldMovedCursor(0), dupsTested(0), dupsDropped(0), seenInvalidated(0), @@ -402,7 +401,6 @@ namespace mongo { // Whether this index is over a field that contain array values. bool isMultiKey; - size_t yieldMovedCursor; size_t dupsTested; size_t dupsDropped; diff --git a/src/mongo/db/index/2d_access_method.h b/src/mongo/db/index/2d_access_method.h index 6a97ab720c5..208df9235cd 100644 --- a/src/mongo/db/index/2d_access_method.h +++ b/src/mongo/db/index/2d_access_method.h @@ -36,7 +36,6 @@ namespace mongo { class IndexCatalogEntry; - class IndexCursor; class IndexDescriptor; struct TwoDIndexingParams; diff --git a/src/mongo/db/index/btree_access_method.h b/src/mongo/db/index/btree_access_method.h index 4278471d782..a19858ca2b4 100644 --- a/src/mongo/db/index/btree_access_method.h +++ b/src/mongo/db/index/btree_access_method.h @@ -38,7 +38,6 @@ namespace mongo { - class IndexCursor; class IndexDescriptor; /** diff --git a/src/mongo/db/index/index_access_method.cpp b/src/mongo/db/index/index_access_method.cpp index b01a1eafded..1f373090288 100644 --- a/src/mongo/db/index/index_access_method.cpp +++ b/src/mongo/db/index/index_access_method.cpp @@ -166,10 +166,10 @@ namespace mongo { } } - Status IndexAccessMethod::newCursor(OperationContext* txn, const CursorOptions& opts, - IndexCursor** out) const { - *out = new IndexCursor(_newInterface->newCursor(txn, opts.direction)); - return Status::OK(); + std::unique_ptr<SortedDataInterface::Cursor> IndexAccessMethod::newCursor( + OperationContext* txn, + bool isForward) const { + return _newInterface->newCursor(txn, isForward); } // Remove the provided doc from the index. @@ -219,9 +219,9 @@ namespace mongo { BSONObjSet keys; getKeys(obj, &keys); - boost::scoped_ptr<SortedDataInterface::Cursor> cursor(_newInterface->newCursor(txn, 1)); + std::unique_ptr<SortedDataInterface::Cursor> cursor(_newInterface->newCursor(txn)); for (BSONObjSet::const_iterator i = keys.begin(); i != keys.end(); ++i) { - cursor->locate(*i, RecordId()); + cursor->seekExact(*i); } return Status::OK(); @@ -233,22 +233,18 @@ namespace mongo { } RecordId IndexAccessMethod::findSingle(OperationContext* txn, const BSONObj& key) const { - boost::scoped_ptr<SortedDataInterface::Cursor> cursor(_newInterface->newCursor(txn, 1)); - cursor->locate(key, RecordId::min()); - - // A null bucket means the key wasn't found (nor was anything found after it). - if (cursor->isEOF()) { - return RecordId(); - } - - // We found something but it could be a key after 'key'. Examine what we're pointing at. - if (0 != key.woCompare(cursor->getKey(), BSONObj(), false)) { - // If the keys don't match, return "not found." - return RecordId(); + std::unique_ptr<SortedDataInterface::Cursor> cursor(_newInterface->newCursor(txn)); + const auto requestedInfo = kDebugBuild ? SortedDataInterface::Cursor::kKeyAndLoc + : SortedDataInterface::Cursor::kWantLoc; + if (auto kv = cursor->seekExact(key, requestedInfo)) { + // StorageEngine should guarantee these. + dassert(!kv->loc.isNull()); + dassert(kv->key.woCompare(key, /*order*/BSONObj(), /*considerFieldNames*/false) == 0); + + return kv->loc; } - // Return the RecordId found. - return cursor->getRecordId(); + return RecordId(); } Status IndexAccessMethod::validate(OperationContext* txn, bool full, int64_t* numKeys, diff --git a/src/mongo/db/index/index_access_method.h b/src/mongo/db/index/index_access_method.h index fea0c8e4a10..3f311128a2e 100644 --- a/src/mongo/db/index/index_access_method.h +++ b/src/mongo/db/index/index_access_method.h @@ -31,7 +31,6 @@ #include <memory> #include "mongo/base/disallow_copying.h" -#include "mongo/db/index/index_cursor.h" #include "mongo/db/index/index_descriptor.h" #include "mongo/db/jsobj.h" #include "mongo/db/operation_context.h" @@ -120,10 +119,10 @@ namespace mongo { Status update(OperationContext* txn, const UpdateTicket& ticket, int64_t* numUpdated); /** - * Fills in '*out' with an IndexCursor. Return a status indicating success or reason of - * failure. If the latter, '*out' contains NULL. See index_cursor.h for IndexCursor usage. + * Returns an unpositioned cursor over 'this' index. */ - Status newCursor(OperationContext* txn, const CursorOptions& opts, IndexCursor** out) const; + std::unique_ptr<SortedDataInterface::Cursor> newCursor(OperationContext* txn, + bool isForward = true) const; // ------ index level operations ------ @@ -177,7 +176,6 @@ namespace mongo { */ long long getSpaceUsedBytes( OperationContext* txn ) const; - // XXX: consider migrating callers to use IndexCursor instead RecordId findSingle( OperationContext* txn, const BSONObj& key ) const; // diff --git a/src/mongo/db/index/index_cursor.cpp b/src/mongo/db/index/index_cursor.cpp deleted file mode 100644 index de09e7c1d3d..00000000000 --- a/src/mongo/db/index/index_cursor.cpp +++ /dev/null @@ -1,123 +0,0 @@ -/** -* Copyright (C) 2013 10gen 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/db/index/index_cursor.h" - -#include <vector> - -#include "mongo/base/status.h" -#include "mongo/db/index/index_cursor.h" -#include "mongo/db/index/index_descriptor.h" -#include "mongo/db/jsobj.h" -#include "mongo/db/record_id.h" -#include "mongo/platform/unordered_set.h" - -namespace mongo { - - using std::string; - using std::vector; - - IndexCursor::IndexCursor(SortedDataInterface::Cursor* cursor) : _cursor(cursor) { } - - bool IndexCursor::isEOF() const { return _cursor->isEOF(); } - - Status IndexCursor::seek(const BSONObj& position) { - _cursor->locate(position, - 1 == _cursor->getDirection() ? RecordId::min() : RecordId::max()); - return Status::OK(); - } - - void IndexCursor::seek(const BSONObj& position, bool afterKey) { - const bool forward = (1 == _cursor->getDirection()); - _cursor->locate(position, (afterKey == forward) ? RecordId::max() : RecordId::min()); - } - - bool IndexCursor::pointsAt(const IndexCursor& other) { - return _cursor->pointsToSamePlaceAs(*other._cursor); - } - - Status IndexCursor::seek(const vector<const BSONElement*>& position, - const vector<bool>& inclusive) { - - BSONObj emptyObj; - - _cursor->customLocate(emptyObj, - 0, - false, - position, - inclusive); - return Status::OK(); - } - - Status IndexCursor::skip(const BSONObj &keyBegin, - int keyBeginLen, - bool afterKey, - const vector<const BSONElement*>& keyEnd, - const vector<bool>& keyEndInclusive) { - - _cursor->advanceTo(keyBegin, - keyBeginLen, - afterKey, - keyEnd, - keyEndInclusive); - return Status::OK(); - } - - BSONObj IndexCursor::getKey() const { - return _cursor->getKey(); - } - - RecordId IndexCursor::getValue() const { - return _cursor->getRecordId(); - } - - void IndexCursor::next() { - advance(); - } - - Status IndexCursor::savePosition() { - _cursor->savePosition(); - return Status::OK(); - } - - Status IndexCursor::restorePosition(OperationContext* txn) { - _cursor->restorePosition(txn); - return Status::OK(); - } - - string IndexCursor::toString() { - // TODO: is this ever called? - return "I AM A BTREE INDEX CURSOR!\n"; - } - - // Move to the next/prev. key. Used by normal getNext and also skipping unused keys. - void IndexCursor::advance() { - _cursor->advance(); - } - -} // namespace mongo diff --git a/src/mongo/db/index/index_cursor.h b/src/mongo/db/index/index_cursor.h deleted file mode 100644 index deb8969e409..00000000000 --- a/src/mongo/db/index/index_cursor.h +++ /dev/null @@ -1,188 +0,0 @@ -/** -* Copyright (C) 2013 10gen 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. -*/ - -#pragma once - -#include <memory> -#include <vector> - -#include "mongo/base/status.h" -#include "mongo/db/index/index_cursor.h" -#include "mongo/db/index/index_descriptor.h" -#include "mongo/db/jsobj.h" -#include "mongo/db/record_id.h" -#include "mongo/db/storage/sorted_data_interface.h" - -namespace mongo { - - struct CursorOptions; - - /** - * TODO remove this class in favor of direct usage of SortedDataInterface::Cursor. - * - * An IndexCursor is the interface through which one traverses the entries of a given - * index. The internal structure of an index is kept isolated. - * - * The cursor must be initialized by seek()ing to a given entry in the index. The index is - * traversed by calling next() or skip()-ping ahead. - * - * The set of predicates a given index can understand is known a priori. These predicates may - * be simple (a key location for a Btree index) or rich ($within for a geo index). - * - * Locking is the responsibility of the caller. The IndexCursor keeps state. If the caller - * wishes to yield or unlock, it must call savePosition() first. When it decides to unyield it - * must call restorePosition(). The cursor may be EOF after a restorePosition(). - */ - class IndexCursor { - public: - - /** - * A cursor doesn't point anywhere by default. You must seek to the start position. - * The provided position must be a predicate that the index understands. The - * predicate must describe one value, though there may be several instances - * - * Possible return values: - * 1. Success: seeked to the position. - * 2. Success: seeked to 'closest' key oriented according to the cursor's direction. - * 3. Error: can't seek to the position. - */ - Status seek(const BSONObj& position); - - Status seek(const std::vector<const BSONElement*>& position, - const std::vector<bool>& inclusive); - - /** - * Seek to the key 'position'. If 'afterKey' is true, seeks to the first - * key that is oriented after 'position'. - * - * Btree-specific. - */ - void seek(const BSONObj& position, bool afterKey); - - Status skip(const BSONObj& keyBegin, - int keyBeginLen, - bool afterKey, - const std::vector<const BSONElement*>& keyEnd, - const std::vector<bool>& keyEndInclusive); - - /** - * Returns true if 'this' points at the same exact key as 'other'. - * Returns false otherwise. - */ - bool pointsAt(const IndexCursor& other); - - // - // Iteration support - // - - // Are we out of documents? - bool isEOF() const; - - // Move to the next key/value pair. Assumes !isEOF(). - void next(); - - // - // Accessors - // - - // Current key we point at. Assumes !isEOF(). - BSONObj getKey() const; - - // Current value we point at. Assumes !isEOF(). - RecordId getValue() const; - - // - // Yielding support - // - - /** - * Yielding semantics: - * If the entry that a cursor points at is not deleted during a yield, the cursor will - * point at that entry after a restore. - * An entry inserted during a yield may or may not be returned by an in-progress scan. - * An entry deleted during a yield may or may not be returned by an in-progress scan. - * An entry modified during a yield may or may not be returned by an in-progress scan. - * An entry that is not inserted or deleted during a yield will be returned, and only once. - * If the index returns entries in a given order (Btree), this order will be mantained even - * if the entry corresponding to a saved position is deleted during a yield. - */ - - /** - * Save our current position in the index. - */ - Status savePosition(); - - /** - * Restore the saved position. Errors if there is no saved position. - * The cursor may be EOF after a restore. - */ - Status restorePosition(OperationContext* txn); - - /** - * Return a std::string describing the cursor. - */ - std::string toString(); - - private: - // We keep the constructor private and only allow the AM to create us. - friend class IndexAccessMethod; - - /** - * interface is an abstraction to hide the fact that we have two types of Btrees. - * - * Intentionally private, we're friends with the only class allowed to call it. - */ - IndexCursor(SortedDataInterface::Cursor* cursor); - - bool isSavedPositionValid(); - - /** - * Move to the next (or previous depending on the direction) key. Used by normal getNext - * and also skipping unused keys. - */ - void advance(); - - const std::unique_ptr<SortedDataInterface::Cursor> _cursor; - }; - - // All the options we might want to set on a cursor. - struct CursorOptions { - // Set the direction of the scan. Ignored if the cursor doesn't have directions (geo). - enum Direction { - DECREASING = -1, - INCREASING = 1, - }; - - Direction direction; - - // 2d indices need to know exactly how many results you want beforehand. - // Ignored by every other index. - int numWanted; - }; - -} // namespace mongo diff --git a/src/mongo/db/index/s2_access_method.h b/src/mongo/db/index/s2_access_method.h index 85f4af8e5b0..9ce655e47f7 100644 --- a/src/mongo/db/index/s2_access_method.h +++ b/src/mongo/db/index/s2_access_method.h @@ -36,9 +36,6 @@ namespace mongo { - class IndexCursor; - struct S2IndexingParams; - class S2AccessMethod : public IndexAccessMethod { public: S2AccessMethod(IndexCatalogEntry* btreeState, SortedDataInterface* btree); diff --git a/src/mongo/db/query/index_bounds.cpp b/src/mongo/db/query/index_bounds.cpp index b88bbe67f42..f7592994e29 100644 --- a/src/mongo/db/query/index_bounds.cpp +++ b/src/mongo/db/query/index_bounds.cpp @@ -291,17 +291,18 @@ namespace mongo { } } - bool IndexBoundsChecker::getStartKey(vector<const BSONElement*>* valueOut, - vector<bool>* inclusiveOut) { - verify(valueOut->size() == _bounds->fields.size()); - verify(inclusiveOut->size() == _bounds->fields.size()); + bool IndexBoundsChecker::getStartSeekPoint(IndexSeekPoint* out) { + out->prefixLen = 0; + out->prefixExclusive = false; + out->keySuffix.resize(_bounds->fields.size()); + out->suffixInclusive.resize(_bounds->fields.size()); for (size_t i = 0; i < _bounds->fields.size(); ++i) { if (0 == _bounds->fields[i].intervals.size()) { return false; } - (*valueOut)[i] = &_bounds->fields[i].intervals[0].start; - (*inclusiveOut)[i] = _bounds->fields[i].intervals[0].startInclusive; + out->keySuffix[i] = &_bounds->fields[i].intervals[0].start; + out->suffixInclusive[i] = _bounds->fields[i].intervals[0].startInclusive; } return true; @@ -374,13 +375,10 @@ namespace mongo { } IndexBoundsChecker::KeyState IndexBoundsChecker::checkKey(const BSONObj& key, - int* keyEltsToUse, - bool* movePastKeyElts, - vector<const BSONElement*>* out, - vector<bool>* incOut) { + IndexSeekPoint* out) { verify(_curInterval.size() > 0); - verify(out->size() == _curInterval.size()); - verify(incOut->size() == _curInterval.size()); + out->keySuffix.resize(_curInterval.size()); + out->suffixInclusive.resize(_curInterval.size()); // It's useful later to go from a field number to the value for that field. Store these. // TODO: on optimization pass, populate the vector as-needed and keep the vector around as a @@ -419,13 +417,14 @@ namespace mongo { // Field number 'firstNonContainedField' of the index key is before all current intervals. if (BEHIND == orientation) { // Tell the caller to move forward to the start of the current interval. - *keyEltsToUse = firstNonContainedField; - *movePastKeyElts = false; + out->keyPrefix = key.getOwned(); + out->prefixLen = firstNonContainedField; + out->prefixExclusive = false; for (size_t j = firstNonContainedField; j < _curInterval.size(); ++j) { const OrderedIntervalList& oil = _bounds->fields[j]; - (*out)[j] = &oil.intervals[_curInterval[j]].start; - (*incOut)[j] = oil.intervals[_curInterval[j]].startInclusive; + out->keySuffix[j] = &oil.intervals[_curInterval[j]].start; + out->suffixInclusive[j] = oil.intervals[_curInterval[j]].startInclusive; } return MUST_ADVANCE; @@ -463,13 +462,13 @@ namespace mongo { _curInterval[i] = 0; } - *keyEltsToUse = firstNonContainedField; - *movePastKeyElts = false; - + out->keyPrefix = key.getOwned(); + out->prefixLen = firstNonContainedField; + out->prefixExclusive = false; for (size_t i = firstNonContainedField; i < _curInterval.size(); ++i) { const OrderedIntervalList& oil = _bounds->fields[i]; - (*out)[i] = &oil.intervals[_curInterval[i]].start; - (*incOut)[i] = oil.intervals[_curInterval[i]].startInclusive; + out->keySuffix[i] = &oil.intervals[_curInterval[i]].start; + out->suffixInclusive[i] = oil.intervals[_curInterval[i]].startInclusive; } return MUST_ADVANCE; @@ -486,8 +485,9 @@ namespace mongo { return DONE; } - *keyEltsToUse = firstNonContainedField; - *movePastKeyElts = true; + out->keyPrefix = key.getOwned(); + out->prefixLen = firstNonContainedField; + out->prefixExclusive = true; for (size_t i = firstNonContainedField; i < _curInterval.size(); ++i) { _curInterval[i] = 0; diff --git a/src/mongo/db/query/index_bounds.h b/src/mongo/db/query/index_bounds.h index e99754680b9..d1e140f3932 100644 --- a/src/mongo/db/query/index_bounds.h +++ b/src/mongo/db/query/index_bounds.h @@ -33,6 +33,7 @@ #include "mongo/db/jsobj.h" #include "mongo/db/query/interval.h" +#include "mongo/db/storage/index_entry_comparison.h" namespace mongo { @@ -125,12 +126,15 @@ namespace mongo { */ IndexBoundsChecker(const IndexBounds* bounds, const BSONObj& keyPattern, int direction); + /** - * Get the key that we should with. + * Get the IndexSeekPoint that we should with. * - * Returns true if there is a valid start key. Returns false otherwise. + * Returns false if there are no possible index entries that match the bounds. In this case + * there is no valid start point to seek to so out will not be filled out and the caller + * should emit no results. */ - bool getStartKey(std::vector<const BSONElement*>* valueOut, std::vector<bool>* inclusiveOut); + bool getStartSeekPoint(IndexSeekPoint* out); /** * The states of a key from an index scan. See checkKey below. @@ -157,8 +161,8 @@ namespace mongo { * key. * * 2. The key is not in our bounds but has not exceeded the maximum value in our bounds. - * Returns MUST_ADVANCE. Caller must advance to the key provided in the out parameters and - * call checkKey again. + * Returns MUST_ADVANCE. Caller must advance to the query provided in the out parameters + * and call checkKey again. * * 3. The key is past our bounds. Returns DONE. No further keys will satisfy the bounds * and the caller should stop. @@ -167,28 +171,11 @@ namespace mongo { * out and incOut must already be resized to have as many elements as the key has fields. * * In parameters: - * key is the index key. - * - * Out parameters, only valid if we return MUST_ADVANCE: - * - * keyEltsToUse: The key that the caller should advance to is made up of the first - * 'keyEltsToUse' of the key that was provided. - * - * movePastKeyElts: If true, the caller must only use the first 'keyEltsToUse' of the - * provided key to form its key. It moves to the first key that is after - * the key formed by only using those elements. - * - * out: If keyEltsToUse is less than the number of indexed fields in the key, the remaining - * fields are taken from here. out is not filled from the start but from the position - * that the key corresponds to. An example: If keyEltsToUse is 1, movePastKeyElts is - * false, and the index we're iterating over has two fields, out[1] will have the value - * for the second field. + * currentKey is the index key. * - * incOut: If the i-th element is false, seek to the key *after* the i-th element of out. - * If the i-th element is true, seek to the i-th element of out. + * Out parameter only valid if we return MUST_ADVANCE. */ - KeyState checkKey(const BSONObj& key, int* keyEltsToUse, bool* movePastKeyElts, - std::vector<const BSONElement*>* out, std::vector<bool>* incOut); + KeyState checkKey(const BSONObj& currentKey, IndexSeekPoint* query); /** * Relative position of a key to an interval. diff --git a/src/mongo/db/query/index_bounds_test.cpp b/src/mongo/db/query/index_bounds_test.cpp index 2e285f227ce..d5cc470b3af 100644 --- a/src/mongo/db/query/index_bounds_test.cpp +++ b/src/mongo/db/query/index_bounds_test.cpp @@ -42,9 +42,6 @@ using namespace mongo; namespace { - using std::string; - using std::vector; - // // Validation // @@ -268,15 +265,13 @@ namespace { bounds.fields.push_back(barList); IndexBoundsChecker it(&bounds, BSON("foo" << 1 << "bar" << 1), 1); - vector<const BSONElement*> elt(2); - vector<bool> inc(2); - - it.getStartKey(&elt, &inc); + IndexSeekPoint seekPoint; + it.getStartSeekPoint(&seekPoint); - ASSERT_EQUALS(elt[0]->numberInt(), 7); - ASSERT_EQUALS(inc[0], true); - ASSERT_EQUALS(elt[1]->numberInt(), 0); - ASSERT_EQUALS(inc[1], false); + ASSERT_EQUALS(seekPoint.keySuffix[0]->numberInt(), 7); + ASSERT_EQUALS(seekPoint.suffixInclusive[0], true); + ASSERT_EQUALS(seekPoint.keySuffix[1]->numberInt(), 0); + ASSERT_EQUALS(seekPoint.suffixInclusive[1], false); } TEST(IndexBoundsCheckerTest, CheckEnd) { @@ -292,52 +287,32 @@ namespace { bounds.fields.push_back(barList); IndexBoundsChecker it(&bounds, BSON("foo" << 1 << "bar" << 1), 1); - int keyEltsToUse; - bool movePastKeyElts; - vector<const BSONElement*> elt(2); - vector<bool> inc(2); - + IndexSeekPoint seekPoint; IndexBoundsChecker::KeyState state; // Start at something in our range. - state = it.checkKey(BSON("" << 7 << "" << 1), - &keyEltsToUse, - &movePastKeyElts, - &elt, - &inc); + state = it.checkKey(BSON("" << 7 << "" << 1), &seekPoint); ASSERT_EQUALS(state, IndexBoundsChecker::VALID); // Second field moves past the end, but we're not done, since there's still an interval in // the previous field that the key hasn't advanced to. - state = it.checkKey(BSON("" << 20 << "" << 5), - &keyEltsToUse, - &movePastKeyElts, - &elt, - &inc); + state = it.checkKey(BSON("" << 20 << "" << 5), &seekPoint); ASSERT_EQUALS(state, IndexBoundsChecker::MUST_ADVANCE); - ASSERT_EQUALS(keyEltsToUse, 1); - ASSERT(movePastKeyElts); + ASSERT_EQUALS(seekPoint.prefixLen, 1); + ASSERT(seekPoint.prefixExclusive); // The next index key is in the second interval for 'foo' and there is a valid interval for // 'bar'. - state = it.checkKey(BSON("" << 22 << "" << 1), - &keyEltsToUse, - &movePastKeyElts, - &elt, - &inc); + state = it.checkKey(BSON("" << 22 << "" << 1), &seekPoint); ASSERT_EQUALS(state, IndexBoundsChecker::VALID); // The next index key is very close to the end of the open interval for foo, and it's past // the interval for 'bar'. Since the interval for foo is open, we are asked to move // forward, since we possibly could. - state = it.checkKey(BSON("" << 29.9 << "" << 5), - &keyEltsToUse, - &movePastKeyElts, - &elt, - &inc); + state = it.checkKey(BSON("" << 29.9 << "" << 5), &seekPoint); ASSERT_EQUALS(state, IndexBoundsChecker::MUST_ADVANCE); - ASSERT_EQUALS(keyEltsToUse, 1); - ASSERT(movePastKeyElts); + ASSERT_EQUALS(seekPoint.prefixLen, 1); + ASSERT(seekPoint.prefixExclusive); } TEST(IndexBoundsCheckerTest, MoveIntervalForwardToNextInterval) { @@ -353,35 +328,23 @@ namespace { bounds.fields.push_back(barList); IndexBoundsChecker it(&bounds, BSON("foo" << 1 << "bar" << 1), 1); - int keyEltsToUse; - bool movePastKeyElts; - vector<const BSONElement*> elt(2); - vector<bool> inc(2); - + IndexSeekPoint seekPoint; IndexBoundsChecker::KeyState state; // Start at something in our range. - state = it.checkKey(BSON("" << 7 << "" << 1), - &keyEltsToUse, - &movePastKeyElts, - &elt, - &inc); + state = it.checkKey(BSON("" << 7 << "" << 1), &seekPoint); ASSERT_EQUALS(state, IndexBoundsChecker::VALID); // "foo" moves between two intervals. - state = it.checkKey(BSON("" << 20.5 << "" << 1), - &keyEltsToUse, - &movePastKeyElts, - &elt, - &inc); + state = it.checkKey(BSON("" << 20.5 << "" << 1), &seekPoint); ASSERT_EQUALS(state, IndexBoundsChecker::MUST_ADVANCE); - ASSERT_EQUALS(keyEltsToUse, 0); + ASSERT_EQUALS(seekPoint.prefixLen, 0); // Should be told to move exactly to the next interval's beginning. - ASSERT_EQUALS(movePastKeyElts, false); - ASSERT_EQUALS(elt[0]->numberInt(), 21); - ASSERT_EQUALS(inc[0], true); - ASSERT_EQUALS(elt[1]->numberInt(), 0); - ASSERT_EQUALS(inc[1], false); + ASSERT_EQUALS(seekPoint.prefixExclusive, false); + ASSERT_EQUALS(seekPoint.keySuffix[0]->numberInt(), 21); + ASSERT_EQUALS(seekPoint.suffixInclusive[0], true); + ASSERT_EQUALS(seekPoint.keySuffix[1]->numberInt(), 0); + ASSERT_EQUALS(seekPoint.suffixInclusive[1], false); } TEST(IndexBoundsCheckerTest, MoveIntervalForwardManyIntervals) { @@ -395,27 +358,15 @@ namespace { bounds.fields.push_back(fooList); IndexBoundsChecker it(&bounds, BSON("foo" << 1), 1); - int keyEltsToUse; - bool movePastKeyElts; - vector<const BSONElement*> elt(1); - vector<bool> inc(1); - + IndexSeekPoint seekPoint; IndexBoundsChecker::KeyState state; // Start at something in our range. - state = it.checkKey(BSON("" << 7), - &keyEltsToUse, - &movePastKeyElts, - &elt, - &inc); + state = it.checkKey(BSON("" << 7), &seekPoint); ASSERT_EQUALS(state, IndexBoundsChecker::VALID); // "foo" moves forward a few intervals. - state = it.checkKey(BSON("" << 42), - &keyEltsToUse, - &movePastKeyElts, - &elt, - &inc); + state = it.checkKey(BSON("" << 42), &seekPoint); ASSERT_EQUALS(state, IndexBoundsChecker::VALID); } @@ -431,58 +382,34 @@ namespace { bounds.fields.push_back(barList); IndexBoundsChecker it(&bounds, BSON("foo" << 1 << "bar" << 1), 1); - int keyEltsToUse; - bool movePastKeyElts; - vector<const BSONElement*> elt(2); - vector<bool> inc(2); - + IndexSeekPoint seekPoint; IndexBoundsChecker::KeyState state; // Start at something in our range. - state = it.checkKey(BSON("" << 7 << "" << 1), - &keyEltsToUse, - &movePastKeyElts, - &elt, - &inc); + state = it.checkKey(BSON("" << 7 << "" << 1), &seekPoint); ASSERT_EQUALS(state, IndexBoundsChecker::VALID); // The rightmost key is past the range. We should be told to move past the key before the // one whose interval we exhausted. - state = it.checkKey(BSON("" << 7 << "" << 5.00001), - &keyEltsToUse, - &movePastKeyElts, - &elt, - &inc); + state = it.checkKey(BSON("" << 7 << "" << 5.00001), &seekPoint); ASSERT_EQUALS(state, IndexBoundsChecker::MUST_ADVANCE); - ASSERT_EQUALS(keyEltsToUse, 1); - ASSERT_EQUALS(movePastKeyElts, true); + ASSERT_EQUALS(seekPoint.prefixLen, 1); + ASSERT_EQUALS(seekPoint.prefixExclusive, true); // Move a little forward, but note that the rightmost key isn't in the interval yet. - state = it.checkKey(BSON("" << 7.2 << "" << 0), - &keyEltsToUse, - &movePastKeyElts, - &elt, - &inc); + state = it.checkKey(BSON("" << 7.2 << "" << 0), &seekPoint); ASSERT_EQUALS(state, IndexBoundsChecker::MUST_ADVANCE); - ASSERT_EQUALS(keyEltsToUse, 1); - ASSERT_EQUALS(movePastKeyElts, false); - ASSERT_EQUALS(elt[1]->numberInt(), 0); - ASSERT_EQUALS(inc[1], false); + ASSERT_EQUALS(seekPoint.prefixLen, 1); + ASSERT_EQUALS(seekPoint.prefixExclusive, false); + ASSERT_EQUALS(seekPoint.keySuffix[1]->numberInt(), 0); + ASSERT_EQUALS(seekPoint.suffixInclusive[1], false); // Move to the edge of both intervals, 20,5 - state = it.checkKey(BSON("" << 20 << "" << 5), - &keyEltsToUse, - &movePastKeyElts, - &elt, - &inc); + state = it.checkKey(BSON("" << 20 << "" << 5), &seekPoint); ASSERT_EQUALS(state, IndexBoundsChecker::VALID); // And a little beyond. - state = it.checkKey(BSON("" << 20 << "" << 5.1), - &keyEltsToUse, - &movePastKeyElts, - &elt, - &inc); + state = it.checkKey(BSON("" << 20 << "" << 5.1), &seekPoint); ASSERT_EQUALS(state, IndexBoundsChecker::DONE); } @@ -499,32 +426,20 @@ namespace { bounds.fields.push_back(barList); IndexBoundsChecker it(&bounds, BSON("foo" << 1 << "bar" << 1), 1); - int keyEltsToUse; - bool movePastKeyElts; - vector<const BSONElement*> elt(2); - vector<bool> inc(2); - + IndexSeekPoint seekPoint; IndexBoundsChecker::KeyState state; // Start at something in our range. - state = it.checkKey(BSON("" << 0 << "" << 1), - &keyEltsToUse, - &movePastKeyElts, - &elt, - &inc); + state = it.checkKey(BSON("" << 0 << "" << 1), &seekPoint); ASSERT_EQUALS(state, IndexBoundsChecker::VALID); // First key moves to next interval, second key needs to be advanced. - state = it.checkKey(BSON("" << 10 << "" << -1), - &keyEltsToUse, - &movePastKeyElts, - &elt, - &inc); + state = it.checkKey(BSON("" << 10 << "" << -1), &seekPoint); ASSERT_EQUALS(state, IndexBoundsChecker::MUST_ADVANCE); - ASSERT_EQUALS(keyEltsToUse, 1); - ASSERT_EQUALS(movePastKeyElts, false); - ASSERT_EQUALS(elt[1]->numberInt(), 0); - ASSERT_EQUALS(inc[1], false); + ASSERT_EQUALS(seekPoint.prefixLen, 1); + ASSERT_EQUALS(seekPoint.prefixExclusive, false); + ASSERT_EQUALS(seekPoint.keySuffix[1]->numberInt(), 0); + ASSERT_EQUALS(seekPoint.suffixInclusive[1], false); } TEST(IndexBoundsCheckerTest, SecondIntervalMustRewind) { @@ -543,46 +458,25 @@ namespace { ASSERT(bounds.isValidFor(idx, 1)); IndexBoundsChecker it(&bounds, idx, 1); - int keyEltsToUse; - bool movePastKeyElts; - - vector<const BSONElement*> elt(2); - vector<bool> inc(2); - + IndexSeekPoint seekPoint; IndexBoundsChecker::KeyState state; - state = it.checkKey(BSON("" << 25 << "" << 0), - &keyEltsToUse, - &movePastKeyElts, - &elt, - &inc); + state = it.checkKey(BSON("" << 25 << "" << 0), &seekPoint); ASSERT_EQUALS(state, IndexBoundsChecker::VALID); - state = it.checkKey(BSON("" << 25 << "" << 1), - &keyEltsToUse, - &movePastKeyElts, - &elt, - &inc); + state = it.checkKey(BSON("" << 25 << "" << 1), &seekPoint); ASSERT_EQUALS(state, IndexBoundsChecker::MUST_ADVANCE); - ASSERT_EQUALS(keyEltsToUse, 1); - ASSERT_EQUALS(movePastKeyElts, false); - ASSERT_EQUALS(elt[1]->numberInt(), 9); - ASSERT_EQUALS(inc[1], true); - - state = it.checkKey(BSON("" << 25 << "" << 9), - &keyEltsToUse, - &movePastKeyElts, - &elt, - &inc); + ASSERT_EQUALS(seekPoint.prefixLen, 1); + ASSERT_EQUALS(seekPoint.prefixExclusive, false); + ASSERT_EQUALS(seekPoint.keySuffix[1]->numberInt(), 9); + ASSERT_EQUALS(seekPoint.suffixInclusive[1], true); + + state = it.checkKey(BSON("" << 25 << "" << 9), &seekPoint); ASSERT_EQUALS(state, IndexBoundsChecker::VALID); // First key moved forward. The second key moved back to a valid state but it's behind // the interval that the checker thought it was in. - state = it.checkKey(BSON("" << 26 << "" << 0), - &keyEltsToUse, - &movePastKeyElts, - &elt, - &inc); + state = it.checkKey(BSON("" << 26 << "" << 0), &seekPoint); ASSERT_EQUALS(state, IndexBoundsChecker::VALID); } @@ -601,58 +495,34 @@ namespace { ASSERT(bounds.isValidFor(idx, 1)); IndexBoundsChecker it(&bounds, idx, 1); - int keyEltsToUse; - bool movePastKeyElts; - vector<const BSONElement*> elt(2); - vector<bool> inc(2); - + IndexSeekPoint seekPoint; IndexBoundsChecker::KeyState state; // Start at something in our range. - state = it.checkKey(BSON("" << 20 << "" << 5), - &keyEltsToUse, - &movePastKeyElts, - &elt, - &inc); + state = it.checkKey(BSON("" << 20 << "" << 5), &seekPoint); ASSERT_EQUALS(state, IndexBoundsChecker::VALID); // The rightmost key is past the range. We should be told to move past the key before the // one whose interval we exhausted. - state = it.checkKey(BSON("" << 20 << "" << 0), - &keyEltsToUse, - &movePastKeyElts, - &elt, - &inc); + state = it.checkKey(BSON("" << 20 << "" << 0), &seekPoint); ASSERT_EQUALS(state, IndexBoundsChecker::MUST_ADVANCE); - ASSERT_EQUALS(keyEltsToUse, 1); - ASSERT_EQUALS(movePastKeyElts, true); + ASSERT_EQUALS(seekPoint.prefixLen, 1); + ASSERT_EQUALS(seekPoint.prefixExclusive, true); // Move a little forward, but note that the rightmost key isn't in the interval yet. - state = it.checkKey(BSON("" << 19 << "" << 6), - &keyEltsToUse, - &movePastKeyElts, - &elt, - &inc); + state = it.checkKey(BSON("" << 19 << "" << 6), &seekPoint); ASSERT_EQUALS(state, IndexBoundsChecker::MUST_ADVANCE); - ASSERT_EQUALS(keyEltsToUse, 1); - ASSERT_EQUALS(movePastKeyElts, false); - ASSERT_EQUALS(elt[1]->numberInt(), 5); - ASSERT_EQUALS(inc[1], true); + ASSERT_EQUALS(seekPoint.prefixLen, 1); + ASSERT_EQUALS(seekPoint.prefixExclusive, false); + ASSERT_EQUALS(seekPoint.keySuffix[1]->numberInt(), 5); + ASSERT_EQUALS(seekPoint.suffixInclusive[1], true); // Move to the edge of both intervals - state = it.checkKey(BSON("" << 7 << "" << 0.01), - &keyEltsToUse, - &movePastKeyElts, - &elt, - &inc); + state = it.checkKey(BSON("" << 7 << "" << 0.01), &seekPoint); ASSERT_EQUALS(state, IndexBoundsChecker::VALID); // And a little beyond. - state = it.checkKey(BSON("" << 7 << "" << 0), - &keyEltsToUse, - &movePastKeyElts, - &elt, - &inc); + state = it.checkKey(BSON("" << 7 << "" << 0), &seekPoint); ASSERT_EQUALS(state, IndexBoundsChecker::DONE); } @@ -672,52 +542,32 @@ namespace { ASSERT(bounds.isValidFor(idx, -1)); IndexBoundsChecker it(&bounds, idx, -1); - int keyEltsToUse; - bool movePastKeyElts; - vector<const BSONElement*> elt(2); - vector<bool> inc(2); - + IndexSeekPoint seekPoint; IndexBoundsChecker::KeyState state; // Start at something in our range. - state = it.checkKey(BSON("" << 30 << "" << 1), - &keyEltsToUse, - &movePastKeyElts, - &elt, - &inc); + state = it.checkKey(BSON("" << 30 << "" << 1), &seekPoint); ASSERT_EQUALS(state, IndexBoundsChecker::VALID); // Second field moves past the end, but we're not done, since there's still an interval in // the previous field that the key hasn't advanced to. - state = it.checkKey(BSON("" << 30 << "" << 5), - &keyEltsToUse, - &movePastKeyElts, - &elt, - &inc); + state = it.checkKey(BSON("" << 30 << "" << 5), &seekPoint); ASSERT_EQUALS(state, IndexBoundsChecker::MUST_ADVANCE); - ASSERT_EQUALS(keyEltsToUse, 1); - ASSERT(movePastKeyElts); + ASSERT_EQUALS(seekPoint.prefixLen, 1); + ASSERT(seekPoint.prefixExclusive); // The next index key is in the second interval for 'foo' and there is a valid interval for // 'bar'. - state = it.checkKey(BSON("" << 20 << "" << 1), - &keyEltsToUse, - &movePastKeyElts, - &elt, - &inc); + state = it.checkKey(BSON("" << 20 << "" << 1), &seekPoint); ASSERT_EQUALS(state, IndexBoundsChecker::VALID); // The next index key is very close to the end of the open interval for foo, and it's past // the interval for 'bar'. Since the interval for foo is open, we are asked to move // forward, since we possibly could. - state = it.checkKey(BSON("" << 7.001 << "" << 5), - &keyEltsToUse, - &movePastKeyElts, - &elt, - &inc); + state = it.checkKey(BSON("" << 7.001 << "" << 5), &seekPoint); ASSERT_EQUALS(state, IndexBoundsChecker::MUST_ADVANCE); - ASSERT_EQUALS(keyEltsToUse, 1); - ASSERT(movePastKeyElts); + ASSERT_EQUALS(seekPoint.prefixLen, 1); + ASSERT(seekPoint.prefixExclusive); } // @@ -727,7 +577,7 @@ namespace { /** * Returns string representation of IndexBoundsChecker::Location. */ - string toString(IndexBoundsChecker::Location location) { + std::string toString(IndexBoundsChecker::Location location) { switch(location) { case IndexBoundsChecker::BEHIND: return "BEHIND"; case IndexBoundsChecker::WITHIN: return "WITHIN"; diff --git a/src/mongo/db/storage/SConscript b/src/mongo/db/storage/SConscript index cefbcf752f5..2627532afbd 100644 --- a/src/mongo/db/storage/SConscript +++ b/src/mongo/db/storage/SConscript @@ -42,7 +42,6 @@ env.Library( 'sorted_data_interface_test_cursor.cpp', 'sorted_data_interface_test_cursor_advanceto.cpp', 'sorted_data_interface_test_cursor_locate.cpp', - 'sorted_data_interface_test_cursor_position.cpp', 'sorted_data_interface_test_cursor_saverestore.cpp', 'sorted_data_interface_test_dupkeycheck.cpp', 'sorted_data_interface_test_fullvalidate.cpp', @@ -53,9 +52,11 @@ env.Library( 'sorted_data_interface_test_spaceused.cpp', 'sorted_data_interface_test_touch.cpp', 'sorted_data_interface_test_unindex.cpp', - ], - LIBDEPS=[] - ) + ], + LIBDEPS=[ + 'index_entry_comparison', + ], +) env.Library( target='record_store_test_harness', diff --git a/src/mongo/db/storage/devnull/devnull_kv_engine.cpp b/src/mongo/db/storage/devnull/devnull_kv_engine.cpp index 074637d147a..9ef68a68dbf 100644 --- a/src/mongo/db/storage/devnull/devnull_kv_engine.cpp +++ b/src/mongo/db/storage/devnull/devnull_kv_engine.cpp @@ -216,8 +216,9 @@ namespace mongo { virtual bool isEmpty(OperationContext* txn) { return true; } - virtual SortedDataInterface::Cursor* newCursor(OperationContext* txn, int direction) const { - return NULL; + virtual std::unique_ptr<SortedDataInterface::Cursor> newCursor(OperationContext* txn, + bool isForward) const { + return {}; } virtual Status initAsEmpty(OperationContext* txn) { return Status::OK(); } diff --git a/src/mongo/db/storage/in_memory/in_memory_btree_impl.cpp b/src/mongo/db/storage/in_memory/in_memory_btree_impl.cpp index ea303575bf5..1b937118bb3 100644 --- a/src/mongo/db/storage/in_memory/in_memory_btree_impl.cpp +++ b/src/mongo/db/storage/in_memory/in_memory_btree_impl.cpp @@ -39,6 +39,7 @@ #include "mongo/db/catalog/index_catalog_entry.h" #include "mongo/db/storage/index_entry_comparison.h" #include "mongo/db/storage/in_memory/in_memory_recovery_unit.h" +#include "mongo/stdx/memory.h" #include "mongo/util/mongoutils/str.h" namespace mongo { @@ -224,238 +225,225 @@ namespace { return Status::OK(); } - class ForwardCursor : public SortedDataInterface::Cursor { + class Cursor final : public SortedDataInterface::Cursor { public: - ForwardCursor(const IndexSet& data, OperationContext* txn) + Cursor(OperationContext* txn, const IndexSet& data, bool isForward) : _txn(txn), _data(data), + _forward(isForward), _it(data.end()) {} - - virtual int getDirection() const { return 1; } - - virtual bool isEOF() const { - return _it == _data.end(); - } - - virtual bool pointsToSamePlaceAs(const SortedDataInterface::Cursor& otherBase) const { - const ForwardCursor& other = static_cast<const ForwardCursor&>(otherBase); - invariant(&_data == &other._data); // iterators over same index - return _it == other._it; - } - - virtual bool locate(const BSONObj& keyRaw, const RecordId& loc) { - const BSONObj key = stripFieldNames(keyRaw); - _it = _data.lower_bound(IndexKeyEntry(key, loc)); // lower_bound is >= key - if ( _it == _data.end() ) { - return false; + + boost::optional<IndexKeyEntry> next(RequestedInfo parts) override { + if (_lastMoveWasRestore) { + // Return current position rather than advancing. + _lastMoveWasRestore = false; } - - if ( _it->key != key ) { - return false; + else { + advance(); + if (atEndPoint()) _isEOF = true; } - return _it->loc == loc; + if (_isEOF) return {}; + return *_it; } - - 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 = _data.lower_bound(IndexKeyEntry(IndexEntryComparison::makeQueryObject( - keyBegin, - keyBeginLen, - afterKey, - keyEnd, - keyEndInclusive, - 1), // forward - RecordId())); - } - - void advanceTo(const BSONObj &keyBegin, - int keyBeginLen, - bool afterKey, - const vector<const BSONElement*>& keyEnd, - const vector<bool>& keyEndInclusive) { - // XXX I think these do the same thing???? - customLocate(keyBegin, keyBeginLen, afterKey, keyEnd, keyEndInclusive); + + void setEndPosition(const BSONObj& key, bool inclusive) override { + if (key.isEmpty()) { + // This means scan to end of index. + _endState = {}; + return; + } + + // NOTE: this uses the opposite min/max rules as a normal seek because a forward + // scan should land after the key if inclusive and before if exclusive. + _endState = EndState(stripFieldNames(key), + _forward == inclusive ? RecordId::max() : RecordId::min()); + seekEndCursor(); } - virtual BSONObj getKey() const { - return _it->key; + boost::optional<IndexKeyEntry> seek(const BSONObj& key, bool inclusive, + RequestedInfo parts) override { + const BSONObj query = stripFieldNames(key); + locate(query, _forward == inclusive ? RecordId::min() : RecordId::max()); + _lastMoveWasRestore = false; + if (_isEOF) return {}; + dassert(inclusive ? compareKeys(_it->key, query) >= 0 + : compareKeys(_it->key, query) > 0); + return *_it; } - virtual RecordId getRecordId() const { - return _it->loc; + boost::optional<IndexKeyEntry> seek(const IndexSeekPoint& seekPoint, + RequestedInfo parts) override { + // Query encodes exclusive case so it can be treated as an inclusive query. + const BSONObj query = IndexEntryComparison::makeQueryObject(seekPoint, _forward); + locate(query, _forward ? RecordId::min() : RecordId::max()); + _lastMoveWasRestore = false; + if (_isEOF) return {}; + dassert(compareKeys(_it->key, query) >= 0); + return *_it; } - virtual void advance() { - if (_it != _data.end()) - ++_it; - } + void savePositioned() override { + // Keep original position if we haven't moved since the last restore. + _txn = nullptr; + if (_lastMoveWasRestore) return; - virtual void savePosition() { - if (_it == _data.end()) { - _savedAtEnd = true; + if (_isEOF) { + saveUnpositioned(); return; } _savedAtEnd = false; _savedKey = _it->key.getOwned(); _savedLoc = _it->loc; + // Doing nothing with end cursor since it will do full reseek on restore. } - virtual void restorePosition(OperationContext* txn) { - if (_savedAtEnd) { - _it = _data.end(); - } - else { - locate(_savedKey, _savedLoc); - } + void saveUnpositioned() override { + _txn = nullptr; + _savedAtEnd = true; + // Doing nothing with end cursor since it will do full reseek on restore. } - private: - - OperationContext* _txn; // not owned - const IndexSet& _data; - IndexSet::const_iterator _it; - - // For save/restorePosition since _it may be invalidated durring a yield. - bool _savedAtEnd; - BSONObj _savedKey; - RecordId _savedLoc; - - }; + void restore(OperationContext* txn) override { + _txn = txn; - // TODO see if this can share any code with ForwardIterator - class ReverseCursor : public SortedDataInterface::Cursor { - public: - ReverseCursor(const IndexSet& data, OperationContext* txn) - : _txn(txn), - _data(data), - _it(data.rend()) - {} + // Always do a full seek on restore. We cannot use our last position since index + // entries may have been inserted closer to our endpoint and we would need to move + // over them. + seekEndCursor(); - virtual int getDirection() const { return -1; } + if (_savedAtEnd) { + _isEOF = true; + return; + } + + // Need to find our position from the root. + locate(_savedKey, _savedLoc); - virtual bool isEOF() const { - return _it == _data.rend(); + _lastMoveWasRestore = _isEOF // We weren't EOF but now are. + || _data.value_comp().compare(*_it, {_savedKey, _savedLoc}) != 0; } - virtual bool pointsToSamePlaceAs(const SortedDataInterface::Cursor& otherBase) const { - const ReverseCursor& other = static_cast<const ReverseCursor&>(otherBase); - invariant(&_data == &other._data); // iterators over same index - return _it == other._it; + private: + bool atEndPoint() const { + return _endState && _it == _endState->it; } - virtual bool locate(const BSONObj& keyRaw, const RecordId& loc) { - const BSONObj key = stripFieldNames(keyRaw); - _it = lower_bound(IndexKeyEntry(key, loc)); // lower_bound is <= query - - if ( _it == _data.rend() ) { - return false; + // Advances once in the direction of the scan, updating _isEOF as needed. + // Does nothing if already _isEOF. + void advance() { + if (_isEOF) return; + if (_forward) { + if (_it != _data.end()) ++_it; + if (_it == _data.end() || atEndPoint()) _isEOF = true; } - - - if ( _it->key != key ) { - return false; + else { + if (_it == _data.begin() || _data.empty()) { + _isEOF = true; + } + else { + --_it; + } + if (atEndPoint()) _isEOF = true; } - - return _it->loc == loc; } - 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 - RecordId())); - } + bool atOrPastEndPointAfterSeeking() const { + if (_isEOF) return true; + if (!_endState) return false; + + const int cmp = _data.value_comp().compare(*_it, _endState->query); - void advanceTo(const BSONObj &keyBegin, - int keyBeginLen, - bool afterKey, - const vector<const BSONElement*>& keyEnd, - const vector<bool>& keyEndInclusive) { - // XXX I think these do the same thing???? - customLocate(keyBegin, keyBeginLen, afterKey, keyEnd, keyEndInclusive); - } + // We set up _endState->query to be in between the last in-range value and the first + // out-of-range value. In particular, it is constructed to never equal any legal + // index key. + dassert(cmp != 0); - virtual BSONObj getKey() const { - return _it->key; + if (_forward) { + // We may have landed after the end point. + return cmp > 0; + } + else { + // We may have landed before the end point. + return cmp < 0; + } } - virtual RecordId getRecordId() const { - return _it->loc; - } + 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. + } - virtual void advance() { - if (_it != _data.rend()) - ++_it; + if (atOrPastEndPointAfterSeeking()) _isEOF = true; } - virtual void savePosition() { - if (_it == _data.rend()) { - _savedAtEnd = true; - return; - } - - _savedAtEnd = false; - _savedKey = _it->key.getOwned(); - _savedLoc = _it->loc; + // Returns comparison relative to direction of scan. If rhs would be seen later, returns + // a positive value. + int compareKeys(const BSONObj& lhs, const BSONObj& rhs) const { + int cmp = _data.value_comp().compare({lhs, RecordId()}, {rhs, RecordId()}); + return _forward ? cmp : -cmp; } - virtual void restorePosition(OperationContext* txn) { - if (_savedAtEnd) { - _it = _data.rend(); - } - else { - locate(_savedKey, _savedLoc); + void seekEndCursor() { + if (!_endState || _data.empty()) return; + + auto it = _data.lower_bound(_endState->query); + if (!_forward) { + // lower_bound lands us on or after query. Reverse cursors must be on or before. + if (it == _data.end() || _data.value_comp().compare(*it, + _endState->query) > 0) { + if (it == _data.begin()) { + it = _data.end(); // all existing data in range. + } + else { + --it; + } + } } - } - private: - /** - * Returns the first entry <= query. This is equivalent to ForwardCursors use of - * _data.lower_bound which returns the first entry >= query. - */ - IndexSet::const_reverse_iterator lower_bound(const IndexKeyEntry& query) const { - // using upper_bound since we want to the right-most entry matching the query. - IndexSet::const_iterator it = _data.upper_bound(query); - - // upper_bound returns the entry to the right of the one we want. Helpfully, - // converting to a reverse_iterator moves one to the left. This also correctly - // handles the case where upper_bound returns end() by converting to rbegin(), - // meaning that all data is to the right of the query. - return IndexSet::const_reverse_iterator(it); + if (it != _data.end()) dassert(compareKeys(it->key, _endState->query.key) >= 0); + _endState->it = it; } OperationContext* _txn; // not owned const IndexSet& _data; - IndexSet::const_reverse_iterator _it; - - // For save/restorePosition since _it may be invalidated durring a yield. - bool _savedAtEnd; + const bool _forward; + bool _isEOF = true; + IndexSet::const_iterator _it; + + struct EndState { + EndState(BSONObj key, RecordId loc) : query(std::move(key), loc) {} + + IndexKeyEntry query; + IndexSet::const_iterator it; + }; + boost::optional<EndState> _endState; + + // Used by next to decide to return current position rather than moving. Should be reset + // to false by any operation that moves the cursor, other than subsequent save/restore + // pairs. + bool _lastMoveWasRestore = false; + + // For save/restore since _it may be invalidated during a yield. + bool _savedAtEnd = false; BSONObj _savedKey; RecordId _savedLoc; }; - virtual SortedDataInterface::Cursor* newCursor(OperationContext* txn, int direction) const { - if (direction == 1) - return new ForwardCursor(*_data, txn); - - invariant(direction == -1); - return new ReverseCursor(*_data, txn); + virtual std::unique_ptr<SortedDataInterface::Cursor> newCursor( + OperationContext* txn, + bool isForward) const { + return stdx::make_unique<Cursor>(txn, *_data, isForward); } virtual Status initAsEmpty(OperationContext* txn) { diff --git a/src/mongo/db/storage/index_entry_comparison.cpp b/src/mongo/db/storage/index_entry_comparison.cpp index c609933eb38..4a5d4fdab1a 100644 --- a/src/mongo/db/storage/index_entry_comparison.cpp +++ b/src/mongo/db/storage/index_entry_comparison.cpp @@ -27,11 +27,17 @@ */ #include "mongo/platform/basic.h" +#include <ostream> + #include "mongo/db/jsobj.h" #include "mongo/db/storage/index_entry_comparison.h" namespace mongo { + std::ostream& operator<<(std::ostream& stream, const IndexKeyEntry& entry) { + return stream << entry.key << '@' << entry.loc; + } + // Due to the limitations of various APIs, we need to use the same type (IndexKeyEntry) // for both the stored data and the "query". We cheat and encode extra information in the // first byte of the field names in the query. This works because all stored objects should diff --git a/src/mongo/db/storage/index_entry_comparison.h b/src/mongo/db/storage/index_entry_comparison.h index c1415c2fba2..dbdd15e0368 100644 --- a/src/mongo/db/storage/index_entry_comparison.h +++ b/src/mongo/db/storage/index_entry_comparison.h @@ -28,6 +28,8 @@ #pragma once +#include <iosfwd> +#include <tuple> #include <vector> #include "mongo/db/jsobj.h" @@ -40,17 +42,98 @@ namespace mongo { * and a disk location. */ struct IndexKeyEntry { - IndexKeyEntry(const BSONObj& key, RecordId loc) :key(key), loc(loc) {} + IndexKeyEntry(BSONObj key, RecordId loc) :key(std::move(key)), loc(std::move(loc)) {} BSONObj key; RecordId loc; }; + std::ostream& operator<<(std::ostream& stream, const IndexKeyEntry& entry); + + inline bool operator==(const IndexKeyEntry& lhs, const IndexKeyEntry& rhs) { + return std::tie(lhs.key, lhs.loc) == std::tie(rhs.key, rhs.loc); + } + + inline bool operator!=(const IndexKeyEntry& lhs, const IndexKeyEntry& rhs) { + return std::tie(lhs.key, lhs.loc) != std::tie(rhs.key, rhs.loc); + } + + /** + * Describes a query that can be compared against an IndexKeyEntry in a way that allows + * expressing exclusiveness on a prefix of the key. This is mostly used to express a location to + * seek to in an index that may not be representable as a valid key. + * + * The "key" used for comparison is the concatenation of the first 'prefixLen' elements of + * 'keyPrefix' followed by the last 'keySuffix.size() - prefixLen' elements of + * 'keySuffix'. + * + * The comparison is exclusive if either 'prefixExclusive' is true or if there are any false + * values in 'suffixInclusive' that are false at index >= 'prefixLen'. + * + * Portions of the key following the first exclusive part may be ignored. + * + * e.g. + * + * Suppose that + * + * keyPrefix = { "" : 1, "" : 2 } + * prefixLen = 1 + * prefixExclusive = false + * keySuffix = [ IGNORED, { "" : 5 } ] + * suffixInclusive = [ IGNORED, false ] + * + * ==> key is { "" : 1, "" : 5 } + * with the comparison being done exclusively + * + * Suppose that + * + * keyPrefix = { "" : 1, "" : 2 } + * prefixLen = 1 + * prefixExclusive = true + * keySuffix = IGNORED + * suffixInclusive = IGNORED + * + * ==> represented key is { "" : 1 } + * with the comparison being done exclusively + * + * 'prefixLen = 0' and 'prefixExclusive = true' are mutually incompatible. + * + * @see IndexEntryComparison::makeQueryObject + */ + struct IndexSeekPoint { + BSONObj keyPrefix; + + /** + * Use this many fields in 'keyPrefix'. + */ + int prefixLen = 0; + + /** + * If true, compare exclusively on just the fields on keyPrefix and ignore the suffix. + */ + bool prefixExclusive = false; + + /** + * Elements starting at index 'prefixLen' are logically appended to the prefix. + * The elements before index 'prefixLen' should be ignored. + */ + std::vector<const BSONElement*> keySuffix; + + /** + * If the ith element is false, ignore indexes > i in keySuffix and treat the + * concatenated key as exclusive. + * The elements before index 'prefixLen' should be ignored. + * + * Must have identical size as keySuffix. + */ + std::vector<bool> suffixInclusive; + }; + /** * Compares two different IndexKeyEntry instances. - * The existense of compound indexes necessitates some complicated logic. This is meant to - * support the implementation of the SortedDataInterface::customLocate() and - * SortedDataInterface::advanceTo() methods, which require fine-grained control over whether the + * The existence of compound indexes necessitates some complicated logic. This is meant to + * support the comparisons of IndexKeyEntries (that are stored in an index) with IndexSeekPoints + * (that were encoded with makeQueryObject) to support fine-grained control over whether the * ranges of various keys comprising a compound index are inclusive or exclusive. */ class IndexEntryComparison { @@ -105,6 +188,15 @@ namespace mongo { const std::vector<bool>& suffixInclusive, const int cursorDirection); + static BSONObj makeQueryObject(const IndexSeekPoint& seekPoint, bool isForward) { + return makeQueryObject(seekPoint.keyPrefix, + seekPoint.prefixLen, + seekPoint.prefixExclusive, + seekPoint.keySuffix, + seekPoint.suffixInclusive, + isForward ? 1 : -1); + } + private: // Ordering is used in comparison() to compare BSONElements const Ordering _order; diff --git a/src/mongo/db/storage/key_string.cpp b/src/mongo/db/storage/key_string.cpp index 2b3391e1d42..2cd548aadf7 100644 --- a/src/mongo/db/storage/key_string.cpp +++ b/src/mongo/db/storage/key_string.cpp @@ -262,22 +262,24 @@ namespace mongo { void KeyString::resetToKey(const BSONObj& obj, Ordering ord, RecordId recordId) { resetToEmpty(); - _appendAllElementsForIndexing(obj, ord); + _appendAllElementsForIndexing(obj, ord, kInclusive); appendRecordId(recordId); } - void KeyString::resetToKey(const BSONObj& obj, Ordering ord) { + void KeyString::resetToKey(const BSONObj& obj, Ordering ord, Discriminator discriminator) { resetToEmpty(); - _appendAllElementsForIndexing(obj, ord); + _appendAllElementsForIndexing(obj, ord, discriminator); } // ---------------------------------------------------------------------- // ----------- APPEND CODE ------------------------------------------- // ---------------------------------------------------------------------- - void KeyString::_appendAllElementsForIndexing(const BSONObj& obj, Ordering ord) { + void KeyString::_appendAllElementsForIndexing(const BSONObj& obj, Ordering ord, + Discriminator discriminator) { int elemCount = 0; - BSONForEach(elem, obj) { + BSONObjIterator it(obj); + while (auto elem = it.next()) { const int elemIdx = elemCount++; const bool invert = (ord.get(elemIdx) == -1); @@ -285,12 +287,30 @@ namespace mongo { dassert(elem.fieldNameSize() < 3); // fieldNameSize includes the NUL - // These are used in IndexEntryComparison::makeQueryObject() - switch (*elem.fieldName()) { - case 'l': _append(kLess, false); break; - case 'g': _append(kGreater, false); break; + // IndexEntryComparison::makeQueryObject() encodes a discriminator in the first byte of + // the field name. This discriminator overrides the passed in one. Normal elements only + // have the NUL byte terminator. Entries stored in an index are not allowed to have a + // discriminator. + if (char ch = *elem.fieldName()) { + // l for less / g for greater. + invariant(ch == 'l' || ch == 'g'); + discriminator = ch == 'l' ? kExclusiveBefore : kExclusiveAfter; + invariant(!it.more()); } } + + // The discriminator forces this KeyString to compare Less/Greater than any KeyString with + // the same prefix of keys. As an example, this can be used to land on the first key in the + // index with the value "a" regardless of the RecordId. In compound indexes it can use a + // prefix of the full key to ignore the later keys. + switch (discriminator) { + case kExclusiveBefore: _append(kLess, false); break; + case kExclusiveAfter: _append(kGreater, false); break; + case kInclusive: break; // No discriminator byte. + } + + // TODO consider omitting kEnd when using a discriminator byte. It is not a storage format + // change since keystrings with discriminators are not allowed to be stored. _append(kEnd, false); } diff --git a/src/mongo/db/storage/key_string.h b/src/mongo/db/storage/key_string.h index 5bd9cf7c8a9..0d895941f9e 100644 --- a/src/mongo/db/storage/key_string.h +++ b/src/mongo/db/storage/key_string.h @@ -181,14 +181,20 @@ namespace mongo { uint8_t _buf[1/*size*/ + kMaxBytesNeeded]; }; + enum Discriminator { + kInclusive, // Anything to be stored in an index must use this. + kExclusiveBefore, + kExclusiveAfter, + }; + KeyString() {} KeyString(const BSONObj& obj, Ordering ord, RecordId recordId) { resetToKey(obj, ord, recordId); } - KeyString(const BSONObj& obj, Ordering ord) { - resetToKey(obj, ord); + KeyString(const BSONObj& obj, Ordering ord, Discriminator discriminator = kInclusive) { + resetToKey(obj, ord, discriminator); } explicit KeyString(RecordId rid) { @@ -222,7 +228,7 @@ namespace mongo { } void resetToKey(const BSONObj& obj, Ordering ord, RecordId recordId); - void resetToKey(const BSONObj& obj, Ordering ord); + void resetToKey(const BSONObj& obj, Ordering ord, Discriminator discriminator = kInclusive); void resetFromBuffer(const void* buffer, size_t size) { _buffer.reset(); memcpy(_buffer.skip(size), buffer, size); @@ -243,7 +249,8 @@ namespace mongo { private: - void _appendAllElementsForIndexing(const BSONObj& obj, Ordering ord); + void _appendAllElementsForIndexing(const BSONObj& obj, Ordering ord, + Discriminator discriminator); void _appendBool(bool val, bool invert); void _appendDate(Date_t val, bool invert); diff --git a/src/mongo/db/storage/mmap_v1/btree/btree_interface.cpp b/src/mongo/db/storage/mmap_v1/btree/btree_interface.cpp index fbd1e6ad063..4c0a436d27c 100644 --- a/src/mongo/db/storage/mmap_v1/btree/btree_interface.cpp +++ b/src/mongo/db/storage/mmap_v1/btree/btree_interface.cpp @@ -35,15 +35,17 @@ #include "mongo/db/operation_context.h" #include "mongo/db/storage/mmap_v1/btree/btree_logic.h" #include "mongo/db/storage/mmap_v1/record_store_v1_base.h" +#include "mongo/stdx/memory.h" namespace mongo { +namespace { using boost::scoped_ptr; using std::string; using std::vector; template <class OnDiskFormat> - class BtreeBuilderInterfaceImpl : public SortedDataBuilderInterface { + class BtreeBuilderInterfaceImpl final : public SortedDataBuilderInterface { public: BtreeBuilderInterfaceImpl(OperationContext* trans, typename BtreeLogic<OnDiskFormat>::Builder* builder) @@ -61,7 +63,7 @@ namespace mongo { }; template <class OnDiskFormat> - class BtreeInterfaceImpl : public SortedDataInterface { + class BtreeInterfaceImpl final : public SortedDataInterface { public: BtreeInterfaceImpl(HeadManager* headManager, RecordStore* recordStore, @@ -128,135 +130,213 @@ namespace mongo { return _btree->touch(txn); } - class Cursor : public SortedDataInterface::Cursor { + class Cursor final : public SortedDataInterface::Cursor { public: Cursor(OperationContext* txn, const BtreeLogic<OnDiskFormat>* btree, - int direction) + bool forward) : _txn(txn), _btree(btree), - _direction(direction), - _bucket(btree->getHead(txn)), // XXX this shouldn't be nessisary, but is. - _ofs(0) { - } + _direction(forward ? 1 : -1), + _ofs(0) + {} + + boost::optional<IndexKeyEntry> next(RequestedInfo parts) override { + if (isEOF()) return {}; + if (_lastMoveWasRestore) { + // Return current position rather than advancing. + _lastMoveWasRestore = false; + } + else { + _btree->advance(_txn, &_bucket, &_ofs, _direction); + } - virtual int getDirection() const { return _direction; } + if (atEndPoint()) markEOF(); + return curr(parts); + } - virtual bool isEOF() const { return _bucket.isNull(); } + void setEndPosition(const BSONObj& key, bool inclusive) override { + if (key.isEmpty()) { + // This means scan to end of index. + _endState = {}; + return; + } - virtual bool pointsToSamePlaceAs(const SortedDataInterface::Cursor& otherBase) const { - const Cursor& other = static_cast<const Cursor&>(otherBase); - if (isEOF()) - return other.isEOF(); + _endState = {{key, inclusive}}; + seekEndCursor(); // Completes initialization of _endState. + } - return _bucket == other._bucket && _ofs == other._ofs; + boost::optional<IndexKeyEntry> seek(const BSONObj& key, bool inclusive, + RequestedInfo parts) override { + locate(key, inclusive == forward() ? RecordId::min() : RecordId::max()); + _lastMoveWasRestore = false; + if (isEOF()) return {}; + dassert(inclusive ? compareKeys(getKey(), key) >= 0 + : compareKeys(getKey(), key) > 0); + return curr(parts); } - virtual void aboutToDeleteBucket(const RecordId& bucket) { - if (_bucket.toRecordId() == bucket) - _ofs = -1; - } - virtual bool locate(const BSONObj& key, const RecordId& loc) { - return _btree->locate(_txn, key, DiskLoc::fromRecordId(loc), _direction, &_ofs, - &_bucket); + boost::optional<IndexKeyEntry> seek(const IndexSeekPoint& seekPoint, + RequestedInfo parts) override { + bool canUseAdvanceTo = false; + if (!isEOF()) { + int cmp = _btree->customBSONCmp(getKey(), seekPoint, _direction); + + // advanceTo requires that we are positioned "earlier" in the index than the + // seek point, in scan order. + canUseAdvanceTo = forward() ? cmp < 0 : cmp > 0; + } + + + if (canUseAdvanceTo) { + // This takes advantage of current location. + _btree->advanceTo(_txn, &_bucket, &_ofs, seekPoint, _direction); + } + else { + // Start at root. + _bucket = _btree->getHead(_txn); + _ofs = 0; + _btree->customLocate(_txn, &_bucket, &_ofs, seekPoint, _direction); + } + + _lastMoveWasRestore = false; + + if (atOrPastEndPointAfterSeeking()) markEOF(); + return curr(parts); } - virtual void customLocate(const BSONObj& keyBegin, - int keyBeginLen, - bool afterKey, - const vector<const BSONElement*>& keyEnd, - const vector<bool>& keyEndInclusive) { - - _btree->customLocate(_txn, - &_bucket, - &_ofs, - keyBegin, - keyBeginLen, - afterKey, - keyEnd, - keyEndInclusive, - _direction); + void savePositioned() override { + _txn = nullptr; + if (!isEOF()) { + _saved.bucket = _bucket; + _btree->savedCursors()->registerCursor(&_saved); + // Don't want to change saved position if we only moved during restore. + if (!_lastMoveWasRestore) { + _saved.key = getKey().getOwned(); + _saved.loc = getDiskLoc(); + } + } + // Doing nothing with end cursor since it will do full reseek on restore. } - void advanceTo(const BSONObj &keyBegin, - int keyBeginLen, - bool afterKey, - const vector<const BSONElement*>& keyEnd, - const vector<bool>& keyEndInclusive) { - - _btree->advanceTo(_txn, - &_bucket, - &_ofs, - keyBegin, - keyBeginLen, - afterKey, - keyEnd, - keyEndInclusive, - _direction); + void saveUnpositioned() override { + _txn = nullptr; + // Don't leak our registration if savePositioned() was previously called. + if (!_saved.bucket.isNull()) _btree->savedCursors()->unregisterCursor(&_saved); + + _saved.bucket = DiskLoc(); + markEOF(); } - virtual BSONObj getKey() const { - return _btree->getKey(_txn, _bucket, _ofs); + void restore(OperationContext* txn) override { + _txn = txn; + + // Always do a full seek on restore. We cannot use our last position since index + // entries may have been inserted closer to our endpoint and we would need to move + // over them. + seekEndCursor(); + + if (isEOF()) return; + + // guard against accidental double restore + invariant(!_saved.bucket.isNull()); + _saved.bucket = DiskLoc(); + + if (_btree->savedCursors()->unregisterCursor(&_saved)) { + // We can use the fast restore mechanism. + _btree->restorePosition(_txn, _saved.key, _saved.loc, _direction, + &_bucket, &_ofs); + } + else { + // Need to find our position from the root. + locate(_saved.key, _saved.loc.toRecordId()); + } + + _lastMoveWasRestore = isEOF() // We weren't EOF but now are. + || getDiskLoc() != _saved.loc + || compareKeys(getKey(), _saved.key) != 0; } - DiskLoc getDiskLoc() const { - return _btree->getDiskLoc(_txn, _bucket, _ofs); + private: + bool isEOF() const { return _bucket.isNull(); } + void markEOF() { _bucket = DiskLoc(); } + + boost::optional<IndexKeyEntry> curr(RequestedInfo parts) { + if (isEOF()) return {}; + return {{(parts & kWantKey) ? getKey() : BSONObj(), + (parts & kWantLoc) ? getDiskLoc().toRecordId() : RecordId()}}; } - virtual RecordId getRecordId() const { - return getDiskLoc().toRecordId(); + bool atEndPoint() const { + return _endState + && _bucket == _endState->bucket + && (isEOF() || _ofs == _endState->ofs); } - virtual void advance() { - if (!_bucket.isNull()) { - _btree->advance(_txn, &_bucket, &_ofs, _direction); - } + bool atOrPastEndPointAfterSeeking() const { + if (!_endState) return false; + if (isEOF()) return true; + + int cmp = compareKeys(getKey(), _endState->key); + return _endState->inclusive ? cmp > 0 : cmp >= 0; } - virtual void savePosition() { - if (!_bucket.isNull()) { - _saved.bucket = _bucket; - _saved.key = getKey().getOwned(); - _saved.loc = getDiskLoc(); - _btree->savedCursors()->registerCursor(&_saved); - } + void locate(const BSONObj& key, const RecordId& loc) { + _btree->locate(_txn, key, DiskLoc::fromRecordId(loc), _direction, &_ofs, &_bucket); + if (atOrPastEndPointAfterSeeking()) markEOF(); } - virtual void restorePosition(OperationContext* txn) { - if (!_bucket.isNull()) { - invariant(!_saved.bucket.isNull()); - _saved.bucket = DiskLoc(); // guard against accidental double restore + // Returns comparison relative to direction of scan. If rhs would be seen later, returns + // a positive value. + int compareKeys(const BSONObj& lhs, const BSONObj& rhs) const { + int cmp = lhs.woCompare(rhs, _btree->ordering(), /*considerFieldName*/false); + return forward() ? cmp : -cmp; + } - if (!_btree->savedCursors()->unregisterCursor(&_saved)) { - locate(_saved.key, _saved.loc.toRecordId()); - return; - } + BSONObj getKey() const { return _btree->getKey(_txn, _bucket, _ofs); } + DiskLoc getDiskLoc() const { return _btree->getDiskLoc(_txn, _bucket, _ofs); } - _btree->restorePosition(_txn, - _saved.key, - _saved.loc, - _direction, - &_bucket, - &_ofs); - } + void seekEndCursor() { + if (!_endState) return; + _btree->locate(_txn, + _endState->key, + forward() == _endState->inclusive ? DiskLoc::max() : DiskLoc::min(), + _direction, + &_endState->ofs, &_endState->bucket); // pure out params. } - private: + bool forward() const { return _direction == 1; } + OperationContext* _txn; // not owned const BtreeLogic<OnDiskFormat>* const _btree; const int _direction; DiskLoc _bucket; int _ofs; - - // Only used by save/restorePosition() if _bucket is non-Null. + + struct EndState { + BSONObj key; + bool inclusive; + DiskLoc bucket; + int ofs; + }; + boost::optional<EndState> _endState; + + // Used by next to decide to return current position rather than moving. Should be reset + // to false by any operation that moves the cursor, other than subsequent save/restore + // pairs. + bool _lastMoveWasRestore = false; + + // Only used by save/restore() if _bucket is non-Null. SavedCursorRegistry::SavedCursor _saved; }; - virtual Cursor* newCursor(OperationContext* txn, int direction) const { - return new Cursor(txn, _btree.get(), direction); + virtual std::unique_ptr<SortedDataInterface::Cursor> newCursor( + OperationContext* txn, + bool isForward = true) const { + return stdx::make_unique<Cursor>(txn, _btree.get(), isForward); } virtual Status initAsEmpty(OperationContext* txn) { @@ -266,6 +346,7 @@ namespace mongo { private: scoped_ptr<BtreeLogic<OnDiskFormat> > _btree; }; +} // namespace SortedDataInterface* getMMAPV1Interface(HeadManager* headManager, RecordStore* recordStore, diff --git a/src/mongo/db/storage/mmap_v1/btree/btree_logic.cpp b/src/mongo/db/storage/mmap_v1/btree/btree_logic.cpp index 62f77773dd1..98f1d1497e9 100644 --- a/src/mongo/db/storage/mmap_v1/btree/btree_logic.cpp +++ b/src/mongo/db/storage/mmap_v1/btree/btree_logic.cpp @@ -678,25 +678,11 @@ namespace mongo { void BtreeLogic<BtreeLayout>::customLocate(OperationContext* txn, DiskLoc* locInOut, int* keyOfsInOut, - const BSONObj& keyBegin, - int keyBeginLen, - bool afterKey, - const vector<const BSONElement*>& keyEnd, - const vector<bool>& keyEndInclusive, + const IndexSeekPoint& seekPoint, int direction) const { pair<DiskLoc, int> unused; - customLocate(txn, - locInOut, - keyOfsInOut, - keyBegin, - keyBeginLen, - afterKey, - keyEnd, - keyEndInclusive, - direction, - unused); - + customLocate(txn, locInOut, keyOfsInOut, seekPoint, direction, unused); skipUnusedKeys(txn, locInOut, keyOfsInOut, direction); } @@ -724,23 +710,10 @@ namespace mongo { void BtreeLogic<BtreeLayout>::advanceTo(OperationContext* txn, DiskLoc* thisLocInOut, int* keyOfsInOut, - const BSONObj &keyBegin, - int keyBeginLen, - bool afterKey, - const vector<const BSONElement*>& keyEnd, - const vector<bool>& keyEndInclusive, + const IndexSeekPoint& seekPoint, int direction) const { - advanceToImpl(txn, - thisLocInOut, - keyOfsInOut, - keyBegin, - keyBeginLen, - afterKey, - keyEnd, - keyEndInclusive, - direction); - + advanceToImpl(txn, thisLocInOut, keyOfsInOut, seekPoint, direction); skipUnusedKeys(txn, thisLocInOut, keyOfsInOut, direction); } @@ -757,11 +730,7 @@ namespace mongo { void BtreeLogic<BtreeLayout>::advanceToImpl(OperationContext* txn, DiskLoc* thisLocInOut, int* keyOfsInOut, - const BSONObj &keyBegin, - int keyBeginLen, - bool afterKey, - const vector<const BSONElement*>& keyEnd, - const vector<bool>& keyEndInclusive, + const IndexSeekPoint& seekPoint, int direction) const { BucketType* bucket = getBucket(txn, *thisLocInOut); @@ -773,12 +742,7 @@ namespace mongo { l = *keyOfsInOut; h = bucket->n - 1; int cmpResult = customBSONCmp(getFullKey(bucket, h).data.toBson(), - keyBegin, - keyBeginLen, - afterKey, - keyEnd, - keyEndInclusive, - _ordering, + seekPoint, direction); dontGoUp = (cmpResult >= 0); } @@ -786,12 +750,7 @@ namespace mongo { l = 0; h = *keyOfsInOut; int cmpResult = customBSONCmp(getFullKey(bucket, l).data.toBson(), - keyBegin, - keyBeginLen, - afterKey, - keyEnd, - keyEndInclusive, - _ordering, + seekPoint, direction); dontGoUp = (cmpResult <= 0); } @@ -803,12 +762,7 @@ namespace mongo { if (!customFind(txn, l, h, - keyBegin, - keyBeginLen, - afterKey, - keyEnd, - keyEndInclusive, - _ordering, + seekPoint, direction, thisLocInOut, keyOfsInOut, @@ -825,24 +779,14 @@ namespace mongo { if (direction > 0) { if (customBSONCmp(getFullKey(bucket, bucket->n - 1).data.toBson(), - keyBegin, - keyBeginLen, - afterKey, - keyEnd, - keyEndInclusive, - _ordering, + seekPoint, direction) >= 0 ) { break; } } else { if (customBSONCmp(getFullKey(bucket, 0).data.toBson(), - keyBegin, - keyBeginLen, - afterKey, - keyEnd, - keyEndInclusive, - _ordering, + seekPoint, direction) <= 0) { break; } @@ -850,27 +794,14 @@ namespace mongo { } } - customLocate(txn, - thisLocInOut, - keyOfsInOut, - keyBegin, - keyBeginLen, - afterKey, - keyEnd, - keyEndInclusive, - direction, - bestParent); + customLocate(txn, thisLocInOut, keyOfsInOut, seekPoint, direction, bestParent); } template <class BtreeLayout> void BtreeLogic<BtreeLayout>::customLocate(OperationContext* txn, DiskLoc* locInOut, int* keyOfsInOut, - const BSONObj& keyBegin, - int keyBeginLen, - bool afterKey, - const vector<const BSONElement*>& keyEnd, - const vector<bool>& keyEndInclusive, + const IndexSeekPoint& seekPoint, int direction, pair<DiskLoc, int>& bestParent) const { @@ -890,16 +821,7 @@ namespace mongo { int z = (direction > 0) ? 0 : h; // leftmost/rightmost key may possibly be >=/<= search key - int res = customBSONCmp(getFullKey(bucket, z).data.toBson(), - keyBegin, - keyBeginLen, - afterKey, - keyEnd, - keyEndInclusive, - _ordering, - direction); - - + int res = customBSONCmp(getFullKey(bucket, z).data.toBson(), seekPoint, direction); if (direction * res >= 0) { DiskLoc next; *keyOfsInOut = z; @@ -923,15 +845,7 @@ namespace mongo { } } - res = customBSONCmp(getFullKey(bucket, h - z).data.toBson(), - keyBegin, - keyBeginLen, - afterKey, - keyEnd, - keyEndInclusive, - _ordering, - direction); - + res = customBSONCmp(getFullKey(bucket, h - z).data.toBson(), seekPoint, direction); if (direction * res < 0) { DiskLoc next; if (direction > 0) { @@ -957,12 +871,7 @@ namespace mongo { if (!customFind(txn, l, h, - keyBegin, - keyBeginLen, - afterKey, - keyEnd, - keyEndInclusive, - _ordering, + seekPoint, direction, locInOut, keyOfsInOut, @@ -978,12 +887,7 @@ namespace mongo { bool BtreeLogic<BtreeLayout>::customFind(OperationContext* txn, int low, int high, - const BSONObj& keyBegin, - int keyBeginLen, - bool afterKey, - const vector<const BSONElement*>& keyEnd, - const vector<bool>& keyEndInclusive, - const Ordering& order, + const IndexSeekPoint& seekPoint, int direction, DiskLoc* thisLocInOut, int* keyOfsInOut, @@ -1007,15 +911,7 @@ namespace mongo { int middle = low + (high - low) / 2; - int cmp = customBSONCmp(getFullKey(bucket, middle).data.toBson(), - keyBegin, - keyBeginLen, - afterKey, - keyEnd, - keyEndInclusive, - order, - direction); - + int cmp = customBSONCmp(getFullKey(bucket, middle).data.toBson(), seekPoint, direction); if (cmp < 0) { low = middle; } @@ -1047,50 +943,45 @@ namespace mongo { */ // static template <class BtreeLayout> - int BtreeLogic<BtreeLayout>::customBSONCmp(const BSONObj& l, - const BSONObj& rBegin, - int rBeginLen, - bool rSup, - const vector<const BSONElement*>& rEnd, - const vector<bool>& rEndInclusive, - const Ordering& o, + int BtreeLogic<BtreeLayout>::customBSONCmp(const BSONObj& left, + const IndexSeekPoint& right, int direction) const { // XXX: make this readable - BSONObjIterator ll( l ); - BSONObjIterator rr( rBegin ); - vector< const BSONElement * >::const_iterator rr2 = rEnd.begin(); - vector< bool >::const_iterator inc = rEndInclusive.begin(); + dassert(right.keySuffix.size() == right.suffixInclusive.size()); + + BSONObjIterator ll( left ); + BSONObjIterator rr( right.keyPrefix ); unsigned mask = 1; - for( int i = 0; i < rBeginLen; ++i, mask <<= 1 ) { + size_t i = 0; + for( ; i < size_t(right.prefixLen); ++i, mask <<= 1 ) { BSONElement lll = ll.next(); BSONElement rrr = rr.next(); - ++rr2; - ++inc; int x = lll.woCompare( rrr, false ); - if ( o.descending( mask ) ) + if ( _ordering.descending( mask ) ) x = -x; if ( x != 0 ) return x; } - if ( rSup ) { + if (right.prefixExclusive) { return -direction; } - for( ; ll.more(); mask <<= 1 ) { + for( ; i < right.keySuffix.size(); ++i, mask <<= 1 ) { + if (!ll.more()) + return -direction; + BSONElement lll = ll.next(); - BSONElement rrr = **rr2; - ++rr2; + BSONElement rrr = *right.keySuffix[i]; int x = lll.woCompare( rrr, false ); - if ( o.descending( mask ) ) + if ( _ordering.descending( mask ) ) x = -x; if ( x != 0 ) return x; - if ( !*inc ) { + if ( !right.suffixInclusive[i] ) { return -direction; } - ++inc; } - return 0; + return ll.more() ? direction : 0; } template <class BtreeLayout> diff --git a/src/mongo/db/storage/mmap_v1/btree/btree_logic.h b/src/mongo/db/storage/mmap_v1/btree/btree_logic.h index 942f2b41365..5b4fdc8205e 100644 --- a/src/mongo/db/storage/mmap_v1/btree/btree_logic.h +++ b/src/mongo/db/storage/mmap_v1/btree/btree_logic.h @@ -34,6 +34,7 @@ #include "mongo/db/catalog/index_catalog_entry.h" #include "mongo/db/jsobj.h" #include "mongo/db/operation_context.h" +#include "mongo/db/storage/index_entry_comparison.h" #include "mongo/db/storage/mmap_v1/btree/btree_ondisk.h" #include "mongo/db/storage/mmap_v1/btree/key.h" #include "mongo/db/storage/mmap_v1/diskloc.h" @@ -196,21 +197,13 @@ namespace mongo { void customLocate(OperationContext* txn, DiskLoc* locInOut, int* keyOfsInOut, - const BSONObj& keyBegin, - int keyBeginLen, - bool afterKey, - const std::vector<const BSONElement*>& keyEnd, - const std::vector<bool>& keyEndInclusive, + const IndexSeekPoint& seekPoint, int direction) const; void advanceTo(OperationContext*, DiskLoc* thisLocInOut, int* keyOfsInOut, - const BSONObj &keyBegin, - int keyBeginLen, - bool afterKey, - const std::vector<const BSONElement*>& keyEnd, - const std::vector<bool>& keyEndInclusive, + const IndexSeekPoint& seekPoint, int direction) const; void restorePosition(OperationContext* txn, @@ -238,6 +231,12 @@ namespace mongo { SavedCursorRegistry* savedCursors() const { return _cursorRegistry; } static int lowWaterMark(); + + Ordering ordering() const { return _ordering; } + + int customBSONCmp(const BSONObj& inIndex_left, + const IndexSeekPoint& seekPoint_right, + int direction) const; private: friend class BtreeLogic::Builder; @@ -348,11 +347,7 @@ namespace mongo { void customLocate(OperationContext* txn, DiskLoc* locInOut, int* keyOfsInOut, - const BSONObj& keyBegin, - int keyBeginLen, - bool afterKey, - const std::vector<const BSONElement*>& keyEnd, - const std::vector<bool>& keyEndInclusive, + const IndexSeekPoint& seekPoint, int direction, std::pair<DiskLoc, int>& bestParent) const; @@ -367,12 +362,7 @@ namespace mongo { bool customFind(OperationContext* txn, int low, int high, - const BSONObj& keyBegin, - int keyBeginLen, - bool afterKey, - const std::vector<const BSONElement*>& keyEnd, - const std::vector<bool>& keyEndInclusive, - const Ordering& order, + const IndexSeekPoint& seekPoint, int direction, DiskLoc* thisLocInOut, int* keyOfsInOut, @@ -381,11 +371,7 @@ namespace mongo { void advanceToImpl(OperationContext* txn, DiskLoc* thisLocInOut, int* keyOfsInOut, - const BSONObj &keyBegin, - int keyBeginLen, - bool afterKey, - const std::vector<const BSONElement*>& keyEnd, - const std::vector<bool>& keyEndInclusive, + const IndexSeekPoint& seekPoint, int direction) const; bool wouldCreateDup(OperationContext* txn, @@ -547,16 +533,6 @@ namespace mongo { BucketType* bucket, int keyPos) const; - // TODO 'this' for _ordering(?) - int customBSONCmp(const BSONObj& l, - const BSONObj& rBegin, - int rBeginLen, - bool rSup, - const std::vector<const BSONElement*>& rEnd, - const std::vector<bool>& rEndInclusive, - const Ordering& o, - int direction) const; - /** * Tries to push key into bucket. Return false if it can't because key doesn't fit. * diff --git a/src/mongo/db/storage/sorted_data_interface.h b/src/mongo/db/storage/sorted_data_interface.h index 98bac8e5136..006fa7ff4dd 100644 --- a/src/mongo/db/storage/sorted_data_interface.h +++ b/src/mongo/db/storage/sorted_data_interface.h @@ -26,12 +26,14 @@ * it in the license file. */ -#include "mongo/bson/ordering.h" -#include "mongo/db/catalog/head_manager.h" +#include <boost/optional/optional.hpp> +#include <boost/optional/optional_io.hpp> +#include <memory> + #include "mongo/db/jsobj.h" #include "mongo/db/operation_context.h" #include "mongo/db/record_id.h" -#include "mongo/db/storage/record_store.h" +#include "mongo/db/storage/index_entry_comparison.h" #pragma once @@ -179,172 +181,161 @@ namespace mongo { } /** - * Navigation + * Navigates over the sorted data. + * + * A cursor is constructed with a direction flag with the following effects: + * - The direction that next() moves. + * - If a seek method hits an exact match on key, forward cursors will be positioned on + * the first value for that key, reverse cursors on the last. + * - If a seek method or restore does not hit an exact match, cursors will be + * positioned on the closest position *after* the query in the direction of the + * search. + * - The end position is on the "far" side of the query. In a forward cursor that means + * that it is the lowest value for the key if the end is exclusive or the first entry + * past the key if the end is inclusive or there are no exact matches. * * A cursor is tied to a transaction, such as the OperationContext or a WriteUnitOfWork * inside that context. Any cursor acquired inside a transaction is invalid outside - * of that transaction, instead use the savePosition() and restorePosition() methods - * reestablish the cursor. + * of that transaction, instead use the save and restore methods to reestablish the cursor. + * + * Any method other than the save methods may throw WriteConflict exception. If that + * happens, the cursor may not be used again until it has been saved and successfully + * restored. If next() or restore() throw a WCE the cursor's position will be the same as + * before the call (strong exception guarantee). All other methods leave the cursor in a + * valid state but with an unspecified position (basic exception guarantee). All methods + * only provide the basic guarantee for exceptions other than WCE. + * + * Any returned unowned BSON is only valid until the next call to any method on this + * interface. The implementations must assume that passed-in unowned BSON is only valid for + * the duration of the call. + * + * Implementations may override any default implementation if they can provide a more + * efficient implementation. */ class Cursor { public: - virtual ~Cursor() {} /** - * Return the direction of 'this' cursor. + * Tells methods that return an IndexKeyEntry what part of the data the caller is + * interested in. + * + * Methods returning an engaged optional<T> will only return null RecordIds or empty + * BSONObjs if they have been explicitly left out of the request. * - * @return +1 for a forward cursor or -1 for a reverse cursor + * Implementations are allowed to return more data than requested, but not less. */ - virtual int getDirection() const = 0; + enum RequestedInfo { + // Only usable part of the return is whether it is engaged or not. + kJustExistance = 0, + // Key must be filled in. + kWantKey = 1, + // Loc must be fulled in. + kWantLoc = 2, + // Both must be returned. + kKeyAndLoc = kWantKey | kWantLoc, + }; - /** - * Return true if 'this' forward (reverse) cursor is positioned - * past the end (before the beginning) of the index, and false otherwise. - */ - virtual bool isEOF() const = 0; + virtual ~Cursor() = default; - /** - * Return true if 'this' cursor and the 'other' cursor are positioned at - * the same key and RecordId, or if both cursors are at EOF. Otherwise, - * this function returns false. - * - * Implementations should prohibit the comparison of cursors associated - * with different indices. - */ - virtual bool pointsToSamePlaceAs(const Cursor& other) const = 0; /** - * Position 'this' forward (reverse) cursor either at the entry or - * immediately after (or immediately before) the specified key and RecordId. - * The cursor should be positioned at EOF if no such entry exists. + * Sets the position to stop scanning. An empty key unsets the end position. * - * @return true if the entry (key, RecordId) exists within the index, - * and false otherwise - */ - virtual bool locate(const BSONObj& key, const RecordId& loc) = 0; - - /** - * Position 'this' forward (reverse) cursor either at the next - * (previous) occurrence of a particular key or immediately after - * (or immediately before). + * If next() hits this position, or a seek method attempts to seek past it they + * unposition the cursor and return boost::none. * - * @see SortedDataInterface::customLocate + * Setting the end position should be done before seeking since the current position, if + * any, isn't checked. */ - virtual void advanceTo(const BSONObj &keyPrefix, - int prefixLen, - bool prefixExclusive, - const std::vector<const BSONElement*>& keySuffix, - const std::vector<bool>& suffixInclusive) = 0; + virtual void setEndPosition(const BSONObj& key, bool inclusive) = 0; /** - * Position 'this' forward (reverse) cursor either at the first - * (last) occurrence of a particular key or immediately after - * (or immediately before). The key is a typical BSONObj, - * represented by the specified parameters in the following way: - * - * The first 'prefixLen' elements of 'keyPrefix' followed by - * the last 'keySuffix.size() - prefixLen' elements of 'keySuffix'. - * - * e.g. - * - * Suppose that - * - * keyPrefix = { "" : 1, "" : 2 } - * prefixLen = 1 - * prefixExclusive = false - * keySuffix = [ IGNORED; { "" : 5 } ] - * suffixInclusive = [ IGNORED; false ] - * - * ==> represented key is { "" : 1, "" : 5 } - * with the exclusive byte set on the second field - * - * Suppose that - * - * keyPrefix = { "" : 1, "" : 2 } - * prefixLen = 1 - * prefixExclusive = true - * keySuffix = IGNORED - * suffixInclusive = IGNORED - * - * ==> represented key is { "" : 1 } - * with the exclusive byte set on the first field - * - * @param prefixExclusive true if 'this' forward (reverse) cursor - * should be positioned immediately after (immediately - * before) the represented key, and false otherwise - * - * @param suffixInclusive an element of the vector is false if - * 'this' forward (reverse) cursor should be positioned - * immediately after (immediately before) the represented - * key, and true otherwise - * - * Implementations should prohibit callers from specifying - * 'prefixLen = 0' when 'prefixExclusive = true'. - * - * @see IndexEntryComparison::makeQueryObject + * Moves forward and returns the new data or boost::none if there is no more data. + * If not positioned, returns boost::none. */ - virtual void customLocate(const BSONObj& keyPrefix, - int prefixLen, - bool prefixExclusive, - const std::vector<const BSONElement*>& keySuffix, - const std::vector<bool>& suffixInclusive) = 0; + virtual boost::optional<IndexKeyEntry> next(RequestedInfo parts = kKeyAndLoc) = 0; + + // + // Seeking + // /** - * Return the key associated with the current position of 'this' cursor. + * Seeks to the provided key and returns current position. + * + * TODO consider removing once IndexSeekPoint has been cleaned up a bit. In particular, + * need a way to specify use whole keyPrefix and nothing else and to support the + * combination of empty and exclusive. Should also make it easier to construct for the + * common cases. */ - virtual BSONObj getKey() const = 0; + virtual boost::optional<IndexKeyEntry> seek(const BSONObj& key, + bool inclusive, + RequestedInfo parts = kKeyAndLoc) = 0; /** - * Return the RecordId associated with the current position of 'this' cursor. + * Seeks to the position described by seekPoint and returns the current position. + * + * NOTE: most implementations should just pass seekPoint to + * IndexEntryComparison::makeQueryObject(). */ - virtual RecordId getRecordId() const = 0; + virtual boost::optional<IndexKeyEntry> seek(const IndexSeekPoint& seekPoint, + RequestedInfo parts = kKeyAndLoc) = 0; /** - * Position 'this' forward (reverse) cursor at the next (preceding) entry - * in the index. A cursor positioned at EOF should remain at EOF when advanced. + * Seeks to a key with a hint to the implementation that you only want exact matches. If + * an exact match can't be found, boost::none will be returned and the resulting + * position of the cursor is unspecified. */ - virtual void advance() = 0; + virtual boost::optional<IndexKeyEntry> seekExact(const BSONObj& key, + RequestedInfo parts = kKeyAndLoc) { + auto kv = seek(key, true, kKeyAndLoc); + if (kv && kv->key.woCompare(key, BSONObj(), /*considerFieldNames*/false) == 0) + return kv; + return {}; + } // // Saving and restoring state // /** - * Save the entry in the index (i.e. its key and RecordId) of where - * 'this' cursor is currently positioned. + * Prepares for state changes in underlying data in a way that allows the cursor's + * current position to be restored. * - * Implementations can assume that no operations other than delete - * or restorePosition() will be called on 'this' cursor after its - * position has been saved. + * It is safe to call savePositioned multiple times in a row. + * No other method (excluding destructor) may be called until successfully restored. */ - virtual void savePosition() = 0; + virtual void savePositioned() = 0; /** - * Restore 'this' cursor to the previously saved entry in the index. + * Prepares for state changes in underlying data without necessarily saving the current + * state. * - * Implementations should have the same behavior as calling locate() - * with the saved key and RecordId. + * The cursor's position when restored is unspecified. Caller is expected to seek + * following the restore. + * + * It is safe to call saveUnpositioned multiple times in a row. + * No other method (excluding destructor) may be called until successfully restored. + */ + virtual void saveUnpositioned() { savePositioned(); } + + /** + * Recovers from potential state changes in underlying data. + * + * If the former position no longer exists, a following call to next() will return the + * next closest position in the direction of the scan, if any. + * + * This handles restoring after either savePositioned() or saveUnpositioned(). */ - virtual void restorePosition(OperationContext* txn) = 0; + virtual void restore(OperationContext* txn) = 0; }; /** - * Return a cursor over 'this' index. The cursor need not be positioned - * at any particular entry. - * - * Implementations can assume that locate() is called on the cursor - * before it gets used. - * - * Implementations can assume that 'this' index outlives all cursors - * it produces. + * Returns an unpositioned cursor over 'this' index. * - * @param txn the transaction to which the cursor is tied - * @param direction the direction of the cursor. - * +1 for a forward cursor or -1 for a reverse cursor. - * - * @return caller takes ownership + * Implementations can assume that 'this' index outlives all cursors it produces. */ - virtual Cursor* newCursor(OperationContext* txn, int direction) const = 0; + virtual std::unique_ptr<Cursor> newCursor(OperationContext* txn, + bool isForward = true) const = 0; // // Index creation diff --git a/src/mongo/db/storage/sorted_data_interface_test_cursor.cpp b/src/mongo/db/storage/sorted_data_interface_test_cursor.cpp index 069c93a4041..d4a99333fdc 100644 --- a/src/mongo/db/storage/sorted_data_interface_test_cursor.cpp +++ b/src/mongo/db/storage/sorted_data_interface_test_cursor.cpp @@ -30,99 +30,69 @@ #include "mongo/db/storage/sorted_data_interface_test_harness.h" -#include <boost/scoped_ptr.hpp> +#include <memory> #include "mongo/db/storage/sorted_data_interface.h" #include "mongo/unittest/unittest.h" namespace mongo { - using boost::scoped_ptr; - - // Call getDirection() on a forward cursor and verify the result equals +1. - TEST( SortedDataInterface, GetCursorDirection ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), 1 ) ); - ASSERT_EQUALS( 1, cursor->getDirection() ); - } - } - - // Call getDirection() on a reverse cursor and verify the result equals -1. - TEST( SortedDataInterface, GetCursorDirectionReversed ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), -1 ) ); - ASSERT_EQUALS( -1, cursor->getDirection() ); - } - } - // Verify that a forward cursor is positioned at EOF when the index is empty. TEST( SortedDataInterface, CursorIsEOFWhenEmpty ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT( sorted->isEmpty( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), 1 ) ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get()) ); - ASSERT( !cursor->locate( minKey, RecordId::min() ) ); - ASSERT( cursor->isEOF() ); + ASSERT( !cursor->seek(minKey, true) ); // Cursor at EOF should remain at EOF when advanced - cursor->advance(); - ASSERT( cursor->isEOF() ); + ASSERT( !cursor->next() ); } } // Verify that a reverse cursor is positioned at EOF when the index is empty. TEST( SortedDataInterface, CursorIsEOFWhenEmptyReversed ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT( sorted->isEmpty( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), -1 ) ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get(), false) ); - ASSERT( !cursor->locate( maxKey, RecordId::max() ) ); - ASSERT( cursor->isEOF() ); + ASSERT( !cursor->seek( maxKey, true ) ); // Cursor at EOF should remain at EOF when advanced - cursor->advance(); - ASSERT( cursor->isEOF() ); + ASSERT( !cursor->next() ); } } // Call advance() on a forward cursor until it is exhausted. // When a cursor positioned at EOF is advanced, it stays at EOF. TEST( SortedDataInterface, ExhaustCursor ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT( sorted->isEmpty( opCtx.get() ) ); } int nToInsert = 10; for ( int i = 0; i < nToInsert; i++ ) { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); BSONObj key = BSON( "" << i ); @@ -133,42 +103,38 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( nToInsert, sorted->numEntries( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), 1 ) ); - ASSERT( !cursor->locate( minKey, RecordId::min() ) ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get()) ); for ( int i = 0; i < nToInsert; i++ ) { - ASSERT( !cursor->isEOF() ); - ASSERT_EQUALS( BSON( "" << i ), cursor->getKey() ); - ASSERT_EQUALS( RecordId( 42, i * 2 ), cursor->getRecordId() ); - cursor->advance(); + auto entry = i == 0 ? cursor->seek(minKey, true) : cursor->next(); + ASSERT_EQ(entry, IndexKeyEntry(BSON("" << i), RecordId(42, i * 2))); } - ASSERT( cursor->isEOF() ); + ASSERT( !cursor->next() ); // Cursor at EOF should remain at EOF when advanced - cursor->advance(); - ASSERT( cursor->isEOF() ); + ASSERT( !cursor->next() ); } } // Call advance() on a reverse cursor until it is exhausted. // When a cursor positioned at EOF is advanced, it stays at EOF. TEST( SortedDataInterface, ExhaustCursorReversed ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT( sorted->isEmpty( opCtx.get() ) ); } int nToInsert = 10; for ( int i = 0; i < nToInsert; i++ ) { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); BSONObj key = BSON( "" << i ); @@ -179,25 +145,21 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( nToInsert, sorted->numEntries( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), -1 ) ); - ASSERT( !cursor->locate( maxKey, RecordId::max() ) ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get(), false) ); for ( int i = nToInsert - 1; i >= 0; i-- ) { - ASSERT( !cursor->isEOF() ); - ASSERT_EQUALS( BSON( "" << i ), cursor->getKey() ); - ASSERT_EQUALS( RecordId( 42, i * 2 ), cursor->getRecordId() ); - cursor->advance(); + auto entry = (i == nToInsert - 1) ? cursor->seek(maxKey, true) : cursor->next(); + ASSERT_EQ(entry, IndexKeyEntry(BSON("" << i), RecordId(42, i * 2))); } - ASSERT( cursor->isEOF() ); + ASSERT( !cursor->next() ); // Cursor at EOF should remain at EOF when advanced - cursor->advance(); - ASSERT( cursor->isEOF() ); + ASSERT( !cursor->next() ); } } diff --git a/src/mongo/db/storage/sorted_data_interface_test_cursor_advanceto.cpp b/src/mongo/db/storage/sorted_data_interface_test_cursor_advanceto.cpp index 29f4122d539..2b094626ac6 100644 --- a/src/mongo/db/storage/sorted_data_interface_test_cursor_advanceto.cpp +++ b/src/mongo/db/storage/sorted_data_interface_test_cursor_advanceto.cpp @@ -30,32 +30,29 @@ #include "mongo/db/storage/sorted_data_interface_test_harness.h" -#include <boost/scoped_ptr.hpp> +#include <memory> #include "mongo/db/storage/sorted_data_interface.h" #include "mongo/unittest/unittest.h" namespace mongo { - using boost::scoped_ptr; - using std::vector; - // Insert multiple single-field keys and advance to each of them // using a forward cursor by specifying their exact key. When // advanceTo() is called on a duplicate key, the cursor is - // positioned at the next occurrence of that key in ascending + // positioned at the first occurrence of that key in ascending // order by RecordId. TEST( SortedDataInterface, AdvanceTo ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT( sorted->isEmpty( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), key1, loc1, true ) ); @@ -68,73 +65,49 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( 5, sorted->numEntries( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), 1 ) ); - - ASSERT( cursor->locate( key1, loc1 ) ); - ASSERT_EQUALS( key1, cursor->getKey() ); - ASSERT_EQUALS( loc1, cursor->getRecordId() ); - - { - vector<const BSONElement*> keyEnd( 1 ); - vector<bool> keyEndInclusive( 1 ); - - cursor->advanceTo( key1, 1, false, keyEnd, keyEndInclusive ); - // SERVER-15489 forward cursor is positioned at first occurrence of key in index - // when advanceTo() called on duplicate key - // ASSERT_EQUALS( key1, cursor->getKey() ); - // ASSERT_EQUALS( loc2, cursor->getRecordId() ); - } - - { - vector<const BSONElement*> keyEnd( 1 ); - vector<bool> keyEndInclusive( 1 ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get()) ); - cursor->advanceTo( key2, 1, false, keyEnd, keyEndInclusive ); - ASSERT_EQUALS( key2, cursor->getKey() ); - ASSERT_EQUALS( loc4, cursor->getRecordId() ); - } + ASSERT_EQ(cursor->seek(key1, true), IndexKeyEntry(key1, loc1)); - { - vector<const BSONElement*> keyEnd( 1 ); - vector<bool> keyEndInclusive( 1 ); + IndexSeekPoint seekPoint; + seekPoint.keyPrefix = key1; + seekPoint.prefixLen = 1; + seekPoint.prefixExclusive = false; + ASSERT_EQ(cursor->seek(seekPoint), IndexKeyEntry(key1, loc1)); - cursor->advanceTo( key3, 1, false, keyEnd, keyEndInclusive ); - ASSERT_EQUALS( key3, cursor->getKey() ); - ASSERT_EQUALS( loc5, cursor->getRecordId() ); - } + seekPoint.keyPrefix = key2; + ASSERT_EQ(cursor->seek(seekPoint), IndexKeyEntry(key2, loc4)); - { - vector<const BSONElement*> keyEnd( 1 ); - vector<bool> keyEndInclusive( 1 ); + seekPoint.keyPrefix = key3; + ASSERT_EQ(cursor->seek(seekPoint), IndexKeyEntry(key3, loc5)); - cursor->advanceTo( key4, 1, false, keyEnd, keyEndInclusive ); - ASSERT( cursor->isEOF() ); - } + seekPoint.keyPrefix = key4; + ASSERT_EQ(cursor->seek(seekPoint), boost::none); } } // Insert multiple single-field keys and advance to each of them // using a reverse cursor by specifying their exact key. When // advanceTo() is called on a duplicate key, the cursor is - // positioned at the next occurrence of that key in descending - // order by RecordId. + // positioned at the first occurrence of that key in descending + // order by RecordId (last occurrence in index order). TEST( SortedDataInterface, AdvanceToReversed ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT( sorted->isEmpty( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), key1, loc1, true ) ); @@ -147,71 +120,47 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( 5, sorted->numEntries( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), -1 ) ); - - ASSERT( cursor->locate( key3, loc5 ) ); - ASSERT_EQUALS( key3, cursor->getKey() ); - ASSERT_EQUALS( loc5, cursor->getRecordId() ); - - { - vector<const BSONElement*> keyEnd( 1 ); - vector<bool> keyEndInclusive( 1 ); - - cursor->advanceTo( key3, 1, false, keyEnd, keyEndInclusive ); - // SERVER-15490 reverse cursor is positioned at last occurrence of key in index - // when advanceTo() called on duplicate key - // ASSERT_EQUALS( key3, cursor->getKey() ); - // ASSERT_EQUALS( loc4, cursor->getRecordId() ); - } - - { - vector<const BSONElement*> keyEnd( 1 ); - vector<bool> keyEndInclusive( 1 ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get(), false) ); - cursor->advanceTo( key2, 1, false, keyEnd, keyEndInclusive ); - ASSERT_EQUALS( key2, cursor->getKey() ); - ASSERT_EQUALS( loc2, cursor->getRecordId() ); - } + ASSERT_EQ(cursor->seek(key3, true), IndexKeyEntry(key3, loc5)); - { - vector<const BSONElement*> keyEnd( 1 ); - vector<bool> keyEndInclusive( 1 ); + IndexSeekPoint seekPoint; + seekPoint.keyPrefix = key3; + seekPoint.prefixLen = 1; + seekPoint.prefixExclusive = false; + ASSERT_EQ(cursor->seek(seekPoint), IndexKeyEntry(key3, loc5)); - cursor->advanceTo( key1, 1, false, keyEnd, keyEndInclusive ); - ASSERT_EQUALS( key1, cursor->getKey() ); - ASSERT_EQUALS( loc1, cursor->getRecordId() ); - } + seekPoint.keyPrefix = key2; + ASSERT_EQ(cursor->seek(seekPoint), IndexKeyEntry(key2, loc2)); - { - vector<const BSONElement*> keyEnd( 1 ); - vector<bool> keyEndInclusive( 1 ); + seekPoint.keyPrefix = key1; + ASSERT_EQ(cursor->seek(seekPoint), IndexKeyEntry(key1, loc1)); - cursor->advanceTo( key0, 1, false, keyEnd, keyEndInclusive ); - ASSERT( cursor->isEOF() ); - } + seekPoint.keyPrefix = key0; + ASSERT_EQ(cursor->seek(seekPoint), boost::none); } } - // Insert two single-field keys and advance to the larger one using - // a forward cursor positioned at the smaller one, by specifying a key - // before the current position of the cursor. + // Insert two single-field keys, then seek a forward cursor to the larger one then seek behind + // the smaller one. Ending position is on the smaller one since a seek describes where to go + // and should not be effected by current position. TEST( SortedDataInterface, AdvanceToKeyBeforeCursorPosition ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT( sorted->isEmpty( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), key1, loc1, true ) ); @@ -221,65 +170,41 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( 2, sorted->numEntries( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), 1 ) ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get()) ); - ASSERT( cursor->locate( key1, loc1 ) ); - ASSERT_EQUALS( key1, cursor->getKey() ); - ASSERT_EQUALS( loc1, cursor->getRecordId() ); + ASSERT_EQ(cursor->seek(key1, true), IndexKeyEntry(key1, loc1)); - { - vector<const BSONElement*> keyEnd( 1 ); - vector<bool> keyEndInclusive( 1 ); - - cursor->advanceTo( key0, 1, false, keyEnd, keyEndInclusive ); - // SERVER-15489 forward cursor is positioned at first key in index - // when advanceTo() called with key smaller than any entry - // ASSERT_EQUALS( key2, cursor->getKey() ); - // ASSERT_EQUALS( loc2, cursor->getRecordId() ); - } - } - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), 1 ) ); - - ASSERT( cursor->locate( key1, loc1 ) ); - ASSERT_EQUALS( key1, cursor->getKey() ); - ASSERT_EQUALS( loc1, cursor->getRecordId() ); + IndexSeekPoint seekPoint; + seekPoint.keyPrefix = key0; + seekPoint.prefixLen = 1; + seekPoint.prefixExclusive = false; + ASSERT_EQ(cursor->seek(seekPoint), IndexKeyEntry(key1, loc1)); - { - vector<const BSONElement*> keyEnd( 1 ); - vector<bool> keyEndInclusive( 1 ); - - cursor->advanceTo( key0, 1, true, keyEnd, keyEndInclusive ); - // SERVER-15489 forward cursor is positioned at first key in index - // when advanceTo() called with key smaller than any entry - // ASSERT_EQUALS( key2, cursor->getKey() ); - // ASSERT_EQUALS( loc2, cursor->getRecordId() ); - } + seekPoint.prefixExclusive = true; + ASSERT_EQ(cursor->seek(seekPoint), IndexKeyEntry(key1, loc1)); } } - // Insert two single-field keys and advance to the smaller one using - // a reverse cursor positioned at the larger one, by specifying a key - // after the current position of the cursor. + // Insert two single-field keys, then seek a reverse cursor to the smaller one then seek behind + // the larger one. Ending position is on the larger one since a seek describes where to go + // and should not be effected by current position. TEST( SortedDataInterface, AdvanceToKeyAfterCursorPositionReversed ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT( sorted->isEmpty( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), key1, loc1, true ) ); @@ -289,67 +214,43 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( 2, sorted->numEntries( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), -1 ) ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get(), false) ); - ASSERT( cursor->locate( key2, loc2 ) ); - ASSERT_EQUALS( key2, cursor->getKey() ); - ASSERT_EQUALS( loc2, cursor->getRecordId() ); + ASSERT_EQ(cursor->seek(key2, true), IndexKeyEntry(key2, loc2)); - { - vector<const BSONElement*> keyEnd( 1 ); - vector<bool> keyEndInclusive( 1 ); - - cursor->advanceTo( key3, 1, false, keyEnd, keyEndInclusive ); - // SERVER-15490 reverse cursor is positioned at last key in index - // when advanceTo() called with key larger than any entry - // ASSERT_EQUALS( key1, cursor->getKey() ); - // ASSERT_EQUALS( loc1, cursor->getRecordId() ); - } - } - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), -1 ) ); - - ASSERT( cursor->locate( key2, loc2 ) ); - ASSERT_EQUALS( key2, cursor->getKey() ); - ASSERT_EQUALS( loc2, cursor->getRecordId() ); + IndexSeekPoint seekPoint; + seekPoint.keyPrefix = key3; + seekPoint.prefixLen = 1; + seekPoint.prefixExclusive = false; + ASSERT_EQ(cursor->seek(seekPoint), IndexKeyEntry(key2, loc2)); - { - vector<const BSONElement*> keyEnd( 1 ); - vector<bool> keyEndInclusive( 1 ); - - cursor->advanceTo( key3, 1, true, keyEnd, keyEndInclusive ); - // SERVER-15490 reverse cursor is positioned at last key in index - // when advanceTo() called with key larger than any entry - // ASSERT_EQUALS( key1, cursor->getKey() ); - // ASSERT_EQUALS( loc1, cursor->getRecordId() ); - } + seekPoint.prefixExclusive = true; + ASSERT_EQ(cursor->seek(seekPoint), IndexKeyEntry(key2, loc2)); } } // Insert a single-field key and advance to EOF using a forward cursor - // by specifying that exact key. When advanceTo() is called with the key - // where the cursor is positioned (and it is the last entry for that key), - // the cursor should advance to EOF regardless of whether it is in non- - // or inclusive mode. + // by specifying that exact key. When seek() is called with the key + // where the cursor is positioned (and it is the first entry for that key), + // the cursor should remain at its current position. An exclusive seek will + // position the cursor on the next position, which may be EOF. TEST( SortedDataInterface, AdvanceToKeyAtCursorPosition ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT( sorted->isEmpty( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), key1, loc1, true ) ); @@ -358,63 +259,43 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( 1, sorted->numEntries( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), 1 ) ); - - ASSERT( cursor->locate( key1, loc1 ) ); - ASSERT_EQUALS( key1, cursor->getKey() ); - ASSERT_EQUALS( loc1, cursor->getRecordId() ); - - { - vector<const BSONElement*> keyEnd( 1 ); - vector<bool> keyEndInclusive( 1 ); - - // SERVER-15483 forward cursor positioned at last entry in index should move - // to EOF when advanceTo() is called with that particular key - // cursor->advanceTo( key1, 1, false, keyEnd, keyEndInclusive ); - // ASSERT( cursor->isEOF() ); - } - } - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), 1 ) ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get()) ); - ASSERT( cursor->locate( key1, loc1 ) ); - ASSERT_EQUALS( key1, cursor->getKey() ); - ASSERT_EQUALS( loc1, cursor->getRecordId() ); + ASSERT_EQ(cursor->seek(key1, true), IndexKeyEntry(key1, loc1)); - { - vector<const BSONElement*> keyEnd( 1 ); - vector<bool> keyEndInclusive( 1 ); + IndexSeekPoint seekPoint; + seekPoint.keyPrefix = key1; + seekPoint.prefixLen = 1; + seekPoint.prefixExclusive = false; + ASSERT_EQ(cursor->seek(seekPoint), IndexKeyEntry(key1, loc1)); - cursor->advanceTo( key1, 1, true, keyEnd, keyEndInclusive ); - ASSERT( cursor->isEOF() ); - } + seekPoint.prefixExclusive = true; + ASSERT_EQ(cursor->seek(seekPoint), boost::none); } } // Insert a single-field key and advance to EOF using a reverse cursor - // by specifying that exact key. When advanceTo() is called with the key + // by specifying that exact key. When seek() is called with the key // where the cursor is positioned (and it is the first entry for that key), - // the cursor should advance to EOF regardless of whether it is in non- - // or inclusive mode. + // the cursor should remain at its current position. An exclusive seek will + // position the cursor on the next position, which may be EOF. TEST( SortedDataInterface, AdvanceToKeyAtCursorPositionReversed ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT( sorted->isEmpty( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), key1, loc1, true ) ); @@ -423,44 +304,24 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( 1, sorted->numEntries( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), -1 ) ); - - ASSERT( cursor->locate( key1, loc1 ) ); - ASSERT_EQUALS( key1, cursor->getKey() ); - ASSERT_EQUALS( loc1, cursor->getRecordId() ); - - { - vector<const BSONElement*> keyEnd( 1 ); - vector<bool> keyEndInclusive( 1 ); - - // SERVER-15483 reverse cursor positioned at first entry in index should move - // to EOF when advanceTo() is called with that particular key - // cursor->advanceTo( key1, 1, false, keyEnd, keyEndInclusive ); - // ASSERT( cursor->isEOF() ); - } - } - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), -1 ) ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get(), false) ); - ASSERT( cursor->locate( key1, loc1 ) ); - ASSERT_EQUALS( key1, cursor->getKey() ); - ASSERT_EQUALS( loc1, cursor->getRecordId() ); + ASSERT_EQ(cursor->seek(key1, true), IndexKeyEntry(key1, loc1)); - { - vector<const BSONElement*> keyEnd( 1 ); - vector<bool> keyEndInclusive( 1 ); + IndexSeekPoint seekPoint; + seekPoint.keyPrefix = key1; + seekPoint.prefixLen = 1; + seekPoint.prefixExclusive = false; + ASSERT_EQ(cursor->seek(seekPoint), IndexKeyEntry(key1, loc1)); - cursor->advanceTo( key1, 1, true, keyEnd, keyEndInclusive ); - ASSERT( cursor->isEOF() ); - } + seekPoint.prefixExclusive = true; + ASSERT_EQ(cursor->seek(seekPoint), boost::none); } } @@ -469,16 +330,16 @@ namespace mongo { // When advanceTo() is called in non-inclusive mode, the cursor is // positioned at the key that comes after the one specified. TEST( SortedDataInterface, AdvanceToExclusive ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT( sorted->isEmpty( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), key1, loc1, true ) ); @@ -491,53 +352,30 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( 5, sorted->numEntries( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), 1 ) ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get()) ); - ASSERT( cursor->locate( key1, loc1 ) ); - ASSERT_EQUALS( key1, cursor->getKey() ); - ASSERT_EQUALS( loc1, cursor->getRecordId() ); + ASSERT_EQ(cursor->seek(key1, true), IndexKeyEntry(key1, loc1)); - { - vector<const BSONElement*> keyEnd( 1 ); - vector<bool> keyEndInclusive( 1 ); + IndexSeekPoint seekPoint; + seekPoint.keyPrefix = key1; + seekPoint.prefixLen = 1; + seekPoint.prefixExclusive = true; + ASSERT_EQ(cursor->seek(seekPoint), IndexKeyEntry(key2, loc4)); - cursor->advanceTo( key1, 1, true, keyEnd, keyEndInclusive ); - ASSERT_EQUALS( key2, cursor->getKey() ); - ASSERT_EQUALS( loc4, cursor->getRecordId() ); - } + seekPoint.keyPrefix = key2; + ASSERT_EQ(cursor->seek(seekPoint), IndexKeyEntry(key3, loc5)); - { - vector<const BSONElement*> keyEnd( 1 ); - vector<bool> keyEndInclusive( 1 ); + seekPoint.keyPrefix = key3; + ASSERT_EQ(cursor->seek(seekPoint), boost::none); - cursor->advanceTo( key2, 1, true, keyEnd, keyEndInclusive ); - ASSERT_EQUALS( key3, cursor->getKey() ); - ASSERT_EQUALS( loc5, cursor->getRecordId() ); - } - - { - vector<const BSONElement*> keyEnd( 1 ); - vector<bool> keyEndInclusive( 1 ); - - cursor->advanceTo( key3, 1, true, keyEnd, keyEndInclusive ); - ASSERT( cursor->isEOF() ); - } - - { - vector<const BSONElement*> keyEnd( 1 ); - vector<bool> keyEndInclusive( 1 ); - - // SERVER-15449 forward cursor positioned at EOF should stay at EOF - // when advanceTo() is called - // cursor->advanceTo( key4, 1, true, keyEnd, keyEndInclusive ); - // ASSERT( cursor->isEOF() ); - } + seekPoint.keyPrefix = key4; + ASSERT_EQ(cursor->seek(seekPoint), boost::none); } } @@ -546,16 +384,16 @@ namespace mongo { // When advanceTo() is called in non-inclusive mode, the cursor is // positioned at the key that comes before the one specified. TEST( SortedDataInterface, AdvanceToExclusiveReversed ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT( sorted->isEmpty( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), key1, loc1, true ) ); @@ -568,53 +406,30 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( 5, sorted->numEntries( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), -1 ) ); - - ASSERT( cursor->locate( key3, loc5 ) ); - ASSERT_EQUALS( key3, cursor->getKey() ); - ASSERT_EQUALS( loc5, cursor->getRecordId() ); - - { - vector<const BSONElement*> keyEnd( 1 ); - vector<bool> keyEndInclusive( 1 ); - - cursor->advanceTo( key3, 1, true, keyEnd, keyEndInclusive ); - ASSERT_EQUALS( key2, cursor->getKey() ); - ASSERT_EQUALS( loc2, cursor->getRecordId() ); - } - - { - vector<const BSONElement*> keyEnd( 1 ); - vector<bool> keyEndInclusive( 1 ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get(), false) ); - cursor->advanceTo( key2, 1, true, keyEnd, keyEndInclusive ); - ASSERT_EQUALS( key1, cursor->getKey() ); - ASSERT_EQUALS( loc1, cursor->getRecordId() ); - } + ASSERT_EQ(cursor->seek(key3, true), IndexKeyEntry(key3, loc5)); - { - vector<const BSONElement*> keyEnd( 1 ); - vector<bool> keyEndInclusive( 1 ); + IndexSeekPoint seekPoint; + seekPoint.keyPrefix = key3; + seekPoint.prefixLen = 1; + seekPoint.prefixExclusive = true; + ASSERT_EQ(cursor->seek(seekPoint), IndexKeyEntry(key2, loc2)); - cursor->advanceTo( key1, 1, true, keyEnd, keyEndInclusive ); - ASSERT( cursor->isEOF() ); - } + seekPoint.keyPrefix = key2; + ASSERT_EQ(cursor->seek(seekPoint), IndexKeyEntry(key1, loc1)); - { - vector<const BSONElement*> keyEnd( 1 ); - vector<bool> keyEndInclusive( 1 ); + seekPoint.keyPrefix = key1; + ASSERT_EQ(cursor->seek(seekPoint), boost::none); - // SERVER-15449 reverse cursor positioned at EOF should stay at EOF - // when advanceTo() is called - // cursor->advanceTo( key0, 1, true, keyEnd, keyEndInclusive ); - // ASSERT( cursor->isEOF() ); - } + seekPoint.keyPrefix = key0; + ASSERT_EQ(cursor->seek(seekPoint), boost::none); } } @@ -622,18 +437,18 @@ namespace mongo { // each of them using a forward cursor by specifying a key between their // exact key and the current position of the cursor. TEST( SortedDataInterface, AdvanceToIndirect ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); BSONObj unusedKey = key6; // larger than any inserted key { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT( sorted->isEmpty( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), key1, loc1, true ) ); @@ -644,43 +459,27 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( 3, sorted->numEntries( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), 1 ) ); - - ASSERT( cursor->locate( key1, loc1 ) ); - ASSERT_EQUALS( key1, cursor->getKey() ); - ASSERT_EQUALS( loc1, cursor->getRecordId() ); - - { - vector<const BSONElement*> keyEnd( 1 ); - vector<bool> keyEndInclusive( 1 ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get()) ); - const BSONElement end0 = key2.firstElement(); - keyEnd[0] = &end0; - keyEndInclusive[0] = true; + ASSERT_EQ(cursor->seek(key1, true), IndexKeyEntry(key1, loc1)); - cursor->advanceTo( unusedKey, 0, false, keyEnd, keyEndInclusive ); - ASSERT_EQUALS( key3, cursor->getKey() ); - ASSERT_EQUALS( loc2, cursor->getRecordId() ); - } - - { - vector<const BSONElement*> keyEnd( 1 ); - vector<bool> keyEndInclusive( 1 ); + IndexSeekPoint seekPoint; + seekPoint.prefixLen = 0; + BSONElement suffix0; + seekPoint.keySuffix = {&suffix0}; + seekPoint.suffixInclusive = {true}; - const BSONElement end0 = key4.firstElement(); - keyEnd[0] = &end0; - keyEndInclusive[0] = true; + suffix0 = key2.firstElement(); + ASSERT_EQ(cursor->seek(seekPoint), IndexKeyEntry(key3, loc2)); - cursor->advanceTo( unusedKey, 0, false, keyEnd, keyEndInclusive ); - ASSERT_EQUALS( key5, cursor->getKey() ); - ASSERT_EQUALS( loc3, cursor->getRecordId() ); - } + suffix0 = key4.firstElement(); + ASSERT_EQ(cursor->seek(seekPoint), IndexKeyEntry(key5, loc3)); } } @@ -688,18 +487,18 @@ namespace mongo { // each of them using a reverse cursor by specifying a key between their // exact key and the current position of the cursor. TEST( SortedDataInterface, AdvanceToIndirectReversed ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); BSONObj unusedKey = key0; // smaller than any inserted key { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT( sorted->isEmpty( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), key1, loc1, true ) ); @@ -710,43 +509,27 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( 3, sorted->numEntries( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), -1 ) ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get(), false) ); - ASSERT( cursor->locate( key5, loc3 ) ); - ASSERT_EQUALS( key5, cursor->getKey() ); - ASSERT_EQUALS( loc3, cursor->getRecordId() ); + ASSERT_EQ(cursor->seek(key5, true), IndexKeyEntry(key5, loc3)); - { - vector<const BSONElement*> keyEnd( 1 ); - vector<bool> keyEndInclusive( 1 ); - - const BSONElement end0 = key4.firstElement(); - keyEnd[0] = &end0; - keyEndInclusive[0] = true; + IndexSeekPoint seekPoint; + seekPoint.prefixLen = 0; + BSONElement suffix0; + seekPoint.keySuffix = {&suffix0}; + seekPoint.suffixInclusive = {true}; - cursor->advanceTo( unusedKey, 0, false, keyEnd, keyEndInclusive ); - ASSERT_EQUALS( key3, cursor->getKey() ); - ASSERT_EQUALS( loc2, cursor->getRecordId() ); - } + suffix0 = key4.firstElement(); + ASSERT_EQ(cursor->seek(seekPoint), IndexKeyEntry(key3, loc2)); - { - vector<const BSONElement*> keyEnd( 1 ); - vector<bool> keyEndInclusive( 1 ); - - const BSONElement end0 = key2.firstElement(); - keyEnd[0] = &end0; - keyEndInclusive[0] = true; - - cursor->advanceTo( unusedKey, 0, false, keyEnd, keyEndInclusive ); - ASSERT_EQUALS( key1, cursor->getKey() ); - ASSERT_EQUALS( loc1, cursor->getRecordId() ); - } + suffix0 = key2.firstElement(); + ASSERT_EQ(cursor->seek(seekPoint), IndexKeyEntry(key1, loc1)); } } @@ -756,18 +539,18 @@ namespace mongo { // is called in non-inclusive mode, the cursor is positioned at the key // that comes after the one specified. TEST( SortedDataInterface, AdvanceToIndirectExclusive ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); BSONObj unusedKey = key6; // larger than any inserted key { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT( sorted->isEmpty( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), key1, loc1, true ) ); @@ -778,68 +561,32 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( 3, sorted->numEntries( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), 1 ) ); - - ASSERT( cursor->locate( key1, loc1 ) ); - ASSERT_EQUALS( key1, cursor->getKey() ); - ASSERT_EQUALS( loc1, cursor->getRecordId() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get()) ); - { - vector<const BSONElement*> keyEnd( 1 ); - vector<bool> keyEndInclusive( 1 ); + ASSERT_EQ(cursor->seek(key1, true), IndexKeyEntry(key1, loc1)); - const BSONElement end0 = key2.firstElement(); - keyEnd[0] = &end0; - keyEndInclusive[0] = false; + IndexSeekPoint seekPoint; + seekPoint.prefixLen = 0; + BSONElement suffix0; + seekPoint.keySuffix = {&suffix0}; + seekPoint.suffixInclusive = {false}; - cursor->advanceTo( unusedKey, 0, false, keyEnd, keyEndInclusive ); - ASSERT( !cursor->isEOF() ); - ASSERT_EQUALS( key3, cursor->getKey() ); - ASSERT_EQUALS( loc2, cursor->getRecordId() ); - } + suffix0 = key2.firstElement(); + ASSERT_EQ(cursor->seek(seekPoint), IndexKeyEntry(key3, loc2)); - { - vector<const BSONElement*> keyEnd( 1 ); - vector<bool> keyEndInclusive( 1 ); + suffix0 = key4.firstElement(); + ASSERT_EQ(cursor->seek(seekPoint), IndexKeyEntry(key5, loc3)); - const BSONElement end0 = key4.firstElement(); - keyEnd[0] = &end0; - keyEndInclusive[0] = false; + ASSERT_EQ(cursor->seek(key1, true), IndexKeyEntry(key1, loc1)); - cursor->advanceTo( unusedKey, 0, false, keyEnd, keyEndInclusive ); - ASSERT( !cursor->isEOF() ); - ASSERT_EQUALS( key5, cursor->getKey() ); - ASSERT_EQUALS( loc3, cursor->getRecordId() ); - } - } - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), 1 ) ); - - ASSERT( cursor->locate( key1, loc1 ) ); - ASSERT_EQUALS( key1, cursor->getKey() ); - ASSERT_EQUALS( loc1, cursor->getRecordId() ); - - { - vector<const BSONElement*> keyEnd( 1 ); - vector<bool> keyEndInclusive( 1 ); - - const BSONElement end0 = key3.firstElement(); - keyEnd[0] = &end0; - keyEndInclusive[0] = false; - - cursor->advanceTo( unusedKey, 0, false, keyEnd, keyEndInclusive ); - ASSERT( !cursor->isEOF() ); - ASSERT_EQUALS( key5, cursor->getKey() ); - ASSERT_EQUALS( loc3, cursor->getRecordId() ); - } + suffix0 = key3.firstElement(); + ASSERT_EQ(cursor->seek(seekPoint), IndexKeyEntry(key5, loc3)); } } @@ -849,18 +596,18 @@ namespace mongo { // is called in non-inclusive mode, the cursor is positioned at the key // that comes before the one specified. TEST( SortedDataInterface, AdvanceToIndirectExclusiveReversed ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); BSONObj unusedKey = key0; // smaller than any inserted key { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT( sorted->isEmpty( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), key1, loc1, true ) ); @@ -871,68 +618,32 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( 3, sorted->numEntries( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), -1 ) ); - - ASSERT( cursor->locate( key5, loc3 ) ); - ASSERT_EQUALS( key5, cursor->getKey() ); - ASSERT_EQUALS( loc3, cursor->getRecordId() ); - - { - vector<const BSONElement*> keyEnd( 1 ); - vector<bool> keyEndInclusive( 1 ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get(), false) ); - const BSONElement end0 = key4.firstElement(); - keyEnd[0] = &end0; - keyEndInclusive[0] = false; + ASSERT_EQ(cursor->seek(key5, true), IndexKeyEntry(key5, loc3)); - cursor->advanceTo( unusedKey, 0, false, keyEnd, keyEndInclusive ); - ASSERT( !cursor->isEOF() ); - ASSERT_EQUALS( key3, cursor->getKey() ); - ASSERT_EQUALS( loc2, cursor->getRecordId() ); - } + IndexSeekPoint seekPoint; + seekPoint.prefixLen = 0; + BSONElement suffix0; + seekPoint.keySuffix = {&suffix0}; + seekPoint.suffixInclusive = {false}; - { - vector<const BSONElement*> keyEnd( 1 ); - vector<bool> keyEndInclusive( 1 ); + suffix0 = key4.firstElement(); + ASSERT_EQ(cursor->seek(seekPoint), IndexKeyEntry(key3, loc2)); - const BSONElement end0 = key2.firstElement(); - keyEnd[0] = &end0; - keyEndInclusive[0] = false; + suffix0 = key2.firstElement(); + ASSERT_EQ(cursor->seek(seekPoint), IndexKeyEntry(key1, loc1)); - cursor->advanceTo( unusedKey, 0, false, keyEnd, keyEndInclusive ); - ASSERT( !cursor->isEOF() ); - ASSERT_EQUALS( key1, cursor->getKey() ); - ASSERT_EQUALS( loc1, cursor->getRecordId() ); - } - } + ASSERT_EQ(cursor->seek(key5, true), IndexKeyEntry(key5, loc3)); - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), -1 ) ); - - ASSERT( cursor->locate( key5, loc3 ) ); - ASSERT_EQUALS( key5, cursor->getKey() ); - ASSERT_EQUALS( loc3, cursor->getRecordId() ); - - { - vector<const BSONElement*> keyEnd( 1 ); - vector<bool> keyEndInclusive( 1 ); - - const BSONElement end0 = key3.firstElement(); - keyEnd[0] = &end0; - keyEndInclusive[0] = false; - - cursor->advanceTo( unusedKey, 0, false, keyEnd, keyEndInclusive ); - ASSERT( !cursor->isEOF() ); - ASSERT_EQUALS( key1, cursor->getKey() ); - ASSERT_EQUALS( loc1, cursor->getRecordId() ); - } + suffix0 = key3.firstElement(); + ASSERT_EQ(cursor->seek(seekPoint), IndexKeyEntry(key1, loc1)); } } diff --git a/src/mongo/db/storage/sorted_data_interface_test_cursor_locate.cpp b/src/mongo/db/storage/sorted_data_interface_test_cursor_locate.cpp index 3d0a95e99a6..b69a39a15fd 100644 --- a/src/mongo/db/storage/sorted_data_interface_test_cursor_locate.cpp +++ b/src/mongo/db/storage/sorted_data_interface_test_cursor_locate.cpp @@ -30,29 +30,27 @@ #include "mongo/db/storage/sorted_data_interface_test_harness.h" -#include <boost/scoped_ptr.hpp> +#include <memory> #include "mongo/db/storage/sorted_data_interface.h" #include "mongo/unittest/unittest.h" namespace mongo { - using boost::scoped_ptr; - // Insert a key and try to locate it using a forward cursor // by specifying its exact key and RecordId. TEST( SortedDataInterface, Locate ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), 1 ) ); - ASSERT( !cursor->locate( key1, loc1 ) ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get()) ); + ASSERT( !cursor->seek( key1, true ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), key1, loc1, true ) ); @@ -61,32 +59,28 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), 1 ) ); - - ASSERT( cursor->locate( key1, loc1 ) ); - ASSERT_EQUALS( key1, cursor->getKey() ); - ASSERT_EQUALS( loc1, cursor->getRecordId() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get()) ); - cursor->advance(); - ASSERT( cursor->isEOF() ); + ASSERT_EQ(cursor->seek(key1, true), IndexKeyEntry(key1, loc1)); + ASSERT_EQ(cursor->next(), boost::none); } } // Insert a key and try to locate it using a reverse cursor // by specifying its exact key and RecordId. TEST( SortedDataInterface, LocateReversed ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), -1 ) ); - ASSERT( !cursor->locate( key1, loc1 ) ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get(), false) ); + ASSERT( !cursor->seek( key1, true ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), key1, loc1, true ) ); @@ -95,32 +89,28 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), -1 ) ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get(), false) ); - ASSERT( cursor->locate( key1, loc1 ) ); - ASSERT_EQUALS( key1, cursor->getKey() ); - ASSERT_EQUALS( loc1, cursor->getRecordId() ); - - cursor->advance(); - ASSERT( cursor->isEOF() ); + ASSERT_EQ(cursor->seek(key1, true), IndexKeyEntry(key1, loc1)); + ASSERT_EQ(cursor->next(), boost::none); } } // Insert a compound key and try to locate it using a forward cursor // by specifying its exact key and RecordId. TEST( SortedDataInterface, LocateCompoundKey ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), 1 ) ); - ASSERT( !cursor->locate( compoundKey1a, loc1 ) ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get()) ); + ASSERT( !cursor->seek( compoundKey1a, true ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), compoundKey1a, loc1, true ) ); @@ -129,32 +119,28 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), 1 ) ); - - ASSERT( cursor->locate( compoundKey1a, loc1 ) ); - ASSERT_EQUALS( compoundKey1a, cursor->getKey() ); - ASSERT_EQUALS( loc1, cursor->getRecordId() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get()) ); - cursor->advance(); - ASSERT( cursor->isEOF() ); + ASSERT_EQ(cursor->seek(compoundKey1a, true), IndexKeyEntry(compoundKey1a, loc1)); + ASSERT_EQ(cursor->next(), boost::none); } } // Insert a compound key and try to locate it using a reverse cursor // by specifying its exact key and RecordId. TEST( SortedDataInterface, LocateCompoundKeyReversed ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), -1 ) ); - ASSERT( !cursor->locate( compoundKey1a, loc1 ) ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get(), false) ); + ASSERT( !cursor->seek( compoundKey1a, true ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), compoundKey1a, loc1, true ) ); @@ -163,32 +149,28 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), -1 ) ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get(), false) ); - ASSERT( cursor->locate( compoundKey1a, loc1 ) ); - ASSERT_EQUALS( compoundKey1a, cursor->getKey() ); - ASSERT_EQUALS( loc1, cursor->getRecordId() ); - - cursor->advance(); - ASSERT( cursor->isEOF() ); + ASSERT_EQ(cursor->seek(compoundKey1a, true), IndexKeyEntry(compoundKey1a, loc1)); + ASSERT_EQ(cursor->next(), boost::none); } } // Insert multiple keys and try to locate them using a forward cursor // by specifying their exact key and RecordId. TEST( SortedDataInterface, LocateMultiple ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), 1 ) ); - ASSERT( !cursor->locate( key1, loc1 ) ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get()) ); + ASSERT( !cursor->seek( key1, true ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), key1, loc1, true ) ); @@ -198,23 +180,16 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), 1 ) ); - - ASSERT( cursor->locate( key1, loc1 ) ); - ASSERT_EQUALS( key1, cursor->getKey() ); - ASSERT_EQUALS( loc1, cursor->getRecordId() ); - - cursor->advance(); - ASSERT_EQUALS( key2, cursor->getKey() ); - ASSERT_EQUALS( loc2, cursor->getRecordId() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get()) ); - cursor->advance(); - ASSERT( cursor->isEOF() ); + ASSERT_EQ(cursor->seek(key1, true), IndexKeyEntry(key1, loc1)); + ASSERT_EQ(cursor->next(), IndexKeyEntry(key2, loc2)); + ASSERT_EQ(cursor->next(), boost::none); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), key3, loc3, true ) ); @@ -223,51 +198,34 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), 1 ) ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get()) ); - ASSERT( cursor->locate( key2, loc2 ) ); - ASSERT_EQUALS( key2, cursor->getKey() ); - ASSERT_EQUALS( loc2, cursor->getRecordId() ); + ASSERT_EQ(cursor->seek(key2, true), IndexKeyEntry(key2, loc2)); + ASSERT_EQ(cursor->next(), IndexKeyEntry(key3, loc3)); + ASSERT_EQ(cursor->next(), boost::none); - cursor->advance(); - ASSERT_EQUALS( key3, cursor->getKey() ); - ASSERT_EQUALS( loc3, cursor->getRecordId() ); - - cursor->advance(); - ASSERT( cursor->isEOF() ); - - ASSERT( cursor->locate( key1, loc1 ) ); - ASSERT_EQUALS( key1, cursor->getKey() ); - ASSERT_EQUALS( loc1, cursor->getRecordId() ); - - cursor->advance(); - ASSERT_EQUALS( key2, cursor->getKey() ); - ASSERT_EQUALS( loc2, cursor->getRecordId() ); - - cursor->advance(); - ASSERT_EQUALS( key3, cursor->getKey() ); - ASSERT_EQUALS( loc3, cursor->getRecordId() ); - - cursor->advance(); - ASSERT( cursor->isEOF() ); + ASSERT_EQ(cursor->seek(key1, true), IndexKeyEntry(key1, loc1)); + ASSERT_EQ(cursor->next(), IndexKeyEntry(key2, loc2)); + ASSERT_EQ(cursor->next(), IndexKeyEntry(key3, loc3)); + ASSERT_EQ(cursor->next(), boost::none); } } // Insert multiple keys and try to locate them using a reverse cursor // by specifying their exact key and RecordId. TEST( SortedDataInterface, LocateMultipleReversed ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), -1 ) ); - ASSERT( !cursor->locate( key3, loc1 ) ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get(), false) ); + ASSERT( !cursor->seek( key3, true ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), key1, loc1, true ) ); @@ -277,23 +235,16 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), -1 ) ); - - ASSERT( cursor->locate( key2, loc2 ) ); - ASSERT_EQUALS( key2, cursor->getKey() ); - ASSERT_EQUALS( loc2, cursor->getRecordId() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get(), false) ); - cursor->advance(); - ASSERT_EQUALS( key1, cursor->getKey() ); - ASSERT_EQUALS( loc1, cursor->getRecordId() ); - - cursor->advance(); - ASSERT( cursor->isEOF() ); + ASSERT_EQ(cursor->seek(key2, true), IndexKeyEntry(key2, loc2)); + ASSERT_EQ(cursor->next(), IndexKeyEntry(key1, loc1)); + ASSERT_EQ(cursor->next(), boost::none); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), key3, loc3, true ) ); @@ -302,51 +253,34 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), -1 ) ); - - ASSERT( cursor->locate( key2, loc2 ) ); - ASSERT_EQUALS( key2, cursor->getKey() ); - ASSERT_EQUALS( loc2, cursor->getRecordId() ); - - cursor->advance(); - ASSERT_EQUALS( key1, cursor->getKey() ); - ASSERT_EQUALS( loc1, cursor->getRecordId() ); - - cursor->advance(); - ASSERT( cursor->isEOF() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get(), false) ); - ASSERT( cursor->locate( key3, loc3 ) ); - ASSERT_EQUALS( key3, cursor->getKey() ); - ASSERT_EQUALS( loc3, cursor->getRecordId() ); + ASSERT_EQ(cursor->seek(key2, true), IndexKeyEntry(key2, loc2)); + ASSERT_EQ(cursor->next(), IndexKeyEntry(key1, loc1)); + ASSERT_EQ(cursor->next(), boost::none); - cursor->advance(); - ASSERT_EQUALS( key2, cursor->getKey() ); - ASSERT_EQUALS( loc2, cursor->getRecordId() ); - - cursor->advance(); - ASSERT_EQUALS( key1, cursor->getKey() ); - ASSERT_EQUALS( loc1, cursor->getRecordId() ); - - cursor->advance(); - ASSERT( cursor->isEOF() ); + ASSERT_EQ(cursor->seek(key3, true), IndexKeyEntry(key3, loc3)); + ASSERT_EQ(cursor->next(), IndexKeyEntry(key2, loc2)); + ASSERT_EQ(cursor->next(), IndexKeyEntry(key1, loc1)); + ASSERT_EQ(cursor->next(), boost::none); } } // Insert multiple compound keys and try to locate them using a forward cursor // by specifying their exact key and RecordId. TEST( SortedDataInterface, LocateMultipleCompoundKeys ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), 1 ) ); - ASSERT( !cursor->locate( compoundKey1a, loc1 ) ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get()) ); + ASSERT( !cursor->seek( compoundKey1a, true ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), compoundKey1a, loc1, true ) ); @@ -357,27 +291,17 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), 1 ) ); - - ASSERT( cursor->locate( compoundKey1a, loc1 ) ); - ASSERT_EQUALS( compoundKey1a, cursor->getKey() ); - ASSERT_EQUALS( loc1, cursor->getRecordId() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get()) ); - cursor->advance(); - ASSERT_EQUALS( compoundKey1b, cursor->getKey() ); - ASSERT_EQUALS( loc2, cursor->getRecordId() ); - - cursor->advance(); - ASSERT_EQUALS( compoundKey2b, cursor->getKey() ); - ASSERT_EQUALS( loc3, cursor->getRecordId() ); - - cursor->advance(); - ASSERT( cursor->isEOF() ); + ASSERT_EQ(cursor->seek(compoundKey1a, true), IndexKeyEntry(compoundKey1a, loc1)); + ASSERT_EQ(cursor->next(), IndexKeyEntry(compoundKey1b, loc2)); + ASSERT_EQ(cursor->next(), IndexKeyEntry(compoundKey2b, loc3)); + ASSERT_EQ(cursor->next(), boost::none); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), compoundKey1c, loc4, true ) ); @@ -387,48 +311,32 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), 1 ) ); - - ASSERT( cursor->locate( compoundKey1a, loc1 ) ); - ASSERT_EQUALS( compoundKey1a, cursor->getKey() ); - ASSERT_EQUALS( loc1, cursor->getRecordId() ); - - cursor->advance(); - ASSERT_EQUALS( compoundKey1b, cursor->getKey() ); - ASSERT_EQUALS( loc2, cursor->getRecordId() ); - - cursor->advance(); - ASSERT_EQUALS( compoundKey1c, cursor->getKey() ); - ASSERT_EQUALS( loc4, cursor->getRecordId() ); - - cursor->advance(); - ASSERT_EQUALS( compoundKey2b, cursor->getKey() ); - ASSERT_EQUALS( loc3, cursor->getRecordId() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get()) ); - cursor->advance(); - ASSERT_EQUALS( compoundKey3a, cursor->getKey() ); - ASSERT_EQUALS( loc5, cursor->getRecordId() ); - - cursor->advance(); - ASSERT( cursor->isEOF() ); + ASSERT_EQ(cursor->seek(compoundKey1a, true), IndexKeyEntry(compoundKey1a, loc1)); + ASSERT_EQ(cursor->next(), IndexKeyEntry(compoundKey1b, loc2)); + ASSERT_EQ(cursor->next(), IndexKeyEntry(compoundKey1c, loc4)); + ASSERT_EQ(cursor->next(), IndexKeyEntry(compoundKey2b, loc3)); + ASSERT_EQ(cursor->next(), IndexKeyEntry(compoundKey3a, loc5)); + ASSERT_EQ(cursor->next(), boost::none); } } // Insert multiple compound keys and try to locate them using a reverse cursor // by specifying their exact key and RecordId. TEST( SortedDataInterface, LocateMultipleCompoundKeysReversed ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), -1 ) ); - ASSERT( !cursor->locate( compoundKey3a, loc1 ) ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get(), false) ); + ASSERT( !cursor->seek( compoundKey3a, true ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), compoundKey1a, loc1, true ) ); @@ -439,27 +347,17 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), -1 ) ); - - ASSERT( cursor->locate( compoundKey2b, loc3 ) ); - ASSERT_EQUALS( compoundKey2b, cursor->getKey() ); - ASSERT_EQUALS( loc3, cursor->getRecordId() ); - - cursor->advance(); - ASSERT_EQUALS( compoundKey1b, cursor->getKey() ); - ASSERT_EQUALS( loc2, cursor->getRecordId() ); - - cursor->advance(); - ASSERT_EQUALS( compoundKey1a, cursor->getKey() ); - ASSERT_EQUALS( loc1, cursor->getRecordId() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get(), false) ); - cursor->advance(); - ASSERT( cursor->isEOF() ); + ASSERT_EQ(cursor->seek(compoundKey2b, true), IndexKeyEntry(compoundKey2b, loc3)); + ASSERT_EQ(cursor->next(), IndexKeyEntry(compoundKey1b, loc2)); + ASSERT_EQ(cursor->next(), IndexKeyEntry(compoundKey1a, loc1)); + ASSERT_EQ(cursor->next(), boost::none); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), compoundKey1c, loc4, true ) ); @@ -469,48 +367,32 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), -1 ) ); - - ASSERT( cursor->locate( compoundKey3a, loc5 ) ); - ASSERT_EQUALS( compoundKey3a, cursor->getKey() ); - ASSERT_EQUALS( loc5, cursor->getRecordId() ); - - cursor->advance(); - ASSERT_EQUALS( compoundKey2b, cursor->getKey() ); - ASSERT_EQUALS( loc3, cursor->getRecordId() ); - - cursor->advance(); - ASSERT_EQUALS( compoundKey1c, cursor->getKey() ); - ASSERT_EQUALS( loc4, cursor->getRecordId() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get(), false) ); - cursor->advance(); - ASSERT_EQUALS( compoundKey1b, cursor->getKey() ); - ASSERT_EQUALS( loc2, cursor->getRecordId() ); - - cursor->advance(); - ASSERT_EQUALS( compoundKey1a, cursor->getKey() ); - ASSERT_EQUALS( loc1, cursor->getRecordId() ); - - cursor->advance(); - ASSERT( cursor->isEOF() ); + ASSERT_EQ(cursor->seek(compoundKey3a, true), IndexKeyEntry(compoundKey3a, loc5)); + ASSERT_EQ(cursor->next(), IndexKeyEntry(compoundKey2b, loc3)); + ASSERT_EQ(cursor->next(), IndexKeyEntry(compoundKey1c, loc4)); + ASSERT_EQ(cursor->next(), IndexKeyEntry(compoundKey1b, loc2)); + ASSERT_EQ(cursor->next(), IndexKeyEntry(compoundKey1a, loc1)); + ASSERT_EQ(cursor->next(), boost::none); } } // Insert multiple keys and try to locate them using a forward cursor // by specifying either a smaller key or RecordId. TEST( SortedDataInterface, LocateIndirect ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), 1 ) ); - ASSERT( !cursor->locate( key1, loc1 ) ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get()) ); + ASSERT( !cursor->seek( key1, true ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), key1, loc1, true ) ); @@ -520,19 +402,15 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), 1 ) ); - - ASSERT( !cursor->locate( key1, RecordId::max() ) ); - ASSERT_EQUALS( key2, cursor->getKey() ); - ASSERT_EQUALS( loc2, cursor->getRecordId() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get()) ); - cursor->advance(); - ASSERT( cursor->isEOF() ); + ASSERT_EQ(cursor->seek(key1, false), IndexKeyEntry(key2, loc2)); + ASSERT_EQ(cursor->next(), boost::none); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), key3, loc3, true ) ); @@ -541,40 +419,30 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), 1 ) ); - - ASSERT( !cursor->locate( key1, RecordId::min() ) ); - ASSERT_EQUALS( key1, cursor->getKey() ); - ASSERT_EQUALS( loc1, cursor->getRecordId() ); - - cursor->advance(); - ASSERT_EQUALS( key2, cursor->getKey() ); - ASSERT_EQUALS( loc2, cursor->getRecordId() ); - - cursor->advance(); - ASSERT_EQUALS( key3, cursor->getKey() ); - ASSERT_EQUALS( loc3, cursor->getRecordId() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get()) ); - cursor->advance(); - ASSERT( cursor->isEOF() ); + ASSERT_EQ(cursor->seek(key1, true), IndexKeyEntry(key1, loc1)); + ASSERT_EQ(cursor->next(), IndexKeyEntry(key2, loc2)); + ASSERT_EQ(cursor->next(), IndexKeyEntry(key3, loc3)); + ASSERT_EQ(cursor->next(), boost::none); } } // Insert multiple keys and try to locate them using a reverse cursor // by specifying either a larger key or RecordId. TEST( SortedDataInterface, LocateIndirectReversed ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), -1 ) ); - ASSERT( !cursor->locate( key3, loc1 ) ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get(), false) ); + ASSERT( !cursor->seek( key3, true ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), key1, loc1, true ) ); @@ -584,19 +452,15 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), -1 ) ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get(), false) ); - ASSERT( !cursor->locate( key2, RecordId::min() ) ); - ASSERT_EQUALS( key1, cursor->getKey() ); - ASSERT_EQUALS( loc1, cursor->getRecordId() ); - - cursor->advance(); - ASSERT( cursor->isEOF() ); + ASSERT_EQ(cursor->seek(key2, false), IndexKeyEntry(key1, loc1)); + ASSERT_EQ(cursor->next(), boost::none); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), key3, loc3, true ) ); @@ -605,40 +469,30 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), -1 ) ); - - ASSERT( !cursor->locate( key3, RecordId::max() ) ); - ASSERT_EQUALS( key3, cursor->getKey() ); - ASSERT_EQUALS( loc3, cursor->getRecordId() ); - - cursor->advance(); - ASSERT_EQUALS( key2, cursor->getKey() ); - ASSERT_EQUALS( loc2, cursor->getRecordId() ); - - cursor->advance(); - ASSERT_EQUALS( key1, cursor->getKey() ); - ASSERT_EQUALS( loc1, cursor->getRecordId() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get(), false) ); - cursor->advance(); - ASSERT( cursor->isEOF() ); + ASSERT_EQ(cursor->seek(key3, true), IndexKeyEntry(key3, loc3)); + ASSERT_EQ(cursor->next(), IndexKeyEntry(key2, loc2)); + ASSERT_EQ(cursor->next(), IndexKeyEntry(key1, loc1)); + ASSERT_EQ(cursor->next(), boost::none); } } // Insert multiple compound keys and try to locate them using a forward cursor // by specifying either a smaller key or RecordId. TEST( SortedDataInterface, LocateIndirectCompoundKeys ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), 1 ) ); - ASSERT( !cursor->locate( compoundKey1a, loc1 ) ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get()) ); + ASSERT( !cursor->seek( compoundKey1a, true ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), compoundKey1a, loc1, true ) ); @@ -649,23 +503,16 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), 1 ) ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get()) ); - ASSERT( !cursor->locate( compoundKey1a, RecordId::max() ) ); - ASSERT_EQUALS( compoundKey1b, cursor->getKey() ); - ASSERT_EQUALS( loc2, cursor->getRecordId() ); - - cursor->advance(); - ASSERT_EQUALS( compoundKey2b, cursor->getKey() ); - ASSERT_EQUALS( loc3, cursor->getRecordId() ); - - cursor->advance(); - ASSERT( cursor->isEOF() ); + ASSERT_EQ(cursor->seek(compoundKey1a, false), IndexKeyEntry(compoundKey1b, loc2)); + ASSERT_EQ(cursor->next(), IndexKeyEntry(compoundKey2b, loc3)); + ASSERT_EQ(cursor->next(), boost::none); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), compoundKey1c, loc4, true ) ); @@ -675,36 +522,29 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), 1 ) ); - - ASSERT( !cursor->locate( compoundKey2a, loc1 ) ); - ASSERT_EQUALS( compoundKey2b, cursor->getKey() ); - ASSERT_EQUALS( loc3, cursor->getRecordId() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get()) ); - cursor->advance(); - ASSERT_EQUALS( compoundKey3a, cursor->getKey() ); - ASSERT_EQUALS( loc5, cursor->getRecordId() ); - - cursor->advance(); - ASSERT( cursor->isEOF() ); + ASSERT_EQ(cursor->seek(compoundKey2a, true), IndexKeyEntry(compoundKey2b, loc3)); + ASSERT_EQ(cursor->next(), IndexKeyEntry(compoundKey3a, loc5)); + ASSERT_EQ(cursor->next(), boost::none); } } // Insert multiple compound keys and try to locate them using a reverse cursor // by specifying either a larger key or RecordId. TEST( SortedDataInterface, LocateIndirectCompoundKeysReversed ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), -1 ) ); - ASSERT( !cursor->locate( compoundKey3a, loc1 ) ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get(), false) ); + ASSERT( !cursor->seek( compoundKey3a, true ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), compoundKey1a, loc1, true ) ); @@ -715,23 +555,16 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), -1 ) ); - - ASSERT( !cursor->locate( compoundKey2b, RecordId::min() ) ); - ASSERT_EQUALS( compoundKey1b, cursor->getKey() ); - ASSERT_EQUALS( loc2, cursor->getRecordId() ); - - cursor->advance(); - ASSERT_EQUALS( compoundKey1a, cursor->getKey() ); - ASSERT_EQUALS( loc1, cursor->getRecordId() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get(), false) ); - cursor->advance(); - ASSERT( cursor->isEOF() ); + ASSERT_EQ(cursor->seek(compoundKey2b, false), IndexKeyEntry(compoundKey1b, loc2)); + ASSERT_EQ(cursor->next(), IndexKeyEntry(compoundKey1a, loc1)); + ASSERT_EQ(cursor->next(), boost::none); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), compoundKey1c, loc4, true ) ); @@ -741,63 +574,53 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), -1 ) ); - - ASSERT( !cursor->locate( compoundKey1d, loc1 ) ); - ASSERT_EQUALS( compoundKey1c, cursor->getKey() ); - ASSERT_EQUALS( loc4, cursor->getRecordId() ); - - cursor->advance(); - ASSERT_EQUALS( compoundKey1b, cursor->getKey() ); - ASSERT_EQUALS( loc2, cursor->getRecordId() ); - - cursor->advance(); - ASSERT_EQUALS( compoundKey1a, cursor->getKey() ); - ASSERT_EQUALS( loc1, cursor->getRecordId() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get(), false) ); - cursor->advance(); - ASSERT( cursor->isEOF() ); + ASSERT_EQ(cursor->seek(compoundKey1d, true), IndexKeyEntry(compoundKey1c, loc4)); + ASSERT_EQ(cursor->next(), IndexKeyEntry(compoundKey1b, loc2)); + ASSERT_EQ(cursor->next(), IndexKeyEntry(compoundKey1a, loc1)); + ASSERT_EQ(cursor->next(), boost::none); } } // Call locate on a forward cursor of an empty index and verify that the cursor // is positioned at EOF. TEST( SortedDataInterface, LocateEmpty ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT( sorted->isEmpty( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), 1 ) ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get()) ); - ASSERT( !cursor->locate( BSONObj(), RecordId::min() ) ); - ASSERT( cursor->isEOF() ); + ASSERT( !cursor->seek( BSONObj(), true ) ); + ASSERT( !cursor->next() ); } } // Call locate on a reverse cursor of an empty index and verify that the cursor // is positioned at EOF. TEST( SortedDataInterface, LocateEmptyReversed ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT( sorted->isEmpty( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), -1 ) ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get(), false) ); - ASSERT( !cursor->locate( BSONObj(), RecordId::max() ) ); - ASSERT( cursor->isEOF() ); + ASSERT( !cursor->seek( BSONObj(), true ) ); + ASSERT( !cursor->next() ); } } diff --git a/src/mongo/db/storage/sorted_data_interface_test_cursor_position.cpp b/src/mongo/db/storage/sorted_data_interface_test_cursor_position.cpp deleted file mode 100644 index 41e74cddf4e..00000000000 --- a/src/mongo/db/storage/sorted_data_interface_test_cursor_position.cpp +++ /dev/null @@ -1,481 +0,0 @@ -// sorted_data_interface_test_cursor_position.cpp - -/** - * Copyright (C) 2014 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/db/storage/sorted_data_interface_test_harness.h" - -#include <boost/scoped_ptr.hpp> - -#include "mongo/db/storage/sorted_data_interface.h" -#include "mongo/unittest/unittest.h" - -namespace mongo { - - using boost::scoped_ptr; - - // Verify that two forward cursors positioned at EOF are considered - // to point to the same place. - TEST( SortedDataInterface, CursorsPointToSamePlaceIfEOF ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - ASSERT( sorted->isEmpty( opCtx.get() ) ); - } - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor1( sorted->newCursor( opCtx.get(), 1 ) ); - scoped_ptr<SortedDataInterface::Cursor> cursor2( sorted->newCursor( opCtx.get(), 1 ) ); - - ASSERT( !cursor1->locate( minKey, RecordId::min() ) ); - ASSERT( !cursor2->locate( minKey, RecordId::min() ) ); - ASSERT( cursor1->isEOF() ); - ASSERT( cursor2->isEOF() ); - ASSERT( cursor1->pointsToSamePlaceAs( *cursor2 ) ); - ASSERT( cursor2->pointsToSamePlaceAs( *cursor1 ) ); - } - } - - // Verify that two reverse cursors positioned at EOF are considered - // to point to the same place. - TEST( SortedDataInterface, CursorsPointToSamePlaceIfEOFReversed ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - ASSERT( sorted->isEmpty( opCtx.get() ) ); - } - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor1( sorted->newCursor( opCtx.get(), -1 ) ); - scoped_ptr<SortedDataInterface::Cursor> cursor2( sorted->newCursor( opCtx.get(), -1 ) ); - - ASSERT( !cursor1->locate( maxKey, RecordId::max() ) ); - ASSERT( !cursor2->locate( maxKey, RecordId::max() ) ); - ASSERT( cursor1->isEOF() ); - ASSERT( cursor2->isEOF() ); - ASSERT( cursor1->pointsToSamePlaceAs( *cursor2 ) ); - ASSERT( cursor2->pointsToSamePlaceAs( *cursor1 ) ); - } - } - - // Iterate two forward cursors simultaneously and verify they are considered - // to point to the same place. - TEST( SortedDataInterface, CursorsPointToSamePlace ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( true ) ); - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - ASSERT( sorted->isEmpty( opCtx.get() ) ); - } - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - { - WriteUnitOfWork uow( opCtx.get() ); - ASSERT_OK( sorted->insert( opCtx.get(), key1, loc1, false ) ); - ASSERT_OK( sorted->insert( opCtx.get(), key2, loc2, false ) ); - uow.commit(); - } - } - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - ASSERT_EQUALS( 2, sorted->numEntries( opCtx.get() ) ); - } - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor1( sorted->newCursor( opCtx.get(), 1 ) ); - scoped_ptr<SortedDataInterface::Cursor> cursor2( sorted->newCursor( opCtx.get(), 1 ) ); - - ASSERT( cursor1->locate( key1, loc1 ) ); - ASSERT( cursor2->locate( key1, loc1 ) ); - ASSERT( cursor1->pointsToSamePlaceAs( *cursor2 ) ); - ASSERT( cursor2->pointsToSamePlaceAs( *cursor1 ) ); - - cursor1->advance(); - cursor2->advance(); - ASSERT( cursor1->pointsToSamePlaceAs( *cursor2 ) ); - ASSERT( cursor2->pointsToSamePlaceAs( *cursor1 ) ); - - cursor1->advance(); - cursor2->advance(); - ASSERT( cursor1->pointsToSamePlaceAs( *cursor2 ) ); - ASSERT( cursor2->pointsToSamePlaceAs( *cursor1 ) ); - } - } - - // Iterate two reverse cursors simultaneously and verify they are considered - // to point to the same place. - TEST( SortedDataInterface, CursorsPointToSamePlaceReversed ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - ASSERT( sorted->isEmpty( opCtx.get() ) ); - } - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - { - WriteUnitOfWork uow( opCtx.get() ); - ASSERT_OK( sorted->insert( opCtx.get(), key1, loc1, true ) ); - ASSERT_OK( sorted->insert( opCtx.get(), key2, loc2, true ) ); - uow.commit(); - } - } - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - ASSERT_EQUALS( 2, sorted->numEntries( opCtx.get() ) ); - } - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor1( sorted->newCursor( opCtx.get(), -1 ) ); - scoped_ptr<SortedDataInterface::Cursor> cursor2( sorted->newCursor( opCtx.get(), -1 ) ); - - ASSERT( cursor1->locate( key2, loc2 ) ); - ASSERT( cursor2->locate( key2, loc2 ) ); - ASSERT( cursor1->pointsToSamePlaceAs( *cursor2 ) ); - ASSERT( cursor2->pointsToSamePlaceAs( *cursor1 ) ); - - cursor1->advance(); - cursor2->advance(); - ASSERT( cursor1->pointsToSamePlaceAs( *cursor2 ) ); - ASSERT( cursor2->pointsToSamePlaceAs( *cursor1 ) ); - - cursor1->advance(); - cursor2->advance(); - ASSERT( cursor1->pointsToSamePlaceAs( *cursor2 ) ); - ASSERT( cursor2->pointsToSamePlaceAs( *cursor1 ) ); - } - } - - // Verify that two forward cursors positioned at different keys are not considered - // to point to the same place. - TEST( SortedDataInterface, CursorsPointToDifferentKeys ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - ASSERT( sorted->isEmpty( opCtx.get() ) ); - } - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - { - WriteUnitOfWork uow( opCtx.get() ); - ASSERT_OK( sorted->insert( opCtx.get(), key1, loc1, true ) ); - ASSERT_OK( sorted->insert( opCtx.get(), key2, loc2, true ) ); - uow.commit(); - } - } - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - ASSERT_EQUALS( 2, sorted->numEntries( opCtx.get() ) ); - } - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor1( sorted->newCursor( opCtx.get(), 1 ) ); - scoped_ptr<SortedDataInterface::Cursor> cursor2( sorted->newCursor( opCtx.get(), 1 ) ); - - ASSERT( cursor1->locate( key1, loc1 ) ); - ASSERT( cursor2->locate( key2, loc2 ) ); - ASSERT( !cursor1->pointsToSamePlaceAs( *cursor2 ) ); - ASSERT( !cursor2->pointsToSamePlaceAs( *cursor1 ) ); - } - } - - // Verify that two reverse cursors positioned at different keys are not considered - // to point to the same place. - TEST( SortedDataInterface, CursorsPointToDifferentKeysReversed ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - ASSERT( sorted->isEmpty( opCtx.get() ) ); - } - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - { - WriteUnitOfWork uow( opCtx.get() ); - ASSERT_OK( sorted->insert( opCtx.get(), key1, loc1, true ) ); - ASSERT_OK( sorted->insert( opCtx.get(), key2, loc2, true ) ); - uow.commit(); - } - } - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - ASSERT_EQUALS( 2, sorted->numEntries( opCtx.get() ) ); - } - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor1( sorted->newCursor( opCtx.get(), -1 ) ); - scoped_ptr<SortedDataInterface::Cursor> cursor2( sorted->newCursor( opCtx.get(), -1 ) ); - - ASSERT( cursor1->locate( key1, loc1 ) ); - ASSERT( cursor2->locate( key2, loc2 ) ); - ASSERT( !cursor1->pointsToSamePlaceAs( *cursor2 ) ); - ASSERT( !cursor2->pointsToSamePlaceAs( *cursor1 ) ); - } - } - - // Verify that two forward cursors positioned at a duplicate key, but with - // different RecordIds are not considered to point to the same place. - TEST( SortedDataInterface, CursorsPointToDifferentDiskLocs ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - ASSERT( sorted->isEmpty( opCtx.get() ) ); - } - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - { - WriteUnitOfWork uow( opCtx.get() ); - ASSERT_OK( sorted->insert( opCtx.get(), key1, loc1, true ) ); - ASSERT_OK( sorted->insert( opCtx.get(), key1, loc2, true ) ); - uow.commit(); - } - } - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - ASSERT_EQUALS( 2, sorted->numEntries( opCtx.get() ) ); - } - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor1( sorted->newCursor( opCtx.get(), 1 ) ); - scoped_ptr<SortedDataInterface::Cursor> cursor2( sorted->newCursor( opCtx.get(), 1 ) ); - - ASSERT( cursor1->locate( key1, loc1 ) ); - ASSERT( cursor2->locate( key1, loc2 ) ); - ASSERT( !cursor1->pointsToSamePlaceAs( *cursor2 ) ); - ASSERT( !cursor2->pointsToSamePlaceAs( *cursor1 ) ); - } - } - - // Verify that two reverse cursors positioned at a duplicate key, but with - // different RecordIds are not considered to point to the same place. - TEST( SortedDataInterface, CursorsPointToDifferentDiskLocsReversed ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - ASSERT( sorted->isEmpty( opCtx.get() ) ); - } - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - { - WriteUnitOfWork uow( opCtx.get() ); - ASSERT_OK( sorted->insert( opCtx.get(), key1, loc1, true ) ); - ASSERT_OK( sorted->insert( opCtx.get(), key1, loc2, true /* allow duplicates */ ) ); - uow.commit(); - } - } - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - ASSERT_EQUALS( 2, sorted->numEntries( opCtx.get() ) ); - } - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor1( sorted->newCursor( opCtx.get(), -1 ) ); - scoped_ptr<SortedDataInterface::Cursor> cursor2( sorted->newCursor( opCtx.get(), -1 ) ); - - ASSERT( cursor1->locate( key1, loc1 ) ); - ASSERT( cursor2->locate( key1, loc2 ) ); - ASSERT( !cursor1->pointsToSamePlaceAs( *cursor2 ) ); - ASSERT( !cursor2->pointsToSamePlaceAs( *cursor1 ) ); - } - } - - // Verify that a forward cursor and a reverse cursor positioned at the same key - // are considered to point to the same place. - TEST( SortedDataInterface, CursorPointsToSamePlaceRegardlessOfDirection ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( true ) ); - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - ASSERT( sorted->isEmpty( opCtx.get() ) ); - } - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - { - WriteUnitOfWork uow( opCtx.get() ); - ASSERT_OK( sorted->insert( opCtx.get(), key1, loc1, false ) ); - ASSERT_OK( sorted->insert( opCtx.get(), key2, loc2, false ) ); - ASSERT_OK( sorted->insert( opCtx.get(), key3, loc3, false ) ); - uow.commit(); - } - } - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - ASSERT_EQUALS( 3, sorted->numEntries( opCtx.get() ) ); - } - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor1( sorted->newCursor( opCtx.get(), 1 ) ); - scoped_ptr<SortedDataInterface::Cursor> cursor2( sorted->newCursor( opCtx.get(), -1 ) ); - - ASSERT( cursor1->locate( key1, loc1 ) ); - ASSERT( cursor2->locate( key3, loc3 ) ); - // SERVER-15480 the reverse cursor is incorrectly casted to a - // cursor of type InMemoryBtreeImpl::ForwardCursor - // ASSERT( !cursor1->pointsToSamePlaceAs( *cursor2 ) ); - // SERVER-15480 the forward cursor is incorrectly casted to a - // cursor of type InMemoryBtreeImpl::ReverseCursor - // ASSERT( !cursor2->pointsToSamePlaceAs( *cursor1 ) ); - - cursor1->advance(); - cursor2->advance(); - // SERVER-15480 the reverse cursor is incorrectly casted to a - // cursor of type InMemoryBtreeImpl::ForwardCursor - // ASSERT( cursor1->pointsToSamePlaceAs( *cursor2 ) ); - // SERVER-15480 the forward cursor is incorrectly casted to a - // cursor of type InMemoryBtreeImpl::ReverseCursor - // ASSERT( cursor2->pointsToSamePlaceAs( *cursor1 ) ); - - cursor1->advance(); - cursor2->advance(); - // SERVER-15480 the reverse cursor is incorrectly casted to a - // cursor of type InMemoryBtreeImpl::ForwardCursor - // ASSERT( !cursor1->pointsToSamePlaceAs( *cursor2 ) ); - // SERVER-15480 the forward cursor is incorrectly casted to a - // cursor of type InMemoryBtreeImpl::ReverseCursor - // ASSERT( !cursor2->pointsToSamePlaceAs( *cursor1 ) ); - } - } - - // Verify that a forward cursor always points to the same place as itself. - TEST( SortedDataInterface, CursorPointsToSamePlaceAsItself ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - ASSERT( sorted->isEmpty( opCtx.get() ) ); - } - - int nToInsert = 10; - for ( int i = 0; i < nToInsert; i++ ) { - scoped_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(); - } - } - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - ASSERT_EQUALS( nToInsert, sorted->numEntries( opCtx.get() ) ); - } - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), 1 ) ); - ASSERT( !cursor->locate( minKey, RecordId::min() ) ); - for ( int i = 0; i < nToInsert; i++ ) { - ASSERT( !cursor->isEOF() ); - ASSERT( cursor->pointsToSamePlaceAs( *cursor ) ); - cursor->advance(); - } - ASSERT( cursor->isEOF() ); - } - } - - // Verify that a reverse cursor always points to the same place as itself. - TEST( SortedDataInterface, CursorPointsToSamePlaceAsItselfReversed ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - ASSERT( sorted->isEmpty( opCtx.get() ) ); - } - - int nToInsert = 10; - for ( int i = 0; i < nToInsert; i++ ) { - scoped_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(); - } - } - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - ASSERT_EQUALS( nToInsert, sorted->numEntries( opCtx.get() ) ); - } - - { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), -1 ) ); - ASSERT( !cursor->locate( maxKey, RecordId::max() ) ); - for ( int i = nToInsert - 1; i >= 0; i-- ) { - ASSERT( !cursor->isEOF() ); - ASSERT( cursor->pointsToSamePlaceAs( *cursor ) ); - cursor->advance(); - } - ASSERT( cursor->isEOF() ); - } - } - -} // namespace mongo diff --git a/src/mongo/db/storage/sorted_data_interface_test_cursor_saverestore.cpp b/src/mongo/db/storage/sorted_data_interface_test_cursor_saverestore.cpp index 2e71e60b145..2518d96490e 100644 --- a/src/mongo/db/storage/sorted_data_interface_test_cursor_saverestore.cpp +++ b/src/mongo/db/storage/sorted_data_interface_test_cursor_saverestore.cpp @@ -30,30 +30,28 @@ #include "mongo/db/storage/sorted_data_interface_test_harness.h" -#include <boost/scoped_ptr.hpp> +#include <memory> #include "mongo/db/storage/sorted_data_interface.h" #include "mongo/unittest/unittest.h" namespace mongo { - using boost::scoped_ptr; - // Insert multiple keys and try to iterate through all of them // using a forward cursor while calling savePosition() and // restorePosition() in succession. TEST( SortedDataInterface, SaveAndRestorePositionWhileIterateCursor ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT( sorted->isEmpty( opCtx.get() ) ); } int nToInsert = 10; for ( int i = 0; i < nToInsert; i++ ) { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); BSONObj key = BSON( "" << i ); @@ -64,23 +62,23 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( nToInsert, sorted->numEntries( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), 1 ) ); - ASSERT( !cursor->locate( minKey, RecordId::min() ) ); - for ( int i = 0; i < nToInsert; i++ ) { - ASSERT( !cursor->isEOF() ); - ASSERT_EQUALS( BSON( "" << i ), cursor->getKey() ); - ASSERT_EQUALS( RecordId( 42, i * 2 ), cursor->getRecordId() ); - cursor->advance(); - cursor->savePosition(); - cursor->restorePosition( 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(BSON( "" << i), RecordId(42, i * 2))); + + cursor->savePositioned(); + cursor->restore( opCtx.get() ); } - ASSERT( cursor->isEOF() ); + ASSERT( !cursor->next() ); + ASSERT_EQ(i, nToInsert); } } @@ -88,17 +86,17 @@ namespace mongo { // using a reverse cursor while calling savePosition() and // restorePosition() in succession. TEST( SortedDataInterface, SaveAndRestorePositionWhileIterateCursorReversed ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT( sorted->isEmpty( opCtx.get() ) ); } int nToInsert = 10; for ( int i = 0; i < nToInsert; i++ ) { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); BSONObj key = BSON( "" << i ); @@ -109,23 +107,23 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( nToInsert, sorted->numEntries( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), -1 ) ); - ASSERT( !cursor->locate( maxKey, RecordId::max() ) ); - for ( int i = nToInsert - 1; i >= 0; i-- ) { - ASSERT( !cursor->isEOF() ); - ASSERT_EQUALS( BSON( "" << i ), cursor->getKey() ); - ASSERT_EQUALS( RecordId( 42, i * 2 ), cursor->getRecordId() ); - cursor->advance(); - cursor->savePosition(); - cursor->restorePosition( 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->isEOF() ); + ASSERT( !cursor->next() ); + ASSERT_EQ(i, -1); } } @@ -134,17 +132,17 @@ namespace mongo { // restorePosition() in succession. Verify that the RecordId is saved // as part of the current position of the cursor. TEST( SortedDataInterface, SaveAndRestorePositionWhileIterateCursorWithDupKeys ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT( sorted->isEmpty( opCtx.get() ) ); } int nToInsert = 10; for ( int i = 0; i < nToInsert; i++ ) { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); RecordId loc( 42, i * 2 ); @@ -154,23 +152,23 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( nToInsert, sorted->numEntries( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), 1 ) ); - ASSERT( !cursor->locate( minKey, RecordId::min() ) ); - for ( int i = 0; i < nToInsert; i++ ) { - ASSERT( !cursor->isEOF() ); - ASSERT_EQUALS( key1, cursor->getKey() ); - ASSERT_EQUALS( RecordId( 42, i * 2 ), cursor->getRecordId() ); - cursor->advance(); - cursor->savePosition(); - cursor->restorePosition( 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->isEOF() ); + ASSERT( !cursor->next() ); + ASSERT_EQ(i, nToInsert); } } @@ -179,17 +177,17 @@ namespace mongo { // restorePosition() in succession. Verify that the RecordId is saved // as part of the current position of the cursor. TEST( SortedDataInterface, SaveAndRestorePositionWhileIterateCursorWithDupKeysReversed ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT( sorted->isEmpty( opCtx.get() ) ); } int nToInsert = 10; for ( int i = 0; i < nToInsert; i++ ) { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); RecordId loc( 42, i * 2 ); @@ -199,39 +197,39 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( nToInsert, sorted->numEntries( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), -1 ) ); - ASSERT( !cursor->locate( maxKey, RecordId::max() ) ); - for ( int i = nToInsert - 1; i >= 0; i-- ) { - ASSERT( !cursor->isEOF() ); - ASSERT_EQUALS( key1, cursor->getKey() ); - ASSERT_EQUALS( RecordId( 42, i * 2 ), cursor->getRecordId() ); - cursor->advance(); - cursor->savePosition(); - cursor->restorePosition( 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(key1, RecordId(42, i * 2))); + + cursor->savePositioned(); + cursor->restore( opCtx.get() ); } - ASSERT( cursor->isEOF() ); + ASSERT( !cursor->next() ); + ASSERT_EQ(i, -1); } } // Call savePosition() on a forward cursor without ever calling restorePosition(). // May be useful to run this test under valgrind to verify there are no leaks. TEST( SortedDataInterface, SavePositionWithoutRestore ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( true ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( true ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT( sorted->isEmpty( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), key1, loc1, false ) ); @@ -240,30 +238,30 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( 1, sorted->numEntries( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), 1 ) ); - cursor->savePosition(); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get()) ); + cursor->savePositioned(); } } // Call savePosition() on a reverse cursor without ever calling restorePosition(). // May be useful to run this test under valgrind to verify there are no leaks. TEST( SortedDataInterface, SavePositionWithoutRestoreReversed ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT( sorted->isEmpty( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), key1, loc1, true ) ); @@ -272,14 +270,14 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( 1, sorted->numEntries( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), -1 ) ); - cursor->savePosition(); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get(), false) ); + cursor->savePositioned(); } } diff --git a/src/mongo/db/storage/sorted_data_interface_test_harness.cpp b/src/mongo/db/storage/sorted_data_interface_test_harness.cpp index f26ac1ef494..066b6a0b1a9 100644 --- a/src/mongo/db/storage/sorted_data_interface_test_harness.cpp +++ b/src/mongo/db/storage/sorted_data_interface_test_harness.cpp @@ -30,21 +30,19 @@ #include "mongo/db/storage/sorted_data_interface_test_harness.h" -#include <boost/scoped_ptr.hpp> +#include <memory> #include "mongo/db/storage/sorted_data_interface.h" #include "mongo/unittest/unittest.h" namespace mongo { - using boost::scoped_ptr; - TEST( SortedDataInterface, InsertWithDups1 ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); sorted->insert( opCtx.get(), BSON( "" << 1 ), RecordId( 5, 2 ), true ); @@ -53,7 +51,7 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); sorted->insert( opCtx.get(), BSON( "" << 1 ), RecordId( 6, 2 ), true ); @@ -62,7 +60,7 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( 2, sorted->numEntries( opCtx.get() ) ); long long x = 0; @@ -72,11 +70,11 @@ namespace mongo { } TEST( SortedDataInterface, InsertWithDups2 ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); sorted->insert( opCtx.get(), BSON( "" << 1 ), RecordId( 5, 18 ), true ); @@ -85,7 +83,7 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); sorted->insert( opCtx.get(), BSON( "" << 1 ), RecordId( 5, 20 ), true ); @@ -94,17 +92,17 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( 2, sorted->numEntries( opCtx.get() ) ); } } TEST( SortedDataInterface, InsertWithDups3AndRollback ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); sorted->insert( opCtx.get(), BSON( "" << 1 ), RecordId( 5, 18 ), true ); @@ -113,7 +111,7 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); sorted->insert( opCtx.get(), BSON( "" << 1 ), RecordId( 5, 20 ), true ); @@ -122,17 +120,17 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( 1, sorted->numEntries( opCtx.get() ) ); } } TEST( SortedDataInterface, InsertNoDups1 ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( true ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( true ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); sorted->insert( opCtx.get(), BSON( "" << 1 ), RecordId( 5, 18 ), false ); @@ -141,7 +139,7 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); sorted->insert( opCtx.get(), BSON( "" << 2 ), RecordId( 5, 20 ), false ); @@ -150,18 +148,18 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( 2, sorted->numEntries( opCtx.get() ) ); } } TEST( SortedDataInterface, InsertNoDups2 ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( true ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( true ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); sorted->insert( opCtx.get(), BSON( "" << 1 ), RecordId( 5, 2 ), false ); @@ -170,7 +168,7 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); sorted->insert( opCtx.get(), BSON( "" << 1 ), RecordId( 5, 4 ), false ); @@ -179,18 +177,18 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( 1, sorted->numEntries( opCtx.get() ) ); } } TEST( SortedDataInterface, Unindex1 ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); sorted->insert( opCtx.get(), BSON( "" << 1 ), RecordId( 5, 18 ), true ); @@ -199,12 +197,12 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( 1, sorted->numEntries( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); sorted->unindex( opCtx.get(), BSON( "" << 1 ), RecordId( 5, 20 ), true ); @@ -214,12 +212,12 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( 1, sorted->numEntries( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); sorted->unindex( opCtx.get(), BSON( "" << 2 ), RecordId( 5, 18 ), true ); @@ -229,13 +227,13 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( 1, sorted->numEntries( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); sorted->unindex( opCtx.get(), BSON( "" << 1 ), RecordId( 5, 18 ), true ); @@ -245,18 +243,18 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT( sorted->isEmpty( opCtx.get() ) ); } } TEST( SortedDataInterface, Unindex2Rollback ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); sorted->insert( opCtx.get(), BSON( "" << 1 ), RecordId( 5, 18 ), true ); @@ -265,12 +263,12 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( 1, sorted->numEntries( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); sorted->unindex( opCtx.get(), BSON( "" << 1 ), RecordId( 5, 18 ), true ); @@ -280,7 +278,7 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( 1, sorted->numEntries( opCtx.get() ) ); } @@ -288,12 +286,12 @@ namespace mongo { TEST( SortedDataInterface, CursorIterate1 ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); int N = 5; for ( int i = 0; i < N; i++ ) { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), BSON( "" << i ), RecordId( 5, i * 2 ), true ) ); @@ -302,16 +300,12 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), 1 ) ); - cursor->locate( BSONObj(), RecordId::min() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get()) ); int n = 0; - while ( !cursor->isEOF() ) { - RecordId loc = cursor->getRecordId(); - ASSERT_EQUALS( RecordId(5, n * 2), loc ); - ASSERT_EQUALS( BSON( "" << n ), cursor->getKey() ); + for (auto entry = cursor->seek(BSONObj(), true); entry; entry = cursor->next()) { + ASSERT_EQ(entry, IndexKeyEntry(BSON("" << n), RecordId(5, n * 2))); n++; - cursor->advance(); } ASSERT_EQUALS( N, n ); } @@ -320,12 +314,12 @@ namespace mongo { } TEST( SortedDataInterface, CursorIterate1WithSaveRestore ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); int N = 5; for ( int i = 0; i < N; i++ ) { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); sorted->insert( opCtx.get(), BSON( "" << i ), RecordId( 5, i * 2 ), true ); @@ -334,18 +328,14 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), 1 ) ); - cursor->locate( BSONObj(), RecordId::min() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get()) ); int n = 0; - while ( !cursor->isEOF() ) { - RecordId loc = cursor->getRecordId(); - ASSERT_EQUALS( RecordId(5, n * 2), loc ); - ASSERT_EQUALS( BSON( "" << n ), cursor->getKey() ); + for (auto entry = cursor->seek(BSONObj(), true); entry; entry = cursor->next()) { + ASSERT_EQ(entry, IndexKeyEntry(BSON("" << n), RecordId(5, n * 2))); n++; - cursor->advance(); - cursor->savePosition(); - cursor->restorePosition( opCtx.get() ); + cursor->savePositioned(); + cursor->restore( opCtx.get() ); } ASSERT_EQUALS( N, n ); } @@ -353,13 +343,13 @@ namespace mongo { } - TEST( SortedDataInterface, CursorIterate2WithSaveRestore ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + TEST( SortedDataInterface, CursorIterateAllDupKeysWithSaveRestore ) { + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); int N = 5; for ( int i = 0; i < N; i++ ) { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); sorted->insert( opCtx.get(), BSON( "" << 5 ), RecordId( 5, i * 2 ), true ); @@ -368,17 +358,14 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), 1 ) ); - cursor->locate( BSONObj(), RecordId::min() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get()) ); int n = 0; - while ( !cursor->isEOF() ) { - RecordId loc = cursor->getRecordId(); - ASSERT_EQUALS( RecordId(5, n * 2), loc ); + for (auto entry = cursor->seek(BSONObj(), true); entry; entry = cursor->next()) { + ASSERT_EQ(entry, IndexKeyEntry(BSON("" << 5), RecordId(5, n * 2))); n++; - cursor->advance(); - cursor->savePosition(); - cursor->restorePosition( opCtx.get() ); + cursor->savePositioned(); + cursor->restore( opCtx.get() ); } ASSERT_EQUALS( N, n ); } @@ -387,20 +374,20 @@ namespace mongo { TEST( SortedDataInterface, Locate1 ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); BSONObj key = BSON( "" << 1 ); RecordId loc( 5, 16 ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), 1 ) ); - ASSERT( !cursor->locate( key, loc ) ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get()) ); + ASSERT( !cursor->seek( key, true ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); Status res = sorted->insert( opCtx.get(), key, loc, true ); @@ -410,20 +397,18 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), 1 ) ); - ASSERT( cursor->locate( key, loc ) ); - ASSERT_EQUALS( key, cursor->getKey() ); - ASSERT_EQUALS( loc, cursor->getRecordId() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get()) ); + ASSERT_EQ(cursor->seek(key, true), IndexKeyEntry(key, loc)); } } TEST( SortedDataInterface, Locate2 ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); @@ -435,28 +420,22 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), 1 ) ); - ASSERT( !cursor->locate( BSON( "a" << 2 ), RecordId::min() ) ); - ASSERT( !cursor->isEOF() ); - ASSERT_EQUALS( BSON( "" << 2 ), cursor->getKey() ); - ASSERT_EQUALS( RecordId(1,4), cursor->getRecordId() ); - - cursor->advance(); - ASSERT_EQUALS( BSON( "" << 3 ), cursor->getKey() ); - ASSERT_EQUALS( RecordId(1,6), cursor->getRecordId() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get()) ); + ASSERT_EQ(cursor->seek(BSON("a" << 2), true), + IndexKeyEntry(BSON("" << 2), RecordId(1, 4))); - cursor->advance(); - ASSERT( cursor->isEOF() ); + ASSERT_EQ(cursor->next(), IndexKeyEntry(BSON("" << 3), RecordId(1, 6))); + ASSERT_EQ(cursor->next(), boost::none); } } TEST( SortedDataInterface, Locate2Empty ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); @@ -468,88 +447,69 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), 1 ) ); - ASSERT( !cursor->locate( BSONObj(), RecordId::min() ) ); - ASSERT( !cursor->isEOF() ); - ASSERT_EQUALS( BSON( "" << 1 ), cursor->getKey() ); - ASSERT_EQUALS( RecordId(1,2), cursor->getRecordId() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get()) ); + ASSERT_EQ(cursor->seek(BSONObj(), true), IndexKeyEntry(BSON("" << 1), RecordId(1, 2))); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), -1 ) ); - ASSERT( !cursor->locate( BSONObj(), RecordId::min() ) ); - ASSERT( cursor->isEOF() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get(), false) ); + ASSERT_EQ(cursor->seek(BSONObj(), false), boost::none); } } TEST( SortedDataInterface, Locate3Descending ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + + auto buildEntry = [](int i) { return IndexKeyEntry(BSON("" << i), RecordId(1, i*2)); }; { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); for ( int i = 0; i < 10; i++ ) { if ( i == 6 ) continue; WriteUnitOfWork uow( opCtx.get() ); - ASSERT_OK( sorted->insert( opCtx.get(), BSON( "" << i ), RecordId(1,i*2), true ) ); + auto entry = buildEntry(i); + ASSERT_OK( sorted->insert( opCtx.get(), entry.key, entry.loc, true ) ); uow.commit(); } } - scoped_ptr<OperationContext> opCtx(harnessHelper->newOperationContext()); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), 1 ) ); - ASSERT( !cursor->locate( BSON( "" << 5 ), RecordId::min() ) ); - ASSERT( !cursor->isEOF() ); - ASSERT_EQUALS( BSON( "" << 5 ), cursor->getKey() ); - cursor->advance(); - ASSERT_EQUALS( BSON( "" << 7 ), cursor->getKey() ); - - cursor.reset( sorted->newCursor( opCtx.get(), -1 ) ); - ASSERT( !cursor->locate( BSON( "" << 5 ), RecordId::min() ) ); - ASSERT( !cursor->isEOF() ); - ASSERT_EQUALS( BSON( "" << 4 ), cursor->getKey() ); - - cursor.reset( sorted->newCursor( opCtx.get(), -1 ) ); - ASSERT( !cursor->locate( BSON( "" << 5 ), RecordId::max() ) ); - ASSERT( !cursor->isEOF() ); - ASSERT_EQUALS( BSON( "" << 5 ), cursor->getKey() ); - cursor->advance(); - ASSERT_EQUALS( BSON( "" << 4 ), cursor->getKey() ); - - cursor.reset( sorted->newCursor( opCtx.get(), -1 ) ); - ASSERT( !cursor->locate( BSON( "" << 5 ), RecordId::min() ) ); - ASSERT( !cursor->isEOF() ); - ASSERT_EQUALS( BSON( "" << 4 ), cursor->getKey() ); - cursor->advance(); - ASSERT_EQUALS( BSON( "" << 3 ), cursor->getKey() ); - - cursor.reset( sorted->newCursor( opCtx.get(), -1 ) ); - cursor->locate( BSON( "" << 6 ), RecordId::max() ); - ASSERT( !cursor->isEOF() ); - ASSERT_EQUALS( BSON( "" << 5 ), cursor->getKey() ); - cursor->advance(); - ASSERT_EQUALS( BSON( "" << 4 ), cursor->getKey() ); - - cursor.reset( sorted->newCursor( opCtx.get(), -1 ) ); - cursor->locate( BSON( "" << 500 ), RecordId::max() ); - ASSERT( !cursor->isEOF() ); - ASSERT_EQUALS( BSON( "" << 9 ), cursor->getKey() ); - cursor->advance(); - ASSERT_EQUALS( BSON( "" << 8 ), cursor->getKey() ); + const std::unique_ptr<OperationContext> opCtx(harnessHelper->newOperationContext()); + std::unique_ptr<SortedDataInterface::Cursor> cursor(sorted->newCursor(opCtx.get(), true)); + ASSERT_EQ(cursor->seek(BSON("" << 5), true), buildEntry(5)); + ASSERT_EQ(cursor->next(), buildEntry(7)); + + cursor = sorted->newCursor(opCtx.get(), /*forward*/false); + ASSERT_EQ(cursor->seek(BSON("" << 5), /*inclusive*/false), buildEntry(4)); + + cursor = sorted->newCursor(opCtx.get(), /*forward*/false); + ASSERT_EQ(cursor->seek(BSON("" << 5), /*inclusive*/true), buildEntry(5)); + ASSERT_EQ(cursor->next(), buildEntry(4)); + + cursor = sorted->newCursor(opCtx.get(), /*forward*/false); + ASSERT_EQ(cursor->seek(BSON("" << 5), /*inclusive*/false), buildEntry(4)); + ASSERT_EQ(cursor->next(), buildEntry(3)); + cursor = sorted->newCursor(opCtx.get(), /*forward*/false); + ASSERT_EQ(cursor->seek(BSON("" << 6), /*inclusive*/true), buildEntry(5)); + ASSERT_EQ(cursor->next(), buildEntry(4)); + + cursor = sorted->newCursor(opCtx.get(), /*forward*/false); + ASSERT_EQ(cursor->seek(BSON("" << 500), /*inclusive*/true), buildEntry(9)); + ASSERT_EQ(cursor->next(), buildEntry(8)); } TEST( SortedDataInterface, Locate4 ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); @@ -562,41 +522,26 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), 1 ) ); - ASSERT( !cursor->locate( BSON( "a" << 1 ), RecordId::min() ) ); - ASSERT( !cursor->isEOF() ); - ASSERT_EQUALS( RecordId(1,2), cursor->getRecordId() ); - - cursor->advance(); - ASSERT_EQUALS( RecordId(1,4), cursor->getRecordId() ); - - cursor->advance(); - ASSERT_EQUALS( RecordId(1,6), cursor->getRecordId() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get()) ); + ASSERT_EQ(cursor->seek(BSON("a" << 1), true), + IndexKeyEntry(BSON("" << 1), RecordId(1, 2))); - cursor->advance(); - ASSERT_EQUALS( RecordId(1,8), cursor->getRecordId() ); - - cursor->advance(); - ASSERT( cursor->isEOF() ); + ASSERT_EQ(cursor->next(), IndexKeyEntry(BSON("" << 1), RecordId(1, 4))); + ASSERT_EQ(cursor->next(), IndexKeyEntry(BSON("" << 1), RecordId(1, 6))); + ASSERT_EQ(cursor->next(), IndexKeyEntry(BSON("" << 2), RecordId(1, 8))); + ASSERT_EQ(cursor->next(), boost::none); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); - scoped_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor( opCtx.get(), -1 ) ); - ASSERT( !cursor->locate( BSON( "a" << 1 ), RecordId::max() ) ); - ASSERT( !cursor->isEOF() ); - ASSERT( cursor->getDirection() == -1 ); - ASSERT_EQUALS( RecordId(1,6), cursor->getRecordId() ); - - cursor->advance(); - ASSERT_EQUALS( RecordId(1,4), cursor->getRecordId() ); - - cursor->advance(); - ASSERT_EQUALS( RecordId(1,2), cursor->getRecordId() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<SortedDataInterface::Cursor> cursor( sorted->newCursor(opCtx.get(), false) ); + ASSERT_EQ(cursor->seek(BSON("a" << 1), true), + IndexKeyEntry(BSON("" << 1), RecordId(1, 6))); - cursor->advance(); - ASSERT( cursor->isEOF() ); + ASSERT_EQ(cursor->next(), IndexKeyEntry(BSON("" << 1), RecordId(1, 4))); + ASSERT_EQ(cursor->next(), IndexKeyEntry(BSON("" << 1), RecordId(1, 2))); + ASSERT_EQ(cursor->next(), boost::none); } } diff --git a/src/mongo/db/storage/sorted_data_interface_test_insert.cpp b/src/mongo/db/storage/sorted_data_interface_test_insert.cpp index 6158aa0ed60..2aa254f2f3f 100644 --- a/src/mongo/db/storage/sorted_data_interface_test_insert.cpp +++ b/src/mongo/db/storage/sorted_data_interface_test_insert.cpp @@ -30,27 +30,25 @@ #include "mongo/db/storage/sorted_data_interface_test_harness.h" -#include <boost/scoped_ptr.hpp> +#include <memory> #include "mongo/db/storage/sorted_data_interface.h" #include "mongo/unittest/unittest.h" namespace mongo { - using boost::scoped_ptr; - // Insert a key and verify that the number of entries in the index equals 1. TEST( SortedDataInterface, Insert ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT( sorted->isEmpty( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), key1, loc1, true ) ); @@ -59,23 +57,23 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( 1, sorted->numEntries( opCtx.get() ) ); } } // Insert a compound key and verify that the number of entries in the index equals 1. TEST( SortedDataInterface, InsertCompoundKey ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT( sorted->isEmpty( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), compoundKey1a, loc1, true ) ); @@ -84,7 +82,7 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( 1, sorted->numEntries( opCtx.get() ) ); } } @@ -93,16 +91,16 @@ namespace mongo { // number of entries in the index equals the number that were inserted, even // when duplicates are not allowed. TEST( SortedDataInterface, InsertSameDiskLoc ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( false ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT( sorted->isEmpty( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), key1, loc1, true ) ); @@ -112,12 +110,12 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( 2, sorted->numEntries( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), key3, loc1, true ) ); @@ -126,7 +124,7 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( 3, sorted->numEntries( opCtx.get() ) ); } } @@ -135,16 +133,16 @@ namespace mongo { // number of entries in the index equals the number that were inserted, even // when duplicates are allowed. TEST( SortedDataInterface, InsertSameDiskLocWithDupsAllowed ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( true ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( true ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT( sorted->isEmpty( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), key1, loc1, false ) ); @@ -154,12 +152,12 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( 2, sorted->numEntries( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), key3, loc1, true /* allow duplicates */ ) ); @@ -168,7 +166,7 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( 3, sorted->numEntries( opCtx.get() ) ); } } @@ -176,16 +174,16 @@ namespace mongo { // Insert the same key multiple times and verify that only 1 entry exists // in the index when duplicates are not allowed. TEST( SortedDataInterface, InsertSameKey ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( true ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( true ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT( sorted->isEmpty( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), key1, loc1, false ) ); @@ -195,12 +193,12 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( 1, sorted->numEntries( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_NOT_OK( sorted->insert( opCtx.get(), key1, loc2, false ) ); @@ -209,7 +207,7 @@ namespace mongo { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( 1, sorted->numEntries( opCtx.get() ) ); } } @@ -222,16 +220,16 @@ namespace { // removing all but one loc each time and verifying the correct loc remains. void _testInsertSameKeyWithDupsAllowed(const RecordId locs[3]) { for (int keeper = 0; keeper < 3; keeper++) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( true ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( true ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT( sorted->isEmpty( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK(sorted->insert(opCtx.get(), key1, locs[0], false)); @@ -242,7 +240,7 @@ namespace { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); for (int i = 0; i < 3; i++) { @@ -255,13 +253,11 @@ namespace { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( 1, sorted->numEntries( opCtx.get() ) ); - scoped_ptr<SortedDataInterface::Cursor> cursor(sorted->newCursor(opCtx.get(), 1)); - cursor->locate(key1, RecordId::min()); - ASSERT_EQUALS(key1, cursor->getKey()); - ASSERT_EQUALS(locs[keeper], cursor->getRecordId()); + const std::unique_ptr<SortedDataInterface::Cursor> cursor(sorted->newCursor(opCtx.get())); + ASSERT_EQ(cursor->seek(key1, true), IndexKeyEntry(key1, locs[keeper])); } } } @@ -281,16 +277,16 @@ namespace { // Insert multiple keys and verify that the number of entries // in the index equals the number that were inserted. TEST( SortedDataInterface, InsertMultiple ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( true ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( true ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT( sorted->isEmpty( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), key1, loc1, false ) ); @@ -300,12 +296,12 @@ namespace { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( 2, sorted->numEntries( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), key3, loc3, false ) ); @@ -314,7 +310,7 @@ namespace { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( 3, sorted->numEntries( opCtx.get() ) ); } } @@ -322,16 +318,16 @@ namespace { // Insert multiple compound keys and verify that the number of entries // in the index equals the number that were inserted. TEST( SortedDataInterface, InsertMultipleCompoundKeys ) { - scoped_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); - scoped_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( true ) ); + const std::unique_ptr<HarnessHelper> harnessHelper( newHarnessHelper() ); + const std::unique_ptr<SortedDataInterface> sorted( harnessHelper->newSortedDataInterface( true ) ); { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT( sorted->isEmpty( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), compoundKey1a, loc1, false ) ); @@ -342,12 +338,12 @@ namespace { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( 3, sorted->numEntries( opCtx.get() ) ); } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); { WriteUnitOfWork uow( opCtx.get() ); ASSERT_OK( sorted->insert( opCtx.get(), compoundKey1c, loc4, false ) ); @@ -357,7 +353,7 @@ namespace { } { - scoped_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); + const std::unique_ptr<OperationContext> opCtx( harnessHelper->newOperationContext() ); ASSERT_EQUALS( 5, sorted->numEntries( opCtx.get() ) ); } } diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_index.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_index.cpp index 70d9f4cd768..ce1f10cde29 100644 --- a/src/mongo/db/storage/wiredtiger/wiredtiger_index.cpp +++ b/src/mongo/db/storage/wiredtiger/wiredtiger_index.cpp @@ -47,12 +47,15 @@ #include "mongo/db/storage/wiredtiger/wiredtiger_session_cache.h" #include "mongo/db/storage/wiredtiger/wiredtiger_util.h" #include "mongo/db/storage_options.h" +#include "mongo/stdx/memory.h" #include "mongo/util/assert_util.h" #include "mongo/util/fail_point.h" #include "mongo/util/log.h" #include "mongo/util/mongoutils/str.h" -#if 0 +#define TRACING_ENABLED 0 + +#if TRACING_ENABLED #define TRACE_CURSOR log() << "WT index (" << (const void*)&_idx << ") " #define TRACE_INDEX log() << "WT index (" << (const void*)this << ") " #else @@ -281,15 +284,16 @@ namespace { if (output) *output << "valid" << true; - boost::scoped_ptr<SortedDataInterface::Cursor> cursor(newCursor(txn, 1)); - cursor->locate( minKey, RecordId::min() ); + auto cursor = newCursor(txn); long long count = 0; TRACE_INDEX << " fullValidate"; - while ( !cursor->isEOF() ) { - TRACE_INDEX << "\t" << cursor->getKey(); - cursor->advance(); + + const auto requestedInfo = TRACING_ENABLED ? Cursor::kKeyAndLoc : Cursor::kJustExistance; + for (auto kv = cursor->seek(minKey, true, requestedInfo); kv; kv = cursor->next()) { + TRACE_INDEX << "\t" << kv->key << ' ' << kv->loc; count++; } + if ( numKeysOut ) { *numKeysOut = count; } @@ -598,111 +602,86 @@ namespace { : _txn(txn), _cursor(idx.uri(), idx.instanceId(), false, txn), _idx(idx), - _forward(forward), - _eof(true), - _cursorAtEof(true) { + _forward(forward) { } - virtual int getDirection() const { return _forward ? 1 : -1; } - virtual bool isEOF() const { return _eof; } - - virtual bool pointsToSamePlaceAs(const SortedDataInterface::Cursor& genOther) const { - const WiredTigerIndexCursorBase& other = - checked_cast<const WiredTigerIndexCursorBase&>(genOther); - - if ( _eof && other._eof ) - return true; - else if ( _eof || other._eof ) - return false; + boost::optional<IndexKeyEntry> next(RequestedInfo parts) override { + // Advance on a cursor at the end is a no-op + if (_eof) return {}; - // First try WT_CURSOR equals(), as this should be cheap. - int equal; - invariantWTOK(_cursor.get()->equals(_cursor.get(), other._cursor.get(), &equal)); - if (!equal) - return false; + if (_lastMoveWasRestore) { + // Return current position rather than advancing. + updatePosition(); + } + else { + advanceWTCursor(); + updatePosition(/*checkEndPosition*/false); + if (!_eof && atEndPoint()) _eof = true; + } - // WT says cursors are equal, but need to double-check that the RecordIds match. - return getRecordId() == other.getRecordId(); + return curr(parts); } - virtual void advance() { - // Advance on a cursor at the end is a no-op - if (_eof) + void setEndPosition(const BSONObj& key, bool inclusive) override { + TRACE_CURSOR << "setEndPosition inclusive: " << inclusive << ' ' << key; + if (key.isEmpty()) { + // This means scan to end of index. + _endState.reset(); return; - advanceWTCursor(); - updatePosition(); + } + + // NOTE: this uses the opposite rules as a normal seek because a forward scan should + // end after the key if inclusive and before if exclusive. + const auto discriminator = _forward == inclusive ? KeyString::kExclusiveAfter + : KeyString::kExclusiveBefore; + _endState = stdx::make_unique<EndState>(); + _endState->query.resetToKey(stripFieldNames(key), _idx.ordering(), discriminator); + seekEndCursor(); } - bool locate(const BSONObj &key, const RecordId& loc) { + boost::optional<IndexKeyEntry> seek(const BSONObj& key, bool inclusive, + RequestedInfo parts) override { const BSONObj finalKey = stripFieldNames(key); - fillQuery(finalKey, loc, &_query); - bool result = _locate(_query, loc); + const auto discriminator = _forward == inclusive ? KeyString::kExclusiveBefore + : KeyString::kExclusiveAfter; + // By using a discriminator other than kInclusive, there is no need to distinguish + // unique vs non-unique key formats since both start with the key. + _query.resetToKey(finalKey, _idx.ordering(), discriminator); + seekWTCursor(_query); updatePosition(); + return curr(parts); + } - // An explicit search at the start of the range should always return false - if (loc == RecordId::min() || loc == RecordId::max() ) - return false; - return result; - } - - void advanceTo(const BSONObj &keyBegin, - int keyBeginLen, - bool afterKey, - const vector<const BSONElement*>& keyEnd, - const vector<bool>& keyEndInclusive) { + boost::optional<IndexKeyEntry> seek(const IndexSeekPoint& seekPoint, + RequestedInfo parts) override { // TODO: don't go to a bson obj then to a KeyString, go straight - BSONObj key = IndexEntryComparison::makeQueryObject( - keyBegin, keyBeginLen, - afterKey, keyEnd, keyEndInclusive, getDirection() ); + BSONObj key = IndexEntryComparison::makeQueryObject(seekPoint, _forward); - fillQuery(key, RecordId(), &_query); - _locate(_query, RecordId()); + // makeQueryObject handles the discriminator in the real exclusive cases. + const auto discriminator = _forward ? KeyString::kExclusiveBefore + : KeyString::kExclusiveAfter; + _query.resetToKey(key, _idx.ordering(), discriminator); + seekWTCursor(_query); updatePosition(); + return curr(parts); } - void customLocate(const BSONObj& keyBegin, - int keyBeginLen, - bool afterKey, - const vector<const BSONElement*>& keyEnd, - const vector<bool>& keyEndInclusive) { - advanceTo(keyBegin, keyBeginLen, afterKey, keyEnd, keyEndInclusive); - } - - - BSONObj getKey() const { - if (_eof) - return BSONObj(); - - if (!_keyBsonCache.isEmpty()) - return _keyBsonCache; - - _keyBsonCache = KeyString::toBson(_key.getBuffer(), _key.getSize(), _idx.ordering(), - _typeBits); - - TRACE_INDEX << " returning key: " << _keyBsonCache; - return _keyBsonCache; - } - - RecordId getRecordId() const { return _loc; } - - void savePosition() { - if (!_txn) - return; // still saved + void savePositioned() override { + if (!_txn) return; // still saved _savedForCheck = _txn->recoveryUnit(); - if ( !wt_keeptxnopen() && !_eof ) { + if (!wt_keeptxnopen()) { try { _cursor.reset(); + if (_endState && _endState->cursor) _endState->cursor->reset(); } catch (const WriteConflictException& wce) { // Ignore since this is only called when we are about to kill our transaction // anyway. } - _cursorAtEof = true; - // Our saved position is wherever we were when we last called updatePosition(). // Any partially completed repositions should not effect our saved position. } @@ -710,28 +689,120 @@ namespace { _txn = NULL; } - void restorePosition( OperationContext *txn ) { + void saveUnpositioned() override { + savePositioned(); + _eof = true; + } + + void restore(OperationContext *txn) override { // Update the session handle with our new operation context. invariant( _savedForCheck == txn->recoveryUnit() ); + _txn = txn; - if ( !wt_keeptxnopen() && !_eof ) { - // Ensure an active session exists, so any restored cursors will bind to it - WiredTigerRecoveryUnit::get(txn)->getSession(txn); - - _locate(_key, _loc); - updatePosition(); + if (!wt_keeptxnopen()) { + seekEndCursor(); + if (!_eof) { + // Ensure an active session exists, so any restored cursors will bind to it + WiredTigerRecoveryUnit::get(txn)->getSession(txn); + _lastMoveWasRestore = !seekWTCursor(_key); + TRACE_CURSOR << "restore _lastMoveWasRestore:" << _lastMoveWasRestore; + } } - _txn = txn; } protected: - virtual bool _locate(const KeyString& query, RecordId loc) = 0; - - virtual void fillQuery(const BSONObj& key, RecordId loc, KeyString* query) const = 0; - // Called after _key has been filled in. Must not throw WriteConflictException. virtual void updateLocAndTypeBits() = 0; + boost::optional<IndexKeyEntry> curr(RequestedInfo parts) const { + if (_eof) return {}; + + dassert(!atOrPastEndPointAfterSeeking()); + dassert(!_loc.isNull()); + + BSONObj bson; + if (TRACING_ENABLED || (parts & kWantKey)) { + bson = KeyString::toBson(_key.getBuffer(), _key.getSize(), _idx.ordering(), + _typeBits); + + TRACE_CURSOR << " returning " << bson << ' ' << _loc; + } + + return {{std::move(bson), _loc}}; + } + + bool atEndPoint() const { + if (_cursorAtEof || !_endState || !_endState->cursor) return false; + + // TODO verify that _cursor->equals is actually faster now that we are using KeyString. + // In particular is it fast enough to make up for overhead of maintaining an extra + // cursor. + int equal; + invariantWTOK(_cursor->equals(_cursor.get(), _endState->cursor->get(), &equal)); + dassert(bool(equal) == atOrPastEndPointAfterSeeking()); + return equal; + } + + bool atOrPastEndPointAfterSeeking() const { + if (_eof) return true; + if (!_endState) return false; + + const int cmp = _key.compare(_endState->query); + + // We set up _endState->query to be in between the last in-range value and the first + // out-of-range value. In particular, it is constructed to never equal any legal index + // key. + dassert(cmp != 0); + + if (_forward) { + // We may have landed after the end point. + return cmp > 0; + } + else { + // We may have landed before the end point. + return cmp < 0; + } + } + + void seekEndCursor() { + if (!_endState) return; + + if (!_endState->cursor) { + _endState->cursor.reset(new WiredTigerCursor(_idx.uri(), + _idx.instanceId(), + false, + _txn)); + } + + WT_CURSOR* c = _endState->cursor->get(); + WiredTigerItem keyItem(_endState->query.getBuffer(), _endState->query.getSize()); + c->set_key(c, keyItem.Get()); + + int cmp = 0; + int ret = WT_OP_CHECK(c->search_near(c, &cmp)); + TRACE_CURSOR << "seekEndCursor() search_near" + << " fwd: " << _forward + << " ret:" << ret + << " cmp:" << cmp; + + if (ret != WT_NOTFOUND) { + invariantWTOK(ret); + + // Need to land after/before query for forward/reverse cursors + if ( _forward && cmp < 0) ret = WT_OP_CHECK(c->next(c)); + if (!_forward && cmp > 0) ret = WT_OP_CHECK(c->prev(c)); + + TRACE_CURSOR << "seekEndCursor() ret:" << ret; + } + + if (ret == WT_NOTFOUND) { + _endState->cursor.reset(); + return; + } + + invariantWTOK(ret); + } + void advanceWTCursor() { WT_CURSOR *c = _cursor.get(); int ret = WT_OP_CHECK(_forward ? c->next(c) : c->prev(c)); @@ -767,35 +838,21 @@ namespace { return true; } - // Make sure we land on a matching key - if (_forward) { - // We need to be >= - if (cmp < 0) { - ret = WT_OP_CHECK(c->next(c)); - } - } - else { - // We need to be <= - if (cmp > 0) { - ret = WT_OP_CHECK(c->prev(c)); - } - } - - if (ret == WT_NOTFOUND) { - _cursorAtEof = true; - TRACE_CURSOR << "\t eof " << ret << " _forward: " << _forward; - } - else { - invariantWTOK(ret); + // Make sure we land on a matching key (after/before for forward/reverse). + if (_forward ? cmp < 0 : cmp > 0) { + advanceWTCursor(); } return false; } - // This must be called after every successful public method that repositions the cursor. If - // a public reposition method partially completes, the key and value should be left at their - // original position. This is why it is not called from the internal reposition methods. - void updatePosition() { + /** + * This must be called after moving the cursor to update our cached position. It should not + * be called after a restore that did not restore to original state since that does not + * logically move the cursor until the following call to next(). + */ + void updatePosition(bool checkEndPosition = true) { + _lastMoveWasRestore = false; if (_cursorAtEof) { _eof = true; _loc = RecordId(); @@ -808,7 +865,11 @@ namespace { WT_ITEM item; invariantWTOK(c->get_key(c, &item)); _key.resetFromBuffer(item.data, item.size); - _keyBsonCache = BSONObj(); // Invalidate cached BSONObj. + + if (checkEndPosition && atOrPastEndPointAfterSeeking()) { + _eof = true; + return; + } updateLocAndTypeBits(); } @@ -818,50 +879,41 @@ namespace { const WiredTigerIndex& _idx; // not owned const bool _forward; - // For save/restorePosition + // Ensures we have the same RU at restore time. RecoveryUnit* _savedForCheck; // These are where this cursor instance is. They are not changed in the face of a failing - // reposition operation. + // next(). KeyString _key; KeyString::TypeBits _typeBits; RecordId _loc; - bool _eof; - mutable BSONObj _keyBsonCache; // if isEmpty, cache invalid and must be loaded from _key. + bool _eof = false; // This differs from _eof in that it always reflects the result of the most recent call to // reposition _cursor. - bool _cursorAtEof; + bool _cursorAtEof = false; + + // Used by next to decide to return current position rather than moving. Should be reset to + // false by any operation that moves the cursor, other than subsequent save/restore pairs. + bool _lastMoveWasRestore = false; KeyString _query; + + struct EndState { + KeyString query; + std::unique_ptr<WiredTigerCursor> cursor; + }; + std::unique_ptr<EndState> _endState; }; - class WiredTigerIndexStandardCursor : public WiredTigerIndexCursorBase { + class WiredTigerIndexStandardCursor final : public WiredTigerIndexCursorBase { public: WiredTigerIndexStandardCursor(const WiredTigerIndex& idx, OperationContext *txn, bool forward) : WiredTigerIndexCursorBase(idx, txn, forward) { } - virtual void fillQuery(const BSONObj& key, RecordId loc, KeyString* query) const { - TRACE_CURSOR << " fillQuery " << key << " " << loc - << (_forward ? " forward" : " backward"); - - // Null cursors should start at the zero key to maintain search ordering in the - // collator. - // Reverse cursors should start on the last matching key. - if (loc.isNull()) - loc = _forward ? RecordId::min() : RecordId::max(); - - query->resetToKey(key, _idx.ordering(), loc); - } - - virtual bool _locate(const KeyString& query, RecordId loc) { - // loc already encoded in query - return seekWTCursor(query); - } - - virtual void updateLocAndTypeBits() { + void updateLocAndTypeBits() override { _loc = KeyString::decodeRecordIdAtEnd(_key.getBuffer(), _key.getSize()); WT_CURSOR *c = _cursor.get(); @@ -872,25 +924,19 @@ namespace { } }; - class WiredTigerIndexUniqueCursor : public WiredTigerIndexCursorBase { + class WiredTigerIndexUniqueCursor final : public WiredTigerIndexCursorBase { public: WiredTigerIndexUniqueCursor(const WiredTigerIndex& idx, OperationContext *txn, bool forward) : WiredTigerIndexCursorBase(idx, txn, forward) { } - virtual void fillQuery(const BSONObj& key, RecordId loc, KeyString* query) const { - TRACE_CURSOR << " fillQuery " << key << " " << loc - << (_forward ? " forward" : " backward"); + void restore(OperationContext *txn) override { + WiredTigerIndexCursorBase::restore(txn); - query->resetToKey(key, _idx.ordering()); // loc doesn't go in _query for unique indexes - } - - virtual bool _locate(const KeyString& query, RecordId loc) { - if (!seekWTCursor(query)) { - // If didn't seek to exact key, start at beginning of wherever we ended up. - return false; - } - dassert(!_cursorAtEof); + // In addition to seeking to the correct key, we also need to make sure that the loc is + // on the correct side of _loc. + if (_lastMoveWasRestore) return; // We are on a different key so no need to check loc. + if (_eof) return; // If we get here we need to look at the actual RecordId for this key and make sure we // are supposed to see it. @@ -901,13 +947,18 @@ namespace { BufReader br(item.data, item.size); RecordId locInIndex = KeyString::decodeRecordId(&br); - if ( _forward && (locInIndex < loc)) advanceWTCursor(); - if (!_forward && (locInIndex > loc)) advanceWTCursor(); + TRACE_CURSOR << "restore" + << " _loc:" << _loc + << " locInIndex:" << locInIndex; - return true; + if (locInIndex == _loc) return; + + _lastMoveWasRestore = true; + if ( _forward && (locInIndex < _loc)) advanceWTCursor(); + if (!_forward && (locInIndex > _loc)) advanceWTCursor(); } - void updateLocAndTypeBits() { + void updateLocAndTypeBits() override { // We assume that cursors can only ever see unique indexes in their "pristine" state, // where no duplicates are possible. The cases where dups are allowed should hold // sufficient locks to ensure that no cursor ever sees them. @@ -920,10 +971,27 @@ namespace { _typeBits.resetFromBuffer(&br); if (!br.atEof()) { - severe() << "Unique index cursor seeing multiple records for key " << getKey(); + severe() << "Unique index cursor seeing multiple records for key " + << curr(kWantKey)->key; fassertFailed(28608); } } + + boost::optional<IndexKeyEntry> seekExact(const BSONObj& key, RequestedInfo parts) override { + _query.resetToKey(stripFieldNames(key), _idx.ordering()); + const WiredTigerItem keyItem(_query.getBuffer(), _query.getSize()); + + WT_CURSOR* c = _cursor.get(); + c->set_key(c, keyItem.Get()); + + // Using search rather than search_near. + int ret = WT_OP_CHECK(c->search(c)); + if (ret != WT_NOTFOUND) invariantWTOK(ret); + _cursorAtEof = ret == WT_NOTFOUND; + updatePosition(); + dassert(_eof || _key.compare(_query) == 0); + return curr(parts); + } }; } // namespace @@ -934,10 +1002,10 @@ namespace { : WiredTigerIndex( ctx, uri, desc ) { } - SortedDataInterface::Cursor* WiredTigerIndexUnique::newCursor(OperationContext* txn, - int direction) const { - invariant((direction == 1) || (direction == -1)); - return new WiredTigerIndexUniqueCursor(*this, txn, direction == 1); + std::unique_ptr<SortedDataInterface::Cursor> WiredTigerIndexUnique::newCursor( + OperationContext* txn, + bool forward) const { + return stdx::make_unique<WiredTigerIndexUniqueCursor>(*this, txn, forward); } SortedDataBuilderInterface* WiredTigerIndexUnique::getBulkBuilder(OperationContext* txn, @@ -1091,10 +1159,10 @@ namespace { : WiredTigerIndex( ctx, uri, desc ) { } - SortedDataInterface::Cursor* WiredTigerIndexStandard::newCursor(OperationContext* txn, - int direction) const { - invariant((direction == 1) || (direction == -1)); - return new WiredTigerIndexStandardCursor(*this, txn, direction == 1); + std::unique_ptr<SortedDataInterface::Cursor> WiredTigerIndexStandard::newCursor( + OperationContext* txn, + bool forward) const { + return stdx::make_unique<WiredTigerIndexStandardCursor>(*this, txn, forward); } SortedDataBuilderInterface* WiredTigerIndexStandard::getBulkBuilder(OperationContext* txn, diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_index.h b/src/mongo/db/storage/wiredtiger/wiredtiger_index.h index 169c3789471..4d3c50c09f7 100644 --- a/src/mongo/db/storage/wiredtiger/wiredtiger_index.h +++ b/src/mongo/db/storage/wiredtiger/wiredtiger_index.h @@ -145,20 +145,23 @@ namespace mongo { const std::string& uri, const IndexDescriptor* desc ); - virtual SortedDataInterface::Cursor* newCursor(OperationContext* txn, int direction) const; - SortedDataBuilderInterface* getBulkBuilder(OperationContext* txn, bool dupsAllowed); + std::unique_ptr<SortedDataInterface::Cursor> newCursor(OperationContext* txn, + bool forward) const override; - virtual bool unique() const { return true; } + SortedDataBuilderInterface* getBulkBuilder(OperationContext* txn, + bool dupsAllowed) override; - virtual Status _insert( WT_CURSOR* c, - const BSONObj& key, - const RecordId& loc, - bool dupsAllowed ); + bool unique() const override { return true; } - virtual void _unindex( WT_CURSOR* c, - const BSONObj& key, - const RecordId& loc, - bool dupsAllowed ); + Status _insert(WT_CURSOR* c, + const BSONObj& key, + const RecordId& loc, + bool dupsAllowed) override; + + void _unindex(WT_CURSOR* c, + const BSONObj& key, + const RecordId& loc, + bool dupsAllowed) override; }; class WiredTigerIndexStandard : public WiredTigerIndex { @@ -167,20 +170,23 @@ namespace mongo { const std::string& uri, const IndexDescriptor* desc ); - virtual SortedDataInterface::Cursor* newCursor(OperationContext* txn, int direction) const; - SortedDataBuilderInterface* getBulkBuilder(OperationContext* txn, bool dupsAllowed); + std::unique_ptr<SortedDataInterface::Cursor> newCursor(OperationContext* txn, + bool forward) const override; - virtual bool unique() const { return false; } + SortedDataBuilderInterface* getBulkBuilder(OperationContext* txn, + bool dupsAllowed) override; - virtual Status _insert( WT_CURSOR* c, - const BSONObj& key, - const RecordId& loc, - bool dupsAllowed ); + bool unique() const override { return false; } - virtual void _unindex( WT_CURSOR* c, - const BSONObj& key, - const RecordId& loc, - bool dupsAllowed ); + Status _insert(WT_CURSOR* c, + const BSONObj& key, + const RecordId& loc, + bool dupsAllowed) override; + + void _unindex(WT_CURSOR* c, + const BSONObj& key, + const RecordId& loc, + bool dupsAllowed) override; }; diff --git a/src/mongo/dbtests/query_stage_ixscan.cpp b/src/mongo/dbtests/query_stage_ixscan.cpp index b5aadc755d2..e3f5f8877cc 100644 --- a/src/mongo/dbtests/query_stage_ixscan.cpp +++ b/src/mongo/dbtests/query_stage_ixscan.cpp @@ -200,10 +200,13 @@ namespace QueryStageIxscan { insert(fromjson("{_id: 5, x: 11}")); ixscan->restoreState(&_txn); - // Expect EOF: we miss {'': 10} because it is inserted behind the cursor. - ASSERT(ixscan->isEOF()); + member = getNext(ixscan.get()); + ASSERT_EQ(WorkingSetMember::LOC_AND_IDX, member->state); + ASSERT_EQ(member->keyData[0].keyData, BSON("" << 10)); + WorkingSetID id; ASSERT_EQ(PlanStage::IS_EOF, ixscan->work(&id)); + ASSERT(ixscan->isEOF()); } }; @@ -232,11 +235,13 @@ namespace QueryStageIxscan { insert(fromjson("{_id: 4, x: 7}")); ixscan->restoreState(&_txn); - // Expect EOF: we miss {'': 7} because it is inserted behind the cursor, and - // {'': 10} is not in the range (5, 10) - ASSERT(ixscan->isEOF()); + member = getNext(ixscan.get()); + ASSERT_EQ(WorkingSetMember::LOC_AND_IDX, member->state); + ASSERT_EQ(member->keyData[0].keyData, BSON("" << 7)); + WorkingSetID id; ASSERT_EQ(PlanStage::IS_EOF, ixscan->work(&id)); + ASSERT(ixscan->isEOF()); } }; @@ -266,9 +271,9 @@ namespace QueryStageIxscan { ixscan->restoreState(&_txn); // Ensure that we're EOF and we don't erroneously return {'': 12}. - ASSERT(ixscan->isEOF()); WorkingSetID id; ASSERT_EQ(PlanStage::IS_EOF, ixscan->work(&id)); + ASSERT(ixscan->isEOF()); } }; @@ -299,12 +304,17 @@ namespace QueryStageIxscan { // Save state and insert an indexed doc. ixscan->saveState(); insert(fromjson("{_id: 4, x: 6}")); + insert(fromjson("{_id: 5, x: 9}")); ixscan->restoreState(&_txn); - // Ensure that we're EOF and we don't erroneously return {'': 6}. - ASSERT(ixscan->isEOF()); + // Ensure that we don't erroneously return {'': 9} or {'':3}. + member = getNext(ixscan.get()); + ASSERT_EQ(WorkingSetMember::LOC_AND_IDX, member->state); + ASSERT_EQ(member->keyData[0].keyData, BSON("" << 6)); + WorkingSetID id; ASSERT_EQ(PlanStage::IS_EOF, ixscan->work(&id)); + ASSERT(ixscan->isEOF()); } }; diff --git a/src/mongo/dbtests/rollbacktests.cpp b/src/mongo/dbtests/rollbacktests.cpp index 2f3180f749a..8abfe02ff43 100644 --- a/src/mongo/dbtests/rollbacktests.cpp +++ b/src/mongo/dbtests/rollbacktests.cpp @@ -128,22 +128,16 @@ namespace { IndexDescriptor* desc = catalog->findIndexByName( txn, idxName, false ); if ( desc ) { - CursorOptions cursorOptions; - cursorOptions.direction = CursorOptions::INCREASING; + auto cursor = catalog->getIndex(desc)->newCursor(txn); - IndexCursor *cursor; - ASSERT_OK( catalog->getIndex( desc )->newCursor( txn, cursorOptions, &cursor ) ); - ASSERT_OK( cursor->seek( minKey ) ); - - while ( !cursor->isEOF() ) { + for (auto kv = cursor->seek(minKey, true); kv; kv = cursor->next()) { numEntries++; - cursor->next(); } - delete cursor; } return numEntries; } + void dropIndex( OperationContext* txn, const NamespaceString& nss, const string& idxName ) { Collection* coll = dbHolder().get( txn, nss.db() )->getCollection(nss.ns() ); IndexDescriptor* desc = coll->getIndexCatalog()->findIndexByName( txn, idxName ); |