/node_modules/mongoose/node_modules/mongodb-core/lib/cursor.js

https://bitbucket.org/coleman333/smartsite · JavaScript · 794 lines · 476 code · 103 blank · 215 comment · 112 complexity · 24a4f12c8dbf74a5b559dcd38d73b376 MD5 · raw file

  1. 'use strict';
  2. var Logger = require('./connection/logger'),
  3. retrieveBSON = require('./connection/utils').retrieveBSON,
  4. MongoError = require('./error').MongoError,
  5. MongoNetworkError = require('./error').MongoNetworkError,
  6. f = require('util').format;
  7. var BSON = retrieveBSON(),
  8. Long = BSON.Long;
  9. /**
  10. * This is a cursor results callback
  11. *
  12. * @callback resultCallback
  13. * @param {error} error An error object. Set to null if no error present
  14. * @param {object} document
  15. */
  16. /**
  17. * @fileOverview The **Cursor** class is an internal class that embodies a cursor on MongoDB
  18. * allowing for iteration over the results returned from the underlying query.
  19. *
  20. * **CURSORS Cannot directly be instantiated**
  21. * @example
  22. * var Server = require('mongodb-core').Server
  23. * , ReadPreference = require('mongodb-core').ReadPreference
  24. * , assert = require('assert');
  25. *
  26. * var server = new Server({host: 'localhost', port: 27017});
  27. * // Wait for the connection event
  28. * server.on('connect', function(server) {
  29. * assert.equal(null, err);
  30. *
  31. * // Execute the write
  32. * var cursor = _server.cursor('integration_tests.inserts_example4', {
  33. * find: 'integration_tests.example4'
  34. * , query: {a:1}
  35. * }, {
  36. * readPreference: new ReadPreference('secondary');
  37. * });
  38. *
  39. * // Get the first document
  40. * cursor.next(function(err, doc) {
  41. * assert.equal(null, err);
  42. * server.destroy();
  43. * });
  44. * });
  45. *
  46. * // Start connecting
  47. * server.connect();
  48. */
  49. /**
  50. * Creates a new Cursor, not to be used directly
  51. * @class
  52. * @param {object} bson An instance of the BSON parser
  53. * @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
  54. * @param {{object}|Long} cmd The selector (can be a command or a cursorId)
  55. * @param {object} [options=null] Optional settings.
  56. * @param {object} [options.batchSize=1000] Batchsize for the operation
  57. * @param {array} [options.documents=[]] Initial documents list for cursor
  58. * @param {object} [options.transforms=null] Transform methods for the cursor results
  59. * @param {function} [options.transforms.query] Transform the value returned from the initial query
  60. * @param {function} [options.transforms.doc] Transform each document returned from Cursor.prototype.next
  61. * @param {object} topology The server topology instance.
  62. * @param {object} topologyOptions The server topology options.
  63. * @return {Cursor} A cursor instance
  64. * @property {number} cursorBatchSize The current cursorBatchSize for the cursor
  65. * @property {number} cursorLimit The current cursorLimit for the cursor
  66. * @property {number} cursorSkip The current cursorSkip for the cursor
  67. */
  68. var Cursor = function(bson, ns, cmd, options, topology, topologyOptions) {
  69. options = options || {};
  70. // Cursor pool
  71. this.pool = null;
  72. // Cursor server
  73. this.server = null;
  74. // Do we have a not connected handler
  75. this.disconnectHandler = options.disconnectHandler;
  76. // Set local values
  77. this.bson = bson;
  78. this.ns = ns;
  79. this.cmd = cmd;
  80. this.options = options;
  81. this.topology = topology;
  82. // All internal state
  83. this.cursorState = {
  84. cursorId: null,
  85. cmd: cmd,
  86. documents: options.documents || [],
  87. cursorIndex: 0,
  88. dead: false,
  89. killed: false,
  90. init: false,
  91. notified: false,
  92. limit: options.limit || cmd.limit || 0,
  93. skip: options.skip || cmd.skip || 0,
  94. batchSize: options.batchSize || cmd.batchSize || 1000,
  95. currentLimit: 0,
  96. // Result field name if not a cursor (contains the array of results)
  97. transforms: options.transforms
  98. };
  99. // Add promoteLong to cursor state
  100. if (typeof topologyOptions.promoteLongs === 'boolean') {
  101. this.cursorState.promoteLongs = topologyOptions.promoteLongs;
  102. } else if (typeof options.promoteLongs === 'boolean') {
  103. this.cursorState.promoteLongs = options.promoteLongs;
  104. } else if (typeof options.session === 'object') {
  105. this.cursorState.session = options.session;
  106. }
  107. // Add promoteValues to cursor state
  108. if (typeof topologyOptions.promoteValues === 'boolean') {
  109. this.cursorState.promoteValues = topologyOptions.promoteValues;
  110. } else if (typeof options.promoteValues === 'boolean') {
  111. this.cursorState.promoteValues = options.promoteValues;
  112. }
  113. // Add promoteBuffers to cursor state
  114. if (typeof topologyOptions.promoteBuffers === 'boolean') {
  115. this.cursorState.promoteBuffers = topologyOptions.promoteBuffers;
  116. } else if (typeof options.promoteBuffers === 'boolean') {
  117. this.cursorState.promoteBuffers = options.promoteBuffers;
  118. }
  119. if (topologyOptions.reconnect) {
  120. this.cursorState.reconnect = topologyOptions.reconnect;
  121. }
  122. // Logger
  123. this.logger = Logger('Cursor', topologyOptions);
  124. //
  125. // Did we pass in a cursor id
  126. if (typeof cmd === 'number') {
  127. this.cursorState.cursorId = Long.fromNumber(cmd);
  128. this.cursorState.lastCursorId = this.cursorState.cursorId;
  129. } else if (cmd instanceof Long) {
  130. this.cursorState.cursorId = cmd;
  131. this.cursorState.lastCursorId = cmd;
  132. }
  133. };
  134. Cursor.prototype.setCursorBatchSize = function(value) {
  135. this.cursorState.batchSize = value;
  136. };
  137. Cursor.prototype.cursorBatchSize = function() {
  138. return this.cursorState.batchSize;
  139. };
  140. Cursor.prototype.setCursorLimit = function(value) {
  141. this.cursorState.limit = value;
  142. };
  143. Cursor.prototype.cursorLimit = function() {
  144. return this.cursorState.limit;
  145. };
  146. Cursor.prototype.setCursorSkip = function(value) {
  147. this.cursorState.skip = value;
  148. };
  149. Cursor.prototype.cursorSkip = function() {
  150. return this.cursorState.skip;
  151. };
  152. //
  153. // Handle callback (including any exceptions thrown)
  154. var handleCallback = function(callback, err, result) {
  155. try {
  156. callback(err, result);
  157. } catch (err) {
  158. process.nextTick(function() {
  159. throw err;
  160. });
  161. }
  162. };
  163. // Internal methods
  164. Cursor.prototype._find = function(callback) {
  165. var self = this;
  166. if (self.logger.isDebug()) {
  167. self.logger.debug(
  168. f(
  169. 'issue initial query [%s] with flags [%s]',
  170. JSON.stringify(self.cmd),
  171. JSON.stringify(self.query)
  172. )
  173. );
  174. }
  175. var queryCallback = function(err, r) {
  176. if (err) return callback(err);
  177. // Get the raw message
  178. var result = r.message;
  179. // Query failure bit set
  180. if (result.queryFailure) {
  181. return callback(new MongoError(result.documents[0]), null);
  182. }
  183. // Check if we have a command cursor
  184. if (
  185. Array.isArray(result.documents) &&
  186. result.documents.length === 1 &&
  187. (!self.cmd.find || (self.cmd.find && self.cmd.virtual === false)) &&
  188. (result.documents[0].cursor !== 'string' ||
  189. result.documents[0]['$err'] ||
  190. result.documents[0]['errmsg'] ||
  191. Array.isArray(result.documents[0].result))
  192. ) {
  193. // We have a an error document return the error
  194. if (result.documents[0]['$err'] || result.documents[0]['errmsg']) {
  195. return callback(new MongoError(result.documents[0]), null);
  196. }
  197. // We have a cursor document
  198. if (result.documents[0].cursor != null && typeof result.documents[0].cursor !== 'string') {
  199. var id = result.documents[0].cursor.id;
  200. // If we have a namespace change set the new namespace for getmores
  201. if (result.documents[0].cursor.ns) {
  202. self.ns = result.documents[0].cursor.ns;
  203. }
  204. // Promote id to long if needed
  205. self.cursorState.cursorId = typeof id === 'number' ? Long.fromNumber(id) : id;
  206. self.cursorState.lastCursorId = self.cursorState.cursorId;
  207. // If we have a firstBatch set it
  208. if (Array.isArray(result.documents[0].cursor.firstBatch)) {
  209. self.cursorState.documents = result.documents[0].cursor.firstBatch; //.reverse();
  210. }
  211. // Return after processing command cursor
  212. return callback(null, result);
  213. }
  214. if (Array.isArray(result.documents[0].result)) {
  215. self.cursorState.documents = result.documents[0].result;
  216. self.cursorState.cursorId = Long.ZERO;
  217. return callback(null, result);
  218. }
  219. }
  220. // Otherwise fall back to regular find path
  221. self.cursorState.cursorId = result.cursorId;
  222. self.cursorState.documents = result.documents;
  223. self.cursorState.lastCursorId = result.cursorId;
  224. // Transform the results with passed in transformation method if provided
  225. if (self.cursorState.transforms && typeof self.cursorState.transforms.query === 'function') {
  226. self.cursorState.documents = self.cursorState.transforms.query(result);
  227. }
  228. // Return callback
  229. callback(null, result);
  230. };
  231. // Options passed to the pool
  232. var queryOptions = {};
  233. // If we have a raw query decorate the function
  234. if (self.options.raw || self.cmd.raw) {
  235. // queryCallback.raw = self.options.raw || self.cmd.raw;
  236. queryOptions.raw = self.options.raw || self.cmd.raw;
  237. }
  238. // Do we have documentsReturnedIn set on the query
  239. if (typeof self.query.documentsReturnedIn === 'string') {
  240. // queryCallback.documentsReturnedIn = self.query.documentsReturnedIn;
  241. queryOptions.documentsReturnedIn = self.query.documentsReturnedIn;
  242. }
  243. // Add promote Long value if defined
  244. if (typeof self.cursorState.promoteLongs === 'boolean') {
  245. queryOptions.promoteLongs = self.cursorState.promoteLongs;
  246. }
  247. // Add promote values if defined
  248. if (typeof self.cursorState.promoteValues === 'boolean') {
  249. queryOptions.promoteValues = self.cursorState.promoteValues;
  250. }
  251. // Add promote values if defined
  252. if (typeof self.cursorState.promoteBuffers === 'boolean') {
  253. queryOptions.promoteBuffers = self.cursorState.promoteBuffers;
  254. }
  255. if (typeof self.cursorState.session === 'object') {
  256. queryOptions.session = self.cursorState.session;
  257. }
  258. // Write the initial command out
  259. self.server.s.pool.write(self.query, queryOptions, queryCallback);
  260. };
  261. Cursor.prototype._getmore = function(callback) {
  262. if (this.logger.isDebug())
  263. this.logger.debug(f('schedule getMore call for query [%s]', JSON.stringify(this.query)));
  264. // Determine if it's a raw query
  265. var raw = this.options.raw || this.cmd.raw;
  266. // Set the current batchSize
  267. var batchSize = this.cursorState.batchSize;
  268. if (
  269. this.cursorState.limit > 0 &&
  270. this.cursorState.currentLimit + batchSize > this.cursorState.limit
  271. ) {
  272. batchSize = this.cursorState.limit - this.cursorState.currentLimit;
  273. }
  274. // Default pool
  275. var pool = this.server.s.pool;
  276. // We have a wire protocol handler
  277. this.server.wireProtocolHandler.getMore(
  278. this.bson,
  279. this.ns,
  280. this.cursorState,
  281. batchSize,
  282. raw,
  283. pool,
  284. this.options,
  285. callback
  286. );
  287. };
  288. Cursor.prototype._killcursor = function(callback) {
  289. // Set cursor to dead
  290. this.cursorState.dead = true;
  291. this.cursorState.killed = true;
  292. // Remove documents
  293. this.cursorState.documents = [];
  294. // If no cursor id just return
  295. if (
  296. this.cursorState.cursorId == null ||
  297. this.cursorState.cursorId.isZero() ||
  298. this.cursorState.init === false
  299. ) {
  300. if (callback) callback(null, null);
  301. return;
  302. }
  303. // Default pool
  304. var pool = this.server.s.pool;
  305. // Execute command
  306. this.server.wireProtocolHandler.killCursor(this.bson, this.ns, this.cursorState, pool, callback);
  307. };
  308. /**
  309. * Clone the cursor
  310. * @method
  311. * @return {Cursor}
  312. */
  313. Cursor.prototype.clone = function() {
  314. return this.topology.cursor(this.ns, this.cmd, this.options);
  315. };
  316. /**
  317. * Checks if the cursor is dead
  318. * @method
  319. * @return {boolean} A boolean signifying if the cursor is dead or not
  320. */
  321. Cursor.prototype.isDead = function() {
  322. return this.cursorState.dead === true;
  323. };
  324. /**
  325. * Checks if the cursor was killed by the application
  326. * @method
  327. * @return {boolean} A boolean signifying if the cursor was killed by the application
  328. */
  329. Cursor.prototype.isKilled = function() {
  330. return this.cursorState.killed === true;
  331. };
  332. /**
  333. * Checks if the cursor notified it's caller about it's death
  334. * @method
  335. * @return {boolean} A boolean signifying if the cursor notified the callback
  336. */
  337. Cursor.prototype.isNotified = function() {
  338. return this.cursorState.notified === true;
  339. };
  340. /**
  341. * Returns current buffered documents length
  342. * @method
  343. * @return {number} The number of items in the buffered documents
  344. */
  345. Cursor.prototype.bufferedCount = function() {
  346. return this.cursorState.documents.length - this.cursorState.cursorIndex;
  347. };
  348. /**
  349. * Returns current buffered documents
  350. * @method
  351. * @return {Array} An array of buffered documents
  352. */
  353. Cursor.prototype.readBufferedDocuments = function(number) {
  354. var unreadDocumentsLength = this.cursorState.documents.length - this.cursorState.cursorIndex;
  355. var length = number < unreadDocumentsLength ? number : unreadDocumentsLength;
  356. var elements = this.cursorState.documents.slice(
  357. this.cursorState.cursorIndex,
  358. this.cursorState.cursorIndex + length
  359. );
  360. // Transform the doc with passed in transformation method if provided
  361. if (this.cursorState.transforms && typeof this.cursorState.transforms.doc === 'function') {
  362. // Transform all the elements
  363. for (var i = 0; i < elements.length; i++) {
  364. elements[i] = this.cursorState.transforms.doc(elements[i]);
  365. }
  366. }
  367. // Ensure we do not return any more documents than the limit imposed
  368. // Just return the number of elements up to the limit
  369. if (
  370. this.cursorState.limit > 0 &&
  371. this.cursorState.currentLimit + elements.length > this.cursorState.limit
  372. ) {
  373. elements = elements.slice(0, this.cursorState.limit - this.cursorState.currentLimit);
  374. this.kill();
  375. }
  376. // Adjust current limit
  377. this.cursorState.currentLimit = this.cursorState.currentLimit + elements.length;
  378. this.cursorState.cursorIndex = this.cursorState.cursorIndex + elements.length;
  379. // Return elements
  380. return elements;
  381. };
  382. /**
  383. * Kill the cursor
  384. * @method
  385. * @param {resultCallback} callback A callback function
  386. */
  387. Cursor.prototype.kill = function(callback) {
  388. this._killcursor(callback);
  389. };
  390. /**
  391. * Resets the cursor
  392. * @method
  393. * @return {null}
  394. */
  395. Cursor.prototype.rewind = function() {
  396. if (this.cursorState.init) {
  397. if (!this.cursorState.dead) {
  398. this.kill();
  399. }
  400. this.cursorState.currentLimit = 0;
  401. this.cursorState.init = false;
  402. this.cursorState.dead = false;
  403. this.cursorState.killed = false;
  404. this.cursorState.notified = false;
  405. this.cursorState.documents = [];
  406. this.cursorState.cursorId = null;
  407. this.cursorState.cursorIndex = 0;
  408. }
  409. };
  410. /**
  411. * Validate if the pool is dead and return error
  412. */
  413. var isConnectionDead = function(self, callback) {
  414. if (self.pool && self.pool.isDestroyed()) {
  415. self.cursorState.notified = true;
  416. self.cursorState.killed = true;
  417. self.cursorState.documents = [];
  418. self.cursorState.cursorIndex = 0;
  419. callback(
  420. new MongoNetworkError(
  421. f('connection to host %s:%s was destroyed', self.pool.host, self.pool.port)
  422. )
  423. );
  424. return true;
  425. }
  426. return false;
  427. };
  428. /**
  429. * Validate if the cursor is dead but was not explicitly killed by user
  430. */
  431. var isCursorDeadButNotkilled = function(self, callback) {
  432. // Cursor is dead but not marked killed, return null
  433. if (self.cursorState.dead && !self.cursorState.killed) {
  434. self.cursorState.notified = true;
  435. self.cursorState.killed = true;
  436. self.cursorState.documents = [];
  437. self.cursorState.cursorIndex = 0;
  438. handleCallback(callback, null, null);
  439. return true;
  440. }
  441. return false;
  442. };
  443. /**
  444. * Validate if the cursor is dead and was killed by user
  445. */
  446. var isCursorDeadAndKilled = function(self, callback) {
  447. if (self.cursorState.dead && self.cursorState.killed) {
  448. handleCallback(callback, new MongoError('cursor is dead'));
  449. return true;
  450. }
  451. return false;
  452. };
  453. /**
  454. * Validate if the cursor was killed by the user
  455. */
  456. var isCursorKilled = function(self, callback) {
  457. if (self.cursorState.killed) {
  458. self.cursorState.notified = true;
  459. self.cursorState.documents = [];
  460. self.cursorState.cursorIndex = 0;
  461. handleCallback(callback, null, null);
  462. return true;
  463. }
  464. return false;
  465. };
  466. /**
  467. * Mark cursor as being dead and notified
  468. */
  469. var setCursorDeadAndNotified = function(self, callback) {
  470. self.cursorState.dead = true;
  471. self.cursorState.notified = true;
  472. self.cursorState.documents = [];
  473. self.cursorState.cursorIndex = 0;
  474. handleCallback(callback, null, null);
  475. };
  476. /**
  477. * Mark cursor as being notified
  478. */
  479. var setCursorNotified = function(self, callback) {
  480. self.cursorState.notified = true;
  481. self.cursorState.documents = [];
  482. self.cursorState.cursorIndex = 0;
  483. handleCallback(callback, null, null);
  484. };
  485. var nextFunction = function(self, callback) {
  486. // We have notified about it
  487. if (self.cursorState.notified) {
  488. return callback(new Error('cursor is exhausted'));
  489. }
  490. // Cursor is killed return null
  491. if (isCursorKilled(self, callback)) return;
  492. // Cursor is dead but not marked killed, return null
  493. if (isCursorDeadButNotkilled(self, callback)) return;
  494. // We have a dead and killed cursor, attempting to call next should error
  495. if (isCursorDeadAndKilled(self, callback)) return;
  496. // We have just started the cursor
  497. if (!self.cursorState.init) {
  498. // Topology is not connected, save the call in the provided store to be
  499. // Executed at some point when the handler deems it's reconnected
  500. if (!self.topology.isConnected(self.options)) {
  501. // Only need this for single server, because repl sets and mongos
  502. // will always continue trying to reconnect
  503. if (self.topology._type === 'server' && !self.topology.s.options.reconnect) {
  504. // Reconnect is disabled, so we'll never reconnect
  505. return callback(new MongoError('no connection available'));
  506. }
  507. if (self.disconnectHandler != null) {
  508. if (self.topology.isDestroyed()) {
  509. // Topology was destroyed, so don't try to wait for it to reconnect
  510. return callback(new MongoError('Topology was destroyed'));
  511. }
  512. return self.disconnectHandler.addObjectAndMethod(
  513. 'cursor',
  514. self,
  515. 'next',
  516. [callback],
  517. callback
  518. );
  519. }
  520. }
  521. try {
  522. self.server = self.topology.getServer(self.options);
  523. } catch (err) {
  524. // Handle the error and add object to next method call
  525. if (self.disconnectHandler != null) {
  526. return self.disconnectHandler.addObjectAndMethod(
  527. 'cursor',
  528. self,
  529. 'next',
  530. [callback],
  531. callback
  532. );
  533. }
  534. // Otherwise return the error
  535. return callback(err);
  536. }
  537. // Set as init
  538. self.cursorState.init = true;
  539. // Server does not support server
  540. if (self.cmd && self.cmd.collation && self.server.ismaster.maxWireVersion < 5) {
  541. return callback(new MongoError(f('server %s does not support collation', self.server.name)));
  542. }
  543. try {
  544. self.query = self.server.wireProtocolHandler.command(
  545. self.bson,
  546. self.ns,
  547. self.cmd,
  548. self.cursorState,
  549. self.topology,
  550. self.options
  551. );
  552. } catch (err) {
  553. return callback(err);
  554. }
  555. }
  556. // If we don't have a cursorId execute the first query
  557. if (self.cursorState.cursorId == null) {
  558. // Check if pool is dead and return if not possible to
  559. // execute the query against the db
  560. if (isConnectionDead(self, callback)) return;
  561. // Check if topology is destroyed
  562. if (self.topology.isDestroyed())
  563. return callback(
  564. new MongoNetworkError('connection destroyed, not possible to instantiate cursor')
  565. );
  566. // query, cmd, options, cursorState, callback
  567. self._find(function(err) {
  568. if (err) return handleCallback(callback, err, null);
  569. if (
  570. self.cursorState.documents.length === 0 &&
  571. self.cursorState.cursorId &&
  572. self.cursorState.cursorId.isZero() &&
  573. !self.cmd.tailable &&
  574. !self.cmd.awaitData
  575. ) {
  576. return setCursorNotified(self, callback);
  577. }
  578. nextFunction(self, callback);
  579. });
  580. } else if (
  581. self.cursorState.limit > 0 &&
  582. self.cursorState.currentLimit >= self.cursorState.limit
  583. ) {
  584. // Ensure we kill the cursor on the server
  585. self.kill();
  586. // Set cursor in dead and notified state
  587. return setCursorDeadAndNotified(self, callback);
  588. } else if (
  589. self.cursorState.cursorIndex === self.cursorState.documents.length &&
  590. !Long.ZERO.equals(self.cursorState.cursorId)
  591. ) {
  592. // Ensure an empty cursor state
  593. self.cursorState.documents = [];
  594. self.cursorState.cursorIndex = 0;
  595. // Check if topology is destroyed
  596. if (self.topology.isDestroyed())
  597. return callback(
  598. new MongoNetworkError('connection destroyed, not possible to instantiate cursor')
  599. );
  600. // Check if connection is dead and return if not possible to
  601. // execute a getmore on this connection
  602. if (isConnectionDead(self, callback)) return;
  603. // Execute the next get more
  604. self._getmore(function(err, doc, connection) {
  605. if (err) return handleCallback(callback, err);
  606. // Save the returned connection to ensure all getMore's fire over the same connection
  607. self.connection = connection;
  608. // Tailable cursor getMore result, notify owner about it
  609. // No attempt is made here to retry, this is left to the user of the
  610. // core module to handle to keep core simple
  611. if (
  612. self.cursorState.documents.length === 0 &&
  613. self.cmd.tailable &&
  614. Long.ZERO.equals(self.cursorState.cursorId)
  615. ) {
  616. // No more documents in the tailed cursor
  617. return handleCallback(
  618. callback,
  619. new MongoError({
  620. message: 'No more documents in tailed cursor',
  621. tailable: self.cmd.tailable,
  622. awaitData: self.cmd.awaitData
  623. })
  624. );
  625. } else if (
  626. self.cursorState.documents.length === 0 &&
  627. self.cmd.tailable &&
  628. !Long.ZERO.equals(self.cursorState.cursorId)
  629. ) {
  630. return nextFunction(self, callback);
  631. }
  632. if (self.cursorState.limit > 0 && self.cursorState.currentLimit >= self.cursorState.limit) {
  633. return setCursorDeadAndNotified(self, callback);
  634. }
  635. nextFunction(self, callback);
  636. });
  637. } else if (
  638. self.cursorState.documents.length === self.cursorState.cursorIndex &&
  639. self.cmd.tailable &&
  640. Long.ZERO.equals(self.cursorState.cursorId)
  641. ) {
  642. return handleCallback(
  643. callback,
  644. new MongoError({
  645. message: 'No more documents in tailed cursor',
  646. tailable: self.cmd.tailable,
  647. awaitData: self.cmd.awaitData
  648. })
  649. );
  650. } else if (
  651. self.cursorState.documents.length === self.cursorState.cursorIndex &&
  652. Long.ZERO.equals(self.cursorState.cursorId)
  653. ) {
  654. setCursorDeadAndNotified(self, callback);
  655. } else {
  656. if (self.cursorState.limit > 0 && self.cursorState.currentLimit >= self.cursorState.limit) {
  657. // Ensure we kill the cursor on the server
  658. self.kill();
  659. // Set cursor in dead and notified state
  660. return setCursorDeadAndNotified(self, callback);
  661. }
  662. // Increment the current cursor limit
  663. self.cursorState.currentLimit += 1;
  664. // Get the document
  665. var doc = self.cursorState.documents[self.cursorState.cursorIndex++];
  666. // Doc overflow
  667. if (!doc || doc.$err) {
  668. // Ensure we kill the cursor on the server
  669. self.kill();
  670. // Set cursor in dead and notified state
  671. return setCursorDeadAndNotified(self, function() {
  672. handleCallback(callback, new MongoError(doc ? doc.$err : undefined));
  673. });
  674. }
  675. // Transform the doc with passed in transformation method if provided
  676. if (self.cursorState.transforms && typeof self.cursorState.transforms.doc === 'function') {
  677. doc = self.cursorState.transforms.doc(doc);
  678. }
  679. // Return the document
  680. handleCallback(callback, null, doc);
  681. }
  682. };
  683. /**
  684. * Retrieve the next document from the cursor
  685. * @method
  686. * @param {resultCallback} callback A callback function
  687. */
  688. Cursor.prototype.next = function(callback) {
  689. nextFunction(this, callback);
  690. };
  691. module.exports = Cursor;