mongodb 3.6.12 → 3.7.3

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/index.js CHANGED
@@ -17,6 +17,9 @@ connect.MongoWriteConcernError = core.MongoWriteConcernError;
17
17
  connect.MongoBulkWriteError = require('./lib/bulk/common').BulkWriteError;
18
18
  connect.BulkWriteError = connect.MongoBulkWriteError;
19
19
 
20
+ // Expose server versions
21
+ connect.ServerApiVersion = core.ServerApiVersion;
22
+
20
23
  // Actual driver classes exported
21
24
  connect.Admin = require('./lib/admin');
22
25
  connect.MongoClient = require('./lib/mongo_client');
@@ -47,6 +50,7 @@ connect.Int32 = core.BSON.Int32;
47
50
  connect.Long = core.BSON.Long;
48
51
  connect.MinKey = core.BSON.MinKey;
49
52
  connect.MaxKey = core.BSON.MaxKey;
53
+ /** @deprecated Please use `ObjectId` */
50
54
  connect.ObjectID = core.BSON.ObjectID;
51
55
  connect.ObjectId = core.BSON.ObjectID;
52
56
  connect.Symbol = core.BSON.Symbol;
@@ -414,6 +414,15 @@ class WriteError {
414
414
  }
415
415
  }
416
416
 
