// @file queryoptimizercursorimpl.cpp - A cursor interleaving multiple candidate cursors.
/**
* Copyright (C) 2011 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 .
*/
#include "pch.h"
#include "mongo/db/queryoptimizercursorimpl.h"
#include "mongo/db/btreecursor.h"
#include "mongo/db/queryoptimizer.h"
namespace mongo {
extern bool useHints;
QueryPlanSelectionPolicy::Any QueryPlanSelectionPolicy::__any;
const QueryPlanSelectionPolicy &QueryPlanSelectionPolicy::any() { return __any; }
bool QueryPlanSelectionPolicy::IndexOnly::permitPlan( const QueryPlan &plan ) const {
return !plan.willScanTable();
}
QueryPlanSelectionPolicy::IndexOnly QueryPlanSelectionPolicy::__indexOnly;
const QueryPlanSelectionPolicy &QueryPlanSelectionPolicy::indexOnly() { return __indexOnly; }
bool QueryPlanSelectionPolicy::IdElseNatural::permitPlan( const QueryPlan &plan ) const {
return !plan.indexed() || plan.index()->isIdIndex();
}
BSONObj QueryPlanSelectionPolicy::IdElseNatural::planHint( const StringData& ns ) const {
NamespaceDetails *nsd = nsdetails( ns );
if ( !nsd || !nsd->haveIdIndex() ) {
return BSON( "$hint" << BSON( "$natural" << 1 ) );
}
return BSON( "$hint" << nsd->idx( nsd->findIdIndex() ).indexName() );
}
QueryPlanSelectionPolicy::IdElseNatural QueryPlanSelectionPolicy::__idElseNatural;
const QueryPlanSelectionPolicy &QueryPlanSelectionPolicy::idElseNatural() {
return __idElseNatural;
}
QueryOptimizerCursorImpl* QueryOptimizerCursorImpl::make
( auto_ptr& mps,
const QueryPlanSelectionPolicy& planPolicy,
bool requireOrder,
bool explain ) {
auto_ptr ret( new QueryOptimizerCursorImpl( mps, planPolicy,
requireOrder ) );
ret->init( explain );
return ret.release();
}
bool QueryOptimizerCursorImpl::ok() {
return _takeover ? _takeover->ok() : !currLoc().isNull();
}
Record* QueryOptimizerCursorImpl::_current() {
if ( _takeover ) {
return _takeover->_current();
}
assertOk();
return currLoc().rec();
}
BSONObj QueryOptimizerCursorImpl::current() {
if ( _takeover ) {
return _takeover->current();
}
assertOk();
return currLoc().obj();
}
DiskLoc QueryOptimizerCursorImpl::currLoc() {
return _takeover ? _takeover->currLoc() : _currLoc();
}
DiskLoc QueryOptimizerCursorImpl::_currLoc() const {
dassert( !_takeover );
return _currRunner ? _currRunner->currLoc() : DiskLoc();
}
bool QueryOptimizerCursorImpl::advance() {
return _advance( false );
}
BSONObj QueryOptimizerCursorImpl::currKey() const {
if ( _takeover ) {
return _takeover->currKey();
}
assertOk();
return _currRunner->currKey();
}
DiskLoc QueryOptimizerCursorImpl::refLoc() {
return _takeover ? _takeover->refLoc() : DiskLoc();
}
BSONObj QueryOptimizerCursorImpl::indexKeyPattern() {
if ( _takeover ) {
return _takeover->indexKeyPattern();
}
assertOk();
return _currRunner->cursor()->indexKeyPattern();
}
void QueryOptimizerCursorImpl::prepareToTouchEarlierIterate() {
if ( _takeover ) {
_takeover->prepareToTouchEarlierIterate();
}
else if ( _currRunner ) {
if ( _mps->currentNPlans() == 1 ) {
// This single plan version is a bit more performant, so we use it when possible.
_currRunner->prepareToTouchEarlierIterate();
}
else {
// With multiple plans, the 'earlier iterate' could be the current iterate of one of
// the component plans. We do a full yield of all plans, using ClientCursors.
_mps->prepareToYield();
}
}
}
void QueryOptimizerCursorImpl::recoverFromTouchingEarlierIterate() {
if ( _takeover ) {
_takeover->recoverFromTouchingEarlierIterate();
}
else if ( _currRunner ) {
if ( _mps->currentNPlans() == 1 ) {
_currRunner->recoverFromTouchingEarlierIterate();
}
else {
recoverFromYield();
}
}
}
void QueryOptimizerCursorImpl::prepareToYield() {
if ( _takeover ) {
_takeover->prepareToYield();
}
else if ( _currRunner ) {
_mps->prepareToYield();
}
}
void QueryOptimizerCursorImpl::recoverFromYield() {
if ( _takeover ) {
_takeover->recoverFromYield();
return;
}
if ( _currRunner ) {
_mps->recoverFromYield();
if ( _currRunner->error() || !ok() ) {
// Advance to a non error op if one of the ops errored out.
// Advance to a following $or clause if the $or clause returned all results.
verify( !_mps->doneRunners() );
_advance( true );
}
}
}
bool QueryOptimizerCursorImpl::getsetdup(DiskLoc loc) {
if ( _takeover ) {
if ( getdupInternal( loc ) ) {
return true;
}
return _takeover->getsetdup( loc );
}
assertOk();
return getsetdupInternal( loc );
}
bool QueryOptimizerCursorImpl::isMultiKey() const {
if ( _takeover ) {
return _takeover->isMultiKey();
}
assertOk();
return _currRunner->cursor()->isMultiKey();
}
bool QueryOptimizerCursorImpl::capped() const {
// Initial capped wrapping cases (before takeover) are handled internally by a component
// ClientCursor.
return _takeover ? _takeover->capped() : false;
}
long long QueryOptimizerCursorImpl::nscanned() {
return _takeover ? _takeover->nscanned() : _nscanned;
}
CoveredIndexMatcher* QueryOptimizerCursorImpl::matcher() const {
if ( _takeover ) {
return _takeover->matcher();
}
assertOk();
return _currRunner->queryPlan().matcher().get();
}
bool QueryOptimizerCursorImpl::currentMatches( MatchDetails* details ) {
if ( _takeover ) {
return _takeover->currentMatches( details );
}
assertOk();
return _currRunner->currentMatches( details );
}
const FieldRangeSet* QueryOptimizerCursorImpl::initialFieldRangeSet() const {
if ( _takeover ) {
return 0;
}
assertOk();
return &_currRunner->queryPlan().multikeyFrs();
}
bool QueryOptimizerCursorImpl::currentPlanScanAndOrderRequired() const {
if ( _takeover ) {
return _takeover->queryPlan().scanAndOrderRequired();
}
assertOk();
return _currRunner->queryPlan().scanAndOrderRequired();
}
const Projection::KeyOnly* QueryOptimizerCursorImpl::keyFieldsOnly() const {
if ( _takeover ) {
return _takeover->keyFieldsOnly();
}
assertOk();
return _currRunner->keyFieldsOnly();
}
bool QueryOptimizerCursorImpl::runningInitialInOrderPlan() const {
if ( _takeover ) {
return false;
}
assertOk();
return _mps->haveInOrderPlan();
}
bool QueryOptimizerCursorImpl::hasPossiblyExcludedPlans() const {
if ( _takeover ) {
return false;
}
assertOk();
return _mps->hasPossiblyExcludedPlans();
}
void QueryOptimizerCursorImpl::clearIndexesForPatterns() {
if ( !_takeover ) {
_mps->clearIndexesForPatterns();
}
}
void QueryOptimizerCursorImpl::abortOutOfOrderPlans() {
_requireOrder = true;
}
void QueryOptimizerCursorImpl::noteIterate( bool match, bool loadedDocument, bool chunkSkip ) {
if ( _explainQueryInfo ) {
_explainQueryInfo->noteIterate( match, loadedDocument, chunkSkip );
}
if ( _takeover ) {
_takeover->noteIterate( match, loadedDocument );
}
}
void QueryOptimizerCursorImpl::noteYield() {
if ( _explainQueryInfo ) {
_explainQueryInfo->noteYield();
}
}
QueryOptimizerCursorImpl::QueryOptimizerCursorImpl( auto_ptr& mps,
const QueryPlanSelectionPolicy& planPolicy,
bool requireOrder ) :
_requireOrder( requireOrder ),
_mps( mps ),
_initialCandidatePlans( _mps->possibleInOrderPlan(), _mps->possibleOutOfOrderPlan() ),
_originalRunner( new QueryPlanRunner( _nscanned,
planPolicy,
_requireOrder,
!_initialCandidatePlans.hybridPlanSet() ) ),
_currRunner(),
_completePlanOfHybridSetScanAndOrderRequired(),
_nscanned() {
}
void QueryOptimizerCursorImpl::init( bool explain ) {
_mps->initialRunner( _originalRunner );
if ( explain ) {
_explainQueryInfo = _mps->generateExplainInfo();
}
shared_ptr runner = _mps->nextRunner();
rethrowOnError( runner );
if ( !runner->complete() ) {
_currRunner = runner.get();
}
}
bool QueryOptimizerCursorImpl::_advance( bool force ) {
if ( _takeover ) {
return _takeover->advance();
}
if ( !force && !ok() ) {
return false;
}
_currRunner = 0;
shared_ptr runner = _mps->nextRunner();
rethrowOnError( runner );
if ( !runner->complete() ) {
// The 'runner' will be valid until we call _mps->nextOp() again. We return 'current'
// values from this op.
_currRunner = runner.get();
}
else if ( runner->stopRequested() ) {
if ( runner->cursor() ) {
_takeover.reset( new MultiCursor( _mps,
runner->cursor(),
runner->queryPlan().matcher(),
runner->explainInfo(),
*runner,
_nscanned - runner->cursor()->nscanned() ) );
}
}
else {
if ( _initialCandidatePlans.hybridPlanSet() ) {
_completePlanOfHybridSetScanAndOrderRequired =
runner->queryPlan().scanAndOrderRequired();
}
}
return ok();
}
/** Forward an exception when the runner errs out. */
void QueryOptimizerCursorImpl::rethrowOnError( const shared_ptr< QueryPlanRunner > &runner ) {
if ( runner->error() ) {
throw MsgAssertionException( runner->exception() );
}
}
bool QueryOptimizerCursorImpl::getsetdupInternal(const DiskLoc &loc) {
return _dups.getsetdup( loc );
}
bool QueryOptimizerCursorImpl::getdupInternal(const DiskLoc &loc) {
dassert( _takeover );
return _dups.getdup( loc );
}
shared_ptr newQueryOptimizerCursor( auto_ptr mps,
const QueryPlanSelectionPolicy &planPolicy,
bool requireOrder, bool explain ) {
try {
shared_ptr ret
( QueryOptimizerCursorImpl::make( mps, planPolicy, requireOrder, explain ) );
return ret;
} catch( const AssertionException &e ) {
if ( e.getCode() == OutOfOrderDocumentsAssertionCode ) {
// If no indexes follow the requested sort order, return an
// empty pointer. This is legacy behavior based on bestGuessCursor().
return shared_ptr();
}
throw;
}
return shared_ptr();
}
shared_ptr
NamespaceDetailsTransient::getCursor( const StringData &ns,
const BSONObj &query,
const BSONObj &order,
const QueryPlanSelectionPolicy &planPolicy,
const shared_ptr &parsedQuery,
bool requireOrder,
QueryPlanSummary *singlePlanSummary ) {
CursorGenerator generator( ns,
query,
order,
planPolicy,
parsedQuery,
requireOrder,
singlePlanSummary );
return generator.generate();
}
CursorGenerator::CursorGenerator( const StringData &ns,
const BSONObj &query,
const BSONObj &order,
const QueryPlanSelectionPolicy &planPolicy,
const shared_ptr &parsedQuery,
bool requireOrder,
QueryPlanSummary *singlePlanSummary ) :
_ns( ns ),
_query( query ),
_order( order ),
_planPolicy( planPolicy ),
_parsedQuery( parsedQuery ),
_requireOrder( requireOrder ),
_singlePlanSummary( singlePlanSummary ) {
// Initialize optional return variables.
if ( _singlePlanSummary ) {
*_singlePlanSummary = QueryPlanSummary();
}
}
void CursorGenerator::setArgumentsHint() {
if ( useHints && _parsedQuery ) {
_argumentsHint = _parsedQuery->getHint();
}
if ( snapshot() ) {
NamespaceDetails *d = nsdetails( _ns );
if ( d ) {
int i = d->findIdIndex();
if( i < 0 ) {
if ( _ns.find( ".system." ) == string::npos )
log() << "warning: no _id index on $snapshot query, ns:" << _ns << endl;
}
else {
/* [dm] the name of an _id index tends to vary, so we build the hint the hard
way here. probably need a better way to specify "use the _id index" as a hint.
if someone is in the query optimizer please fix this then!
*/
_argumentsHint = BSON( "$hint" << d->idx(i).indexName() );
}
}
}
}
shared_ptr CursorGenerator::shortcutCursor() const {
if ( !mayShortcutQueryOptimizer() ) {
return shared_ptr();
}
if ( _planPolicy.permitOptimalNaturalPlan() && _query.isEmpty() && _order.isEmpty() ) {
return theDataFileMgr.findAll( _ns );
}
if ( _planPolicy.permitOptimalIdPlan() && isSimpleIdQuery( _query ) ) {
Database *database = cc().database();
verify( database );
NamespaceDetails *d = database->namespaceIndex.details( _ns );
if ( d ) {
int idxNo = d->findIdIndex();
if ( idxNo >= 0 ) {
IndexDetails& i = d->idx( idxNo );
BSONObj key = i.getKeyFromQuery( _query );
return shared_ptr( BtreeCursor::make( d, i, key, key, true, 1 ) );
}
}
}
return shared_ptr();
}
void CursorGenerator::setMultiPlanScanner() {
_mps.reset( MultiPlanScanner::make( _ns, _query, _order, _parsedQuery, hint(),
explain() ? QueryPlanGenerator::Ignore :
QueryPlanGenerator::Use,
min(), max() ) );
}
shared_ptr CursorGenerator::singlePlanCursor() {
const QueryPlan *singlePlan = _mps->singlePlan();
if ( !singlePlan || ( isOrderRequired() && singlePlan->scanAndOrderRequired() ) ) {
return shared_ptr();
}
if ( !_planPolicy.permitPlan( *singlePlan ) ) {
return shared_ptr();
}
if ( _singlePlanSummary ) {
*_singlePlanSummary = singlePlan->summary();
}
shared_ptr single = singlePlan->newCursor( DiskLoc(),
_planPolicy.requestIntervalCursor() );
if ( !_query.isEmpty() && !single->matcher() ) {
// The query plan must have a matcher. The matcher's constructor performs some aspects
// of query validation that should occur before a cursor is returned.
fassert( 16449, singlePlan->matcher() );
if ( // If a matcher is requested or ...
_planPolicy.requestMatcher() ||
// ... the index ranges do not exactly match the query or ...
singlePlan->mayBeMatcherNecessary() ||
// ... the matcher must look at the full record ...
singlePlan->matcher()->needRecord() ) {
// ... then set the cursor's matcher to the query plan's matcher.
single->setMatcher( singlePlan->matcher() );
}
}
if ( singlePlan->keyFieldsOnly() ) {
single->setKeyFieldsOnly( singlePlan->keyFieldsOnly() );
}
return single;
}
shared_ptr CursorGenerator::generate() {
setArgumentsHint();
shared_ptr cursor = shortcutCursor();
if ( cursor ) {
return cursor;
}
setMultiPlanScanner();
cursor = singlePlanCursor();
if ( cursor ) {
return cursor;
}
return newQueryOptimizerCursor( _mps, _planPolicy, isOrderRequired(), explain() );
}
/** This interface is just available for testing. */
shared_ptr newQueryOptimizerCursor
( const char *ns, const BSONObj &query, const BSONObj &order,
const QueryPlanSelectionPolicy &planPolicy, bool requireOrder,
const shared_ptr &parsedQuery ) {
auto_ptr mps( MultiPlanScanner::make( ns, query, order, parsedQuery ) );
return newQueryOptimizerCursor( mps, planPolicy, requireOrder, false );
}
} // namespace mongo;