Ohm-Management - Projektarbeit B-ME
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

connection.js 29KB

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