// @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;