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.

386 lines
9.9 KiB

  1. /*!
  2. * Module dependencies.
  3. */
  4. var MongooseConnection = require('../../connection');
  5. var mongo = require('mongodb');
  6. var Db = mongo.Db;
  7. var Server = mongo.Server;
  8. var Mongos = mongo.Mongos;
  9. var STATES = require('../../connectionstate');
  10. var ReplSetServers = mongo.ReplSet;
  11. var DisconnectedError = require('../../error/disconnected');
  12. /**
  13. * A [node-mongodb-native](https://github.com/mongodb/node-mongodb-native) connection implementation.
  14. *
  15. * @inherits Connection
  16. * @api private
  17. */
  18. function NativeConnection() {
  19. MongooseConnection.apply(this, arguments);
  20. this._listening = false;
  21. }
  22. /**
  23. * Expose the possible connection states.
  24. * @api public
  25. */
  26. NativeConnection.STATES = STATES;
  27. /*!
  28. * Inherits from Connection.
  29. */
  30. NativeConnection.prototype.__proto__ = MongooseConnection.prototype;
  31. /**
  32. * Opens the connection to MongoDB.
  33. *
  34. * @param {Function} fn
  35. * @return {Connection} this
  36. * @api private
  37. */
  38. NativeConnection.prototype.doOpen = function(fn) {
  39. var _this = this;
  40. var server = new Server(this.host, this.port, this.options.server);
  41. if (this.options && this.options.mongos) {
  42. var mongos = new Mongos([server], this.options.mongos);
  43. this.db = new Db(this.name, mongos, this.options.db);
  44. } else {
  45. this.db = new Db(this.name, server, this.options.db);
  46. }
  47. this.db.open(function(err) {
  48. listen(_this);
  49. if (!mongos) {
  50. server.s.server.on('error', function(error) {
  51. if (/after \d+ retries/.test(error.message)) {
  52. _this.emit('error', new DisconnectedError(server.s.server.name));
  53. }
  54. });
  55. }
  56. if (err) return fn(err);
  57. fn();
  58. });
  59. return this;
  60. };
  61. /**
  62. * Switches to a different database using the same connection pool.
  63. *
  64. * Returns a new connection object, with the new db.
  65. *
  66. * @param {String} name The database name
  67. * @return {Connection} New Connection Object
  68. * @api public
  69. */
  70. NativeConnection.prototype.useDb = function(name) {
  71. // we have to manually copy all of the attributes...
  72. var newConn = new this.constructor();
  73. newConn.name = name;
  74. newConn.base = this.base;
  75. newConn.collections = {};
  76. newConn.models = {};
  77. newConn.replica = this.replica;
  78. newConn.hosts = this.hosts;
  79. newConn.host = this.host;
  80. newConn.port = this.port;
  81. newConn.user = this.user;
  82. newConn.pass = this.pass;
  83. newConn.options = this.options;
  84. newConn._readyState = this._readyState;
  85. newConn._closeCalled = this._closeCalled;
  86. newConn._hasOpened = this._hasOpened;
  87. newConn._listening = false;
  88. // First, when we create another db object, we are not guaranteed to have a
  89. // db object to work with. So, in the case where we have a db object and it
  90. // is connected, we can just proceed with setting everything up. However, if
  91. // we do not have a db or the state is not connected, then we need to wait on
  92. // the 'open' event of the connection before doing the rest of the setup
  93. // the 'connected' event is the first time we'll have access to the db object
  94. var _this = this;
  95. if (this.db && this._readyState === STATES.connected) {
  96. wireup();
  97. } else {
  98. this.once('connected', wireup);
  99. }
  100. function wireup() {
  101. newConn.db = _this.db.db(name);
  102. newConn.onOpen();
  103. // setup the events appropriately
  104. listen(newConn);
  105. }
  106. newConn.name = name;
  107. // push onto the otherDbs stack, this is used when state changes
  108. this.otherDbs.push(newConn);
  109. newConn.otherDbs.push(this);
  110. return newConn;
  111. };
  112. /*!
  113. * Register listeners for important events and bubble appropriately.
  114. */
  115. function listen(conn) {
  116. if (conn.db._listening) {
  117. return;
  118. }
  119. conn.db._listening = true;
  120. conn.db.on('close', function() {
  121. if (conn._closeCalled) return;
  122. // the driver never emits an `open` event. auto_reconnect still
  123. // emits a `close` event but since we never get another
  124. // `open` we can't emit close
  125. if (conn.db.serverConfig.autoReconnect) {
  126. conn.readyState = STATES.disconnected;
  127. conn.emit('close');
  128. return;
  129. }
  130. conn.onClose();
  131. });
  132. conn.db.on('error', function(err) {
  133. conn.emit('error', err);
  134. });
  135. conn.db.on('reconnect', function() {
  136. conn.readyState = STATES.connected;
  137. conn.emit('reconnected');
  138. });
  139. conn.db.on('timeout', function(err) {
  140. var error = new Error(err && err.err || 'connection timeout');
  141. conn.emit('error', error);
  142. });
  143. conn.db.on('open', function(err, db) {
  144. if (STATES.disconnected === conn.readyState && db && db.databaseName) {
  145. conn.readyState = STATES.connected;
  146. conn.emit('reconnected');
  147. }
  148. });
  149. conn.db.on('parseError', function(err) {
  150. conn.emit('parseError', err);
  151. });
  152. }
  153. /**
  154. * Opens a connection to a MongoDB ReplicaSet.
  155. *
  156. * See description of [doOpen](#NativeConnection-doOpen) for server options. In this case `options.replset` is also passed to ReplSetServers.
  157. *
  158. * @param {Function} fn
  159. * @api private
  160. * @return {Connection} this
  161. */
  162. NativeConnection.prototype.doOpenSet = function(fn) {
  163. var servers = [],
  164. _this = this;
  165. this.hosts.forEach(function(server) {
  166. var host = server.host || server.ipc;
  167. var port = server.port || 27017;
  168. servers.push(new Server(host, port, _this.options.server));
  169. });
  170. var server = this.options.mongos
  171. ? new Mongos(servers, this.options.mongos)
  172. : new ReplSetServers(servers, this.options.replset || this.options.replSet);
  173. this.db = new Db(this.name, server, this.options.db);
  174. this.db.on('fullsetup', function() {
  175. _this.emit('fullsetup');
  176. });
  177. this.db.on('all', function() {
  178. _this.emit('all');
  179. });
  180. this.db.open(function(err) {
  181. if (err) return fn(err);
  182. fn();
  183. listen(_this);
  184. });
  185. return this;
  186. };
  187. /**
  188. * Closes the connection
  189. *
  190. * @param {Function} fn
  191. * @return {Connection} this
  192. * @api private
  193. */
  194. NativeConnection.prototype.doClose = function(fn) {
  195. this.db.close(fn);
  196. return this;
  197. };
  198. /**
  199. * Prepares default connection options for the node-mongodb-native driver.
  200. *
  201. * _NOTE: `passed` options take precedence over connection string options._
  202. *
  203. * @param {Object} passed options that were passed directly during connection
  204. * @param {Object} [connStrOptions] options that were passed in the connection string
  205. * @api private
  206. */
  207. NativeConnection.prototype.parseOptions = function(passed, connStrOpts) {
  208. var o = passed || {};
  209. o.db || (o.db = {});
  210. o.auth || (o.auth = {});
  211. o.server || (o.server = {});
  212. o.replset || (o.replset = o.replSet) || (o.replset = {});
  213. o.server.socketOptions || (o.server.socketOptions = {});
  214. o.replset.socketOptions || (o.replset.socketOptions = {});
  215. o.mongos || (o.mongos = (connStrOpts && connStrOpts.mongos));
  216. (o.mongos === true) && (o.mongos = {});
  217. var opts = connStrOpts || {};
  218. Object.keys(opts).forEach(function(name) {
  219. switch (name) {
  220. case 'ssl':
  221. o.server.ssl = opts.ssl;
  222. o.replset.ssl = opts.ssl;
  223. o.mongos && (o.mongos.ssl = opts.ssl);
  224. break;
  225. case 'poolSize':
  226. if (typeof o.server[name] === 'undefined') {
  227. o.server[name] = o.replset[name] = opts[name];
  228. }
  229. break;
  230. case 'slaveOk':
  231. if (typeof o.server.slave_ok === 'undefined') {
  232. o.server.slave_ok = opts[name];
  233. }
  234. break;
  235. case 'autoReconnect':
  236. if (typeof o.server.auto_reconnect === 'undefined') {
  237. o.server.auto_reconnect = opts[name];
  238. }
  239. break;
  240. case 'socketTimeoutMS':
  241. case 'connectTimeoutMS':
  242. if (typeof o.server.socketOptions[name] === 'undefined') {
  243. o.server.socketOptions[name] = o.replset.socketOptions[name] = opts[name];
  244. }
  245. break;
  246. case 'authdb':
  247. if (typeof o.auth.authdb === 'undefined') {
  248. o.auth.authdb = opts[name];
  249. }
  250. break;
  251. case 'authSource':
  252. if (typeof o.auth.authSource === 'undefined') {
  253. o.auth.authSource = opts[name];
  254. }
  255. break;
  256. case 'retries':
  257. case 'reconnectWait':
  258. case 'rs_name':
  259. if (typeof o.replset[name] === 'undefined') {
  260. o.replset[name] = opts[name];
  261. }
  262. break;
  263. case 'replicaSet':
  264. if (typeof o.replset.rs_name === 'undefined') {
  265. o.replset.rs_name = opts[name];
  266. }
  267. break;
  268. case 'readSecondary':
  269. if (typeof o.replset.read_secondary === 'undefined') {
  270. o.replset.read_secondary = opts[name];
  271. }
  272. break;
  273. case 'nativeParser':
  274. if (typeof o.db.native_parser === 'undefined') {
  275. o.db.native_parser = opts[name];
  276. }
  277. break;
  278. case 'w':
  279. case 'safe':
  280. case 'fsync':
  281. case 'journal':
  282. case 'wtimeoutMS':
  283. if (typeof o.db[name] === 'undefined') {
  284. o.db[name] = opts[name];
  285. }
  286. break;
  287. case 'readPreference':
  288. if (typeof o.db.readPreference === 'undefined') {
  289. o.db.readPreference = opts[name];
  290. }
  291. break;
  292. case 'readPreferenceTags':
  293. if (typeof o.db.read_preference_tags === 'undefined') {
  294. o.db.read_preference_tags = opts[name];
  295. }
  296. break;
  297. case 'sslValidate':
  298. o.server.sslValidate = opts.sslValidate;
  299. o.replset.sslValidate = opts.sslValidate;
  300. o.mongos && (o.mongos.sslValidate = opts.sslValidate);
  301. }
  302. });
  303. if (!('auto_reconnect' in o.server)) {
  304. o.server.auto_reconnect = true;
  305. }
  306. // mongoose creates its own ObjectIds
  307. o.db.forceServerObjectId = false;
  308. // default safe using new nomenclature
  309. if (!('journal' in o.db || 'j' in o.db ||
  310. 'fsync' in o.db || 'safe' in o.db || 'w' in o.db)) {
  311. o.db.w = 1;
  312. }
  313. if (o.promiseLibrary) {
  314. o.db.promiseLibrary = o.promiseLibrary;
  315. }
  316. validate(o);
  317. return o;
  318. };
  319. /*!
  320. * Validates the driver db options.
  321. *
  322. * @param {Object} o
  323. */
  324. function validate(o) {
  325. if (o.db.w === -1 || o.db.w === 0) {
  326. if (o.db.journal || o.db.fsync || o.db.safe) {
  327. throw new Error(
  328. 'Invalid writeConcern: '
  329. + 'w set to -1 or 0 cannot be combined with safe|fsync|journal');
  330. }
  331. }
  332. }
  333. /*!
  334. * Module exports.
  335. */
  336. module.exports = NativeConnection;