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 26KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954
  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 private
  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. _this.readyState = STATES.connected;
  467. _this.emit('reconnect');
  468. _this.emit('reconnected');
  469. });
  470. db.s.topology.on('reconnectFailed', function() {
  471. _this.emit('reconnectFailed');
  472. });
  473. db.s.topology.on('left', function(data) {
  474. _this.emit('left', data);
  475. });
  476. db.s.topology.on('joined', function(data) {
  477. _this.emit('joined', data);
  478. });
  479. db.s.topology.on('fullsetup', function(data) {
  480. _this.emit('fullsetup', data);
  481. });
  482. db.on('close', function() {
  483. // Implicitly emits 'disconnected'
  484. _this.readyState = STATES.disconnected;
  485. });
  486. client.on('left', function() {
  487. if (_this.readyState === STATES.connected &&
  488. get(db, 's.topology.s.coreTopology.s.replicaSetState.topologyType') === 'ReplicaSetNoPrimary') {
  489. _this.readyState = STATES.disconnected;
  490. }
  491. });
  492. db.on('timeout', function() {
  493. _this.emit('timeout');
  494. });
  495. delete _this.then;
  496. delete _this.catch;
  497. _this.readyState = STATES.connected;
  498. for (const i in _this.collections) {
  499. if (utils.object.hasOwnProperty(_this.collections, i)) {
  500. _this.collections[i].onOpen();
  501. }
  502. }
  503. resolve(_this);
  504. _this.emit('open');
  505. });
  506. });
  507. this.$initialConnection = Promise.all([promise, parsePromise]).
  508. then(res => res[0]).
  509. catch(err => {
  510. if (this.listeners('error').length > 0) {
  511. process.nextTick(() => this.emit('error', err));
  512. return;
  513. }
  514. throw err;
  515. });
  516. this.then = function(resolve, reject) {
  517. return this.$initialConnection.then(resolve, reject);
  518. };
  519. this.catch = function(reject) {
  520. return this.$initialConnection.catch(reject);
  521. };
  522. if (callback != null) {
  523. this.$initialConnection = this.$initialConnection.then(
  524. () => callback(null, this),
  525. err => callback(err)
  526. );
  527. }
  528. return this;
  529. };
  530. /*!
  531. * ignore
  532. */
  533. const handleUseMongoClient = function handleUseMongoClient(options) {
  534. console.warn('WARNING: The `useMongoClient` option is no longer ' +
  535. 'necessary in mongoose 5.x, please remove it.');
  536. const stack = new Error().stack;
  537. console.warn(stack.substr(stack.indexOf('\n') + 1));
  538. delete options.useMongoClient;
  539. };
  540. /**
  541. * Closes the connection
  542. *
  543. * @param {Boolean} [force] optional
  544. * @param {Function} [callback] optional
  545. * @return {Connection} self
  546. * @api public
  547. */
  548. Connection.prototype.close = function(force, callback) {
  549. if (typeof force === 'function') {
  550. callback = force;
  551. force = false;
  552. }
  553. this.$wasForceClosed = !!force;
  554. return utils.promiseOrCallback(callback, cb => {
  555. this._close(force, cb);
  556. });
  557. };
  558. /**
  559. * Handles closing the connection
  560. *
  561. * @param {Boolean} force
  562. * @param {Function} callback
  563. * @api private
  564. */
  565. Connection.prototype._close = function(force, callback) {
  566. const _this = this;
  567. this._closeCalled = true;
  568. switch (this.readyState) {
  569. case 0: // disconnected
  570. callback();
  571. break;
  572. case 1: // connected
  573. this.readyState = STATES.disconnecting;
  574. this.doClose(force, function(err) {
  575. if (err) {
  576. return callback(err);
  577. }
  578. _this.onClose(force);
  579. callback(null);
  580. });
  581. break;
  582. case 2: // connecting
  583. this.once('open', function() {
  584. _this.close(callback);
  585. });
  586. break;
  587. case 3: // disconnecting
  588. this.once('close', function() {
  589. callback();
  590. });
  591. break;
  592. }
  593. return this;
  594. };
  595. /**
  596. * Called when the connection closes
  597. *
  598. * @api private
  599. */
  600. Connection.prototype.onClose = function(force) {
  601. this.readyState = STATES.disconnected;
  602. // avoid having the collection subscribe to our event emitter
  603. // to prevent 0.3 warning
  604. for (const i in this.collections) {
  605. if (utils.object.hasOwnProperty(this.collections, i)) {
  606. this.collections[i].onClose(force);
  607. }
  608. }
  609. this.emit('close', force);
  610. };
  611. /**
  612. * Retrieves a collection, creating it if not cached.
  613. *
  614. * Not typically needed by applications. Just talk to your collection through your model.
  615. *
  616. * @param {String} name of the collection
  617. * @param {Object} [options] optional collection options
  618. * @return {Collection} collection instance
  619. * @api public
  620. */
  621. Connection.prototype.collection = function(name, options) {
  622. options = options ? utils.clone(options) : {};
  623. options.$wasForceClosed = this.$wasForceClosed;
  624. if (!(name in this.collections)) {
  625. this.collections[name] = new Collection(name, this, options);
  626. }
  627. return this.collections[name];
  628. };
  629. /**
  630. * Defines or retrieves a model.
  631. *
  632. * var mongoose = require('mongoose');
  633. * var db = mongoose.createConnection(..);
  634. * db.model('Venue', new Schema(..));
  635. * var Ticket = db.model('Ticket', new Schema(..));
  636. * var Venue = db.model('Venue');
  637. *
  638. * _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._
  639. *
  640. * ####Example:
  641. *
  642. * var schema = new Schema({ name: String }, { collection: 'actor' });
  643. *
  644. * // or
  645. *
  646. * schema.set('collection', 'actor');
  647. *
  648. * // or
  649. *
  650. * var collectionName = 'actor'
  651. * var M = conn.model('Actor', schema, collectionName)
  652. *
  653. * @param {String|Function} name the model name or class extending Model
  654. * @param {Schema} [schema] a schema. necessary when defining a model
  655. * @param {String} [collection] name of mongodb collection (optional) if not given it will be induced from model name
  656. * @see Mongoose#model #index_Mongoose-model
  657. * @return {Model} The compiled model
  658. * @api public
  659. */
  660. Connection.prototype.model = function(name, schema, collection) {
  661. if (!(this instanceof Connection)) {
  662. throw new MongooseError('`connection.model()` should not be run with ' +
  663. '`new`. If you are doing `new db.model(foo)(bar)`, use ' +
  664. '`db.model(foo)(bar)` instead');
  665. }
  666. let fn;
  667. if (typeof name === 'function') {
  668. fn = name;
  669. name = fn.name;
  670. }
  671. // collection name discovery
  672. if (typeof schema === 'string') {
  673. collection = schema;
  674. schema = false;
  675. }
  676. if (utils.isObject(schema) && !schema.instanceOfSchema) {
  677. schema = new Schema(schema);
  678. }
  679. if (schema && !schema.instanceOfSchema) {
  680. throw new Error('The 2nd parameter to `mongoose.model()` should be a ' +
  681. 'schema or a POJO');
  682. }
  683. if (this.models[name] && !collection) {
  684. // model exists but we are not subclassing with custom collection
  685. if (schema && schema.instanceOfSchema && schema !== this.models[name].schema) {
  686. throw new MongooseError.OverwriteModelError(name);
  687. }
  688. return this.models[name];
  689. }
  690. const opts = {cache: false, connection: this};
  691. let model;
  692. if (schema && schema.instanceOfSchema) {
  693. // compile a model
  694. model = this.base.model(fn || name, schema, collection, opts);
  695. // only the first model with this name is cached to allow
  696. // for one-offs with custom collection names etc.
  697. if (!this.models[name]) {
  698. this.models[name] = model;
  699. }
  700. // Errors handled internally, so safe to ignore error
  701. model.init(function $modelInitNoop() {});
  702. return model;
  703. }
  704. if (this.models[name] && collection) {
  705. // subclassing current model with alternate collection
  706. model = this.models[name];
  707. schema = model.prototype.schema;
  708. const sub = model.__subclass(this, schema, collection);
  709. // do not cache the sub model
  710. return sub;
  711. }
  712. // lookup model in mongoose module
  713. model = this.base.models[name];
  714. if (!model) {
  715. throw new MongooseError.MissingSchemaError(name);
  716. }
  717. if (this === model.prototype.db
  718. && (!collection || collection === model.collection.name)) {
  719. // model already uses this connection.
  720. // only the first model with this name is cached to allow
  721. // for one-offs with custom collection names etc.
  722. if (!this.models[name]) {
  723. this.models[name] = model;
  724. }
  725. return model;
  726. }
  727. this.models[name] = model.__subclass(this, schema, collection);
  728. return this.models[name];
  729. };
  730. /**
  731. * Removes the model named `name` from this connection, if it exists. You can
  732. * use this function to clean up any models you created in your tests to
  733. * prevent OverwriteModelErrors.
  734. *
  735. * ####Example:
  736. *
  737. * conn.model('User', new Schema({ name: String }));
  738. * console.log(conn.model('User')); // Model object
  739. * conn.deleteModel('User');
  740. * console.log(conn.model('User')); // undefined
  741. *
  742. * // Usually useful in a Mocha `afterEach()` hook
  743. * afterEach(function() {
  744. * conn.deleteModel(/.+/); // Delete every model
  745. * });
  746. *
  747. * @api public
  748. * @param {String|RegExp} name if string, the name of the model to remove. If regexp, removes all models whose name matches the regexp.
  749. * @return {Connection} this
  750. */
  751. Connection.prototype.deleteModel = function(name) {
  752. if (typeof name === 'string') {
  753. const model = this.model(name);
  754. if (model == null) {
  755. return this;
  756. }
  757. delete this.models[name];
  758. delete this.collections[model.collection.name];
  759. delete this.base.modelSchemas[name];
  760. } else if (name instanceof RegExp) {
  761. const pattern = name;
  762. const names = this.modelNames();
  763. for (const name of names) {
  764. if (pattern.test(name)) {
  765. this.deleteModel(name);
  766. }
  767. }
  768. } else {
  769. throw new Error('First parameter to `deleteModel()` must be a string ' +
  770. 'or regexp, got "' + name + '"');
  771. }
  772. return this;
  773. };
  774. /**
  775. * Returns an array of model names created on this connection.
  776. * @api public
  777. * @return {Array}
  778. */
  779. Connection.prototype.modelNames = function() {
  780. return Object.keys(this.models);
  781. };
  782. /**
  783. * @brief Returns if the connection requires authentication after it is opened. Generally if a
  784. * username and password are both provided than authentication is needed, but in some cases a
  785. * password is not required.
  786. * @api private
  787. * @return {Boolean} true if the connection should be authenticated after it is opened, otherwise false.
  788. */
  789. Connection.prototype.shouldAuthenticate = function() {
  790. return this.user != null &&
  791. (this.pass != null || this.authMechanismDoesNotRequirePassword());
  792. };
  793. /**
  794. * @brief Returns a boolean value that specifies if the current authentication mechanism needs a
  795. * password to authenticate according to the auth objects passed into the openUri methods.
  796. * @api private
  797. * @return {Boolean} true if the authentication mechanism specified in the options object requires
  798. * a password, otherwise false.
  799. */
  800. Connection.prototype.authMechanismDoesNotRequirePassword = function() {
  801. if (this.options && this.options.auth) {
  802. return noPasswordAuthMechanisms.indexOf(this.options.auth.authMechanism) >= 0;
  803. }
  804. return true;
  805. };
  806. /**
  807. * @brief Returns a boolean value that specifies if the provided objects object provides enough
  808. * data to authenticate with. Generally this is true if the username and password are both specified
  809. * but in some authentication methods, a password is not required for authentication so only a username
  810. * is required.
  811. * @param {Object} [options] the options object passed into the openUri methods.
  812. * @api private
  813. * @return {Boolean} true if the provided options object provides enough data to authenticate with,
  814. * otherwise false.
  815. */
  816. Connection.prototype.optionsProvideAuthenticationData = function(options) {
  817. return (options) &&
  818. (options.user) &&
  819. ((options.pass) || this.authMechanismDoesNotRequirePassword());
  820. };
  821. /**
  822. * Switches to a different database using the same connection pool.
  823. *
  824. * Returns a new connection object, with the new db.
  825. *
  826. * @method useDb
  827. * @memberOf Connection
  828. * @param {String} name The database name
  829. * @return {Connection} New Connection Object
  830. * @api public
  831. */
  832. /*!
  833. * Module exports.
  834. */
  835. Connection.STATES = STATES;
  836. module.exports = Connection;