diff options
Diffstat (limited to 'src/mongo/db/exec/text.cpp')
-rw-r--r-- | src/mongo/db/exec/text.cpp | 689 |
1 files changed, 336 insertions, 353 deletions
diff --git a/src/mongo/db/exec/text.cpp b/src/mongo/db/exec/text.cpp index f9db2e94be3..933e2b6aba2 100644 --- a/src/mongo/db/exec/text.cpp +++ b/src/mongo/db/exec/text.cpp @@ -41,54 +41,55 @@ namespace mongo { - using std::unique_ptr; - using std::string; - using std::vector; - - // static - const char* TextStage::kStageType = "TEXT"; - - TextStage::TextStage(OperationContext* txn, - const TextStageParams& params, - WorkingSet* ws, - const MatchExpression* filter) - : _txn(txn), - _params(params), - _ftsMatcher(params.query, params.spec), - _ws(ws), - _filter(filter), - _commonStats(kStageType), - _internalState(INIT_SCANS), - _currentIndexScanner(0), - _idRetrying(WorkingSet::INVALID_ID) { - _scoreIterator = _scores.end(); - _specificStats.indexPrefix = _params.indexPrefix; - _specificStats.indexName = _params.index->indexName(); +using std::unique_ptr; +using std::string; +using std::vector; + +// static +const char* TextStage::kStageType = "TEXT"; + +TextStage::TextStage(OperationContext* txn, + const TextStageParams& params, + WorkingSet* ws, + const MatchExpression* filter) + : _txn(txn), + _params(params), + _ftsMatcher(params.query, params.spec), + _ws(ws), + _filter(filter), + _commonStats(kStageType), + _internalState(INIT_SCANS), + _currentIndexScanner(0), + _idRetrying(WorkingSet::INVALID_ID) { + _scoreIterator = _scores.end(); + _specificStats.indexPrefix = _params.indexPrefix; + _specificStats.indexName = _params.index->indexName(); +} + +TextStage::~TextStage() {} + +bool TextStage::isEOF() { + return _internalState == DONE; +} + +PlanStage::StageState TextStage::work(WorkingSetID* out) { + ++_commonStats.works; + + // Adds the amount of time taken by work() to executionTimeMillis. + ScopedTimer timer(&_commonStats.executionTimeMillis); + + if (isEOF()) { + return PlanStage::IS_EOF; } + invariant(_internalState != DONE); - TextStage::~TextStage() { } + PlanStage::StageState stageState = PlanStage::IS_EOF; - bool TextStage::isEOF() { - return _internalState == DONE; - } - - PlanStage::StageState TextStage::work(WorkingSetID* out) { - ++_commonStats.works; - - // Adds the amount of time taken by work() to executionTimeMillis. - ScopedTimer timer(&_commonStats.executionTimeMillis); - - if (isEOF()) { return PlanStage::IS_EOF; } - invariant(_internalState != DONE); - - PlanStage::StageState stageState = PlanStage::IS_EOF; - - switch (_internalState) { + switch (_internalState) { case INIT_SCANS: try { stageState = initScans(out); - } - catch (const WriteConflictException& wce) { + } catch (const WriteConflictException& wce) { // Reset and try again next time. _internalState = INIT_SCANS; _scanners.clear(); @@ -106,10 +107,10 @@ namespace mongo { case DONE: // Handled above. break; - } + } - // Increment common stats counters that are specific to the return value of work(). - switch (stageState) { + // Increment common stats counters that are specific to the return value of work(). + switch (stageState) { case PlanStage::ADVANCED: ++_commonStats.advanced; break; @@ -121,376 +122,358 @@ namespace mongo { break; default: break; - } - - return stageState; } - void TextStage::saveState() { - _txn = NULL; - ++_commonStats.yields; + return stageState; +} - for (size_t i = 0; i < _scanners.size(); ++i) { - _scanners.mutableVector()[i]->saveState(); - } +void TextStage::saveState() { + _txn = NULL; + ++_commonStats.yields; - if (_recordCursor) _recordCursor->saveUnpositioned(); + for (size_t i = 0; i < _scanners.size(); ++i) { + _scanners.mutableVector()[i]->saveState(); } - void TextStage::restoreState(OperationContext* opCtx) { - invariant(_txn == NULL); - _txn = opCtx; - ++_commonStats.unyields; + if (_recordCursor) + _recordCursor->saveUnpositioned(); +} - for (size_t i = 0; i < _scanners.size(); ++i) { - _scanners.mutableVector()[i]->restoreState(opCtx); - } +void TextStage::restoreState(OperationContext* opCtx) { + invariant(_txn == NULL); + _txn = opCtx; + ++_commonStats.unyields; - if (_recordCursor) invariant(_recordCursor->restore(opCtx)); + for (size_t i = 0; i < _scanners.size(); ++i) { + _scanners.mutableVector()[i]->restoreState(opCtx); } - void TextStage::invalidate(OperationContext* txn, const RecordId& dl, InvalidationType type) { - ++_commonStats.invalidates; + if (_recordCursor) + invariant(_recordCursor->restore(opCtx)); +} - // Propagate invalidate to children. - for (size_t i = 0; i < _scanners.size(); ++i) { - _scanners.mutableVector()[i]->invalidate(txn, dl, type); - } +void TextStage::invalidate(OperationContext* txn, const RecordId& dl, InvalidationType type) { + ++_commonStats.invalidates; - // We store the score keyed by RecordId. We have to toss out our state when the RecordId - // changes. - // TODO: If we're RETURNING_RESULTS we could somehow buffer the object. - ScoreMap::iterator scoreIt = _scores.find(dl); - if (scoreIt != _scores.end()) { - if (scoreIt == _scoreIterator) { - _scoreIterator++; - } - _scores.erase(scoreIt); - } + // Propagate invalidate to children. + for (size_t i = 0; i < _scanners.size(); ++i) { + _scanners.mutableVector()[i]->invalidate(txn, dl, type); } - vector<PlanStage*> TextStage::getChildren() const { - vector<PlanStage*> empty; - return empty; + // We store the score keyed by RecordId. We have to toss out our state when the RecordId + // changes. + // TODO: If we're RETURNING_RESULTS we could somehow buffer the object. + ScoreMap::iterator scoreIt = _scores.find(dl); + if (scoreIt != _scores.end()) { + if (scoreIt == _scoreIterator) { + _scoreIterator++; + } + _scores.erase(scoreIt); } +} - PlanStageStats* TextStage::getStats() { - _commonStats.isEOF = isEOF(); +vector<PlanStage*> TextStage::getChildren() const { + vector<PlanStage*> empty; + return empty; +} - // Add a BSON representation of the filter to the stats tree, if there is one. - if (NULL != _filter) { - BSONObjBuilder bob; - _filter->toBSON(&bob); - _commonStats.filter = bob.obj(); - } +PlanStageStats* TextStage::getStats() { + _commonStats.isEOF = isEOF(); - unique_ptr<PlanStageStats> ret(new PlanStageStats(_commonStats, STAGE_TEXT)); - ret->specific.reset(new TextStats(_specificStats)); - return ret.release(); + // Add a BSON representation of the filter to the stats tree, if there is one. + if (NULL != _filter) { + BSONObjBuilder bob; + _filter->toBSON(&bob); + _commonStats.filter = bob.obj(); } - const CommonStats* TextStage::getCommonStats() const { - return &_commonStats; + unique_ptr<PlanStageStats> ret(new PlanStageStats(_commonStats, STAGE_TEXT)); + ret->specific.reset(new TextStats(_specificStats)); + return ret.release(); +} + +const CommonStats* TextStage::getCommonStats() const { + return &_commonStats; +} + +const SpecificStats* TextStage::getSpecificStats() const { + return &_specificStats; +} + +PlanStage::StageState TextStage::initScans(WorkingSetID* out) { + invariant(0 == _scanners.size()); + + _recordCursor = _params.index->getCollection()->getCursor(_txn); + + _specificStats.parsedTextQuery = _params.query.toBSON(); + + // Get all the index scans for each term in our query. + // TODO it would be more efficient to only have one active scan at a time and create the + // next when each finishes. + for (std::set<std::string>::const_iterator it = _params.query.getTermsForBounds().begin(); + it != _params.query.getTermsForBounds().end(); + ++it) { + const string& term = *it; + IndexScanParams params; + params.bounds.startKey = FTSIndexFormat::getIndexKey( + MAX_WEIGHT, term, _params.indexPrefix, _params.spec.getTextIndexVersion()); + params.bounds.endKey = FTSIndexFormat::getIndexKey( + 0, term, _params.indexPrefix, _params.spec.getTextIndexVersion()); + params.bounds.endKeyInclusive = true; + params.bounds.isSimpleRange = true; + params.descriptor = _params.index; + params.direction = -1; + _scanners.mutableVector().push_back(new IndexScan(_txn, params, _ws, NULL)); } - const SpecificStats* TextStage::getSpecificStats() const { - return &_specificStats; + // If we have no terms we go right to EOF. + if (0 == _scanners.size()) { + _internalState = DONE; + return PlanStage::IS_EOF; } - PlanStage::StageState TextStage::initScans(WorkingSetID* out) { - invariant(0 == _scanners.size()); - - _recordCursor = _params.index->getCollection()->getCursor(_txn); - - _specificStats.parsedTextQuery = _params.query.toBSON(); - - // Get all the index scans for each term in our query. - // TODO it would be more efficient to only have one active scan at a time and create the - // next when each finishes. - for (std::set<std::string>::const_iterator it = _params.query.getTermsForBounds().begin(); - it != _params.query.getTermsForBounds().end(); - ++it) { - const string& term = *it; - IndexScanParams params; - params.bounds.startKey = FTSIndexFormat::getIndexKey(MAX_WEIGHT, - term, - _params.indexPrefix, - _params.spec.getTextIndexVersion()); - params.bounds.endKey = FTSIndexFormat::getIndexKey(0, - term, - _params.indexPrefix, - _params.spec.getTextIndexVersion()); - params.bounds.endKeyInclusive = true; - params.bounds.isSimpleRange = true; - params.descriptor = _params.index; - params.direction = -1; - _scanners.mutableVector().push_back(new IndexScan(_txn, params, _ws, NULL)); - } - - // If we have no terms we go right to EOF. - if (0 == _scanners.size()) { - _internalState = DONE; - return PlanStage::IS_EOF; - } - - // Transition to the next state. - _internalState = READING_TERMS; - return PlanStage::NEED_TIME; + // Transition to the next state. + _internalState = READING_TERMS; + return PlanStage::NEED_TIME; +} + +PlanStage::StageState TextStage::readFromSubScanners(WorkingSetID* out) { + // This should be checked before we get here. + invariant(_currentIndexScanner < _scanners.size()); + + // Either retry the last WSM we worked on or get a new one from our current scanner. + WorkingSetID id; + StageState childState; + if (_idRetrying == WorkingSet::INVALID_ID) { + childState = _scanners.vector()[_currentIndexScanner]->work(&id); + } else { + childState = ADVANCED; + id = _idRetrying; + _idRetrying = WorkingSet::INVALID_ID; } - PlanStage::StageState TextStage::readFromSubScanners(WorkingSetID* out) { - // This should be checked before we get here. - invariant(_currentIndexScanner < _scanners.size()); - - // Either retry the last WSM we worked on or get a new one from our current scanner. - WorkingSetID id; - StageState childState; - if (_idRetrying == WorkingSet::INVALID_ID) { - childState = _scanners.vector()[_currentIndexScanner]->work(&id); - } - else { - childState = ADVANCED; - id = _idRetrying; - _idRetrying = WorkingSet::INVALID_ID; - } + if (PlanStage::ADVANCED == childState) { + return addTerm(id, out); + } else if (PlanStage::IS_EOF == childState) { + // Done with this scan. + ++_currentIndexScanner; - if (PlanStage::ADVANCED == childState) { - return addTerm(id, out); + if (_currentIndexScanner < _scanners.size()) { + // We have another scan to read from. + return PlanStage::NEED_TIME; } - else if (PlanStage::IS_EOF == childState) { - // Done with this scan. - ++_currentIndexScanner; - if (_currentIndexScanner < _scanners.size()) { - // We have another scan to read from. - return PlanStage::NEED_TIME; - } + // If we're here we are done reading results. Move to the next state. + _scoreIterator = _scores.begin(); + _internalState = RETURNING_RESULTS; - // If we're here we are done reading results. Move to the next state. - _scoreIterator = _scores.begin(); - _internalState = RETURNING_RESULTS; - - // Don't need to keep these around. - _scanners.clear(); - return PlanStage::NEED_TIME; - } - else { - // Propagate WSID from below. - *out = id; - if (PlanStage::FAILURE == childState) { - // If a stage fails, it may create a status WSM to indicate why it - // failed, in which case 'id' is valid. If ID is invalid, we - // create our own error message. - if (WorkingSet::INVALID_ID == id) { - mongoutils::str::stream ss; - ss << "text stage failed to read in results from child"; - Status status(ErrorCodes::InternalError, ss); - *out = WorkingSetCommon::allocateStatusMember( _ws, status); - } + // Don't need to keep these around. + _scanners.clear(); + return PlanStage::NEED_TIME; + } else { + // Propagate WSID from below. + *out = id; + if (PlanStage::FAILURE == childState) { + // If a stage fails, it may create a status WSM to indicate why it + // failed, in which case 'id' is valid. If ID is invalid, we + // create our own error message. + if (WorkingSet::INVALID_ID == id) { + mongoutils::str::stream ss; + ss << "text stage failed to read in results from child"; + Status status(ErrorCodes::InternalError, ss); + *out = WorkingSetCommon::allocateStatusMember(_ws, status); } - return childState; } + return childState; } +} - PlanStage::StageState TextStage::returnResults(WorkingSetID* out) { - if (_scoreIterator == _scores.end()) { - _internalState = DONE; - return PlanStage::IS_EOF; - } - - // Filter for phrases and negative terms, score and truncate. - TextRecordData textRecordData = _scoreIterator->second; +PlanStage::StageState TextStage::returnResults(WorkingSetID* out) { + if (_scoreIterator == _scores.end()) { + _internalState = DONE; + return PlanStage::IS_EOF; + } - // Ignore non-matched documents. - if (textRecordData.score < 0) { - _scoreIterator++; - invariant(textRecordData.wsid == WorkingSet::INVALID_ID); - return PlanStage::NEED_TIME; - } - - WorkingSetMember* wsm = _ws->get(textRecordData.wsid); - try { - if (!WorkingSetCommon::fetchIfUnfetched(_txn, wsm, _recordCursor)) { - _scoreIterator++; - _ws->free(textRecordData.wsid); - _commonStats.needTime++; - return NEED_TIME; - } - } - catch (const WriteConflictException& wce) { - // Do this record again next time around. - *out = WorkingSet::INVALID_ID; - _commonStats.needYield++; - return NEED_YIELD; - } + // Filter for phrases and negative terms, score and truncate. + TextRecordData textRecordData = _scoreIterator->second; + // Ignore non-matched documents. + if (textRecordData.score < 0) { _scoreIterator++; + invariant(textRecordData.wsid == WorkingSet::INVALID_ID); + return PlanStage::NEED_TIME; + } - // Filter for phrases and negated terms - if (!_ftsMatcher.matches(wsm->obj.value())) { + WorkingSetMember* wsm = _ws->get(textRecordData.wsid); + try { + if (!WorkingSetCommon::fetchIfUnfetched(_txn, wsm, _recordCursor)) { + _scoreIterator++; _ws->free(textRecordData.wsid); - return PlanStage::NEED_TIME; + _commonStats.needTime++; + return NEED_TIME; } + } catch (const WriteConflictException& wce) { + // Do this record again next time around. + *out = WorkingSet::INVALID_ID; + _commonStats.needYield++; + return NEED_YIELD; + } - // Populate the working set member with the text score and return it. - wsm->addComputed(new TextScoreComputedData(textRecordData.score)); - *out = textRecordData.wsid; - return PlanStage::ADVANCED; + _scoreIterator++; + + // Filter for phrases and negated terms + if (!_ftsMatcher.matches(wsm->obj.value())) { + _ws->free(textRecordData.wsid); + return PlanStage::NEED_TIME; } - class TextMatchableDocument : public MatchableDocument { - public: - TextMatchableDocument(OperationContext* txn, - const BSONObj& keyPattern, - const BSONObj& key, - WorkingSetMember* wsm, - unowned_ptr<RecordCursor> recordCursor) - : _txn(txn), - _recordCursor(recordCursor), - _keyPattern(keyPattern), - _key(key), - _wsm(wsm) { } - - BSONObj toBSON() const { - return getObj(); - } + // Populate the working set member with the text score and return it. + wsm->addComputed(new TextScoreComputedData(textRecordData.score)); + *out = textRecordData.wsid; + return PlanStage::ADVANCED; +} + +class TextMatchableDocument : public MatchableDocument { +public: + TextMatchableDocument(OperationContext* txn, + const BSONObj& keyPattern, + const BSONObj& key, + WorkingSetMember* wsm, + unowned_ptr<RecordCursor> recordCursor) + : _txn(txn), _recordCursor(recordCursor), _keyPattern(keyPattern), _key(key), _wsm(wsm) {} + + BSONObj toBSON() const { + return getObj(); + } - virtual ElementIterator* allocateIterator(const ElementPath* path) const { - if (!_wsm->hasObj()) { - // Try to look in the key. - BSONObjIterator keyPatternIt(_keyPattern); - BSONObjIterator keyDataIt(_key); - - while (keyPatternIt.more()) { - BSONElement keyPatternElt = keyPatternIt.next(); - verify(keyDataIt.more()); - BSONElement keyDataElt = keyDataIt.next(); - - if (path->fieldRef().equalsDottedField(keyPatternElt.fieldName())) { - if (Array == keyDataElt.type()) { - return new SimpleArrayElementIterator(keyDataElt, true); - } - else { - return new SingleElementElementIterator(keyDataElt); - } + virtual ElementIterator* allocateIterator(const ElementPath* path) const { + if (!_wsm->hasObj()) { + // Try to look in the key. + BSONObjIterator keyPatternIt(_keyPattern); + BSONObjIterator keyDataIt(_key); + + while (keyPatternIt.more()) { + BSONElement keyPatternElt = keyPatternIt.next(); + verify(keyDataIt.more()); + BSONElement keyDataElt = keyDataIt.next(); + + if (path->fieldRef().equalsDottedField(keyPatternElt.fieldName())) { + if (Array == keyDataElt.type()) { + return new SimpleArrayElementIterator(keyDataElt, true); + } else { + return new SingleElementElementIterator(keyDataElt); } } } - - // Go to the raw document, fetching if needed. - return new BSONElementIterator(path, getObj()); } - virtual void releaseIterator( ElementIterator* iterator ) const { - delete iterator; - } + // Go to the raw document, fetching if needed. + return new BSONElementIterator(path, getObj()); + } - // Thrown if we detect that the document being matched was deleted. - class DocumentDeletedException {}; + virtual void releaseIterator(ElementIterator* iterator) const { + delete iterator; + } - private: - BSONObj getObj() const { - if (!WorkingSetCommon::fetchIfUnfetched(_txn, _wsm, _recordCursor)) - throw DocumentDeletedException(); + // Thrown if we detect that the document being matched was deleted. + class DocumentDeletedException {}; - // Make it owned since we are buffering results. - _wsm->obj.setValue(_wsm->obj.value().getOwned()); - return _wsm->obj.value(); - } +private: + BSONObj getObj() const { + if (!WorkingSetCommon::fetchIfUnfetched(_txn, _wsm, _recordCursor)) + throw DocumentDeletedException(); - OperationContext* _txn; - unowned_ptr<RecordCursor> _recordCursor; - BSONObj _keyPattern; - BSONObj _key; - WorkingSetMember* _wsm; - }; - - PlanStage::StageState TextStage::addTerm(WorkingSetID wsid, WorkingSetID* out) { - WorkingSetMember* wsm = _ws->get(wsid); - invariant(wsm->state == WorkingSetMember::LOC_AND_IDX); - invariant(1 == wsm->keyData.size()); - const IndexKeyDatum newKeyData = wsm->keyData.back(); // copy to keep it around. - - TextRecordData* textRecordData = &_scores[wsm->loc]; - double* documentAggregateScore = &textRecordData->score; - - if (WorkingSet::INVALID_ID == textRecordData->wsid) { - // We haven't seen this RecordId before. Keep the working set member around - // (it may be force-fetched on saveState()). - textRecordData->wsid = wsid; - - if (_filter) { - // We have not seen this document before and need to apply a filter. - bool shouldKeep; - bool wasDeleted = false; - try { - TextMatchableDocument tdoc(_txn, - newKeyData.indexKeyPattern, - newKeyData.keyData, - wsm, - _recordCursor); - shouldKeep = _filter->matches(&tdoc); - } - catch (const WriteConflictException& wce) { - _idRetrying = wsid; - *out = WorkingSet::INVALID_ID; - return NEED_YIELD; - } - catch (const TextMatchableDocument::DocumentDeletedException&) { - // We attempted to fetch the document but decided it should be excluded from the - // result set. - shouldKeep = false; - wasDeleted = true; - } + // Make it owned since we are buffering results. + _wsm->obj.setValue(_wsm->obj.value().getOwned()); + return _wsm->obj.value(); + } - if (!shouldKeep) { - if (wasDeleted || wsm->hasObj()) { - // We had to fetch but we're not going to return it. - ++_specificStats.fetches; - } - _ws->free(textRecordData->wsid); - textRecordData->wsid = WorkingSet::INVALID_ID; - *documentAggregateScore = -1; - return NEED_TIME; - } + OperationContext* _txn; + unowned_ptr<RecordCursor> _recordCursor; + BSONObj _keyPattern; + BSONObj _key; + WorkingSetMember* _wsm; +}; + +PlanStage::StageState TextStage::addTerm(WorkingSetID wsid, WorkingSetID* out) { + WorkingSetMember* wsm = _ws->get(wsid); + invariant(wsm->state == WorkingSetMember::LOC_AND_IDX); + invariant(1 == wsm->keyData.size()); + const IndexKeyDatum newKeyData = wsm->keyData.back(); // copy to keep it around. + + TextRecordData* textRecordData = &_scores[wsm->loc]; + double* documentAggregateScore = &textRecordData->score; + + if (WorkingSet::INVALID_ID == textRecordData->wsid) { + // We haven't seen this RecordId before. Keep the working set member around + // (it may be force-fetched on saveState()). + textRecordData->wsid = wsid; + + if (_filter) { + // We have not seen this document before and need to apply a filter. + bool shouldKeep; + bool wasDeleted = false; + try { + TextMatchableDocument tdoc( + _txn, newKeyData.indexKeyPattern, newKeyData.keyData, wsm, _recordCursor); + shouldKeep = _filter->matches(&tdoc); + } catch (const WriteConflictException& wce) { + _idRetrying = wsid; + *out = WorkingSet::INVALID_ID; + return NEED_YIELD; + } catch (const TextMatchableDocument::DocumentDeletedException&) { + // We attempted to fetch the document but decided it should be excluded from the + // result set. + shouldKeep = false; + wasDeleted = true; } - else { - // If we're here, we're going to return the doc, and we do a fetch later. - ++_specificStats.fetches; + + if (!shouldKeep) { + if (wasDeleted || wsm->hasObj()) { + // We had to fetch but we're not going to return it. + ++_specificStats.fetches; + } + _ws->free(textRecordData->wsid); + textRecordData->wsid = WorkingSet::INVALID_ID; + *documentAggregateScore = -1; + return NEED_TIME; } + } else { + // If we're here, we're going to return the doc, and we do a fetch later. + ++_specificStats.fetches; } - else { - // We already have a working set member for this RecordId. Free the new - // WSM and retrieve the old one. - // Note that since we don't keep all index keys, we could get a score that doesn't match - // the document, but this has always been a problem. - // TODO something to improve the situation. - invariant(wsid != textRecordData->wsid); - _ws->free(wsid); - wsm = _ws->get(textRecordData->wsid); - } + } else { + // We already have a working set member for this RecordId. Free the new + // WSM and retrieve the old one. + // Note that since we don't keep all index keys, we could get a score that doesn't match + // the document, but this has always been a problem. + // TODO something to improve the situation. + invariant(wsid != textRecordData->wsid); + _ws->free(wsid); + wsm = _ws->get(textRecordData->wsid); + } - ++_specificStats.keysExamined; + ++_specificStats.keysExamined; - if (*documentAggregateScore < 0) { - // We have already rejected this document for not matching the filter. - return NEED_TIME; - } + if (*documentAggregateScore < 0) { + // We have already rejected this document for not matching the filter. + return NEED_TIME; + } - // Locate score within possibly compound key: {prefix,term,score,suffix}. - BSONObjIterator keyIt(newKeyData.keyData); - for (unsigned i = 0; i < _params.spec.numExtraBefore(); i++) { - keyIt.next(); - } + // Locate score within possibly compound key: {prefix,term,score,suffix}. + BSONObjIterator keyIt(newKeyData.keyData); + for (unsigned i = 0; i < _params.spec.numExtraBefore(); i++) { + keyIt.next(); + } - keyIt.next(); // Skip past 'term'. + keyIt.next(); // Skip past 'term'. - BSONElement scoreElement = keyIt.next(); - double documentTermScore = scoreElement.number(); + BSONElement scoreElement = keyIt.next(); + double documentTermScore = scoreElement.number(); - // Aggregate relevance score, term keys. - *documentAggregateScore += documentTermScore; - return NEED_TIME; - } + // Aggregate relevance score, term keys. + *documentAggregateScore += documentTermScore; + return NEED_TIME; +} } // namespace mongo |