Files
mongo/db/queryoptimizer.cpp

303 lines
11 KiB
C++
Raw Normal View History

2008-11-15 19:04:01 -05:00
/* queryoptimizer.cpp */
2008-11-14 16:19:47 -05:00
/**
* Copyright (C) 2008 10gen Inc.
2008-12-28 20:28:49 -05:00
*
2008-11-14 16:19:47 -05:00
* 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.
2008-12-28 20:28:49 -05:00
*
2008-11-14 16:19:47 -05:00
* 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.
2008-12-28 20:28:49 -05:00
*
2008-11-14 16:19:47 -05:00
* 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 "stdafx.h"
2009-02-20 11:08:22 -05:00
#include "btree.h"
2008-11-14 16:19:47 -05:00
#include "pdfile.h"
#include "queryoptimizer.h"
2009-01-14 17:09:51 -05:00
namespace mongo {
2009-02-20 11:08:22 -05:00
QueryPlan::QueryPlan( const FieldBoundSet &fbs, const BSONObj &order, const IndexDetails *index ) :
fbs_( fbs ),
order_( order ),
index_( index ),
2009-02-17 20:57:36 -05:00
optimal_( false ),
scanAndOrderRequired_( true ),
keyMatch_( false ),
2009-02-19 13:58:22 -05:00
exactKeyMatch_( false ),
direction_( 0 ),
unhelpful_( false ) {
// full table scan case
2009-02-20 11:08:22 -05:00
if ( !index_ ) {
if ( order_.isEmpty() || !strcmp( order_.firstElement().fieldName(), "$natural" ) )
scanAndOrderRequired_ = false;
return;
}
2009-02-20 11:08:22 -05:00
BSONObj idxKey = index->keyPattern();
2009-02-17 20:57:36 -05:00
BSONObjIterator o( order );
BSONObjIterator k( idxKey );
2009-02-18 15:25:50 -05:00
if ( !o.more() )
scanAndOrderRequired_ = false;
2009-02-17 20:57:36 -05:00
while( o.more() ) {
BSONElement oe = o.next();
if ( oe.eoo() ) {
scanAndOrderRequired_ = false;
break;
}
2009-02-18 15:25:50 -05:00
if ( !k.more() )
2009-02-17 20:57:36 -05:00
break;
2009-02-18 15:25:50 -05:00
BSONElement ke;
while( 1 ) {
ke = k.next();
if ( ke.eoo() )
goto doneCheckOrder;
if ( strcmp( oe.fieldName(), ke.fieldName() ) == 0 )
break;
if ( !fbs.bound( ke.fieldName() ).equality() )
goto doneCheckOrder;
}
2009-02-17 20:57:36 -05:00
int d = oe.number() == ke.number() ? 1 : -1;
2009-02-19 13:58:22 -05:00
if ( direction_ == 0 )
direction_ = d;
else if ( direction_ != d )
break;
2009-02-17 20:57:36 -05:00
}
2009-02-18 15:25:50 -05:00
doneCheckOrder:
2009-02-19 13:58:22 -05:00
if ( scanAndOrderRequired_ )
direction_ = 0;
2009-02-18 15:25:50 -05:00
BSONObjIterator i( idxKey );
int indexedQueryCount = 0;
int exactIndexedQueryCount = 0;
2009-02-19 10:26:46 -05:00
int optimalIndexedQueryCount = 0;
bool stillOptimalIndexedQueryCount = true;
2009-02-18 15:25:50 -05:00
set< string > orderFieldsUnindexed;
order.getFieldNames( orderFieldsUnindexed );
BSONObjBuilder startKeyBuilder;
BSONObjBuilder endKeyBuilder;
2009-02-18 15:25:50 -05:00
while( i.more() ) {
BSONElement e = i.next();
if ( e.eoo() )
break;
const FieldBound &fb = fbs.bound( e.fieldName() );
int number = (int) e.number(); // returns 0.0 if not numeric
bool forward = ( ( number >= 0 ? 1 : -1 ) * ( direction_ >= 0 ? 1 : -1 ) > 0 );
startKeyBuilder.appendAs( forward ? fb.lower() : fb.upper(), "" );
endKeyBuilder.appendAs( forward ? fb.upper() : fb.lower(), "" );
if ( fb.nontrivial() )
2009-02-18 15:25:50 -05:00
++indexedQueryCount;
2009-02-19 10:26:46 -05:00
if ( stillOptimalIndexedQueryCount ) {
if ( fb.nontrivial() )
++optimalIndexedQueryCount;
if ( !fb.equality() )
stillOptimalIndexedQueryCount = false;
} else {
if ( fb.nontrivial() )
optimalIndexedQueryCount = -1;
}
if ( fb.equality() ) {
BSONElement e = fb.upper();
if ( !e.isNumber() && !e.mayEncapsulate() && e.type() != RegEx )
++exactIndexedQueryCount;
}
2009-02-18 15:25:50 -05:00
orderFieldsUnindexed.erase( e.fieldName() );
}
if ( !scanAndOrderRequired_ &&
2009-02-19 10:26:46 -05:00
( optimalIndexedQueryCount == fbs.nNontrivialBounds() ) )
optimal_ = true;
2009-02-18 15:25:50 -05:00
if ( indexedQueryCount == fbs.nNontrivialBounds() &&
orderFieldsUnindexed.size() == 0 ) {
keyMatch_ = true;
if ( exactIndexedQueryCount == fbs.nNontrivialBounds() )
2009-02-18 15:25:50 -05:00
exactKeyMatch_ = true;
}
startKey_ = startKeyBuilder.obj();
endKey_ = endKeyBuilder.obj();
if ( !keyMatch_ &&
( scanAndOrderRequired_ || order_.isEmpty() ) &&
!fbs.bound( idxKey.firstElement().fieldName() ).nontrivial() )
unhelpful_ = true;
}
2009-02-17 20:57:36 -05:00
2009-02-20 11:08:22 -05:00
auto_ptr< Cursor > QueryPlan::newCursor() const {
if ( !fbs_.matchPossible() )
return auto_ptr< Cursor >( new BasicCursor( DiskLoc() ) );
2009-02-20 11:08:22 -05:00
if ( !index_ )
return findTableScan( fbs_.ns(), order_, 0 );
//TODO This constructor should really take a const ref to the index details.
return auto_ptr< Cursor >( new BtreeCursor( *const_cast< IndexDetails* >( index_ ), startKey_, endKey_, direction_ >= 0 ? 1 : -1 ) );
2009-02-20 11:08:22 -05:00
}
BSONObj QueryPlan::indexKey() const {
2009-02-24 17:48:06 -05:00
if ( !index_ )
return BSON( "$natural" << 1 );
return index_->keyPattern();
}
2009-02-19 14:23:07 -05:00
QueryPlanSet::QueryPlanSet( const char *ns, const BSONObj &query, const BSONObj &order, const BSONElement *hint ) :
2009-02-25 10:48:41 -05:00
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();
2009-02-19 13:58:22 -05:00
NamespaceDetails *d = nsdetails( ns );
if ( !d || !fbs_.matchPossible() ) {
2009-02-24 17:48:06 -05:00
// Table scan plan, when no matches are possible
2009-02-25 10:48:41 -05:00
plans_.push_back( PlanPtr( new QueryPlan( fbs_, order_ ) ) );
return;
}
2009-02-25 10:48:41 -05:00
BSONElement hint = hint_.firstElement();
if ( !hint.eoo() ) {
mayRecordPlan_ = false;
if( hint.type() == String ) {
string hintstr = hint.valuestr();
2009-02-19 13:58:22 -05:00
for (int i = 0; i < d->nIndexes; i++ ) {
IndexDetails& ii = d->indexes[i];
if ( ii.indexName() == hintstr ) {
2009-02-25 10:48:41 -05:00
plans_.push_back( PlanPtr( new QueryPlan( fbs_, order_, &ii ) ) );
2009-02-19 13:58:22 -05:00
return;
}
}
}
2009-02-25 10:48:41 -05:00
else if( hint.type() == Object ) {
BSONObj hintobj = hint.embeddedObject();
uassert( "bad hint", !hintobj.isEmpty() );
if ( !strcmp( hintobj.firstElement().fieldName(), "$natural" ) ) {
// Table scan plan
2009-02-25 10:48:41 -05:00
plans_.push_back( PlanPtr( new QueryPlan( fbs_, order_ ) ) );
return;
}
2009-02-19 13:58:22 -05:00
for (int i = 0; i < d->nIndexes; i++ ) {
IndexDetails& ii = d->indexes[i];
if( ii.keyPattern().woCompare(hintobj) == 0 ) {
2009-02-25 10:48:41 -05:00
plans_.push_back( PlanPtr( new QueryPlan( fbs_, order_, &ii ) ) );
2009-02-19 13:58:22 -05:00
return;
}
}
}
uassert( "bad hint", false );
}
2009-02-24 17:48:06 -05:00
BSONObj bestIndex = indexForPattern( ns, fbs_.pattern() );
if ( !bestIndex.isEmpty() ) {
2009-02-25 10:48:41 -05:00
usingPrerecordedPlan_ = true;
2009-02-24 17:48:06 -05:00
if ( !strcmp( bestIndex.firstElement().fieldName(), "$natural" ) ) {
// Table scan plan
2009-02-25 10:48:41 -05:00
plans_.push_back( PlanPtr( new QueryPlan( fbs_, order_ ) ) );
2009-02-24 17:48:06 -05:00
return;
}
for (int i = 0; i < d->nIndexes; i++ ) {
IndexDetails& ii = d->indexes[i];
if( ii.keyPattern().woCompare(bestIndex) == 0 ) {
2009-02-25 10:48:41 -05:00
plans_.push_back( PlanPtr( new QueryPlan( fbs_, order_, &ii ) ) );
2009-02-24 17:48:06 -05:00
return;
}
}
2009-02-25 10:48:41 -05:00
assert( false );
2009-02-24 17:48:06 -05:00
}
// Table scan plan
2009-02-25 10:48:41 -05:00
plans_.push_back( PlanPtr( new QueryPlan( fbs_, order_ ) ) );
2009-02-19 13:58:22 -05:00
// If table scan is optimal
2009-02-25 10:48:41 -05:00
if ( fbs_.nNontrivialBounds() == 0 && order_.isEmpty() )
return;
// Only table scan can give natural order.
2009-02-25 10:48:41 -05:00
if ( !order_.isEmpty() && !strcmp( order_.firstElement().fieldName(), "$natural" ) )
return;
2009-02-20 11:08:22 -05:00
PlanSet plans;
for( int i = 0; i < d->nIndexes; ++i ) {
2009-02-25 10:48:41 -05:00
PlanPtr p( new QueryPlan( fbs_, order_, &d->indexes[ i ] ) );
2009-02-20 11:08:22 -05:00
if ( p->optimal() ) {
plans_.push_back( p );
return;
} else if ( !p->unhelpful() ) {
plans.push_back( p );
}
}
2009-02-20 11:08:22 -05:00
for( PlanSet::iterator i = plans.begin(); i != plans.end(); ++i )
plans_.push_back( *i );
}
2009-02-20 15:37:24 -05:00
shared_ptr< QueryOp > QueryPlanSet::runOp( QueryOp &op ) {
2009-02-25 10:48:41 -05:00
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();
2009-02-20 11:08:22 -05:00
}
QueryPlanSet::Runner::Runner( QueryPlanSet &plans, QueryOp &op ) :
2009-02-20 11:08:22 -05:00
op_( op ),
plans_( plans ) {
2009-02-20 11:08:22 -05:00
}
shared_ptr< QueryOp > QueryPlanSet::Runner::run() {
massert( "no plans", plans_.plans_.size() > 0 );
2009-02-20 15:37:24 -05:00
vector< shared_ptr< QueryOp > > ops;
for( PlanSet::iterator i = plans_.plans_.begin(); i != plans_.plans_.end(); ++i ) {
shared_ptr< QueryOp > op( op_.clone() );
op->setQueryPlan( i->get() );
ops.push_back( op );
2009-02-20 11:08:22 -05:00
}
2009-02-20 15:37:24 -05:00
for( vector< shared_ptr< QueryOp > >::iterator i = ops.begin(); i != ops.end(); ++i )
(*i)->init();
while( 1 ) {
unsigned errCount = 0;
for( vector< shared_ptr< QueryOp > >::iterator i = ops.begin(); i != ops.end(); ++i ) {
QueryOp &op = **i;
try {
2009-02-24 15:45:18 -05:00
if ( !op.error() )
op.next();
} catch ( const std::exception &e ) {
op.setExceptionMessage( e.what() );
} catch ( ... ) {
op.setExceptionMessage( "Caught unknown exception" );
}
2009-02-24 17:48:06 -05:00
if ( op.complete() ) {
2009-02-25 10:48:41 -05:00
if ( plans_.mayRecordPlan_ && op.mayRecordPlan() )
2009-02-24 18:32:08 -05:00
op.qp().registerSelf();
return *i;
2009-02-24 17:48:06 -05:00
}
2009-02-24 15:45:18 -05:00
if ( op.error() )
++errCount;
}
if ( errCount == ops.size() )
break;
}
return ops[ 0 ];
2009-02-20 11:08:22 -05:00
}
2009-01-14 17:09:51 -05:00
} // namespace mongo