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