mongodb 3.3.0-beta1 → 3.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/HISTORY.md +117 -0
  2. package/index.js +1 -0
  3. package/lib/admin.js +6 -6
  4. package/lib/aggregation_cursor.js +180 -226
  5. package/lib/change_stream.js +233 -121
  6. package/lib/collection.js +74 -39
  7. package/lib/command_cursor.js +87 -165
  8. package/lib/core/connection/connect.js +1 -1
  9. package/lib/core/connection/msg.js +2 -1
  10. package/lib/core/connection/pool.js +66 -41
  11. package/lib/core/cursor.js +605 -461
  12. package/lib/core/error.js +70 -1
  13. package/lib/core/index.js +1 -1
  14. package/lib/core/sdam/server.js +109 -10
  15. package/lib/core/sdam/server_description.js +14 -0
  16. package/lib/core/sdam/topology.js +34 -14
  17. package/lib/core/sdam/topology_description.js +6 -14
  18. package/lib/core/sessions.js +68 -12
  19. package/lib/core/topologies/mongos.js +10 -3
  20. package/lib/core/topologies/replset.js +49 -11
  21. package/lib/core/topologies/server.js +38 -3
  22. package/lib/core/topologies/shared.js +22 -0
  23. package/lib/core/transactions.js +1 -0
  24. package/lib/core/utils.js +45 -1
  25. package/lib/core/wireprotocol/command.js +2 -2
  26. package/lib/core/wireprotocol/get_more.js +4 -0
  27. package/lib/core/wireprotocol/query.js +8 -1
  28. package/lib/cursor.js +780 -840
  29. package/lib/db.js +31 -73
  30. package/lib/error.js +10 -8
  31. package/lib/gridfs-stream/download.js +12 -5
  32. package/lib/mongo_client.js +33 -21
  33. package/lib/operations/aggregate.js +77 -95
  34. package/lib/operations/close.js +46 -0
  35. package/lib/operations/collection_ops.js +7 -916
  36. package/lib/operations/command.js +2 -1
  37. package/lib/operations/command_v2.js +109 -0
  38. package/lib/operations/common_functions.js +48 -14
  39. package/lib/operations/connect.js +73 -9
  40. package/lib/operations/count.js +8 -8
  41. package/lib/operations/count_documents.js +24 -29
  42. package/lib/operations/create_collection.js +1 -0
  43. package/lib/operations/cursor_ops.js +22 -32
  44. package/lib/operations/db_ops.js +0 -178
  45. package/lib/operations/distinct.js +20 -21
  46. package/lib/operations/drop.js +1 -0
  47. package/lib/operations/estimated_document_count.js +43 -18
  48. package/lib/operations/execute_operation.js +115 -3
  49. package/lib/operations/explain.js +2 -6
  50. package/lib/operations/find.js +35 -0
  51. package/lib/operations/insert_one.js +1 -37
  52. package/lib/operations/list_collections.js +106 -0
  53. package/lib/operations/list_databases.js +38 -0
  54. package/lib/operations/list_indexes.js +28 -52
  55. package/lib/operations/operation.js +7 -1
  56. package/lib/operations/rename.js +1 -1
  57. package/lib/operations/to_array.js +3 -5
  58. package/lib/read_concern.js +7 -1
  59. package/lib/topologies/mongos.js +1 -1
  60. package/lib/topologies/replset.js +1 -1
  61. package/lib/topologies/server.js +2 -2
  62. package/lib/topologies/topology_base.js +4 -5
  63. package/lib/utils.js +4 -4
  64. package/package.json +1 -1
  65. package/lib/operations/aggregate_operation.js +0 -127
  66. package/lib/operations/mongo_client_ops.js +0 -731
@@ -5,12 +5,38 @@ const retrieveBSON = require('./connection/utils').retrieveBSON;
5
5
  const MongoError = require('./error').MongoError;
6
6
  const MongoNetworkError = require('./error').MongoNetworkError;
7
7
  const mongoErrorContextSymbol = require('./error').mongoErrorContextSymbol;
8
- const f = require('util').format;
9
8
  const collationNotSupported = require('./utils').collationNotSupported;
10
- const wireProtocol = require('./wireprotocol');
9
+ const ReadPreference = require('./topologies/read_preference');
10
+ const isUnifiedTopology = require('./utils').isUnifiedTopology;
11
+ const executeOperation = require('../operations/execute_operation');
12
+ const Readable = require('stream').Readable;
13
+ const SUPPORTS = require('../utils').SUPPORTS;
14
+ const MongoDBNamespace = require('../utils').MongoDBNamespace;
15
+ const OperationBase = require('../operations/operation').OperationBase;
16
+
11
17
  const BSON = retrieveBSON();
12
18
  const Long = BSON.Long;
13
19
 
20
+ // Possible states for a cursor
21
+ const CursorState = {
22
+ INIT: 0,
23
+ OPEN: 1,
24
+ CLOSED: 2,
25
+ GET_MORE: 3
26
+ };
27
+
28
+ //
29
+ // Handle callback (including any exceptions thrown)
30
+ function handleCallback(callback, err, result) {
31
+ try {
32
+ callback(err, result);
33
+ } catch (err) {
34
+ process.nextTick(function() {
35
+ throw err;
36
+ });
37
+ }
38
+ }
39
+
14
40
  /**
15
41
  * This is a cursor results callback
16
42
  *
@@ -27,329 +53,598 @@ const Long = BSON.Long;
27
53
  */
28
54
 
29
55
  /**
30
- * Creates a new Cursor, not to be used directly
31
- * @class
32
- * @param {object} bson An instance of the BSON parser
33
- * @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
34
- * @param {{object}|Long} cmd The selector (can be a command or a cursorId)
35
- * @param {object} [options=null] Optional settings.
36
- * @param {object} [options.batchSize=1000] Batchsize for the operation
37
- * @param {array} [options.documents=[]] Initial documents list for cursor
38
- * @param {object} [options.transforms=null] Transform methods for the cursor results
39
- * @param {function} [options.transforms.query] Transform the value returned from the initial query
40
- * @param {function} [options.transforms.doc] Transform each document returned from Cursor.prototype.next
41
- * @param {object} topology The server topology instance.
42
- * @param {object} topologyOptions The server topology options.
43
- * @return {Cursor} A cursor instance
56
+ * The core cursor class. All cursors in the driver build off of this one.
57
+ *
44
58
  * @property {number} cursorBatchSize The current cursorBatchSize for the cursor
45
59
  * @property {number} cursorLimit The current cursorLimit for the cursor
46
60
  * @property {number} cursorSkip The current cursorSkip for the cursor
47
61
  */
