PlanStage::StageState IndexScan::work(WorkingSetID* out) { ++_commonStats.works; if (NULL == _indexCursor.get()) { // First call to work(). Perform possibly heavy init. initIndexScan(); checkEnd(); } else if (_yieldMovedCursor) { _yieldMovedCursor = false; // Note that we're not calling next() here. We got the next thing when we recovered // from yielding. } if (isEOF()) { return PlanStage::IS_EOF; } // Grab the next (key, value) from the index. BSONObj keyObj = _indexCursor->getKey(); DiskLoc loc = _indexCursor->getValue(); // 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. _indexCursor->next(); checkEnd(); if (_shouldDedup) { ++_specificStats.dupsTested; if (_returned.end() != _returned.find(loc)) { ++_specificStats.dupsDropped; ++_commonStats.needTime; return PlanStage::NEED_TIME; } else { _returned.insert(loc); } } if (Filter::passes(keyObj, _keyPattern, _filter)) { if (NULL != _filter) { ++_specificStats.matchTested; } // We must make a copy of the on-disk data since it can mutate during the execution of // this query. BSONObj ownedKeyObj = keyObj.getOwned(); // Fill out the WSM. WorkingSetID id = _workingSet->allocate(); WorkingSetMember* member = _workingSet->get(id); member->loc = loc; member->keyData.push_back(IndexKeyDatum(_keyPattern, ownedKeyObj)); member->state = WorkingSetMember::LOC_AND_IDX; if (_params.addKeyMetadata) { BSONObjBuilder bob; bob.appendKeys(_keyPattern, ownedKeyObj); member->addComputed(new IndexKeyComputedData(bob.obj())); } *out = id; ++_commonStats.advanced; return PlanStage::ADVANCED; } ++_commonStats.needTime; return PlanStage::NEED_TIME; }
PlanStage::StageState IndexScan::doWork(WorkingSetID* out) { // 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: ++_specificStats.seeks; 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 (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; } } if (kv && _checker) { switch (_checker->checkKey(kv->key, &_seekPoint)) { case IndexBoundsChecker::VALID: break; case IndexBoundsChecker::DONE: kv = boost::none; break; case IndexBoundsChecker::MUST_ADVANCE: _scanState = NEED_SEEK; return PlanStage::NEED_TIME; } } if (!kv) { _scanState = HIT_END; _commonStats.isEOF = true; _indexCursor.reset(); return PlanStage::IS_EOF; } _scanState = GETTING_NEXT; if (_shouldDedup) { ++_specificStats.dupsTested; if (!_returned.insert(kv->loc).second) { // We've seen this RecordId before. Skip it this time. ++_specificStats.dupsDropped; return PlanStage::NEED_TIME; } } if (_filter) { if (!Filter::passes(kv->key, _keyPattern, _filter)) { return PlanStage::NEED_TIME; } } if (!kv->key.isOwned()) kv->key = kv->key.getOwned(); // We found something to return, so fill out the WSM. WorkingSetID id = _workingSet->allocate(); WorkingSetMember* member = _workingSet->get(id); member->recordId = kv->loc; member->keyData.push_back(IndexKeyDatum(_keyPattern, kv->key, _iam)); _workingSet->transitionToRecordIdAndIdx(id); if (_params.addKeyMetadata) { BSONObjBuilder bob; bob.appendKeys(_keyPattern, kv->key); member->addComputed(new IndexKeyComputedData(bob.obj())); } *out = id; return PlanStage::ADVANCED; }