// @file queryoptimizer.h /** * Copyright (C) 2008 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 . */ #pragma once #include "cursor.h" #include "queryutil.h" #include "matcher.h" #include "explain.h" #include "../util/net/listen.h" #include "mongo/db/querypattern.h" namespace mongo { class IndexDetails; class IndexType; class QueryPlanSummary; /** * A plan for executing a query using the given index spec and FieldRangeSet. An object of this * class may only be used by one thread at a time. */ class QueryPlan : boost::noncopyable { public: /** * @param originalFrsp - original constraints for this query clause. If null, frsp will be used instead. */ static QueryPlan *make( NamespaceDetails *d, int idxNo, // -1 = no index const FieldRangeSetPair &frsp, const FieldRangeSetPair *originalFrsp, const BSONObj &originalQuery, const BSONObj &order, const shared_ptr &parsedQuery = shared_ptr(), const BSONObj &startKey = BSONObj(), const BSONObj &endKey = BSONObj(), const std::string& special="" ); /** Categorical classification of a QueryPlan's utility. */ enum Utility { Impossible, // Cannot produce any matches, so the query must have an empty result set. // No other plans need to be considered. Optimal, // Should run as the only candidate plan in the absence of an Impossible // plan. Helpful, // Should be considered. Unhelpful, // Should not be considered. Disallowed // Must not be considered unless explicitly hinted. May produce a // semantically incorrect result set. }; Utility utility() const { return _utility; } /** @return true if ScanAndOrder processing will be required for result set. */ bool scanAndOrderRequired() const { return _scanAndOrderRequired; } /** * @return true if the index we are using has keys such that it can completely resolve the * query expression to match by itself without ever checking the main object. */ bool exactKeyMatch() const { return _exactKeyMatch; } /** @return true if this QueryPlan would perform an unindexed scan. */ bool willScanTable() const { return _idxNo < 0 && ( _utility != Impossible ); } /** @return 'special' attribute of the plan, which was either set explicitly or generated from the index. */ const string &special() const { return _special; } /** @return a new cursor based on this QueryPlan's index and FieldRangeSet. */ shared_ptr newCursor( const DiskLoc &startLoc = DiskLoc() ) const; /** @return a new reverse cursor if this is an unindexed plan. */ shared_ptr newReverseCursor() const; /** Register this plan as a winner for its QueryPattern, with specified 'nscanned'. */ void registerSelf( long long nScanned, CandidatePlanCharacter candidatePlans ) const; int direction() const { return _direction; } BSONObj indexKey() const; bool indexed() const { return _index != 0; } const IndexDetails *index() const { return _index; } int idxNo() const { return _idxNo; } const char *ns() const { return _frs.ns(); } NamespaceDetails *nsd() const { return _d; } BSONObj originalQuery() const { return _originalQuery; } shared_ptr originalFrv() const { return _originalFrv; } const FieldRangeSet &multikeyFrs() const { return _frsMulti; } shared_ptr keyFieldsOnly() const { return _keyFieldsOnly; } const ParsedQuery* parsedQuery() const { return _parsedQuery.get(); } /** @return a shared, lazily initialized matcher for the query plan. */ shared_ptr matcher() const; QueryPlanSummary summary() const; /** The following member functions are for testing, or public for testing. */ shared_ptr frv() const { return _frv; } bool isMultiKey() const; string toString() const; bool queryBoundsExactOrderSuffix() const; private: QueryPlan(NamespaceDetails *d, int idxNo, const FieldRangeSetPair &frsp, const BSONObj &originalQuery, const BSONObj &order, const shared_ptr &parsedQuery, const std::string& special ); void init( const FieldRangeSetPair *originalFrsp, const BSONObj &startKey, const BSONObj &endKey ); void checkTableScanAllowed() const; int independentRangesSingleIntervalLimit() const; /** @return true when the plan's query may contains an $exists:false predicate. */ bool hasPossibleExistsFalsePredicate() const; NamespaceDetails * _d; int _idxNo; const FieldRangeSet &_frs; const FieldRangeSet &_frsMulti; const BSONObj _originalQuery; const BSONObj _order; shared_ptr _parsedQuery; const IndexDetails * _index; bool _scanAndOrderRequired; bool _exactKeyMatch; int _direction; shared_ptr _frv; shared_ptr _originalFrv; BSONObj _startKey; BSONObj _endKey; bool _endKeyInclusive; Utility _utility; string _special; IndexType * _type; bool _startOrEndSpec; shared_ptr _keyFieldsOnly; mutable shared_ptr _matcher; // Lazy initialization. }; std::ostream &operator<< ( std::ostream &out, const QueryPlan::Utility &utility ); /** * A QueryPlanSummary owns its own attributes and may be shared. Currently a QueryPlan * should only be owned by a QueryPlanSet. */ class QueryPlanSummary { public: QueryPlanSummary() : _scanAndOrderRequired() { } QueryPlanSummary( const QueryPlan &queryPlan ) : _fieldRangeSetMulti( new FieldRangeSet( queryPlan.multikeyFrs() ) ), _keyFieldsOnly( queryPlan.keyFieldsOnly() ), _scanAndOrderRequired( queryPlan.scanAndOrderRequired() ) { } bool valid() const { return _fieldRangeSetMulti; } shared_ptr _fieldRangeSetMulti; shared_ptr _keyFieldsOnly; bool _scanAndOrderRequired; }; /** * NOTE This interface is deprecated and will be replaced by a special purpose delegation class * for the query optimizer cursor (QueryOptimizerCursorOp). * * Inherit from this interface to implement a new query operation. * The query optimizer will clone the QueryOp that is provided, giving * each clone its own query plan. * * Normal sequence of events: * 1) A new QueryOp is generated using createChild(). * 2) A QueryPlan is assigned to this QueryOp with setQueryPlan(). * 3) _init() is called on the QueryPlan. * 4) next() is called repeatedly, with nscanned() checked after each call. * 5) In one of these calls to next(), setComplete() is called. * 6) The QueryPattern for the QueryPlan may be recorded as a winner. */ class QueryOp : private boost::noncopyable { public: QueryOp() : _complete(), _stopRequested(), _queryPlan(), _error() {} virtual ~QueryOp() {} /** @return QueryPlan assigned to this QueryOp by the query optimizer. */ const QueryPlan &queryPlan() const { return *_queryPlan; } /** Advance to next potential matching document (eg using a cursor). */ virtual void next() = 0; /** * @return current 'nscanned' metric for this QueryOp. Used to compare * cost to other QueryOps. */ virtual long long nscanned() = 0; /** Take any steps necessary before the db mutex is yielded. */ virtual void prepareToYield() = 0; /** Recover once the db mutex is regained. */ virtual void recoverFromYield() = 0; /** * @return true iff the QueryPlan for this QueryOp may be registered * as a winning plan. */ virtual bool mayRecordPlan() const = 0; /** @return true iff the implementation called setComplete() or setStop(). */ bool complete() const { return _complete; } /** @return true iff the implementation called steStop(). */ bool stopRequested() const { return _stopRequested; } bool completeWithoutStop() const { return complete() && !stopRequested(); } /** @return true iff the implementation threw an exception. */ bool error() const { return _error; } /** @return the exception thrown by implementation if one was thrown. */ ExceptionInfo exception() const { return _exception; } /** To be called by QueryPlanSet::Runner only. */ /** * @return a copy of the inheriting class, which will be run with its own query plan. The * child QueryOp will assume its parent QueryOp has completed execution. */ virtual QueryOp *createChild() const = 0; void setQueryPlan( const QueryPlan *queryPlan ) { _queryPlan = queryPlan; verify( _queryPlan != NULL ); } /** Handle initialization after a QueryPlan has been set. */ virtual void init() = 0; void setException( const DBException &e ) { _error = true; _exception = e.getInfo(); } /** @return an ExplainPlanInfo object that will be updated as the query runs. */ virtual shared_ptr generateExplainInfo() { return shared_ptr( new ExplainPlanInfo() ); } protected: /** Call if all results have been found. */ void setComplete() { _complete = true; } /** Call if the scan is complete even if not all results have been found. */ void setStop() { setComplete(); _stopRequested = true; } private: bool _complete; bool _stopRequested; ExceptionInfo _exception; const QueryPlan *_queryPlan; bool _error; }; // temp. this class works if T::operator< is variant unlike a regular stl priority queue. // but it's very slow. however if v.size() is always very small, it would be fine, // maybe even faster than a smart impl that does more memory allocations. template class our_priority_queue : boost::noncopyable { vector v; public: our_priority_queue() { v.reserve(4); } int size() const { return v.size(); } bool empty() const { return v.empty(); } void push(const T & x) { v.push_back(x); } T pop() { size_t t = 0; for( size_t i = 1; i < v.size(); i++ ) { if( v[t] < v[i] ) t = i; } T ret = v[t]; v.erase(v.begin()+t); return ret; } }; class QueryPlanSet; /** Populates a provided QueryPlanSet with candidate query plans, when requested. */ class QueryPlanGenerator { public: /** Policies for utilizing recorded plans. */ typedef enum { Ignore, // Ignore the recorded plan and try all candidate plans. UseIfInOrder, // Use the recorded plan if it is properly ordered. Use // Always use the recorded plan. } RecordedPlanPolicy; /** @param qps The QueryPlanSet to which plans will be provided. */ QueryPlanGenerator( QueryPlanSet &qps, auto_ptr originalFrsp, const shared_ptr &parsedQuery, const BSONObj &hint, RecordedPlanPolicy recordedPlanPolicy, const BSONObj &min, const BSONObj &max, bool allowSpecial ); /** Populate the provided QueryPlanSet with an initial set of plans. */ void addInitialPlans(); /** Supplement a cached plan provided earlier by adding additional query plans. */ void addFallbackPlans(); private: bool addShortCircuitPlan( NamespaceDetails *d ); bool addHintPlan( NamespaceDetails *d ); bool addSpecialPlan( NamespaceDetails *d ); void addStandardPlans( NamespaceDetails *d ); bool addCachedPlan( NamespaceDetails *d ); shared_ptr newPlan( NamespaceDetails *d, int idxNo, const BSONObj &min = BSONObj(), const BSONObj &max = BSONObj(), const string &special = "" ) const; bool setUnindexedPlanIf( bool set, NamespaceDetails *d ); void setSingleUnindexedPlan( NamespaceDetails *d ); void setHintedPlanForIndex( IndexDetails& id ); void validateAndSetHintedPlan( const shared_ptr& plan ); void warnOnCappedIdTableScan() const; QueryPlanSet &_qps; auto_ptr _originalFrsp; shared_ptr _parsedQuery; BSONObj _hint; RecordedPlanPolicy _recordedPlanPolicy; BSONObj _min; BSONObj _max; bool _allowSpecial; }; /** * A set of candidate query plans for a query. This class can return a best guess plan or run a * QueryOp on all the plans. */ class QueryPlanSet { public: typedef boost::shared_ptr QueryPlanPtr; typedef vector PlanSet; /** * @param originalFrsp - original constraints for this query clause; if null, frsp will be * used. */ static QueryPlanSet* make( const char* ns, auto_ptr frsp, auto_ptr originalFrsp, const BSONObj& originalQuery, const BSONObj& order, const shared_ptr& parsedQuery, const BSONObj& hint, QueryPlanGenerator::RecordedPlanPolicy recordedPlanPolicy, const BSONObj& min, const BSONObj& max, bool allowSpecial ); /** @return number of candidate plans. */ int nPlans() const { return _plans.size(); } QueryPlanPtr firstPlan() const { return _plans[ 0 ]; } /** @return true if a plan is selected based on previous success of this plan. */ bool usingCachedPlan() const { return _usingCachedPlan; } /** @return true if some candidate plans may have been excluded due to plan caching. */ bool hasPossiblyExcludedPlans() const; /** @return a single plan that may work well for the specified query. */ QueryPlanPtr getBestGuess() const; const FieldRangeSetPair &frsp() const { return *_frsp; } BSONObj originalQuery() const { return _originalQuery; } BSONObj order() const { return _order; } /** @return true if an active plan is in order. */ bool haveInOrderPlan() const; /** @return true if an active or fallback plan is in order. */ bool possibleInOrderPlan() const; /** @return true if an active or fallback plan is out of order. */ bool possibleOutOfOrderPlan() const; CandidatePlanCharacter characterizeCandidatePlans() const; bool prepareToRetryQuery(); string toString() const; /** Configure a single query plan if one has not already been provided. */ void setSinglePlan( const QueryPlanPtr &plan ); /** Configure a query plan from the plan cache. */ void setCachedPlan( const QueryPlanPtr &plan, const CachedQueryPlan &cachedPlan ); /** Add a candidate query plan, potentially one of many. */ void addCandidatePlan( const QueryPlanPtr &plan ); //for testing bool modifiedKeys() const; bool hasMultiKey() const; class Runner { public: Runner( QueryPlanSet &plans, QueryOp &op ); /** * Advance the runner, if it is not done(). * @return the next non error op if there is one, otherwise an error op. * If the returned op is complete() or error(), the Runner becomes done(). */ shared_ptr next(); /** @return true if done iterating. */ bool done() const { return _done; } void prepareToYield(); void recoverFromYield(); /** @return an ExplainClauseInfo object that will be updated as the query runs. */ shared_ptr generateExplainInfo() { _explainClauseInfo.reset( new ExplainClauseInfo() ); return _explainClauseInfo; } private: QueryOp &_op; QueryPlanSet &_plans; static void initOp( QueryOp &op ); static void nextOp( QueryOp &op ); static void prepareToYieldOp( QueryOp &op ); static void recoverFromYieldOp( QueryOp &op ); /** Initialize the Runner. */ shared_ptr init(); /** Move the Runner forward one iteration, and @return the plan for the iteration. */ shared_ptr _next(); vector > _ops; struct OpHolder { OpHolder( const shared_ptr &op ) : _op( op ), _offset() {} shared_ptr _op; long long _offset; bool operator<( const OpHolder &other ) const { return _op->nscanned() + _offset > other._op->nscanned() + other._offset; } }; our_priority_queue _queue; shared_ptr _explainClauseInfo; bool _done; }; private: QueryPlanSet( const char *ns, auto_ptr frsp, auto_ptr originalFrsp, const BSONObj &originalQuery, const BSONObj &order, const shared_ptr &parsedQuery, const BSONObj &hint, QueryPlanGenerator::RecordedPlanPolicy recordedPlanPolicy, const BSONObj &min, const BSONObj &max, bool allowSpecial ); void init(); void addFallbackPlans(); void pushPlan( const QueryPlanPtr& plan ); QueryPlanGenerator _generator; BSONObj _originalQuery; auto_ptr _frsp; PlanSet _plans; bool _mayRecordPlan; bool _usingCachedPlan; CandidatePlanCharacter _cachedPlanCharacter; BSONObj _order; long long _oldNScanned; ElapsedTracker _yieldSometimesTracker; bool _allowSpecial; }; /** Handles $or type queries by generating a QueryPlanSet for each $or clause. */ class MultiPlanScanner { public: static MultiPlanScanner *make( const char *ns, const BSONObj &query, const BSONObj &order, const shared_ptr &parsedQuery = shared_ptr(), const BSONObj &hint = BSONObj(), QueryPlanGenerator::RecordedPlanPolicy recordedPlanPolicy = QueryPlanGenerator::Use, const BSONObj &min = BSONObj(), const BSONObj &max = BSONObj() ); /** Set the initial QueryOp for QueryPlanSet iteration. */ void initialOp( const shared_ptr &originalOp ) { _baseOp = originalOp; } /** * Advance to the next QueryOp, if not doneOps(). * @return the next non error op if there is one, otherwise an error op. * If the returned op is complete() or error(), the MultiPlanScanner becomes doneOps() and * no further QueryOp iteration is possible. */ shared_ptr nextOp(); /** @return true if done with QueryOp iteration. */ bool doneOps() const { return _doneOps; } /** * Advance to the next $or clause; hasMoreClauses() must be true. * @param currentPlan QueryPlan of the current $or clause * @return best guess query plan of the next $or clause, 0 if there is no such plan. */ const QueryPlan *nextClauseBestGuessPlan( const QueryPlan ¤tPlan ); /** Add explain information for a new clause. */ void addClauseInfo( const shared_ptr &clauseInfo ) { verify( _explainQueryInfo ); _explainQueryInfo->addClauseInfo( clauseInfo ); } /** @return an ExplainQueryInfo object that will be updated as the query runs. */ shared_ptr generateExplainInfo() { _explainQueryInfo.reset( new ExplainQueryInfo() ); return _explainQueryInfo; } /** Yield the runner member. */ void prepareToYield(); void recoverFromYield(); /** Clear the runner member. */ void clearRunner(); void setRecordedPlanPolicy( QueryPlanGenerator::RecordedPlanPolicy recordedPlanPolicy ) { _recordedPlanPolicy = recordedPlanPolicy; } int currentNPlans() const; /** * @return the query plan that would be used if the scanner would run a single * cursor for this query, otherwise 0. The returned plan is invalid if this * MultiPlanScanner is destroyed, hence we return a raw pointer. */ const QueryPlan *singlePlan() const; /** @return true if more $or clauses need to be scanned. */ bool hasMoreClauses() const { return _or ? ( !_tableScanned && !_org->orRangesExhausted() ) : _i == 0; } /** * @return plan information if there is a cached plan for a non $or query, otherwise an * empty object. */ BSONObj cachedPlanExplainSummary() const; /** * @return true if this is not a $or query and some candidate plans may have been excluded * due to plan caching. */ bool hasPossiblyExcludedPlans() const { return !_or && _currentQps->hasPossiblyExcludedPlans(); } bool hasMultiKey() const { return _currentQps->hasMultiKey(); } /** Clear recorded indexes for the current QueryPlanSet's patterns. */ void clearIndexesForPatterns() const; /** @return true if an active plan of _currentQps is in order. */ bool haveInOrderPlan() const; /** @return true if an active or fallback plan of _currentQps is in order. */ bool possibleInOrderPlan() const; /** @return true if an active or fallback plan of _currentQps is out of order. */ bool possibleOutOfOrderPlan() const; int i() const { return _i; } string toString() const; private: MultiPlanScanner( const char *ns, const BSONObj &query, const shared_ptr &parsedQuery, const BSONObj &hint, QueryPlanGenerator::RecordedPlanPolicy recordedPlanPolicy ); void init( const BSONObj &order, const BSONObj &min, const BSONObj &max ); /** Initialize or iterate a runner generated from @param originalOp. */ shared_ptr iterateRunner( QueryOp &originalOp, bool retried = false ); shared_ptr nextOpSimple(); shared_ptr nextOpOr(); void updateCurrentQps( QueryPlanSet *qps ); void assertNotOr() const { massert( 13266, "not implemented for $or query", !_or ); } void assertHasMoreClauses() const { massert( 13271, "no more clauses", hasMoreClauses() ); } void handleEndOfClause( const QueryPlan &clausePlan ); void handleBeginningOfClause(); bool mayHandleBeginningOfClause(); bool haveUselessOr() const; const string _ns; bool _or; BSONObj _query; shared_ptr _parsedQuery; scoped_ptr _org; // May be null in certain non $or query cases. scoped_ptr _currentQps; int _i; QueryPlanGenerator::RecordedPlanPolicy _recordedPlanPolicy; BSONObj _hint; bool _tableScanned; shared_ptr _baseOp; scoped_ptr _runner; shared_ptr _explainQueryInfo; bool _doneOps; }; /** * Provides a cursor interface for serial single Cursor iteration using a MultiPlanScanner. * Currently used internally by a QueryOptimizerCursor. * * A MultiCursor is backed by one BasicCursor or BtreeCursor at a time and forwards calls for * ensuring a consistent state after a write to its backing Cursor. */ class MultiCursor : public Cursor { public: /** @param nscanned is the initial nscanned value. */ MultiCursor( auto_ptr mps, const shared_ptr &c, const shared_ptr &matcher, const shared_ptr &explainPlanInfo, const QueryOp &op, long long nscanned ); virtual bool ok() { return _c->ok(); } virtual Record* _current() { return _c->_current(); } virtual BSONObj current() { return _c->current(); } virtual DiskLoc currLoc() { return _c->currLoc(); } virtual bool advance(); virtual BSONObj currKey() const { return _c->currKey(); } virtual DiskLoc refLoc() { return _c->refLoc(); } virtual void noteLocation() { _c->noteLocation(); } virtual void checkLocation() { _c->checkLocation(); } virtual void recoverFromYield(); virtual bool supportGetMore() { return true; } virtual bool supportYields() { return true; } virtual BSONObj indexKeyPattern() { return _c->indexKeyPattern(); } /** Deduping documents from a prior cursor is handled by the matcher. */ virtual bool getsetdup(DiskLoc loc) { return _c->getsetdup( loc ); } virtual bool modifiedKeys() const { return true; } virtual bool isMultiKey() const { return _mps->hasMultiKey(); } virtual CoveredIndexMatcher* matcher() const { return _matcher.get(); } virtual bool capped() const { return _c->capped(); } virtual long long nscanned() { return _nscanned + _c->nscanned(); } void noteIterate( bool match, bool loadedRecord ); const QueryPlan &queryPlan() const { verify( _c->ok() && _queryPlan ); return *_queryPlan; } const Projection::KeyOnly *keyFieldsOnly() const { verify( _c->ok() && _queryPlan ); return _queryPlan->keyFieldsOnly().get(); } private: void advanceClause(); void advanceExhaustedClauses(); auto_ptr _mps; shared_ptr _c; shared_ptr _matcher; const QueryPlan *_queryPlan; long long _nscanned; shared_ptr _explainPlanInfo; }; /** NOTE min, max, and keyPattern will be updated to be consistent with the selected index. */ IndexDetails *indexDetailsForRange( const char *ns, string &errmsg, BSONObj &min, BSONObj &max, BSONObj &keyPattern ); class CachedQueryPlan; /** * Add-on functionality for queryutil classes requiring access to indexing * functionality not currently linked to mongos. * TODO Clean this up a bit, possibly with separate sharded and non sharded * implementations for the appropriate queryutil classes or by pulling index * related functionality into separate wrapper classes. */ struct QueryUtilIndexed { /** @return true if the index may be useful according to its KeySpec. */ static bool indexUseful( const FieldRangeSetPair &frsp, NamespaceDetails *d, int idxNo, const BSONObj &order ); /** Clear any indexes recorded as the best for either the single or multi key pattern. */ static void clearIndexesForPatterns( const FieldRangeSetPair &frsp, const BSONObj &order ); /** Return a recorded best index for the single or multi key pattern. */ static CachedQueryPlan bestIndexForPatterns( const FieldRangeSetPair &frsp, const BSONObj &order ); static bool uselessOr( const OrRangeGenerator& org, NamespaceDetails *d, int hintIdx ); }; } // namespace mongo