mongodb 3.6.1 → 3.6.5

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 (61) hide show
  1. package/HISTORY.md +71 -1
  2. package/README.md +63 -69
  3. package/lib/admin.js +10 -8
  4. package/lib/aggregation_cursor.js +7 -8
  5. package/lib/bulk/common.js +5 -4
  6. package/lib/change_stream.js +1 -1
  7. package/lib/cmap/connection.js +1 -1
  8. package/lib/cmap/connection_pool.js +5 -15
  9. package/lib/collection.js +114 -67
  10. package/lib/core/auth/gssapi.js +66 -66
  11. package/lib/core/auth/scram.js +2 -1
  12. package/lib/core/auth/x509.js +1 -1
  13. package/lib/core/connection/connect.js +1 -1
  14. package/lib/core/connection/connection.js +3 -4
  15. package/lib/core/connection/logger.js +1 -0
  16. package/lib/core/connection/msg.js +3 -1
  17. package/lib/core/connection/pool.js +3 -3
  18. package/lib/core/cursor.js +22 -12
  19. package/lib/core/sdam/monitor.js +14 -11
  20. package/lib/core/sdam/topology.js +4 -2
  21. package/lib/core/sdam/topology_description.js +25 -6
  22. package/lib/core/sessions.js +29 -35
  23. package/lib/core/tools/smoke_plugin.js +1 -0
  24. package/lib/core/topologies/mongos.js +2 -1
  25. package/lib/core/topologies/read_preference.js +2 -1
  26. package/lib/core/topologies/replset.js +14 -8
  27. package/lib/core/topologies/server.js +5 -4
  28. package/lib/core/uri_parser.js +11 -3
  29. package/lib/core/wireprotocol/kill_cursors.js +2 -1
  30. package/lib/core/wireprotocol/query.js +12 -11
  31. package/lib/core/wireprotocol/write_command.js +10 -1
  32. package/lib/cursor.js +15 -13
  33. package/lib/db.js +52 -30
  34. package/lib/explain.js +55 -0
  35. package/lib/gridfs/grid_store.js +14 -9
  36. package/lib/gridfs-stream/upload.js +5 -3
  37. package/lib/mongo_client.js +14 -15
  38. package/lib/operations/add_user.js +5 -2
  39. package/lib/operations/admin_ops.js +1 -1
  40. package/lib/operations/aggregate.js +5 -7
  41. package/lib/operations/command_v2.js +13 -2
  42. package/lib/operations/common_functions.js +14 -27
  43. package/lib/operations/connect.js +38 -15
  44. package/lib/operations/delete_many.js +15 -2
  45. package/lib/operations/delete_one.js +15 -2
  46. package/lib/operations/distinct.js +10 -2
  47. package/lib/operations/execute_operation.js +47 -69
  48. package/lib/operations/find.js +7 -1
  49. package/lib/operations/find_and_modify.js +14 -1
  50. package/lib/operations/find_one.js +4 -0
  51. package/lib/operations/map_reduce.js +20 -1
  52. package/lib/operations/operation.js +11 -1
  53. package/lib/operations/update_many.js +23 -2
  54. package/lib/operations/update_one.js +22 -16
  55. package/lib/operations/validate_collection.js +1 -1
  56. package/lib/topologies/mongos.js +1 -1
  57. package/lib/topologies/server.js +1 -1
  58. package/lib/url_parser.js +19 -14
  59. package/lib/utils.js +115 -32
  60. package/lib/write_concern.js +22 -4
  61. package/package.json +32 -9
@@ -11,6 +11,7 @@ const MongoError = require('../core').MongoError;
11
11
  const ReadPreference = require('../core').ReadPreference;
12
12
  const toError = require('../utils').toError;
13
13
  const CursorState = require('../core/cursor').CursorState;
14
+ const maxWireVersion = require('../core/utils').maxWireVersion;
14
15
 
