/** * 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 . */ #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/parsed_query.h" #include "mongo/db/query_optimizer_internal.h" #include "mongo/db/queryutil.h" #include "mongo/dbtests/dbtests.h" namespace mongo { extern BSONObj id_obj; void runQuery(Message& m, QueryMessage& q, Message &response ) { CurOp op( &(cc()) ); op.ensureStarted(); runQuery( m , q , op, response ); } void runQuery(Message& m, QueryMessage& q ) { Message response; runQuery( m, q, response ); } void __forceLinkGeoPlugin(); } // namespace mongo namespace { using boost::shared_ptr; namespace QueryPlanTests { class ToString { public: void run() { BSONObj obj = BSON( "a" << 1 ); FieldRangeSetPair fieldRangeSetPair( "", obj ); BSONObj order = BSON( "b" << 1 ); scoped_ptr queryPlan( QueryPlan::make( 0, -1, fieldRangeSetPair, 0, obj, order ) ); queryPlan->toString(); // Just test that we don't crash. } }; class Base { public: Base() : _ctx( ns() ) , indexNum_( 0 ) { string err; userCreateNS( ns(), BSONObj(), err, false ); } ~Base() { if ( !nsd() ) return; cc().database()->dropCollection( ns() ); } protected: static const char *ns() { return "unittests.QueryPlanTests"; } static NamespaceDetails *nsd() { return nsdetails( ns() ); } IndexDetails *index( const BSONObj &key ) { stringstream ss; ss << indexNum_++; string name = ss.str(); client_.resetIndexCache(); client_.ensureIndex( ns(), key, false, name.c_str() ); return &nsd()->idx( existingIndexNo( key ) ); } int indexno( const BSONObj &key ) { return nsd()->idxNo( *index(key) ); } int existingIndexNo( const BSONObj &key ) const { NamespaceDetails *d = nsd(); for( int i = 0; i < d->getCompletedIndexCount(); ++i ) { if ( ( d->idx( i ).keyPattern() == key ) || ( d->idx( i ).isIdIndex() && IndexDetails::isIdIndexPattern( key ) ) ) { return i; } } verify( false ); return -1; } BSONObj startKey( const QueryPlan &p ) const { return p.frv()->startKey(); } BSONObj endKey( const QueryPlan &p ) const { return p.frv()->endKey(); } DBDirectClient &client() const { return client_; } private: Lock::GlobalWrite lk_; Client::Context _ctx; int indexNum_; static DBDirectClient client_; }; DBDirectClient Base::client_; // There's a limit of 10 indexes total, make sure not to exceed this in a given test. #define INDEXNO(x) nsd()->idxNo( *this->index( BSON(x) ) ) #define INDEX(x) this->index( BSON(x) ) auto_ptr< FieldRangeSetPair > FieldRangeSetPair_GLOBAL; #define FRSP(x) ( FieldRangeSetPair_GLOBAL.reset( new FieldRangeSetPair( ns(), x ) ), *FieldRangeSetPair_GLOBAL ) auto_ptr< FieldRangeSetPair > FieldRangeSetPair_GLOBAL2; #define FRSP2(x) ( FieldRangeSetPair_GLOBAL2.reset( new FieldRangeSetPair( ns(), x ) ), FieldRangeSetPair_GLOBAL2.get() ) class NoIndex : public Base { public: void run() { scoped_ptr p( QueryPlan::make( nsd(), -1, FRSP( BSONObj() ), FRSP2( BSONObj() ), BSONObj(), BSONObj() ) ); ASSERT_EQUALS( QueryPlan::Helpful, p->utility() ); ASSERT( !p->scanAndOrderRequired() ); ASSERT( p->mayBeMatcherNecessary() ); } }; class SimpleOrder : public Base { public: void run() { BSONObjBuilder b; b.appendMinKey( "" ); BSONObj start = b.obj(); BSONObjBuilder b2; b2.appendMaxKey( "" ); BSONObj end = b2.obj(); scoped_ptr p( QueryPlan::make( nsd(), INDEXNO( "a" << 1 ), FRSP( BSONObj() ), FRSP2( BSONObj() ), BSONObj(), BSON( "a" << 1 ) ) ); ASSERT( !p->scanAndOrderRequired() ); ASSERT( !startKey( *p ).woCompare( start ) ); ASSERT( !endKey( *p ).woCompare( end ) ); scoped_ptr p2( QueryPlan::make( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FRSP( BSONObj() ), FRSP2( BSONObj() ), BSONObj(), BSON( "a" << 1 << "b" << 1 ) ) ); ASSERT( !p2->scanAndOrderRequired() ); scoped_ptr p3( QueryPlan::make( nsd(), INDEXNO( "a" << 1 ), FRSP( BSONObj() ), FRSP2( BSONObj() ), BSONObj(), BSON( "b" << 1 ) ) ); ASSERT( p3->scanAndOrderRequired() ); ASSERT( !startKey( *p3 ).woCompare( start ) ); ASSERT( !endKey( *p3 ).woCompare( end ) ); } }; class MoreIndexThanNeeded : public Base { public: void run() { scoped_ptr p( QueryPlan::make( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FRSP( BSONObj() ), FRSP2( BSONObj() ), BSONObj(), BSON( "a" << 1 ) ) ); ASSERT( !p->scanAndOrderRequired() ); } }; class IndexSigns : public Base { public: void run() { scoped_ptr p( QueryPlan::make( nsd(), INDEXNO( "a" << 1 << "b" << -1 ), FRSP( BSONObj() ), FRSP2( BSONObj() ), BSONObj(), BSON( "a" << 1 << "b" << -1 ) ) ); ASSERT( !p->scanAndOrderRequired() ); ASSERT_EQUALS( 1, p->direction() ); scoped_ptr p2( QueryPlan::make( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FRSP( BSONObj() ), FRSP2( BSONObj() ), BSONObj(), BSON( "a" << 1 << "b" << -1 ) ) ); ASSERT( p2->scanAndOrderRequired() ); ASSERT_EQUALS( 0, p2->direction() ); scoped_ptr p3( QueryPlan::make( nsd(), indexno( id_obj ), FRSP( BSONObj() ), FRSP2( BSONObj() ), BSONObj(), BSON( "_id" << 1 ) ) ); ASSERT( !p3->scanAndOrderRequired() ); ASSERT_EQUALS( 1, p3->direction() ); } }; class IndexReverse : public Base { public: void run() { BSONObjBuilder b; b.appendMinKey( "" ); b.appendMaxKey( "" ); BSONObj start = b.obj(); BSONObjBuilder b2; b2.appendMaxKey( "" ); b2.appendMinKey( "" ); BSONObj end = b2.obj(); scoped_ptr p( QueryPlan::make( nsd(), INDEXNO( "a" << -1 << "b" << 1 ), FRSP( BSONObj() ), FRSP2( BSONObj() ), BSONObj(), BSON( "a" << 1 << "b" << -1 ) ) ); ASSERT( !p->scanAndOrderRequired() ); ASSERT_EQUALS( -1, p->direction() ); ASSERT( !startKey( *p ).woCompare( start ) ); ASSERT( !endKey( *p ).woCompare( end ) ); scoped_ptr p2( QueryPlan::make( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FRSP( BSONObj() ), FRSP2( BSONObj() ), BSONObj(), BSON( "a" << -1 << "b" << -1 ) ) ); ASSERT( !p2->scanAndOrderRequired() ); ASSERT_EQUALS( -1, p2->direction() ); scoped_ptr p3( QueryPlan::make( nsd(), INDEXNO( "a" << 1 << "b" << -1 ), FRSP( BSONObj() ), FRSP2( BSONObj() ), BSONObj(), BSON( "a" << -1 << "b" << -1 ) ) ); ASSERT( p3->scanAndOrderRequired() ); ASSERT_EQUALS( 0, p3->direction() ); } }; class NoOrder : public Base { public: void run() { BSONObjBuilder b; b.append( "", 3 ); b.appendMinKey( "" ); BSONObj start = b.obj(); BSONObjBuilder b2; b2.append( "", 3 ); b2.appendMaxKey( "" ); BSONObj end = b2.obj(); scoped_ptr p( QueryPlan::make( nsd(), INDEXNO( "a" << -1 << "b" << 1 ), FRSP( BSON( "a" << 3 ) ), FRSP2( BSON( "a" << 3 ) ), BSON( "a" << 3 ), BSONObj() ) ); ASSERT( !p->scanAndOrderRequired() ); ASSERT( !startKey( *p ).woCompare( start ) ); ASSERT( !endKey( *p ).woCompare( end ) ); scoped_ptr p2( QueryPlan::make( nsd(), INDEXNO( "a" << -1 << "b" << 1 ), FRSP( BSON( "a" << 3 ) ), FRSP2( BSON( "a" << 3 ) ), BSON( "a" << 3 ), BSONObj() ) ); ASSERT( !p2->scanAndOrderRequired() ); ASSERT( !startKey( *p ).woCompare( start ) ); ASSERT( !endKey( *p ).woCompare( end ) ); } }; class EqualWithOrder : public Base { public: void run() { scoped_ptr p( QueryPlan::make( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FRSP( BSON( "a" << 4 ) ), FRSP2( BSON( "a" << 4 ) ), BSON( "a" << 4 ), BSON( "b" << 1 ) ) ); ASSERT( !p->scanAndOrderRequired() ); scoped_ptr p2 ( QueryPlan::make( nsd(), INDEXNO( "a" << 1 << "b" << 1 << "c" << 1 ), FRSP( BSON( "b" << 4 ) ), FRSP2( BSON( "b" << 4 ) ), BSON( "b" << 4 ), BSON( "a" << 1 << "c" << 1 ) ) ); ASSERT( !p2->scanAndOrderRequired() ); scoped_ptr p3( QueryPlan::make( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FRSP( BSON( "b" << 4 ) ), FRSP2( BSON( "b" << 4 ) ), BSON( "b" << 4 ), BSON( "a" << 1 << "c" << 1 ) ) ); ASSERT( p3->scanAndOrderRequired() ); } }; class Optimal : public Base { public: void run() { scoped_ptr p( QueryPlan::make( nsd(), INDEXNO( "a" << 1 ), FRSP( BSONObj() ), FRSP2( BSONObj() ), BSONObj(), BSON( "a" << 1 ) ) ); ASSERT_EQUALS( QueryPlan::Optimal, p->utility() ); scoped_ptr p2( QueryPlan::make( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FRSP( BSONObj() ), FRSP2( BSONObj() ), BSONObj(), BSON( "a" << 1 ) ) ); ASSERT_EQUALS( QueryPlan::Optimal, p2->utility() ); scoped_ptr p3( QueryPlan::make( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FRSP( BSON( "a" << 1 ) ), FRSP2( BSON( "a" << 1 ) ), BSON( "a" << 1 ), BSON( "a" << 1 ) ) ); ASSERT_EQUALS( QueryPlan::Optimal, p3->utility() ); scoped_ptr p4( QueryPlan::make( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FRSP( BSON( "b" << 1 ) ), FRSP2( BSON( "b" << 1 ) ), BSON( "b" << 1 ), BSON( "a" << 1 ) ) ); ASSERT_EQUALS( QueryPlan::Helpful, p4->utility() ); scoped_ptr p5( QueryPlan::make( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FRSP( BSON( "a" << 1 ) ), FRSP2( BSON( "a" << 1 ) ), BSON( "a" << 1 ), BSON( "b" << 1 ) ) ); ASSERT_EQUALS( QueryPlan::Optimal, p5->utility() ); scoped_ptr p6( QueryPlan::make( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FRSP( BSON( "b" << 1 ) ), FRSP2( BSON( "b" << 1 ) ), BSON( "b" << 1 ), BSON( "b" << 1 ) ) ); ASSERT_EQUALS( QueryPlan::Unhelpful, p6->utility() ); scoped_ptr p7( QueryPlan::make( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FRSP( BSON( "a" << 1 << "b" << 1 ) ), FRSP2( BSON( "a" << 1 << "b" << 1 ) ), BSON( "a" << 1 << "b" << 1 ), BSON( "a" << 1 ) ) ); ASSERT_EQUALS( QueryPlan::Optimal, p7->utility() ); scoped_ptr p8 ( QueryPlan::make( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FRSP( BSON( "a" << 1 << "b" << LT << 1 ) ), FRSP2( BSON( "a" << 1 << "b" << LT << 1 ) ), BSON( "a" << 1 << "b" << LT << 1 ), BSON( "a" << 1 ) ) ); ASSERT_EQUALS( QueryPlan::Optimal, p8->utility() ); scoped_ptr p9( QueryPlan::make ( nsd(), INDEXNO( "a" << 1 << "b" << 1 << "c" << 1 ), FRSP( BSON( "a" << 1 << "b" << LT << 1 ) ), FRSP2( BSON( "a" << 1 << "b" << LT << 1 ) ), BSON( "a" << 1 << "b" << LT << 1 ), BSON( "a" << 1 ) ) ); ASSERT_EQUALS( QueryPlan::Optimal, p9->utility() ); } }; class MoreOptimal : public Base { public: void run() { scoped_ptr p10 ( QueryPlan::make( nsd(), INDEXNO( "a" << 1 << "b" << 1 << "c" << 1 ), FRSP( BSON( "a" << 1 ) ), FRSP2( BSON( "a" << 1 ) ), BSON( "a" << 1 ), BSONObj() ) ); ASSERT_EQUALS( QueryPlan::Optimal, p10->utility() ); scoped_ptr p11 ( QueryPlan::make( nsd(), INDEXNO( "a" << 1 << "b" << 1 << "c" << 1 ), FRSP( BSON( "a" << 1 << "b" << LT << 1 ) ), FRSP2( BSON( "a" << 1 << "b" << LT << 1 ) ), BSON( "a" << 1 << "b" << LT << 1 ), BSONObj() ) ); ASSERT_EQUALS( QueryPlan::Optimal, p11->utility() ); scoped_ptr p12 ( QueryPlan::make( nsd(), INDEXNO( "a" << 1 << "b" << 1 << "c" << 1 ), FRSP( BSON( "a" << LT << 1 ) ), FRSP2( BSON( "a" << LT << 1 ) ), BSON( "a" << LT << 1 ), BSONObj() ) ); ASSERT_EQUALS( QueryPlan::Optimal, p12->utility() ); scoped_ptr p13 ( QueryPlan::make( nsd(), INDEXNO( "a" << 1 << "b" << 1 << "c" << 1 ), FRSP( BSON( "a" << LT << 1 ) ), FRSP2( BSON( "a" << LT << 1 ) ), BSON( "a" << LT << 1 ), BSON( "a" << 1 ) ) ); ASSERT_EQUALS( QueryPlan::Optimal, p13->utility() ); } }; /** Cases where a QueryPlan's Utility is Impossible. */ class Impossible : public Base { public: void run() { // When no match is possible on an indexed field, the plan is Impossible. BSONObj impossibleQuery = BSON( "a" << BSON( "$in" << BSONArray() ) ); scoped_ptr p1( QueryPlan::make( nsd(), INDEXNO( "a" << 1 ), FRSP( impossibleQuery ), FRSP2( impossibleQuery ), impossibleQuery, BSONObj() ) ); ASSERT_EQUALS( QueryPlan::Impossible, p1->utility() ); // When no match is possible on an unindexed field, the plan is Helpful. // (Descriptive test only.) BSONObj bImpossibleQuery = BSON( "a" << 1 << "b" << BSON( "$in" << BSONArray() ) ); scoped_ptr p2( QueryPlan::make( nsd(), INDEXNO( "a" << 1 ), FRSP( bImpossibleQuery ), FRSP2( bImpossibleQuery ), bImpossibleQuery, BSONObj() ) ); ASSERT_EQUALS( QueryPlan::Helpful, p2->utility() ); } }; /** * QueryPlan::mayBeMatcherNecessary() returns false when an index is optimal and a field * range set mustBeExactMatchRepresentation() (for a single key index). */ class NotMatcherNecessary : public Base { public: void run() { // Non compound index tests. ASSERT( !matcherNecessary( BSON( "a" << 1 ), BSON( "a" << 5 ) ) ); ASSERT( !matcherNecessary( BSON( "a" << 1 ), BSON( "a" << GT << 5 ) ) ); ASSERT( !matcherNecessary( BSON( "a" << 1 ), BSON( "a" << GT << 5 << LT << 10 ) ) ); ASSERT( !matcherNecessary( BSON( "a" << 1 ), BSON( "a" << BSON( "$in" << BSON_ARRAY( 1 << 2 ) ) ) ) ); // Compound index tests. ASSERT( !matcherNecessary( BSON( "a" << 1 << "b" << 1 ), BSON( "a" << 5 << "b" << 6 ) ) ); ASSERT( !matcherNecessary( BSON( "a" << 1 << "b" << -1 ), BSON( "a" << 2 << "b" << GT << 5 ) ) ); ASSERT( !matcherNecessary( BSON( "a" << -1 << "b" << 1 ), BSON( "a" << 3 << "b" << GT << 5 << LT << 10 ) ) ); ASSERT( !matcherNecessary( BSON( "a" << -1 << "b" << -1 ), BSON( "a" << "q" << "b" << BSON( "$in" << BSON_ARRAY( 1 << 2 ) ) ) ) ); } private: bool matcherNecessary( const BSONObj& index, const BSONObj& query ) { scoped_ptr plan( makePlan( index, query ) ); return plan->mayBeMatcherNecessary(); } QueryPlan* makePlan( const BSONObj& index, const BSONObj& query ) { return QueryPlan::make( nsd(), nsd()->idxNo( *this->index( index ) ), FRSP( query ), FRSP2( query ), query, BSONObj() ); } }; /** * QueryPlan::mayBeMatcherNecessary() returns true when an index is not optimal or a field * range set !mustBeExactMatchRepresentation(). */ class MatcherNecessary : public Base { public: void run() { // Not mustBeExactMatchRepresentation. ASSERT( matcherNecessary( BSON( "a" << 1 ), BSON( "a" << BSON_ARRAY( 5 ) ) ) ); ASSERT( matcherNecessary( BSON( "a" << 1 ), BSON( "a" << NE << 5 ) ) ); ASSERT( matcherNecessary( BSON( "a" << 1 ), fromjson( "{a:/b/}" ) ) ); ASSERT( matcherNecessary( BSON( "a" << 1 ), BSON( "a" << 1 << "$where" << "false" ) ) ); // Not optimal index. ASSERT( matcherNecessary( BSON( "a" << 1 ), BSON( "a" << 5 << "b" << 6 ) ) ); ASSERT( matcherNecessary( BSON( "a" << 1 << "b" << -1 ), BSON( "b" << GT << 5 ) ) ); ASSERT( matcherNecessary( BSON( "a" << -1 << "b" << 1 ), BSON( "a" << GT << 2 << "b" << LT << 10 ) ) ); ASSERT( matcherNecessary( BSON( "a" << -1 << "b" << -1 ), BSON( "a" << BSON( "$in" << BSON_ARRAY( 1 << 2 ) ) << "b" << "q" ) ) ); // Not mustBeExactMatchRepresentation and not optimal index. ASSERT( matcherNecessary( BSON( "a" << 1 << "b" << 1 ), BSON( "b" << BSON_ARRAY( 5 ) ) ) ); } private: bool matcherNecessary( const BSONObj& index, const BSONObj& query ) { scoped_ptr plan( makePlan( index, query ) ); return plan->mayBeMatcherNecessary(); } QueryPlan* makePlan( const BSONObj& index, const BSONObj& query ) { return QueryPlan::make( nsd(), nsd()->idxNo( *this->index( index ) ), FRSP( query ), FRSP2( query ), query, BSONObj() ); } }; /** * QueryPlan::mustBeMatcherNecessary() returns true when field ranges on a multikey index * cannot be intersected for a single field or across multiple fields. */ class MatcherNecessaryMultikey : public Base { public: MatcherNecessaryMultikey() { client().insert( ns(), fromjson( "{ a:[ { b:1, c:1 }, { b:2, c:2 } ] }" ) ); } void run() { ASSERT( !matcherNecessary( BSON( "a" << 1 ), BSON( "a" << GT << 4 ) ) ); ASSERT( !matcherNecessary( BSON( "a" << 1 << "b" << 1 ), BSON( "a" << 4 << "b" << LT << 8 ) ) ); // The two constraints on 'a' cannot be intersected for a multikey index on 'a'. ASSERT( matcherNecessary( BSON( "a" << 1 ), BSON( "a" << GT << 4 << LT << 8 ) ) ); ASSERT( !matcherNecessary( BSON( "a.b" << 1 ), BSON( "a.b" << 5 ) ) ); ASSERT( !matcherNecessary( BSON( "a.b" << 1 << "c.d" << 1 ), BSON( "a.b" << 5 << "c.d" << 6 ) ) ); // The constraints on 'a.b' and 'a.c' cannot be intersected, see comments on // SERVER-958 in FieldRangeVector(). ASSERT( matcherNecessary( BSON( "a.b" << 1 << "a.c" << 1 ), BSON( "a.b" << 5 << "a.c" << 6 ) ) ); // The constraints on 'a.b' and 'a.c' can be intersected, but // mustBeExactMatchRepresentation() is false for an '$elemMatch' query. ASSERT( matcherNecessary( BSON( "a.b" << 1 << "a.c" << 1 ), fromjson( "{ a:{ $elemMatch:{ b:5, c:6 } } }" ) ) ); } private: bool matcherNecessary( const BSONObj& index, const BSONObj& query ) { scoped_ptr plan( makePlan( index, query ) ); return plan->mayBeMatcherNecessary(); } QueryPlan* makePlan( const BSONObj& index, const BSONObj& query ) { return QueryPlan::make( nsd(), nsd()->idxNo( *this->index( index ) ), FRSP( query ), FRSP2( query ), query, BSONObj() ); } }; class Unhelpful : public Base { public: void run() { scoped_ptr p( QueryPlan::make( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FRSP( BSON( "b" << 1 ) ), FRSP2( BSON( "b" << 1 ) ), BSON( "b" << 1 ), BSONObj() ) ); ASSERT( p->multikeyFrs().range( "a" ).universal() ); ASSERT_EQUALS( QueryPlan::Unhelpful, p->utility() ); scoped_ptr p2( QueryPlan::make( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FRSP( BSON( "b" << 1 << "c" << 1 ) ), FRSP2( BSON( "b" << 1 << "c" << 1 ) ), BSON( "b" << 1 << "c" << 1 ), BSON( "a" << 1 ) ) ); ASSERT( !p2->scanAndOrderRequired() ); ASSERT( p2->multikeyFrs().range( "a" ).universal() ); ASSERT_EQUALS( QueryPlan::Helpful, p2->utility() ); scoped_ptr p3( QueryPlan::make( nsd(), INDEXNO( "b" << 1 ), FRSP( BSON( "b" << 1 << "c" << 1 ) ), FRSP2( BSON( "b" << 1 << "c" << 1 ) ), BSON( "b" << 1 << "c" << 1 ), BSONObj() ) ); ASSERT( !p3->multikeyFrs().range( "b" ).universal() ); ASSERT_EQUALS( QueryPlan::Helpful, p3->utility() ); scoped_ptr p4( QueryPlan::make( nsd(), INDEXNO( "b" << 1 << "c" << 1 ), FRSP( BSON( "c" << 1 << "d" << 1 ) ), FRSP2( BSON( "c" << 1 << "d" << 1 ) ), BSON( "c" << 1 << "d" << 1 ), BSONObj() ) ); ASSERT( p4->multikeyFrs().range( "b" ).universal() ); ASSERT_EQUALS( QueryPlan::Unhelpful, p4->utility() ); } }; class KeyFieldsOnly : public Base { public: void run() { int idx = INDEXNO( "a" << 1 ); // No fields supplied. scoped_ptr p( QueryPlan::make( nsd(), idx, FRSP( BSON( "a" << 1 ) ), FRSP2( BSON( "a" << 1 ) ), BSON( "a" << 1 ), BSONObj() ) ); ASSERT( !p->keyFieldsOnly() ); // Fields supplied. shared_ptr parsedQuery ( new ParsedQuery( ns(), 0, 0, 0, BSONObj(), BSON( "_id" << 0 << "a" << 1 ) ) ); scoped_ptr p2( QueryPlan::make( nsd(), idx, FRSP( BSON( "a" << 1 ) ), FRSP2( BSON( "a" << 1 ) ), BSON( "a" << 1 ), BSONObj(), parsedQuery ) ); ASSERT( p2->keyFieldsOnly() ); ASSERT_EQUALS( BSON( "a" << 4 ), p2->keyFieldsOnly()->hydrate( BSON( "" << 4 ) ) ); // Fields supplied, but index is multikey. DBDirectClient client; client.insert( ns(), BSON( "a" << BSON_ARRAY( 1 << 2 ) ) ); scoped_ptr p3( QueryPlan::make( nsd(), idx, FRSP( BSON( "a" << 1 ) ), FRSP2( BSON( "a" << 1 ) ), BSON( "a" << 1 ), BSONObj(), parsedQuery ) ); ASSERT( !p3->keyFieldsOnly() ); } }; /** $exists:false and some $exists:true predicates disallow sparse index query plans. */ class SparseExistsFalse : 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 ) ); // Non $exists predicates allow the sparse index. assertAllowed( BSON( "a" << 1 ) ); assertAllowed( BSON( "b" << 1 ) ); // Top level $exists:false and $not:{$exists:true} queries disallow the sparse // index, regardless of query field. Otherwise the sparse index is allowed. assertDisallowed( BSON( "a" << BSON( "$exists" << false ) ) ); assertDisallowed( BSON( "b" << BSON( "$exists" << false ) ) ); assertAllowed( BSON( "a" << BSON( "$exists" << true ) ) ); assertAllowed( BSON( "b" << BSON( "$exists" << true ) ) ); assertAllowed( BSON( "a" << BSON( "$not" << BSON( "$exists" << false ) ) ) ); assertAllowed( BSON( "b" << BSON( "$not" << BSON( "$exists" << false ) ) ) ); assertDisallowed( BSON( "a" << BSON( "$not" << BSON( "$exists" << true ) ) ) ); assertDisallowed( BSON( "b" << BSON( "$not" << BSON( "$exists" << true ) ) ) ); // All nested non $exists predicates allow the sparse index. assertAllowed( BSON( "$nor" << BSON_ARRAY( BSON( "a" << 1 ) ) ) ); assertAllowed( BSON( "$nor" << BSON_ARRAY( BSON( "b" << 1 ) ) ) ); // All nested $exists predicates disallow the sparse index. assertDisallowed( BSON( "$nor" << BSON_ARRAY ( BSON( "a" << BSON( "$exists" << false ) ) ) ) ); assertDisallowed( BSON( "$nor" << BSON_ARRAY ( BSON( "b" << BSON( "$exists" << false ) ) ) ) ); assertDisallowed( BSON( "$nor" << BSON_ARRAY ( BSON( "a" << BSON( "$exists" << true ) ) ) ) ); assertDisallowed( BSON( "$nor" << BSON_ARRAY ( BSON( "b" << BSON( "$exists" << true ) ) ) ) ); assertDisallowed( BSON( "$nor" << BSON_ARRAY ( BSON( "a" << BSON( "$not" << BSON( "$exists" << false ) ) ) ) ) ); assertDisallowed( BSON( "$nor" << BSON_ARRAY ( BSON( "b" << BSON( "$not" << BSON( "$exists" << false ) ) ) ) ) ); assertDisallowed( BSON( "$nor" << BSON_ARRAY ( BSON( "a" << BSON( "$not" << BSON( "$exists" << true ) ) ) ) ) ); assertDisallowed( BSON( "$nor" << BSON_ARRAY ( BSON( "b" << BSON( "$not" << BSON( "$exists" << true ) ) ) ) ) ); } private: shared_ptr newPlan( const BSONObj &query ) const { shared_ptr ret ( QueryPlan::make( nsd(), existingIndexNo( BSON( "a" << 1 ) ), FRSP( query ), FRSP2( query ), query, BSONObj() ) ); return ret; } void assertAllowed( const BSONObj &query ) const { ASSERT_NOT_EQUALS( QueryPlan::Disallowed, newPlan( query )->utility() ); } void assertDisallowed( const BSONObj &query ) const { ASSERT_EQUALS( QueryPlan::Disallowed, newPlan( query )->utility() ); } }; /** Test conditions in which QueryPlan::newCursor() returns an IntervalBtreeCursor. */ class IntervalCursorCreate : public Base { public: void run() { // An interval cursor will not be created if the query plan is not Optimal. (See // comments on Optimal value of QueryPlan::Utility enum.) BSONObj query = fromjson( "{a:{$gt:4},b:{$gt:5}}" ); scoped_ptr plan( QueryPlan::make( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FRSP( query ), FRSP2( query ), query, BSONObj() ) ); shared_ptr cursor = plan->newCursor( DiskLoc(), true ); ASSERT_NOT_EQUALS( "IntervalBtreeCursor", cursor->toString() ); // An interval cursor will not be created if the query plan is Optimal but does not // consist of a single interval. query = fromjson( "{a:4,b:{$in:[5,6]}}" ); plan.reset( QueryPlan::make( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FRSP( query ), FRSP2( query ), query, BSONObj() ) ); cursor = plan->newCursor( DiskLoc(), true ); ASSERT_NOT_EQUALS( "IntervalBtreeCursor", cursor->toString() ); // An interval cursor will be created if the field ranges consist of a single // interval. query = fromjson( "{a:4,b:{$gt:6,$lte:9}}" ); plan.reset( QueryPlan::make( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FRSP( query ), FRSP2( query ), query, BSONObj() ) ); cursor = plan->newCursor( DiskLoc(), true ); ASSERT_EQUALS( "IntervalBtreeCursor", cursor->toString() ); ASSERT_EQUALS( BSON( "lower" << BSON( "a" << 4 << "b" << 6 ) << "upper" << BSON( "a" << 4 << "b" << 9 ) ), cursor->prettyIndexBounds() ); // But an interval cursor will not be created if it is not requested. query = fromjson( "{a:4,b:{$gt:6,$lte:9}}" ); plan.reset( QueryPlan::make( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FRSP( query ), FRSP2( query ), query, BSONObj() ) ); cursor = plan->newCursor( DiskLoc(), /* requestIntervalCursor */ false ); ASSERT_NOT_EQUALS( "IntervalBtreeCursor", cursor->toString() ); // An interval cursor will not be created for a v0 index (unsupported). client().ensureIndex( ns(), BSON( "x" << 1 << "y" << 1 ), false, "", false, false, 0 ); query = fromjson( "{x:2,y:3}" ); plan.reset( QueryPlan::make( nsd(), indexno( BSON( "x" << 1 << "y" << 1 ) ), FRSP( query ), FRSP2( query ), query, BSONObj() ) ); cursor = plan->newCursor( DiskLoc(), true ); ASSERT_NOT_EQUALS( "IntervalBtreeCursor", cursor->toString() ); } }; /** * Test that interval cursors returned by newCursor iterate over matching documents only. */ class IntervalCursorBounds : public Base { public: void run() { client().insert( ns(), BSON( "_id" << 0 << "a" << 1 << "b" << 1 ) ); client().insert( ns(), BSON( "_id" << 1 << "a" << 1 << "b" << 2 ) ); client().insert( ns(), BSON( "_id" << 2 << "a" << 2 << "b" << 1 ) ); client().insert( ns(), BSON( "_id" << 3 << "a" << 2 << "b" << 2 ) ); BSONObj query = fromjson( "{a:2,b:{$lte:2}}" ); scoped_ptr plan( QueryPlan::make( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FRSP( query ), FRSP2( query ), query, BSONObj() ) ); shared_ptr cursor = plan->newCursor( DiskLoc(), true ); ASSERT_EQUALS( "IntervalBtreeCursor", cursor->toString() ); ASSERT_EQUALS( BSON( "_id" << 2 << "a" << 2 << "b" << 1 ), cursor->current() ); ASSERT( cursor->advance() ); ASSERT_EQUALS( BSON( "_id" << 3 << "a" << 2 << "b" << 2 ), cursor->current() ); ASSERT( !cursor->advance() ); query = fromjson( "{a:{$lt:2}}" ); plan.reset( QueryPlan::make( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FRSP( query ), FRSP2( query ), query, BSONObj() ) ); cursor = plan->newCursor( DiskLoc(), true ); ASSERT_EQUALS( "IntervalBtreeCursor", cursor->toString() ); ASSERT_EQUALS( BSON( "_id" << 0 << "a" << 1 << "b" << 1 ), cursor->current() ); ASSERT( cursor->advance() ); ASSERT_EQUALS( BSON( "_id" << 1 << "a" << 1 << "b" << 2 ), cursor->current() ); ASSERT( !cursor->advance() ); query = fromjson( "{a:{$lt:2}}" ); plan.reset( QueryPlan::make( nsd(), INDEXNO( "a" << 1 << "b" << -1 ), // note -1 FRSP( query ), FRSP2( query ), query, BSONObj() ) ); cursor = plan->newCursor( DiskLoc(), true ); ASSERT_EQUALS( "IntervalBtreeCursor", cursor->toString() ); ASSERT_EQUALS( BSON( "_id" << 1 << "a" << 1 << "b" << 2 ), cursor->current() ); ASSERT( cursor->advance() ); ASSERT_EQUALS( BSON( "_id" << 0 << "a" << 1 << "b" << 1 ), cursor->current() ); ASSERT( !cursor->advance() ); query = fromjson( "{a:{$gt:1}}" ); plan.reset( QueryPlan::make( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FRSP( query ), FRSP2( query ), query, BSONObj() ) ); cursor = plan->newCursor( DiskLoc(), true ); ASSERT_EQUALS( "IntervalBtreeCursor", cursor->toString() ); ASSERT_EQUALS( BSON( "_id" << 2 << "a" << 2 << "b" << 1 ), cursor->current() ); ASSERT( cursor->advance() ); ASSERT_EQUALS( BSON( "_id" << 3 << "a" << 2 << "b" << 2 ), cursor->current() ); ASSERT( !cursor->advance() ); query = fromjson( "{a:{$gt:1}}" ); plan.reset( QueryPlan::make( nsd(), INDEXNO( "a" << 1 << "b" << -1 ), // note -1 FRSP( query ), FRSP2( query ), query, BSONObj() ) ); cursor = plan->newCursor( DiskLoc(), true ); ASSERT_EQUALS( "IntervalBtreeCursor", cursor->toString() ); ASSERT_EQUALS( BSON( "_id" << 3 << "a" << 2 << "b" << 2 ), cursor->current() ); ASSERT( cursor->advance() ); ASSERT_EQUALS( BSON( "_id" << 2 << "a" << 2 << "b" << 1 ), cursor->current() ); ASSERT( !cursor->advance() ); query = fromjson( "{a:2,b:{$lte:2}}" ); plan.reset( QueryPlan::make( nsd(), INDEXNO( "a" << 1 << "b" << -1 ), // note -1 FRSP( query ), FRSP2( query ), query, BSONObj() ) ); cursor = plan->newCursor( DiskLoc(), true ); ASSERT_EQUALS( "IntervalBtreeCursor", cursor->toString() ); ASSERT_EQUALS( BSON( "_id" << 3 << "a" << 2 << "b" << 2 ), cursor->current() ); ASSERT( cursor->advance() ); ASSERT_EQUALS( BSON( "_id" << 2 << "a" << 2 << "b" << 1 ), cursor->current() ); ASSERT( !cursor->advance() ); query = fromjson( "{a:1,b:{$gte:1}}" ); plan.reset( QueryPlan::make( nsd(), INDEXNO( "a" << -1 << "b" << -1 ), // note -1 FRSP( query ), FRSP2( query ), query, BSONObj() ) ); cursor = plan->newCursor( DiskLoc(), true ); ASSERT_EQUALS( "IntervalBtreeCursor", cursor->toString() ); ASSERT_EQUALS( BSON( "_id" << 1 << "a" << 1 << "b" << 2 ), cursor->current() ); ASSERT( cursor->advance() ); ASSERT_EQUALS( BSON( "_id" << 0 << "a" << 1 << "b" << 1 ), cursor->current() ); ASSERT( !cursor->advance() ); } }; /** IntervalBtreeCursor is used and a Matcher is necessary. */ class IntervalCursorWithMatcher : public Base { public: void run() { client().insert( ns(), BSON( "_id" << 0 << "a" << 1 ) ); client().insert( ns(), BSON( "_id" << 1 << "a" << 1 << "b" << "exists" ) ); BSONObj query = BSON( "a" << 1 << "b" << BSON( "$exists" << true ) ); scoped_ptr plan( QueryPlan::make( nsd(), INDEXNO( "a" << 1 ), FRSP( query ), FRSP2( query ), query, BSONObj() ) ); shared_ptr cursor = plan->newCursor( DiskLoc(), true ); ASSERT_EQUALS( "IntervalBtreeCursor", cursor->toString() ); ASSERT( plan->mayBeMatcherNecessary() ); cursor->setMatcher( plan->matcher() ); // Check the cursor's results, and whether they match. ASSERT_EQUALS( 0, cursor->current()[ "_id" ].Int() ); ASSERT( !cursor->currentMatches() ); ASSERT( cursor->advance() ); ASSERT_EQUALS( 1, cursor->current()[ "_id" ].Int() ); ASSERT( cursor->currentMatches() ); ASSERT( !cursor->advance() ); } }; namespace QueryBoundsExactOrderSuffix { class Base : public QueryPlanTests::Base { public: virtual ~Base() {} void run() { BSONObj planQuery = query(); BSONObj planOrder = order(); scoped_ptr plan( QueryPlan::make( nsd(), indexIdx(), FRSP( planQuery ), FRSP2( planQuery ), planQuery, planOrder ) ); ASSERT_EQUALS( queryBoundsExactOrderSuffix(), plan->queryBoundsExactOrderSuffix() ); } protected: virtual bool queryBoundsExactOrderSuffix() = 0; virtual int indexIdx() { return indexno( index() ); } virtual BSONObj index() = 0; virtual BSONObj query() = 0; virtual BSONObj order() = 0; }; class True : public Base { bool queryBoundsExactOrderSuffix() { return true; } }; class False : public Base { bool queryBoundsExactOrderSuffix() { return false; } }; class Unindexed : public False { int indexIdx() { return -1; } BSONObj index() { return BSON( "wrong" << 1 ); } BSONObj query() { return BSON( "a" << 1 ); } BSONObj order() { return BSON( "b" << 1 ); } }; class RangeSort : public True { BSONObj index() { return BSON( "a" << 1 ); } BSONObj query() { return BSON( "a" << GT << 1 ); } BSONObj order() { return BSON( "a" << 1 ); } }; class RangeBeforeSort : public False { BSONObj index() { return BSON( "a" << 1 << "b" << 1 ); } BSONObj query() { return BSON( "a" << GT << 1 ); } BSONObj order() { return BSON( "b" << 1 ); } }; class EqualityRangeBeforeSort : public False { BSONObj index() { return BSON( "a" << 1 << "b" << 1 << "c" << 1 ); } BSONObj query() { return BSON( "a" << 1 << "b" << GT << 1 ); } BSONObj order() { return BSON( "c" << 1 ); } }; class EqualSort : public True { BSONObj index() { return BSON( "a" << 1 << "b" << 1 ); } BSONObj query() { return BSON( "a" << 1 ); } BSONObj order() { return BSON( "b" << 1 ); } }; class InSort : public True { BSONObj index() { return BSON( "a" << 1 << "b" << 1 ); } BSONObj query() { return fromjson( "{a:{$in:[0,1]}}" ); } BSONObj order() { return BSON( "b" << 1 ); } }; class EqualInSort : public True { BSONObj index() { return BSON( "a" << 1 << "b" << 1 << "c" << 1 ); } BSONObj query() { return fromjson( "{a:10,b:{$in:[0,1]}}" ); } BSONObj order() { return BSON( "c" << 1 ); } }; class InInSort : public True { BSONObj index() { return BSON( "a" << 1 << "b" << 1 << "c" << 1 ); } BSONObj query() { return fromjson( "{a:{$in:[5,6]},b:{$in:[0,1]}}" ); } BSONObj order() { return BSON( "c" << 1 ); } }; class NonCoveredRange : public False { BSONObj index() { return BSON( "a" << 1 << "b" << 1 ); } BSONObj query() { return fromjson( "{a:{$in:[5,6]},z:4}" ); } BSONObj order() { return BSON( "b" << 1 ); } }; class QuerySortOverlap : public True { BSONObj index() { return BSON( "a" << 1 << "b" << 1 << "c" << 1 ); } BSONObj query() { return fromjson( "{a:10,b:{$in:[0,1]}}" ); } BSONObj order() { return BSON( "b" << 1 << "c" << 1 ); } }; class OrderDirection : public False { BSONObj index() { return BSON( "a" << 1 << "b" << 1 ); } BSONObj query() { return fromjson( "{a:{$in:[0,1]}}" ); } BSONObj order() { return BSON( "a" << 1 << "b" << -1 ); } }; class InterveningIndexField : public False { BSONObj index() { return BSON( "a" << 1 << "b" << 1 << "c" << 1 ); } BSONObj query() { return fromjson( "{a:{$in:[0,1]}}" ); } BSONObj order() { return BSON( "c" << 1 ); } }; class TailingIndexField : public True { BSONObj index() { return BSON( "a" << 1 << "b" << 1 << "c" << 1 ); } BSONObj query() { return fromjson( "{a:{$in:[0,1]}}" ); } BSONObj order() { return BSON( "b" << 1 ); } }; class EmptySort : public True { BSONObj index() { return BSON( "a" << 1 << "b" << 1 ); } BSONObj query() { return fromjson( "{a:{$in:[0,1]}}" ); } BSONObj order() { return BSONObj(); } }; class EmptyStringField : public True { BSONObj index() { return BSON( "a" << 1 << "" << 1 ); } BSONObj query() { return fromjson( "{a:4,'':{$in:[0,1]}}" ); } BSONObj order() { return BSONObj(); } }; class SortedRange : public True { BSONObj index() { return BSON( "a" << 1 << "b" << 1 ); } BSONObj query() { return fromjson( "{a:{$in:[0,1]},b:{$gt:5}}" ); } BSONObj order() { return BSON( "b" << 1 ); } }; class SortedRangeWrongDirection : public False { BSONObj index() { return BSON( "a" << 1 << "b" << 1 ); } BSONObj query() { return fromjson( "{a:{$in:[0,1]},b:{$gt:5}}" ); } BSONObj order() { return BSON( "b" << -1 ); } }; class SortedDoubleRange : public True { BSONObj index() { return BSON( "a" << 1 << "b" << 1 ); } BSONObj query() { return fromjson( "{a:{$in:[0,1]},b:{$gt:5,$lt:10}}" ); } BSONObj order() { return BSON( "b" << 1 ); } }; class RangeSortPrefix : public True { BSONObj index() { return BSON( "a" << 1 << "b" << 1 << "c" << 1 ); } BSONObj query() { return fromjson( "{a:{$in:[0,1]},b:{$gt:5}}" ); } BSONObj order() { return BSON( "b" << 1 << "c" << 1 ); } }; class RangeSortInfix : public True { BSONObj index() { return BSON( "a" << 1 << "b" << 1 << "c" << 1 ); } BSONObj query() { return fromjson( "{a:{$in:[0,1]},b:{$gt:5}}" ); } BSONObj order() { return BSON( "a" << 1 << "b" << 1 << "c" << 1 ); } }; class RangeEquality : public False { BSONObj index() { return BSON( "a" << 1 << "b" << 1 << "c" << 1 ); } BSONObj query() { return fromjson( "{a:{$in:[0,1]},b:{$gt:5},c:2}" ); } BSONObj order() { return BSON( "b" << 1 << "c" << 1 ); } }; class RangeRange : public False { BSONObj index() { return BSON( "a" << 1 << "b" << 1 << "c" << 1 ); } BSONObj query() { return fromjson( "{a:{$in:[0,1]},b:{$gt:5},c:{$gt:2}}" ); } BSONObj order() { return BSON( "b" << 1 << "c" << 1 ); } }; class Unsatisfiable : public False { BSONObj index() { return BSON( "a" << 1 ); } BSONObj query() { return fromjson( "{a:{$gt:0,$lt:0}}" ); } BSONObj order() { return BSON( "a" << 1 ); } }; class EqualityUnsatisfiable : public False { BSONObj index() { return BSON( "a" << 1 << "b" << 1 ); } BSONObj query() { return fromjson( "{a:{$in:[0,1]},b:{$gt:0,$lt:0}}" ); } BSONObj order() { return BSON( "b" << 1 ); } }; } // namespace QueryBoundsExactOrderSuffix /** Checks related to 'special' QueryPlans. */ class Special : public Base { public: void run() { int idx = INDEXNO( "a" << "2d" ); BSONObj query = fromjson( "{ a:{ $near:[ 50, 50 ] } }" ); FieldRangeSetPair frsp( ns(), query ); scoped_ptr plan( QueryPlan::make( nsd(), idx, frsp, FRSP2( query ), query, BSONObj(), shared_ptr(), BSONObj(), BSONObj(), "2d")); // A 'special' plan is not optimal. ASSERT_EQUALS( QueryPlan::Helpful, plan->utility() ); } }; } // namespace QueryPlanTests class All : public Suite { public: All() : Suite( "queryoptimizer" ) {} void setupTests() { add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); } } myall; } // namespace QueryOptimizerTests