1 // query.js
  2 
  3 if ( typeof DBQuery == "undefined" ){
  4     DBQuery = function( mongo , db , collection , ns , query , fields , limit , skip , batchSize , options ){
  5         
  6         this._mongo = mongo; // 0
  7         this._db = db; // 1
  8         this._collection = collection; // 2
  9         this._ns = ns; // 3
 10         
 11         this._query = query || {}; // 4
 12         this._fields = fields; // 5
 13         this._limit = limit || 0; // 6
 14         this._skip = skip || 0; // 7
 15         this._batchSize = batchSize || 0;
 16         this._options = options || 0;
 17 
 18         this._cursor = null;
 19         this._numReturned = 0;
 20         this._special = false;
 21         this._prettyShell = false;
 22     }
 23     print( "DBQuery probably won't have array access " );
 24 }
 25 
 26 DBQuery.prototype.help = function () {
 27     print("find() modifiers")
 28     print("\t.sort( {...} )")
 29     print("\t.limit( n )")
 30     print("\t.skip( n )")
 31     print("\t.count() - total # of objects matching query, ignores skip,limit")
 32     print("\t.size() - total # of objects cursor would return, honors skip,limit")
 33     print("\t.explain([verbose])")
 34     print("\t.hint(...)")
 35     print("\t.showDiskLoc() - adds a $diskLoc field to each returned object")
 36     print("\nCursor methods");
 37     print("\t.forEach( func )")
 38     print("\t.print() - output to console in full pretty format")
 39     print("\t.map( func )")
 40     print("\t.hasNext()")
 41     print("\t.next()")
 42 }
 43 
 44 DBQuery.prototype.clone = function(){
 45     var q =  new DBQuery( this._mongo , this._db , this._collection , this._ns , 
 46         this._query , this._fields , 
 47         this._limit , this._skip , this._batchSize , this._options );
 48     q._special = this._special;
 49     return q;
 50 }
 51 
 52 DBQuery.prototype._ensureSpecial = function(){
 53     if ( this._special )
 54         return;
 55     
 56     var n = { query : this._query };
 57     this._query = n;
 58     this._special = true;
 59 }
 60 
 61 DBQuery.prototype._checkModify = function(){
 62     if ( this._cursor )
 63         throw "query already executed";
 64 }
 65 
 66 DBQuery.prototype._exec = function(){
 67     if ( ! this._cursor ){
 68         assert.eq( 0 , this._numReturned );
 69         this._cursor = this._mongo.find( this._ns , this._query , this._fields , this._limit , this._skip , this._batchSize , this._options );
 70         this._cursorSeen = 0;
 71     }
 72     return this._cursor;
 73 }
 74 
 75 DBQuery.prototype.limit = function( limit ){
 76     this._checkModify();
 77     this._limit = limit;
 78     return this;
 79 }
 80 
 81 DBQuery.prototype.batchSize = function( batchSize ){
 82     this._checkModify();
 83     this._batchSize = batchSize;
 84     return this;
 85 }
 86 
 87 
 88 DBQuery.prototype.addOption = function( option ){
 89     this._options |= option;
 90     return this;
 91 }
 92 
 93 DBQuery.prototype.skip = function( skip ){
 94     this._checkModify();
 95     this._skip = skip;
 96     return this;
 97 }
 98 
 99 DBQuery.prototype.hasNext = function(){
100     this._exec();
101 
102     if ( this._limit > 0 && this._cursorSeen >= this._limit )
103         return false;
104     var o = this._cursor.hasNext();
105     return o;
106 }
107 
108 DBQuery.prototype.next = function(){
109     this._exec();
110     
111     var o = this._cursor.hasNext();
112     if ( o )
113         this._cursorSeen++;
114     else
115         throw "error hasNext: " + o;
116     
117     var ret = this._cursor.next();
118     if ( ret.$err && this._numReturned == 0 && ! this.hasNext() )
119         throw "error: " + tojson( ret );
120 
121     this._numReturned++;
122     return ret;
123 }
124 
125 DBQuery.prototype.objsLeftInBatch = function(){
126     this._exec();
127 
128     var ret = this._cursor.objsLeftInBatch();
129     if ( ret.$err )
130         throw "error: " + tojson( ret );
131 
132     return ret;
133 }
134 
135 DBQuery.prototype.toArray = function(){
136     if ( this._arr )
137         return this._arr;
138     
139     var a = [];
140     while ( this.hasNext() )
141         a.push( this.next() );
142     this._arr = a;
143     return a;
144 }
145 
146 DBQuery.prototype.count = function( applySkipLimit ){
147     var cmd = { count: this._collection.getName() };
148     if ( this._query ){
149         if ( this._special )
150             cmd.query = this._query.query;
151         else 
152             cmd.query = this._query;
153     }
154     cmd.fields = this._fields || {};
155 
156     if ( applySkipLimit ){
157         if ( this._limit )
158             cmd.limit = this._limit;
159         if ( this._skip )
160             cmd.skip = this._skip;
161     }
162     
163     var res = this._db.runCommand( cmd );
164     if( res && res.n != null ) return res.n;
165     throw "count failed: " + tojson( res );
166 }
167 
168 DBQuery.prototype.size = function(){
169     return this.count( true );
170 }
171 
172 DBQuery.prototype.countReturn = function(){
173     var c = this.count();
174 
175     if ( this._skip )
176         c = c - this._skip;
177 
178     if ( this._limit > 0 && this._limit < c )
179         return this._limit;
180     
181     return c;
182 }
183 
184 /**
185 * iterative count - only for testing
186 */
187 DBQuery.prototype.itcount = function(){
188     var num = 0;
189     while ( this.hasNext() ){
190         num++;
191         this.next();
192     }
193     return num;
194 }
195 
196 DBQuery.prototype.length = function(){
197     return this.toArray().length;
198 }
199 
200 DBQuery.prototype._addSpecial = function( name , value ){
201     this._ensureSpecial();
202     this._query[name] = value;
203     return this;
204 }
205 
206 DBQuery.prototype.sort = function( sortBy ){
207     return this._addSpecial( "orderby" , sortBy );
208 }
209 
210 DBQuery.prototype.hint = function( hint ){
211     return this._addSpecial( "$hint" , hint );
212 }
213 
214 DBQuery.prototype.min = function( min ) {
215     return this._addSpecial( "$min" , min );
216 }
217 
218 DBQuery.prototype.max = function( max ) {
219     return this._addSpecial( "$max" , max );
220 }
221 
222 DBQuery.prototype.showDiskLoc = function() {
223     return this._addSpecial( "$showDiskLoc" , true);
224 }
225 
226 DBQuery.prototype.forEach = function( func ){
227     while ( this.hasNext() )
228         func( this.next() );
229 }
230 
231 DBQuery.prototype.map = function( func ){
232     var a = [];
233     while ( this.hasNext() )
234         a.push( func( this.next() ) );
235     return a;
236 }
237 
238 DBQuery.prototype.arrayAccess = function( idx ){
239     return this.toArray()[idx];
240 }
241 
242 DBQuery.prototype.explain = function (verbose) {
243     /* verbose=true --> include allPlans, oldPlan fields */
244     var n = this.clone();
245     n._ensureSpecial();
246     n._query.$explain = true;
247     n._limit = Math.abs(n._limit) * -1;
248     var e = n.next();
249 
250     function cleanup(obj){
251         if (typeof(obj) != 'object'){
252             return;
253         }
254 
255         delete obj.allPlans;
256         delete obj.oldPlan;
257 
258         if (typeof(obj.length) == 'number'){
259             for (var i=0; i < obj.length; i++){
260                 cleanup(obj[i]);
261             }
262         }
263 
264         if (obj.shards){
265             for (var key in obj.shards){
266                 cleanup(obj.shards[key]);
267             }
268         }
269 
270         if (obj.clauses){
271             cleanup(obj.clauses);
272         }
273     }
274 
275     if (!verbose)
276         cleanup(e);
277 
278     return e;
279 }
280 
281 DBQuery.prototype.snapshot = function(){
282     this._ensureSpecial();
283     this._query.$snapshot = true;
284     return this;
285 }
286 
287 DBQuery.prototype.pretty = function(){
288     this._prettyShell = true;
289     return this;
290 }
291 
292 DBQuery.prototype.shellPrint = function(){
293     try {
294         var n = 0;
295         while ( this.hasNext() && n < DBQuery.shellBatchSize ){
296             var s = this._prettyShell ? tojson( this.next() ) : tojson( this.next() , "" , true );
297             print( s );
298             n++;
299         }
300         if ( this.hasNext() ){
301             print( "has more" );
302             ___it___  = this;
303         }
304         else {
305             ___it___  = null;
306         }
307     }
308     catch ( e ){
309         print( e );
310     }
311     
312 }
313 
314 DBQuery.prototype.toString = function(){
315     return "DBQuery: " + this._ns + " -> " + tojson( this.query );
316 }
317 
318 DBQuery.shellBatchSize = 20;
319