/* 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 "jsobj.h" #include "queryutil.h" #include "matcher.h" #include "../util/message.h" namespace mongo { class IndexDetails; class IndexType; class QueryPlan : boost::noncopyable { public: QueryPlan(NamespaceDetails *d, int idxNo, // -1 = no index const FieldRangeSet &fbs, const FieldRangeSet &originalFrs, const BSONObj &originalQuery, const BSONObj &order, const BSONObj &startKey = BSONObj(), const BSONObj &endKey = BSONObj() , string special="" ); /* If true, no other index can do better. */ bool optimal() const { return _optimal; } /* ScanAndOrder processing will be required if true */ bool scanAndOrderRequired() const { return _scanAndOrderRequired; } /* When true, 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; } /* If true, the startKey and endKey are unhelpful and the index order doesn't match the requested sort order */ bool unhelpful() const { return _unhelpful; } int direction() const { return _direction; } shared_ptr newCursor( const DiskLoc &startLoc = DiskLoc() , int numWanted=0 ) const; shared_ptr newReverseCursor() const; BSONObj indexKey() const; bool indexed() const { return _index; } bool willScanTable() const { return !_index && _fbs.matchPossible(); } const char *ns() const { return _fbs.ns(); } NamespaceDetails *nsd() const { return _d; } BSONObj originalQuery() const { return _originalQuery; } BSONObj simplifiedQuery( const BSONObj& fields = BSONObj() ) const { return _fbs.simplifiedQuery( fields ); } const FieldRange &range( const char *fieldName ) const { return _fbs.range( fieldName ); } void registerSelf( long long nScanned ) const; shared_ptr< FieldRangeVector > originalFrv() const { return _originalFrv; } // just for testing shared_ptr< FieldRangeVector > frv() const { return _frv; } bool isMultiKey() const; private: NamespaceDetails * _d; int _idxNo; const FieldRangeSet &_fbs; const BSONObj &_originalQuery; const BSONObj &_order; const IndexDetails * _index; bool _optimal; bool _scanAndOrderRequired; bool _exactKeyMatch; int _direction; shared_ptr< FieldRangeVector > _frv; shared_ptr< FieldRangeVector > _originalFrv; BSONObj _startKey; BSONObj _endKey; bool _endKeyInclusive; bool _unhelpful; string _special; IndexType * _type; bool _startOrEndSpec; }; // 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. class QueryOp { public: QueryOp() : _complete(), _stopRequested(), _qp(), _error() {} // Used when handing off from one QueryOp type to another QueryOp( const QueryOp &other ) : _complete(), _stopRequested(), _qp(), _error(), _matcher( other._matcher ), _orConstraint( other._orConstraint ) {} virtual ~QueryOp() {} /** these gets called after a query plan is set */ void init() { if ( _oldMatcher.get() ) { _matcher.reset( _oldMatcher->nextClauseMatcher( qp().indexKey() ) ); } else { _matcher.reset( new CoveredIndexMatcher( qp().originalQuery(), qp().indexKey(), alwaysUseRecord() ) ); } _init(); } virtual void next() = 0; virtual bool mayRecordPlan() const = 0; virtual bool prepareToYield() { massert( 13335, "yield not supported", false ); return false; } virtual void recoverFromYield() { massert( 13336, "yield not supported", false ); } virtual long long nscanned() = 0; /** @return a copy of the inheriting class, which will be run with its own query plan. If multiple plan sets are required for an $or query, the QueryOp of the winning plan from a given set will be cloned to generate QueryOps for the subsequent plan set. This function should only be called after the query op has completed executing. */ QueryOp *createChild() { if( _orConstraint.get() ) { _matcher->advanceOrClause( _orConstraint ); _orConstraint.reset(); } QueryOp *ret = _createChild(); ret->_oldMatcher = _matcher; return ret; } bool complete() const { return _complete; } bool error() const { return _error; } bool stopRequested() const { return _stopRequested; } ExceptionInfo exception() const { return _exception; } const QueryPlan &qp() const { return *_qp; } // To be called by QueryPlanSet::Runner only. void setQueryPlan( const QueryPlan *qp ) { _qp = qp; } void setException( const DBException &e ) { _error = true; _exception = e.getInfo(); } shared_ptr< CoveredIndexMatcher > matcher() const { return _matcher; } protected: void setComplete() { _orConstraint = qp().originalFrv(); _complete = true; } void setStop() { setComplete(); _stopRequested = true; } virtual void _init() = 0; virtual QueryOp *_createChild() const = 0; virtual bool alwaysUseRecord() const { return false; } private: bool _complete; bool _stopRequested; ExceptionInfo _exception; const QueryPlan *_qp; bool _error; shared_ptr< CoveredIndexMatcher > _matcher; shared_ptr< CoveredIndexMatcher > _oldMatcher; shared_ptr< FieldRangeVector > _orConstraint; }; // Set of candidate query plans for a particular query. Used for running // a QueryOp on these plans. class QueryPlanSet { public: typedef boost::shared_ptr< QueryPlan > QueryPlanPtr; typedef vector< QueryPlanPtr > PlanSet; QueryPlanSet( const char *ns, auto_ptr< FieldRangeSet > frs, auto_ptr< FieldRangeSet > originalFrs, const BSONObj &originalQuery, const BSONObj &order, const BSONElement *hint = 0, bool honorRecordedPlan = true, const BSONObj &min = BSONObj(), const BSONObj &max = BSONObj(), bool bestGuessOnly = false, bool mayYield = false); int nPlans() const { return _plans.size(); } shared_ptr< QueryOp > runOp( QueryOp &op ); template< class T > shared_ptr< T > runOp( T &op ) { return dynamic_pointer_cast< T >( runOp( static_cast< QueryOp& >( op ) ) ); } BSONObj explain() const; bool usingPrerecordedPlan() const { return _usingPrerecordedPlan; } QueryPlanPtr getBestGuess() const; //for testing const FieldRangeSet &fbs() const { return *_fbs; } const FieldRangeSet &originalFrs() const { return *_originalFrs; } bool modifiedKeys() const; bool hasMultiKey() const; private: void addOtherPlans( bool checkFirst ); void addPlan( QueryPlanPtr plan, bool checkFirst ) { if ( checkFirst && plan->indexKey().woCompare( _plans[ 0 ]->indexKey() ) == 0 ) return; _plans.push_back( plan ); } void init(); void addHint( IndexDetails &id ); struct Runner { Runner( QueryPlanSet &plans, QueryOp &op ); shared_ptr< QueryOp > run(); void mayYield( const vector< shared_ptr< QueryOp > > &ops ); QueryOp &_op; QueryPlanSet &_plans; static void initOp( QueryOp &op ); static void nextOp( QueryOp &op ); static bool prepareToYield( QueryOp &op ); static void recoverFromYield( QueryOp &op ); }; const char *_ns; BSONObj _originalQuery; auto_ptr< FieldRangeSet > _fbs; auto_ptr< FieldRangeSet > _originalFrs; PlanSet _plans; bool _mayRecordPlan; bool _usingPrerecordedPlan; BSONObj _hint; BSONObj _order; long long _oldNScanned; bool _honorRecordedPlan; BSONObj _min; BSONObj _max; string _special; bool _bestGuessOnly; bool _mayYield; ElapsedTracker _yieldSometimesTracker; }; // Handles $or type queries by generating a QueryPlanSet for each $or clause // NOTE on our $or implementation: In our current qo implementation we don't // keep statistics on our data, but we can conceptualize the problem of // selecting an index when statistics exist for all index ranges. The // d-hitting set problem on k sets and n elements can be reduced to the // problem of index selection on k $or clauses and n index ranges (where // d is the max number of indexes, and the number of ranges n is unbounded). // In light of the fact that d-hitting set is np complete, and we don't even // track statistics (so cost calculations are expensive) our first // implementation uses the following greedy approach: We take one $or clause // at a time and treat each as a separate query for index selection purposes. // But if an index range is scanned for a particular $or clause, we eliminate // that range from all subsequent clauses. One could imagine an opposite // implementation where we select indexes based on the union of index ranges // for all $or clauses, but this can have much poorer worst case behavior. // (An index range that suits one $or clause may not suit another, and this // is worse than the typical case of index range choice staleness because // with $or the clauses may likely be logically distinct.) The greedy // implementation won't do any worse than all the $or clauses individually, // and it can often do better. In the first cut we are intentionally using // QueryPattern tracking to record successful plans on $or clauses for use by // subsequent $or clauses, even though there may be a significant aggregate // $nor component that would not be represented in QueryPattern. class MultiPlanScanner { public: MultiPlanScanner( const char *ns, const BSONObj &query, const BSONObj &order, const BSONElement *hint = 0, bool honorRecordedPlan = true, const BSONObj &min = BSONObj(), const BSONObj &max = BSONObj(), bool bestGuessOnly = false, bool mayYield = false); shared_ptr< QueryOp > runOp( QueryOp &op ); template< class T > shared_ptr< T > runOp( T &op ) { return dynamic_pointer_cast< T >( runOp( static_cast< QueryOp& >( op ) ) ); } shared_ptr< QueryOp > runOpOnce( QueryOp &op ); template< class T > shared_ptr< T > runOpOnce( T &op ) { return dynamic_pointer_cast< T >( runOpOnce( static_cast< QueryOp& >( op ) ) ); } bool mayRunMore() const { return _or ? ( !_tableScanned && !_fros.orFinished() ) : _i == 0; } BSONObj oldExplain() const { assertNotOr(); return _currentQps->explain(); } // just report this when only one query op bool usingPrerecordedPlan() const { return !_or && _currentQps->usingPrerecordedPlan(); } void setBestGuessOnly() { _bestGuessOnly = true; } void mayYield( bool val ) { _mayYield = val; } bool modifiedKeys() const { return _currentQps->modifiedKeys(); } bool hasMultiKey() const { return _currentQps->hasMultiKey(); } private: void assertNotOr() const { massert( 13266, "not implemented for $or query", !_or ); } bool uselessOr( const BSONElement &hint ) const; const char * _ns; bool _or; BSONObj _query; FieldRangeOrSet _fros; auto_ptr< QueryPlanSet > _currentQps; int _i; bool _honorRecordedPlan; bool _bestGuessOnly; BSONObj _hint; bool _mayYield; bool _tableScanned; }; class MultiCursor : public Cursor { public: class CursorOp : public QueryOp { public: CursorOp() {} CursorOp( const QueryOp &other ) : QueryOp( other ) {} virtual shared_ptr< Cursor > newCursor() const = 0; }; // takes ownership of 'op' MultiCursor( const char *ns, const BSONObj &pattern, const BSONObj &order, shared_ptr< CursorOp > op = shared_ptr< CursorOp >(), bool mayYield = false ) : _mps( new MultiPlanScanner( ns, pattern, order, 0, true, BSONObj(), BSONObj(), !op.get(), mayYield ) ), _nscanned() { if ( op.get() ) { _op = op; } else { _op.reset( new NoOp() ); } if ( _mps->mayRunMore() ) { nextClause(); if ( !ok() ) { advance(); } } else { _c.reset( new BasicCursor( DiskLoc() ) ); } } // used to handoff a query to a getMore() MultiCursor( auto_ptr< MultiPlanScanner > mps, const shared_ptr< Cursor > &c, const shared_ptr< CoveredIndexMatcher > &matcher, const QueryOp &op ) : _op( new NoOp( op ) ), _c( c ), _mps( mps ), _matcher( matcher ), _nscanned( -1 ) { _mps->setBestGuessOnly(); _mps->mayYield( false ); // with a NoOp, there's no need to yield in QueryPlanSet if ( !ok() ) { // would have been advanced by UserQueryOp if possible advance(); } } 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() { _c->advance(); while( !ok() && _mps->mayRunMore() ) { nextClause(); } return ok(); } virtual BSONObj currKey() const { return _c->currKey(); } virtual DiskLoc refLoc() { return _c->refLoc(); } virtual void noteLocation() { _c->noteLocation(); } virtual void checkLocation() { _c->checkLocation(); } virtual bool supportGetMore() { return true; } virtual bool supportYields() { return _c->supportYields(); } // with update we could potentially get the same document on multiple // indexes, but update appears to already handle this with seenObjects // so we don't have to do anything special here. virtual bool getsetdup(DiskLoc loc) { return _c->getsetdup( loc ); } virtual bool modifiedKeys() const { return _mps->modifiedKeys(); } virtual bool isMultiKey() const { return _mps->hasMultiKey(); } virtual CoveredIndexMatcher *matcher() const { return _matcher.get(); } // return -1 if we're a getmore handoff virtual long long nscanned() { return _nscanned >= 0 ? _nscanned + _c->nscanned() : _nscanned; } // just for testing shared_ptr< Cursor > sub_c() const { return _c; } private: class NoOp : public CursorOp { public: NoOp() {} NoOp( const QueryOp &other ) : CursorOp( other ) {} virtual void _init() { setComplete(); } virtual void next() {} virtual bool mayRecordPlan() const { return false; } virtual QueryOp *_createChild() const { return new NoOp(); } virtual shared_ptr< Cursor > newCursor() const { return qp().newCursor(); } virtual long long nscanned() { assert( false ); return 0; } }; void nextClause() { if ( _nscanned >= 0 && _c.get() ) { _nscanned += _c->nscanned(); } shared_ptr< CursorOp > best = _mps->runOpOnce( *_op ); if ( ! best->complete() ) throw MsgAssertionException( best->exception() ); _c = best->newCursor(); _matcher = best->matcher(); _op = best; } shared_ptr< CursorOp > _op; shared_ptr< Cursor > _c; auto_ptr< MultiPlanScanner > _mps; shared_ptr< CoveredIndexMatcher > _matcher; long long _nscanned; }; // 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 ); inline bool isSimpleIdQuery( const BSONObj& query ) { BSONObjIterator i(query); if( !i.more() ) return false; BSONElement e = i.next(); if( i.more() ) return false; if( strcmp("_id", e.fieldName()) != 0 ) return false; return e.isSimpleType(); // e.g. not something like { _id : { $gt : ... } // matcher() will always work on the returned cursor inline shared_ptr< Cursor > bestGuessCursor( const char *ns, const BSONObj &query, const BSONObj &sort ) { if( !query.getField( "$or" ).eoo() ) { return shared_ptr< Cursor >( new MultiCursor( ns, query, sort ) ); } else { auto_ptr< FieldRangeSet > frs( new FieldRangeSet( ns, query ) ); auto_ptr< FieldRangeSet > origFrs( new FieldRangeSet( *frs ) ); shared_ptr< Cursor > ret = QueryPlanSet( ns, frs, origFrs, query, sort ).getBestGuess()->newCursor(); if ( !query.isEmpty() ) { shared_ptr< CoveredIndexMatcher > matcher( new CoveredIndexMatcher( query, ret->indexKeyPattern() ) ); ret->setMatcher( matcher ); } return ret; } } } // namespace mongo