mirror of
https://github.com/mongodb/mongo.git
synced 2024-12-01 09:32:32 +01:00
1686 lines
74 KiB
C++
1686 lines
74 KiB
C++
// queryoptimizertests.cpp : query optimizer unit tests
|
|
//
|
|
|
|
/**
|
|
* Copyright (C) 2009 10gen Inc.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License, version 3,
|
|
* as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "pch.h"
|
|
#include "../db/queryoptimizer.h"
|
|
|
|
#include "../db/db.h"
|
|
#include "../db/dbhelpers.h"
|
|
#include "../db/instance.h"
|
|
#include "../db/query.h"
|
|
|
|
#include "dbtests.h"
|
|
|
|
namespace mongo {
|
|
extern BSONObj id_obj;
|
|
void runQuery(Message& m, QueryMessage& q, Message &response ){
|
|
CurOp op( &(cc()) );
|
|
op.ensureStarted();
|
|
runQuery( m , q , op, response );
|
|
}
|
|
void runQuery(Message& m, QueryMessage& q ){
|
|
Message response;
|
|
runQuery( m, q, response );
|
|
}
|
|
} // namespace mongo
|
|
|
|
namespace QueryOptimizerTests {
|
|
|
|
namespace FieldRangeTests {
|
|
class Base {
|
|
public:
|
|
virtual ~Base() {}
|
|
void run() {
|
|
const FieldRangeSet s( "ns", query() );
|
|
checkElt( lower(), s.range( "a" ).min() );
|
|
checkElt( upper(), s.range( "a" ).max() );
|
|
ASSERT_EQUALS( lowerInclusive(), s.range( "a" ).minInclusive() );
|
|
ASSERT_EQUALS( upperInclusive(), s.range( "a" ).maxInclusive() );
|
|
}
|
|
protected:
|
|
virtual BSONObj query() = 0;
|
|
virtual BSONElement lower() { return minKey.firstElement(); }
|
|
virtual bool lowerInclusive() { return true; }
|
|
virtual BSONElement upper() { return maxKey.firstElement(); }
|
|
virtual bool upperInclusive() { return true; }
|
|
static void checkElt( BSONElement expected, BSONElement actual ) {
|
|
if ( expected.woCompare( actual, false ) ) {
|
|
log() << "expected: " << expected << ", got: " << actual;
|
|
ASSERT( false );
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
class NumericBase : public Base {
|
|
public:
|
|
NumericBase(){
|
|
o = BSON( "min" << -numeric_limits<double>::max() << "max" << numeric_limits<double>::max() );
|
|
}
|
|
|
|
virtual BSONElement lower() { return o["min"]; }
|
|
virtual BSONElement upper() { return o["max"]; }
|
|
private:
|
|
BSONObj o;
|
|
};
|
|
|
|
class Empty : public Base {
|
|
virtual BSONObj query() { return BSONObj(); }
|
|
};
|
|
|
|
class Eq : public Base {
|
|
public:
|
|
Eq() : o_( BSON( "a" << 1 ) ) {}
|
|
virtual BSONObj query() { return o_; }
|
|
virtual BSONElement lower() { return o_.firstElement(); }
|
|
virtual BSONElement upper() { return o_.firstElement(); }
|
|
BSONObj o_;
|
|
};
|
|
|
|
class DupEq : public Eq {
|
|
public:
|
|
virtual BSONObj query() { return BSON( "a" << 1 << "b" << 2 << "a" << 1 ); }
|
|
};
|
|
|
|
class Lt : public NumericBase {
|
|
public:
|
|
Lt() : o_( BSON( "-" << 1 ) ) {}
|
|
virtual BSONObj query() { return BSON( "a" << LT << 1 ); }
|
|
virtual BSONElement upper() { return o_.firstElement(); }
|
|
virtual bool upperInclusive() { return false; }
|
|
BSONObj o_;
|
|
};
|
|
|
|
class Lte : public Lt {
|
|
virtual BSONObj query() { return BSON( "a" << LTE << 1 ); }
|
|
virtual bool upperInclusive() { return true; }
|
|
};
|
|
|
|
class Gt : public NumericBase {
|
|
public:
|
|
Gt() : o_( BSON( "-" << 1 ) ) {}
|
|
virtual BSONObj query() { return BSON( "a" << GT << 1 ); }
|
|
virtual BSONElement lower() { return o_.firstElement(); }
|
|
virtual bool lowerInclusive() { return false; }
|
|
BSONObj o_;
|
|
};
|
|
|
|
class Gte : public Gt {
|
|
virtual BSONObj query() { return BSON( "a" << GTE << 1 ); }
|
|
virtual bool lowerInclusive() { return true; }
|
|
};
|
|
|
|
class TwoLt : public Lt {
|
|
virtual BSONObj query() { return BSON( "a" << LT << 1 << LT << 5 ); }
|
|
};
|
|
|
|
class TwoGt : public Gt {
|
|
virtual BSONObj query() { return BSON( "a" << GT << 0 << GT << 1 ); }
|
|
};
|
|
|
|
class EqGte : public Eq {
|
|
virtual BSONObj query() { return BSON( "a" << 1 << "a" << GTE << 1 ); }
|
|
};
|
|
|
|
class EqGteInvalid {
|
|
public:
|
|
void run() {
|
|
FieldRangeSet fbs( "ns", BSON( "a" << 1 << "a" << GTE << 2 ) );
|
|
ASSERT( !fbs.matchPossible() );
|
|
}
|
|
};
|
|
|
|
struct RegexBase : Base {
|
|
void run() { //need to only look at first interval
|
|
FieldRangeSet s( "ns", query() );
|
|
checkElt( lower(), s.range( "a" ).intervals()[0]._lower._bound );
|
|
checkElt( upper(), s.range( "a" ).intervals()[0]._upper._bound );
|
|
ASSERT_EQUALS( lowerInclusive(), s.range( "a" ).intervals()[0]._lower._inclusive );
|
|
ASSERT_EQUALS( upperInclusive(), s.range( "a" ).intervals()[0]._upper._inclusive );
|
|
}
|
|
};
|
|
|
|
class Regex : public RegexBase {
|
|
public:
|
|
Regex() : o1_( BSON( "" << "abc" ) ), o2_( BSON( "" << "abd" ) ) {}
|
|
virtual BSONObj query() {
|
|
BSONObjBuilder b;
|
|
b.appendRegex( "a", "^abc" );
|
|
return b.obj();
|
|
}
|
|
virtual BSONElement lower() { return o1_.firstElement(); }
|
|
virtual BSONElement upper() { return o2_.firstElement(); }
|
|
virtual bool upperInclusive() { return false; }
|
|
BSONObj o1_, o2_;
|
|
};
|
|
|
|
class RegexObj : public RegexBase {
|
|
public:
|
|
RegexObj() : o1_( BSON( "" << "abc" ) ), o2_( BSON( "" << "abd" ) ) {}
|
|
virtual BSONObj query() { return BSON("a" << BSON("$regex" << "^abc")); }
|
|
virtual BSONElement lower() { return o1_.firstElement(); }
|
|
virtual BSONElement upper() { return o2_.firstElement(); }
|
|
virtual bool upperInclusive() { return false; }
|
|
BSONObj o1_, o2_;
|
|
};
|
|
|
|
class UnhelpfulRegex : public RegexBase {
|
|
public:
|
|
UnhelpfulRegex() {
|
|
BSONObjBuilder b;
|
|
b.appendMinForType("lower", String);
|
|
b.appendMaxForType("upper", String);
|
|
limits = b.obj();
|
|
}
|
|
|
|
virtual BSONObj query() {
|
|
BSONObjBuilder b;
|
|
b.appendRegex( "a", "abc" );
|
|
return b.obj();
|
|
}
|
|
virtual BSONElement lower() { return limits["lower"]; }
|
|
virtual BSONElement upper() { return limits["upper"]; }
|
|
virtual bool upperInclusive() { return false; }
|
|
BSONObj limits;
|
|
};
|
|
|
|
class In : public Base {
|
|
public:
|
|
In() : o1_( BSON( "-" << -3 ) ), o2_( BSON( "-" << 44 ) ) {}
|
|
virtual BSONObj query() {
|
|
vector< int > vals;
|
|
vals.push_back( 4 );
|
|
vals.push_back( 8 );
|
|
vals.push_back( 44 );
|
|
vals.push_back( -1 );
|
|
vals.push_back( -3 );
|
|
vals.push_back( 0 );
|
|
BSONObjBuilder bb;
|
|
bb.append( "$in", vals );
|
|
BSONObjBuilder b;
|
|
b.append( "a", bb.done() );
|
|
return b.obj();
|
|
}
|
|
virtual BSONElement lower() { return o1_.firstElement(); }
|
|
virtual BSONElement upper() { return o2_.firstElement(); }
|
|
BSONObj o1_, o2_;
|
|
};
|
|
|
|
class Equality {
|
|
public:
|
|
void run() {
|
|
FieldRangeSet s( "ns", BSON( "a" << 1 ) );
|
|
ASSERT( s.range( "a" ).equality() );
|
|
FieldRangeSet s2( "ns", BSON( "a" << GTE << 1 << LTE << 1 ) );
|
|
ASSERT( s2.range( "a" ).equality() );
|
|
FieldRangeSet s3( "ns", BSON( "a" << GT << 1 << LTE << 1 ) );
|
|
ASSERT( !s3.range( "a" ).equality() );
|
|
FieldRangeSet s4( "ns", BSON( "a" << GTE << 1 << LT << 1 ) );
|
|
ASSERT( !s4.range( "a" ).equality() );
|
|
FieldRangeSet s5( "ns", BSON( "a" << GTE << 1 << LTE << 1 << GT << 1 ) );
|
|
ASSERT( !s5.range( "a" ).equality() );
|
|
FieldRangeSet s6( "ns", BSON( "a" << GTE << 1 << LTE << 1 << LT << 1 ) );
|
|
ASSERT( !s6.range( "a" ).equality() );
|
|
}
|
|
};
|
|
|
|
class SimplifiedQuery {
|
|
public:
|
|
void run() {
|
|
FieldRangeSet fbs( "ns", BSON( "a" << GT << 1 << GT << 5 << LT << 10 << "b" << 4 << "c" << LT << 4 << LT << 6 << "d" << GTE << 0 << GT << 0 << "e" << GTE << 0 << LTE << 10 ) );
|
|
BSONObj simple = fbs.simplifiedQuery();
|
|
cout << "simple: " << simple << endl;
|
|
ASSERT( !simple.getObjectField( "a" ).woCompare( fromjson( "{$gt:5,$lt:10}" ) ) );
|
|
ASSERT_EQUALS( 4, simple.getIntField( "b" ) );
|
|
ASSERT( !simple.getObjectField( "c" ).woCompare( BSON("$gte" << -numeric_limits<double>::max() << "$lt" << 4 ) ) );
|
|
ASSERT( !simple.getObjectField( "d" ).woCompare( BSON("$gt" << 0 << "$lte" << numeric_limits<double>::max() ) ) );
|
|
ASSERT( !simple.getObjectField( "e" ).woCompare( fromjson( "{$gte:0,$lte:10}" ) ) );
|
|
}
|
|
};
|
|
|
|
class QueryPatternTest {
|
|
public:
|
|
void run() {
|
|
ASSERT( p( BSON( "a" << 1 ) ) == p( BSON( "a" << 1 ) ) );
|
|
ASSERT( p( BSON( "a" << 1 ) ) == p( BSON( "a" << 5 ) ) );
|
|
ASSERT( p( BSON( "a" << 1 ) ) != p( BSON( "b" << 1 ) ) );
|
|
ASSERT( p( BSON( "a" << 1 ) ) != p( BSON( "a" << LTE << 1 ) ) );
|
|
ASSERT( p( BSON( "a" << 1 ) ) != p( BSON( "a" << 1 << "b" << 2 ) ) );
|
|
ASSERT( p( BSON( "a" << 1 << "b" << 3 ) ) != p( BSON( "a" << 1 ) ) );
|
|
ASSERT( p( BSON( "a" << LT << 1 ) ) == p( BSON( "a" << LTE << 5 ) ) );
|
|
ASSERT( p( BSON( "a" << LT << 1 << GTE << 0 ) ) == p( BSON( "a" << LTE << 5 << GTE << 0 ) ) );
|
|
ASSERT( p( BSON( "a" << 1 ) ) < p( BSON( "a" << 1 << "b" << 1 ) ) );
|
|
ASSERT( !( p( BSON( "a" << 1 << "b" << 1 ) ) < p( BSON( "a" << 1 ) ) ) );
|
|
ASSERT( p( BSON( "a" << 1 ), BSON( "b" << 1 ) ) == p( BSON( "a" << 4 ), BSON( "b" << "a" ) ) );
|
|
ASSERT( p( BSON( "a" << 1 ), BSON( "b" << 1 ) ) == p( BSON( "a" << 4 ), BSON( "b" << -1 ) ) );
|
|
ASSERT( p( BSON( "a" << 1 ), BSON( "b" << 1 ) ) != p( BSON( "a" << 4 ), BSON( "c" << 1 ) ) );
|
|
ASSERT( p( BSON( "a" << 1 ), BSON( "b" << 1 << "c" << -1 ) ) == p( BSON( "a" << 4 ), BSON( "b" << -1 << "c" << 1 ) ) );
|
|
ASSERT( p( BSON( "a" << 1 ), BSON( "b" << 1 << "c" << 1 ) ) != p( BSON( "a" << 4 ), BSON( "b" << 1 ) ) );
|
|
ASSERT( p( BSON( "a" << 1 ), BSON( "b" << 1 ) ) != p( BSON( "a" << 4 ), BSON( "b" << 1 << "c" << 1 ) ) );
|
|
}
|
|
private:
|
|
static QueryPattern p( const BSONObj &query, const BSONObj &sort = BSONObj() ) {
|
|
return FieldRangeSet( "", query ).pattern( sort );
|
|
}
|
|
};
|
|
|
|
class NoWhere {
|
|
public:
|
|
void run() {
|
|
ASSERT_EQUALS( 0, FieldRangeSet( "ns", BSON( "$where" << 1 ) ).nNontrivialRanges() );
|
|
}
|
|
};
|
|
|
|
class Numeric {
|
|
public:
|
|
void run() {
|
|
FieldRangeSet f( "", BSON( "a" << 1 ) );
|
|
ASSERT( f.range( "a" ).min().woCompare( BSON( "a" << 2.0 ).firstElement() ) < 0 );
|
|
ASSERT( f.range( "a" ).min().woCompare( BSON( "a" << 0.0 ).firstElement() ) > 0 );
|
|
}
|
|
};
|
|
|
|
class InLowerBound {
|
|
public:
|
|
void run() {
|
|
FieldRangeSet f( "", fromjson( "{a:{$gt:4,$in:[1,2,3,4,5,6]}}" ) );
|
|
ASSERT( f.range( "a" ).min().woCompare( BSON( "a" << 5.0 ).firstElement(), false ) == 0 );
|
|
ASSERT( f.range( "a" ).max().woCompare( BSON( "a" << 6.0 ).firstElement(), false ) == 0 );
|
|
}
|
|
};
|
|
|
|
class InUpperBound {
|
|
public:
|
|
void run() {
|
|
FieldRangeSet f( "", fromjson( "{a:{$lt:4,$in:[1,2,3,4,5,6]}}" ) );
|
|
ASSERT( f.range( "a" ).min().woCompare( BSON( "a" << 1.0 ).firstElement(), false ) == 0 );
|
|
ASSERT( f.range( "a" ).max().woCompare( BSON( "a" << 3.0 ).firstElement(), false ) == 0 );
|
|
}
|
|
};
|
|
|
|
class MultiBound {
|
|
public:
|
|
void run() {
|
|
FieldRangeSet frs1( "", fromjson( "{a:{$in:[1,3,5,7,9]}}" ) );
|
|
FieldRangeSet frs2( "", fromjson( "{a:{$in:[2,3,5,8,9]}}" ) );
|
|
FieldRange fr1 = frs1.range( "a" );
|
|
FieldRange fr2 = frs2.range( "a" );
|
|
fr1 &= fr2;
|
|
ASSERT( fr1.min().woCompare( BSON( "a" << 3.0 ).firstElement(), false ) == 0 );
|
|
ASSERT( fr1.max().woCompare( BSON( "a" << 9.0 ).firstElement(), false ) == 0 );
|
|
vector< FieldInterval > intervals = fr1.intervals();
|
|
vector< FieldInterval >::const_iterator j = intervals.begin();
|
|
double expected[] = { 3, 5, 9 };
|
|
for( int i = 0; i < 3; ++i, ++j ) {
|
|
ASSERT_EQUALS( expected[ i ], j->_lower._bound.number() );
|
|
ASSERT( j->_lower._inclusive );
|
|
ASSERT( j->_lower == j->_upper );
|
|
}
|
|
ASSERT( j == intervals.end() );
|
|
}
|
|
};
|
|
|
|
class DiffBase {
|
|
public:
|
|
virtual ~DiffBase() {}
|
|
void run() {
|
|
FieldRangeSet frs( "", fromjson( obj() ) );
|
|
FieldRange ret = frs.range( "a" );
|
|
ret -= frs.range( "b" );
|
|
check( ret );
|
|
}
|
|
protected:
|
|
void check( const FieldRange &fr ) {
|
|
vector< FieldInterval > fi = fr.intervals();
|
|
ASSERT_EQUALS( len(), fi.size() );
|
|
int i = 0;
|
|
for( vector< FieldInterval >::const_iterator j = fi.begin(); j != fi.end(); ++j ) {
|
|
ASSERT_EQUALS( nums()[ i ], j->_lower._bound.numberInt() );
|
|
ASSERT_EQUALS( incs()[ i ], j->_lower._inclusive );
|
|
++i;
|
|
ASSERT_EQUALS( nums()[ i ], j->_upper._bound.numberInt() );
|
|
ASSERT_EQUALS( incs()[ i ], j->_upper._inclusive );
|
|
++i;
|
|
}
|
|
}
|
|
virtual unsigned len() const = 0;
|
|
virtual const int *nums() const = 0;
|
|
virtual const bool *incs() const = 0;
|
|
virtual BSONObj obj() const = 0;
|
|
};
|
|
|
|
class TwoRangeBase : public DiffBase {
|
|
public:
|
|
TwoRangeBase( string obj, int low, int high, bool lowI, bool highI )
|
|
: _obj( obj ) {
|
|
_n[ 0 ] = low;
|
|
_n[ 1 ] = high;
|
|
_b[ 0 ] = lowI;
|
|
_b[ 1 ] = highI;
|
|
}
|
|
private:
|
|
virtual unsigned len() const { return 1; }
|
|
virtual const int *nums() const { return _n; }
|
|
virtual const bool *incs() const { return _b; }
|
|
virtual BSONObj obj() const { return fromjson( _obj ); }
|
|
string _obj;
|
|
int _n[ 2 ];
|
|
bool _b[ 2 ];
|
|
};
|
|
|
|
struct Diff1 : public TwoRangeBase {
|
|
Diff1() : TwoRangeBase( "{a:{$gt:1,$lt:2},b:{$gt:3,$lt:4}}", 1, 2, false, false ) {}
|
|
};
|
|
|
|
struct Diff2 : public TwoRangeBase {
|
|
Diff2() : TwoRangeBase( "{a:{$gt:1,$lt:2},b:{$gt:2,$lt:4}}", 1, 2, false, false ) {}
|
|
};
|
|
|
|
struct Diff3 : public TwoRangeBase {
|
|
Diff3() : TwoRangeBase( "{a:{$gt:1,$lte:2},b:{$gt:2,$lt:4}}", 1, 2, false, true ) {}
|
|
};
|
|
|
|
struct Diff4 : public TwoRangeBase {
|
|
Diff4() : TwoRangeBase( "{a:{$gt:1,$lt:2},b:{$gte:2,$lt:4}}", 1, 2, false, false) {}
|
|
};
|
|
|
|
struct Diff5 : public TwoRangeBase {
|
|
Diff5() : TwoRangeBase( "{a:{$gt:1,$lte:2},b:{$gte:2,$lt:4}}", 1, 2, false, false) {}
|
|
};
|
|
|
|
struct Diff6 : public TwoRangeBase {
|
|
Diff6() : TwoRangeBase( "{a:{$gt:1,$lte:3},b:{$gte:2,$lt:4}}", 1, 2, false, false) {}
|
|
};
|
|
|
|
struct Diff7 : public TwoRangeBase {
|
|
Diff7() : TwoRangeBase( "{a:{$gt:1,$lte:3},b:{$gt:2,$lt:4}}", 1, 2, false, true) {}
|
|
};
|
|
|
|
struct Diff8 : public TwoRangeBase {
|
|
Diff8() : TwoRangeBase( "{a:{$gt:1,$lt:4},b:{$gt:2,$lt:4}}", 1, 2, false, true) {}
|
|
};
|
|
|
|
struct Diff9 : public TwoRangeBase {
|
|
Diff9() : TwoRangeBase( "{a:{$gt:1,$lt:4},b:{$gt:2,$lte:4}}", 1, 2, false, true) {}
|
|
};
|
|
|
|
struct Diff10 : public TwoRangeBase {
|
|
Diff10() : TwoRangeBase( "{a:{$gt:1,$lte:4},b:{$gt:2,$lte:4}}", 1, 2, false, true) {}
|
|
};
|
|
|
|
struct Diff11 : public TwoRangeBase {
|
|
Diff11() : TwoRangeBase( "{a:{$gt:1,$lte:4},b:{$gt:2,$lt:4}}", 1, 4, false, true) {}
|
|
};
|
|
|
|
struct Diff12 : public TwoRangeBase {
|
|
Diff12() : TwoRangeBase( "{a:{$gt:1,$lt:5},b:{$gt:2,$lt:4}}", 1, 5, false, false) {}
|
|
};
|
|
|
|
struct Diff13 : public TwoRangeBase {
|
|
Diff13() : TwoRangeBase( "{a:{$gt:1,$lt:5},b:{$gt:1,$lt:4}}", 4, 5, true, false) {}
|
|
};
|
|
|
|
struct Diff14 : public TwoRangeBase {
|
|
Diff14() : TwoRangeBase( "{a:{$gte:1,$lt:5},b:{$gt:1,$lt:4}}", 1, 5, true, false) {}
|
|
};
|
|
|
|
struct Diff15 : public TwoRangeBase {
|
|
Diff15() : TwoRangeBase( "{a:{$gt:1,$lt:5},b:{$gte:1,$lt:4}}", 4, 5, true, false) {}
|
|
};
|
|
|
|
struct Diff16 : public TwoRangeBase {
|
|
Diff16() : TwoRangeBase( "{a:{$gte:1,$lt:5},b:{$gte:1,$lt:4}}", 4, 5, true, false) {}
|
|
};
|
|
|
|
struct Diff17 : public TwoRangeBase {
|
|
Diff17() : TwoRangeBase( "{a:{$gt:1,$lt:5},b:{$gt:0,$lt:4}}", 4, 5, true, false) {}
|
|
};
|
|
|
|
struct Diff18 : public TwoRangeBase {
|
|
Diff18() : TwoRangeBase( "{a:{$gt:1,$lt:5},b:{$gt:0,$lte:4}}", 4, 5, false, false) {}
|
|
};
|
|
|
|
struct Diff19 : public TwoRangeBase {
|
|
Diff19() : TwoRangeBase( "{a:{$gte:1,$lte:5},b:{$gte:0,$lte:1}}", 1, 5, false, true) {}
|
|
};
|
|
|
|
struct Diff20 : public TwoRangeBase {
|
|
Diff20() : TwoRangeBase( "{a:{$gt:1,$lte:5},b:{$gte:0,$lte:1}}", 1, 5, false, true) {}
|
|
};
|
|
|
|
struct Diff21 : public TwoRangeBase {
|
|
Diff21() : TwoRangeBase( "{a:{$gte:1,$lte:5},b:{$gte:0,$lt:1}}", 1, 5, true, true) {}
|
|
};
|
|
|
|
struct Diff22 : public TwoRangeBase {
|
|
Diff22() : TwoRangeBase( "{a:{$gt:1,$lte:5},b:{$gte:0,$lt:1}}", 1, 5, false, true) {}
|
|
};
|
|
|
|
struct Diff23 : public TwoRangeBase {
|
|
Diff23() : TwoRangeBase( "{a:{$gt:1,$lte:5},b:{$gte:0,$lt:0.5}}", 1, 5, false, true) {}
|
|
};
|
|
|
|
struct Diff24 : public TwoRangeBase {
|
|
Diff24() : TwoRangeBase( "{a:{$gt:1,$lte:5},b:0}", 1, 5, false, true) {}
|
|
};
|
|
|
|
struct Diff25 : public TwoRangeBase {
|
|
Diff25() : TwoRangeBase( "{a:{$gte:1,$lte:5},b:0}", 1, 5, true, true) {}
|
|
};
|
|
|
|
struct Diff26 : public TwoRangeBase {
|
|
Diff26() : TwoRangeBase( "{a:{$gt:1,$lte:5},b:1}", 1, 5, false, true) {}
|
|
};
|
|
|
|
struct Diff27 : public TwoRangeBase {
|
|
Diff27() : TwoRangeBase( "{a:{$gte:1,$lte:5},b:1}", 1, 5, false, true) {}
|
|
};
|
|
|
|
struct Diff28 : public TwoRangeBase {
|
|
Diff28() : TwoRangeBase( "{a:{$gte:1,$lte:5},b:3}", 1, 5, true, true) {}
|
|
};
|
|
|
|
struct Diff29 : public TwoRangeBase {
|
|
Diff29() : TwoRangeBase( "{a:{$gte:1,$lte:5},b:5}", 1, 5, true, false) {}
|
|
};
|
|
|
|
struct Diff30 : public TwoRangeBase {
|
|
Diff30() : TwoRangeBase( "{a:{$gte:1,$lt:5},b:5}", 1, 5, true, false) {}
|
|
};
|
|
|
|
struct Diff31 : public TwoRangeBase {
|
|
Diff31() : TwoRangeBase( "{a:{$gte:1,$lt:5},b:6}", 1, 5, true, false) {}
|
|
};
|
|
|
|
struct Diff32 : public TwoRangeBase {
|
|
Diff32() : TwoRangeBase( "{a:{$gte:1,$lte:5},b:6}", 1, 5, true, true) {}
|
|
};
|
|
|
|
class EmptyBase : public DiffBase {
|
|
public:
|
|
EmptyBase( string obj )
|
|
: _obj( obj ) {}
|
|
private:
|
|
virtual unsigned len() const { return 0; }
|
|
virtual const int *nums() const { return 0; }
|
|
virtual const bool *incs() const { return 0; }
|
|
virtual BSONObj obj() const { return fromjson( _obj ); }
|
|
string _obj;
|
|
};
|
|
|
|
struct Diff33 : public EmptyBase {
|
|
Diff33() : EmptyBase( "{a:{$gte:1,$lte:5},b:{$gt:0,$lt:6}}" ) {}
|
|
};
|
|
|
|
struct Diff34 : public EmptyBase {
|
|
Diff34() : EmptyBase( "{a:{$gte:1,$lte:5},b:{$gte:1,$lt:6}}" ) {}
|
|
};
|
|
|
|
struct Diff35 : public EmptyBase {
|
|
Diff35() : EmptyBase( "{a:{$gt:1,$lte:5},b:{$gte:1,$lt:6}}" ) {}
|
|
};
|
|
|
|
struct Diff36 : public EmptyBase {
|
|
Diff36() : EmptyBase( "{a:{$gt:1,$lte:5},b:{$gt:1,$lt:6}}" ) {}
|
|
};
|
|
|
|
struct Diff37 : public TwoRangeBase {
|
|
Diff37() : TwoRangeBase( "{a:{$gte:1,$lte:5},b:{$gt:1,$lt:6}}", 1, 1, true, true ) {}
|
|
};
|
|
|
|
struct Diff38 : public EmptyBase {
|
|
Diff38() : EmptyBase( "{a:{$gt:1,$lt:5},b:{$gt:0,$lt:5}}" ) {}
|
|
};
|
|
|
|
struct Diff39 : public EmptyBase {
|
|
Diff39() : EmptyBase( "{a:{$gt:1,$lt:5},b:{$gt:0,$lte:5}}" ) {}
|
|
};
|
|
|
|
struct Diff40 : public EmptyBase {
|
|
Diff40() : EmptyBase( "{a:{$gt:1,$lte:5},b:{$gt:0,$lte:5}}" ) {}
|
|
};
|
|
|
|
struct Diff41 : public TwoRangeBase {
|
|
Diff41() : TwoRangeBase( "{a:{$gte:1,$lte:5},b:{$gt:0,$lt:5}}", 5, 5, true, true ) {}
|
|
};
|
|
|
|
struct Diff42 : public EmptyBase {
|
|
Diff42() : EmptyBase( "{a:{$gt:1,$lt:5},b:{$gt:1,$lt:5}}" ) {}
|
|
};
|
|
|
|
struct Diff43 : public EmptyBase {
|
|
Diff43() : EmptyBase( "{a:{$gt:1,$lt:5},b:{$gt:1,$lte:5}}" ) {}
|
|
};
|
|
|
|
struct Diff44 : public EmptyBase {
|
|
Diff44() : EmptyBase( "{a:{$gt:1,$lt:5},b:{$gte:1,$lt:5}}" ) {}
|
|
};
|
|
|
|
struct Diff45 : public EmptyBase {
|
|
Diff45() : EmptyBase( "{a:{$gt:1,$lt:5},b:{$gte:1,$lte:5}}" ) {}
|
|
};
|
|
|
|
struct Diff46 : public TwoRangeBase {
|
|
Diff46() : TwoRangeBase( "{a:{$gt:1,$lte:5},b:{$gt:1,$lt:5}}", 5, 5, true, true ) {}
|
|
};
|
|
|
|
struct Diff47 : public EmptyBase {
|
|
Diff47() : EmptyBase( "{a:{$gt:1,$lte:5},b:{$gt:1,$lte:5}}" ) {}
|
|
};
|
|
|
|
struct Diff48 : public TwoRangeBase {
|
|
Diff48() : TwoRangeBase( "{a:{$gt:1,$lte:5},b:{$gte:1,$lt:5}}", 5, 5, true, true ) {}
|
|
};
|
|
|
|
struct Diff49 : public EmptyBase {
|
|
Diff49() : EmptyBase( "{a:{$gt:1,$lte:5},b:{$gte:1,$lte:5}}" ) {}
|
|
};
|
|
|
|
struct Diff50 : public TwoRangeBase {
|
|
Diff50() : TwoRangeBase( "{a:{$gte:1,$lt:5},b:{$gt:1,$lt:5}}", 1, 1, true, true ) {}
|
|
};
|
|
|
|
struct Diff51 : public TwoRangeBase {
|
|
Diff51() : TwoRangeBase( "{a:{$gte:1,$lt:5},b:{$gt:1,$lte:5}}", 1, 1, true, true ) {}
|
|
};
|
|
|
|
struct Diff52 : public EmptyBase {
|
|
Diff52() : EmptyBase( "{a:{$gte:1,$lt:5},b:{$gte:1,$lt:5}}" ) {}
|
|
};
|
|
|
|
struct Diff53 : public EmptyBase {
|
|
Diff53() : EmptyBase( "{a:{$gte:1,$lt:5},b:{$gte:1,$lte:5}}" ) {}
|
|
};
|
|
|
|
struct Diff54 : public TwoRangeBase {
|
|
Diff54() : TwoRangeBase( "{a:{$gte:1,$lte:5},b:{$gt:1,$lt:5}}", 1, 5, true, true ) {}
|
|
};
|
|
|
|
struct Diff55 : public TwoRangeBase {
|
|
Diff55() : TwoRangeBase( "{a:{$gte:1,$lte:5},b:{$gt:1,$lte:5}}", 1, 1, true, true ) {}
|
|
};
|
|
|
|
struct Diff56 : public TwoRangeBase {
|
|
Diff56() : TwoRangeBase( "{a:{$gte:1,$lte:5},b:{$gte:1,$lt:5}}", 5, 5, true, true ) {}
|
|
};
|
|
|
|
struct Diff57 : public EmptyBase {
|
|
Diff57() : EmptyBase( "{a:{$gte:1,$lte:5},b:{$gte:1,$lte:5}}" ) {}
|
|
};
|
|
|
|
struct Diff58 : public TwoRangeBase {
|
|
Diff58() : TwoRangeBase( "{a:1,b:{$gt:1,$lt:5}}", 1, 1, true, true ) {}
|
|
};
|
|
|
|
struct Diff59 : public EmptyBase {
|
|
Diff59() : EmptyBase( "{a:1,b:{$gte:1,$lt:5}}" ) {}
|
|
};
|
|
|
|
struct Diff60 : public EmptyBase {
|
|
Diff60() : EmptyBase( "{a:2,b:{$gte:1,$lt:5}}" ) {}
|
|
};
|
|
|
|
struct Diff61 : public EmptyBase {
|
|
Diff61() : EmptyBase( "{a:5,b:{$gte:1,$lte:5}}" ) {}
|
|
};
|
|
|
|
struct Diff62 : public TwoRangeBase {
|
|
Diff62() : TwoRangeBase( "{a:5,b:{$gt:1,$lt:5}}", 5, 5, true, true ) {}
|
|
};
|
|
|
|
struct Diff63 : public EmptyBase {
|
|
Diff63() : EmptyBase( "{a:5,b:5}" ) {}
|
|
};
|
|
|
|
class DiffMulti1 : public DiffBase {
|
|
public:
|
|
void run() {
|
|
FieldRangeSet frs( "", fromjson( "{a:{$gt:1,$lt:9},b:{$gt:0,$lt:2},c:3,d:{$gt:4,$lt:5},e:{$gt:7,$lt:10}}" ) );
|
|
FieldRange ret = frs.range( "a" );
|
|
FieldRange other = frs.range( "b" );
|
|
other |= frs.range( "c" );
|
|
other |= frs.range( "d" );
|
|
other |= frs.range( "e" );
|
|
ret -= other;
|
|
check( ret );
|
|
}
|
|
protected:
|
|
virtual unsigned len() const { return 1; }
|
|
virtual const int *nums() const { static int n[] = { 2, 7 }; return n; }
|
|
virtual const bool *incs() const { static bool b[] = { true, true }; return b; }
|
|
virtual BSONObj obj() const { return BSONObj(); }
|
|
};
|
|
|
|
class DiffMulti2 : public DiffBase {
|
|
public:
|
|
void run() {
|
|
FieldRangeSet frs( "", fromjson( "{a:{$gt:1,$lt:9},b:{$gt:0,$lt:2},c:3,d:{$gt:4,$lt:5},e:{$gt:7,$lt:10}}" ) );
|
|
FieldRange mask = frs.range( "a" );
|
|
FieldRange ret = frs.range( "b" );
|
|
ret |= frs.range( "c" );
|
|
ret |= frs.range( "d" );
|
|
ret |= frs.range( "e" );
|
|
ret -= mask;
|
|
check( ret );
|
|
}
|
|
protected:
|
|
virtual unsigned len() const { return 2; }
|
|
virtual const int *nums() const { static int n[] = { 0, 1, 9, 10 }; return n; }
|
|
virtual const bool *incs() const { static bool b[] = { false, true, true, false }; return b; }
|
|
virtual BSONObj obj() const { return BSONObj(); }
|
|
};
|
|
|
|
class SetDiff {
|
|
public:
|
|
void run() {
|
|
FieldRangeSet frs1( "", fromjson( "{a:5,c:{$in:[6,7]},e:{$in:[7,8]},f:8}" ) );
|
|
FieldRangeSet frs2( "", fromjson( "{b:5,c:6,d:7,e:7}" ) );
|
|
frs1 -= frs2;
|
|
ASSERT_EQUALS( BSON( "a" << 5 << "c" << 7 << "e" << 8 << "f" << 8 ), frs1.simplifiedQuery() );
|
|
FieldRangeSet frs3( "", fromjson( "{a:5}" ) );
|
|
frs1 -= frs3;
|
|
ASSERT( !frs1.matchPossible() );
|
|
}
|
|
};
|
|
|
|
class SetIntersect {
|
|
public:
|
|
void run() {
|
|
FieldRangeSet frs1( "", fromjson( "{b:{$in:[5,6]},c:7,d:{$in:[8,9]}}" ) );
|
|
FieldRangeSet frs2( "", fromjson( "{a:1,b:5,c:{$in:[7,8]},d:{$in:[8,9]},e:10}" ) );
|
|
frs1 &= frs2;
|
|
ASSERT_EQUALS( fromjson( "{a:1,b:5,c:7,d:{$in:[8,9]},e:10}" ), frs1.simplifiedQuery( BSONObj(), true ) );
|
|
}
|
|
};
|
|
|
|
} // namespace FieldRangeTests
|
|
|
|
namespace QueryPlanTests {
|
|
class Base {
|
|
public:
|
|
Base() : _ctx( ns() ) , indexNum_( 0 ) {
|
|
string err;
|
|
userCreateNS( ns(), BSONObj(), err, false );
|
|
}
|
|
~Base() {
|
|
if ( !nsd() )
|
|
return;
|
|
string s( ns() );
|
|
dropNS( s );
|
|
}
|
|
protected:
|
|
static const char *ns() { return "unittests.QueryPlanTests"; }
|
|
static NamespaceDetails *nsd() { return nsdetails( ns() ); }
|
|
IndexDetails *index( const BSONObj &key ) {
|
|
stringstream ss;
|
|
ss << indexNum_++;
|
|
string name = ss.str();
|
|
client_.resetIndexCache();
|
|
client_.ensureIndex( ns(), key, false, name.c_str() );
|
|
NamespaceDetails *d = nsd();
|
|
for( int i = 0; i < d->nIndexes; ++i ) {
|
|
if ( d->idx(i).keyPattern() == key /*indexName() == name*/ || ( d->idx(i).isIdIndex() && IndexDetails::isIdIndexPattern( key ) ) )
|
|
return &d->idx(i);
|
|
}
|
|
assert( false );
|
|
return 0;
|
|
}
|
|
int indexno( const BSONObj &key ) {
|
|
return nsd()->idxNo( *index(key) );
|
|
}
|
|
BSONObj startKey( const QueryPlan &p ) const {
|
|
BoundList bl = p.indexBounds();
|
|
return bl[ 0 ].first.getOwned();
|
|
}
|
|
BSONObj endKey( const QueryPlan &p ) const {
|
|
BoundList bl = p.indexBounds();
|
|
return bl[ bl.size() - 1 ].second.getOwned();
|
|
}
|
|
private:
|
|
dblock lk_;
|
|
Client::Context _ctx;
|
|
int indexNum_;
|
|
static DBDirectClient client_;
|
|
};
|
|
DBDirectClient Base::client_;
|
|
|
|
// There's a limit of 10 indexes total, make sure not to exceed this in a given test.
|
|
#define INDEXNO(x) nsd()->idxNo( *this->index( BSON(x) ) )
|
|
#define INDEX(x) this->index( BSON(x) )
|
|
auto_ptr< FieldRangeSet > FieldRangeSet_GLOBAL;
|
|
#define FBS(x) ( FieldRangeSet_GLOBAL.reset( new FieldRangeSet( ns(), x ) ), *FieldRangeSet_GLOBAL )
|
|
|
|
class NoIndex : public Base {
|
|
public:
|
|
void run() {
|
|
QueryPlan p( nsd(), -1, FBS( BSONObj() ), BSONObj(), BSONObj() );
|
|
ASSERT( !p.optimal() );
|
|
ASSERT( !p.scanAndOrderRequired() );
|
|
ASSERT( !p.exactKeyMatch() );
|
|
}
|
|
};
|
|
|
|
class SimpleOrder : public Base {
|
|
public:
|
|
void run() {
|
|
BSONObjBuilder b;
|
|
b.appendMinKey( "" );
|
|
BSONObj start = b.obj();
|
|
BSONObjBuilder b2;
|
|
b2.appendMaxKey( "" );
|
|
BSONObj end = b2.obj();
|
|
|
|
QueryPlan p( nsd(), INDEXNO( "a" << 1 ), FBS( BSONObj() ), BSONObj(), BSON( "a" << 1 ) );
|
|
ASSERT( !p.scanAndOrderRequired() );
|
|
ASSERT( !startKey( p ).woCompare( start ) );
|
|
ASSERT( !endKey( p ).woCompare( end ) );
|
|
QueryPlan p2( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FBS( BSONObj() ), BSONObj(), BSON( "a" << 1 << "b" << 1 ) );
|
|
ASSERT( !p2.scanAndOrderRequired() );
|
|
QueryPlan p3( nsd(), INDEXNO( "a" << 1 ), FBS( BSONObj() ), BSONObj(), BSON( "b" << 1 ) );
|
|
ASSERT( p3.scanAndOrderRequired() );
|
|
ASSERT( !startKey( p3 ).woCompare( start ) );
|
|
ASSERT( !endKey( p3 ).woCompare( end ) );
|
|
}
|
|
};
|
|
|
|
class MoreIndexThanNeeded : public Base {
|
|
public:
|
|
void run() {
|
|
QueryPlan p( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FBS( BSONObj() ), BSONObj(), BSON( "a" << 1 ) );
|
|
ASSERT( !p.scanAndOrderRequired() );
|
|
}
|
|
};
|
|
|
|
class IndexSigns : public Base {
|
|
public:
|
|
void run() {
|
|
QueryPlan p( nsd(), INDEXNO( "a" << 1 << "b" << -1 ) , FBS( BSONObj() ), BSONObj(), BSON( "a" << 1 << "b" << -1 ) );
|
|
ASSERT( !p.scanAndOrderRequired() );
|
|
ASSERT_EQUALS( 1, p.direction() );
|
|
QueryPlan p2( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FBS( BSONObj() ), BSONObj(), BSON( "a" << 1 << "b" << -1 ) );
|
|
ASSERT( p2.scanAndOrderRequired() );
|
|
ASSERT_EQUALS( 0, p2.direction() );
|
|
QueryPlan p3( nsd(), indexno( id_obj ), FBS( BSONObj() ), BSONObj(), BSON( "_id" << 1 ) );
|
|
ASSERT( !p3.scanAndOrderRequired() );
|
|
ASSERT_EQUALS( 1, p3.direction() );
|
|
}
|
|
};
|
|
|
|
class IndexReverse : public Base {
|
|
public:
|
|
void run() {
|
|
BSONObjBuilder b;
|
|
b.appendMinKey( "" );
|
|
b.appendMaxKey( "" );
|
|
BSONObj start = b.obj();
|
|
BSONObjBuilder b2;
|
|
b2.appendMaxKey( "" );
|
|
b2.appendMinKey( "" );
|
|
BSONObj end = b2.obj();
|
|
QueryPlan p( nsd(), INDEXNO( "a" << -1 << "b" << 1 ),FBS( BSONObj() ), BSONObj(), BSON( "a" << 1 << "b" << -1 ) );
|
|
ASSERT( !p.scanAndOrderRequired() );
|
|
ASSERT_EQUALS( -1, p.direction() );
|
|
ASSERT( !startKey( p ).woCompare( start ) );
|
|
ASSERT( !endKey( p ).woCompare( end ) );
|
|
QueryPlan p2( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FBS( BSONObj() ), BSONObj(), BSON( "a" << -1 << "b" << -1 ) );
|
|
ASSERT( !p2.scanAndOrderRequired() );
|
|
ASSERT_EQUALS( -1, p2.direction() );
|
|
QueryPlan p3( nsd(), INDEXNO( "a" << 1 << "b" << -1 ), FBS( BSONObj() ), BSONObj(), BSON( "a" << -1 << "b" << -1 ) );
|
|
ASSERT( p3.scanAndOrderRequired() );
|
|
ASSERT_EQUALS( 0, p3.direction() );
|
|
}
|
|
};
|
|
|
|
class NoOrder : public Base {
|
|
public:
|
|
void run() {
|
|
BSONObjBuilder b;
|
|
b.append( "", 3 );
|
|
b.appendMinKey( "" );
|
|
BSONObj start = b.obj();
|
|
BSONObjBuilder b2;
|
|
b2.append( "", 3 );
|
|
b2.appendMaxKey( "" );
|
|
BSONObj end = b2.obj();
|
|
QueryPlan p( nsd(), INDEXNO( "a" << -1 << "b" << 1 ), FBS( BSON( "a" << 3 ) ), BSON( "a" << 3 ), BSONObj() );
|
|
ASSERT( !p.scanAndOrderRequired() );
|
|
ASSERT( !startKey( p ).woCompare( start ) );
|
|
ASSERT( !endKey( p ).woCompare( end ) );
|
|
QueryPlan p2( nsd(), INDEXNO( "a" << -1 << "b" << 1 ), FBS( BSON( "a" << 3 ) ), BSON( "a" << 3 ), BSONObj() );
|
|
ASSERT( !p2.scanAndOrderRequired() );
|
|
ASSERT( !startKey( p ).woCompare( start ) );
|
|
ASSERT( !endKey( p ).woCompare( end ) );
|
|
}
|
|
};
|
|
|
|
class EqualWithOrder : public Base {
|
|
public:
|
|
void run() {
|
|
QueryPlan p( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FBS( BSON( "a" << 4 ) ), BSON( "a" << 4 ), BSON( "b" << 1 ) );
|
|
ASSERT( !p.scanAndOrderRequired() );
|
|
QueryPlan p2( nsd(), INDEXNO( "a" << 1 << "b" << 1 << "c" << 1 ), FBS( BSON( "b" << 4 ) ), BSON( "b" << 4 ), BSON( "a" << 1 << "c" << 1 ) );
|
|
ASSERT( !p2.scanAndOrderRequired() );
|
|
QueryPlan p3( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FBS( BSON( "b" << 4 ) ), BSON( "b" << 4 ), BSON( "a" << 1 << "c" << 1 ) );
|
|
ASSERT( p3.scanAndOrderRequired() );
|
|
}
|
|
};
|
|
|
|
class Optimal : public Base {
|
|
public:
|
|
void run() {
|
|
QueryPlan p( nsd(), INDEXNO( "a" << 1 ), FBS( BSONObj() ), BSONObj(), BSON( "a" << 1 ) );
|
|
ASSERT( p.optimal() );
|
|
QueryPlan p2( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FBS( BSONObj() ), BSONObj(), BSON( "a" << 1 ) );
|
|
ASSERT( p2.optimal() );
|
|
QueryPlan p3( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FBS( BSON( "a" << 1 ) ), BSON( "a" << 1 ), BSON( "a" << 1 ) );
|
|
ASSERT( p3.optimal() );
|
|
QueryPlan p4( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FBS( BSON( "b" << 1 ) ), BSON( "b" << 1 ), BSON( "a" << 1 ) );
|
|
ASSERT( !p4.optimal() );
|
|
QueryPlan p5( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FBS( BSON( "a" << 1 ) ), BSON( "a" << 1 ), BSON( "b" << 1 ) );
|
|
ASSERT( p5.optimal() );
|
|
QueryPlan p6( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FBS( BSON( "b" << 1 ) ), BSON( "b" << 1 ), BSON( "b" << 1 ) );
|
|
ASSERT( !p6.optimal() );
|
|
QueryPlan p7( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FBS( BSON( "a" << 1 << "b" << 1 ) ), BSON( "a" << 1 << "b" << 1 ), BSON( "a" << 1 ) );
|
|
ASSERT( p7.optimal() );
|
|
QueryPlan p8( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FBS( BSON( "a" << 1 << "b" << LT << 1 ) ), BSON( "a" << 1 << "b" << LT << 1 ), BSON( "a" << 1 ) );
|
|
ASSERT( p8.optimal() );
|
|
QueryPlan p9( nsd(), INDEXNO( "a" << 1 << "b" << 1 << "c" << 1 ), FBS( BSON( "a" << 1 << "b" << LT << 1 ) ), BSON( "a" << 1 << "b" << LT << 1 ), BSON( "a" << 1 ) );
|
|
ASSERT( p9.optimal() );
|
|
}
|
|
};
|
|
|
|
class MoreOptimal : public Base {
|
|
public:
|
|
void run() {
|
|
QueryPlan p10( nsd(), INDEXNO( "a" << 1 << "b" << 1 << "c" << 1 ), FBS( BSON( "a" << 1 ) ), BSON( "a" << 1 ), BSONObj() );
|
|
ASSERT( p10.optimal() );
|
|
QueryPlan p11( nsd(), INDEXNO( "a" << 1 << "b" << 1 << "c" << 1 ), FBS( BSON( "a" << 1 << "b" << LT << 1 ) ), BSON( "a" << 1 << "b" << LT << 1 ), BSONObj() );
|
|
ASSERT( p11.optimal() );
|
|
QueryPlan p12( nsd(), INDEXNO( "a" << 1 << "b" << 1 << "c" << 1 ), FBS( BSON( "a" << LT << 1 ) ), BSON( "a" << LT << 1 ), BSONObj() );
|
|
ASSERT( p12.optimal() );
|
|
QueryPlan p13( nsd(), INDEXNO( "a" << 1 << "b" << 1 << "c" << 1 ), FBS( BSON( "a" << LT << 1 ) ), BSON( "a" << LT << 1 ), BSON( "a" << 1 ) );
|
|
ASSERT( p13.optimal() );
|
|
}
|
|
};
|
|
|
|
class KeyMatch : public Base {
|
|
public:
|
|
void run() {
|
|
QueryPlan p( nsd(), INDEXNO( "a" << 1 ), FBS( BSONObj() ), BSONObj(), BSON( "a" << 1 ) );
|
|
ASSERT( !p.exactKeyMatch() );
|
|
QueryPlan p2( nsd(), INDEXNO( "b" << 1 << "a" << 1 ), FBS( BSONObj() ), BSONObj(), BSON( "a" << 1 ) );
|
|
ASSERT( !p2.exactKeyMatch() );
|
|
QueryPlan p3( nsd(), INDEXNO( "b" << 1 << "a" << 1 ), FBS( BSON( "b" << "z" ) ), BSON( "b" << "z" ), BSON( "a" << 1 ) );
|
|
ASSERT( !p3.exactKeyMatch() );
|
|
QueryPlan p4( nsd(), INDEXNO( "b" << 1 << "a" << 1 << "c" << 1 ), FBS( BSON( "c" << "y" << "b" << "z" ) ), BSON( "c" << "y" << "b" << "z" ), BSON( "a" << 1 ) );
|
|
ASSERT( !p4.exactKeyMatch() );
|
|
QueryPlan p5( nsd(), INDEXNO( "b" << 1 << "a" << 1 << "c" << 1 ), FBS( BSON( "c" << "y" << "b" << "z" ) ), BSON( "c" << "y" << "b" << "z" ), BSONObj() );
|
|
ASSERT( !p5.exactKeyMatch() );
|
|
QueryPlan p6( nsd(), INDEXNO( "b" << 1 << "a" << 1 << "c" << 1 ), FBS( BSON( "c" << LT << "y" << "b" << GT << "z" ) ), BSON( "c" << LT << "y" << "b" << GT << "z" ), BSONObj() );
|
|
ASSERT( !p6.exactKeyMatch() );
|
|
QueryPlan p7( nsd(), INDEXNO( "b" << 1 ), FBS( BSONObj() ), BSONObj(), BSON( "a" << 1 ) );
|
|
ASSERT( !p7.exactKeyMatch() );
|
|
QueryPlan p8( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FBS( BSON( "b" << "y" << "a" << "z" ) ), BSON( "b" << "y" << "a" << "z" ), BSONObj() );
|
|
ASSERT( p8.exactKeyMatch() );
|
|
QueryPlan p9( nsd(), INDEXNO( "a" << 1 ), FBS( BSON( "a" << "z" ) ), BSON( "a" << "z" ), BSON( "a" << 1 ) );
|
|
ASSERT( p9.exactKeyMatch() );
|
|
}
|
|
};
|
|
|
|
class MoreKeyMatch : public Base {
|
|
public:
|
|
void run() {
|
|
QueryPlan p( nsd(), INDEXNO( "a" << 1 ), FBS( BSON( "a" << "r" << "b" << NE << "q" ) ), BSON( "a" << "r" << "b" << NE << "q" ), BSON( "a" << 1 ) );
|
|
ASSERT( !p.exactKeyMatch() );
|
|
}
|
|
};
|
|
|
|
class ExactKeyQueryTypes : public Base {
|
|
public:
|
|
void run() {
|
|
QueryPlan p( nsd(), INDEXNO( "a" << 1 ), FBS( BSON( "a" << "b" ) ), BSON( "a" << "b" ), BSONObj() );
|
|
ASSERT( p.exactKeyMatch() );
|
|
QueryPlan p2( nsd(), INDEXNO( "a" << 1 ), FBS( BSON( "a" << 4 ) ), BSON( "a" << 4 ), BSONObj() );
|
|
ASSERT( !p2.exactKeyMatch() );
|
|
QueryPlan p3( nsd(), INDEXNO( "a" << 1 ), FBS( BSON( "a" << BSON( "c" << "d" ) ) ), BSON( "a" << BSON( "c" << "d" ) ), BSONObj() );
|
|
ASSERT( !p3.exactKeyMatch() );
|
|
BSONObjBuilder b;
|
|
b.appendRegex( "a", "^ddd" );
|
|
BSONObj q = b.obj();
|
|
QueryPlan p4( nsd(), INDEXNO( "a" << 1 ), FBS( q ), q, BSONObj() );
|
|
ASSERT( !p4.exactKeyMatch() );
|
|
QueryPlan p5( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FBS( BSON( "a" << "z" << "b" << 4 ) ), BSON( "a" << "z" << "b" << 4 ), BSONObj() );
|
|
ASSERT( !p5.exactKeyMatch() );
|
|
}
|
|
};
|
|
|
|
class Unhelpful : public Base {
|
|
public:
|
|
void run() {
|
|
QueryPlan p( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FBS( BSON( "b" << 1 ) ), BSON( "b" << 1 ), BSONObj() );
|
|
ASSERT( !p.range( "a" ).nontrivial() );
|
|
ASSERT( p.unhelpful() );
|
|
QueryPlan p2( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FBS( BSON( "b" << 1 << "c" << 1 ) ), BSON( "b" << 1 << "c" << 1 ), BSON( "a" << 1 ) );
|
|
ASSERT( !p2.scanAndOrderRequired() );
|
|
ASSERT( !p2.range( "a" ).nontrivial() );
|
|
ASSERT( !p2.unhelpful() );
|
|
QueryPlan p3( nsd(), INDEXNO( "b" << 1 ), FBS( BSON( "b" << 1 << "c" << 1 ) ), BSON( "b" << 1 << "c" << 1 ), BSONObj() );
|
|
ASSERT( p3.range( "b" ).nontrivial() );
|
|
ASSERT( !p3.unhelpful() );
|
|
QueryPlan p4( nsd(), INDEXNO( "b" << 1 << "c" << 1 ), FBS( BSON( "c" << 1 << "d" << 1 ) ), BSON( "c" << 1 << "d" << 1 ), BSONObj() );
|
|
ASSERT( !p4.range( "b" ).nontrivial() );
|
|
ASSERT( p4.unhelpful() );
|
|
}
|
|
};
|
|
|
|
} // namespace QueryPlanTests
|
|
|
|
namespace QueryPlanSetTests {
|
|
class Base {
|
|
public:
|
|
Base() : _context( ns() ){
|
|
string err;
|
|
userCreateNS( ns(), BSONObj(), err, false );
|
|
}
|
|
virtual ~Base() {
|
|
if ( !nsd() )
|
|
return;
|
|
NamespaceDetailsTransient::_get( ns() ).clearQueryCache();
|
|
string s( ns() );
|
|
dropNS( s );
|
|
}
|
|
static void assembleRequest( const string &ns, BSONObj query, int nToReturn, int nToSkip, BSONObj *fieldsToReturn, int queryOptions, Message &toSend ) {
|
|
// see query.h for the protocol we are using here.
|
|
BufBuilder b;
|
|
int opts = queryOptions;
|
|
b.append(opts);
|
|
b.append(ns.c_str());
|
|
b.append(nToSkip);
|
|
b.append(nToReturn);
|
|
query.appendSelfToBufBuilder(b);
|
|
if ( fieldsToReturn )
|
|
fieldsToReturn->appendSelfToBufBuilder(b);
|
|
toSend.setData(dbQuery, b.buf(), b.len());
|
|
}
|
|
protected:
|
|
static const char *ns() { return "unittests.QueryPlanSetTests"; }
|
|
static NamespaceDetails *nsd() { return nsdetails( ns() ); }
|
|
private:
|
|
dblock lk_;
|
|
Client::Context _context;
|
|
};
|
|
|
|
class NoIndexes : public Base {
|
|
public:
|
|
void run() {
|
|
auto_ptr< FieldRangeSet > frs( new FieldRangeSet( ns(), BSON( "a" << 4 ) ) );
|
|
QueryPlanSet s( ns(), frs, BSON( "a" << 4 ), BSON( "b" << 1 ) );
|
|
ASSERT_EQUALS( 1, s.nPlans() );
|
|
}
|
|
};
|
|
|
|
class Optimal : public Base {
|
|
public:
|
|
void run() {
|
|
Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" );
|
|
Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "b_2" );
|
|
auto_ptr< FieldRangeSet > frs( new FieldRangeSet( ns(), BSON( "a" << 4 ) ) );
|
|
QueryPlanSet s( ns(), frs, BSON( "a" << 4 ), BSONObj() );
|
|
ASSERT_EQUALS( 1, s.nPlans() );
|
|
}
|
|
};
|
|
|
|
class NoOptimal : public Base {
|
|
public:
|
|
void run() {
|
|
Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" );
|
|
Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" );
|
|
auto_ptr< FieldRangeSet > frs( new FieldRangeSet( ns(), BSON( "a" << 4 ) ) );
|
|
QueryPlanSet s( ns(), frs, BSON( "a" << 4 ), BSON( "b" << 1 ) );
|
|
ASSERT_EQUALS( 3, s.nPlans() );
|
|
}
|
|
};
|
|
|
|
class NoSpec : public Base {
|
|
public:
|
|
void run() {
|
|
Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" );
|
|
Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" );
|
|
auto_ptr< FieldRangeSet > frs( new FieldRangeSet( ns(), BSONObj() ) );
|
|
QueryPlanSet s( ns(), frs, BSONObj(), BSONObj() );
|
|
ASSERT_EQUALS( 1, s.nPlans() );
|
|
}
|
|
};
|
|
|
|
class HintSpec : public Base {
|
|
public:
|
|
void run() {
|
|
Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" );
|
|
Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" );
|
|
BSONObj b = BSON( "hint" << BSON( "a" << 1 ) );
|
|
BSONElement e = b.firstElement();
|
|
auto_ptr< FieldRangeSet > frs( new FieldRangeSet( ns(), BSON( "a" << 1 ) ) );
|
|
QueryPlanSet s( ns(), frs, BSON( "a" << 1 ), BSON( "b" << 1 ), &e );
|
|
ASSERT_EQUALS( 1, s.nPlans() );
|
|
}
|
|
};
|
|
|
|
class HintName : public Base {
|
|
public:
|
|
void run() {
|
|
Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" );
|
|
Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" );
|
|
BSONObj b = BSON( "hint" << "a_1" );
|
|
BSONElement e = b.firstElement();
|
|
auto_ptr< FieldRangeSet > frs( new FieldRangeSet( ns(), BSON( "a" << 1 ) ) );
|
|
QueryPlanSet s( ns(), frs, BSON( "a" << 1 ), BSON( "b" << 1 ), &e );
|
|
ASSERT_EQUALS( 1, s.nPlans() );
|
|
}
|
|
};
|
|
|
|
class NaturalHint : public Base {
|
|
public:
|
|
void run() {
|
|
Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" );
|
|
Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" );
|
|
BSONObj b = BSON( "hint" << BSON( "$natural" << 1 ) );
|
|
BSONElement e = b.firstElement();
|
|
auto_ptr< FieldRangeSet > frs( new FieldRangeSet( ns(), BSON( "a" << 1 ) ) );
|
|
QueryPlanSet s( ns(), frs, BSON( "a" << 1 ), BSON( "b" << 1 ), &e );
|
|
ASSERT_EQUALS( 1, s.nPlans() );
|
|
}
|
|
};
|
|
|
|
class NaturalSort : public Base {
|
|
public:
|
|
void run() {
|
|
Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" );
|
|
Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "b_2" );
|
|
auto_ptr< FieldRangeSet > frs( new FieldRangeSet( ns(), BSON( "a" << 1 ) ) );
|
|
QueryPlanSet s( ns(), frs, BSON( "a" << 1 ), BSON( "$natural" << 1 ) );
|
|
ASSERT_EQUALS( 1, s.nPlans() );
|
|
}
|
|
};
|
|
|
|
class BadHint : public Base {
|
|
public:
|
|
void run() {
|
|
BSONObj b = BSON( "hint" << "a_1" );
|
|
BSONElement e = b.firstElement();
|
|
auto_ptr< FieldRangeSet > frs( new FieldRangeSet( ns(), BSON( "a" << 1 ) ) );
|
|
ASSERT_EXCEPTION( QueryPlanSet s( ns(), frs, BSON( "a" << 1 ), BSON( "b" << 1 ), &e ),
|
|
AssertionException );
|
|
}
|
|
};
|
|
|
|
class Count : public Base {
|
|
public:
|
|
void run() {
|
|
Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" );
|
|
Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" );
|
|
string err;
|
|
ASSERT_EQUALS( 0, runCount( ns(), BSON( "query" << BSON( "a" << 4 ) ), err ) );
|
|
BSONObj one = BSON( "a" << 1 );
|
|
BSONObj fourA = BSON( "a" << 4 );
|
|
BSONObj fourB = BSON( "a" << 4 );
|
|
theDataFileMgr.insertWithObjMod( ns(), one );
|
|
ASSERT_EQUALS( 0, runCount( ns(), BSON( "query" << BSON( "a" << 4 ) ), err ) );
|
|
theDataFileMgr.insertWithObjMod( ns(), fourA );
|
|
ASSERT_EQUALS( 1, runCount( ns(), BSON( "query" << BSON( "a" << 4 ) ), err ) );
|
|
theDataFileMgr.insertWithObjMod( ns(), fourB );
|
|
ASSERT_EQUALS( 2, runCount( ns(), BSON( "query" << BSON( "a" << 4 ) ), err ) );
|
|
ASSERT_EQUALS( 3, runCount( ns(), BSON( "query" << BSONObj() ), err ) );
|
|
ASSERT_EQUALS( 3, runCount( ns(), BSON( "query" << BSON( "a" << GT << 0 ) ), err ) );
|
|
// missing ns
|
|
ASSERT_EQUALS( -1, runCount( "unittests.missingNS", BSONObj(), err ) );
|
|
// impossible match
|
|
ASSERT_EQUALS( 0, runCount( ns(), BSON( "query" << BSON( "a" << GT << 0 << LT << -1 ) ), err ) );
|
|
}
|
|
};
|
|
|
|
class QueryMissingNs : public Base {
|
|
public:
|
|
QueryMissingNs() { log() << "querymissingns starts" << endl; }
|
|
~QueryMissingNs() {
|
|
log() << "end QueryMissingNs" << endl;
|
|
}
|
|
void run() {
|
|
Message m;
|
|
assembleRequest( "unittests.missingNS", BSONObj(), 0, 0, 0, 0, m );
|
|
DbMessage d(m);
|
|
QueryMessage q(d);
|
|
Message ret;
|
|
runQuery( m, q, ret );
|
|
ASSERT_EQUALS( 0, ((QueryResult*)ret.header())->nReturned );
|
|
}
|
|
|
|
};
|
|
|
|
class UnhelpfulIndex : public Base {
|
|
public:
|
|
void run() {
|
|
Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" );
|
|
Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" );
|
|
auto_ptr< FieldRangeSet > frs( new FieldRangeSet( ns(), BSON( "a" << 1 << "c" << 2 ) ) );
|
|
QueryPlanSet s( ns(), frs, BSON( "a" << 1 << "c" << 2 ), BSONObj() );
|
|
ASSERT_EQUALS( 2, s.nPlans() );
|
|
}
|
|
};
|
|
|
|
class SingleException : public Base {
|
|
public:
|
|
void run() {
|
|
Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" );
|
|
Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" );
|
|
auto_ptr< FieldRangeSet > frs( new FieldRangeSet( ns(), BSON( "a" << 4 ) ) );
|
|
QueryPlanSet s( ns(), frs, BSON( "a" << 4 ), BSON( "b" << 1 ) );
|
|
ASSERT_EQUALS( 3, s.nPlans() );
|
|
bool threw = false;
|
|
auto_ptr< TestOp > t( new TestOp( true, threw ) );
|
|
boost::shared_ptr< TestOp > done = s.runOp( *t );
|
|
ASSERT( threw );
|
|
ASSERT( done->complete() );
|
|
ASSERT( done->exception().empty() );
|
|
ASSERT( !done->error() );
|
|
}
|
|
private:
|
|
class TestOp : public QueryOp {
|
|
public:
|
|
TestOp( bool iThrow, bool &threw ) : iThrow_( iThrow ), threw_( threw ), i_(), youThrow_( false ) {}
|
|
virtual void _init() {}
|
|
virtual void next() {
|
|
if ( iThrow_ )
|
|
threw_ = true;
|
|
massert( 10408 , "throw", !iThrow_ );
|
|
if ( ++i_ > 10 )
|
|
setComplete();
|
|
}
|
|
virtual QueryOp *_createChild() const {
|
|
QueryOp *op = new TestOp( youThrow_, threw_ );
|
|
youThrow_ = !youThrow_;
|
|
return op;
|
|
}
|
|
virtual bool mayRecordPlan() const { return true; }
|
|
private:
|
|
bool iThrow_;
|
|
bool &threw_;
|
|
int i_;
|
|
mutable bool youThrow_;
|
|
};
|
|
};
|
|
|
|
class AllException : public Base {
|
|
public:
|
|
void run() {
|
|
Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" );
|
|
Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" );
|
|
auto_ptr< FieldRangeSet > frs( new FieldRangeSet( ns(), BSON( "a" << 4 ) ) );
|
|
QueryPlanSet s( ns(), frs, BSON( "a" << 4 ), BSON( "b" << 1 ) );
|
|
ASSERT_EQUALS( 3, s.nPlans() );
|
|
auto_ptr< TestOp > t( new TestOp() );
|
|
boost::shared_ptr< TestOp > done = s.runOp( *t );
|
|
ASSERT( !done->complete() );
|
|
ASSERT_EQUALS( "throw", done->exception().msg );
|
|
ASSERT( done->error() );
|
|
}
|
|
private:
|
|
class TestOp : public QueryOp {
|
|
public:
|
|
virtual void _init() {}
|
|
virtual void next() {
|
|
massert( 10409 , "throw", false );
|
|
}
|
|
virtual QueryOp *_createChild() const {
|
|
return new TestOp();
|
|
}
|
|
virtual bool mayRecordPlan() const { return true; }
|
|
};
|
|
};
|
|
|
|
class SaveGoodIndex : public Base {
|
|
public:
|
|
void run() {
|
|
Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" );
|
|
Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" );
|
|
nPlans( 3 );
|
|
runQuery();
|
|
nPlans( 1 );
|
|
nPlans( 1 );
|
|
Helpers::ensureIndex( ns(), BSON( "c" << 1 ), false, "c_1" );
|
|
nPlans( 3 );
|
|
runQuery();
|
|
nPlans( 1 );
|
|
|
|
{
|
|
DBDirectClient client;
|
|
for( int i = 0; i < 34; ++i ) {
|
|
client.insert( ns(), BSON( "i" << i ) );
|
|
client.update( ns(), QUERY( "i" << i ), BSON( "i" << i + 1 ) );
|
|
client.remove( ns(), BSON( "i" << i + 1 ) );
|
|
}
|
|
}
|
|
nPlans( 3 );
|
|
|
|
auto_ptr< FieldRangeSet > frs( new FieldRangeSet( ns(), BSON( "a" << 4 ) ) );
|
|
QueryPlanSet s( ns(), frs, BSON( "a" << 4 ), BSON( "b" << 1 ) );
|
|
NoRecordTestOp original;
|
|
s.runOp( original );
|
|
nPlans( 3 );
|
|
|
|
BSONObj hint = fromjson( "{hint:{$natural:1}}" );
|
|
BSONElement hintElt = hint.firstElement();
|
|
auto_ptr< FieldRangeSet > frs2( new FieldRangeSet( ns(), BSON( "a" << 4 ) ) );
|
|
QueryPlanSet s2( ns(), frs2, BSON( "a" << 4 ), BSON( "b" << 1 ), &hintElt );
|
|
TestOp newOriginal;
|
|
s2.runOp( newOriginal );
|
|
nPlans( 3 );
|
|
|
|
auto_ptr< FieldRangeSet > frs3( new FieldRangeSet( ns(), BSON( "a" << 4 ) ) );
|
|
QueryPlanSet s3( ns(), frs3, BSON( "a" << 4 ), BSON( "b" << 1 << "c" << 1 ) );
|
|
TestOp newerOriginal;
|
|
s3.runOp( newerOriginal );
|
|
nPlans( 3 );
|
|
|
|
runQuery();
|
|
nPlans( 1 );
|
|
}
|
|
private:
|
|
void nPlans( int n ) {
|
|
auto_ptr< FieldRangeSet > frs( new FieldRangeSet( ns(), BSON( "a" << 4 ) ) );
|
|
QueryPlanSet s( ns(), frs, BSON( "a" << 4 ), BSON( "b" << 1 ) );
|
|
ASSERT_EQUALS( n, s.nPlans() );
|
|
}
|
|
void runQuery() {
|
|
auto_ptr< FieldRangeSet > frs( new FieldRangeSet( ns(), BSON( "a" << 4 ) ) );
|
|
QueryPlanSet s( ns(), frs, BSON( "a" << 4 ), BSON( "b" << 1 ) );
|
|
TestOp original;
|
|
s.runOp( original );
|
|
}
|
|
class TestOp : public QueryOp {
|
|
public:
|
|
virtual void _init() {}
|
|
virtual void next() {
|
|
setComplete();
|
|
}
|
|
virtual QueryOp *_createChild() const {
|
|
return new TestOp();
|
|
}
|
|
virtual bool mayRecordPlan() const { return true; }
|
|
};
|
|
class NoRecordTestOp : public TestOp {
|
|
virtual bool mayRecordPlan() const { return false; }
|
|
virtual QueryOp *_createChild() const { return new NoRecordTestOp(); }
|
|
};
|
|
};
|
|
|
|
class TryAllPlansOnErr : public Base {
|
|
public:
|
|
void run() {
|
|
Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" );
|
|
|
|
auto_ptr< FieldRangeSet > frs( new FieldRangeSet( ns(), BSON( "a" << 4 ) ) );
|
|
QueryPlanSet s( ns(), frs, BSON( "a" << 4 ), BSON( "b" << 1 ) );
|
|
ScanOnlyTestOp op;
|
|
s.runOp( op );
|
|
ASSERT( fromjson( "{$natural:1}" ).woCompare( NamespaceDetailsTransient::_get( ns() ).indexForPattern( s.fbs().pattern( BSON( "b" << 1 ) ) ) ) == 0 );
|
|
ASSERT_EQUALS( 1, NamespaceDetailsTransient::_get( ns() ).nScannedForPattern( s.fbs().pattern( BSON( "b" << 1 ) ) ) );
|
|
|
|
auto_ptr< FieldRangeSet > frs2( new FieldRangeSet( ns(), BSON( "a" << 4 ) ) );
|
|
QueryPlanSet s2( ns(), frs2, BSON( "a" << 4 ), BSON( "b" << 1 ) );
|
|
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( 10410 , "throw", false );
|
|
setComplete();
|
|
}
|
|
virtual QueryOp *_createChild() 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( 10411 , "throw", false );
|
|
}
|
|
virtual QueryOp *_createChild() const {
|
|
return new ScanOnlyTestOp();
|
|
}
|
|
};
|
|
};
|
|
|
|
class FindOne : public Base {
|
|
public:
|
|
void run() {
|
|
BSONObj one = BSON( "a" << 1 );
|
|
theDataFileMgr.insertWithObjMod( ns(), one );
|
|
BSONObj result;
|
|
ASSERT( Helpers::findOne( ns(), BSON( "a" << 1 ), result ) );
|
|
ASSERT_EXCEPTION( Helpers::findOne( ns(), BSON( "a" << 1 ), result, true ), AssertionException );
|
|
Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" );
|
|
ASSERT( Helpers::findOne( ns(), BSON( "a" << 1 ), result, true ) );
|
|
}
|
|
};
|
|
|
|
class Delete : public Base {
|
|
public:
|
|
void run() {
|
|
Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" );
|
|
for( int i = 0; i < 200; ++i ) {
|
|
BSONObj two = BSON( "a" << 2 );
|
|
theDataFileMgr.insertWithObjMod( ns(), two );
|
|
}
|
|
BSONObj one = BSON( "a" << 1 );
|
|
theDataFileMgr.insertWithObjMod( ns(), one );
|
|
deleteObjects( ns(), BSON( "a" << 1 ), false );
|
|
ASSERT( BSON( "a" << 1 ).woCompare( NamespaceDetailsTransient::_get( ns() ).indexForPattern( FieldRangeSet( ns(), BSON( "a" << 1 ) ).pattern() ) ) == 0 );
|
|
ASSERT_EQUALS( 2, NamespaceDetailsTransient::_get( ns() ).nScannedForPattern( FieldRangeSet( ns(), BSON( "a" << 1 ) ).pattern() ) );
|
|
}
|
|
};
|
|
|
|
class DeleteOneScan : public Base {
|
|
public:
|
|
void run() {
|
|
Helpers::ensureIndex( ns(), BSON( "_id" << 1 ), false, "_id_1" );
|
|
BSONObj one = BSON( "_id" << 3 << "a" << 1 );
|
|
BSONObj two = BSON( "_id" << 2 << "a" << 1 );
|
|
BSONObj three = BSON( "_id" << 1 << "a" << -1 );
|
|
theDataFileMgr.insertWithObjMod( ns(), one );
|
|
theDataFileMgr.insertWithObjMod( ns(), two );
|
|
theDataFileMgr.insertWithObjMod( ns(), three );
|
|
deleteObjects( ns(), BSON( "_id" << GT << 0 << "a" << GT << 0 ), true );
|
|
for( boost::shared_ptr<Cursor> c = theDataFileMgr.findAll( ns() ); c->ok(); c->advance() )
|
|
ASSERT( 3 != c->current().getIntField( "_id" ) );
|
|
}
|
|
};
|
|
|
|
class DeleteOneIndex : public Base {
|
|
public:
|
|
void run() {
|
|
Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a" );
|
|
BSONObj one = BSON( "a" << 2 << "_id" << 0 );
|
|
BSONObj two = BSON( "a" << 1 << "_id" << 1 );
|
|
BSONObj three = BSON( "a" << 0 << "_id" << 2 );
|
|
theDataFileMgr.insertWithObjMod( ns(), one );
|
|
theDataFileMgr.insertWithObjMod( ns(), two );
|
|
theDataFileMgr.insertWithObjMod( ns(), three );
|
|
deleteObjects( ns(), BSON( "a" << GTE << 0 << "_id" << GT << 0 ), true );
|
|
for( boost::shared_ptr<Cursor> c = theDataFileMgr.findAll( ns() ); c->ok(); c->advance() )
|
|
ASSERT( 2 != c->current().getIntField( "_id" ) );
|
|
}
|
|
};
|
|
|
|
class TryOtherPlansBeforeFinish : public Base {
|
|
public:
|
|
void run() {
|
|
Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" );
|
|
for( int i = 0; i < 100; ++i ) {
|
|
for( int j = 0; j < 2; ++j ) {
|
|
BSONObj temp = BSON( "a" << 100 - i - 1 << "b" << i );
|
|
theDataFileMgr.insertWithObjMod( ns(), temp );
|
|
}
|
|
}
|
|
Message m;
|
|
// Need to return at least 2 records to cause plan to be recorded.
|
|
assembleRequest( ns(), QUERY( "b" << 0 << "a" << GTE << 0 ).obj, 2, 0, 0, 0, m );
|
|
stringstream ss;
|
|
{
|
|
DbMessage d(m);
|
|
QueryMessage q(d);
|
|
runQuery( m, q);
|
|
}
|
|
ASSERT( BSON( "$natural" << 1 ).woCompare( NamespaceDetailsTransient::_get( ns() ).indexForPattern( FieldRangeSet( ns(), BSON( "b" << 0 << "a" << GTE << 0 ) ).pattern() ) ) == 0 );
|
|
|
|
Message m2;
|
|
assembleRequest( ns(), QUERY( "b" << 99 << "a" << GTE << 0 ).obj, 2, 0, 0, 0, m2 );
|
|
{
|
|
DbMessage d(m2);
|
|
QueryMessage q(d);
|
|
runQuery( m2, q);
|
|
}
|
|
ASSERT( BSON( "a" << 1 ).woCompare( NamespaceDetailsTransient::_get( ns() ).indexForPattern( FieldRangeSet( ns(), BSON( "b" << 0 << "a" << GTE << 0 ) ).pattern() ) ) == 0 );
|
|
ASSERT_EQUALS( 2, NamespaceDetailsTransient::_get( ns() ).nScannedForPattern( FieldRangeSet( ns(), BSON( "b" << 0 << "a" << GTE << 0 ) ).pattern() ) );
|
|
}
|
|
};
|
|
|
|
class InQueryIntervals : public Base {
|
|
public:
|
|
void run() {
|
|
Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" );
|
|
for( int i = 0; i < 10; ++i ) {
|
|
BSONObj temp = BSON( "a" << i );
|
|
theDataFileMgr.insertWithObjMod( ns(), temp );
|
|
}
|
|
BSONObj hint = fromjson( "{$hint:{a:1}}" );
|
|
BSONElement hintElt = hint.firstElement();
|
|
auto_ptr< FieldRangeSet > frs( new FieldRangeSet( ns(), fromjson( "{a:{$in:[2,3,6,9,11]}}" ) ) );
|
|
QueryPlanSet s( ns(), frs, fromjson( "{a:{$in:[2,3,6,9,11]}}" ), BSONObj(), &hintElt );
|
|
QueryPlan qp( nsd(), 1, s.fbs(), fromjson( "{a:{$in:[2,3,6,9,11]}}" ), BSONObj() );
|
|
boost::shared_ptr<Cursor> c = qp.newCursor();
|
|
double expected[] = { 2, 3, 6, 9 };
|
|
for( int i = 0; i < 4; ++i, c->advance() ) {
|
|
ASSERT_EQUALS( expected[ i ], c->current().getField( "a" ).number() );
|
|
}
|
|
ASSERT( !c->ok() );
|
|
|
|
// now check reverse
|
|
{
|
|
auto_ptr< FieldRangeSet > frs( new FieldRangeSet( ns(), fromjson( "{a:{$in:[2,3,6,9,11]}}" ) ) );
|
|
QueryPlanSet s( ns(), frs, fromjson( "{a:{$in:[2,3,6,9,11]}}" ), BSON( "a" << -1 ), &hintElt );
|
|
QueryPlan qp( nsd(), 1, s.fbs(), fromjson( "{a:{$in:[2,3,6,9,11]}}" ), BSON( "a" << -1 ) );
|
|
boost::shared_ptr<Cursor> c = qp.newCursor();
|
|
double expected[] = { 9, 6, 3, 2 };
|
|
for( int i = 0; i < 4; ++i, c->advance() ) {
|
|
ASSERT_EQUALS( expected[ i ], c->current().getField( "a" ).number() );
|
|
}
|
|
ASSERT( !c->ok() );
|
|
}
|
|
}
|
|
};
|
|
|
|
class EqualityThenIn : public Base {
|
|
public:
|
|
void run() {
|
|
Helpers::ensureIndex( ns(), BSON( "a" << 1 << "b" << 1 ), false, "a_1_b_1" );
|
|
for( int i = 0; i < 10; ++i ) {
|
|
BSONObj temp = BSON( "a" << 5 << "b" << i );
|
|
theDataFileMgr.insertWithObjMod( ns(), temp );
|
|
}
|
|
BSONObj hint = fromjson( "{$hint:{a:1,b:1}}" );
|
|
BSONElement hintElt = hint.firstElement();
|
|
auto_ptr< FieldRangeSet > frs( new FieldRangeSet( ns(), fromjson( "{a:5,b:{$in:[2,3,6,9,11]}}" ) ) );
|
|
QueryPlan qp( nsd(), 1, *frs, fromjson( "{a:5,b:{$in:[2,3,6,9,11]}}" ), BSONObj() );
|
|
boost::shared_ptr<Cursor> c = qp.newCursor();
|
|
double expected[] = { 2, 3, 6, 9 };
|
|
ASSERT( c->ok() );
|
|
for( int i = 0; i < 4; ++i, c->advance() ) {
|
|
ASSERT( c->ok() );
|
|
ASSERT_EQUALS( expected[ i ], c->current().getField( "b" ).number() );
|
|
}
|
|
ASSERT( !c->ok() );
|
|
}
|
|
};
|
|
|
|
class NotEqualityThenIn : public Base {
|
|
public:
|
|
void run() {
|
|
Helpers::ensureIndex( ns(), BSON( "a" << 1 << "b" << 1 ), false, "a_1_b_1" );
|
|
for( int i = 0; i < 10; ++i ) {
|
|
BSONObj temp = BSON( "a" << 5 << "b" << i );
|
|
theDataFileMgr.insertWithObjMod( ns(), temp );
|
|
}
|
|
BSONObj hint = fromjson( "{$hint:{a:1,b:1}}" );
|
|
BSONElement hintElt = hint.firstElement();
|
|
auto_ptr< FieldRangeSet > frs( new FieldRangeSet( ns(), fromjson( "{a:{$gte:5},b:{$in:[2,3,6,9,11]}}" ) ) );
|
|
QueryPlan qp( nsd(), 1, *frs, fromjson( "{a:{$gte:5},b:{$in:[2,3,6,9,11]}}" ), BSONObj() );
|
|
boost::shared_ptr<Cursor> c = qp.newCursor();
|
|
for( int i = 2; i < 10; ++i, c->advance() ) {
|
|
ASSERT_EQUALS( i, c->current().getField( "b" ).number() );
|
|
}
|
|
ASSERT( !c->ok() );
|
|
}
|
|
};
|
|
|
|
} // namespace QueryPlanSetTests
|
|
|
|
class All : public Suite {
|
|
public:
|
|
All() : Suite( "queryoptimizer" ){}
|
|
|
|
void setupTests(){
|
|
add< FieldRangeTests::Empty >();
|
|
add< FieldRangeTests::Eq >();
|
|
add< FieldRangeTests::DupEq >();
|
|
add< FieldRangeTests::Lt >();
|
|
add< FieldRangeTests::Lte >();
|
|
add< FieldRangeTests::Gt >();
|
|
add< FieldRangeTests::Gte >();
|
|
add< FieldRangeTests::TwoLt >();
|
|
add< FieldRangeTests::TwoGt >();
|
|
add< FieldRangeTests::EqGte >();
|
|
add< FieldRangeTests::EqGteInvalid >();
|
|
add< FieldRangeTests::Regex >();
|
|
add< FieldRangeTests::RegexObj >();
|
|
add< FieldRangeTests::UnhelpfulRegex >();
|
|
add< FieldRangeTests::In >();
|
|
add< FieldRangeTests::Equality >();
|
|
add< FieldRangeTests::SimplifiedQuery >();
|
|
add< FieldRangeTests::QueryPatternTest >();
|
|
add< FieldRangeTests::NoWhere >();
|
|
add< FieldRangeTests::Numeric >();
|
|
add< FieldRangeTests::InLowerBound >();
|
|
add< FieldRangeTests::InUpperBound >();
|
|
add< FieldRangeTests::MultiBound >();
|
|
add< FieldRangeTests::Diff1 >();
|
|
add< FieldRangeTests::Diff2 >();
|
|
add< FieldRangeTests::Diff3 >();
|
|
add< FieldRangeTests::Diff4 >();
|
|
add< FieldRangeTests::Diff5 >();
|
|
add< FieldRangeTests::Diff6 >();
|
|
add< FieldRangeTests::Diff7 >();
|
|
add< FieldRangeTests::Diff8 >();
|
|
add< FieldRangeTests::Diff9 >();
|
|
add< FieldRangeTests::Diff10 >();
|
|
add< FieldRangeTests::Diff11 >();
|
|
add< FieldRangeTests::Diff12 >();
|
|
add< FieldRangeTests::Diff13 >();
|
|
add< FieldRangeTests::Diff14 >();
|
|
add< FieldRangeTests::Diff15 >();
|
|
add< FieldRangeTests::Diff16 >();
|
|
add< FieldRangeTests::Diff17 >();
|
|
add< FieldRangeTests::Diff18 >();
|
|
add< FieldRangeTests::Diff19 >();
|
|
add< FieldRangeTests::Diff20 >();
|
|
add< FieldRangeTests::Diff21 >();
|
|
add< FieldRangeTests::Diff22 >();
|
|
add< FieldRangeTests::Diff23 >();
|
|
add< FieldRangeTests::Diff24 >();
|
|
add< FieldRangeTests::Diff25 >();
|
|
add< FieldRangeTests::Diff26 >();
|
|
add< FieldRangeTests::Diff27 >();
|
|
add< FieldRangeTests::Diff28 >();
|
|
add< FieldRangeTests::Diff29 >();
|
|
add< FieldRangeTests::Diff30 >();
|
|
add< FieldRangeTests::Diff31 >();
|
|
add< FieldRangeTests::Diff32 >();
|
|
add< FieldRangeTests::Diff33 >();
|
|
add< FieldRangeTests::Diff34 >();
|
|
add< FieldRangeTests::Diff35 >();
|
|
add< FieldRangeTests::Diff36 >();
|
|
add< FieldRangeTests::Diff37 >();
|
|
add< FieldRangeTests::Diff38 >();
|
|
add< FieldRangeTests::Diff39 >();
|
|
add< FieldRangeTests::Diff40 >();
|
|
add< FieldRangeTests::Diff41 >();
|
|
add< FieldRangeTests::Diff42 >();
|
|
add< FieldRangeTests::Diff43 >();
|
|
add< FieldRangeTests::Diff44 >();
|
|
add< FieldRangeTests::Diff45 >();
|
|
add< FieldRangeTests::Diff46 >();
|
|
add< FieldRangeTests::Diff47 >();
|
|
add< FieldRangeTests::Diff48 >();
|
|
add< FieldRangeTests::Diff49 >();
|
|
add< FieldRangeTests::Diff50 >();
|
|
add< FieldRangeTests::Diff51 >();
|
|
add< FieldRangeTests::Diff52 >();
|
|
add< FieldRangeTests::Diff53 >();
|
|
add< FieldRangeTests::Diff54 >();
|
|
add< FieldRangeTests::Diff55 >();
|
|
add< FieldRangeTests::Diff56 >();
|
|
add< FieldRangeTests::Diff57 >();
|
|
add< FieldRangeTests::Diff58 >();
|
|
add< FieldRangeTests::Diff59 >();
|
|
add< FieldRangeTests::Diff60 >();
|
|
add< FieldRangeTests::Diff61 >();
|
|
add< FieldRangeTests::Diff62 >();
|
|
add< FieldRangeTests::Diff63 >();
|
|
add< FieldRangeTests::DiffMulti1 >();
|
|
add< FieldRangeTests::DiffMulti2 >();
|
|
add< FieldRangeTests::SetDiff >();
|
|
add< FieldRangeTests::SetIntersect >();
|
|
add< QueryPlanTests::NoIndex >();
|
|
add< QueryPlanTests::SimpleOrder >();
|
|
add< QueryPlanTests::MoreIndexThanNeeded >();
|
|
add< QueryPlanTests::IndexSigns >();
|
|
add< QueryPlanTests::IndexReverse >();
|
|
add< QueryPlanTests::NoOrder >();
|
|
add< QueryPlanTests::EqualWithOrder >();
|
|
add< QueryPlanTests::Optimal >();
|
|
add< QueryPlanTests::MoreOptimal >();
|
|
add< QueryPlanTests::KeyMatch >();
|
|
add< QueryPlanTests::MoreKeyMatch >();
|
|
add< QueryPlanTests::ExactKeyQueryTypes >();
|
|
add< QueryPlanTests::Unhelpful >();
|
|
add< QueryPlanSetTests::NoIndexes >();
|
|
add< QueryPlanSetTests::Optimal >();
|
|
add< QueryPlanSetTests::NoOptimal >();
|
|
add< QueryPlanSetTests::NoSpec >();
|
|
add< QueryPlanSetTests::HintSpec >();
|
|
add< QueryPlanSetTests::HintName >();
|
|
add< QueryPlanSetTests::NaturalHint >();
|
|
add< QueryPlanSetTests::NaturalSort >();
|
|
add< QueryPlanSetTests::BadHint >();
|
|
add< QueryPlanSetTests::Count >();
|
|
add< QueryPlanSetTests::QueryMissingNs >();
|
|
add< QueryPlanSetTests::UnhelpfulIndex >();
|
|
add< QueryPlanSetTests::SingleException >();
|
|
add< QueryPlanSetTests::AllException >();
|
|
add< QueryPlanSetTests::SaveGoodIndex >();
|
|
add< QueryPlanSetTests::TryAllPlansOnErr >();
|
|
add< QueryPlanSetTests::FindOne >();
|
|
add< QueryPlanSetTests::Delete >();
|
|
add< QueryPlanSetTests::DeleteOneScan >();
|
|
add< QueryPlanSetTests::DeleteOneIndex >();
|
|
add< QueryPlanSetTests::TryOtherPlansBeforeFinish >();
|
|
add< QueryPlanSetTests::InQueryIntervals >();
|
|
add< QueryPlanSetTests::EqualityThenIn >();
|
|
add< QueryPlanSetTests::NotEqualityThenIn >();
|
|
}
|
|
} myall;
|
|
|
|
} // namespace QueryOptimizerTests
|
|
|