mongodb 3.3.0-beta1 → 3.3.0-beta2

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.
package/lib/core/utils.js CHANGED
@@ -116,6 +116,41 @@ function isPromiseLike(maybePromise) {
116
116
  return maybePromise && typeof maybePromise.then === 'function';
117
117
  }
118
118
 
119
+ /**
120
+ * Applies the function `eachFn` to each item in `arr`, in parallel.
121
+ *
122
+ * @param {array} arr an array of items to asynchronusly iterate over
123
+ * @param {function} eachFn A function to call on each item of the array. The callback signature is `(item, callback)`, where the callback indicates iteration is complete.
124
+ * @param {function} callback The callback called after every item has been iterated
125
+ */
126
+ function eachAsync(arr, eachFn, callback) {
127
+ if (arr.length === 0) {
128
+ callback(null);
129
+ return;
130
+ }
131
+
132
+ const length = arr.length;
133
+ let completed = 0;
134
+ function eachCallback(err) {
135
+ if (err) {
136
+ callback(err, null);
137
+ return;
138
+ }
139
+
140
+ if (++completed === length) {
141
+ callback(null);
142
+ }
143
+ }
144
+
145
+ for (let idx = 0; idx < length; ++idx) {
146
+ eachFn(arr[idx], eachCallback);
147
+ }
148
+ }
149
+
150
+ function isUnifiedTopology(topology) {
151
+ return topology.description != null;
152
+ }
153
+
119
154
  module.exports = {
120
155
  uuidV4,
121
156
  calculateDurationInMs,
@@ -124,5 +159,7 @@ module.exports = {
124
159
  retrieveEJSON,
125
160
  retrieveKerberos,
126
161
  maxWireVersion,
127
- isPromiseLike
162
+ isPromiseLike,
163
+ eachAsync,
164
+ isUnifiedTopology
128
165
  };
@@ -10,7 +10,7 @@ const isTransactionCommand = require('../transactions').isTransactionCommand;
10
10
  const applySession = require('../sessions').applySession;
11
11
 
12
12
  function isClientEncryptionEnabled(server) {
13
- return server && server.s && server.s.options && server.s.options.autoEncrypter;
13
+ return server.autoEncrypter;
14
14
  }
15
15
 
16
16
  function command(server, ns, cmd, options, callback) {
@@ -133,7 +133,7 @@ function supportsOpMsg(topologyOrServer) {
133
133
 
134
134
  function _cryptCommand(server, ns, cmd, options, callback) {
135
135
  const shouldBypassAutoEncryption = !!server.s.options.bypassAutoEncryption;
136
- const autoEncrypter = server.s.options.autoEncrypter;
136
+ const autoEncrypter = server.autoEncrypter;
137
137
  function commandResponseHandler(err, response) {
138
138
  if (err || response == null) {
139
139
  callback(err, response);
package/lib/cursor.js CHANGED
@@ -106,10 +106,12 @@ const fields = ['numberOfRetries', 'tailableRetryInterval'];
106
106
  *
107
107
  * collection.find({}).maxTimeMS(1000).maxScan(100).skip(1).toArray(..)
108
108
  */
109
- function Cursor(bson, ns, cmd, options, topology, topologyOptions) {
109
+ function Cursor(topology, ns, cmd, options) {
110
110
  CoreCursor.apply(this, Array.prototype.slice.call(arguments, 0));
111
111
  const state = Cursor.INIT;
112
112
  const streamOptions = {};
113
+ const bson = topology.s.bson;
114
+ const topologyOptions = topology.s.options;
113
115
 
114
116
  // Tailable cursor options
115
117
  const numberOfRetries = options.numberOfRetries || 5;
@@ -214,9 +216,6 @@ if (SUPPORTS.ASYNC_ITERATOR) {
214
216
 
215
217
  // Map core cursor _next method so we can apply mapping
216
218
  Cursor.prototype._next = function() {
217
- if (this._initImplicitSession) {
218
- this._initImplicitSession();
219
- }
220
219
  return CoreCursor.prototype.next.apply(this, arguments);
221
220
  };
222
221
 
@@ -224,11 +223,14 @@ for (let name in CoreCursor.prototype) {
224
223
  Cursor.prototype[name] = CoreCursor.prototype[name];
225
224
  }
226
225
 
227
- Cursor.prototype._initImplicitSession = function() {
226
+ Cursor.prototype._initializeCursor = function(callback) {
227
+ // implicitly create a session if one has not been provided
228
228
  if (!this.s.explicitlyIgnoreSession && !this.s.session && this.s.topology.hasSessionSupport()) {
229
229
  this.s.session = this.s.topology.startSession({ owner: this });
230
230
  this.cursorState.session = this.s.session;
231
231
  }
232
+
233
+ CoreCursor.prototype._initializeCursor.apply(this, [callback]);
232
234
  };
233
235
 
234
236
  Cursor.prototype._endSession = function() {
package/lib/db.js CHANGED
@@ -505,7 +505,9 @@ Db.prototype.createCollection = deprecateOptions(
505
505
  if (typeof options === 'function') (callback = options), (options = {});
506
506
  options = options || {};
507
507
  options.promiseLibrary = options.promiseLibrary || this.s.promiseLibrary;
508
-
508
+ options.readConcern = options.readConcern
509
+ ? new ReadConcern(options.readConcern.level)
510
+ : this.readConcern;
509
511
  const createCollectionOperation = new CreateCollectionOperation(this, name, options);
510
512
 
511
513
  return executeOperation(this.s.topology, createCollectionOperation, callback);
@@ -687,6 +689,10 @@ Db.prototype.renameCollection = function(fromCollection, toCollection, options,
687
689
  * @method
688
690
  * @param {string} name Name of collection to drop
689
691
  * @param {Object} [options] Optional settings
692
+ * @param {WriteConcern} [options.writeConcern] A full WriteConcern object
693
+ * @param {(number|string)} [options.w] The write concern
694
+ * @param {number} [options.wtimeout] The write concern timeout
695
+ * @param {boolean} [options.j] The journal write concern
690
696
  * @param {ClientSession} [options.session] optional session to use for this operation
691
697
  * @param {Db~resultCallback} [callback] The results callback
692
698
  * @return {Promise} returns Promise if no callback passed
package/lib/error.js CHANGED
@@ -9,15 +9,15 @@ const GET_MORE_NON_RESUMABLE_CODES = new Set([
9
9
  11601 // Interrupted
10
10
  ]);
11
11
 
12
- // From spec@https://github.com/mongodb/specifications/blob/35e466ddf25059cb30e4113de71cdebd3754657f/source/change-streams.rst#resumable-error:
12
+ // From spec@https://github.com/mongodb/specifications/blob/7a2e93d85935ee4b1046a8d2ad3514c657dc74fa/source/change-streams/change-streams.rst#resumable-error:
13
13
  //
14
14
  // An error is considered resumable if it meets any of the following criteria:
15
15
  // - any error encountered which is not a server error (e.g. a timeout error or network error)
16
- // - any server error response from a getMore command excluding those containing the following error codes
16
+ // - any server error response from a getMore command excluding those containing the error label
17
+ // NonRetryableChangeStreamError and those containing the following error codes:
17
18
  // - Interrupted: 11601
18
19
  // - CappedPositionLost: 136
19
20
  // - CursorKilled: 237
20
- // - a server error response with an error message containing the substring "not master" or "node is recovering"
21
21
  //
22
22
  // An error on an aggregate command is not a resumable error. Only errors on a getMore command may be considered resumable errors.
23
23
 
@@ -32,11 +32,13 @@ function isResumableError(error) {
32
32
  return false;
33
33
  }
34
34
 
35
- return !!(
36
- error instanceof MongoNetworkError ||
37
- !GET_MORE_NON_RESUMABLE_CODES.has(error.code) ||
38
- error.message.match(/not master/) ||
39
- error.message.match(/node is recovering/)
35
+ if (error instanceof MongoNetworkError) {
36
+ return true;
37
+ }
38
+
39
+ return !(
40
+ GET_MORE_NON_RESUMABLE_CODES.has(error.code) ||
41
+ error.hasErrorLabel('NonRetryableChangeStreamError')
40
42
  );
41
43
  }
42
44
 
@@ -3,16 +3,15 @@
3
3
  const ChangeStream = require('./change_stream');
4
4
  const Db = require('./db');
5
5
  const EventEmitter = require('events').EventEmitter;
6
- const executeLegacyOperation = require('./utils').executeLegacyOperation;
6
+ const executeOperation = require('./operations/execute_operation');
7
7
  const inherits = require('util').inherits;
8
8
  const MongoError = require('./core').MongoError;
9
9
  const deprecate = require('util').deprecate;
10
10
  const WriteConcern = require('./write_concern');
11
11
 
12
12
  // Operations
13
- const connectOp = require('./operations/mongo_client_ops').connectOp;
14
- const validOptions = require('./operations/mongo_client_ops').validOptions;
15
- const closeOperation = require('./operations/mongo_client_ops').closeOperation;
13
+ const ConnectOperation = require('./operations/connect');
14
+ const CloseOperation = require('./operations/close');
16
15
 
17
16
  /**
18
17
  * @fileOverview The **MongoClient** class is a class that allows for making Connections to MongoDB.
@@ -67,6 +66,7 @@ const closeOperation = require('./operations/mongo_client_ops').closeOperation;
67
66
  * @property {string} [kmsProviders.local.key] The master key used to encrypt/decrypt data keys
68
67
  * @property {object} [schemaMap] A map of namespaces to a local JSON schema for encryption
69
68
  * @property {boolean} [bypassAutoEncryption] Allows the user to bypass auto encryption, maintaining implicit decryption
69
+ * @param {string} [extraOptions.mongocryptURI] A local process the driver communicates with to determine how to encrypt values in a command. Defaults to "mongodb://%2Fvar%2Fmongocryptd.sock" if domain sockets are available or "mongodb://localhost:27020" otherwise.
70
70
  */
71
71
 
72
72
  /**
@@ -191,16 +191,13 @@ Object.defineProperty(MongoClient.prototype, 'writeConcern', {
191
191
  * @return {Promise<MongoClient>} returns Promise if no callback passed
192
192
  */
193
193
  MongoClient.prototype.connect = function(callback) {
194
- // Validate options object
195
- const err = validOptions(this.s.options);
196
-
197
194
  if (typeof callback === 'string') {
198
195
  throw new TypeError('`connect` only accepts a callback');
199
196
  }
200
197
 
201
- return executeLegacyOperation(this, connectOp, [this, err, callback], {
202
- skipSessions: true
203
- });
198
+ const operation = new ConnectOperation(this);
199
+
200
+ return executeOperation(this, operation, callback);
204
201
  };
205
202
 
206
203
  MongoClient.prototype.logout = deprecate(function(options, callback) {
@@ -217,9 +214,8 @@ MongoClient.prototype.logout = deprecate(function(options, callback) {
217
214
  */
218
215
  MongoClient.prototype.close = function(force, callback) {
219
216
  if (typeof force === 'function') (callback = force), (force = false);
220
- return executeLegacyOperation(this, closeOperation, [this, force, callback], {
221
- skipSessions: true
222
- });
217
+ const operation = new CloseOperation(this, force);
218
+ return executeOperation(this, operation, callback);
223
219
  };
224
220
 
225
221
  /**
@@ -8,9 +8,9 @@ const handleCallback = require('../utils').handleCallback;
8
8
  const MongoError = require('../core').MongoError;
9
9
  const resolveReadPreference = require('../utils').resolveReadPreference;
10
10
  const toError = require('../utils').toError;
11
+ const ReadPreference = require('../core').ReadPreference;
11
12
 
12
13
  const DB_AGGREGATE_COLLECTION = 1;
13
-
14
14
  const MIN_WIRE_VERSION_$OUT_READ_CONCERN_SUPPORT = 8;
15
15
 
16
16
  /**
@@ -27,13 +27,16 @@ function aggregate(db, coll, pipeline, options, callback) {
27
27
  const isDbAggregate = typeof coll === 'string';
28
28
  const target = isDbAggregate ? db : coll;
29
29
  const topology = target.s.topology;
30
- let hasOutStage = false;
30
+ let hasWriteStage = false;
31
31
 
32
32
  if (typeof options.out === 'string') {
33
33
  pipeline = pipeline.concat({ $out: options.out });
34
- hasOutStage = true;
35
- } else if (pipeline.length > 0 && pipeline[pipeline.length - 1]['$out']) {
36
- hasOutStage = true;
34
+ hasWriteStage = true;
35
+ } else if (pipeline.length > 0) {
36
+ const finalStage = pipeline[pipeline.length - 1];
37
+ if (finalStage.$out || finalStage.$merge) {
38
+ hasWriteStage = true;
39
+ }
37
40
  }
38
41
 
39
42
  let command;
@@ -55,11 +58,11 @@ function aggregate(db, coll, pipeline, options, callback) {
55
58
  const takesWriteConcern = topology.capabilities().commandsTakeWriteConcern;
56
59
  const ismaster = topology.lastIsMaster() || {};
57
60
 
58
- if (!hasOutStage || ismaster.maxWireVersion >= MIN_WIRE_VERSION_$OUT_READ_CONCERN_SUPPORT) {
61
+ if (!hasWriteStage || ismaster.maxWireVersion >= MIN_WIRE_VERSION_$OUT_READ_CONCERN_SUPPORT) {
59
62
  decorateWithReadConcern(command, target, options);
60
63
  }
61
64
 
62
- if (pipeline.length > 0 && pipeline[pipeline.length - 1]['$out'] && takesWriteConcern) {
65
+ if (hasWriteStage && takesWriteConcern) {
63
66
  applyWriteConcern(command, optionSources, options);
64
67
  }
65
68
 
@@ -82,7 +85,9 @@ function aggregate(db, coll, pipeline, options, callback) {
82
85
  options = Object.assign({}, options);
83
86
 
84
87
  // Ensure we have the right read preference inheritance
85
- options.readPreference = resolveReadPreference(isDbAggregate ? db : coll, options);
88
+ options.readPreference = hasWriteStage
89
+ ? ReadPreference.primary
90
+ : resolveReadPreference(isDbAggregate ? db : coll, options);
86
91
 
87
92
  if (options.explain) {
88
93
  if (command.readConcern || command.writeConcern) {
@@ -99,7 +104,10 @@ function aggregate(db, coll, pipeline, options, callback) {
99
104
  }
100
105
 
101
106
  options.cursor = options.cursor || {};
102
- if (options.batchSize && !hasOutStage) options.cursor.batchSize = options.batchSize;
107
+ if (options.batchSize && !hasWriteStage) {
108
+ options.cursor.batchSize = options.batchSize;
109
+ }
110
+
103
111
  command.cursor = options.cursor;
104
112
 
105
113
  // promiseLibrary
@@ -0,0 +1,47 @@
1
+ 'use strict';
2
+
3
+ const Aspect = require('./operation').Aspect;
4
+ const defineAspects = require('./operation').defineAspects;
5
+ const OperationBase = require('./operation').OperationBase;
6
+
7
+ class CloseOperation extends OperationBase {
8
+ constructor(client, force) {
9
+ super();
10
+ this.client = client;
11
+ this.force = force;
12
+ }
13
+
14
+ execute(callback) {
15
+ const client = this.client;
16
+ const force = this.force;
17
+ const completeClose = err => {
18
+ client.emit('close', client);
19
+ for (const name in client.s.dbCache) {
20
+ client.s.dbCache[name].emit('close', client);
21
+ }
22
+
23
+ client.removeAllListeners('close');
24
+ callback(err, null);
25
+ };
26
+ const mongocryptdClientClose = err => {
27
+ const mongocryptdClient = client.s.mongocryptdClient;
28
+ if (!mongocryptdClient) {
29
+ completeClose(err);
30
+ return;
31
+ }
32
+
33
+ mongocryptdClient.close(force, err2 => completeClose(err || err2));
34
+ };
35
+
36
+ if (client.topology == null) {
37
+ mongocryptdClientClose();
38
+ return;
39
+ }
40
+
41
+ client.topology.close(force, mongocryptdClientClose);
42
+ }
43
+ }
44
+
45
+ defineAspects(CloseOperation, [Aspect.SKIP_SESSION]);
46
+
47
+ module.exports = CloseOperation;
@@ -156,6 +156,10 @@ function bulkWrite(coll, operations, options, callback) {
156
156
 
157
157
  // Check the update operation to ensure it has atomic operators.
158
158
  function checkForAtomicOperators(update) {
159
+ if (Array.isArray(update)) {
160
+ return update.reduce((err, u) => err || checkForAtomicOperators(u), null);
161
+ }
162
+
159
163
  const keys = Object.keys(update);
160
164
 
161
165
  // same errors as the server would give for update doc lacking atomic operators
@@ -105,7 +105,8 @@ class CommandOperation extends OperationBase {
105
105
  );
106
106
  }
107
107
 
108
- const namespace = new MongoDBNamespace(dbName, '$cmd');
108
+ const namespace =
109
+ this.namespace != null ? this.namespace : new MongoDBNamespace(dbName, '$cmd');
109
110
 
110
111
  // Execute command
111
112
  db.s.topology.command(namespace, command, options, (err, result) => {
@@ -0,0 +1,43 @@
1
+ 'use strict';
2
+
3
+ const OperationBase = require('./operation').OperationBase;
4
+ const resolveReadPreference = require('../utils').resolveReadPreference;
5
+
6
+ class CommandOperationV2 extends OperationBase {
7
+ constructor(parent, options) {
8
+ super(options);
9
+
10
+ this.ns = parent.s.namespace.withCollection('$cmd');
11
+ this.readPreference = resolveReadPreference(parent, options);
12
+
13
+ // TODO(NODE-2056): make logger another "inheritable" property
14
+ if (parent.s.logger) {
15
+ this.logger = parent.s.logger;
16
+ } else if (parent.s.db && parent.s.db.logger) {
17
+ this.logger = parent.s.db.logger;
18
+ }
19
+ }
20
+
21
+ executeCommand(server, cmd, callback) {
22
+ if (this.logger && this.logger.isDebug()) {
23
+ this.logger.debug(`executing command ${JSON.stringify(cmd)} against ${this.ns}`);
24
+ }
25
+
26
+ server.command(this.ns.toString(), cmd, this.options, (err, result) => {
27
+ if (err) {
28
+ callback(err, null);
29
+ return;
30
+ }
31
+
32
+ // full response was requested
33
+ if (this.options.full) {
34
+ callback(null, result);
35
+ return;
36
+ }
37
+
38
+ callback(null, result.result);
39
+ });
40
+ }
41
+ }
42
+
43
+ module.exports = CommandOperationV2;
@@ -1,6 +1,8 @@
1
1
  'use strict';
2
2
 
3
+ const os = require('os');
3
4
  const OperationBase = require('./operation').OperationBase;
5
+ const defineAspects = require('./operation').defineAspects;
4
6
  const Aspect = require('./operation').Aspect;
5
7
  const deprecate = require('util').deprecate;
6
8
  const Logger = require('../core').Logger;
@@ -131,31 +133,58 @@ const validOptionNames = [
131
133
  'minSize',
132
134
  'monitorCommands',
133
135
  'retryWrites',
136
+ 'retryReads',
134
137
  'useNewUrlParser',
135
138
  'useUnifiedTopology',
136
- 'serverSelectionTimeoutMS'
139
+ 'serverSelectionTimeoutMS',
140
+ 'useRecoveryToken',
141
+ 'autoEncryption'
137
142
  ];
138
143
 
144
+ const ignoreOptionNames = ['native_parser'];
145
+ const legacyOptionNames = ['server', 'replset', 'replSet', 'mongos', 'db'];
146
+
147
+ // Validate options object
148
+ function validOptions(options) {
149
+ const _validOptions = validOptionNames.concat(legacyOptionNames);
150
+
151
+ for (const name in options) {
152
+ if (ignoreOptionNames.indexOf(name) !== -1) {
153
+ continue;
154
+ }
155
+
156
+ if (_validOptions.indexOf(name) === -1) {
157
+ if (options.validateOptions) {
158
+ return new MongoError(`option ${name} is not supported`);
159
+ } else {
160
+ console.warn(`the options [${name}] is not supported`);
161
+ }
162
+ }
163
+
164
+ if (legacyOptionNames.indexOf(name) !== -1) {
165
+ console.warn(
166
+ `the server/replset/mongos/db options are deprecated, ` +
167
+ `all their options are supported at the top level of the options object [${validOptionNames}]`
168
+ );
169
+ }
170
+ }
171
+ }
172
+
139
173
  const LEGACY_OPTIONS_MAP = validOptionNames.reduce((obj, name) => {
140
174
  obj[name.toLowerCase()] = name;
141
175
  return obj;
142
176
  }, {});
143
177
 
144
178
  class ConnectOperation extends OperationBase {
145
- constructor(mongoClient, err) {
179
+ constructor(mongoClient) {
146
180
  super();
147
181
 
148
182
  this.mongoClient = mongoClient;
149
- this.err = err;
150
- }
151
-
152
- static get aspects() {
153
- return new Set([Aspect.SKIP_SESSIONS]);
154
183
  }
155
184
 
156
185
  execute(callback) {
157
186
  const mongoClient = this.mongoClient;
158
- const err = this.err;
187
+ const err = validOptions(mongoClient.s.options);
159
188
 
160
189
  // Did we have a validation error
161
190
  if (err) return callback(err);
@@ -166,6 +195,7 @@ class ConnectOperation extends OperationBase {
166
195
  });
167
196
  }
168
197
  }
198
+ defineAspects(ConnectOperation, [Aspect.SKIP_SESSION]);
169
199
 
170
200
  function addListeners(mongoClient, topology) {
171
201
  topology.on('authenticated', createListener(mongoClient, 'authenticated'));
@@ -245,6 +275,7 @@ function connect(mongoClient, url, options, callback) {
245
275
  if (_finalOptions.socketTimeoutMS == null) _finalOptions.socketTimeoutMS = 360000;
246
276
  if (_finalOptions.connectTimeoutMS == null) _finalOptions.connectTimeoutMS = 30000;
247
277
  if (_finalOptions.retryWrites == null) _finalOptions.retryWrites = true;
278
+ if (_finalOptions.useRecoveryToken == null) _finalOptions.useRecoveryToken = true;
248
279
 
249
280
  if (_finalOptions.db_options && _finalOptions.db_options.auth) {
250
281
  delete _finalOptions.db_options.auth;
@@ -433,7 +464,50 @@ function createTopology(mongoClient, topologyType, options, callback) {
433
464
  }
434
465
 
435
466
  assignTopology(mongoClient, newTopology);
436
- callback(null, newTopology);
467
+ if (options.autoEncryption == null) {
468
+ callback(null, newTopology);
469
+ return;
470
+ }
471
+
472
+ // setup for client side encryption
473
+ let AutoEncrypter;
474
+ try {
475
+ AutoEncrypter = require('mongodb-client-encryption').AutoEncrypter;
476
+ } catch (err) {
477
+ callback(
478
+ new MongoError(
479
+ 'Auto-encryption requested, but the module is not installed. Please add `mongodb-client-encryption` as a dependency of your project'
480
+ )
481
+ );
482
+
483
+ return;
484
+ }
485
+
486
+ const MongoClient = loadClient();
487
+ let connectionString;
488
+ if (options.autoEncryption.extraOptions && options.autoEncryption.extraOptions.mongocryptURI) {
489
+ connectionString = options.autoEncryption.extraOptions.mongocryptURI;
490
+ } else if (os.platform() === 'win32') {
491
+ connectionString = 'mongodb://localhost:27020/?serverSelectionTimeoutMS=1000';
492
+ } else {
493
+ connectionString = 'mongodb://%2Ftmp%2Fmongocryptd.sock/?serverSelectionTimeoutMS=1000';
494
+ }
495
+
496
+ const mongocryptdClient = new MongoClient(connectionString, {
497
+ useNewUrlParser: true,
498
+ useUnifiedTopology: true
499
+ });
500
+ mongoClient.s.mongocryptdClient = mongocryptdClient;
501
+ mongocryptdClient.connect(err => {
502
+ if (err) return callback(err, null);
503
+
504
+ const mongoCryptOptions = Object.assign({}, options.autoEncryption, {
505
+ mongocryptdClient
506
+ });
507
+
508
+ topology.s.options.autoEncrypter = new AutoEncrypter(mongoClient, mongoCryptOptions);
509
+ callback(null, newTopology);
510
+ });
437
511
  });
438
512
  }
439
513
 
@@ -22,6 +22,7 @@ const illegalCommandFields = [
22
22
  'raw',
23
23
  'readPreference',
24
24
  'session',
25
+ 'readConcern',
25
26
  'writeConcern'
26
27
  ];
27
28
 
@@ -2,12 +2,9 @@
2
2
 
3
3
  const Aspect = require('./operation').Aspect;
4
4
  const defineAspects = require('./operation').defineAspects;
5
- const OperationBase = require('./operation').OperationBase;
5
+ const CommandOperationV2 = require('./command_v2');
6
6
  const decorateWithCollation = require('../utils').decorateWithCollation;
7
7
  const decorateWithReadConcern = require('../utils').decorateWithReadConcern;
8
- const executeCommand = require('./db_ops').executeCommand;
9
- const handleCallback = require('../utils').handleCallback;
10
- const resolveReadPreference = require('../utils').resolveReadPreference;
11
8
 
12
9
  /**
13
10
  * Return a list of distinct values for the given key across a collection.
@@ -18,7 +15,7 @@ const resolveReadPreference = require('../utils').resolveReadPreference;
18
15
  * @property {object} query The query for filtering the set of documents to which we apply the distinct filter.
19
16
  * @property {object} [options] Optional settings. See Collection.prototype.distinct for a list of options.
20
17
  */
21
- class DistinctOperation extends OperationBase {
18
+ class DistinctOperation extends CommandOperationV2 {
22
19
  /**
23
20
  * Construct a Distinct operation.
24
21
  *
@@ -28,7 +25,7 @@ class DistinctOperation extends OperationBase {
28
25
  * @param {object} [options] Optional settings. See Collection.prototype.distinct for a list of options.
29
26
  */
30
27
  constructor(collection, key, query, options) {
31
- super(options);
28
+ super(collection, options);
32
29
 
33
30
  this.collection = collection;
34
31
  this.key = key;
@@ -40,14 +37,11 @@ class DistinctOperation extends OperationBase {
40
37
  *
41
38
  * @param {Collection~resultCallback} [callback] The command result callback
42
39
  */
43
- execute(callback) {
40
+ execute(server, callback) {
44
41
  const coll = this.collection;
45
42
  const key = this.key;
46
43
  const query = this.query;
47
- let options = this.options;
48
-
49
- // maxTimeMS option
50
- const maxTimeMS = options.maxTimeMS;
44
+ const options = this.options;
51
45
 
52
46
  // Distinct command
53
47
  const cmd = {
@@ -56,12 +50,10 @@ class DistinctOperation extends OperationBase {
56
50
  query: query
57
51
  };
58
52
 
59
- options = Object.assign({}, options);
60
- // Ensure we have the right read preference inheritance
61
- options.readPreference = resolveReadPreference(coll, options);
62
-
63
53
  // Add maxTimeMS if defined
64
- if (typeof maxTimeMS === 'number') cmd.maxTimeMS = maxTimeMS;
54
+ if (typeof options.maxTimeMS === 'number') {
55
+ cmd.maxTimeMS = options.maxTimeMS;
56
+ }
65
57
 
66
58
  // Do we have a readConcern specified
67
59
  decorateWithReadConcern(cmd, coll, options);
@@ -73,14 +65,21 @@ class DistinctOperation extends OperationBase {
73
65
  return callback(err, null);
74
66
  }
75
67
 
76
- // Execute the command
77
- executeCommand(coll.s.db, cmd, options, (err, result) => {
78
- if (err) return handleCallback(callback, err);
79
- handleCallback(callback, null, result.values);
68
+ super.executeCommand(server, cmd, (err, result) => {
69
+ if (err) {
70
+ callback(err);
71
+ return;
72
+ }
73
+
74
+ callback(null, this.options.full ? result : result.values);
80
75
  });
81
76
  }
82
77
  }
83
78
 
84
- defineAspects(DistinctOperation, Aspect.READ_OPERATION);
79
+ defineAspects(DistinctOperation, [
80
+ Aspect.READ_OPERATION,
81
+ Aspect.RETRYABLE,
82
+ Aspect.EXECUTE_WITH_SELECTION
83
+ ]);
85
84
 
86
85
  module.exports = DistinctOperation;
@@ -32,6 +32,7 @@ class DropCollectionOperation extends DropOperation {
32
32
  super(db, options);
33
33
 
34
34
  this.name = name;
35
+ this.namespace = `${db.namespace}.${name}`;
35
36
  }
36
37
 
37
38
  _buildCommand() {