connection.js 41 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286
  1. 'use strict';
  2. /*!
  3. * Module dependencies.
  4. */
  5. const ChangeStream = require('./cursor/ChangeStream');
  6. const EventEmitter = require('events').EventEmitter;
  7. const Schema = require('./schema');
  8. const Collection = require('./driver').get().Collection;
  9. const STATES = require('./connectionstate');
  10. const MongooseError = require('./error/index');
  11. const PromiseProvider = require('./promise_provider');
  12. const ServerSelectionError = require('./error/serverSelection');
  13. const applyPlugins = require('./helpers/schema/applyPlugins');
  14. const promiseOrCallback = require('./helpers/promiseOrCallback');
  15. const get = require('./helpers/get');
  16. const immediate = require('./helpers/immediate');
  17. const mongodb = require('mongodb');
  18. const pkg = require('../package.json');
  19. const utils = require('./utils');
  20. const parseConnectionString = require('mongodb/lib/core').parseConnectionString;
  21. let id = 0;
  22. /*!
  23. * A list of authentication mechanisms that don't require a password for authentication.
  24. * This is used by the authMechanismDoesNotRequirePassword method.
  25. *
  26. * @api private
  27. */
  28. const noPasswordAuthMechanisms = [
  29. 'MONGODB-X509'
  30. ];
  31. /**
  32. * Connection constructor
  33. *
  34. * For practical reasons, a Connection equals a Db.
  35. *
  36. * @param {Mongoose} base a mongoose instance
  37. * @inherits NodeJS EventEmitter http://nodejs.org/api/events.html#events_class_events_eventemitter
  38. * @event `connecting`: Emitted when `connection.openUri()` is executed on this connection.
  39. * @event `connected`: Emitted when this connection successfully connects to the db. May be emitted _multiple_ times in `reconnected` scenarios.
  40. * @event `open`: Emitted after we `connected` and `onOpen` is executed on all of this connections models.
  41. * @event `disconnecting`: Emitted when `connection.close()` was executed.
  42. * @event `disconnected`: Emitted after getting disconnected from the db.
  43. * @event `close`: Emitted after we `disconnected` and `onClose` executed on all of this connections models.
  44. * @event `reconnected`: Emitted after we `connected` and subsequently `disconnected`, followed by successfully another successful connection.
  45. * @event `error`: Emitted when an error occurs on this connection.
  46. * @event `fullsetup`: Emitted after the driver has connected to primary and all secondaries if specified in the connection string.
  47. * @api public
  48. */
  49. function Connection(base) {
  50. this.base = base;
  51. this.collections = {};
  52. this.models = {};
  53. this.config = { autoIndex: true };
  54. this.replica = false;
  55. this.options = null;
  56. this.otherDbs = []; // FIXME: To be replaced with relatedDbs
  57. this.relatedDbs = {}; // Hashmap of other dbs that share underlying connection
  58. this.states = STATES;
  59. this._readyState = STATES.disconnected;
  60. this._closeCalled = false;
  61. this._hasOpened = false;
  62. this.plugins = [];
  63. this.id = id++;
  64. }
  65. /*!
  66. * Inherit from EventEmitter
  67. */
  68. Connection.prototype.__proto__ = EventEmitter.prototype;
  69. /**
  70. * Connection ready state
  71. *
  72. * - 0 = disconnected
  73. * - 1 = connected
  74. * - 2 = connecting
  75. * - 3 = disconnecting
  76. *
  77. * Each state change emits its associated event name.
  78. *
  79. * ####Example
  80. *
  81. * conn.on('connected', callback);
  82. * conn.on('disconnected', callback);
  83. *
  84. * @property readyState
  85. * @memberOf Connection
  86. * @instance
  87. * @api public
  88. */
  89. Object.defineProperty(Connection.prototype, 'readyState', {
  90. get: function() {
  91. return this._readyState;
  92. },
  93. set: function(val) {
  94. if (!(val in STATES)) {
  95. throw new Error('Invalid connection state: ' + val);
  96. }
  97. if (this._readyState !== val) {
  98. this._readyState = val;
  99. // [legacy] loop over the otherDbs on this connection and change their state
  100. for (const db of this.otherDbs) {
  101. db.readyState = val;
  102. }
  103. // loop over relatedDbs on this connection and change their state
  104. for (const k in this.relatedDbs) {
  105. this.relatedDbs[k].readyState = val;
  106. }
  107. if (STATES.connected === val) {
  108. this._hasOpened = true;
  109. }
  110. this.emit(STATES[val]);
  111. }
  112. }
  113. });
  114. /**
  115. * Gets the value of the option `key`. Equivalent to `conn.options[key]`
  116. *
  117. * ####Example:
  118. *
  119. * conn.get('test'); // returns the 'test' value
  120. *
  121. * @param {String} key
  122. * @method get
  123. * @api public
  124. */
  125. Connection.prototype.get = function(key) {
  126. return get(this.options, key);
  127. };
  128. /**
  129. * Sets the value of the option `key`. Equivalent to `conn.options[key] = val`
  130. *
  131. * Supported options include:
  132. *
  133. * - `maxTimeMS`: Set [`maxTimeMS`](/docs/api.html#query_Query-maxTimeMS) for all queries on this connection.
  134. * - `useFindAndModify`: Set to `false` to work around the [`findAndModify()` deprecation warning](/docs/deprecations.html#findandmodify)
  135. *
  136. * ####Example:
  137. *
  138. * conn.set('test', 'foo');
  139. * conn.get('test'); // 'foo'
  140. * conn.options.test; // 'foo'
  141. *
  142. * @param {String} key
  143. * @param {Any} val
  144. * @method set
  145. * @api public
  146. */
  147. Connection.prototype.set = function(key, val) {
  148. this.options = this.options || {};
  149. this.options[key] = val;
  150. return val;
  151. };
  152. /**
  153. * A hash of the collections associated with this connection
  154. *
  155. * @property collections
  156. * @memberOf Connection
  157. * @instance
  158. * @api public
  159. */
  160. Connection.prototype.collections;
  161. /**
  162. * The name of the database this connection points to.
  163. *
  164. * ####Example
  165. *
  166. * mongoose.createConnection('mongodb://localhost:27017/mydb').name; // "mydb"
  167. *
  168. * @property name
  169. * @memberOf Connection
  170. * @instance
  171. * @api public
  172. */
  173. Connection.prototype.name;
  174. /**
  175. * A [POJO](https://masteringjs.io/tutorials/fundamentals/pojo) containing
  176. * a map from model names to models. Contains all models that have been
  177. * added to this connection using [`Connection#model()`](/docs/api/connection.html#connection_Connection-model).
  178. *
  179. * ####Example
  180. *
  181. * const conn = mongoose.createConnection();
  182. * const Test = conn.model('Test', mongoose.Schema({ name: String }));
  183. *
  184. * Object.keys(conn.models).length; // 1
  185. * conn.models.Test === Test; // true
  186. *
  187. * @property models
  188. * @memberOf Connection
  189. * @instance
  190. * @api public
  191. */
  192. Connection.prototype.models;
  193. /**
  194. * A number identifier for this connection. Used for debugging when
  195. * you have [multiple connections](/docs/connections.html#multiple_connections).
  196. *
  197. * ####Example
  198. *
  199. * // The default connection has `id = 0`
  200. * mongoose.connection.id; // 0
  201. *
  202. * // If you create a new connection, Mongoose increments id
  203. * const conn = mongoose.createConnection();
  204. * conn.id; // 1
  205. *
  206. * @property id
  207. * @memberOf Connection
  208. * @instance
  209. * @api public
  210. */
  211. Connection.prototype.id;
  212. /**
  213. * The plugins that will be applied to all models created on this connection.
  214. *
  215. * ####Example:
  216. *
  217. * const db = mongoose.createConnection('mongodb://localhost:27017/mydb');
  218. * db.plugin(() => console.log('Applied'));
  219. * db.plugins.length; // 1
  220. *
  221. * db.model('Test', new Schema({})); // Prints "Applied"
  222. *
  223. * @property plugins
  224. * @memberOf Connection
  225. * @instance
  226. * @api public
  227. */
  228. Object.defineProperty(Connection.prototype, 'plugins', {
  229. configurable: false,
  230. enumerable: true,
  231. writable: true
  232. });
  233. /**
  234. * The host name portion of the URI. If multiple hosts, such as a replica set,
  235. * this will contain the first host name in the URI
  236. *
  237. * ####Example
  238. *
  239. * mongoose.createConnection('mongodb://localhost:27017/mydb').host; // "localhost"
  240. *
  241. * @property host
  242. * @memberOf Connection
  243. * @instance
  244. * @api public
  245. */
  246. Object.defineProperty(Connection.prototype, 'host', {
  247. configurable: true,
  248. enumerable: true,
  249. writable: true
  250. });
  251. /**
  252. * The port portion of the URI. If multiple hosts, such as a replica set,
  253. * this will contain the port from the first host name in the URI.
  254. *
  255. * ####Example
  256. *
  257. * mongoose.createConnection('mongodb://localhost:27017/mydb').port; // 27017
  258. *
  259. * @property port
  260. * @memberOf Connection
  261. * @instance
  262. * @api public
  263. */
  264. Object.defineProperty(Connection.prototype, 'port', {
  265. configurable: true,
  266. enumerable: true,
  267. writable: true
  268. });
  269. /**
  270. * The username specified in the URI
  271. *
  272. * ####Example
  273. *
  274. * mongoose.createConnection('mongodb://val:psw@localhost:27017/mydb').user; // "val"
  275. *
  276. * @property user
  277. * @memberOf Connection
  278. * @instance
  279. * @api public
  280. */
  281. Object.defineProperty(Connection.prototype, 'user', {
  282. configurable: true,
  283. enumerable: true,
  284. writable: true
  285. });
  286. /**
  287. * The password specified in the URI
  288. *
  289. * ####Example
  290. *
  291. * mongoose.createConnection('mongodb://val:psw@localhost:27017/mydb').pass; // "psw"
  292. *
  293. * @property pass
  294. * @memberOf Connection
  295. * @instance
  296. * @api public
  297. */
  298. Object.defineProperty(Connection.prototype, 'pass', {
  299. configurable: true,
  300. enumerable: true,
  301. writable: true
  302. });
  303. /**
  304. * The mongodb.Db instance, set when the connection is opened
  305. *
  306. * @property db
  307. * @memberOf Connection
  308. * @instance
  309. * @api public
  310. */
  311. Connection.prototype.db;
  312. /**
  313. * A hash of the global options that are associated with this connection
  314. *
  315. * @property config
  316. * @memberOf Connection
  317. * @instance
  318. * @api public
  319. */
  320. Connection.prototype.config;
  321. /**
  322. * Helper for `createCollection()`. Will explicitly create the given collection
  323. * with specified options. Used to create [capped collections](https://docs.mongodb.com/manual/core/capped-collections/)
  324. * and [views](https://docs.mongodb.com/manual/core/views/) from mongoose.
  325. *
  326. * Options are passed down without modification to the [MongoDB driver's `createCollection()` function](http://mongodb.github.io/node-mongodb-native/2.2/api/Db.html#createCollection)
  327. *
  328. * @method createCollection
  329. * @param {string} collection The collection to create
  330. * @param {Object} [options] see [MongoDB driver docs](http://mongodb.github.io/node-mongodb-native/2.2/api/Db.html#createCollection)
  331. * @param {Function} [callback]
  332. * @return {Promise}
  333. * @api public
  334. */
  335. Connection.prototype.createCollection = _wrapConnHelper(function createCollection(collection, options, cb) {
  336. if (typeof options === 'function') {
  337. cb = options;
  338. options = {};
  339. }
  340. this.db.createCollection(collection, options, cb);
  341. });
  342. /**
  343. * _Requires MongoDB >= 3.6.0._ Starts a [MongoDB session](https://docs.mongodb.com/manual/release-notes/3.6/#client-sessions)
  344. * for benefits like causal consistency, [retryable writes](https://docs.mongodb.com/manual/core/retryable-writes/),
  345. * and [transactions](http://thecodebarbarian.com/a-node-js-perspective-on-mongodb-4-transactions.html).
  346. *
  347. * ####Example:
  348. *
  349. * const session = await conn.startSession();
  350. * let doc = await Person.findOne({ name: 'Ned Stark' }, null, { session });
  351. * await doc.remove();
  352. * // `doc` will always be null, even if reading from a replica set
  353. * // secondary. Without causal consistency, it is possible to
  354. * // get a doc back from the below query if the query reads from a
  355. * // secondary that is experiencing replication lag.
  356. * doc = await Person.findOne({ name: 'Ned Stark' }, null, { session, readPreference: 'secondary' });
  357. *
  358. *
  359. * @method startSession
  360. * @param {Object} [options] see the [mongodb driver options](http://mongodb.github.io/node-mongodb-native/3.0/api/MongoClient.html#startSession)
  361. * @param {Boolean} [options.causalConsistency=true] set to false to disable causal consistency
  362. * @param {Function} [callback]
  363. * @return {Promise<ClientSession>} promise that resolves to a MongoDB driver `ClientSession`
  364. * @api public
  365. */
  366. Connection.prototype.startSession = _wrapConnHelper(function startSession(options, cb) {
  367. if (typeof options === 'function') {
  368. cb = options;
  369. options = null;
  370. }
  371. const session = this.client.startSession(options);
  372. cb(null, session);
  373. });
  374. /**
  375. * Helper for `dropCollection()`. Will delete the given collection, including
  376. * all documents and indexes.
  377. *
  378. * @method dropCollection
  379. * @param {string} collection The collection to delete
  380. * @param {Function} [callback]
  381. * @return {Promise}
  382. * @api public
  383. */
  384. Connection.prototype.dropCollection = _wrapConnHelper(function dropCollection(collection, cb) {
  385. this.db.dropCollection(collection, cb);
  386. });
  387. /**
  388. * Helper for `dropDatabase()`. Deletes the given database, including all
  389. * collections, documents, and indexes.
  390. *
  391. * ####Example:
  392. *
  393. * const conn = mongoose.createConnection('mongodb://localhost:27017/mydb');
  394. * // Deletes the entire 'mydb' database
  395. * await conn.dropDatabase();
  396. *
  397. * @method dropDatabase
  398. * @param {Function} [callback]
  399. * @return {Promise}
  400. * @api public
  401. */
  402. Connection.prototype.dropDatabase = _wrapConnHelper(function dropDatabase(cb) {
  403. // If `dropDatabase()` is called, this model's collection will not be
  404. // init-ed. It is sufficiently common to call `dropDatabase()` after
  405. // `mongoose.connect()` but before creating models that we want to
  406. // support this. See gh-6967
  407. for (const name of Object.keys(this.models)) {
  408. delete this.models[name].$init;
  409. }
  410. this.db.dropDatabase(cb);
  411. });
  412. /*!
  413. * ignore
  414. */
  415. function _wrapConnHelper(fn) {
  416. return function() {
  417. const cb = arguments.length > 0 ? arguments[arguments.length - 1] : null;
  418. const argsWithoutCb = typeof cb === 'function' ?
  419. Array.prototype.slice.call(arguments, 0, arguments.length - 1) :
  420. Array.prototype.slice.call(arguments);
  421. const disconnectedError = new MongooseError('Connection ' + this.id +
  422. ' was disconnected when calling `' + fn.name + '`');
  423. return promiseOrCallback(cb, cb => {
  424. // Make it ok to call collection helpers before `mongoose.connect()`
  425. // as long as `mongoose.connect()` is called on the same tick.
  426. // Re: gh-8534
  427. immediate(() => {
  428. if (this.readyState === STATES.connecting) {
  429. this.once('open', function() {
  430. fn.apply(this, argsWithoutCb.concat([cb]));
  431. });
  432. } else if (this.readyState === STATES.disconnected && this.db == null) {
  433. cb(disconnectedError);
  434. } else {
  435. fn.apply(this, argsWithoutCb.concat([cb]));
  436. }
  437. });
  438. });
  439. };
  440. }
  441. /**
  442. * error
  443. *
  444. * Graceful error handling, passes error to callback
  445. * if available, else emits error on the connection.
  446. *
  447. * @param {Error} err
  448. * @param {Function} callback optional
  449. * @api private
  450. */
  451. Connection.prototype.error = function(err, callback) {
  452. if (callback) {
  453. callback(err);
  454. return null;
  455. }
  456. if (this.listeners('error').length > 0) {
  457. this.emit('error', err);
  458. }
  459. return Promise.reject(err);
  460. };
  461. /**
  462. * Called when the connection is opened
  463. *
  464. * @api private
  465. */
  466. Connection.prototype.onOpen = function() {
  467. this.readyState = STATES.connected;
  468. // avoid having the collection subscribe to our event emitter
  469. // to prevent 0.3 warning
  470. for (const i in this.collections) {
  471. if (utils.object.hasOwnProperty(this.collections, i)) {
  472. this.collections[i].onOpen();
  473. }
  474. }
  475. this.emit('open');
  476. };
  477. /**
  478. * Opens the connection with a URI using `MongoClient.connect()`.
  479. *
  480. * @param {String} uri The URI to connect with.
  481. * @param {Object} [options] Passed on to http://mongodb.github.io/node-mongodb-native/2.2/api/MongoClient.html#connect
  482. * @param {Boolean} [options.bufferCommands=true] Mongoose specific option. Set to false to [disable buffering](http://mongoosejs.com/docs/faq.html#callback_never_executes) on all models associated with this connection.
  483. * @param {String} [options.dbName] The name of the database we want to use. If not provided, use database name from connection string.
  484. * @param {String} [options.user] username for authentication, equivalent to `options.auth.user`. Maintained for backwards compatibility.
  485. * @param {String} [options.pass] password for authentication, equivalent to `options.auth.password`. Maintained for backwards compatibility.
  486. * @param {Boolean} [options.autoIndex=true] Mongoose-specific option. Set to false to disable automatic index creation for all models associated with this connection.
  487. * @param {Boolean} [options.useNewUrlParser=false] False by default. Set to `true` to opt in to the MongoDB driver's new URL parser logic.
  488. * @param {Boolean} [options.useUnifiedTopology=false] False by default. Set to `true` to opt in to the MongoDB driver's replica set and sharded cluster monitoring engine.
  489. * @param {Boolean} [options.useCreateIndex=true] Mongoose-specific option. If `true`, this connection will use [`createIndex()` instead of `ensureIndex()`](/docs/deprecations.html#ensureindex) for automatic index builds via [`Model.init()`](/docs/api.html#model_Model.init).
  490. * @param {Boolean} [options.useFindAndModify=true] True by default. Set to `false` to make `findOneAndUpdate()` and `findOneAndRemove()` use native `findOneAndUpdate()` rather than `findAndModify()`.
  491. * @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.
  492. * @param {Number} [options.reconnectInterval=1000] See `reconnectTries` option above.
  493. * @param {Class} [options.promiseLibrary] Sets the [underlying driver's promise library](http://mongodb.github.io/node-mongodb-native/3.1/api/MongoClient.html).
  494. * @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).
  495. * @param {Number} [options.bufferMaxEntries] This option does nothing if `useUnifiedTopology` is set. The MongoDB driver also has its own buffering mechanism that kicks in when the driver is disconnected. Set this option to 0 and set `bufferCommands` to `false` on your schemas if you want your database operations to fail immediately when the driver is not connected, as opposed to waiting for reconnection.
  496. * @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).
  497. * @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.
  498. * @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.
  499. * @param {Function} [callback]
  500. * @returns {Connection} this
  501. * @api public
  502. */
  503. Connection.prototype.openUri = function(uri, options, callback) {
  504. this.readyState = STATES.connecting;
  505. this._closeCalled = false;
  506. if (typeof options === 'function') {
  507. callback = options;
  508. options = null;
  509. }
  510. if (['string', 'number'].indexOf(typeof options) !== -1) {
  511. throw new MongooseError('Mongoose 5.x no longer supports ' +
  512. '`mongoose.connect(host, dbname, port)` or ' +
  513. '`mongoose.createConnection(host, dbname, port)`. See ' +
  514. 'http://mongoosejs.com/docs/connections.html for supported connection syntax');
  515. }
  516. if (typeof uri !== 'string') {
  517. throw new MongooseError('The `uri` parameter to `openUri()` must be a ' +
  518. `string, got "${typeof uri}". Make sure the first parameter to ` +
  519. '`mongoose.connect()` or `mongoose.createConnection()` is a string.');
  520. }
  521. if (callback != null && typeof callback !== 'function') {
  522. throw new MongooseError('3rd parameter to `mongoose.connect()` or ' +
  523. '`mongoose.createConnection()` must be a function, got "' +
  524. typeof callback + '"');
  525. }
  526. const Promise = PromiseProvider.get();
  527. const _this = this;
  528. if (options) {
  529. options = utils.clone(options);
  530. const autoIndex = options.config && options.config.autoIndex != null ?
  531. options.config.autoIndex :
  532. options.autoIndex;
  533. if (autoIndex != null) {
  534. this.config.autoIndex = autoIndex !== false;
  535. delete options.config;
  536. delete options.autoIndex;
  537. }
  538. if ('autoCreate' in options) {
  539. this.config.autoCreate = !!options.autoCreate;
  540. delete options.autoCreate;
  541. }
  542. if ('useCreateIndex' in options) {
  543. this.config.useCreateIndex = !!options.useCreateIndex;
  544. delete options.useCreateIndex;
  545. }
  546. if ('useFindAndModify' in options) {
  547. this.config.useFindAndModify = !!options.useFindAndModify;
  548. delete options.useFindAndModify;
  549. }
  550. // Backwards compat
  551. if (options.user || options.pass) {
  552. options.auth = options.auth || {};
  553. options.auth.user = options.user;
  554. options.auth.password = options.pass;
  555. this.user = options.user;
  556. this.pass = options.pass;
  557. }
  558. delete options.user;
  559. delete options.pass;
  560. if (options.bufferCommands != null) {
  561. options.bufferMaxEntries = 0;
  562. this.config.bufferCommands = options.bufferCommands;
  563. delete options.bufferCommands;
  564. }
  565. if (options.useMongoClient != null) {
  566. handleUseMongoClient(options);
  567. }
  568. } else {
  569. options = {};
  570. }
  571. this._connectionOptions = options;
  572. const dbName = options.dbName;
  573. if (dbName != null) {
  574. this.$dbName = dbName;
  575. }
  576. delete options.dbName;
  577. if (!('promiseLibrary' in options)) {
  578. options.promiseLibrary = PromiseProvider.get();
  579. }
  580. if (!('useNewUrlParser' in options)) {
  581. if ('useNewUrlParser' in this.base.options) {
  582. options.useNewUrlParser = this.base.options.useNewUrlParser;
  583. } else {
  584. options.useNewUrlParser = false;
  585. }
  586. }
  587. if (!utils.hasUserDefinedProperty(options, 'useUnifiedTopology')) {
  588. if (utils.hasUserDefinedProperty(this.base.options, 'useUnifiedTopology')) {
  589. options.useUnifiedTopology = this.base.options.useUnifiedTopology;
  590. } else {
  591. options.useUnifiedTopology = false;
  592. }
  593. }
  594. if (!utils.hasUserDefinedProperty(options, 'driverInfo')) {
  595. options.driverInfo = {
  596. name: 'Mongoose',
  597. version: pkg.version
  598. };
  599. }
  600. const parsePromise = new Promise((resolve, reject) => {
  601. parseConnectionString(uri, options, (err, parsed) => {
  602. if (err) {
  603. return reject(err);
  604. }
  605. if (dbName) {
  606. this.name = dbName;
  607. } else if (parsed.defaultDatabase) {
  608. this.name = parsed.defaultDatabase;
  609. } else {
  610. this.name = get(parsed, 'auth.db', null);
  611. }
  612. this.host = get(parsed, 'hosts.0.host', 'localhost');
  613. this.port = get(parsed, 'hosts.0.port', 27017);
  614. this.user = this.user || get(parsed, 'auth.username');
  615. this.pass = this.pass || get(parsed, 'auth.password');
  616. resolve();
  617. });
  618. });
  619. const _handleReconnect = () => {
  620. // If we aren't disconnected, we assume this reconnect is due to a
  621. // socket timeout. If there's no activity on a socket for
  622. // `socketTimeoutMS`, the driver will attempt to reconnect and emit
  623. // this event.
  624. if (_this.readyState !== STATES.connected) {
  625. _this.readyState = STATES.connected;
  626. _this.emit('reconnect');
  627. _this.emit('reconnected');
  628. }
  629. };
  630. const promise = new Promise((resolve, reject) => {
  631. const client = new mongodb.MongoClient(uri, options);
  632. _this.client = client;
  633. client.connect(function(error) {
  634. if (error) {
  635. _this.readyState = STATES.disconnected;
  636. return reject(error);
  637. }
  638. const db = dbName != null ? client.db(dbName) : client.db();
  639. _this.db = db;
  640. // `useUnifiedTopology` events
  641. const type = get(db, 's.topology.s.description.type', '');
  642. if (options.useUnifiedTopology) {
  643. if (type === 'Single') {
  644. const server = Array.from(db.s.topology.s.servers.values())[0];
  645. server.s.topology.on('serverHeartbeatSucceeded', () => {
  646. _handleReconnect();
  647. });
  648. server.s.pool.on('reconnect', () => {
  649. _handleReconnect();
  650. });
  651. client.on('serverDescriptionChanged', ev => {
  652. const newDescription = ev.newDescription;
  653. if (newDescription.type === 'Standalone') {
  654. _handleReconnect();
  655. } else {
  656. _this.readyState = STATES.disconnected;
  657. }
  658. });
  659. } else if (type.startsWith('ReplicaSet')) {
  660. client.on('topologyDescriptionChanged', ev => {
  661. // Emit disconnected if we've lost connectivity to _all_ servers
  662. // in the replica set.
  663. const description = ev.newDescription;
  664. const servers = Array.from(ev.newDescription.servers.values());
  665. const allServersDisconnected = description.type === 'ReplicaSetNoPrimary' &&
  666. servers.reduce((cur, d) => cur || d.type === 'Unknown', false);
  667. if (_this.readyState === STATES.connected && allServersDisconnected) {
  668. // Implicitly emits 'disconnected'
  669. _this.readyState = STATES.disconnected;
  670. } else if (_this.readyState === STATES.disconnected && !allServersDisconnected) {
  671. _handleReconnect();
  672. }
  673. });
  674. db.on('close', function() {
  675. const type = get(db, 's.topology.s.description.type', '');
  676. if (type !== 'ReplicaSetWithPrimary') {
  677. // Implicitly emits 'disconnected'
  678. _this.readyState = STATES.disconnected;
  679. }
  680. });
  681. }
  682. }
  683. // Backwards compat for mongoose 4.x
  684. db.on('reconnect', function() {
  685. _handleReconnect();
  686. });
  687. db.s.topology.on('reconnectFailed', function() {
  688. _this.emit('reconnectFailed');
  689. });
  690. if (!options.useUnifiedTopology) {
  691. db.s.topology.on('left', function(data) {
  692. _this.emit('left', data);
  693. });
  694. }
  695. db.s.topology.on('joined', function(data) {
  696. _this.emit('joined', data);
  697. });
  698. db.s.topology.on('fullsetup', function(data) {
  699. _this.emit('fullsetup', data);
  700. });
  701. if (get(db, 's.topology.s.coreTopology.s.pool') != null) {
  702. db.s.topology.s.coreTopology.s.pool.on('attemptReconnect', function() {
  703. _this.emit('attemptReconnect');
  704. });
  705. }
  706. if (!options.useUnifiedTopology || !type.startsWith('ReplicaSet')) {
  707. db.on('close', function() {
  708. // Implicitly emits 'disconnected'
  709. _this.readyState = STATES.disconnected;
  710. });
  711. }
  712. if (!options.useUnifiedTopology) {
  713. client.on('left', function() {
  714. if (_this.readyState === STATES.connected &&
  715. get(db, 's.topology.s.coreTopology.s.replicaSetState.topologyType') === 'ReplicaSetNoPrimary') {
  716. _this.readyState = STATES.disconnected;
  717. }
  718. });
  719. }
  720. db.on('timeout', function() {
  721. _this.emit('timeout');
  722. });
  723. delete _this.then;
  724. delete _this.catch;
  725. _this.readyState = STATES.connected;
  726. for (const i in _this.collections) {
  727. if (utils.object.hasOwnProperty(_this.collections, i)) {
  728. _this.collections[i].onOpen();
  729. }
  730. }
  731. resolve(_this);
  732. _this.emit('open');
  733. });
  734. });
  735. const serverSelectionError = new ServerSelectionError();
  736. this.$initialConnection = Promise.all([promise, parsePromise]).
  737. then(res => res[0]).
  738. catch(err => {
  739. if (err != null && err.name === 'MongoServerSelectionError') {
  740. err = serverSelectionError.assimilateError(err);
  741. }
  742. if (this.listeners('error').length > 0) {
  743. process.nextTick(() => this.emit('error', err));
  744. }
  745. throw err;
  746. });
  747. this.then = function(resolve, reject) {
  748. return this.$initialConnection.then(resolve, reject);
  749. };
  750. this.catch = function(reject) {
  751. return this.$initialConnection.catch(reject);
  752. };
  753. if (callback != null) {
  754. this.$initialConnection = this.$initialConnection.then(
  755. () => callback(null, this),
  756. err => callback(err)
  757. );
  758. }
  759. return this;
  760. };
  761. /*!
  762. * ignore
  763. */
  764. const handleUseMongoClient = function handleUseMongoClient(options) {
  765. console.warn('WARNING: The `useMongoClient` option is no longer ' +
  766. 'necessary in mongoose 5.x, please remove it.');
  767. const stack = new Error().stack;
  768. console.warn(stack.substr(stack.indexOf('\n') + 1));
  769. delete options.useMongoClient;
  770. };
  771. /**
  772. * Closes the connection
  773. *
  774. * @param {Boolean} [force] optional
  775. * @param {Function} [callback] optional
  776. * @return {Promise}
  777. * @api public
  778. */
  779. Connection.prototype.close = function(force, callback) {
  780. if (typeof force === 'function') {
  781. callback = force;
  782. force = false;
  783. }
  784. this.$wasForceClosed = !!force;
  785. return promiseOrCallback(callback, cb => {
  786. this._close(force, cb);
  787. });
  788. };
  789. /**
  790. * Handles closing the connection
  791. *
  792. * @param {Boolean} force
  793. * @param {Function} callback
  794. * @api private
  795. */
  796. Connection.prototype._close = function(force, callback) {
  797. const _this = this;
  798. this._closeCalled = true;
  799. switch (this.readyState) {
  800. case STATES.disconnected:
  801. callback();
  802. break;
  803. case STATES.connected:
  804. this.readyState = STATES.disconnecting;
  805. this.doClose(force, function(err) {
  806. if (err) {
  807. return callback(err);
  808. }
  809. _this.onClose(force);
  810. callback(null);
  811. });
  812. break;
  813. case STATES.connecting:
  814. this.once('open', function() {
  815. _this.close(callback);
  816. });
  817. break;
  818. case STATES.disconnecting:
  819. this.once('close', function() {
  820. callback();
  821. });
  822. break;
  823. }
  824. return this;
  825. };
  826. /**
  827. * Called when the connection closes
  828. *
  829. * @api private
  830. */
  831. Connection.prototype.onClose = function(force) {
  832. this.readyState = STATES.disconnected;
  833. // avoid having the collection subscribe to our event emitter
  834. // to prevent 0.3 warning
  835. for (const i in this.collections) {
  836. if (utils.object.hasOwnProperty(this.collections, i)) {
  837. this.collections[i].onClose(force);
  838. }
  839. }
  840. this.emit('close', force);
  841. };
  842. /**
  843. * Retrieves a collection, creating it if not cached.
  844. *
  845. * Not typically needed by applications. Just talk to your collection through your model.
  846. *
  847. * @param {String} name of the collection
  848. * @param {Object} [options] optional collection options
  849. * @return {Collection} collection instance
  850. * @api public
  851. */
  852. Connection.prototype.collection = function(name, options) {
  853. options = options ? utils.clone(options) : {};
  854. options.$wasForceClosed = this.$wasForceClosed;
  855. if (!(name in this.collections)) {
  856. this.collections[name] = new Collection(name, this, options);
  857. }
  858. return this.collections[name];
  859. };
  860. /**
  861. * Declares a plugin executed on all schemas you pass to `conn.model()`
  862. *
  863. * Equivalent to calling `.plugin(fn)` on each schema you create.
  864. *
  865. * ####Example:
  866. * const db = mongoose.createConnection('mongodb://localhost:27017/mydb');
  867. * db.plugin(() => console.log('Applied'));
  868. * db.plugins.length; // 1
  869. *
  870. * db.model('Test', new Schema({})); // Prints "Applied"
  871. *
  872. * @param {Function} fn plugin callback
  873. * @param {Object} [opts] optional options
  874. * @return {Connection} this
  875. * @see plugins ./plugins.html
  876. * @api public
  877. */
  878. Connection.prototype.plugin = function(fn, opts) {
  879. this.plugins.push([fn, opts]);
  880. return this;
  881. };
  882. /**
  883. * Defines or retrieves a model.
  884. *
  885. * var mongoose = require('mongoose');
  886. * var db = mongoose.createConnection(..);
  887. * db.model('Venue', new Schema(..));
  888. * var Ticket = db.model('Ticket', new Schema(..));
  889. * var Venue = db.model('Venue');
  890. *
  891. * _When no `collection` argument is passed, Mongoose produces a collection name by passing the model `name` to the [utils.toCollectionName](#utils_exports.toCollectionName) method. This method pluralizes the name. If you don't like this behavior, either pass a collection name or set your schemas collection name option._
  892. *
  893. * ####Example:
  894. *
  895. * var schema = new Schema({ name: String }, { collection: 'actor' });
  896. *
  897. * // or
  898. *
  899. * schema.set('collection', 'actor');
  900. *
  901. * // or
  902. *
  903. * var collectionName = 'actor'
  904. * var M = conn.model('Actor', schema, collectionName)
  905. *
  906. * @param {String|Function} name the model name or class extending Model
  907. * @param {Schema} [schema] a schema. necessary when defining a model
  908. * @param {String} [collection] name of mongodb collection (optional) if not given it will be induced from model name
  909. * @see Mongoose#model #index_Mongoose-model
  910. * @return {Model} The compiled model
  911. * @api public
  912. */
  913. Connection.prototype.model = function(name, schema, collection) {
  914. if (!(this instanceof Connection)) {
  915. throw new MongooseError('`connection.model()` should not be run with ' +
  916. '`new`. If you are doing `new db.model(foo)(bar)`, use ' +
  917. '`db.model(foo)(bar)` instead');
  918. }
  919. let fn;
  920. if (typeof name === 'function') {
  921. fn = name;
  922. name = fn.name;
  923. }
  924. // collection name discovery
  925. if (typeof schema === 'string') {
  926. collection = schema;
  927. schema = false;
  928. }
  929. if (utils.isObject(schema) && !schema.instanceOfSchema) {
  930. schema = new Schema(schema);
  931. }
  932. if (schema && !schema.instanceOfSchema) {
  933. throw new Error('The 2nd parameter to `mongoose.model()` should be a ' +
  934. 'schema or a POJO');
  935. }
  936. if (this.models[name] && !collection) {
  937. // model exists but we are not subclassing with custom collection
  938. if (schema && schema.instanceOfSchema && schema !== this.models[name].schema) {
  939. throw new MongooseError.OverwriteModelError(name);
  940. }
  941. return this.models[name];
  942. }
  943. const opts = { cache: false, connection: this };
  944. let model;
  945. if (schema && schema.instanceOfSchema) {
  946. applyPlugins(schema, this.plugins, null, '$connectionPluginsApplied');
  947. // compile a model
  948. model = this.base.model(fn || name, schema, collection, opts);
  949. // only the first model with this name is cached to allow
  950. // for one-offs with custom collection names etc.
  951. if (!this.models[name]) {
  952. this.models[name] = model;
  953. }
  954. // Errors handled internally, so safe to ignore error
  955. model.init(function $modelInitNoop() {});
  956. return model;
  957. }
  958. if (this.models[name] && collection) {
  959. // subclassing current model with alternate collection
  960. model = this.models[name];
  961. schema = model.prototype.schema;
  962. const sub = model.__subclass(this, schema, collection);
  963. // do not cache the sub model
  964. return sub;
  965. }
  966. // lookup model in mongoose module
  967. model = this.base.models[name];
  968. if (!model) {
  969. throw new MongooseError.MissingSchemaError(name);
  970. }
  971. if (this === model.prototype.db
  972. && (!collection || collection === model.collection.name)) {
  973. // model already uses this connection.
  974. // only the first model with this name is cached to allow
  975. // for one-offs with custom collection names etc.
  976. if (!this.models[name]) {
  977. this.models[name] = model;
  978. }
  979. return model;
  980. }
  981. this.models[name] = model.__subclass(this, schema, collection);
  982. return this.models[name];
  983. };
  984. /**
  985. * Removes the model named `name` from this connection, if it exists. You can
  986. * use this function to clean up any models you created in your tests to
  987. * prevent OverwriteModelErrors.
  988. *
  989. * ####Example:
  990. *
  991. * conn.model('User', new Schema({ name: String }));
  992. * console.log(conn.model('User')); // Model object
  993. * conn.deleteModel('User');
  994. * console.log(conn.model('User')); // undefined
  995. *
  996. * // Usually useful in a Mocha `afterEach()` hook
  997. * afterEach(function() {
  998. * conn.deleteModel(/.+/); // Delete every model
  999. * });
  1000. *
  1001. * @api public
  1002. * @param {String|RegExp} name if string, the name of the model to remove. If regexp, removes all models whose name matches the regexp.
  1003. * @return {Connection} this
  1004. */
  1005. Connection.prototype.deleteModel = function(name) {
  1006. if (typeof name === 'string') {
  1007. const model = this.model(name);
  1008. if (model == null) {
  1009. return this;
  1010. }
  1011. const collectionName = model.collection.name;
  1012. delete this.models[name];
  1013. delete this.collections[collectionName];
  1014. delete this.base.modelSchemas[name];
  1015. } else if (name instanceof RegExp) {
  1016. const pattern = name;
  1017. const names = this.modelNames();
  1018. for (const name of names) {
  1019. if (pattern.test(name)) {
  1020. this.deleteModel(name);
  1021. }
  1022. }
  1023. } else {
  1024. throw new Error('First parameter to `deleteModel()` must be a string ' +
  1025. 'or regexp, got "' + name + '"');
  1026. }
  1027. return this;
  1028. };
  1029. /**
  1030. * Watches the entire underlying database for changes. Similar to
  1031. * [`Model.watch()`](/docs/api/model.html#model_Model.watch).
  1032. *
  1033. * This function does **not** trigger any middleware. In particular, it
  1034. * does **not** trigger aggregate middleware.
  1035. *
  1036. * The ChangeStream object is an event emitter that emits the following events:
  1037. *
  1038. * - 'change': A change occurred, see below example
  1039. * - 'error': An unrecoverable error occurred. In particular, change streams currently error out if they lose connection to the replica set primary. Follow [this GitHub issue](https://github.com/Automattic/mongoose/issues/6799) for updates.
  1040. * - 'end': Emitted if the underlying stream is closed
  1041. * - 'close': Emitted if the underlying stream is closed
  1042. *
  1043. * ####Example:
  1044. *
  1045. * const User = conn.model('User', new Schema({ name: String }));
  1046. *
  1047. * const changeStream = conn.watch().on('change', data => console.log(data));
  1048. *
  1049. * // Triggers a 'change' event on the change stream.
  1050. * await User.create({ name: 'test' });
  1051. *
  1052. * @api public
  1053. * @param {Array} [pipeline]
  1054. * @param {Object} [options] passed without changes to [the MongoDB driver's `Db#watch()` function](https://mongodb.github.io/node-mongodb-native/3.4/api/Db.html#watch)
  1055. * @return {ChangeStream} mongoose-specific change stream wrapper, inherits from EventEmitter
  1056. */
  1057. Connection.prototype.watch = function(pipeline, options) {
  1058. const disconnectedError = new MongooseError('Connection ' + this.id +
  1059. ' was disconnected when calling `watch()`');
  1060. const changeStreamThunk = cb => {
  1061. immediate(() => {
  1062. if (this.readyState === STATES.connecting) {
  1063. this.once('open', function() {
  1064. const driverChangeStream = this.db.watch(pipeline, options);
  1065. cb(null, driverChangeStream);
  1066. });
  1067. } else if (this.readyState === STATES.disconnected && this.db == null) {
  1068. cb(disconnectedError);
  1069. } else {
  1070. const driverChangeStream = this.db.watch(pipeline, options);
  1071. cb(null, driverChangeStream);
  1072. }
  1073. });
  1074. };
  1075. const changeStream = new ChangeStream(changeStreamThunk, pipeline, options);
  1076. return changeStream;
  1077. };
  1078. /**
  1079. * Returns an array of model names created on this connection.
  1080. * @api public
  1081. * @return {Array}
  1082. */
  1083. Connection.prototype.modelNames = function() {
  1084. return Object.keys(this.models);
  1085. };
  1086. /**
  1087. * @brief Returns if the connection requires authentication after it is opened. Generally if a
  1088. * username and password are both provided than authentication is needed, but in some cases a
  1089. * password is not required.
  1090. * @api private
  1091. * @return {Boolean} true if the connection should be authenticated after it is opened, otherwise false.
  1092. */
  1093. Connection.prototype.shouldAuthenticate = function() {
  1094. return this.user != null &&
  1095. (this.pass != null || this.authMechanismDoesNotRequirePassword());
  1096. };
  1097. /**
  1098. * @brief Returns a boolean value that specifies if the current authentication mechanism needs a
  1099. * password to authenticate according to the auth objects passed into the openUri methods.
  1100. * @api private
  1101. * @return {Boolean} true if the authentication mechanism specified in the options object requires
  1102. * a password, otherwise false.
  1103. */
  1104. Connection.prototype.authMechanismDoesNotRequirePassword = function() {
  1105. if (this.options && this.options.auth) {
  1106. return noPasswordAuthMechanisms.indexOf(this.options.auth.authMechanism) >= 0;
  1107. }
  1108. return true;
  1109. };
  1110. /**
  1111. * @brief Returns a boolean value that specifies if the provided objects object provides enough
  1112. * data to authenticate with. Generally this is true if the username and password are both specified
  1113. * but in some authentication methods, a password is not required for authentication so only a username
  1114. * is required.
  1115. * @param {Object} [options] the options object passed into the openUri methods.
  1116. * @api private
  1117. * @return {Boolean} true if the provided options object provides enough data to authenticate with,
  1118. * otherwise false.
  1119. */
  1120. Connection.prototype.optionsProvideAuthenticationData = function(options) {
  1121. return (options) &&
  1122. (options.user) &&
  1123. ((options.pass) || this.authMechanismDoesNotRequirePassword());
  1124. };
  1125. /**
  1126. * Switches to a different database using the same connection pool.
  1127. *
  1128. * Returns a new connection object, with the new db.
  1129. *
  1130. * @method useDb
  1131. * @memberOf Connection
  1132. * @param {String} name The database name
  1133. * @param {Object} [options]
  1134. * @param {Boolean} [options.useCache=false] If true, cache results so calling `useDb()` multiple times with the same name only creates 1 connection object.
  1135. * @return {Connection} New Connection Object
  1136. * @api public
  1137. */
  1138. /*!
  1139. * Module exports.
  1140. */
  1141. Connection.STATES = STATES;
  1142. module.exports = Connection;