diff --git a/db/matcher.cpp b/db/matcher.cpp index b46e9dcdf9f..681a6dc8e45 100644 --- a/db/matcher.cpp +++ b/db/matcher.cpp @@ -799,6 +799,13 @@ namespace mongo { } } } + + for( vector< shared_ptr< FieldRangeVector > >::const_iterator i = _orConstraints.begin(); + i != _orConstraints.end(); ++i ) { + if ( (*i)->matches( jsobj ) ) { + return false; + } + } if ( where ) { if ( where->func == 0 ) { @@ -849,6 +856,9 @@ namespace mongo { if ( _orMatchers.size() != other._orMatchers.size() ) { return false; } + if ( _orConstraints.size() != other._orConstraints.size() ) { + return false; + } { list< shared_ptr< Matcher > >::const_iterator i = _norMatchers.begin(); list< shared_ptr< Matcher > >::const_iterator j = other._norMatchers.begin(); diff --git a/db/matcher.h b/db/matcher.h index 362376b8132..f8e8ae9843d 100644 --- a/db/matcher.h +++ b/db/matcher.h @@ -28,6 +28,7 @@ namespace mongo { class Cursor; class CoveredIndexMatcher; class Matcher; + class FieldRangeVector; class RegexMatcher { public: @@ -153,8 +154,8 @@ namespace mongo { return jsobj.toString(); } - void addOrConstraint( const BSONObj &o ) { - _norMatchers.push_back( shared_ptr< Matcher >( new Matcher( o ) ) ); + void addOrConstraint( const shared_ptr< FieldRangeVector > &frv ) { + _orConstraints.push_back( frv ); } bool sameCriteriaCount( const Matcher &other ) const; @@ -202,6 +203,7 @@ namespace mongo { vector< shared_ptr< BSONObjBuilder > > _builders; list< shared_ptr< Matcher > > _orMatchers; list< shared_ptr< Matcher > > _norMatchers; + vector< shared_ptr< FieldRangeVector > > _orConstraints; friend class CoveredIndexMatcher; }; @@ -218,8 +220,8 @@ namespace mongo { Matcher& docMatcher() { return *_docMatcher; } // once this is called, shouldn't use this matcher for matching any more - void addOrConstraint( const BSONObj &o ) { - _docMatcher->addOrConstraint( o ); + void addOrConstraint( const shared_ptr< FieldRangeVector > &frv ) { + _docMatcher->addOrConstraint( frv ); } CoveredIndexMatcher *nextClauseMatcher( const BSONObj &indexKeyPattern, bool alwaysUseRecord=false ) { diff --git a/db/queryoptimizer.h b/db/queryoptimizer.h index 45cfbf5ea71..55a30b2f98a 100644 --- a/db/queryoptimizer.h +++ b/db/queryoptimizer.h @@ -58,11 +58,10 @@ namespace mongo { const char *ns() const { return fbs_.ns(); } NamespaceDetails *nsd() const { return d; } BSONObj originalQuery() const { return _originalQuery; } - BSONObj simplifiedQuery( const BSONObj& fields = BSONObj(), bool expandIn = false ) const { return fbs_.simplifiedQuery( fields, expandIn ); } + BSONObj simplifiedQuery( const BSONObj& fields = BSONObj() ) const { return fbs_.simplifiedQuery( fields ); } const FieldRange &range( const char *fieldName ) const { return fbs_.range( fieldName ); } void registerSelf( long long nScanned ) const; - // just here for testing - const FieldRangeVector *frv() const { return _frv.get(); } + shared_ptr< FieldRangeVector > frv() const { return _frv; } private: NamespaceDetails *d; int idxNo; @@ -89,12 +88,12 @@ namespace mongo { // each clone its own query plan. class QueryOp { public: - QueryOp() : _complete(), _stopRequested(), _qp(), _error(), _haveOrConstraint() {} + QueryOp() : _complete(), _stopRequested(), _qp(), _error() {} // Used when handing off from one QueryOp type to another QueryOp( const QueryOp &other ) : _complete(), _stopRequested(), _qp(), _error(), _matcher( other._matcher ), - _haveOrConstraint( other._haveOrConstraint ), _orConstraint( other._orConstraint ) {} + _orConstraint( other._orConstraint ) {} virtual ~QueryOp() {} @@ -121,9 +120,9 @@ namespace mongo { should only be called after the query op has completed executing. */ QueryOp *createChild() { - if( _haveOrConstraint ) { + if( _orConstraint.get() ) { _matcher->addOrConstraint( _orConstraint ); - _haveOrConstraint = false; + _orConstraint.reset(); } QueryOp *ret = _createChild(); ret->_oldMatcher = _matcher; @@ -143,8 +142,7 @@ namespace mongo { shared_ptr< CoveredIndexMatcher > matcher() const { return _matcher; } protected: void setComplete() { - _haveOrConstraint = true; - _orConstraint = qp().simplifiedQuery( qp().indexKey(), true ); + _orConstraint = qp().frv(); _complete = true; } void setStop() { setComplete(); _stopRequested = true; } @@ -163,8 +161,7 @@ namespace mongo { bool _error; shared_ptr< CoveredIndexMatcher > _matcher; shared_ptr< CoveredIndexMatcher > _oldMatcher; - bool _haveOrConstraint; - BSONObj _orConstraint; + shared_ptr< FieldRangeVector > _orConstraint; }; // Set of candidate query plans for a particular query. Used for running diff --git a/db/queryutil.cpp b/db/queryutil.cpp index 15b8241d873..b1ed64af961 100644 --- a/db/queryutil.cpp +++ b/db/queryutil.cpp @@ -24,6 +24,8 @@ #include "../util/unittest.h" namespace mongo { + extern BSONObj staticNull; + /** returns a string that when used as a matcher, would match a super set of regex() returns "" for complex regular expressions used to optimize queries in some simple regex cases that start with '^' @@ -494,60 +496,7 @@ namespace mongo { _objData.push_back( o ); return o; } - - BSONObj FieldRange::simplifiedComplex() const { - BSONObjBuilder bb; - BSONArrayBuilder a; - set< string > regexLow; - set< string > regexHigh; - for( vector< FieldInterval >::const_iterator i = _intervals.begin(); i != _intervals.end(); ++i ) { - // this recovers exact $in fields and regexes - should be everything for equality - if ( i->equality() ) { - a << i->_upper._bound; - // right now btree cursor doesn't do exclusive bounds so we need to match end of the regex range - if ( i->_upper._bound.type() == RegEx ) { - string r = simpleRegex( i->_upper._bound ); - if ( !r.empty() ) { - regexLow.insert( r ); - string re = simpleRegexEnd( r ); - regexHigh.insert( re ); - a << re; - } - } - } - } - BSONArray in = a.arr(); - if ( !in.isEmpty() ) { - bb << "$in" << in; - } - BSONObj low; - BSONObj high; - // should only be one non regex ineq range - for( vector< FieldInterval >::const_iterator i = _intervals.begin(); i != _intervals.end(); ++i ) { - if ( !i->equality() ) { - if ( !i->_lower._inclusive || i->_lower._bound.type() != String || !regexLow.count( i->_lower._bound.valuestr() ) ) { - BSONObjBuilder b; - // in btree impl lower bound always inclusive - b.appendAs( i->_lower._bound, "$gte" ); - low = b.obj(); - } - if ( i->_upper._inclusive || i->_upper._bound.type() != String || !regexHigh.count( i->_upper._bound.valuestr() ) ) { - BSONObjBuilder b; - // in btree impl upper bound always - b.appendAs( i->_upper._bound, "$lte" ); - high = b.obj(); - } - } - } - if ( !low.isEmpty() ) { - bb.appendElements( low ); - } - if ( !high.isEmpty() ) { - bb.appendElements( high ); - } - return bb.obj(); - } - + string FieldRangeSet::getSpecial() const { string s = ""; for ( map::iterator i=_ranges.begin(); i!=_ranges.end(); i++ ){ @@ -688,7 +637,7 @@ namespace mongo { return *trivialRange_; } - BSONObj FieldRangeSet::simplifiedQuery( const BSONObj &_fields, bool expandIn ) const { + BSONObj FieldRangeSet::simplifiedQuery( const BSONObj &_fields ) const { BSONObj fields = _fields; if ( fields.isEmpty() ) { BSONObjBuilder b; @@ -708,16 +657,12 @@ namespace mongo { b.appendAs( range.min(), name ); else if ( range.nontrivial() ) { BSONObj o; - if ( expandIn ) { - o = range.simplifiedComplex(); - } else { - BSONObjBuilder c; - if ( range.min().type() != MinKey ) - c.appendAs( range.min(), range.minInclusive() ? "$gte" : "$gt" ); - if ( range.max().type() != MaxKey ) - c.appendAs( range.max(), range.maxInclusive() ? "$lte" : "$lt" ); - o = c.obj(); - } + BSONObjBuilder c; + if ( range.min().type() != MinKey ) + c.appendAs( range.min(), range.minInclusive() ? "$gte" : "$gt" ); + if ( range.max().type() != MaxKey ) + c.appendAs( range.max(), range.maxInclusive() ? "$lte" : "$lt" ); + o = c.obj(); b.append( name, o ); } } @@ -986,6 +931,65 @@ namespace mongo { } } + bool FieldRangeVector::matchesElement( const BSONElement &e, int i, bool forward ) const { + int l = -1; + int h = _ranges[ i ].intervals().size() * 2; + while( l + 1 < h ) { + int m = ( l + h ) / 2; + BSONElement toCmp; + if ( m % 2 == 0 ) { + toCmp = _ranges[ i ].intervals()[ m / 2 ]._lower._bound; + } else { + toCmp = _ranges[ i ].intervals()[ m / 2 ]._upper._bound; + } + int cmp = toCmp.woCompare( e, false ); + if ( !forward ) { + cmp = -cmp; + } + if ( cmp < 0 ) { + l = m; + } else if ( cmp > 0 ) { + h = m; + } else { + return true; + } + } + assert( l + 1 == h ); + return ( l % 2 == 0 ); // if we're inside an interval + } + + bool FieldRangeVector::matches( const BSONObj &obj ) const { + BSONObjIterator k( _keyPattern ); + for( int i = 0; i < (int)_ranges.size(); ++i ) { + if ( _ranges[ i ].empty() ) { + return false; + } + BSONElement kk = k.next(); + int number = (int) kk.number(); + bool forward = ( ( number >= 0 ? 1 : -1 ) * ( _direction >= 0 ? 1 : -1 ) > 0 ); + BSONElement e = obj.getField( kk.fieldName() ); + if ( e.eoo() ) { + e = staticNull.firstElement(); + } + if ( e.type() == Array ) { + BSONObjIterator j( e.embeddedObject() ); + bool match = false; + while( j.more() ) { + if ( matchesElement( j.next(), i, forward ) ) { + match = true; + break; + } + } + if ( !match ) { + return false; + } + } else if ( !matchesElement( e, i, forward ) ) { + return false; + } + } + return true; + } + int FieldRangeVector::Iterator::advance( const BSONObj &curr ) { BSONObjIterator j( curr ); BSONObjIterator o( _v._keyPattern ); diff --git a/db/queryutil.h b/db/queryutil.h index 42976f6300e..2d7a636a5a1 100644 --- a/db/queryutil.h +++ b/db/queryutil.h @@ -93,9 +93,6 @@ namespace mongo { i->_upper._inclusive = false; } } - // reconstructs $in, regex, inequality matches - // this is a hack - we should submit FieldRange directly to a Matcher instead - BSONObj simplifiedComplex() const; // constructs a range which is the reverse of the current one // note - the resulting intervals may not be strictValid() void reverse( FieldRange &ret ) const { @@ -221,7 +218,7 @@ namespace mongo { } const char *ns() const { return _ns; } // if fields is specified, order fields of returned object to match those of 'fields' - BSONObj simplifiedQuery( const BSONObj &fields = BSONObj(), bool expandIn = false ) const; + BSONObj simplifiedQuery( const BSONObj &fields = BSONObj() ) const; bool matchPossible() const { for( map< string, FieldRange >::const_iterator i = _ranges.begin(); i != _ranges.end(); ++i ) if ( i->second.empty() ) @@ -333,6 +330,7 @@ namespace mongo { } return b.obj(); } + bool matches( const BSONObj &obj ) const; class Iterator { public: Iterator( const FieldRangeVector &v ) : _v( v ), _i( _v._ranges.size(), 0 ), _cmp( _v._ranges.size(), 0 ), _superlative( _v._ranges.size(), 0 ) { @@ -411,6 +409,7 @@ namespace mongo { vector< const BSONElement* > _superlative; }; private: + bool matchesElement( const BSONElement &e, int i, bool direction ) const; vector< FieldRange > _ranges; BSONObj _keyPattern; int _direction; diff --git a/dbtests/queryoptimizertests.cpp b/dbtests/queryoptimizertests.cpp index d317bbaab80..93719041b35 100644 --- a/dbtests/queryoptimizertests.cpp +++ b/dbtests/queryoptimizertests.cpp @@ -703,7 +703,7 @@ namespace QueryOptimizerTests { 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 ) ); + ASSERT_EQUALS( fromjson( "{a:1,b:5,c:7,d:{$gte:8,$lte:9},e:10}" ), frs1.simplifiedQuery( BSONObj() ) ); } }; diff --git a/jstests/or4.js b/jstests/or4.js index 5e690832c3f..d9fd7ec2db0 100644 --- a/jstests/or4.js +++ b/jstests/or4.js @@ -81,6 +81,8 @@ t.remove( {} ); t.save( {a:[1,2]} ); assert.eq.automsg( "1", "t.find( {$or:[{a:1},{a:2}]} ).toArray().length" ); assert.eq.automsg( "1", "t.count( {$or:[{a:1},{a:2}]} )" ); +assert.eq.automsg( "1", "t.find( {$or:[{a:2},{a:1}]} ).toArray().length" ); +assert.eq.automsg( "1", "t.count( {$or:[{a:2},{a:1}]} )" ); t.remove(); diff --git a/jstests/or8.js b/jstests/or8.js index c58507ee30e..fbd2c781b57 100644 --- a/jstests/or8.js +++ b/jstests/or8.js @@ -4,3 +4,8 @@ t = db.jstests_or8; t.drop(); t.find({ "$or": [ { "PropA": { "$lt": "b" } }, { "PropA": { "$lt": "b", "$gt": "a" } } ] }).toArray(); + +// empty $in + +//t.ensureIndex( {a:1} ); +//t.find({ $or: [ { a: {$in:[]} } ] } ).toArray();