PlanStage::StageState IDHackStage::advance(WorkingSetID id, WorkingSetMember* member, WorkingSetID* out) { invariant(member->hasObj()); if (_addKeyMetadata) { BSONObjBuilder bob; BSONObj ownedKeyObj = member->obj.value()["_id"].wrap().getOwned(); bob.appendKeys(_key, ownedKeyObj); member->addComputed(new IndexKeyComputedData(bob.obj())); } _done = true; *out = id; return PlanStage::ADVANCED; }
BSONObj ResponseBuildStrategy::current( bool allowCovered ) const { if ( _parsedQuery.returnKey() ) { BSONObjBuilder bob; bob.appendKeys( _cursor->indexKeyPattern(), _cursor->currKey() ); return bob.obj(); } if ( allowCovered ) { const Projection::KeyOnly *fields = keyFieldsOnly(); if ( fields ) { return fields->hydrate( _cursor->currKey() ); } } BSONObj ret = _cursor->current(); verify( ret.isValid() ); return ret; }
BSONObj ResponseBuildStrategy::current( bool allowCovered, ResultDetails* resultDetails ) const { if ( _parsedQuery.returnKey() ) { BSONObjBuilder bob; bob.appendKeys( _cursor->indexKeyPattern(), _cursor->currKey() ); return bob.obj(); } if ( allowCovered ) { const Projection::KeyOnly *keyFieldsOnly = _cursor->keyFieldsOnly(); if ( keyFieldsOnly ) { return keyFieldsOnly->hydrate( _cursor->currKey() ); } } resultDetails->loadedRecord = true; BSONObj ret = _cursor->current(); verify( ret.isValid() ); return ret; }
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::work(WorkingSetID* out) { ++_commonStats.works; if (NULL == _indexCursor.get()) { // First call to work(). Perform cursor init. CursorOptions cursorOptions; // The limit is *required* for 2d $near, which is the only index that pays attention to // it anyway. cursorOptions.numWanted = _params.limit; if (1 == _params.direction) { cursorOptions.direction = CursorOptions::INCREASING; } else { cursorOptions.direction = CursorOptions::DECREASING; } IndexCursor *cursor; Status s = _iam->newCursor(&cursor); verify(s.isOK()); _indexCursor.reset(cursor); _indexCursor->setOptions(cursorOptions); if (_params.bounds.isSimpleRange) { // Start at one key, end at another. Status status = _indexCursor->seek(_params.bounds.startKey); if (!status.isOK()) { warning() << "Seek failed: " << status.toString(); _hitEnd = true; return PlanStage::FAILURE; } if (!isEOF()) { _specificStats.keysExamined = 1; } } else { // "Fast" Btree-specific navigation. _btreeCursor = static_cast<BtreeIndexCursor*>(_indexCursor.get()); _checker.reset(new IndexBoundsChecker(&_params.bounds, _descriptor->keyPattern(), _params.direction)); int nFields = _descriptor->keyPattern().nFields(); vector<const BSONElement*> key; vector<bool> inc; key.resize(nFields); inc.resize(nFields); if (_checker->getStartKey(&key, &inc)) { _btreeCursor->seek(key, inc); _keyElts.resize(nFields); _keyEltsInc.resize(nFields); } else { _hitEnd = true; } } checkEnd(); } else if (_yieldMovedCursor) { _yieldMovedCursor = false; // Note that we're not calling next() here. } else { // You're allowed to call work() even if the stage is EOF, but we can't call // _indexCursor->next() if we're EOF. if (!isEOF()) { _indexCursor->next(); checkEnd(); } } if (isEOF()) { return PlanStage::IS_EOF; } DiskLoc loc = _indexCursor->getValue(); if (_shouldDedup) { ++_specificStats.dupsTested; if (_returned.end() != _returned.find(loc)) { ++_specificStats.dupsDropped; ++_commonStats.needTime; return PlanStage::NEED_TIME; } else { _returned.insert(loc); } } BSONObj ownedKeyObj = _indexCursor->getKey().getOwned(); WorkingSetID id = _workingSet->allocate(); WorkingSetMember* member = _workingSet->get(id); member->loc = loc; member->keyData.push_back(IndexKeyDatum(_descriptor->keyPattern(), ownedKeyObj)); member->state = WorkingSetMember::LOC_AND_IDX; if (Filter::passes(member, _filter)) { if (NULL != _filter) { ++_specificStats.matchTested; } if (_params.addKeyMetadata) { BSONObjBuilder bob; bob.appendKeys(_descriptor->keyPattern(), ownedKeyObj); member->addComputed(new IndexKeyComputedData(bob.obj())); } *out = id; ++_commonStats.advanced; return PlanStage::ADVANCED; } _workingSet->free(id); ++_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; }
PlanStage::StageState IndexScan::work(WorkingSetID* out) { ++_commonStats.works; // Adds the amount of time taken by work() to executionTimeMillis. ScopedTimer timer(&_commonStats.executionTimeMillis); if (INITIALIZING == _scanState) { invariant(NULL == _indexCursor.get()); initIndexScan(); } if (CHECKING_END == _scanState) { checkEnd(); } if (isEOF()) { _commonStats.isEOF = true; 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(); } // 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(); _scanState = CHECKING_END; if (_shouldDedup) { ++_specificStats.dupsTested; if (_returned.end() != _returned.find(loc)) { ++_specificStats.dupsDropped; ++_commonStats.needTime; return PlanStage::NEED_TIME; } else { _returned.insert(loc); } } 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)); 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; } } ++_commonStats.needTime; return PlanStage::NEED_TIME; }