Permalink
Browse files

allow multiple locations to be nested in different subdocuments SERVE…

…R-838
  • Loading branch information...
1 parent d6c8b5e commit f45587a30a9537398c01d2a666f7e3f72cd26948 gregs committed Mar 28, 2011
Showing with 190 additions and 66 deletions.
  1. +4 −2 bson/bsonobj.h
  2. +74 −57 db/geo/2d.cpp
  3. +6 −6 db/jsobj.cpp
  4. +6 −1 jstests/geo_array2.js
  5. +63 −0 jstests/geo_multinest0.js
  6. +37 −0 jstests/geo_multinest1.js
View
@@ -152,9 +152,11 @@ namespace mongo {
return getFieldDotted( name.c_str() );
}
- /** Like getFieldDotted(), but expands multikey arrays and returns all matching objects
+ /** Like getFieldDotted(), but expands multikey arrays and returns all matching objects.
+ * Turning off expandLastArray allows you to retrieve nested array objects instead of
+ * their contents.
*/
- void getFieldsDotted(const StringData& name, BSONElementSet &ret ) const;
+ void getFieldsDotted(const StringData& name, BSONElementSet &ret, bool expandLastArray = true ) const;
/** Like getFieldDotted(), but returns first array encountered while traversing the
dotted fields of name. The name variable is updated to represent field
names with respect to the returned element. */
View
@@ -155,87 +155,100 @@ namespace mongo {
}
virtual void getKeys( const BSONObj &obj, BSONObjSetDefaultOrder &keys ) const {
- BSONElement geo = obj.getFieldDotted(_geo.c_str());
- if ( geo.eoo() )
- return;
- //
- // Grammar for location lookup:
- // locs ::= [loc,loc,...,loc]|{<k>:loc,<k>:loc}|loc
- // loc ::= { <k1> : #, <k2> : # }|[#, #]|{}
- //
- // Empty locations are ignored, preserving single-location semantics
- //
+ BSONElementSet bSet;
+ // Get all the nested location fields, but don't return individual elements from
+ // the last array, if it exists.
+ obj.getFieldsDotted(_geo.c_str(), bSet, false);
- if ( ! geo.isABSONObj() )
+ if( bSet.empty() )
return;
- BSONObj embed = geo.embeddedObject();
- if ( embed.isEmpty() )
- return;
+ for( BSONElementSet::iterator setI = bSet.begin(); setI != bSet.end(); ++setI ) {
- // Differentiate between location arrays and locations
- // by seeing if the first element value is a number
- bool singleElement = embed.firstElement().isNumber();
+ BSONElement geo = *setI;
- BSONObjIterator oi(embed);
+ GEODEBUG( "Element " << geo << " found for query " << _geo.c_str() );
- while( oi.more() ) {
+ if ( geo.eoo() || ! geo.isABSONObj() )
+ continue;
- BSONObj locObj;
+ //
+ // Grammar for location lookup:
+ // locs ::= [loc,loc,...,loc]|{<k>:loc,<k>:loc}|loc
+ // loc ::= { <k1> : #, <k2> : # }|[#, #]|{}
+ //
+ // Empty locations are ignored, preserving single-location semantics
+ //
- if( singleElement ) locObj = embed;
- else {
- BSONElement locElement = oi.next();
+ BSONObj embed = geo.embeddedObject();
+ if ( embed.isEmpty() )
+ continue;
- uassert( 13654, str::stream() << "location object expected, location array not in correct format",
- locElement.isABSONObj() );
+ // Differentiate between location arrays and locations
+ // by seeing if the first element value is a number
+ bool singleElement = embed.firstElement().isNumber();
- locObj = locElement.embeddedObject();
+ BSONObjIterator oi(embed);
- if( locObj.isEmpty() )
- continue;
- }
+ while( oi.more() ) {
- BSONObjBuilder b(64);
+ BSONObj locObj;
- _hash( locObj ).append( b , "" );
+ if( singleElement ) locObj = embed;
+ else {
+ BSONElement locElement = oi.next();
- // Go through all the other index keys
- for ( vector<string>::const_iterator i = _other.begin(); i != _other.end(); ++i ) {
+ uassert( 13654, str::stream() << "location object expected, location array not in correct format",
+ locElement.isABSONObj() );
- // Get *all* fields for the index key
- BSONElementSet eSet;
- obj.getFieldsDotted( *i, eSet );
+ locObj = locElement.embeddedObject();
+ if( locObj.isEmpty() )
+ continue;
+ }
- if ( eSet.size() == 0 )
- b.appendAs( _spec->missingField(), "" );
- else if ( eSet.size() == 1 )
- b.appendAs( *(eSet.begin()), "" );
- else {
+ BSONObjBuilder b(64);
- // If we have more than one key, store as an array of the objects
+ _hash( locObj ).append( b , "" );
- BSONArrayBuilder aBuilder;
+ // Go through all the other index keys
+ for ( vector<string>::const_iterator i = _other.begin(); i != _other.end(); ++i ) {
- for( BSONElementSet::iterator ei = eSet.begin(); ei != eSet.end(); ++ei ) {
- aBuilder.append( *ei );
- }
+ // Get *all* fields for the index key
+ BSONElementSet eSet;
+ obj.getFieldsDotted( *i, eSet );
- BSONArray arr = aBuilder.arr();
- b.append( "", arr );
+ if ( eSet.size() == 0 )
+ b.appendAs( _spec->missingField(), "" );
+ else if ( eSet.size() == 1 )
+ b.appendAs( *(eSet.begin()), "" );
+ else {
- }
+ // If we have more than one key, store as an array of the objects
- }
+ BSONArrayBuilder aBuilder;
+
+ for( BSONElementSet::iterator ei = eSet.begin(); ei != eSet.end(); ++ei ) {
+ aBuilder.append( *ei );
+ }
+
+ BSONArray arr = aBuilder.arr();
+
+ b.append( "", arr );
- keys.insert( b.obj() );
+ }
+
+ }
- if( singleElement ) break;
+ keys.insert( b.obj() );
+ if( singleElement ) break;
+
+ }
}
+
}
GeoHash _tohash( const BSONElement& e ) const {
@@ -765,7 +778,8 @@ namespace mongo {
// Remember match results for each object
map<DiskLoc, bool>::iterator match = _matched.find( node.recordLoc );
- if( match == _matched.end() ) {
+ bool newDoc = match == _matched.end();
+ if( newDoc ) {
// matcher
MatchDetails details;
@@ -792,11 +806,11 @@ namespace mongo {
return;
}
- addSpecific( node , d );
+ addSpecific( node , d, newDoc );
_found++;
}
- virtual void addSpecific( const KeyNode& node , double d ) = 0;
+ virtual void addSpecific( const KeyNode& node , double d, bool newDoc ) = 0;
virtual bool checkDistance( const GeoHash& node , double& d ) = 0;
long long found() const {
@@ -838,7 +852,7 @@ namespace mongo {
return good;
}
- virtual void addSpecific( const KeyNode& node , double d ) {
+ virtual void addSpecific( const KeyNode& node , double d, bool newDoc ) {
GEODEBUG( "\t\t" << GeoHash( node.key.firstElement() ) << "\t" << node.recordLoc.obj() << "\t" << d );
_points.insert( GeoPoint( node.key , node.recordLoc , d ) );
if ( _points.size() > _max ) {
@@ -1278,7 +1292,10 @@ namespace mongo {
virtual bool moreToDo() = 0;
virtual void fillStack() = 0;
- virtual void addSpecific( const KeyNode& node , double d ) {
+ virtual void addSpecific( const KeyNode& node , double d, bool newDoc ) {
+
+ if( ! newDoc ) return;
+
if ( _cur.isEmpty() )
_cur = GeoPoint( node , d );
else
View
@@ -690,7 +690,7 @@ namespace mongo {
return -1;
}
- void BSONObj::getFieldsDotted(const StringData& name, BSONElementSet &ret ) const {
+ void BSONObj::getFieldsDotted(const StringData& name, BSONElementSet &ret, bool expandLastArray ) const {
BSONElement e = getField( name );
if ( e.eoo() ) {
const char *p = strchr(name.data(), '.');
@@ -700,7 +700,7 @@ namespace mongo {
BSONElement e = getField( left.c_str() );
if (e.type() == Object) {
- e.embeddedObject().getFieldsDotted(next, ret);
+ e.embeddedObject().getFieldsDotted(next, ret, expandLastArray );
}
else if (e.type() == Array) {
bool allDigits = false;
@@ -711,14 +711,14 @@ namespace mongo {
allDigits = (*temp == '.' || *temp == '\0');
}
if (allDigits) {
- e.embeddedObject().getFieldsDotted(next, ret);
+ e.embeddedObject().getFieldsDotted(next, ret, expandLastArray );
}
else {
BSONObjIterator i(e.embeddedObject());
while ( i.more() ) {
BSONElement e2 = i.next();
if (e2.type() == Object || e2.type() == Array)
- e2.embeddedObject().getFieldsDotted(next, ret);
+ e2.embeddedObject().getFieldsDotted(next, ret, expandLastArray );
}
}
}
@@ -728,7 +728,7 @@ namespace mongo {
}
}
else {
- if (e.type() == Array) {
+ if (e.type() == Array && expandLastArray) {
BSONObjIterator i(e.embeddedObject());
while ( i.more() )
ret.insert(i.next());
@@ -741,7 +741,7 @@ namespace mongo {
BSONElement BSONObj::getFieldDottedOrArray(const char *&name) const {
const char *p = strchr(name, '.');
-
+
BSONElement sub;
if ( p ) {
View
@@ -77,7 +77,7 @@ for( var i = -1; i < 2; i++ ){
assert.eq( objCount , objsFound[q] )
}
-
+
// Do nearSphere check
// Earth Radius
@@ -103,6 +103,11 @@ for( var i = -1; i < 2; i++ ){
+ // Within results do not return duplicate documents
+
+ var count = i == 0 && j == 0 ? 9 : 1
+ var objCount = i == 0 && j == 0 ? 1 : 1
+
// Do within check
objsFound = {}
View
@@ -0,0 +1,63 @@
+// Make sure nesting of location arrays also works.
+
+t = db.geonest
+t.drop();
+
+t.insert( { zip : "10001", data : [ { loc : [ 10, 10 ], type : "home" },
+ { loc : [ 50, 50 ], type : "work" } ] } )
+t.insert( { zip : "10002", data : [ { loc : [ 20, 20 ], type : "home" },
+ { loc : [ 50, 50 ], type : "work" } ] } )
+t.insert( { zip : "10003", data : [ { loc : [ 30, 30 ], type : "home" },
+ { loc : [ 50, 50 ], type : "work" } ] } )
+assert.isnull( db.getLastError() )
+
+t.ensureIndex( { "data.loc" : "2d", zip : 1 } );
+assert.isnull( db.getLastError() )
+assert.eq( 2, t.getIndexKeys().length )
+
+t.insert( { zip : "10004", data : [ { loc : [ 40, 40 ], type : "home" },
+ { loc : [ 50, 50 ], type : "work" } ] } )
+assert.isnull( db.getLastError() )
+
+// test normal access
+
+printjson( t.find( { "data.loc" : { $within : { $box : [ [ 0, 0 ], [ 45, 45 ] ] } } } ).toArray() )
+
+assert.eq( 4, t.find( { "data.loc" : { $within : { $box : [ [ 0, 0 ], [ 45, 45 ] ] } } } ).count() );
+
+assert.eq( 4, t.find( { "data.loc" : { $within : { $box : [ [ 45, 45 ], [ 50, 50 ] ] } } } ).count() );
+
+
+
+
+
+// Try a complex nesting
+
+t = db.geonest
+t.drop();
+
+t.insert( { zip : "10001", data : [ { loc : [ [ 10, 10 ], { lat : 50, long : 50 } ], type : "home" } ] } )
+t.insert( { zip : "10002", data : [ { loc : [ 20, 20 ], type : "home" },
+ { loc : [ 50, 50 ], type : "work" } ] } )
+t.insert( { zip : "10003", data : [ { loc : [ { x : 30, y : 30 }, [ 50, 50 ] ], type : "home" } ] } )
+assert.isnull( db.getLastError() )
+
+t.ensureIndex( { "data.loc" : "2d", zip : 1 } );
+assert.isnull( db.getLastError() )
+assert.eq( 2, t.getIndexKeys().length )
+
+t.insert( { zip : "10004", data : [ { loc : [ 40, 40 ], type : "home" },
+ { loc : [ 50, 50 ], type : "work" } ] } )
+
+
+assert.isnull( db.getLastError() )
+
+// test normal access
+printjson( t.find( { "data.loc" : { $within : { $box : [ [ 0, 0 ], [ 45, 45 ] ] } } } ).toArray() )
+
+assert.eq( 4, t.find( { "data.loc" : { $within : { $box : [ [ 0, 0 ], [ 45, 45 ] ] } } } ).count() );
+
+assert.eq( 4, t.find( { "data.loc" : { $within : { $box : [ [ 45, 45 ], [ 50, 50 ] ] } } } ).count() );
+
+
+
View
@@ -0,0 +1,37 @@
+// Test distance queries with interleaved distances
+
+t = db.multinest
+t.drop();
+
+t.insert( { zip : "10001", data : [ { loc : [ 10, 10 ], type : "home" },
+ { loc : [ 29, 29 ], type : "work" } ] } )
+t.insert( { zip : "10002", data : [ { loc : [ 20, 20 ], type : "home" },
+ { loc : [ 39, 39 ], type : "work" } ] } )
+t.insert( { zip : "10003", data : [ { loc : [ 30, 30 ], type : "home" },
+ { loc : [ 49, 49 ], type : "work" } ] } )
+assert.isnull( db.getLastError() )
+
+t.ensureIndex( { "data.loc" : "2d", zip : 1 } );
+assert.isnull( db.getLastError() )
+assert.eq( 2, t.getIndexKeys().length )
+
+t.insert( { zip : "10004", data : [ { loc : [ 40, 40 ], type : "home" },
+ { loc : [ 59, 59 ], type : "work" } ] } )
+assert.isnull( db.getLastError() )
+
+// test normal access
+
+var result = t.find({ "data.loc" : { $near : [0, 0] } }).toArray();
+
+printjson( result )
+
+assert.eq( 8, result.length )
+
+var order = [ 1, 2, 1, 3, 2, 4, 3, 4 ]
+
+for( var i = 0; i < result.length; i++ ){
+ assert.eq( "1000" + order[i], result[i].zip )
+}
+
+
+

0 comments on commit f45587a

Please sign in to comment.