NewsBlur-viq/node/node_modules/mongodb/lib/mongo_client.js
2016-11-29 18:29:50 -08:00

327 lines
11 KiB
JavaScript

"use strict";
var parse = require('./url_parser')
, Server = require('./server')
, Mongos = require('./mongos')
, ReplSet = require('./replset')
, Define = require('./metadata')
, ReadPreference = require('./read_preference')
, Logger = require('mongodb-core').Logger
, MongoError = require('mongodb-core').MongoError
, Db = require('./db')
, f = require('util').format
, shallowClone = require('./utils').shallowClone;
/**
* @fileOverview The **MongoClient** class is a class that allows for making Connections to MongoDB.
*
* @example
* var MongoClient = require('mongodb').MongoClient,
* test = require('assert');
* // Connection url
* var url = 'mongodb://localhost:27017/test';
* // Connect using MongoClient
* MongoClient.connect(url, function(err, db) {
* // Get an additional db
* db.close();
* });
*/
/**
* Creates a new MongoClient instance
* @class
* @return {MongoClient} a MongoClient instance.
*/
function MongoClient() {
/**
* The callback format for results
* @callback MongoClient~connectCallback
* @param {MongoError} error An error instance representing the error during the execution.
* @param {Db} db The connected database.
*/
/**
* Connect to MongoDB using a url as documented at
*
* docs.mongodb.org/manual/reference/connection-string/
*
* Note that for replicasets the replicaSet query parameter is required in the 2.0 driver
*
* @method
* @param {string} url The connection URI string
* @param {object} [options=null] Optional settings.
* @param {boolean} [options.uri_decode_auth=false] Uri decode the user name and password for authentication
* @param {object} [options.db=null] A hash of options to set on the db object, see **Db constructor**
* @param {object} [options.server=null] A hash of options to set on the server objects, see **Server** constructor**
* @param {object} [options.replSet=null] A hash of options to set on the replSet object, see **ReplSet** constructor**
* @param {object} [options.mongos=null] A hash of options to set on the mongos object, see **Mongos** constructor**
* @param {object} [options.promiseLibrary=null] A Promise library class the application wishes to use such as Bluebird, must be ES6 compatible
* @param {MongoClient~connectCallback} [callback] The command result callback
* @return {Promise} returns Promise if no callback passed
*/
this.connect = MongoClient.connect;
}
var define = MongoClient.define = new Define('MongoClient', MongoClient, false);
/**
* Connect to MongoDB using a url as documented at
*
* docs.mongodb.org/manual/reference/connection-string/
*
* Note that for replicasets the replicaSet query parameter is required in the 2.0 driver
*
* @method
* @static
* @param {string} url The connection URI string
* @param {object} [options=null] Optional settings.
* @param {boolean} [options.uri_decode_auth=false] Uri decode the user name and password for authentication
* @param {object} [options.db=null] A hash of options to set on the db object, see **Db constructor**
* @param {object} [options.server=null] A hash of options to set on the server objects, see **Server** constructor**
* @param {object} [options.replSet=null] A hash of options to set on the replSet object, see **ReplSet** constructor**
* @param {object} [options.mongos=null] A hash of options to set on the mongos object, see **Mongos** constructor**
* @param {object} [options.promiseLibrary=null] A Promise library class the application wishes to use such as Bluebird, must be ES6 compatible
* @param {MongoClient~connectCallback} [callback] The command result callback
* @return {Promise} returns Promise if no callback passed
*/
MongoClient.connect = function(url, options, callback) {
var args = Array.prototype.slice.call(arguments, 1);
callback = typeof args[args.length - 1] == 'function' ? args.pop() : null;
options = args.length ? args.shift() : null;
options = options || {};
// Get the promiseLibrary
var promiseLibrary = options.promiseLibrary;
// No promise library selected fall back
if(!promiseLibrary) {
promiseLibrary = typeof global.Promise == 'function' ?
global.Promise : require('es6-promise').Promise;
}
// Return a promise
if(typeof callback != 'function') {
return new promiseLibrary(function(resolve, reject) {
connect(url, options, function(err, db) {
if(err) return reject(err);
resolve(db);
});
});
}
// Fallback to callback based connect
connect(url, options, callback);
}
define.staticMethod('connect', {callback: true, promise:true});
var mergeOptions = function(target, source, flatten) {
for(var name in source) {
if(source[name] && typeof source[name] == 'object' && flatten) {
target = mergeOptions(target, source[name], flatten);
} else {
target[name] = source[name];
}
}
return target;
}
var createUnifiedOptions = function(finalOptions, options) {
var childOptions = ['mongos', 'server', 'db'
, 'replset', 'db_options', 'server_options', 'rs_options', 'mongos_options'];
var noMerge = [];
for(var name in options) {
if(noMerge.indexOf(name.toLowerCase()) != -1) {
finalOptions[name] = options[name];
} else if(childOptions.indexOf(name.toLowerCase()) != -1) {
finalOptions = mergeOptions(finalOptions, options[name], false);
} else {
if(options[name] && typeof options[name] == 'object' && !Buffer.isBuffer(options[name]) && !Array.isArray(options[name])) {
finalOptions = mergeOptions(finalOptions, options[name], true);
} else {
finalOptions[name] = options[name];
}
}
}
return finalOptions;
}
function translateOptions(options) {
// If we have a readPreference passed in by the db options
if(typeof options.readPreference == 'string' || typeof options.read_preference == 'string') {
options.readPreference = new ReadPreference(options.readPreference || options.read_preference);
}
// Do we have readPreference tags, add them
if(options.readPreference && (options.readPreferenceTags || options.read_preference_tags)) {
options.readPreference.tags = options.readPreferenceTags || options.read_preference_tags;
}
// Do we have maxStalenessSeconds
if(options.maxStalenessSeconds) {
options.readPreference.maxStalenessSeconds = options.maxStalenessSeconds;
}
// Set the socket and connection timeouts
if(options.socketTimeoutMS == null) options.socketTimeoutMS = 30000;
if(options.connectTimeoutMS == null) options.connectTimeoutMS = 30000;
// Create server instances
return options.servers.map(function(serverObj) {
return serverObj.domain_socket ?
new Server(serverObj.domain_socket, 27017, options)
: new Server(serverObj.host, serverObj.port, options);
});
}
function createReplicaset(options, callback) {
// Set default options
var servers = translateOptions(options);
// Create Db instance
new Db(options.dbName, new ReplSet(servers, options), options).open(callback);
}
function createMongos(options, callback) {
// Set default options
var servers = translateOptions(options);
// Create Db instance
new Db(options.dbName, new Mongos(servers, options), options).open(callback);
}
function createServer(options, callback) {
// Set default options
var servers = translateOptions(options);
// Create Db instance
new Db(options.dbName, servers[0], options).open(function(err, db) {
if(err) return callback(err);
// Check if we are really speaking to a mongos
var ismaster = db.serverConfig.lastIsMaster();
// Do we actually have a mongos
if(ismaster && ismaster.msg == 'isdbgrid') {
// Destroy the current connection
db.close();
// Create mongos connection instead
return createMongos(options, callback);
}
// Otherwise callback
callback(err, db);
});
}
function connectHandler(options, callback) {
return function (err, db) {
if(err) {
return process.nextTick(function() {
try {
callback(err, null);
} catch (err) {
if(db) db.close();
throw err
}
});
}
// No authentication just reconnect
if(!options.auth) {
return process.nextTick(function() {
try {
callback(err, db);
} catch (err) {
if(db) db.close();
throw err
}
})
}
// What db to authenticate against
var authentication_db = db;
if(options.authSource) {
authentication_db = db.db(options.authSource);
}
// Authenticate
authentication_db.authenticate(options.user, options.password, options, function(err, success){
if(success){
process.nextTick(function() {
try {
callback(null, db);
} catch (err) {
if(db) db.close();
throw err
}
});
} else {
if(db) db.close();
process.nextTick(function() {
try {
callback(err ? err : new Error('Could not authenticate user ' + options.auth[0]), null);
} catch (err) {
if(db) db.close();
throw err
}
});
}
});
}
}
/*
* Connect using MongoClient
*/
var connect = function(url, options, callback) {
options = options || {};
options = shallowClone(options);
// If callback is null throw an exception
if(callback == null) {
throw new Error("no callback function provided");
}
// Get a logger for MongoClient
var logger = Logger('MongoClient', options);
// Parse the string
var object = parse(url, options);
var _finalOptions = createUnifiedOptions({}, object);
_finalOptions = mergeOptions(_finalOptions, object, false);
_finalOptions = createUnifiedOptions(_finalOptions, options);
// Check if we have connection and socket timeout set
if(_finalOptions.socketTimeoutMS == null) _finalOptions.socketTimeoutMS = 30000;
if(_finalOptions.connectTimeoutMS == null) _finalOptions.connectTimeoutMS = 30000;
// Failure modes
if(object.servers.length == 0) {
throw new Error("connection string must contain at least one seed host");
}
function connectCallback(err, db) {
if(err && err.message == 'no mongos proxies found in seed list') {
if(logger.isWarn()) {
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'));
}
// Return a more specific error message for MongoClient.connect
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'));
}
// Return the error and db instance
callback(err, db);
}
// Do we have a replicaset then skip discovery and go straight to connectivity
if(_finalOptions.replicaSet || _finalOptions.rs_name) {
return createReplicaset(_finalOptions, connectHandler(_finalOptions, connectCallback));
} else if(object.servers.length > 1) {
return createMongos(_finalOptions, connectHandler(_finalOptions, connectCallback));
} else {
return createServer(_finalOptions, connectHandler(_finalOptions, connectCallback));
}
}
module.exports = MongoClient