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 24KB

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