Skip to content

Commit

Permalink
support for indexing arrays of locations SERVER-838
Browse files Browse the repository at this point in the history
  • Loading branch information
gregs committed Mar 28, 2011
1 parent 2278a80 commit 8ebc664
Show file tree
Hide file tree
Showing 4 changed files with 313 additions and 49 deletions.
149 changes: 100 additions & 49 deletions db/geo/2d.cpp
Expand Up @@ -122,8 +122,8 @@ namespace mongo {


double _configval( const IndexSpec* spec , const string& name , double def ) { double _configval( const IndexSpec* spec , const string& name , double def ) {
BSONElement e = spec->info[name]; BSONElement e = spec->info[name];
if ( e.isNumber() ){ if ( e.isNumber() ) {
return e.numberDouble(); return e.numberDouble();
} }
return def; return def;
} }
Expand Down Expand Up @@ -159,7 +159,13 @@ namespace mongo {
if ( geo.eoo() ) if ( geo.eoo() )
return; return;


BSONObjBuilder b(64); //
// 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 ( ! geo.isABSONObj() ) if ( ! geo.isABSONObj() )
return; return;
Expand All @@ -168,40 +174,68 @@ namespace mongo {
if ( embed.isEmpty() ) if ( embed.isEmpty() )
return; return;


_hash( embed ).append( b , "" ); // Differentiate between location arrays and locations
// by seeing if the first element value is a number
bool singleElement = embed.firstElement().isNumber();


// Go through all the other index keys BSONObjIterator oi(embed);
for ( vector<string>::const_iterator i = _other.begin(); i != _other.end(); ++i ){


// Get *all* fields for the index key while( oi.more() ) {
BSONElementSet eSet;
obj.getFieldsDotted( *i, eSet );


BSONObj locObj;


if ( eSet.size() == 0 ) if( singleElement ) locObj = embed;
b.appendAs( _spec->missingField(), "" ); else {
else if ( eSet.size() == 1 ) BSONElement locElement = oi.next();
b.appendAs( *(eSet.begin()), "" );
else{ uassert( 13654, str::stream() << "location object expected, location array not in correct format",
locElement.isABSONObj() );

locObj = locElement.embeddedObject();

if( locObj.isEmpty() )
continue;
}


// If we have more than one key, store as an array of the objects BSONObjBuilder b(64);
// TODO: Store multiple keys?


BSONArrayBuilder aBuilder; _hash( locObj ).append( b , "" );


for( BSONElementSet::iterator ei = eSet.begin(); ei != eSet.end(); ++ei ){ // Go through all the other index keys
aBuilder.append( *ei ); for ( vector<string>::const_iterator i = _other.begin(); i != _other.end(); ++i ) {
}
// Get *all* fields for the index key
BSONElementSet eSet;
obj.getFieldsDotted( *i, eSet );


if ( eSet.size() == 0 )
b.appendAs( _spec->missingField(), "" );
else if ( eSet.size() == 1 )
b.appendAs( *(eSet.begin()), "" );
else {


BSONArray arr = aBuilder.arr(); // If we have more than one key, store as an array of the objects


b.append( "", arr ); 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() ); }
} }


GeoHash _tohash( const BSONElement& e ) const { GeoHash _tohash( const BSONElement& e ) const {
Expand Down Expand Up @@ -307,9 +341,9 @@ namespace mongo {
} }
} }
case Array: case Array:
// Non-geo index data is stored in a non-standard way, cannot use for exact lookups with // Non-geo index data is stored in a non-standard way, cannot use for exact lookups with
// additional criteria // additional criteria
if ( query.nFields() > 1 ) return USELESS; if ( query.nFields() > 1 ) return USELESS;
return HELPFUL; return HELPFUL;
default: default:
return USELESS; return USELESS;
Expand Down Expand Up @@ -710,10 +744,13 @@ namespace mongo {
} }


virtual void add( const KeyNode& node ) { virtual void add( const KeyNode& node ) {
// when looking at other boxes, don't want to look at some object twice
pair<set<DiskLoc>::iterator,bool> seenBefore = _seen.insert( node.recordLoc ); GEODEBUG( "\t\t\t\t checking key " << node.key.toString() )

// when looking at other boxes, don't want to look at some key/object pair twice
pair<set<pair<const char*, DiskLoc> >::iterator,bool> seenBefore = _seen.insert( make_pair(node.key.objdata(), node.recordLoc) );
if ( ! seenBefore.second ) { if ( ! seenBefore.second ) {
GEODEBUG( "\t\t\t\t already seen : " << node.recordLoc.obj()["_id"] ); GEODEBUG( "\t\t\t\t already seen : " << node.key.toString() << " with " << node.recordLoc.obj()["_id"] );
return; return;
} }
_lookedAt++; _lookedAt++;
Expand All @@ -726,21 +763,34 @@ namespace mongo {
} }
GEODEBUG( "\t\t\t\t good distance : " << node.recordLoc.obj() << "\t" << d ); GEODEBUG( "\t\t\t\t good distance : " << node.recordLoc.obj() << "\t" << d );


// matcher // Remember match results for each object
MatchDetails details; map<DiskLoc, bool>::iterator match = _matched.find( node.recordLoc );
if ( _matcher.get() ) { if( match == _matched.end() ) {
bool good = _matcher->matches( node.key , node.recordLoc , &details );
if ( details.loadedObject )
_objectsLoaded++;


if ( ! good ) { // matcher
GEODEBUG( "\t\t\t\t didn't match : " << node.recordLoc.obj()["_id"] ); MatchDetails details;
return; if ( _matcher.get() ) {
bool good = _matcher->matches( node.key , node.recordLoc , &details );
if ( details.loadedObject )
_objectsLoaded++;

if ( ! good ) {
GEODEBUG( "\t\t\t\t didn't match : " << node.recordLoc.obj()["_id"] );
_matched[ node.recordLoc ] = false;
return;
}
} }
}


if ( ! details.loadedObject ) // dont double count _matched[ node.recordLoc ] = true;
_objectsLoaded++;
if ( ! details.loadedObject ) // don't double count
_objectsLoaded++;

}
else if( !((*match).second) ) {
GEODEBUG( "\t\t\t\t previously didn't match : " << node.recordLoc.obj()["_id"] );
return;
}


addSpecific( node , d ); addSpecific( node , d );
_found++; _found++;
Expand All @@ -754,7 +804,8 @@ namespace mongo {
} }


const Geo2dType * _g; const Geo2dType * _g;
set<DiskLoc> _seen; set< pair<const char*, DiskLoc> > _seen;
map<DiskLoc, bool> _matched;
auto_ptr<CoveredIndexMatcher> _matcher; auto_ptr<CoveredIndexMatcher> _matcher;


long long _lookedAt; long long _lookedAt;
Expand Down Expand Up @@ -1513,12 +1564,12 @@ namespace mongo {
GEODEBUG( "box prefix [" << _prefix << "]" ); GEODEBUG( "box prefix [" << _prefix << "]" );


#ifdef GEODEBUGGING #ifdef GEODEBUGGING
if( _prefix.constrains() ){ if( _prefix.constrains() ) {
Box in( _g , GeoHash( _prefix ) ); Box in( _g , GeoHash( _prefix ) );
log() << "current expand box : " << in.toString() << endl; log() << "current expand box : " << in.toString() << endl;
} }
else{ else {
log() << "max expand box." << endl; log() << "max expand box." << endl;
} }
#endif #endif


Expand Down
25 changes: 25 additions & 0 deletions jstests/geo_array0.js
@@ -0,0 +1,25 @@
// Make sure the very basics of geo arrays are sane by creating a few multi location docs

t = db.geoarray
t.drop();

t.insert( { zip : "10001", loc : { home : [ 10, 10 ], work : [ 50, 50 ] } } )
t.insert( { zip : "10002", loc : { home : [ 20, 20 ], work : [ 50, 50 ] } } )
t.insert( { zip : "10003", loc : { home : [ 30, 30 ], work : [ 50, 50 ] } } )
assert.isnull( db.getLastError() )

t.ensureIndex( { loc : "2d", zip : 1 } );
assert.isnull( db.getLastError() )
assert.eq( 2, t.getIndexKeys().length )

t.insert( { zip : "10004", loc : { home : [ 40, 40 ], work : [ 50, 50 ] } } )

assert.isnull( db.getLastError() )

// test normal access

printjson( t.find( { loc : { $within : { $box : [ [ 0, 0 ], [ 45, 45 ] ] } } } ).toArray() )

assert.eq( 4, t.find( { loc : { $within : { $box : [ [ 0, 0 ], [ 45, 45 ] ] } } } ).count() );

assert.eq( 4, t.find( { loc : { $within : { $box : [ [ 45, 45 ], [ 50, 50 ] ] } } } ).count() );
30 changes: 30 additions & 0 deletions jstests/geo_array1.js
@@ -0,0 +1,30 @@
// Make sure many locations in one doc works, in the form of an array

t = db.geoarray1
t.drop();

var locObj = []

// Add locations everywhere
for ( var i = 0; i < 10; i++ ) {
for ( var j = 0; j < 10; j++ ) {
if ( j % 2 == 0 )
locObj.push( [ i, j ] )
else
locObj.push( { x : i, y : j } )
}
}

// Add docs with all these locations
for( var i = 0; i < 300; i++ ){
t.insert( { loc : locObj } )
}
t.ensureIndex( { loc : "2d" } )

// Pull them back
for ( var i = 0; i < 10; i++ ) {
for ( var j = 0; j < 10; j++ ) {
assert.eq( 300, t.find( { loc : { $within : { $box : [ [ i - 0.5, j - 0.5 ], [ i + 0.5, j + 0.5 ] ] } } } )
.count() )
}
}

0 comments on commit 8ebc664

Please sign in to comment.