15
16
  /**
16
17
  * Build the count command.
@@ -57,14 +58,6 @@ function buildCountCommand(collectionOrCursor, query, options) {
57
58
  return cmd;
58
59
  }
59
60
 
60
- function deleteCallback(err, r, callback) {
61
- if (callback == null) return;
62
- if (err && callback) return callback(err);
63
- if (r == null) return callback(null, { result: { ok: 1 } });
64
- r.deletedCount = r.result.n;
65
- if (callback) callback(null, r);
66
- }
67
-
68
61
  /**
69
62
  * Find and update a document.
70
63
  *
@@ -308,6 +301,12 @@ function removeDocuments(coll, selector, options, callback) {
308
301
  return callback(err, null);
309
302
  }
310
303
 
304
+ if (options.explain !== undefined && maxWireVersion(coll.s.topology) < 3) {
305
+ return callback
306
+ ? callback(new MongoError(`server does not support explain on remove`))
307
+ : undefined;
308
+ }
309
+
311
310
  // Execute the remove
312
311
  coll.s.topology.remove(coll.s.namespace, [op], finalOptions, (err, result) => {
313
312
  if (callback == null) return;
@@ -369,6 +368,12 @@ function updateDocuments(coll, selector, document, options, callback) {
369
368
  return callback(err, null);
370
369
  }
371
370
 
371
+ if (options.explain !== undefined && maxWireVersion(coll.s.topology) < 3) {
372
+ return callback
373
+ ? callback(new MongoError(`server does not support explain on update`))
374
+ : undefined;
375
+ }
376
+
372
377
  // Update options
373
378
  coll.s.topology.update(coll.s.namespace, [op], finalOptions, (err, result) => {
374
379
  if (callback == null) return;
@@ -382,31 +387,13 @@ function updateDocuments(coll, selector, document, options, callback) {
382
387
  });
383
388
  }
384
389
 
385
- function updateCallback(err, r, callback) {
386
- if (callback == null) return;
387
- if (err) return callback(err);
388
- if (r == null) return callback(null, { result: { ok: 1 } });
389
- r.modifiedCount = r.result.nModified != null ? r.result.nModified : r.result.n;
390
- r.upsertedId =
391
- Array.isArray(r.result.upserted) && r.result.upserted.length > 0
392
- ? r.result.upserted[0] // FIXME(major): should be `r.result.upserted[0]._id`
393
- : null;
394
- r.upsertedCount =
395
- Array.isArray(r.result.upserted) && r.result.upserted.length ? r.result.upserted.length : 0;
396
- r.matchedCount =
397
- Array.isArray(r.result.upserted) && r.result.upserted.length > 0 ? 0 : r.result.n;
398
- callback(null, r);
399
- }
400
-
401
390
  module.exports = {
402
391
  buildCountCommand,
403
- deleteCallback,
404
392
  findAndModify,
405
393
  indexInformation,
406
394
  nextObject,
407
395
  prepareDocs,
408
396
  insertDocuments,
409
397
  removeDocuments,
410
- updateDocuments,
411
- updateCallback
398
+ updateDocuments
412
399
  };
@@ -13,7 +13,9 @@ const ReplSet = require('../topologies/replset');
13
13
  const Server = require('../topologies/server');
14
14
  const ServerSessionPool = require('../core').Sessions.ServerSessionPool;
15
15
  const emitDeprecationWarning = require('../utils').emitDeprecationWarning;
16
+ const emitWarningOnce = require('../utils').emitWarningOnce;
16
17
  const fs = require('fs');
18
+ const WriteConcern = require('../write_concern');
17
19
  const BSON = require('../core/connection/utils').retrieveBSON();
18
20
  const CMAP_EVENT_NAMES = require('../cmap/events').CMAP_EVENT_NAMES;
19
21
 
@@ -105,6 +107,7 @@ const validOptionNames = [
105
107
  'w',
106
108
  'wtimeout',
107
109
  'j',
110
+ 'writeConcern',
108
111
  'forceServerObjectId',
109
112
  'serializeFunctions',
110
113
  'ignoreUndefined',
@@ -180,12 +183,12 @@ function validOptions(options) {
180
183
  if (options.validateOptions) {
181
184
  return new MongoError(`option ${name} is not supported`);
182
185
  } else {
183
- console.warn(`the options [${name}] is not supported`);
186
+ emitWarningOnce(`the options [${name}] is not supported`);
184
187
  }
185
188
  }
186
189
 
187
190
  if (legacyOptionNames.indexOf(name) !== -1) {
188
- console.warn(
191
+ emitWarningOnce(
189
192
  `the server/replset/mongos/db options are deprecated, ` +
190
193
  `all their options are supported at the top level of the options object [${validOptionNames}]`
191
194
  );
@@ -255,9 +258,6 @@ function resolveTLSOptions(options) {
255
258
  });
256
259
  }
257
260
 
258
- const emitDeprecationForNonUnifiedTopology = deprecate(() => {},
259
- 'current Server Discovery and Monitoring engine is deprecated, and will be removed in a future version. ' + 'To use the new Server Discover and Monitoring engine, pass option { useUnifiedTopology: true } to the MongoClient constructor.');
260
-
261
261
  function connect(mongoClient, url, options, callback) {
262
262
  options = Object.assign({}, options);
263
263
 
@@ -290,7 +290,7 @@ function connect(mongoClient, url, options, callback) {
290
290
  const _finalOptions = createUnifiedOptions(object, options);
291
291
 
292
292
  // Check if we have connection and socket timeout set
293
- if (_finalOptions.socketTimeoutMS == null) _finalOptions.socketTimeoutMS = 360000;
293
+ if (_finalOptions.socketTimeoutMS == null) _finalOptions.socketTimeoutMS = 0;
294
294
  if (_finalOptions.connectTimeoutMS == null) _finalOptions.connectTimeoutMS = 10000;
295
295
  if (_finalOptions.retryWrites == null) _finalOptions.retryWrites = true;
296
296
  if (_finalOptions.useRecoveryToken == null) _finalOptions.useRecoveryToken = true;
@@ -300,18 +300,16 @@ function connect(mongoClient, url, options, callback) {
300
300
  delete _finalOptions.db_options.auth;
301
301
  }
302
302
 
303
- // `journal` should be translated to `j` for the driver
304
- if (_finalOptions.journal != null) {
305
- _finalOptions.j = _finalOptions.journal;
306
- _finalOptions.journal = undefined;
307
- }
308
-
309
303
  // resolve tls options if needed
310
304
  resolveTLSOptions(_finalOptions);
311
305
 
312
306
  // Store the merged options object
313
307
  mongoClient.s.options = _finalOptions;
314
308
 
309
+ // Apply read and write concern from parsed url
310
+ mongoClient.s.readPreference = ReadPreference.fromOptions(_finalOptions);
311
+ mongoClient.s.writeConcern = WriteConcern.fromOptions(_finalOptions);
312
+
315
313
  // Failure modes
316
314
  if (object.servers.length === 0) {
317
315
  return callback(new Error('connection string must contain at least one seed host'));
@@ -335,7 +333,9 @@ function connect(mongoClient, url, options, callback) {
335
333
  return createTopology(mongoClient, 'unified', _finalOptions, connectCallback);
336
334
  }
337
335
 
338
- emitDeprecationForNonUnifiedTopology();
336
+ emitWarningOnce(
337
+ 'Current Server Discovery and Monitoring engine is deprecated, and will be removed in a future version. To use the new Server Discover and Monitoring engine, pass option { useUnifiedTopology: true } to the MongoClient constructor.'
338
+ );
339
339
 
340
340
  // Do we have a replicaset then skip discovery and go straight to connectivity
341
341
  if (_finalOptions.replicaSet || _finalOptions.rs_name) {
@@ -616,9 +616,12 @@ function createUnifiedOptions(finalOptions, options) {
616
616
  'mongos_options'
617
617
  ];
618
618
  const noMerge = ['readconcern', 'compression', 'autoencryption'];
619
+ const skip = ['w', 'wtimeout', 'j', 'journal', 'fsync', 'writeConcern'];
619
620
 
620
621
  for (const name in options) {
621
- if (noMerge.indexOf(name.toLowerCase()) !== -1) {
622
+ if (skip.indexOf(name.toLowerCase()) !== -1) {
623
+ continue;
624
+ } else if (noMerge.indexOf(name.toLowerCase()) !== -1) {
622
625
  finalOptions[name] = options[name];
623
626
  } else if (childOptions.indexOf(name.toLowerCase()) !== -1) {
624
627
  finalOptions = mergeOptions(finalOptions, options[name], false);
@@ -636,6 +639,14 @@ function createUnifiedOptions(finalOptions, options) {
636
639
  }
637
640
  }
638
641
 
642
+ // Handle write concern keys separately, since `options` may have the keys at the top level or
643
+ // under `options.writeConcern`. The final merged keys will be under `finalOptions.writeConcern`.
644
+ // This way, `fromOptions` will warn once if `options` is using deprecated write concern options
645
+ const optionsWriteConcern = WriteConcern.fromOptions(options);
646
+ if (optionsWriteConcern) {
647
+ finalOptions.writeConcern = Object.assign({}, finalOptions.writeConcern, optionsWriteConcern);
648
+ }
649
+
639
650
  return finalOptions;
640
651
  }
641
652
 
@@ -760,12 +771,24 @@ function transformUrlOptions(_object) {
760
771
 
761
772
  if (object.wTimeoutMS) {
762
773
  object.wtimeout = object.wTimeoutMS;
774
+ object.wTimeoutMS = undefined;
763
775
  }
764
776
 
765
777
  if (_object.srvHost) {
766
778
  object.srvHost = _object.srvHost;
767
779
  }
768
780
 
781
+ // Any write concern options from the URL will be top-level, so we manually
782
+ // move them options under `object.writeConcern` to avoid warnings later
783
+ const wcKeys = ['w', 'wtimeout', 'j', 'journal', 'fsync'];
784
+ for (const key of wcKeys) {
785
+ if (object[key] !== undefined) {
786
+ if (object.writeConcern === undefined) object.writeConcern = {};
787
+ object.writeConcern[key] = object[key];
788
+ object[key] = undefined;
789
+ }
790
+ }
791
+
769
792
  return object;
770
793
  }
771
794
 
@@ -788,7 +811,7 @@ function translateOptions(options, translationOptions) {
788
811
  }
789
812
 
790
813
  // Set the socket and connection timeouts
791
- if (options.socketTimeoutMS == null) options.socketTimeoutMS = 360000;
814
+ if (options.socketTimeoutMS == null) options.socketTimeoutMS = 0;
792
815
  if (options.connectTimeoutMS == null) options.connectTimeoutMS = 10000;
793
816
 
794
817
  if (!translationOptions.createServers) {
@@ -1,8 +1,9 @@
1
1
  'use strict';
2
2
 
3
3
  const OperationBase = require('./operation').OperationBase;
4
- const deleteCallback = require('./common_functions').deleteCallback;
5
4
  const removeDocuments = require('./common_functions').removeDocuments;
5
+ const Aspect = require('./operation').Aspect;
6
+ const defineAspects = require('./operation').defineAspects;
6
7
 
7
8
  class DeleteManyOperation extends OperationBase {
8
9
  constructor(collection, filter, options) {
@@ -18,8 +19,20 @@ class DeleteManyOperation extends OperationBase {
18
19
  const options = this.options;
19
20
 
20
21
  options.single = false;
21
- removeDocuments(coll, filter, options, (err, r) => deleteCallback(err, r, callback));
22
+ removeDocuments(coll, filter, options, (err, r) => {
23
+ if (callback == null) return;
24
+ if (err && callback) return callback(err);
25
+ if (r == null) return callback(null, { result: { ok: 1 } });
26
+
27
+ // If an explain operation was executed, don't process the server results
28
+ if (this.explain) return callback(undefined, r.result);
29
+
30
+ r.deletedCount = r.result.n;
31
+ callback(null, r);
32
+ });
22
33
  }
23
34
  }
24
35
 
36
+ defineAspects(DeleteManyOperation, [Aspect.EXPLAINABLE]);
37
+
25
38
  module.exports = DeleteManyOperation;
@@ -1,8 +1,9 @@
1
1
  'use strict';
2
2
 
3
3
  const OperationBase = require('./operation').OperationBase;
4
- const deleteCallback = require('./common_functions').deleteCallback;
5
4
  const removeDocuments = require('./common_functions').removeDocuments;
5
+ const Aspect = require('./operation').Aspect;
6
+ const defineAspects = require('./operation').defineAspects;
6
7
 
7
8
  class DeleteOneOperation extends OperationBase {
8
9
  constructor(collection, filter, options) {
@@ -18,8 +19,20 @@ class DeleteOneOperation extends OperationBase {
18
19
  const options = this.options;
19
20
 
20
21
  options.single = true;
21
- removeDocuments(coll, filter, options, (err, r) => deleteCallback(err, r, callback));
22
+ removeDocuments(coll, filter, options, (err, r) => {
23
+ if (callback == null) return;
24
+ if (err && callback) return callback(err);
25
+ if (r == null) return callback(null, { result: { ok: 1 } });
26
+
27
+ // If an explain operation was executed, don't process the server results
28
+ if (this.explain) return callback(undefined, r.result);
29
+
30
+ r.deletedCount = r.result.n;
31
+ callback(null, r);
32
+ });
22
33
  }
23
34
  }
24
35
 
36
+ defineAspects(DeleteOneOperation, [Aspect.EXPLAINABLE]);
37
+
25
38
  module.exports = DeleteOneOperation;
@@ -5,6 +5,8 @@ const defineAspects = require('./operation').defineAspects;
5
5
  const CommandOperationV2 = require('./command_v2');
6
6
  const decorateWithCollation = require('../utils').decorateWithCollation;
7
7
  const decorateWithReadConcern = require('../utils').decorateWithReadConcern;
8
+ const maxWireVersion = require('../core/utils').maxWireVersion;
9
+ const MongoError = require('../error').MongoError;
8
10
 
9
11
  /**
10
12
  * Return a list of distinct values for the given key across a collection.
@@ -65,13 +67,18 @@ class DistinctOperation extends CommandOperationV2 {
65
67
  return callback(err, null);
66
68
  }
67
69
 
70
+ if (this.explain && maxWireVersion(server) < 4) {
71
+ callback(new MongoError(`server does not support explain on distinct`));
72
+ return;
73
+ }
74
+
68
75
  super.executeCommand(server, cmd, (err, result) => {
69
76
  if (err) {
70
77
  callback(err);
71
78
  return;
72
79
  }
73
80
 
74
- callback(null, this.options.full ? result : result.values);
81
+ callback(null, this.options.full || this.explain ? result : result.values);
75
82
  });
76
83
  }
77
84
  }
@@ -79,7 +86,8 @@ class DistinctOperation extends CommandOperationV2 {
79
86
  defineAspects(DistinctOperation, [
80
87
  Aspect.READ_OPERATION,
81
88
  Aspect.RETRYABLE,
82
- Aspect.EXECUTE_WITH_SELECTION
89
+ Aspect.EXECUTE_WITH_SELECTION,
90
+ Aspect.EXPLAINABLE
83
91
  ]);
84
92
 
85
93
  module.exports = DistinctOperation;
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ const maybePromise = require('../utils').maybePromise;
3
4
  const MongoError = require('../core/error').MongoError;
4
5
  const Aspect = require('./operation').Aspect;
5
6
  const OperationBase = require('./operation').OperationBase;
@@ -21,7 +22,7 @@ const isUnifiedTopology = require('../core/utils').isUnifiedTopology;
21
22
  * @param {Operation} operation The operation to execute
22
23
  * @param {function} callback The command result callback
23
24
  */
