Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

allow multiple locations to be nested in different subdocuments SERVE…

…R-838
  • Loading branch information...
commit f45587a30a9537398c01d2a666f7e3f72cd26948 1 parent d6c8b5e
Greg Studer authored March 28, 2011
6  bson/bsonobj.h
@@ -152,9 +152,11 @@ namespace mongo {
152 152
             return getFieldDotted( name.c_str() );
153 153
         }
154 154
 
155  
-        /** Like getFieldDotted(), but expands multikey arrays and returns all matching objects
  155
+        /** Like getFieldDotted(), but expands multikey arrays and returns all matching objects.
  156
+         *  Turning off expandLastArray allows you to retrieve nested array objects instead of
  157
+         *  their contents.
156 158
          */
157  
-        void getFieldsDotted(const StringData& name, BSONElementSet &ret ) const;
  159
+        void getFieldsDotted(const StringData& name, BSONElementSet &ret, bool expandLastArray = true ) const;
158 160
         /** Like getFieldDotted(), but returns first array encountered while traversing the
159 161
             dotted fields of name.  The name variable is updated to represent field
160 162
             names with respect to the returned element. */
131  db/geo/2d.cpp
@@ -155,87 +155,100 @@ namespace mongo {
155 155
         }
156 156
 
157 157
         virtual void getKeys( const BSONObj &obj, BSONObjSetDefaultOrder &keys ) const {
158  
-            BSONElement geo = obj.getFieldDotted(_geo.c_str());
159  
-            if ( geo.eoo() )
160  
-                return;
161 158
 
162  
-            //
163  
-            // Grammar for location lookup:
164  
-            // locs ::= [loc,loc,...,loc]|{<k>:loc,<k>:loc}|loc
165  
-            // loc  ::= { <k1> : #, <k2> : # }|[#, #]|{}
166  
-            //
167  
-            // Empty locations are ignored, preserving single-location semantics
168  
-            //
  159
+            BSONElementSet bSet;
  160
+            // Get all the nested location fields, but don't return individual elements from
  161
+            // the last array, if it exists.
  162
+            obj.getFieldsDotted(_geo.c_str(), bSet, false);
169 163
 
170  
-            if ( ! geo.isABSONObj() )
  164
+            if( bSet.empty() )
171 165
                 return;
172 166
 
173  
-            BSONObj embed = geo.embeddedObject();
174  
-            if ( embed.isEmpty() )
175  
-                return;
  167
+            for( BSONElementSet::iterator setI = bSet.begin(); setI != bSet.end(); ++setI ) {
176 168
 
177  
-            // Differentiate between location arrays and locations
178  
-            // by seeing if the first element value is a number
179  
-            bool singleElement = embed.firstElement().isNumber();
  169
+                BSONElement geo = *setI;
180 170
 
181  
-            BSONObjIterator oi(embed);
  171
+                GEODEBUG( "Element " << geo << " found for query " << _geo.c_str() );
182 172
 
183  
-            while( oi.more() ) {
  173
+                if ( geo.eoo() || ! geo.isABSONObj() )
  174
+                    continue;
184 175
 
185  
-                BSONObj locObj;
  176
+                //
  177
+                // Grammar for location lookup:
  178
+                // locs ::= [loc,loc,...,loc]|{<k>:loc,<k>:loc}|loc
  179
+                // loc  ::= { <k1> : #, <k2> : # }|[#, #]|{}
  180
+                //
  181
+                // Empty locations are ignored, preserving single-location semantics
  182
+                //
186 183
 
187  
-                if( singleElement ) locObj = embed;
188  
-                else {
189  
-                    BSONElement locElement = oi.next();
  184
+                BSONObj embed = geo.embeddedObject();
  185
+                if ( embed.isEmpty() )
  186
+                    continue;
190 187
 
191  
-                    uassert( 13654, str::stream() << "location object expected, location array not in correct format",
192  
-                             locElement.isABSONObj() );
  188
+                // Differentiate between location arrays and locations
  189
+                // by seeing if the first element value is a number
  190
+                bool singleElement = embed.firstElement().isNumber();
193 191
 
194  
-                    locObj = locElement.embeddedObject();
  192
+                BSONObjIterator oi(embed);
195 193
 
196  
-                    if( locObj.isEmpty() )
197  
-                        continue;
198  
-                }
  194
+                while( oi.more() ) {
199 195
 
200  
-                BSONObjBuilder b(64);
  196
+                    BSONObj locObj;
201 197
 
202  
-                _hash( locObj ).append( b , "" );
  198
+                    if( singleElement ) locObj = embed;
  199
+                    else {
  200
+                        BSONElement locElement = oi.next();
203 201
 
204  
-                // Go through all the other index keys
205  
-                for ( vector<string>::const_iterator i = _other.begin(); i != _other.end(); ++i ) {
  202
+                        uassert( 13654, str::stream() << "location object expected, location array not in correct format",
  203
+                                 locElement.isABSONObj() );
206 204
 
207  
-                    // Get *all* fields for the index key
208  
-                    BSONElementSet eSet;
209  
-                    obj.getFieldsDotted( *i, eSet );
  205
+                        locObj = locElement.embeddedObject();
210 206
 
  207
+                        if( locObj.isEmpty() )
  208
+                            continue;
  209
+                    }
211 210
 
212  
-                    if ( eSet.size() == 0 )
213  
-                        b.appendAs( _spec->missingField(), "" );
214  
-                    else if ( eSet.size() == 1 )
215  
-                        b.appendAs( *(eSet.begin()), "" );
216  
-                    else {
  211
+                    BSONObjBuilder b(64);
217 212
 
218  
-                        // If we have more than one key, store as an array of the objects
  213
+                    _hash( locObj ).append( b , "" );
219 214
 
220  
-                        BSONArrayBuilder aBuilder;
  215
+                    // Go through all the other index keys
  216
+                    for ( vector<string>::const_iterator i = _other.begin(); i != _other.end(); ++i ) {
221 217
 
222  
-                        for( BSONElementSet::iterator ei = eSet.begin(); ei != eSet.end(); ++ei ) {
223  
-                            aBuilder.append( *ei );
224  
-                        }
  218
+                        // Get *all* fields for the index key
  219
+                        BSONElementSet eSet;
  220
+                        obj.getFieldsDotted( *i, eSet );
225 221
 
226  
-                        BSONArray arr = aBuilder.arr();
227 222
 
228  
-                        b.append( "", arr );
  223
+                        if ( eSet.size() == 0 )
  224
+                            b.appendAs( _spec->missingField(), "" );
  225
+                        else if ( eSet.size() == 1 )
  226
+                            b.appendAs( *(eSet.begin()), "" );
  227
+                        else {
229 228
 
230  
-                    }
  229
+                            // If we have more than one key, store as an array of the objects
231 230
 
232  
-                }
  231
+                            BSONArrayBuilder aBuilder;
  232
+
  233
+                            for( BSONElementSet::iterator ei = eSet.begin(); ei != eSet.end(); ++ei ) {
  234
+                                aBuilder.append( *ei );
  235
+                            }
  236
+
  237
+                            BSONArray arr = aBuilder.arr();
  238
+
  239
+                            b.append( "", arr );
233 240
 
234  
-                keys.insert( b.obj() );
  241
+                        }
  242
+
  243
+                    }
235 244
 
236  
-                if( singleElement ) break;
  245
+                    keys.insert( b.obj() );
237 246
 
  247
+                    if( singleElement ) break;
  248
+
  249
+                }
238 250
             }
  251
+
239 252
         }
240 253
 
241 254
         GeoHash _tohash( const BSONElement& e ) const {
@@ -765,7 +778,8 @@ namespace mongo {
765 778
 
766 779
             // Remember match results for each object
767 780
             map<DiskLoc, bool>::iterator match = _matched.find( node.recordLoc );
768  
-            if( match == _matched.end() ) {
  781
+            bool newDoc = match == _matched.end();
  782
+            if( newDoc ) {
769 783
 
770 784
                 // matcher
771 785
                 MatchDetails details;
@@ -792,11 +806,11 @@ namespace mongo {
792 806
                 return;
793 807
             }
794 808
 
795  
-            addSpecific( node , d );
  809
+            addSpecific( node , d, newDoc );
796 810
             _found++;
797 811
         }
798 812
 
799  
-        virtual void addSpecific( const KeyNode& node , double d ) = 0;
  813
+        virtual void addSpecific( const KeyNode& node , double d, bool newDoc ) = 0;
800 814
         virtual bool checkDistance( const GeoHash& node , double& d ) = 0;
801 815
 
802 816
         long long found() const {
@@ -838,7 +852,7 @@ namespace mongo {
838 852
             return good;
839 853
         }
840 854
 
841  
-        virtual void addSpecific( const KeyNode& node , double d ) {
  855
+        virtual void addSpecific( const KeyNode& node , double d, bool newDoc ) {
842 856
             GEODEBUG( "\t\t" << GeoHash( node.key.firstElement() ) << "\t" << node.recordLoc.obj() << "\t" << d );
843 857
             _points.insert( GeoPoint( node.key , node.recordLoc , d ) );
844 858
             if ( _points.size() > _max ) {
@@ -1278,7 +1292,10 @@ namespace mongo {
1278 1292
         virtual bool moreToDo() = 0;
1279 1293
         virtual void fillStack() = 0;
1280 1294
 
1281  
-        virtual void addSpecific( const KeyNode& node , double d ) {
  1295
+        virtual void addSpecific( const KeyNode& node , double d, bool newDoc ) {
  1296
+
  1297
+            if( ! newDoc ) return;
  1298
+
1282 1299
             if ( _cur.isEmpty() )
1283 1300
                 _cur = GeoPoint( node , d );
1284 1301
             else
12  db/jsobj.cpp
@@ -690,7 +690,7 @@ namespace mongo {
690 690
         return -1;
691 691
     }
692 692
 
693  
-    void BSONObj::getFieldsDotted(const StringData& name, BSONElementSet &ret ) const {
  693
+    void BSONObj::getFieldsDotted(const StringData& name, BSONElementSet &ret, bool expandLastArray ) const {
694 694
         BSONElement e = getField( name );
695 695
         if ( e.eoo() ) {
696 696
             const char *p = strchr(name.data(), '.');
@@ -700,7 +700,7 @@ namespace mongo {
700 700
                 BSONElement e = getField( left.c_str() );
701 701
 
702 702
                 if (e.type() == Object) {
703  
-                    e.embeddedObject().getFieldsDotted(next, ret);
  703
+                    e.embeddedObject().getFieldsDotted(next, ret, expandLastArray );
704 704
                 }
705 705
                 else if (e.type() == Array) {
706 706
                     bool allDigits = false;
@@ -711,14 +711,14 @@ namespace mongo {
711 711
                         allDigits = (*temp == '.' || *temp == '\0');
712 712
                     }
713 713
                     if (allDigits) {
714  
-                        e.embeddedObject().getFieldsDotted(next, ret);
  714
+                        e.embeddedObject().getFieldsDotted(next, ret, expandLastArray );
715 715
                     }
716 716
                     else {
717 717
                         BSONObjIterator i(e.embeddedObject());
718 718
                         while ( i.more() ) {
719 719
                             BSONElement e2 = i.next();
720 720
                             if (e2.type() == Object || e2.type() == Array)
721  
-                                e2.embeddedObject().getFieldsDotted(next, ret);
  721
+                                e2.embeddedObject().getFieldsDotted(next, ret, expandLastArray );
722 722
                         }
723 723
                     }
724 724
                 }
@@ -728,7 +728,7 @@ namespace mongo {
728 728
             }
729 729
         }
730 730
         else {
731  
-            if (e.type() == Array) {
  731
+            if (e.type() == Array && expandLastArray) {
732 732
                 BSONObjIterator i(e.embeddedObject());
733 733
                 while ( i.more() )
734 734
                     ret.insert(i.next());
@@ -741,7 +741,7 @@ namespace mongo {
741 741
 
742 742
     BSONElement BSONObj::getFieldDottedOrArray(const char *&name) const {
743 743
         const char *p = strchr(name, '.');
744  
-        
  744
+
745 745
         BSONElement sub;
746 746
 
747 747
         if ( p ) {
7  jstests/geo_array2.js
@@ -77,7 +77,7 @@ for( var i = -1; i < 2; i++ ){
77 77
 			assert.eq( objCount , objsFound[q] )
78 78
 		}
79 79
 		
80  
-		
  80
+				
81 81
 		// Do nearSphere check
82 82
 		
83 83
 		// Earth Radius
@@ -103,6 +103,11 @@ for( var i = -1; i < 2; i++ ){
103 103
 		
104 104
 		
105 105
 		
  106
+		// Within results do not return duplicate documents
  107
+		
  108
+		var count = i == 0 && j == 0 ? 9 : 1
  109
+		var objCount = i == 0 && j == 0 ? 1 : 1
  110
+		
106 111
 		// Do within check
107 112
 		objsFound = {}
108 113
 		
63  jstests/geo_multinest0.js
... ...
@@ -0,0 +1,63 @@
  1
+// Make sure nesting of location arrays also works.
  2
+
  3
+t = db.geonest
  4
+t.drop();
  5
+
  6
+t.insert( { zip : "10001", data : [ { loc : [ 10, 10 ], type : "home" }, 
  7
+									{ loc : [ 50, 50 ], type : "work" } ] } )
  8
+t.insert( { zip : "10002", data : [ { loc : [ 20, 20 ], type : "home" }, 
  9
+									{ loc : [ 50, 50 ], type : "work" } ] } )
  10
+t.insert( { zip : "10003", data : [ { loc : [ 30, 30 ], type : "home" }, 
  11
+									{ loc : [ 50, 50 ], type : "work" } ] } )
  12
+assert.isnull( db.getLastError() )
  13
+
  14
+t.ensureIndex( { "data.loc" : "2d", zip : 1 } );
  15
+assert.isnull( db.getLastError() )
  16
+assert.eq( 2, t.getIndexKeys().length )
  17
+
  18
+t.insert( { zip : "10004", data : [ { loc : [ 40, 40 ], type : "home" }, 
  19
+									{ loc : [ 50, 50 ], type : "work" } ] } )
  20
+assert.isnull( db.getLastError() )
  21
+
  22
+// test normal access
  23
+
  24
+printjson( t.find( { "data.loc" : { $within : { $box : [ [ 0, 0 ], [ 45, 45 ] ] } } } ).toArray() )
  25
+
  26
+assert.eq( 4, t.find( { "data.loc" : { $within : { $box : [ [ 0, 0 ], [ 45, 45 ] ] } } } ).count() );
  27
+
  28
+assert.eq( 4, t.find( { "data.loc" : { $within : { $box : [ [ 45, 45 ], [ 50, 50 ] ] } } } ).count() );
  29
+
  30
+
  31
+
  32
+
  33
+
  34
+// Try a complex nesting
  35
+
  36
+t = db.geonest
  37
+t.drop();
  38
+
  39
+t.insert( { zip : "10001", data : [ { loc : [ [ 10, 10 ], { lat : 50, long : 50 } ], type : "home" } ] } )
  40
+t.insert( { zip : "10002", data : [ { loc : [ 20, 20 ], type : "home" }, 
  41
+									{ loc : [ 50, 50 ], type : "work" } ] } )
  42
+t.insert( { zip : "10003", data : [ { loc : [ { x : 30, y : 30 }, [ 50, 50 ] ], type : "home" } ] } )
  43
+assert.isnull( db.getLastError() )
  44
+
  45
+t.ensureIndex( { "data.loc" : "2d", zip : 1 } );
  46
+assert.isnull( db.getLastError() )
  47
+assert.eq( 2, t.getIndexKeys().length )
  48
+
  49
+t.insert( { zip : "10004", data : [ { loc : [ 40, 40 ], type : "home" }, 
  50
+									{ loc : [ 50, 50 ], type : "work" } ] } )
  51
+
  52
+									
  53
+assert.isnull( db.getLastError() )
  54
+
  55
+// test normal access
  56
+printjson( t.find( { "data.loc" : { $within : { $box : [ [ 0, 0 ], [ 45, 45 ] ] } } } ).toArray() )
  57
+
  58
+assert.eq( 4, t.find( { "data.loc" : { $within : { $box : [ [ 0, 0 ], [ 45, 45 ] ] } } } ).count() );
  59
+
  60
+assert.eq( 4, t.find( { "data.loc" : { $within : { $box : [ [ 45, 45 ], [ 50, 50 ] ] } } } ).count() );
  61
+
  62
+
  63
+
37  jstests/geo_multinest1.js
... ...
@@ -0,0 +1,37 @@
  1
+// Test distance queries with interleaved distances
  2
+
  3
+t = db.multinest
  4
+t.drop();
  5
+
  6
+t.insert( { zip : "10001", data : [ { loc : [ 10, 10 ], type : "home" }, 
  7
+									{ loc : [ 29, 29 ], type : "work" } ] } )
  8
+t.insert( { zip : "10002", data : [ { loc : [ 20, 20 ], type : "home" }, 
  9
+									{ loc : [ 39, 39 ], type : "work" } ] } )
  10
+t.insert( { zip : "10003", data : [ { loc : [ 30, 30 ], type : "home" }, 
  11
+									{ loc : [ 49, 49 ], type : "work" } ] } )
  12
+assert.isnull( db.getLastError() )
  13
+
  14
+t.ensureIndex( { "data.loc" : "2d", zip : 1 } );
  15
+assert.isnull( db.getLastError() )
  16
+assert.eq( 2, t.getIndexKeys().length )
  17
+
  18
+t.insert( { zip : "10004", data : [ { loc : [ 40, 40 ], type : "home" }, 
  19
+									{ loc : [ 59, 59 ], type : "work" } ] } )
  20
+assert.isnull( db.getLastError() )
  21
+
  22
+// test normal access
  23
+
  24
+var result = t.find({ "data.loc" : { $near : [0, 0] } }).toArray();
  25
+
  26
+printjson( result )
  27
+
  28
+assert.eq( 8, result.length )
  29
+
  30
+var order = [ 1, 2, 1, 3, 2, 4, 3, 4 ]
  31
+
  32
+for( var i = 0; i < result.length; i++ ){
  33
+	assert.eq( "1000" + order[i], result[i].zip )
  34
+}
  35
+
  36
+
  37
+

0 notes on commit f45587a

Please sign in to comment.
Something went wrong with that request. Please try again.