connection.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701
  1. /*!
  2. * Module dependencies.
  3. */
  4. var url = require('url')
  5. , utils = require('./utils')
  6. , EventEmitter = require('events').EventEmitter
  7. , driver = global.MONGOOSE_DRIVER_PATH || 'node-mongodb-native'
  8. , Model = require('./model')
  9. , Schema = require('./schema')
  10. , Collection = require('./drivers/' + driver + '/collection')
  11. , STATES = require('./connectionstate')
  12. , MongooseError = require('./error')
  13. , assert =require('assert')
  14. , muri = require('muri')
  15. /*!
  16. * Protocol prefix regexp.
  17. *
  18. * @api private
  19. */
  20. var rgxProtocol = /^(?:.)+:\/\//;
  21. /**
  22. * Connection constructor
  23. *
  24. * For practical reasons, a Connection equals a Db.
  25. *
  26. * @param {Mongoose} base a mongoose instance
  27. * @inherits NodeJS EventEmitter http://nodejs.org/api/events.html#events_class_events_eventemitter
  28. * @event `connecting`: Emitted when `connection.{open,openSet}()` is executed on this connection.
  29. * @event `connected`: Emitted when this connection successfully connects to the db. May be emitted _multiple_ times in `reconnected` scenarios.
  30. * @event `open`: Emitted after we `connected` and `onOpen` is executed on all of this connections models.
  31. * @event `disconnecting`: Emitted when `connection.close()` was executed.
  32. * @event `disconnected`: Emitted after getting disconnected from the db.
  33. * @event `close`: Emitted after we `disconnected` and `onClose` executed on all of this connections models.
  34. * @event `reconnected`: Emitted after we `connected` and subsequently `disconnected`, followed by successfully another successfull connection.
  35. * @event `error`: Emitted when an error occurs on this connection.
  36. * @event `fullsetup`: Emitted in a replica-set scenario, when all nodes specified in the connection string are connected.
  37. * @api public
  38. */
  39. function Connection (base) {
  40. this.base = base;
  41. this.collections = {};
  42. this.models = {};
  43. this.config = {autoIndex: true};
  44. this.replica = false;
  45. this.hosts = null;
  46. this.host = null;
  47. this.port = null;
  48. this.user = null;
  49. this.pass = null;
  50. this.name = null;
  51. this.options = null;
  52. this.otherDbs = [];
  53. this._readyState = STATES.disconnected;
  54. this._closeCalled = false;
  55. this._hasOpened = false;
  56. };
  57. /*!
  58. * Inherit from EventEmitter
  59. */
  60. Connection.prototype.__proto__ = EventEmitter.prototype;
  61. /**
  62. * Connection ready state
  63. *
  64. * - 0 = disconnected
  65. * - 1 = connected
  66. * - 2 = connecting
  67. * - 3 = disconnecting
  68. *
  69. * Each state change emits its associated event name.
  70. *
  71. * ####Example
  72. *
  73. * conn.on('connected', callback);
  74. * conn.on('disconnected', callback);
  75. *
  76. * @property readyState
  77. * @api public
  78. */
  79. Object.defineProperty(Connection.prototype, 'readyState', {
  80. get: function(){ return this._readyState; }
  81. , set: function (val) {
  82. if (!(val in STATES)) {
  83. throw new Error('Invalid connection state: ' + val);
  84. }
  85. if (this._readyState !== val) {
  86. this._readyState = val;
  87. // loop over the otherDbs on this connection and change their state
  88. for (var i=0; i < this.otherDbs.length; i++) {
  89. this.otherDbs[i].readyState = val;
  90. }
  91. if (STATES.connected === val)
  92. this._hasOpened = true;
  93. this.emit(STATES[val]);
  94. }
  95. }
  96. });
  97. /**
  98. * A hash of the collections associated with this connection
  99. *
  100. * @property collections
  101. */
  102. Connection.prototype.collections;
  103. /**
  104. * The mongodb.Db instance, set when the connection is opened
  105. *
  106. * @property db
  107. */
  108. Connection.prototype.db;
  109. /**
  110. * A hash of the global options that are associated with this connection
  111. *
  112. * @property global
  113. */
  114. Connection.prototype.config;
  115. /**
  116. * Opens the connection to MongoDB.
  117. *
  118. * `options` is a hash with the following possible properties:
  119. *
  120. * config - passed to the connection config instance
  121. * db - passed to the connection db instance
  122. * server - passed to the connection server instance(s)
  123. * replset - passed to the connection ReplSet instance
  124. * user - username for authentication
  125. * pass - password for authentication
  126. * auth - options for authentication (see http://mongodb.github.com/node-mongodb-native/api-generated/db.html#authenticate)
  127. *
  128. * ####Notes:
  129. *
  130. * Mongoose forces the db option `forceServerObjectId` false and cannot be overridden.
  131. * Mongoose defaults the server `auto_reconnect` options to true which can be overridden.
  132. * See the node-mongodb-native driver instance for options that it understands.
  133. *
  134. * _Options passed take precedence over options included in connection strings._
  135. *
  136. * @param {String} connection_string mongodb://uri or the host to which you are connecting
  137. * @param {String} [database] database name
  138. * @param {Number} [port] database port
  139. * @param {Object} [options] options
  140. * @param {Function} [callback]
  141. * @see node-mongodb-native https://github.com/mongodb/node-mongodb-native
  142. * @see http://mongodb.github.com/node-mongodb-native/api-generated/db.html#authenticate
  143. * @api public
  144. */
  145. Connection.prototype.open = function (host, database, port, options, callback) {
  146. var self = this
  147. , parsed
  148. , uri;
  149. if ('string' === typeof database) {
  150. switch (arguments.length) {
  151. case 2:
  152. port = 27017;
  153. case 3:
  154. switch (typeof port) {
  155. case 'function':
  156. callback = port, port = 27017;
  157. break;
  158. case 'object':
  159. options = port, port = 27017;
  160. break;
  161. }
  162. break;
  163. case 4:
  164. if ('function' === typeof options)
  165. callback = options, options = {};
  166. }
  167. } else {
  168. switch (typeof database) {
  169. case 'function':
  170. callback = database, database = undefined;
  171. break;
  172. case 'object':
  173. options = database;
  174. database = undefined;
  175. callback = port;
  176. break;
  177. }
  178. if (!rgxProtocol.test(host)) {
  179. host = 'mongodb://' + host;
  180. }
  181. try {
  182. parsed = muri(host);
  183. } catch (err) {
  184. this.error(err, callback);
  185. return this;
  186. }
  187. database = parsed.db;
  188. host = parsed.hosts[0].host || parsed.hosts[0].ipc;
  189. port = parsed.hosts[0].port || 27017;
  190. }
  191. this.options = this.parseOptions(options, parsed && parsed.options);
  192. // make sure we can open
  193. if (STATES.disconnected !== this.readyState) {
  194. var err = new Error('Trying to open unclosed connection.');
  195. err.state = this.readyState;
  196. this.error(err, callback);
  197. return this;
  198. }
  199. if (!host) {
  200. this.error(new Error('Missing hostname.'), callback);
  201. return this;
  202. }
  203. if (!database) {
  204. this.error(new Error('Missing database name.'), callback);
  205. return this;
  206. }
  207. // authentication
  208. if (options && options.user && options.pass) {
  209. this.user = options.user;
  210. this.pass = options.pass;
  211. } else if (parsed && parsed.auth) {
  212. this.user = parsed.auth.user;
  213. this.pass = parsed.auth.pass;
  214. // Check hostname for user/pass
  215. } else if (/@/.test(host) && /:/.test(host.split('@')[0])) {
  216. host = host.split('@');
  217. var auth = host.shift().split(':');
  218. host = host.pop();
  219. this.user = auth[0];
  220. this.pass = auth[1];
  221. } else {
  222. this.user = this.pass = undefined;
  223. }
  224. // global configuration options
  225. if (options && options.config) {
  226. if (options.config.autoIndex === false){
  227. this.config.autoIndex = false;
  228. }
  229. else {
  230. this.config.autoIndex = true;
  231. }
  232. }
  233. this.name = database;
  234. this.host = host;
  235. this.port = port;
  236. this._open(callback);
  237. return this;
  238. };
  239. /**
  240. * Opens the connection to a replica set.
  241. *
  242. * ####Example:
  243. *
  244. * var db = mongoose.createConnection();
  245. * db.openSet("mongodb://user:pwd@localhost:27020/testing,mongodb://example.com:27020,mongodb://localhost:27019");
  246. *
  247. * The database name and/or auth need only be included in one URI.
  248. * The `options` is a hash which is passed to the internal driver connection object.
  249. *
  250. * Valid `options`
  251. *
  252. * db - passed to the connection db instance
  253. * server - passed to the connection server instance(s)
  254. * replset - passed to the connection ReplSetServer instance
  255. * user - username for authentication
  256. * pass - password for authentication
  257. * auth - options for authentication (see http://mongodb.github.com/node-mongodb-native/api-generated/db.html#authenticate)
  258. * mongos - Boolean - if true, enables High Availability support for mongos
  259. *
  260. * _Options passed take precedence over options included in connection strings._
  261. *
  262. * ####Notes:
  263. *
  264. * _If connecting to multiple mongos servers, set the `mongos` option to true._
  265. *
  266. * conn.open('mongodb://mongosA:27501,mongosB:27501', { mongos: true }, cb);
  267. *
  268. * Mongoose forces the db option `forceServerObjectId` false and cannot be overridden.
  269. * Mongoose defaults the server `auto_reconnect` options to true which can be overridden.
  270. * See the node-mongodb-native driver instance for options that it understands.
  271. *
  272. * _Options passed take precedence over options included in connection strings._
  273. *
  274. * @param {String} uris comma-separated mongodb:// `URI`s
  275. * @param {String} [database] database name if not included in `uris`
  276. * @param {Object} [options] passed to the internal driver
  277. * @param {Function} [callback]
  278. * @see node-mongodb-native https://github.com/mongodb/node-mongodb-native
  279. * @see http://mongodb.github.com/node-mongodb-native/api-generated/db.html#authenticate
  280. * @api public
  281. */
  282. Connection.prototype.openSet = function (uris, database, options, callback) {
  283. if (!rgxProtocol.test(uris)) {
  284. uris = 'mongodb://' + uris;
  285. }
  286. var self = this;
  287. switch (arguments.length) {
  288. case 3:
  289. switch (typeof database) {
  290. case 'string':
  291. this.name = database;
  292. break;
  293. case 'object':
  294. callback = options;
  295. options = database;
  296. database = null;
  297. break;
  298. }
  299. if ('function' === typeof options) {
  300. callback = options;
  301. options = {};
  302. }
  303. break;
  304. case 2:
  305. switch (typeof database) {
  306. case 'string':
  307. this.name = database;
  308. break;
  309. case 'function':
  310. callback = database, database = null;
  311. break;
  312. case 'object':
  313. options = database, database = null;
  314. break;
  315. }
  316. }
  317. var parsed;
  318. try {
  319. parsed = muri(uris);
  320. } catch (err) {
  321. this.error(err, callback);
  322. return this;
  323. }
  324. if (!this.name) {
  325. this.name = parsed.db;
  326. }
  327. this.hosts = parsed.hosts;
  328. this.options = this.parseOptions(options, parsed && parsed.options);
  329. this.replica = true;
  330. if (!this.name) {
  331. this.error(new Error('No database name provided for replica set'), callback);
  332. return this;
  333. }
  334. // authentication
  335. if (options && options.user && options.pass) {
  336. this.user = options.user;
  337. this.pass = options.pass;
  338. } else if (parsed && parsed.auth) {
  339. this.user = parsed.auth.user;
  340. this.pass = parsed.auth.pass;
  341. } else {
  342. this.user = this.pass = undefined;
  343. }
  344. // global configuration options
  345. if (options && options.config) {
  346. if (options.config.autoIndex === false){
  347. this.config.autoIndex = false;
  348. }
  349. else {
  350. this.config.autoIndex = true;
  351. }
  352. }
  353. this._open(callback);
  354. return this;
  355. };
  356. /**
  357. * error
  358. *
  359. * Graceful error handling, passes error to callback
  360. * if available, else emits error on the connection.
  361. *
  362. * @param {Error} err
  363. * @param {Function} callback optional
  364. * @api private
  365. */
  366. Connection.prototype.error = function (err, callback) {
  367. if (callback) return callback(err);
  368. this.emit('error', err);
  369. }
  370. /**
  371. * Handles opening the connection with the appropriate method based on connection type.
  372. *
  373. * @param {Function} callback
  374. * @api private
  375. */
  376. Connection.prototype._open = function (callback) {
  377. this.readyState = STATES.connecting;
  378. this._closeCalled = false;
  379. var self = this;
  380. var method = this.replica
  381. ? 'doOpenSet'
  382. : 'doOpen';
  383. // open connection
  384. this[method](function (err) {
  385. if (err) {
  386. self.readyState = STATES.disconnected;
  387. if (self._hasOpened) {
  388. if (callback) callback(err);
  389. } else {
  390. self.error(err, callback);
  391. }
  392. return;
  393. }
  394. self.onOpen(callback);
  395. });
  396. }
  397. /**
  398. * Called when the connection is opened
  399. *
  400. * @api private
  401. */
  402. Connection.prototype.onOpen = function (callback) {
  403. var self = this;
  404. function open(err, isAuth) {
  405. if (err) {
  406. self.readyState = isAuth ? STATES.unauthorized : STATES.disconnected;
  407. if (self._hasOpened) {
  408. if (callback) callback(err);
  409. } else {
  410. self.error(err, callback);
  411. }
  412. return;
  413. }
  414. self.readyState = STATES.connected;
  415. // avoid having the collection subscribe to our event emitter
  416. // to prevent 0.3 warning
  417. for (var i in self.collections)
  418. self.collections[i].onOpen();
  419. callback && callback();
  420. self.emit('open');
  421. };
  422. // re-authenticate
  423. if (self.user && self.pass) {
  424. self.db.authenticate(self.user, self.pass, self.options.auth, function(err) {
  425. open(err, true);
  426. });
  427. } else {
  428. open();
  429. }
  430. };
  431. /**
  432. * Closes the connection
  433. *
  434. * @param {Function} [callback] optional
  435. * @return {Connection} self
  436. * @api public
  437. */
  438. Connection.prototype.close = function (callback) {
  439. var self = this;
  440. this._closeCalled = true;
  441. switch (this.readyState){
  442. case 0: // disconnected
  443. callback && callback();
  444. break;
  445. case 1: // connected
  446. case 4: // unauthorized
  447. this.readyState = STATES.disconnecting;
  448. this.doClose(function(err){
  449. if (err){
  450. self.error(err, callback);
  451. } else {
  452. self.onClose();
  453. callback && callback();
  454. }
  455. });
  456. break;
  457. case 2: // connecting
  458. this.once('open', function(){
  459. self.close(callback);
  460. });
  461. break;
  462. case 3: // disconnecting
  463. if (!callback) break;
  464. this.once('close', function () {
  465. callback();
  466. });
  467. break;
  468. }
  469. return this;
  470. };
  471. /**
  472. * Called when the connection closes
  473. *
  474. * @api private
  475. */
  476. Connection.prototype.onClose = function () {
  477. this.readyState = STATES.disconnected;
  478. // avoid having the collection subscribe to our event emitter
  479. // to prevent 0.3 warning
  480. for (var i in this.collections)
  481. this.collections[i].onClose();
  482. this.emit('close');
  483. };
  484. /**
  485. * Retrieves a collection, creating it if not cached.
  486. *
  487. * Not typically needed by applications. Just talk to your collection through your model.
  488. *
  489. * @param {String} name of the collection
  490. * @param {Object} [options] optional collection options
  491. * @return {Collection} collection instance
  492. * @api public
  493. */
  494. Connection.prototype.collection = function (name, options) {
  495. if (!(name in this.collections))
  496. this.collections[name] = new Collection(name, this, options);
  497. return this.collections[name];
  498. };
  499. /**
  500. * Defines or retrieves a model.
  501. *
  502. * var mongoose = require('mongoose');
  503. * var db = mongoose.createConnection(..);
  504. * db.model('Venue', new Schema(..));
  505. * var Ticket = db.model('Ticket', new Schema(..));
  506. * var Venue = db.model('Venue');
  507. *
  508. * _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._
  509. *
  510. * ####Example:
  511. *
  512. * var schema = new Schema({ name: String }, { collection: 'actor' });
  513. *
  514. * // or
  515. *
  516. * schema.set('collection', 'actor');
  517. *
  518. * // or
  519. *
  520. * var collectionName = 'actor'
  521. * var M = conn.model('Actor', schema, collectionName)
  522. *
  523. * @param {String} name the model name
  524. * @param {Schema} [schema] a schema. necessary when defining a model
  525. * @param {String} [collection] name of mongodb collection (optional) if not given it will be induced from model name
  526. * @see Mongoose#model #index_Mongoose-model
  527. * @return {Model} The compiled model
  528. * @api public
  529. */
  530. Connection.prototype.model = function (name, schema, collection) {
  531. // collection name discovery
  532. if ('string' == typeof schema) {
  533. collection = schema;
  534. schema = false;
  535. }
  536. if (utils.isObject(schema) && !(schema instanceof Schema)) {
  537. schema = new Schema(schema);
  538. }
  539. if (this.models[name] && !collection) {
  540. // model exists but we are not subclassing with custom collection
  541. if (schema instanceof Schema && schema != this.models[name].schema) {
  542. throw new MongooseError.OverwriteModelError(name);
  543. }
  544. return this.models[name];
  545. }
  546. var opts = { cache: false, connection: this }
  547. var model;
  548. if (schema instanceof Schema) {
  549. // compile a model
  550. model = this.base.model(name, schema, collection, opts)
  551. // only the first model with this name is cached to allow
  552. // for one-offs with custom collection names etc.
  553. if (!this.models[name]) {
  554. this.models[name] = model;
  555. }
  556. model.init();
  557. return model;
  558. }
  559. if (this.models[name] && collection) {
  560. // subclassing current model with alternate collection
  561. model = this.models[name];
  562. schema = model.prototype.schema;
  563. var sub = model.__subclass(this, schema, collection);
  564. // do not cache the sub model
  565. return sub;
  566. }
  567. // lookup model in mongoose module
  568. model = this.base.models[name];
  569. if (!model) {
  570. throw new MongooseError.MissingSchemaError(name);
  571. }
  572. if (this == model.prototype.db
  573. && (!collection || collection == model.collection.name)) {
  574. // model already uses this connection.
  575. // only the first model with this name is cached to allow
  576. // for one-offs with custom collection names etc.
  577. if (!this.models[name]) {
  578. this.models[name] = model;
  579. }
  580. return model;
  581. }
  582. return this.models[name] = model.__subclass(this, schema, collection);
  583. };
  584. /**
  585. * Returns an array of model names created on this connection.
  586. * @api public
  587. * @return {Array}
  588. */
  589. Connection.prototype.modelNames = function () {
  590. return Object.keys(this.models);
  591. };
  592. /*!
  593. * Noop.
  594. */
  595. function noop () {}
  596. /*!
  597. * Module exports.
  598. */
  599. Connection.STATES = STATES;
  600. module.exports = Connection;