417
+ /**
418
+ * Converts the number to a Long or returns it.
419
+ *
420
+ * @ignore
421
+ */
422
+ function longOrConvert(value) {
423
+ return typeof value === 'number' ? Long.fromNumber(value) : value;
424
+ }
425
+
417
426
  /**
418
427
  * Merges results into shared data structure
419
428
  * @ignore
@@ -445,42 +454,37 @@ function mergeBatchResults(batch, bulkResult, err, result) {
445
454
  return;
446
455
  }
447
456
 
448
- // Deal with opTime if available
457
+ // The server write command specification states that lastOp is an optional
458
+ // mongod only field that has a type of timestamp. Across various scarce specs
459
+ // where opTime is mentioned, it is an "opaque" object that can have a "ts" and
460
+ // "t" field with Timestamp and Long as their types respectively.
461
+ // The "lastOp" field of the bulk write result is never mentioned in the driver
462
+ // specifications or the bulk write spec, so we should probably just keep its
463
+ // value consistent since it seems to vary.
464
+ // See: https://github.com/mongodb/specifications/blob/master/source/driver-bulk-update.rst#results-object
449
465
  if (result.opTime || result.lastOp) {
450
- const opTime = result.lastOp || result.opTime;
451
- let lastOpTS = null;
452
- let lastOpT = null;
466
+ let opTime = result.lastOp || result.opTime;
453
467
 
454
- // We have a time stamp
455
- if (opTime && opTime._bsontype === 'Timestamp') {
456
- if (bulkResult.lastOp == null) {
457
- bulkResult.lastOp = opTime;
458
- } else if (opTime.greaterThan(bulkResult.lastOp)) {
459
- bulkResult.lastOp = opTime;
460
- }
461
- } else {
462
- // Existing TS
463
- if (bulkResult.lastOp) {
464
- lastOpTS =
465
- typeof bulkResult.lastOp.ts === 'number'
466
- ? Long.fromNumber(bulkResult.lastOp.ts)
467
- : bulkResult.lastOp.ts;
468
- lastOpT =
469
- typeof bulkResult.lastOp.t === 'number'
470
- ? Long.fromNumber(bulkResult.lastOp.t)
471
- : bulkResult.lastOp.t;
472
- }
473
-
474
- // Current OpTime TS
475
- const opTimeTS = typeof opTime.ts === 'number' ? Long.fromNumber(opTime.ts) : opTime.ts;
476
- const opTimeT = typeof opTime.t === 'number' ? Long.fromNumber(opTime.t) : opTime.t;
468
+ // If the opTime is a Timestamp, convert it to a consistent format to be
469
+ // able to compare easily. Converting to the object from a timestamp is
470
+ // much more straightforward than the other direction.
471
+ if (opTime._bsontype === 'Timestamp') {
472
+ opTime = { ts: opTime, t: Long.ZERO };
473
+ }
477
474
 
478
- // Compare the opTime's
479
- if (bulkResult.lastOp == null) {
480
- bulkResult.lastOp = opTime;
481
- } else if (opTimeTS.greaterThan(lastOpTS)) {
475
+ // If there's no lastOp, just set it.
476
+ if (!bulkResult.lastOp) {
477
+ bulkResult.lastOp = opTime;
478
+ } else {
479
+ // First compare the ts values and set if the opTimeTS value is greater.
480
+ const lastOpTS = longOrConvert(bulkResult.lastOp.ts);
481
+ const opTimeTS = longOrConvert(opTime.ts);
482
+ if (opTimeTS.greaterThan(lastOpTS)) {
482
483
  bulkResult.lastOp = opTime;
483
484
  } else if (opTimeTS.equals(lastOpTS)) {
485
+ // If the ts values are equal, then compare using the t values.
486
+ const lastOpT = longOrConvert(bulkResult.lastOp.t);
487
+ const opTimeT = longOrConvert(opTime.t);
484
488
  if (opTimeT.greaterThan(lastOpT)) {
485
489
  bulkResult.lastOp = opTime;
486
490
  }
@@ -1387,6 +1391,7 @@ Object.defineProperty(BulkOperationBase.prototype, 'length', {
1387
1391
  module.exports = {
1388
1392
  Batch,
1389
1393
  BulkOperationBase,
1394
+ mergeBatchResults,
1390
1395
  bson,
1391
1396
  INSERT: INSERT,
1392
1397
  UPDATE: UPDATE,
@@ -37,6 +37,8 @@ class Connection extends EventEmitter {
37
37
  this.port = options.port || 27017;
38
38
  this.monitorCommands =
39
39
  typeof options.monitorCommands === 'boolean' ? options.monitorCommands : false;
40
+ this.serverApi = options.serverApi;
41
+
40
42
  this.closed = false;
41
43
  this.destroyed = false;
42
44
 
@@ -170,33 +172,58 @@ class Connection extends EventEmitter {
170
172
  });
171
173
  }
172
174
 
175
+ applyApiVersion(options) {
176
+ if (this.serverApi) {
177
+ options.serverApi = this.serverApi;
178
+ }
179
+ return options;
180
+ }
181
+
173
182
  // Wire protocol methods
174
183
  command(ns, cmd, options, callback) {
175
- wp.command(makeServerTrampoline(this), ns, cmd, options, callback);
184
+ if (typeof options === 'function') {
185
+ callback = options;
186
+ options = {};
187
+ }
188
+ wp.command(makeServerTrampoline(this), ns, cmd, this.applyApiVersion(options), callback);
176
189
  }
177
190
 
178
191
  query(ns, cmd, cursorState, options, callback) {
179
- wp.query(makeServerTrampoline(this), ns, cmd, cursorState, options, callback);
192
+ wp.query(
193
+ makeServerTrampoline(this),
194
+ ns,
195
+ cmd,
196
+ cursorState,
197
+ this.applyApiVersion(options),
198
+ callback
199
+ );
180
200
  }
181
201
 
182
202
  getMore(ns, cursorState, batchSize, options, callback) {
183
- wp.getMore(makeServerTrampoline(this), ns, cursorState, batchSize, options, callback);
203
+ wp.getMore(
204
+ makeServerTrampoline(this),
205
+ ns,
206
+ cursorState,
207
+ batchSize,
208
+ this.applyApiVersion(options),
209
+ callback
210
+ );
184
211
  }
185
212
 
186
213
  killCursors(ns, cursorState, callback) {
187
- wp.killCursors(makeServerTrampoline(this), ns, cursorState, callback);
214
+ wp.killCursors(makeServerTrampoline(this), ns, cursorState, this.applyApiVersion({}), callback);
188
215
  }
189
216
 
190
217
  insert(ns, ops, options, callback) {
191
- wp.insert(makeServerTrampoline(this), ns, ops, options, callback);
218
+ wp.insert(makeServerTrampoline(this), ns, ops, this.applyApiVersion(options), callback);
192
219
  }
193
220
 
194
221
  update(ns, ops, options, callback) {
195
- wp.update(makeServerTrampoline(this), ns, ops, options, callback);
222
+ wp.update(makeServerTrampoline(this), ns, ops, this.applyApiVersion(options), callback);
196
223
  }
197
224
 
198
225
  remove(ns, ops, options, callback) {
199
- wp.remove(makeServerTrampoline(this), ns, ops, options, callback);
226
+ wp.remove(makeServerTrampoline(this), ns, ops, this.applyApiVersion(options), callback);
200
227
  }
201
228
  }
202
229
 
@@ -41,6 +41,7 @@ const VALID_POOL_OPTIONS = new Set([
41
41
  'ssl',
42
42
  'bson',
43
43
  'connectionType',
44
+ 'serverApi',
44
45
  'monitorCommands',
45
46
  'socketTimeout',
46
47
  'credentials',
@@ -157,12 +158,13 @@ class ConnectionPool extends EventEmitter {
157
158
  waitQueueTimeoutMS:
158
159
  typeof options.waitQueueTimeoutMS === 'number' ? options.waitQueueTimeoutMS : 0,
159
160
  autoEncrypter: options.autoEncrypter,
160
- metadata: options.metadata
161
+ metadata: options.metadata,
162
+ useUnifiedTopology: options.useUnifiedTopology
161
163
  });
162
164
 
163
165
  if (options.minSize > options.maxSize) {
164
166
  throw new TypeError(
165
- 'Connection pool minimum size must not be greater than maxiumum pool size'
167
+ 'Connection pool minimum size must not be greater than maximum pool size'
166
168
  );
167
169
  }
168
170
 
package/lib/collection.js CHANGED
@@ -1532,7 +1532,7 @@ Collection.prototype.count = deprecate(function(query, options, callback) {
1532
1532
 
1533
1533
  return executeOperation(
1534
1534
  this.s.topology,
1535
- new EstimatedDocumentCountOperation(this, query, options),
1535
+ new EstimatedDocumentCountOperation(this, Object.assign({ query }, options)),
1536
1536
  callback
1537
1537
  );
1538
1538
  }, 'collection.count is deprecated, and will be removed in a future version.' +
@@ -95,6 +95,8 @@ function performInitialHandshake(conn, options, _callback) {
95
95
  handshakeOptions.socketTimeout = options.connectTimeoutMS || options.connectionTimeout;
96
96
  }
97
97
 
98
+ handshakeDoc.helloOk = !!options.useUnifiedTopology;
99
+
98
100
  const start = new Date().getTime();
99
101
  conn.command('admin.$cmd', handshakeDoc, handshakeOptions, (err, result) => {
100
102
  if (err) {
@@ -108,6 +110,15 @@ function performInitialHandshake(conn, options, _callback) {
108
110
  return;
109
111
  }
110
112
 
113
+ if ('isWritablePrimary' in response) {
114
+ // Provide pre-hello-style response document.
115
+ response.ismaster = response.isWritablePrimary;
116
+ }
117
+
118
+ if (options.useUnifiedTopology && response.helloOk) {
119
+ conn.helloOk = true;
120
+ }
121
+
111
122
  const supportedServerErr = checkSupportedServer(response, options);
112
123
  if (supportedServerErr) {
113
124
  callback(supportedServerErr);
@@ -158,11 +169,12 @@ function performInitialHandshake(conn, options, _callback) {
158
169
 
159
170
  function prepareHandshakeDocument(authContext, callback) {
160
171
  const options = authContext.options;
172
+ const serverApi = authContext.connection.serverApi;
161
173
  const compressors =
162
174
  options.compression && options.compression.compressors ? options.compression.compressors : [];
163
175
 
164
176
  const handshakeDoc = {
165
- ismaster: true,
177
+ [serverApi ? 'hello' : 'ismaster']: true,
166
178
  client: options.metadata || makeClientMetadata(options),
167
179
  compression: compressors
168
180
  };
@@ -266,12 +278,17 @@ function makeConnection(family, options, cancellationToken, _callback) {
266
278
  : typeof options.connectTimeoutMS === 'number'
267
279
  ? options.connectTimeoutMS
268
280
  : 30000;
269
- const socketTimeout = typeof options.socketTimeout === 'number' ? options.socketTimeout : 0;
281
+ const socketTimeoutMS =
282
+ typeof options.socketTimeoutMS === 'number'
283
+ ? options.socketTimeoutMS
284
+ : typeof options.socketTimeout === 'number'
285
+ ? options.socketTimeout
286
+ : 0;
270
287
  const rejectUnauthorized =
271
288
  typeof options.rejectUnauthorized === 'boolean' ? options.rejectUnauthorized : true;
272
289
 
273
- if (keepAliveInitialDelay > socketTimeout) {
274
- keepAliveInitialDelay = Math.round(socketTimeout / 2);
290
+ if (keepAliveInitialDelay > socketTimeoutMS) {
291
+ keepAliveInitialDelay = Math.round(socketTimeoutMS / 2);
275
292
  }
276
293
 
277
294
  let socket;
@@ -324,7 +341,7 @@ function makeConnection(family, options, cancellationToken, _callback) {
324
341
  return callback(socket.authorizationError);
325
342
  }
326
343
 
327
- socket.setTimeout(socketTimeout);
344
+ socket.setTimeout(socketTimeoutMS);
328
345
  callback(null, socket);
329
346
  }
330
347
 
@@ -91,6 +91,7 @@ class Connection extends EventEmitter {
91
91
  this.bson = options.bson;
92
92
  this.tag = options.tag;
93
93
  this.maxBsonMessageSize = options.maxBsonMessageSize || DEFAULT_MAX_BSON_MESSAGE_SIZE;
94
+ this.helloOk = undefined;
94
95
 
95
96
  this.port = options.port || 27017;
96
97
  this.host = options.host || 'localhost';
package/lib/core/error.js CHANGED
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ const MONGODB_ERROR_CODES = require('../error_codes').MONGODB_ERROR_CODES;
4
+
3
5
  const kErrorLabels = Symbol('errorLabels');
4
6
 
5
7
  /**
@@ -216,32 +218,32 @@ class MongoWriteConcernError extends MongoError {
216
218
 
217
219
  // see: https://github.com/mongodb/specifications/blob/master/source/retryable-writes/retryable-writes.rst#terms
218
220
  const RETRYABLE_ERROR_CODES = new Set([
219
- 6, // HostUnreachable
220
- 7, // HostNotFound
221
- 89, // NetworkTimeout
222
- 91, // ShutdownInProgress
223
- 189, // PrimarySteppedDown
224
- 9001, // SocketException
225
- 10107, // NotMaster
226
- 11600, // InterruptedAtShutdown
227
- 11602, // InterruptedDueToReplStateChange
228
- 13435, // NotMasterNoSlaveOk
229
- 13436 // NotMasterOrSecondary
221
+ MONGODB_ERROR_CODES.HostUnreachable,
222
+ MONGODB_ERROR_CODES.HostNotFound,
223
+ MONGODB_ERROR_CODES.NetworkTimeout,
224
+ MONGODB_ERROR_CODES.ShutdownInProgress,
225
+ MONGODB_ERROR_CODES.PrimarySteppedDown,
226
+ MONGODB_ERROR_CODES.SocketException,
227
+ MONGODB_ERROR_CODES.NotMaster,
228
+ MONGODB_ERROR_CODES.InterruptedAtShutdown,
229
+ MONGODB_ERROR_CODES.InterruptedDueToReplStateChange,
230
+ MONGODB_ERROR_CODES.NotMasterNoSlaveOk,
231
+ MONGODB_ERROR_CODES.NotMasterOrSecondary
230
232
  ]);
231
233
 
232
234
  const RETRYABLE_WRITE_ERROR_CODES = new Set([
233
- 11600, // InterruptedAtShutdown
234
- 11602, // InterruptedDueToReplStateChange
235
- 10107, // NotMaster
236
- 13435, // NotMasterNoSlaveOk
237
- 13436, // NotMasterOrSecondary
238
- 189, // PrimarySteppedDown
239
- 91, // ShutdownInProgress
240
- 7, // HostNotFound
241
- 6, // HostUnreachable
242
- 89, // NetworkTimeout
243
- 9001, // SocketException
244
- 262 // ExceededTimeLimit
235
+ MONGODB_ERROR_CODES.InterruptedAtShutdown,
236
+ MONGODB_ERROR_CODES.InterruptedDueToReplStateChange,
237
+ MONGODB_ERROR_CODES.NotMaster,
238
+ MONGODB_ERROR_CODES.NotMasterNoSlaveOk,
239
+ MONGODB_ERROR_CODES.NotMasterOrSecondary,
240
+ MONGODB_ERROR_CODES.PrimarySteppedDown,
241
+ MONGODB_ERROR_CODES.ShutdownInProgress,
242
+ MONGODB_ERROR_CODES.HostNotFound,
243
+ MONGODB_ERROR_CODES.HostUnreachable,
244
+ MONGODB_ERROR_CODES.NetworkTimeout,
245
+ MONGODB_ERROR_CODES.SocketException,
246
+ MONGODB_ERROR_CODES.ExceededTimeLimit
245
247
  ]);
246
248
 
247
249
  function isRetryableWriteError(error) {
@@ -271,41 +273,44 @@ function isRetryableError(error) {
271
273
  }
272
274
 
273
275
  const SDAM_RECOVERING_CODES = new Set([
274
- 91, // ShutdownInProgress
275
- 189, // PrimarySteppedDown
276
- 11600, // InterruptedAtShutdown
277
- 11602, // InterruptedDueToReplStateChange
278
- 13436 // NotMasterOrSecondary
276
+ MONGODB_ERROR_CODES.ShutdownInProgress,
277
+ MONGODB_ERROR_CODES.PrimarySteppedDown,
278
+ MONGODB_ERROR_CODES.InterruptedAtShutdown,
279
+ MONGODB_ERROR_CODES.InterruptedDueToReplStateChange,
280
+ MONGODB_ERROR_CODES.NotMasterOrSecondary
279
281
  ]);
280
282
 
281
283
  const SDAM_NOTMASTER_CODES = new Set([
282
- 10107, // NotMaster
283
- 13435 // NotMasterNoSlaveOk
284
+ MONGODB_ERROR_CODES.NotMaster,
285
+ MONGODB_ERROR_CODES.NotMasterNoSlaveOk,
286
+ MONGODB_ERROR_CODES.LegacyNotPrimary
284
287
  ]);
285
288
 
286
289
  const SDAM_NODE_SHUTTING_DOWN_ERROR_CODES = new Set([
287
- 11600, // InterruptedAtShutdown
288
- 91 // ShutdownInProgress
290
+ MONGODB_ERROR_CODES.InterruptedAtShutdown,
291
+ MONGODB_ERROR_CODES.ShutdownInProgress
289
292
  ]);
290
293
 
291
294
  function isRecoveringError(err) {
292
- if (err.code && SDAM_RECOVERING_CODES.has(err.code)) {
293
- return true;
295
+ if (typeof err.code === 'number') {
296
+ // If any error code exists, we ignore the error.message
297
+ return SDAM_RECOVERING_CODES.has(err.code);
294
298
  }
295
299
 
296
- return err.message.match(/not master or secondary/) || err.message.match(/node is recovering/);
300
+ return /not master or secondary/.test(err.message) || /node is recovering/.test(err.message);
297
301
  }
298
302
 
299
303
  function isNotMasterError(err) {
300
- if (err.code && SDAM_NOTMASTER_CODES.has(err.code)) {
301
- return true;
304
+ if (typeof err.code === 'number') {
305
+ // If any error code exists, we ignore the error.message
306
+ return SDAM_NOTMASTER_CODES.has(err.code);
302
307
  }
303
308
 
304
309
  if (isRecoveringError(err)) {
305
310
  return false;
306
311
  }
307
312
 
308
- return err.message.match(/not master/);
313
+ return /not master/.test(err.message);
309
314
  }
310
315
 
311
316
  function isNodeShuttingDownError(err) {
@@ -316,10 +321,9 @@ function isNodeShuttingDownError(err) {
316
321
  * Determines whether SDAM can recover from a given error. If it cannot
317
322
  * then the pool will be cleared, and server state will completely reset
318
323
  * locally.
319
- *
320
- * @ignore
321
324
  * @see https://github.com/mongodb/specifications/blob/master/source/server-discovery-and-monitoring/server-discovery-and-monitoring.rst#not-master-and-node-is-recovering
322
- * @param {MongoError|Error} error
325
+ * @param {MongoError} error
326
+ * @returns {boolean}
323
327
  */
