Skip to content

Commit 2995e11

Browse files
authored
feat(read-preference): unify means of read preference resolution (#1738)
* feat(read-preference): unify means of read preference resolution NODE-1515
1 parent fdb828b commit 2995e11

File tree

5 files changed

+66
-76
lines changed

5 files changed

+66
-76
lines changed

lib/collection.js

+8-5
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const ordered = require('./bulk/ordered');
1717
const ChangeStream = require('./change_stream');
1818
const executeOperation = require('./utils').executeOperation;
1919
const applyWriteConcern = require('./utils').applyWriteConcern;
20-
const getReadPreference = require('./utils').getReadPreference;
20+
const resolveReadPreference = require('./utils').resolveReadPreference;
2121

2222
// Operations
2323
const bulkWrite = require('./operations/collection_ops').bulkWrite;
@@ -340,7 +340,10 @@ Collection.prototype.find = function(query, options, callback) {
340340
newOptions.slaveOk = options.slaveOk != null ? options.slaveOk : this.s.db.slaveOk;
341341

342342
// Add read preference if needed
343-
newOptions = getReadPreference(this, newOptions, this.s.db);
343+
newOptions.readPreference = resolveReadPreference(newOptions, {
344+
db: this.s.db,
345+
collection: this
346+
});
344347

345348
// Set slave ok to true if read preference different from primary
346349
if (
@@ -1223,7 +1226,7 @@ Collection.prototype.listIndexes = function(options) {
12231226
// Clone the options
12241227
options = Object.assign({}, options);
12251228
// Determine the read preference in the options.
1226-
options = getReadPreference(this, options, this.s.db, this);
1229+
options.readPreference = resolveReadPreference(options, { db: this.s.db, collection: this });
12271230
// Set the CommandCursor constructor
12281231
options.cursorFactory = CommandCursor;
12291232
// Set the promiseLibrary
@@ -1738,7 +1741,7 @@ Collection.prototype.aggregate = function(pipeline, options, callback) {
17381741

17391742
options = Object.assign({}, options);
17401743
// Ensure we have the right read preference inheritance
1741-
options = getReadPreference(this, options, this.s.db, this);
1744+
options.readPreference = resolveReadPreference(options, { db: this.s.db, collection: this });
17421745

17431746
// If explain has been specified add it
17441747
if (options.explain) {
@@ -1836,7 +1839,7 @@ Collection.prototype.parallelCollectionScan = function(options, callback) {
18361839

18371840
options = Object.assign({}, options);
18381841
// Ensure we have the right read preference inheritance
1839-
options = getReadPreference(this, options, this.s.db, this);
1842+
options.readPreference = resolveReadPreference(options, { db: this.s.db, collection: this });
18401843

18411844
// Add a promiseLibrary
18421845
options.promiseLibrary = this.s.promiseLibrary;

lib/db.js

+6-11
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const Collection = require('./collection');
1515
const mergeOptionsAndWriteConcern = require('./utils').mergeOptionsAndWriteConcern;
1616
const executeOperation = require('./utils').executeOperation;
1717
const applyWriteConcern = require('./utils').applyWriteConcern;
18-
const convertReadPreference = require('./utils').convertReadPreference;
18+
const resolveReadPreference = require('./utils').resolveReadPreference;
1919
const ChangeStream = require('./change_stream');
2020

2121
// Operations
@@ -477,11 +477,10 @@ Db.prototype.listCollections = function(filter, options) {
477477
options.promiseLibrary = this.s.promiseLibrary;
478478

479479
// Ensure valid readPreference
480-
if (options.readPreference) {
481-
options.readPreference = convertReadPreference(options.readPreference);
482-
} else {
483-
options.readPreference = this.s.readPreference || ReadPreference.primary;
484-
}
480+
options.readPreference = resolveReadPreference(options, {
481+
db: this,
482+
default: ReadPreference.primary
483+
});
485484

486485
// Cursor options
487486
let cursor = options.batchSize ? { batchSize: options.batchSize } : {};
@@ -673,11 +672,7 @@ Db.prototype.collections = function(options, callback) {
673672
Db.prototype.executeDbAdminCommand = function(selector, options, callback) {
674673
if (typeof options === 'function') (callback = options), (options = {});
675674
options = options || {};
676-
677-
// Convert read preference
678-
if (options.readPreference) {
679-
options.readPreference = convertReadPreference(options.readPreference);
680-
}
675+
options.readPreference = resolveReadPreference(options);
681676

682677
return executeOperation(this.s.topology, executeDbAdminCommand, [
683678
this,

lib/operations/collection_ops.js

+7-7
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const evaluate = require('./db_ops').evaluate;
1212
const executeCommand = require('./db_ops').executeCommand;
1313
const executeDbAdminCommand = require('./db_ops').executeDbAdminCommand;
1414
const formattedOrderClause = require('../utils').formattedOrderClause;
15-
const getReadPreference = require('../utils').getReadPreference;
15+
const resolveReadPreference = require('../utils').resolveReadPreference;
1616
const handleCallback = require('../utils').handleCallback;
1717
const indexInformationDb = require('./db_ops').indexInformation;
1818
const isObject = require('../utils').isObject;
@@ -195,7 +195,7 @@ function count(coll, query, options, callback) {
195195
if (hint) cmd.hint = hint;
196196

197197
// Ensure we have the right read preference inheritance
198-
options = getReadPreference(coll, options, coll.s.db);
198+
options.readPreference = resolveReadPreference(options, { db: coll.s.db, collection: coll });
199199

200200
// Do we have a readConcern specified
201201
decorateWithReadConcern(cmd, coll, options);
@@ -359,7 +359,7 @@ function distinct(coll, key, query, options, callback) {
359359

360360
options = Object.assign({}, options);
361361
// Ensure we have the right read preference inheritance
362-
options = getReadPreference(coll, options, coll.s.db, coll);
362+
options.readPreference = resolveReadPreference(options, { db: coll.s.db, collection: coll });
363363

364364
// Add maxTimeMS if defined
365365
if (typeof maxTimeMS === 'number') cmd.maxTimeMS = maxTimeMS;
@@ -634,7 +634,7 @@ function geoHaystackSearch(coll, x, y, options, callback) {
634634

635635
options = Object.assign({}, options);
636636
// Ensure we have the right read preference inheritance
637-
options = getReadPreference(coll, options, coll.s.db, coll);
637+
options.readPreference = resolveReadPreference(options, { db: coll.s.db, collection: coll });
638638

639639
// Do we have a readConcern specified
640640
decorateWithReadConcern(commandObject, coll, options);
@@ -694,7 +694,7 @@ function group(coll, keys, condition, initial, reduce, finalize, command, option
694694

695695
options = Object.assign({}, options);
696696
// Ensure we have the right read preference inheritance
697-
options = getReadPreference(coll, options, coll.s.db, coll);
697+
options.readPreference = resolveReadPreference(options, { db: coll.s.db, collection: coll });
698698

699699
// Do we have a readConcern specified
700700
decorateWithReadConcern(selector, coll, options);
@@ -891,7 +891,7 @@ function mapReduce(coll, map, reduce, options, callback) {
891891
options = Object.assign({}, options);
892892

893893
// Ensure we have the right read preference inheritance
894-
options = getReadPreference(coll, options, coll.s.db, coll);
894+
options.readPreference = resolveReadPreference(options, { db: coll.s.db, collection: coll });
895895

896896
// If we have a read preference and inline is not set as output fail hard
897897
if (
@@ -1280,7 +1280,7 @@ function stats(coll, options, callback) {
12801280

12811281
options = Object.assign({}, options);
12821282
// Ensure we have the right read preference inheritance
1283-
options = getReadPreference(coll, options, coll.s.db, coll);
1283+
options.readPreference = resolveReadPreference(options, { db: coll.s.db, collection: coll });
12841284

12851285
// Execute the command
12861286
executeCommand(coll.s.db, commandObject, options, callback);

lib/operations/db_ops.js

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

33
const applyWriteConcern = require('../utils').applyWriteConcern;
44
const Code = require('mongodb-core').BSON.Code;
5-
const convertReadPreference = require('../utils').convertReadPreference;
5+
const resolveReadPreference = require('../utils').resolveReadPreference;
66
const crypto = require('crypto');
77
const Db = require('../db');
88
const debugOptions = require('../utils').debugOptions;
@@ -482,11 +482,7 @@ function executeCommand(db, command, options, callback) {
482482
}
483483

484484
// Convert the readPreference if its not a write
485-
if (options.readPreference) {
486-
options.readPreference = convertReadPreference(options.readPreference);
487-
} else {
488-
options.readPreference = ReadPreference.primary;
489-
}
485+
options.readPreference = resolveReadPreference(options, { default: ReadPreference.primary });
490486

491487
// Debug information
492488
if (db.s.logger.isDebug())

lib/utils.js

+43-47
Original file line numberDiff line numberDiff line change
@@ -496,60 +496,57 @@ function applyWriteConcern(target, sources, options) {
496496
}
497497

498498
/**
499-
* Ensures provided read preference is properly converted into an object
500-
* @param {(ReadPreference|string|object)} readPreference the user provided read preference
501-
* @return {ReadPreference}
499+
* Resolves a read preference based on well-defined inheritance rules. This method will not only
500+
* determine the read preference (if there is one), but will also ensure the returned value is a
501+
* properly constructed instance of `ReadPreference`.
502+
*
503+
* @param {Object} options The options passed into the method, potentially containing a read preference
504+
* @param {Object} sources Sources from which we can inherit a read preference
505+
* @returns {(ReadPreference|null)} The resolved read preference
502506
*/
503-
function convertReadPreference(readPreference) {
504-
if (readPreference) {
505-
if (typeof readPreference === 'string') {
506-
return new ReadPreference(readPreference);
507-
} else if (
508-
readPreference &&
509-
!(readPreference instanceof ReadPreference) &&
510-
typeof readPreference === 'object'
511-
) {
512-
const mode = readPreference.mode || readPreference.preference;
513-
if (mode && typeof mode === 'string') {
514-
return new ReadPreference(mode, readPreference.tags, {
515-
maxStalenessSeconds: readPreference.maxStalenessSeconds
516-
});
517-
}
518-
} else if (!(readPreference instanceof ReadPreference)) {
519-
throw new TypeError('Invalid read preference: ' + readPreference);
520-
}
521-
}
507+
function resolveReadPreference(options, sources) {
508+
options = options || {};
509+
sources = sources || {};
522510

523-
return readPreference;
524-
}
511+
const db = sources.db;
512+
const coll = sources.collection;
513+
const defaultReadPreference = sources.default;
525514

526-
// Figure out the read preference
527-
function getReadPreference(coll, options, db) {
528-
let r = null;
515+
let readPreference;
529516
if (options.readPreference) {
530-
r = options.readPreference;
531-
} else if (coll.s.readPreference) {
532-
r = coll.s.readPreference;
533-
} else if (db.s.readPreference) {
534-
r = db.s.readPreference;
535-
} else {
536-
return options;
537-
}
538-
539-
if (typeof r === 'string') {
540-
options.readPreference = new ReadPreference(r);
541-
} else if (r && !(r instanceof ReadPreference) && typeof r === 'object') {
542-
const mode = r.mode || r.preference;
517+
readPreference = options.readPreference;
518+
} else if (coll && coll.s.readPreference) {
519+
readPreference = coll.s.readPreference;
520+
} else if (db && db.s.readPreference) {
521+
readPreference = db.s.readPreference;
522+
} else if (defaultReadPreference) {
523+
readPreference = defaultReadPreference;
524+
}
525+
526+
// do we even have a read preference?
527+
if (readPreference == null) {
528+
return null;
529+
}
530+
531+
// now attempt to convert the read preference if necessary
532+
if (typeof readPreference === 'string') {
533+
readPreference = new ReadPreference(readPreference);
534+
} else if (
535+
readPreference &&
536+
!(readPreference instanceof ReadPreference) &&
537+
typeof readPreference === 'object'
538+
) {
539+
const mode = readPreference.mode || readPreference.preference;
543540
if (mode && typeof mode === 'string') {
544-
options.readPreference = new ReadPreference(mode, r.tags, {
545-
maxStalenessSeconds: r.maxStalenessSeconds
541+
readPreference = new ReadPreference(mode, readPreference.tags, {
542+
maxStalenessSeconds: readPreference.maxStalenessSeconds
546543
});
547544
}
548-
} else if (!(r instanceof ReadPreference)) {
549-
throw new TypeError('Invalid read preference: ' + r);
545+
} else if (!(readPreference instanceof ReadPreference)) {
546+
throw new TypeError('Invalid read preference: ' + readPreference);
550547
}
551548

552-
return options;
549+
return readPreference;
553550
}
554551

555552
/**
@@ -625,9 +622,8 @@ module.exports = {
625622
translateReadPreference,
626623
executeOperation,
627624
applyWriteConcern,
628-
convertReadPreference,
625+
resolveReadPreference,
629626
isPromiseLike,
630-
getReadPreference,
631627
decorateWithCollation,
632628
decorateWithReadConcern
633629
};

0 commit comments

Comments
 (0)