// queryutiltests.cpp : query utility 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 . */ #include "mongo/pch.h" #include "mongo/db/instance.h" #include "mongo/db/json.h" #include "mongo/db/pdfile.h" #include "mongo/db/query_optimizer_internal.h" #include "mongo/db/querypattern.h" #include "mongo/db/queryutil.h" #include "mongo/db/structure/collection.h" #include "mongo/dbtests/dbtests.h" namespace QueryUtilTests { namespace FieldIntervalTests { class ToString { public: void run() { BSONObj obj = BSON( "a" << 1 ); FieldInterval fieldInterval( obj.firstElement() ); fieldInterval.toString(); // Just test that we don't crash. } }; } // namespace FieldIntervalTests namespace FieldRangeTests { class ToString { public: void run() { BSONObj obj = BSON( "a" << 1 ); FieldRange fieldRange( obj.firstElement(), false, true ); fieldRange.toString(); // Just test that we don't crash. } }; class Base { public: virtual ~Base() {} void run() { const FieldRangeSet s( "ns", query(), true, true ); 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() ); ASSERT_EQUALS( mustBeExactMatchRepresentation(), s.range( "a" ).mustBeExactMatchRepresentation() ); ASSERT_EQUALS( isPointIntervalSet(), s.range( "a" ).isPointIntervalSet() ); } 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; } virtual bool mustBeExactMatchRepresentation() { return false; } virtual bool isPointIntervalSet() { return false; } static void checkElt( BSONElement expected, BSONElement actual ) { if ( expected.woCompare( actual, false ) ) { mongo::unittest::log() << "expected: " << expected << ", got: " << actual; ASSERT( false ); } } }; class NumericBase : public Base { public: NumericBase() { o = BSON( "min" << -numeric_limits::max() << "max" << numeric_limits::max() ); } virtual BSONElement lower() { return o["min"]; } virtual BSONElement upper() { return o["max"]; } private: BSONObj o; }; class EmptyQuery : public Base { virtual BSONObj query() { return BSONObj(); } virtual bool mustBeExactMatchRepresentation() { return true; } }; 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(); } virtual bool mustBeExactMatchRepresentation() { return true; } virtual bool isPointIntervalSet() { return true; } 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; } virtual bool mustBeExactMatchRepresentation() { return true; } BSONObj o_; }; class Lte : public Lt { virtual BSONObj query() { return BSON( "a" << LTE << 1 ); } virtual bool upperInclusive() { return true; } virtual bool mustBeExactMatchRepresentation() { return true; } }; class LtDate : public Base { public: LtDate() : _o( BSON( "" << Date_t( 5000 ) ) ), _o2( BSON( "" << true ) ) { } virtual BSONObj query() { return BSON( "a" << LT << _o.firstElement() ); } virtual BSONElement lower() { // $lt:date is bounded from below by 'true', the highest value of the next lowest // canonical type. return _o2.firstElement(); } virtual bool lowerInclusive() { // 'true' should not match $lt:date, so the bound is exclusive. return false; } virtual BSONElement upper() { return _o.firstElement(); } virtual bool upperInclusive() { return false; } virtual bool mustBeExactMatchRepresentation() { return true; } private: BSONObj _o, _o2; }; 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; } virtual bool mustBeExactMatchRepresentation() { return true; } BSONObj o_; }; class Gte : public Gt { virtual BSONObj query() { return BSON( "a" << GTE << 1 ); } virtual bool mustBeExactMatchRepresentation() { return true; } virtual bool lowerInclusive() { return true; } }; class GtString : public Base { public: GtString() : _o( BSON( "" << "abc" ) ), _o2( BSON( "" << BSONObj() ) ) { } virtual BSONObj query() { return BSON( "a" << GT << _o.firstElement() ); } virtual BSONElement lower() { return _o.firstElement(); } virtual bool lowerInclusive() { return false; } virtual BSONElement upper() { // $gt:string is bounded from above by '{}', the lowest value of the next highest // canonical type. return _o2.firstElement(); } virtual bool upperInclusive() { // '{}' should not match $gt:string, so the bound is exclusive. return false; } virtual bool mustBeExactMatchRepresentation() { return true; } private: BSONObj _o, _o2; }; 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 frs( "ns", BSON( "a" << 1 << "a" << GTE << 2 ), true, true ); ASSERT( !frs.matchPossible() ); } }; struct RegexBase : Base { void run() { //need to only look at first interval FieldRangeSet s( "ns", query(), true, true ); 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(); } virtual bool mustBeExactMatchRepresentation() { return true; } virtual bool isPointIntervalSet() { return true; } BSONObj o1_, o2_; }; class And : public Base { public: And() : _o1( BSON( "-" << 0 ) ), _o2( BSON( "-" << 10 ) ) {} void run() { Base::run(); const FieldRangeSet s( "ns", query(), true, true ); // There should not be an index constraint recorded for the $and field. ASSERT( s.range( "$and" ).universal() ); } private: virtual BSONObj query() { return BSON( "$and" << BSON_ARRAY( BSON( "a" << GT << 0 ) << BSON( "a" << LTE << 10 ) ) ); } virtual bool mustBeExactMatchRepresentation() { return true; } virtual BSONElement lower() { return _o1.firstElement(); } virtual bool lowerInclusive() { return false; } virtual BSONElement upper() { return _o2.firstElement(); } BSONObj _o1, _o2; }; /** * Field bounds of a query clause within a singleton $or expression are intersected with * those of the overall query. SERVER-6416 */ class SingletonOr : public Base { public: SingletonOr() : _obj( BSON( "" << 5 ) ) { } void run() { Base::run(); const FieldRangeSet s( "ns", query(), true, true ); // There should not be an index constraint recorded for the $or field. ASSERT( s.range( "$or" ).universal() ); } private: virtual BSONObj query() { // There is a single $or clause. return fromjson( "{$or:[{a:5}]}" ); } virtual bool mustBeExactMatchRepresentation() { // The 'a' FieldRange is an exact match representation (it is an equality), though // not the overall FieldRangeSet. return true; } virtual bool isPointIntervalSet() { return true; } virtual BSONElement lower() { return _obj.firstElement(); } virtual BSONElement upper() { return _obj.firstElement(); } BSONObj _obj; }; /** * Field bounds of a query clause within a nested singleton $or expression are intersected * with those of the overall query. SERVER-6416 */ class NestedSingletonOr : public SingletonOr { virtual BSONObj query() { // There is a single $or clause. return fromjson( "{$or:[{$or:[{a:5}]}]}" ); } }; /** * Field bounds of a query clause within a non singleton $or expression are not intersected * with those of the overall query. */ class NonSingletonOr : public Base { public: void run() { Base::run(); const FieldRangeSet s( "ns", query(), true, true ); // There should not be an index constraint recorded for the $or field. ASSERT( s.range( "$or" ).universal() ); } private: virtual BSONObj query() { // There is more than one $or clause. return fromjson( "{$or:[{a:5},{a:6}]}" ); } virtual bool mustBeExactMatchRepresentation() { // The 'a' FieldRange is an exact match representation (the default universal range // matches anything), though not the overall FieldRangeSet. return true; } }; class Empty { public: void run() { FieldRangeSet s( "ns", BSON( "a" << GT << 5 << LT << 5 ), true, true ); const FieldRange &r = s.range( "a" ); ASSERT( r.empty() ); ASSERT( !r.equality() ); ASSERT( !r.universal() ); ASSERT( r.mustBeExactMatchRepresentation() ); } }; class Equality { public: void run() { FieldRangeSet s( "ns", BSON( "a" << 1 ), true, true ); ASSERT( s.range( "a" ).equality() ); FieldRangeSet s2( "ns", BSON( "a" << GTE << 1 << LTE << 1 ), true, true ); ASSERT( s2.range( "a" ).equality() ); FieldRangeSet s3( "ns", BSON( "a" << GT << 1 << LTE << 1 ), true, true ); ASSERT( !s3.range( "a" ).equality() ); FieldRangeSet s4( "ns", BSON( "a" << GTE << 1 << LT << 1 ), true, true ); ASSERT( !s4.range( "a" ).equality() ); FieldRangeSet s5( "ns", BSON( "a" << GTE << 1 << LTE << 1 << GT << 1 ), true, true ); ASSERT( !s5.range( "a" ).equality() ); FieldRangeSet s6( "ns", BSON( "a" << GTE << 1 << LTE << 1 << LT << 1 ), true, true ); ASSERT( !s6.range( "a" ).equality() ); } }; class QueryPatternBase { protected: static QueryPattern p( const BSONObj &query, const BSONObj &sort = BSONObj() ) { return FieldRangeSet( "", query, true, true ).pattern( sort ); } }; class QueryPatternTest : public QueryPatternBase { 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 ) ) ); } }; class QueryPatternEmpty : public QueryPatternBase { public: void run() { ASSERT( p( BSON( "a" << GT << 5 << LT << 7 ) ) != p( BSON( "a" << GT << 7 << LT << 5 ) ) ); } }; class QueryPatternNeConstraint : public QueryPatternBase { public: void run() { ASSERT( p( BSON( "a" << NE << 5 ) ) != p( BSON( "a" << GT << 1 ) ) ); ASSERT( p( BSON( "a" << NE << 5 ) ) != p( BSONObj() ) ); ASSERT( p( BSON( "a" << NE << 5 ) ) == p( BSON( "a" << NE << "a" ) ) ); } }; /** Check QueryPattern categories for optimized bounds. */ class QueryPatternOptimizedBounds { public: void run() { // With unoptimized bounds, different inequalities yield different query patterns. ASSERT( p( BSON( "a" << GT << 1 ), false ) != p( BSON( "a" << LT << 1 ), false ) ); // SERVER-4675 Descriptive test - With optimized bounds, different inequalities // yield different query patterns. ASSERT( p( BSON( "a" << GT << 1 ), true ) == p( BSON( "a" << LT << 1 ), true ) ); } private: static QueryPattern p( const BSONObj &query, bool optimize ) { return FieldRangeSet( "", query, true, optimize ).pattern( BSONObj() ); } }; class NoWhere { public: void run() { ASSERT_EQUALS( 0, FieldRangeSet( "ns", BSON( "$where" << 1 ), true, true ) .numNonUniversalRanges() ); } }; class Numeric { public: void run() { FieldRangeSet f( "", BSON( "a" << 1 ), true, true ); 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]}}" ), true, true ); 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]}}" ), true, true ); 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 ); } }; /** Check union of two non overlapping ranges. */ class BoundUnion { public: void run() { FieldRangeSet frs( "", fromjson( "{a:{$gt:1,$lt:9},b:{$gt:9,$lt:12}}" ), true, true ); FieldRange ret = frs.range( "a" ); ret |= frs.range( "b" ); ASSERT_EQUALS( 2U, ret.intervals().size() ); } }; /** Check union of two ranges where one includes another. */ class BoundUnionFullyContained { public: void run() { FieldRangeSet frs( "", fromjson( "{a:{$gt:1,$lte:9},b:{$gt:2,$lt:8}," "c:{$gt:2,$lt:9},d:{$gt:2,$lte:9}}" ), true, true ); FieldRange u = frs.range( "a" ); u |= frs.range( "b" ); ASSERT_EQUALS( 1U, u.intervals().size() ); ASSERT_EQUALS( frs.range( "a" ).toString(), u.toString() ); u |= frs.range( "c" ); ASSERT_EQUALS( 1U, u.intervals().size() ); ASSERT_EQUALS( frs.range( "a" ).toString(), u.toString() ); u |= frs.range( "d" ); ASSERT_EQUALS( 1U, u.intervals().size() ); ASSERT_EQUALS( frs.range( "a" ).toString(), u.toString() ); } }; /** * Check union of two ranges where one does not include another because of an inclusive * bound. */ class BoundUnionOverlapWithInclusivity { public: void run() { FieldRangeSet frs( "", fromjson( "{a:{$gt:1,$lt:9},b:{$gt:2,$lte:9}}" ), true, true ); FieldRange u = frs.range( "a" ); u |= frs.range( "b" ); ASSERT_EQUALS( 1U, u.intervals().size() ); FieldRangeSet expected( "", fromjson( "{a:{$gt:1,$lte:9}}" ), true, true ); ASSERT_EQUALS( expected.range( "a" ).toString(), u.toString() ); } }; /** Check union of two empty ranges. */ class BoundUnionEmpty { public: void run() { FieldRangeSet frs( "", fromjson( "{a:{$in:[]},b:{$in:[]}}" ), true, true ); FieldRange a = frs.range( "a" ); a |= frs.range( "b" ); ASSERT( a.empty() ); ASSERT_EQUALS( 0U, a.intervals().size() ); } }; class MultiBound { public: void run() { FieldRangeSet frs1( "", fromjson( "{a:{$in:[1,3,5,7,9]}}" ), true, true ); FieldRangeSet frs2( "", fromjson( "{a:{$in:[2,3,5,8,9]}}" ), true, true ); FieldRange fr1 = frs1.range( "a" ); FieldRange fr2 = frs2.range( "a" ); fr1.intersect( fr2, true ); 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().toString() ), true, true ); 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) {} }; class SplitRangeBase : public DiffBase { public: SplitRangeBase( string obj, int low1, bool low1I, int high1, bool high1I, int low2, bool low2I, int high2, bool high2I ) : _obj( obj ) { _n[ 0 ] = low1; _n[ 1 ] = high1; _n[ 2 ] = low2; _n[ 3 ] = high2; _b[ 0 ] = low1I; _b[ 1 ] = high1I; _b[ 2 ] = low2I; _b[ 3 ] = high2I; } private: virtual unsigned len() const { return 2; } 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[ 4 ]; bool _b[ 4 ]; }; struct Diff11 : public SplitRangeBase { Diff11() : SplitRangeBase( "{a:{$gt:1,$lte:4},b:{$gt:2,$lt:4}}", 1, false, 2, true, 4, true, 4, true) {} }; struct Diff12 : public SplitRangeBase { Diff12() : SplitRangeBase( "{a:{$gt:1,$lt:5},b:{$gt:2,$lt:4}}", 1, false, 2, true, 4, true, 5, false) {} }; struct Diff13 : public TwoRangeBase { Diff13() : TwoRangeBase( "{a:{$gt:1,$lt:5},b:{$gt:1,$lt:4}}", 4, 5, true, false) {} }; struct Diff14 : public SplitRangeBase { Diff14() : SplitRangeBase( "{a:{$gte:1,$lt:5},b:{$gt:1,$lt:4}}", 1, true, 1, true, 4, true, 5, 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 SplitRangeBase { Diff28() : SplitRangeBase( "{a:{$gte:1,$lte:5},b:3}", 1, true, 3, false, 3, false, 5, 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 SplitRangeBase { Diff54() : SplitRangeBase( "{a:{$gte:1,$lte:5},b:{$gt:1,$lt:5}}", 1, true, 1, true, 5, true, 5, 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}" ) {} }; struct Diff64 : public TwoRangeBase { Diff64() : TwoRangeBase( "{a:{$gte:1,$lte:2},b:{$gt:0,$lte:1}}", 1, 2, false, true ) {} }; 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}}" ), true, true ); 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 3; } virtual const int *nums() const { static int n[] = { 2, 3, 3, 4, 5, 7 }; return n; } virtual const bool *incs() const { static bool b[] = { true, false, false, true, 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}}" ), true, true ); 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 Universal { public: void run() { FieldRangeSet frs1( "", BSON( "a" << 1 ), true, true ); FieldRange f1 = frs1.range( "a" ); ASSERT( !f1.universal() ); ASSERT( frs1.range( "b" ).universal() ); FieldRangeSet frs2( "", BSON( "a" << GT << 1 ), true, true ); FieldRange f2 = frs2.range( "a" ); ASSERT( !f2.universal() ); FieldRangeSet frs3( "", BSON( "a" << LT << 1 ), true, true ); FieldRange f3 = frs3.range( "a" ); ASSERT( !frs3.range( "a" ).universal() ); FieldRangeSet frs4( "", BSON( "a" << NE << 1 ), true, true ); FieldRange f4 = frs4.range( "a" ); ASSERT( !f4.universal() ); f1 |= f4; ASSERT( f1.universal() ); f1.intersect( f2, true ); ASSERT( !f1.universal() ); FieldRangeSet frs5( "", BSON( "a" << GT << 1 << LTE << 2 ), true, true ); FieldRange f5 = frs5.range( "a" ); ASSERT( !f5.universal() ); FieldRangeSet frs6( "", BSONObj(), true, true ); FieldRange f6 = frs6.range( "a" ); ASSERT( f6.universal() ); f6 -= f5; ASSERT( !f6.universal() ); } }; /** Field range calculation for an untyped $regex within a $elemMatch. */ class ElemMatchRegex { public: void run() { FieldRangeSet frs( "", BSON( "a" << BSON( "$elemMatch" << BSON("$regex" << "^x") ) ), true, true ); ASSERT( !frs.range( "a" ).universal() ); ASSERT_EQUALS( "x", frs.range( "a" ).min().String() ); } }; /** * The elemMatchContext is preserved when two FieldRanges are intersected, singleKey == * false, and the resulting FieldRange is an unmodified copy of one of the original two * FieldRanges. For example, if FieldRange a == [[1, 10]] is intersected with FieldRange b * == [[5, 5]], a will be replaced with b. In this case, because a becomes an exact copy of * b, b's elemMatchContext will be copied to a. */ class PreserveNonUniversalElemMatchContext { public: void run() { BSONObj query = fromjson( "{a:{$elemMatch:{b:1}},c:{$lt:5},d:1}" ); _expectedElemMatchContext = query.firstElement().Obj().firstElement(); // The elemMatchContext is set properly for the 'a.b' field. FieldRangeSet frs( "", query, true, true ); assertElemMatchContext( frs.range( "a.b" ) ); // The elemMatchContext is preserved after intersecting with a superset range. frs.range( "a.b" ).intersect( frs.range( "c" ), false ); assertElemMatchContext( frs.range( "a.b" ) ); // The elemMatchContext is forwarded after intersecting with a subset range. frs.range( "c" ).intersect( frs.range( "a.b" ), false ); assertElemMatchContext( frs.range( "c" ) ); // The elemMatchContext is cleared after a _single key_ intersection. frs.range( "a.b" ).intersect( frs.range( "d" ), true /* singleKey */ ); ASSERT( frs.range( "a.b" ).elemMatchContext().eoo() ); } private: void assertElemMatchContext( const FieldRange& range ) { ASSERT_EQUALS( _expectedElemMatchContext.rawdata(), range.elemMatchContext().rawdata() ); } BSONElement _expectedElemMatchContext; }; /** Intersect two universal ranges, one special. */ class IntersectSpecial { public: void run() { FieldRangeSet frs( "", fromjson( "{loc:{$near:[0,0],$maxDistance:5}}" ), true, true ); // The intersection is special. ASSERT( !frs.range( "loc" ).getSpecial().empty() ); } }; namespace ExactMatchRepresentation { class NotExactMatchRepresentation { public: NotExactMatchRepresentation( const BSONObj &query ) : _query( query ) {} virtual ~NotExactMatchRepresentation() {} void run() { ASSERT( !FieldRangeSet( "", _query, !multikey(), true ).range( "a" ).mustBeExactMatchRepresentation() ); } protected: virtual bool multikey() const { return false; } private: BSONObj _query; }; struct EqualArray : public NotExactMatchRepresentation { EqualArray() : NotExactMatchRepresentation( BSON( "a" << BSON_ARRAY( "1" ) ) ) {} }; struct EqualEmptyArray : public NotExactMatchRepresentation { EqualEmptyArray() : NotExactMatchRepresentation( fromjson( "{a:[]}" ) ) {} }; struct EqualNull : public NotExactMatchRepresentation { EqualNull() : NotExactMatchRepresentation( fromjson( "{a:null}" ) ) {} }; struct InArray : public NotExactMatchRepresentation { InArray() : NotExactMatchRepresentation( fromjson( "{a:{$in:[[1]]}}" ) ) {} }; struct InRegex : public NotExactMatchRepresentation { InRegex() : NotExactMatchRepresentation( fromjson( "{a:{$in:[/^a/]}}" ) ) {} }; struct InNull : public NotExactMatchRepresentation { InNull() : NotExactMatchRepresentation( fromjson( "{a:{$in:[null]}}" ) ) {} }; struct Exists : public NotExactMatchRepresentation { Exists() : NotExactMatchRepresentation( fromjson( "{a:{$exists:false}}" ) ) {} }; struct UntypedRegex : public NotExactMatchRepresentation { UntypedRegex() : NotExactMatchRepresentation( BSON( "a" << BSONObjBuilder().appendRegex("$regex", "^a", "").obj() ) ) {} }; struct UntypedRegexString : public NotExactMatchRepresentation { UntypedRegexString() : NotExactMatchRepresentation( BSON( "a" << BSON( "$regex" << "^a" ) ) ) {} }; struct NotIn : public NotExactMatchRepresentation { NotIn() : NotExactMatchRepresentation( fromjson( "{a:{$not:{$in:[0]}}}" ) ) {} }; struct NotGt : public NotExactMatchRepresentation { NotGt() : NotExactMatchRepresentation( fromjson( "{a:{$not:{$gt:0}}}" ) ) {} }; struct GtArray : public NotExactMatchRepresentation { GtArray() : NotExactMatchRepresentation( fromjson( "{a:{$gt:[0]}}" ) ) {} }; struct GtNull : public NotExactMatchRepresentation { GtNull() : NotExactMatchRepresentation( fromjson( "{a:{$gt:null}}" ) ) {} }; struct LtObject : public NotExactMatchRepresentation { LtObject() : NotExactMatchRepresentation( fromjson( "{a:{$lt:{}}}" ) ) {} }; /** Descriptive test - behavior could potentially be different. */ struct NotNe : public NotExactMatchRepresentation { NotNe() : NotExactMatchRepresentation( fromjson( "{a:{$not:{$ne:4}}}" ) ) {} }; class MultikeyIntersection : public NotExactMatchRepresentation { public: MultikeyIntersection() : NotExactMatchRepresentation ( BSON( "a" << GTE << 0 << "a" << 0 ) ) { } private: virtual bool multikey() const { return true; } }; class Intersection { public: void run() { FieldRangeSet set( "", BSON( "a" << 1 << "b" << GT << 2 << "c" << NE << 10 ), true, true ); FieldRange &a = set.range( "a" ); FieldRange &b = set.range( "b" ); FieldRange &c = set.range( "c" ); FieldRange &missing = set.range( "missing" ); ASSERT( a.mustBeExactMatchRepresentation() ); ASSERT( b.mustBeExactMatchRepresentation() ); ASSERT( !c.mustBeExactMatchRepresentation() ); ASSERT( missing.mustBeExactMatchRepresentation() ); // Intersecting two exact matches preserves the mustBeExactMatchRepresentation // property. a.intersect( missing, true ); ASSERT( a.mustBeExactMatchRepresentation() ); a.intersect( b, true ); ASSERT( a.mustBeExactMatchRepresentation() ); // Other operations clear the mustBeExactMatchRepresentation property. b.intersect( c, true ); ASSERT( !b.mustBeExactMatchRepresentation() ); } }; class Union { public: void run() { FieldRangeSet set( "", BSON( "left" << 1 << "right" << GT << 2 ), true, true ); FieldRange &left = set.range( "left" ); FieldRange &right = set.range( "right" ); left |= right; ASSERT( !left.mustBeExactMatchRepresentation() ); } }; class Difference { public: void run() { FieldRangeSet set( "", BSON( "left" << 1 << "right" << GT << 2 ), true, true ); FieldRange &left = set.range( "left" ); FieldRange &right = set.range( "right" ); left -= right; ASSERT( !left.mustBeExactMatchRepresentation() ); } }; } // namespace ExactMatchRepresentation } // namespace FieldRangeTests namespace FieldRangeSetTests { class Basics { public: void run() { { BSONObj obj = BSON( "a" << 1 ); FieldRangeSet fieldRangeSet( "", obj, true, true ); fieldRangeSet.toString(); // Just test that we don't crash. } { boost::shared_ptr frs; { string ns = str::stream() << "foo"; frs.reset( new FieldRangeSet( ns.c_str(), BSONObj(), true, true ) ); } ASSERT_EQUALS( string( "foo" ), frs->ns() ); } } }; class Intersect { public: void run() { FieldRangeSet frs1( "", fromjson( "{b:{$in:[5,6]},c:7,d:{$in:[8,9]}}" ), true, true ); FieldRangeSet frs2( "", fromjson( "{a:1,b:5,c:{$in:[7,8]},d:{$in:[8,9]},e:10}" ), true, true ); frs1 &= frs2; FieldRangeSet expectedResult( "", fromjson( "{a:1,b:5,c:7,d:{$in:[8,9]},e:10}" ), true, true ); ASSERT_EQUALS( frs1.toString(), expectedResult.toString() ); } }; class MultiKeyIntersect { public: void run() { FieldRangeSet frs1( "", BSONObj(), false, true ); FieldRangeSet frs2( "", BSON( "a" << GT << 4 ), false, true ); FieldRangeSet frs3( "", BSON( "a" << LT << 6 ), false, true ); // An intersection with a universal range is allowed. frs1 &= frs2; ASSERT_EQUALS( frs2.toString(), frs1.toString() ); // An intersection with non universal range is not allowed, as it might prevent a // valid multikey match. frs1 &= frs3; ASSERT_EQUALS( frs2.toString(), frs1.toString() ); // Now intersect with a fully contained range. FieldRangeSet frs4( "", BSON( "a" << GT << 6 ), false, true ); frs1 &= frs4; ASSERT_EQUALS( frs4.toString(), frs1.toString() ); } }; /* Intersecting an empty multikey range with another range produces an empty range. */ class EmptyMultiKeyIntersect { public: void run() { FieldRangeSet frs1( "", BSON( "a" << BSON( "$in" << BSONArray() ) ), false, true ); FieldRangeSet frs2( "", BSON( "a" << 5 ), false, true ); ASSERT( frs1.range( "a" ).empty() ); frs1 &= frs2; ASSERT( frs1.range( "a" ).empty() ); } }; class MultiKeyDiff { public: void run() { FieldRangeSet frs1( "", BSON( "a" << GT << 4 ), false, true ); FieldRangeSet frs2( "", BSON( "a" << GT << 6 ), false, true ); // Range subtraction is no different for multikey ranges. frs1 -= frs2; FieldRangeSet expectedResult( "", BSON( "a" << GT << 4 << LTE << 6 ), true, true ); ASSERT_EQUALS( frs1.toString(), expectedResult.toString() ); } }; class MatchPossible { public: void run() { FieldRangeSet frs1( "", BSON( "a" << GT << 4 ), true, true ); ASSERT( frs1.matchPossible() ); // Conflicting constraints invalid for a single key set. FieldRangeSet frs2( "", BSON( "a" << GT << 4 << LT << 2 ), true, true ); ASSERT( !frs2.matchPossible() ); // Conflicting constraints not possible for a multi key set. FieldRangeSet frs3( "", BSON( "a" << GT << 4 << LT << 2 ), false, true ); ASSERT( frs3.matchPossible() ); } }; class MatchPossibleForIndex { public: void run() { // Conflicting constraints not possible for a multi key set. FieldRangeSet frs1( "", BSON( "a" << GT << 4 << LT << 2 ), false, true ); ASSERT( frs1.matchPossibleForIndex( BSON( "a" << 1 ) ) ); // Conflicting constraints for a multi key set. FieldRangeSet frs2( "", BSON( "a" << GT << 4 << LT << 2 ), true, true ); ASSERT( !frs2.matchPossibleForIndex( BSON( "a" << 1 ) ) ); // If the index doesn't include the key, it is not single key invalid. ASSERT( frs2.matchPossibleForIndex( BSON( "b" << 1 ) ) ); // If the index key is not an index, the set is not single key invalid. ASSERT( frs2.matchPossibleForIndex( BSON( "$natural" << 1 ) ) ); ASSERT( frs2.matchPossibleForIndex( BSONObj() ) ); } }; class Subset { public: void run() { _frs1.reset ( new FieldRangeSet ( "", BSON( "a" << GT << 4 << LT << 4 << "b" << 5 << "c" << 6 ), true, true ) ); _frs2.reset( _frs1->subset( BSON( "a" << 1 << "b" << 1 << "d" << 1 ) ) ); // An empty range should be copied. ASSERT( _frs1->range( "a" ).empty() ); ASSERT( _frs2->range( "a" ).empty() ); assertRangeCopied( "a" ); assertRangeCopied( "b" ); assertRangeNotCopied( "c" ); assertRangeNotCopied( "d" ); } private: void assertRangeCopied( const string &fieldName ) { ASSERT_EQUALS( _frs1->range( fieldName.c_str() ).toString(), _frs2->range( fieldName.c_str() ).toString() ); } void assertRangeNotCopied( const string &fieldName ) { ASSERT_EQUALS( _frs1->range( "qqqqq" ).toString(), // Missing field, universal range _frs2->range( fieldName.c_str() ).toString() ); } auto_ptr _frs1; auto_ptr _frs2; }; /** Duplicate a FieldRangeSet, but with prefixed field names. */ class Prefixed { public: void run() { FieldRangeSet original( "", BSON( "a" << 1 ), true, true ); scoped_ptr prefixed( original.prefixed( "prefix" ) ); ASSERT( prefixed->singleKey() ); ASSERT( prefixed->range( "a" ).universal() ); ASSERT( prefixed->range( "prefix.a" ).equality() ); } }; /** No field range is generated for an $atomic field. SERVER-5354 */ class Atomic { public: void run() { FieldRangeSet ranges( "", BSON( "a" << 1 << "$atomic" << 1 ), true, true ); // No range is computed for the '$atomic' field. ASSERT( ranges.range( "$atomic" ).universal() ); // A standard equality range is computed for the 'a' field. ASSERT( ranges.range( "a" ).equality() ); } }; namespace ElemMatch { /** Field ranges generated for the $elemMatch operator. */ class Ranges { public: void run() { FieldRangeSet set( "", fromjson( "{ a:{ $elemMatch:{ b:1, c:2 } } }" ), true, true ); ASSERT( set.range( "a.b" ).equality() ); ASSERT( set.range( "a.c" ).equality() ); ASSERT( !set.range( "a.d" ).equality() ); ASSERT( !set.range( "a" ).equality() ); } }; /** * Field ranges generated when the $elemMatch operator is applied to top level * elements. */ class TopLevelElements { public: void run() { FieldRangeSet set( "", fromjson( "{ a:{ $elemMatch:{ $gte:5, $lte:5 } } }" ), true, true ); // No constraints exist for operator based field names like "a.$gte". ASSERT( set.range( "a.$gte" ).universal() ); ASSERT( set.range( "a.$lte" ).universal() ); // The index ranges for the $elemMatch subexpressions are intersected in a // single value context, generating an equality index bound. SERVER-4180 ASSERT( set.range( "a" ).equality() ); } }; /** * Field ranges generated when the $elemMatch operator is applied to a top level * $not meta operator. */ class TopLevelNotElement { public: void run() { FieldRangeSet set( "", fromjson( "{ a:{ $elemMatch:{ $not:{ $ne:5 } } } }" ), true, true ); // No constraints exist for operator based field names like "a.$not". ASSERT( set.range( "a.$not" ).universal() ); ASSERT( set.range( "a.$not.$ne" ).universal() ); ASSERT( set.range( "a" ).equality() ); } }; /** Field ranges generated for $elemMatch nested within a top level $elemMatch. */ class TopLevelNested { public: void run() { FieldRangeSet set( "", fromjson( "{ a:{ $elemMatch:{ $elemMatch:{ $in:[ 5 ] " "} } } }" ), true, true ); ASSERT_EQUALS( 0, set.numNonUniversalRanges() ); FieldRangeSet set2( "", fromjson( "{ a:{ $elemMatch:{ $elemMatch:{ b:{ " "$in:[ 5 ] } } } } }" ), true, true ); ASSERT_EQUALS( 0, set2.numNonUniversalRanges() ); } }; /** Field ranges generated for $not:$elemMatch queries. */ class Not { public: void run() { FieldRangeSet set( "", fromjson( "{ a:{ $not:{ $elemMatch:{ b:{ $ne:1 } } } } }" ), true, true ); ASSERT( !set.range( "a.b" ).equality() ); ASSERT( set.range( "a.b" ).universal() ); } }; /** Field ranges generated for nested $elemMatch expressions. */ class Nested { public: void run() { FieldRangeSet set( "", fromjson( "{ a:{ $elemMatch:{ b:{ $elemMatch:{ c:1" " } } } } }" ), true, true ); // No constraints are generated for the following fields. BSONArray universalFields = BSON_ARRAY( "a" << "a.b" << "b" << "b.c" << "c" ); BSONObjIterator i( universalFields ); while( i.more() ) { ASSERT( set.range( i.next().String().c_str() ).universal() ); } // A correct constraint is generated for a nested $elemMatch field. ASSERT( set.range( "a.b.c" ).equality() ); ASSERT_EQUALS( 1, set.range( "a.b.c" ).min().number() ); } }; /** Field ranges generated for an $elemMatch expression nested within $all. */ class AllNested { public: void run() { FieldRangeSet set( "", fromjson( "{ a:{ $elemMatch:{ b:{ $all:" "[ { $elemMatch:{ c:1 } }, { $elemMatch:{ d:2 } } ]" " } } } }" ), true, true ); // No constraints are generated for the following fields. BSONArray universalFields = BSON_ARRAY( "a" << "a.b" << "b" << "b.c" << "c" << "b.d" << "d" << "a.b.d" ); BSONObjIterator i( universalFields ); while( i.more() ) { ASSERT( set.range( i.next().String().c_str() ).universal() ); } // A correct constraint is generated for the first nested $elemMatch field. ASSERT( set.range( "a.b.c" ).equality() ); ASSERT_EQUALS( 1, set.range( "a.b.c" ).min().number() ); } }; } // namespace ElemMatch namespace ExactMatchRepresentation { class ExactMatchRepresentation { public: ExactMatchRepresentation( const BSONObj &query ) : _query( query ) {} void run() { ASSERT( FieldRangeSet( "", _query, true, true ) .mustBeExactMatchRepresentation() ); } private: BSONObj _query; }; class NotExactMatchRepresentation { public: NotExactMatchRepresentation( const BSONObj &query ) : _query( query ) {} void run() { ASSERT( !FieldRangeSet( "", _query, true, true ) .mustBeExactMatchRepresentation() ); } private: BSONObj _query; }; struct EmptyQuery : public ExactMatchRepresentation { EmptyQuery() : ExactMatchRepresentation( BSONObj() ) {} }; struct Equal : public ExactMatchRepresentation { Equal() : ExactMatchRepresentation( BSON( "a" << 0 ) ) {} }; struct In : public ExactMatchRepresentation { In() : ExactMatchRepresentation( fromjson( "{a:{$in:[0,1]}}" ) ) {} }; struct Where : public NotExactMatchRepresentation { Where() : NotExactMatchRepresentation( BSON( "a" << 0 << "$where" << "foo" ) ) {} }; struct Not : public NotExactMatchRepresentation { Not() : NotExactMatchRepresentation( fromjson( "{a:{$not:{$in:[0]}}}" ) ) {} }; struct Regex : public NotExactMatchRepresentation { Regex() : NotExactMatchRepresentation( fromjson( "{a:/^a/}" ) ) {} }; struct UntypedRegex : public NotExactMatchRepresentation { UntypedRegex() : NotExactMatchRepresentation( BSON( "a" << BSONObjBuilder().appendRegex("$regex", "^a", "").obj() ) ) {} }; struct UntypedRegexString : public NotExactMatchRepresentation { UntypedRegexString() : NotExactMatchRepresentation( BSON( "a" << BSON( "$regex" << "^a" ) ) ) {} }; struct And : public ExactMatchRepresentation { And() : ExactMatchRepresentation( fromjson( "{$and:[{a:{$in:[0,1]}}]}" ) ) {} }; struct Or : public NotExactMatchRepresentation { Or() : NotExactMatchRepresentation( fromjson( "{$or:[{a:{$in:[0,1]}}]}" ) ) {} }; struct All : public NotExactMatchRepresentation { All() : NotExactMatchRepresentation( fromjson( "{a:{$all:[0]}}" ) ) {} }; struct ElemMatch : public NotExactMatchRepresentation { ElemMatch() : NotExactMatchRepresentation( fromjson( "{a:{$elemMatch:{b:1}}}" ) ) {} }; struct AllElemMatch : public NotExactMatchRepresentation { AllElemMatch() : NotExactMatchRepresentation( fromjson( "{a:{$all:[{$elemMatch:{b:1}}]}}" ) ) {} }; struct NotSecondField : public NotExactMatchRepresentation { NotSecondField() : NotExactMatchRepresentation( fromjson( "{a:{$in:[1],$not:{$in:[0]}}}" ) ) {} }; } // namespace ExactMatchRepresentation } // namespace FieldRangeSetTests namespace FieldRangeSetPairTests { class ToString { public: void run() { BSONObj obj = BSON( "a" << 1 ); FieldRangeSetPair FieldRangeSetPair( "", obj ); FieldRangeSetPair.toString(); // Just test that we don't crash. } }; class NoNonUniversalRanges { public: void run() { FieldRangeSetPair frsp1( "", BSONObj() ); ASSERT( frsp1.noNonUniversalRanges() ); FieldRangeSetPair frsp2( "", BSON( "a" << 1 ) ); ASSERT( !frsp2.noNonUniversalRanges() ); FieldRangeSetPair frsp3( "", BSON( "a" << GT << 1 ) ); ASSERT( !frsp3.noNonUniversalRanges() ); // A single key invalid constraint is not universal. FieldRangeSetPair frsp4( "", BSON( "a" << GT << 1 << LT << 0 ) ); ASSERT( frsp4.frsForIndex( 0, -1 ).matchPossible() ); ASSERT( !frsp4.noNonUniversalRanges() ); // Still not universal if multikey invalid constraint. FieldRangeSetPair frsp5( "", BSON( "a" << BSON( "$in" << BSONArray() ) ) ); ASSERT( !frsp5.frsForIndex( 0, -1 ).matchPossible() ); ASSERT( !frsp5.noNonUniversalRanges() ); } }; class MatchPossible { public: void run() { // Match possible for simple query. FieldRangeSetPair frsp1( "", BSON( "a" << 1 ) ); ASSERT( frsp1.matchPossible() ); // Match possible for single key invalid query. FieldRangeSetPair frsp2( "", BSON( "a" << GT << 1 << LT << 0 ) ); ASSERT( frsp2.matchPossible() ); // Match not possible for multi key invalid query. frsp1 -= frsp1.frsForIndex( 0, - 1 ); ASSERT( !frsp1.matchPossible() ); } }; class IndexBase { Lock::DBWrite _lk; Client::Context _ctx; public: IndexBase() : _lk(ns()), _ctx( ns() ) , indexNum_( 0 ) { string err; userCreateNS( ns(), BSONObj(), err, false ); } ~IndexBase() { if ( !nsd() ) return; _ctx.db()->dropCollection( ns() ); } protected: static const char *ns() { return "unittests.FieldRangeSetPairTests"; } static NamespaceDetails *nsd() { return nsdetails( ns() ); } Client::Context* ctx() { return &_ctx; } 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->getCompletedIndexCount(); ++i ) { if ( d->idx(i).keyPattern() == key /*indexName() == name*/ || ( d->idx(i).isIdIndex() && IndexDetails::isIdIndexPattern( key ) ) ) return &d->idx(i); } verify( false ); return 0; } int indexno( const BSONObj &key ) { return nsd()->idxNo( *index(key) ); } static DBDirectClient client_; private: int indexNum_; }; DBDirectClient IndexBase::client_; class MatchPossibleForIndex : public IndexBase { public: void run() { int a = indexno( BSON( "a" << 1 ) ); int b = indexno( BSON( "b" << 1 ) ); IndexBase::client_.insert( ns(), BSON( "a" << BSON_ARRAY( 1 << 2 ) << "b" << 1 ) ); // Valid ranges match possible for both indexes. FieldRangeSetPair frsp1( ns(), BSON( "a" << GT << 1 << LT << 4 << "b" << GT << 1 << LT << 4 ) ); ASSERT( frsp1.matchPossibleForIndex( nsd(), a, BSON( "a" << 1 ) ) ); ASSERT( frsp1.matchPossibleForIndex( nsd(), b, BSON( "b" << 1 ) ) ); // Single key invalid range means match impossible for single key index. FieldRangeSetPair frsp2( ns(), BSON( "a" << GT << 4 << LT << 1 << "b" << GT << 4 << LT << 1 ) ); ASSERT( frsp2.matchPossibleForIndex( nsd(), a, BSON( "a" << 1 ) ) ); ASSERT( !frsp2.matchPossibleForIndex( nsd(), b, BSON( "b" << 1 ) ) ); } }; /** Check that clearIndexesForPatterns() clears recorded query plans. */ class ClearIndexesForPatterns : public IndexBase { public: void run() { index( BSON( "a" << 1 ) ); BSONObj query = BSON( "a" << GT << 5 << LT << 5 ); BSONObj sort = BSON( "a" << 1 ); Collection* collection = ctx()->db()->getCollection( ns() ); verify( collection ); CollectionInfoCache* cache = collection->infoCache(); // Record the a:1 index for the query's single and multi key query patterns. QueryPattern singleKey = FieldRangeSet( ns(), query, true, true ).pattern( sort ); cache->registerCachedQueryPlanForPattern( singleKey, CachedQueryPlan( BSON( "a" << 1 ), 1, CandidatePlanCharacter( true, true ) ) ); QueryPattern multiKey = FieldRangeSet( ns(), query, false, true ).pattern( sort ); cache->registerCachedQueryPlanForPattern( multiKey, CachedQueryPlan( BSON( "a" << 1 ), 5, CandidatePlanCharacter( true, true ) ) ); // The single and multi key fields for this query must differ for the test to be // valid. ASSERT( singleKey != multiKey ); // Clear the recorded query plans using clearIndexesForPatterns. FieldRangeSetPair frsp( ns(), query ); QueryUtilIndexed::clearIndexesForPatterns( frsp, sort ); // Check that the recorded query plans were cleared. ASSERT_EQUALS( BSONObj(), cache->cachedQueryPlanForPattern( singleKey ).indexKey() ); ASSERT_EQUALS( BSONObj(), cache->cachedQueryPlanForPattern( multiKey ).indexKey() ); } }; /** Check query plan returned by bestIndexForPatterns(). */ class BestIndexForPatterns : public IndexBase { public: void run() { index( BSON( "a" << 1 ) ); index( BSON( "b" << 1 ) ); BSONObj query = BSON( "a" << GT << 5 << LT << 5 ); BSONObj sort = BSON( "a" << 1 ); Collection* collection = ctx()->db()->getCollection( ns() ); verify( collection ); CollectionInfoCache* cache = collection->infoCache(); // No query plan is returned when none has been recorded. FieldRangeSetPair frsp( ns(), query ); ASSERT_EQUALS( BSONObj(), QueryUtilIndexed::bestIndexForPatterns( frsp, sort ).indexKey() ); // A multikey index query plan is returned if recorded. QueryPattern multiKey = FieldRangeSet( ns(), query, false, true ).pattern( sort ); cache->registerCachedQueryPlanForPattern( multiKey, CachedQueryPlan( BSON( "a" << 1 ), 5, CandidatePlanCharacter( true, true ) ) ); ASSERT_EQUALS( BSON( "a" << 1 ), QueryUtilIndexed::bestIndexForPatterns( frsp, sort ).indexKey() ); // A non multikey index query plan is preferentially returned if recorded. QueryPattern singleKey = FieldRangeSet( ns(), query, true, true ).pattern( sort ); cache->registerCachedQueryPlanForPattern( singleKey, CachedQueryPlan( BSON( "b" << 1 ), 5, CandidatePlanCharacter( true, true ) ) ); ASSERT_EQUALS( BSON( "b" << 1 ), QueryUtilIndexed::bestIndexForPatterns( frsp, sort ).indexKey() ); // The single and multi key fields for this query must differ for the test to be // valid. ASSERT( singleKey != multiKey ); } }; } // namespace FieldRangeSetPairTests namespace FieldRangeVectorTests { class ToString { public: void run() { BSONObj obj = BSON( "a" << 1 ); FieldRangeSet fieldRangeSet( "", obj, true, true ); BSONObj indexSpec( BSON( "a" << 1 ) ); FieldRangeVector fieldRangeVector( fieldRangeSet, indexSpec, 1 ); fieldRangeVector.toString(); // Just test that we don't crash. } }; /** * Check FieldRangeVector::hasAllIndexedRanges(), indicating when all indexed field ranges * in a field range set are represented in a field range vector (and none are excluded due * to multikey index field name conflicts). */ class HasAllIndexedRanges { public: void run() { // Single key index. ASSERT( rangesRepresented( BSON( "a" << 1 ), true, BSON( "a" << 1 ) ) ); // Multikey index, but no unrepresented ranges. ASSERT( rangesRepresented( BSON( "a" << 1 ), false, BSON( "a" << 1 ) ) ); // Multikey index, but no unrepresented ranges in the index. ASSERT( rangesRepresented( BSON( "a" << 1 ), false, BSON( "a" << 1 << "b" << 2 ) ) ); // Compound multikey index with no unrepresented ranges. ASSERT( rangesRepresented( BSON( "a" << 1 << "b" << 1 ), false, BSON( "a" << 2 << "b" << 3 ) ) ); // Compound multikey index with range 'a.c' unrepresented because of a conflict // with range 'a.b', hence 'false' expected. ASSERT( !rangesRepresented( BSON( "a.b" << 1 << "a.c" << 1 ), false, BSON( "a.b" << 2 << "a.c" << 3 ) ) ); // Compound multikey index without conflicts due to use of the $elemMatch operator. ASSERT( rangesRepresented( BSON( "a.b" << 1 << "a.c" << 1 ), false, BSON( "a" << BSON( "$elemMatch" << BSON( "b" << 2 << "c" << 3 ) ) ) ) ); // Single key index. ASSERT( rangesRepresented( BSON( "a.b" << 1 << "a.c" << 1 ), true, BSON( "a.b" << 2 << "a.c" << 3 ) ) ); } private: bool rangesRepresented( const BSONObj& index, bool singleKey, const BSONObj& query ) { FieldRangeSet fieldRangeSet( "", query, singleKey, true ); BSONObj indexSpec( index ); FieldRangeVector fieldRangeVector( fieldRangeSet, indexSpec, 1 ); return fieldRangeVector.hasAllIndexedRanges(); } }; /** Detecting cases where a FieldRangeVector describes a single btree interval. */ class SingleInterval { public: void run() { // Equality on a single field is a single interval. FieldRangeVector frv1( FieldRangeSet( "dummy", BSON( "a" << 5 ), true, true ), ( BSON( "a" << 1 ) ), 1 ); ASSERT( frv1.isSingleInterval() ); // Single interval on a single field is a single interval. FieldRangeVector frv2( FieldRangeSet( "dummy", BSON( "a" << GT << 5 ), true, true ), ( BSON( "a" << 1 ) ), 1 ); ASSERT( frv2.isSingleInterval() ); // Multiple intervals on a single field is not a single interval. FieldRangeVector frv3( FieldRangeSet( "dummy", fromjson( "{a:{$in:[4,5]}}" ), true, true ), ( BSON( "a" << 1 ) ), 1 ); ASSERT( !frv3.isSingleInterval() ); // Equality on two fields is a compound single interval. FieldRangeVector frv4( FieldRangeSet( "dummy", BSON( "a" << 5 << "b" << 6 ), true, true ), ( BSON( "a" << 1 << "b" << 1 ) ), 1 ); ASSERT( frv4.isSingleInterval() ); // Equality on first field and single interval on second field is a compound // single interval. FieldRangeVector frv5( FieldRangeSet( "dummy", BSON( "a" << 5 << "b" << GT << 6 ), true, true ), ( BSON( "a" << 1 << "b" << 1 ) ), 1 ); ASSERT( frv5.isSingleInterval() ); // Single interval on first field and single interval on second field is not a // compound single interval. FieldRangeVector frv6( FieldRangeSet( "dummy", BSON( "a" << LT << 5 << "b" << GT << 6 ), true, true ), ( BSON( "a" << 1 << "b" << 1 ) ), 1 ); ASSERT( !frv6.isSingleInterval() ); // Multiple intervals on two fields is not a compound single interval. FieldRangeVector frv7( FieldRangeSet( "dummy", fromjson( "{a:{$in:[4,5]},b:{$in:[7,8]}}" ), true, true ), ( BSON( "a" << 1 << "b" << 1 ) ), 1 ); ASSERT( !frv7.isSingleInterval() ); // With missing second field is still a single compound interval. FieldRangeVector frv8( FieldRangeSet( "dummy", BSON( "a" << 5 ), true, true ), ( BSON( "a" << 1 << "b" << 1 ) ), 1 ); ASSERT( frv8.isSingleInterval() ); // With missing second field is still a single compound interval. FieldRangeVector frv9( FieldRangeSet( "dummy", BSON( "b" << 5 ), true, true ), ( BSON( "a" << 1 << "b" << 1 ) ), 1 ); ASSERT( !frv9.isSingleInterval() ); // Equality on first two fields and single interval on third field is a compound // single interval. FieldRangeVector frv10( FieldRangeSet( "dummy", fromjson( "{a:5,b:6,c:{$gt:7}}" ), true, true ), ( BSON( "a" << 1 << "b" << 1 << "c" << 1 ) ), 1 ); ASSERT( frv10.isSingleInterval() ); // Equality, then single interval, then missing is a compound single interval. FieldRangeVector frv11( FieldRangeSet( "dummy", fromjson( "{a:5,b:{$gt:7}}" ), true, true ), ( BSON( "a" << 1 << "b" << 1 << "c" << 1 ) ), 1 ); ASSERT( frv11.isSingleInterval() ); // Equality, then single interval, then missing, then missing is a compound single // interval. FieldRangeVector frv12( FieldRangeSet( "dummy", fromjson( "{a:5,b:{$gt:7}}" ), true, true ), ( BSON( "a" << 1 << "b" << 1 << "c" << 1 << "d" << 1 ) ), 1 ); ASSERT( frv12.isSingleInterval() ); // Equality, then single interval, then missing, then missing, with mixed order // fields is a compound single interval. FieldRangeVector frv13( FieldRangeSet( "dummy", fromjson( "{a:5,b:{$gt:7}}" ), true, true ), ( BSON( "a" << 1 << "b" << 1 << "c" << 1 << "d" << -1 ) ), 1 ); ASSERT( frv13.isSingleInterval() ); // Equality, then single interval, then missing, then single interval is not a // compound single interval. FieldRangeVector frv14( FieldRangeSet( "dummy", fromjson( "{a:5,b:{$gt:7},d:{$gt:1}}" ), true, true ), ( BSON( "a" << 1 << "b" << 1 << "c" << 1 << "d" << 1 ) ), 1 ); ASSERT( !frv14.isSingleInterval() ); } }; /** Check start and end key values. */ class StartEndKey { public: void run() { // Equality on a single field. FieldRangeVector frv1( FieldRangeSet( "dummy", BSON( "a" << 5 ), true, true ), ( BSON( "a" << 1 ) ), 1 ); ASSERT_EQUALS( BSON( "" << 5 ), frv1.startKey() ); ASSERT( frv1.startKeyInclusive() ); ASSERT_EQUALS( BSON( "" << 5 ), frv1.endKey() ); ASSERT( frv1.endKeyInclusive() ); // Single interval on a single field. FieldRangeVector frv2( FieldRangeSet( "dummy", BSON( "a" << GT << 5 ), true, true ), ( BSON( "a" << 1 ) ), 1 ); ASSERT_EQUALS( BSON( "" << 5 ), frv2.startKey() ); ASSERT( !frv2.startKeyInclusive() ); ASSERT_EQUALS( BSON( "" << numeric_limits::max() ), frv2.endKey() ); ASSERT( frv2.endKeyInclusive() ); // Equality on two fields. FieldRangeVector frv3( FieldRangeSet( "dummy", BSON( "a" << 5 << "b" << 6 ), true, true ), ( BSON( "a" << 1 << "b" << 1 ) ), 1 ); ASSERT_EQUALS( BSON( "" << 5 << "" << 6 ), frv3.startKey() ); ASSERT( frv3.startKeyInclusive() ); ASSERT_EQUALS( BSON( "" << 5 << "" << 6 ), frv3.endKey() ); ASSERT( frv3.endKeyInclusive() ); // Equality on first field and single interval on second field. FieldRangeVector frv4( FieldRangeSet( "dummy", BSON( "a" << 5 << "b" << LT << 6 ), true, true ), ( BSON( "a" << 1 << "b" << 1 ) ), 1 ); ASSERT_EQUALS( BSON( "" << 5 << "" << -numeric_limits::max() ), frv4.startKey() ); ASSERT( frv4.startKeyInclusive() ); ASSERT_EQUALS( BSON( "" << 5 << "" << 6 ), frv4.endKey() ); ASSERT( !frv4.endKeyInclusive() ); // With missing second field. FieldRangeVector frv5( FieldRangeSet( "dummy", BSON( "a" << 5 ), true, true ), ( BSON( "a" << 1 << "b" << 1 ) ), 1 ); ASSERT_EQUALS( BSON( "" << 5 << "" << MINKEY ), frv5.startKey() ); ASSERT( frv5.startKeyInclusive() ); ASSERT_EQUALS( BSON( "" << 5 << "" << MAXKEY ), frv5.endKey() ); ASSERT( frv5.endKeyInclusive() ); // Equality, then single interval, then missing. FieldRangeVector frv6( FieldRangeSet( "dummy", fromjson( "{a:5,b:{$gt:7}}" ), true, true ), ( BSON( "a" << 1 << "b" << 1 << "c" << 1 ) ), 1 ); ASSERT_EQUALS( BSON( "" << 5 << "" << 7 << "" << MAXKEY ), frv6.startKey() ); ASSERT( !frv6.startKeyInclusive() ); ASSERT_EQUALS( BSON( "" << 5 << "" << numeric_limits::max() << "" << MAXKEY ), frv6.endKey() ); ASSERT( frv6.endKeyInclusive() ); // Equality, then single interval, then missing, then missing. FieldRangeVector frv7( FieldRangeSet( "dummy", fromjson( "{a:5,b:{$gt:7}}" ), true, true ), ( BSON( "a" << 1 << "b" << 1 << "c" << 1 << "d" << 1 ) ), 1 ); ASSERT_EQUALS( BSON( "" << 5 << "" << 7 << "" << MAXKEY << "" << MAXKEY ), frv7.startKey() ); ASSERT( !frv7.startKeyInclusive() ); ASSERT_EQUALS( BSON( "" << 5 << "" << numeric_limits::max() << "" << MAXKEY << "" << MAXKEY ), frv7.endKey() ); ASSERT( frv7.endKeyInclusive() ); // Equality, then single exclusive interval on both ends, then missing, then // missing with mixed direction index ordering. FieldRangeVector frv8( FieldRangeSet( "dummy", fromjson( "{a:5,b:{$gt:7,$lt:10}}" ), true, true ), ( BSON( "a" << 1 << "b" << 1 << "c" << 1 << "d" << -1 ) ), 1 ); ASSERT_EQUALS( BSON( "" << 5 << "" << 7 << "" << MAXKEY << "" << MINKEY ), frv8.startKey() ); ASSERT( !frv8.startKeyInclusive() ); ASSERT_EQUALS( BSON( "" << 5 << "" << 10 << "" << MINKEY << "" << MAXKEY ), frv8.endKey() ); ASSERT( !frv8.endKeyInclusive() ); // Equality, then single exclusive interval on both ends, then missing, then // missing with mixed direction index ordering and reverse direction traversal. FieldRangeVector frv9( FieldRangeSet( "dummy", fromjson( "{a:5,b:{$gt:7,$lt:10}}" ), true, true ), ( BSON( "a" << 1 << "b" << 1 << "c" << 1 << "d" << -1 ) ), -1 ); ASSERT_EQUALS( BSON( "" << 5 << "" << 10 << "" << MINKEY << "" << MAXKEY ), frv9.startKey() ); ASSERT( !frv9.startKeyInclusive() ); ASSERT_EQUALS( BSON( "" << 5 << "" << 7 << "" << MAXKEY << "" << MINKEY ), frv9.endKey() ); ASSERT( !frv9.endKeyInclusive() ); } }; } // namespace FieldRangeVectorTests // These are currently descriptive, not normative tests. SERVER-5450 namespace FieldRangeVectorIteratorTests { class Base { public: virtual ~Base() {} void run() { FieldRangeSet fieldRangeSet( "", query(), true, true ); FieldRangeVector fieldRangeVector( fieldRangeSet, index(), 1 ); _iterator.reset( new FieldRangeVectorIterator( fieldRangeVector, singleIntervalLimit() ) ); _iterator->advance( fieldRangeVector.startKey() ); _iterator->prepDive(); check(); } protected: virtual BSONObj query() = 0; virtual BSONObj index() = 0; virtual int singleIntervalLimit() { return 0; } virtual void check() = 0; void assertAdvanceToNext( const BSONObj ¤t ) { ASSERT_EQUALS( -1, advance( current ) ); } void assertAdvanceTo( const BSONObj ¤t, const BSONObj &target, const BSONObj &inclusive = BSONObj() ) { int partition = advance( current ); ASSERT( !iterator().after() ); BSONObjBuilder advanceToBuilder; advanceToBuilder.appendElements( currentPrefix( current, partition ) ); for( int i = partition; i < (int)iterator().cmp().size(); ++i ) { advanceToBuilder << *iterator().cmp()[ i ]; } assertEqualWithoutFieldNames( target, advanceToBuilder.obj() ); if ( !inclusive.isEmpty() ) { BSONObjIterator inc( inclusive ); for( int i = 0; i < partition; ++i ) inc.next(); for( int i = partition; i < (int)iterator().inc().size(); ++i ) { ASSERT_EQUALS( inc.next().Bool(), iterator().inc()[ i ] ); } } } void assertAdvanceToAfter( const BSONObj ¤t, const BSONObj &target ) { int partition = advance( current ); ASSERT( iterator().after() ); assertEqualWithoutFieldNames( target, currentPrefix( current, partition ) ); } void assertDoneAdvancing( const BSONObj ¤t ) { ASSERT_EQUALS( -2, advance( current ) ); } BSONElement minElt() const { return minKey.firstElement(); } BSONElement maxElt() const { return maxKey.firstElement(); } private: static bool equalWithoutFieldNames( const BSONObj &one, const BSONObj &two ) { return one.woCompare( two, BSONObj(), false ) == 0; } static void assertEqualWithoutFieldNames( const BSONObj &one, const BSONObj &two ) { if ( !equalWithoutFieldNames( one, two ) ) { mongo::unittest::log() << one << " != " << two << endl; ASSERT( equalWithoutFieldNames( one, two ) ); } } BSONObj currentPrefix( const BSONObj ¤t, int partition ) { ASSERT( partition >= 0 ); BSONObjIterator currentIter( current ); BSONObjBuilder prefixBuilder; for( int i = 0; i < partition; ++i ) { prefixBuilder << currentIter.next(); } return prefixBuilder.obj(); } FieldRangeVectorIterator &iterator() { return *_iterator; } int advance( const BSONObj ¤t ) { return iterator().advance( current ); } scoped_ptr _iterator; }; class AdvanceToNextIntervalEquality : public Base { BSONObj query() { return fromjson( "{a:{$in:[0,1]}}" ); } BSONObj index() { return BSON( "a" << 1 ); } void check() { assertAdvanceToNext( BSON( "a" << 0 ) ); assertAdvanceTo( BSON( "a" << 0.5 ), BSON( "a" << 1 ) ); } }; class AdvanceToNextIntervalExclusiveInequality : public Base { BSONObj query() { return fromjson( "{a:{$in:['a',/^q/,'z']}}" ); } BSONObj index() { return BSON( "a" << 1 ); } void check() { assertAdvanceToNext( BSON( "a" << "a" ) ); assertAdvanceToNext( BSON( "a" << "q" ) ); assertAdvanceTo( BSON( "a" << "r" ), BSON( "a" << "z" ) ); assertAdvanceToNext( BSON( "a" << "z" ) ); } }; class AdvanceToNextIntervalEqualityReverse : public Base { BSONObj query() { return fromjson( "{a:{$in:[0,1]}}" ); } BSONObj index() { return BSON( "a" << -1 ); } void check() { assertAdvanceToNext( BSON( "a" << 1 ) ); assertAdvanceTo( BSON( "a" << 0.5 ), BSON( "a" << 0 ) ); } }; class AdvanceToNextIntervalEqualityCompound : public Base { BSONObj query() { return fromjson( "{a:{$in:[0,1]},b:{$in:[4,5]}}" ); } BSONObj index() { return BSON( "a" << 1 << "b" << 1 ); } void check() { assertAdvanceToNext( BSON( "a" << 0 << "b" << 5 ) ); assertAdvanceToAfter( BSON( "a" << 0 << "b" << 6 ), BSON( "a" << 0 ) ); assertAdvanceTo( BSON( "a" << 1 << "b" << 2 ), BSON( "a" << 1 << "b" << 4 ) ); assertAdvanceToNext( BSON( "a" << 1 << "b" << 4 ) ); assertDoneAdvancing( BSON( "a" << 1 << "b" << 5.1 ) ); } }; class AdvanceToNextIntervalIntermediateEqualityCompound : public Base { BSONObj query() { return fromjson( "{a:{$in:[0,1]},b:{$in:[4,5]}}" ); } BSONObj index() { return BSON( "a" << 1 << "b" << 1 ); } void check() { assertAdvanceToNext( BSON( "a" << 0 << "b" << 5 ) ); assertAdvanceTo( BSON( "a" << 0.5 << "b" << 6 ), BSON( "a" << 1 << "b" << 4 ) ); assertAdvanceToNext( BSON( "a" << 1 << "b" << 4 ) ); } }; class AdvanceToNextIntervalIntermediateInMixed : public Base { BSONObj query() { return fromjson( "{a:{$in:[0,1]},b:{$in:[/^a/,'c'],$ne:'a'}}" ); } BSONObj index() { return BSON( "a" << 1 << "b" << 1 ); } void check() { assertAdvanceTo( BSON( "a" << 0 << "b" << "bb" ), BSON( "a" << 0 << "b" << "c" ), BSON( "a" << 0 << "b" << true ) ); assertAdvanceTo( BSON( "a" << 0.5 << "b" << "a" ), BSON( "a" << 1 << "b" << "a" ), BSON( "a" << true << "b" << false ) ); } }; class BeforeLowerBound : public Base { BSONObj query() { return fromjson( "{a:{$in:[0,1]},b:{$in:[4,5]}}" ); } BSONObj index() { return BSON( "a" << 1 << "b" << 1 ); } void check() { assertAdvanceTo( BSON( "a" << 0 << "b" << 1 ), BSON( "a" << 0 << "b" << 4 ) ); assertAdvanceToNext( BSON( "a" << 0 << "b" << 4 ) ); } }; class BeforeLowerBoundMixed : public Base { BSONObj query() { return fromjson( "{a:{$in:[0,1]},b:{$in:[/^a/,'c'],$ne:'a'}}" ); } BSONObj index() { return BSON( "a" << 1 << "b" << 1 ); } void check() { assertAdvanceTo( BSON( "a" << 0 << "b" << "" ), BSON( "a" << 0 << "b" << "a" ), BSON( "a" << 0 << "b" << false ) ); assertAdvanceTo( BSON( "a" << 1 << "b" << "bb" ), BSON( "a" << 1 << "b" << "c" ), BSON( "a" << 0 << "b" << true ) ); } }; class AdvanceToNextExclusiveIntervalCompound : public Base { BSONObj query() { return fromjson( "{a:{$in:['x',/^y/],$ne:'y'},b:{$in:[0,1]}}" ); } BSONObj index() { return fromjson( "{a:1,b:1}" ); } void check() { assertAdvanceToNext( BSON( "a" << "x" << "b" << 1 ) ); assertAdvanceToAfter( BSON( "a" << "y" << "b" << 0 ), BSON( "a" << "y" ) ); assertAdvanceToNext( BSON( "a" << "yy" << "b" << 0 ) ); } }; class AdvanceRange : public Base { BSONObj query() { return fromjson( "{a:{$gt:2,$lt:8}}" ); } BSONObj index() { return fromjson( "{a:1}" ); } void check() { assertAdvanceToNext( BSON( "a" << 2 ) ); assertAdvanceToNext( BSON( "a" << 4 ) ); assertAdvanceToNext( BSON( "a" << 5 ) ); assertDoneAdvancing( BSON( "a" << 9 ) ); } }; class AdvanceInRange : public Base { BSONObj query() { return fromjson( "{a:{$in:[0,1]},b:{$gt:2,$lt:8}}" ); } BSONObj index() { return fromjson( "{a:1,b:1}" ); } void check() { assertAdvanceToNext( BSON( "a" << 0 << "b" << 5 ) ); assertAdvanceToNext( BSON( "a" << 0 << "b" << 5 ) ); } }; class AdvanceRangeIn : public Base { BSONObj query() { return fromjson( "{a:{$gt:2,$lt:8},b:{$in:[0,1]}}" ); } BSONObj index() { return fromjson( "{a:1,b:1}" ); } void check() { assertAdvanceToNext( BSON( "a" << 4 << "b" << 1 ) ); assertAdvanceToNext( BSON( "a" << 5 << "b" << 0 ) ); } }; class AdvanceRangeRange : public Base { BSONObj query() { return fromjson( "{a:{$gt:2,$lt:8},b:{$gt:1,$lt:4}}" ); } BSONObj index() { return fromjson( "{a:1,b:1}" ); } void check() { assertAdvanceTo( BSON( "a" << 4 << "b" << 0 ), BSON( "a" << 4 << "b" << 1 ) ); assertAdvanceToAfter( BSON( "a" << 4 << "b" << 6 ), BSON( "a" << 4 ) ); } }; class AdvanceRangeRangeMultiple : public Base { BSONObj query() { return fromjson( "{a:{$gt:2,$lt:8},b:{$gt:1,$lt:4}}" ); } BSONObj index() { return fromjson( "{a:1,b:1}" ); } void check() { assertAdvanceToNext( BSON( "a" << 4 << "b" << 3 ) ); assertAdvanceToNext( BSON( "a" << 4 << "b" << 3 ) ); } }; class AdvanceRangeRangeIn : public Base { BSONObj query() { return fromjson( "{a:{$gt:2,$lt:8},b:{$gt:1,$lt:4},c:{$in:[6,7]}}" ); } BSONObj index() { return fromjson( "{a:1,b:1,c:1}" ); } void check() { assertAdvanceToNext( BSON( "a" << 4 << "b" << 3 << "c" << 7 ) ); assertAdvanceToAfter( BSON( "a" << 4 << "b" << 6 << "c" << 7 ), BSON( "a" << 4 ) ); assertAdvanceToNext( BSON( "a" << 5 << "b" << 3 << "c" << 6 ) ); } }; class AdvanceRangeMixed : public Base { BSONObj query() { return fromjson( "{a:{$gt:2,$lt:8},b:{$in:['a',/^b/],$ne:'b'}}" ); } BSONObj index() { return fromjson( "{a:1,b:1}" ); } void check() { assertAdvanceToNext( BSON( "a" << 4 << "b" << "a" ) ); assertAdvanceToAfter( fromjson( "{a:4,b:'b'}" ), BSON( "a" << 4 << "b" << "b" ) ); } }; class AdvanceRangeMixed2 : public Base { BSONObj query() { return fromjson( "{a:{$gt:2,$lt:8},b:{$in:['a',/^b/],$ne:'b'}}" ); } BSONObj index() { return fromjson( "{a:1,b:1}" ); } void check() { assertAdvanceToNext( BSON( "a" << 4 << "b" << "a" ) ); assertAdvanceTo( BSON( "a" << 4 << "b" << "aa" ), BSON( "a" << 4 << "b" << "b" ), BSON( "a" << 0 << "b" << false ) ); } }; class AdvanceRangeMixedIn : public Base { BSONObj query() { return fromjson( "{a:{$gt:2,$lt:8},b:{$in:[/^a/,'c']},c:{$in:[6,7]}}" ); } BSONObj index() { return fromjson( "{a:1,b:1,c:1}" ); } void check() { assertAdvanceToNext( BSON( "a" << 4 << "b" << "aa" << "c" << 7 ) ); assertAdvanceToAfter( fromjson( "{a:4,b:/^q/,c:7}" ), BSON( "a" << 4 ) ); assertAdvanceToNext( BSON( "a" << 5 << "b" << "c" << "c" << 6 ) ); } }; class AdvanceRangeMixedMixed : public Base { BSONObj query() { return fromjson( "{a:{$gt:2,$lt:8},b:{$in:['a',/^b/],$ne:'b'}," "c:{$in:[/^a/,'c'],$ne:'a'}}" ); } BSONObj index() { return fromjson( "{a:1,b:1,c:1}" ); } void check() { assertAdvanceTo( BSON( "a" << 4 << "b" << "a" << "c" << "bb" ), BSON( "a" << 4 << "b" << "a" << "c" << "c" ) ); assertAdvanceTo( BSON( "a" << 4 << "b" << "aa" << "c" << "b" ), BSON( "a" << 4 << "b" << "b" << "c" << "a" ), BSON( "a" << 0 << "b" << false << "c" << false ) ); } }; class AdvanceMixedMixedIn : public Base { BSONObj query() { return fromjson( "{a:{$in:[/^a/,'c']},b:{$in:[/^a/,'c']},c:{$in:[6,7]}}" ); } BSONObj index() { return fromjson( "{a:1,b:1,c:1}" ); } void check() { assertAdvanceToNext( BSON( "a" << "a" << "b" << "aa" << "c" << 7 ) ); assertAdvanceToAfter( fromjson( "{a:'a',b:/^q/,c:7}" ), BSON( "a" << "a" ) ); assertAdvanceToNext( BSON( "a" << "c" << "b" << "c" << "c" << 6 ) ); } }; namespace CompoundRangeCounter { class RangeTracking { public: RangeTracking() : _counter( 2, 0 ) {} void run() { ASSERT_EQUALS( 2, _counter.size() ); checkValues( -1, -1 ); _counter.setZeroes( 0 ); checkValues( 0, 0 ); _counter.setUnknowns( 1 ); checkValues( 0, -1 ); _counter.setZeroes( 1 ); checkValues( 0, 0 ); _counter.setUnknowns( 0 ); checkValues( -1, -1 ); _counter.set( 0, 3 ); checkValues( 3, -1 ); _counter.inc( 0 ); checkValues( 4, -1 ); _counter.set( 1, 5 ); checkValues( 4, 5 ); _counter.inc( 1 ); checkValues( 4, 6 ); } private: void checkValues( int first, int second ) { ASSERT_EQUALS( first, _counter.get( 0 ) ); ASSERT_EQUALS( second, _counter.get( 1 ) ); } FieldRangeVectorIterator::CompoundRangeCounter _counter; }; class SingleIntervalCount { public: SingleIntervalCount() : _counter( 2, 2 ) {} void run() { assertLimitNotReached(); _counter.incSingleIntervalCount(); assertLimitNotReached(); _counter.incSingleIntervalCount(); assertLimitReached(); _counter.set( 1, 1 ); assertLimitNotReached(); } private: void assertLimitReached() const { ASSERT( _counter.hasSingleIntervalCountReachedLimit() ); } void assertLimitNotReached() const { ASSERT( !_counter.hasSingleIntervalCountReachedLimit() ); } FieldRangeVectorIterator::CompoundRangeCounter _counter; }; class SingleIntervalCountUpdateBase { public: SingleIntervalCountUpdateBase() : _counter( 2, 1 ) {} virtual ~SingleIntervalCountUpdateBase() {} void run() { _counter.incSingleIntervalCount(); ASSERT( _counter.hasSingleIntervalCountReachedLimit() ); applyUpdate(); ASSERT( !_counter.hasSingleIntervalCountReachedLimit() ); } protected: virtual void applyUpdate() = 0; FieldRangeVectorIterator::CompoundRangeCounter &counter() { return _counter; } private: FieldRangeVectorIterator::CompoundRangeCounter _counter; }; class Set : public SingleIntervalCountUpdateBase { void applyUpdate() { counter().set( 0, 1 ); } }; class Inc : public SingleIntervalCountUpdateBase { void applyUpdate() { counter().inc( 1 ); } }; class SetZeroes : public SingleIntervalCountUpdateBase { void applyUpdate() { counter().setZeroes( 0 ); } }; class SetUnknowns : public SingleIntervalCountUpdateBase { void applyUpdate() { counter().setUnknowns( 1 ); } }; } // namespace CompoundRangeCounter namespace FieldIntervalMatcher { class IsEqInclusiveUpperBound { public: void run() { BSONObj exclusiveInterval = BSON( "$lt" << 5 ); for ( int i = 4; i <= 6; ++i ) { for ( int reverse = 0; reverse < 2; ++reverse ) { ASSERT( !isEqInclusiveUpperBound( exclusiveInterval, BSON( "" << i ), reverse ) ); } } BSONObj inclusiveInterval = BSON( "$lte" << 4 ); for ( int i = 3; i <= 5; i += 2 ) { for ( int reverse = 0; reverse < 2; ++reverse ) { ASSERT( !isEqInclusiveUpperBound( inclusiveInterval, BSON( "" << i ), reverse ) ); } } ASSERT( isEqInclusiveUpperBound( inclusiveInterval, BSON( "" << 4 ), true ) ); ASSERT( isEqInclusiveUpperBound( inclusiveInterval, BSON( "" << 4 ), false ) ); } private: bool isEqInclusiveUpperBound( const BSONObj &intervalSpec, const BSONObj &elementSpec, bool reverse ) { FieldRange range( intervalSpec.firstElement(), false, true ); BSONElement element = elementSpec.firstElement(); FieldRangeVectorIterator::FieldIntervalMatcher matcher ( range.intervals()[ 0 ], element, reverse ); return matcher.isEqInclusiveUpperBound(); } }; class IsGteUpperBound { public: void run() { BSONObj exclusiveInterval = BSON( "$lt" << 5 ); ASSERT( !isGteUpperBound( exclusiveInterval, BSON( "" << 4 ), false ) ); ASSERT( isGteUpperBound( exclusiveInterval, BSON( "" << 5 ), false ) ); ASSERT( isGteUpperBound( exclusiveInterval, BSON( "" << 6 ), false ) ); ASSERT( isGteUpperBound( exclusiveInterval, BSON( "" << 4 ), true ) ); ASSERT( isGteUpperBound( exclusiveInterval, BSON( "" << 5 ), true ) ); ASSERT( !isGteUpperBound( exclusiveInterval, BSON( "" << 6 ), true ) ); BSONObj inclusiveInterval = BSON( "$lte" << 4 ); ASSERT( !isGteUpperBound( inclusiveInterval, BSON( "" << 3 ), false ) ); ASSERT( isGteUpperBound( inclusiveInterval, BSON( "" << 4 ), false ) ); ASSERT( isGteUpperBound( inclusiveInterval, BSON( "" << 5 ), false ) ); ASSERT( isGteUpperBound( inclusiveInterval, BSON( "" << 3 ), true ) ); ASSERT( isGteUpperBound( inclusiveInterval, BSON( "" << 4 ), true ) ); ASSERT( !isGteUpperBound( inclusiveInterval, BSON( "" << 5 ), true ) ); } private: bool isGteUpperBound( const BSONObj &intervalSpec, const BSONObj &elementSpec, bool reverse ) { FieldRange range( intervalSpec.firstElement(), false, true ); BSONElement element = elementSpec.firstElement(); FieldRangeVectorIterator::FieldIntervalMatcher matcher ( range.intervals()[ 0 ], element, reverse ); return matcher.isGteUpperBound(); } }; class IsEqExclusiveLowerBound { public: void run() { BSONObj exclusiveInterval = BSON( "$gt" << 5 ); for ( int i = 4; i <= 6; i += 2 ) { for ( int reverse = 0; reverse < 2; ++reverse ) { ASSERT( !isEqExclusiveLowerBound( exclusiveInterval, BSON( "" << i ), reverse ) ); } } ASSERT( isEqExclusiveLowerBound( exclusiveInterval, BSON( "" << 5 ), true ) ); ASSERT( isEqExclusiveLowerBound( exclusiveInterval, BSON( "" << 5 ), false ) ); BSONObj inclusiveInterval = BSON( "$gte" << 4 ); for ( int i = 3; i <= 5; ++i ) { for ( int reverse = 0; reverse < 2; ++reverse ) { ASSERT( !isEqExclusiveLowerBound( inclusiveInterval, BSON( "" << i ), reverse ) ); } } } private: bool isEqExclusiveLowerBound( const BSONObj &intervalSpec, const BSONObj &elementSpec, bool reverse ) { FieldRange range( intervalSpec.firstElement(), false, true ); BSONElement element = elementSpec.firstElement(); FieldRangeVectorIterator::FieldIntervalMatcher matcher ( range.intervals()[ 0 ], element, reverse ); return matcher.isEqExclusiveLowerBound(); } }; class IsLtLowerBound { public: void run() { BSONObj exclusiveInterval = BSON( "$gt" << 5 ); ASSERT( isLtLowerBound( exclusiveInterval, BSON( "" << 4 ), false ) ); ASSERT( !isLtLowerBound( exclusiveInterval, BSON( "" << 5 ), false ) ); ASSERT( !isLtLowerBound( exclusiveInterval, BSON( "" << 6 ), false ) ); ASSERT( !isLtLowerBound( exclusiveInterval, BSON( "" << 4 ), true ) ); ASSERT( !isLtLowerBound( exclusiveInterval, BSON( "" << 5 ), true ) ); ASSERT( isLtLowerBound( exclusiveInterval, BSON( "" << 6 ), true ) ); BSONObj inclusiveInterval = BSON( "$gte" << 4 ); ASSERT( isLtLowerBound( inclusiveInterval, BSON( "" << 3 ), false ) ); ASSERT( !isLtLowerBound( inclusiveInterval, BSON( "" << 4 ), false ) ); ASSERT( !isLtLowerBound( inclusiveInterval, BSON( "" << 5 ), false ) ); ASSERT( !isLtLowerBound( inclusiveInterval, BSON( "" << 3 ), true ) ); ASSERT( !isLtLowerBound( inclusiveInterval, BSON( "" << 4 ), true ) ); ASSERT( isLtLowerBound( inclusiveInterval, BSON( "" << 5 ), true ) ); } private: bool isLtLowerBound( const BSONObj &intervalSpec, const BSONObj &elementSpec, bool reverse ) { FieldRange range( intervalSpec.firstElement(), false, true ); BSONElement element = elementSpec.firstElement(); FieldRangeVectorIterator::FieldIntervalMatcher matcher ( range.intervals()[ 0 ], element, reverse ); return matcher.isLtLowerBound(); } }; class CheckLowerAfterUpper { public: void run() { BSONObj intervalSpec = BSON( "$in" << BSON_ARRAY( 1 << 2 ) ); BSONObj elementSpec = BSON( "" << 1 ); FieldRange range( intervalSpec.firstElement(), false, true ); BSONElement element = elementSpec.firstElement(); FieldRangeVectorIterator::FieldIntervalMatcher matcher ( range.intervals()[ 0 ], element, false ); ASSERT( matcher.isEqInclusiveUpperBound() ); ASSERT( matcher.isGteUpperBound() ); ASSERT( !matcher.isEqExclusiveLowerBound() ); ASSERT( !matcher.isLtLowerBound() ); } }; } // namespace FieldIntervalMatcher namespace SingleIntervalLimit { class NoLimit : public Base { BSONObj query() { return BSON( "a" << 1 ); } BSONObj index() { return BSON( "a" << 1 ); } int singleIntervalLimit() { return 0; } void check() { for( int i = 0; i < 100; ++i ) { assertAdvanceToNext( BSON( "a" << 1 ) ); } } }; class OneIntervalLimit : public Base { BSONObj query() { return BSON( "a" << 1 ); } BSONObj index() { return BSON( "a" << 1 ); } int singleIntervalLimit() { return 3; } void check() { for( int i = 0; i < 3; ++i ) { assertAdvanceToNext( BSON( "a" << 1 ) ); } assertDoneAdvancing( BSON( "a" << 1 ) ); } }; class TwoIntervalLimit : public Base { BSONObj query() { return fromjson( "{a:{$in:[0,1]}}" ); } BSONObj index() { return BSON( "a" << 1 ); } int singleIntervalLimit() { return 2; } void check() { for( int i = 0; i < 2; ++i ) { assertAdvanceToNext( BSON( "a" << 0 ) ); } assertAdvanceTo( BSON( "a" << 0 ), BSON( "a" << 1 ) ); for( int i = 0; i < 2; ++i ) { assertAdvanceToNext( BSON( "a" << 1 ) ); } assertDoneAdvancing( BSON( "a" << 1 ) ); } }; class ThreeIntervalLimitUnreached : public Base { BSONObj query() { return fromjson( "{a:{$in:[0,1,2]}}" ); } BSONObj index() { return BSON( "a" << 1 ); } int singleIntervalLimit() { return 2; } void check() { for( int i = 0; i < 2; ++i ) { assertAdvanceToNext( BSON( "a" << 0 ) ); } assertAdvanceTo( BSON( "a" << 1.5 ), BSON( "a" << 2 ) ); } }; class FirstIntervalExhaustedBeforeLimit : public Base { BSONObj query() { return fromjson( "{a:{$in:[0,1]}}" ); } BSONObj index() { return BSON( "a" << 1 ); } int singleIntervalLimit() { return 3; } void check() { assertAdvanceToNext( BSON( "a" << 0 ) ); for( int i = 0; i < 3; ++i ) { assertAdvanceToNext( BSON( "a" << 1 ) ); } assertDoneAdvancing( BSON( "a" << 1 ) ); } }; class FirstIntervalNotExhaustedAtLimit : public Base { BSONObj query() { return fromjson( "{a:{$in:[0,1]}}" ); } BSONObj index() { return BSON( "a" << 1 ); } int singleIntervalLimit() { return 3; } void check() { for( int i = 0; i < 3; ++i ) { assertAdvanceToNext( BSON( "a" << 0 ) ); } for( int i = 0; i < 3; ++i ) { assertAdvanceToNext( BSON( "a" << 1 ) ); } assertDoneAdvancing( BSON( "a" << 1 ) ); } }; class EqualIn : public Base { BSONObj query() { return fromjson( "{a:1,b:{$in:[0,1]}}" ); } BSONObj index() { return BSON( "a" << 1 << "b" << 1 ); } int singleIntervalLimit() { return 3; } void check() { for( int i = 0; i < 3; ++i ) { assertAdvanceToNext( BSON( "a" << 1 << "b" << 0 ) ); } assertAdvanceTo( BSON( "a" << 1 << "b" << 0 ), BSON( "a" << 1 << "b" << 1 ) ); for( int i = 0; i < 3; ++i ) { assertAdvanceToNext( BSON( "a" << 1 << "b" << 1 ) ); } assertDoneAdvancing( BSON( "a" << 1 << "b" << 1 ) ); } }; class TwoIntervalIntermediateValue : public Base { BSONObj query() { return fromjson( "{a:{$in:[0,1]}}" ); } BSONObj index() { return BSON( "a" << 1 ); } int singleIntervalLimit() { return 3; } void check() { for( int i = 0; i < 2; ++i ) { assertAdvanceToNext( BSON( "a" << 0 ) ); } assertAdvanceTo( BSON( "a" << 0.5 ), BSON( "a" << 1 ) ); for( int i = 0; i < 3; ++i ) { assertAdvanceToNext( BSON( "a" << 1 ) ); } assertDoneAdvancing( BSON( "a" << 1 ) ); } }; class InGte : public Base { BSONObj query() { return fromjson( "{a:{$in:[0,1]},b:{$gte:5}}" ); } BSONObj index() { return BSON( "a" << 1 << "b" << 1 ); } int singleIntervalLimit() { return 1; } void check() { assertAdvanceToNext( BSON( "a" << 0 << "b" << 5 ) ); assertAdvanceToAfter( BSON( "a" << 0 << "b" << 5 ), BSON( "a" << 0 ) ); assertAdvanceTo( BSON( "a" << 0.5 << "b" << 6 ), BSON( "a" << 1 << "b" << 5 ) ); assertAdvanceToNext( BSON( "a" << 1 << "b" << 7 ) ); assertDoneAdvancing( BSON( "a" << 1 << "b" << 7 ) ); } }; class InEmptyGte : public Base { BSONObj query() { return fromjson( "{a:{$in:[0,1,2,3,4]},b:{$gte:5}}" ); } BSONObj index() { return BSON( "a" << 1 << "b" << 1 ); } int singleIntervalLimit() { return 1; } void check() { assertAdvanceToNext( BSON( "a" << 1 << "b" << 5 ) ); assertAdvanceToAfter( BSON( "a" << 1 << "b" << 5 ), BSON( "a" << 1 ) ); assertAdvanceTo( BSON( "a" << 2 << "b" << 2 ), BSON( "a" << 2 << "b" << 5 ) ); assertAdvanceToNext( BSON( "a" << 3 << "b" << 5 ) ); assertDoneAdvancing( BSON( "a" << 4.1 << "b" << 2 ) ); } }; class InGt : public Base { BSONObj query() { return fromjson( "{a:{$in:[0,1]},b:{$gt:5}}" ); } BSONObj index() { return BSON( "a" << 1 << "b" << 1 ); } int singleIntervalLimit() { return 1; } void check() { assertAdvanceToNext( BSON( "a" << 0 << "b" << 5.1 ) ); assertAdvanceToAfter( BSON( "a" << 0 << "b" << 5.1 ), BSON( "a" << 0 ) ); assertAdvanceToAfter( BSON( "a" << 1 << "b" << 5 ), BSON( "a" << 1 << "b" << 5 ) ); assertAdvanceToNext( BSON( "a" << 1 << "b" << 5.01 ) ); assertDoneAdvancing( BSON( "a" << 1 << "b" << 5.01 ) ); } }; class InEmptyGt : public Base { BSONObj query() { return fromjson( "{a:{$in:[0,1,2,3,4]},b:{$gt:5}}" ); } BSONObj index() { return BSON( "a" << 1 << "b" << 1 ); } int singleIntervalLimit() { return 1; } void check() { assertAdvanceToNext( BSON( "a" << 1 << "b" << 5.1 ) ); assertAdvanceToAfter( BSON( "a" << 1 << "b" << 5.1 ), BSON( "a" << 1 ) ); assertAdvanceTo( BSON( "a" << 2 << "b" << 2 ), BSON( "a" << 2 << "b" << 5 ) ); assertAdvanceToNext( BSON( "a" << 3 << "b" << 5.01 ) ); assertDoneAdvancing( BSON( "a" << 4.1 << "b" << 2 ) ); } }; class InLt : public Base { BSONObj query() { return fromjson( "{a:{$in:[0,1]},b:{$lt:5}}" ); } BSONObj index() { return BSON( "a" << 1 << "b" << 1 ); } int singleIntervalLimit() { return 1; } void check() { assertAdvanceToNext( BSON( "a" << 0 << "b" << 2 ) ); assertAdvanceToAfter( BSON( "a" << 0 << "b" << 2 ), BSON( "a" << 0 ) ); assertDoneAdvancing( BSON( "a" << 1 << "b" << 5 ) ); } }; class InGteLt : public Base { BSONObj query() { return fromjson( "{a:{$in:[0,1,2]},b:{$gte:2,$lt:5}}" ); } BSONObj index() { return BSON( "a" << 1 << "b" << 1 ); } int singleIntervalLimit() { return 1; } void check() { assertAdvanceToNext( BSON( "a" << 0 << "b" << 3 ) ); assertAdvanceToAfter( BSON( "a" << 0 << "b" << 4 ), BSON( "a" << 0 ) ); assertAdvanceToAfter( BSON( "a" << 1 << "b" << 5 ), BSON( "a" << 1 ) ); assertAdvanceToNext( BSON( "a" << 2 << "b" << 4 ) ); assertDoneAdvancing( BSON( "a" << 2 << "b" << 4 ) ); } }; class InGtHigherLimit : public Base { BSONObj query() { return fromjson( "{a:{$in:[0,1,2]},b:{$gt:5}}" ); } BSONObj index() { return BSON( "a" << 1 << "b" << 1 ); } int singleIntervalLimit() { return 2; } void check() { assertAdvanceToNext( BSON( "a" << 0 << "b" << 5.1 ) ); assertAdvanceToNext( BSON( "a" << 0 << "b" << 6 ) ); assertAdvanceToAfter( BSON( "a" << 0 << "b" << 7 ), BSON( "a" << 0 ) ); assertAdvanceToAfter( BSON( "a" << 1 << "b" << 5 ), BSON( "a" << 1 << "b" << 5 ) ); assertAdvanceToAfter( BSON( "a" << 2 << "b" << 5 ), BSON( "a" << 2 << "b" << 5 ) ); assertAdvanceToNext( BSON( "a" << 2 << "b" << 5.01 ) ); assertAdvanceToNext( BSON( "a" << 2 << "b" << 5.02 ) ); assertDoneAdvancing( BSON( "a" << 2 << "b" << 5.02 ) ); } }; /** * The singleIntervalLimit feature should not be used with multiple range bounds, in * spite of the corner case checked in this test. */ class TwoRange : public Base { BSONObj query() { return fromjson( "{a:{$in:[/^a/,/^c/]}}" ); } BSONObj index() { return BSON( "a" << 1 ); } int singleIntervalLimit() { return 2; } void check() { for( int i = 0; i < 2; ++i ) { assertAdvanceToNext( BSON( "a" << "a" ) ); } assertAdvanceTo( BSON( "a" << "b" ), BSON( "a" << "c" ) ); } }; class IndexPrefix : public Base { BSONObj query() { return fromjson( "{a:{$in:[1,2]}}" ); } BSONObj index() { return BSON( "a" << 1 << "b" << 1 ); } int singleIntervalLimit() { return 1; } void check() { assertAdvanceToNext( BSON( "a" << 1 << "b" << 1 ) ); assertAdvanceTo( BSON( "a" << 1 << "b" << 3 ), BSON( "a" << 2 << "b" << minElt() ) ); assertAdvanceToNext( BSON( "a" << 2 << "b" << 0 ) ); assertDoneAdvancing( BSON( "a" << 2 << "b" << 1 ) ); } }; class IndexPrefixReverseOrder : public Base { BSONObj query() { return fromjson( "{a:{$in:[1,2]}}" ); } BSONObj index() { return BSON( "a" << 1 << "b" << -1 ); } int singleIntervalLimit() { return 1; } void check() { assertAdvanceToNext( BSON( "a" << 1 << "b" << 1 ) ); assertAdvanceTo( BSON( "a" << 1 << "b" << 0 ), BSON( "a" << 2 << "b" << maxElt() ) ); assertAdvanceToNext( BSON( "a" << 2 << "b" << 1 ) ); assertDoneAdvancing( BSON( "a" << 2 << "b" << 0 ) ); } }; class LongerIndexPrefix : public Base { BSONObj query() { return fromjson( "{a:{$in:[1,2,3]}}" ); } BSONObj index() { return BSON( "a" << 1 << "b" << 1 << "c" << 1 ); } int singleIntervalLimit() { return 1; } void check() { assertAdvanceToNext( BSON( "a" << 1 << "b" << 1 << "c" << 1 ) ); assertAdvanceTo( BSON( "a" << 1 << "b" << 1 << "c" << 1 ), BSON( "a" << 2 << "b" << minElt() << "c" << minElt() ) ); assertAdvanceTo( BSON( "a" << 2.5 << "b" << 1 << "c" << 2 ), BSON( "a" << 3 << "b" << minElt() << "c" << minElt() ) ); assertDoneAdvancing( BSON( "a" << 4 << "b" << 1 << "c" << 1 ) ); } }; } // namespace SameRangeLimit } // namespace FieldRangeVectorIteratorTests class All : public Suite { public: All() : Suite( "queryutil" ) {} void setupTests() { add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); } } myall; } // namespace QueryUtilTests