connection.js 46 KB

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