324
328
  function isSDAMUnrecoverableError(error) {
325
329
  // NOTE: null check is here for a strictly pre-CMAP world, a timeout or
@@ -328,11 +332,7 @@ function isSDAMUnrecoverableError(error) {
328
332
  return true;
329
333
  }
330
334
 
331
- if (isRecoveringError(error) || isNotMasterError(error)) {
332
- return true;
333
- }
334
-
335
- return false;
335
+ return isRecoveringError(error) || isNotMasterError(error);
336
336
  }
337
337
 
338
338
  module.exports = {
package/lib/core/index.js CHANGED
@@ -15,7 +15,16 @@ try {
15
15
  }
16
16
  } catch (err) {} // eslint-disable-line
17
17
 
18
+ /** An enumeration of valid server API versions */
19
+ const ServerApiVersion = Object.freeze({
20
+ v1: '1'
21
+ });
22
+ const ValidServerApiVersions = Object.keys(ServerApiVersion).map(key => ServerApiVersion[key]);
23
+
18
24
  module.exports = {
25
+ // Versioned API
26
+ ServerApiVersion,
27
+ ValidServerApiVersions,
19
28
  // Errors
20
29
  MongoError: require('./error').MongoError,
21
30
  MongoNetworkError: require('./error').MongoNetworkError,
@@ -65,7 +65,8 @@ class Monitor extends EventEmitter {
65
65
  heartbeatFrequencyMS:
66
66
  typeof options.heartbeatFrequencyMS === 'number' ? options.heartbeatFrequencyMS : 10000,
67
67
  minHeartbeatFrequencyMS:
68
- typeof options.minHeartbeatFrequencyMS === 'number' ? options.minHeartbeatFrequencyMS : 500
68
+ typeof options.minHeartbeatFrequencyMS === 'number' ? options.minHeartbeatFrequencyMS : 500,
69
+ useUnifiedTopology: options.useUnifiedTopology
69
70
  });
