mongo_client.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. "use strict";
  2. var parse = require('./url_parser')
  3. , Server = require('./server')
  4. , Mongos = require('./mongos')
  5. , ReplSet = require('./replset')
  6. , Define = require('./metadata')
  7. , ReadPreference = require('./read_preference')
  8. , Logger = require('mongodb-core').Logger
  9. , MongoError = require('mongodb-core').MongoError
  10. , Db = require('./db')
  11. , f = require('util').format
  12. , shallowClone = require('./utils').shallowClone;
  13. /**
  14. * @fileOverview The **MongoClient** class is a class that allows for making Connections to MongoDB.
  15. *
  16. * @example
  17. * var MongoClient = require('mongodb').MongoClient,
  18. * test = require('assert');
  19. * // Connection url
  20. * var url = 'mongodb://localhost:27017/test';
  21. * // Connect using MongoClient
  22. * MongoClient.connect(url, function(err, db) {
  23. * // Get an additional db
  24. * db.close();
  25. * });
  26. */
  27. /**
  28. * Creates a new MongoClient instance
  29. * @class
  30. * @return {MongoClient} a MongoClient instance.
  31. */
  32. function MongoClient() {
  33. /**
  34. * The callback format for results
  35. * @callback MongoClient~connectCallback
  36. * @param {MongoError} error An error instance representing the error during the execution.
  37. * @param {Db} db The connected database.
  38. */
  39. /**
  40. * Connect to MongoDB using a url as documented at
  41. *
  42. * docs.mongodb.org/manual/reference/connection-string/
  43. *
  44. * Note that for replicasets the replicaSet query parameter is required in the 2.0 driver
  45. *
  46. * @method
  47. * @param {string} url The connection URI string
  48. * @param {object} [options=null] Optional settings.
  49. * @param {boolean} [options.uri_decode_auth=false] Uri decode the user name and password for authentication
  50. * @param {object} [options.db=null] A hash of options to set on the db object, see **Db constructor**
  51. * @param {object} [options.server=null] A hash of options to set on the server objects, see **Server** constructor**
  52. * @param {object} [options.replSet=null] A hash of options to set on the replSet object, see **ReplSet** constructor**
  53. * @param {object} [options.mongos=null] A hash of options to set on the mongos object, see **Mongos** constructor**
  54. * @param {object} [options.promiseLibrary=null] A Promise library class the application wishes to use such as Bluebird, must be ES6 compatible
  55. * @param {MongoClient~connectCallback} [callback] The command result callback
  56. * @return {Promise} returns Promise if no callback passed
  57. */
  58. this.connect = MongoClient.connect;
  59. }
  60. var define = MongoClient.define = new Define('MongoClient', MongoClient, false);
  61. /**
  62. * Connect to MongoDB using a url as documented at
  63. *
  64. * docs.mongodb.org/manual/reference/connection-string/
  65. *
  66. * Note that for replicasets the replicaSet query parameter is required in the 2.0 driver
  67. *
  68. * @method
  69. * @static
  70. * @param {string} url The connection URI string
  71. * @param {object} [options=null] Optional settings.
  72. * @param {boolean} [options.uri_decode_auth=false] Uri decode the user name and password for authentication
  73. * @param {object} [options.db=null] A hash of options to set on the db object, see **Db constructor**
  74. * @param {object} [options.server=null] A hash of options to set on the server objects, see **Server** constructor**
  75. * @param {object} [options.replSet=null] A hash of options to set on the replSet object, see **ReplSet** constructor**
  76. * @param {object} [options.mongos=null] A hash of options to set on the mongos object, see **Mongos** constructor**
  77. * @param {object} [options.promiseLibrary=null] A Promise library class the application wishes to use such as Bluebird, must be ES6 compatible
  78. * @param {MongoClient~connectCallback} [callback] The command result callback
  79. * @return {Promise} returns Promise if no callback passed
  80. */
  81. MongoClient.connect = function(url, options, callback) {
  82. var args = Array.prototype.slice.call(arguments, 1);
  83. callback = typeof args[args.length - 1] == 'function' ? args.pop() : null;
  84. options = args.length ? args.shift() : null;
  85. options = options || {};
  86. // Get the promiseLibrary
  87. var promiseLibrary = options.promiseLibrary;
  88. // No promise library selected fall back
  89. if(!promiseLibrary) {
  90. promiseLibrary = typeof global.Promise == 'function' ?
  91. global.Promise : require('es6-promise').Promise;
  92. }
  93. // Return a promise
  94. if(typeof callback != 'function') {
  95. return new promiseLibrary(function(resolve, reject) {
  96. connect(url, options, function(err, db) {
  97. if(err) return reject(err);
  98. resolve(db);
  99. });
  100. });
  101. }
  102. // Fallback to callback based connect
  103. connect(url, options, callback);
  104. }
  105. define.staticMethod('connect', {callback: true, promise:true});
  106. var mergeOptions = function(target, source, flatten) {
  107. for(var name in source) {
  108. if(source[name] && typeof source[name] == 'object' && flatten) {
  109. target = mergeOptions(target, source[name], flatten);
  110. } else {
  111. target[name] = source[name];
  112. }
  113. }
  114. return target;
  115. }
  116. var createUnifiedOptions = function(finalOptions, options) {
  117. var childOptions = ['mongos', 'server', 'db'
  118. , 'replset', 'db_options', 'server_options', 'rs_options', 'mongos_options'];
  119. var noMerge = [];
  120. for(var name in options) {
  121. if(noMerge.indexOf(name.toLowerCase()) != -1) {
  122. finalOptions[name] = options[name];
  123. } else if(childOptions.indexOf(name.toLowerCase()) != -1) {
  124. finalOptions = mergeOptions(finalOptions, options[name], false);
  125. } else {
  126. if(options[name] && typeof options[name] == 'object' && !Buffer.isBuffer(options[name]) && !Array.isArray(options[name])) {
  127. finalOptions = mergeOptions(finalOptions, options[name], true);
  128. } else {
  129. finalOptions[name] = options[name];
  130. }
  131. }
  132. }
  133. return finalOptions;
  134. }
  135. function translateOptions(options) {
  136. // If we have a readPreference passed in by the db options
  137. if(typeof options.readPreference == 'string' || typeof options.read_preference == 'string') {
  138. options.readPreference = new ReadPreference(options.readPreference || options.read_preference);
  139. }
  140. // Do we have readPreference tags, add them
  141. if(options.readPreference && (options.readPreferenceTags || options.read_preference_tags)) {
  142. options.readPreference.tags = options.readPreferenceTags || options.read_preference_tags;
  143. }
  144. // Do we have maxStalenessSeconds
  145. if(options.maxStalenessSeconds) {
  146. options.readPreference.maxStalenessSeconds = options.maxStalenessSeconds;
  147. }
  148. // Set the socket and connection timeouts
  149. if(options.socketTimeoutMS == null) options.socketTimeoutMS = 30000;
  150. if(options.connectTimeoutMS == null) options.connectTimeoutMS = 30000;
  151. // Create server instances
  152. return options.servers.map(function(serverObj) {
  153. return serverObj.domain_socket ?
  154. new Server(serverObj.domain_socket, 27017, options)
  155. : new Server(serverObj.host, serverObj.port, options);
  156. });
  157. }
  158. function createReplicaset(options, callback) {
  159. // Set default options
  160. var servers = translateOptions(options);
  161. // Create Db instance
  162. new Db(options.dbName, new ReplSet(servers, options), options).open(callback);
  163. }
  164. function createMongos(options, callback) {
  165. // Set default options
  166. var servers = translateOptions(options);
  167. // Create Db instance
  168. new Db(options.dbName, new Mongos(servers, options), options).open(callback);
  169. }
  170. function createServer(options, callback) {
  171. // Set default options
  172. var servers = translateOptions(options);
  173. // Create Db instance
  174. new Db(options.dbName, servers[0], options).open(function(err, db) {
  175. if(err) return callback(err);
  176. // Check if we are really speaking to a mongos
  177. var ismaster = db.serverConfig.lastIsMaster();
  178. // Do we actually have a mongos
  179. if(ismaster && ismaster.msg == 'isdbgrid') {
  180. // Destroy the current connection
  181. db.close();
  182. // Create mongos connection instead
  183. return createMongos(options, callback);
  184. }
  185. // Otherwise callback
  186. callback(err, db);
  187. });
  188. }
  189. function connectHandler(options, callback) {
  190. return function (err, db) {
  191. if(err) {
  192. return process.nextTick(function() {
  193. try {
  194. callback(err, null);
  195. } catch (err) {
  196. if(db) db.close();
  197. throw err
  198. }
  199. });
  200. }
  201. // No authentication just reconnect
  202. if(!options.auth) {
  203. return process.nextTick(function() {
  204. try {
  205. callback(err, db);
  206. } catch (err) {
  207. if(db) db.close();
  208. throw err
  209. }
  210. })
  211. }
  212. // What db to authenticate against
  213. var authentication_db = db;
  214. if(options.authSource) {
  215. authentication_db = db.db(options.authSource);
  216. }
  217. // Authenticate
  218. authentication_db.authenticate(options.user, options.password, options, function(err, success){
  219. if(success){
  220. process.nextTick(function() {
  221. try {
  222. callback(null, db);
  223. } catch (err) {
  224. if(db) db.close();
  225. throw err
  226. }
  227. });
  228. } else {
  229. if(db) db.close();
  230. process.nextTick(function() {
  231. try {
  232. callback(err ? err : new Error('Could not authenticate user ' + options.auth[0]), null);
  233. } catch (err) {
  234. if(db) db.close();
  235. throw err
  236. }
  237. });
  238. }
  239. });
  240. }
  241. }
  242. /*
  243. * Connect using MongoClient
  244. */
  245. var connect = function(url, options, callback) {
  246. options = options || {};
  247. options = shallowClone(options);
  248. // If callback is null throw an exception
  249. if(callback == null) {
  250. throw new Error("no callback function provided");
  251. }
  252. // Get a logger for MongoClient
  253. var logger = Logger('MongoClient', options);
  254. // Parse the string
  255. var object = parse(url, options);
  256. var _finalOptions = createUnifiedOptions({}, object);
  257. _finalOptions = mergeOptions(_finalOptions, object, false);
  258. _finalOptions = createUnifiedOptions(_finalOptions, options);
  259. // Check if we have connection and socket timeout set
  260. if(_finalOptions.socketTimeoutMS == null) _finalOptions.socketTimeoutMS = 30000;
  261. if(_finalOptions.connectTimeoutMS == null) _finalOptions.connectTimeoutMS = 30000;
  262. // Failure modes
  263. if(object.servers.length == 0) {
  264. throw new Error("connection string must contain at least one seed host");
  265. }
  266. function connectCallback(err, db) {
  267. if(err && err.message == 'no mongos proxies found in seed list') {
  268. if(logger.isWarn()) {
  269. logger.warn(f('seed list contains no mongos proxies, replicaset connections requires the parameter replicaSet to be supplied in the URI or options object, mongodb://server:port/db?replicaSet=name'));
  270. }
  271. // Return a more specific error message for MongoClient.connect
  272. return callback(new MongoError('seed list contains no mongos proxies, replicaset connections requires the parameter replicaSet to be supplied in the URI or options object, mongodb://server:port/db?replicaSet=name'));
  273. }
  274. // Return the error and db instance
  275. callback(err, db);
  276. }
  277. // Do we have a replicaset then skip discovery and go straight to connectivity
  278. if(_finalOptions.replicaSet || _finalOptions.rs_name) {
  279. return createReplicaset(_finalOptions, connectHandler(_finalOptions, connectCallback));
  280. } else if(object.servers.length > 1) {
  281. return createMongos(_finalOptions, connectHandler(_finalOptions, connectCallback));
  282. } else {
  283. return createServer(_finalOptions, connectHandler(_finalOptions, connectCallback));
  284. }
  285. }
  286. module.exports = MongoClient