diff options
author | Eric Milkie <milkie@10gen.com> | 2012-10-18 19:15:12 -0400 |
---|---|---|
committer | Eric Milkie <milkie@10gen.com> | 2012-10-18 19:15:48 -0400 |
commit | b2d72c6390804e049d4094a34f09081f49cb95de (patch) | |
tree | bf7f8d136431342005fa7880c2d314913fbdcbc0 /src | |
parent | 4469161b8833728d62ad9ee00a9ee31799e1cc9a (diff) | |
download | mongo-b2d72c6390804e049d4094a34f09081f49cb95de.tar.gz |
split queryoptimizertests into two translation units
This file was taking too long to compile and causing build breakages.
Diffstat (limited to 'src')
-rw-r--r-- | src/mongo/dbtests/queryoptimizertests.cpp | 798 | ||||
-rw-r--r-- | src/mongo/dbtests/queryoptimizertests2.cpp | 831 |
2 files changed, 840 insertions, 789 deletions
diff --git a/src/mongo/dbtests/queryoptimizertests.cpp b/src/mongo/dbtests/queryoptimizertests.cpp index 9be3867b39d..15861f929cc 100644 --- a/src/mongo/dbtests/queryoptimizertests.cpp +++ b/src/mongo/dbtests/queryoptimizertests.cpp @@ -1,6 +1,3 @@ -// queryoptimizertests.cpp : query optimizer unit tests -// - /** * Copyright (C) 2009 10gen Inc. * @@ -17,14 +14,15 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#include "pch.h" -#include "../db/queryoptimizer.h" -#include "../db/instance.h" -#include "../db/ops/count.h" -#include "../db/ops/query.h" -#include "../db/ops/delete.h" +#include "mongo/pch.h" + +#include "mongo/db/instance.h" #include "mongo/db/json.h" -#include "dbtests.h" +#include "mongo/db/ops/count.h" +#include "mongo/db/ops/delete.h" +#include "mongo/db/ops/query.h" +#include "mongo/db/queryoptimizer.h" +#include "mongo/dbtests/dbtests.h" namespace mongo { @@ -41,7 +39,7 @@ namespace mongo { void __forceLinkGeoPlugin(); } // namespace mongo -namespace QueryOptimizerTests { +namespace { using boost::shared_ptr; @@ -835,755 +833,6 @@ namespace QueryOptimizerTests { }; } // namespace QueryPlanTests - - namespace QueryPlanSetTests { - - class Base { - public: - Base() : _context( ns() ) { - string err; - userCreateNS( ns(), BSONObj(), err, false ); - } - virtual ~Base() { - if ( !nsd() ) - return; - NamespaceDetailsTransient::get_inlock( ns() ).clearQueryCache(); - dropCollection( ns() ); - } - protected: - static void assembleRequest( const string &ns, BSONObj query, int nToReturn, int nToSkip, BSONObj *fieldsToReturn, int queryOptions, Message &toSend ) { - // see query.h for the protocol we are using here. - BufBuilder b; - int opts = queryOptions; - b.appendNum(opts); - b.appendStr(ns); - b.appendNum(nToSkip); - b.appendNum(nToReturn); - query.appendSelfToBufBuilder(b); - if ( fieldsToReturn ) - fieldsToReturn->appendSelfToBufBuilder(b); - toSend.setData(dbQuery, b.buf(), b.len()); - } - QueryPattern makePattern( const BSONObj &query, const BSONObj &order ) { - FieldRangeSet frs( ns(), query, true, true ); - return QueryPattern( frs, order ); - } - shared_ptr<QueryPlanSet> makeQps( const BSONObj& query = BSONObj(), - const BSONObj& order = BSONObj(), - const BSONObj& hint = BSONObj(), - bool allowSpecial = true ) { - auto_ptr<FieldRangeSetPair> frsp( new FieldRangeSetPair( ns(), query ) ); - auto_ptr<FieldRangeSetPair> frspOrig( new FieldRangeSetPair( *frsp ) ); - return shared_ptr<QueryPlanSet> - ( QueryPlanSet::make( ns(), frsp, frspOrig, query, order, - shared_ptr<const ParsedQuery>(), hint, - QueryPlanGenerator::Use, BSONObj(), BSONObj(), - allowSpecial ) ); - } - static const char *ns() { return "unittests.QueryPlanSetTests"; } - static NamespaceDetails *nsd() { return nsdetails( ns() ); } - DBDirectClient &client() { return _client; } - private: - Lock::GlobalWrite lk_; - Client::Context _context; - DBDirectClient _client; - }; - - class ToString : public Base { - public: - void run() { - // Just test that we don't crash. - makeQps( BSON( "a" << 1 ) )->toString(); - } - }; - - class NoIndexes : public Base { - public: - void run() { - ASSERT_EQUALS( 1, makeQps( BSON( "a" << 4 ), BSON( "b" << 1 ) )->nPlans() ); - } - }; - - class Optimal : public Base { - public: - void run() { - Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); - Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "b_2" ); - BSONObj query = BSON( "a" << 4 ); - - // Only one optimal plan is added to the plan set. - ASSERT_EQUALS( 1, makeQps( query )->nPlans() ); - - // The optimal plan is recorded in the plan cache. - FieldRangeSet frs( ns(), query, true, true ); - CachedQueryPlan cachedPlan = - NamespaceDetailsTransient::get( ns() ).cachedQueryPlanForPattern - ( QueryPattern( frs, BSONObj() ) ); - ASSERT_EQUALS( BSON( "a" << 1 ), cachedPlan.indexKey() ); - CandidatePlanCharacter planCharacter = cachedPlan.planCharacter(); - ASSERT( planCharacter.mayRunInOrderPlan() ); - ASSERT( !planCharacter.mayRunOutOfOrderPlan() ); - } - }; - - class NoOptimal : public Base { - public: - void run() { - Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); - Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" ); - ASSERT_EQUALS( 3, makeQps( BSON( "a" << 4 ), BSON( "b" << 1 ) )->nPlans() ); - } - }; - - class NoSpec : public Base { - public: - void run() { - Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); - Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" ); - ASSERT_EQUALS( 1, makeQps()->nPlans() ); - } - }; - - class HintSpec : public Base { - public: - void run() { - Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); - Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" ); - ASSERT_EQUALS( 1, makeQps( BSON( "a" << 1 ), BSON( "b" << 1 ), - BSON( "hint" << BSON( "a" << 1 ) ) )->nPlans() ); - } - }; - - class HintName : public Base { - public: - void run() { - Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); - Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" ); - ASSERT_EQUALS( 1, makeQps( BSON( "a" << 1 ), BSON( "b" << 1 ), - BSON( "hint" << "a_1" ) )->nPlans() ); - } - }; - - class NaturalHint : public Base { - public: - void run() { - Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); - Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" ); - ASSERT_EQUALS( 1, makeQps( BSON( "a" << 1 ), BSON( "b" << 1 ), - BSON( "hint" << BSON( "$natural" << 1 ) ) )->nPlans() ); - } - }; - - class NaturalSort : public Base { - public: - void run() { - Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); - Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "b_2" ); - ASSERT_EQUALS( 1, makeQps( BSON( "a" << 1 ), BSON( "$natural" << 1 ) )->nPlans() ); - } - }; - - class BadHint : public Base { - public: - void run() { - ASSERT_THROWS( makeQps( BSON( "a" << 1 ), BSON( "b" << 1 ), - BSON( "hint" << "a_1" ) ), - AssertionException ); - } - }; - - class Count : public Base { - public: - void run() { - Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); - Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" ); - string err; - int errCode; - ASSERT_EQUALS( 0, runCount( ns(), BSON( "query" << BSON( "a" << 4 ) ), err, errCode ) ); - BSONObj one = BSON( "a" << 1 ); - BSONObj fourA = BSON( "a" << 4 ); - BSONObj fourB = BSON( "a" << 4 ); - theDataFileMgr.insertWithObjMod( ns(), one ); - ASSERT_EQUALS( 0, runCount( ns(), BSON( "query" << BSON( "a" << 4 ) ), err, errCode ) ); - theDataFileMgr.insertWithObjMod( ns(), fourA ); - ASSERT_EQUALS( 1, runCount( ns(), BSON( "query" << BSON( "a" << 4 ) ), err, errCode ) ); - theDataFileMgr.insertWithObjMod( ns(), fourB ); - ASSERT_EQUALS( 2, runCount( ns(), BSON( "query" << BSON( "a" << 4 ) ), err, errCode ) ); - ASSERT_EQUALS( 3, runCount( ns(), BSON( "query" << BSONObj() ), err, errCode ) ); - ASSERT_EQUALS( 3, runCount( ns(), BSON( "query" << BSON( "a" << GT << 0 ) ), err, errCode ) ); - // missing ns - ASSERT_EQUALS( -1, runCount( "unittests.missingNS", BSONObj(), err, errCode ) ); - // impossible match - ASSERT_EQUALS( 0, runCount( ns(), BSON( "query" << BSON( "a" << GT << 0 << LT << -1 ) ), err, errCode ) ); - } - }; - - class QueryMissingNs : public Base { - public: - QueryMissingNs() { log() << "querymissingns starts" << endl; } - ~QueryMissingNs() { - log() << "end QueryMissingNs" << endl; - } - void run() { - Message m; - assembleRequest( "unittests.missingNS", BSONObj(), 0, 0, 0, 0, m ); - DbMessage d(m); - QueryMessage q(d); - Message ret; - runQuery( m, q, ret ); - ASSERT_EQUALS( 0, ((QueryResult*)ret.header())->nReturned ); - } - - }; - - class UnhelpfulIndex : public Base { - public: - void run() { - Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); - Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" ); - ASSERT_EQUALS( 2, makeQps( BSON( "a" << 1 << "c" << 2 ) )->nPlans() ); - } - }; - - class FindOne : public Base { - public: - void run() { - BSONObj one = BSON( "a" << 1 ); - theDataFileMgr.insertWithObjMod( ns(), one ); - BSONObj result; - ASSERT( Helpers::findOne( ns(), BSON( "a" << 1 ), result ) ); - ASSERT_THROWS( Helpers::findOne( ns(), BSON( "a" << 1 ), result, true ), AssertionException ); - Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); - ASSERT( Helpers::findOne( ns(), BSON( "a" << 1 ), result, true ) ); - } - }; - - class Delete : public Base { - public: - void run() { - Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); - for( int i = 0; i < 200; ++i ) { - BSONObj two = BSON( "a" << 2 ); - theDataFileMgr.insertWithObjMod( ns(), two ); - } - BSONObj one = BSON( "a" << 1 ); - theDataFileMgr.insertWithObjMod( ns(), one ); - BSONObj delSpec = BSON( "a" << 1 << "_id" << NE << 0 ); - deleteObjects( ns(), delSpec, false ); - - NamespaceDetailsTransient &nsdt = NamespaceDetailsTransient::get( ns() ); - QueryPattern queryPattern = FieldRangeSet( ns(), delSpec, true, true ).pattern(); - CachedQueryPlan cachedQueryPlan = nsdt.cachedQueryPlanForPattern( queryPattern ); - ASSERT_EQUALS( BSON( "a" << 1 ), cachedQueryPlan.indexKey() ); - ASSERT_EQUALS( 1, cachedQueryPlan.nScanned() ); - } - }; - - class DeleteOneScan : public Base { - public: - void run() { - Helpers::ensureIndex( ns(), BSON( "_id" << 1 ), false, "_id_1" ); - BSONObj one = BSON( "_id" << 3 << "a" << 1 ); - BSONObj two = BSON( "_id" << 2 << "a" << 1 ); - BSONObj three = BSON( "_id" << 1 << "a" << -1 ); - theDataFileMgr.insertWithObjMod( ns(), one ); - theDataFileMgr.insertWithObjMod( ns(), two ); - theDataFileMgr.insertWithObjMod( ns(), three ); - deleteObjects( ns(), BSON( "_id" << GT << 0 << "a" << GT << 0 ), true ); - for( boost::shared_ptr<Cursor> c = theDataFileMgr.findAll( ns() ); c->ok(); c->advance() ) - ASSERT( 3 != c->current().getIntField( "_id" ) ); - } - }; - - class DeleteOneIndex : public Base { - public: - void run() { - Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a" ); - BSONObj one = BSON( "a" << 2 << "_id" << 0 ); - BSONObj two = BSON( "a" << 1 << "_id" << 1 ); - BSONObj three = BSON( "a" << 0 << "_id" << 2 ); - theDataFileMgr.insertWithObjMod( ns(), one ); - theDataFileMgr.insertWithObjMod( ns(), two ); - theDataFileMgr.insertWithObjMod( ns(), three ); - deleteObjects( ns(), BSON( "a" << GTE << 0 ), true ); - for( boost::shared_ptr<Cursor> c = theDataFileMgr.findAll( ns() ); c->ok(); c->advance() ) - ASSERT( 2 != c->current().getIntField( "_id" ) ); - } - }; - - class InQueryIntervals : public Base { - public: - void run() { - Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); - for( int i = 0; i < 10; ++i ) { - BSONObj temp = BSON( "a" << i ); - theDataFileMgr.insertWithObjMod( ns(), temp ); - } - BSONObj query = fromjson( "{a:{$in:[2,3,6,9,11]}}" ); - BSONObj order; - BSONObj hint = fromjson( "{$hint:{a:1}}" ); - auto_ptr< FieldRangeSetPair > frsp( new FieldRangeSetPair( ns(), query ) ); - shared_ptr<QueryPlanSet> s = makeQps( query, order, hint ); - scoped_ptr<QueryPlan> qp( QueryPlan::make( nsd(), 1, s->frsp(), frsp.get(), - query, order ) ); - boost::shared_ptr<Cursor> c = qp->newCursor(); - double expected[] = { 2, 3, 6, 9 }; - for( int i = 0; i < 4; ++i, c->advance() ) { - ASSERT_EQUALS( expected[ i ], c->current().getField( "a" ).number() ); - } - ASSERT( !c->ok() ); - - // now check reverse - { - order = BSON( "a" << -1 ); - auto_ptr< FieldRangeSetPair > frsp( new FieldRangeSetPair( ns(), query ) ); - shared_ptr<QueryPlanSet> s = makeQps( query, order, hint ); - scoped_ptr<QueryPlan> qp( QueryPlan::make( nsd(), 1, s->frsp(), frsp.get(), - query, order ) ); - boost::shared_ptr<Cursor> c = qp->newCursor(); - double expected[] = { 9, 6, 3, 2 }; - for( int i = 0; i < 4; ++i, c->advance() ) { - ASSERT_EQUALS( expected[ i ], c->current().getField( "a" ).number() ); - } - ASSERT( !c->ok() ); - } - } - }; - - class EqualityThenIn : public Base { - public: - void run() { - Helpers::ensureIndex( ns(), BSON( "a" << 1 << "b" << 1 ), false, "a_1_b_1" ); - for( int i = 0; i < 10; ++i ) { - BSONObj temp = BSON( "a" << 5 << "b" << i ); - theDataFileMgr.insertWithObjMod( ns(), temp ); - } - auto_ptr< FieldRangeSetPair > frsp( new FieldRangeSetPair( ns(), fromjson( "{a:5,b:{$in:[2,3,6,9,11]}}" ) ) ); - scoped_ptr<QueryPlan> qp( QueryPlan::make( nsd(), 1, *frsp, frsp.get(), - fromjson( "{a:5,b:{$in:[2,3,6,9,11]}}" ), - BSONObj() ) ); - boost::shared_ptr<Cursor> c = qp->newCursor(); - double expected[] = { 2, 3, 6, 9 }; - ASSERT( c->ok() ); - for( int i = 0; i < 4; ++i, c->advance() ) { - ASSERT( c->ok() ); - ASSERT_EQUALS( expected[ i ], c->current().getField( "b" ).number() ); - } - ASSERT( !c->ok() ); - } - }; - - class NotEqualityThenIn : public Base { - public: - void run() { - Helpers::ensureIndex( ns(), BSON( "a" << 1 << "b" << 1 ), false, "a_1_b_1" ); - for( int i = 0; i < 10; ++i ) { - BSONObj temp = BSON( "a" << 5 << "b" << i ); - theDataFileMgr.insertWithObjMod( ns(), temp ); - } - auto_ptr< FieldRangeSetPair > frsp( new FieldRangeSetPair( ns(), fromjson( "{a:{$gte:5},b:{$in:[2,3,6,9,11]}}" ) ) ); - scoped_ptr<QueryPlan> qp - ( QueryPlan::make( nsd(), 1, *frsp, frsp.get(), - fromjson( "{a:{$gte:5},b:{$in:[2,3,6,9,11]}}" ), - BSONObj() ) ); - boost::shared_ptr<Cursor> c = qp->newCursor(); - int matches[] = { 2, 3, 6, 9 }; - for( int i = 0; i < 4; ++i, c->advance() ) { - ASSERT_EQUALS( matches[ i ], c->current().getField( "b" ).number() ); - } - ASSERT( !c->ok() ); - } - }; - - /** Exclude special plan candidate if there are btree plan candidates. SERVER-4531 */ - class ExcludeSpecialPlanWhenBtreePlan : public Base { - public: - void run() { - Helpers::ensureIndex( ns(), BSON( "a" << "2d" ), false, "a_2d" ); - Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); - shared_ptr<QueryPlanSet> s = - makeQps( BSON( "a" << BSON_ARRAY( 0 << 0 ) << "b" << 1 ) ); - // Two query plans, btree and collection scan. - ASSERT_EQUALS( 2, s->nPlans() ); - // Not the geo plan. - ASSERT( s->firstPlan()->special().empty() ); - } - }; - - /** Exclude unindexed plan candidate if there is a special plan candidate. SERVER-4531 */ - class ExcludeUnindexedPlanWhenSpecialPlan : public Base { - public: - void run() { - Helpers::ensureIndex( ns(), BSON( "a" << "2d" ), false, "a_2d" ); - shared_ptr<QueryPlanSet> s = - makeQps( BSON( "a" << BSON_ARRAY( 0 << 0 ) << "b" << 1 ) ); - // Single query plan. - ASSERT_EQUALS( 1, s->nPlans() ); - // It's the geo plan. - ASSERT( !s->firstPlan()->special().empty() ); - } - }; - - class PossiblePlans : public Base { - public: - void run() { - client().ensureIndex( ns(), BSON( "a" << 1 ) ); - client().ensureIndex( ns(), BSON( "b" << 1 ) ); - - { - shared_ptr<QueryPlanSet> qps = makeQps( BSON( "a" << 1 ), BSONObj() ); - ASSERT_EQUALS( 1, qps->nPlans() ); - ASSERT( qps->possibleInOrderPlan() ); - ASSERT( qps->haveInOrderPlan() ); - ASSERT( !qps->possibleOutOfOrderPlan() ); - ASSERT( !qps->hasPossiblyExcludedPlans() ); - ASSERT( !qps->usingCachedPlan() ); - } - - { - shared_ptr<QueryPlanSet> qps = makeQps( BSON( "a" << 1 ), BSON( "b" << 1 ) ); - ASSERT_EQUALS( 3, qps->nPlans() ); - ASSERT( qps->possibleInOrderPlan() ); - ASSERT( qps->haveInOrderPlan() ); - ASSERT( qps->possibleOutOfOrderPlan() ); - ASSERT( !qps->hasPossiblyExcludedPlans() ); - ASSERT( !qps->usingCachedPlan() ); - } - - NamespaceDetailsTransient &nsdt = NamespaceDetailsTransient::get( ns() ); - - nsdt.registerCachedQueryPlanForPattern( makePattern( BSON( "a" << 1 ), BSONObj() ), - CachedQueryPlan( BSON( "a" << 1 ), 1, - CandidatePlanCharacter( true, false ) ) ); - { - shared_ptr<QueryPlanSet> qps = makeQps( BSON( "a" << 1 ), BSONObj() ); - ASSERT_EQUALS( 1, qps->nPlans() ); - ASSERT( qps->possibleInOrderPlan() ); - ASSERT( qps->haveInOrderPlan() ); - ASSERT( !qps->possibleOutOfOrderPlan() ); - ASSERT( !qps->hasPossiblyExcludedPlans() ); - ASSERT( qps->usingCachedPlan() ); - } - - nsdt.registerCachedQueryPlanForPattern - ( makePattern( BSON( "a" << 1 ), BSON( "b" << 1 ) ), - CachedQueryPlan( BSON( "a" << 1 ), 1, - CandidatePlanCharacter( true, true ) ) ); - - { - shared_ptr<QueryPlanSet> qps = makeQps( BSON( "a" << 1 ), BSON( "b" << 1 ) ); - ASSERT_EQUALS( 1, qps->nPlans() ); - ASSERT( qps->possibleInOrderPlan() ); - ASSERT( !qps->haveInOrderPlan() ); - ASSERT( qps->possibleOutOfOrderPlan() ); - ASSERT( qps->hasPossiblyExcludedPlans() ); - ASSERT( qps->usingCachedPlan() ); - } - - nsdt.registerCachedQueryPlanForPattern - ( makePattern( BSON( "a" << 1 ), BSON( "b" << 1 ) ), - CachedQueryPlan( BSON( "b" << 1 ), 1, - CandidatePlanCharacter( true, true ) ) ); - - { - shared_ptr<QueryPlanSet> qps = makeQps( BSON( "a" << 1 ), BSON( "b" << 1 ) ); - ASSERT_EQUALS( 1, qps->nPlans() ); - ASSERT( qps->possibleInOrderPlan() ); - ASSERT( qps->haveInOrderPlan() ); - ASSERT( qps->possibleOutOfOrderPlan() ); - ASSERT( qps->hasPossiblyExcludedPlans() ); - ASSERT( qps->usingCachedPlan() ); - } - - { - shared_ptr<QueryPlanSet> qps = makeQps( BSON( "a" << 1 ), BSON( "c" << 1 ) ); - ASSERT_EQUALS( 2, qps->nPlans() ); - ASSERT( !qps->possibleInOrderPlan() ); - ASSERT( !qps->haveInOrderPlan() ); - ASSERT( qps->possibleOutOfOrderPlan() ); - ASSERT( !qps->hasPossiblyExcludedPlans() ); - ASSERT( !qps->usingCachedPlan() ); - } - } - }; - - /** An unhelpful query plan will not be used if recorded in the query plan cache. */ - class AvoidUnhelpfulRecordedPlan : public Base { - public: - void run() { - client().ensureIndex( ns(), BSON( "a" << 1 ) ); - - // Record the {a:1} index for a {b:1} query. - NamespaceDetailsTransient &nsdt = NamespaceDetailsTransient::get( ns() ); - nsdt.registerCachedQueryPlanForPattern - ( makePattern( BSON( "b" << 1 ), BSONObj() ), - CachedQueryPlan( BSON( "a" << 1 ), 1, - CandidatePlanCharacter( true, false ) ) ); - - // The {a:1} index is not used for a {b:1} query because it generates an unhelpful - // plan. - shared_ptr<QueryPlanSet> qps = makeQps( BSON( "b" << 1 ), BSONObj() ); - ASSERT_EQUALS( 1, qps->nPlans() ); - ASSERT_EQUALS( BSON( "$natural" << 1 ), qps->firstPlan()->indexKey() ); - } - }; - - /** An unhelpful query plan will not be used if recorded in the query plan cache. */ - class AvoidDisallowedRecordedPlan : public Base { - public: - void run() { - client().insert( "unittests.system.indexes", - BSON( "ns" << ns() << - "key" << BSON( "a" << 1 ) << - "name" << client().genIndexName( BSON( "a" << 1 ) ) << - "sparse" << true ) ); - - // Record the {a:1} index for a {a:null} query. - NamespaceDetailsTransient &nsdt = NamespaceDetailsTransient::get( ns() ); - nsdt.registerCachedQueryPlanForPattern - ( makePattern( BSON( "a" << BSONNULL ), BSONObj() ), - CachedQueryPlan( BSON( "a" << 1 ), 1, - CandidatePlanCharacter( true, false ) ) ); - - // The {a:1} index is not used for an {a:{$exists:false}} query because it generates - // a disallowed plan. - shared_ptr<QueryPlanSet> qps = makeQps( BSON( "a" << BSON( "$exists" << false ) ), - BSONObj() ); - ASSERT_EQUALS( 1, qps->nPlans() ); - ASSERT_EQUALS( BSON( "$natural" << 1 ), qps->firstPlan()->indexKey() ); - } - }; - - /** Special plans are only selected when allowed. */ - class AllowSpecial : public Base { - public: - void run() { - BSONObj naturalIndex = BSON( "$natural" << 1 ); - BSONObj specialIndex = BSON( "a" << "2d" ); - BSONObj query = BSON( "a" << BSON_ARRAY( 0 << 0 ) ); - client().ensureIndex( ns(), specialIndex ); - - // The special plan is chosen if allowed. - assertSingleIndex( specialIndex, makeQps( query ) ); - - // The special plan is not chosen if not allowed - assertSingleIndex( naturalIndex, makeQps( query, BSONObj(), BSONObj(), false ) ); - - // Attempting to hint a special plan when not allowed triggers an assertion. - ASSERT_THROWS( makeQps( query, BSONObj(), BSON( "$hint" << specialIndex ), false ), - UserException ); - - // Attempting to use a geo operator when special plans are not allowed triggers an - // assertion. - ASSERT_THROWS( makeQps( BSON( "a" << BSON( "$near" << BSON_ARRAY( 0 << 0 ) ) ), - BSONObj(), BSONObj(), false ), - UserException ); - - // The special plan is not chosen if not allowed, even if cached. - NamespaceDetailsTransient &nsdt = NamespaceDetailsTransient::get( ns() ); - nsdt.registerCachedQueryPlanForPattern - ( makePattern( query, BSONObj() ), - CachedQueryPlan( specialIndex, 1, - CandidatePlanCharacter( true, false ) ) ); - assertSingleIndex( naturalIndex, makeQps( query, BSONObj(), BSONObj(), false ) ); - } - private: - void assertSingleIndex( const BSONObj& index, const shared_ptr<QueryPlanSet>& set ) { - ASSERT_EQUALS( 1, set->nPlans() ); - ASSERT_EQUALS( index, set->firstPlan()->indexKey() ); - } - }; - - } // namespace QueryPlanSetTests - - class Base { - public: - Base() : _ctx( ns() ) { - string err; - userCreateNS( ns(), BSONObj(), err, false ); - } - ~Base() { - if ( !nsd() ) - return; - string s( ns() ); - dropCollection( ns() ); - } - protected: - static const char *ns() { return "unittests.QueryOptimizerTests"; } - static NamespaceDetails *nsd() { return nsdetails( ns() ); } - QueryPattern makePattern( const BSONObj &query, const BSONObj &order ) { - FieldRangeSet frs( ns(), query, true, true ); - return QueryPattern( frs, order ); - } - shared_ptr<MultiPlanScanner> makeMps( const BSONObj &query, const BSONObj &order ) { - shared_ptr<MultiPlanScanner> ret( MultiPlanScanner::make( ns(), query, order ) ); - return ret; - } - DBDirectClient &client() { return _client; } - private: - Lock::GlobalWrite lk_; - Client::Context _ctx; - DBDirectClient _client; - }; - - namespace MultiPlanScannerTests { - class ToString : public Base { - public: - void run() { - scoped_ptr<MultiPlanScanner> multiPlanScanner - ( MultiPlanScanner::make( ns(), BSON( "a" << 1 ), BSONObj() ) ); - multiPlanScanner->toString(); // Just test that we don't crash. - } - }; - - class PossiblePlans : public Base { - public: - void run() { - client().ensureIndex( ns(), BSON( "a" << 1 ) ); - client().ensureIndex( ns(), BSON( "b" << 1 ) ); - - { - shared_ptr<MultiPlanScanner> mps = makeMps( BSON( "a" << 1 ), BSONObj() ); - ASSERT_EQUALS( 1, mps->currentNPlans() ); - ASSERT( mps->possibleInOrderPlan() ); - ASSERT( mps->haveInOrderPlan() ); - ASSERT( !mps->possibleOutOfOrderPlan() ); - ASSERT( !mps->hasPossiblyExcludedPlans() ); - } - - { - shared_ptr<MultiPlanScanner> mps = - makeMps( BSON( "a" << 1 ), BSON( "b" << 1 ) ); - ASSERT_EQUALS( 3, mps->currentNPlans() ); - ASSERT( mps->possibleInOrderPlan() ); - ASSERT( mps->haveInOrderPlan() ); - ASSERT( mps->possibleOutOfOrderPlan() ); - ASSERT( !mps->hasPossiblyExcludedPlans() ); - } - - NamespaceDetailsTransient &nsdt = NamespaceDetailsTransient::get( ns() ); - - nsdt.registerCachedQueryPlanForPattern( makePattern( BSON( "a" << 1 ), BSONObj() ), - CachedQueryPlan( BSON( "a" << 1 ), 1, - CandidatePlanCharacter( true, false ) ) ); - { - shared_ptr<MultiPlanScanner> mps = makeMps( BSON( "a" << 1 ), BSONObj() ); - ASSERT_EQUALS( 1, mps->currentNPlans() ); - ASSERT( mps->possibleInOrderPlan() ); - ASSERT( mps->haveInOrderPlan() ); - ASSERT( !mps->possibleOutOfOrderPlan() ); - ASSERT( !mps->hasPossiblyExcludedPlans() ); - } - - nsdt.registerCachedQueryPlanForPattern - ( makePattern( BSON( "a" << 1 ), BSON( "b" << 1 ) ), - CachedQueryPlan( BSON( "a" << 1 ), 1, - CandidatePlanCharacter( true, true ) ) ); - - { - shared_ptr<MultiPlanScanner> mps = - makeMps( BSON( "a" << 1 ), BSON( "b" << 1 ) ); - ASSERT_EQUALS( 1, mps->currentNPlans() ); - ASSERT( mps->possibleInOrderPlan() ); - ASSERT( !mps->haveInOrderPlan() ); - ASSERT( mps->possibleOutOfOrderPlan() ); - ASSERT( mps->hasPossiblyExcludedPlans() ); - } - - nsdt.registerCachedQueryPlanForPattern - ( makePattern( BSON( "a" << 1 ), BSON( "b" << 1 ) ), - CachedQueryPlan( BSON( "b" << 1 ), 1, - CandidatePlanCharacter( true, true ) ) ); - - { - shared_ptr<MultiPlanScanner> mps = - makeMps( BSON( "a" << 1 ), BSON( "b" << 1 ) ); - ASSERT_EQUALS( 1, mps->currentNPlans() ); - ASSERT( mps->possibleInOrderPlan() ); - ASSERT( mps->haveInOrderPlan() ); - ASSERT( mps->possibleOutOfOrderPlan() ); - ASSERT( mps->hasPossiblyExcludedPlans() ); - } - - { - shared_ptr<MultiPlanScanner> mps = - makeMps( BSON( "a" << 1 ), BSON( "c" << 1 ) ); - ASSERT_EQUALS( 2, mps->currentNPlans() ); - ASSERT( !mps->possibleInOrderPlan() ); - ASSERT( !mps->haveInOrderPlan() ); - ASSERT( mps->possibleOutOfOrderPlan() ); - ASSERT( !mps->hasPossiblyExcludedPlans() ); - } - - { - shared_ptr<MultiPlanScanner> mps = - makeMps( fromjson( "{$or:[{a:1},{a:2}]}" ), BSON( "c" << 1 ) ); - ASSERT_EQUALS( 1, mps->currentNPlans() ); - ASSERT( !mps->possibleInOrderPlan() ); - ASSERT( !mps->haveInOrderPlan() ); - ASSERT( mps->possibleOutOfOrderPlan() ); - ASSERT( !mps->hasPossiblyExcludedPlans() ); - } - - { - shared_ptr<MultiPlanScanner> mps = - makeMps( fromjson( "{$or:[{a:1,b:1},{a:2,b:2}]}" ), BSONObj() ); - ASSERT_EQUALS( 3, mps->currentNPlans() ); - ASSERT( mps->possibleInOrderPlan() ); - ASSERT( mps->haveInOrderPlan() ); - ASSERT( !mps->possibleOutOfOrderPlan() ); - ASSERT( !mps->hasPossiblyExcludedPlans() ); - } - } - }; - - } // namespace MultiPlanScannerTests - - class BestGuess : public Base { - public: - void run() { - Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); - Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" ); - BSONObj temp = BSON( "a" << 1 ); - theDataFileMgr.insertWithObjMod( ns(), temp ); - temp = BSON( "b" << 1 ); - theDataFileMgr.insertWithObjMod( ns(), temp ); - - boost::shared_ptr< Cursor > c = - NamespaceDetailsTransient::bestGuessCursor( ns(), BSON( "b" << 1 ), BSON( "a" << 1 ) ); - ASSERT_EQUALS( string( "a" ), c->indexKeyPattern().firstElement().fieldName() ); - - c = NamespaceDetailsTransient::bestGuessCursor( ns(), BSON( "a" << 1 ), - BSON( "b" << 1 ) ); - ASSERT_EQUALS( string( "b" ), c->indexKeyPattern().firstElementFieldName() ); - ASSERT( c->matcher() ); - ASSERT( c->currentMatches() ); // { b:1 } document - c->advance(); - ASSERT( !c->currentMatches() ); // { a:1 } document - - c = NamespaceDetailsTransient::bestGuessCursor( ns(), fromjson( "{b:1,$or:[{z:1}]}" ), - BSON( "a" << 1 ) ); - ASSERT_EQUALS( string( "a" ), c->indexKeyPattern().firstElement().fieldName() ); - - c = NamespaceDetailsTransient::bestGuessCursor( ns(), fromjson( "{a:1,$or:[{y:1}]}" ), - BSON( "b" << 1 ) ); - ASSERT_EQUALS( string( "b" ), c->indexKeyPattern().firstElementFieldName() ); - - FieldRangeSet frs( "ns", BSON( "a" << 1 ), true, true ); - { - SimpleMutex::scoped_lock lk(NamespaceDetailsTransient::_qcMutex); - NamespaceDetailsTransient::get_inlock( ns() ). - registerCachedQueryPlanForPattern( frs.pattern( BSON( "b" << 1 ) ), - CachedQueryPlan( BSON( "a" << 1 ), 0, - CandidatePlanCharacter( true, true ) ) ); - } - - c = NamespaceDetailsTransient::bestGuessCursor( ns(), fromjson( "{a:1,$or:[{y:1}]}" ), - BSON( "b" << 1 ) ); - ASSERT_EQUALS( string( "b" ), - c->indexKeyPattern().firstElement().fieldName() ); - } - }; class All : public Suite { public: @@ -1633,35 +882,6 @@ namespace QueryOptimizerTests { add<QueryPlanTests::QueryBoundsExactOrderSuffix::Unsatisfiable>(); add<QueryPlanTests::QueryBoundsExactOrderSuffix::EqualityUnsatisfiable>(); add<QueryPlanTests::Special>(); - add<QueryPlanSetTests::ToString>(); - add<QueryPlanSetTests::NoIndexes>(); - add<QueryPlanSetTests::Optimal>(); - add<QueryPlanSetTests::NoOptimal>(); - add<QueryPlanSetTests::NoSpec>(); - add<QueryPlanSetTests::HintSpec>(); - add<QueryPlanSetTests::HintName>(); - add<QueryPlanSetTests::NaturalHint>(); - add<QueryPlanSetTests::NaturalSort>(); - add<QueryPlanSetTests::BadHint>(); - add<QueryPlanSetTests::Count>(); - add<QueryPlanSetTests::QueryMissingNs>(); - add<QueryPlanSetTests::UnhelpfulIndex>(); - add<QueryPlanSetTests::FindOne>(); - add<QueryPlanSetTests::Delete>(); - add<QueryPlanSetTests::DeleteOneScan>(); - add<QueryPlanSetTests::DeleteOneIndex>(); - add<QueryPlanSetTests::InQueryIntervals>(); - add<QueryPlanSetTests::EqualityThenIn>(); - add<QueryPlanSetTests::NotEqualityThenIn>(); - add<QueryPlanSetTests::ExcludeSpecialPlanWhenBtreePlan>(); - add<QueryPlanSetTests::ExcludeUnindexedPlanWhenSpecialPlan>(); - add<QueryPlanSetTests::PossiblePlans>(); - add<QueryPlanSetTests::AvoidUnhelpfulRecordedPlan>(); - add<QueryPlanSetTests::AvoidDisallowedRecordedPlan>(); - add<QueryPlanSetTests::AllowSpecial>(); - add<MultiPlanScannerTests::ToString>(); - add<MultiPlanScannerTests::PossiblePlans>(); - add<BestGuess>(); } } myall; diff --git a/src/mongo/dbtests/queryoptimizertests2.cpp b/src/mongo/dbtests/queryoptimizertests2.cpp new file mode 100644 index 00000000000..2323f9fbcc9 --- /dev/null +++ b/src/mongo/dbtests/queryoptimizertests2.cpp @@ -0,0 +1,831 @@ +/** + * Copyright (C) 2009 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 <http://www.gnu.org/licenses/>. + */ + +#include "mongo/pch.h" + +#include "mongo/db/instance.h" +#include "mongo/db/json.h" +#include "mongo/db/ops/count.h" +#include "mongo/db/ops/delete.h" +#include "mongo/db/ops/query.h" +#include "mongo/db/queryoptimizer.h" +#include "mongo/dbtests/dbtests.h" + + +namespace mongo { + extern void runQuery(Message& m, QueryMessage& q, Message &response ); + extern void __forceLinkGeoPlugin(); +} // namespace mongo + +namespace { + + using boost::shared_ptr; + + void dropCollection( const char *ns ) { + string errmsg; + BSONObjBuilder result; + dropCollection( ns, errmsg, result ); + } + + namespace QueryPlanSetTests { + + class Base { + public: + Base() : _context( ns() ) { + string err; + userCreateNS( ns(), BSONObj(), err, false ); + } + virtual ~Base() { + if ( !nsd() ) + return; + NamespaceDetailsTransient::get_inlock( ns() ).clearQueryCache(); + dropCollection( ns() ); + } + protected: + static void assembleRequest( const string &ns, BSONObj query, int nToReturn, int nToSkip, BSONObj *fieldsToReturn, int queryOptions, Message &toSend ) { + // see query.h for the protocol we are using here. + BufBuilder b; + int opts = queryOptions; + b.appendNum(opts); + b.appendStr(ns); + b.appendNum(nToSkip); + b.appendNum(nToReturn); + query.appendSelfToBufBuilder(b); + if ( fieldsToReturn ) + fieldsToReturn->appendSelfToBufBuilder(b); + toSend.setData(dbQuery, b.buf(), b.len()); + } + QueryPattern makePattern( const BSONObj &query, const BSONObj &order ) { + FieldRangeSet frs( ns(), query, true, true ); + return QueryPattern( frs, order ); + } + shared_ptr<QueryPlanSet> makeQps( const BSONObj& query = BSONObj(), + const BSONObj& order = BSONObj(), + const BSONObj& hint = BSONObj(), + bool allowSpecial = true ) { + auto_ptr<FieldRangeSetPair> frsp( new FieldRangeSetPair( ns(), query ) ); + auto_ptr<FieldRangeSetPair> frspOrig( new FieldRangeSetPair( *frsp ) ); + return shared_ptr<QueryPlanSet> + ( QueryPlanSet::make( ns(), frsp, frspOrig, query, order, + shared_ptr<const ParsedQuery>(), hint, + QueryPlanGenerator::Use, BSONObj(), BSONObj(), + allowSpecial ) ); + } + static const char *ns() { return "unittests.QueryPlanSetTests"; } + static NamespaceDetails *nsd() { return nsdetails( ns() ); } + DBDirectClient &client() { return _client; } + private: + Lock::GlobalWrite lk_; + Client::Context _context; + DBDirectClient _client; + }; + + class ToString : public Base { + public: + void run() { + // Just test that we don't crash. + makeQps( BSON( "a" << 1 ) )->toString(); + } + }; + + class NoIndexes : public Base { + public: + void run() { + ASSERT_EQUALS( 1, makeQps( BSON( "a" << 4 ), BSON( "b" << 1 ) )->nPlans() ); + } + }; + + class Optimal : public Base { + public: + void run() { + Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); + Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "b_2" ); + BSONObj query = BSON( "a" << 4 ); + + // Only one optimal plan is added to the plan set. + ASSERT_EQUALS( 1, makeQps( query )->nPlans() ); + + // The optimal plan is recorded in the plan cache. + FieldRangeSet frs( ns(), query, true, true ); + CachedQueryPlan cachedPlan = + NamespaceDetailsTransient::get( ns() ).cachedQueryPlanForPattern + ( QueryPattern( frs, BSONObj() ) ); + ASSERT_EQUALS( BSON( "a" << 1 ), cachedPlan.indexKey() ); + CandidatePlanCharacter planCharacter = cachedPlan.planCharacter(); + ASSERT( planCharacter.mayRunInOrderPlan() ); + ASSERT( !planCharacter.mayRunOutOfOrderPlan() ); + } + }; + + class NoOptimal : public Base { + public: + void run() { + Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); + Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" ); + ASSERT_EQUALS( 3, makeQps( BSON( "a" << 4 ), BSON( "b" << 1 ) )->nPlans() ); + } + }; + + class NoSpec : public Base { + public: + void run() { + Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); + Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" ); + ASSERT_EQUALS( 1, makeQps()->nPlans() ); + } + }; + + class HintSpec : public Base { + public: + void run() { + Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); + Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" ); + ASSERT_EQUALS( 1, makeQps( BSON( "a" << 1 ), BSON( "b" << 1 ), + BSON( "hint" << BSON( "a" << 1 ) ) )->nPlans() ); + } + }; + + class HintName : public Base { + public: + void run() { + Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); + Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" ); + ASSERT_EQUALS( 1, makeQps( BSON( "a" << 1 ), BSON( "b" << 1 ), + BSON( "hint" << "a_1" ) )->nPlans() ); + } + }; + + class NaturalHint : public Base { + public: + void run() { + Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); + Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" ); + ASSERT_EQUALS( 1, makeQps( BSON( "a" << 1 ), BSON( "b" << 1 ), + BSON( "hint" << BSON( "$natural" << 1 ) ) )->nPlans() ); + } + }; + + class NaturalSort : public Base { + public: + void run() { + Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); + Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "b_2" ); + ASSERT_EQUALS( 1, makeQps( BSON( "a" << 1 ), BSON( "$natural" << 1 ) )->nPlans() ); + } + }; + + class BadHint : public Base { + public: + void run() { + ASSERT_THROWS( makeQps( BSON( "a" << 1 ), BSON( "b" << 1 ), + BSON( "hint" << "a_1" ) ), + AssertionException ); + } + }; + + class Count : public Base { + public: + void run() { + Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); + Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" ); + string err; + int errCode; + ASSERT_EQUALS( 0, runCount( ns(), BSON( "query" << BSON( "a" << 4 ) ), err, errCode ) ); + BSONObj one = BSON( "a" << 1 ); + BSONObj fourA = BSON( "a" << 4 ); + BSONObj fourB = BSON( "a" << 4 ); + theDataFileMgr.insertWithObjMod( ns(), one ); + ASSERT_EQUALS( 0, runCount( ns(), BSON( "query" << BSON( "a" << 4 ) ), err, errCode ) ); + theDataFileMgr.insertWithObjMod( ns(), fourA ); + ASSERT_EQUALS( 1, runCount( ns(), BSON( "query" << BSON( "a" << 4 ) ), err, errCode ) ); + theDataFileMgr.insertWithObjMod( ns(), fourB ); + ASSERT_EQUALS( 2, runCount( ns(), BSON( "query" << BSON( "a" << 4 ) ), err, errCode ) ); + ASSERT_EQUALS( 3, runCount( ns(), BSON( "query" << BSONObj() ), err, errCode ) ); + ASSERT_EQUALS( 3, runCount( ns(), BSON( "query" << BSON( "a" << GT << 0 ) ), err, errCode ) ); + // missing ns + ASSERT_EQUALS( -1, runCount( "unittests.missingNS", BSONObj(), err, errCode ) ); + // impossible match + ASSERT_EQUALS( 0, runCount( ns(), BSON( "query" << BSON( "a" << GT << 0 << LT << -1 ) ), err, errCode ) ); + } + }; + + class QueryMissingNs : public Base { + public: + QueryMissingNs() { log() << "querymissingns starts" << endl; } + ~QueryMissingNs() { + log() << "end QueryMissingNs" << endl; + } + void run() { + Message m; + assembleRequest( "unittests.missingNS", BSONObj(), 0, 0, 0, 0, m ); + DbMessage d(m); + QueryMessage q(d); + Message ret; + runQuery( m, q, ret ); + ASSERT_EQUALS( 0, ((QueryResult*)ret.header())->nReturned ); + } + + }; + + class UnhelpfulIndex : public Base { + public: + void run() { + Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); + Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" ); + ASSERT_EQUALS( 2, makeQps( BSON( "a" << 1 << "c" << 2 ) )->nPlans() ); + } + }; + + class FindOne : public Base { + public: + void run() { + BSONObj one = BSON( "a" << 1 ); + theDataFileMgr.insertWithObjMod( ns(), one ); + BSONObj result; + ASSERT( Helpers::findOne( ns(), BSON( "a" << 1 ), result ) ); + ASSERT_THROWS( Helpers::findOne( ns(), BSON( "a" << 1 ), result, true ), AssertionException ); + Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); + ASSERT( Helpers::findOne( ns(), BSON( "a" << 1 ), result, true ) ); + } + }; + + class Delete : public Base { + public: + void run() { + Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); + for( int i = 0; i < 200; ++i ) { + BSONObj two = BSON( "a" << 2 ); + theDataFileMgr.insertWithObjMod( ns(), two ); + } + BSONObj one = BSON( "a" << 1 ); + theDataFileMgr.insertWithObjMod( ns(), one ); + BSONObj delSpec = BSON( "a" << 1 << "_id" << NE << 0 ); + deleteObjects( ns(), delSpec, false ); + + NamespaceDetailsTransient &nsdt = NamespaceDetailsTransient::get( ns() ); + QueryPattern queryPattern = FieldRangeSet( ns(), delSpec, true, true ).pattern(); + CachedQueryPlan cachedQueryPlan = nsdt.cachedQueryPlanForPattern( queryPattern ); + ASSERT_EQUALS( BSON( "a" << 1 ), cachedQueryPlan.indexKey() ); + ASSERT_EQUALS( 1, cachedQueryPlan.nScanned() ); + } + }; + + class DeleteOneScan : public Base { + public: + void run() { + Helpers::ensureIndex( ns(), BSON( "_id" << 1 ), false, "_id_1" ); + BSONObj one = BSON( "_id" << 3 << "a" << 1 ); + BSONObj two = BSON( "_id" << 2 << "a" << 1 ); + BSONObj three = BSON( "_id" << 1 << "a" << -1 ); + theDataFileMgr.insertWithObjMod( ns(), one ); + theDataFileMgr.insertWithObjMod( ns(), two ); + theDataFileMgr.insertWithObjMod( ns(), three ); + deleteObjects( ns(), BSON( "_id" << GT << 0 << "a" << GT << 0 ), true ); + for( boost::shared_ptr<Cursor> c = theDataFileMgr.findAll( ns() ); c->ok(); c->advance() ) + ASSERT( 3 != c->current().getIntField( "_id" ) ); + } + }; + + class DeleteOneIndex : public Base { + public: + void run() { + Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a" ); + BSONObj one = BSON( "a" << 2 << "_id" << 0 ); + BSONObj two = BSON( "a" << 1 << "_id" << 1 ); + BSONObj three = BSON( "a" << 0 << "_id" << 2 ); + theDataFileMgr.insertWithObjMod( ns(), one ); + theDataFileMgr.insertWithObjMod( ns(), two ); + theDataFileMgr.insertWithObjMod( ns(), three ); + deleteObjects( ns(), BSON( "a" << GTE << 0 ), true ); + for( boost::shared_ptr<Cursor> c = theDataFileMgr.findAll( ns() ); c->ok(); c->advance() ) + ASSERT( 2 != c->current().getIntField( "_id" ) ); + } + }; + + class InQueryIntervals : public Base { + public: + void run() { + Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); + for( int i = 0; i < 10; ++i ) { + BSONObj temp = BSON( "a" << i ); + theDataFileMgr.insertWithObjMod( ns(), temp ); + } + BSONObj query = fromjson( "{a:{$in:[2,3,6,9,11]}}" ); + BSONObj order; + BSONObj hint = fromjson( "{$hint:{a:1}}" ); + auto_ptr< FieldRangeSetPair > frsp( new FieldRangeSetPair( ns(), query ) ); + shared_ptr<QueryPlanSet> s = makeQps( query, order, hint ); + scoped_ptr<QueryPlan> qp( QueryPlan::make( nsd(), 1, s->frsp(), frsp.get(), + query, order ) ); + boost::shared_ptr<Cursor> c = qp->newCursor(); + double expected[] = { 2, 3, 6, 9 }; + for( int i = 0; i < 4; ++i, c->advance() ) { + ASSERT_EQUALS( expected[ i ], c->current().getField( "a" ).number() ); + } + ASSERT( !c->ok() ); + + // now check reverse + { + order = BSON( "a" << -1 ); + auto_ptr< FieldRangeSetPair > frsp( new FieldRangeSetPair( ns(), query ) ); + shared_ptr<QueryPlanSet> s = makeQps( query, order, hint ); + scoped_ptr<QueryPlan> qp( QueryPlan::make( nsd(), 1, s->frsp(), frsp.get(), + query, order ) ); + boost::shared_ptr<Cursor> c = qp->newCursor(); + double expected[] = { 9, 6, 3, 2 }; + for( int i = 0; i < 4; ++i, c->advance() ) { + ASSERT_EQUALS( expected[ i ], c->current().getField( "a" ).number() ); + } + ASSERT( !c->ok() ); + } + } + }; + + class EqualityThenIn : public Base { + public: + void run() { + Helpers::ensureIndex( ns(), BSON( "a" << 1 << "b" << 1 ), false, "a_1_b_1" ); + for( int i = 0; i < 10; ++i ) { + BSONObj temp = BSON( "a" << 5 << "b" << i ); + theDataFileMgr.insertWithObjMod( ns(), temp ); + } + auto_ptr< FieldRangeSetPair > frsp( new FieldRangeSetPair( ns(), fromjson( "{a:5,b:{$in:[2,3,6,9,11]}}" ) ) ); + scoped_ptr<QueryPlan> qp( QueryPlan::make( nsd(), 1, *frsp, frsp.get(), + fromjson( "{a:5,b:{$in:[2,3,6,9,11]}}" ), + BSONObj() ) ); + boost::shared_ptr<Cursor> c = qp->newCursor(); + double expected[] = { 2, 3, 6, 9 }; + ASSERT( c->ok() ); + for( int i = 0; i < 4; ++i, c->advance() ) { + ASSERT( c->ok() ); + ASSERT_EQUALS( expected[ i ], c->current().getField( "b" ).number() ); + } + ASSERT( !c->ok() ); + } + }; + + class NotEqualityThenIn : public Base { + public: + void run() { + Helpers::ensureIndex( ns(), BSON( "a" << 1 << "b" << 1 ), false, "a_1_b_1" ); + for( int i = 0; i < 10; ++i ) { + BSONObj temp = BSON( "a" << 5 << "b" << i ); + theDataFileMgr.insertWithObjMod( ns(), temp ); + } + auto_ptr< FieldRangeSetPair > frsp( new FieldRangeSetPair( ns(), fromjson( "{a:{$gte:5},b:{$in:[2,3,6,9,11]}}" ) ) ); + scoped_ptr<QueryPlan> qp + ( QueryPlan::make( nsd(), 1, *frsp, frsp.get(), + fromjson( "{a:{$gte:5},b:{$in:[2,3,6,9,11]}}" ), + BSONObj() ) ); + boost::shared_ptr<Cursor> c = qp->newCursor(); + int matches[] = { 2, 3, 6, 9 }; + for( int i = 0; i < 4; ++i, c->advance() ) { + ASSERT_EQUALS( matches[ i ], c->current().getField( "b" ).number() ); + } + ASSERT( !c->ok() ); + } + }; + + /** Exclude special plan candidate if there are btree plan candidates. SERVER-4531 */ + class ExcludeSpecialPlanWhenBtreePlan : public Base { + public: + void run() { + Helpers::ensureIndex( ns(), BSON( "a" << "2d" ), false, "a_2d" ); + Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); + shared_ptr<QueryPlanSet> s = + makeQps( BSON( "a" << BSON_ARRAY( 0 << 0 ) << "b" << 1 ) ); + // Two query plans, btree and collection scan. + ASSERT_EQUALS( 2, s->nPlans() ); + // Not the geo plan. + ASSERT( s->firstPlan()->special().empty() ); + } + }; + + /** Exclude unindexed plan candidate if there is a special plan candidate. SERVER-4531 */ + class ExcludeUnindexedPlanWhenSpecialPlan : public Base { + public: + void run() { + Helpers::ensureIndex( ns(), BSON( "a" << "2d" ), false, "a_2d" ); + shared_ptr<QueryPlanSet> s = + makeQps( BSON( "a" << BSON_ARRAY( 0 << 0 ) << "b" << 1 ) ); + // Single query plan. + ASSERT_EQUALS( 1, s->nPlans() ); + // It's the geo plan. + ASSERT( !s->firstPlan()->special().empty() ); + } + }; + + class PossiblePlans : public Base { + public: + void run() { + client().ensureIndex( ns(), BSON( "a" << 1 ) ); + client().ensureIndex( ns(), BSON( "b" << 1 ) ); + + { + shared_ptr<QueryPlanSet> qps = makeQps( BSON( "a" << 1 ), BSONObj() ); + ASSERT_EQUALS( 1, qps->nPlans() ); + ASSERT( qps->possibleInOrderPlan() ); + ASSERT( qps->haveInOrderPlan() ); + ASSERT( !qps->possibleOutOfOrderPlan() ); + ASSERT( !qps->hasPossiblyExcludedPlans() ); + ASSERT( !qps->usingCachedPlan() ); + } + + { + shared_ptr<QueryPlanSet> qps = makeQps( BSON( "a" << 1 ), BSON( "b" << 1 ) ); + ASSERT_EQUALS( 3, qps->nPlans() ); + ASSERT( qps->possibleInOrderPlan() ); + ASSERT( qps->haveInOrderPlan() ); + ASSERT( qps->possibleOutOfOrderPlan() ); + ASSERT( !qps->hasPossiblyExcludedPlans() ); + ASSERT( !qps->usingCachedPlan() ); + } + + NamespaceDetailsTransient &nsdt = NamespaceDetailsTransient::get( ns() ); + + nsdt.registerCachedQueryPlanForPattern( makePattern( BSON( "a" << 1 ), BSONObj() ), + CachedQueryPlan( BSON( "a" << 1 ), 1, + CandidatePlanCharacter( true, false ) ) ); + { + shared_ptr<QueryPlanSet> qps = makeQps( BSON( "a" << 1 ), BSONObj() ); + ASSERT_EQUALS( 1, qps->nPlans() ); + ASSERT( qps->possibleInOrderPlan() ); + ASSERT( qps->haveInOrderPlan() ); + ASSERT( !qps->possibleOutOfOrderPlan() ); + ASSERT( !qps->hasPossiblyExcludedPlans() ); + ASSERT( qps->usingCachedPlan() ); + } + + nsdt.registerCachedQueryPlanForPattern + ( makePattern( BSON( "a" << 1 ), BSON( "b" << 1 ) ), + CachedQueryPlan( BSON( "a" << 1 ), 1, + CandidatePlanCharacter( true, true ) ) ); + + { + shared_ptr<QueryPlanSet> qps = makeQps( BSON( "a" << 1 ), BSON( "b" << 1 ) ); + ASSERT_EQUALS( 1, qps->nPlans() ); + ASSERT( qps->possibleInOrderPlan() ); + ASSERT( !qps->haveInOrderPlan() ); + ASSERT( qps->possibleOutOfOrderPlan() ); + ASSERT( qps->hasPossiblyExcludedPlans() ); + ASSERT( qps->usingCachedPlan() ); + } + + nsdt.registerCachedQueryPlanForPattern + ( makePattern( BSON( "a" << 1 ), BSON( "b" << 1 ) ), + CachedQueryPlan( BSON( "b" << 1 ), 1, + CandidatePlanCharacter( true, true ) ) ); + + { + shared_ptr<QueryPlanSet> qps = makeQps( BSON( "a" << 1 ), BSON( "b" << 1 ) ); + ASSERT_EQUALS( 1, qps->nPlans() ); + ASSERT( qps->possibleInOrderPlan() ); + ASSERT( qps->haveInOrderPlan() ); + ASSERT( qps->possibleOutOfOrderPlan() ); + ASSERT( qps->hasPossiblyExcludedPlans() ); + ASSERT( qps->usingCachedPlan() ); + } + + { + shared_ptr<QueryPlanSet> qps = makeQps( BSON( "a" << 1 ), BSON( "c" << 1 ) ); + ASSERT_EQUALS( 2, qps->nPlans() ); + ASSERT( !qps->possibleInOrderPlan() ); + ASSERT( !qps->haveInOrderPlan() ); + ASSERT( qps->possibleOutOfOrderPlan() ); + ASSERT( !qps->hasPossiblyExcludedPlans() ); + ASSERT( !qps->usingCachedPlan() ); + } + } + }; + + /** An unhelpful query plan will not be used if recorded in the query plan cache. */ + class AvoidUnhelpfulRecordedPlan : public Base { + public: + void run() { + client().ensureIndex( ns(), BSON( "a" << 1 ) ); + + // Record the {a:1} index for a {b:1} query. + NamespaceDetailsTransient &nsdt = NamespaceDetailsTransient::get( ns() ); + nsdt.registerCachedQueryPlanForPattern + ( makePattern( BSON( "b" << 1 ), BSONObj() ), + CachedQueryPlan( BSON( "a" << 1 ), 1, + CandidatePlanCharacter( true, false ) ) ); + + // The {a:1} index is not used for a {b:1} query because it generates an unhelpful + // plan. + shared_ptr<QueryPlanSet> qps = makeQps( BSON( "b" << 1 ), BSONObj() ); + ASSERT_EQUALS( 1, qps->nPlans() ); + ASSERT_EQUALS( BSON( "$natural" << 1 ), qps->firstPlan()->indexKey() ); + } + }; + + /** An unhelpful query plan will not be used if recorded in the query plan cache. */ + class AvoidDisallowedRecordedPlan : public Base { + public: + void run() { + client().insert( "unittests.system.indexes", + BSON( "ns" << ns() << + "key" << BSON( "a" << 1 ) << + "name" << client().genIndexName( BSON( "a" << 1 ) ) << + "sparse" << true ) ); + + // Record the {a:1} index for a {a:null} query. + NamespaceDetailsTransient &nsdt = NamespaceDetailsTransient::get( ns() ); + nsdt.registerCachedQueryPlanForPattern + ( makePattern( BSON( "a" << BSONNULL ), BSONObj() ), + CachedQueryPlan( BSON( "a" << 1 ), 1, + CandidatePlanCharacter( true, false ) ) ); + + // The {a:1} index is not used for an {a:{$exists:false}} query because it generates + // a disallowed plan. + shared_ptr<QueryPlanSet> qps = makeQps( BSON( "a" << BSON( "$exists" << false ) ), + BSONObj() ); + ASSERT_EQUALS( 1, qps->nPlans() ); + ASSERT_EQUALS( BSON( "$natural" << 1 ), qps->firstPlan()->indexKey() ); + } + }; + + /** Special plans are only selected when allowed. */ + class AllowSpecial : public Base { + public: + void run() { + BSONObj naturalIndex = BSON( "$natural" << 1 ); + BSONObj specialIndex = BSON( "a" << "2d" ); + BSONObj query = BSON( "a" << BSON_ARRAY( 0 << 0 ) ); + client().ensureIndex( ns(), specialIndex ); + + // The special plan is chosen if allowed. + assertSingleIndex( specialIndex, makeQps( query ) ); + + // The special plan is not chosen if not allowed + assertSingleIndex( naturalIndex, makeQps( query, BSONObj(), BSONObj(), false ) ); + + // Attempting to hint a special plan when not allowed triggers an assertion. + ASSERT_THROWS( makeQps( query, BSONObj(), BSON( "$hint" << specialIndex ), false ), + UserException ); + + // Attempting to use a geo operator when special plans are not allowed triggers an + // assertion. + ASSERT_THROWS( makeQps( BSON( "a" << BSON( "$near" << BSON_ARRAY( 0 << 0 ) ) ), + BSONObj(), BSONObj(), false ), + UserException ); + + // The special plan is not chosen if not allowed, even if cached. + NamespaceDetailsTransient &nsdt = NamespaceDetailsTransient::get( ns() ); + nsdt.registerCachedQueryPlanForPattern + ( makePattern( query, BSONObj() ), + CachedQueryPlan( specialIndex, 1, + CandidatePlanCharacter( true, false ) ) ); + assertSingleIndex( naturalIndex, makeQps( query, BSONObj(), BSONObj(), false ) ); + } + private: + void assertSingleIndex( const BSONObj& index, const shared_ptr<QueryPlanSet>& set ) { + ASSERT_EQUALS( 1, set->nPlans() ); + ASSERT_EQUALS( index, set->firstPlan()->indexKey() ); + } + }; + + } // namespace QueryPlanSetTests + + class Base { + public: + Base() : _ctx( ns() ) { + string err; + userCreateNS( ns(), BSONObj(), err, false ); + } + ~Base() { + if ( !nsd() ) + return; + string s( ns() ); + dropCollection( ns() ); + } + protected: + static const char *ns() { return "unittests.QueryOptimizerTests"; } + static NamespaceDetails *nsd() { return nsdetails( ns() ); } + QueryPattern makePattern( const BSONObj &query, const BSONObj &order ) { + FieldRangeSet frs( ns(), query, true, true ); + return QueryPattern( frs, order ); + } + shared_ptr<MultiPlanScanner> makeMps( const BSONObj &query, const BSONObj &order ) { + shared_ptr<MultiPlanScanner> ret( MultiPlanScanner::make( ns(), query, order ) ); + return ret; + } + DBDirectClient &client() { return _client; } + private: + Lock::GlobalWrite lk_; + Client::Context _ctx; + DBDirectClient _client; + }; + + namespace MultiPlanScannerTests { + class ToString : public Base { + public: + void run() { + scoped_ptr<MultiPlanScanner> multiPlanScanner + ( MultiPlanScanner::make( ns(), BSON( "a" << 1 ), BSONObj() ) ); + multiPlanScanner->toString(); // Just test that we don't crash. + } + }; + + class PossiblePlans : public Base { + public: + void run() { + client().ensureIndex( ns(), BSON( "a" << 1 ) ); + client().ensureIndex( ns(), BSON( "b" << 1 ) ); + + { + shared_ptr<MultiPlanScanner> mps = makeMps( BSON( "a" << 1 ), BSONObj() ); + ASSERT_EQUALS( 1, mps->currentNPlans() ); + ASSERT( mps->possibleInOrderPlan() ); + ASSERT( mps->haveInOrderPlan() ); + ASSERT( !mps->possibleOutOfOrderPlan() ); + ASSERT( !mps->hasPossiblyExcludedPlans() ); + } + + { + shared_ptr<MultiPlanScanner> mps = + makeMps( BSON( "a" << 1 ), BSON( "b" << 1 ) ); + ASSERT_EQUALS( 3, mps->currentNPlans() ); + ASSERT( mps->possibleInOrderPlan() ); + ASSERT( mps->haveInOrderPlan() ); + ASSERT( mps->possibleOutOfOrderPlan() ); + ASSERT( !mps->hasPossiblyExcludedPlans() ); + } + + NamespaceDetailsTransient &nsdt = NamespaceDetailsTransient::get( ns() ); + + nsdt.registerCachedQueryPlanForPattern( makePattern( BSON( "a" << 1 ), BSONObj() ), + CachedQueryPlan( BSON( "a" << 1 ), 1, + CandidatePlanCharacter( true, false ) ) ); + { + shared_ptr<MultiPlanScanner> mps = makeMps( BSON( "a" << 1 ), BSONObj() ); + ASSERT_EQUALS( 1, mps->currentNPlans() ); + ASSERT( mps->possibleInOrderPlan() ); + ASSERT( mps->haveInOrderPlan() ); + ASSERT( !mps->possibleOutOfOrderPlan() ); + ASSERT( !mps->hasPossiblyExcludedPlans() ); + } + + nsdt.registerCachedQueryPlanForPattern + ( makePattern( BSON( "a" << 1 ), BSON( "b" << 1 ) ), + CachedQueryPlan( BSON( "a" << 1 ), 1, + CandidatePlanCharacter( true, true ) ) ); + + { + shared_ptr<MultiPlanScanner> mps = + makeMps( BSON( "a" << 1 ), BSON( "b" << 1 ) ); + ASSERT_EQUALS( 1, mps->currentNPlans() ); + ASSERT( mps->possibleInOrderPlan() ); + ASSERT( !mps->haveInOrderPlan() ); + ASSERT( mps->possibleOutOfOrderPlan() ); + ASSERT( mps->hasPossiblyExcludedPlans() ); + } + + nsdt.registerCachedQueryPlanForPattern + ( makePattern( BSON( "a" << 1 ), BSON( "b" << 1 ) ), + CachedQueryPlan( BSON( "b" << 1 ), 1, + CandidatePlanCharacter( true, true ) ) ); + + { + shared_ptr<MultiPlanScanner> mps = + makeMps( BSON( "a" << 1 ), BSON( "b" << 1 ) ); + ASSERT_EQUALS( 1, mps->currentNPlans() ); + ASSERT( mps->possibleInOrderPlan() ); + ASSERT( mps->haveInOrderPlan() ); + ASSERT( mps->possibleOutOfOrderPlan() ); + ASSERT( mps->hasPossiblyExcludedPlans() ); + } + + { + shared_ptr<MultiPlanScanner> mps = + makeMps( BSON( "a" << 1 ), BSON( "c" << 1 ) ); + ASSERT_EQUALS( 2, mps->currentNPlans() ); + ASSERT( !mps->possibleInOrderPlan() ); + ASSERT( !mps->haveInOrderPlan() ); + ASSERT( mps->possibleOutOfOrderPlan() ); + ASSERT( !mps->hasPossiblyExcludedPlans() ); + } + + { + shared_ptr<MultiPlanScanner> mps = + makeMps( fromjson( "{$or:[{a:1},{a:2}]}" ), BSON( "c" << 1 ) ); + ASSERT_EQUALS( 1, mps->currentNPlans() ); + ASSERT( !mps->possibleInOrderPlan() ); + ASSERT( !mps->haveInOrderPlan() ); + ASSERT( mps->possibleOutOfOrderPlan() ); + ASSERT( !mps->hasPossiblyExcludedPlans() ); + } + + { + shared_ptr<MultiPlanScanner> mps = + makeMps( fromjson( "{$or:[{a:1,b:1},{a:2,b:2}]}" ), BSONObj() ); + ASSERT_EQUALS( 3, mps->currentNPlans() ); + ASSERT( mps->possibleInOrderPlan() ); + ASSERT( mps->haveInOrderPlan() ); + ASSERT( !mps->possibleOutOfOrderPlan() ); + ASSERT( !mps->hasPossiblyExcludedPlans() ); + } + } + }; + + } // namespace MultiPlanScannerTests + + class BestGuess : public Base { + public: + void run() { + Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); + Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" ); + BSONObj temp = BSON( "a" << 1 ); + theDataFileMgr.insertWithObjMod( ns(), temp ); + temp = BSON( "b" << 1 ); + theDataFileMgr.insertWithObjMod( ns(), temp ); + + boost::shared_ptr< Cursor > c = + NamespaceDetailsTransient::bestGuessCursor( ns(), BSON( "b" << 1 ), BSON( "a" << 1 ) ); + ASSERT_EQUALS( string( "a" ), c->indexKeyPattern().firstElement().fieldName() ); + + c = NamespaceDetailsTransient::bestGuessCursor( ns(), BSON( "a" << 1 ), + BSON( "b" << 1 ) ); + ASSERT_EQUALS( string( "b" ), c->indexKeyPattern().firstElementFieldName() ); + ASSERT( c->matcher() ); + ASSERT( c->currentMatches() ); // { b:1 } document + c->advance(); + ASSERT( !c->currentMatches() ); // { a:1 } document + + c = NamespaceDetailsTransient::bestGuessCursor( ns(), fromjson( "{b:1,$or:[{z:1}]}" ), + BSON( "a" << 1 ) ); + ASSERT_EQUALS( string( "a" ), c->indexKeyPattern().firstElement().fieldName() ); + + c = NamespaceDetailsTransient::bestGuessCursor( ns(), fromjson( "{a:1,$or:[{y:1}]}" ), + BSON( "b" << 1 ) ); + ASSERT_EQUALS( string( "b" ), c->indexKeyPattern().firstElementFieldName() ); + + FieldRangeSet frs( "ns", BSON( "a" << 1 ), true, true ); + { + SimpleMutex::scoped_lock lk(NamespaceDetailsTransient::_qcMutex); + NamespaceDetailsTransient::get_inlock( ns() ). + registerCachedQueryPlanForPattern( frs.pattern( BSON( "b" << 1 ) ), + CachedQueryPlan( BSON( "a" << 1 ), 0, + CandidatePlanCharacter( true, true ) ) ); + } + + c = NamespaceDetailsTransient::bestGuessCursor( ns(), fromjson( "{a:1,$or:[{y:1}]}" ), + BSON( "b" << 1 ) ); + ASSERT_EQUALS( string( "b" ), + c->indexKeyPattern().firstElement().fieldName() ); + } + }; + + class All : public Suite { + public: + All() : Suite( "queryoptimizer2" ) {} + + void setupTests() { + __forceLinkGeoPlugin(); + add<QueryPlanSetTests::ToString>(); + add<QueryPlanSetTests::NoIndexes>(); + add<QueryPlanSetTests::Optimal>(); + add<QueryPlanSetTests::NoOptimal>(); + add<QueryPlanSetTests::NoSpec>(); + add<QueryPlanSetTests::HintSpec>(); + add<QueryPlanSetTests::HintName>(); + add<QueryPlanSetTests::NaturalHint>(); + add<QueryPlanSetTests::NaturalSort>(); + add<QueryPlanSetTests::BadHint>(); + add<QueryPlanSetTests::Count>(); + add<QueryPlanSetTests::QueryMissingNs>(); + add<QueryPlanSetTests::UnhelpfulIndex>(); + add<QueryPlanSetTests::FindOne>(); + add<QueryPlanSetTests::Delete>(); + add<QueryPlanSetTests::DeleteOneScan>(); + add<QueryPlanSetTests::DeleteOneIndex>(); + add<QueryPlanSetTests::InQueryIntervals>(); + add<QueryPlanSetTests::EqualityThenIn>(); + add<QueryPlanSetTests::NotEqualityThenIn>(); + add<QueryPlanSetTests::ExcludeSpecialPlanWhenBtreePlan>(); + add<QueryPlanSetTests::ExcludeUnindexedPlanWhenSpecialPlan>(); + add<QueryPlanSetTests::PossiblePlans>(); + add<QueryPlanSetTests::AvoidUnhelpfulRecordedPlan>(); + add<QueryPlanSetTests::AvoidDisallowedRecordedPlan>(); + add<QueryPlanSetTests::AllowSpecial>(); + add<MultiPlanScannerTests::ToString>(); + add<MultiPlanScannerTests::PossiblePlans>(); + add<BestGuess>(); + } + } myall; + +} // namespace QueryOptimizerTests + |