// @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(),
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; }
/** @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,
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 shared_ptr< CoveredIndexMatcher > matcherPtr() const { return _matcher; }
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