diff --git a/lib/connection.js b/lib/connection.js index d35acdc4c19..baa70e554ee 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -652,7 +652,8 @@ Connection.prototype.onOpen = function() { * @param {String} [options.dbName] The name of the database we want to use. If not provided, use database name from connection string. * @param {String} [options.user] username for authentication, equivalent to `options.auth.user`. Maintained for backwards compatibility. * @param {String} [options.pass] password for authentication, equivalent to `options.auth.password`. Maintained for backwards compatibility. - * @param {Number} [options.poolSize=5] The maximum number of sockets the MongoDB driver will keep open for this connection. By default, `poolSize` is 5. Keep in mind that, as of MongoDB 3.4, MongoDB only allows one operation per socket at a time, so you may want to increase this if you find you have a few slow queries that are blocking faster queries from proceeding. See [Slow Trains in MongoDB and Node.js](http://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs). + * @param {Number} [options.maxPoolSize=5] The maximum number of sockets the MongoDB driver will keep open for this connection. Keep in mind that MongoDB only allows one operation per socket at a time, so you may want to increase this if you find you have a few slow queries that are blocking faster queries from proceeding. See [Slow Trains in MongoDB and Node.js](http://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs). + * @param {Number} [options.minPoolSize=1] The minimum number of sockets the MongoDB driver will keep open for this connection. Keep in mind that MongoDB only allows one operation per socket at a time, so you may want to increase this if you find you have a few slow queries that are blocking faster queries from proceeding. See [Slow Trains in MongoDB and Node.js](http://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs). * @param {Number} [options.serverSelectionTimeoutMS] If `useUnifiedTopology = true`, the MongoDB driver will try to find a server to send any given operation to, and keep retrying for `serverSelectionTimeoutMS` milliseconds before erroring out. If not set, the MongoDB driver defaults to using `30000` (30 seconds). * @param {Number} [options.heartbeatFrequencyMS] If `useUnifiedTopology = true`, the MongoDB driver sends a heartbeat every `heartbeatFrequencyMS` to check on the status of the connection. A heartbeat is subject to `serverSelectionTimeoutMS`, so the MongoDB driver will retry failed heartbeats for up to 30 seconds by default. Mongoose only emits a `'disconnected'` event after a heartbeat has failed, so you may want to decrease this setting to reduce the time between when your server goes down and when Mongoose emits `'disconnected'`. We recommend you do **not** set this setting below 1000, too many heartbeats can lead to performance degradation. * @param {Boolean} [options.autoIndex=true] Mongoose-specific option. Set to false to disable automatic index creation for all models associated with this connection. @@ -776,7 +777,13 @@ Connection.prototype.openUri = function(uri, options, callback) { } const promise = new Promise((resolve, reject) => { - const client = new mongodb.MongoClient(uri, options); + let client; + try { + client = new mongodb.MongoClient(uri, options); + } catch (error) { + _this.readyState = STATES.disconnected; + return reject(error); + } _this.client = client; client.connect((error) => { if (error) { @@ -991,6 +998,10 @@ Connection.prototype.onClose = function(force) { } this.emit('close', force); + + for (const db of this.otherDbs) { + db.close(force); + } }; /** diff --git a/lib/index.js b/lib/index.js index 12dd03565e1..934a4e58568 100644 --- a/lib/index.js +++ b/lib/index.js @@ -256,7 +256,8 @@ Mongoose.prototype.get = Mongoose.prototype.set; * @param {Number} [options.reconnectTries=30] If you're connected to a single server or mongos proxy (as opposed to a replica set), the MongoDB driver will try to reconnect every `reconnectInterval` milliseconds for `reconnectTries` times, and give up afterward. When the driver gives up, the mongoose connection emits a `reconnectFailed` event. This option does nothing for replica set connections. * @param {Number} [options.reconnectInterval=1000] See `reconnectTries` option above. * @param {Class} [options.promiseLibrary] Sets the [underlying driver's promise library](http://mongodb.github.io/node-mongodb-native/3.1/api/MongoClient.html). - * @param {Number} [options.poolSize=5] The maximum number of sockets the MongoDB driver will keep open for this connection. By default, `poolSize` is 5. Keep in mind that, as of MongoDB 3.4, MongoDB only allows one operation per socket at a time, so you may want to increase this if you find you have a few slow queries that are blocking faster queries from proceeding. See [Slow Trains in MongoDB and Node.js](http://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs). + * @param {Number} [options.maxPoolSize=5] The maximum number of sockets the MongoDB driver will keep open for this connection. Keep in mind that MongoDB only allows one operation per socket at a time, so you may want to increase this if you find you have a few slow queries that are blocking faster queries from proceeding. See [Slow Trains in MongoDB and Node.js](http://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs). + * @param {Number} [options.minPoolSize=1] The minimum number of sockets the MongoDB driver will keep open for this connection. Keep in mind that MongoDB only allows one operation per socket at a time, so you may want to increase this if you find you have a few slow queries that are blocking faster queries from proceeding. See [Slow Trains in MongoDB and Node.js](http://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs). * @param {Number} [options.connectTimeoutMS=30000] How long the MongoDB driver will wait before killing a socket due to inactivity _during initial connection_. Defaults to 30000. This option is passed transparently to [Node.js' `socket#setTimeout()` function](https://nodejs.org/api/net.html#net_socket_settimeout_timeout_callback). * @param {Number} [options.socketTimeoutMS=30000] How long the MongoDB driver will wait before killing a socket due to inactivity _after initial connection_. A socket may be inactive because of either no activity or a long-running operation. This is set to `30000` by default, you should set this to 2-3x your longest running operation if you expect some of your database operations to run longer than 20 seconds. This option is passed to [Node.js `socket#setTimeout()` function](https://nodejs.org/api/net.html#net_socket_settimeout_timeout_callback) after the MongoDB driver successfully completes. * @param {Number} [options.family=0] Passed transparently to [Node.js' `dns.lookup()`](https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback) function. May be either `0`, `4`, or `6`. `4` means use IPv4 only, `6` means use IPv6 only, `0` means try both. diff --git a/lib/model.js b/lib/model.js index 562809b22af..58545a358d0 100644 --- a/lib/model.js +++ b/lib/model.js @@ -360,8 +360,8 @@ Model.prototype.$__save = function(options, callback) { if (result) { if (Array.isArray(result)) { numAffected = result.length; - } else if (result.modifiedCount != null) { - numAffected = result.modifiedCount; + } else if (result.matchedCount != null) { + numAffected = result.matchedCount; } else { numAffected = result; } @@ -4052,93 +4052,6 @@ Model.validate = function validate(obj, pathsToValidate, context, callback) { }); }; -/** - * Implements `$geoSearch` functionality for Mongoose - * - * This function does not trigger any middleware - * - * ####Example: - * - * const options = { near: [10, 10], maxDistance: 5 }; - * Locations.geoSearch({ type : "house" }, options, function(err, res) { - * console.log(res); - * }); - * - * ####Options: - * - `near` {Array} x,y point to search for - * - `maxDistance` {Number} the maximum distance from the point near that a result can be - * - `limit` {Number} The maximum number of results to return - * - `lean` {Object|Boolean} return the raw object instead of the Mongoose Model - * - * @param {Object} conditions an object that specifies the match condition (required) - * @param {Object} options for the geoSearch, some (near, maxDistance) are required - * @param {Object|Boolean} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](/docs/api.html#query_Query-lean) and the [Mongoose lean tutorial](/docs/tutorials/lean.html). - * @param {Function} [callback] optional callback - * @return {Promise} - * @see http://docs.mongodb.org/manual/reference/command/geoSearch/ - * @see http://docs.mongodb.org/manual/core/geohaystack/ - * @api public - */ - -Model.geoSearch = function(conditions, options, callback) { - _checkContext(this, 'geoSearch'); - - if (typeof options === 'function') { - callback = options; - options = {}; - } - - callback = this.$handleCallbackError(callback); - - return this.db.base._promiseOrCallback(callback, cb => { - cb = this.$wrapCallback(cb); - let error; - if (conditions === undefined || !utils.isObject(conditions)) { - error = new MongooseError('Must pass conditions to geoSearch'); - } else if (!options.near) { - error = new MongooseError('Must specify the near option in geoSearch'); - } else if (!Array.isArray(options.near)) { - error = new MongooseError('near option must be an array [x, y]'); - } - - if (error) { - return cb(error); - } - - // send the conditions in the options object - options.search = conditions; - - this.collection.geoHaystackSearch(options.near[0], options.near[1], options, (err, res) => { - if (err) { - return cb(err); - } - - let count = res.results.length; - if (options.lean || count === 0) { - return cb(null, res.results); - } - - const errSeen = false; - - function init(err) { - if (err && !errSeen) { - return cb(err); - } - - if (!--count && !errSeen) { - cb(null, res.results); - } - } - - for (let i = 0; i < res.results.length; ++i) { - const temp = res.results[i]; - res.results[i] = new this(); - res.results[i].init(temp, {}, init); - } - }); - }, this.events); -}; - /** * Populates document references. * diff --git a/test/connection.test.js b/test/connection.test.js index eddf3084653..4237d5f3ec6 100644 --- a/test/connection.test.js +++ b/test/connection.test.js @@ -259,6 +259,7 @@ describe('connections:', function() { yield db.openUri('fail connection'); } catch (err) { assert.ok(err); + assert.equal(err.name, 'MongoParseError'); threw = true; } @@ -511,7 +512,6 @@ describe('connections:', function() { const db2 = db.useDb('mongoose2'); assert.equal('mongoose2', db2.name); - assert.equal('mongoose1', db.name); assert.equal(db2.port, db.port); assert.equal(db2.replica, db.replica); diff --git a/test/docs/validation.test.js b/test/docs/validation.test.js index ff97d58fe67..91a8b7a40e5 100644 --- a/test/docs/validation.test.js +++ b/test/docs/validation.test.js @@ -10,7 +10,8 @@ describe('validation docs', function() { before(function() { db = mongoose.createConnection('mongodb://localhost:27017/mongoose_test', { - poolSize: 1 + minPoolSize: 1, + maxPoolSize: 1 }); }); diff --git a/test/document.test.js b/test/document.test.js index 01f0782f5a4..2f4e3daaf3f 100644 --- a/test/document.test.js +++ b/test/document.test.js @@ -2073,7 +2073,7 @@ describe('document', function() { } catch (err) { assert.equal(err instanceof DocumentNotFoundError, true); - assert.equal(err.message, `No document found for query "{ _id: ${person._id} }" on model "Person"`); + assert.equal(err.message, `No document found for query "{ _id: ObjectId("${person._id}") }" on model "Person"`); threw = true; } @@ -2098,7 +2098,7 @@ describe('document', function() { } catch (err) { assert.equal(err instanceof DocumentNotFoundError, true); - assert.equal(err.message, `No document found for query "{ _id: ${person._id} }" on model "Person"`); + assert.equal(err.message, `No document found for query "{ _id: ObjectId("${person._id}") }" on model "Person"`); threw = true; } diff --git a/test/es-next/lean.test.es6.js b/test/es-next/lean.test.es6.js index 2c09e166ecc..34e962d3e6f 100644 --- a/test/es-next/lean.test.es6.js +++ b/test/es-next/lean.test.es6.js @@ -34,16 +34,16 @@ describe('Lean Tutorial', function() { // To enable the `lean` option for a query, use the `lean()` function. const leanDoc = await MyModel.findOne().lean(); - sizeof(normalDoc); // >= 1000 - sizeof(leanDoc); // 86, 10x smaller! + sizeof(normalDoc); // approximately 1000 + sizeof(leanDoc); // 36, more than 10x smaller! // In case you were wondering, the JSON form of a Mongoose doc is the same // as the POJO. This additional memory only affects how much memory your // Node.js process uses, not how much data is sent over the network. JSON.stringify(normalDoc).length === JSON.stringify(leanDoc.length); // true // acquit:ignore:start - assert.ok(sizeof(normalDoc) >= 1000); - assert.equal(sizeof(leanDoc), 86); + assert.ok(sizeof(normalDoc) >= 750 && sizeof(normalDoc) <= 1250, sizeof(normalDoc)); + assert.equal(sizeof(leanDoc), 36); assert.equal(JSON.stringify(normalDoc).length, JSON.stringify(leanDoc).length); // acquit:ignore:end }); diff --git a/test/index.test.js b/test/index.test.js index fdbc5dbaf62..234e19aa38c 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -751,7 +751,8 @@ describe('mongoose module:', function() { it('connect with url doesnt cause unhandled rejection (gh-6997)', function(done) { const m = new mongoose.Mongoose; - m.connect('mongodb://doesnotexist:27009/test', options, function(error) { + const _options = Object.assign({}, options, { serverSelectionTimeoutMS: 100 }); + m.connect('mongodb://doesnotexist:27009/test', _options, function(error) { assert.ok(error); done(); }); diff --git a/test/model.geosearch.test.js b/test/model.geosearch.test.js deleted file mode 100644 index 55e1e002713..00000000000 --- a/test/model.geosearch.test.js +++ /dev/null @@ -1,177 +0,0 @@ -'use strict'; - -const start = require('./common'); - -const assert = require('assert'); - -const mongoose = start.mongoose; -const Schema = mongoose.Schema; - -describe('model', function() { - let db, schema; - let Geo; - - before(function() { - schema = new Schema({ - pos: [Number], - complex: {}, - type: String - }); - schema.index({ pos: 'geoHaystack', type: 1 }, { bucketSize: 1 }); - db = start(); - - Geo = db.model('Test', schema); - }); - - after(function(done) { - db.close(done); - }); - - afterEach(() => Geo.deleteMany({})); - - describe('geoSearch', function() { - this.timeout(process.env.TRAVIS ? 8000 : 4500); - - it('works', function(done) { - assert.ok(Geo.geoSearch instanceof Function); - - Geo.init(function() { - const geos = []; - geos[0] = new Geo({ pos: [10, 10], type: 'place' }); - geos[1] = new Geo({ pos: [15, 5], type: 'place' }); - geos[2] = new Geo({ pos: [20, 15], type: 'house' }); - geos[3] = new Geo({ pos: [1, -1], type: 'house' }); - let count = geos.length; - - for (const geo of geos) { - geo.save(function(err) { - assert.ifError(err); - --count || next(); - }); - } - - function next() { - Geo.geoSearch({ type: 'place' }, { near: [9, 9], maxDistance: 5 }, function(err, results) { - assert.ifError(err); - assert.equal(results.length, 1); - - assert.equal(results[0].type, 'place'); - assert.equal(results[0].pos.length, 2); - assert.equal(results[0].pos[0], 10); - assert.equal(results[0].pos[1], 10); - assert.equal(results[0].id, geos[0].id); - assert.ok(results[0] instanceof Geo); - - Geo.geoSearch({ type: 'place' }, { near: [40, 40], maxDistance: 5 }, function(err, results) { - assert.ifError(err); - assert.equal(results.length, 0); - done(); - }); - }); - } - }); - }); - it('works with lean', function(done) { - assert.ok(Geo.geoSearch instanceof Function); - - Geo.init(function(err) { - assert.ifError(err); - - const geos = []; - geos[0] = new Geo({ pos: [10, 10], type: 'place' }); - geos[1] = new Geo({ pos: [15, 5], type: 'place' }); - geos[2] = new Geo({ pos: [20, 15], type: 'house' }); - geos[3] = new Geo({ pos: [1, -1], type: 'house' }); - let count = geos.length; - - for (const geo of geos) { - geo.save(function(err) { - assert.ifError(err); - --count || next(); - }); - } - - function next() { - Geo.geoSearch({ type: 'place' }, { near: [9, 9], maxDistance: 5, lean: true }, function(err, results) { - assert.ifError(err); - assert.equal(results.length, 1); - - assert.equal(results[0].type, 'place'); - assert.equal(results[0].pos.length, 2); - assert.equal(results[0].pos[0], 10); - assert.equal(results[0].pos[1], 10); - assert.equal(results[0]._id, geos[0].id); - assert.strictEqual(results[0].id, undefined); - assert.ok(!(results[0] instanceof Geo)); - done(); - }); - } - }); - }); - it('throws the correct error messages', function(done) { - assert.ok(Geo.geoSearch instanceof Function); - - Geo.init(function(err) { - assert.ifError(err); - - const g = new Geo({ pos: [10, 10], type: 'place' }); - g.save(function() { - Geo.geoSearch([], {}, function(e) { - assert.ok(e); - assert.equal(e.message, 'Must pass conditions to geoSearch'); - - Geo.geoSearch({ type: 'test' }, {}, function(e) { - assert.ok(e); - assert.equal(e.message, 'Must specify the near option in geoSearch'); - - Geo.geoSearch({ type: 'test' }, { near: 'hello' }, function(e) { - assert.ok(e); - assert.equal(e.message, 'near option must be an array [x, y]'); - - Geo.geoSearch({ type: 'test' }, { near: [1, 2] }, function(err) { - assert.ok(err); - assert.ok(/maxDistance needs a number/.test(err)); - done(); - }); - }); - }); - }); - }); - }); - }); - - it('returns a promise (gh-1614)', function(done) { - Geo.init(function() { - const prom = Geo.geoSearch({ type: 'place' }, { near: [9, 9], maxDistance: 5 }); - assert.ok(prom instanceof mongoose.Promise); - - prom.then(() => done(), err => done(err)); - }); - }); - - it('allows not passing a callback (gh-1614)', function(done) { - Geo.init(function(err) { - assert.ifError(err); - const g = new Geo({ pos: [10, 10], type: 'place' }); - g.save(function(err) { - assert.ifError(err); - - let promise; - assert.doesNotThrow(function() { - promise = Geo.geoSearch({ type: 'place' }, { near: [9, 9], maxDistance: 5 }); - }); - function validate(ret) { - assert.equal(ret.length, 1); - assert.equal(ret[0].pos[0], 10); - assert.equal(ret[0].pos[1], 10); - } - - function finish() { - done(); - } - promise.then(validate, assert.ifError).then(finish); - }); - }); - }); - }); -}); diff --git a/test/model.populate.test.js b/test/model.populate.test.js index 2b670220f05..7f1c3b399e4 100644 --- a/test/model.populate.test.js +++ b/test/model.populate.test.js @@ -3233,12 +3233,12 @@ describe('model: populate:', function() { const User = db.model('User', UserSchema); const user = { - _id: mongoose.Types.ObjectId(), + _id: new mongoose.Types.ObjectId(), name: 'Arnold' }; const post = { - _id: mongoose.Types.ObjectId(), + _id: new mongoose.Types.ObjectId(), comments: [ {}, { diff --git a/test/model.query.casting.test.js b/test/model.query.casting.test.js index 981dfb7249a..b1de963f164 100644 --- a/test/model.query.casting.test.js +++ b/test/model.query.casting.test.js @@ -818,7 +818,7 @@ describe('model query casting', function() { describe('$elemMatch', function() { it('should cast String to ObjectId in $elemMatch', function(done) { - const commentId = mongoose.Types.ObjectId(111); + const commentId = new mongoose.Types.ObjectId(111); const post = new BlogPostB({ comments: [{ _id: commentId }] }); const id = post._id.toString(); @@ -836,7 +836,7 @@ describe('model query casting', function() { }); it('should cast String to ObjectId in $elemMatch inside $not', function(done) { - const commentId = mongoose.Types.ObjectId(111); + const commentId = new mongoose.Types.ObjectId(111); const post = new BlogPostB({ comments: [{ _id: commentId }] }); const id = post._id.toString(); diff --git a/test/query.test.js b/test/query.test.js index 2f46dac4f1d..8f0ad74fd8a 100644 --- a/test/query.test.js +++ b/test/query.test.js @@ -1189,57 +1189,6 @@ describe('Query', function() { }); }, done); }); - - it('single option, default', function(done) { - const Test = db.model('Person', new Schema({ name: String })); - - Test.create([{ name: 'Eddard Stark' }, { name: 'Robb Stark' }], function(error) { - assert.ifError(error); - Test.deleteMany({ name: /Stark/ }).exec(function(error, res) { - assert.ifError(error); - assert.equal(res.n, 2); - Test.countDocuments({}, function(error, count) { - assert.ifError(error); - assert.equal(count, 0); - done(); - }); - }); - }); - }); - - it.skip('single option, false', function(done) { - const Test = db.model('Person', new Schema({ name: String })); - - Test.create([{ name: 'Eddard Stark' }, { name: 'Robb Stark' }], function(error) { - assert.ifError(error); - Test.remove({ name: /Stark/ }).setOptions({ single: false }).exec(function(error, res) { - assert.ifError(error); - assert.equal(res.n, 2); - Test.countDocuments({}, function(error, count) { - assert.ifError(error); - assert.equal(count, 0); - done(); - }); - }); - }); - }); - - it.skip('single option, true', function(done) { - const Test = db.model('Person', new Schema({ name: String })); - - Test.create([{ name: 'Eddard Stark' }, { name: 'Robb Stark' }], function(error) { - assert.ifError(error); - Test.remove({ name: /Stark/ }).setOptions({ single: true }).exec(function(error, res) { - assert.ifError(error); - assert.equal(res.n, 1); - Test.countDocuments({}, function(error, count) { - assert.ifError(error); - assert.equal(count, 1); - done(); - }); - }); - }); - }); }); describe('querying/updating with model instance containing embedded docs should work (#454)', function() { diff --git a/test/schema.onthefly.test.js b/test/schema.onthefly.test.js index 67411e6aa52..f88b2612b31 100644 --- a/test/schema.onthefly.test.js +++ b/test/schema.onthefly.test.js @@ -129,7 +129,7 @@ describe('schema.onthefly', function() { assert.equal(typeof d.get('title', Number), 'number'); d.title = '000000000000000000000001'; - assert.equal(d.get('title', ObjectId).constructor.name, 'ObjectID'); + assert.equal(d.get('title', ObjectId).constructor.name, 'ObjectId'); d.set('title', 1, Number); assert.equal(typeof d.get('title'), 'number');