48
- var Cursor = function(bson, ns, cmd, options, topology, topologyOptions) {
49
- options = options || {};
50
-
51
- // Cursor pool
52
- this.pool = null;
53
- // Cursor server
54
- this.server = null;
55
-
56
- // Do we have a not connected handler
57
- this.disconnectHandler = options.disconnectHandler;
58
-
59
- // Set local values
60
- this.bson = bson;
61
- this.ns = ns;
62
- this.cmd = cmd;
63
- this.options = options;
64
- this.topology = topology;
65
-
66
- // All internal state
67
- this.cursorState = {
68
- cursorId: null,
69
- cmd: cmd,
70
- documents: options.documents || [],
71
- cursorIndex: 0,
72
- dead: false,
73
- killed: false,
74
- init: false,
75
- notified: false,
76
- limit: options.limit || cmd.limit || 0,
77
- skip: options.skip || cmd.skip || 0,
78
- batchSize: options.batchSize || cmd.batchSize || 1000,
79
- currentLimit: 0,
80
- // Result field name if not a cursor (contains the array of results)
81
- transforms: options.transforms,
82
- raw: options.raw || (cmd && cmd.raw)
83
- };
84
-
85
- if (typeof options.session === 'object') {
86
- this.cursorState.session = options.session;
62
+ class CoreCursor extends Readable {
63
+ /**
64
+ * Create a new core `Cursor` instance.
65
+ * **NOTE** Not to be instantiated directly
66
+ *
67
+ * @param {object} topology The server topology instance.
68
+ * @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
69
+ * @param {{object}|Long} cmd The selector (can be a command or a cursorId)
70
+ * @param {object} [options=null] Optional settings.
71
+ * @param {object} [options.batchSize=1000] Batchsize for the operation
72
+ * @param {array} [options.documents=[]] Initial documents list for cursor
73
+ * @param {object} [options.transforms=null] Transform methods for the cursor results
74
+ * @param {function} [options.transforms.query] Transform the value returned from the initial query
75
+ * @param {function} [options.transforms.doc] Transform each document returned from Cursor.prototype._next
76
+ */
77
+ constructor(topology, ns, cmd, options) {
78
+ super({ objectMode: true });
79
+ options = options || {};
80
+
81
+ if (ns instanceof OperationBase) {
82
+ this.operation = ns;
83
+ ns = this.operation.ns.toString();
84
+ options = this.operation.options;
85
+ cmd = this.operation.cmd ? this.operation.cmd : {};
86
+ }
87
+
88
+ // Cursor pool
89
+ this.pool = null;
90
+ // Cursor server
91
+ this.server = null;
92
+
93
+ // Do we have a not connected handler
94
+ this.disconnectHandler = options.disconnectHandler;
95
+
96
+ // Set local values
97
+ this.bson = topology.s.bson;
98
+ this.ns = ns;
99
+ this.namespace = MongoDBNamespace.fromString(ns);
100
+ this.cmd = cmd;
101
+ this.options = options;
102
+ this.topology = topology;
103
+
104
+ // All internal state
105
+ this.cursorState = {
106
+ cursorId: null,
107
+ cmd,
108
+ documents: options.documents || [],
109
+ cursorIndex: 0,
110
+ dead: false,
111
+ killed: false,
112
+ init: false,
113
+ notified: false,
114
+ limit: options.limit || cmd.limit || 0,
115
+ skip: options.skip || cmd.skip || 0,
116
+ batchSize: options.batchSize || cmd.batchSize || 1000,
117
+ currentLimit: 0,
118
+ // Result field name if not a cursor (contains the array of results)
119
+ transforms: options.transforms,
120
+ raw: options.raw || (cmd && cmd.raw)
121
+ };
122
+
123
+ if (typeof options.session === 'object') {
124
+ this.cursorState.session = options.session;
125
+ }
126
+
127
+ // Add promoteLong to cursor state
128
+ const topologyOptions = topology.s.options;
129
+ if (typeof topologyOptions.promoteLongs === 'boolean') {
130
+ this.cursorState.promoteLongs = topologyOptions.promoteLongs;
131
+ } else if (typeof options.promoteLongs === 'boolean') {
132
+ this.cursorState.promoteLongs = options.promoteLongs;
133
+ }
134
+
135
+ // Add promoteValues to cursor state
136
+ if (typeof topologyOptions.promoteValues === 'boolean') {
137
+ this.cursorState.promoteValues = topologyOptions.promoteValues;
138
+ } else if (typeof options.promoteValues === 'boolean') {
139
+ this.cursorState.promoteValues = options.promoteValues;
140
+ }
141
+
142
+ // Add promoteBuffers to cursor state
143
+ if (typeof topologyOptions.promoteBuffers === 'boolean') {
144
+ this.cursorState.promoteBuffers = topologyOptions.promoteBuffers;
145
+ } else if (typeof options.promoteBuffers === 'boolean') {
146
+ this.cursorState.promoteBuffers = options.promoteBuffers;
147
+ }
148
+
149
+ if (topologyOptions.reconnect) {
150
+ this.cursorState.reconnect = topologyOptions.reconnect;
151
+ }
152
+
153
+ // Logger
154
+ this.logger = Logger('Cursor', topologyOptions);
155
+
156
+ //
157
+ // Did we pass in a cursor id
158
+ if (typeof cmd === 'number') {
159
+ this.cursorState.cursorId = Long.fromNumber(cmd);
160
+ this.cursorState.lastCursorId = this.cursorState.cursorId;
161
+ } else if (cmd instanceof Long) {
162
+ this.cursorState.cursorId = cmd;
163
+ this.cursorState.lastCursorId = cmd;
164
+ }
165
+
166
+ // TODO: remove as part of NODE-2104
167
+ if (this.operation) {
168
+ this.operation.cursorState = this.cursorState;
169
+ }
87
170
  }
88
171
 
89
- // Add promoteLong to cursor state
90
- if (typeof topologyOptions.promoteLongs === 'boolean') {
91
- this.cursorState.promoteLongs = topologyOptions.promoteLongs;
92
- } else if (typeof options.promoteLongs === 'boolean') {
93
- this.cursorState.promoteLongs = options.promoteLongs;
172
+ setCursorBatchSize(value) {
173
+ this.cursorState.batchSize = value;
94
174
  }
95
175
 
96
- // Add promoteValues to cursor state
97
- if (typeof topologyOptions.promoteValues === 'boolean') {
98
- this.cursorState.promoteValues = topologyOptions.promoteValues;
99
- } else if (typeof options.promoteValues === 'boolean') {
100
- this.cursorState.promoteValues = options.promoteValues;
176
+ cursorBatchSize() {
177
+ return this.cursorState.batchSize;
101
178
  }
102
179
 
103
- // Add promoteBuffers to cursor state
104
- if (typeof topologyOptions.promoteBuffers === 'boolean') {
105
- this.cursorState.promoteBuffers = topologyOptions.promoteBuffers;
106
- } else if (typeof options.promoteBuffers === 'boolean') {
107
- this.cursorState.promoteBuffers = options.promoteBuffers;
180
+ setCursorLimit(value) {
181
+ this.cursorState.limit = value;
108
182
  }
109
183
 
110
- if (topologyOptions.reconnect) {
111
- this.cursorState.reconnect = topologyOptions.reconnect;
184
+ cursorLimit() {
185
+ return this.cursorState.limit;
112
186
  }
113
187
 
114
- // Logger
115
- this.logger = Logger('Cursor', topologyOptions);
116
-
117
- //
118
- // Did we pass in a cursor id
119
- if (typeof cmd === 'number') {
120
- this.cursorState.cursorId = Long.fromNumber(cmd);
121
- this.cursorState.lastCursorId = this.cursorState.cursorId;
122
- } else if (cmd instanceof Long) {
123
- this.cursorState.cursorId = cmd;
124
- this.cursorState.lastCursorId = cmd;
188
+ setCursorSkip(value) {
189
+ this.cursorState.skip = value;
125
190
  }
126
- };
127
191
 
128
- Cursor.prototype.setCursorBatchSize = function(value) {
129
- this.cursorState.batchSize = value;
130
- };
192
+ cursorSkip() {
193
+ return this.cursorState.skip;
194
+ }
131
195
 
132
- Cursor.prototype.cursorBatchSize = function() {
133
- return this.cursorState.batchSize;
134
- };
196
+ /**
197
+ * Retrieve the next document from the cursor
198
+ * @method
199
+ * @param {resultCallback} callback A callback function
200
+ */
201
+ _next(callback) {
202
+ nextFunction(this, callback);
203
+ }
135
204
 
136
- Cursor.prototype.setCursorLimit = function(value) {
137
- this.cursorState.limit = value;
138
- };
205
+ /**
206
+ * Clone the cursor
207
+ * @method
208
+ * @return {Cursor}
209
+ */
210
+ clone() {
211
+ return this.topology.cursor(this.ns, this.cmd, this.options);
212
+ }
139
213
 
140
- Cursor.prototype.cursorLimit = function() {
141
- return this.cursorState.limit;
142
- };
214
+ /**
215
+ * Checks if the cursor is dead
216
+ * @method
217
+ * @return {boolean} A boolean signifying if the cursor is dead or not
218
+ */
219
+ isDead() {
220
+ return this.cursorState.dead === true;
221
+ }
143
222
 
144
- Cursor.prototype.setCursorSkip = function(value) {
145
- this.cursorState.skip = value;
146
- };
223
+ /**
224
+ * Checks if the cursor was killed by the application
225
+ * @method
226
+ * @return {boolean} A boolean signifying if the cursor was killed by the application
227
+ */
228
+ isKilled() {
229
+ return this.cursorState.killed === true;
230
+ }
147
231
 
148
- Cursor.prototype.cursorSkip = function() {
149
- return this.cursorState.skip;
150
- };
232
+ /**
233
+ * Checks if the cursor notified it's caller about it's death
234
+ * @method
235
+ * @return {boolean} A boolean signifying if the cursor notified the callback
236
+ */
237
+ isNotified() {
238
+ return this.cursorState.notified === true;
239
+ }
151
240
 
152
- Cursor.prototype._endSession = function(options, callback) {
153
- if (typeof options === 'function') {
154
- callback = options;
155
- options = {};
241
+ /**
242
+ * Returns current buffered documents length
243
+ * @method
244
+ * @return {number} The number of items in the buffered documents
245
+ */
246
+ bufferedCount() {
247
+ return this.cursorState.documents.length - this.cursorState.cursorIndex;
156
248
  }
157
- options = options || {};
158
249
 
159
- const session = this.cursorState.session;
250
+ /**
251
+ * Returns current buffered documents
252
+ * @method
253
+ * @return {Array} An array of buffered documents
254
+ */
255
+ readBufferedDocuments(number) {
256
+ const unreadDocumentsLength = this.cursorState.documents.length - this.cursorState.cursorIndex;
257
+ const length = number < unreadDocumentsLength ? number : unreadDocumentsLength;
258
+ let elements = this.cursorState.documents.slice(
259
+ this.cursorState.cursorIndex,
260
+ this.cursorState.cursorIndex + length
261
+ );
160
262
 
161
- if (session && (options.force || session.owner === this)) {
162
- this.cursorState.session = undefined;
163
- session.endSession(callback);
164
- return true;
165
- }
263
+ // Transform the doc with passed in transformation method if provided
264
+ if (this.cursorState.transforms && typeof this.cursorState.transforms.doc === 'function') {
265
+ // Transform all the elements
266
+ for (let i = 0; i < elements.length; i++) {
267
+ elements[i] = this.cursorState.transforms.doc(elements[i]);
268
+ }
269
+ }
270
+
271
+ // Ensure we do not return any more documents than the limit imposed
272
+ // Just return the number of elements up to the limit
273
+ if (
274
+ this.cursorState.limit > 0 &&
275
+ this.cursorState.currentLimit + elements.length > this.cursorState.limit
276
+ ) {
277
+ elements = elements.slice(0, this.cursorState.limit - this.cursorState.currentLimit);
278
+ this.kill();
279
+ }
166
280
 
167
- if (callback) {
168
- callback();
281
+ // Adjust current limit
282
+ this.cursorState.currentLimit = this.cursorState.currentLimit + elements.length;
283
+ this.cursorState.cursorIndex = this.cursorState.cursorIndex + elements.length;
284
+
285
+ // Return elements
286
+ return elements;
169
287
  }
170
- return false;
171
- };
172
288
 
173
- //
174
- // Handle callback (including any exceptions thrown)
175
- var handleCallback = function(callback, err, result) {
176
- try {
177
- callback(err, result);
178
- } catch (err) {
179
- process.nextTick(function() {
180
- throw err;
181
- });
289
+ /**
290
+ * Resets local state for this cursor instance, and issues a `killCursors` command to the server
291
+ *
292
+ * @param {resultCallback} callback A callback function
293
+ */
294
+ kill(callback) {
295
+ // Set cursor to dead
296
+ this.cursorState.dead = true;
297
+ this.cursorState.killed = true;
298
+ // Remove documents
299
+ this.cursorState.documents = [];
300
+
301
+ // If no cursor id just return
302
+ if (
303
+ this.cursorState.cursorId == null ||
304
+ this.cursorState.cursorId.isZero() ||
305
+ this.cursorState.init === false
306
+ ) {
307
+ if (callback) callback(null, null);
308
+ return;
309
+ }
310
+
311
+ this.server.killCursors(this.ns, this.cursorState, callback);
182
312
  }
183
- };
184
313
 
185
- // Internal methods
186
- Cursor.prototype._getmore = function(callback) {
187
- if (this.logger.isDebug())
188
- this.logger.debug(f('schedule getMore call for query [%s]', JSON.stringify(this.query)));
314
+ /**
315
+ * Resets the cursor
316
+ */
317
+ rewind() {
318
+ if (this.cursorState.init) {
319
+ if (!this.cursorState.dead) {
320
+ this.kill();
321
+ }
189
322
 
190
- // Set the current batchSize
191
- var batchSize = this.cursorState.batchSize;
192
- if (
193
- this.cursorState.limit > 0 &&
194
- this.cursorState.currentLimit + batchSize > this.cursorState.limit
195
- ) {
196
- batchSize = this.cursorState.limit - this.cursorState.currentLimit;
323
+ this.cursorState.currentLimit = 0;
324
+ this.cursorState.init = false;
325
+ this.cursorState.dead = false;
326
+ this.cursorState.killed = false;
327
+ this.cursorState.notified = false;
328
+ this.cursorState.documents = [];
329
+ this.cursorState.cursorId = null;
330
+ this.cursorState.cursorIndex = 0;
331
+ }
197
332
  }
198
333
 
199
- wireProtocol.getMore(this.server, this.ns, this.cursorState, batchSize, this.options, callback);
200
- };
334
+ // Internal methods
335
+ _read() {
336
+ if ((this.s && this.s.state === CursorState.CLOSED) || this.isDead()) {
337
+ return this.push(null);
338
+ }
201
339
 
202
- /**
203
- * Clone the cursor
204
- * @method
205
- * @return {Cursor}
206
- */
207
- Cursor.prototype.clone = function() {
208
- return this.topology.cursor(this.ns, this.cmd, this.options);
209
- };
340
+ // Get the next item
341
+ this._next((err, result) => {
342
+ if (err) {
343
+ if (this.listeners('error') && this.listeners('error').length > 0) {
344
+ this.emit('error', err);
345
+ }
346
+ if (!this.isDead()) this.close();
210
347
 
211
- /**
212
- * Checks if the cursor is dead
213
- * @method
214
- * @return {boolean} A boolean signifying if the cursor is dead or not
215
- */
216
- Cursor.prototype.isDead = function() {
217
- return this.cursorState.dead === true;
218
- };
348
+ // Emit end event
349
+ this.emit('end');
350
+ return this.emit('finish');
351
+ }
219
352
 
220
- /**
221
- * Checks if the cursor was killed by the application
222
- * @method
223
- * @return {boolean} A boolean signifying if the cursor was killed by the application
224
- */
225
- Cursor.prototype.isKilled = function() {
226
- return this.cursorState.killed === true;
227
- };
353
+ // If we provided a transformation method
354
+ if (
355
+ this.cursorState.streamOptions &&
356
+ typeof this.cursorState.streamOptions.transform === 'function' &&
357
+ result != null
358
+ ) {
359
+ return this.push(this.cursorState.streamOptions.transform(result));
360
+ }
228
361
 
229
- /**
230
- * Checks if the cursor notified it's caller about it's death
231
- * @method
232
- * @return {boolean} A boolean signifying if the cursor notified the callback
233
- */
234
- Cursor.prototype.isNotified = function() {
235
- return this.cursorState.notified === true;
236
- };
362
+ // If we provided a map function
363
+ if (
364
+ this.cursorState.transforms &&
365
+ typeof this.cursorState.transforms.doc === 'function' &&
366
+ result != null
367
+ ) {
368
+ return this.push(this.cursorState.transforms.doc(result));
369
+ }
237
370
 
238
- /**
239
- * Returns current buffered documents length
240
- * @method
241
- * @return {number} The number of items in the buffered documents
242
- */
243
- Cursor.prototype.bufferedCount = function() {
244
- return this.cursorState.documents.length - this.cursorState.cursorIndex;
245
- };
371
+ // Return the result
372
+ this.push(result);
246
373
 
247
- /**
248
- * Returns current buffered documents
249
- * @method
250
- * @return {Array} An array of buffered documents
251
- */
252
- Cursor.prototype.readBufferedDocuments = function(number) {
253
- var unreadDocumentsLength = this.cursorState.documents.length - this.cursorState.cursorIndex;
254
- var length = number < unreadDocumentsLength ? number : unreadDocumentsLength;
255
- var elements = this.cursorState.documents.slice(
256
- this.cursorState.cursorIndex,
257
- this.cursorState.cursorIndex + length
258
- );
259
-
260
- // Transform the doc with passed in transformation method if provided
261
- if (this.cursorState.transforms && typeof this.cursorState.transforms.doc === 'function') {
262
- // Transform all the elements
263
- for (var i = 0; i < elements.length; i++) {
264
- elements[i] = this.cursorState.transforms.doc(elements[i]);
265
- }
374
+ if (result === null && this.isDead()) {
375
+ this.once('end', () => {
376
+ this.close();
377
+ this.emit('finish');
378
+ });
379
+ }
380
+ });
266
381
  }
267
382
 
268
- // Ensure we do not return any more documents than the limit imposed
269
- // Just return the number of elements up to the limit
270
- if (
271
- this.cursorState.limit > 0 &&
272
- this.cursorState.currentLimit + elements.length > this.cursorState.limit
273
- ) {
274
- elements = elements.slice(0, this.cursorState.limit - this.cursorState.currentLimit);
275
- this.kill();
383
+ _endSession(options, callback) {
384
+ if (typeof options === 'function') {
385
+ callback = options;
386
+ options = {};
387
+ }
388
+ options = options || {};
389
+
390
+ const session = this.cursorState.session;
391
+
392
+ if (session && (options.force || session.owner === this)) {
393
+ this.cursorState.session = undefined;
394
+
395
+ if (this.operation) {
396
+ this.operation.clearSession();
397
+ }
398
+
399
+ session.endSession(callback);
400
+ return true;
401
+ }
402
+
403
+ if (callback) {
404
+ callback();
405
+ }
406
+
407
+ return false;
276
408
  }
277
409
 
278
- // Adjust current limit
279
- this.cursorState.currentLimit = this.cursorState.currentLimit + elements.length;
280
- this.cursorState.cursorIndex = this.cursorState.cursorIndex + elements.length;
410
+ _getMore(callback) {
411
+ if (this.logger.isDebug()) {
412
+ this.logger.debug(`schedule getMore call for query [${JSON.stringify(this.query)}]`);
413
+ }
281
414
 
282
- // Return elements
283
- return elements;
284
- };
415
+ // Set the current batchSize
416
+ let batchSize = this.cursorState.batchSize;
417
+ if (
418
+ this.cursorState.limit > 0 &&
419
+ this.cursorState.currentLimit + batchSize > this.cursorState.limit
420
+ ) {
421
+ batchSize = this.cursorState.limit - this.cursorState.currentLimit;
422
+ }
285
423
 
286
- /**
287
- * Kill the cursor
288
- * @method
289
- * @param {resultCallback} callback A callback function
290
- */
291
- Cursor.prototype.kill = function(callback) {
292
- // Set cursor to dead
293
- this.cursorState.dead = true;
294
- this.cursorState.killed = true;
295
- // Remove documents
296
- this.cursorState.documents = [];
297
-
298
- // If no cursor id just return
299
- if (
300
- this.cursorState.cursorId == null ||
301
- this.cursorState.cursorId.isZero() ||
302
- this.cursorState.init === false
303
- ) {
304
- if (callback) callback(null, null);
305
- return;
424
+ this.server.getMore(this.ns, this.cursorState, batchSize, this.options, callback);
306
425
  }
307
426
 
308
- wireProtocol.killCursors(this.server, this.ns, this.cursorState, callback);
309
- };
427
+ _initializeCursor(callback) {
428
+ const cursor = this;
310
429
 
311
- /**
312
- * Resets the cursor
313
- * @method
314
- * @return {null}
315
- */
316
- Cursor.prototype.rewind = function() {
317
- if (this.cursorState.init) {
318
- if (!this.cursorState.dead) {
319
- this.kill();
430
+ // NOTE: this goes away once cursors use `executeOperation`
431
+ if (isUnifiedTopology(cursor.topology) && cursor.topology.shouldCheckForSessionSupport()) {
432
+ cursor.topology.selectServer(ReadPreference.primaryPreferred, err => {
433
+ if (err) {
434
+ callback(err);
435
+ return;
436
+ }
437
+
438
+ cursor._next(callback);
439
+ });
440
+
441
+ return;
320
442
  }
321
443
 
322
- this.cursorState.currentLimit = 0;
323
- this.cursorState.init = false;
324
- this.cursorState.dead = false;
325
- this.cursorState.killed = false;
326
- this.cursorState.notified = false;
327
- this.cursorState.documents = [];
328
- this.cursorState.cursorId = null;
329
- this.cursorState.cursorIndex = 0;
444
+ function done(err, result) {
445
+ if (
446
+ cursor.cursorState.cursorId &&
447
+ cursor.cursorState.cursorId.isZero() &&
448
+ cursor._endSession
449
+ ) {
450
+ cursor._endSession();
451
+ }
452
+
453
+ if (
454
+ cursor.cursorState.documents.length === 0 &&
455
+ cursor.cursorState.cursorId &&
456
+ cursor.cursorState.cursorId.isZero() &&
457
+ !cursor.cmd.tailable &&
458
+ !cursor.cmd.awaitData
459
+ ) {
460
+ return setCursorNotified(cursor, callback);
461
+ }
462
+
463
+ callback(err, result);
464
+ }
465
+
466
+ const queryCallback = (err, r) => {
467
+ if (err) {
468
+ return done(err);
469
+ }
470
+
471
+ const result = r.message;
472
+ if (result.queryFailure) {
473
+ return done(new MongoError(result.documents[0]), null);
474
+ }
475
+
476
+ // Check if we have a command cursor
477
+ if (
478
+ Array.isArray(result.documents) &&
479
+ result.documents.length === 1 &&
480
+ (!cursor.cmd.find || (cursor.cmd.find && cursor.cmd.virtual === false)) &&
481
+ (typeof result.documents[0].cursor !== 'string' ||
482
+ result.documents[0]['$err'] ||
483
+ result.documents[0]['errmsg'] ||
484
+ Array.isArray(result.documents[0].result))
485
+ ) {
486
+ // We have an error document, return the error
487
+ if (result.documents[0]['$err'] || result.documents[0]['errmsg']) {
488
+ return done(new MongoError(result.documents[0]), null);
489
+ }
490
+
491
+ // We have a cursor document
492
+ if (result.documents[0].cursor != null && typeof result.documents[0].cursor !== 'string') {
493
+ const id = result.documents[0].cursor.id;
494
+ // If we have a namespace change set the new namespace for getmores
495
+ if (result.documents[0].cursor.ns) {
496
+ cursor.ns = result.documents[0].cursor.ns;
497
+ }
498
+ // Promote id to long if needed
499
+ cursor.cursorState.cursorId = typeof id === 'number' ? Long.fromNumber(id) : id;
500
+ cursor.cursorState.lastCursorId = cursor.cursorState.cursorId;
501
+ cursor.cursorState.operationTime = result.documents[0].operationTime;
502
+
503
+ // If we have a firstBatch set it
504
+ if (Array.isArray(result.documents[0].cursor.firstBatch)) {
505
+ cursor.cursorState.documents = result.documents[0].cursor.firstBatch; //.reverse();
506
+ }
507
+
508
+ // Return after processing command cursor
509
+ return done(null, result);
510
+ }
511
+
512
+ if (Array.isArray(result.documents[0].result)) {
513
+ cursor.cursorState.documents = result.documents[0].result;
514
+ cursor.cursorState.cursorId = Long.ZERO;
515
+ return done(null, result);
516
+ }
517
+ }
518
+
519
+ // Otherwise fall back to regular find path
520
+ const cursorId = result.cursorId || 0;
521
+ cursor.cursorState.cursorId = cursorId instanceof Long ? cursorId : Long.fromNumber(cursorId);
522
+ cursor.cursorState.documents = result.documents;
523
+ cursor.cursorState.lastCursorId = result.cursorId;
524
+
525
+ // Transform the results with passed in transformation method if provided
526
+ if (
527
+ cursor.cursorState.transforms &&
528
+ typeof cursor.cursorState.transforms.query === 'function'
529
+ ) {
530
+ cursor.cursorState.documents = cursor.cursorState.transforms.query(result);
531
+ }
532
+
533
+ done(null, result);
534
+ };
535
+
536
+ if (cursor.operation) {
537
+ if (cursor.logger.isDebug()) {
538
+ cursor.logger.debug(
539
+ `issue initial query [${JSON.stringify(cursor.cmd)}] with flags [${JSON.stringify(
540
+ cursor.query
541
+ )}]`
542
+ );
543
+ }
544
+
545
+ executeOperation(cursor.topology, cursor.operation, (err, result) => {
546
+ if (err) {
547
+ done(err);
548
+ return;
549
+ }
550
+
551
+ cursor.server = cursor.operation.server;
552
+ cursor.cursorState.init = true;
553
+
554
+ // NOTE: this is a special internal method for cloning a cursor, consider removing
555
+ if (cursor.cursorState.cursorId != null) {
556
+ return done();
557
+ }
558
+
559
+ queryCallback(err, result);
560
+ });
561
+
562
+ return;
563
+ }
564
+
565
+ // Very explicitly choose what is passed to selectServer
566
+ const serverSelectOptions = {};
567
+ if (cursor.cursorState.session) {
568
+ serverSelectOptions.session = cursor.cursorState.session;
569
+ }
570
+
571
+ if (cursor.operation) {
572
+ serverSelectOptions.readPreference = cursor.operation.readPreference;
573
+ } else if (cursor.options.readPreference) {
574
+ serverSelectOptions.readPreference = cursor.options.readPreference;
575
+ }
576
+
577
+ return cursor.topology.selectServer(serverSelectOptions, (err, server) => {
578
+ if (err) {
579
+ const disconnectHandler = cursor.disconnectHandler;
580
+ if (disconnectHandler != null) {
581
+ return disconnectHandler.addObjectAndMethod(
582
+ 'cursor',
583
+ cursor,
584
+ 'next',
585
+ [callback],
586
+ callback
587
+ );
588
+ }
589
+
590
+ return callback(err);
591
+ }
592
+
593
+ cursor.server = server;
594
+ cursor.cursorState.init = true;
595
+ if (collationNotSupported(cursor.server, cursor.cmd)) {
596
+ return callback(new MongoError(`server ${cursor.server.name} does not support collation`));
597
+ }
598
+
599
+ // NOTE: this is a special internal method for cloning a cursor, consider removing
600
+ if (cursor.cursorState.cursorId != null) {
601
+ return done();
602
+ }
603
+
604
+ if (cursor.logger.isDebug()) {
605
+ cursor.logger.debug(
606
+ `issue initial query [${JSON.stringify(cursor.cmd)}] with flags [${JSON.stringify(
607
+ cursor.query
608
+ )}]`
609
+ );
610
+ }
611
+
612
+ if (cursor.cmd.find != null) {
613
+ server.query(cursor.ns, cursor.cmd, cursor.cursorState, cursor.options, queryCallback);
614
+ return;
615
+ }
616
+
617
+ const commandOptions = Object.assign({ session: cursor.cursorState.session }, cursor.options);
618
+ server.command(cursor.ns, cursor.cmd, commandOptions, queryCallback);
619
+ });
330
620
  }
331
- };
621
+ }
622
+
623
+ if (SUPPORTS.ASYNC_ITERATOR) {
624
+ CoreCursor.prototype[Symbol.asyncIterator] = require('../async/async_iterator').asyncIterator;
625
+ }
332
626
 
333
627
  /**
334
628
  * Validate if the pool is dead and return error
335
629
  */
336
- var isConnectionDead = function(self, callback) {
630
+ function isConnectionDead(self, callback) {
337
631
  if (self.pool && self.pool.isDestroyed()) {
338
632
  self.cursorState.killed = true;
339
633
  const err = new MongoNetworkError(
340
- f('connection to host %s:%s was destroyed', self.pool.host, self.pool.port)
634
+ `connection to host ${self.pool.host}:${self.pool.port} was destroyed`
341
635
  );
636
+
342
637
  _setCursorNotifiedImpl(self, () => callback(err));
343
638
  return true;
344
639
  }
345
640
 
346
641
  return false;
347
- };
642
+ }
348
643
 
349
644
  /**
350
645
  * Validate if the cursor is dead but was not explicitly killed by user
351
646
  */
352
- var isCursorDeadButNotkilled = function(self, callback) {
647
+ function isCursorDeadButNotkilled(self, callback) {
353
648
  // Cursor is dead but not marked killed, return null
354
649
  if (self.cursorState.dead && !self.cursorState.killed) {
355
650
  self.cursorState.killed = true;
@@ -358,58 +653,61 @@ var isCursorDeadButNotkilled = function(self, callback) {
358
653
  }
359
654
 
360
655
  return false;
361
- };
656
+ }
362
657
 
363
658
  /**
364
659
  * Validate if the cursor is dead and was killed by user
365
660
  */
366
- var isCursorDeadAndKilled = function(self, callback) {
661
+ function isCursorDeadAndKilled(self, callback) {
367
662
  if (self.cursorState.dead && self.cursorState.killed) {
368
663
  handleCallback(callback, new MongoError('cursor is dead'));
369
664
  return true;
370
665
  }
371
666
 
372
667
  return false;
373
- };
668
+ }
374
669
 
375
670
  /**
376
671
  * Validate if the cursor was killed by the user
377
672
  */
378
- var isCursorKilled = function(self, callback) {
673
+ function isCursorKilled(self, callback) {
379
674
  if (self.cursorState.killed) {
380
675
  setCursorNotified(self, callback);
381
676
  return true;
382
677
  }
383
678
 
384
679
  return false;
385
- };
680
+ }
386
681
 
387
682
  /**
388
683
  * Mark cursor as being dead and notified
389
684
  */
390
- var setCursorDeadAndNotified = function(self, callback) {
685
+ function setCursorDeadAndNotified(self, callback) {
391
686
  self.cursorState.dead = true;
392
687
  setCursorNotified(self, callback);
393
- };
688
+ }
394
689
 
395
690
  /**
396
691
  * Mark cursor as being notified
397
692
  */
398
- var setCursorNotified = function(self, callback) {
693
+ function setCursorNotified(self, callback) {
399
694
  _setCursorNotifiedImpl(self, () => handleCallback(callback, null, null));
400
- };
695
+ }
401
696
 
402
- var _setCursorNotifiedImpl = function(self, callback) {
697
+ function _setCursorNotifiedImpl(self, callback) {
403
698
  self.cursorState.notified = true;
404
699
  self.cursorState.documents = [];
405
700
  self.cursorState.cursorIndex = 0;
701
+
406
702
  if (self._endSession) {
407
- return self._endSession(undefined, () => callback());
703
+ self._endSession(undefined, () => callback());
704
+ return;
408
705
  }
706
+
409
707
  return callback();
410
- };
708
+ }
411
709
 
412
- var nextFunction = function(self, callback) {
710
+ function nextFunction(self, callback) {
413
711
  // We have notified about it
414
712
  if (self.cursorState.notified) {
415
713
  return callback(new Error('cursor is exhausted'));
@@ -426,7 +724,37 @@ var nextFunction = function(self, callback) {
426
724
 
427
725
  // We have just started the cursor
428
726
  if (!self.cursorState.init) {
429
- return initializeCursor(self, callback);
727
+ // Topology is not connected, save the call in the provided store to be
728
+ // Executed at some point when the handler deems it's reconnected
729
+ if (!self.topology.isConnected(self.options)) {
730
+ // Only need this for single server, because repl sets and mongos
731
+ // will always continue trying to reconnect
732
+ if (self.topology._type === 'server' && !self.topology.s.options.reconnect) {
733
+ // Reconnect is disabled, so we'll never reconnect
734
+ return callback(new MongoError('no connection available'));
735
+ }
736
+
737
+ if (self.disconnectHandler != null) {
738
+ if (self.topology.isDestroyed()) {
739
+ // Topology was destroyed, so don't try to wait for it to reconnect
740
+ return callback(new MongoError('Topology was destroyed'));
741
+ }
742
+
743
+ self.disconnectHandler.addObjectAndMethod('cursor', self, 'next', [callback], callback);
744
+ return;
745
+ }
746
+ }
747
+
748
+ self._initializeCursor((err, result) => {
749
+ if (err || result === null) {
750
+ callback(err, result);
751
+ return;
752
+ }
753
+
754
+ nextFunction(self, callback);
755
+ });
756
+
757
+ return;
430
758
  }
431
759
 
432
760
  if (self.cursorState.limit > 0 && self.cursorState.currentLimit >= self.cursorState.limit) {
@@ -449,11 +777,11 @@ var nextFunction = function(self, callback) {
449
777
  );
450
778
 
451
779
  // Check if connection is dead and return if not possible to
452
- // execute a getmore on this connection
780
+ // execute a getMore on this connection
453
781
  if (isConnectionDead(self, callback)) return;
454
782
 
455
783
  // Execute the next get more
456
- self._getmore(function(err, doc, connection) {
784
+ self._getMore(function(err, doc, connection) {
457
785
  if (err) {
458
786
  if (err instanceof MongoError) {
459
787
  err[mongoErrorContextSymbol].isGetMore = true;
@@ -530,7 +858,7 @@ var nextFunction = function(self, callback) {
530
858
  self.cursorState.currentLimit += 1;
531
859
 
532
860
  // Get the document
533
- var doc = self.cursorState.documents[self.cursorState.cursorIndex++];
861
+ let doc = self.cursorState.documents[self.cursorState.cursorIndex++];
534
862
 
535
863
  // Doc overflow
536
864
  if (!doc || doc.$err) {
@@ -550,193 +878,9 @@ var nextFunction = function(self, callback) {
550
878
  // Return the document
551
879
  handleCallback(callback, null, doc);
552
880
  }
553
- };
554
-
555
- function initializeCursor(cursor, callback) {
556
- // Topology is not connected, save the call in the provided store to be
557
- // Executed at some point when the handler deems it's reconnected
558
- if (!cursor.topology.isConnected(cursor.options)) {
559
- // Only need this for single server, because repl sets and mongos
560
- // will always continue trying to reconnect
561
- if (cursor.topology._type === 'server' && !cursor.topology.s.options.reconnect) {
562
- // Reconnect is disabled, so we'll never reconnect
563
- return callback(new MongoError('no connection available'));
564
- }
565
-
566
- if (cursor.disconnectHandler != null) {
567
- if (cursor.topology.isDestroyed()) {
568
- // Topology was destroyed, so don't try to wait for it to reconnect
569
- return callback(new MongoError('Topology was destroyed'));
570
- }
571
-
572
- return cursor.disconnectHandler.addObjectAndMethod(
573
- 'cursor',
574
- cursor,
575
- 'next',
576
- [callback],
577
- callback
578
- );
579
- }
580
- }
581
-
582
- // Very explicitly choose what is passed to selectServer
583
- const serverSelectOptions = {};
584
- if (cursor.cursorState.session) {
585
- serverSelectOptions.session = cursor.cursorState.session;
586
- }
587
- if (cursor.options.readPreference) {
588
- serverSelectOptions.readPreference = cursor.options.readPreference;
589
- }
590
-
591
- return cursor.topology.selectServer(serverSelectOptions, (err, server) => {
592
- if (err) {
593
- const disconnectHandler = cursor.disconnectHandler;
594
- if (disconnectHandler != null) {
595
- return disconnectHandler.addObjectAndMethod('cursor', cursor, 'next', [callback], callback);
596
- }
597
-
598
- return callback(err);
599
- }
600
-
601
- cursor.server = server;
602
- cursor.cursorState.init = true;
603
- if (collationNotSupported(cursor.server, cursor.cmd)) {
604
- return callback(new MongoError(`server ${cursor.server.name} does not support collation`));
605
- }
606
-
607
- function done() {
608
- if (
609
- cursor.cursorState.cursorId &&
610
- cursor.cursorState.cursorId.isZero() &&
611
- cursor._endSession
612
- ) {
613
- cursor._endSession();
614
- }
615
-
616
- if (
617
- cursor.cursorState.documents.length === 0 &&
618
- cursor.cursorState.cursorId &&
619
- cursor.cursorState.cursorId.isZero() &&
620
- !cursor.cmd.tailable &&
621
- !cursor.cmd.awaitData
622
- ) {
623
- return setCursorNotified(cursor, callback);
624
- }
625
-
626
- nextFunction(cursor, callback);
627
- }
628
-
629
- // NOTE: this is a special internal method for cloning a cursor, consider removing
630
- if (cursor.cursorState.cursorId != null) {
631
- return done();
632
- }
633
-
634
- const queryCallback = (err, r) => {
635
- if (err) return callback(err);
636
-
637
- const result = r.message;
638
- if (result.queryFailure) {
639
- return callback(new MongoError(result.documents[0]), null);
640
- }
641
-
642
- // Check if we have a command cursor
643
- if (
644
- Array.isArray(result.documents) &&
645
- result.documents.length === 1 &&
646
- (!cursor.cmd.find || (cursor.cmd.find && cursor.cmd.virtual === false)) &&
647
- (typeof result.documents[0].cursor !== 'string' ||
648
- result.documents[0]['$err'] ||
649
- result.documents[0]['errmsg'] ||
650
- Array.isArray(result.documents[0].result))
651
- ) {
652
- // We have an error document, return the error
653
- if (result.documents[0]['$err'] || result.documents[0]['errmsg']) {
654
- return callback(new MongoError(result.documents[0]), null);
655
- }
656
-
657
- // We have a cursor document
658
- if (result.documents[0].cursor != null && typeof result.documents[0].cursor !== 'string') {
659
- var id = result.documents[0].cursor.id;
660
- // If we have a namespace change set the new namespace for getmores
661
- if (result.documents[0].cursor.ns) {
662
- cursor.ns = result.documents[0].cursor.ns;
663
- }
664
- // Promote id to long if needed
665
- cursor.cursorState.cursorId = typeof id === 'number' ? Long.fromNumber(id) : id;
666
- cursor.cursorState.lastCursorId = cursor.cursorState.cursorId;
667
- cursor.cursorState.operationTime = result.documents[0].operationTime;
668
- // If we have a firstBatch set it
669
- if (Array.isArray(result.documents[0].cursor.firstBatch)) {
670
- cursor.cursorState.documents = result.documents[0].cursor.firstBatch; //.reverse();
671
- }
672
-
673
- // Return after processing command cursor
674
- return done(result);
675
- }
676
-
677
- if (Array.isArray(result.documents[0].result)) {
678
- cursor.cursorState.documents = result.documents[0].result;
679
- cursor.cursorState.cursorId = Long.ZERO;
680
- return done(result);
681
- }
682
- }
683
-
684
- // Otherwise fall back to regular find path
685
- const cursorId = result.cursorId || 0;
686
- cursor.cursorState.cursorId = cursorId instanceof Long ? cursorId : Long.fromNumber(cursorId);
687
- cursor.cursorState.documents = result.documents;
688
- cursor.cursorState.lastCursorId = result.cursorId;
689
-
690
- // Transform the results with passed in transformation method if provided
691
- if (
692
- cursor.cursorState.transforms &&
693
- typeof cursor.cursorState.transforms.query === 'function'
694
- ) {
695
- cursor.cursorState.documents = cursor.cursorState.transforms.query(result);
696
- }
697
-
698
- // Return callback
699
- done(result);
700
- };
701
-
702
- if (cursor.logger.isDebug()) {
703
- cursor.logger.debug(
704
- `issue initial query [${JSON.stringify(cursor.cmd)}] with flags [${JSON.stringify(
705
- cursor.query
706
- )}]`
707
- );
708
- }
709
-
710
- if (cursor.cmd.find != null) {
711
- wireProtocol.query(
712
- cursor.server,
713
- cursor.ns,
714
- cursor.cmd,
715
- cursor.cursorState,
716
- cursor.options,
717
- queryCallback
718
- );
719
-
720
- return;
721
- }
722
-
723
- cursor.query = wireProtocol.command(
724
- cursor.server,
725
- cursor.ns,
726
- cursor.cmd,
727
- cursor.options,
728
- queryCallback
729
- );
730
- });
731
881
  }
732
882
 
733
- /**
734
- * Retrieve the next document from the cursor
735
- * @method
736
- * @param {resultCallback} callback A callback function
737
- */
738
- Cursor.prototype.next = function(callback) {
739
- nextFunction(this, callback);
883
+ module.exports = {
884
+ CursorState,
885
+ CoreCursor
740
886
  };
741
-
742
- module.exports = Cursor;