70
71
 
71
72
  // TODO: refactor this to pull it directly from the pool, requires new ConnectionPool integration
@@ -204,8 +205,16 @@ function checkServer(monitor, callback) {
204
205
  const maxAwaitTimeMS = monitor.options.heartbeatFrequencyMS;
205
206
  const topologyVersion = monitor[kServer].description.topologyVersion;
206
207
  const isAwaitable = topologyVersion != null;
208
+ const serverApi = monitor[kConnection].serverApi;
209
+ const helloOk = monitor[kConnection].helloOk;
210
+
211
+ const cmd = {
212
+ [serverApi || helloOk ? 'hello' : 'ismaster']: true
213
+ };
214
+
215
+ // written this way omit helloOk from the command if its false-y (do not want -> helloOk: null)
216
+ if (helloOk) cmd.helloOk = helloOk;
207
217
 
208
- const cmd = { ismaster: true };
209
218
  const options = { socketTimeout: connectTimeoutMS };
210
219
 
211
220
  if (isAwaitable) {
@@ -229,6 +238,11 @@ function checkServer(monitor, callback) {
229
238
  const isMaster = result.result;
230
239
  const rttPinger = monitor[kRTTPinger];
231
240
 
241
+ if ('isWritablePrimary' in isMaster) {
242
+ // Provide pre-hello-style response document.
243
+ isMaster.ismaster = isMaster.isWritablePrimary;
244
+ }
245
+
232
246
  const duration =
233
247
  isAwaitable && rttPinger ? rttPinger.roundTripTime : calculateDurationInMs(start);
234
248
 
@@ -114,6 +114,7 @@ class Server extends EventEmitter {
114
114
  credentials: options.credentials,
115
115
  topology
116
116
  };
117
+ this.serverApi = options.serverApi;
117
118
 
118
119
  // create the connection pool
119
120
  // NOTE: this used to happen in `connect`, we supported overriding pool options there
@@ -251,6 +252,7 @@ class Server extends EventEmitter {
251
252
  if (typeof options === 'function') {
252
253
  (callback = options), (options = {}), (options = options || {});
253
254
  }
255
+ options.serverApi = this.serverApi;
254
256
 
255
257
  if (this.s.state === STATE_CLOSING || this.s.state === STATE_CLOSED) {
256
258
  callback(new MongoError('server is closed'));
@@ -70,6 +70,10 @@ class ServerDescription {
70
70
  ismaster
71
71
  );
72
72
 
73
+ if (ismaster.isWritablePrimary != null) {
74
+ ismaster.ismaster = ismaster.isWritablePrimary;
75
+ }
76
+
73
77
  this.address = address;
74
78
  this.error = options.error;
75
79
  this.roundTripTime = options.roundTripTime || -1;
@@ -200,6 +200,7 @@ class Topology extends EventEmitter {
200
200
  // timer management
201
201
  connectionTimers: new Set()
202
202
  };
203
+ this.serverApi = options.serverApi;
203
204
 
204
205
  if (options.srvHost) {
205
206
  this.s.srvPoller =
@@ -494,7 +495,11 @@ class Topology extends EventEmitter {
494
495
  this.command(
495
496
  'admin.$cmd',
496
497
  { endSessions: sessions },
497
- { readPreference: ReadPreference.primaryPreferred, noResponse: true },
498
+ {
499
+ readPreference: ReadPreference.primaryPreferred,
500
+ noResponse: true,
501
+ serverApi: this.serverApi
502
+ },
498
503
  () => {
499
504
  // intentionally ignored, per spec
500
505
  if (typeof callback === 'function') callback();
@@ -472,17 +472,20 @@ function endTransaction(session, commandName, callback) {
472
472
  if (commandName === 'commitTransaction') {
473
473
  session.transaction.transition(TxnState.TRANSACTION_COMMITTED);
474
474
 
475
- if (
476
- e &&
477
- (e instanceof MongoNetworkError ||
475
+ if (e) {
476
+ if (
477
+ e instanceof MongoNetworkError ||
478
478
  e instanceof MongoWriteConcernError ||
479
479
  isRetryableError(e) ||
480
- isMaxTimeMSExpiredError(e))
481
- ) {
482
- if (isUnknownTransactionCommitResult(e)) {
483
- e.addErrorLabel('UnknownTransactionCommitResult');
480
+ isMaxTimeMSExpiredError(e)
481
+ ) {
482
+ if (isUnknownTransactionCommitResult(e)) {
483
+ e.addErrorLabel('UnknownTransactionCommitResult');
484
484
 
485
- // per txns spec, must unpin session in this case
485
+ // per txns spec, must unpin session in this case
486
+ session.transaction.unpinServer();
487
+ }
488
+ } else if (e.hasErrorLabel('TransientTransactionError')) {
486
489
  session.transaction.unpinServer();
487
490
  }
488
491
  }
@@ -34,7 +34,7 @@ var ReplSetState = function(options) {
34
34
  // Add event listener
35
35
  EventEmitter.call(this);
36
36
  // Topology state
37
- this.topologyType = TopologyType.ReplicaSetNoPrimary;
37
+ this.topologyType = options.setName ? TopologyType.ReplicaSetNoPrimary : TopologyType.Unknown;
38
38
  this.setName = options.setName;
39
39
 
40
40
  // Server set
@@ -218,7 +218,8 @@ const isArbiter = ismaster => ismaster.arbiterOnly && ismaster.setName;
218
218
  ReplSetState.prototype.update = function(server) {
219
219
  var self = this;
220
220
  // Get the current ismaster
221
- var ismaster = server.lastIsMaster();
221
+ const ismaster = server.lastIsMaster();
222
+ if (ismaster && ismaster.isWritablePrimary) ismaster.ismaster = ismaster.isWritablePrimary;
222
223
 
223
224
  // Get the server name and lowerCase it
224
225
  var serverName = server.name.toLowerCase();
@@ -358,7 +359,8 @@ ReplSetState.prototype.update = function(server) {
358
359
  // Standalone server, destroy and return
359
360
  //
360
361
  if (ismaster && ismaster.ismaster && !ismaster.setName) {
361
- this.topologyType = this.primary ? TopologyType.ReplicaSetWithPrimary : TopologyType.Unknown;
362
+ // We should not mark the topology as Unknown because of one standalone
363
+ // we should just remove this server from the set
362
364
  this.remove(server, { force: true });
363
365
  return false;
364
366
  }
@@ -1,11 +1,14 @@
1
1
  'use strict';
2
+
3
+ const MONGODB_ERROR_CODES = require('../../error_codes').MONGODB_ERROR_CODES;
2
4
  const ReadPreference = require('./read_preference');
3
5
  const TopologyType = require('../sdam/common').TopologyType;
4
6
  const MongoError = require('../error').MongoError;
5
7
  const isRetryableWriteError = require('../error').isRetryableWriteError;
6
8
  const maxWireVersion = require('../utils').maxWireVersion;
7
9
  const MongoNetworkError = require('../error').MongoNetworkError;
8
- const MMAPv1_RETRY_WRITES_ERROR_CODE = 20;
10
+
11
+ const MMAPv1_RETRY_WRITES_ERROR_CODE = MONGODB_ERROR_CODES.IllegalOperation;
9
12
 
10
13
  /**
11
14
  * Emit event if it exists
@@ -150,7 +150,11 @@ class Transaction {
150
150
  const nextStates = stateMachine[this.state];
151
151
  if (nextStates && nextStates.indexOf(nextState) !== -1) {
152
152
  this.state = nextState;
153
- if (this.state === TxnState.NO_TRANSACTION || this.state === TxnState.STARTING_TRANSACTION) {
153
+ if (
154
+ this.state === TxnState.NO_TRANSACTION ||
155
+ this.state === TxnState.STARTING_TRANSACTION ||
156
+ this.state === TxnState.TRANSACTION_ABORTED
157
+ ) {
154
158
  this.unpinServer();
155
159
  }
156
160
  return;