// @file queryoptimizercursor.h /** * 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 . */ #pragma once #include "diskloc.h" namespace mongo { class QueryPlan; /** * An interface for policies overriding the query optimizer's default query plan selection * behavior. */ class QueryPlanSelectionPolicy { public: virtual ~QueryPlanSelectionPolicy() {} virtual string name() const = 0; virtual bool permitOptimalNaturalPlan() const { return true; } virtual bool permitOptimalIdPlan() const { return true; } virtual bool permitPlan( const QueryPlan &plan ) const { return true; } virtual BSONObj planHint( const char *ns ) const { return BSONObj(); } /** Allow any query plan selection, permitting the query optimizer's default behavior. */ static const QueryPlanSelectionPolicy &any(); /** Prevent unindexed collection scans. */ static const QueryPlanSelectionPolicy &indexOnly(); /** * Generally hints to use the _id plan, falling back to the $natural plan. However, the * $natural plan will always be used if optimal for the query. */ static const QueryPlanSelectionPolicy &idElseNatural(); private: class Any; static Any __any; class IndexOnly; static IndexOnly __indexOnly; class IdElseNatural; static IdElseNatural __idElseNatural; }; class QueryPlanSelectionPolicy::Any : public QueryPlanSelectionPolicy { public: virtual string name() const { return "any"; } }; class QueryPlanSelectionPolicy::IndexOnly : public QueryPlanSelectionPolicy { public: virtual string name() const { return "indexOnly"; } virtual bool permitOptimalNaturalPlan() const { return false; } virtual bool permitPlan( const QueryPlan &plan ) const; }; class QueryPlanSelectionPolicy::IdElseNatural : public QueryPlanSelectionPolicy { public: virtual string name() const { return "idElseNatural"; } virtual bool permitPlan( const QueryPlan &plan ) const; virtual BSONObj planHint( const char *ns ) const; }; /** Helper class for caching and counting matches during execution of a QueryPlan. */ class CachedMatchCounter { public: /** * @param aggregateNscanned - shared count of nscanned for this and othe plans. * @param cumulativeCount - starting point for accumulated count over a series of plans. */ CachedMatchCounter( long long &aggregateNscanned, int cumulativeCount ) : _aggregateNscanned( aggregateNscanned ), _nscanned(), _cumulativeCount( cumulativeCount ), _count(), _checkDups(), _match( Unknown ), _counted() {} /** Set whether dup checking is enabled when counting. */ void setCheckDups( bool checkDups ) { _checkDups = checkDups; } /** * Usual sequence of events: * 1) resetMatch() - reset stored match value to Unkonwn. * 2) setMatch() - set match value to a definite true/false value. * 3) knowMatch() - check if setMatch() has been called. * 4) countMatch() - increment count if match is true. */ void resetMatch() { _match = Unknown; _counted = false; } void setMatch( bool match ) { _match = match ? True : False; } bool knowMatch() const { return _match != Unknown; } void countMatch( const DiskLoc &loc ) { if ( !_counted && _match == True && !getsetdup( loc ) ) { ++_cumulativeCount; ++_count; _counted = true; } } bool enoughCumulativeMatchesToChooseAPlan() const { // This is equivalent to the default condition for switching from // a query to a getMore, which was the historical default match count for // choosing a plan. return _cumulativeCount >= 101; } bool enoughMatchesToRecordPlan() const { // Recording after 50 matches is a historical default (101 default limit / 2). return _count > 50; } int cumulativeCount() const { return _cumulativeCount; } int count() const { return _count; } /** Update local and aggregate nscanned counts. */ void updateNscanned( long long nscanned ) { _aggregateNscanned += ( nscanned - _nscanned ); _nscanned = nscanned; } long long nscanned() const { return _nscanned; } long long &aggregateNscanned() const { return _aggregateNscanned; } private: bool getsetdup( const DiskLoc &loc ) { if ( !_checkDups ) { return false; } pair::iterator, bool> p = _dups.insert( loc ); return !p.second; } long long &_aggregateNscanned; long long _nscanned; int _cumulativeCount; int _count; bool _checkDups; enum MatchState { Unknown, False, True }; MatchState _match; bool _counted; set _dups; }; /** Dup tracking class, optimizing one common case with small set and few initial reads. */ class SmallDupSet { public: SmallDupSet() : _accesses() { _vec.reserve( 250 ); } /** @return true if @param 'loc' already added to the set, false if adding to the set in this call. */ bool getsetdup( const DiskLoc &loc ) { access(); return vec() ? getsetdupVec( loc ) : getsetdupSet( loc ); } /** @return true when @param loc in the set. */ bool getdup( const DiskLoc &loc ) { access(); return vec() ? getdupVec( loc ) : getdupSet( loc ); } private: void access() { ++_accesses; mayUpgrade(); } void mayUpgrade() { if ( vec() && _accesses > 500 ) { _set.insert( _vec.begin(), _vec.end() ); } } bool vec() const { return _set.size() == 0; } bool getsetdupVec( const DiskLoc &loc ) { if ( getdupVec( loc ) ) { return true; } _vec.push_back( loc ); return false; } bool getdupVec( const DiskLoc &loc ) const { for( vector::const_iterator i = _vec.begin(); i != _vec.end(); ++i ) { if ( *i == loc ) { return true; } } return false; } bool getsetdupSet( const DiskLoc &loc ) { pair::iterator, bool> p = _set.insert(loc); return !p.second; } bool getdupSet( const DiskLoc &loc ) { return _set.count( loc ) > 0; } vector _vec; set _set; long long _accesses; }; } // namespace mongo