connection.js 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023
  1. 'use strict';
  2. /*!
  3. * Module dependencies.
  4. */
  5. const EventEmitter = require('events').EventEmitter;
  6. const Schema = require('./schema');
  7. const Collection = require('./driver').get().Collection;
  8. const STATES = require('./connectionstate');
  9. const MongooseError = require('./error');
  10. const PromiseProvider = require('./promise_provider');
  11. const applyPlugins = require('./helpers/schema/applyPlugins');
  12. const get = require('./helpers/get');
  13. const mongodb = require('mongodb');
  14. const utils = require('./utils');
  15. const parseConnectionString = require('mongodb-core').parseConnectionString;
  16. /*!
  17. * A list of authentication mechanisms that don't require a password for authentication.
  18. * This is used by the authMechanismDoesNotRequirePassword method.
  19. *
  20. * @api private
  21. */
  22. const noPasswordAuthMechanisms = [
  23. 'MONGODB-X509'
  24. ];
  25. /**
  26. * Connection constructor
  27. *
  28. * For practical reasons, a Connection equals a Db.
  29. *
  30. * @param {Mongoose} base a mongoose instance
  31. * @inherits NodeJS EventEmitter http://nodejs.org/api/events.html#events_class_events_eventemitter
  32. * @event `connecting`: Emitted when `connection.openUri()` is executed on this connection.
  33. * @event `connected`: Emitted when this connection successfully connects to the db. May be emitted _multiple_ times in `reconnected` scenarios.
  34. * @event `open`: Emitted after we `connected` and `onOpen` is executed on all of this connections models.
  35. * @event `disconnecting`: Emitted when `connection.close()` was executed.
  36. * @event `disconnected`: Emitted after getting disconnected from the db.
  37. * @event `close`: Emitted after we `disconnected` and `onClose` executed on all of this connections models.
  38. * @event `reconnected`: Emitted after we `connected` and subsequently `disconnected`, followed by successfully another successfull connection.
  39. * @event `error`: Emitted when an error occurs on this connection.
  40. * @event `fullsetup`: Emitted in a replica-set scenario, when primary and at least one seconaries specified in the connection string are connected.
  41. * @event `all`: Emitted in a replica-set scenario, when all nodes specified in the connection string are connected.
  42. * @api public
  43. */
  44. function Connection(base) {
  45. this.base = base;
  46. this.collections = {};
  47. this.models = {};
  48. this.config = {autoIndex: true};
  49. this.replica = false;
  50. this.options = null;
  51. this.otherDbs = []; // FIXME: To be replaced with relatedDbs
  52. this.relatedDbs = {}; // Hashmap of other dbs that share underlying connection
  53. this.states = STATES;
  54. this._readyState = STATES.disconnected;
  55. this._closeCalled = false;
  56. this._hasOpened = false;
  57. this.plugins = [];
  58. this.$internalEmitter = new EventEmitter();
  59. this.$internalEmitter.setMaxListeners(0);
  60. }
  61. /*!
  62. * Inherit from EventEmitter
  63. */
  64. Connection.prototype.__proto__ = EventEmitter.prototype;
  65. /**
  66. * Connection ready state
  67. *
  68. * - 0 = disconnected
  69. * - 1 = connected
  70. * - 2 = connecting
  71. * - 3 = disconnecting
  72. *
  73. * Each state change emits its associated event name.
  74. *
  75. * ####Example
  76. *
  77. * conn.on('connected', callback);
  78. * conn.on('disconnected', callback);
  79. *
  80. * @property readyState
  81. * @memberOf Connection
  82. * @instance
  83. * @api public
  84. */
  85. Object.defineProperty(Connection.prototype, 'readyState', {
  86. get: function() {
  87. return this._readyState;
  88. },
  89. set: function(val) {
  90. if (!(val in STATES)) {
  91. throw new Error('Invalid connection state: ' + val);
  92. }
  93. if (this._readyState !== val) {
  94. this._readyState = val;
  95. // [legacy] loop over the otherDbs on this connection and change their state
  96. for (let i = 0; i < this.otherDbs.length; i++) {
  97. this.otherDbs[i].readyState = val;
  98. }
  99. // loop over relatedDbs on this connection and change their state
  100. for (const k in this.relatedDbs) {
  101. this.relatedDbs[k].readyState = val;
  102. }
  103. if (STATES.connected === val) {
  104. this._hasOpened = true;
  105. }
  106. this.emit(STATES[val]);
  107. }
  108. }
  109. });
  110. /**
  111. * A hash of the collections associated with this connection
  112. *
  113. * @property collections
  114. * @memberOf Connection
  115. * @instance
  116. * @api public
  117. */
  118. Connection.prototype.collections;
  119. /**
  120. * The name of the database this connection points to.
  121. *
  122. * ####Example
  123. *
  124. * mongoose.createConnection('mongodb://localhost:27017/mydb').name; // "mydb"
  125. *
  126. * @property name
  127. * @memberOf Connection
  128. * @instance
  129. * @api public
  130. */
  131. Connection.prototype.name;
  132. /**
  133. * The plugins that will be applied to all models created on this connection.
  134. *
  135. * ####Example:
  136. *
  137. * const db = mongoose.createConnection('mongodb://localhost:27017/mydb');
  138. * db.plugin(() => console.log('Applied'));
  139. * db.plugins.length; // 1
  140. *
  141. * db.model('Test', new Schema({})); // Prints "Applied"
  142. *
  143. * @property plugins
  144. * @memberOf Connection
  145. * @instance
  146. * @api public
  147. */
  148. Object.defineProperty(Connection.prototype, 'plugins', {
  149. configurable: false,
  150. enumerable: true,
  151. writable: true
  152. });
  153. /**
  154. * The host name portion of the URI. If multiple hosts, such as a replica set,
  155. * this will contain the first host name in the URI
  156. *
  157. * ####Example
  158. *
  159. * mongoose.createConnection('mongodb://localhost:27017/mydb').host; // "localhost"
  160. *
  161. * @property host
  162. * @memberOf Connection
  163. * @instance
  164. * @api public
  165. */
  166. Object.defineProperty(Connection.prototype, 'host', {
  167. configurable: true,
  168. enumerable: true,
  169. writable: true
  170. });
  171. /**
  172. * The port portion of the URI. If multiple hosts, such as a replica set,
  173. * this will contain the port from the first host name in the URI.
  174. *
  175. * ####Example
  176. *
  177. * mongoose.createConnection('mongodb://localhost:27017/mydb').port; // 27017
  178. *
  179. * @property port
  180. * @memberOf Connection
  181. * @instance
  182. * @api public
  183. */
  184. Object.defineProperty(Connection.prototype, 'port', {
  185. configurable: true,
  186. enumerable: true,
  187. writable: true
  188. });
  189. /**
  190. * The username specified in the URI
  191. *
  192. * ####Example
  193. *
  194. * mongoose.createConnection('mongodb://val:psw@localhost:27017/mydb').user; // "val"
  195. *
  196. * @property user
  197. * @memberOf Connection
  198. * @instance
  199. * @api public
  200. */
  201. Object.defineProperty(Connection.prototype, 'user', {
  202. configurable: true,
  203. enumerable: true,
  204. writable: true
  205. });
  206. /**
  207. * The password specified in the URI
  208. *
  209. * ####Example
  210. *
  211. * mongoose.createConnection('mongodb://val:psw@localhost:27017/mydb').pass; // "psw"
  212. *
  213. * @property pass
  214. * @memberOf Connection
  215. * @instance
  216. * @api public
  217. */
  218. Object.defineProperty(Connection.prototype, 'pass', {
  219. configurable: true,
  220. enumerable: true,
  221. writable: true
  222. });
  223. /**
  224. * The mongodb.Db instance, set when the connection is opened
  225. *
  226. * @property db
  227. * @memberOf Connection
  228. * @instance
  229. * @api public
  230. */
  231. Connection.prototype.db;
  232. /**
  233. * A hash of the global options that are associated with this connection
  234. *
  235. * @property config
  236. * @memberOf Connection
  237. * @instance
  238. * @api public
  239. */
  240. Connection.prototype.config;
  241. /**
  242. * Helper for `createCollection()`. Will explicitly create the given collection
  243. * with specified options. Used to create [capped collections](https://docs.mongodb.com/manual/core/capped-collections/)
  244. * and [views](https://docs.mongodb.com/manual/core/views/) from mongoose.
  245. *
  246. * 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)
  247. *
  248. * @method createCollection
  249. * @param {string} collection The collection to create
  250. * @param {Object} [options] see [MongoDB driver docs](http://mongodb.github.io/node-mongodb-native/2.2/api/Db.html#createCollection)
  251. * @param {Function} [callback]
  252. * @return {Promise}
  253. * @api public
  254. */
  255. Connection.prototype.createCollection = _wrapConnHelper(function createCollection(collection, options, cb) {
  256. if (typeof options === 'function') {
  257. cb = options;
  258. options = {};
  259. }
  260. this.db.createCollection(collection, options, cb);
  261. });
  262. /**
  263. * _Requires MongoDB >= 3.6.0._ Starts a [MongoDB session](https://docs.mongodb.com/manual/release-notes/3.6/#client-sessions)
  264. * for benefits like causal consistency, [retryable writes](https://docs.mongodb.com/manual/core/retryable-writes/),
  265. * and [transactions](http://thecodebarbarian.com/a-node-js-perspective-on-mongodb-4-transactions.html).
  266. *
  267. * ####Example:
  268. *
  269. * const session = await conn.startSession();
  270. * let doc = await Person.findOne({ name: 'Ned Stark' }, null, { session });
  271. * await doc.remove();
  272. * // `doc` will always be null, even if reading from a replica set
  273. * // secondary. Without causal consistency, it is possible to
  274. * // get a doc back from the below query if the query reads from a
  275. * // secondary that is experiencing replication lag.
  276. * doc = await Person.findOne({ name: 'Ned Stark' }, null, { session, readPreference: 'secondary' });
  277. *
  278. *
  279. * @method startSession
  280. * @param {Object} [options] see the [mongodb driver options](http://mongodb.github.io/node-mongodb-native/3.0/api/MongoClient.html#startSession)
  281. * @param {Boolean} [options.causalConsistency=true] set to false to disable causal consistency
  282. * @param {Function} [callback]
  283. * @return {Promise<ClientSession>} promise that resolves to a MongoDB driver `ClientSession`
  284. * @api public
  285. */
  286. Connection.prototype.startSession = _wrapConnHelper(function startSession(options, cb) {
  287. if (typeof options === 'function') {
  288. cb = options;
  289. options = null;
  290. }
  291. const session = this.client.startSession(options);
  292. cb(null, session);
  293. });
  294. /**
  295. * Helper for `dropCollection()`. Will delete the given collection, including
  296. * all documents and indexes.
  297. *
  298. * @method dropCollection
  299. * @param {string} collection The collection to delete
  300. * @param {Function} [callback]
  301. * @return {Promise}
  302. * @api public
  303. */
  304. Connection.prototype.dropCollection = _wrapConnHelper(function dropCollection(collection, cb) {
  305. this.db.dropCollection(collection, cb);
  306. });
  307. /**
  308. * Helper for `dropDatabase()`. Deletes the given database, including all
  309. * collections, documents, and indexes.
  310. *
  311. * ####Example:
  312. *
  313. * const conn = mongoose.createConnection('mongodb://localhost:27017/mydb');
  314. * // Deletes the entire 'mydb' database
  315. * await conn.dropDatabase();
  316. *
  317. * @method dropDatabase
  318. * @param {Function} [callback]
  319. * @return {Promise}
  320. * @api public
  321. */
  322. Connection.prototype.dropDatabase = _wrapConnHelper(function dropDatabase(cb) {
  323. // If `dropDatabase()` is called, this model's collection will not be
  324. // init-ed. It is sufficiently common to call `dropDatabase()` after
  325. // `mongoose.connect()` but before creating models that we want to
  326. // support this. See gh-6967
  327. for (const name of Object.keys(this.models)) {
  328. delete this.models[name].$init;
  329. }
  330. this.db.dropDatabase(cb);
  331. });
  332. /*!
  333. * ignore
  334. */
  335. function _wrapConnHelper(fn) {
  336. return function() {
  337. const cb = arguments.length > 0 ? arguments[arguments.length - 1] : null;
  338. const argsWithoutCb = typeof cb === 'function' ?
  339. Array.prototype.slice.call(arguments, 0, arguments.length - 1) :
  340. Array.prototype.slice.call(arguments);
  341. return utils.promiseOrCallback(cb, cb => {
  342. if (this.readyState !== STATES.connected) {
  343. this.once('open', function() {
  344. fn.apply(this, argsWithoutCb.concat([cb]));
  345. });
  346. } else {
  347. fn.apply(this, argsWithoutCb.concat([cb]));
  348. }
  349. });
  350. };
  351. }
  352. /**
  353. * error
  354. *
  355. * Graceful error handling, passes error to callback
  356. * if available, else emits error on the connection.
  357. *
  358. * @param {Error} err
  359. * @param {Function} callback optional
  360. * @api private
  361. */
  362. Connection.prototype.error = function(err, callback) {
  363. if (callback) {
  364. callback(err);
  365. return null;
  366. }
  367. if (this.listeners('error').length > 0) {
  368. this.emit('error', err);
  369. }
  370. return Promise.reject(err);
  371. };
  372. /**
  373. * Called when the connection is opened
  374. *
  375. * @api private
  376. */
  377. Connection.prototype.onOpen = function() {
  378. this.readyState = STATES.connected;
  379. // avoid having the collection subscribe to our event emitter
  380. // to prevent 0.3 warning
  381. for (const i in this.collections) {
  382. if (utils.object.hasOwnProperty(this.collections, i)) {
  383. this.collections[i].onOpen();
  384. }
  385. }
  386. this.emit('open');
  387. };
  388. /**
  389. * Opens the connection with a URI using `MongoClient.connect()`.
  390. *
  391. * @param {String} uri The URI to connect with.
  392. * @param {Object} [options] Passed on to http://mongodb.github.io/node-mongodb-native/2.2/api/MongoClient.html#connect
  393. * @param {Function} [callback]
  394. * @returns {Connection} this
  395. * @api public
  396. */
  397. Connection.prototype.openUri = function(uri, options, callback) {
  398. this.readyState = STATES.connecting;
  399. this._closeCalled = false;
  400. if (typeof options === 'function') {
  401. callback = options;
  402. options = null;
  403. }
  404. if (['string', 'number'].indexOf(typeof options) !== -1) {
  405. throw new MongooseError('Mongoose 5.x no longer supports ' +
  406. '`mongoose.connect(host, dbname, port)` or ' +
  407. '`mongoose.createConnection(host, dbname, port)`. See ' +
  408. 'http://mongoosejs.com/docs/connections.html for supported connection syntax');
  409. }
  410. if (typeof uri !== 'string') {
  411. throw new MongooseError('The `uri` parameter to `openUri()` must be a ' +
  412. `string, got "${typeof uri}". Make sure the first parameter to ` +
  413. '`mongoose.connect()` or `mongoose.createConnection()` is a string.');
  414. }
  415. const Promise = PromiseProvider.get();
  416. const _this = this;
  417. if (options) {
  418. options = utils.clone(options);
  419. const autoIndex = options.config && options.config.autoIndex != null ?
  420. options.config.autoIndex :
  421. options.autoIndex;
  422. if (autoIndex != null) {
  423. this.config.autoIndex = autoIndex !== false;
  424. delete options.config;
  425. delete options.autoIndex;
  426. }
  427. if ('autoCreate' in options) {
  428. this.config.autoCreate = !!options.autoCreate;
  429. delete options.autoCreate;
  430. }
  431. if ('useCreateIndex' in options) {
  432. this.config.useCreateIndex = !!options.useCreateIndex;
  433. delete options.useCreateIndex;
  434. }
  435. if ('useFindAndModify' in options) {
  436. this.config.useFindAndModify = !!options.useFindAndModify;
  437. delete options.useFindAndModify;
  438. }
  439. // Backwards compat
  440. if (options.user || options.pass) {
  441. options.auth = options.auth || {};
  442. options.auth.user = options.user;
  443. options.auth.password = options.pass;
  444. this.user = options.user;
  445. this.pass = options.pass;
  446. }
  447. delete options.user;
  448. delete options.pass;
  449. if (options.bufferCommands != null) {
  450. options.bufferMaxEntries = 0;
  451. this.config.bufferCommands = options.bufferCommands;
  452. delete options.bufferCommands;
  453. }
  454. if (options.useMongoClient != null) {
  455. handleUseMongoClient(options);
  456. }
  457. } else {
  458. options = {};
  459. }
  460. this._connectionOptions = options;
  461. const dbName = options.dbName;
  462. if (dbName != null) {
  463. this.$dbName = dbName;
  464. }
  465. delete options.dbName;
  466. if (!('promiseLibrary' in options)) {
  467. options.promiseLibrary = PromiseProvider.get();
  468. }
  469. if (!('useNewUrlParser' in options)) {
  470. if ('useNewUrlParser' in this.base.options) {
  471. options.useNewUrlParser = this.base.options.useNewUrlParser;
  472. } else {
  473. options.useNewUrlParser = false;
  474. }
  475. }
  476. const parsePromise = new Promise((resolve, reject) => {
  477. parseConnectionString(uri, options, (err, parsed) => {
  478. if (err) {
  479. return reject(err);
  480. }
  481. this.name = dbName != null ? dbName : get(parsed, 'auth.db', null);
  482. this.host = get(parsed, 'hosts.0.host', 'localhost');
  483. this.port = get(parsed, 'hosts.0.port', 27017);
  484. this.user = this.user || get(parsed, 'auth.username');
  485. this.pass = this.pass || get(parsed, 'auth.password');
  486. resolve();
  487. });
  488. });
  489. const promise = new Promise((resolve, reject) => {
  490. const client = new mongodb.MongoClient(uri, options);
  491. _this.client = client;
  492. client.connect(function(error) {
  493. if (error) {
  494. _this.readyState = STATES.disconnected;
  495. return reject(error);
  496. }
  497. const db = dbName != null ? client.db(dbName) : client.db();
  498. _this.db = db;
  499. // Backwards compat for mongoose 4.x
  500. db.on('reconnect', function() {
  501. // If we aren't disconnected, we assume this reconnect is due to a
  502. // socket timeout. If there's no activity on a socket for
  503. // `socketTimeoutMS`, the driver will attempt to reconnect and emit
  504. // this event.
  505. if (_this.readyState !== STATES.connected) {
  506. _this.readyState = STATES.connected;
  507. _this.emit('reconnect');
  508. _this.emit('reconnected');
  509. }
  510. });
  511. db.s.topology.on('reconnectFailed', function() {
  512. _this.emit('reconnectFailed');
  513. });
  514. db.s.topology.on('left', function(data) {
  515. _this.emit('left', data);
  516. });
  517. db.s.topology.on('joined', function(data) {
  518. _this.emit('joined', data);
  519. });
  520. db.s.topology.on('fullsetup', function(data) {
  521. _this.emit('fullsetup', data);
  522. });
  523. db.on('close', function() {
  524. // Implicitly emits 'disconnected'
  525. _this.readyState = STATES.disconnected;
  526. });
  527. client.on('left', function() {
  528. if (_this.readyState === STATES.connected &&
  529. get(db, 's.topology.s.coreTopology.s.replicaSetState.topologyType') === 'ReplicaSetNoPrimary') {
  530. _this.readyState = STATES.disconnected;
  531. }
  532. });
  533. db.on('timeout', function() {
  534. _this.emit('timeout');
  535. });
  536. delete _this.then;
  537. delete _this.catch;
  538. _this.readyState = STATES.connected;
  539. for (const i in _this.collections) {
  540. if (utils.object.hasOwnProperty(_this.collections, i)) {
  541. _this.collections[i].onOpen();
  542. }
  543. }
  544. resolve(_this);
  545. _this.emit('open');
  546. });
  547. });
  548. this.$initialConnection = Promise.all([promise, parsePromise]).
  549. then(res => res[0]).
  550. catch(err => {
  551. if (this.listeners('error').length > 0) {
  552. process.nextTick(() => this.emit('error', err));
  553. return;
  554. }
  555. throw err;
  556. });
  557. this.then = function(resolve, reject) {
  558. return this.$initialConnection.then(resolve, reject);
  559. };
  560. this.catch = function(reject) {
  561. return this.$initialConnection.catch(reject);
  562. };
  563. if (callback != null) {
  564. this.$initialConnection = this.$initialConnection.then(
  565. () => callback(null, this),
  566. err => callback(err)
  567. );
  568. }
  569. return this;
  570. };
  571. /*!
  572. * ignore
  573. */
  574. const handleUseMongoClient = function handleUseMongoClient(options) {
  575. console.warn('WARNING: The `useMongoClient` option is no longer ' +
  576. 'necessary in mongoose 5.x, please remove it.');
  577. const stack = new Error().stack;
  578. console.warn(stack.substr(stack.indexOf('\n') + 1));
  579. delete options.useMongoClient;
  580. };
  581. /**
  582. * Closes the connection
  583. *
  584. * @param {Boolean} [force] optional
  585. * @param {Function} [callback] optional
  586. * @return {Connection} self
  587. * @api public
  588. */
  589. Connection.prototype.close = function(force, callback) {
  590. if (typeof force === 'function') {
  591. callback = force;
  592. force = false;
  593. }
  594. this.$wasForceClosed = !!force;
  595. return utils.promiseOrCallback(callback, cb => {
  596. this._close(force, cb);
  597. });
  598. };
  599. /**
  600. * Handles closing the connection
  601. *
  602. * @param {Boolean} force
  603. * @param {Function} callback
  604. * @api private
  605. */
  606. Connection.prototype._close = function(force, callback) {
  607. const _this = this;
  608. this._closeCalled = true;
  609. switch (this.readyState) {
  610. case 0: // disconnected
  611. callback();
  612. break;
  613. case 1: // connected
  614. this.readyState = STATES.disconnecting;
  615. this.doClose(force, function(err) {
  616. if (err) {
  617. return callback(err);
  618. }
  619. _this.onClose(force);
  620. callback(null);
  621. });
  622. break;
  623. case 2: // connecting
  624. this.once('open', function() {
  625. _this.close(callback);
  626. });
  627. break;
  628. case 3: // disconnecting
  629. this.once('close', function() {
  630. callback();
  631. });
  632. break;
  633. }
  634. return this;
  635. };
  636. /**
  637. * Called when the connection closes
  638. *
  639. * @api private
  640. */
  641. Connection.prototype.onClose = function(force) {
  642. this.readyState = STATES.disconnected;
  643. // avoid having the collection subscribe to our event emitter
  644. // to prevent 0.3 warning
  645. for (const i in this.collections) {
  646. if (utils.object.hasOwnProperty(this.collections, i)) {
  647. this.collections[i].onClose(force);
  648. }
  649. }
  650. this.emit('close', force);
  651. };
  652. /**
  653. * Retrieves a collection, creating it if not cached.
  654. *
  655. * Not typically needed by applications. Just talk to your collection through your model.
  656. *
  657. * @param {String} name of the collection
  658. * @param {Object} [options] optional collection options
  659. * @return {Collection} collection instance
  660. * @api public
  661. */
  662. Connection.prototype.collection = function(name, options) {
  663. options = options ? utils.clone(options) : {};
  664. options.$wasForceClosed = this.$wasForceClosed;
  665. if (!(name in this.collections)) {
  666. this.collections[name] = new Collection(name, this, options);
  667. }
  668. return this.collections[name];
  669. };
  670. /**
  671. * Declares a plugin executed on all schemas you pass to `conn.model()`
  672. *
  673. * Equivalent to calling `.plugin(fn)` on each schema you create.
  674. *
  675. * ####Example:
  676. * const db = mongoose.createConnection('mongodb://localhost:27017/mydb');
  677. * db.plugin(() => console.log('Applied'));
  678. * db.plugins.length; // 1
  679. *
  680. * db.model('Test', new Schema({})); // Prints "Applied"
  681. *
  682. * @param {Function} fn plugin callback
  683. * @param {Object} [opts] optional options
  684. * @return {Connection} this
  685. * @see plugins ./plugins.html
  686. * @api public
  687. */
  688. Connection.prototype.plugin = function(fn, opts) {
  689. this.plugins.push([fn, opts]);
  690. return this;
  691. };
  692. /**
  693. * Defines or retrieves a model.
  694. *
  695. * var mongoose = require('mongoose');
  696. * var db = mongoose.createConnection(..);
  697. * db.model('Venue', new Schema(..));
  698. * var Ticket = db.model('Ticket', new Schema(..));
  699. * var Venue = db.model('Venue');
  700. *
  701. * _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._
  702. *
  703. * ####Example:
  704. *
  705. * var schema = new Schema({ name: String }, { collection: 'actor' });
  706. *
  707. * // or
  708. *
  709. * schema.set('collection', 'actor');
  710. *
  711. * // or
  712. *
  713. * var collectionName = 'actor'
  714. * var M = conn.model('Actor', schema, collectionName)
  715. *
  716. * @param {String|Function} name the model name or class extending Model
  717. * @param {Schema} [schema] a schema. necessary when defining a model
  718. * @param {String} [collection] name of mongodb collection (optional) if not given it will be induced from model name
  719. * @see Mongoose#model #index_Mongoose-model
  720. * @return {Model} The compiled model
  721. * @api public
  722. */
  723. Connection.prototype.model = function(name, schema, collection) {
  724. if (!(this instanceof Connection)) {
  725. throw new MongooseError('`connection.model()` should not be run with ' +
  726. '`new`. If you are doing `new db.model(foo)(bar)`, use ' +
  727. '`db.model(foo)(bar)` instead');
  728. }
  729. let fn;
  730. if (typeof name === 'function') {
  731. fn = name;
  732. name = fn.name;
  733. }
  734. // collection name discovery
  735. if (typeof schema === 'string') {
  736. collection = schema;
  737. schema = false;
  738. }
  739. if (utils.isObject(schema) && !schema.instanceOfSchema) {
  740. schema = new Schema(schema);
  741. }
  742. if (schema && !schema.instanceOfSchema) {
  743. throw new Error('The 2nd parameter to `mongoose.model()` should be a ' +
  744. 'schema or a POJO');
  745. }
  746. if (this.models[name] && !collection) {
  747. // model exists but we are not subclassing with custom collection
  748. if (schema && schema.instanceOfSchema && schema !== this.models[name].schema) {
  749. throw new MongooseError.OverwriteModelError(name);
  750. }
  751. return this.models[name];
  752. }
  753. const opts = {cache: false, connection: this};
  754. let model;
  755. if (schema && schema.instanceOfSchema) {
  756. applyPlugins(schema, this.plugins, null, '$connectionPluginsApplied');
  757. // compile a model
  758. model = this.base.model(fn || name, schema, collection, opts);
  759. // only the first model with this name is cached to allow
  760. // for one-offs with custom collection names etc.
  761. if (!this.models[name]) {
  762. this.models[name] = model;
  763. }
  764. // Errors handled internally, so safe to ignore error
  765. model.init(function $modelInitNoop() {});
  766. return model;
  767. }
  768. if (this.models[name] && collection) {
  769. // subclassing current model with alternate collection
  770. model = this.models[name];
  771. schema = model.prototype.schema;
  772. const sub = model.__subclass(this, schema, collection);
  773. // do not cache the sub model
  774. return sub;
  775. }
  776. // lookup model in mongoose module
  777. model = this.base.models[name];
  778. if (!model) {
  779. throw new MongooseError.MissingSchemaError(name);
  780. }
  781. if (this === model.prototype.db
  782. && (!collection || collection === model.collection.name)) {
  783. // model already uses this connection.
  784. // only the first model with this name is cached to allow
  785. // for one-offs with custom collection names etc.
  786. if (!this.models[name]) {
  787. this.models[name] = model;
  788. }
  789. return model;
  790. }
  791. this.models[name] = model.__subclass(this, schema, collection);
  792. return this.models[name];
  793. };
  794. /**
  795. * Removes the model named `name` from this connection, if it exists. You can
  796. * use this function to clean up any models you created in your tests to
  797. * prevent OverwriteModelErrors.
  798. *
  799. * ####Example:
  800. *
  801. * conn.model('User', new Schema({ name: String }));
  802. * console.log(conn.model('User')); // Model object
  803. * conn.deleteModel('User');
  804. * console.log(conn.model('User')); // undefined
  805. *
  806. * // Usually useful in a Mocha `afterEach()` hook
  807. * afterEach(function() {
  808. * conn.deleteModel(/.+/); // Delete every model
  809. * });
  810. *
  811. * @api public
  812. * @param {String|RegExp} name if string, the name of the model to remove. If regexp, removes all models whose name matches the regexp.
  813. * @return {Connection} this
  814. */
  815. Connection.prototype.deleteModel = function(name) {
  816. if (typeof name === 'string') {
  817. const model = this.model(name);
  818. if (model == null) {
  819. return this;
  820. }
  821. delete this.models[name];
  822. delete this.collections[model.collection.name];
  823. delete this.base.modelSchemas[name];
  824. } else if (name instanceof RegExp) {
  825. const pattern = name;
  826. const names = this.modelNames();
  827. for (const name of names) {
  828. if (pattern.test(name)) {
  829. this.deleteModel(name);
  830. }
  831. }
  832. } else {
  833. throw new Error('First parameter to `deleteModel()` must be a string ' +
  834. 'or regexp, got "' + name + '"');
  835. }
  836. return this;
  837. };
  838. /**
  839. * Returns an array of model names created on this connection.
  840. * @api public
  841. * @return {Array}
  842. */
  843. Connection.prototype.modelNames = function() {
  844. return Object.keys(this.models);
  845. };
  846. /**
  847. * @brief Returns if the connection requires authentication after it is opened. Generally if a
  848. * username and password are both provided than authentication is needed, but in some cases a
  849. * password is not required.
  850. * @api private
  851. * @return {Boolean} true if the connection should be authenticated after it is opened, otherwise false.
  852. */
  853. Connection.prototype.shouldAuthenticate = function() {
  854. return this.user != null &&
  855. (this.pass != null || this.authMechanismDoesNotRequirePassword());
  856. };
  857. /**
  858. * @brief Returns a boolean value that specifies if the current authentication mechanism needs a
  859. * password to authenticate according to the auth objects passed into the openUri methods.
  860. * @api private
  861. * @return {Boolean} true if the authentication mechanism specified in the options object requires
  862. * a password, otherwise false.
  863. */
  864. Connection.prototype.authMechanismDoesNotRequirePassword = function() {
  865. if (this.options && this.options.auth) {
  866. return noPasswordAuthMechanisms.indexOf(this.options.auth.authMechanism) >= 0;
  867. }
  868. return true;
  869. };
  870. /**
  871. * @brief Returns a boolean value that specifies if the provided objects object provides enough
  872. * data to authenticate with. Generally this is true if the username and password are both specified
  873. * but in some authentication methods, a password is not required for authentication so only a username
  874. * is required.
  875. * @param {Object} [options] the options object passed into the openUri methods.
  876. * @api private
  877. * @return {Boolean} true if the provided options object provides enough data to authenticate with,
  878. * otherwise false.
  879. */
  880. Connection.prototype.optionsProvideAuthenticationData = function(options) {
  881. return (options) &&
  882. (options.user) &&
  883. ((options.pass) || this.authMechanismDoesNotRequirePassword());
  884. };
  885. /**
  886. * Switches to a different database using the same connection pool.
  887. *
  888. * Returns a new connection object, with the new db.
  889. *
  890. * @method useDb
  891. * @memberOf Connection
  892. * @param {String} name The database name
  893. * @return {Connection} New Connection Object
  894. * @api public
  895. */
  896. /*!
  897. * Module exports.
  898. */
  899. Connection.STATES = STATES;
  900. module.exports = Connection;