Skip to content

Commit 13d776f

Browse files
authored
fix(cursor): set readPreference for cursor.count
Fixes NODE-1581
1 parent a5d0f1d commit 13d776f

File tree

3 files changed

+103
-51
lines changed

3 files changed

+103
-51
lines changed

lib/operations/collection_ops.js

+52-25
Original file line numberDiff line numberDiff line change
@@ -175,33 +175,14 @@ function checkForAtomicOperators(update) {
175175
function count(coll, query, options, callback) {
176176
if (typeof options === 'function') (callback = options), (options = {});
177177
options = Object.assign({}, options);
178+
options.collectionName = coll.s.name;
178179

179-
const skip = options.skip;
180-
const limit = options.limit;
181-
const hint = options.hint;
182-
const maxTimeMS = options.maxTimeMS;
183-
query = query || {};
184-
185-
// Final query
186-
const cmd = {
187-
count: coll.s.name,
188-
query: query
189-
};
190-
191-
// Add limit, skip and maxTimeMS if defined
192-
if (typeof skip === 'number') cmd.skip = skip;
193-
if (typeof limit === 'number') cmd.limit = limit;
194-
if (typeof maxTimeMS === 'number') cmd.maxTimeMS = maxTimeMS;
195-
if (hint) cmd.hint = hint;
196-
197-
// Ensure we have the right read preference inheritance
198-
options.readPreference = resolveReadPreference(options, { db: coll.s.db, collection: coll });
199-
200-
// Do we have a readConcern specified
201-
decorateWithReadConcern(cmd, coll, options);
180+
options.readPreference = resolveReadPreference(options, {
181+
db: coll.s.db,
182+
collection: coll
183+
});
202184

203-
// Have we specified collation
204-
decorateWithCollation(cmd, coll, options);
185+
const cmd = buildCountCommand(coll, query, options);
205186

206187
executeCommand(coll.s.db, cmd, options, (err, result) => {
207188
if (err) return handleCallback(callback, err);
@@ -236,6 +217,51 @@ function countDocuments(coll, query, options, callback) {
236217
});
237218
}
238219

220+
/**
221+
* Build the count command.
222+
*
223+
* @method
224+
* @param {collectionOrCursor} an instance of a collection or cursor
225+
* @param {object} query The query for the count.
226+
* @param {object} [options] Optional settings. See Collection.prototype.count and Cursor.prototype.count for a list of options.
227+
*/
228+
function buildCountCommand(collectionOrCursor, query, options) {
229+
const skip = options.skip;
230+
const limit = options.limit;
231+
let hint = options.hint;
232+
const maxTimeMS = options.maxTimeMS;
233+
query = query || {};
234+
235+
// Final query
236+
const cmd = {
237+
count: options.collectionName,
238+
query: query
239+
};
240+
241+
// check if collectionOrCursor is a cursor by using cursor.s.numberOfRetries
242+
if (collectionOrCursor.s.numberOfRetries) {
243+
if (collectionOrCursor.s.options.hint) {
244+
hint = collectionOrCursor.s.options.hint;
245+
} else if (collectionOrCursor.s.cmd.hint) {
246+
hint = collectionOrCursor.s.cmd.hint;
247+
}
248+
decorateWithCollation(cmd, collectionOrCursor, collectionOrCursor.s.cmd);
249+
} else {
250+
decorateWithCollation(cmd, collectionOrCursor, options);
251+
}
252+
253+
// Add limit, skip and maxTimeMS if defined
254+
if (typeof skip === 'number') cmd.skip = skip;
255+
if (typeof limit === 'number') cmd.limit = limit;
256+
if (typeof maxTimeMS === 'number') cmd.maxTimeMS = maxTimeMS;
257+
if (hint) cmd.hint = hint;
258+
259+
// Do we have a readConcern specified
260+
decorateWithReadConcern(cmd, collectionOrCursor);
261+
262+
return cmd;
263+
}
264+
239265
/**
240266
* Create an index on the db and collection.
241267
*
@@ -1391,6 +1417,7 @@ module.exports = {
13911417
checkForAtomicOperators,
13921418
count,
13931419
countDocuments,
1420+
buildCountCommand,
13941421
createIndex,
13951422
createIndexes,
13961423
deleteMany,

lib/operations/cursor_ops.js

+19-26
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22

3-
const decorateWithCollation = require('../utils').decorateWithCollation;
3+
const buildCountCommand = require('./collection_ops').buildCountCommand;
44
const formattedOrderClause = require('../utils').formattedOrderClause;
55
const handleCallback = require('../utils').handleCallback;
66
const MongoError = require('mongodb-core').MongoError;
@@ -21,37 +21,30 @@ function count(cursor, applySkipLimit, opts, callback) {
2121
if (typeof cursor.cursorLimit() === 'number') opts.limit = cursor.cursorLimit();
2222
}
2323

24-
// Command
25-
const delimiter = cursor.s.ns.indexOf('.');
26-
27-
let command = {
28-
count: cursor.s.ns.substr(delimiter + 1),
29-
query: cursor.s.cmd.query
30-
};
31-
32-
// Apply a readConcern if set
33-
if (cursor.s.cmd.readConcern) {
34-
command.readConcern = cursor.s.cmd.readConcern;
24+
// Ensure we have the right read preference inheritance
25+
if (opts.readPreference) {
26+
cursor.setReadPreference(opts.readPreference);
3527
}
3628

37-
// Apply a hint if set
38-
if (cursor.s.cmd.hint) {
39-
command.hint = cursor.s.cmd.hint;
29+
if (
30+
typeof opts.maxTimeMS !== 'number' &&
31+
cursor.s.cmd &&
32+
typeof cursor.s.cmd.maxTimeMS === 'number'
33+
) {
34+
opts.maxTimeMS = cursor.s.cmd.maxTimeMS;
4035
}
4136

42-
// Apply a collation if set
43-
decorateWithCollation(command, cursor, cursor.s.cmd);
37+
let options = {};
38+
options.skip = opts.skip;
39+
options.limit = opts.limit;
40+
options.hint = opts.hint;
41+
options.maxTimeMS = opts.maxTimeMS;
4442

45-
if (typeof opts.maxTimeMS === 'number') {
46-
command.maxTimeMS = opts.maxTimeMS;
47-
} else if (cursor.s.cmd && typeof cursor.s.cmd.maxTimeMS === 'number') {
48-
command.maxTimeMS = cursor.s.cmd.maxTimeMS;
49-
}
43+
// Command
44+
const delimiter = cursor.s.ns.indexOf('.');
45+
options.collectionName = cursor.s.ns.substr(delimiter + 1);
5046

51-
// Merge in any options
52-
if (opts.skip) command.skip = opts.skip;
53-
if (opts.limit) command.limit = opts.limit;
54-
if (cursor.s.options.hint) command.hint = cursor.s.options.hint;
47+
const command = buildCountCommand(cursor, cursor.s.cmd.query, options);
5548

5649
// Set cursor server to the same as the topology
5750
cursor.server = cursor.topology.s.coreTopology;

test/functional/cursor_tests.js

+32
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const fs = require('fs');
55
const expect = require('chai').expect;
66
const Long = require('bson').Long;
77
const sinon = require('sinon');
8+
const ReadPreference = require('mongodb-core').ReadPreference;
89

910
describe('Cursor', function() {
1011
before(function() {
@@ -280,6 +281,37 @@ describe('Cursor', function() {
280281
}
281282
});
282283

284+
it('Should correctly execute cursor count with secondary readPreference', {
285+
// Add a tag that our runner can trigger on
286+
// in this case we are setting that node needs to be higher than 0.10.X to run
287+
metadata: {
288+
requires: { topology: 'replicaset' }
289+
},
290+
291+
// The actual test we wish to run
292+
test: function(done) {
293+
const configuration = this.configuration;
294+
const client = configuration.newClient(configuration.writeConcernMax(), { poolSize: 1 });
295+
296+
client.connect((err, client) => {
297+
const db = client.db(configuration.db);
298+
299+
const internalClientCursor = sinon.spy(client.topology.s.coreTopology, 'cursor');
300+
const expectedReadPreference = new ReadPreference(ReadPreference.SECONDARY);
301+
302+
const cursor = db.collection('countTEST').find({ qty: { $gt: 4 } });
303+
cursor.count(true, { readPreference: ReadPreference.SECONDARY }, err => {
304+
expect(err).to.be.null;
305+
expect(internalClientCursor.getCall(0).args[2])
306+
.to.have.nested.property('readPreference')
307+
.that.deep.equals(expectedReadPreference);
308+
client.close();
309+
done();
310+
});
311+
});
312+
}
313+
});
314+
283315
/**
284316
* @ignore
285317
* @api private

0 commit comments

Comments
 (0)