From 71879c381f6aa3fd592cdc12a06d76f2d1b8a20d Mon Sep 17 00:00:00 2001 From: Rebecca Weinberger Date: Tue, 12 Jun 2018 09:53:51 -0400 Subject: [PATCH] fix(cursor): cursor count with collation fix When a collation set is applied to a cursor, the count method reflects this and returns the correct value. Fixes NODE-1369 --- lib/collection.js | 12 +--------- lib/cursor.js | 4 ++++ lib/utils.js | 27 +++++++++++++++++++++- test/functional/collations_tests.js | 36 ++++++++++++++++++++++++++++- 4 files changed, 66 insertions(+), 13 deletions(-) diff --git a/lib/collection.js b/lib/collection.js index 6f571c2ea8..65ec0c4519 100644 --- a/lib/collection.js +++ b/lib/collection.js @@ -13,6 +13,7 @@ const toError = require('./utils').toError; const normalizeHintField = require('./utils').normalizeHintField; const handleCallback = require('./utils').handleCallback; const decorateCommand = require('./utils').decorateCommand; +const decorateWithCollation = require('./utils').decorateWithCollation; const formattedOrderClause = require('./utils').formattedOrderClause; const ReadPreference = require('mongodb-core').ReadPreference; const CommandCursor = require('./command_cursor'); @@ -2279,17 +2280,6 @@ var findAndRemove = function(self, query, sort, options, callback) { self.findAndModify(query, sort, null, options, callback); }; -function decorateWithCollation(command, self, options) { - // Do we support collation 3.4 and higher - var capabilities = self.s.topology.capabilities(); - // Do we support write concerns 3.4 and higher - if (capabilities && capabilities.commandsTakeCollation) { - if (options.collation && typeof options.collation === 'object') { - command.collation = options.collation; - } - } -} - function decorateWithReadConcern(command, self, options) { let readConcern = Object.assign({}, command.readConcern || {}); if (self.s.readConcern) { diff --git a/lib/cursor.js b/lib/cursor.js index d96c187c17..9411263ef1 100644 --- a/lib/cursor.js +++ b/lib/cursor.js @@ -5,6 +5,7 @@ const f = require('util').format; const deprecate = require('util').deprecate; const formattedOrderClause = require('./utils').formattedOrderClause; const handleCallback = require('./utils').handleCallback; +const decorateWithCollation = require('./utils').decorateWithCollation; const ReadPreference = require('mongodb-core').ReadPreference; const MongoError = require('mongodb-core').MongoError; const Readable = require('stream').Readable; @@ -1013,6 +1014,9 @@ var count = function(self, applySkipLimit, opts, callback) { command.hint = self.s.cmd.hint; } + // Apply a collation if set + decorateWithCollation(command, self, self.s.cmd); + if (typeof opts.maxTimeMS === 'number') { command.maxTimeMS = opts.maxTimeMS; } else if (self.s.cmd && typeof self.s.cmd.maxTimeMS === 'number') { diff --git a/lib/utils.js b/lib/utils.js index b0d35a2935..8853d7476c 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -533,6 +533,30 @@ function isPromiseLike(maybePromise) { return maybePromise && typeof maybePromise.then === 'function'; } +/** + * Applies collation to a given command. + * + * @param {object} [command] the command on which to apply collation + * @param {(Cursor|Collection)} [target] target of command + * @param {object} [options] options containing collation settings + */ +function decorateWithCollation(command, target, options) { + const topology = target.s && target.s.topology; + + if (!topology) { + throw new TypeError('parameter "target" is missing a topology'); + } + + // Do we support collation 3.4 and higher + const capabilities = target.s.topology.capabilities(); + // Do we support write concerns 3.4 and higher + if (capabilities && capabilities.commandsTakeCollation) { + if (options.collation && typeof options.collation === 'object') { + command.collation = options.collation; + } + } +} + module.exports = { filterOptions, mergeOptions, @@ -554,5 +578,6 @@ module.exports = { executeOperation, applyWriteConcern, convertReadPreference, - isPromiseLike + isPromiseLike, + decorateWithCollation }; diff --git a/test/functional/collations_tests.js b/test/functional/collations_tests.js index 0ba0c829a2..1d89a0dfc3 100644 --- a/test/functional/collations_tests.js +++ b/test/functional/collations_tests.js @@ -3,6 +3,7 @@ var test = require('./shared').assert; var setupDatabase = require('./shared').setupDatabase; var co = require('co'); var mock = require('mongodb-mock-server'); +const expect = require('chai').expect; var defaultFields = { ismaster: true, @@ -1102,6 +1103,33 @@ describe('Collation', function() { } }); + it('cursor count method should return the correct number when used with collation set', { + metadata: { requires: { mongodb: '>=3.4.0' } }, + test: function(done) { + const configuration = this.configuration; + const client = configuration.newClient({ w: 1 }, { poolSize: 1, auto_reconnect: false }); + + client.connect(function(err, client) { + const db = client.db(configuration.db); + const docs = [{ _id: 0, name: 'foo' }, { _id: 1, name: 'Foo' }]; + const collation = { locale: 'en_US', strength: 2 }; + let collection, cursor; + const close = e => cursor.close(() => client.close(() => done(e))); + + Promise.resolve() + .then(() => db.createCollection('cursor_collation_count')) + .then(() => (collection = db.collection('cursor_collation_count'))) + .then(() => collection.insertMany(docs)) + .then(() => collection.find({ name: 'foo' }).collation(collation)) + .then(_cursor => (cursor = _cursor)) + .then(() => cursor.count()) + .then(val => expect(val).to.equal(2)) + .then(() => close()) + .catch(e => close(e)); + }); + } + }); + /****************************************************************************** .___ __ __ .__ | | _____/ |_ ____ ________________ _/ |_|__| ____ ____ @@ -1125,7 +1153,13 @@ describe('Collation', function() { var col = db.collection('collation_test'); // Create collation index col.createIndexes( - [{ key: { a: 1 }, collation: { locale: 'nn' }, name: 'collation_test' }], + [ + { + key: { a: 1 }, + collation: { locale: 'nn' }, + name: 'collation_test' + } + ], function(err) { test.equal(null, err);