mongodb 3.2.5 → 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.
Files changed (133) hide show
  1. package/HISTORY.md +0 -10
  2. package/index.js +4 -4
  3. package/lib/admin.js +56 -56
  4. package/lib/aggregation_cursor.js +7 -3
  5. package/lib/bulk/common.js +18 -13
  6. package/lib/change_stream.js +196 -89
  7. package/lib/collection.js +217 -169
  8. package/lib/command_cursor.js +17 -7
  9. package/lib/core/auth/auth_provider.js +158 -0
  10. package/lib/core/auth/defaultAuthProviders.js +29 -0
  11. package/lib/core/auth/gssapi.js +241 -0
  12. package/lib/core/auth/mongo_credentials.js +81 -0
  13. package/lib/core/auth/mongocr.js +51 -0
  14. package/lib/core/auth/plain.js +35 -0
  15. package/lib/core/auth/scram.js +293 -0
  16. package/lib/core/auth/sspi.js +131 -0
  17. package/lib/core/auth/x509.js +26 -0
  18. package/lib/core/connection/apm.js +236 -0
  19. package/lib/core/connection/command_result.js +36 -0
  20. package/lib/core/connection/commands.js +507 -0
  21. package/lib/core/connection/connect.js +370 -0
  22. package/lib/core/connection/connection.js +624 -0
  23. package/lib/core/connection/logger.js +246 -0
  24. package/lib/core/connection/msg.js +219 -0
  25. package/lib/core/connection/pool.js +1285 -0
  26. package/lib/core/connection/utils.js +57 -0
  27. package/lib/core/cursor.js +752 -0
  28. package/lib/core/error.js +186 -0
  29. package/lib/core/index.js +50 -0
  30. package/lib/core/sdam/monitoring.js +228 -0
  31. package/lib/core/sdam/server.js +467 -0
  32. package/lib/core/sdam/server_description.js +163 -0
  33. package/lib/core/sdam/server_selectors.js +244 -0
  34. package/lib/core/sdam/srv_polling.js +135 -0
  35. package/lib/core/sdam/topology.js +1151 -0
  36. package/lib/core/sdam/topology_description.js +408 -0
  37. package/lib/core/sessions.js +711 -0
  38. package/lib/core/tools/smoke_plugin.js +61 -0
  39. package/lib/core/topologies/mongos.js +1337 -0
  40. package/lib/core/topologies/read_preference.js +202 -0
  41. package/lib/core/topologies/replset.js +1507 -0
  42. package/lib/core/topologies/replset_state.js +1121 -0
  43. package/lib/core/topologies/server.js +984 -0
  44. package/lib/core/topologies/shared.js +453 -0
  45. package/lib/core/transactions.js +167 -0
  46. package/lib/core/uri_parser.js +631 -0
  47. package/lib/core/utils.js +165 -0
  48. package/lib/core/wireprotocol/command.js +170 -0
  49. package/lib/core/wireprotocol/compression.js +73 -0
  50. package/lib/core/wireprotocol/constants.js +13 -0
  51. package/lib/core/wireprotocol/get_more.js +86 -0
  52. package/lib/core/wireprotocol/index.js +18 -0
  53. package/lib/core/wireprotocol/kill_cursors.js +70 -0
  54. package/lib/core/wireprotocol/query.js +224 -0
  55. package/lib/core/wireprotocol/shared.js +115 -0
  56. package/lib/core/wireprotocol/write_command.js +50 -0
  57. package/lib/cursor.js +40 -46
  58. package/lib/db.js +141 -95
  59. package/lib/dynamic_loaders.js +32 -0
  60. package/lib/error.js +12 -10
  61. package/lib/gridfs/chunk.js +2 -2
  62. package/lib/gridfs/grid_store.js +31 -25
  63. package/lib/gridfs-stream/index.js +4 -4
  64. package/lib/gridfs-stream/upload.js +1 -1
  65. package/lib/mongo_client.js +37 -15
  66. package/lib/operations/add_user.js +96 -0
  67. package/lib/operations/aggregate.js +24 -13
  68. package/lib/operations/aggregate_operation.js +127 -0
  69. package/lib/operations/bulk_write.js +104 -0
  70. package/lib/operations/close.js +47 -0
  71. package/lib/operations/collection_ops.js +28 -287
  72. package/lib/operations/collections.js +55 -0
  73. package/lib/operations/command.js +120 -0
  74. package/lib/operations/command_v2.js +43 -0
  75. package/lib/operations/common_functions.js +372 -0
  76. package/lib/operations/{mongo_client_ops.js → connect.js} +185 -157
  77. package/lib/operations/count.js +72 -0
  78. package/lib/operations/count_documents.js +46 -0
  79. package/lib/operations/create_collection.js +118 -0
  80. package/lib/operations/create_index.js +92 -0
  81. package/lib/operations/create_indexes.js +61 -0
  82. package/lib/operations/cursor_ops.js +3 -4
  83. package/lib/operations/db_ops.js +15 -12
  84. package/lib/operations/delete_many.js +25 -0
  85. package/lib/operations/delete_one.js +25 -0
  86. package/lib/operations/distinct.js +85 -0
  87. package/lib/operations/drop.js +53 -0
  88. package/lib/operations/drop_index.js +42 -0
  89. package/lib/operations/drop_indexes.js +23 -0
  90. package/lib/operations/estimated_document_count.js +33 -0
  91. package/lib/operations/execute_db_admin_command.js +34 -0
  92. package/lib/operations/execute_operation.js +165 -0
  93. package/lib/operations/explain.js +23 -0
  94. package/lib/operations/find_and_modify.js +98 -0
  95. package/lib/operations/find_one.js +33 -0
  96. package/lib/operations/find_one_and_delete.js +16 -0
  97. package/lib/operations/find_one_and_replace.js +18 -0
  98. package/lib/operations/find_one_and_update.js +19 -0
  99. package/lib/operations/geo_haystack_search.js +79 -0
  100. package/lib/operations/has_next.js +40 -0
  101. package/lib/operations/index_exists.js +39 -0
  102. package/lib/operations/index_information.js +23 -0
  103. package/lib/operations/indexes.js +22 -0
  104. package/lib/operations/insert_many.js +63 -0
  105. package/lib/operations/insert_one.js +75 -0
  106. package/lib/operations/is_capped.js +19 -0
  107. package/lib/operations/list_indexes.js +66 -0
  108. package/lib/operations/map_reduce.js +189 -0
  109. package/lib/operations/next.js +32 -0
  110. package/lib/operations/operation.js +63 -0
  111. package/lib/operations/options_operation.js +32 -0
  112. package/lib/operations/profiling_level.js +31 -0
  113. package/lib/operations/re_index.js +28 -0
  114. package/lib/operations/remove_user.js +52 -0
  115. package/lib/operations/rename.js +61 -0
  116. package/lib/operations/replace_one.js +47 -0
  117. package/lib/operations/set_profiling_level.js +48 -0
  118. package/lib/operations/stats.js +45 -0
  119. package/lib/operations/to_array.js +68 -0
  120. package/lib/operations/update_many.js +29 -0
  121. package/lib/operations/update_one.js +44 -0
  122. package/lib/operations/validate_collection.js +40 -0
  123. package/lib/read_concern.js +55 -0
  124. package/lib/topologies/mongos.js +3 -3
  125. package/lib/topologies/native_topology.js +22 -2
  126. package/lib/topologies/replset.js +3 -3
  127. package/lib/topologies/server.js +4 -4
  128. package/lib/topologies/topology_base.js +6 -6
  129. package/lib/url_parser.js +4 -3
  130. package/lib/utils.js +46 -59
  131. package/lib/write_concern.js +66 -0
  132. package/package.json +15 -6
  133. package/lib/.DS_Store +0 -0
