Skip to content

Commit f7a50e2

Browse files
author
Thomas Reggi
authored
fix: db.command to not inherit options from parent
Fixes `db.command` method to not inherit readPreference, writeConcern, readConcern from parent. centralizes readPreference helpers translate, resolve, fromOptions to the ReadPreference class. Adds Aspect "NO_INHERIT_OPTIONS" to command operations for flagging specific operations. Creates `RunCommand` operation to wrap `CommandV2`, updating `db.command` to use `CommandV2`. NODE-2649
1 parent 5082971 commit f7a50e2

16 files changed

+106
-121
lines changed

lib/collection.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ const unordered = require('./bulk/unordered');
1616
const ordered = require('./bulk/ordered');
1717
const ChangeStream = require('./change_stream');
1818
const executeLegacyOperation = require('./utils').executeLegacyOperation;
19-
const resolveReadPreference = require('./utils').resolveReadPreference;
2019
const WriteConcern = require('./write_concern');
2120
const ReadConcern = require('./read_concern');
2221
const MongoDBNamespace = require('./utils').MongoDBNamespace;
@@ -399,7 +398,7 @@ Collection.prototype.find = deprecateOptions(
399398
newOptions.slaveOk = options.slaveOk != null ? options.slaveOk : this.s.db.slaveOk;
400399

401400
// Add read preference if needed
402-
newOptions.readPreference = resolveReadPreference(this, newOptions);
401+
newOptions.readPreference = ReadPreference.resolve(this, newOptions);
403402

404403
// Set slave ok to true if read preference different from primary
405404
if (
@@ -1978,7 +1977,7 @@ Collection.prototype.parallelCollectionScan = deprecate(function(options, callba
19781977

19791978
options = Object.assign({}, options);
19801979
// Ensure we have the right read preference inheritance
1981-
options.readPreference = resolveReadPreference(this, options);
1980+
options.readPreference = ReadPreference.resolve(this, options);
19821981

19831982
// Add a promiseLibrary
19841983
options.promiseLibrary = this.s.promiseLibrary;

lib/core/sdam/topology.js

+4-26
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ class Topology extends EventEmitter {
275275
// connect all known servers, then attempt server selection to connect
276276
connectServers(this, Array.from(this.s.description.servers.values()));
277277

278-
translateReadPreference(options);
278+
ReadPreference.translate(options);
279279
const readPreference = options.readPreference || ReadPreference.primary;
280280
this.selectServer(readPreferenceServerSelector(readPreference), options, err => {
281281
if (err) {
@@ -381,7 +381,7 @@ class Topology extends EventEmitter {
381381
} else if (typeof selector === 'string') {
382382
readPreference = new ReadPreference(selector);
383383
} else {
384-
translateReadPreference(options);
384+
ReadPreference.translate(options);
385385
readPreference = options.readPreference || ReadPreference.primary;
386386
}
387387

@@ -647,7 +647,7 @@ class Topology extends EventEmitter {
647647
(callback = options), (options = {}), (options = options || {});
648648
}
649649

650-
translateReadPreference(options);
650+
ReadPreference.translate(options);
651651
const readPreference = options.readPreference || ReadPreference.primary;
652652

653653
this.selectServer(readPreferenceServerSelector(readPreference), options, (err, server) => {
@@ -708,7 +708,7 @@ class Topology extends EventEmitter {
708708
options = options || {};
709709
const topology = options.topology || this;
710710
const CursorClass = options.cursorFactory || this.s.Cursor;
711-
translateReadPreference(options);
711+
ReadPreference.translate(options);
712712

713713
return new CursorClass(topology, ns, cmd, options);
714714
}
@@ -939,28 +939,6 @@ function executeWriteOperation(args, options, callback) {
939939
});
940940
}
941941

942-
function translateReadPreference(options) {
943-
if (options.readPreference == null) {
944-
return;
945-
}
946-
947-
let r = options.readPreference;
948-
if (typeof r === 'string') {
949-
options.readPreference = new ReadPreference(r);
950-
} else if (r && !(r instanceof ReadPreference) && typeof r === 'object') {
951-
const mode = r.mode || r.preference;
952-
if (mode && typeof mode === 'string') {
953-
options.readPreference = new ReadPreference(mode, r.tags, {
954-
maxStalenessSeconds: r.maxStalenessSeconds
955-
});
956-
}
957-
} else if (!(r instanceof ReadPreference)) {
958-
throw new TypeError('Invalid read preference: ' + r);
959-
}
960-
961-
return options;
962-
}
963-
964942
function srvPollingHandler(topology) {
965943
return function handleSrvPolling(ev) {
966944
const previousTopologyDescription = topology.s.description;

lib/core/topologies/read_preference.js

+54
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,60 @@ ReadPreference.fromOptions = function(options) {
112112
return readPreference;
113113
};
114114

115+
/**
116+
* Resolves a read preference based on well-defined inheritance rules. This method will not only
117+
* determine the read preference (if there is one), but will also ensure the returned value is a
118+
* properly constructed instance of `ReadPreference`.
119+
*
120+
* @param {Collection|Db|MongoClient} parent The parent of the operation on which to determine the read
121+
* preference, used for determining the inherited read preference.
122+
* @param {object} options The options passed into the method, potentially containing a read preference
123+
* @returns {(ReadPreference|null)} The resolved read preference
124+
*/
125+
ReadPreference.resolve = function(parent, options) {
126+
options = options || {};
127+
const session = options.session;
128+
129+
const inheritedReadPreference = parent && parent.readPreference;
130+
131+
let readPreference;
132+
if (options.readPreference) {
133+
readPreference = ReadPreference.fromOptions(options);
134+
} else if (session && session.inTransaction() && session.transaction.options.readPreference) {
135+
// The transaction’s read preference MUST override all other user configurable read preferences.
136+
readPreference = session.transaction.options.readPreference;
137+
} else if (inheritedReadPreference != null) {
138+
readPreference = inheritedReadPreference;
139+
} else {
140+
readPreference = ReadPreference.primary;
141+
}
142+
143+
return typeof readPreference === 'string' ? new ReadPreference(readPreference) : readPreference;
144+
};
145+
146+
/**
147+
* Replaces options.readPreference with a ReadPreference instance
148+
*/
149+
ReadPreference.translate = function(options) {
150+
if (options.readPreference == null) return options;
151+
const r = options.readPreference;
152+
153+
if (typeof r === 'string') {
154+
options.readPreference = new ReadPreference(r);
155+
} else if (r && !(r instanceof ReadPreference) && typeof r === 'object') {
156+
const mode = r.mode || r.preference;
157+
if (mode && typeof mode === 'string') {
158+
options.readPreference = new ReadPreference(mode, r.tags, {
159+
maxStalenessSeconds: r.maxStalenessSeconds
160+
});
161+
}
162+
} else if (!(r instanceof ReadPreference)) {
163+
throw new TypeError('Invalid read preference: ' + r);
164+
}
165+
166+
return options;
167+
};
168+
115169
/**
116170
* Validate if a mode is legal
117171
*

lib/core/wireprotocol/command.js

+1-6
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,7 @@ function _command(server, ns, cmd, options, callback) {
6666
finalCmd.$clusterTime = clusterTime;
6767
}
6868

69-
if (
70-
isSharded(server) &&
71-
!shouldUseOpMsg &&
72-
readPreference &&
73-
readPreference.preference !== 'primary'
74-
) {
69+
if (isSharded(server) && !shouldUseOpMsg && readPreference && readPreference.mode !== 'primary') {
7570
finalCmd = {
7671
$query: finalCmd,
7772
$readPreference: readPreference.toJSON()

lib/db.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ const Logger = require('./core').Logger;
1414
const Collection = require('./collection');
1515
const mergeOptionsAndWriteConcern = require('./utils').mergeOptionsAndWriteConcern;
1616
const executeLegacyOperation = require('./utils').executeLegacyOperation;
17-
const resolveReadPreference = require('./utils').resolveReadPreference;
1817
const ChangeStream = require('./change_stream');
1918
const deprecate = require('util').deprecate;
2019
const deprecateOptions = require('./utils').deprecateOptions;
@@ -35,6 +34,7 @@ const AggregateOperation = require('./operations/aggregate');
3534
const AddUserOperation = require('./operations/add_user');
3635
const CollectionsOperation = require('./operations/collections');
3736
const CommandOperation = require('./operations/command');
37+
const RunCommandOperation = require('./operations/run_command');
3838
const CreateCollectionOperation = require('./operations/create_collection');
3939
const CreateIndexOperation = require('./operations/create_index');
4040
const DropCollectionOperation = require('./operations/drop').DropCollectionOperation;
@@ -290,7 +290,7 @@ Db.prototype.command = function(command, options, callback) {
290290
if (typeof options === 'function') (callback = options), (options = {});
291291
options = Object.assign({}, options);
292292

293-
const commandOperation = new CommandOperation(this, options, null, command);
293+
const commandOperation = new RunCommandOperation(this, command, options);
294294

295295
return executeOperation(this.s.topology, commandOperation, callback);
296296
};
@@ -709,7 +709,7 @@ Db.prototype.collections = function(options, callback) {
709709
Db.prototype.executeDbAdminCommand = function(selector, options, callback) {
710710
if (typeof options === 'function') (callback = options), (options = {});
711711
options = options || {};
712-
options.readPreference = resolveReadPreference(this, options);
712+
options.readPreference = ReadPreference.resolve(this, options);
713713

714714
const executeDbAdminCommandOperation = new ExecuteDbAdminCommandOperation(
715715
this,

lib/operations/collection_ops.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ const decorateWithReadConcern = require('../utils').decorateWithReadConcern;
88
const ensureIndexDb = require('./db_ops').ensureIndex;
99
const evaluate = require('./db_ops').evaluate;
1010
const executeCommand = require('./db_ops').executeCommand;
11-
const resolveReadPreference = require('../utils').resolveReadPreference;
1211
const handleCallback = require('../utils').handleCallback;
1312
const indexInformationDb = require('./db_ops').indexInformation;
1413
const Long = require('../core').BSON.Long;
@@ -188,7 +187,7 @@ function group(coll, keys, condition, initial, reduce, finalize, command, option
188187

189188
options = Object.assign({}, options);
190189
// Ensure we have the right read preference inheritance
191-
options.readPreference = resolveReadPreference(coll, options);
190+
options.readPreference = ReadPreference.resolve(coll, options);
192191

193192
// Do we have a readConcern specified
194193
decorateWithReadConcern(selector, coll, options);

lib/operations/command.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ const debugOptions = require('../utils').debugOptions;
77
const handleCallback = require('../utils').handleCallback;
88
const MongoError = require('../core').MongoError;
99
const ReadPreference = require('../core').ReadPreference;
10-
const resolveReadPreference = require('../utils').resolveReadPreference;
1110
const MongoDBNamespace = require('../utils').MongoDBNamespace;
1211

1312
const debugFields = [
@@ -38,9 +37,9 @@ class CommandOperation extends OperationBase {
3837

3938
if (!this.hasAspect(Aspect.WRITE_OPERATION)) {
4039
if (collection != null) {
41-
this.options.readPreference = resolveReadPreference(collection, options);
40+
this.options.readPreference = ReadPreference.resolve(collection, options);
4241
} else {
43-
this.options.readPreference = resolveReadPreference(db, options);
42+
this.options.readPreference = ReadPreference.resolve(db, options);
4443
}
4544
} else {
4645
if (collection != null) {

lib/operations/command_v2.js

+7-6
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
const Aspect = require('./operation').Aspect;
44
const OperationBase = require('./operation').OperationBase;
5-
const resolveReadPreference = require('../utils').resolveReadPreference;
5+
const ReadPreference = require('../core').ReadPreference;
66
const ReadConcern = require('../read_concern');
77
const WriteConcern = require('../write_concern');
88
const maxWireVersion = require('../core/utils').maxWireVersion;
@@ -16,9 +16,10 @@ class CommandOperationV2 extends OperationBase {
1616
super(options);
1717

1818
this.ns = parent.s.namespace.withCollection('$cmd');
19-
this.readPreference = resolveReadPreference(parent, this.options);
20-
this.readConcern = resolveReadConcern(parent, this.options);
21-
this.writeConcern = resolveWriteConcern(parent, this.options);
19+
const propertyProvider = this.hasAspect(Aspect.NO_INHERIT_OPTIONS) ? undefined : parent;
20+
this.readPreference = ReadPreference.resolve(propertyProvider, this.options);
21+
this.readConcern = resolveReadConcern(propertyProvider, this.options);
22+
this.writeConcern = resolveWriteConcern(propertyProvider, this.options);
2223
this.explain = false;
2324

2425
if (operationOptions && typeof operationOptions.fullResponse === 'boolean') {
@@ -97,11 +98,11 @@ class CommandOperationV2 extends OperationBase {
9798
}
9899

99100
function resolveWriteConcern(parent, options) {
100-
return WriteConcern.fromOptions(options) || parent.writeConcern;
101+
return WriteConcern.fromOptions(options) || (parent && parent.writeConcern);
101102
}
102103

103104
function resolveReadConcern(parent, options) {
104-
return ReadConcern.fromOptions(options) || parent.readConcern;
105+
return ReadConcern.fromOptions(options) || (parent && parent.readConcern);
105106
}
106107

107108
module.exports = CommandOperationV2;

lib/operations/db_ops.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
const applyWriteConcern = require('../utils').applyWriteConcern;
44
const Code = require('../core').BSON.Code;
5-
const resolveReadPreference = require('../utils').resolveReadPreference;
65
const debugOptions = require('../utils').debugOptions;
76
const handleCallback = require('../utils').handleCallback;
87
const MongoError = require('../core').MongoError;
@@ -225,7 +224,7 @@ function executeCommand(db, command, options, callback) {
225224
const dbName = options.dbName || options.authdb || db.databaseName;
226225

227226
// Convert the readPreference if its not a write
228-
options.readPreference = resolveReadPreference(db, options);
227+
options.readPreference = ReadPreference.resolve(db, options);
229228

230229
// Debug information
231230
if (db.s.logger.isDebug())

lib/operations/find.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@
33
const OperationBase = require('./operation').OperationBase;
44
const Aspect = require('./operation').Aspect;
55
const defineAspects = require('./operation').defineAspects;
6-
const resolveReadPreference = require('../utils').resolveReadPreference;
6+
const ReadPreference = require('../core').ReadPreference;
77

88
class FindOperation extends OperationBase {
99
constructor(collection, ns, command, options) {
1010
super(options);
1111

1212
this.ns = ns;
1313
this.cmd = command;
14-
this.readPreference = resolveReadPreference(collection, this.options);
14+
this.readPreference = ReadPreference.resolve(collection, this.options);
1515
}
1616

1717
execute(server, callback) {

lib/operations/geo_haystack_search.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const decorateCommand = require('../utils').decorateCommand;
77
const decorateWithReadConcern = require('../utils').decorateWithReadConcern;
88
const executeCommand = require('./db_ops').executeCommand;
99
const handleCallback = require('../utils').handleCallback;
10-
const resolveReadPreference = require('../utils').resolveReadPreference;
10+
const ReadPreference = require('../core').ReadPreference;
1111
const toError = require('../utils').toError;
1212

1313
/**
@@ -58,7 +58,7 @@ class GeoHaystackSearchOperation extends OperationBase {
5858

5959
options = Object.assign({}, options);
6060
// Ensure we have the right read preference inheritance
61-
options.readPreference = resolveReadPreference(coll, options);
61+
options.readPreference = ReadPreference.resolve(coll, options);
6262

6363
// Do we have a readConcern specified
6464
decorateWithReadConcern(commandObject, coll, options);

lib/operations/map_reduce.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const handleCallback = require('../utils').handleCallback;
99
const isObject = require('../utils').isObject;
1010
const loadDb = require('../dynamic_loaders').loadDb;
1111
const OperationBase = require('./operation').OperationBase;
12-
const resolveReadPreference = require('../utils').resolveReadPreference;
12+
const ReadPreference = require('../core').ReadPreference;
1313
const toError = require('../utils').toError;
1414

1515
const exclusionList = [
@@ -80,7 +80,7 @@ class MapReduceOperation extends OperationBase {
8080
options = Object.assign({}, options);
8181

8282
// Ensure we have the right read preference inheritance
83-
options.readPreference = resolveReadPreference(coll, options);
83+
options.readPreference = ReadPreference.resolve(coll, options);
8484

8585
// If we have a read preference and inline is not set as output fail hard
8686
if (

lib/operations/operation.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ const Aspect = {
44
READ_OPERATION: Symbol('READ_OPERATION'),
55
WRITE_OPERATION: Symbol('WRITE_OPERATION'),
66
RETRYABLE: Symbol('RETRYABLE'),
7-
EXECUTE_WITH_SELECTION: Symbol('EXECUTE_WITH_SELECTION')
7+
EXECUTE_WITH_SELECTION: Symbol('EXECUTE_WITH_SELECTION'),
8+
NO_INHERIT_OPTIONS: Symbol('NO_INHERIT_OPTIONS')
89
};
910

1011
/**

lib/operations/run_command.js

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
'use strict';
2+
3+
const CommandOperationV2 = require('./command_v2');
4+
const defineAspects = require('./operation').defineAspects;
5+
const Aspect = require('./operation').Aspect;
6+
7+
class RunCommandOperation extends CommandOperationV2 {
8+
constructor(parent, command, options) {
9+
super(parent, options);
10+
this.command = command;
11+
}
12+
execute(server, callback) {
13+
const command = this.command;
14+
this.executeCommand(server, command, callback);
15+
}
16+
}
17+
defineAspects(RunCommandOperation, [Aspect.EXECUTE_WITH_SELECTION, Aspect.NO_INHERIT_OPTIONS]);
18+
19+
module.exports = RunCommandOperation;

0 commit comments

Comments
 (0)