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(<predicate>, <projection>) modifiers");
 28     print("\t.sort({...})");
 29     print("\t.limit(<n>)");
 30     print("\t.skip(<n>)");
 31     print("\t.batchSize(<n>) - sets the number of docs to return per getMore");
 32     print("\t.collation({...})");
 33     print("\t.hint({...})");
 34     print("\t.readConcern(<level>)");
 35     print("\t.readPref(<mode>, <tagset>)");
 36     print(
 37         "\t.count(<applySkipLimit>) - total # of objects matching query. by default ignores skip,limit");
 38     print("\t.size() - total # of objects cursor would return, honors skip,limit");
 39     print(
 40         "\t.explain(<verbosity>) - accepted verbosities are {'queryPlanner', 'executionStats', 'allPlansExecution'}");
 41     print("\t.min({...})");
 42     print("\t.max({...})");
 43     print("\t.maxScan(<n>)");
 44     print("\t.maxTimeMS(<n>)");
 45     print("\t.comment(<comment>)");
 46     print("\t.snapshot()");
 47     print("\t.tailable(<isAwaitData>)");
 48     print("\t.noCursorTimeout()");
 49     print("\t.allowPartialResults()");
 50     print("\t.returnKey()");
 51     print("\t.showRecordId() - adds a $recordId field to each returned object");
 52 
 53     print("\nCursor methods");
 54     print("\t.toArray() - iterates through docs and returns an array of the results");
 55     print("\t.forEach(<func>)");
 56     print("\t.map(<func>)");
 57     print("\t.hasNext()");
 58     print("\t.next()");
 59     print("\t.close()");
 60     print(
 61         "\t.objsLeftInBatch() - returns count of docs left in current batch (when exhausted, a new getMore will be issued)");
 62     print("\t.itcount() - iterates through documents and counts them");
 63     print(
 64         "\t.getQueryPlan() - get query plans associated with shape. To get more info on query plans, " +
 65         "call getQueryPlan().help().");
 66     print("\t.pretty() - pretty print each document, possibly over multiple lines");
 67 };
 68 
 69 DBQuery.prototype.clone = function() {
 70     var q = new DBQuery(this._mongo,
 71                         this._db,
 72                         this._collection,
 73                         this._ns,
 74                         this._query,
 75                         this._fields,
 76                         this._limit,
 77                         this._skip,
 78                         this._batchSize,
 79                         this._options);
 80     q._special = this._special;
 81     return q;
 82 };
 83 
 84 DBQuery.prototype._ensureSpecial = function() {
 85     if (this._special)
 86         return;
 87 
 88     var n = {query: this._query};
 89     this._query = n;
 90     this._special = true;
 91 };
 92 
 93 DBQuery.prototype._checkModify = function() {
 94     if (this._cursor)
 95         throw Error("query already executed");
 96 };
 97 
 98 DBQuery.prototype._canUseFindCommand = function() {
 99     // Since runCommand() is implemented by running a findOne() against the $cmd collection, we have
100     // to make sure that we don't try to run a find command against the $cmd collection.
101     //
102     // We also forbid queries with the exhaust option from running as find commands, because the
103     // find command does not support exhaust.
104     return (this._collection.getName().indexOf("$cmd") !== 0) &&
105         (this._options & DBQuery.Option.exhaust) === 0;
106 };
107 
108 DBQuery.prototype._exec = function() {
109     if (!this._cursor) {
110         assert.eq(0, this._numReturned);
111         this._cursorSeen = 0;
112 
113         if (this._mongo.useReadCommands() && this._canUseFindCommand()) {
114             var canAttachReadPref = true;
115             var findCmd = this._convertToCommand(canAttachReadPref);
116             var cmdRes = this._db.runReadCommand(findCmd, null, this._options);
117             this._cursor = new DBCommandCursor(this._mongo, cmdRes, this._batchSize);
118         } else {
119             if (this._special && this._query.readConcern) {
120                 throw new Error("readConcern requires use of read commands");
121             }
122 
123             if (this._special && this._query.collation) {
124                 throw new Error("collation requires use of read commands");
125             }
126 
127             this._cursor = this._mongo.find(this._ns,
128                                             this._query,
129                                             this._fields,
130                                             this._limit,
131                                             this._skip,
132                                             this._batchSize,
133                                             this._options);
134         }
135     }
136     return this._cursor;
137 };
138 
139 /**
140  * Internal helper used to convert this cursor into the format required by the find command.
141  *
142  * If canAttachReadPref is true, may attach a read preference to the resulting command using the
143  * "wrapped form": { $query: { <cmd>: ... }, $readPreference: { ... } }.
144  */
145 DBQuery.prototype._convertToCommand = function(canAttachReadPref) {
146     var cmd = {};
147 
148     cmd["find"] = this._collection.getName();
149 
150     if (this._special) {
151         if (this._query.query) {
152             cmd["filter"] = this._query.query;
153         }
154     } else if (this._query) {
155         cmd["filter"] = this._query;
156     }
157 
158     if (this._skip) {
159         cmd["skip"] = this._skip;
160     }
161 
162     if (this._batchSize) {
163         if (this._batchSize < 0) {
164             cmd["batchSize"] = -this._batchSize;
165             cmd["singleBatch"] = true;
166         } else {
167             cmd["batchSize"] = this._batchSize;
168         }
169     }
170 
171     if (this._limit) {
172         if (this._limit < 0) {
173             cmd["limit"] = -this._limit;
174             cmd["singleBatch"] = true;
175         } else {
176             cmd["limit"] = this._limit;
177             cmd["singleBatch"] = false;
178         }
179     }
180 
181     if ("orderby" in this._query) {
182         cmd["sort"] = this._query.orderby;
183     }
184 
185     if (this._fields) {
186         cmd["projection"] = this._fields;
187     }
188 
189     if ("$hint" in this._query) {
190         cmd["hint"] = this._query.$hint;
191     }
192 
193     if ("$comment" in this._query) {
194         cmd["comment"] = this._query.$comment;
195     }
196 
197     if ("$maxScan" in this._query) {
198         cmd["maxScan"] = this._query.$maxScan;
199     }
200 
201     if ("$maxTimeMS" in this._query) {
202         cmd["maxTimeMS"] = this._query.$maxTimeMS;
203     }
204 
205     if ("$max" in this._query) {
206         cmd["max"] = this._query.$max;
207     }
208 
209     if ("$min" in this._query) {
210         cmd["min"] = this._query.$min;
211     }
212 
213     if ("$returnKey" in this._query) {
214         cmd["returnKey"] = this._query.$returnKey;
215     }
216 
217     if ("$showDiskLoc" in this._query) {
218         cmd["showRecordId"] = this._query.$showDiskLoc;
219     }
220 
221     if ("$snapshot" in this._query) {
222         cmd["snapshot"] = this._query.$snapshot;
223     }
224 
225     if ("readConcern" in this._query) {
226         cmd["readConcern"] = this._query.readConcern;
227     }
228 
229     if ("collation" in this._query) {
230         cmd["collation"] = this._query.collation;
231     }
232 
233     if ((this._options & DBQuery.Option.tailable) != 0) {
234         cmd["tailable"] = true;
235     }
236 
237     if ((this._options & DBQuery.Option.oplogReplay) != 0) {
238         cmd["oplogReplay"] = true;
239     }
240 
241     if ((this._options & DBQuery.Option.noTimeout) != 0) {
242         cmd["noCursorTimeout"] = true;
243     }
244 
245     if ((this._options & DBQuery.Option.awaitData) != 0) {
246         cmd["awaitData"] = true;
247     }
248 
249     if ((this._options & DBQuery.Option.partial) != 0) {
250         cmd["allowPartialResults"] = true;
251     }
252 
253     if (canAttachReadPref) {
254         // If there is a readPreference, use the wrapped command form.
255         if ("$readPreference" in this._query) {
256             var prefObj = this._query.$readPreference;
257             cmd = this._db._attachReadPreferenceToCommand(cmd, prefObj);
258         }
259     }
260 
261     return cmd;
262 };
263 
264 DBQuery.prototype.limit = function(limit) {
265     this._checkModify();
266     this._limit = limit;
267     return this;
268 };
269 
270 DBQuery.prototype.batchSize = function(batchSize) {
271     this._checkModify();
272     this._batchSize = batchSize;
273     return this;
274 };
275 
276 DBQuery.prototype.addOption = function(option) {
277     this._options |= option;
278     return this;
279 };
280 
281 DBQuery.prototype.skip = function(skip) {
282     this._checkModify();
283     this._skip = skip;
284     return this;
285 };
286 
287 DBQuery.prototype.hasNext = function() {
288     this._exec();
289 
290     if (this._limit > 0 && this._cursorSeen >= this._limit) {
291         this._cursor.close();
292         return false;
293     }
294     var o = this._cursor.hasNext();
295     return o;
296 };
297 
298 DBQuery.prototype.next = function() {
299     this._exec();
300 
301     var o = this._cursor.hasNext();
302     if (o)
303         this._cursorSeen++;
304     else
305         throw Error("error hasNext: " + o);
306 
307     var ret = this._cursor.next();
308     if (ret.$err) {
309         throw _getErrorWithCode(ret, "error: " + tojson(ret));
310     }
311 
312     this._numReturned++;
313     return ret;
314 };
315 
316 DBQuery.prototype.objsLeftInBatch = function() {
317     this._exec();
318 
319     var ret = this._cursor.objsLeftInBatch();
320     if (ret.$err)
321         throw _getErrorWithCode(ret, "error: " + tojson(ret));
322 
323     return ret;
324 };
325 
326 DBQuery.prototype.readOnly = function() {
327     this._exec();
328     this._cursor.readOnly();
329     return this;
330 };
331 
332 DBQuery.prototype.toArray = function() {
333     if (this._arr)
334         return this._arr;
335 
336     var a = [];
337     while (this.hasNext())
338         a.push(this.next());
339     this._arr = a;
340     return a;
341 };
342 
343 DBQuery.prototype._convertToCountCmd = function(applySkipLimit) {
344     var cmd = {count: this._collection.getName()};
345 
346     if (this._query) {
347         if (this._special) {
348             cmd.query = this._query.query;
349             if (this._query.$maxTimeMS) {
350                 cmd.maxTimeMS = this._query.$maxTimeMS;
351             }
352             if (this._query.$hint) {
353                 cmd.hint = this._query.$hint;
354             }
355             if (this._query.readConcern) {
356                 cmd.readConcern = this._query.readConcern;
357             }
358             if (this._query.collation) {
359                 cmd.collation = this._query.collation;
360             }
361         } else {
362             cmd.query = this._query;
363         }
364     }
365     cmd.fields = this._fields || {};
366 
367     if (applySkipLimit) {
368         if (this._limit)
369             cmd.limit = this._limit;
370         if (this._skip)
371             cmd.skip = this._skip;
372     }
373 
374     return cmd;
375 };
376 
377 DBQuery.prototype.count = function(applySkipLimit) {
378     var cmd = this._convertToCountCmd(applySkipLimit);
379 
380     var res = this._db.runReadCommand(cmd);
381     if (res && res.n != null)
382         return res.n;
383     throw _getErrorWithCode(res, "count failed: " + tojson(res));
384 };
385 
386 DBQuery.prototype.size = function() {
387     return this.count(true);
388 };
389 
390 DBQuery.prototype.countReturn = function() {
391     var c = this.count();
392 
393     if (this._skip)
394         c = c - this._skip;
395 
396     if (this._limit > 0 && this._limit < c)
397         return this._limit;
398 
399     return c;
400 };
401 
402 /**
403 * iterative count - only for testing
404 */
405 DBQuery.prototype.itcount = function() {
406     var num = 0;
407 
408     // Track how many bytes we've used this cursor to iterate iterated.  This function can be called
409     // with some very large cursors.  SpiderMonkey appears happy to allow these objects to
410     // accumulate, so regular gc() avoids an overly large memory footprint.
411     //
412     // TODO: migrate this function into c++
413     var bytesSinceGC = 0;
414 
415     while (this.hasNext()) {
416         num++;
417         var nextDoc = this.next();
418         bytesSinceGC += Object.bsonsize(nextDoc);
419 
420         // Garbage collect every 10 MB.
421         if (bytesSinceGC > (10 * 1024 * 1024)) {
422             bytesSinceGC = 0;
423             gc();
424         }
425     }
426     return num;
427 };
428 
429 DBQuery.prototype.length = function() {
430     return this.toArray().length;
431 };
432 
433 DBQuery.prototype._addSpecial = function(name, value) {
434     this._ensureSpecial();
435     this._query[name] = value;
436     return this;
437 };
438 
439 DBQuery.prototype.sort = function(sortBy) {
440     return this._addSpecial("orderby", sortBy);
441 };
442 
443 DBQuery.prototype.hint = function(hint) {
444     return this._addSpecial("$hint", hint);
445 };
446 
447 DBQuery.prototype.min = function(min) {
448     return this._addSpecial("$min", min);
449 };
450 
451 DBQuery.prototype.max = function(max) {
452     return this._addSpecial("$max", max);
453 };
454 
455 /**
456  * Deprecated. Use showRecordId().
457  */
458 DBQuery.prototype.showDiskLoc = function() {
459     return this.showRecordId();
460 };
461 
462 DBQuery.prototype.showRecordId = function() {
463     return this._addSpecial("$showDiskLoc", true);
464 };
465 
466 DBQuery.prototype.maxTimeMS = function(maxTimeMS) {
467     return this._addSpecial("$maxTimeMS", maxTimeMS);
468 };
469 
470 DBQuery.prototype.readConcern = function(level) {
471     var readConcernObj = {level: level};
472 
473     return this._addSpecial("readConcern", readConcernObj);
474 };
475 
476 DBQuery.prototype.collation = function(collationSpec) {
477     return this._addSpecial("collation", collationSpec);
478 };
479 
480 /**
481  * Sets the read preference for this cursor.
482  *
483  * @param mode {string} read preference mode to use.
484  * @param tagSet {Array.<Object>} optional. The list of tags to use, order matters.
485  *     Note that this object only keeps a shallow copy of this array.
486  *
487  * @return this cursor
488  */
489 DBQuery.prototype.readPref = function(mode, tagSet) {
490     var readPrefObj = {mode: mode};
491 
492     if (tagSet) {
493         readPrefObj.tags = tagSet;
494     }
495 
496     return this._addSpecial("$readPreference", readPrefObj);
497 };
498 
499 DBQuery.prototype.forEach = function(func) {
500     while (this.hasNext())
501         func(this.next());
502 };
503 
504 DBQuery.prototype.map = function(func) {
505     var a = [];
506     while (this.hasNext())
507         a.push(func(this.next()));
508     return a;
509 };
510 
511 DBQuery.prototype.arrayAccess = function(idx) {
512     return this.toArray()[idx];
513 };
514 
515 DBQuery.prototype.comment = function(comment) {
516     return this._addSpecial("$comment", comment);
517 };
518 
519 DBQuery.prototype.explain = function(verbose) {
520     var explainQuery = new DBExplainQuery(this, verbose);
521     return explainQuery.finish();
522 };
523 
524 DBQuery.prototype.snapshot = function() {
525     return this._addSpecial("$snapshot", true);
526 };
527 
528 DBQuery.prototype.returnKey = function() {
529     return this._addSpecial("$returnKey", true);
530 };
531 
532 DBQuery.prototype.maxScan = function(n) {
533     return this._addSpecial("$maxScan", n);
534 };
535 
536 DBQuery.prototype.pretty = function() {
537     this._prettyShell = true;
538     return this;
539 };
540 
541 DBQuery.prototype.shellPrint = function() {
542     try {
543         var start = new Date().getTime();
544         var n = 0;
545         while (this.hasNext() && n < DBQuery.shellBatchSize) {
546             var s = this._prettyShell ? tojson(this.next()) : tojson(this.next(), "", true);
547             print(s);
548             n++;
549         }
550         if (typeof _verboseShell !== 'undefined' && _verboseShell) {
551             var time = new Date().getTime() - start;
552             print("Fetched " + n + " record(s) in " + time + "ms");
553         }
554         if (this.hasNext()) {
555             print("Type \"it\" for more");
556             ___it___ = this;
557         } else {
558             ___it___ = null;
559         }
560     } catch (e) {
561         print(e);
562     }
563 
564 };
565 
566 /**
567  * Returns a QueryPlan for the query.
568  */
569 DBQuery.prototype.getQueryPlan = function() {
570     return new QueryPlan(this);
571 };
572 
573 DBQuery.prototype.toString = function() {
574     return "DBQuery: " + this._ns + " -> " + tojson(this._query);
575 };
576 
577 //
578 // CRUD specification find cursor extension
579 //
580 
581 /**
582 * Get partial results from a mongos if some shards are down (instead of throwing an error).
583 *
584 * @method
585 * @see http://docs.mongodb.org/meta-driver/latest/legacy/mongodb-wire-protocol/#op-query
586 * @return {DBQuery}
587 */
588 DBQuery.prototype.allowPartialResults = function() {
589     this._checkModify();
590     this.addOption(DBQuery.Option.partial);
591     return this;
592 };
593 
594 /**
595 * The server normally times out idle cursors after an inactivity period (10 minutes)
596 * to prevent excess memory use. Set this option to prevent that.
597 *
598 * @method
599 * @see http://docs.mongodb.org/meta-driver/latest/legacy/mongodb-wire-protocol/#op-query
600 * @return {DBQuery}
601 */
602 DBQuery.prototype.noCursorTimeout = function() {
603     this._checkModify();
604     this.addOption(DBQuery.Option.noTimeout);
605     return this;
606 };
607 
608 /**
609 * Internal replication use only - driver should not set
610 *
611 * @method
612 * @see http://docs.mongodb.org/meta-driver/latest/legacy/mongodb-wire-protocol/#op-query
613 * @return {DBQuery}
614 */
615 DBQuery.prototype.oplogReplay = function() {
616     this._checkModify();
617     this.addOption(DBQuery.Option.oplogReplay);
618     return this;
619 };
620 
621 /**
622 * Limits the fields to return for all matching documents.
623 *
624 * @method
625 * @see http://docs.mongodb.org/manual/tutorial/project-fields-from-query-results/
626 * @param {object} document Document specifying the projection of the resulting documents.
627 * @return {DBQuery}
628 */
629 DBQuery.prototype.projection = function(document) {
630     this._checkModify();
631     this._fields = document;
632     return this;
633 };
634 
635 /**
636 * Specify cursor as a tailable cursor, allowing to specify if it will use awaitData
637 *
638 * @method
639 * @see http://docs.mongodb.org/manual/tutorial/create-tailable-cursor/
640 * @param {boolean} [awaitData=true] cursor blocks for a few seconds to wait for data if no documents
641 *found.
642 * @return {DBQuery}
643 */
644 DBQuery.prototype.tailable = function(awaitData) {
645     this._checkModify();
646     this.addOption(DBQuery.Option.tailable);
647 
648     // Set await data if either specifically set or not specified
649     if (awaitData || awaitData == null) {
650         this.addOption(DBQuery.Option.awaitData);
651     }
652 
653     return this;
654 };
655 
656 /**
657 * Specify a document containing modifiers for the query.
658 *
659 * @method
660 * @see http://docs.mongodb.org/manual/reference/operator/query-modifier/
661 * @param {object} document A document containing modifers to apply to the cursor.
662 * @return {DBQuery}
663 */
664 DBQuery.prototype.modifiers = function(document) {
665     this._checkModify();
666 
667     for (var name in document) {
668         if (name[0] != '$') {
669             throw new Error('All modifiers must start with a $ such as $maxScan or $returnKey');
670         }
671     }
672 
673     for (var name in document) {
674         this._addSpecial(name, document[name]);
675     }
676 
677     return this;
678 };
679 
680 DBQuery.prototype.close = function() {
681     this._cursor.close();
682 };
683 
684 DBQuery.shellBatchSize = 20;
685 
686 /**
687  * Query option flag bit constants.
688  * @see http://dochub.mongodb.org/core/mongowireprotocol#MongoWireProtocol-OPQUERY
689  */
690 DBQuery.Option = {
691     tailable: 0x2,
692     slaveOk: 0x4,
693     oplogReplay: 0x8,
694     noTimeout: 0x10,
695     awaitData: 0x20,
696     exhaust: 0x40,
697     partial: 0x80
698 };
699 
700 function DBCommandCursor(mongo, cmdResult, batchSize) {
701     if (cmdResult.ok != 1) {
702         throw _getErrorWithCode(cmdResult, "error: " + tojson(cmdResult));
703     }
704 
705     this._batch = cmdResult.cursor.firstBatch.reverse();  // modifies input to allow popping
706 
707     if (mongo.useReadCommands()) {
708         this._useReadCommands = true;
709         this._cursorid = cmdResult.cursor.id;
710         this._batchSize = batchSize;
711 
712         this._ns = cmdResult.cursor.ns;
713         this._db = mongo.getDB(this._ns.substr(0, this._ns.indexOf(".")));
714         this._collName = this._ns.substr(this._ns.indexOf(".") + 1);
715 
716         if (cmdResult.cursor.id) {
717             // Note that setting this._cursorid to 0 should be accompanied by
718             // this._cursorHandle.zeroCursorId().
719             this._cursorHandle = mongo.cursorHandleFromId(cmdResult.cursor.id);
720         }
721     } else {
722         this._cursor = mongo.cursorFromId(cmdResult.cursor.ns, cmdResult.cursor.id, batchSize);
723     }
724 }
725 
726 DBCommandCursor.prototype = {};
727 
728 DBCommandCursor.prototype.close = function() {
729     if (!this._useReadCommands) {
730         this._cursor.close();
731     } else if (this._cursorid != 0) {
732         var killCursorCmd = {
733             killCursors: this._collName,
734             cursors: [this._cursorid],
735         };
736         var cmdRes = this._db.runCommand(killCursorCmd);
737         if (cmdRes.ok != 1) {
738             throw _getErrorWithCode(cmdRes, "killCursors command failed: " + tojson(cmdRes));
739         }
740 
741         this._cursorHandle.zeroCursorId();
742         this._cursorid = NumberLong(0);
743     }
744 };
745 
746 /**
747  * Fills out this._batch by running a getMore command. If the cursor is exhausted, also resets
748  * this._cursorid to 0.
749  *
750  * Throws on error.
751  */
752 DBCommandCursor.prototype._runGetMoreCommand = function() {
753     // Construct the getMore command.
754     var getMoreCmd = {getMore: this._cursorid, collection: this._collName};
755 
756     if (this._batchSize) {
757         getMoreCmd["batchSize"] = this._batchSize;
758     }
759 
760     // Deliver the getMore command, and check for errors in the response.
761     var cmdRes = this._db.runCommand(getMoreCmd);
762     if (cmdRes.ok != 1) {
763         throw _getErrorWithCode(cmdRes, "getMore command failed: " + tojson(cmdRes));
764     }
765 
766     if (this._ns !== cmdRes.cursor.ns) {
767         throw Error("unexpected collection in getMore response: " + this._ns + " != " +
768                     cmdRes.cursor.ns);
769     }
770 
771     if (!cmdRes.cursor.id.compare(NumberLong("0"))) {
772         this._cursorHandle.zeroCursorId();
773         this._cursorid = NumberLong("0");
774     } else if (this._cursorid.compare(cmdRes.cursor.id)) {
775         throw Error("unexpected cursor id: " + this._cursorid.toString() + " != " +
776                     cmdRes.cursor.id.toString());
777     }
778 
779     // Successfully retrieved the next batch.
780     this._batch = cmdRes.cursor.nextBatch.reverse();
781 };
782 
783 DBCommandCursor.prototype._hasNextUsingCommands = function() {
784     assert(this._useReadCommands);
785 
786     if (!this._batch.length) {
787         if (!this._cursorid.compare(NumberLong("0"))) {
788             return false;
789         }
790 
791         this._runGetMoreCommand();
792     }
793 
794     return this._batch.length > 0;
795 };
796 
797 DBCommandCursor.prototype.hasNext = function() {
798     if (this._useReadCommands) {
799         return this._hasNextUsingCommands();
800     }
801 
802     return this._batch.length || this._cursor.hasNext();
803 };
804 
805 DBCommandCursor.prototype.next = function() {
806     if (this._batch.length) {
807         // $err wouldn't be in _firstBatch since ok was true.
808         return this._batch.pop();
809     } else if (this._useReadCommands) {
810         // Have to call hasNext() here, as this is where we may issue a getMore in order to retrieve
811         // the next batch of results.
812         if (!this.hasNext())
813             throw Error("error hasNext: false");
814         return this._batch.pop();
815     } else {
816         if (!this._cursor.hasNext())
817             throw Error("error hasNext: false");
818 
819         var ret = this._cursor.next();
820         if (ret.$err)
821             throw _getErrorWithCode(ret, "error: " + tojson(ret));
822         return ret;
823     }
824 };
825 DBCommandCursor.prototype.objsLeftInBatch = function() {
826     if (this._useReadCommands) {
827         return this._batch.length;
828     } else if (this._batch.length) {
829         return this._batch.length;
830     } else {
831         return this._cursor.objsLeftInBatch();
832     }
833 };
834 
835 DBCommandCursor.prototype.help = function() {
836     // This is the same as the "Cursor Methods" section of DBQuery.help().
837     print("\nCursor methods");
838     print("\t.toArray() - iterates through docs and returns an array of the results");
839     print("\t.forEach( func )");
840     print("\t.map( func )");
841     print("\t.hasNext()");
842     print("\t.next()");
843     print(
844         "\t.objsLeftInBatch() - returns count of docs left in current batch (when exhausted, a new getMore will be issued)");
845     print("\t.itcount() - iterates through documents and counts them");
846     print("\t.pretty() - pretty print each document, possibly over multiple lines");
847     print("\t.close()");
848 };
849 
850 // Copy these methods from DBQuery
851 DBCommandCursor.prototype.toArray = DBQuery.prototype.toArray;
852 DBCommandCursor.prototype.forEach = DBQuery.prototype.forEach;
853 DBCommandCursor.prototype.map = DBQuery.prototype.map;
854 DBCommandCursor.prototype.itcount = DBQuery.prototype.itcount;
855 DBCommandCursor.prototype.shellPrint = DBQuery.prototype.shellPrint;
856 DBCommandCursor.prototype.pretty = DBQuery.prototype.pretty;
857 
858 /**
859  * QueryCache
860  * Holds a reference to the cursor.
861  * Proxy for planCache* query shape-specific commands.
862  */
863 if ((typeof QueryPlan) == "undefined") {
864     QueryPlan = function(cursor) {
865         this._cursor = cursor;
866     };
867 }
868 
869 /**
870  * Name of QueryPlan.
871  * Same as collection.
872  */
873 QueryPlan.prototype.getName = function() {
874     return this._cursor._collection.getName();
875 };
876 
877 /**
878  * tojson prints the name of the collection
879  */
880 
881 QueryPlan.prototype.tojson = function(indent, nolint) {
882     return tojson(this.getPlans());
883 };
884 
885 /**
886  * Displays help for a PlanCache object.
887  */
888 QueryPlan.prototype.help = function() {
889     var shortName = this.getName();
890     print("QueryPlan help");
891     print("\t.help() - show QueryPlan help");
892     print("\t.clearPlans() - drops query shape from plan cache");
893     print("\t.getPlans() - displays the cached plans for a query shape");
894     return __magicNoPrint;
895 };
896 
897 /**
898  * List plans for a query shape.
899  */
900 QueryPlan.prototype.getPlans = function() {
901     return this._cursor._collection.getPlanCache().getPlansByQuery(this._cursor);
902 };
903 
904 /**
905  * Drop query shape from the plan cache.
906  */
907 QueryPlan.prototype.clearPlans = function() {
908     this._cursor._collection.getPlanCache().clearPlansByQuery(this._cursor);
909     return;
910 };
911