UNPKG

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