From 222ea643ad4d8484957bd4e91ae9ca996a73bba3 Mon Sep 17 00:00:00 2001 From: Aaron Date: Wed, 25 Feb 2009 10:48:41 -0500 Subject: [PATCH] Runner improvements --- db/jsobj.h | 2 +- db/queryoptimizer.cpp | 63 ++++++++++++++++++++++++--------- db/queryoptimizer.h | 6 ++++ dbtests/queryoptimizertests.cpp | 48 +++++++++++++++++++++++++ 4 files changed, 101 insertions(+), 18 deletions(-) diff --git a/db/jsobj.h b/db/jsobj.h index 09dcae502fb..b345626011c 100644 --- a/db/jsobj.h +++ b/db/jsobj.h @@ -820,7 +820,7 @@ namespace mongo { BSONObjBuilder& appendElements(BSONObj x); /** append element to the object we are building */ - void append(BSONElement& e) { + void append( const BSONElement& e) { assert( !e.eoo() ); // do not append eoo, that would corrupt us. the builder auto appends when done() is called. b.append((void*) e.rawdata(), e.size()); } diff --git a/db/queryoptimizer.cpp b/db/queryoptimizer.cpp index 932192a620c..5dc52b2a02a 100644 --- a/db/queryoptimizer.cpp +++ b/db/queryoptimizer.cpp @@ -142,37 +142,56 @@ namespace mongo { } QueryPlanSet::QueryPlanSet( const char *ns, const BSONObj &query, const BSONObj &order, const BSONElement *hint ) : - fbs_( ns, query ) { + fbs_( ns, query ), + mayRecordPlan_( true ), + usingPrerecordedPlan_( false ), + hint_( emptyObj ), + order_( order.copy() ) { + if ( hint && !hint->eoo() ) { + BSONObjBuilder b; + b.append( *hint ); + hint_ = b.obj(); + } + init(); + } + + void QueryPlanSet::init() { + mayRecordPlan_ = true; + usingPrerecordedPlan_ = false; + + const char *ns = fbs_.ns(); NamespaceDetails *d = nsdetails( ns ); if ( !d || !fbs_.matchPossible() ) { // Table scan plan, when no matches are possible - plans_.push_back( PlanPtr( new QueryPlan( fbs_, order ) ) ); + plans_.push_back( PlanPtr( new QueryPlan( fbs_, order_ ) ) ); return; } - if ( hint && !hint->eoo() ) { - if( hint->type() == String ) { - string hintstr = hint->valuestr(); + BSONElement hint = hint_.firstElement(); + if ( !hint.eoo() ) { + mayRecordPlan_ = false; + if( hint.type() == String ) { + string hintstr = hint.valuestr(); for (int i = 0; i < d->nIndexes; i++ ) { IndexDetails& ii = d->indexes[i]; if ( ii.indexName() == hintstr ) { - plans_.push_back( PlanPtr( new QueryPlan( fbs_, order, &ii ) ) ); + plans_.push_back( PlanPtr( new QueryPlan( fbs_, order_, &ii ) ) ); return; } } } - else if( hint->type() == Object ) { - BSONObj hintobj = hint->embeddedObject(); + else if( hint.type() == Object ) { + BSONObj hintobj = hint.embeddedObject(); uassert( "bad hint", !hintobj.isEmpty() ); if ( !strcmp( hintobj.firstElement().fieldName(), "$natural" ) ) { // Table scan plan - plans_.push_back( PlanPtr( new QueryPlan( fbs_, order ) ) ); + plans_.push_back( PlanPtr( new QueryPlan( fbs_, order_ ) ) ); return; } for (int i = 0; i < d->nIndexes; i++ ) { IndexDetails& ii = d->indexes[i]; if( ii.keyPattern().woCompare(hintobj) == 0 ) { - plans_.push_back( PlanPtr( new QueryPlan( fbs_, order, &ii ) ) ); + plans_.push_back( PlanPtr( new QueryPlan( fbs_, order_, &ii ) ) ); return; } } @@ -182,34 +201,36 @@ namespace mongo { BSONObj bestIndex = indexForPattern( ns, fbs_.pattern() ); if ( !bestIndex.isEmpty() ) { + usingPrerecordedPlan_ = true; if ( !strcmp( bestIndex.firstElement().fieldName(), "$natural" ) ) { // Table scan plan - plans_.push_back( PlanPtr( new QueryPlan( fbs_, order ) ) ); + plans_.push_back( PlanPtr( new QueryPlan( fbs_, order_ ) ) ); return; } for (int i = 0; i < d->nIndexes; i++ ) { IndexDetails& ii = d->indexes[i]; if( ii.keyPattern().woCompare(bestIndex) == 0 ) { - plans_.push_back( PlanPtr( new QueryPlan( fbs_, order, &ii ) ) ); + plans_.push_back( PlanPtr( new QueryPlan( fbs_, order_, &ii ) ) ); return; } } + assert( false ); } // Table scan plan - plans_.push_back( PlanPtr( new QueryPlan( fbs_, order ) ) ); + plans_.push_back( PlanPtr( new QueryPlan( fbs_, order_ ) ) ); // If table scan is optimal - if ( fbs_.nNontrivialBounds() == 0 && order.isEmpty() ) + if ( fbs_.nNontrivialBounds() == 0 && order_.isEmpty() ) return; // Only table scan can give natural order. - if ( !order.isEmpty() && !strcmp( order.firstElement().fieldName(), "$natural" ) ) + if ( !order_.isEmpty() && !strcmp( order_.firstElement().fieldName(), "$natural" ) ) return; PlanSet plans; for( int i = 0; i < d->nIndexes; ++i ) { - PlanPtr p( new QueryPlan( fbs_, order, &d->indexes[ i ] ) ); + PlanPtr p( new QueryPlan( fbs_, order_, &d->indexes[ i ] ) ); if ( p->optimal() ) { plans_.push_back( p ); return; @@ -222,6 +243,14 @@ namespace mongo { } shared_ptr< QueryOp > QueryPlanSet::runOp( QueryOp &op ) { + if ( usingPrerecordedPlan_ ) { + Runner r( *this, op ); + shared_ptr< QueryOp > res = r.run(); + if ( res->complete() ) + return res; + registerIndexForPattern( fbs_.ns(), fbs_.pattern(), BSONObj() ); + init(); + } Runner r( *this, op ); return r.run(); } @@ -257,7 +286,7 @@ namespace mongo { op.setExceptionMessage( "Caught unknown exception" ); } if ( op.complete() ) { - if ( op.mayRecordPlan() ) + if ( plans_.mayRecordPlan_ && op.mayRecordPlan() ) op.qp().registerSelf(); return *i; } diff --git a/db/queryoptimizer.h b/db/queryoptimizer.h index 4a60b5abde8..200c7914ffb 100644 --- a/db/queryoptimizer.h +++ b/db/queryoptimizer.h @@ -106,7 +106,9 @@ namespace mongo { shared_ptr< T > runOp( T &op ) { return dynamic_pointer_cast< T >( runOp( static_cast< QueryOp& >( op ) ) ); } + const FieldBoundSet &fbs() const { return fbs_; } private: + void init(); struct Runner { Runner( QueryPlanSet &plans, QueryOp &op ); shared_ptr< QueryOp > run(); @@ -117,6 +119,10 @@ namespace mongo { typedef boost::shared_ptr< QueryPlan > PlanPtr; typedef vector< PlanPtr > PlanSet; PlanSet plans_; + bool mayRecordPlan_; + bool usingPrerecordedPlan_; + BSONObj hint_; + BSONObj order_; }; } // namespace mongo diff --git a/dbtests/queryoptimizertests.cpp b/dbtests/queryoptimizertests.cpp index 8ad6ba13cf1..5ea12900d69 100644 --- a/dbtests/queryoptimizertests.cpp +++ b/dbtests/queryoptimizertests.cpp @@ -731,6 +731,13 @@ namespace QueryOptimizerTests { NoRecordTestOp original; s.runOp( original ); nPlans( 3 ); + + BSONObj hint = fromjson( "{hint:{$natural:1}}" ); + BSONElement hintElt = hint.firstElement(); + QueryPlanSet s2( ns(), BSON( "a" << 4 ), BSON( "b" << 1 ), &hintElt ); + TestOp newOriginal; + s2.runOp( newOriginal ); + nPlans( 3 ); runQuery(); nPlans( 1 ); @@ -762,6 +769,46 @@ namespace QueryOptimizerTests { }; }; + class TryAllPlansOnErr : public Base { + public: + void run() { + Helpers::ensureIndex( ns(), BSON( "a" << 1 ), "a_1" ); + + QueryPlanSet s( ns(), BSON( "a" << 4 ), emptyObj ); + ScanOnlyTestOp op; + s.runOp( op ); + ASSERT( !fromjson( "{$natural:1}" ).woCompare( indexForPattern( ns(), s.fbs().pattern() ) ) ); + + QueryPlanSet s2( ns(), BSON( "a" << 4 ), emptyObj ); + TestOp op2; + ASSERT( s2.runOp( op2 )->complete() ); + } + private: + class TestOp : public QueryOp { + public: + virtual void init() {} + virtual void next() { + if ( qp().indexKey().firstElement().fieldName() == string( "$natural" ) ) + massert( "throw", false ); + setComplete(); + } + virtual QueryOp *clone() const { + return new TestOp(); + } + virtual bool mayRecordPlan() const { return true; } + }; + class ScanOnlyTestOp : public TestOp { + virtual void next() { + if ( qp().indexKey().firstElement().fieldName() == string( "$natural" ) ) + setComplete(); + massert( "throw", false ); + } + virtual QueryOp *clone() const { + return new ScanOnlyTestOp(); + } + }; + }; + } // namespace QueryPlanSetTests class All : public UnitTest::Suite { @@ -810,6 +857,7 @@ namespace QueryOptimizerTests { add< QueryPlanSetTests::SingleException >(); add< QueryPlanSetTests::AllException >(); add< QueryPlanSetTests::SaveGoodIndex >(); + add< QueryPlanSetTests::TryAllPlansOnErr >(); } };