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