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