Ohm-Management - Projektarbeit B-ME
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

cursor.js 21KB

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