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.

711 lines
18 KiB

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