summaryrefslogtreecommitdiff
path: root/src/mongo/dbtests/queryoptimizertests.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/dbtests/queryoptimizertests.cpp')
-rw-r--r--src/mongo/dbtests/queryoptimizertests.cpp1063
1 files changed, 1063 insertions, 0 deletions
diff --git a/src/mongo/dbtests/queryoptimizertests.cpp b/src/mongo/dbtests/queryoptimizertests.cpp
new file mode 100644
index 00000000000..8da13578b45
--- /dev/null
+++ b/src/mongo/dbtests/queryoptimizertests.cpp
@@ -0,0 +1,1063 @@
+// queryoptimizertests.cpp : query optimizer unit tests
+//
+
+/**
+ * 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 "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 "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 QueryOptimizerTests {
+
+ void dropCollection( const char *ns ) {
+ string errmsg;
+ BSONObjBuilder result;
+ dropCollection( ns, errmsg, result );
+ }
+
+ namespace QueryPlanTests {
+
+ using boost::shared_ptr;
+
+ class Base {
+ public:
+ Base() : _ctx( ns() ) , indexNum_( 0 ) {
+ string err;
+ userCreateNS( ns(), BSONObj(), err, false );
+ }
+ ~Base() {
+ if ( !nsd() )
+ return;
+ 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() );
+ NamespaceDetails *d = nsd();
+ for( int i = 0; i < d->nIndexes; ++i ) {
+ if ( d->idx(i).keyPattern() == key /*indexName() == name*/ || ( d->idx(i).isIdIndex() && IndexDetails::isIdIndexPattern( key ) ) )
+ return &d->idx(i);
+ }
+ assert( false );
+ return 0;
+ }
+ int indexno( const BSONObj &key ) {
+ return nsd()->idxNo( *index(key) );
+ }
+ BSONObj startKey( const QueryPlan &p ) const {
+ return p.frv()->startKey();
+ }
+ BSONObj endKey( const QueryPlan &p ) const {
+ return p.frv()->endKey();
+ }
+ private:
+ dblock 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() {
+ QueryPlan p( nsd(), -1, FRSP( BSONObj() ), FRSP2( BSONObj() ), BSONObj(), BSONObj() );
+ ASSERT( !p.optimal() );
+ ASSERT( !p.scanAndOrderRequired() );
+ ASSERT( !p.exactKeyMatch() );
+ }
+ };
+
+ class SimpleOrder : public Base {
+ public:
+ void run() {
+ BSONObjBuilder b;
+ b.appendMinKey( "" );
+ BSONObj start = b.obj();
+ BSONObjBuilder b2;
+ b2.appendMaxKey( "" );
+ BSONObj end = b2.obj();
+
+ QueryPlan p( 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 ) );
+ QueryPlan p2( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FRSP( BSONObj() ), FRSP2( BSONObj() ), BSONObj(), BSON( "a" << 1 << "b" << 1 ) );
+ ASSERT( !p2.scanAndOrderRequired() );
+ QueryPlan p3( 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() {
+ QueryPlan p( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FRSP( BSONObj() ), FRSP2( BSONObj() ), BSONObj(), BSON( "a" << 1 ) );
+ ASSERT( !p.scanAndOrderRequired() );
+ }
+ };
+
+ class IndexSigns : public Base {
+ public:
+ void run() {
+ QueryPlan p( nsd(), INDEXNO( "a" << 1 << "b" << -1 ) , FRSP( BSONObj() ), FRSP2( BSONObj() ), BSONObj(), BSON( "a" << 1 << "b" << -1 ) );
+ ASSERT( !p.scanAndOrderRequired() );
+ ASSERT_EQUALS( 1, p.direction() );
+ QueryPlan p2( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FRSP( BSONObj() ), FRSP2( BSONObj() ), BSONObj(), BSON( "a" << 1 << "b" << -1 ) );
+ ASSERT( p2.scanAndOrderRequired() );
+ ASSERT_EQUALS( 0, p2.direction() );
+ QueryPlan p3( 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();
+ QueryPlan p( 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 ) );
+ QueryPlan p2( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FRSP( BSONObj() ), FRSP2( BSONObj() ), BSONObj(), BSON( "a" << -1 << "b" << -1 ) );
+ ASSERT( !p2.scanAndOrderRequired() );
+ ASSERT_EQUALS( -1, p2.direction() );
+ QueryPlan p3( 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();
+ QueryPlan p( 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 ) );
+ QueryPlan p2( 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() {
+ QueryPlan p( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FRSP( BSON( "a" << 4 ) ), FRSP2( BSON( "a" << 4 ) ), BSON( "a" << 4 ), BSON( "b" << 1 ) );
+ ASSERT( !p.scanAndOrderRequired() );
+ QueryPlan p2( 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() );
+ QueryPlan p3( 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() {
+ QueryPlan p( nsd(), INDEXNO( "a" << 1 ), FRSP( BSONObj() ), FRSP2( BSONObj() ), BSONObj(), BSON( "a" << 1 ) );
+ ASSERT( p.optimal() );
+ QueryPlan p2( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FRSP( BSONObj() ), FRSP2( BSONObj() ), BSONObj(), BSON( "a" << 1 ) );
+ ASSERT( p2.optimal() );
+ QueryPlan p3( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FRSP( BSON( "a" << 1 ) ), FRSP2( BSON( "a" << 1 ) ), BSON( "a" << 1 ), BSON( "a" << 1 ) );
+ ASSERT( p3.optimal() );
+ QueryPlan p4( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FRSP( BSON( "b" << 1 ) ), FRSP2( BSON( "b" << 1 ) ), BSON( "b" << 1 ), BSON( "a" << 1 ) );
+ ASSERT( !p4.optimal() );
+ QueryPlan p5( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FRSP( BSON( "a" << 1 ) ), FRSP2( BSON( "a" << 1 ) ), BSON( "a" << 1 ), BSON( "b" << 1 ) );
+ ASSERT( p5.optimal() );
+ QueryPlan p6( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FRSP( BSON( "b" << 1 ) ), FRSP2( BSON( "b" << 1 ) ), BSON( "b" << 1 ), BSON( "b" << 1 ) );
+ ASSERT( !p6.optimal() );
+ QueryPlan p7( 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( p7.optimal() );
+ QueryPlan p8( 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( p8.optimal() );
+ QueryPlan p9( 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( p9.optimal() );
+ }
+ };
+
+ class MoreOptimal : public Base {
+ public:
+ void run() {
+ QueryPlan p10( nsd(), INDEXNO( "a" << 1 << "b" << 1 << "c" << 1 ), FRSP( BSON( "a" << 1 ) ), FRSP2( BSON( "a" << 1 ) ), BSON( "a" << 1 ), BSONObj() );
+ ASSERT( p10.optimal() );
+ QueryPlan p11( 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( p11.optimal() );
+ QueryPlan p12( nsd(), INDEXNO( "a" << 1 << "b" << 1 << "c" << 1 ), FRSP( BSON( "a" << LT << 1 ) ), FRSP2( BSON( "a" << LT << 1 ) ), BSON( "a" << LT << 1 ), BSONObj() );
+ ASSERT( p12.optimal() );
+ QueryPlan p13( 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( p13.optimal() );
+ }
+ };
+
+ class KeyMatch : public Base {
+ public:
+ void run() {
+ QueryPlan p( nsd(), INDEXNO( "a" << 1 ), FRSP( BSONObj() ), FRSP2( BSONObj() ), BSONObj(), BSON( "a" << 1 ) );
+ ASSERT( !p.exactKeyMatch() );
+ QueryPlan p2( nsd(), INDEXNO( "b" << 1 << "a" << 1 ), FRSP( BSONObj() ), FRSP2( BSONObj() ), BSONObj(), BSON( "a" << 1 ) );
+ ASSERT( !p2.exactKeyMatch() );
+ QueryPlan p3( nsd(), INDEXNO( "b" << 1 << "a" << 1 ), FRSP( BSON( "b" << "z" ) ), FRSP2( BSON( "b" << "z" ) ), BSON( "b" << "z" ), BSON( "a" << 1 ) );
+ ASSERT( !p3.exactKeyMatch() );
+ QueryPlan p4( nsd(), INDEXNO( "b" << 1 << "a" << 1 << "c" << 1 ), FRSP( BSON( "c" << "y" << "b" << "z" ) ), FRSP2( BSON( "c" << "y" << "b" << "z" ) ), BSON( "c" << "y" << "b" << "z" ), BSON( "a" << 1 ) );
+ ASSERT( !p4.exactKeyMatch() );
+ QueryPlan p5( nsd(), INDEXNO( "b" << 1 << "a" << 1 << "c" << 1 ), FRSP( BSON( "c" << "y" << "b" << "z" ) ), FRSP2( BSON( "c" << "y" << "b" << "z" ) ), BSON( "c" << "y" << "b" << "z" ), BSONObj() );
+ ASSERT( !p5.exactKeyMatch() );
+ QueryPlan p6( nsd(), INDEXNO( "b" << 1 << "a" << 1 << "c" << 1 ), FRSP( BSON( "c" << LT << "y" << "b" << GT << "z" ) ), FRSP2( BSON( "c" << LT << "y" << "b" << GT << "z" ) ), BSON( "c" << LT << "y" << "b" << GT << "z" ), BSONObj() );
+ ASSERT( !p6.exactKeyMatch() );
+ QueryPlan p7( nsd(), INDEXNO( "b" << 1 ), FRSP( BSONObj() ), FRSP2( BSONObj() ), BSONObj(), BSON( "a" << 1 ) );
+ ASSERT( !p7.exactKeyMatch() );
+ QueryPlan p8( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FRSP( BSON( "b" << "y" << "a" << "z" ) ), FRSP2( BSON( "b" << "y" << "a" << "z" ) ), BSON( "b" << "y" << "a" << "z" ), BSONObj() );
+ ASSERT( p8.exactKeyMatch() );
+ QueryPlan p9( nsd(), INDEXNO( "a" << 1 ), FRSP( BSON( "a" << "z" ) ), FRSP2( BSON( "a" << "z" ) ), BSON( "a" << "z" ), BSON( "a" << 1 ) );
+ ASSERT( p9.exactKeyMatch() );
+ }
+ };
+
+ class MoreKeyMatch : public Base {
+ public:
+ void run() {
+ QueryPlan p( nsd(), INDEXNO( "a" << 1 ), FRSP( BSON( "a" << "r" << "b" << NE << "q" ) ), FRSP2( BSON( "a" << "r" << "b" << NE << "q" ) ), BSON( "a" << "r" << "b" << NE << "q" ), BSON( "a" << 1 ) );
+ ASSERT( !p.exactKeyMatch() );
+ }
+ };
+
+ class ExactKeyQueryTypes : public Base {
+ public:
+ void run() {
+ QueryPlan p( nsd(), INDEXNO( "a" << 1 ), FRSP( BSON( "a" << "b" ) ), FRSP2( BSON( "a" << "b" ) ), BSON( "a" << "b" ), BSONObj() );
+ ASSERT( p.exactKeyMatch() );
+ QueryPlan p2( nsd(), INDEXNO( "a" << 1 ), FRSP( BSON( "a" << 4 ) ), FRSP2( BSON( "a" << 4 ) ), BSON( "a" << 4 ), BSONObj() );
+ ASSERT( !p2.exactKeyMatch() );
+ QueryPlan p3( nsd(), INDEXNO( "a" << 1 ), FRSP( BSON( "a" << BSON( "c" << "d" ) ) ), FRSP2( BSON( "a" << BSON( "c" << "d" ) ) ), BSON( "a" << BSON( "c" << "d" ) ), BSONObj() );
+ ASSERT( !p3.exactKeyMatch() );
+ BSONObjBuilder b;
+ b.appendRegex( "a", "^ddd" );
+ BSONObj q = b.obj();
+ QueryPlan p4( nsd(), INDEXNO( "a" << 1 ), FRSP( q ), FRSP2( q ), q, BSONObj() );
+ ASSERT( !p4.exactKeyMatch() );
+ QueryPlan p5( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FRSP( BSON( "a" << "z" << "b" << 4 ) ), FRSP2( BSON( "a" << "z" << "b" << 4 ) ), BSON( "a" << "z" << "b" << 4 ), BSONObj() );
+ ASSERT( !p5.exactKeyMatch() );
+ }
+ };
+
+ class Unhelpful : public Base {
+ public:
+ void run() {
+ QueryPlan p( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FRSP( BSON( "b" << 1 ) ), FRSP2( BSON( "b" << 1 ) ), BSON( "b" << 1 ), BSONObj() );
+ ASSERT( !p.range( "a" ).nontrivial() );
+ ASSERT( p.unhelpful() );
+ QueryPlan p2( 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.range( "a" ).nontrivial() );
+ ASSERT( !p2.unhelpful() );
+ QueryPlan p3( nsd(), INDEXNO( "b" << 1 ), FRSP( BSON( "b" << 1 << "c" << 1 ) ), FRSP2( BSON( "b" << 1 << "c" << 1 ) ), BSON( "b" << 1 << "c" << 1 ), BSONObj() );
+ ASSERT( p3.range( "b" ).nontrivial() );
+ ASSERT( !p3.unhelpful() );
+ QueryPlan p4( 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.range( "b" ).nontrivial() );
+ ASSERT( p4.unhelpful() );
+ }
+ };
+
+ } // 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() );
+ }
+ 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());
+ }
+ protected:
+ static const char *ns() { return "unittests.QueryPlanSetTests"; }
+ static NamespaceDetails *nsd() { return nsdetails( ns() ); }
+ private:
+ dblock lk_;
+ Client::Context _context;
+ };
+
+ class NoIndexes : public Base {
+ public:
+ void run() {
+ auto_ptr< FieldRangeSetPair > frsp( new FieldRangeSetPair( ns(), BSON( "a" << 4 ) ) );
+ auto_ptr< FieldRangeSetPair > frspOrig( new FieldRangeSetPair( *frsp ) );
+ QueryPlanSet s( ns(), frsp, frspOrig, BSON( "a" << 4 ), BSON( "b" << 1 ) );
+ ASSERT_EQUALS( 1, s.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" );
+ auto_ptr< FieldRangeSetPair > frsp( new FieldRangeSetPair( ns(), BSON( "a" << 4 ) ) );
+ auto_ptr< FieldRangeSetPair > frspOrig( new FieldRangeSetPair( *frsp ) );
+ QueryPlanSet s( ns(), frsp, frspOrig, BSON( "a" << 4 ), BSONObj() );
+ ASSERT_EQUALS( 1, s.nPlans() );
+ }
+ };
+
+ 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" );
+ auto_ptr< FieldRangeSetPair > frsp( new FieldRangeSetPair( ns(), BSON( "a" << 4 ) ) );
+ auto_ptr< FieldRangeSetPair > frspOrig( new FieldRangeSetPair( *frsp ) );
+ QueryPlanSet s( ns(), frsp, frspOrig, BSON( "a" << 4 ), BSON( "b" << 1 ) );
+ ASSERT_EQUALS( 3, s.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" );
+ auto_ptr< FieldRangeSetPair > frsp( new FieldRangeSetPair( ns(), BSONObj() ) );
+ auto_ptr< FieldRangeSetPair > frspOrig( new FieldRangeSetPair( *frsp ) );
+ QueryPlanSet s( ns(), frsp, frspOrig, BSONObj(), BSONObj() );
+ ASSERT_EQUALS( 1, s.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" );
+ BSONObj b = BSON( "hint" << BSON( "a" << 1 ) );
+ BSONElement e = b.firstElement();
+ auto_ptr< FieldRangeSetPair > frsp( new FieldRangeSetPair( ns(), BSON( "a" << 1 ) ) );
+ auto_ptr< FieldRangeSetPair > frspOrig( new FieldRangeSetPair( *frsp ) );
+ QueryPlanSet s( ns(), frsp, frspOrig, BSON( "a" << 1 ), BSON( "b" << 1 ), true, &e );
+ ASSERT_EQUALS( 1, s.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" );
+ BSONObj b = BSON( "hint" << "a_1" );
+ BSONElement e = b.firstElement();
+ auto_ptr< FieldRangeSetPair > frsp( new FieldRangeSetPair( ns(), BSON( "a" << 1 ) ) );
+ auto_ptr< FieldRangeSetPair > frspOrig( new FieldRangeSetPair( *frsp ) );
+ QueryPlanSet s( ns(), frsp, frspOrig, BSON( "a" << 1 ), BSON( "b" << 1 ), true, &e );
+ ASSERT_EQUALS( 1, s.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" );
+ BSONObj b = BSON( "hint" << BSON( "$natural" << 1 ) );
+ BSONElement e = b.firstElement();
+ auto_ptr< FieldRangeSetPair > frsp( new FieldRangeSetPair( ns(), BSON( "a" << 1 ) ) );
+ auto_ptr< FieldRangeSetPair > frspOrig( new FieldRangeSetPair( *frsp ) );
+ QueryPlanSet s( ns(), frsp, frspOrig, BSON( "a" << 1 ), BSON( "b" << 1 ), true, &e );
+ ASSERT_EQUALS( 1, s.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" );
+ auto_ptr< FieldRangeSetPair > frsp( new FieldRangeSetPair( ns(), BSON( "a" << 1 ) ) );
+ auto_ptr< FieldRangeSetPair > frspOrig( new FieldRangeSetPair( *frsp ) );
+ QueryPlanSet s( ns(), frsp, frspOrig, BSON( "a" << 1 ), BSON( "$natural" << 1 ) );
+ ASSERT_EQUALS( 1, s.nPlans() );
+ }
+ };
+
+ class BadHint : public Base {
+ public:
+ void run() {
+ BSONObj b = BSON( "hint" << "a_1" );
+ BSONElement e = b.firstElement();
+ auto_ptr< FieldRangeSetPair > frsp( new FieldRangeSetPair( ns(), BSON( "a" << 1 ) ) );
+ auto_ptr< FieldRangeSetPair > frspOrig( new FieldRangeSetPair( *frsp ) );
+ ASSERT_THROWS( QueryPlanSet s( ns(), frsp, frspOrig, BSON( "a" << 1 ), BSON( "b" << 1 ), true, &e ),
+ 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;
+ ASSERT_EQUALS( 0, runCount( ns(), BSON( "query" << BSON( "a" << 4 ) ), err ) );
+ 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 ) );
+ theDataFileMgr.insertWithObjMod( ns(), fourA );
+ ASSERT_EQUALS( 1, runCount( ns(), BSON( "query" << BSON( "a" << 4 ) ), err ) );
+ theDataFileMgr.insertWithObjMod( ns(), fourB );
+ ASSERT_EQUALS( 2, runCount( ns(), BSON( "query" << BSON( "a" << 4 ) ), err ) );
+ ASSERT_EQUALS( 3, runCount( ns(), BSON( "query" << BSONObj() ), err ) );
+ ASSERT_EQUALS( 3, runCount( ns(), BSON( "query" << BSON( "a" << GT << 0 ) ), err ) );
+ // missing ns
+ ASSERT_EQUALS( -1, runCount( "unittests.missingNS", BSONObj(), err ) );
+ // impossible match
+ ASSERT_EQUALS( 0, runCount( ns(), BSON( "query" << BSON( "a" << GT << 0 << LT << -1 ) ), err ) );
+ }
+ };
+
+ 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" );
+ auto_ptr< FieldRangeSetPair > frsp( new FieldRangeSetPair( ns(), BSON( "a" << 1 << "c" << 2 ) ) );
+ auto_ptr< FieldRangeSetPair > frspOrig( new FieldRangeSetPair( *frsp ) );
+ QueryPlanSet s( ns(), frsp, frspOrig, BSON( "a" << 1 << "c" << 2 ), BSONObj() );
+ ASSERT_EQUALS( 2, s.nPlans() );
+ }
+ };
+
+ class SingleException : public Base {
+ public:
+ void run() {
+ Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" );
+ Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" );
+ auto_ptr< FieldRangeSetPair > frsp( new FieldRangeSetPair( ns(), BSON( "a" << 4 ) ) );
+ auto_ptr< FieldRangeSetPair > frspOrig( new FieldRangeSetPair( *frsp ) );
+ QueryPlanSet s( ns(), frsp, frspOrig, BSON( "a" << 4 ), BSON( "b" << 1 ) );
+ ASSERT_EQUALS( 3, s.nPlans() );
+ bool threw = false;
+ auto_ptr< TestOp > t( new TestOp( true, threw ) );
+ boost::shared_ptr< TestOp > done = s.runOp( *t );
+ ASSERT( threw );
+ ASSERT( done->complete() );
+ ASSERT( done->exception().empty() );
+ ASSERT( !done->error() );
+ }
+ private:
+ class TestOp : public QueryOp {
+ public:
+ TestOp( bool iThrow, bool &threw ) : iThrow_( iThrow ), threw_( threw ), i_(), youThrow_( false ) {}
+ virtual void _init() {}
+ virtual void next() {
+ if ( iThrow_ )
+ threw_ = true;
+ massert( 10408 , "throw", !iThrow_ );
+ if ( ++i_ > 10 )
+ setComplete();
+ }
+ virtual QueryOp *_createChild() const {
+ QueryOp *op = new TestOp( youThrow_, threw_ );
+ youThrow_ = !youThrow_;
+ return op;
+ }
+ virtual bool mayRecordPlan() const { return true; }
+ virtual long long nscanned() { return 0; }
+ private:
+ bool iThrow_;
+ bool &threw_;
+ int i_;
+ mutable bool youThrow_;
+ };
+ };
+
+ class AllException : public Base {
+ public:
+ void run() {
+ Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" );
+ Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" );
+ auto_ptr< FieldRangeSetPair > frsp( new FieldRangeSetPair( ns(), BSON( "a" << 4 ) ) );
+ auto_ptr< FieldRangeSetPair > frspOrig( new FieldRangeSetPair( *frsp ) );
+ QueryPlanSet s( ns(), frsp, frspOrig, BSON( "a" << 4 ), BSON( "b" << 1 ) );
+ ASSERT_EQUALS( 3, s.nPlans() );
+ auto_ptr< TestOp > t( new TestOp() );
+ boost::shared_ptr< TestOp > done = s.runOp( *t );
+ ASSERT( !done->complete() );
+ ASSERT_EQUALS( "throw", done->exception().msg );
+ ASSERT( done->error() );
+ }
+ private:
+ class TestOp : public QueryOp {
+ public:
+ virtual void _init() {}
+ virtual void next() {
+ massert( 10409 , "throw", false );
+ }
+ virtual QueryOp *_createChild() const {
+ return new TestOp();
+ }
+ virtual bool mayRecordPlan() const { return true; }
+ virtual long long nscanned() { return 0; }
+ };
+ };
+
+ class SaveGoodIndex : public Base {
+ public:
+ void run() {
+ Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" );
+ Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" );
+ // No best plan - all must be tried.
+ nPlans( 3 );
+ runQuery();
+ // Best plan selected by query.
+ nPlans( 1 );
+ nPlans( 1 );
+ Helpers::ensureIndex( ns(), BSON( "c" << 1 ), false, "c_1" );
+ // Best plan cleared when new index added.
+ nPlans( 3 );
+ runQuery();
+ // Best plan selected by query.
+ nPlans( 1 );
+
+ {
+ DBDirectClient client;
+ for( int i = 0; i < 334; ++i ) {
+ client.insert( ns(), BSON( "i" << i ) );
+ client.update( ns(), QUERY( "i" << i ), BSON( "i" << i + 1 ) );
+ client.remove( ns(), BSON( "i" << i + 1 ) );
+ }
+ }
+ // Best plan cleared by ~1000 writes.
+ nPlans( 3 );
+
+ auto_ptr< FieldRangeSetPair > frsp( new FieldRangeSetPair( ns(), BSON( "a" << 4 ) ) );
+ auto_ptr< FieldRangeSetPair > frspOrig( new FieldRangeSetPair( *frsp ) );
+ QueryPlanSet s( ns(), frsp, frspOrig, BSON( "a" << 4 ), BSON( "b" << 1 ) );
+ NoRecordTestOp original;
+ s.runOp( original );
+ // NoRecordTestOp doesn't record a best plan (test cases where mayRecordPlan() is false).
+ nPlans( 3 );
+
+ BSONObj hint = fromjson( "{hint:{$natural:1}}" );
+ BSONElement hintElt = hint.firstElement();
+ auto_ptr< FieldRangeSetPair > frsp2( new FieldRangeSetPair( ns(), BSON( "a" << 4 ) ) );
+ auto_ptr< FieldRangeSetPair > frspOrig2( new FieldRangeSetPair( *frsp2 ) );
+ QueryPlanSet s2( ns(), frsp2, frspOrig2, BSON( "a" << 4 ), BSON( "b" << 1 ), true, &hintElt );
+ TestOp newOriginal;
+ s2.runOp( newOriginal );
+ // No plan recorded when a hint is used.
+ nPlans( 3 );
+
+ auto_ptr< FieldRangeSetPair > frsp3( new FieldRangeSetPair( ns(), BSON( "a" << 4 ), true ) );
+ auto_ptr< FieldRangeSetPair > frspOrig3( new FieldRangeSetPair( *frsp3 ) );
+ QueryPlanSet s3( ns(), frsp3, frspOrig3, BSON( "a" << 4 ), BSON( "b" << 1 << "c" << 1 ) );
+ TestOp newerOriginal;
+ s3.runOp( newerOriginal );
+ // Plan recorded was for a different query pattern (different sort spec).
+ nPlans( 3 );
+
+ // Best plan still selected by query after all these other tests.
+ runQuery();
+ nPlans( 1 );
+ }
+ private:
+ void nPlans( int n ) {
+ auto_ptr< FieldRangeSetPair > frsp( new FieldRangeSetPair( ns(), BSON( "a" << 4 ) ) );
+ auto_ptr< FieldRangeSetPair > frspOrig( new FieldRangeSetPair( *frsp ) );
+ QueryPlanSet s( ns(), frsp, frspOrig, BSON( "a" << 4 ), BSON( "b" << 1 ) );
+ ASSERT_EQUALS( n, s.nPlans() );
+ }
+ void runQuery() {
+ auto_ptr< FieldRangeSetPair > frsp( new FieldRangeSetPair( ns(), BSON( "a" << 4 ) ) );
+ auto_ptr< FieldRangeSetPair > frspOrig( new FieldRangeSetPair( *frsp ) );
+ QueryPlanSet s( ns(), frsp, frspOrig, BSON( "a" << 4 ), BSON( "b" << 1 ) );
+ TestOp original;
+ s.runOp( original );
+ }
+ class TestOp : public QueryOp {
+ public:
+ virtual void _init() {}
+ virtual void next() {
+ setComplete();
+ }
+ virtual QueryOp *_createChild() const {
+ return new TestOp();
+ }
+ virtual bool mayRecordPlan() const { return true; }
+ virtual long long nscanned() { return 0; }
+ };
+ class NoRecordTestOp : public TestOp {
+ virtual bool mayRecordPlan() const { return false; }
+ virtual QueryOp *_createChild() const { return new NoRecordTestOp(); }
+ };
+ };
+
+ class TryAllPlansOnErr : public Base {
+ public:
+ void run() {
+ Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" );
+
+ auto_ptr< FieldRangeSetPair > frsp( new FieldRangeSetPair( ns(), BSON( "a" << 4 ) ) );
+ auto_ptr< FieldRangeSetPair > frspOrig( new FieldRangeSetPair( *frsp ) );
+ QueryPlanSet s( ns(), frsp, frspOrig, BSON( "a" << 4 ), BSON( "b" << 1 ) );
+ ScanOnlyTestOp op;
+ s.runOp( op );
+ pair< BSONObj, long long > best = QueryUtilIndexed::bestIndexForPatterns( s.frsp(), BSON( "b" << 1 ) );
+ ASSERT( fromjson( "{$natural:1}" ).woCompare( best.first ) == 0 );
+ ASSERT_EQUALS( 1, best.second );
+
+ auto_ptr< FieldRangeSetPair > frsp2( new FieldRangeSetPair( ns(), BSON( "a" << 4 ) ) );
+ auto_ptr< FieldRangeSetPair > frspOrig2( new FieldRangeSetPair( *frsp2 ) );
+ QueryPlanSet s2( ns(), frsp2, frspOrig2, BSON( "a" << 4 ), BSON( "b" << 1 ) );
+ TestOp op2;
+ ASSERT( s2.runOp( op2 )->complete() );
+ }
+ private:
+ class TestOp : public QueryOp {
+ public:
+ TestOp() {}
+ virtual void _init() {}
+ virtual void next() {
+ if ( qp().indexKey().firstElementFieldName() == string( "$natural" ) )
+ massert( 10410 , "throw", false );
+ setComplete();
+ }
+ virtual QueryOp *_createChild() const {
+ return new TestOp();
+ }
+ virtual bool mayRecordPlan() const { return true; }
+ virtual long long nscanned() { return 1; }
+ };
+ class ScanOnlyTestOp : public TestOp {
+ virtual void next() {
+ if ( qp().indexKey().firstElement().fieldName() == string( "$natural" ) )
+ setComplete();
+ massert( 10411 , "throw", false );
+ }
+ virtual QueryOp *_createChild() const {
+ return new ScanOnlyTestOp();
+ }
+ };
+ };
+
+ 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 );
+ ASSERT( BSON( "a" << 1 ).woCompare( NamespaceDetailsTransient::get_inlock( ns() ).indexForPattern( FieldRangeSet( ns(), delSpec, true ).pattern() ) ) == 0 );
+ ASSERT_EQUALS( 1, NamespaceDetailsTransient::get_inlock( ns() ).nScannedForPattern( FieldRangeSet( ns(), delSpec, true ).pattern() ) );
+ }
+ };
+
+ 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 TryOtherPlansBeforeFinish : public Base {
+ public:
+ void run() {
+ Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" );
+ for( int i = 0; i < 100; ++i ) {
+ for( int j = 0; j < 2; ++j ) {
+ BSONObj temp = BSON( "a" << 100 - i - 1 << "b" << i );
+ theDataFileMgr.insertWithObjMod( ns(), temp );
+ }
+ }
+ Message m;
+ // Need to return at least 2 records to cause plan to be recorded.
+ assembleRequest( ns(), QUERY( "b" << 0 << "a" << GTE << 0 ).obj, 2, 0, 0, 0, m );
+ stringstream ss;
+ {
+ DbMessage d(m);
+ QueryMessage q(d);
+ runQuery( m, q);
+ }
+ ASSERT( BSON( "$natural" << 1 ).woCompare( NamespaceDetailsTransient::get_inlock( ns() ).indexForPattern( FieldRangeSet( ns(), BSON( "b" << 0 << "a" << GTE << 0 ), true ).pattern() ) ) == 0 );
+
+ Message m2;
+ assembleRequest( ns(), QUERY( "b" << 99 << "a" << GTE << 0 ).obj, 2, 0, 0, 0, m2 );
+ {
+ DbMessage d(m2);
+ QueryMessage q(d);
+ runQuery( m2, q);
+ }
+ ASSERT( BSON( "a" << 1 ).woCompare( NamespaceDetailsTransient::get_inlock( ns() ).indexForPattern( FieldRangeSet( ns(), BSON( "b" << 0 << "a" << GTE << 0 ), true ).pattern() ) ) == 0 );
+ ASSERT_EQUALS( 3, NamespaceDetailsTransient::get_inlock( ns() ).nScannedForPattern( FieldRangeSet( ns(), BSON( "b" << 0 << "a" << GTE << 0 ), true ).pattern() ) );
+ }
+ };
+
+ 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 hint = fromjson( "{$hint:{a:1}}" );
+ BSONElement hintElt = hint.firstElement();
+ auto_ptr< FieldRangeSetPair > frsp( new FieldRangeSetPair( ns(), fromjson( "{a:{$in:[2,3,6,9,11]}}" ) ) );
+ auto_ptr< FieldRangeSetPair > frspOrig( new FieldRangeSetPair( *frsp ) );
+ QueryPlanSet s( ns(), frsp, frspOrig, fromjson( "{a:{$in:[2,3,6,9,11]}}" ), BSONObj(), true, &hintElt );
+ QueryPlan qp( nsd(), 1, s.frsp(), s.originalFrsp(), fromjson( "{a:{$in:[2,3,6,9,11]}}" ), BSONObj() );
+ 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
+ {
+ auto_ptr< FieldRangeSetPair > frsp( new FieldRangeSetPair( ns(), fromjson( "{a:{$in:[2,3,6,9,11]}}" ) ) );
+ auto_ptr< FieldRangeSetPair > frspOrig( new FieldRangeSetPair( *frsp ) );
+ QueryPlanSet s( ns(), frsp, frspOrig, fromjson( "{a:{$in:[2,3,6,9,11]}}" ), BSON( "a" << -1 ), true, &hintElt );
+ QueryPlan qp( nsd(), 1, s.frsp(), s.originalFrsp(), fromjson( "{a:{$in:[2,3,6,9,11]}}" ), BSON( "a" << -1 ) );
+ 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 );
+ }
+ BSONObj hint = fromjson( "{$hint:{a:1,b:1}}" );
+ auto_ptr< FieldRangeSetPair > frsp( new FieldRangeSetPair( ns(), fromjson( "{a:5,b:{$in:[2,3,6,9,11]}}" ) ) );
+ QueryPlan qp( 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 );
+ }
+ BSONObj hint = fromjson( "{$hint:{a:1,b:1}}" );
+ auto_ptr< FieldRangeSetPair > frsp( new FieldRangeSetPair( ns(), fromjson( "{a:{$gte:5},b:{$in:[2,3,6,9,11]}}" ) ) );
+ QueryPlan qp( 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" );
+ BSONObj query = BSON( "a" << BSON_ARRAY( 0 << 0 ) << "b" << 1 );
+ auto_ptr< FieldRangeSetPair > frsp( new FieldRangeSetPair( ns(), query ) );
+ auto_ptr< FieldRangeSetPair > frspOrig( new FieldRangeSetPair( *frsp ) );
+ QueryPlanSet s( ns(), frsp, frspOrig, query, BSONObj() );
+ // 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" );
+ BSONObj query = BSON( "a" << BSON_ARRAY( 0 << 0 ) << "b" << 1 );
+ auto_ptr< FieldRangeSetPair > frsp( new FieldRangeSetPair( ns(), query ) );
+ auto_ptr< FieldRangeSetPair > frspOrig( new FieldRangeSetPair( *frsp ) );
+ QueryPlanSet s( ns(), frsp, frspOrig, query, BSONObj() );
+ // Single query plan.
+ ASSERT_EQUALS( 1, s.nPlans() );
+ // It's the geo plan.
+ ASSERT( !s.firstPlan()->special().empty() );
+ }
+ };
+
+ } // 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() ); }
+ private:
+ dblock lk_;
+ Client::Context _ctx;
+ };
+
+ 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 = bestGuessCursor( ns(), BSON( "b" << 1 ), BSON( "a" << 1 ) );
+ ASSERT_EQUALS( string( "a" ), c->indexKeyPattern().firstElement().fieldName() );
+ c = bestGuessCursor( ns(), BSON( "a" << 1 ), BSON( "b" << 1 ) );
+ ASSERT_EQUALS( string( "b" ), c->indexKeyPattern().firstElementFieldName() );
+ boost::shared_ptr< MultiCursor > m = dynamic_pointer_cast< MultiCursor >( bestGuessCursor( ns(), fromjson( "{b:1,$or:[{z:1}]}" ), BSON( "a" << 1 ) ) );
+ ASSERT_EQUALS( string( "a" ), m->sub_c()->indexKeyPattern().firstElement().fieldName() );
+ m = dynamic_pointer_cast< MultiCursor >( bestGuessCursor( ns(), fromjson( "{a:1,$or:[{y:1}]}" ), BSON( "b" << 1 ) ) );
+ ASSERT_EQUALS( string( "b" ), m->sub_c()->indexKeyPattern().firstElementFieldName() );
+
+ FieldRangeSet frs( "ns", BSON( "a" << 1 ), true );
+ {
+ SimpleMutex::scoped_lock lk(NamespaceDetailsTransient::_qcMutex);
+ NamespaceDetailsTransient::get_inlock( ns() ).registerIndexForPattern( frs.pattern( BSON( "b" << 1 ) ), BSON( "a" << 1 ), 0 );
+ }
+ m = dynamic_pointer_cast< MultiCursor >( bestGuessCursor( ns(), fromjson( "{a:1,$or:[{y:1}]}" ), BSON( "b" << 1 ) ) );
+ ASSERT_EQUALS( string( "b" ), m->sub_c()->indexKeyPattern().firstElement().fieldName() );
+ }
+ };
+
+ class BestGuessOrSortAssertion : public Base {
+ public:
+ void run() {
+ ASSERT_THROWS( bestGuessCursor( ns(), BSON( "$or" << BSON_ARRAY( BSON( "b" << 1 ) ) ), BSON( "a" << 1 ) ), MsgAssertionException );
+ }
+ };
+
+ class All : public Suite {
+ public:
+ All() : Suite( "queryoptimizer" ) {}
+
+ void setupTests() {
+ __forceLinkGeoPlugin();
+ add<QueryPlanTests::NoIndex>();
+ add<QueryPlanTests::SimpleOrder>();
+ add<QueryPlanTests::MoreIndexThanNeeded>();
+ add<QueryPlanTests::IndexSigns>();
+ add<QueryPlanTests::IndexReverse>();
+ add<QueryPlanTests::NoOrder>();
+ add<QueryPlanTests::EqualWithOrder>();
+ add<QueryPlanTests::Optimal>();
+ add<QueryPlanTests::MoreOptimal>();
+ add<QueryPlanTests::KeyMatch>();
+ add<QueryPlanTests::MoreKeyMatch>();
+ add<QueryPlanTests::ExactKeyQueryTypes>();
+ add<QueryPlanTests::Unhelpful>();
+ 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::SingleException>();
+ add<QueryPlanSetTests::AllException>();
+ add<QueryPlanSetTests::SaveGoodIndex>();
+ add<QueryPlanSetTests::TryAllPlansOnErr>();
+ add<QueryPlanSetTests::FindOne>();
+ add<QueryPlanSetTests::Delete>();
+ add<QueryPlanSetTests::DeleteOneScan>();
+ add<QueryPlanSetTests::DeleteOneIndex>();
+ add<QueryPlanSetTests::TryOtherPlansBeforeFinish>();
+ add<QueryPlanSetTests::InQueryIntervals>();
+ add<QueryPlanSetTests::EqualityThenIn>();
+ add<QueryPlanSetTests::NotEqualityThenIn>();
+ add<QueryPlanSetTests::ExcludeSpecialPlanWhenBtreePlan>();
+ add<QueryPlanSetTests::ExcludeUnindexedPlanWhenSpecialPlan>();
+ add<BestGuess>();
+ add<BestGuessOrSortAssertion>();
+ }
+ } myall;
+
+} // namespace QueryOptimizerTests
+