connection.js 27 KB

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