diff --git a/db/queryutil.cpp b/db/queryutil.cpp index d188d75aa8d..78992779651 100644 --- a/db/queryutil.cpp +++ b/db/queryutil.cpp @@ -104,7 +104,7 @@ namespace mongo { } } - FieldRange::FieldRange( const BSONElement &e, bool optimize ) { + FieldRange::FieldRange( const BSONElement &e, bool isNot, bool optimize ) { if ( !e.eoo() && e.type() != RegEx && e.getGtLtOp() == BSONObj::opIN ) { set< BSONElement, element_lt > vals; uassert( 12580 , "invalid query" , e.isABSONObj() ); @@ -150,15 +150,45 @@ namespace mongo { || (e.type() == Object && !e.embeddedObject()["$regex"].eoo()) ) { - const string r = simpleRegex(e); - if ( r.size() ) { - lower = addObj( BSON( "" << r ) ).firstElement(); - upper = addObj( BSON( "" << simpleRegexEnd( r ) ) ).firstElement(); - upperInclusive = false; - } + if ( !isNot ) { // no optimization for negated regex - we could consider creating 2 intervals comprising all nonmatching prefixes + const string r = simpleRegex(e); + if ( r.size() ) { + lower = addObj( BSON( "" << r ) ).firstElement(); + upper = addObj( BSON( "" << simpleRegexEnd( r ) ) ).firstElement(); + upperInclusive = false; + } + } return; } - switch( e.getGtLtOp() ) { + int op = e.getGtLtOp(); + if ( isNot ) { + switch( op ) { + case BSONObj::Equality: + case BSONObj::opALL: + case BSONObj::opMOD: // NOTE for mod and type, we could consider having 1-2 intervals comprising the complementary types (multiple intervals already possible with $in) + case BSONObj::opTYPE: + op = BSONObj::NE; // no bound calculation + break; + case BSONObj::NE: + op = BSONObj::Equality; + break; + case BSONObj::LT: + op = BSONObj::GTE; + break; + case BSONObj::LTE: + op = BSONObj::GT; + break; + case BSONObj::GT: + op = BSONObj::LTE; + break; + case BSONObj::GTE: + op = BSONObj::LT; + break; + default: // otherwise doesn't matter + break; + } + } + switch( op ) { case BSONObj::Equality: lower = upper = e; break; @@ -294,6 +324,32 @@ namespace mongo { return o; } + void FieldRangeSet::processOpElement( const char *fieldName, const BSONElement &f, bool isNot, bool optimize ) { + int op2 = f.getGtLtOp(); + if ( op2 == BSONObj::opELEM_MATCH ) { + BSONObjIterator k( f.embeddedObjectUserCheck() ); + while ( k.more() ){ + BSONElement g = k.next(); + StringBuilder buf(32); + buf << fieldName << "." << g.fieldName(); + string fullname = buf.str(); + + int op3 = getGtLtOp( g ); + if ( op3 == BSONObj::Equality ){ + ranges_[ fullname ] &= FieldRange( g , isNot , optimize ); + } + else { + BSONObjIterator l( g.embeddedObject() ); + while ( l.more() ){ + ranges_[ fullname ] &= FieldRange( l.next() , isNot , optimize ); + } + } + } + } else { + ranges_[ fieldName ] &= FieldRange( f , isNot , optimize ); + } + } + FieldRangeSet::FieldRangeSet( const char *ns, const BSONObj &query , bool optimize ) : ns_( ns ), query_( query.getOwned() ) { BSONObjIterator i( query_ ); @@ -305,37 +361,38 @@ namespace mongo { if ( strcmp( e.fieldName(), "$where" ) == 0 ) continue; - int op = getGtLtOp( e ); - - if ( op == BSONObj::Equality || ( e.type() == Object && !e.embeddedObject()[ "$regex" ].eoo() ) ) { - ranges_[ e.fieldName() ] &= FieldRange( e , optimize ); + bool equality = ( getGtLtOp( e ) == BSONObj::Equality ); + if ( equality && e.type() == Object ) { + equality = ( strcmp( e.embeddedObject().firstElement().fieldName(), "$not" ) != 0 ); } - if ( op != BSONObj::Equality ) { + + if ( equality || ( e.type() == Object && !e.embeddedObject()[ "$regex" ].eoo() ) ) { + ranges_[ e.fieldName() ] &= FieldRange( e , false , optimize ); + } + if ( !equality ) { BSONObjIterator j( e.embeddedObject() ); while( j.more() ) { BSONElement f = j.next(); - int op2 = f.getGtLtOp(); - if ( op2 == BSONObj::opELEM_MATCH ) { - BSONObjIterator k( f.embeddedObjectUserCheck() ); - while ( k.more() ){ - BSONElement g = k.next(); - StringBuilder buf(32); - buf << e.fieldName() << "." << g.fieldName(); - string fullname = buf.str(); - - int op3 = getGtLtOp( g ); - if ( op3 == BSONObj::Equality ){ - ranges_[ fullname ] &= FieldRange( g , optimize ); - } - else { - BSONObjIterator l( g.embeddedObject() ); - while ( l.more() ){ - ranges_[ fullname ] &= FieldRange( l.next() , optimize ); + if ( strcmp( f.fieldName(), "$not" ) == 0 ) { + switch( f.type() ) { + case Object: { + BSONObjIterator k( f.embeddedObject() ); + while( k.more() ) { + BSONElement g = k.next(); + uassert( 13034, "invalid use of $not", g.getGtLtOp() != BSONObj::Equality ); + processOpElement( e.fieldName(), g, true, optimize ); } + break; } - } + case RegEx: + log() << "regex: " << f << endl; + processOpElement( e.fieldName(), f, true, optimize ); + break; + default: + uassert( 13033, "invalid use of $not", false ); + } } else { - ranges_[ e.fieldName() ] &= FieldRange( f , optimize ); + processOpElement( e.fieldName(), f, false, optimize ); } } } diff --git a/db/queryutil.h b/db/queryutil.h index 6fcf3a74571..9e2abecad24 100644 --- a/db/queryutil.h +++ b/db/queryutil.h @@ -48,7 +48,7 @@ namespace mongo { // determine index limits class FieldRange { public: - FieldRange( const BSONElement &e = BSONObj().firstElement() , bool optimize=true ); + FieldRange( const BSONElement &e = BSONObj().firstElement() , bool isNot=false , bool optimize=true ); const FieldRange &operator&=( const FieldRange &other ); BSONElement min() const { assert( !empty() ); return intervals_[ 0 ].lower_.bound_; } BSONElement max() const { assert( !empty() ); return intervals_[ intervals_.size() - 1 ].upper_.bound_; } @@ -172,6 +172,7 @@ namespace mongo { QueryPattern pattern( const BSONObj &sort = BSONObj() ) const; BoundList indexBounds( const BSONObj &keyPattern, int direction ) const; private: + void processOpElement( const char *fieldName, const BSONElement &f, bool isNot, bool optimize ); static FieldRange *trivialRange_; static FieldRange &trivialRange(); mutable map< string, FieldRange > ranges_; diff --git a/jstests/not2.js b/jstests/not2.js index 7b673e712a7..2eaf7b72f1b 100644 --- a/jstests/not2.js +++ b/jstests/not2.js @@ -12,7 +12,10 @@ check = function( query, expected, size ) { } fail = function( query ) { - t.count( query ); + try { + t.count( query ); + } catch ( e ) { + } assert( db.getLastError(), tojson( query ) ); } @@ -41,7 +44,8 @@ check( {i:{$not:/a/,$regex:"a"}}, "", 0 ); check( {i:{$not:/aa/}}, "a", 2 ); fail( {i:{$not:{$regex:"a"}}} ); fail( {i:{$not:{$options:"a"}}} ); -check( {i:{$not:{$type:0}}}, "a", 2 ); +check( {i:{$type:2}}, "a", 2 ); +check( {i:{$not:{$type:1}}}, "a", 2 ); check( {i:{$not:{$type:2}}}, "", 0 ); check( {i:{$not:{$gt:"c",$lt:"b"}}}, "b" ); @@ -60,8 +64,12 @@ check( {i:{$not:{$gt:"a"}}}, null, 0 ); check( {i:{$not:{$gt:"c"}}}, ["a","b"] ); check( {i:{$not:{$all:["a","b"]}}}, null, 0 ); check( {i:{$not:{$all:["c"]}}}, ["a","b"] ); -check( {i:{$not:{$elemMatch:"a"}}}, null, 0 ); -check( {i:{$not:{$elemMatch:"f"}}}, null, 0 ); + +t.drop(); +t.save( {i:{j:"a"}} ); +t.save( {i:{j:"b"}} ); +check( {i:{$not:{$elemMatch:{j:"a"}}}}, {j:"b"} ); +check( {i:{$not:{$elemMatch:{j:"f"}}}}, {j:"a"}, 2 ); t.drop(); t.save( {i:"a"} ); @@ -69,14 +77,14 @@ t.save( {i:"b"} ); t.ensureIndex( {i:1} ); indexed = function( query, min, max ) { - min = min || minKey; - max = max || maxKey; + min = min || MinKey; + max = max || MaxKey; exp = t.find( query ).explain(); printjson( exp ); assert( exp.cursor.match( /Btree/ ), tojson( query ) ); assert( exp.allPlans.length == 1, tojson( query ) ); - assert.eq( exp.startKey, {i:min} ); - assert.eq( exp.endKey, {i:max} ); + assert.eq( exp.startKey.i, min ); + assert.eq( exp.endKey.i, max ); } not = function( query ) { @@ -89,5 +97,17 @@ not = function( query ) { indexed( {i:1}, 1, 1 ); not( {i:{$ne:1}} ); -//indexed( {i:{$not:{$ne:"a"}}}, "a", "a" ); -//not( {i:{$not:/a/}} ); \ No newline at end of file +indexed( {i:{$not:{$ne:"a"}}}, "a", "a" ); +not( {i:{$not:/^a/}} ); + +//indexed( {i:{$gt:"a"}}, "a", null ); +//indexed( {i:{$not:{$gt:"a"}}}, null, "a" ); +// +//indexed( {i:{$gte:"a"}}, "a", null ); +//indexed( {i:{$not:{$gte:"a"}}}, null, "a" ); +// +//indexed( {i:{$lt:"b"}}, null, "b" ); +//indexed( {i:{$not:{$gte:"b"}}}, "b", null ); +// +//indexed( {i:{$lte:"b"}}, null, "b" ); +//indexed( {i:{$not:{$gt:"b"}}}, "b", null ); \ No newline at end of file