PageRenderTime 53ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/src/mongo/shell/query.js

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