Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

2758 lines (2462 sloc) 110.202 kB
// 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/queryoptimizercursorimpl.h"
#include "../db/queryoptimizer.h"
#include "../db/instance.h"
#include "../db/ops/delete.h"
#include "dbtests.h"
namespace mongo {
void __forceLinkGeoPlugin();
shared_ptr<Cursor> newQueryOptimizerCursor( const char *ns, const BSONObj &query,
const BSONObj &order = BSONObj(),
const QueryPlanSelectionPolicy &planPolicy =
QueryPlanSelectionPolicy::any(),
bool requireOrder = true );
} // namespace mongo
namespace QueryOptimizerCursorTests {
void dropCollection( const char *ns ) {
string errmsg;
BSONObjBuilder result;
dropCollection( ns, errmsg, result );
}
using boost::shared_ptr;
class CachedMatchCounterCount {
public:
void run() {
long long aggregateNscanned;
CachedMatchCounter c( aggregateNscanned, 0 );
ASSERT_EQUALS( 0, c.count() );
ASSERT_EQUALS( 0, c.cumulativeCount() );
c.resetMatch();
ASSERT( !c.knowMatch() );
c.setMatch( false );
ASSERT( c.knowMatch() );
c.countMatch( DiskLoc() );
ASSERT_EQUALS( 0, c.count() );
ASSERT_EQUALS( 0, c.cumulativeCount() );
c.resetMatch();
ASSERT( !c.knowMatch() );
c.setMatch( true );
ASSERT( c.knowMatch() );
c.countMatch( DiskLoc() );
ASSERT_EQUALS( 1, c.count() );
ASSERT_EQUALS( 1, c.cumulativeCount() );
// Don't count the same match twice, without checking the document location.
c.countMatch( DiskLoc( 1, 1 ) );
ASSERT_EQUALS( 1, c.count() );
ASSERT_EQUALS( 1, c.cumulativeCount() );
// Reset and count another match.
c.resetMatch();
c.setMatch( true );
c.countMatch( DiskLoc( 1, 1 ) );
ASSERT_EQUALS( 2, c.count() );
ASSERT_EQUALS( 2, c.cumulativeCount() );
}
};
class CachedMatchCounterAccumulate {
public:
void run() {
long long aggregateNscanned;
CachedMatchCounter c( aggregateNscanned, 10 );
ASSERT_EQUALS( 0, c.count() );
ASSERT_EQUALS( 10, c.cumulativeCount() );
c.setMatch( true );
c.countMatch( DiskLoc() );
ASSERT_EQUALS( 1, c.count() );
ASSERT_EQUALS( 11, c.cumulativeCount() );
}
};
class CachedMatchCounterDedup {
public:
void run() {
long long aggregateNscanned;
CachedMatchCounter c( aggregateNscanned, 0 );
c.setCheckDups( true );
c.setMatch( true );
c.countMatch( DiskLoc() );
ASSERT_EQUALS( 1, c.count() );
c.resetMatch();
c.setMatch( true );
c.countMatch( DiskLoc() );
ASSERT_EQUALS( 1, c.count() );
}
};
class CachedMatchCounterNscanned {
public:
void run() {
long long aggregateNscanned = 5;
CachedMatchCounter c( aggregateNscanned, 0 );
ASSERT_EQUALS( 0, c.nscanned() );
ASSERT_EQUALS( 5, c.aggregateNscanned() );
c.updateNscanned( 4 );
ASSERT_EQUALS( 4, c.nscanned() );
ASSERT_EQUALS( 9, c.aggregateNscanned() );
}
};
class SmallDupSetUpgrade {
public:
void run() {
SmallDupSet d;
for( int i = 0; i < 100; ++i ) {
ASSERT( !d.getsetdup( DiskLoc( 0, i ) ) );
for( int j = 0; j <= i; ++j ) {
ASSERT( d.getdup( DiskLoc( 0, j ) ) );
}
}
}
};
class SmallDupSetUpgradeRead {
public:
void run() {
SmallDupSet d;
d.getsetdup( DiskLoc( 0, 0 ) );
for( int i = 0; i < 550; ++i ) {
ASSERT( d.getdup( DiskLoc( 0, 0 ) ) );
}
ASSERT( d.getsetdup( DiskLoc( 0, 0 ) ) );
}
};
class SmallDupSetUpgradeWrite {
public:
void run() {
SmallDupSet d;
for( int i = 0; i < 550; ++i ) {
ASSERT( !d.getsetdup( DiskLoc( 0, i ) ) );
}
for( int i = 0; i < 550; ++i ) {
ASSERT( d.getsetdup( DiskLoc( 0, i ) ) );
}
}
};
class Base {
public:
Base() {
dblock lk;
Client::Context ctx( ns() );
string err;
userCreateNS( ns(), BSONObj(), err, false );
dropCollection( ns() );
}
~Base() {
cc().curop()->reset();
}
protected:
DBDirectClient _cli;
static const char *ns() { return "unittests.QueryOptimizerTests"; }
void setQueryOptimizerCursor( const BSONObj &query, const BSONObj &order = BSONObj() ) {
setQueryOptimizerCursorWithoutAdvancing( query, order );
if ( ok() && !mayReturnCurrent() ) {
advance();
}
}
void setQueryOptimizerCursorWithoutAdvancing( const BSONObj &query, const BSONObj &order = BSONObj() ) {
_c = newQueryOptimizerCursor( ns(), query, order );
}
bool ok() const { return _c->ok(); }
/** Handles matching and deduping. */
bool advance() {
while( _c->advance() && !mayReturnCurrent() );
return ok();
}
int itcount() {
int ret = 0;
while( ok() ) {
++ret;
advance();
}
return ret;
}
BSONObj current() const { return _c->current(); }
DiskLoc currLoc() const { return _c->currLoc(); }
void prepareToTouchEarlierIterate() { _c->prepareToTouchEarlierIterate(); }
void recoverFromTouchingEarlierIterate() { _c->recoverFromTouchingEarlierIterate(); }
bool mayReturnCurrent() {
// return _c->currentMatches() && !_c->getsetdup( _c->currLoc() );
return ( !_c->matcher() || _c->matcher()->matchesCurrent( _c.get() ) ) && !_c->getsetdup( _c->currLoc() );
}
bool prepareToYield() const { return _c->prepareToYield(); }
void recoverFromYield() {
_c->recoverFromYield();
if ( ok() && !mayReturnCurrent() ) {
advance();
}
}
shared_ptr<Cursor> c() { return _c; }
long long nscanned() const { return _c->nscanned(); }
unsigned nNsCursors() const {
set<CursorId> nsCursors;
ClientCursor::find( ns(), nsCursors );
return nsCursors.size();
}
private:
shared_ptr<Cursor> _c;
};
/** No results for empty collection. */
class Empty : public Base {
public:
void run() {
dblock lk;
Client::Context ctx( ns() );
shared_ptr<Cursor> c = newQueryOptimizerCursor( ns(), BSONObj() );
ASSERT( !c->ok() );
ASSERT_THROWS( c->_current(), AssertionException );
ASSERT_THROWS( c->current(), AssertionException );
ASSERT( c->currLoc().isNull() );
ASSERT( !c->advance() );
ASSERT_THROWS( c->currKey(), AssertionException );
ASSERT_THROWS( c->getsetdup( DiskLoc() ), AssertionException );
ASSERT_THROWS( c->isMultiKey(), AssertionException );
ASSERT_THROWS( c->matcher(), AssertionException );
}
};
/** Simple table scan. */
class Unindexed : public Base {
public:
void run() {
_cli.insert( ns(), BSON( "_id" << 1 ) );
_cli.insert( ns(), BSON( "_id" << 2 ) );
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSONObj() );
ASSERT_EQUALS( 2, itcount() );
}
};
/** Basic test with two indexes and deduping requirement. */
class Basic : public Base {
public:
void run() {
_cli.insert( ns(), BSON( "_id" << 1 << "a" << 2 ) );
_cli.insert( ns(), BSON( "_id" << 2 << "a" << 1 ) );
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "_id" << GT << 0 << "a" << GT << 0 ) );
ASSERT( ok() );
ASSERT_EQUALS( BSON( "_id" << 1 << "a" << 2 ), current() );
ASSERT( advance() );
ASSERT_EQUALS( BSON( "_id" << 2 << "a" << 1 ), current() );
ASSERT( !advance() );
ASSERT( !ok() );
}
};
class NoMatch : public Base {
public:
void run() {
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "_id" << GT << 5 << LT << 4 << "a" << GT << 0 ) );
ASSERT( !ok() );
}
};
/** Order of results indicates that interleaving is occurring. */
class Interleaved : public Base {
public:
void run() {
_cli.insert( ns(), BSON( "_id" << 1 << "a" << 2 ) );
_cli.insert( ns(), BSON( "_id" << 3 << "a" << 1 ) );
_cli.insert( ns(), BSON( "_id" << 2 << "a" << 2 ) );
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "_id" << GT << 0 << "a" << GT << 0 ) );
ASSERT( ok() );
ASSERT_EQUALS( BSON( "_id" << 1 << "a" << 2 ), current() );
ASSERT( advance() );
ASSERT_EQUALS( BSON( "_id" << 3 << "a" << 1 ), current() );
ASSERT( advance() );
ASSERT_EQUALS( BSON( "_id" << 2 << "a" << 2 ), current() );
ASSERT( !advance() );
ASSERT( !ok() );
}
};
/** Some values on each index do not match. */
class NotMatch : public Base {
public:
void run() {
_cli.insert( ns(), BSON( "_id" << 0 << "a" << 10 ) );
_cli.insert( ns(), BSON( "_id" << 10 << "a" << 0 ) );
_cli.insert( ns(), BSON( "_id" << 11 << "a" << 12 ) );
_cli.insert( ns(), BSON( "_id" << 12 << "a" << 11 ) );
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "_id" << GT << 5 << "a" << GT << 5 ) );
ASSERT( ok() );
ASSERT_EQUALS( BSON( "_id" << 11 << "a" << 12 ), current() );
ASSERT( advance() );
ASSERT_EQUALS( BSON( "_id" << 12 << "a" << 11 ), current() );
ASSERT( !advance() );
ASSERT( !ok() );
}
};
/** After the first 101 matches for a plan, we stop interleaving the plans. */
class StopInterleaving : public Base {
public:
void run() {
for( int i = 0; i < 101; ++i ) {
_cli.insert( ns(), BSON( "_id" << i << "a" << i ) );
}
for( int i = 101; i < 200; ++i ) {
_cli.insert( ns(), BSON( "_id" << i << "a" << (301-i) ) );
}
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "_id" << GT << -1 << "a" << GT << -1 ) );
for( int i = 0; i < 200; ++i ) {
ASSERT( ok() );
ASSERT_EQUALS( i, current().getIntField( "_id" ) );
advance();
}
ASSERT( !advance() );
ASSERT( !ok() );
}
};
/** Test correct deduping with the takeover cursor. */
class TakeoverWithDup : public Base {
public:
void run() {
for( int i = 0; i < 101; ++i ) {
_cli.insert( ns(), BSON( "_id" << i << "a" << i ) );
}
_cli.insert( ns(), BSON( "_id" << 500 << "a" << BSON_ARRAY( 0 << 300 ) ) );
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "_id" << GT << -1 << "a" << GT << -1 ) );
ASSERT_EQUALS( 102, itcount() );
}
};
/** Test usage of matcher with takeover cursor. */
class TakeoverWithNonMatches : public Base {
public:
void run() {
for( int i = 0; i < 101; ++i ) {
_cli.insert( ns(), BSON( "_id" << i << "a" << i ) );
}
_cli.insert( ns(), BSON( "_id" << 101 << "a" << 600 ) );
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "_id" << GT << -1 << "a" << LT << 500 ) );
ASSERT_EQUALS( 101, itcount() );
}
};
/** Check deduping of dups within just the takeover cursor. */
class TakeoverWithTakeoverDup : public Base {
public:
void run() {
for( int i = 0; i < 101; ++i ) {
_cli.insert( ns(), BSON( "_id" << i*2 << "a" << 0 ) );
_cli.insert( ns(), BSON( "_id" << i*2+1 << "a" << 1 ) );
}
_cli.insert( ns(), BSON( "_id" << 202 << "a" << BSON_ARRAY( 2 << 3 ) ) );
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "_id" << GT << -1 << "a" << GT << 0) );
ASSERT_EQUALS( 102, itcount() );
}
};
/** Basic test with $or query. */
class BasicOr : public Base {
public:
void run() {
_cli.insert( ns(), BSON( "_id" << 0 << "a" << 0 ) );
_cli.insert( ns(), BSON( "_id" << 1 << "a" << 1 ) );
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "$or" << BSON_ARRAY( BSON( "_id" << 0 ) << BSON( "a" << 1 ) ) ) );
ASSERT_EQUALS( BSON( "_id" << 0 << "a" << 0 ), current() );
ASSERT( advance() );
ASSERT_EQUALS( BSON( "_id" << 1 << "a" << 1 ), current() );
ASSERT( !advance() );
}
};
/** $or first clause empty. */
class OrFirstClauseEmpty : public Base {
public:
void run() {
_cli.insert( ns(), BSON( "_id" << 0 << "a" << 1 ) );
_cli.insert( ns(), BSON( "_id" << 1 << "a" << 1 ) );
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "$or" << BSON_ARRAY( BSON( "_id" << -1 ) << BSON( "a" << 1 ) ) ) );
ASSERT_EQUALS( BSON( "_id" << 0 << "a" << 1 ), current() );
ASSERT( advance() );
ASSERT_EQUALS( BSON( "_id" << 1 << "a" << 1 ), current() );
ASSERT( !advance() );
}
};
/** $or second clause empty. */
class OrSecondClauseEmpty : public Base {
public:
void run() {
_cli.insert( ns(), BSON( "_id" << 0 << "a" << 1 ) );
_cli.insert( ns(), BSON( "_id" << 1 << "a" << 1 ) );
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "$or" << BSON_ARRAY( BSON( "_id" << 0 ) << BSON( "_id" << -1 ) << BSON( "a" << 1 ) ) ) );
ASSERT_EQUALS( BSON( "_id" << 0 << "a" << 1 ), current() );
ASSERT( advance() );
ASSERT_EQUALS( BSON( "_id" << 1 << "a" << 1 ), current() );
ASSERT( !advance() );
}
};
/** $or multiple clauses empty empty. */
class OrMultipleClausesEmpty : public Base {
public:
void run() {
_cli.insert( ns(), BSON( "_id" << 0 << "a" << 1 ) );
_cli.insert( ns(), BSON( "_id" << 1 << "a" << 1 ) );
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "$or" << BSON_ARRAY( BSON( "_id" << 2 ) << BSON( "_id" << 4 ) << BSON( "_id" << 0 ) << BSON( "_id" << -1 ) << BSON( "_id" << 6 ) << BSON( "a" << 1 ) << BSON( "_id" << 9 ) ) ) );
ASSERT_EQUALS( BSON( "_id" << 0 << "a" << 1 ), current() );
ASSERT( advance() );
ASSERT_EQUALS( BSON( "_id" << 1 << "a" << 1 ), current() );
ASSERT( !advance() );
}
};
/** Check that takeover occurs at proper match count with $or clauses */
class TakeoverCountOr : public Base {
public:
void run() {
for( int i = 0; i < 60; ++i ) {
_cli.insert( ns(), BSON( "_id" << i << "a" << 0 ) );
}
for( int i = 60; i < 120; ++i ) {
_cli.insert( ns(), BSON( "_id" << i << "a" << 1 ) );
}
for( int i = 120; i < 150; ++i ) {
_cli.insert( ns(), BSON( "_id" << i << "a" << (200-i) ) );
}
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "$or" << BSON_ARRAY( BSON( "a" << 0 ) << BSON( "a" << 1 ) << BSON( "_id" << GTE << 120 << "a" << GT << 1 ) ) ) );
for( int i = 0; i < 120; ++i ) {
ASSERT( ok() );
advance();
}
// Expect to be scanning on _id index only.
for( int i = 120; i < 150; ++i ) {
ASSERT_EQUALS( i, current().getIntField( "_id" ) );
advance();
}
ASSERT( !ok() );
}
};
/** Takeover just at end of clause. */
class TakeoverEndOfOrClause : public Base {
public:
void run() {
for( int i = 0; i < 102; ++i ) {
_cli.insert( ns(), BSON( "_id" << i ) );
}
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "$or" << BSON_ARRAY( BSON( "_id" << LT << 101 ) << BSON( "_id" << 101 ) ) ) );
for( int i = 0; i < 102; ++i ) {
ASSERT_EQUALS( i, current().getIntField( "_id" ) );
advance();
}
ASSERT( !ok() );
}
};
class TakeoverBeforeEndOfOrClause : public Base {
public:
void run() {
for( int i = 0; i < 101; ++i ) {
_cli.insert( ns(), BSON( "_id" << i ) );
}
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "$or" << BSON_ARRAY( BSON( "_id" << LT << 100 ) << BSON( "_id" << 100 ) ) ) );
for( int i = 0; i < 101; ++i ) {
ASSERT_EQUALS( i, current().getIntField( "_id" ) );
advance();
}
ASSERT( !ok() );
}
};
class TakeoverAfterEndOfOrClause : public Base {
public:
void run() {
for( int i = 0; i < 103; ++i ) {
_cli.insert( ns(), BSON( "_id" << i ) );
}
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "$or" << BSON_ARRAY( BSON( "_id" << LT << 102 ) << BSON( "_id" << 102 ) ) ) );
for( int i = 0; i < 103; ++i ) {
ASSERT_EQUALS( i, current().getIntField( "_id" ) );
advance();
}
ASSERT( !ok() );
}
};
/** Test matching and deduping done manually by cursor client. */
class ManualMatchingDeduping : public Base {
public:
void run() {
_cli.insert( ns(), BSON( "_id" << 0 << "a" << 10 ) );
_cli.insert( ns(), BSON( "_id" << 10 << "a" << 0 ) );
_cli.insert( ns(), BSON( "_id" << 11 << "a" << 12 ) );
_cli.insert( ns(), BSON( "_id" << 12 << "a" << 11 ) );
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
dblock lk;
Client::Context ctx( ns() );
shared_ptr< Cursor > c = newQueryOptimizerCursor( ns(), BSON( "_id" << GT << 5 << "a" << GT << 5 ) );
ASSERT( c->ok() );
// _id 10 {_id:1}
ASSERT_EQUALS( 10, c->current().getIntField( "_id" ) );
ASSERT( !c->matcher()->matchesCurrent( c.get() ) );
ASSERT( c->advance() );
// _id 0 {a:1}
ASSERT_EQUALS( 0, c->current().getIntField( "_id" ) );
ASSERT( !c->matcher()->matchesCurrent( c.get() ) );
ASSERT( c->advance() );
// _id 0 {$natural:1}
ASSERT_EQUALS( 0, c->current().getIntField( "_id" ) );
ASSERT( !c->matcher()->matchesCurrent( c.get() ) );
ASSERT( c->advance() );
// _id 11 {_id:1}
ASSERT_EQUALS( BSON( "_id" << 11 << "a" << 12 ), c->current() );
ASSERT( c->matcher()->matchesCurrent( c.get() ) );
ASSERT( !c->getsetdup( c->currLoc() ) );
ASSERT( c->advance() );
// _id 12 {a:1}
ASSERT_EQUALS( BSON( "_id" << 12 << "a" << 11 ), c->current() );
ASSERT( c->matcher()->matchesCurrent( c.get() ) );
ASSERT( !c->getsetdup( c->currLoc() ) );
ASSERT( c->advance() );
// _id 10 {$natural:1}
ASSERT_EQUALS( 10, c->current().getIntField( "_id" ) );
ASSERT( !c->matcher()->matchesCurrent( c.get() ) );
ASSERT( c->advance() );
// _id 12 {_id:1}
ASSERT_EQUALS( BSON( "_id" << 12 << "a" << 11 ), c->current() );
ASSERT( c->matcher()->matchesCurrent( c.get() ) );
ASSERT( c->getsetdup( c->currLoc() ) );
ASSERT( c->advance() );
// _id 11 {a:1}
ASSERT_EQUALS( BSON( "_id" << 11 << "a" << 12 ), c->current() );
ASSERT( c->matcher()->matchesCurrent( c.get() ) );
ASSERT( c->getsetdup( c->currLoc() ) );
ASSERT( c->advance() );
// _id 11 {$natural:1}
ASSERT_EQUALS( 11, c->current().getIntField( "_id" ) );
ASSERT( c->matcher()->matchesCurrent( c.get() ) );
ASSERT( c->getsetdup( c->currLoc() ) );
// {_id:1} scan is complete.
ASSERT( !c->advance() );
ASSERT( !c->ok() );
// Scan the results again - this time the winning plan has been
// recorded.
c = newQueryOptimizerCursor( ns(), BSON( "_id" << GT << 5 << "a" << GT << 5 ) );
ASSERT( c->ok() );
// _id 10 {_id:1}
ASSERT_EQUALS( 10, c->current().getIntField( "_id" ) );
ASSERT( !c->matcher()->matchesCurrent( c.get() ) );
ASSERT( c->advance() );
// _id 11 {_id:1}
ASSERT_EQUALS( BSON( "_id" << 11 << "a" << 12 ), c->current() );
ASSERT( c->matcher()->matchesCurrent( c.get() ) );
ASSERT( !c->getsetdup( c->currLoc() ) );
ASSERT( c->advance() );
// _id 12 {_id:1}
ASSERT_EQUALS( BSON( "_id" << 12 << "a" << 11 ), c->current() );
ASSERT( c->matcher()->matchesCurrent( c.get() ) );
ASSERT( !c->getsetdup( c->currLoc() ) );
// {_id:1} scan complete
ASSERT( !c->advance() );
ASSERT( !c->ok() );
}
};
/** Curr key must be correct for currLoc for correct matching. */
class ManualMatchingUsingCurrKey : public Base {
public:
void run() {
_cli.insert( ns(), BSON( "_id" << "a" ) );
_cli.insert( ns(), BSON( "_id" << "b" ) );
_cli.insert( ns(), BSON( "_id" << "ba" ) );
dblock lk;
Client::Context ctx( ns() );
shared_ptr< Cursor > c = newQueryOptimizerCursor( ns(), fromjson( "{_id:/a/}" ) );
ASSERT( c->ok() );
// "a"
ASSERT( c->matcher()->matchesCurrent( c.get() ) );
ASSERT( !c->getsetdup( c->currLoc() ) );
ASSERT( c->advance() );
ASSERT( c->ok() );
// "b"
ASSERT( !c->matcher()->matchesCurrent( c.get() ) );
ASSERT( c->advance() );
ASSERT( c->ok() );
// "ba"
ASSERT( c->matcher()->matchesCurrent( c.get() ) );
ASSERT( !c->getsetdup( c->currLoc() ) );
ASSERT( !c->advance() );
}
};
/** Test matching and deduping done manually by cursor client. */
class ManualMatchingDedupingTakeover : public Base {
public:
void run() {
for( int i = 0; i < 150; ++i ) {
_cli.insert( ns(), BSON( "_id" << i << "a" << 0 ) );
}
_cli.insert( ns(), BSON( "_id" << 300 << "a" << 1 ) );
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
dblock lk;
Client::Context ctx( ns() );
shared_ptr< Cursor > c = newQueryOptimizerCursor( ns(), BSON( "$or" << BSON_ARRAY( BSON( "_id" << LT << 300 ) << BSON( "a" << 1 ) ) ) );
for( int i = 0; i < 151; ++i ) {
ASSERT( c->ok() );
ASSERT( c->matcher()->matchesCurrent( c.get() ) );
ASSERT( !c->getsetdup( c->currLoc() ) );
c->advance();
}
ASSERT( !c->ok() );
}
};
/** Test single key matching bounds. */
class Singlekey : public Base {
public:
void run() {
_cli.insert( ns(), BSON( "a" << "10" ) );
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
dblock lk;
Client::Context ctx( ns() );
shared_ptr< Cursor > c = newQueryOptimizerCursor( ns(), BSON( "a" << GT << 1 << LT << 5 ) );
// Two sided bounds work.
ASSERT( !c->ok() );
}
};
/** Test multi key matching bounds. */
class Multikey : public Base {
public:
void run() {
_cli.insert( ns(), BSON( "a" << BSON_ARRAY( 1 << 10 ) ) );
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "a" << GT << 5 << LT << 3 ) );
// Multi key bounds work.
ASSERT( ok() );
}
};
/** Add other plans when the recorded one is doing more poorly than expected. */
class AddOtherPlans : public Base {
public:
void run() {
_cli.insert( ns(), BSON( "_id" << 0 << "a" << 0 << "b" << 0 ) );
_cli.insert( ns(), BSON( "_id" << 1 << "a" << 1 << "b" << 0 ) );
for( int i = 100; i < 150; ++i ) {
_cli.insert( ns(), BSON( "_id" << i << "a" << 100 << "b" << i ) );
}
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
_cli.ensureIndex( ns(), BSON( "b" << 1 ) );
dblock lk;
Client::Context ctx( ns() );
shared_ptr<Cursor> c = newQueryOptimizerCursor( ns(), BSON( "a" << 0 << "b" << 0 ) );
ASSERT_EQUALS( BSON( "_id" << 0 << "a" << 0 << "b" << 0 ), c->current() );
ASSERT_EQUALS( BSON( "a" << 1 ), c->indexKeyPattern() );
ASSERT( c->advance() );
ASSERT_EQUALS( BSON( "_id" << 0 << "a" << 0 << "b" << 0 ), c->current() );
ASSERT_EQUALS( BSON( "b" << 1 ), c->indexKeyPattern() );
ASSERT( c->advance() );
ASSERT_EQUALS( BSON( "_id" << 0 << "a" << 0 << "b" << 0 ), c->current() );
// Unindexed plan
ASSERT_EQUALS( BSONObj(), c->indexKeyPattern() );
ASSERT( !c->advance() );
c = newQueryOptimizerCursor( ns(), BSON( "a" << 100 << "b" << 149 ) );
// Try {a:1}, which was successful previously.
for( int i = 0; i < 12; ++i ) {
ASSERT( 149 != c->current().getIntField( "b" ) );
ASSERT( c->advance() );
}
bool sawB1Index = false;
do {
if ( c->indexKeyPattern() == BSON( "b" << 1 ) ) {
ASSERT_EQUALS( 149, c->current().getIntField( "b" ) );
// We should try the {b:1} index and only see one result from it.
ASSERT( !sawB1Index );
sawB1Index = true;
}
} while ( c->advance() );
ASSERT( sawB1Index );
}
};
/** Add other plans when the recorded one is doing more poorly than expected, with deletion. */
class AddOtherPlansDelete : public Base {
public:
void run() {
_cli.insert( ns(), BSON( "_id" << 0 << "a" << 0 << "b" << 0 ) );
_cli.insert( ns(), BSON( "_id" << 1 << "a" << 1 << "b" << 0 ) );
for( int i = 100; i < 120; ++i ) {
_cli.insert( ns(), BSON( "_id" << i << "a" << 100 << "b" << i ) );
}
for( int i = 199; i >= 150; --i ) {
_cli.insert( ns(), BSON( "_id" << i << "a" << 100 << "b" << 150 ) );
}
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
_cli.ensureIndex( ns(), BSON( "b" << 1 ) );
dblock lk;
Client::Context ctx( ns() );
shared_ptr<Cursor> c = newQueryOptimizerCursor( ns(), BSON( "a" << 0 << "b" << 0 ) );
ASSERT_EQUALS( BSON( "_id" << 0 << "a" << 0 << "b" << 0 ), c->current() );
ASSERT_EQUALS( BSON( "a" << 1 ), c->indexKeyPattern() );
ASSERT( c->advance() );
ASSERT_EQUALS( BSON( "_id" << 0 << "a" << 0 << "b" << 0 ), c->current() );
ASSERT_EQUALS( BSON( "b" << 1 ), c->indexKeyPattern() );
ASSERT( c->advance() );
ASSERT_EQUALS( BSON( "_id" << 0 << "a" << 0 << "b" << 0 ), c->current() );
// Unindexed plan
ASSERT_EQUALS( BSONObj(), c->indexKeyPattern() );
ASSERT( !c->advance() );
c = newQueryOptimizerCursor( ns(), BSON( "a" << 100 << "b" << 150 ) );
// Try {a:1}, which was successful previously.
for( int i = 0; i < 12; ++i ) {
ASSERT( 150 != c->current().getIntField( "b" ) );
ASSERT_EQUALS( BSON( "a" << 1 ), c->indexKeyPattern() );
ASSERT( c->advance() );
}
// Now try {b:1} plan.
ASSERT_EQUALS( BSON( "b" << 1 ), c->indexKeyPattern() );
ASSERT_EQUALS( 150, c->current().getIntField( "b" ) );
ASSERT( c->currentMatches() );
int id = c->current().getIntField( "_id" );
c->advance();
c->prepareToTouchEarlierIterate();
_cli.remove( ns(), BSON( "_id" << id ) );
c->recoverFromTouchingEarlierIterate();
int count = 1;
while( c->ok() ) {
if ( c->currentMatches() ) {
++count;
int id = c->current().getIntField( "_id" );
c->advance();
c->prepareToTouchEarlierIterate();
_cli.remove( ns(), BSON( "_id" << id ) );
c->recoverFromTouchingEarlierIterate();
}
else {
c->advance();
}
}
ASSERT_EQUALS( 50, count );
}
};
/**
* Add other plans when the recorded one is doing more poorly than expected, with deletion before
* and after adding the additional plans.
*/
class AddOtherPlansContinuousDelete : public Base {
public:
void run() {
_cli.insert( ns(), BSON( "_id" << 0 << "a" << 0 << "b" << 0 ) );
_cli.insert( ns(), BSON( "_id" << 1 << "a" << 1 << "b" << 0 ) );
for( int i = 100; i < 400; ++i ) {
_cli.insert( ns(), BSON( "_id" << i << "a" << i << "b" << ( 499 - i ) ) );
}
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
_cli.ensureIndex( ns(), BSON( "b" << 1 ) );
dblock lk;
Client::Context ctx( ns() );
shared_ptr<Cursor> c = newQueryOptimizerCursor( ns(), BSON( "a" << GTE << -1 << LTE << 0 << "b" << GTE << -1 << LTE << 0 ) );
while( c->advance() );
// {a:1} plan should be recorded now.
c = newQueryOptimizerCursor( ns(), BSON( "a" << GTE << 100 << LTE << 400 << "b" << GTE << 100 << LTE << 400 ) );
int count = 0;
while( c->ok() ) {
if ( c->currentMatches() ) {
ASSERT( !c->getsetdup( c->currLoc() ) );
++count;
int id = c->current().getIntField( "_id" );
c->advance();
c->prepareToTouchEarlierIterate();
_cli.remove( ns(), BSON( "_id" << id ) );
c->recoverFromTouchingEarlierIterate();
} else {
c->advance();
}
}
ASSERT_EQUALS( 300, count );
ASSERT_EQUALS( 2U, _cli.count( ns(), BSONObj() ) );
}
};
/** Check $or clause range elimination. */
class OrRangeElimination : public Base {
public:
void run() {
_cli.insert( ns(), BSON( "_id" << 1 ) );
dblock lk;
Client::Context ctx( ns() );
shared_ptr<Cursor> c = newQueryOptimizerCursor( ns(), BSON( "$or" << BSON_ARRAY( BSON( "_id" << GT << 0 ) << BSON( "_id" << 1 ) ) ) );
ASSERT( c->ok() );
ASSERT( !c->advance() );
}
};
/** Check $or match deduping - in takeover cursor. */
class OrDedup : public Base {
public:
void run() {
for( int i = 0; i < 150; ++i ) {
_cli.insert( ns(), BSON( "_id" << i << "a" << i ) );
}
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
dblock lk;
Client::Context ctx( ns() );
shared_ptr<Cursor> c = newQueryOptimizerCursor( ns(), BSON( "$or" << BSON_ARRAY( BSON( "_id" << LT << 140 ) << BSON( "_id" << 145 ) << BSON( "a" << 145 ) ) ) );
while( c->current().getIntField( "_id" ) < 140 ) {
ASSERT( c->advance() );
}
// Match from second $or clause.
ASSERT_EQUALS( 145, c->current().getIntField( "_id" ) );
ASSERT( c->matcher()->matchesCurrent( c.get() ) );
ASSERT( c->advance() );
// Match from third $or clause.
ASSERT_EQUALS( 145, c->current().getIntField( "_id" ) );
// $or deduping is handled by the matcher.
ASSERT( !c->matcher()->matchesCurrent( c.get() ) );
ASSERT( !c->advance() );
}
};
/** Standard dups with a multikey cursor. */
class EarlyDups : public Base {
public:
void run() {
_cli.insert( ns(), BSON( "a" << BSON_ARRAY( 0 << 1 << 200 ) ) );
for( int i = 2; i < 150; ++i ) {
_cli.insert( ns(), BSON( "a" << i ) );
}
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "a" << GT << -1 ) );
ASSERT_EQUALS( 149, itcount() );
}
};
/** Pop or clause in takeover cursor. */
class OrPopInTakeover : public Base {
public:
void run() {
for( int i = 0; i < 150; ++i ) {
_cli.insert( ns(), BSON( "_id" << i ) );
}
dblock lk;
Client::Context ctx( ns() );
shared_ptr<Cursor> c = newQueryOptimizerCursor( ns(), BSON( "$or" << BSON_ARRAY( BSON( "_id" << LTE << 147 ) << BSON( "_id" << 148 ) << BSON( "_id" << 149 ) ) ) );
for( int i = 0; i < 150; ++i ) {
ASSERT( c->ok() );
ASSERT_EQUALS( i, c->current().getIntField( "_id" ) );
c->advance();
}
ASSERT( !c->ok() );
}
};
/** Or clause iteration abandoned once full collection scan is performed. */
class OrCollectionScanAbort : public Base {
public:
void run() {
_cli.insert( ns(), BSON( "_id" << 0 << "a" << BSON_ARRAY( 1 << 2 << 3 << 4 << 5 ) << "b" << 4 ) );
_cli.insert( ns(), BSON( "_id" << 1 << "a" << BSON_ARRAY( 6 << 7 << 8 << 9 << 10 ) << "b" << 4 ) );
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
dblock lk;
Client::Context ctx( ns() );
shared_ptr<Cursor> c = newQueryOptimizerCursor( ns(), BSON( "$or" << BSON_ARRAY( BSON( "a" << LT << 6 << "b" << 4 ) << BSON( "a" << GTE << 6 << "b" << 4 ) ) ) );
ASSERT( c->ok() );
// _id 0 on {a:1}
ASSERT_EQUALS( 0, c->current().getIntField( "_id" ) );
ASSERT( c->matcher()->matchesCurrent( c.get() ) );
ASSERT( !c->getsetdup( c->currLoc() ) );
c->advance();
// _id 0 on {$natural:1}
ASSERT_EQUALS( 0, c->current().getIntField( "_id" ) );
ASSERT( c->matcher()->matchesCurrent( c.get() ) );
ASSERT( c->getsetdup( c->currLoc() ) );
c->advance();
// _id 0 on {a:1}
ASSERT_EQUALS( 0, c->current().getIntField( "_id" ) );
ASSERT( c->matcher()->matchesCurrent( c.get() ) );
ASSERT( c->getsetdup( c->currLoc() ) );
c->advance();
// _id 1 on {$natural:1}
ASSERT_EQUALS( 1, c->current().getIntField( "_id" ) );
ASSERT( c->matcher()->matchesCurrent( c.get() ) );
ASSERT( !c->getsetdup( c->currLoc() ) );
c->advance();
// _id 0 on {a:1}
ASSERT_EQUALS( 0, c->current().getIntField( "_id" ) );
ASSERT( c->matcher()->matchesCurrent( c.get() ) );
ASSERT( c->getsetdup( c->currLoc() ) );
c->advance();
// {$natural:1} finished
ASSERT( !c->ok() );
}
};
/** Yield cursor and delete current entry, then continue iteration. */
class YieldNoOp : public Base {
public:
void run() {
_cli.insert( ns(), BSON( "_id" << 1 ) );
_cli.insert( ns(), BSON( "_id" << 2 ) );
{
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "_id" << GT << 0 ) );
ASSERT_EQUALS( 1, current().getIntField( "_id" ) );
ASSERT( prepareToYield() );
}
{
dblock lk;
Client::Context ctx( ns() );
recoverFromYield();
ASSERT( ok() );
ASSERT_EQUALS( 2, current().getIntField( "_id" ) );
ASSERT( !advance() );
ASSERT( !ok() );
ASSERT( prepareToYield() );
recoverFromYield();
}
}
};
/** Yield cursor and delete current entry. */
class YieldDelete : public Base {
public:
void run() {
_cli.insert( ns(), BSON( "_id" << 1 ) );
{
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "_id" << 1 ) );
ASSERT_EQUALS( 1, current().getIntField( "_id" ) );
ASSERT( prepareToYield() );
}
_cli.remove( ns(), BSON( "_id" << 1 ) );
{
dblock lk;
Client::Context ctx( ns() );
recoverFromYield();
ASSERT( !ok() );
ASSERT( !advance() );
}
}
};
/** Yield cursor and delete current entry, then continue iteration. */
class YieldDeleteContinue : public Base {
public:
void run() {
_cli.insert( ns(), BSON( "_id" << 1 ) );
_cli.insert( ns(), BSON( "_id" << 2 ) );
{
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "_id" << GT << 0 ) );
ASSERT_EQUALS( 1, current().getIntField( "_id" ) );
ASSERT( prepareToYield() );
}
_cli.remove( ns(), BSON( "_id" << 1 ) );
{
dblock lk;
Client::Context ctx( ns() );
recoverFromYield();
ASSERT( ok() );
ASSERT_EQUALS( 2, current().getIntField( "_id" ) );
ASSERT( !advance() );
ASSERT( !ok() );
}
}
};
/** Yield cursor and delete current entry, then continue iteration. */
class YieldDeleteContinueFurther : public Base {
public:
void run() {
_cli.insert( ns(), BSON( "_id" << 1 ) );
_cli.insert( ns(), BSON( "_id" << 2 ) );
_cli.insert( ns(), BSON( "_id" << 3 ) );
{
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "_id" << GT << 0 ) );
ASSERT_EQUALS( 1, current().getIntField( "_id" ) );
ASSERT( prepareToYield() );
}
_cli.remove( ns(), BSON( "_id" << 1 ) );
{
dblock lk;
Client::Context ctx( ns() );
recoverFromYield();
ASSERT( ok() );
ASSERT_EQUALS( 2, current().getIntField( "_id" ) );
ASSERT( advance() );
ASSERT_EQUALS( 3, current().getIntField( "_id" ) );
ASSERT( !advance() );
ASSERT( !ok() );
}
}
};
/** Yield and update current. */
class YieldUpdate : public Base {
public:
void run() {
_cli.insert( ns(), BSON( "a" << 1 ) );
_cli.insert( ns(), BSON( "a" << 2 ) );
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
{
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "a" << GT << 0 ) );
ASSERT_EQUALS( 1, current().getIntField( "a" ) );
ASSERT( prepareToYield() );
}
_cli.update( ns(), BSON( "a" << 1 ), BSON( "$set" << BSON( "a" << 3 ) ) );
{
dblock lk;
Client::Context ctx( ns() );
recoverFromYield();
ASSERT( ok() );
ASSERT_EQUALS( 2, current().getIntField( "a" ) );
ASSERT( !advance() );
ASSERT( !ok() );
}
}
};
/** Yield and drop collection. */
class YieldDrop : public Base {
public:
void run() {
_cli.insert( ns(), BSON( "_id" << 1 ) );
_cli.insert( ns(), BSON( "_id" << 2 ) );
{
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "_id" << GT << 0 ) );
ASSERT_EQUALS( 1, current().getIntField( "_id" ) );
ASSERT( prepareToYield() );
}
_cli.dropCollection( ns() );
{
dblock lk;
Client::Context ctx( ns() );
recoverFromYield();
ASSERT( !ok() );
}
}
};
/** Yield and drop collection with $or query. */
class YieldDropOr : public Base {
public:
void run() {
_cli.insert( ns(), BSON( "_id" << 1 ) );
_cli.insert( ns(), BSON( "_id" << 2 ) );
{
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "$or" << BSON_ARRAY( BSON( "_id" << 1 ) << BSON( "_id" << 2 ) ) ) );
ASSERT_EQUALS( 1, current().getIntField( "_id" ) );
ASSERT( prepareToYield() );
}
_cli.dropCollection( ns() );
{
dblock lk;
Client::Context ctx( ns() );
ASSERT_THROWS( recoverFromYield(), MsgAssertionException );
ASSERT( !ok() );
}
}
};
/** Yield and remove document with $or query. */
class YieldRemoveOr : public Base {
public:
void run() {
_cli.insert( ns(), BSON( "_id" << 1 ) );
_cli.insert( ns(), BSON( "_id" << 2 ) );
{
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "$or" << BSON_ARRAY( BSON( "_id" << 1 ) << BSON( "_id" << 2 ) ) ) );
ASSERT_EQUALS( 1, current().getIntField( "_id" ) );
ASSERT( prepareToYield() );
}
_cli.remove( ns(), BSON( "_id" << 1 ) );
{
dblock lk;
Client::Context ctx( ns() );
recoverFromYield();
ASSERT( ok() );
ASSERT_EQUALS( 2, current().getIntField( "_id" ) );
}
}
};
/** Yield and overwrite current in capped collection. */
class YieldCappedOverwrite : public Base {
public:
void run() {
_cli.createCollection( ns(), 1000, true );
_cli.insert( ns(), BSON( "x" << 1 ) );
{
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "x" << GT << 0 ) );
ASSERT_EQUALS( 1, current().getIntField( "x" ) );
ASSERT( prepareToYield() );
}
int x = 2;
while( _cli.count( ns(), BSON( "x" << 1 ) ) > 0 ) {
_cli.insert( ns(), BSON( "x" << x++ ) );
}
{
dblock lk;
Client::Context ctx( ns() );
ASSERT_THROWS( recoverFromYield(), MsgAssertionException );
ASSERT( !ok() );
}
}
};
/** Yield and drop unrelated index - see SERVER-2454. */
class YieldDropIndex : public Base {
public:
void run() {
_cli.insert( ns(), BSON( "_id" << 1 ) );
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
{
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "_id" << 1 ) );
ASSERT_EQUALS( 1, current().getIntField( "_id" ) );
ASSERT( prepareToYield() );
}
_cli.dropIndex( ns(), BSON( "a" << 1 ) );
{
dblock lk;
Client::Context ctx( ns() );
recoverFromYield();
ASSERT( !ok() );
}
}
};
/** Yielding with multiple plans active. */
class YieldMultiplePlansNoOp : public Base {
public:
void run() {
_cli.insert( ns(), BSON( "_id" << 1 << "a" << 2 ) );
_cli.insert( ns(), BSON( "_id" << 2 << "a" << 1 ) );
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
{
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "_id" << GT << 0 << "a" << GT << 0 ) );
ASSERT_EQUALS( 1, current().getIntField( "_id" ) );
ASSERT( prepareToYield() );
}
{
dblock lk;
Client::Context ctx( ns() );
recoverFromYield();
ASSERT( ok() );
ASSERT_EQUALS( 2, current().getIntField( "_id" ) );
ASSERT( !advance() );
ASSERT( !ok() );
}
}
};
/** Yielding with advance and multiple plans active. */
class YieldMultiplePlansAdvanceNoOp : public Base {
public:
void run() {
_cli.insert( ns(), BSON( "_id" << 1 << "a" << 2 ) );
_cli.insert( ns(), BSON( "_id" << 2 << "a" << 1 ) );
_cli.insert( ns(), BSON( "_id" << 3 << "a" << 3 ) );
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
{
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "_id" << GT << 0 << "a" << GT << 0 ) );
ASSERT_EQUALS( 1, current().getIntField( "_id" ) );
advance();
ASSERT_EQUALS( 2, current().getIntField( "_id" ) );
ASSERT( prepareToYield() );
}
{
dblock lk;
Client::Context ctx( ns() );
recoverFromYield();
ASSERT( ok() );
ASSERT_EQUALS( 3, current().getIntField( "_id" ) );
ASSERT( !advance() );
ASSERT( !ok() );
}
}
};
/** Yielding with delete and multiple plans active. */
class YieldMultiplePlansDelete : public Base {
public:
void run() {
_cli.insert( ns(), BSON( "_id" << 1 << "a" << 2 ) );
_cli.insert( ns(), BSON( "_id" << 2 << "a" << 1 ) );
_cli.insert( ns(), BSON( "_id" << 3 << "a" << 4 ) );
_cli.insert( ns(), BSON( "_id" << 4 << "a" << 3 ) );
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
{
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "_id" << GT << 0 << "a" << GT << 0 ) );
ASSERT_EQUALS( 1, current().getIntField( "_id" ) );
advance();
ASSERT_EQUALS( 2, current().getIntField( "_id" ) );
ASSERT( prepareToYield() );
}
_cli.remove( ns(), BSON( "_id" << 2 ) );
{
dblock lk;
Client::Context ctx( ns() );
c()->recoverFromYield();
ASSERT( ok() );
// index {a:1} active during yield
ASSERT_EQUALS( 1, current().getIntField( "_id" ) );
ASSERT( advance() );
ASSERT_EQUALS( 3, current().getIntField( "_id" ) );
ASSERT( advance() );
ASSERT_EQUALS( 4, current().getIntField( "_id" ) );
ASSERT( !advance() );
ASSERT( !ok() );
}
}
};
/** Yielding with delete, multiple plans active, and $or clause. */
class YieldMultiplePlansDeleteOr : public Base {
public:
void run() {
_cli.insert( ns(), BSON( "_id" << 1 << "a" << 2 ) );
_cli.insert( ns(), BSON( "_id" << 2 << "a" << 1 ) );
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
{
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "$or" << BSON_ARRAY( BSON( "_id" << 1 << "a" << 2 ) << BSON( "_id" << 2 << "a" << 1 ) ) ) );
ASSERT_EQUALS( 1, current().getIntField( "_id" ) );
ASSERT( prepareToYield() );
}
_cli.remove( ns(), BSON( "_id" << 1 ) );
{
dblock lk;
Client::Context ctx( ns() );
c()->recoverFromYield();
ASSERT( ok() );
ASSERT_EQUALS( 2, current().getIntField( "_id" ) );
ASSERT( !advance() );
ASSERT( !ok() );
}
}
};
/** Yielding with delete, multiple plans active with advancement to the second, and $or clause. */
class YieldMultiplePlansDeleteOrAdvance : public Base {
public:
void run() {
_cli.insert( ns(), BSON( "_id" << 1 << "a" << 2 ) );
_cli.insert( ns(), BSON( "_id" << 2 << "a" << 1 ) );
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
{
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "$or" << BSON_ARRAY( BSON( "_id" << 1 << "a" << 2 ) << BSON( "_id" << 2 << "a" << 1 ) ) ) );
ASSERT_EQUALS( 1, current().getIntField( "_id" ) );
ASSERT( prepareToYield() );
c()->advance();
ASSERT_EQUALS( 1, current().getIntField( "_id" ) );
}
_cli.remove( ns(), BSON( "_id" << 1 ) );
{
dblock lk;
Client::Context ctx( ns() );
c()->recoverFromYield();
ASSERT( ok() );
ASSERT_EQUALS( 2, current().getIntField( "_id" ) );
ASSERT( !advance() );
ASSERT( !ok() );
}
}
};
/** Yielding with multiple plans and capped overwrite. */
class YieldMultiplePlansCappedOverwrite : public Base {
public:
void run() {
_cli.createCollection( ns(), 1000, true );
_cli.insert( ns(), BSON( "_id" << 1 << "a" << 1 ) );
_cli.ensureIndex( ns(), BSON( "_id" << 1 ) );
{
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "_id" << GT << 0 << "a" << GT << 0 ) );
ASSERT_EQUALS( 1, current().getIntField( "_id" ) );
ASSERT( prepareToYield() );
}
int i = 1;
while( _cli.count( ns(), BSON( "_id" << 1 ) ) > 0 ) {
++i;
_cli.insert( ns(), BSON( "_id" << i << "a" << i ) );
}
{
dblock lk;
Client::Context ctx( ns() );
recoverFromYield();
ASSERT( ok() );
// {$natural:1} plan does not recover, {_id:1} plan does.
ASSERT( 1 < current().getIntField( "_id" ) );
}
}
};
/**
* Yielding with multiple plans and capped overwrite with unrecoverable cursor
* active at time of yield.
*/
class YieldMultiplePlansCappedOverwriteManual : public Base {
public:
void run() {
_cli.createCollection( ns(), 1000, true );
_cli.insert( ns(), BSON( "a" << 1 << "b" << 1 ) );
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
shared_ptr<Cursor> c;
{
dblock lk;
Client::Context ctx( ns() );
c = newQueryOptimizerCursor( ns(), BSON( "a" << GT << 0 << "b" << GT << 0 ) );
ASSERT_EQUALS( 1, c->current().getIntField( "a" ) );
ASSERT( !c->getsetdup( c->currLoc() ) );
c->advance();
ASSERT_EQUALS( 1, c->current().getIntField( "a" ) );
ASSERT( c->getsetdup( c->currLoc() ) );
ASSERT( c->prepareToYield() );
}
int i = 1;
while( _cli.count( ns(), BSON( "a" << 1 ) ) > 0 ) {
++i;
_cli.insert( ns(), BSON( "a" << i << "b" << i ) );
}
{
dblock lk;
Client::Context ctx( ns() );
c->recoverFromYield();
ASSERT( c->ok() );
// {$natural:1} plan does not recover, {_id:1} plan does.
ASSERT( 1 < c->current().getIntField( "a" ) );
}
}
};
/**
* Yielding with multiple plans and capped overwrite with unrecoverable cursor
* inctive at time of yield.
*/
class YieldMultiplePlansCappedOverwriteManual2 : public Base {
public:
void run() {
_cli.createCollection( ns(), 1000, true );
_cli.insert( ns(), BSON( "_id" << 1 << "a" << 1 ) );
_cli.ensureIndex( ns(), BSON( "_id" << 1 ) );
shared_ptr<Cursor> c;
{
dblock lk;
Client::Context ctx( ns() );
c = newQueryOptimizerCursor( ns(), BSON( "_id" << GT << 0 << "a" << GT << 0 ) );
ASSERT_EQUALS( 1, c->current().getIntField( "_id" ) );
ASSERT( !c->getsetdup( c->currLoc() ) );
ASSERT( c->prepareToYield() );
}
int n = 1;
while( _cli.count( ns(), BSON( "_id" << 1 ) ) > 0 ) {
++n;
_cli.insert( ns(), BSON( "_id" << n << "a" << n ) );
}
{
dblock lk;
Client::Context ctx( ns() );
c->recoverFromYield();
ASSERT( c->ok() );
// {$natural:1} plan does not recover, {_id:1} plan does.
ASSERT( 1 < c->current().getIntField( "_id" ) );
ASSERT( !c->getsetdup( c->currLoc() ) );
int i = c->current().getIntField( "_id" );
ASSERT( c->advance() );
ASSERT( c->getsetdup( c->currLoc() ) );
while( i < n ) {
ASSERT( c->advance() );
++i;
ASSERT_EQUALS( i, c->current().getIntField( "_id" ) );
}
}
}
};
/** Yield with takeover cursor. */
class YieldTakeover : public Base {
public:
void run() {
for( int i = 0; i < 150; ++i ) {
_cli.insert( ns(), BSON( "_id" << i << "a" << i ) );
}
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
{
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "_id" << GTE << 0 << "a" << GTE << 0 ) );
for( int i = 0; i < 120; ++i ) {
ASSERT( advance() );
}
ASSERT( ok() );
ASSERT_EQUALS( 120, current().getIntField( "_id" ) );
ASSERT( prepareToYield() );
}
_cli.remove( ns(), BSON( "_id" << 120 ) );
{
dblock lk;
Client::Context ctx( ns() );
recoverFromYield();
ASSERT( ok() );
ASSERT_EQUALS( 121, current().getIntField( "_id" ) );
ASSERT( advance() );
ASSERT_EQUALS( 122, current().getIntField( "_id" ) );
}
}
};
/** Yield with BacicCursor takeover cursor. */
class YieldTakeoverBasic : public Base {
public:
void run() {
for( int i = 0; i < 150; ++i ) {
_cli.insert( ns(), BSON( "_id" << i << "a" << BSON_ARRAY( i << i+1 ) ) );
}
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
auto_ptr<ClientCursor> cc;
auto_ptr<ClientCursor::YieldData> data( new ClientCursor::YieldData() );
{
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "b" << NE << 0 << "a" << GTE << 0 ) );
cc.reset( new ClientCursor( QueryOption_NoCursorTimeout, c(), ns() ) );
for( int i = 0; i < 120; ++i ) {
ASSERT( advance() );
}
ASSERT( ok() );
ASSERT_EQUALS( 120, current().getIntField( "_id" ) );
cc->prepareToYield( *data );
}
_cli.remove( ns(), BSON( "_id" << 120 ) );
{
dblock lk;
Client::Context ctx( ns() );
ASSERT( ClientCursor::recoverFromYield( *data ) );
ASSERT( ok() );
ASSERT_EQUALS( 121, current().getIntField( "_id" ) );
ASSERT( advance() );
ASSERT_EQUALS( 122, current().getIntField( "_id" ) );
}
}
};
/** Yield with advance of inactive cursor. */
class YieldInactiveCursorAdvance : public Base {
public:
void run() {
for( int i = 0; i < 10; ++i ) {
_cli.insert( ns(), BSON( "_id" << i << "a" << 10 - i ) );
}
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
{
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "_id" << GT << 0 << "a" << GT << 0 ) );
ASSERT( ok() );
ASSERT_EQUALS( 1, current().getIntField( "_id" ) );
ASSERT( advance() );
ASSERT_EQUALS( 9, current().getIntField( "_id" ) );
ASSERT( advance() );
ASSERT_EQUALS( 2, current().getIntField( "_id" ) );
ASSERT( prepareToYield() );
}
_cli.remove( ns(), BSON( "_id" << 9 ) );
{
dblock lk;
Client::Context ctx( ns() );
recoverFromYield();
ASSERT( ok() );
ASSERT_EQUALS( 8, current().getIntField( "_id" ) );
ASSERT( advance() );
ASSERT_EQUALS( 3, current().getIntField( "_id" ) );
ASSERT( advance() );
ASSERT_EQUALS( 7, current().getIntField( "_id" ) );
}
}
};
class OrderId : public Base {
public:
void run() {
for( int i = 0; i < 10; ++i ) {
_cli.insert( ns(), BSON( "_id" << i ) );
}
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSONObj(), BSON( "_id" << 1 ) );
for( int i = 0; i < 10; ++i, advance() ) {
ASSERT( ok() );
ASSERT_EQUALS( i, current().getIntField( "_id" ) );
}
}
};
class OrderMultiIndex : public Base {
public:
void run() {
for( int i = 0; i < 10; ++i ) {
_cli.insert( ns(), BSON( "_id" << i << "a" << 1 ) );
}
_cli.ensureIndex( ns(), BSON( "_id" << 1 << "a" << 1 ) );
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "_id" << GTE << 0 << "a" << GTE << 0 ), BSON( "_id" << 1 ) );
for( int i = 0; i < 10; ++i, advance() ) {
ASSERT( ok() );
ASSERT_EQUALS( i, current().getIntField( "_id" ) );
}
}
};
class OrderReject : public Base {
public:
void run() {
for( int i = 0; i < 10; ++i ) {
_cli.insert( ns(), BSON( "_id" << i << "a" << i % 5 ) );
}
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "a" << GTE << 3 ), BSON( "_id" << 1 ) );
ASSERT( ok() );
ASSERT_EQUALS( 3, current().getIntField( "_id" ) );
ASSERT( advance() );
ASSERT_EQUALS( 4, current().getIntField( "_id" ) );
ASSERT( advance() );
ASSERT_EQUALS( 8, current().getIntField( "_id" ) );
ASSERT( advance() );
ASSERT_EQUALS( 9, current().getIntField( "_id" ) );
ASSERT( !advance() );
}
};
class OrderNatural : public Base {
public:
void run() {
_cli.insert( ns(), BSON( "_id" << 5 ) );
_cli.insert( ns(), BSON( "_id" << 4 ) );
_cli.insert( ns(), BSON( "_id" << 6 ) );
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "_id" << GT << 0 ), BSON( "$natural" << 1 ) );
ASSERT( ok() );
ASSERT_EQUALS( 5, current().getIntField( "_id" ) );
ASSERT( advance() );
ASSERT_EQUALS( 4, current().getIntField( "_id" ) );
ASSERT( advance() );
ASSERT_EQUALS( 6, current().getIntField( "_id" ) );
ASSERT( !advance() );
}
};
class OrderUnindexed : public Base {
public:
void run() {
dblock lk;
Client::Context ctx( ns() );
ASSERT( !newQueryOptimizerCursor( ns(), BSONObj(), BSON( "a" << 1 ) ).get() );
}
};
class RecordedOrderInvalid : public Base {
public:
void run() {
_cli.insert( ns(), BSON( "a" << 1 << "b" << 1 ) );
_cli.insert( ns(), BSON( "a" << 2 << "b" << 2 ) );
_cli.insert( ns(), BSON( "a" << 3 << "b" << 3 ) );
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
_cli.ensureIndex( ns(), BSON( "b" << 1 ) );
// Plan {a:1} will be chosen and recorded.
ASSERT( _cli.query( ns(), QUERY( "a" << 2 ).sort( "b" ) )->more() );
dblock lk;
Client::Context ctx( ns() );
shared_ptr<Cursor> c = newQueryOptimizerCursor( ns(), BSON( "a" << 2 ), BSON( "b" << 1 ) );
// Check that we are scanning {b:1} not {a:1}, since {a:1} is not properly ordered.
for( int i = 0; i < 3; ++i ) {
ASSERT( c->ok() );
c->advance();
}
ASSERT( !c->ok() );
}
};
class KillOp : public Base {
public:
void run() {
_cli.insert( ns(), BSON( "_id" << 1 << "b" << 1 ) );
_cli.insert( ns(), BSON( "_id" << 2 << "b" << 2 ) );
_cli.ensureIndex( ns(), BSON( "b" << 1 ) );
Client::ReadContext ctx( ns() );
setQueryOptimizerCursor( BSON( "_id" << GT << 0 << "b" << GT << 0 ) );
ASSERT( ok() );
cc().curop()->kill();
// First advance() call throws, subsequent calls just fail.
ASSERT_THROWS( advance(), MsgAssertionException );
ASSERT( !advance() );
}
};
class KillOpFirstClause : public Base {
public:
void run() {
_cli.insert( ns(), BSON( "_id" << 1 << "b" << 1 ) );
_cli.insert( ns(), BSON( "_id" << 2 << "b" << 2 ) );
_cli.ensureIndex( ns(), BSON( "b" << 1 ) );
Client::ReadContext ctx( ns() );
shared_ptr<Cursor> c = newQueryOptimizerCursor( ns(), BSON( "$or" << BSON_ARRAY( BSON( "_id" << GT << 0 ) << BSON( "b" << GT << 0 ) ) ) );
ASSERT( c->ok() );
cc().curop()->kill();
// First advance() call throws, subsequent calls just fail.
ASSERT_THROWS( c->advance(), MsgAssertionException );
ASSERT( !c->advance() );
}
};
class Nscanned : public Base {
public:
void run() {
for( int i = 0; i < 120; ++i ) {
_cli.insert( ns(), BSON( "_id" << i << "a" << i ) );
}
dblock lk;
Client::Context ctx( ns() );
shared_ptr<Cursor> c = newQueryOptimizerCursor( ns(), BSON( "_id" << GTE << 0 << "a" << GTE << 0 ) );
ASSERT( c->ok() );
ASSERT_EQUALS( 2, c->nscanned() );
c->advance();
ASSERT( c->ok() );
ASSERT_EQUALS( 2, c->nscanned() );
c->advance();
for( int i = 3; i < 222; ++i ) {
ASSERT( c->ok() );
c->advance();
}
ASSERT( !c->ok() );
}
};
/* Test 'touching earlier iterate' without doc modifications. */
class TouchEarlierIterate : public Base {
public:
void run() {
_cli.insert( ns(), BSON( "_id" << 1 << "b" << 1 ) );
_cli.insert( ns(), BSON( "_id" << 2 << "b" << 2 ) );
_cli.ensureIndex( ns(), BSON( "b" << 1 ) );
Client::ReadContext ctx( ns() );
shared_ptr<Cursor> c = newQueryOptimizerCursor( ns(), BSON( "_id" << GT << 0 << "b" << GT << 0 ) );
ASSERT( c->ok() );
while( c->ok() ) {
DiskLoc loc = c->currLoc();
BSONObj obj = c->current();
c->prepareToTouchEarlierIterate();
c->recoverFromTouchingEarlierIterate();
ASSERT( loc == c->currLoc() );
ASSERT_EQUALS( obj, c->current() );
c->advance();
}
}
};
/* Test 'touching earlier iterate' with doc modifications. */
class TouchEarlierIterateDelete : public Base {
public:
void run() {
_cli.insert( ns(), BSON( "_id" << 1 << "b" << 1 ) );
_cli.insert( ns(), BSON( "_id" << 2 << "b" << 2 ) );
_cli.ensureIndex( ns(), BSON( "b" << 1 ) );
DiskLoc firstLoc;
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "_id" << GT << 0 << "b" << GT << 0 ) );
ASSERT( ok() );
firstLoc = currLoc();
ASSERT( c()->advance() );
prepareToTouchEarlierIterate();
_cli.remove( ns(), BSON( "_id" << 1 ), true );
recoverFromTouchingEarlierIterate();
ASSERT( ok() );
while( ok() ) {
ASSERT( firstLoc != currLoc() );
c()->advance();
}
}
};
/* Test 'touch earlier iterate' with several doc modifications. */
class TouchEarlierIterateDeleteMultiple : public Base {
public:
void run() {
for( int i = 1; i < 10; ++i ) {
_cli.insert( ns(), BSON( "_id" << i << "b" << i ) );
}
_cli.ensureIndex( ns(), BSON( "b" << 1 ) );
set<DiskLoc> deleted;
int id = 0;
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "_id" << GT << 0 << "b" << GT << 0 ) );
while( 1 ) {
if ( !ok() ) {
break;
}
ASSERT( deleted.count( currLoc() ) == 0 );
id = current()["_id"].Int();
deleted.insert( currLoc() );
c()->advance();
prepareToTouchEarlierIterate();
_cli.remove( ns(), BSON( "_id" << id ), true );
recoverFromTouchingEarlierIterate();
}
ASSERT_EQUALS( 9U, deleted.size() );
}
};
/* Test 'touch earlier iterate' with takeover. */
class TouchEarlierIterateTakeover : public Base {
public:
void run() {
for( int i = 1; i < 600; ++i ) {
_cli.insert( ns(), BSON( "_id" << i << "b" << i ) );
}
_cli.ensureIndex( ns(), BSON( "b" << 1 ) );
Client::ReadContext ctx( ns() );
setQueryOptimizerCursor( BSON( "_id" << GT << 0 << "b" << GT << 0 ) );
ASSERT( ok() );
int count = 1;
while( ok() ) {
DiskLoc loc = currLoc();
BSONObj obj = current();
prepareToTouchEarlierIterate();
recoverFromTouchingEarlierIterate();
ASSERT( loc == currLoc() );
ASSERT_EQUALS( obj, current() );
count += mayReturnCurrent();
c()->advance();
}
ASSERT_EQUALS( 599, count );
}
};
/* Test 'touch earlier iterate' with takeover and deletes. */
class TouchEarlierIterateTakeoverDeleteMultiple : public Base {
public:
void run() {
for( int i = 1; i < 600; ++i ) {
_cli.insert( ns(), BSON( "_id" << i << "b" << i ) );
}
_cli.ensureIndex( ns(), BSON( "b" << 1 ) );
set<DiskLoc> deleted;
int id = 0;
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursorWithoutAdvancing( BSON( "_id" << GT << 0 << "b" << GT << 0 ) );
while( 1 ) {
if ( !ok() ) {
break;
}
ASSERT( deleted.count( currLoc() ) == 0 );
id = current()["_id"].Int();
ASSERT( c()->currentMatches() );
ASSERT( !c()->getsetdup( currLoc() ) );
deleted.insert( currLoc() );
c()->advance();
prepareToTouchEarlierIterate();
_cli.remove( ns(), BSON( "_id" << id ), true );
recoverFromTouchingEarlierIterate();
}
ASSERT_EQUALS( 599U, deleted.size() );
}
};
/* Test 'touch earlier iterate' with undexed cursor takeover and deletes. */
class TouchEarlierIterateUnindexedTakeoverDeleteMultiple : public Base {
public:
void run() {
for( int i = 1; i < 600; ++i ) {
_cli.insert( ns(), BSON( "a" << BSON_ARRAY( i << i+1 ) << "b" << BSON_ARRAY( i << i+1 ) << "_id" << i ) );
}
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
_cli.ensureIndex( ns(), BSON( "b" << 1 ) );
set<DiskLoc> deleted;
int id = 0;
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursorWithoutAdvancing( BSON( "a" << GT << 0 << "b" << GT << 0 ) );
while( 1 ) {
if ( !ok() ) {
break;
}
ASSERT( deleted.count( currLoc() ) == 0 );
id = current()["_id"].Int();
ASSERT( c()->currentMatches() );
ASSERT( !c()->getsetdup( currLoc() ) );
deleted.insert( currLoc() );
c()->advance();
prepareToTouchEarlierIterate();
_cli.remove( ns(), BSON( "_id" << id ), true );
recoverFromTouchingEarlierIterate();
}
ASSERT_EQUALS( 599U, deleted.size() );
}
};
/* Test 'touch earlier iterate' with takeover and deletes, with multiple advances in a row. */
class TouchEarlierIterateTakeoverDeleteMultipleMultiAdvance : public Base {
public:
void run() {
for( int i = 1; i < 600; ++i ) {
_cli.insert( ns(), BSON( "_id" << i << "b" << i ) );
}
_cli.ensureIndex( ns(), BSON( "b" << 1 ) );
set<DiskLoc> deleted;
int id = 0;
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "_id" << GT << 0 << "b" << GT << 0 ) );
while( 1 ) {
if ( !ok() ) {
break;
}
ASSERT( deleted.count( currLoc() ) == 0 );
id = current()["_id"].Int();
ASSERT( c()->currentMatches() );
deleted.insert( currLoc() );
advance();
prepareToTouchEarlierIterate();
_cli.remove( ns(), BSON( "_id" << id ), true );
recoverFromTouchingEarlierIterate();
}
ASSERT_EQUALS( 599U, deleted.size() );
}
};
/* Test yield recovery failure of component capped cursor. */
class InitialCappedWrapYieldRecoveryFailure : public Base {
public:
void run() {
_cli.createCollection( ns(), 1000, true );
_cli.insert( ns(), BSON( "_id" << 1 << "x" << 1 ) );
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "x" << GT << 0 ) );
ASSERT_EQUALS( 1, current().getIntField( "x" ) );
ClientCursor::CleanupPointer p;
p.reset( new ClientCursor( QueryOption_NoCursorTimeout, c(), ns() ) );
ClientCursor::YieldData yieldData;
p->prepareToYield( yieldData );
int x = 2;
while( _cli.count( ns(), BSON( "x" << 1 ) ) > 0 ) {
_cli.insert( ns(), BSON( "_id" << x << "x" << x ) );
++x;
}
// TODO - Might be preferable to return false rather than assert here.
ASSERT_THROWS( ClientCursor::recoverFromYield( yieldData ), AssertionException );
}
};
/* Test yield recovery failure of takeover capped cursor. */
class TakeoverCappedWrapYieldRecoveryFailure : public Base {
public:
void run() {
_cli.createCollection( ns(), 10000, true );
for( int i = 0; i < 300; ++i ) {
_cli.insert( ns(), BSON( "_id" << i << "x" << i ) );
}
ClientCursor::CleanupPointer p;
ClientCursor::YieldData yieldData;
{
dblock lk;
Client::Context ctx( ns() );
setQueryOptimizerCursor( BSON( "x" << GTE << 0 ) );
for( int i = 0; i < 299; ++i ) {
advance();
}
ASSERT_EQUALS( 299, current().getIntField( "x" ) );
p.reset( new ClientCursor( QueryOption_NoCursorTimeout, c(), ns() ) );
p->prepareToYield( yieldData );
}
int i = 300;
while( _cli.count( ns(), BSON( "x" << 299 ) ) > 0 ) {
_cli.insert( ns(), BSON( "_id" << i << "x" << i ) );
++i;
}
dblock lk;
Client::Context ctx( ns() );
ASSERT( !ClientCursor::recoverFromYield( yieldData ) );
}
};
/** Test that a ClientCursor holding a QueryOptimizerCursor may be safely invalidated. */
class InvalidateClientCursorHolder : public Base {
public:
void run() {
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
_cli.ensureIndex( ns(), BSON( "b" << 1 ) );
_cli.insert( ns(), BSON( "a" << 1 << "b" << 1 ) );
dblock lk;
Client::Context ctx( ns() );
ClientCursor::CleanupPointer p;
p.reset
( new ClientCursor
( QueryOption_NoCursorTimeout,
NamespaceDetailsTransient::getCursor
( ns(), BSON( "a" << GTE << 0 << "b" << GTE << 0 ) ),
ns() ) );
// Construct component client cursors.
ClientCursor::YieldData yieldData;
p->prepareToYield( yieldData );
ASSERT( nNsCursors() > 1 );
ClientCursor::invalidate( ns() );
ASSERT_EQUALS( 0U, nNsCursors() );
}
};
/** Test that a ClientCursor holding a QueryOptimizerCursor may be safely timed out. */
class TimeoutClientCursorHolder : public Base {
public:
void run() {
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
_cli.ensureIndex( ns(), BSON( "b" << 1 ) );
_cli.insert( ns(), BSON( "a" << 1 << "b" << 1 ) );
dblock lk;
Client::Context ctx( ns() );
ClientCursor::CleanupPointer p;
p.reset
( new ClientCursor
( 0,
NamespaceDetailsTransient::getCursor
( ns(), BSON( "a" << GTE << 0 << "b" << GTE << 0 ) ),
ns() ) );
// Construct component client cursors.
ClientCursor::YieldData yieldData;
p->prepareToYield( yieldData );
ASSERT( nNsCursors() > 1 );
ClientCursor::idleTimeReport( 600001 );
ASSERT_EQUALS( 0U, nNsCursors() );
}
};
class AllowOutOfOrderPlan : public Base {
public:
void run() {
dblock lk;
Client::Context ctx( ns() );
shared_ptr<Cursor> c =
newQueryOptimizerCursor( ns(), BSONObj(), BSON( "a" << 1 ), false, false );
ASSERT( c );
}
};
class NoTakeoverByOutOfOrderPlan : public Base {
public:
void run() {
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
// Add enough early matches that the {$natural:1} plan would be chosen if it did not
// require scan and order.
for( int i = 0; i < 150; ++i ) {
_cli.insert( ns(), BSON( "a" << 2 << "b" << 1 ) );
}
// Add non matches early on the {a:1} plan.
for( int i = 0; i < 150; ++i ) {
_cli.insert( ns(), BSON( "a" << 1 << "b" << 5 ) );
}
// Add enough matches outside the {a:1} index range that the {$natural:1} scan will not
// complete before the {a:1} plan records 101 matches and is selected for takeover.
for( int i = 0; i < 150; ++i ) {
_cli.insert( ns(), BSON( "a" << 3 << "b" << 10 ) );
}
dblock lk;
Client::Context ctx( ns() );
shared_ptr<Cursor> c =
newQueryOptimizerCursor( ns(), BSON( "a" << LT << 3 << "b" << 1 ), BSON( "a" << 1 ), false, false );
ASSERT( c );
BSONObj idxKey;
while( c->ok() ) {
idxKey = c->indexKeyPattern();
c->advance();
}
// Check that the ordered plan {a:1} took over, despite the unordered plan {$natural:1}
// seeing > 101 matches.
ASSERT_EQUALS( BSON( "a" << 1 ), idxKey );
}
};
namespace GetCursor {
class Base : public QueryOptimizerCursorTests::Base {
public:
Base() {
// create collection
_cli.insert( ns(), BSON( "_id" << 5 ) );
}
virtual ~Base() {}
void run() {
dblock lk;
Client::Context ctx( ns() );
bool simpleEqualityMatch;
if ( expectException() ) {
ASSERT_THROWS
( NamespaceDetailsTransient::getCursor
( ns(), query(), order(), planPolicy(), &simpleEqualityMatch ),
MsgAssertionException );
return;
}
shared_ptr<Cursor> c =
NamespaceDetailsTransient::getCursor( ns(), query(), order(), planPolicy(),
&simpleEqualityMatch );
ASSERT_EQUALS( expectSimpleEquality(), simpleEqualityMatch );
string type = c->toString().substr( 0, expectedType().length() );
ASSERT_EQUALS( expectedType(), type );
check( c );
}
protected:
virtual string expectedType() const { return "TESTDUMMY"; }
virtual bool expectException() const { return false; }
virtual bool expectSimpleEquality() const { return false; }
virtual BSONObj query() const { return BSONObj(); }
virtual BSONObj order() const { return BSONObj(); }
virtual const QueryPlanSelectionPolicy &planPolicy() const {
return QueryPlanSelectionPolicy::any();
}
virtual void check( const shared_ptr<Cursor> &c ) {
ASSERT( c->ok() );
ASSERT( !c->matcher() );
ASSERT_EQUALS( 5, c->current().getIntField( "_id" ) );
ASSERT( !c->advance() );
}
};
class NoConstraints : public Base {
string expectedType() const { return "BasicCursor"; }
};
class SimpleId : public Base {
public:
SimpleId() {
_cli.insert( ns(), BSON( "_id" << 0 ) );
_cli.insert( ns(), BSON( "_id" << 10 ) );
}
string expectedType() const { return "BtreeCursor _id_"; }
BSONObj query() const { return BSON( "_id" << 5 ); }
};
class OptimalIndex : public Base {
public:
OptimalIndex() {
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
_cli.insert( ns(), BSON( "a" << 5 ) );
_cli.insert( ns(), BSON( "a" << 6 ) );
}
string expectedType() const { return "BtreeCursor a_1"; }
BSONObj query() const { return BSON( "a" << GTE << 5 ); }
void check( const shared_ptr<Cursor> &c ) {
ASSERT( c->ok() );
ASSERT( c->matcher() );
ASSERT_EQUALS( 5, c->current().getIntField( "a" ) );
ASSERT( c->matcher()->matchesCurrent( c.get() ) );
ASSERT( c->advance() );
ASSERT_EQUALS( 6, c->current().getIntField( "a" ) );
ASSERT( c->matcher()->matchesCurrent( c.get() ) );
ASSERT( !c->advance() );
}
};
class SimpleKeyMatch : public Base {
public:
SimpleKeyMatch() {
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
_cli.update( ns(), BSONObj(), BSON( "$set" << BSON( "a" << true ) ) );
}
string expectedType() const { return "BtreeCursor a_1"; }
bool expectSimpleEquality() const { return true; }
BSONObj query() const { return BSON( "a" << true ); }
virtual void check( const shared_ptr<Cursor> &c ) {
ASSERT( c->ok() );
ASSERT_EQUALS( 5, c->current().getIntField( "_id" ) );
ASSERT( !c->advance() );
}
};
class Geo : public Base {
public:
Geo() {
_cli.insert( ns(), BSON( "_id" << 44 << "loc" << BSON_ARRAY( 44 << 45 ) ) );
_cli.ensureIndex( ns(), BSON( "loc" << "2d" ) );
}
string expectedType() const { return "GeoSearchCursor"; }
BSONObj query() const { return fromjson( "{ loc : { $near : [50,50] } }" ); }
void check( const shared_ptr<Cursor> &c ) {
ASSERT( c->ok() );
ASSERT( c->matcher() );
ASSERT( c->matcher()->matchesCurrent( c.get() ) );
ASSERT_EQUALS( 44, c->current().getIntField( "_id" ) );
ASSERT( !c->advance() );
}
};
class PreventOutOfOrderPlan : public QueryOptimizerCursorTests::Base {
public:
void run() {
_cli.insert( ns(), BSON( "_id" << 5 ) );
dblock lk;
Client::Context ctx( ns() );
shared_ptr<Cursor> c = NamespaceDetailsTransient::getCursor( ns(), BSONObj(), BSON( "b" << 1 ) );
ASSERT( !c );
}
};
class AllowOutOfOrderPlan : public Base {
public:
void run() {
dblock lk;
Client::Context ctx( ns() );
ParsedQuery parsedQuery
( ns(), 0, 0, 0, BSON( "$query" << BSONObj() << "$orderby" << BSON( "a" << 1 ) ),
BSONObj() );
shared_ptr<Cursor> c =
NamespaceDetailsTransient::getCursor( ns(), BSONObj(), BSON( "a" << 1 ), false, 0,
&parsedQuery );
ASSERT( c );
}
};
class BestSavedOutOfOrder : public QueryOptimizerCursorTests::Base {
public:
void run() {
_cli.insert( ns(), BSON( "_id" << 5 << "b" << BSON_ARRAY( 1 << 2 << 3 << 4 << 5 ) ) );
_cli.insert( ns(), BSON( "_id" << 1 << "b" << 6 ) );
_cli.ensureIndex( ns(), BSON( "b" << 1 ) );
// record {_id:1} index for this query
ASSERT( _cli.query( ns(), QUERY( "_id" << GT << 0 << "b" << GT << 0 ).sort( "b" ) )->more() );
dblock lk;
Client::Context ctx( ns() );
shared_ptr<Cursor> c = NamespaceDetailsTransient::getCursor( ns(), BSON( "_id" << GT << 0 << "b" << GT << 0 ), BSON( "b" << 1 ) );
// {_id:1} requires scan and order, so {b:1} must be chosen.
ASSERT( c );
ASSERT_EQUALS( 5, c->current().getIntField( "_id" ) );
}
};
/**
* If an optimal plan is a candidate, return a cursor for it rather than a QueryOptimizerCursor. Avoid
* caching optimal plans since simple cursors will not save a plan anyway (so in the most common case optimal
* plans won't be cached) and because this simplifies the implementation for selecting a simple cursor.
*/
class BestSavedOptimal : public QueryOptimizerCursorTests::Base {
public:
void run() {
_cli.insert( ns(), BSON( "_id" << 1 ) );
_cli.ensureIndex( ns(), BSON( "_id" << 1 << "q" << 1 ) );
// {_id:1} index not recorded for these queries since it is an optimal index.
ASSERT( _cli.query( ns(), QUERY( "_id" << GT << 0 ) )->more() );
ASSERT( _cli.query( ns(), QUERY( "$or" << BSON_ARRAY( BSON( "_id" << GT << 0 ) ) ) )->more() );
dblock lk;
Client::Context ctx( ns() );
// Check that no plan was recorded for this query.
ASSERT( BSONObj().woCompare( NamespaceDetailsTransient::get_inlock( ns() ).indexForPattern( FieldRangeSet( ns(), BSON( "_id" << GT << 0 ), true ).pattern() ) ) == 0 );
shared_ptr<Cursor> c = NamespaceDetailsTransient::getCursor( ns(), BSON( "_id" << GT << 0 ) );
// No need for query optimizer cursor since the plan is optimal.
ASSERT_EQUALS( "BtreeCursor _id_", c->toString() );
}
};
/** If a no optimal plan is a candidate a QueryOptimizerCursor should be returned, even if plan has been recorded. */
class BestSavedNotOptimal : public QueryOptimizerCursorTests::Base {
public:
void run() {
_cli.insert( ns(), BSON( "_id" << 1 << "q" << 1 ) );
_cli.ensureIndex( ns(), BSON( "q" << 1 ) );
// Record {_id:1} index for this query
ASSERT( _cli.query( ns(), QUERY( "q" << 1 << "_id" << 1 ) )->more() );
dblock lk;
Client::Context ctx( ns() );
ASSERT( BSON( "_id" << 1 ).woCompare( NamespaceDetailsTransient::get_inlock( ns() ).indexForPattern( FieldRangeSet( ns(), BSON( "q" << 1 << "_id" << 1 ), true ).pattern() ) ) == 0 );
shared_ptr<Cursor> c = NamespaceDetailsTransient::getCursor( ns(), BSON( "q" << 1 << "_id" << 1 ) );
// Need query optimizer cursor since the cached plan is not optimal.
ASSERT_EQUALS( "QueryOptimizerCursor", c->toString() );
}
};
class MultiIndex : public Base {
public:
MultiIndex() {
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
}
string expectedType() const { return "QueryOptimizerCursor"; }
BSONObj query() const { return BSON( "_id" << GT << 0 << "a" << GT << 0 ); }
void check( const shared_ptr<Cursor> &c ) {}
};
namespace RequireIndex {
class Base : public GetCursor::Base {
const QueryPlanSelectionPolicy &planPolicy() const {
return QueryPlanSelectionPolicy::indexOnly();
}
};
class NoConstraints : public Base {
bool expectException() const { return true; }
};
class SimpleId : public Base {
string expectedType() const { return "BtreeCursor _id_"; }
BSONObj query() const { return BSON( "_id" << 5 ); }
};
class UnindexedQuery : public Base {
bool expectException() const { return true; }
BSONObj query() const { return BSON( "a" << GTE << 5 ); }
};
class IndexedQuery : public Base {
public:
IndexedQuery() {
_cli.insert( ns(), BSON( "_id" << 6 << "a" << 6 << "c" << 4 ) );
_cli.ensureIndex( ns(), BSON( "a" << 1 << "b" << 1 << "c" << 1 ) );
}
string expectedType() const { return "QueryOptimizerCursor"; }
BSONObj query() const { return BSON( "a" << GTE << 5 << "c" << 4 ); }
void check( const shared_ptr<Cursor> &c ) {
ASSERT( c->ok() );
ASSERT( c->matcher() );
ASSERT_EQUALS( 6, c->current().getIntField( "_id" ) );
ASSERT( !c->advance() );
}
};
class SecondOrClauseIndexed : public Base {
public:
SecondOrClauseIndexed() {
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
_cli.ensureIndex( ns(), BSON( "b" << 1 ) );
_cli.insert( ns(), BSON( "a" << 1 ) );
_cli.insert( ns(), BSON( "b" << 1 ) );
}
string expectedType() const { return "QueryOptimizerCursor"; }
BSONObj query() const { return fromjson( "{$or:[{a:1},{b:1}]}" ); }
void check( const shared_ptr<Cursor> &c ) {
ASSERT( c->ok() );
ASSERT( c->matcher() );
ASSERT( c->advance() );
ASSERT( !c->advance() ); // 2 matches exactly
}
};
class SecondOrClauseUnindexed : public Base {
public:
SecondOrClauseUnindexed() {
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
_cli.insert( ns(), BSON( "a" << 1 ) );
}
bool expectException() const { return true; }
BSONObj query() const { return fromjson( "{$or:[{a:1},{b:1}]}" ); }
};
class SecondOrClauseUnindexedUndetected : public Base {
public:
SecondOrClauseUnindexedUndetected() {
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
_cli.ensureIndex( ns(), BSON( "a" << 1 << "b" << 1 ) );
_cli.insert( ns(), BSON( "a" << 1 ) );
_cli.insert( ns(), BSON( "b" << 1 ) );
}
string expectedType() const { return "QueryOptimizerCursor"; }
BSONObj query() const { return fromjson( "{$or:[{a:1},{b:1}]}" ); }
void check( const shared_ptr<Cursor> &c ) {
ASSERT( c->ok() );
ASSERT( c->matcher() );
// An unindexed cursor is required for the second clause, but is not allowed.
ASSERT_THROWS( c->advance(), MsgAssertionException );
}
};
class RecordedUnindexedPlan : public Base {
public:
RecordedUnindexedPlan() {
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
_cli.insert( ns(), BSON( "a" << BSON_ARRAY( 1 << 2 << 3 ) << "b" << 1 ) );
BSONObj explain = _cli.findOne( ns(), QUERY( "a" << GT << 0 << "b" << 1 ).explain() );
ASSERT_EQUALS( "BasicCursor", explain[ "cursor" ].String() );
}
string expectedType() const { return "QueryOptimizerCursor"; }
BSONObj query() const { return BSON( "a" << GT << 0 << "b" << 1 ); }
void check( const shared_ptr<Cursor> &c ) {
ASSERT( c->ok() );
ASSERT_EQUALS( BSON( "a" << 1 ), c->indexKeyPattern() );
while( c->advance() ) {
ASSERT_EQUALS( BSON( "a" << 1 ), c->indexKeyPattern() );
}
}
};
} // namespace RequireIndex
namespace IdElseNatural {
class Base : public GetCursor::Base {
const QueryPlanSelectionPolicy &planPolicy() const {
return QueryPlanSelectionPolicy::idElseNatural();
}
};
class AllowOptimalNaturalPlan : public Base {
string expectedType() const { return "BasicCursor"; }
void check( const shared_ptr<Cursor> &c ) {
ASSERT( c->ok() );
ASSERT( !c->matcher() );
ASSERT_EQUALS( 5, c->current().getIntField( "_id" ) );
ASSERT( !c->advance() );
}
};
class AllowOptimalIdPlan : public Base {
string expectedType() const { return "BtreeCursor _id_"; }
BSONObj query() const { return BSON( "_id" << 5 ); }
};
class HintedIdForQuery : public Base {
public:
HintedIdForQuery( const BSONObj &query ) : _query( query ) {
_cli.remove( ns(), BSONObj() );
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
_cli.insert( ns(), BSON( "_id" << 1 << "a" << 1 ) );
}
string expectedType() const { return "BtreeCursor _id_"; }
BSONObj query() const { return _query; }
void check( const shared_ptr<Cursor> &c ) {
ASSERT( c->ok() );
ASSERT( c->currentMatches() );
ASSERT_EQUALS( 1, c->current().getIntField( "_id" ) );
ASSERT( !c->advance() );
}
private:
BSONObj _query;
};
class HintedNaturalForQuery : public Base {
public:
HintedNaturalForQuery( const BSONObj &query ) : _query( query ) {
_cli.dropCollection( ns() );
_cli.createCollection( ns(), 1024, true );
_cli.ensureIndex( ns(), BSON( "a" << 1 ) );
_cli.insert( ns(), BSON( "_id" << 1 << "a" << 1 ) );
}
~HintedNaturalForQuery() {
_cli.dropCollection( ns() );
}
string expectedType() const { return "ForwardCappedCursor"; }
BSONObj query() const { return _query; }
void check( const shared_ptr<Cursor> &c ) {
ASSERT( c->ok() );
ASSERT( c->currentMatches() );
ASSERT_EQUALS( 1, c->current().getIntField( "_id" ) );
ASSERT( !c->advance() );
}
private:
BSONObj _query;
};
} // namespace IdElseNatural
} // namespace GetCursor
class All : public Suite {
public:
All() : Suite( "queryoptimizercursor" ) {}
void setupTests() {
__forceLinkGeoPlugin();
add<QueryOptimizerCursorTests::CachedMatchCounterCount>();
add<QueryOptimizerCursorTests::CachedMatchCounterAccumulate>();
add<QueryOptimizerCursorTests::CachedMatchCounterDedup>();
add<QueryOptimizerCursorTests::CachedMatchCounterNscanned>();
add<QueryOptimizerCursorTests::SmallDupSetUpgrade>();
add<QueryOptimizerCursorTests::CachedMatchCounterCount>();
add<QueryOptimizerCursorTests::SmallDupSetUpgradeRead>();
add<QueryOptimizerCursorTests::SmallDupSetUpgradeWrite>();
add<QueryOptimizerCursorTests::Empty>();
add<QueryOptimizerCursorTests::Unindexed>();
add<QueryOptimizerCursorTests::Basic>();
add<QueryOptimizerCursorTests::NoMatch>();
add<QueryOptimizerCursorTests::Interleaved>();
add<QueryOptimizerCursorTests::NotMatch>();
add<QueryOptimizerCursorTests::StopInterleaving>();
add<QueryOptimizerCursorTests::TakeoverWithDup>();
add<QueryOptimizerCursorTests::TakeoverWithNonMatches>();
add<QueryOptimizerCursorTests::TakeoverWithTakeoverDup>();
add<QueryOptimizerCursorTests::BasicOr>();
add<QueryOptimizerCursorTests::OrFirstClauseEmpty>();
add<QueryOptimizerCursorTests::OrSecondClauseEmpty>();
add<QueryOptimizerCursorTests::OrMultipleClausesEmpty>();
add<QueryOptimizerCursorTests::TakeoverCountOr>();
add<QueryOptimizerCursorTests::TakeoverEndOfOrClause>();
add<QueryOptimizerCursorTests::TakeoverBeforeEndOfOrClause>();
add<QueryOptimizerCursorTests::TakeoverAfterEndOfOrClause>();
add<QueryOptimizerCursorTests::ManualMatchingDeduping>();
add<QueryOptimizerCursorTests::ManualMatchingUsingCurrKey>();
add<QueryOptimizerCursorTests::ManualMatchingDedupingTakeover>();
add<QueryOptimizerCursorTests::Singlekey>();
add<QueryOptimizerCursorTests::Multikey>();
add<QueryOptimizerCursorTests::AddOtherPlans>();
add<QueryOptimizerCursorTests::AddOtherPlansDelete>();
add<QueryOptimizerCursorTests::AddOtherPlansContinuousDelete>();
add<QueryOptimizerCursorTests::OrRangeElimination>();
add<QueryOptimizerCursorTests::OrDedup>();
add<QueryOptimizerCursorTests::EarlyDups>();
add<QueryOptimizerCursorTests::OrPopInTakeover>();
add<QueryOptimizerCursorTests::OrCollectionScanAbort>();
add<QueryOptimizerCursorTests::YieldNoOp>();
add<QueryOptimizerCursorTests::YieldDelete>();
add<QueryOptimizerCursorTests::YieldDeleteContinue>();
add<QueryOptimizerCursorTests::YieldDeleteContinueFurther>();
add<QueryOptimizerCursorTests::YieldUpdate>();
add<QueryOptimizerCursorTests::YieldDrop>();
add<QueryOptimizerCursorTests::YieldDropOr>();
add<QueryOptimizerCursorTests::YieldRemoveOr>();
add<QueryOptimizerCursorTests::YieldCappedOverwrite>();
add<QueryOptimizerCursorTests::YieldDropIndex>();
add<QueryOptimizerCursorTests::YieldMultiplePlansNoOp>();
add<QueryOptimizerCursorTests::YieldMultiplePlansAdvanceNoOp>();
add<QueryOptimizerCursorTests::YieldMultiplePlansDelete>();
add<QueryOptimizerCursorTests::YieldMultiplePlansDeleteOr>();
add<QueryOptimizerCursorTests::YieldMultiplePlansDeleteOrAdvance>();
add<QueryOptimizerCursorTests::YieldMultiplePlansCappedOverwrite>();
add<QueryOptimizerCursorTests::YieldMultiplePlansCappedOverwriteManual>();
add<QueryOptimizerCursorTests::YieldMultiplePlansCappedOverwriteManual2>();
add<QueryOptimizerCursorTests::YieldTakeover>();
add<QueryOptimizerCursorTests::YieldTakeoverBasic>();
add<QueryOptimizerCursorTests::YieldInactiveCursorAdvance>();
add<QueryOptimizerCursorTests::OrderId>();
add<QueryOptimizerCursorTests::OrderMultiIndex>();
add<QueryOptimizerCursorTests::OrderReject>();
add<QueryOptimizerCursorTests::OrderNatural>();
add<QueryOptimizerCursorTests::OrderUnindexed>();
add<QueryOptimizerCursorTests::RecordedOrderInvalid>();
add<QueryOptimizerCursorTests::KillOp>();
add<QueryOptimizerCursorTests::KillOpFirstClause>();
add<QueryOptimizerCursorTests::Nscanned>();
add<QueryOptimizerCursorTests::TouchEarlierIterate>();
add<QueryOptimizerCursorTests::TouchEarlierIterateDelete>();
add<QueryOptimizerCursorTests::TouchEarlierIterateDeleteMultiple>();
add<QueryOptimizerCursorTests::TouchEarlierIterateTakeover>();
add<QueryOptimizerCursorTests::TouchEarlierIterateTakeoverDeleteMultiple>();
add<QueryOptimizerCursorTests::TouchEarlierIterateUnindexedTakeoverDeleteMultiple>();
add<QueryOptimizerCursorTests::TouchEarlierIterateTakeoverDeleteMultipleMultiAdvance>();
add<QueryOptimizerCursorTests::InitialCappedWrapYieldRecoveryFailure>();
add<QueryOptimizerCursorTests::TakeoverCappedWrapYieldRecoveryFailure>();
add<QueryOptimizerCursorTests::InvalidateClientCursorHolder>();
add<QueryOptimizerCursorTests::TimeoutClientCursorHolder>();
add<QueryOptimizerCursorTests::AllowOutOfOrderPlan>();
add<QueryOptimizerCursorTests::NoTakeoverByOutOfOrderPlan>();
add<QueryOptimizerCursorTests::GetCursor::NoConstraints>();
add<QueryOptimizerCursorTests::GetCursor::SimpleId>();
add<QueryOptimizerCursorTests::GetCursor::OptimalIndex>();
add<QueryOptimizerCursorTests::GetCursor::SimpleKeyMatch>();
add<QueryOptimizerCursorTests::GetCursor::Geo>();
add<QueryOptimizerCursorTests::GetCursor::PreventOutOfOrderPlan>();
add<QueryOptimizerCursorTests::GetCursor::AllowOutOfOrderPlan>();
add<QueryOptimizerCursorTests::GetCursor::BestSavedOutOfOrder>();
add<QueryOptimizerCursorTests::GetCursor::BestSavedOptimal>();
add<QueryOptimizerCursorTests::GetCursor::BestSavedNotOptimal>();
add<QueryOptimizerCursorTests::GetCursor::MultiIndex>();
add<QueryOptimizerCursorTests::GetCursor::RequireIndex::NoConstraints>();
add<QueryOptimizerCursorTests::GetCursor::RequireIndex::SimpleId>();
add<QueryOptimizerCursorTests::GetCursor::RequireIndex::UnindexedQuery>();
add<QueryOptimizerCursorTests::GetCursor::RequireIndex::IndexedQuery>();
add<QueryOptimizerCursorTests::GetCursor::RequireIndex::SecondOrClauseIndexed>();
add<QueryOptimizerCursorTests::GetCursor::RequireIndex::SecondOrClauseUnindexed>();
add<QueryOptimizerCursorTests::GetCursor::RequireIndex::SecondOrClauseUnindexedUndetected>();
add<QueryOptimizerCursorTests::GetCursor::RequireIndex::RecordedUnindexedPlan>();
add<QueryOptimizerCursorTests::GetCursor::IdElseNatural::AllowOptimalNaturalPlan>();
add<QueryOptimizerCursorTests::GetCursor::IdElseNatural::AllowOptimalIdPlan>();
add<QueryOptimizerCursorTests::GetCursor::IdElseNatural::HintedIdForQuery>( BSON( "_id" << 1 ) );
add<QueryOptimizerCursorTests::GetCursor::IdElseNatural::HintedIdForQuery>( BSON( "a" << 1 ) );
add<QueryOptimizerCursorTests::GetCursor::IdElseNatural::HintedIdForQuery>( BSON( "_id" << 1 << "a" << 1 ) );
add<QueryOptimizerCursorTests::GetCursor::IdElseNatural::HintedNaturalForQuery>( BSONObj() );
add<QueryOptimizerCursorTests::GetCursor::IdElseNatural::HintedNaturalForQuery>( BSON( "_id" << 1 ) );
add<QueryOptimizerCursorTests::GetCursor::IdElseNatural::HintedNaturalForQuery>( BSON( "a" << 1 ) );
add<QueryOptimizerCursorTests::GetCursor::IdElseNatural::HintedNaturalForQuery>( BSON( "_id" << 1 << "a" << 1 ) );
}
} myall;
} // namespace QueryOptimizerTests
Jump to Line
Something went wrong with that request. Please try again.