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,1121 @@
1
+ 'use strict';
2
+
3
+ var inherits = require('util').inherits,
4
+ f = require('util').format,
5
+ diff = require('./shared').diff,
6
+ EventEmitter = require('events').EventEmitter,
7
+ Logger = require('../connection/logger'),
8
+ ReadPreference = require('./read_preference'),
9
+ MongoError = require('../error').MongoError,
10
+ Buffer = require('safe-buffer').Buffer;
11
+
12
+ var TopologyType = {
13
+ Single: 'Single',
14
+ ReplicaSetNoPrimary: 'ReplicaSetNoPrimary',
15
+ ReplicaSetWithPrimary: 'ReplicaSetWithPrimary',
16
+ Sharded: 'Sharded',
17
+ Unknown: 'Unknown'
18
+ };
19
+
20
+ var ServerType = {
21
+ Standalone: 'Standalone',
22
+ Mongos: 'Mongos',
23
+ PossiblePrimary: 'PossiblePrimary',
24
+ RSPrimary: 'RSPrimary',
25
+ RSSecondary: 'RSSecondary',
26
+ RSArbiter: 'RSArbiter',
27
+ RSOther: 'RSOther',
28
+ RSGhost: 'RSGhost',
29
+ Unknown: 'Unknown'
30
+ };
31
+
32
+ var ReplSetState = function(options) {
33
+ options = options || {};
34
+ // Add event listener
35
+ EventEmitter.call(this);
36
+ // Topology state
37
+ this.topologyType = TopologyType.ReplicaSetNoPrimary;
38
+ this.setName = options.setName;
39
+
40
+ // Server set
41
+ this.set = {};
42
+
43
+ // Unpacked options
44
+ this.id = options.id;
45
+ this.setName = options.setName;
46
+
47
+ // Replicaset logger
48
+ this.logger = options.logger || Logger('ReplSet', options);
49
+
50
+ // Server selection index
51
+ this.index = 0;
52
+ // Acceptable latency
53
+ this.acceptableLatency = options.acceptableLatency || 15;
54
+
55
+ // heartbeatFrequencyMS
56
+ this.heartbeatFrequencyMS = options.heartbeatFrequencyMS || 10000;
57
+
58
+ // Server side
59
+ this.primary = null;
60
+ this.secondaries = [];
61
+ this.arbiters = [];
62
+ this.passives = [];
63
+ this.ghosts = [];
64
+ // Current unknown hosts
65
+ this.unknownServers = [];
66
+ // In set status
67
+ this.set = {};
68
+ // Status
69
+ this.maxElectionId = null;
70
+ this.maxSetVersion = 0;
71
+ // Description of the Replicaset
72
+ this.replicasetDescription = {
73
+ topologyType: 'Unknown',
74
+ servers: []
75
+ };
76
+
77
+ this.logicalSessionTimeoutMinutes = undefined;
78
+ };
79
+
80
+ inherits(ReplSetState, EventEmitter);
81
+
82
+ ReplSetState.prototype.hasPrimaryAndSecondary = function() {
83
+ return this.primary != null && this.secondaries.length > 0;
84
+ };
85
+
86
+ ReplSetState.prototype.hasPrimaryOrSecondary = function() {
87
+ return this.hasPrimary() || this.hasSecondary();
88
+ };
89
+
90
+ ReplSetState.prototype.hasPrimary = function() {
91
+ return this.primary != null;
92
+ };
93
+
94
+ ReplSetState.prototype.hasSecondary = function() {
95
+ return this.secondaries.length > 0;
96
+ };
97
+
98
+ ReplSetState.prototype.get = function(host) {
99
+ var servers = this.allServers();
100
+
101
+ for (var i = 0; i < servers.length; i++) {
102
+ if (servers[i].name.toLowerCase() === host.toLowerCase()) {
103
+ return servers[i];
104
+ }
105
+ }
106
+
107
+ return null;
108
+ };
109
+
110
+ ReplSetState.prototype.allServers = function(options) {
111
+ options = options || {};
112
+ var servers = this.primary ? [this.primary] : [];
113
+ servers = servers.concat(this.secondaries);
114
+ if (!options.ignoreArbiters) servers = servers.concat(this.arbiters);
115
+ servers = servers.concat(this.passives);
116
+ return servers;
117
+ };
118
+
119
+ ReplSetState.prototype.destroy = function(options, callback) {
120
+ const serversToDestroy = this.secondaries
121
+ .concat(this.arbiters)
122
+ .concat(this.passives)
123
+ .concat(this.ghosts);
124
+ if (this.primary) serversToDestroy.push(this.primary);
125
+
126
+ let serverCount = serversToDestroy.length;
127
+ const serverDestroyed = () => {
128
+ serverCount--;
129
+ if (serverCount > 0) {
130
+ return;
131
+ }
132
+
133
+ // Clear out the complete state
134
+ this.secondaries = [];
135
+ this.arbiters = [];
136
+ this.passives = [];
137
+ this.ghosts = [];
138
+ this.unknownServers = [];
139
+ this.set = {};
140
+ this.primary = null;
141
+
142
+ // Emit the topology changed
143
+ emitTopologyDescriptionChanged(this);
144
+
145
+ if (typeof callback === 'function') {
146
+ callback(null, null);
147
+ }
148
+ };
149
+
150
+ if (serverCount === 0) {
151
+ serverDestroyed();
152
+ return;
153
+ }
154
+
155
+ serversToDestroy.forEach(server => server.destroy(options, serverDestroyed));
156
+ };
157
+
158
+ ReplSetState.prototype.remove = function(server, options) {
159
+ options = options || {};
160
+
161
+ // Get the server name and lowerCase it
162
+ var serverName = server.name.toLowerCase();
163
+
164
+ // Only remove if the current server is not connected
165
+ var servers = this.primary ? [this.primary] : [];
166
+ servers = servers.concat(this.secondaries);
167
+ servers = servers.concat(this.arbiters);
168
+ servers = servers.concat(this.passives);
169
+
170
+ // Check if it's active and this is just a failed connection attempt
171
+ for (var i = 0; i < servers.length; i++) {
172
+ if (
173
+ !options.force &&
174
+ servers[i].equals(server) &&
175
+ servers[i].isConnected &&
176
+ servers[i].isConnected()
177
+ ) {
178
+ return;
179
+ }
180
+ }
181
+
182
+ // If we have it in the set remove it
183
+ if (this.set[serverName]) {
184
+ this.set[serverName].type = ServerType.Unknown;
185
+ this.set[serverName].electionId = null;
186
+ this.set[serverName].setName = null;
187
+ this.set[serverName].setVersion = null;
188
+ }
189
+
190
+ // Remove type
191
+ var removeType = null;
192
+
193
+ // Remove from any lists
194
+ if (this.primary && this.primary.equals(server)) {
195
+ this.primary = null;
196
+ this.topologyType = TopologyType.ReplicaSetNoPrimary;
197
+ removeType = 'primary';
198
+ }
199
+
200
+ // Remove from any other server lists
201
+ removeType = removeFrom(server, this.secondaries) ? 'secondary' : removeType;
202
+ removeType = removeFrom(server, this.arbiters) ? 'arbiter' : removeType;
203
+ removeType = removeFrom(server, this.passives) ? 'secondary' : removeType;
204
+ removeFrom(server, this.ghosts);
205
+ removeFrom(server, this.unknownServers);
206
+
207
+ // Push to unknownServers
208
+ this.unknownServers.push(serverName);
209
+
210
+ // Do we have a removeType
211
+ if (removeType) {
212
+ this.emit('left', removeType, server);
213
+ }
214
+ };
215
+
216
+ const isArbiter = ismaster => ismaster.arbiterOnly && ismaster.setName;
217
+
218
+ ReplSetState.prototype.update = function(server) {
219
+ var self = this;
220
+ // Get the current ismaster
221
+ var ismaster = server.lastIsMaster();
222
+
223
+ // Get the server name and lowerCase it
224
+ var serverName = server.name.toLowerCase();
225
+
226
+ //
227
+ // Add any hosts
228
+ //
229
+ if (ismaster) {
230
+ // Join all the possible new hosts
231
+ var hosts = Array.isArray(ismaster.hosts) ? ismaster.hosts : [];
232
+ hosts = hosts.concat(Array.isArray(ismaster.arbiters) ? ismaster.arbiters : []);
233
+ hosts = hosts.concat(Array.isArray(ismaster.passives) ? ismaster.passives : []);
234
+ hosts = hosts.map(function(s) {
235
+ return s.toLowerCase();
236
+ });
237
+
238
+ // Add all hosts as unknownServers
239
+ for (var i = 0; i < hosts.length; i++) {
240
+ // Add to the list of unknown server
241
+ if (
242
+ this.unknownServers.indexOf(hosts[i]) === -1 &&
243
+ (!this.set[hosts[i]] || this.set[hosts[i]].type === ServerType.Unknown)
244
+ ) {
245
+ this.unknownServers.push(hosts[i].toLowerCase());
246
+ }
247
+
248
+ if (!this.set[hosts[i]]) {
249
+ this.set[hosts[i]] = {
250
+ type: ServerType.Unknown,
251
+ electionId: null,
252
+ setName: null,
253
+ setVersion: null
254
+ };
255
+ }
256
+ }
257
+ }
258
+
259
+ //
260
+ // Unknown server
261
+ //
262
+ if (!ismaster && !inList(ismaster, server, this.unknownServers)) {
263
+ self.set[serverName] = {
264
+ type: ServerType.Unknown,
265
+ setVersion: null,
266
+ electionId: null,
267
+ setName: null
268
+ };
269
+ // Update set information about the server instance
270
+ self.set[serverName].type = ServerType.Unknown;
271
+ self.set[serverName].electionId = ismaster ? ismaster.electionId : ismaster;
272
+ self.set[serverName].setName = ismaster ? ismaster.setName : ismaster;
273
+ self.set[serverName].setVersion = ismaster ? ismaster.setVersion : ismaster;
274
+
275
+ if (self.unknownServers.indexOf(server.name) === -1) {
276
+ self.unknownServers.push(serverName);
277
+ }
278
+
279
+ // Set the topology
280
+ return false;
281
+ }
282
+
283
+ // Update logicalSessionTimeoutMinutes
284
+ if (ismaster.logicalSessionTimeoutMinutes !== undefined && !isArbiter(ismaster)) {
285
+ if (
286
+ self.logicalSessionTimeoutMinutes === undefined ||
287
+ ismaster.logicalSessionTimeoutMinutes === null
288
+ ) {
289
+ self.logicalSessionTimeoutMinutes = ismaster.logicalSessionTimeoutMinutes;
290
+ } else {
291
+ self.logicalSessionTimeoutMinutes = Math.min(
292
+ self.logicalSessionTimeoutMinutes,
293
+ ismaster.logicalSessionTimeoutMinutes
294
+ );
295
+ }
296
+ }
297
+
298
+ //
299
+ // Is this a mongos
300
+ //
301
+ if (ismaster && ismaster.msg === 'isdbgrid') {
302
+ if (this.primary && this.primary.name === serverName) {
303
+ this.primary = null;
304
+ this.topologyType = TopologyType.ReplicaSetNoPrimary;
305
+ }
306
+
307
+ return false;
308
+ }
309
+
310
+ // A RSGhost instance
311
+ if (ismaster.isreplicaset) {
312
+ self.set[serverName] = {
313
+ type: ServerType.RSGhost,
314
+ setVersion: null,
315
+ electionId: null,
316
+ setName: ismaster.setName
317
+ };
318
+
319
+ if (this.primary && this.primary.name === serverName) {
320
+ this.primary = null;
321
+ }
322
+
323
+ // Set the topology
324
+ this.topologyType = this.primary
325
+ ? TopologyType.ReplicaSetWithPrimary
326
+ : TopologyType.ReplicaSetNoPrimary;
327
+ if (ismaster.setName) this.setName = ismaster.setName;
328
+
329
+ // Set the topology
330
+ return false;
331
+ }
332
+
333
+ // A RSOther instance
334
+ if (
335
+ (ismaster.setName && ismaster.hidden) ||
336
+ (ismaster.setName &&
337
+ !ismaster.ismaster &&
338
+ !ismaster.secondary &&
339
+ !ismaster.arbiterOnly &&
340
+ !ismaster.passive)
341
+ ) {
342
+ self.set[serverName] = {
343
+ type: ServerType.RSOther,
344
+ setVersion: null,
345
+ electionId: null,
346
+ setName: ismaster.setName
347
+ };
348
+
349
+ // Set the topology
350
+ this.topologyType = this.primary
351
+ ? TopologyType.ReplicaSetWithPrimary
352
+ : TopologyType.ReplicaSetNoPrimary;
353
+ if (ismaster.setName) this.setName = ismaster.setName;
354
+ return false;
355
+ }
356
+
357
+ //
358
+ // Standalone server, destroy and return
359
+ //
360
+ if (ismaster && ismaster.ismaster && !ismaster.setName) {
361
+ this.topologyType = this.primary ? TopologyType.ReplicaSetWithPrimary : TopologyType.Unknown;
362
+ this.remove(server, { force: true });
363
+ return false;
364
+ }
365
+
366
+ //
367
+ // Server in maintanance mode
368
+ //
369
+ if (ismaster && !ismaster.ismaster && !ismaster.secondary && !ismaster.arbiterOnly) {
370
+ this.remove(server, { force: true });
371
+ return false;
372
+ }
373
+
374
+ //
375
+ // If the .me field does not match the passed in server
376
+ //
377
+ if (ismaster.me && ismaster.me.toLowerCase() !== serverName) {
378
+ if (this.logger.isWarn()) {
379
+ this.logger.warn(
380
+ f(
381
+ 'the seedlist server was removed due to its address %s not matching its ismaster.me address %s',
382
+ server.name,
383
+ ismaster.me
384
+ )
385
+ );
386
+ }
387
+
388
+ // Delete from the set
389
+ delete this.set[serverName];
390
+ // Delete unknown servers
391
+ removeFrom(server, self.unknownServers);
392
+
393
+ // Destroy the instance
394
+ server.destroy();
395
+
396
+ // Set the type of topology we have
397
+ if (this.primary && !this.primary.equals(server)) {
398
+ this.topologyType = TopologyType.ReplicaSetWithPrimary;
399
+ } else {
400
+ this.topologyType = TopologyType.ReplicaSetNoPrimary;
401
+ }
402
+
403
+ //
404
+ // We have a potential primary
405
+ //
406
+ if (!this.primary && ismaster.primary) {
407
+ this.set[ismaster.primary.toLowerCase()] = {
408
+ type: ServerType.PossiblePrimary,
409
+ setName: null,
410
+ electionId: null,
411
+ setVersion: null
412
+ };
413
+ }
414
+
415
+ return false;
416
+ }
417
+
418
+ //
419
+ // Primary handling
420
+ //
421
+ if (!this.primary && ismaster.ismaster && ismaster.setName) {
422
+ var ismasterElectionId = server.lastIsMaster().electionId;
423
+ if (this.setName && this.setName !== ismaster.setName) {
424
+ this.topologyType = TopologyType.ReplicaSetNoPrimary;
425
+ return new MongoError(
426
+ f(
427
+ 'setName from ismaster does not match provided connection setName [%s] != [%s]',
428
+ ismaster.setName,
429
+ this.setName
430
+ )
431
+ );
432
+ }
433
+
434
+ if (!this.maxElectionId && ismasterElectionId) {
435
+ this.maxElectionId = ismasterElectionId;
436
+ } else if (this.maxElectionId && ismasterElectionId) {
437
+ var result = compareObjectIds(this.maxElectionId, ismasterElectionId);
438
+ // Get the electionIds
439
+ var ismasterSetVersion = server.lastIsMaster().setVersion;
440
+
441
+ if (result === 1) {
442
+ this.topologyType = TopologyType.ReplicaSetNoPrimary;
443
+ return false;
444
+ } else if (result === 0 && ismasterSetVersion) {
445
+ if (ismasterSetVersion < this.maxSetVersion) {
446
+ this.topologyType = TopologyType.ReplicaSetNoPrimary;
447
+ return false;
448
+ }
449
+ }
450
+
451
+ this.maxSetVersion = ismasterSetVersion;
452
+ this.maxElectionId = ismasterElectionId;
453
+ }
454
+
455
+ // Hande normalization of server names
456
+ var normalizedHosts = ismaster.hosts.map(function(x) {
457
+ return x.toLowerCase();
458
+ });
459
+ var locationIndex = normalizedHosts.indexOf(serverName);
460
+
461
+ // Validate that the server exists in the host list
462
+ if (locationIndex !== -1) {
463
+ self.primary = server;
464
+ self.set[serverName] = {
465
+ type: ServerType.RSPrimary,
466
+ setVersion: ismaster.setVersion,
467
+ electionId: ismaster.electionId,
468
+ setName: ismaster.setName
469
+ };
470
+
471
+ // Set the topology
472
+ this.topologyType = TopologyType.ReplicaSetWithPrimary;
473
+ if (ismaster.setName) this.setName = ismaster.setName;
474
+ removeFrom(server, self.unknownServers);
475
+ removeFrom(server, self.secondaries);
476
+ removeFrom(server, self.passives);
477
+ self.emit('joined', 'primary', server);
478
+ } else {
479
+ this.topologyType = TopologyType.ReplicaSetNoPrimary;
480
+ }
481
+
482
+ emitTopologyDescriptionChanged(self);
483
+ return true;
484
+ } else if (ismaster.ismaster && ismaster.setName) {
485
+ // Get the electionIds
486
+ var currentElectionId = self.set[self.primary.name.toLowerCase()].electionId;
487
+ var currentSetVersion = self.set[self.primary.name.toLowerCase()].setVersion;
488
+ var currentSetName = self.set[self.primary.name.toLowerCase()].setName;
489
+ ismasterElectionId = server.lastIsMaster().electionId;
490
+ ismasterSetVersion = server.lastIsMaster().setVersion;
491
+ var ismasterSetName = server.lastIsMaster().setName;
492
+
493
+ // Is it the same server instance
494
+ if (this.primary.equals(server) && currentSetName === ismasterSetName) {
495
+ return false;
496
+ }
497
+
498
+ // If we do not have the same rs name
499
+ if (currentSetName && currentSetName !== ismasterSetName) {
500
+ if (!this.primary.equals(server)) {
501
+ this.topologyType = TopologyType.ReplicaSetWithPrimary;
502
+ } else {
503
+ this.topologyType = TopologyType.ReplicaSetNoPrimary;
504
+ }
505
+
506
+ return false;
507
+ }
508
+
509
+ // Check if we need to replace the server
510
+ if (currentElectionId && ismasterElectionId) {
511
+ result = compareObjectIds(currentElectionId, ismasterElectionId);
512
+
513
+ if (result === 1) {
514
+ return false;
515
+ } else if (result === 0 && currentSetVersion > ismasterSetVersion) {
516
+ return false;
517
+ }
518
+ } else if (!currentElectionId && ismasterElectionId && ismasterSetVersion) {
519
+ if (ismasterSetVersion < this.maxSetVersion) {
520
+ return false;
521
+ }
522
+ }
523
+
524
+ if (!this.maxElectionId && ismasterElectionId) {
525
+ this.maxElectionId = ismasterElectionId;
526
+ } else if (this.maxElectionId && ismasterElectionId) {
527
+ result = compareObjectIds(this.maxElectionId, ismasterElectionId);
528
+
529
+ if (result === 1) {
530
+ return false;
531
+ } else if (result === 0 && currentSetVersion && ismasterSetVersion) {
532
+ if (ismasterSetVersion < this.maxSetVersion) {
533
+ return false;
534
+ }
535
+ } else {
536
+ if (ismasterSetVersion < this.maxSetVersion) {
537
+ return false;
538
+ }
539
+ }
540
+
541
+ this.maxElectionId = ismasterElectionId;
542
+ this.maxSetVersion = ismasterSetVersion;
543
+ } else {
544
+ this.maxSetVersion = ismasterSetVersion;
545
+ }
546
+
547
+ // Modify the entry to unknown
548
+ self.set[self.primary.name.toLowerCase()] = {
549
+ type: ServerType.Unknown,
550
+ setVersion: null,
551
+ electionId: null,
552
+ setName: null
553
+ };
554
+
555
+ // Signal primary left
556
+ self.emit('left', 'primary', this.primary);
557
+ // Destroy the instance
558
+ self.primary.destroy();
559
+ // Set the new instance
560
+ self.primary = server;
561
+ // Set the set information
562
+ self.set[serverName] = {
563
+ type: ServerType.RSPrimary,
564
+ setVersion: ismaster.setVersion,
565
+ electionId: ismaster.electionId,
566
+ setName: ismaster.setName
567
+ };
568
+
569
+ // Set the topology
570
+ this.topologyType = TopologyType.ReplicaSetWithPrimary;
571
+ if (ismaster.setName) this.setName = ismaster.setName;
572
+ removeFrom(server, self.unknownServers);
573
+ removeFrom(server, self.secondaries);
574
+ removeFrom(server, self.passives);
575
+ self.emit('joined', 'primary', server);
576
+ emitTopologyDescriptionChanged(self);
577
+ return true;
578
+ }
579
+
580
+ // A possible instance
581
+ if (!this.primary && ismaster.primary) {
582
+ self.set[ismaster.primary.toLowerCase()] = {
583
+ type: ServerType.PossiblePrimary,
584
+ setVersion: null,
585
+ electionId: null,
586
+ setName: null
587
+ };
588
+ }
589
+
590
+ //
591
+ // Secondary handling
592
+ //
593
+ if (
594
+ ismaster.secondary &&
595
+ ismaster.setName &&
596
+ !inList(ismaster, server, this.secondaries) &&
597
+ this.setName &&
598
+ this.setName === ismaster.setName
599
+ ) {
600
+ addToList(self, ServerType.RSSecondary, ismaster, server, this.secondaries);
601
+ // Set the topology
602
+ this.topologyType = this.primary
603
+ ? TopologyType.ReplicaSetWithPrimary
604
+ : TopologyType.ReplicaSetNoPrimary;
605
+ if (ismaster.setName) this.setName = ismaster.setName;
606
+ removeFrom(server, self.unknownServers);
607
+
608
+ // Remove primary
609
+ if (this.primary && this.primary.name.toLowerCase() === serverName) {
610
+ server.destroy();
611
+ this.primary = null;
612
+ self.emit('left', 'primary', server);
613
+ }
614
+
615
+ // Emit secondary joined replicaset
616
+ self.emit('joined', 'secondary', server);
617
+ emitTopologyDescriptionChanged(self);
618
+ return true;
619
+ }
620
+
621
+ //
622
+ // Arbiter handling
623
+ //
624
+ if (
625
+ isArbiter(ismaster) &&
626
+ !inList(ismaster, server, this.arbiters) &&
627
+ this.setName &&
628
+ this.setName === ismaster.setName
629
+ ) {
630
+ addToList(self, ServerType.RSArbiter, ismaster, server, this.arbiters);
631
+ // Set the topology
632
+ this.topologyType = this.primary
633
+ ? TopologyType.ReplicaSetWithPrimary
634
+ : TopologyType.ReplicaSetNoPrimary;
635
+ if (ismaster.setName) this.setName = ismaster.setName;
636
+ removeFrom(server, self.unknownServers);
637
+ self.emit('joined', 'arbiter', server);
638
+ emitTopologyDescriptionChanged(self);
639
+ return true;
640
+ }
641
+
642
+ //
643
+ // Passive handling
644
+ //
645
+ if (
646
+ ismaster.passive &&
647
+ ismaster.setName &&
648
+ !inList(ismaster, server, this.passives) &&
649
+ this.setName &&
650
+ this.setName === ismaster.setName
651
+ ) {
652
+ addToList(self, ServerType.RSSecondary, ismaster, server, this.passives);
653
+ // Set the topology
654
+ this.topologyType = this.primary
655
+ ? TopologyType.ReplicaSetWithPrimary
656
+ : TopologyType.ReplicaSetNoPrimary;
657
+ if (ismaster.setName) this.setName = ismaster.setName;
658
+ removeFrom(server, self.unknownServers);
659
+
660
+ // Remove primary
661
+ if (this.primary && this.primary.name.toLowerCase() === serverName) {
662
+ server.destroy();
663
+ this.primary = null;
664
+ self.emit('left', 'primary', server);
665
+ }
666
+
667
+ self.emit('joined', 'secondary', server);
668
+ emitTopologyDescriptionChanged(self);
669
+ return true;
670
+ }
671
+
672
+ //
673
+ // Remove the primary
674
+ //
675
+ if (this.set[serverName] && this.set[serverName].type === ServerType.RSPrimary) {
676
+ self.emit('left', 'primary', this.primary);
677
+ this.primary.destroy();
678
+ this.primary = null;
679
+ this.topologyType = TopologyType.ReplicaSetNoPrimary;
680
+ return false;
681
+ }
682
+
683
+ this.topologyType = this.primary
684
+ ? TopologyType.ReplicaSetWithPrimary
685
+ : TopologyType.ReplicaSetNoPrimary;
686
+ return false;
687
+ };
688
+
689
+ /**
690
+ * Recalculate single server max staleness
691
+ * @method
692
+ */
693
+ ReplSetState.prototype.updateServerMaxStaleness = function(server, haInterval) {
694
+ // Locate the max secondary lastwrite
695
+ var max = 0;
696
+ // Go over all secondaries
697
+ for (var i = 0; i < this.secondaries.length; i++) {
698
+ max = Math.max(max, this.secondaries[i].lastWriteDate);
699
+ }
700
+
701
+ // Perform this servers staleness calculation
702
+ if (server.ismaster.maxWireVersion >= 5 && server.ismaster.secondary && this.hasPrimary()) {
703
+ server.staleness =
704
+ server.lastUpdateTime -
705
+ server.lastWriteDate -
706
+ (this.primary.lastUpdateTime - this.primary.lastWriteDate) +
707
+ haInterval;
708
+ } else if (server.ismaster.maxWireVersion >= 5 && server.ismaster.secondary) {
709
+ server.staleness = max - server.lastWriteDate + haInterval;
710
+ }
711
+ };
712
+
713
+ /**
714
+ * Recalculate all the staleness values for secodaries
715
+ * @method
716
+ */
717
+ ReplSetState.prototype.updateSecondariesMaxStaleness = function(haInterval) {
718
+ for (var i = 0; i < this.secondaries.length; i++) {
719
+ this.updateServerMaxStaleness(this.secondaries[i], haInterval);
720
+ }
721
+ };
722
+
723
+ /**
724
+ * Pick a server by the passed in ReadPreference
725
+ * @method
726
+ * @param {ReadPreference} readPreference The ReadPreference instance to use
727
+ */
728
+ ReplSetState.prototype.pickServer = function(readPreference) {
729
+ // If no read Preference set to primary by default
730
+ readPreference = readPreference || ReadPreference.primary;
731
+
732
+ // maxStalenessSeconds is not allowed with a primary read
733
+ if (readPreference.preference === 'primary' && readPreference.maxStalenessSeconds != null) {
734
+ return new MongoError('primary readPreference incompatible with maxStalenessSeconds');
735
+ }
736
+
737
+ // Check if we have any non compatible servers for maxStalenessSeconds
738
+ var allservers = this.primary ? [this.primary] : [];
739
+ allservers = allservers.concat(this.secondaries);
740
+
741
+ // Does any of the servers not support the right wire protocol version
742
+ // for maxStalenessSeconds when maxStalenessSeconds specified on readPreference. Then error out
743
+ if (readPreference.maxStalenessSeconds != null) {
744
+ for (var i = 0; i < allservers.length; i++) {
745
+ if (allservers[i].ismaster.maxWireVersion < 5) {
746
+ return new MongoError(
747
+ 'maxStalenessSeconds not supported by at least one of the replicaset members'
748
+ );
749
+ }
750
+ }
751
+ }
752
+
753
+ // Do we have the nearest readPreference
754
+ if (readPreference.preference === 'nearest' && readPreference.maxStalenessSeconds == null) {
755
+ return pickNearest(this, readPreference);
756
+ } else if (
757
+ readPreference.preference === 'nearest' &&
758
+ readPreference.maxStalenessSeconds != null
759
+ ) {
760
+ return pickNearestMaxStalenessSeconds(this, readPreference);
761
+ }
762
+
763
+ // Get all the secondaries
764
+ var secondaries = this.secondaries;
765
+
766
+ // Check if we can satisfy and of the basic read Preferences
767
+ if (readPreference.equals(ReadPreference.secondary) && secondaries.length === 0) {
768
+ return new MongoError('no secondary server available');
769
+ }
770
+
771
+ if (
772
+ readPreference.equals(ReadPreference.secondaryPreferred) &&
773
+ secondaries.length === 0 &&
774
+ this.primary == null
775
+ ) {
776
+ return new MongoError('no secondary or primary server available');
777
+ }
778
+
779
+ if (readPreference.equals(ReadPreference.primary) && this.primary == null) {
780
+ return new MongoError('no primary server available');
781
+ }
782
+
783
+ // Secondary preferred or just secondaries
784
+ if (
785
+ readPreference.equals(ReadPreference.secondaryPreferred) ||
786
+ readPreference.equals(ReadPreference.secondary)
787
+ ) {
788
+ if (secondaries.length > 0 && readPreference.maxStalenessSeconds == null) {
789
+ // Pick nearest of any other servers available
790
+ var server = pickNearest(this, readPreference);
791
+ // No server in the window return primary
792
+ if (server) {
793
+ return server;
794
+ }
795
+ } else if (secondaries.length > 0 && readPreference.maxStalenessSeconds != null) {
796
+ // Pick nearest of any other servers available
797
+ server = pickNearestMaxStalenessSeconds(this, readPreference);
798
+ // No server in the window return primary
799
+ if (server) {
800
+ return server;
801
+ }
802
+ }
803
+
804
+ if (readPreference.equals(ReadPreference.secondaryPreferred)) {
805
+ return this.primary;
806
+ }
807
+
808
+ return null;
809
+ }
810
+
811
+ // Primary preferred
812
+ if (readPreference.equals(ReadPreference.primaryPreferred)) {
813
+ server = null;
814
+
815
+ // We prefer the primary if it's available
816
+ if (this.primary) {
817
+ return this.primary;
818
+ }
819
+
820
+ // Pick a secondary
821
+ if (secondaries.length > 0 && readPreference.maxStalenessSeconds == null) {
822
+ server = pickNearest(this, readPreference);
823
+ } else if (secondaries.length > 0 && readPreference.maxStalenessSeconds != null) {
824
+ server = pickNearestMaxStalenessSeconds(this, readPreference);
825
+ }
826
+
827
+ // Did we find a server
828
+ if (server) return server;
829
+ }
830
+
831
+ // Return the primary
832
+ return this.primary;
833
+ };
834
+
835
+ //
836
+ // Filter serves by tags
837
+ var filterByTags = function(readPreference, servers) {
838
+ if (readPreference.tags == null) return servers;
839
+ var filteredServers = [];
840
+ var tagsArray = Array.isArray(readPreference.tags) ? readPreference.tags : [readPreference.tags];
841
+
842
+ // Iterate over the tags
843
+ for (var j = 0; j < tagsArray.length; j++) {
844
+ var tags = tagsArray[j];
845
+
846
+ // Iterate over all the servers
847
+ for (var i = 0; i < servers.length; i++) {
848
+ var serverTag = servers[i].lastIsMaster().tags || {};
849
+
850
+ // Did we find the a matching server
851
+ var found = true;
852
+ // Check if the server is valid
853
+ for (var name in tags) {
854
+ if (serverTag[name] !== tags[name]) {
855
+ found = false;
856
+ }
857
+ }
858
+
859
+ // Add to candidate list
860
+ if (found) {
861
+ filteredServers.push(servers[i]);
862
+ }
863
+ }
864
+ }
865
+
866
+ // Returned filtered servers
867
+ return filteredServers;
868
+ };
869
+
870
+ function pickNearestMaxStalenessSeconds(self, readPreference) {
871
+ // Only get primary and secondaries as seeds
872
+ var servers = [];
873
+
874
+ // Get the maxStalenessMS
875
+ var maxStalenessMS = readPreference.maxStalenessSeconds * 1000;
876
+
877
+ // Check if the maxStalenessMS > 90 seconds
878
+ if (maxStalenessMS < 90 * 1000) {
879
+ return new MongoError('maxStalenessSeconds must be set to at least 90 seconds');
880
+ }
881
+
882
+ // Add primary to list if not a secondary read preference
883
+ if (
884
+ self.primary &&
885
+ readPreference.preference !== 'secondary' &&
886
+ readPreference.preference !== 'secondaryPreferred'
887
+ ) {
888
+ servers.push(self.primary);
889
+ }
890
+
891
+ // Add all the secondaries
892
+ for (var i = 0; i < self.secondaries.length; i++) {
893
+ servers.push(self.secondaries[i]);
894
+ }
895
+
896
+ // If we have a secondaryPreferred readPreference and no server add the primary
897
+ if (self.primary && servers.length === 0 && readPreference.preference !== 'secondaryPreferred') {
898
+ servers.push(self.primary);
899
+ }
900
+
901
+ // Filter by tags
902
+ servers = filterByTags(readPreference, servers);
903
+
904
+ // Filter by latency
905
+ servers = servers.filter(function(s) {
906
+ return s.staleness <= maxStalenessMS;
907
+ });
908
+
909
+ // Sort by time
910
+ servers.sort(function(a, b) {
911
+ return a.lastIsMasterMS - b.lastIsMasterMS;
912
+ });
913
+
914
+ // No servers, default to primary
915
+ if (servers.length === 0) {
916
+ return null;
917
+ }
918
+
919
+ // Ensure index does not overflow the number of available servers
920
+ self.index = self.index % servers.length;
921
+
922
+ // Get the server
923
+ var server = servers[self.index];
924
+ // Add to the index
925
+ self.index = self.index + 1;
926
+ // Return the first server of the sorted and filtered list
927
+ return server;
928
+ }
929
+
930
+ function pickNearest(self, readPreference) {
931
+ // Only get primary and secondaries as seeds
932
+ var servers = [];
933
+
934
+ // Add primary to list if not a secondary read preference
935
+ if (
936
+ self.primary &&
937
+ readPreference.preference !== 'secondary' &&
938
+ readPreference.preference !== 'secondaryPreferred'
939
+ ) {
940
+ servers.push(self.primary);
941
+ }
942
+
943
+ // Add all the secondaries
944
+ for (var i = 0; i < self.secondaries.length; i++) {
945
+ servers.push(self.secondaries[i]);
946
+ }
947
+
948
+ // If we have a secondaryPreferred readPreference and no server add the primary
949
+ if (servers.length === 0 && self.primary && readPreference.preference !== 'secondaryPreferred') {
950
+ servers.push(self.primary);
951
+ }
952
+
953
+ // Filter by tags
954
+ servers = filterByTags(readPreference, servers);
955
+
956
+ // Sort by time
957
+ servers.sort(function(a, b) {
958
+ return a.lastIsMasterMS - b.lastIsMasterMS;
959
+ });
960
+
961
+ // Locate lowest time (picked servers are lowest time + acceptable Latency margin)
962
+ var lowest = servers.length > 0 ? servers[0].lastIsMasterMS : 0;
963
+
964
+ // Filter by latency
965
+ servers = servers.filter(function(s) {
966
+ return s.lastIsMasterMS <= lowest + self.acceptableLatency;
967
+ });
968
+
969
+ // No servers, default to primary
970
+ if (servers.length === 0) {
971
+ return null;
972
+ }
973
+
974
+ // Ensure index does not overflow the number of available servers
975
+ self.index = self.index % servers.length;
976
+ // Get the server
977
+ var server = servers[self.index];
978
+ // Add to the index
979
+ self.index = self.index + 1;
980
+ // Return the first server of the sorted and filtered list
981
+ return server;
982
+ }
983
+
984
+ function inList(ismaster, server, list) {
985
+ for (var i = 0; i < list.length; i++) {
986
+ if (list[i] && list[i].name && list[i].name.toLowerCase() === server.name.toLowerCase())
987
+ return true;
988
+ }
989
+
990
+ return false;
991
+ }
992
+
993
+ function addToList(self, type, ismaster, server, list) {
994
+ var serverName = server.name.toLowerCase();
995
+ // Update set information about the server instance
996
+ self.set[serverName].type = type;
997
+ self.set[serverName].electionId = ismaster ? ismaster.electionId : ismaster;
998
+ self.set[serverName].setName = ismaster ? ismaster.setName : ismaster;
999
+ self.set[serverName].setVersion = ismaster ? ismaster.setVersion : ismaster;
1000
+ // Add to the list
1001
+ list.push(server);
1002
+ }
1003
+
1004
+ function compareObjectIds(id1, id2) {
1005
+ var a = Buffer.from(id1.toHexString(), 'hex');
1006
+ var b = Buffer.from(id2.toHexString(), 'hex');
1007
+
1008
+ if (a === b) {
1009
+ return 0;
1010
+ }
1011
+
1012
+ if (typeof Buffer.compare === 'function') {
1013
+ return Buffer.compare(a, b);
1014
+ }
1015
+
1016
+ var x = a.length;
1017
+ var y = b.length;
1018
+ var len = Math.min(x, y);
1019
+
1020
+ for (var i = 0; i < len; i++) {
1021
+ if (a[i] !== b[i]) {
1022
+ break;
1023
+ }
1024
+ }
1025
+
1026
+ if (i !== len) {
1027
+ x = a[i];
1028
+ y = b[i];
1029
+ }
1030
+
1031
+ return x < y ? -1 : y < x ? 1 : 0;
1032
+ }
1033
+
1034
+ function removeFrom(server, list) {
1035
+ for (var i = 0; i < list.length; i++) {
1036
+ if (list[i].equals && list[i].equals(server)) {
1037
+ list.splice(i, 1);
1038
+ return true;
1039
+ } else if (typeof list[i] === 'string' && list[i].toLowerCase() === server.name.toLowerCase()) {
1040
+ list.splice(i, 1);
1041
+ return true;
1042
+ }
1043
+ }
1044
+
1045
+ return false;
1046
+ }
1047
+
1048
+ function emitTopologyDescriptionChanged(self) {
1049
+ if (self.listeners('topologyDescriptionChanged').length > 0) {
1050
+ var topology = 'Unknown';
1051
+ var setName = self.setName;
1052
+
1053
+ if (self.hasPrimaryAndSecondary()) {
1054
+ topology = 'ReplicaSetWithPrimary';
1055
+ } else if (!self.hasPrimary() && self.hasSecondary()) {
1056
+ topology = 'ReplicaSetNoPrimary';
1057
+ }
1058
+
1059
+ // Generate description
1060
+ var description = {
1061
+ topologyType: topology,
1062
+ setName: setName,
1063
+ servers: []
1064
+ };
1065
+
1066
+ // Add the primary to the list
1067
+ if (self.hasPrimary()) {
1068
+ var desc = self.primary.getDescription();
1069
+ desc.type = 'RSPrimary';
1070
+ description.servers.push(desc);
1071
+ }
1072
+
1073
+ // Add all the secondaries
1074
+ description.servers = description.servers.concat(
1075
+ self.secondaries.map(function(x) {
1076
+ var description = x.getDescription();
1077
+ description.type = 'RSSecondary';
1078
+ return description;
1079
+ })
1080
+ );
1081
+
1082
+ // Add all the arbiters
1083
+ description.servers = description.servers.concat(
1084
+ self.arbiters.map(function(x) {
1085
+ var description = x.getDescription();
1086
+ description.type = 'RSArbiter';
1087
+ return description;
1088
+ })
1089
+ );
1090
+
1091
+ // Add all the passives
1092
+ description.servers = description.servers.concat(
1093
+ self.passives.map(function(x) {
1094
+ var description = x.getDescription();
1095
+ description.type = 'RSSecondary';
1096
+ return description;
1097
+ })
1098
+ );
1099
+
1100
+ // Get the diff
1101
+ var diffResult = diff(self.replicasetDescription, description);
1102
+
1103
+ // Create the result
1104
+ var result = {
1105
+ topologyId: self.id,
1106
+ previousDescription: self.replicasetDescription,
1107
+ newDescription: description,
1108
+ diff: diffResult
1109
+ };
1110
+
1111
+ // Emit the topologyDescription change
1112
+ // if(diffResult.servers.length > 0) {
1113
+ self.emit('topologyDescriptionChanged', result);
1114
+ // }
1115
+
1116
+ // Set the new description
1117
+ self.replicasetDescription = description;
1118
+ }
1119
+ }
1120
+
1121
+ module.exports = ReplSetState;