mirror of
https://github.com/viq/NewsBlur.git
synced 2025-08-05 16:49:45 +00:00
327 lines
11 KiB
JavaScript
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
|