summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAaron Staple <aaron@10gen.com>2009-08-12 17:45:29 -0400
committerAaron Staple <aaron@10gen.com>2009-08-12 17:45:29 -0400
commit2f3398e6cc1107f256762da03278afad1d5b73c9 (patch)
tree4960e22ddd4728e11c37b80775ce9b7c4fd9ab6c
parent9fd0168effad55570a39a4c4f3af6b5c8ed9bbe7 (diff)
downloadmongo-2f3398e6cc1107f256762da03278afad1d5b73c9.tar.gz
SERVER-149 implement $exists
-rw-r--r--db/jsobj.h1
-rw-r--r--db/matcher.cpp66
-rw-r--r--jstests/exists.js28
3 files changed, 65 insertions, 30 deletions
diff --git a/db/jsobj.h b/db/jsobj.h
index 8d80c80144a..c3d87c83ff3 100644
--- a/db/jsobj.h
+++ b/db/jsobj.h
@@ -876,6 +876,7 @@ namespace mongo {
opSIZE = 0x0A,
opALL = 0x0B,
NIN = 0x0C,
+ opEXISTS = 0x0D,
};
};
ostream& operator<<( ostream &s, const BSONObj &o );
diff --git a/db/matcher.cpp b/db/matcher.cpp
index 68e69a81654..bc6792c7e67 100644
--- a/db/matcher.cpp
+++ b/db/matcher.cpp
@@ -257,7 +257,7 @@ namespace mongo {
ok = true;
all = true;
}
- else if ( fn[1] == 's' && fn[2] == 'i' && fn[3] == 'z' && fn[4] == 'e' && fe.isNumber() ) {
+ else if ( fn[1] == 's' && fn[2] == 'i' && fn[3] == 'z' && fn[4] == 'e' && fn[5] == 0 && fe.isNumber() ) {
shared_ptr< BSONObjBuilder > b( new BSONObjBuilder() );
builders_.push_back( b );
b->appendAs(fe, e.fieldName());
@@ -265,6 +265,13 @@ namespace mongo {
haveSize = true;
ok = true;
}
+ else if ( fn[1] == 'e' && fn[2] == 'x' && fn[3] == 'i' && fn[4] == 's' && fn[5] == 't' && fn[6] == 's' && fn[7] == 0 && fe.isBoolean() ) {
+ shared_ptr< BSONObjBuilder > b( new BSONObjBuilder() );
+ builders_.push_back( b );
+ b->appendAs(fe, e.fieldName());
+ addBasic(b->done().firstElement(), BSONObj::opEXISTS);
+ ok = true;
+ }
else
uassert( (string)"invalid $operator: " + fn , false);
}
@@ -326,6 +333,12 @@ namespace mongo {
else
return -ret;
}
+
+ int retMissing( const BasicMatcher &bm ) {
+ if ( bm.compareOp != BSONObj::opEXISTS )
+ return 0;
+ return bm.toMatch.boolean() ? -1 : 1;
+ }
/* Check if a particular field matches.
@@ -349,25 +362,25 @@ namespace mongo {
*/
int JSMatcher::matchesDotted(const char *fieldName, const BSONElement& toMatch, const BSONObj& obj, int compareOp, const BasicMatcher& bm , bool *deep, bool isArr) {
- if ( compareOp == BSONObj::opALL ) {
- if ( bm.myset->size() == 0 )
- return -1; // is this desired?
- BSONObjSetDefaultOrder actualKeys;
- getKeysFromObject( BSON( fieldName << 1 ), obj, actualKeys );
- if ( actualKeys.size() == 0 )
- return 0;
- for( set< BSONElement, element_lt >::const_iterator i = bm.myset->begin(); i != bm.myset->end(); ++i ) {
- // ignore nulls
- if ( i->type() == jstNULL )
- continue;
- // parallel traversal would be faster worst case I guess
- BSONObjBuilder b;
- b.appendAs( *i, "" );
- if ( !actualKeys.count( b.done() ) )
- return -1;
- }
- return 1;
- }
+ if ( compareOp == BSONObj::opALL ) {
+ if ( bm.myset->size() == 0 )
+ return -1; // is this desired?
+ BSONObjSetDefaultOrder actualKeys;
+ getKeysFromObject( BSON( fieldName << 1 ), obj, actualKeys );
+ if ( actualKeys.size() == 0 )
+ return 0;
+ for( set< BSONElement, element_lt >::const_iterator i = bm.myset->begin(); i != bm.myset->end(); ++i ) {
+ // ignore nulls
+ if ( i->type() == jstNULL )
+ continue;
+ // parallel traversal would be faster worst case I guess
+ BSONObjBuilder b;
+ b.appendAs( *i, "" );
+ if ( !actualKeys.count( b.done() ) )
+ return -1;
+ }
+ return 1;
+ }
if ( compareOp == BSONObj::NE )
return matchesNe( fieldName, toMatch, obj, bm, deep );
@@ -376,7 +389,6 @@ namespace mongo {
int ret = matchesNe( fieldName, *i, obj, bm, deep );
if ( ret != 1 )
return ret;
- // code to handle 0 (missing) return value doesn't deal with nin yet
}
return 1;
}
@@ -403,7 +415,7 @@ namespace mongo {
}
}
}
- return found ? -1 : 0;
+ return found ? -1 : retMissing( bm );
}
const char *p = strchr(fieldName, '.');
if ( p ) {
@@ -411,9 +423,9 @@ namespace mongo {
BSONElement se = obj.getField(left.c_str());
if ( se.eoo() )
- return 0;
+ return retMissing( bm );
if ( se.type() != Object && se.type() != Array )
- return 0;
+ return retMissing( bm );
BSONObj eo = se.embeddedObject();
return matchesDotted(p+1, toMatch, eo, compareOp, bm, deep, se.type() == Array);
@@ -421,8 +433,10 @@ namespace mongo {
e = obj.getField(fieldName);
}
}
-
- if ( ( e.type() != Array || indexed || compareOp == BSONObj::opSIZE ) &&
+
+ if ( compareOp == BSONObj::opEXISTS ) {
+ return ( e.eoo() ^ toMatch.boolean() ) ? 1 : -1;
+ } else if ( ( e.type() != Array || indexed || compareOp == BSONObj::opSIZE ) &&
valuesMatch(e, toMatch, compareOp, bm, deep) ) {
return 1;
} else if ( e.type() == Array && compareOp != BSONObj::opSIZE ) {
diff --git a/jstests/exists.js b/jstests/exists.js
index dd5a13d6dd5..20c3fe8dbd2 100644
--- a/jstests/exists.js
+++ b/jstests/exists.js
@@ -5,8 +5,28 @@ t.save( {} );
t.save( {a:1} );
t.save( {a:{b:1}} );
t.save( {a:{b:{c:1}}} );
+t.save( {a:{b:{c:{d:null}}}} );
-assert.eq( 4, t.count() );
-assert.eq( 3, t.count( {a:{$ne:null}} ) );
-assert.eq( 2, t.count( {'a.b':{$ne:null}} ) );
-assert.eq( 1, t.count( {'a.b.c':{$ne:null}} ) );
+assert.eq( 5, t.count() );
+assert.eq( 4, t.count( {a:{$ne:null}} ) );
+assert.eq( 3, t.count( {'a.b':{$ne:null}} ) );
+assert.eq( 2, t.count( {'a.b.c':{$ne:null}} ) );
+assert.eq( 0, t.count( {'a.b.c.d':{$ne:null}} ) );
+
+assert.eq( 4, t.count( {a: {$exists:true}} ) );
+assert.eq( 3, t.count( {'a.b': {$exists:true}} ) );
+assert.eq( 2, t.count( {'a.b.c': {$exists:true}} ) );
+assert.eq( 1, t.count( {'a.b.c.d': {$exists:true}} ) );
+
+assert.eq( 1, t.count( {a: {$exists:false}} ) );
+assert.eq( 2, t.count( {'a.b': {$exists:false}} ) );
+assert.eq( 3, t.count( {'a.b.c': {$exists:false}} ) );
+assert.eq( 4, t.count( {'a.b.c.d': {$exists:false}} ) );
+
+t.drop();
+
+t.save( {r:[{s:1}]} );
+assert( t.findOne( {'r.s':{$exists:true}} ) );
+assert( !t.findOne( {'r.s':{$exists:false}} ) );
+assert( !t.findOne( {'r.t':{$exists:true}} ) );
+assert( t.findOne( {'r.t':{$exists:false}} ) );