@@ -0,0 +1,1507 @@
1
+ 'use strict';
2
+
3
+ const inherits = require('util').inherits;
4
+ const f = require('util').format;
5
+ const EventEmitter = require('events').EventEmitter;
6
+ const ReadPreference = require('./read_preference');
7
+ const BasicCursor = require('../cursor');
8
+ const retrieveBSON = require('../connection/utils').retrieveBSON;
9
+ const Logger = require('../connection/logger');
10
+ const MongoError = require('../error').MongoError;
11
+ const Server = require('./server');
12
+ const ReplSetState = require('./replset_state');
13
+ const clone = require('./shared').clone;
14
+ const Timeout = require('./shared').Timeout;
15
+ const Interval = require('./shared').Interval;
16
+ const createClientInfo = require('./shared').createClientInfo;
17
+ const SessionMixins = require('./shared').SessionMixins;
18
+ const isRetryableWritesSupported = require('./shared').isRetryableWritesSupported;
19
+ const relayEvents = require('../utils').relayEvents;
20
+ const isRetryableError = require('../error').isRetryableError;
21
+ const BSON = retrieveBSON();
22
+
23
+ //
24
+ // States
25
+ var DISCONNECTED = 'disconnected';
26
+ var CONNECTING = 'connecting';
27
+ var CONNECTED = 'connected';
28
+ var UNREFERENCED = 'unreferenced';
29
+ var DESTROYED = 'destroyed';
30
+
31
+ function stateTransition(self, newState) {
32
+ var legalTransitions = {
33
+ disconnected: [CONNECTING, DESTROYED, DISCONNECTED],
34
+ connecting: [CONNECTING, DESTROYED, CONNECTED, DISCONNECTED],
35
+ connected: [CONNECTED, DISCONNECTED, DESTROYED, UNREFERENCED],
36
+ unreferenced: [UNREFERENCED, DESTROYED],
37
+ destroyed: [DESTROYED]
38
+ };
39
+
40
+ // Get current state
41
+ var legalStates = legalTransitions[self.state];
42
+ if (legalStates && legalStates.indexOf(newState) !== -1) {
43
+ self.state = newState;
44
+ } else {
45
+ self.s.logger.error(
46
+ f(
47
+ 'Pool with id [%s] failed attempted illegal state transition from [%s] to [%s] only following state allowed [%s]',
48
+ self.id,
49
+ self.state,
50
+ newState,
51
+ legalStates
52
+ )
53
+ );
54
+ }
55
+ }
56
+
57
+ //
58
+ // ReplSet instance id
59
+ var id = 1;
60
+ var handlers = ['connect', 'close', 'error', 'timeout', 'parseError'];
61
+
62
+ /**
63
+ * Creates a new Replset instance
64
+ * @class
65
+ * @param {array} seedlist A list of seeds for the replicaset
66
+ * @param {boolean} options.setName The Replicaset set name
67
+ * @param {boolean} [options.secondaryOnlyConnectionAllowed=false] Allow connection to a secondary only replicaset
68
+ * @param {number} [options.haInterval=10000] The High availability period for replicaset inquiry
69
+ * @param {boolean} [options.emitError=false] Server will emit errors events
70
+ * @param {Cursor} [options.cursorFactory=Cursor] The cursor factory class used for all query cursors
71
+ * @param {number} [options.size=5] Server connection pool size
72
+ * @param {boolean} [options.keepAlive=true] TCP Connection keep alive enabled
73
+ * @param {number} [options.keepAliveInitialDelay=0] Initial delay before TCP keep alive enabled
74
+ * @param {boolean} [options.noDelay=true] TCP Connection no delay
75
+ * @param {number} [options.connectionTimeout=10000] TCP Connection timeout setting
76
+ * @param {number} [options.socketTimeout=0] TCP Socket timeout setting
77
+ * @param {boolean} [options.ssl=false] Use SSL for connection
78
+ * @param {boolean|function} [options.checkServerIdentity=true] Ensure we check server identify during SSL, set to false to disable checking. Only works for Node 0.12.x or higher. You can pass in a boolean or your own checkServerIdentity override function.
79
+ * @param {Buffer} [options.ca] SSL Certificate store binary buffer
80
+ * @param {Buffer} [options.crl] SSL Certificate revocation store binary buffer
81
+ * @param {Buffer} [options.cert] SSL Certificate binary buffer
82
+ * @param {Buffer} [options.key] SSL Key file binary buffer
83
+ * @param {string} [options.passphrase] SSL Certificate pass phrase
84
+ * @param {string} [options.servername=null] String containing the server name requested via TLS SNI.
85
+ * @param {boolean} [options.rejectUnauthorized=true] Reject unauthorized server certificates
86
+ * @param {boolean} [options.promoteLongs=true] Convert Long values from the db into Numbers if they fit into 53 bits
87
+ * @param {boolean} [options.promoteValues=true] Promotes BSON values to native types where possible, set to false to only receive wrapper types.
88
+ * @param {boolean} [options.promoteBuffers=false] Promotes Binary BSON values to native Node Buffers.
89
+ * @param {number} [options.pingInterval=5000] Ping interval to check the response time to the different servers
90
+ * @param {number} [options.localThresholdMS=15] Cutoff latency point in MS for Replicaset member selection
91
+ * @param {boolean} [options.domainsEnabled=false] Enable the wrapping of the callback in the current domain, disabled by default to avoid perf hit.
92
+ * @param {boolean} [options.monitorCommands=false] Enable command monitoring for this topology
93
+ * @return {ReplSet} A cursor instance
94
+ * @fires ReplSet#connect
95
+ * @fires ReplSet#ha
96
+ * @fires ReplSet#joined
97
+ * @fires ReplSet#left
98
+ * @fires ReplSet#failed
99
+ * @fires ReplSet#fullsetup
100
+ * @fires ReplSet#all
101
+ * @fires ReplSet#error
102
+ * @fires ReplSet#serverHeartbeatStarted
103
+ * @fires ReplSet#serverHeartbeatSucceeded
104
+ * @fires ReplSet#serverHeartbeatFailed
105
+ * @fires ReplSet#topologyOpening
106
+ * @fires ReplSet#topologyClosed
107
+ * @fires ReplSet#topologyDescriptionChanged
108
+ * @property {string} type the topology type.
109
+ * @property {string} parserType the parser type used (c++ or js).
110
+ */
111
+ var ReplSet = function(seedlist, options) {
112
+ var self = this;
113
+ options = options || {};
114
+
115
+ // Validate seedlist
116
+ if (!Array.isArray(seedlist)) throw new MongoError('seedlist must be an array');
117
+ // Validate list
118
+ if (seedlist.length === 0) throw new MongoError('seedlist must contain at least one entry');
119
+ // Validate entries
120
+ seedlist.forEach(function(e) {
121
+ if (typeof e.host !== 'string' || typeof e.port !== 'number')
122
+ throw new MongoError('seedlist entry must contain a host and port');
123
+ });
124
+
125
+ // Add event listener
126
+ EventEmitter.call(this);
127
+
128
+ // Get replSet Id
129
+ this.id = id++;
130
+
131
+ // Get the localThresholdMS
132
+ var localThresholdMS = options.localThresholdMS || 15;
133
+ // Backward compatibility
134
+ if (options.acceptableLatency) localThresholdMS = options.acceptableLatency;
135
+
136
+ // Create a logger
137
+ var logger = Logger('ReplSet', options);
138
+
139
+ // Internal state
140
+ this.s = {
141
+ options: Object.assign({}, options),
142
+ // BSON instance
143
+ bson:
144
+ options.bson ||
145
+ new BSON([
146
+ BSON.Binary,
147
+ BSON.Code,
148
+ BSON.DBRef,
149
+ BSON.Decimal128,
150
+ BSON.Double,
151
+ BSON.Int32,
152
+ BSON.Long,
153
+ BSON.Map,
154
+ BSON.MaxKey,
155
+ BSON.MinKey,
156
+ BSON.ObjectId,
157
+ BSON.BSONRegExp,
158
+ BSON.Symbol,
159
+ BSON.Timestamp
160
+ ]),
161
+ // Factory overrides
162
+ Cursor: options.cursorFactory || BasicCursor,
163
+ // Logger instance
164
+ logger: logger,
165
+ // Seedlist
166
+ seedlist: seedlist,
167
+ // Replicaset state
168
+ replicaSetState: new ReplSetState({
169
+ id: this.id,
170
+ setName: options.setName,
171
+ acceptableLatency: localThresholdMS,
172
+ heartbeatFrequencyMS: options.haInterval ? options.haInterval : 10000,
173
+ logger: logger
174
+ }),
175
+ // Current servers we are connecting to
176
+ connectingServers: [],
177
+ // Ha interval
178
+ haInterval: options.haInterval ? options.haInterval : 10000,
179
+ // Minimum heartbeat frequency used if we detect a server close
180
+ minHeartbeatFrequencyMS: 500,
181
+ // Disconnect handler
182
+ disconnectHandler: options.disconnectHandler,
183
+ // Server selection index
184
+ index: 0,
185
+ // Connect function options passed in
186
+ connectOptions: {},
187
+ // Are we running in debug mode
188
+ debug: typeof options.debug === 'boolean' ? options.debug : false,
189
+ // Client info
190
+ clientInfo: createClientInfo(options)
191
+ };
192
+
193
+ // Add handler for topology change
194
+ this.s.replicaSetState.on('topologyDescriptionChanged', function(r) {
195
+ self.emit('topologyDescriptionChanged', r);
196
+ });
197
+
198
+ // Log info warning if the socketTimeout < haInterval as it will cause
199
+ // a lot of recycled connections to happen.
200
+ if (
201
+ this.s.logger.isWarn() &&
202
+ this.s.options.socketTimeout !== 0 &&
203
+ this.s.options.socketTimeout < this.s.haInterval
204
+ ) {
205
+ this.s.logger.warn(
206
+ f(
207
+ 'warning socketTimeout %s is less than haInterval %s. This might cause unnecessary server reconnections due to socket timeouts',
208
+ this.s.options.socketTimeout,
209
+ this.s.haInterval
210
+ )
211
+ );
212
+ }
213
+
214
+ // Add forwarding of events from state handler
215
+ var types = ['joined', 'left'];
216
+ types.forEach(function(x) {
217
+ self.s.replicaSetState.on(x, function(t, s) {
218
+ self.emit(x, t, s);
219
+ });
220
+ });
221
+
222
+ // Connect stat
223
+ this.initialConnectState = {
224
+ connect: false,
225
+ fullsetup: false,
226
+ all: false
227
+ };
228
+
229
+ // Disconnected state
230
+ this.state = DISCONNECTED;
231
+ this.haTimeoutId = null;
232
+ // Last ismaster
233
+ this.ismaster = null;
234
+ // Contains the intervalId
235
+ this.intervalIds = [];
236
+
237
+ // Highest clusterTime seen in responses from the current deployment
238
+ this.clusterTime = null;
239
+ };
240
+
241
+ inherits(ReplSet, EventEmitter);
242
+ Object.assign(ReplSet.prototype, SessionMixins);
243
+
244
+ Object.defineProperty(ReplSet.prototype, 'type', {
245
+ enumerable: true,
246
+ get: function() {
247
+ return 'replset';
248
+ }
249
+ });
250
+
251
+ Object.defineProperty(ReplSet.prototype, 'parserType', {
252
+ enumerable: true,
253
+ get: function() {
254
+ return BSON.native ? 'c++' : 'js';
255
+ }
256
+ });
257
+
258
+ Object.defineProperty(ReplSet.prototype, 'logicalSessionTimeoutMinutes', {
259
+ enumerable: true,
260
+ get: function() {
261
+ return this.s.replicaSetState.logicalSessionTimeoutMinutes || null;
262
+ }
263
+ });
264
+
265
+ function rexecuteOperations(self) {
266
+ // If we have a primary and a disconnect handler, execute
267
+ // buffered operations
268
+ if (self.s.replicaSetState.hasPrimaryAndSecondary() && self.s.disconnectHandler) {
269
+ self.s.disconnectHandler.execute();
270
+ } else if (self.s.replicaSetState.hasPrimary() && self.s.disconnectHandler) {
271
+ self.s.disconnectHandler.execute({ executePrimary: true });
272
+ } else if (self.s.replicaSetState.hasSecondary() && self.s.disconnectHandler) {
273
+ self.s.disconnectHandler.execute({ executeSecondary: true });
274
+ }
275
+ }
276
+
277
+ function connectNewServers(self, servers, callback) {
278
+ // Count lefts
279
+ var count = servers.length;
280
+ var error = null;
281
+
282
+ // Handle events
283
+ var _handleEvent = function(self, event) {
284
+ return function(err) {
285
+ var _self = this;
286
+ count = count - 1;
287
+
288
+ // Destroyed
289
+ if (self.state === DESTROYED || self.state === UNREFERENCED) {
290
+ return this.destroy({ force: true });
291
+ }
292
+
293
+ if (event === 'connect') {
294
+ // Destroyed
295
+ if (self.state === DESTROYED || self.state === UNREFERENCED) {
296
+ return _self.destroy({ force: true });
297
+ }
298
+
299
+ // Update the state
300
+ var result = self.s.replicaSetState.update(_self);
301
+ // Update the state with the new server
302
+ if (result) {
303
+ // Primary lastIsMaster store it
304
+ if (_self.lastIsMaster() && _self.lastIsMaster().ismaster) {
305
+ self.ismaster = _self.lastIsMaster();
306
+ }
307
+
308
+ // Remove the handlers
309
+ for (let i = 0; i < handlers.length; i++) {
310
+ _self.removeAllListeners(handlers[i]);
311
+ }
312
+
313
+ // Add stable state handlers
314
+ _self.on('error', handleEvent(self, 'error'));
315
+ _self.on('close', handleEvent(self, 'close'));
316
+ _self.on('timeout', handleEvent(self, 'timeout'));
317
+ _self.on('parseError', handleEvent(self, 'parseError'));
318
+
319
+ // Enalbe the monitoring of the new server
320
+ monitorServer(_self.lastIsMaster().me, self, {});
321
+
322
+ // Rexecute any stalled operation
323
+ rexecuteOperations(self);
324
+ } else {
325
+ _self.destroy({ force: true });
326
+ }
327
+ } else if (event === 'error') {
328
+ error = err;
329
+ }
330
+
331
+ // Rexecute any stalled operation
332
+ rexecuteOperations(self);
333
+
334
+ // Are we done finish up callback
335
+ if (count === 0) {
336
+ callback(error);
337
+ }
338
+ };
339
+ };
340
+
341
+ // No new servers
342
+ if (count === 0) return callback();
343
+
344
+ // Execute method
345
+ function execute(_server, i) {
346
+ setTimeout(function() {
347
+ // Destroyed
348
+ if (self.state === DESTROYED || self.state === UNREFERENCED) {
349
+ return;
350
+ }
351
+
352
+ // Create a new server instance
353
+ var server = new Server(
354
+ Object.assign({}, self.s.options, {
355
+ host: _server.split(':')[0],
356
+ port: parseInt(_server.split(':')[1], 10),
357
+ reconnect: false,
358
+ monitoring: false,
359
+ parent: self,
360
+ clientInfo: clone(self.s.clientInfo)
361
+ })
362
+ );
363
+
364
+ // Add temp handlers
365
+ server.once('connect', _handleEvent(self, 'connect'));
366
+ server.once('close', _handleEvent(self, 'close'));
367
+ server.once('timeout', _handleEvent(self, 'timeout'));
368
+ server.once('error', _handleEvent(self, 'error'));
369
+ server.once('parseError', _handleEvent(self, 'parseError'));
370
+
371
+ // SDAM Monitoring events
372
+ server.on('serverOpening', e => self.emit('serverOpening', e));
373
+ server.on('serverDescriptionChanged', e => self.emit('serverDescriptionChanged', e));
374
+ server.on('serverClosed', e => self.emit('serverClosed', e));
375
+
376
+ // Command Monitoring events
377
+ relayEvents(server, self, ['commandStarted', 'commandSucceeded', 'commandFailed']);
378
+
379
+ server.connect(self.s.connectOptions);
380
+ }, i);
381
+ }
382
+
383
+ // Create new instances
384
+ for (var i = 0; i < servers.length; i++) {
385
+ execute(servers[i], i);
386
+ }
387
+ }
388
+
389
+ // Ping the server
390
+ var pingServer = function(self, server, cb) {
391
+ // Measure running time
392
+ var start = new Date().getTime();
393
+
394
+ // Emit the server heartbeat start
395
+ emitSDAMEvent(self, 'serverHeartbeatStarted', { connectionId: server.name });
396
+
397
+ // Execute ismaster
398
+ // Set the socketTimeout for a monitoring message to a low number
399
+ // Ensuring ismaster calls are timed out quickly
400
+ server.command(
401
+ 'admin.$cmd',
402
+ {
403
+ ismaster: true
404
+ },
405
+ {
406
+ monitoring: true,
407
+ socketTimeout: self.s.options.connectionTimeout || 2000
408
+ },
409
+ function(err, r) {
410
+ if (self.state === DESTROYED || self.state === UNREFERENCED) {
411
+ server.destroy({ force: true });
412
+ return cb(err, r);
413
+ }
414
+
415
+ // Calculate latency
416
+ var latencyMS = new Date().getTime() - start;
417
+ // Set the last updatedTime
418
+ var hrTime = process.hrtime();
419
+ // Calculate the last update time
420
+ server.lastUpdateTime = hrTime[0] * 1000 + Math.round(hrTime[1] / 1000);
421
+
422
+ // We had an error, remove it from the state
423
+ if (err) {
424
+ // Emit the server heartbeat failure
425
+ emitSDAMEvent(self, 'serverHeartbeatFailed', {
426
+ durationMS: latencyMS,
427
+ failure: err,
428
+ connectionId: server.name
429
+ });
430
+
431
+ // Remove server from the state
432
+ self.s.replicaSetState.remove(server);
433
+ } else {
434
+ // Update the server ismaster
435
+ server.ismaster = r.result;
436
+
437
+ // Check if we have a lastWriteDate convert it to MS
438
+ // and store on the server instance for later use
439
+ if (server.ismaster.lastWrite && server.ismaster.lastWrite.lastWriteDate) {
440
+ server.lastWriteDate = server.ismaster.lastWrite.lastWriteDate.getTime();
441
+ }
442
+
443
+ // Do we have a brand new server
444
+ if (server.lastIsMasterMS === -1) {
445
+ server.lastIsMasterMS = latencyMS;
446
+ } else if (server.lastIsMasterMS) {
447
+ // After the first measurement, average RTT MUST be computed using an
448
+ // exponentially-weighted moving average formula, with a weighting factor (alpha) of 0.2.
449
+ // If the prior average is denoted old_rtt, then the new average (new_rtt) is
450
+ // computed from a new RTT measurement (x) using the following formula:
451
+ // alpha = 0.2
452
+ // new_rtt = alpha * x + (1 - alpha) * old_rtt
453
+ server.lastIsMasterMS = 0.2 * latencyMS + (1 - 0.2) * server.lastIsMasterMS;
454
+ }
455
+
456
+ if (self.s.replicaSetState.update(server)) {
457
+ // Primary lastIsMaster store it
458
+ if (server.lastIsMaster() && server.lastIsMaster().ismaster) {
459
+ self.ismaster = server.lastIsMaster();
460
+ }
461
+ }
462
+
463
+ // Server heart beat event
464
+ emitSDAMEvent(self, 'serverHeartbeatSucceeded', {
465
+ durationMS: latencyMS,
466
+ reply: r.result,
467
+ connectionId: server.name
468
+ });
469
+ }
470
+
471
+ // Calculate the staleness for this server
472
+ self.s.replicaSetState.updateServerMaxStaleness(server, self.s.haInterval);
473
+
474
+ // Callback
475
+ cb(err, r);
476
+ }
477
+ );
478
+ };
479
+
480
+ // Each server is monitored in parallel in their own timeout loop
481
+ var monitorServer = function(host, self, options) {
482
+ // If this is not the initial scan
483
+ // Is this server already being monitoried, then skip monitoring
484
+ if (!options.haInterval) {
485
+ for (var i = 0; i < self.intervalIds.length; i++) {
486
+ if (self.intervalIds[i].__host === host) {
487
+ return;
488
+ }
489
+ }
490
+ }
491
+
492
+ // Get the haInterval
493
+ var _process = options.haInterval ? Timeout : Interval;
494
+ var _haInterval = options.haInterval ? options.haInterval : self.s.haInterval;
495
+
496
+ // Create the interval
497
+ var intervalId = new _process(function() {
498
+ if (self.state === DESTROYED || self.state === UNREFERENCED) {
499
+ // clearInterval(intervalId);
500
+ intervalId.stop();
501
+ return;
502
+ }
503
+
504
+ // Do we already have server connection available for this host
505
+ var _server = self.s.replicaSetState.get(host);
506
+
507
+ // Check if we have a known server connection and reuse
508
+ if (_server) {
509
+ // Ping the server
510
+ return pingServer(self, _server, function(err) {
511
+ if (err) {
512
+ // NOTE: should something happen here?
513
+ return;
514
+ }
515
+
516
+ if (self.state === DESTROYED || self.state === UNREFERENCED) {
517
+ intervalId.stop();
518
+ return;
519
+ }
520
+
521
+ // Filter out all called intervaliIds
522
+ self.intervalIds = self.intervalIds.filter(function(intervalId) {
523
+ return intervalId.isRunning();
524
+ });
525
+
526
+ // Initial sweep
527
+ if (_process === Timeout) {
528
+ if (
529
+ self.state === CONNECTING &&
530
+ ((self.s.replicaSetState.hasSecondary() &&
531
+ self.s.options.secondaryOnlyConnectionAllowed) ||
532
+ self.s.replicaSetState.hasPrimary())
533
+ ) {
534
+ self.state = CONNECTED;
535
+
536
+ // Emit connected sign
537
+ process.nextTick(function() {
538
+ self.emit('connect', self);
539
+ });
540
+
541
+ // Start topology interval check
542
+ topologyMonitor(self, {});
543
+ }
544
+ } else {
545
+ if (
546
+ self.state === DISCONNECTED &&
547
+ ((self.s.replicaSetState.hasSecondary() &&
548
+ self.s.options.secondaryOnlyConnectionAllowed) ||
549
+ self.s.replicaSetState.hasPrimary())
550
+ ) {
551
+ self.state = CONNECTED;
552
+
553
+ // Rexecute any stalled operation
554
+ rexecuteOperations(self);
555
+
556
+ // Emit connected sign
557
+ process.nextTick(function() {
558
+ self.emit('reconnect', self);
559
+ });
560
+ }
561
+ }
562
+
563
+ if (
564
+ self.initialConnectState.connect &&
565
+ !self.initialConnectState.fullsetup &&
566
+ self.s.replicaSetState.hasPrimaryAndSecondary()
567
+ ) {
568
+ // Set initial connect state
569
+ self.initialConnectState.fullsetup = true;
570
+ self.initialConnectState.all = true;
571
+
572
+ process.nextTick(function() {
573
+ self.emit('fullsetup', self);
574
+ self.emit('all', self);
575
+ });
576
+ }
577
+ });
578
+ }
579
+ }, _haInterval);
580
+
581
+ // Start the interval
582
+ intervalId.start();
583
+ // Add the intervalId host name
584
+ intervalId.__host = host;
585
+ // Add the intervalId to our list of intervalIds
586
+ self.intervalIds.push(intervalId);
587
+ };
588
+
589
+ function topologyMonitor(self, options) {
590
+ if (self.state === DESTROYED || self.state === UNREFERENCED) return;
591
+ options = options || {};
592
+
593
+ // Get the servers
594
+ var servers = Object.keys(self.s.replicaSetState.set);
595
+
596
+ // Get the haInterval
597
+ var _process = options.haInterval ? Timeout : Interval;
598
+ var _haInterval = options.haInterval ? options.haInterval : self.s.haInterval;
599
+
600
+ if (_process === Timeout) {
601
+ return connectNewServers(self, self.s.replicaSetState.unknownServers, function(err) {
602
+ // Don't emit errors if the connection was already
603
+ if (self.state === DESTROYED || self.state === UNREFERENCED) {
604
+ return;
605
+ }
606
+
607
+ if (!self.s.replicaSetState.hasPrimary() && !self.s.options.secondaryOnlyConnectionAllowed) {
608
+ if (err) {
609
+ return self.emit('error', err);
610
+ }
611
+
612
+ self.emit(
613
+ 'error',
614
+ new MongoError('no primary found in replicaset or invalid replica set name')
615
+ );
616
+ return self.destroy({ force: true });
617
+ } else if (
618
+ !self.s.replicaSetState.hasSecondary() &&
619
+ self.s.options.secondaryOnlyConnectionAllowed
620
+ ) {
621
+ if (err) {
622
+ return self.emit('error', err);
623
+ }
624
+
625
+ self.emit(
626
+ 'error',
627
+ new MongoError('no secondary found in replicaset or invalid replica set name')
628
+ );
629
+ return self.destroy({ force: true });
630
+ }
631
+
632
+ for (var i = 0; i < servers.length; i++) {
633
+ monitorServer(servers[i], self, options);
634
+ }
635
+ });
636
+ } else {
637
+ for (var i = 0; i < servers.length; i++) {
638
+ monitorServer(servers[i], self, options);
639
+ }
640
+ }
641
+
642
+ // Run the reconnect process
643
+ function executeReconnect(self) {
644
+ return function() {
645
+ if (self.state === DESTROYED || self.state === UNREFERENCED) {
646
+ return;
647
+ }
648
+
649
+ connectNewServers(self, self.s.replicaSetState.unknownServers, function() {
650
+ var monitoringFrequencey = self.s.replicaSetState.hasPrimary()
651
+ ? _haInterval
652
+ : self.s.minHeartbeatFrequencyMS;
653
+
654
+ // Create a timeout
655
+ self.intervalIds.push(new Timeout(executeReconnect(self), monitoringFrequencey).start());
656
+ });
657
+ };
658
+ }
659
+
660
+ // Decide what kind of interval to use
661
+ var intervalTime = !self.s.replicaSetState.hasPrimary()
662
+ ? self.s.minHeartbeatFrequencyMS
663
+ : _haInterval;
664
+
665
+ self.intervalIds.push(new Timeout(executeReconnect(self), intervalTime).start());
666
+ }
667
+
668
+ function addServerToList(list, server) {
669
+ for (var i = 0; i < list.length; i++) {
670
+ if (list[i].name.toLowerCase() === server.name.toLowerCase()) return true;
671
+ }
672
+
673
+ list.push(server);
674
+ }
675
+
676
+ function handleEvent(self, event) {
677
+ return function() {
678
+ if (self.state === DESTROYED || self.state === UNREFERENCED) return;
679
+ // Debug log
680
+ if (self.s.logger.isDebug()) {
681
+ self.s.logger.debug(
682
+ f('handleEvent %s from server %s in replset with id %s', event, this.name, self.id)
683
+ );
684
+ }
685
+
686
+ // Remove from the replicaset state
687
+ self.s.replicaSetState.remove(this);
688
+
689
+ // Are we in a destroyed state return
690
+ if (self.state === DESTROYED || self.state === UNREFERENCED) return;
691
+
692
+ // If no primary and secondary available
693
+ if (
694
+ !self.s.replicaSetState.hasPrimary() &&
695
+ !self.s.replicaSetState.hasSecondary() &&
696
+ self.s.options.secondaryOnlyConnectionAllowed
697
+ ) {
698
+ stateTransition(self, DISCONNECTED);
699
+ } else if (!self.s.replicaSetState.hasPrimary()) {
700
+ stateTransition(self, DISCONNECTED);
701
+ }
702
+
703
+ addServerToList(self.s.connectingServers, this);
704
+ };
705
+ }
706
+
707
+ function shouldTriggerConnect(self) {
708
+ const isConnecting = self.state === CONNECTING;
709
+ const hasPrimary = self.s.replicaSetState.hasPrimary();
710
+ const hasSecondary = self.s.replicaSetState.hasSecondary();
711
+ const secondaryOnlyConnectionAllowed = self.s.options.secondaryOnlyConnectionAllowed;
712
+ const readPreferenceSecondary =
713
+ self.s.connectOptions.readPreference &&
714
+ self.s.connectOptions.readPreference.equals(ReadPreference.secondary);
715
+
716
+ return (
717
+ (isConnecting &&
718
+ ((readPreferenceSecondary && hasSecondary) || (!readPreferenceSecondary && hasPrimary))) ||
719
+ (hasSecondary && secondaryOnlyConnectionAllowed)
720
+ );
721
+ }
722
+
723
+ function handleInitialConnectEvent(self, event) {
724
+ return function() {
725
+ var _this = this;
726
+ // Debug log
727
+ if (self.s.logger.isDebug()) {
728
+ self.s.logger.debug(
729
+ f(
730
+ 'handleInitialConnectEvent %s from server %s in replset with id %s',
731
+ event,
732
+ this.name,
733
+ self.id
734
+ )
735
+ );
736
+ }
737
+
738
+ // Destroy the instance
739
+ if (self.state === DESTROYED || self.state === UNREFERENCED) {
740
+ return this.destroy({ force: true });
741
+ }
742
+
743
+ // Check the type of server
744
+ if (event === 'connect') {
745
+ // Update the state
746
+ var result = self.s.replicaSetState.update(_this);
747
+ if (result === true) {
748
+ // Primary lastIsMaster store it
749
+ if (_this.lastIsMaster() && _this.lastIsMaster().ismaster) {
750
+ self.ismaster = _this.lastIsMaster();
751
+ }
752
+
753
+ // Debug log
754
+ if (self.s.logger.isDebug()) {
755
+ self.s.logger.debug(
756
+ f(
757
+ 'handleInitialConnectEvent %s from server %s in replset with id %s has state [%s]',
758
+ event,
759
+ _this.name,
760
+ self.id,
761
+ JSON.stringify(self.s.replicaSetState.set)
762
+ )
763
+ );
764
+ }
765
+
766
+ // Remove the handlers
767
+ for (let i = 0; i < handlers.length; i++) {
768
+ _this.removeAllListeners(handlers[i]);
769
+ }
770
+
771
+ // Add stable state handlers
772
+ _this.on('error', handleEvent(self, 'error'));
773
+ _this.on('close', handleEvent(self, 'close'));
774
+ _this.on('timeout', handleEvent(self, 'timeout'));
775
+ _this.on('parseError', handleEvent(self, 'parseError'));
776
+
777
+ // Do we have a primary or primaryAndSecondary
778
+ if (shouldTriggerConnect(self)) {
779
+ // We are connected
780
+ self.state = CONNECTED;
781
+
782
+ // Set initial connect state
783
+ self.initialConnectState.connect = true;
784
+ // Emit connect event
785
+ process.nextTick(function() {
786
+ self.emit('connect', self);
787
+ });
788
+
789
+ topologyMonitor(self, {});
790
+ }
791
+ } else if (result instanceof MongoError) {
792
+ _this.destroy({ force: true });
793
+ self.destroy({ force: true });
794
+ return self.emit('error', result);
795
+ } else {
796
+ _this.destroy({ force: true });
797
+ }
798
+ } else {
799
+ // Emit failure to connect
800
+ self.emit('failed', this);
801
+
802
+ addServerToList(self.s.connectingServers, this);
803
+ // Remove from the state
804
+ self.s.replicaSetState.remove(this);
805
+ }
806
+
807
+ if (
808
+ self.initialConnectState.connect &&
809
+ !self.initialConnectState.fullsetup &&
810
+ self.s.replicaSetState.hasPrimaryAndSecondary()
811
+ ) {
812
+ // Set initial connect state
813
+ self.initialConnectState.fullsetup = true;
814
+ self.initialConnectState.all = true;
815
+
816
+ process.nextTick(function() {
817
+ self.emit('fullsetup', self);
818
+ self.emit('all', self);
819
+ });
820
+ }
821
+
822
+ // Remove from the list from connectingServers
823
+ for (var i = 0; i < self.s.connectingServers.length; i++) {
824
+ if (self.s.connectingServers[i].equals(this)) {
825
+ self.s.connectingServers.splice(i, 1);
826
+ }
827
+ }
828
+
829
+ // Trigger topologyMonitor
830
+ if (self.s.connectingServers.length === 0 && self.state === CONNECTING) {
831
+ topologyMonitor(self, { haInterval: 1 });
832
+ }
833
+ };
834
+ }
835
+
836
+ function connectServers(self, servers) {
837
+ // Update connectingServers
838
+ self.s.connectingServers = self.s.connectingServers.concat(servers);
839
+
840
+ // Index used to interleaf the server connects, avoiding
841
+ // runtime issues on io constrained vm's
842
+ var timeoutInterval = 0;
843
+
844
+ function connect(server, timeoutInterval) {
845
+ setTimeout(function() {
846
+ // Add the server to the state
847
+ if (self.s.replicaSetState.update(server)) {
848
+ // Primary lastIsMaster store it
849
+ if (server.lastIsMaster() && server.lastIsMaster().ismaster) {
850
+ self.ismaster = server.lastIsMaster();
851
+ }
852
+ }
853
+
854
+ // Add event handlers
855
+ server.once('close', handleInitialConnectEvent(self, 'close'));
856
+ server.once('timeout', handleInitialConnectEvent(self, 'timeout'));
857
+ server.once('parseError', handleInitialConnectEvent(self, 'parseError'));
858
+ server.once('error', handleInitialConnectEvent(self, 'error'));
859
+ server.once('connect', handleInitialConnectEvent(self, 'connect'));
860
+
861
+ // SDAM Monitoring events
862
+ server.on('serverOpening', e => self.emit('serverOpening', e));
863
+ server.on('serverDescriptionChanged', e => self.emit('serverDescriptionChanged', e));
864
+ server.on('serverClosed', e => self.emit('serverClosed', e));
865
+
866
+ // Command Monitoring events
867
+ relayEvents(server, self, ['commandStarted', 'commandSucceeded', 'commandFailed']);
868
+
869
+ // Start connection
870
+ server.connect(self.s.connectOptions);
871
+ }, timeoutInterval);
872
+ }
873
+
874
+ // Start all the servers
875
+ while (servers.length > 0) {
876
+ connect(servers.shift(), timeoutInterval++);
877
+ }
878
+ }
879
+
880
+ /**
881
+ * Emit event if it exists
882
+ * @method
883
+ */
884
+ function emitSDAMEvent(self, event, description) {
885
+ if (self.listeners(event).length > 0) {
886
+ self.emit(event, description);
887
+ }
888
+ }
889
+
890
+ /**
891
+ * Initiate server connect
892
+ */
893
+ ReplSet.prototype.connect = function(options) {
894
+ var self = this;
895
+ // Add any connect level options to the internal state
896
+ this.s.connectOptions = options || {};
897
+
898
+ // Set connecting state
899
+ stateTransition(this, CONNECTING);
900
+
901
+ // Create server instances
902
+ var servers = this.s.seedlist.map(function(x) {
903
+ return new Server(
904
+ Object.assign({}, self.s.options, x, options, {
905
+ reconnect: false,
906
+ monitoring: false,
907
+ parent: self,
908
+ clientInfo: clone(self.s.clientInfo)
909
+ })
910
+ );
911
+ });
912
+
913
+ // Error out as high availbility interval must be < than socketTimeout
914
+ if (
915
+ this.s.options.socketTimeout > 0 &&
916
+ this.s.options.socketTimeout <= this.s.options.haInterval
917
+ ) {
918
+ return self.emit(
919
+ 'error',
920
+ new MongoError(
921
+ f(
922
+ 'haInterval [%s] MS must be set to less than socketTimeout [%s] MS',
923
+ this.s.options.haInterval,
924
+ this.s.options.socketTimeout
925
+ )
926
+ )
927
+ );
928
+ }
929
+
930
+ // Emit the topology opening event
931
+ emitSDAMEvent(this, 'topologyOpening', { topologyId: this.id });
932
+ // Start all server connections
933
+ connectServers(self, servers);
934
+ };
935
+
936
+ /**
937
+ * Authenticate the topology.
938
+ * @method
939
+ * @param {MongoCredentials} credentials The credentials for authentication we are using
940
+ * @param {authResultCallback} callback A callback function
941
+ */
942
+ ReplSet.prototype.auth = function(credentials, callback) {
943
+ if (typeof callback === 'function') callback(null, null);
944
+ };
945
+
946
+ /**
947
+ * Destroy the server connection
948
+ * @param {boolean} [options.force=false] Force destroy the pool
949
+ * @method
950
+ */
951
+ ReplSet.prototype.destroy = function(options, callback) {
952
+ options = options || {};
953
+
954
+ let destroyCount = this.s.connectingServers.length + 1; // +1 for the callback from `replicaSetState.destroy`
955
+ const serverDestroyed = () => {
956
+ destroyCount--;
957
+ if (destroyCount > 0) {
958
+ return;
959
+ }
960
+
961
+ // Emit toplogy closing event
962
+ emitSDAMEvent(this, 'topologyClosed', { topologyId: this.id });
963
+
964
+ // Transition state
965
+ stateTransition(this, DESTROYED);
966
+
967
+ if (typeof callback === 'function') {
968
+ callback(null, null);
969
+ }
970
+ };
971
+
972
+ // Clear out any monitoring process
973
+ if (this.haTimeoutId) clearTimeout(this.haTimeoutId);
974
+
975
+ // Clear out all monitoring
976
+ for (var i = 0; i < this.intervalIds.length; i++) {
977
+ this.intervalIds[i].stop();
978
+ }
979
+
980
+ // Reset list of intervalIds
981
+ this.intervalIds = [];
982
+
983
+ if (destroyCount === 0) {
984
+ serverDestroyed();
985
+ return;
986
+ }
987
+
988
+ // Destroy the replicaset
989
+ this.s.replicaSetState.destroy(options, serverDestroyed);
990
+
991
+ // Destroy all connecting servers
992
+ this.s.connectingServers.forEach(function(x) {
993
+ x.destroy(options, serverDestroyed);
994
+ });
995
+ };
996
+
997
+ /**
998
+ * Unref all connections belong to this server
999
+ * @method
1000
+ */
1001
+ ReplSet.prototype.unref = function() {
1002
+ // Transition state
1003
+ stateTransition(this, UNREFERENCED);
1004
+
1005
+ this.s.replicaSetState.allServers().forEach(function(x) {
1006
+ x.unref();
1007
+ });
1008
+
1009
+ clearTimeout(this.haTimeoutId);
1010
+ };
1011
+
1012
+ /**
1013
+ * Returns the last known ismaster document for this server
1014
+ * @method
1015
+ * @return {object}
1016
+ */
1017
+ ReplSet.prototype.lastIsMaster = function() {
1018
+ // If secondaryOnlyConnectionAllowed and no primary but secondary
1019
+ // return the secondaries ismaster result.
1020
+ if (
1021
+ this.s.options.secondaryOnlyConnectionAllowed &&
1022
+ !this.s.replicaSetState.hasPrimary() &&
1023
+ this.s.replicaSetState.hasSecondary()
1024
+ ) {
1025
+ return this.s.replicaSetState.secondaries[0].lastIsMaster();
1026
+ }
1027
+
1028
+ return this.s.replicaSetState.primary
1029
+ ? this.s.replicaSetState.primary.lastIsMaster()
1030
+ : this.ismaster;
1031
+ };
1032
+
1033
+ /**
1034
+ * All raw connections
1035
+ * @method
1036
+ * @return {Connection[]}
1037
+ */
1038
+ ReplSet.prototype.connections = function() {
1039
+ var servers = this.s.replicaSetState.allServers();
1040
+ var connections = [];
1041
+ for (var i = 0; i < servers.length; i++) {
1042
+ connections = connections.concat(servers[i].connections());
1043
+ }
1044
+
1045
+ return connections;
1046
+ };
1047
+
1048
+ /**
1049
+ * Figure out if the server is connected
1050
+ * @method
1051
+ * @param {ReadPreference} [options.readPreference] Specify read preference if command supports it
1052
+ * @return {boolean}
1053
+ */
1054
+ ReplSet.prototype.isConnected = function(options) {
1055
+ options = options || {};
1056
+
1057
+ // If we specified a read preference check if we are connected to something
1058
+ // than can satisfy this
1059
+ if (options.readPreference && options.readPreference.equals(ReadPreference.secondary)) {
1060
+ return this.s.replicaSetState.hasSecondary();
1061
+ }
1062
+
1063
+ if (options.readPreference && options.readPreference.equals(ReadPreference.primary)) {
1064
+ return this.s.replicaSetState.hasPrimary();
1065
+ }
1066
+
1067
+ if (options.readPreference && options.readPreference.equals(ReadPreference.primaryPreferred)) {
1068
+ return this.s.replicaSetState.hasSecondary() || this.s.replicaSetState.hasPrimary();
1069
+ }
1070
+
1071
+ if (options.readPreference && options.readPreference.equals(ReadPreference.secondaryPreferred)) {
1072
+ return this.s.replicaSetState.hasSecondary() || this.s.replicaSetState.hasPrimary();
1073
+ }
1074
+
1075
+ if (this.s.options.secondaryOnlyConnectionAllowed && this.s.replicaSetState.hasSecondary()) {
1076
+ return true;
1077
+ }
1078
+
1079
+ return this.s.replicaSetState.hasPrimary();
1080
+ };
1081
+
1082
+ /**
1083
+ * Figure out if the replicaset instance was destroyed by calling destroy
1084
+ * @method
1085
+ * @return {boolean}
1086
+ */
1087
+ ReplSet.prototype.isDestroyed = function() {
1088
+ return this.state === DESTROYED;
1089
+ };
1090
+
1091
+ /**
1092
+ * Selects a server
1093
+ *
1094
+ * @method
1095
+ * @param {function} selector Unused
1096
+ * @param {ReadPreference} [options.readPreference] Specify read preference if command supports it
1097
+ * @param {ClientSession} [options.session] Unused
1098
+ * @param {function} callback
1099
+ */
1100
+ ReplSet.prototype.selectServer = function(selector, options, callback) {
1101
+ if (typeof selector === 'function' && typeof callback === 'undefined')
1102
+ (callback = selector), (selector = undefined), (options = {});
1103
+ if (typeof options === 'function')
1104
+ (callback = options), (options = selector), (selector = undefined);
1105
+ options = options || {};
1106
+
1107
+ const server = this.s.replicaSetState.pickServer(options.readPreference);
1108
+ if (this.s.debug) this.emit('pickedServer', options.readPreference, server);
1109
+ callback(null, server);
1110
+ };
1111
+
1112
+ /**
1113
+ * Get all connected servers
1114
+ * @method
1115
+ * @return {Server[]}
1116
+ */
1117
+ ReplSet.prototype.getServers = function() {
1118
+ return this.s.replicaSetState.allServers();
1119
+ };
1120
+
1121
+ //
1122
+ // Execute write operation
1123
+ function executeWriteOperation(args, options, callback) {
1124
+ if (typeof options === 'function') (callback = options), (options = {});
1125
+ options = options || {};
1126
+
1127
+ // TODO: once we drop Node 4, use destructuring either here or in arguments.
1128
+ const self = args.self;
1129
+ const op = args.op;
1130
+ const ns = args.ns;
1131
+ const ops = args.ops;
1132
+
1133
+ if (self.state === DESTROYED) {
1134
+ return callback(new MongoError(f('topology was destroyed')));
1135
+ }
1136
+
1137
+ const willRetryWrite =
1138
+ !args.retrying &&
1139
+ !!options.retryWrites &&
1140
+ options.session &&
1141
+ isRetryableWritesSupported(self) &&
1142
+ !options.session.inTransaction();
1143
+
1144
+ if (!self.s.replicaSetState.hasPrimary()) {
1145
+ if (self.s.disconnectHandler) {
1146
+ // Not connected but we have a disconnecthandler
1147
+ return self.s.disconnectHandler.add(op, ns, ops, options, callback);
1148
+ } else if (!willRetryWrite) {
1149
+ // No server returned we had an error
1150
+ return callback(new MongoError('no primary server found'));
1151
+ }
1152
+ }
1153
+
1154
+ const handler = (err, result) => {
1155
+ if (!err) return callback(null, result);
1156
+ if (!isRetryableError(err)) {
1157
+ return callback(err);
1158
+ }
1159
+
1160
+ if (willRetryWrite) {
1161
+ const newArgs = Object.assign({}, args, { retrying: true });
1162
+ return executeWriteOperation(newArgs, options, callback);
1163
+ }
1164
+
1165
+ // Per SDAM, remove primary from replicaset
1166
+ if (self.s.replicaSetState.primary) {
1167
+ self.s.replicaSetState.remove(self.s.replicaSetState.primary, { force: true });
1168
+ }
1169
+
1170
+ return callback(err);
1171
+ };
1172
+
1173
+ if (callback.operationId) {
1174
+ handler.operationId = callback.operationId;
1175
+ }
1176
+
1177
+ // increment and assign txnNumber
1178
+ if (willRetryWrite) {
1179
+ options.session.incrementTransactionNumber();
1180
+ options.willRetryWrite = willRetryWrite;
1181
+ }
1182
+
1183
+ self.s.replicaSetState.primary[op](ns, ops, options, handler);
1184
+ }
1185
+
1186
+ /**
1187
+ * Insert one or more documents
1188
+ * @method
1189
+ * @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
1190
+ * @param {array} ops An array of documents to insert
1191
+ * @param {boolean} [options.ordered=true] Execute in order or out of order
1192
+ * @param {object} [options.writeConcern={}] Write concern for the operation
1193
+ * @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized.
1194
+ * @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
1195
+ * @param {ClientSession} [options.session=null] Session to use for the operation
1196
+ * @param {boolean} [options.retryWrites] Enable retryable writes for this operation
1197
+ * @param {opResultCallback} callback A callback function
1198
+ */
1199
+ ReplSet.prototype.insert = function(ns, ops, options, callback) {
1200
+ // Execute write operation
1201
+ executeWriteOperation({ self: this, op: 'insert', ns, ops }, options, callback);
1202
+ };
1203
+
1204
+ /**
1205
+ * Perform one or more update operations
1206
+ * @method
1207
+ * @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
1208
+ * @param {array} ops An array of updates
1209
+ * @param {boolean} [options.ordered=true] Execute in order or out of order
1210
+ * @param {object} [options.writeConcern={}] Write concern for the operation
1211
+ * @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized.
1212
+ * @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
1213
+ * @param {ClientSession} [options.session=null] Session to use for the operation
1214
+ * @param {boolean} [options.retryWrites] Enable retryable writes for this operation
1215
+ * @param {opResultCallback} callback A callback function
1216
+ */
1217
+ ReplSet.prototype.update = function(ns, ops, options, callback) {
1218
+ // Execute write operation
1219
+ executeWriteOperation({ self: this, op: 'update', ns, ops }, options, callback);
1220
+ };
1221
+
1222
+ /**
1223
+ * Perform one or more remove operations
1224
+ * @method
1225
+ * @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
1226
+ * @param {array} ops An array of removes
1227
+ * @param {boolean} [options.ordered=true] Execute in order or out of order
1228
+ * @param {object} [options.writeConcern={}] Write concern for the operation
1229
+ * @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized.
1230
+ * @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
1231
+ * @param {ClientSession} [options.session=null] Session to use for the operation
1232
+ * @param {boolean} [options.retryWrites] Enable retryable writes for this operation
1233
+ * @param {opResultCallback} callback A callback function
1234
+ */
1235
+ ReplSet.prototype.remove = function(ns, ops, options, callback) {
1236
+ // Execute write operation
1237
+ executeWriteOperation({ self: this, op: 'remove', ns, ops }, options, callback);
1238
+ };
1239
+
1240
+ const RETRYABLE_WRITE_OPERATIONS = ['findAndModify', 'insert', 'update', 'delete'];
1241
+
1242
+ function isWriteCommand(command) {
1243
+ return RETRYABLE_WRITE_OPERATIONS.some(op => command[op]);
1244
+ }
1245
+
1246
+ /**
1247
+ * Execute a command
1248
+ * @method
1249
+ * @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
1250
+ * @param {object} cmd The command hash
1251
+ * @param {ReadPreference} [options.readPreference] Specify read preference if command supports it
1252
+ * @param {Connection} [options.connection] Specify connection object to execute command against
1253
+ * @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized.
1254
+ * @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
1255
+ * @param {ClientSession} [options.session=null] Session to use for the operation
1256
+ * @param {opResultCallback} callback A callback function
1257
+ */
1258
+ ReplSet.prototype.command = function(ns, cmd, options, callback) {
1259
+ if (typeof options === 'function') {
1260
+ (callback = options), (options = {}), (options = options || {});
1261
+ }
1262
+
1263
+ if (this.state === DESTROYED) return callback(new MongoError(f('topology was destroyed')));
1264
+ var self = this;
1265
+
1266
+ // Establish readPreference
1267
+ var readPreference = options.readPreference ? options.readPreference : ReadPreference.primary;
1268
+
1269
+ // If the readPreference is primary and we have no primary, store it
1270
+ if (
1271
+ readPreference.preference === 'primary' &&
1272
+ !this.s.replicaSetState.hasPrimary() &&
1273
+ this.s.disconnectHandler != null
1274
+ ) {
1275
+ return this.s.disconnectHandler.add('command', ns, cmd, options, callback);
1276
+ } else if (
1277
+ readPreference.preference === 'secondary' &&
1278
+ !this.s.replicaSetState.hasSecondary() &&
1279
+ this.s.disconnectHandler != null
1280
+ ) {
1281
+ return this.s.disconnectHandler.add('command', ns, cmd, options, callback);
1282
+ } else if (
1283
+ readPreference.preference !== 'primary' &&
1284
+ !this.s.replicaSetState.hasSecondary() &&
1285
+ !this.s.replicaSetState.hasPrimary() &&
1286
+ this.s.disconnectHandler != null
1287
+ ) {
1288
+ return this.s.disconnectHandler.add('command', ns, cmd, options, callback);
1289
+ }
1290
+
1291
+ // Pick a server
1292
+ var server = this.s.replicaSetState.pickServer(readPreference);
1293
+ // We received an error, return it
1294
+ if (!(server instanceof Server)) return callback(server);
1295
+ // Emit debug event
1296
+ if (self.s.debug) self.emit('pickedServer', ReadPreference.primary, server);
1297
+
1298
+ // No server returned we had an error
1299
+ if (server == null) {
1300
+ return callback(
1301
+ new MongoError(
1302
+ f('no server found that matches the provided readPreference %s', readPreference)
1303
+ )
1304
+ );
1305
+ }
1306
+
1307
+ const willRetryWrite =
1308
+ !options.retrying &&
1309
+ !!options.retryWrites &&
1310
+ options.session &&
1311
+ isRetryableWritesSupported(self) &&
1312
+ !options.session.inTransaction() &&
1313
+ isWriteCommand(cmd);
1314
+
1315
+ const cb = (err, result) => {
1316
+ if (!err) return callback(null, result);
1317
+ if (!isRetryableError(err)) {
1318
+ return callback(err);
1319
+ }
1320
+
1321
+ if (willRetryWrite) {
1322
+ const newOptions = Object.assign({}, options, { retrying: true });
1323
+ return this.command(ns, cmd, newOptions, callback);
1324
+ }
1325
+
1326
+ // Per SDAM, remove primary from replicaset
1327
+ if (this.s.replicaSetState.primary) {
1328
+ this.s.replicaSetState.remove(this.s.replicaSetState.primary, { force: true });
1329
+ }
1330
+
1331
+ return callback(err);
1332
+ };
1333
+
1334
+ // increment and assign txnNumber
1335
+ if (willRetryWrite) {
1336
+ options.session.incrementTransactionNumber();
1337
+ options.willRetryWrite = willRetryWrite;
1338
+ }
1339
+
1340
+ // Execute the command
1341
+ server.command(ns, cmd, options, cb);
1342
+ };
1343
+
1344
+ /**
1345
+ * Get a new cursor
1346
+ * @method
1347
+ * @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
1348
+ * @param {object|Long} cmd Can be either a command returning a cursor or a cursorId
1349
+ * @param {object} [options] Options for the cursor
1350
+ * @param {object} [options.batchSize=0] Batchsize for the operation
1351
+ * @param {array} [options.documents=[]] Initial documents list for cursor
1352
+ * @param {ReadPreference} [options.readPreference] Specify read preference if command supports it
1353
+ * @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized.
1354
+ * @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
1355
+ * @param {ClientSession} [options.session=null] Session to use for the operation
1356
+ * @param {object} [options.topology] The internal topology of the created cursor
1357
+ * @returns {Cursor}
1358
+ */
1359
+ ReplSet.prototype.cursor = function(ns, cmd, options) {
1360
+ options = options || {};
1361
+ const topology = options.topology || this;
1362
+
1363
+ // Set up final cursor type
1364
+ var FinalCursor = options.cursorFactory || this.s.Cursor;
1365
+
1366
+ // Return the cursor
1367
+ return new FinalCursor(topology, ns, cmd, options);
1368
+ };
1369
+
1370
+ /**
1371
+ * A replset connect event, used to verify that the connection is up and running
1372
+ *
1373
+ * @event ReplSet#connect
1374
+ * @type {ReplSet}
1375
+ */
1376
+
1377
+ /**
1378
+ * A replset reconnect event, used to verify that the topology reconnected
1379
+ *
1380
+ * @event ReplSet#reconnect
1381
+ * @type {ReplSet}
1382
+ */
1383
+
1384
+ /**
1385
+ * A replset fullsetup event, used to signal that all topology members have been contacted.
1386
+ *
1387
+ * @event ReplSet#fullsetup
1388
+ * @type {ReplSet}
1389
+ */
1390
+
1391
+ /**
1392
+ * A replset all event, used to signal that all topology members have been contacted.
1393
+ *
1394
+ * @event ReplSet#all
1395
+ * @type {ReplSet}
1396
+ */
1397
+
1398
+ /**
1399
+ * A replset failed event, used to signal that initial replset connection failed.
1400
+ *
1401
+ * @event ReplSet#failed
1402
+ * @type {ReplSet}
1403
+ */
1404
+
1405
+ /**
1406
+ * A server member left the replicaset
1407
+ *
1408
+ * @event ReplSet#left
1409
+ * @type {function}
1410
+ * @param {string} type The type of member that left (primary|secondary|arbiter)
1411
+ * @param {Server} server The server object that left
1412
+ */
1413
+
1414
+ /**
1415
+ * A server member joined the replicaset
1416
+ *
1417
+ * @event ReplSet#joined
1418
+ * @type {function}
1419
+ * @param {string} type The type of member that joined (primary|secondary|arbiter)
1420
+ * @param {Server} server The server object that joined
1421
+ */
1422
+
1423
+ /**
1424
+ * A server opening SDAM monitoring event
1425
+ *
1426
+ * @event ReplSet#serverOpening
1427
+ * @type {object}
1428
+ */
1429
+
1430
+ /**
1431
+ * A server closed SDAM monitoring event
1432
+ *
1433
+ * @event ReplSet#serverClosed
1434
+ * @type {object}
1435
+ */
1436
+
1437
+ /**
1438
+ * A server description SDAM change monitoring event
1439
+ *
1440
+ * @event ReplSet#serverDescriptionChanged
1441
+ * @type {object}
1442
+ */
1443
+
1444
+ /**
1445
+ * A topology open SDAM event
1446
+ *
1447
+ * @event ReplSet#topologyOpening
1448
+ * @type {object}
1449
+ */
1450
+
1451
+ /**
1452
+ * A topology closed SDAM event
1453
+ *
1454
+ * @event ReplSet#topologyClosed
1455
+ * @type {object}
1456
+ */
1457
+
1458
+ /**
1459
+ * A topology structure SDAM change event
1460
+ *
1461
+ * @event ReplSet#topologyDescriptionChanged
1462
+ * @type {object}
1463
+ */
1464
+
1465
+ /**
1466
+ * A topology serverHeartbeatStarted SDAM event
1467
+ *
1468
+ * @event ReplSet#serverHeartbeatStarted
1469
+ * @type {object}
1470
+ */
1471
+
1472
+ /**
1473
+ * A topology serverHeartbeatFailed SDAM event
1474
+ *
1475
+ * @event ReplSet#serverHeartbeatFailed
1476
+ * @type {object}
1477
+ */
1478
+
1479
+ /**
1480
+ * A topology serverHeartbeatSucceeded SDAM change event
1481
+ *
1482
+ * @event ReplSet#serverHeartbeatSucceeded
1483
+ * @type {object}
1484
+ */
1485
+
1486
+ /**
1487
+ * An event emitted indicating a command was started, if command monitoring is enabled
1488
+ *
1489
+ * @event ReplSet#commandStarted
1490
+ * @type {object}
1491
+ */
1492
+
1493
+ /**
1494
+ * An event emitted indicating a command succeeded, if command monitoring is enabled
1495
+ *
1496
+ * @event ReplSet#commandSucceeded
1497
+ * @type {object}
1498
+ */
1499
+
1500
+ /**
1501
+ * An event emitted indicating a command failed, if command monitoring is enabled
1502
+ *
1503
+ * @event ReplSet#commandFailed
1504
+ * @type {object}
1505
+ */
1506
+
1507
+ module.exports = ReplSet;