24
- function executeOperation(topology, operation, callback) {
25
+ function executeOperation(topology, operation, cb) {
25
26
  if (topology == null) {
26
27
  throw new TypeError('This method requires a valid topology instance');
27
28
  }
@@ -30,64 +31,57 @@ function executeOperation(topology, operation, callback) {
30
31
  throw new TypeError('This method requires a valid operation instance');
31
32
  }
32
33
 
33
- if (isUnifiedTopology(topology) && topology.shouldCheckForSessionSupport()) {
34
- return selectServerForSessionSupport(topology, operation, callback);
35
- }
36
-
37
- const Promise = topology.s.promiseLibrary;
38
-
39
- // The driver sessions spec mandates that we implicitly create sessions for operations
40
- // that are not explicitly provided with a session.
41
- let session, owner;
42
- if (topology.hasSessionSupport()) {
43
- if (operation.session == null) {
44
- owner = Symbol();
45
- session = topology.startSession({ owner });
46
- operation.session = session;
47
- } else if (operation.session.hasEnded) {
48
- throw new MongoError('Use of expired sessions is not permitted');
34
+ return maybePromise(topology, cb, callback => {
35
+ if (isUnifiedTopology(topology) && topology.shouldCheckForSessionSupport()) {
36
+ // Recursive call to executeOperation after a server selection
37
+ return selectServerForSessionSupport(topology, operation, callback);
49
38
  }
50
- }
51
-
52
- let result;
53
- if (typeof callback !== 'function') {
54
- result = new Promise((resolve, reject) => {
55
- callback = (err, res) => {
56
- if (err) return reject(err);
57
- resolve(res);
58
- };
59
- });
60
- }
61
39
 
62
- function executeCallback(err, result) {
63
- if (session && session.owner === owner) {
64
- session.endSession();
65
- if (operation.session === session) {
66
- operation.clearSession();
40
+ // The driver sessions spec mandates that we implicitly create sessions for operations
41
+ // that are not explicitly provided with a session.
42
+ let session, owner;
43
+ if (topology.hasSessionSupport()) {
44
+ if (operation.session == null) {
45
+ owner = Symbol();
46
+ session = topology.startSession({ owner });
47
+ operation.session = session;
48
+ } else if (operation.session.hasEnded) {
49
+ return callback(new MongoError('Use of expired sessions is not permitted'));
67
50
  }
51
+ } else if (operation.session) {
52
+ // If the user passed an explicit session and we are still, after server selection,
53
+ // trying to run against a topology that doesn't support sessions we error out.
54
+ return callback(new MongoError('Current topology does not support sessions'));
68
55
  }
69
56
 
70
- callback(err, result);
71
- }
72
-
73
- try {
74
- if (operation.hasAspect(Aspect.EXECUTE_WITH_SELECTION)) {
75
- executeWithServerSelection(topology, operation, executeCallback);
76
- } else {
77
- operation.execute(executeCallback);
78
- }
79
- } catch (e) {
80
- if (session && session.owner === owner) {
81
- session.endSession();
82
- if (operation.session === session) {
83
- operation.clearSession();
57
+ function executeCallback(err, result) {
58
+ if (session && session.owner === owner) {
59
+ session.endSession();
60
+ if (operation.session === session) {
61
+ operation.clearSession();
62
+ }
84
63
  }
64
+
65
+ callback(err, result);
85
66
  }
86
67
 
87
- throw e;
88
- }
68
+ try {
69
+ if (operation.hasAspect(Aspect.EXECUTE_WITH_SELECTION)) {
70
+ executeWithServerSelection(topology, operation, executeCallback);
71
+ } else {
72
+ operation.execute(executeCallback);
73
+ }
74
+ } catch (error) {
75
+ if (session && session.owner === owner) {
76
+ session.endSession();
77
+ if (operation.session === session) {
78
+ operation.clearSession();
79
+ }
80
+ }
89
81
 
90
- return result;
82
+ callback(error);
83
+ }
84
+ });
91
85
  }
92
86
 
93
87
  function supportsRetryableReads(server) {
@@ -139,7 +133,6 @@ function executeWithServerSelection(topology, operation, callback) {
139
133
  callback(err, null);
140
134
  return;
141
135
  }
142
-
143
136
  const shouldRetryReads =
144
137
  topology.s.options.retryReads !== false &&
145
138
  operation.session &&
@@ -156,31 +149,16 @@ function executeWithServerSelection(topology, operation, callback) {
156
149
  });
157
150
  }
158
151
 
159
- // TODO: This is only supported for unified topology, it should go away once
160
- // we remove support for legacy topology types.
152
+ // The Unified Topology runs serverSelection before executing every operation
153
+ // Session support is determined by the result of a monitoring check triggered by this selection
161
154
  function selectServerForSessionSupport(topology, operation, callback) {
162
- const Promise = topology.s.promiseLibrary;
163
-
164
- let result;
165
- if (typeof callback !== 'function') {
166
- result = new Promise((resolve, reject) => {
167
- callback = (err, result) => {
168
- if (err) return reject(err);
169
- resolve(result);
170
- };
171
- });
172
- }
173
-
174
155
  topology.selectServer(ReadPreference.primaryPreferred, err => {
175
156
  if (err) {
176
- callback(err);
177
- return;
157
+ return callback(err);
178
158
  }
179
159
 
180
160
  executeOperation(topology, operation, callback);
181
161
  });
182
-
183
- return result;
184
162
  }
185
163
 
186
164
  module.exports = executeOperation;
@@ -25,6 +25,11 @@ class FindOperation extends OperationBase {
25
25
  return;
26
26
  }
27
27
 
28
+ if (this.explain) {
29
+ // We need to manually ensure explain is in the options.
30
+ this.options.explain = this.explain.verbosity;
31
+ }
32
+
28
33
  // TOOD: use `MongoDBNamespace` through and through
29
34
  const cursorState = this.cursorState || {};
30
35
  server.query(this.ns.toString(), this.cmd, cursorState, this.options, callback);
@@ -34,7 +39,8 @@ class FindOperation extends OperationBase {
34
39
  defineAspects(FindOperation, [
35
40
  Aspect.READ_OPERATION,
36
41
  Aspect.RETRYABLE,
37
- Aspect.EXECUTE_WITH_SELECTION
42
+ Aspect.EXECUTE_WITH_SELECTION,
43
+ Aspect.EXPLAINABLE
38
44
  ]);
39
45
 
40
46
  module.exports = FindOperation;
@@ -10,6 +10,9 @@ const handleCallback = require('../utils').handleCallback;
10
10
  const ReadPreference = require('../core').ReadPreference;
11
11
  const maxWireVersion = require('../core/utils').maxWireVersion;
12
12
  const MongoError = require('../error').MongoError;
13
+ const Aspect = require('./operation').Aspect;
14
+ const defineAspects = require('./operation').defineAspects;
15
+ const decorateWithExplain = require('../utils').decorateWithExplain;
13
16
 
14
17
  class FindAndModifyOperation extends OperationBase {
15
18
  constructor(collection, query, sort, doc, options) {
@@ -29,7 +32,7 @@ class FindAndModifyOperation extends OperationBase {
29
32
  let options = this.options;
30
33
 
31
34
  // Create findAndModify command object
32
- const queryObject = {
35
+ let queryObject = {
33
36
  findAndModify: coll.collectionName,
34
37
  query: query
35
38
  };
@@ -103,6 +106,14 @@ class FindAndModifyOperation extends OperationBase {
103
106
  queryObject.hint = options.hint;
104
107
  }
105
108
 
109
+ if (this.explain) {
110
+ if (maxWireVersion(coll.s.topology) < 4) {
111
+ callback(new MongoError(`server does not support explain on findAndModify`));
112
+ return;
113
+ }
114
+ queryObject = decorateWithExplain(queryObject, this.explain);
115
+ }
116
+
106
117
  // Execute the command
107
118
  executeCommand(coll.s.db, queryObject, options, (err, result) => {
108
119
  if (err) return handleCallback(callback, err, null);
@@ -112,4 +123,6 @@ class FindAndModifyOperation extends OperationBase {
112
123
  }
113
124
  }
114
125
 
126
+ defineAspects(FindAndModifyOperation, [Aspect.EXPLAINABLE]);
127
+
115
128
  module.exports = FindAndModifyOperation;
@@ -3,6 +3,8 @@
3
3
  const handleCallback = require('../utils').handleCallback;
4
4
  const OperationBase = require('./operation').OperationBase;
5
5
  const toError = require('../utils').toError;
6
+ const Aspect = require('./operation').Aspect;
7
+ const defineAspects = require('./operation').defineAspects;
6
8
 
7
9
  class FindOneOperation extends OperationBase {
8
10
  constructor(collection, query, options) {
@@ -34,4 +36,6 @@ class FindOneOperation extends OperationBase {
34
36
  }
35
37
  }
36
38
 
39
+ defineAspects(FindOneOperation, [Aspect.EXPLAINABLE]);
40
+
37
41
  module.exports = FindOneOperation;
@@ -11,8 +11,14 @@ const loadDb = require('../dynamic_loaders').loadDb;
11
11
  const OperationBase = require('./operation').OperationBase;
12
12
  const ReadPreference = require('../core').ReadPreference;
13
13
  const toError = require('../utils').toError;
14
+ const Aspect = require('./operation').Aspect;
15
+ const defineAspects = require('./operation').defineAspects;
16
+ const decorateWithExplain = require('../utils').decorateWithExplain;
17
+ const maxWireVersion = require('../core/utils').maxWireVersion;
18
+ const MongoError = require('../error').MongoError;
14
19
 
15
20
  const exclusionList = [
21
+ 'explain',
16
22
  'readPreference',
17
23
  'session',
18
24
  'bypassDocumentValidation',
@@ -59,7 +65,7 @@ class MapReduceOperation extends OperationBase {
59
65
  const reduce = this.reduce;
60
66
  let options = this.options;
61
67
 
62
- const mapCommandHash = {
68
+ let mapCommandHash = {
63
69
  mapReduce: coll.collectionName,
64
70
  map: map,
65
71
  reduce: reduce
@@ -110,6 +116,14 @@ class MapReduceOperation extends OperationBase {
110
116
  return callback(err, null);
111
117
  }
112
118
 
119
+ if (this.explain) {
120
+ if (maxWireVersion(coll.s.topology) < 9) {
121
+ callback(new MongoError(`server does not support explain on mapReduce`));
122
+ return;
123
+ }
124
+ mapCommandHash = decorateWithExplain(mapCommandHash, this.explain);
125
+ }
126
+
113
127
  // Execute command
114
128
  executeCommand(coll.s.db, mapCommandHash, options, (err, result) => {
115
129
  if (err) return handleCallback(callback, err);
@@ -118,6 +132,9 @@ class MapReduceOperation extends OperationBase {
118
132
  return handleCallback(callback, toError(result));
119
133
  }
120
134
 
135
+ // If an explain operation was executed, don't process the server results
136
+ if (this.explain) return callback(undefined, result);
137
+
121
138
  // Create statistics value
122
139
  const stats = {};
123
140
  if (result.timeMillis) stats['processtime'] = result.timeMillis;
@@ -187,4 +204,6 @@ function processScope(scope) {
187
204
  return new_scope;
188
205
  }
189
206
 
207
+ defineAspects(MapReduceOperation, [Aspect.EXPLAINABLE]);
208
+
190
209
  module.exports = MapReduceOperation;
@@ -1,11 +1,15 @@
1
1
  'use strict';
2
2
 
3
+ const Explain = require('../explain').Explain;
4
+ const MongoError = require('../core/error').MongoError;
5
+
3
6
  const Aspect = {
4
7
  READ_OPERATION: Symbol('READ_OPERATION'),
5
8
  WRITE_OPERATION: Symbol('WRITE_OPERATION'),
6
9
  RETRYABLE: Symbol('RETRYABLE'),
7
10
  EXECUTE_WITH_SELECTION: Symbol('EXECUTE_WITH_SELECTION'),
8
- NO_INHERIT_OPTIONS: Symbol('NO_INHERIT_OPTIONS')
11
+ NO_INHERIT_OPTIONS: Symbol('NO_INHERIT_OPTIONS'),
12
+ EXPLAINABLE: Symbol('EXPLAINABLE')
9
13
  };
10
14
 
11
15
  /**
@@ -17,6 +21,12 @@ const Aspect = {
17
21
  class OperationBase {
18
22
  constructor(options) {
19
23
  this.options = Object.assign({}, options);
24
+
25
+ if (this.hasAspect(Aspect.EXPLAINABLE)) {
26
+ this.explain = Explain.fromOptions(options);
27
+ } else if (this.options.explain !== undefined) {
28
+ throw new MongoError(`explain is not supported on this command`);
29
+ }
20
30
  }
21
31
 
22
32
  hasAspect(aspect) {