initial
This commit is contained in:
+262
@@ -0,0 +1,262 @@
|
||||
"use strict";
|
||||
|
||||
var f = require('util').format
|
||||
, require_optional = require('require_optional')
|
||||
, Query = require('../connection/commands').Query
|
||||
, MongoError = require('../error');
|
||||
|
||||
var AuthSession = function(db, username, password, options) {
|
||||
this.db = db;
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
AuthSession.prototype.equal = function(session) {
|
||||
return session.db == this.db
|
||||
&& session.username == this.username
|
||||
&& session.password == this.password;
|
||||
}
|
||||
|
||||
// Kerberos class
|
||||
var Kerberos = null;
|
||||
var MongoAuthProcess = null;
|
||||
|
||||
// Try to grab the Kerberos class
|
||||
try {
|
||||
Kerberos = require_optional('kerberos').Kerberos;
|
||||
// Authentication process for Mongo
|
||||
MongoAuthProcess = require_optional('kerberos').processes.MongoAuthProcess;
|
||||
} catch(err) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new GSSAPI authentication mechanism
|
||||
* @class
|
||||
* @return {GSSAPI} A cursor instance
|
||||
*/
|
||||
var GSSAPI = function(bson) {
|
||||
this.bson = bson;
|
||||
this.authStore = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate
|
||||
* @method
|
||||
* @param {{Server}|{ReplSet}|{Mongos}} server Topology the authentication method is being called on
|
||||
* @param {[]Connections} connections Connections to authenticate using this authenticator
|
||||
* @param {string} db Name of the database
|
||||
* @param {string} username Username
|
||||
* @param {string} password Password
|
||||
* @param {authResultCallback} callback The callback to return the result from the authentication
|
||||
* @return {object}
|
||||
*/
|
||||
GSSAPI.prototype.auth = function(server, connections, db, username, password, options, callback) {
|
||||
var self = this;
|
||||
// We don't have the Kerberos library
|
||||
if(Kerberos == null) return callback(new Error("Kerberos library is not installed"));
|
||||
var gssapiServiceName = options['gssapiServiceName'] || 'mongodb';
|
||||
// Total connections
|
||||
var count = connections.length;
|
||||
if(count == 0) return callback(null, null);
|
||||
|
||||
// Valid connections
|
||||
var numberOfValidConnections = 0;
|
||||
var errorObject = null;
|
||||
|
||||
// For each connection we need to authenticate
|
||||
while(connections.length > 0) {
|
||||
// Execute MongoCR
|
||||
var execute = function(connection) {
|
||||
// Start Auth process for a connection
|
||||
GSSAPIInitialize(self, db, username, password, db, gssapiServiceName, server, connection, options, function(err, r) {
|
||||
// Adjust count
|
||||
count = count - 1;
|
||||
|
||||
// If we have an error
|
||||
if(err) {
|
||||
errorObject = err;
|
||||
} else if(r.result['$err']) {
|
||||
errorObject = r.result;
|
||||
} else if(r.result['errmsg']) {
|
||||
errorObject = r.result;
|
||||
} else {
|
||||
numberOfValidConnections = numberOfValidConnections + 1;
|
||||
}
|
||||
|
||||
// We have authenticated all connections
|
||||
if(count == 0 && numberOfValidConnections > 0) {
|
||||
// Store the auth details
|
||||
addAuthSession(self.authStore, new AuthSession(db, username, password, options));
|
||||
// Return correct authentication
|
||||
callback(null, true);
|
||||
} else if(count == 0) {
|
||||
if(errorObject == null) errorObject = new MongoError(f("failed to authenticate using mongocr"));
|
||||
callback(errorObject, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var _execute = function(_connection) {
|
||||
process.nextTick(function() {
|
||||
execute(_connection);
|
||||
});
|
||||
}
|
||||
|
||||
_execute(connections.shift());
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Initialize step
|
||||
var GSSAPIInitialize = function(self, db, username, password, authdb, gssapiServiceName, server, connection, options, callback) {
|
||||
// Create authenticator
|
||||
var mongo_auth_process = new MongoAuthProcess(connection.host, connection.port, gssapiServiceName, options);
|
||||
|
||||
// Perform initialization
|
||||
mongo_auth_process.init(username, password, function(err) {
|
||||
if(err) return callback(err, false);
|
||||
|
||||
// Perform the first step
|
||||
mongo_auth_process.transition('', function(err, payload) {
|
||||
if(err) return callback(err, false);
|
||||
|
||||
// Call the next db step
|
||||
MongoDBGSSAPIFirstStep(self, mongo_auth_process, payload, db, username, password, authdb, server, connection, callback);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// Perform first step against mongodb
|
||||
var MongoDBGSSAPIFirstStep = function(self, mongo_auth_process, payload, db, username, password, authdb, server, connection, callback) {
|
||||
// Build the sasl start command
|
||||
var command = {
|
||||
saslStart: 1
|
||||
, mechanism: 'GSSAPI'
|
||||
, payload: payload
|
||||
, autoAuthorize: 1
|
||||
};
|
||||
|
||||
// Write the commmand on the connection
|
||||
server(connection, new Query(self.bson, "$external.$cmd", command, {
|
||||
numberToSkip: 0, numberToReturn: 1
|
||||
}), function(err, r) {
|
||||
if(err) return callback(err, false);
|
||||
var doc = r.result;
|
||||
// Execute mongodb transition
|
||||
mongo_auth_process.transition(r.result.payload, function(err, payload) {
|
||||
if(err) return callback(err, false);
|
||||
|
||||
// MongoDB API Second Step
|
||||
MongoDBGSSAPISecondStep(self, mongo_auth_process, payload, doc, db, username, password, authdb, server, connection, callback);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// Perform first step against mongodb
|
||||
var MongoDBGSSAPISecondStep = function(self, mongo_auth_process, payload, doc, db, username, password, authdb, server, connection, callback) {
|
||||
// Build Authentication command to send to MongoDB
|
||||
var command = {
|
||||
saslContinue: 1
|
||||
, conversationId: doc.conversationId
|
||||
, payload: payload
|
||||
};
|
||||
|
||||
// Execute the command
|
||||
// Write the commmand on the connection
|
||||
server(connection, new Query(self.bson, "$external.$cmd", command, {
|
||||
numberToSkip: 0, numberToReturn: 1
|
||||
}), function(err, r) {
|
||||
if(err) return callback(err, false);
|
||||
var doc = r.result;
|
||||
// Call next transition for kerberos
|
||||
mongo_auth_process.transition(doc.payload, function(err, payload) {
|
||||
if(err) return callback(err, false);
|
||||
|
||||
// Call the last and third step
|
||||
MongoDBGSSAPIThirdStep(self, mongo_auth_process, payload, doc, db, username, password, authdb, server, connection, callback);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var MongoDBGSSAPIThirdStep = function(self, mongo_auth_process, payload, doc, db, username, password, authdb, server, connection, callback) {
|
||||
// Build final command
|
||||
var command = {
|
||||
saslContinue: 1
|
||||
, conversationId: doc.conversationId
|
||||
, payload: payload
|
||||
};
|
||||
|
||||
// Execute the command
|
||||
server(connection, new Query(self.bson, "$external.$cmd", command, {
|
||||
numberToSkip: 0, numberToReturn: 1
|
||||
}), function(err, r) {
|
||||
if(err) return callback(err, false);
|
||||
mongo_auth_process.transition(null, function(err) {
|
||||
if(err) return callback(err, null);
|
||||
callback(null, r);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Add to store only if it does not exist
|
||||
var addAuthSession = function(authStore, session) {
|
||||
var found = false;
|
||||
|
||||
for(var i = 0; i < authStore.length; i++) {
|
||||
if(authStore[i].equal(session)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!found) authStore.push(session);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove authStore credentials
|
||||
* @method
|
||||
* @param {string} db Name of database we are removing authStore details about
|
||||
* @return {object}
|
||||
*/
|
||||
GSSAPI.prototype.logout = function(dbName) {
|
||||
this.authStore = this.authStore.filter(function(x) {
|
||||
return x.db != dbName;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Re authenticate pool
|
||||
* @method
|
||||
* @param {{Server}|{ReplSet}|{Mongos}} server Topology the authentication method is being called on
|
||||
* @param {[]Connections} connections Connections to authenticate using this authenticator
|
||||
* @param {authResultCallback} callback The callback to return the result from the authentication
|
||||
* @return {object}
|
||||
*/
|
||||
GSSAPI.prototype.reauthenticate = function(server, connections, callback) {
|
||||
var authStore = this.authStore.slice(0);
|
||||
var count = authStore.length;
|
||||
if(count == 0) return callback(null, null);
|
||||
// Iterate over all the auth details stored
|
||||
for(var i = 0; i < authStore.length; i++) {
|
||||
this.auth(server, connections, authStore[i].db, authStore[i].username, authStore[i].password, authStore[i].options, function(err) {
|
||||
count = count - 1;
|
||||
// Done re-authenticating
|
||||
if(count == 0) {
|
||||
callback(err, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a result from a authentication strategy
|
||||
*
|
||||
* @callback authResultCallback
|
||||
* @param {error} error An error object. Set to null if no error present
|
||||
* @param {boolean} result The result of the authentication process
|
||||
*/
|
||||
|
||||
module.exports = GSSAPI;
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
"use strict";
|
||||
|
||||
var f = require('util').format
|
||||
, crypto = require('crypto')
|
||||
, Query = require('../connection/commands').Query
|
||||
, MongoError = require('../error');
|
||||
|
||||
var AuthSession = function(db, username, password) {
|
||||
this.db = db;
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
AuthSession.prototype.equal = function(session) {
|
||||
return session.db == this.db
|
||||
&& session.username == this.username
|
||||
&& session.password == this.password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new MongoCR authentication mechanism
|
||||
* @class
|
||||
* @return {MongoCR} A cursor instance
|
||||
*/
|
||||
var MongoCR = function(bson) {
|
||||
this.bson = bson;
|
||||
this.authStore = [];
|
||||
}
|
||||
|
||||
// Add to store only if it does not exist
|
||||
var addAuthSession = function(authStore, session) {
|
||||
var found = false;
|
||||
|
||||
for(var i = 0; i < authStore.length; i++) {
|
||||
if(authStore[i].equal(session)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!found) authStore.push(session);
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate
|
||||
* @method
|
||||
* @param {{Server}|{ReplSet}|{Mongos}} server Topology the authentication method is being called on
|
||||
* @param {[]Connections} connections Connections to authenticate using this authenticator
|
||||
* @param {string} db Name of the database
|
||||
* @param {string} username Username
|
||||
* @param {string} password Password
|
||||
* @param {authResultCallback} callback The callback to return the result from the authentication
|
||||
* @return {object}
|
||||
*/
|
||||
MongoCR.prototype.auth = function(server, connections, db, username, password, callback) {
|
||||
var self = this;
|
||||
// Total connections
|
||||
var count = connections.length;
|
||||
if(count == 0) return callback(null, null);
|
||||
|
||||
// Valid connections
|
||||
var numberOfValidConnections = 0;
|
||||
var errorObject = null;
|
||||
|
||||
// For each connection we need to authenticate
|
||||
while(connections.length > 0) {
|
||||
// Execute MongoCR
|
||||
var executeMongoCR = function(connection) {
|
||||
// Write the commmand on the connection
|
||||
server(connection, new Query(self.bson, f("%s.$cmd", db), {
|
||||
getnonce:1
|
||||
}, {
|
||||
numberToSkip: 0, numberToReturn: 1
|
||||
}), function(err, r) {
|
||||
var nonce = null;
|
||||
var key = null;
|
||||
|
||||
// Adjust the number of connections left
|
||||
// Get nonce
|
||||
if(err == null) {
|
||||
nonce = r.result.nonce;
|
||||
// Use node md5 generator
|
||||
var md5 = crypto.createHash('md5');
|
||||
// Generate keys used for authentication
|
||||
md5.update(username + ":mongo:" + password, 'utf8');
|
||||
var hash_password = md5.digest('hex');
|
||||
// Final key
|
||||
md5 = crypto.createHash('md5');
|
||||
md5.update(nonce + username + hash_password, 'utf8');
|
||||
key = md5.digest('hex');
|
||||
}
|
||||
|
||||
// Execute command
|
||||
// Write the commmand on the connection
|
||||
server(connection, new Query(self.bson, f("%s.$cmd", db), {
|
||||
authenticate: 1, user: username, nonce: nonce, key:key
|
||||
}, {
|
||||
numberToSkip: 0, numberToReturn: 1
|
||||
}), function(err, r) {
|
||||
count = count - 1;
|
||||
|
||||
// If we have an error
|
||||
if(err) {
|
||||
errorObject = err;
|
||||
} else if(r.result['$err']) {
|
||||
errorObject = r.result;
|
||||
} else if(r.result['errmsg']) {
|
||||
errorObject = r.result;
|
||||
} else {
|
||||
numberOfValidConnections = numberOfValidConnections + 1;
|
||||
}
|
||||
|
||||
// We have authenticated all connections
|
||||
if(count == 0 && numberOfValidConnections > 0) {
|
||||
// Store the auth details
|
||||
addAuthSession(self.authStore, new AuthSession(db, username, password));
|
||||
// Return correct authentication
|
||||
callback(null, true);
|
||||
} else if(count == 0) {
|
||||
if(errorObject == null) errorObject = new MongoError(f("failed to authenticate using mongocr"));
|
||||
callback(errorObject, false);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var _execute = function(_connection) {
|
||||
process.nextTick(function() {
|
||||
executeMongoCR(_connection);
|
||||
});
|
||||
}
|
||||
|
||||
_execute(connections.shift());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove authStore credentials
|
||||
* @method
|
||||
* @param {string} db Name of database we are removing authStore details about
|
||||
* @return {object}
|
||||
*/
|
||||
MongoCR.prototype.logout = function(dbName) {
|
||||
this.authStore = this.authStore.filter(function(x) {
|
||||
return x.db != dbName;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Re authenticate pool
|
||||
* @method
|
||||
* @param {{Server}|{ReplSet}|{Mongos}} server Topology the authentication method is being called on
|
||||
* @param {[]Connections} connections Connections to authenticate using this authenticator
|
||||
* @param {authResultCallback} callback The callback to return the result from the authentication
|
||||
* @return {object}
|
||||
*/
|
||||
MongoCR.prototype.reauthenticate = function(server, connections, callback) {
|
||||
var authStore = this.authStore.slice(0);
|
||||
var count = authStore.length;
|
||||
if(count == 0) return callback(null, null);
|
||||
// Iterate over all the auth details stored
|
||||
for(var i = 0; i < authStore.length; i++) {
|
||||
this.auth(server, connections, authStore[i].db, authStore[i].username, authStore[i].password, function(err) {
|
||||
count = count - 1;
|
||||
// Done re-authenticating
|
||||
if(count == 0) {
|
||||
callback(err, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a result from a authentication strategy
|
||||
*
|
||||
* @callback authResultCallback
|
||||
* @param {error} error An error object. Set to null if no error present
|
||||
* @param {boolean} result The result of the authentication process
|
||||
*/
|
||||
|
||||
module.exports = MongoCR;
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
"use strict";
|
||||
|
||||
var f = require('util').format
|
||||
, retrieveBSON = require('../connection/utils').retrieveBSON
|
||||
, Query = require('../connection/commands').Query
|
||||
, MongoError = require('../error');
|
||||
|
||||
var BSON = retrieveBSON()
|
||||
, Binary = BSON.Binary;
|
||||
|
||||
var AuthSession = function(db, username, password) {
|
||||
this.db = db;
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
AuthSession.prototype.equal = function(session) {
|
||||
return session.db == this.db
|
||||
&& session.username == this.username
|
||||
&& session.password == this.password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Plain authentication mechanism
|
||||
* @class
|
||||
* @return {Plain} A cursor instance
|
||||
*/
|
||||
var Plain = function(bson) {
|
||||
this.bson = bson;
|
||||
this.authStore = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate
|
||||
* @method
|
||||
* @param {{Server}|{ReplSet}|{Mongos}} server Topology the authentication method is being called on
|
||||
* @param {[]Connections} connections Connections to authenticate using this authenticator
|
||||
* @param {string} db Name of the database
|
||||
* @param {string} username Username
|
||||
* @param {string} password Password
|
||||
* @param {authResultCallback} callback The callback to return the result from the authentication
|
||||
* @return {object}
|
||||
*/
|
||||
Plain.prototype.auth = function(server, connections, db, username, password, callback) {
|
||||
var self = this;
|
||||
// Total connections
|
||||
var count = connections.length;
|
||||
if(count == 0) return callback(null, null);
|
||||
|
||||
// Valid connections
|
||||
var numberOfValidConnections = 0;
|
||||
var errorObject = null;
|
||||
|
||||
// For each connection we need to authenticate
|
||||
while(connections.length > 0) {
|
||||
// Execute MongoCR
|
||||
var execute = function(connection) {
|
||||
// Create payload
|
||||
var payload = new Binary(f("\x00%s\x00%s", username, password));
|
||||
|
||||
// Let's start the sasl process
|
||||
var command = {
|
||||
saslStart: 1
|
||||
, mechanism: 'PLAIN'
|
||||
, payload: payload
|
||||
, autoAuthorize: 1
|
||||
};
|
||||
|
||||
// Let's start the process
|
||||
server(connection, new Query(self.bson, "$external.$cmd", command, {
|
||||
numberToSkip: 0, numberToReturn: 1
|
||||
}), function(err, r) {
|
||||
// Adjust count
|
||||
count = count - 1;
|
||||
|
||||
// If we have an error
|
||||
if(err) {
|
||||
errorObject = err;
|
||||
} else if(r.result['$err']) {
|
||||
errorObject = r.result;
|
||||
} else if(r.result['errmsg']) {
|
||||
errorObject = r.result;
|
||||
} else {
|
||||
numberOfValidConnections = numberOfValidConnections + 1;
|
||||
}
|
||||
|
||||
// We have authenticated all connections
|
||||
if(count == 0 && numberOfValidConnections > 0) {
|
||||
// Store the auth details
|
||||
addAuthSession(self.authStore, new AuthSession(db, username, password));
|
||||
// Return correct authentication
|
||||
callback(null, true);
|
||||
} else if(count == 0) {
|
||||
if(errorObject == null) errorObject = new MongoError(f("failed to authenticate using mongocr"));
|
||||
callback(errorObject, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var _execute = function(_connection) {
|
||||
process.nextTick(function() {
|
||||
execute(_connection);
|
||||
});
|
||||
}
|
||||
|
||||
_execute(connections.shift());
|
||||
}
|
||||
}
|
||||
|
||||
// Add to store only if it does not exist
|
||||
var addAuthSession = function(authStore, session) {
|
||||
var found = false;
|
||||
|
||||
for(var i = 0; i < authStore.length; i++) {
|
||||
if(authStore[i].equal(session)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!found) authStore.push(session);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove authStore credentials
|
||||
* @method
|
||||
* @param {string} db Name of database we are removing authStore details about
|
||||
* @return {object}
|
||||
*/
|
||||
Plain.prototype.logout = function(dbName) {
|
||||
this.authStore = this.authStore.filter(function(x) {
|
||||
return x.db != dbName;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Re authenticate pool
|
||||
* @method
|
||||
* @param {{Server}|{ReplSet}|{Mongos}} server Topology the authentication method is being called on
|
||||
* @param {[]Connections} connections Connections to authenticate using this authenticator
|
||||
* @param {authResultCallback} callback The callback to return the result from the authentication
|
||||
* @return {object}
|
||||
*/
|
||||
Plain.prototype.reauthenticate = function(server, connections, callback) {
|
||||
var authStore = this.authStore.slice(0);
|
||||
var count = authStore.length;
|
||||
if(count == 0) return callback(null, null);
|
||||
// Iterate over all the auth details stored
|
||||
for(var i = 0; i < authStore.length; i++) {
|
||||
this.auth(server, connections, authStore[i].db, authStore[i].username, authStore[i].password, function(err) {
|
||||
count = count - 1;
|
||||
// Done re-authenticating
|
||||
if(count == 0) {
|
||||
callback(err, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a result from a authentication strategy
|
||||
*
|
||||
* @callback authResultCallback
|
||||
* @param {error} error An error object. Set to null if no error present
|
||||
* @param {boolean} result The result of the authentication process
|
||||
*/
|
||||
|
||||
module.exports = Plain;
|
||||
+339
@@ -0,0 +1,339 @@
|
||||
"use strict";
|
||||
|
||||
var f = require('util').format
|
||||
, crypto = require('crypto')
|
||||
, retrieveBSON = require('../connection/utils').retrieveBSON
|
||||
, Query = require('../connection/commands').Query
|
||||
, MongoError = require('../error');
|
||||
|
||||
var BSON = retrieveBSON(),
|
||||
Binary = BSON.Binary;
|
||||
|
||||
var AuthSession = function(db, username, password) {
|
||||
this.db = db;
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
AuthSession.prototype.equal = function(session) {
|
||||
return session.db == this.db
|
||||
&& session.username == this.username
|
||||
&& session.password == this.password;
|
||||
}
|
||||
|
||||
var id = 0;
|
||||
|
||||
/**
|
||||
* Creates a new ScramSHA1 authentication mechanism
|
||||
* @class
|
||||
* @return {ScramSHA1} A cursor instance
|
||||
*/
|
||||
var ScramSHA1 = function(bson) {
|
||||
this.bson = bson;
|
||||
this.authStore = [];
|
||||
this.id = id++;
|
||||
}
|
||||
|
||||
var parsePayload = function(payload) {
|
||||
var dict = {};
|
||||
var parts = payload.split(',');
|
||||
|
||||
for(var i = 0; i < parts.length; i++) {
|
||||
var valueParts = parts[i].split('=');
|
||||
dict[valueParts[0]] = valueParts[1];
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
var passwordDigest = function(username, password) {
|
||||
if(typeof username != 'string') throw new MongoError("username must be a string");
|
||||
if(typeof password != 'string') throw new MongoError("password must be a string");
|
||||
if(password.length == 0) throw new MongoError("password cannot be empty");
|
||||
// Use node md5 generator
|
||||
var md5 = crypto.createHash('md5');
|
||||
// Generate keys used for authentication
|
||||
md5.update(username + ":mongo:" + password, 'utf8');
|
||||
return md5.digest('hex');
|
||||
}
|
||||
|
||||
// XOR two buffers
|
||||
var xor = function(a, b) {
|
||||
if (!Buffer.isBuffer(a)) a = new Buffer(a)
|
||||
if (!Buffer.isBuffer(b)) b = new Buffer(b)
|
||||
var res = []
|
||||
if (a.length > b.length) {
|
||||
for (var i = 0; i < b.length; i++) {
|
||||
res.push(a[i] ^ b[i])
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i < a.length; i++) {
|
||||
res.push(a[i] ^ b[i])
|
||||
}
|
||||
}
|
||||
return new Buffer(res);
|
||||
}
|
||||
|
||||
var _hiCache = {};
|
||||
var _hiCacheCount = 0;
|
||||
var _hiCachePurge = function() {
|
||||
_hiCache = {};
|
||||
_hiCacheCount = 0;
|
||||
};
|
||||
|
||||
var hi = function(data, salt, iterations) {
|
||||
// omit the work if already generated
|
||||
var key = [data, salt.toString('base64'), iterations].join('_');
|
||||
if (_hiCache[key] !== undefined) {
|
||||
return _hiCache[key];
|
||||
}
|
||||
|
||||
// generate the salt
|
||||
var saltedData = crypto.pbkdf2Sync(data, salt, iterations, 20, "sha1");
|
||||
|
||||
// cache a copy to speed up the next lookup, but prevent unbounded cache growth
|
||||
if (_hiCacheCount >= 200) {
|
||||
_hiCachePurge();
|
||||
}
|
||||
|
||||
_hiCache[key] = saltedData;
|
||||
_hiCacheCount += 1;
|
||||
return saltedData;
|
||||
};
|
||||
|
||||
/**
|
||||
* Authenticate
|
||||
* @method
|
||||
* @param {{Server}|{ReplSet}|{Mongos}} server Topology the authentication method is being called on
|
||||
* @param {[]Connections} connections Connections to authenticate using this authenticator
|
||||
* @param {string} db Name of the database
|
||||
* @param {string} username Username
|
||||
* @param {string} password Password
|
||||
* @param {authResultCallback} callback The callback to return the result from the authentication
|
||||
* @return {object}
|
||||
*/
|
||||
ScramSHA1.prototype.auth = function(server, connections, db, username, password, callback) {
|
||||
var self = this;
|
||||
// Total connections
|
||||
var count = connections.length;
|
||||
if(count == 0) return callback(null, null);
|
||||
|
||||
// Valid connections
|
||||
var numberOfValidConnections = 0;
|
||||
var errorObject = null;
|
||||
|
||||
// Execute MongoCR
|
||||
var executeScram = function(connection) {
|
||||
// Clean up the user
|
||||
username = username.replace('=', "=3D").replace(',', '=2C');
|
||||
|
||||
// Create a random nonce
|
||||
var nonce = crypto.randomBytes(24).toString('base64');
|
||||
// var nonce = 'MsQUY9iw0T9fx2MUEz6LZPwGuhVvWAhc'
|
||||
var firstBare = f("n=%s,r=%s", username, nonce);
|
||||
|
||||
// Build command structure
|
||||
var cmd = {
|
||||
saslStart: 1
|
||||
, mechanism: 'SCRAM-SHA-1'
|
||||
, payload: new Binary(f("n,,%s", firstBare))
|
||||
, autoAuthorize: 1
|
||||
}
|
||||
|
||||
// Handle the error
|
||||
var handleError = function(err, r) {
|
||||
if(err) {
|
||||
numberOfValidConnections = numberOfValidConnections - 1;
|
||||
errorObject = err; return false;
|
||||
} else if(r.result['$err']) {
|
||||
errorObject = r.result; return false;
|
||||
} else if(r.result['errmsg']) {
|
||||
errorObject = r.result; return false;
|
||||
} else {
|
||||
numberOfValidConnections = numberOfValidConnections + 1;
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Finish up
|
||||
var finish = function(_count, _numberOfValidConnections) {
|
||||
if(_count == 0 && _numberOfValidConnections > 0) {
|
||||
// Store the auth details
|
||||
addAuthSession(self.authStore, new AuthSession(db, username, password));
|
||||
// Return correct authentication
|
||||
return callback(null, true);
|
||||
} else if(_count == 0) {
|
||||
if(errorObject == null) errorObject = new MongoError(f("failed to authenticate using scram"));
|
||||
return callback(errorObject, false);
|
||||
}
|
||||
}
|
||||
|
||||
var handleEnd = function(_err, _r) {
|
||||
// Handle any error
|
||||
handleError(_err, _r)
|
||||
// Adjust the number of connections
|
||||
count = count - 1;
|
||||
// Execute the finish
|
||||
finish(count, numberOfValidConnections);
|
||||
}
|
||||
|
||||
// Write the commmand on the connection
|
||||
server(connection, new Query(self.bson, f("%s.$cmd", db), cmd, {
|
||||
numberToSkip: 0, numberToReturn: 1
|
||||
}), function(err, r) {
|
||||
// Do we have an error, handle it
|
||||
if(handleError(err, r) == false) {
|
||||
count = count - 1;
|
||||
|
||||
if(count == 0 && numberOfValidConnections > 0) {
|
||||
// Store the auth details
|
||||
addAuthSession(self.authStore, new AuthSession(db, username, password));
|
||||
// Return correct authentication
|
||||
return callback(null, true);
|
||||
} else if(count == 0) {
|
||||
if(errorObject == null) errorObject = new MongoError(f("failed to authenticate using scram"));
|
||||
return callback(errorObject, false);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the dictionary
|
||||
var dict = parsePayload(r.result.payload.value())
|
||||
|
||||
// Unpack dictionary
|
||||
var iterations = parseInt(dict.i, 10);
|
||||
var salt = dict.s;
|
||||
var rnonce = dict.r;
|
||||
|
||||
// Set up start of proof
|
||||
var withoutProof = f("c=biws,r=%s", rnonce);
|
||||
var passwordDig = passwordDigest(username, password);
|
||||
var saltedPassword = hi(passwordDig
|
||||
, new Buffer(salt, 'base64')
|
||||
, iterations);
|
||||
|
||||
// Create the client key
|
||||
var hmac = crypto.createHmac('sha1', saltedPassword);
|
||||
hmac.update(new Buffer("Client Key"));
|
||||
var clientKey = new Buffer(hmac.digest('base64'), 'base64');
|
||||
|
||||
// Create the stored key
|
||||
var hash = crypto.createHash('sha1');
|
||||
hash.update(clientKey);
|
||||
var storedKey = new Buffer(hash.digest('base64'), 'base64');
|
||||
|
||||
// Create the authentication message
|
||||
var authMsg = [firstBare, r.result.payload.value().toString('base64'), withoutProof].join(',');
|
||||
|
||||
// Create client signature
|
||||
hmac = crypto.createHmac('sha1', storedKey);
|
||||
hmac.update(new Buffer(authMsg));
|
||||
var clientSig = new Buffer(hmac.digest('base64'), 'base64');
|
||||
|
||||
// Create client proof
|
||||
var clientProof = f("p=%s", new Buffer(xor(clientKey, clientSig)).toString('base64'));
|
||||
|
||||
// Create client final
|
||||
var clientFinal = [withoutProof, clientProof].join(',');
|
||||
|
||||
//
|
||||
// Create continue message
|
||||
var cmd = {
|
||||
saslContinue: 1
|
||||
, conversationId: r.result.conversationId
|
||||
, payload: new Binary(new Buffer(clientFinal))
|
||||
}
|
||||
|
||||
//
|
||||
// Execute sasl continue
|
||||
// Write the commmand on the connection
|
||||
server(connection, new Query(self.bson, f("%s.$cmd", db), cmd, {
|
||||
numberToSkip: 0, numberToReturn: 1
|
||||
}), function(err, r) {
|
||||
if(r && r.result.done == false) {
|
||||
var cmd = {
|
||||
saslContinue: 1
|
||||
, conversationId: r.result.conversationId
|
||||
, payload: new Buffer(0)
|
||||
}
|
||||
|
||||
// Write the commmand on the connection
|
||||
server(connection, new Query(self.bson, f("%s.$cmd", db), cmd, {
|
||||
numberToSkip: 0, numberToReturn: 1
|
||||
}), function(err, r) {
|
||||
handleEnd(err, r);
|
||||
});
|
||||
} else {
|
||||
handleEnd(err, r);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var _execute = function(_connection) {
|
||||
process.nextTick(function() {
|
||||
executeScram(_connection);
|
||||
});
|
||||
}
|
||||
|
||||
// For each connection we need to authenticate
|
||||
while(connections.length > 0) {
|
||||
_execute(connections.shift());
|
||||
}
|
||||
}
|
||||
|
||||
// Add to store only if it does not exist
|
||||
var addAuthSession = function(authStore, session) {
|
||||
var found = false;
|
||||
|
||||
for(var i = 0; i < authStore.length; i++) {
|
||||
if(authStore[i].equal(session)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!found) authStore.push(session);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove authStore credentials
|
||||
* @method
|
||||
* @param {string} db Name of database we are removing authStore details about
|
||||
* @return {object}
|
||||
*/
|
||||
ScramSHA1.prototype.logout = function(dbName) {
|
||||
this.authStore = this.authStore.filter(function(x) {
|
||||
return x.db != dbName;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Re authenticate pool
|
||||
* @method
|
||||
* @param {{Server}|{ReplSet}|{Mongos}} server Topology the authentication method is being called on
|
||||
* @param {[]Connections} connections Connections to authenticate using this authenticator
|
||||
* @param {authResultCallback} callback The callback to return the result from the authentication
|
||||
* @return {object}
|
||||
*/
|
||||
ScramSHA1.prototype.reauthenticate = function(server, connections, callback) {
|
||||
var authStore = this.authStore.slice(0);
|
||||
var count = authStore.length;
|
||||
// No connections
|
||||
if(count == 0) return callback(null, null);
|
||||
// Iterate over all the auth details stored
|
||||
for(var i = 0; i < authStore.length; i++) {
|
||||
this.auth(server, connections, authStore[i].db, authStore[i].username, authStore[i].password, function(err) {
|
||||
count = count - 1;
|
||||
// Done re-authenticating
|
||||
if(count == 0) {
|
||||
callback(err, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = ScramSHA1;
|
||||
+250
@@ -0,0 +1,250 @@
|
||||
"use strict";
|
||||
|
||||
var f = require('util').format
|
||||
, require_optional = require('require_optional')
|
||||
, Query = require('../connection/commands').Query
|
||||
, MongoError = require('../error');
|
||||
|
||||
var AuthSession = function(db, username, password, options) {
|
||||
this.db = db;
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
AuthSession.prototype.equal = function(session) {
|
||||
return session.db == this.db
|
||||
&& session.username == this.username
|
||||
&& session.password == this.password;
|
||||
}
|
||||
|
||||
// Kerberos class
|
||||
var Kerberos = null;
|
||||
var MongoAuthProcess = null;
|
||||
|
||||
// Try to grab the Kerberos class
|
||||
try {
|
||||
Kerberos = require_optional('kerberos').Kerberos
|
||||
// Authentication process for Mongo
|
||||
MongoAuthProcess = require_optional('kerberos').processes.MongoAuthProcess
|
||||
} catch(err) {}
|
||||
|
||||
/**
|
||||
* Creates a new SSPI authentication mechanism
|
||||
* @class
|
||||
* @return {SSPI} A cursor instance
|
||||
*/
|
||||
var SSPI = function(bson) {
|
||||
this.bson = bson;
|
||||
this.authStore = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate
|
||||
* @method
|
||||
* @param {{Server}|{ReplSet}|{Mongos}} server Topology the authentication method is being called on
|
||||
* @param {[]Connections} connections Connections to authenticate using this authenticator
|
||||
* @param {string} db Name of the database
|
||||
* @param {string} username Username
|
||||
* @param {string} password Password
|
||||
* @param {authResultCallback} callback The callback to return the result from the authentication
|
||||
* @return {object}
|
||||
*/
|
||||
SSPI.prototype.auth = function(server, connections, db, username, password, options, callback) {
|
||||
var self = this;
|
||||
// We don't have the Kerberos library
|
||||
if(Kerberos == null) return callback(new Error("Kerberos library is not installed"));
|
||||
var gssapiServiceName = options['gssapiServiceName'] || 'mongodb';
|
||||
// Total connections
|
||||
var count = connections.length;
|
||||
if(count == 0) return callback(null, null);
|
||||
|
||||
// Valid connections
|
||||
var numberOfValidConnections = 0;
|
||||
var errorObject = null;
|
||||
|
||||
// For each connection we need to authenticate
|
||||
while(connections.length > 0) {
|
||||
// Execute MongoCR
|
||||
var execute = function(connection) {
|
||||
// Start Auth process for a connection
|
||||
SSIPAuthenticate(self, username, password, gssapiServiceName, server, connection, options, function(err, r) {
|
||||
// Adjust count
|
||||
count = count - 1;
|
||||
|
||||
// If we have an error
|
||||
if(err) {
|
||||
errorObject = err;
|
||||
} else if(r && typeof r == 'object' && r.result['$err']) {
|
||||
errorObject = r.result;
|
||||
} else if(r && typeof r == 'object' && r.result['errmsg']) {
|
||||
errorObject = r.result;
|
||||
} else {
|
||||
numberOfValidConnections = numberOfValidConnections + 1;
|
||||
}
|
||||
|
||||
// We have authenticated all connections
|
||||
if(count == 0 && numberOfValidConnections > 0) {
|
||||
// Store the auth details
|
||||
addAuthSession(self.authStore, new AuthSession(db, username, password, options));
|
||||
// Return correct authentication
|
||||
callback(null, true);
|
||||
} else if(count == 0) {
|
||||
if(errorObject == null) errorObject = new MongoError(f("failed to authenticate using mongocr"));
|
||||
callback(errorObject, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var _execute = function(_connection) {
|
||||
process.nextTick(function() {
|
||||
execute(_connection);
|
||||
});
|
||||
}
|
||||
|
||||
_execute(connections.shift());
|
||||
}
|
||||
}
|
||||
|
||||
var SSIPAuthenticate = function(self, username, password, gssapiServiceName, server, connection, options, callback) {
|
||||
// Build Authentication command to send to MongoDB
|
||||
var command = {
|
||||
saslStart: 1
|
||||
, mechanism: 'GSSAPI'
|
||||
, payload: ''
|
||||
, autoAuthorize: 1
|
||||
};
|
||||
|
||||
// Create authenticator
|
||||
var mongo_auth_process = new MongoAuthProcess(connection.host, connection.port, gssapiServiceName, options);
|
||||
|
||||
// Execute first sasl step
|
||||
server(connection, new Query(self.bson, "$external.$cmd", command, {
|
||||
numberToSkip: 0, numberToReturn: 1
|
||||
}), function(err, r) {
|
||||
if(err) return callback(err, false);
|
||||
var doc = r.result;
|
||||
|
||||
mongo_auth_process.init(username, password, function(err) {
|
||||
if(err) return callback(err);
|
||||
|
||||
mongo_auth_process.transition(doc.payload, function(err, payload) {
|
||||
if(err) return callback(err);
|
||||
|
||||
// Perform the next step against mongod
|
||||
var command = {
|
||||
saslContinue: 1
|
||||
, conversationId: doc.conversationId
|
||||
, payload: payload
|
||||
};
|
||||
|
||||
// Execute the command
|
||||
server(connection, new Query(self.bson, "$external.$cmd", command, {
|
||||
numberToSkip: 0, numberToReturn: 1
|
||||
}), function(err, r) {
|
||||
if(err) return callback(err, false);
|
||||
var doc = r.result;
|
||||
|
||||
mongo_auth_process.transition(doc.payload, function(err, payload) {
|
||||
if(err) return callback(err);
|
||||
|
||||
// Perform the next step against mongod
|
||||
var command = {
|
||||
saslContinue: 1
|
||||
, conversationId: doc.conversationId
|
||||
, payload: payload
|
||||
};
|
||||
|
||||
// Execute the command
|
||||
server(connection, new Query(self.bson, "$external.$cmd", command, {
|
||||
numberToSkip: 0, numberToReturn: 1
|
||||
}), function(err, r) {
|
||||
if(err) return callback(err, false);
|
||||
var doc = r.result;
|
||||
|
||||
mongo_auth_process.transition(doc.payload, function(err, payload) {
|
||||
// Perform the next step against mongod
|
||||
var command = {
|
||||
saslContinue: 1
|
||||
, conversationId: doc.conversationId
|
||||
, payload: payload
|
||||
};
|
||||
|
||||
// Execute the command
|
||||
server(connection, new Query(self.bson, "$external.$cmd", command, {
|
||||
numberToSkip: 0, numberToReturn: 1
|
||||
}), function(err, r) {
|
||||
if(err) return callback(err, false);
|
||||
var doc = r.result;
|
||||
|
||||
if(doc.done) return callback(null, true);
|
||||
callback(new Error("Authentication failed"), false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Add to store only if it does not exist
|
||||
var addAuthSession = function(authStore, session) {
|
||||
var found = false;
|
||||
|
||||
for(var i = 0; i < authStore.length; i++) {
|
||||
if(authStore[i].equal(session)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!found) authStore.push(session);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove authStore credentials
|
||||
* @method
|
||||
* @param {string} db Name of database we are removing authStore details about
|
||||
* @return {object}
|
||||
*/
|
||||
SSPI.prototype.logout = function(dbName) {
|
||||
this.authStore = this.authStore.filter(function(x) {
|
||||
return x.db != dbName;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Re authenticate pool
|
||||
* @method
|
||||
* @param {{Server}|{ReplSet}|{Mongos}} server Topology the authentication method is being called on
|
||||
* @param {[]Connections} connections Connections to authenticate using this authenticator
|
||||
* @param {authResultCallback} callback The callback to return the result from the authentication
|
||||
* @return {object}
|
||||
*/
|
||||
SSPI.prototype.reauthenticate = function(server, connections, callback) {
|
||||
var authStore = this.authStore.slice(0);
|
||||
var count = authStore.length;
|
||||
if(count == 0) return callback(null, null);
|
||||
// Iterate over all the auth details stored
|
||||
for(var i = 0; i < authStore.length; i++) {
|
||||
this.auth(server, connections, authStore[i].db, authStore[i].username, authStore[i].password, authStore[i].options, function(err) {
|
||||
count = count - 1;
|
||||
// Done re-authenticating
|
||||
if(count == 0) {
|
||||
callback(err, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a result from a authentication strategy
|
||||
*
|
||||
* @callback authResultCallback
|
||||
* @param {error} error An error object. Set to null if no error present
|
||||
* @param {boolean} result The result of the authentication process
|
||||
*/
|
||||
|
||||
module.exports = SSPI;
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
"use strict";
|
||||
|
||||
var f = require('util').format
|
||||
, Query = require('../connection/commands').Query
|
||||
, MongoError = require('../error');
|
||||
|
||||
var AuthSession = function(db, username, password) {
|
||||
this.db = db;
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
AuthSession.prototype.equal = function(session) {
|
||||
return session.db == this.db
|
||||
&& session.username == this.username
|
||||
&& session.password == this.password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new X509 authentication mechanism
|
||||
* @class
|
||||
* @return {X509} A cursor instance
|
||||
*/
|
||||
var X509 = function(bson) {
|
||||
this.bson = bson;
|
||||
this.authStore = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate
|
||||
* @method
|
||||
* @param {{Server}|{ReplSet}|{Mongos}} server Topology the authentication method is being called on
|
||||
* @param {[]Connections} connections Connections to authenticate using this authenticator
|
||||
* @param {string} db Name of the database
|
||||
* @param {string} username Username
|
||||
* @param {string} password Password
|
||||
* @param {authResultCallback} callback The callback to return the result from the authentication
|
||||
* @return {object}
|
||||
*/
|
||||
X509.prototype.auth = function(server, connections, db, username, password, callback) {
|
||||
var self = this;
|
||||
// Total connections
|
||||
var count = connections.length;
|
||||
if(count == 0) return callback(null, null);
|
||||
|
||||
// Valid connections
|
||||
var numberOfValidConnections = 0;
|
||||
var errorObject = null;
|
||||
|
||||
// For each connection we need to authenticate
|
||||
while(connections.length > 0) {
|
||||
// Execute MongoCR
|
||||
var execute = function(connection) {
|
||||
// Let's start the sasl process
|
||||
var command = {
|
||||
authenticate: 1
|
||||
, mechanism: 'MONGODB-X509'
|
||||
};
|
||||
|
||||
// Add username if specified
|
||||
if(username) {
|
||||
command.user = username;
|
||||
}
|
||||
|
||||
// Let's start the process
|
||||
server(connection, new Query(self.bson, "$external.$cmd", command, {
|
||||
numberToSkip: 0, numberToReturn: 1
|
||||
}), function(err, r) {
|
||||
// Adjust count
|
||||
count = count - 1;
|
||||
|
||||
// If we have an error
|
||||
if(err) {
|
||||
errorObject = err;
|
||||
} else if(r.result['$err']) {
|
||||
errorObject = r.result;
|
||||
} else if(r.result['errmsg']) {
|
||||
errorObject = r.result;
|
||||
} else {
|
||||
numberOfValidConnections = numberOfValidConnections + 1;
|
||||
}
|
||||
|
||||
// We have authenticated all connections
|
||||
if(count == 0 && numberOfValidConnections > 0) {
|
||||
// Store the auth details
|
||||
addAuthSession(self.authStore, new AuthSession(db, username, password));
|
||||
// Return correct authentication
|
||||
callback(null, true);
|
||||
} else if(count == 0) {
|
||||
if(errorObject == null) errorObject = new MongoError(f("failed to authenticate using mongocr"));
|
||||
callback(errorObject, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var _execute = function(_connection) {
|
||||
process.nextTick(function() {
|
||||
execute(_connection);
|
||||
});
|
||||
}
|
||||
|
||||
_execute(connections.shift());
|
||||
}
|
||||
}
|
||||
|
||||
// Add to store only if it does not exist
|
||||
var addAuthSession = function(authStore, session) {
|
||||
var found = false;
|
||||
|
||||
for(var i = 0; i < authStore.length; i++) {
|
||||
if(authStore[i].equal(session)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!found) authStore.push(session);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove authStore credentials
|
||||
* @method
|
||||
* @param {string} db Name of database we are removing authStore details about
|
||||
* @return {object}
|
||||
*/
|
||||
X509.prototype.logout = function(dbName) {
|
||||
this.authStore = this.authStore.filter(function(x) {
|
||||
return x.db != dbName;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Re authenticate pool
|
||||
* @method
|
||||
* @param {{Server}|{ReplSet}|{Mongos}} server Topology the authentication method is being called on
|
||||
* @param {[]Connections} connections Connections to authenticate using this authenticator
|
||||
* @param {authResultCallback} callback The callback to return the result from the authentication
|
||||
* @return {object}
|
||||
*/
|
||||
X509.prototype.reauthenticate = function(server, connections, callback) {
|
||||
var authStore = this.authStore.slice(0);
|
||||
var count = authStore.length;
|
||||
if(count == 0) return callback(null, null);
|
||||
// Iterate over all the auth details stored
|
||||
for(var i = 0; i < authStore.length; i++) {
|
||||
this.auth(server, connections, authStore[i].db, authStore[i].username, authStore[i].password, function(err) {
|
||||
count = count - 1;
|
||||
// Done re-authenticating
|
||||
if(count == 0) {
|
||||
callback(err, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a result from a authentication strategy
|
||||
*
|
||||
* @callback authResultCallback
|
||||
* @param {error} error An error object. Set to null if no error present
|
||||
* @param {boolean} result The result of the authentication process
|
||||
*/
|
||||
|
||||
module.exports = X509;
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Creates a new CommandResult instance
|
||||
* @class
|
||||
* @param {object} result CommandResult object
|
||||
* @param {Connection} connection A connection instance associated with this result
|
||||
* @return {CommandResult} A cursor instance
|
||||
*/
|
||||
var CommandResult = function(result, connection, message) {
|
||||
this.result = result;
|
||||
this.connection = connection;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert CommandResult to JSON
|
||||
* @method
|
||||
* @return {object}
|
||||
*/
|
||||
CommandResult.prototype.toJSON = function() {
|
||||
return this.result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert CommandResult to String representation
|
||||
* @method
|
||||
* @return {string}
|
||||
*/
|
||||
CommandResult.prototype.toString = function() {
|
||||
return JSON.stringify(this.toJSON());
|
||||
}
|
||||
|
||||
module.exports = CommandResult;
|
||||
+546
@@ -0,0 +1,546 @@
|
||||
"use strict";
|
||||
|
||||
var retrieveBSON = require('../connection/utils').retrieveBSON;
|
||||
var BSON = retrieveBSON();
|
||||
var Long = BSON.Long;
|
||||
|
||||
// Incrementing request id
|
||||
var _requestId = 0;
|
||||
|
||||
// Wire command operation ids
|
||||
var OP_QUERY = 2004;
|
||||
var OP_GETMORE = 2005;
|
||||
var OP_KILL_CURSORS = 2007;
|
||||
|
||||
// Query flags
|
||||
var OPTS_TAILABLE_CURSOR = 2;
|
||||
var OPTS_SLAVE = 4;
|
||||
var OPTS_OPLOG_REPLAY = 8;
|
||||
var OPTS_NO_CURSOR_TIMEOUT = 16;
|
||||
var OPTS_AWAIT_DATA = 32;
|
||||
var OPTS_EXHAUST = 64;
|
||||
var OPTS_PARTIAL = 128;
|
||||
|
||||
// Response flags
|
||||
var CURSOR_NOT_FOUND = 0;
|
||||
var QUERY_FAILURE = 2;
|
||||
var SHARD_CONFIG_STALE = 4;
|
||||
var AWAIT_CAPABLE = 8;
|
||||
|
||||
/**************************************************************
|
||||
* QUERY
|
||||
**************************************************************/
|
||||
var Query = function(bson, ns, query, options) {
|
||||
var self = this;
|
||||
// Basic options needed to be passed in
|
||||
if(ns == null) throw new Error("ns must be specified for query");
|
||||
if(query == null) throw new Error("query must be specified for query");
|
||||
|
||||
// Validate that we are not passing 0x00 in the collection name
|
||||
if(!!~ns.indexOf("\x00")) {
|
||||
throw new Error("namespace cannot contain a null character");
|
||||
}
|
||||
|
||||
// Basic options
|
||||
this.bson = bson;
|
||||
this.ns = ns;
|
||||
this.query = query;
|
||||
|
||||
// Ensure empty options
|
||||
this.options = options || {};
|
||||
|
||||
// Additional options
|
||||
this.numberToSkip = options.numberToSkip || 0;
|
||||
this.numberToReturn = options.numberToReturn || 0;
|
||||
this.returnFieldSelector = options.returnFieldSelector || null;
|
||||
this.requestId = Query.getRequestId();
|
||||
|
||||
// Serialization option
|
||||
this.serializeFunctions = typeof options.serializeFunctions == 'boolean' ? options.serializeFunctions : false;
|
||||
this.ignoreUndefined = typeof options.ignoreUndefined == 'boolean' ? options.ignoreUndefined : false;
|
||||
this.maxBsonSize = options.maxBsonSize || 1024 * 1024 * 16;
|
||||
this.checkKeys = typeof options.checkKeys == 'boolean' ? options.checkKeys : true;
|
||||
this.batchSize = self.numberToReturn;
|
||||
|
||||
// Flags
|
||||
this.tailable = false;
|
||||
this.slaveOk = typeof options.slaveOk == 'boolean'? options.slaveOk : false;
|
||||
this.oplogReplay = false;
|
||||
this.noCursorTimeout = false;
|
||||
this.awaitData = false;
|
||||
this.exhaust = false;
|
||||
this.partial = false;
|
||||
}
|
||||
|
||||
//
|
||||
// Assign a new request Id
|
||||
Query.prototype.incRequestId = function() {
|
||||
this.requestId = _requestId++;
|
||||
}
|
||||
|
||||
//
|
||||
// Assign a new request Id
|
||||
Query.nextRequestId = function() {
|
||||
return _requestId + 1;
|
||||
}
|
||||
|
||||
//
|
||||
// Uses a single allocated buffer for the process, avoiding multiple memory allocations
|
||||
Query.prototype.toBin = function() {
|
||||
var self = this;
|
||||
var buffers = [];
|
||||
var projection = null;
|
||||
|
||||
// Set up the flags
|
||||
var flags = 0;
|
||||
if(this.tailable) {
|
||||
flags |= OPTS_TAILABLE_CURSOR;
|
||||
}
|
||||
|
||||
if(this.slaveOk) {
|
||||
flags |= OPTS_SLAVE;
|
||||
}
|
||||
|
||||
if(this.oplogReplay) {
|
||||
flags |= OPTS_OPLOG_REPLAY;
|
||||
}
|
||||
|
||||
if(this.noCursorTimeout) {
|
||||
flags |= OPTS_NO_CURSOR_TIMEOUT;
|
||||
}
|
||||
|
||||
if(this.awaitData) {
|
||||
flags |= OPTS_AWAIT_DATA;
|
||||
}
|
||||
|
||||
if(this.exhaust) {
|
||||
flags |= OPTS_EXHAUST;
|
||||
}
|
||||
|
||||
if(this.partial) {
|
||||
flags |= OPTS_PARTIAL;
|
||||
}
|
||||
|
||||
// If batchSize is different to self.numberToReturn
|
||||
if(self.batchSize != self.numberToReturn) self.numberToReturn = self.batchSize;
|
||||
|
||||
// Allocate write protocol header buffer
|
||||
var header = new Buffer(
|
||||
4 * 4 // Header
|
||||
+ 4 // Flags
|
||||
+ Buffer.byteLength(self.ns) + 1 // namespace
|
||||
+ 4 // numberToSkip
|
||||
+ 4 // numberToReturn
|
||||
);
|
||||
|
||||
// Add header to buffers
|
||||
buffers.push(header);
|
||||
|
||||
// Serialize the query
|
||||
var query = self.bson.serialize(this.query, {
|
||||
checkKeys: this.checkKeys,
|
||||
serializeFunctions: this.serializeFunctions,
|
||||
ignoreUndefined: this.ignoreUndefined,
|
||||
});
|
||||
|
||||
// Add query document
|
||||
buffers.push(query);
|
||||
|
||||
if(self.returnFieldSelector && Object.keys(self.returnFieldSelector).length > 0) {
|
||||
// Serialize the projection document
|
||||
projection = self.bson.serialize(this.returnFieldSelector, {
|
||||
checkKeys: this.checkKeys,
|
||||
serializeFunctions: this.serializeFunctions,
|
||||
ignoreUndefined: this.ignoreUndefined,
|
||||
});
|
||||
// Add projection document
|
||||
buffers.push(projection);
|
||||
}
|
||||
|
||||
// Total message size
|
||||
var totalLength = header.length + query.length + (projection ? projection.length : 0);
|
||||
|
||||
// Set up the index
|
||||
var index = 4;
|
||||
|
||||
// Write total document length
|
||||
header[3] = (totalLength >> 24) & 0xff;
|
||||
header[2] = (totalLength >> 16) & 0xff;
|
||||
header[1] = (totalLength >> 8) & 0xff;
|
||||
header[0] = (totalLength) & 0xff;
|
||||
|
||||
// Write header information requestId
|
||||
header[index + 3] = (this.requestId >> 24) & 0xff;
|
||||
header[index + 2] = (this.requestId >> 16) & 0xff;
|
||||
header[index + 1] = (this.requestId >> 8) & 0xff;
|
||||
header[index] = (this.requestId) & 0xff;
|
||||
index = index + 4;
|
||||
|
||||
// Write header information responseTo
|
||||
header[index + 3] = (0 >> 24) & 0xff;
|
||||
header[index + 2] = (0 >> 16) & 0xff;
|
||||
header[index + 1] = (0 >> 8) & 0xff;
|
||||
header[index] = (0) & 0xff;
|
||||
index = index + 4;
|
||||
|
||||
// Write header information OP_QUERY
|
||||
header[index + 3] = (OP_QUERY >> 24) & 0xff;
|
||||
header[index + 2] = (OP_QUERY >> 16) & 0xff;
|
||||
header[index + 1] = (OP_QUERY >> 8) & 0xff;
|
||||
header[index] = (OP_QUERY) & 0xff;
|
||||
index = index + 4;
|
||||
|
||||
// Write header information flags
|
||||
header[index + 3] = (flags >> 24) & 0xff;
|
||||
header[index + 2] = (flags >> 16) & 0xff;
|
||||
header[index + 1] = (flags >> 8) & 0xff;
|
||||
header[index] = (flags) & 0xff;
|
||||
index = index + 4;
|
||||
|
||||
// Write collection name
|
||||
index = index + header.write(this.ns, index, 'utf8') + 1;
|
||||
header[index - 1] = 0;
|
||||
|
||||
// Write header information flags numberToSkip
|
||||
header[index + 3] = (this.numberToSkip >> 24) & 0xff;
|
||||
header[index + 2] = (this.numberToSkip >> 16) & 0xff;
|
||||
header[index + 1] = (this.numberToSkip >> 8) & 0xff;
|
||||
header[index] = (this.numberToSkip) & 0xff;
|
||||
index = index + 4;
|
||||
|
||||
// Write header information flags numberToReturn
|
||||
header[index + 3] = (this.numberToReturn >> 24) & 0xff;
|
||||
header[index + 2] = (this.numberToReturn >> 16) & 0xff;
|
||||
header[index + 1] = (this.numberToReturn >> 8) & 0xff;
|
||||
header[index] = (this.numberToReturn) & 0xff;
|
||||
index = index + 4;
|
||||
|
||||
// Return the buffers
|
||||
return buffers;
|
||||
}
|
||||
|
||||
Query.getRequestId = function() {
|
||||
return ++_requestId;
|
||||
}
|
||||
|
||||
/**************************************************************
|
||||
* GETMORE
|
||||
**************************************************************/
|
||||
var GetMore = function(bson, ns, cursorId, opts) {
|
||||
opts = opts || {};
|
||||
this.numberToReturn = opts.numberToReturn || 0;
|
||||
this.requestId = _requestId++;
|
||||
this.bson = bson;
|
||||
this.ns = ns;
|
||||
this.cursorId = cursorId;
|
||||
}
|
||||
|
||||
//
|
||||
// Uses a single allocated buffer for the process, avoiding multiple memory allocations
|
||||
GetMore.prototype.toBin = function() {
|
||||
var length = 4 + Buffer.byteLength(this.ns) + 1 + 4 + 8 + (4 * 4);
|
||||
// Create command buffer
|
||||
var index = 0;
|
||||
// Allocate buffer
|
||||
var _buffer = new Buffer(length);
|
||||
|
||||
// Write header information
|
||||
// index = write32bit(index, _buffer, length);
|
||||
_buffer[index + 3] = (length >> 24) & 0xff;
|
||||
_buffer[index + 2] = (length >> 16) & 0xff;
|
||||
_buffer[index + 1] = (length >> 8) & 0xff;
|
||||
_buffer[index] = (length) & 0xff;
|
||||
index = index + 4;
|
||||
|
||||
// index = write32bit(index, _buffer, requestId);
|
||||
_buffer[index + 3] = (this.requestId >> 24) & 0xff;
|
||||
_buffer[index + 2] = (this.requestId >> 16) & 0xff;
|
||||
_buffer[index + 1] = (this.requestId >> 8) & 0xff;
|
||||
_buffer[index] = (this.requestId) & 0xff;
|
||||
index = index + 4;
|
||||
|
||||
// index = write32bit(index, _buffer, 0);
|
||||
_buffer[index + 3] = (0 >> 24) & 0xff;
|
||||
_buffer[index + 2] = (0 >> 16) & 0xff;
|
||||
_buffer[index + 1] = (0 >> 8) & 0xff;
|
||||
_buffer[index] = (0) & 0xff;
|
||||
index = index + 4;
|
||||
|
||||
// index = write32bit(index, _buffer, OP_GETMORE);
|
||||
_buffer[index + 3] = (OP_GETMORE >> 24) & 0xff;
|
||||
_buffer[index + 2] = (OP_GETMORE >> 16) & 0xff;
|
||||
_buffer[index + 1] = (OP_GETMORE >> 8) & 0xff;
|
||||
_buffer[index] = (OP_GETMORE) & 0xff;
|
||||
index = index + 4;
|
||||
|
||||
// index = write32bit(index, _buffer, 0);
|
||||
_buffer[index + 3] = (0 >> 24) & 0xff;
|
||||
_buffer[index + 2] = (0 >> 16) & 0xff;
|
||||
_buffer[index + 1] = (0 >> 8) & 0xff;
|
||||
_buffer[index] = (0) & 0xff;
|
||||
index = index + 4;
|
||||
|
||||
// Write collection name
|
||||
index = index + _buffer.write(this.ns, index, 'utf8') + 1;
|
||||
_buffer[index - 1] = 0;
|
||||
|
||||
// Write batch size
|
||||
// index = write32bit(index, _buffer, numberToReturn);
|
||||
_buffer[index + 3] = (this.numberToReturn >> 24) & 0xff;
|
||||
_buffer[index + 2] = (this.numberToReturn >> 16) & 0xff;
|
||||
_buffer[index + 1] = (this.numberToReturn >> 8) & 0xff;
|
||||
_buffer[index] = (this.numberToReturn) & 0xff;
|
||||
index = index + 4;
|
||||
|
||||
// Write cursor id
|
||||
// index = write32bit(index, _buffer, cursorId.getLowBits());
|
||||
_buffer[index + 3] = (this.cursorId.getLowBits() >> 24) & 0xff;
|
||||
_buffer[index + 2] = (this.cursorId.getLowBits() >> 16) & 0xff;
|
||||
_buffer[index + 1] = (this.cursorId.getLowBits() >> 8) & 0xff;
|
||||
_buffer[index] = (this.cursorId.getLowBits()) & 0xff;
|
||||
index = index + 4;
|
||||
|
||||
// index = write32bit(index, _buffer, cursorId.getHighBits());
|
||||
_buffer[index + 3] = (this.cursorId.getHighBits() >> 24) & 0xff;
|
||||
_buffer[index + 2] = (this.cursorId.getHighBits() >> 16) & 0xff;
|
||||
_buffer[index + 1] = (this.cursorId.getHighBits() >> 8) & 0xff;
|
||||
_buffer[index] = (this.cursorId.getHighBits()) & 0xff;
|
||||
index = index + 4;
|
||||
|
||||
// Return buffer
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
/**************************************************************
|
||||
* KILLCURSOR
|
||||
**************************************************************/
|
||||
var KillCursor = function(bson, cursorIds) {
|
||||
this.requestId = _requestId++;
|
||||
this.cursorIds = cursorIds;
|
||||
}
|
||||
|
||||
//
|
||||
// Uses a single allocated buffer for the process, avoiding multiple memory allocations
|
||||
KillCursor.prototype.toBin = function() {
|
||||
var length = 4 + 4 + (4 * 4) + (this.cursorIds.length * 8);
|
||||
|
||||
// Create command buffer
|
||||
var index = 0;
|
||||
var _buffer = new Buffer(length);
|
||||
|
||||
// Write header information
|
||||
// index = write32bit(index, _buffer, length);
|
||||
_buffer[index + 3] = (length >> 24) & 0xff;
|
||||
_buffer[index + 2] = (length >> 16) & 0xff;
|
||||
_buffer[index + 1] = (length >> 8) & 0xff;
|
||||
_buffer[index] = (length) & 0xff;
|
||||
index = index + 4;
|
||||
|
||||
// index = write32bit(index, _buffer, requestId);
|
||||
_buffer[index + 3] = (this.requestId >> 24) & 0xff;
|
||||
_buffer[index + 2] = (this.requestId >> 16) & 0xff;
|
||||
_buffer[index + 1] = (this.requestId >> 8) & 0xff;
|
||||
_buffer[index] = (this.requestId) & 0xff;
|
||||
index = index + 4;
|
||||
|
||||
// index = write32bit(index, _buffer, 0);
|
||||
_buffer[index + 3] = (0 >> 24) & 0xff;
|
||||
_buffer[index + 2] = (0 >> 16) & 0xff;
|
||||
_buffer[index + 1] = (0 >> 8) & 0xff;
|
||||
_buffer[index] = (0) & 0xff;
|
||||
index = index + 4;
|
||||
|
||||
// index = write32bit(index, _buffer, OP_KILL_CURSORS);
|
||||
_buffer[index + 3] = (OP_KILL_CURSORS >> 24) & 0xff;
|
||||
_buffer[index + 2] = (OP_KILL_CURSORS >> 16) & 0xff;
|
||||
_buffer[index + 1] = (OP_KILL_CURSORS >> 8) & 0xff;
|
||||
_buffer[index] = (OP_KILL_CURSORS) & 0xff;
|
||||
index = index + 4;
|
||||
|
||||
// index = write32bit(index, _buffer, 0);
|
||||
_buffer[index + 3] = (0 >> 24) & 0xff;
|
||||
_buffer[index + 2] = (0 >> 16) & 0xff;
|
||||
_buffer[index + 1] = (0 >> 8) & 0xff;
|
||||
_buffer[index] = (0) & 0xff;
|
||||
index = index + 4;
|
||||
|
||||
// Write batch size
|
||||
// index = write32bit(index, _buffer, this.cursorIds.length);
|
||||
_buffer[index + 3] = (this.cursorIds.length >> 24) & 0xff;
|
||||
_buffer[index + 2] = (this.cursorIds.length >> 16) & 0xff;
|
||||
_buffer[index + 1] = (this.cursorIds.length >> 8) & 0xff;
|
||||
_buffer[index] = (this.cursorIds.length) & 0xff;
|
||||
index = index + 4;
|
||||
|
||||
// Write all the cursor ids into the array
|
||||
for(var i = 0; i < this.cursorIds.length; i++) {
|
||||
// Write cursor id
|
||||
// index = write32bit(index, _buffer, cursorIds[i].getLowBits());
|
||||
_buffer[index + 3] = (this.cursorIds[i].getLowBits() >> 24) & 0xff;
|
||||
_buffer[index + 2] = (this.cursorIds[i].getLowBits() >> 16) & 0xff;
|
||||
_buffer[index + 1] = (this.cursorIds[i].getLowBits() >> 8) & 0xff;
|
||||
_buffer[index] = (this.cursorIds[i].getLowBits()) & 0xff;
|
||||
index = index + 4;
|
||||
|
||||
// index = write32bit(index, _buffer, cursorIds[i].getHighBits());
|
||||
_buffer[index + 3] = (this.cursorIds[i].getHighBits() >> 24) & 0xff;
|
||||
_buffer[index + 2] = (this.cursorIds[i].getHighBits() >> 16) & 0xff;
|
||||
_buffer[index + 1] = (this.cursorIds[i].getHighBits() >> 8) & 0xff;
|
||||
_buffer[index] = (this.cursorIds[i].getHighBits()) & 0xff;
|
||||
index = index + 4;
|
||||
}
|
||||
|
||||
// Return buffer
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
var Response = function(bson, data, opts) {
|
||||
opts = opts || {promoteLongs: true, promoteValues: true, promoteBuffers: false};
|
||||
this.parsed = false;
|
||||
|
||||
//
|
||||
// Parse Header
|
||||
//
|
||||
this.index = 0;
|
||||
this.raw = data;
|
||||
this.data = data;
|
||||
this.bson = bson;
|
||||
this.opts = opts;
|
||||
|
||||
// Read the message length
|
||||
this.length = data[this.index] | data[this.index + 1] << 8 | data[this.index + 2] << 16 | data[this.index + 3] << 24;
|
||||
this.index = this.index + 4;
|
||||
|
||||
// Fetch the request id for this reply
|
||||
this.requestId = data[this.index] | data[this.index + 1] << 8 | data[this.index + 2] << 16 | data[this.index + 3] << 24;
|
||||
this.index = this.index + 4;
|
||||
|
||||
// Fetch the id of the request that triggered the response
|
||||
this.responseTo = data[this.index] | data[this.index + 1] << 8 | data[this.index + 2] << 16 | data[this.index + 3] << 24;
|
||||
this.index = this.index + 4;
|
||||
|
||||
// Skip op-code field
|
||||
this.index = this.index + 4;
|
||||
|
||||
// Unpack flags
|
||||
this.responseFlags = data[this.index] | data[this.index + 1] << 8 | data[this.index + 2] << 16 | data[this.index + 3] << 24;
|
||||
this.index = this.index + 4;
|
||||
|
||||
// Unpack the cursor
|
||||
var lowBits = data[this.index] | data[this.index + 1] << 8 | data[this.index + 2] << 16 | data[this.index + 3] << 24;
|
||||
this.index = this.index + 4;
|
||||
var highBits = data[this.index] | data[this.index + 1] << 8 | data[this.index + 2] << 16 | data[this.index + 3] << 24;
|
||||
this.index = this.index + 4;
|
||||
// Create long object
|
||||
this.cursorId = new Long(lowBits, highBits);
|
||||
|
||||
// Unpack the starting from
|
||||
this.startingFrom = data[this.index] | data[this.index + 1] << 8 | data[this.index + 2] << 16 | data[this.index + 3] << 24;
|
||||
this.index = this.index + 4;
|
||||
|
||||
// Unpack the number of objects returned
|
||||
this.numberReturned = data[this.index] | data[this.index + 1] << 8 | data[this.index + 2] << 16 | data[this.index + 3] << 24;
|
||||
this.index = this.index + 4;
|
||||
|
||||
// Preallocate document array
|
||||
this.documents = new Array(this.numberReturned);
|
||||
|
||||
// Flag values
|
||||
this.cursorNotFound = (this.responseFlags & CURSOR_NOT_FOUND) != 0;
|
||||
this.queryFailure = (this.responseFlags & QUERY_FAILURE) != 0;
|
||||
this.shardConfigStale = (this.responseFlags & SHARD_CONFIG_STALE) != 0;
|
||||
this.awaitCapable = (this.responseFlags & AWAIT_CAPABLE) != 0;
|
||||
this.promoteLongs = typeof opts.promoteLongs == 'boolean' ? opts.promoteLongs : true;
|
||||
this.promoteValues = typeof opts.promoteValues == 'boolean' ? opts.promoteValues : true;
|
||||
this.promoteBuffers = typeof opts.promoteBuffers == 'boolean' ? opts.promoteBuffers : false;
|
||||
}
|
||||
|
||||
Response.prototype.isParsed = function() {
|
||||
return this.parsed;
|
||||
}
|
||||
|
||||
Response.prototype.parse = function(options) {
|
||||
// Don't parse again if not needed
|
||||
if(this.parsed) return;
|
||||
options = options || {};
|
||||
|
||||
// Allow the return of raw documents instead of parsing
|
||||
var raw = options.raw || false;
|
||||
var documentsReturnedIn = options.documentsReturnedIn || null;
|
||||
var promoteLongs = typeof options.promoteLongs == 'boolean'
|
||||
? options.promoteLongs
|
||||
: this.opts.promoteLongs;
|
||||
var promoteValues = typeof options.promoteValues == 'boolean'
|
||||
? options.promoteValues
|
||||
: this.opts.promoteValues;
|
||||
var promoteBuffers = typeof options.promoteBuffers == 'boolean'
|
||||
? options.promoteBuffers
|
||||
: this.opts.promoteBuffers
|
||||
var bsonSize, _options;
|
||||
|
||||
// Set up the options
|
||||
_options = {
|
||||
promoteLongs: promoteLongs,
|
||||
promoteValues: promoteValues,
|
||||
promoteBuffers: promoteBuffers
|
||||
};
|
||||
|
||||
//
|
||||
// Single document and documentsReturnedIn set
|
||||
//
|
||||
if(this.numberReturned == 1 && documentsReturnedIn != null && raw) {
|
||||
// Calculate the bson size
|
||||
bsonSize = this.data[this.index] | this.data[this.index + 1] << 8 | this.data[this.index + 2] << 16 | this.data[this.index + 3] << 24;
|
||||
// Slice out the buffer containing the command result document
|
||||
var document = this.data.slice(this.index, this.index + bsonSize);
|
||||
// Set up field we wish to keep as raw
|
||||
var fieldsAsRaw = {}
|
||||
fieldsAsRaw[documentsReturnedIn] = true;
|
||||
_options.fieldsAsRaw = fieldsAsRaw;
|
||||
|
||||
// Deserialize but keep the array of documents in non-parsed form
|
||||
var doc = this.bson.deserialize(document, _options);
|
||||
|
||||
// Get the documents
|
||||
this.documents = doc.cursor[documentsReturnedIn];
|
||||
this.numberReturned = this.documents.length;
|
||||
// Ensure we have a Long valie cursor id
|
||||
this.cursorId = typeof doc.cursor.id == 'number'
|
||||
? Long.fromNumber(doc.cursor.id)
|
||||
: doc.cursor.id;
|
||||
|
||||
// Adjust the index
|
||||
this.index = this.index + bsonSize;
|
||||
|
||||
// Set as parsed
|
||||
this.parsed = true
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Parse Body
|
||||
//
|
||||
for(var i = 0; i < this.numberReturned; i++) {
|
||||
bsonSize = this.data[this.index] | this.data[this.index + 1] << 8 | this.data[this.index + 2] << 16 | this.data[this.index + 3] << 24;
|
||||
|
||||
// If we have raw results specified slice the return document
|
||||
if(raw) {
|
||||
this.documents[i] = this.data.slice(this.index, this.index + bsonSize);
|
||||
} else {
|
||||
this.documents[i] = this.bson.deserialize(this.data.slice(this.index, this.index + bsonSize), _options);
|
||||
}
|
||||
|
||||
// Adjust the index
|
||||
this.index = this.index + bsonSize;
|
||||
}
|
||||
|
||||
// Set parsed
|
||||
this.parsed = true;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
Query: Query
|
||||
, GetMore: GetMore
|
||||
, Response: Response
|
||||
, KillCursor: KillCursor
|
||||
}
|
||||
+619
@@ -0,0 +1,619 @@
|
||||
"use strict";
|
||||
|
||||
var inherits = require('util').inherits
|
||||
, EventEmitter = require('events').EventEmitter
|
||||
, net = require('net')
|
||||
, tls = require('tls')
|
||||
, crypto = require('crypto')
|
||||
, f = require('util').format
|
||||
, debugOptions = require('./utils').debugOptions
|
||||
, Response = require('./commands').Response
|
||||
, MongoError = require('../error')
|
||||
, Logger = require('./logger');
|
||||
|
||||
var _id = 0;
|
||||
var debugFields = ['host', 'port', 'size', 'keepAlive', 'keepAliveInitialDelay', 'noDelay'
|
||||
, 'connectionTimeout', 'socketTimeout', 'singleBufferSerializtion', 'ssl', 'ca', 'crl', 'cert'
|
||||
, 'rejectUnauthorized', 'promoteLongs', 'promoteValues', 'promoteBuffers', 'checkServerIdentity'];
|
||||
var connectionAccounting = false;
|
||||
var connections = {};
|
||||
|
||||
/**
|
||||
* Creates a new Connection instance
|
||||
* @class
|
||||
* @param {string} options.host The server host
|
||||
* @param {number} options.port The server port
|
||||
* @param {number} [options.family=null] IP version for DNS lookup, passed down to Node's [`dns.lookup()` function](https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback). If set to `6`, will only look for ipv6 addresses.
|
||||
* @param {boolean} [options.keepAlive=true] TCP Connection keep alive enabled
|
||||
* @param {number} [options.keepAliveInitialDelay=300000] Initial delay before TCP keep alive enabled
|
||||
* @param {boolean} [options.noDelay=true] TCP Connection no delay
|
||||
* @param {number} [options.connectionTimeout=30000] TCP Connection timeout setting
|
||||
* @param {number} [options.socketTimeout=360000] TCP Socket timeout setting
|
||||
* @param {boolean} [options.singleBufferSerializtion=true] Serialize into single buffer, trade of peak memory for serialization speed
|
||||
* @param {boolean} [options.ssl=false] Use SSL for connection
|
||||
* @param {boolean|function} [options.checkServerIdentity=true] Ensure we check server identify during SSL, set to false to disable checking. Only works for Node 0.12.x or higher. You can pass in a boolean or your own checkServerIdentity override function.
|
||||
* @param {Buffer} [options.ca] SSL Certificate store binary buffer
|
||||
* @param {Buffer} [options.crl] SSL Certificate revocation store binary buffer
|
||||
* @param {Buffer} [options.cert] SSL Certificate binary buffer
|
||||
* @param {Buffer} [options.key] SSL Key file binary buffer
|
||||
* @param {string} [options.passphrase] SSL Certificate pass phrase
|
||||
* @param {boolean} [options.rejectUnauthorized=true] Reject unauthorized server certificates
|
||||
* @param {boolean} [options.promoteLongs=true] Convert Long values from the db into Numbers if they fit into 53 bits
|
||||
* @param {boolean} [options.promoteValues=true] Promotes BSON values to native types where possible, set to false to only receive wrapper types.
|
||||
* @param {boolean} [options.promoteBuffers=false] Promotes Binary BSON values to native Node Buffers.
|
||||
* @fires Connection#connect
|
||||
* @fires Connection#close
|
||||
* @fires Connection#error
|
||||
* @fires Connection#timeout
|
||||
* @fires Connection#parseError
|
||||
* @return {Connection} A cursor instance
|
||||
*/
|
||||
var Connection = function(messageHandler, options) {
|
||||
// Add event listener
|
||||
EventEmitter.call(this);
|
||||
// Set empty if no options passed
|
||||
this.options = options || {};
|
||||
// Identification information
|
||||
this.id = _id++;
|
||||
// Logger instance
|
||||
this.logger = Logger('Connection', options);
|
||||
// No bson parser passed in
|
||||
if(!options.bson) throw new Error("must pass in valid bson parser");
|
||||
// Get bson parser
|
||||
this.bson = options.bson;
|
||||
// Grouping tag used for debugging purposes
|
||||
this.tag = options.tag;
|
||||
// Message handler
|
||||
this.messageHandler = messageHandler;
|
||||
|
||||
// Max BSON message size
|
||||
this.maxBsonMessageSize = options.maxBsonMessageSize || (1024 * 1024 * 16 * 4);
|
||||
// Debug information
|
||||
if(this.logger.isDebug()) this.logger.debug(f('creating connection %s with options [%s]', this.id, JSON.stringify(debugOptions(debugFields, options))));
|
||||
|
||||
// Default options
|
||||
this.port = options.port || 27017;
|
||||
this.host = options.host || 'localhost';
|
||||
this.family = typeof options.family == 'number' ? options.family : void 0;
|
||||
this.keepAlive = typeof options.keepAlive == 'boolean' ? options.keepAlive : true;
|
||||
this.keepAliveInitialDelay = typeof options.keepAliveInitialDelay == 'number'
|
||||
? options.keepAliveInitialDelay : 300000;
|
||||
this.noDelay = typeof options.noDelay == 'boolean' ? options.noDelay : true;
|
||||
this.connectionTimeout = typeof options.connectionTimeout == 'number'
|
||||
? options.connectionTimeout : 30000;
|
||||
this.socketTimeout = typeof options.socketTimeout == 'number'
|
||||
? options.socketTimeout : 360000;
|
||||
|
||||
// Is the keepAliveInitialDelay > socketTimeout set it to half of socketTimeout
|
||||
if(this.keepAliveInitialDelay > this.socketTimeout) {
|
||||
this.keepAliveInitialDelay = Math.round(this.socketTimeout/2);
|
||||
}
|
||||
|
||||
// If connection was destroyed
|
||||
this.destroyed = false;
|
||||
|
||||
// Check if we have a domain socket
|
||||
this.domainSocket = this.host.indexOf('\/') != -1;
|
||||
|
||||
// Serialize commands using function
|
||||
this.singleBufferSerializtion = typeof options.singleBufferSerializtion == 'boolean' ? options.singleBufferSerializtion : true;
|
||||
this.serializationFunction = this.singleBufferSerializtion ? 'toBinUnified' : 'toBin';
|
||||
|
||||
// SSL options
|
||||
this.ca = options.ca || null;
|
||||
this.crl = options.crl || null;
|
||||
this.cert = options.cert || null;
|
||||
this.key = options.key || null;
|
||||
this.passphrase = options.passphrase || null;
|
||||
this.ciphers = options.ciphers || null;
|
||||
this.ecdhCurve = options.ecdhCurve || null;
|
||||
this.ssl = typeof options.ssl == 'boolean' ? options.ssl : false;
|
||||
this.rejectUnauthorized = typeof options.rejectUnauthorized == 'boolean' ? options.rejectUnauthorized : true;
|
||||
this.checkServerIdentity = typeof options.checkServerIdentity == 'boolean'
|
||||
|| typeof options.checkServerIdentity == 'function' ? options.checkServerIdentity : true;
|
||||
|
||||
// If ssl not enabled
|
||||
if(!this.ssl) this.rejectUnauthorized = false;
|
||||
|
||||
// Response options
|
||||
this.responseOptions = {
|
||||
promoteLongs: typeof options.promoteLongs == 'boolean' ? options.promoteLongs : true,
|
||||
promoteValues: typeof options.promoteValues == 'boolean' ? options.promoteValues : true,
|
||||
promoteBuffers: typeof options.promoteBuffers == 'boolean' ? options.promoteBuffers: false
|
||||
}
|
||||
|
||||
// Flushing
|
||||
this.flushing = false;
|
||||
this.queue = [];
|
||||
|
||||
// Internal state
|
||||
this.connection = null;
|
||||
this.writeStream = null;
|
||||
|
||||
// Create hash method
|
||||
var hash = crypto.createHash('sha1');
|
||||
hash.update(f('%s:%s', this.host, this.port));
|
||||
|
||||
// Create a hash name
|
||||
this.hashedName = hash.digest('hex');
|
||||
|
||||
// All operations in flight on the connection
|
||||
this.workItems = [];
|
||||
}
|
||||
|
||||
inherits(Connection, EventEmitter);
|
||||
|
||||
Connection.prototype.setSocketTimeout = function(value) {
|
||||
if(this.connection) {
|
||||
this.connection.setTimeout(value);
|
||||
}
|
||||
}
|
||||
|
||||
Connection.prototype.resetSocketTimeout = function() {
|
||||
if(this.connection) {
|
||||
this.connection.setTimeout(this.socketTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
Connection.enableConnectionAccounting = function() {
|
||||
connectionAccounting = true;
|
||||
connections = {};
|
||||
}
|
||||
|
||||
Connection.disableConnectionAccounting = function() {
|
||||
connectionAccounting = false;
|
||||
}
|
||||
|
||||
Connection.connections = function() {
|
||||
return connections;
|
||||
}
|
||||
|
||||
function deleteConnection(id) {
|
||||
// console.log("=== deleted connection " + id + " :: " + (connections[id] ? connections[id].port : ''))
|
||||
delete connections[id];
|
||||
}
|
||||
|
||||
function addConnection(id, connection) {
|
||||
// console.log("=== added connection " + id + " :: " + connection.port)
|
||||
connections[id] = connection;
|
||||
}
|
||||
|
||||
//
|
||||
// Connection handlers
|
||||
var errorHandler = function(self) {
|
||||
return function(err) {
|
||||
if(connectionAccounting) deleteConnection(self.id);
|
||||
// Debug information
|
||||
if(self.logger.isDebug()) self.logger.debug(f('connection %s for [%s:%s] errored out with [%s]', self.id, self.host, self.port, JSON.stringify(err)));
|
||||
// Emit the error
|
||||
if(self.listeners('error').length > 0) self.emit("error", MongoError.create(err), self);
|
||||
}
|
||||
}
|
||||
|
||||
var timeoutHandler = function(self) {
|
||||
return function() {
|
||||
if(connectionAccounting) deleteConnection(self.id);
|
||||
// Debug information
|
||||
if(self.logger.isDebug()) self.logger.debug(f('connection %s for [%s:%s] timed out', self.id, self.host, self.port));
|
||||
// Emit timeout error
|
||||
self.emit("timeout"
|
||||
, MongoError.create(f("connection %s to %s:%s timed out", self.id, self.host, self.port))
|
||||
, self);
|
||||
}
|
||||
}
|
||||
|
||||
var closeHandler = function(self) {
|
||||
return function(hadError) {
|
||||
if(connectionAccounting) deleteConnection(self.id);
|
||||
// Debug information
|
||||
if(self.logger.isDebug()) self.logger.debug(f('connection %s with for [%s:%s] closed', self.id, self.host, self.port));
|
||||
|
||||
// Emit close event
|
||||
if(!hadError) {
|
||||
self.emit("close"
|
||||
, MongoError.create(f("connection %s to %s:%s closed", self.id, self.host, self.port))
|
||||
, self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var dataHandler = function(self) {
|
||||
return function(data) {
|
||||
// Parse until we are done with the data
|
||||
while(data.length > 0) {
|
||||
// If we still have bytes to read on the current message
|
||||
if(self.bytesRead > 0 && self.sizeOfMessage > 0) {
|
||||
// Calculate the amount of remaining bytes
|
||||
var remainingBytesToRead = self.sizeOfMessage - self.bytesRead;
|
||||
// Check if the current chunk contains the rest of the message
|
||||
if(remainingBytesToRead > data.length) {
|
||||
// Copy the new data into the exiting buffer (should have been allocated when we know the message size)
|
||||
data.copy(self.buffer, self.bytesRead);
|
||||
// Adjust the number of bytes read so it point to the correct index in the buffer
|
||||
self.bytesRead = self.bytesRead + data.length;
|
||||
|
||||
// Reset state of buffer
|
||||
data = new Buffer(0);
|
||||
} else {
|
||||
// Copy the missing part of the data into our current buffer
|
||||
data.copy(self.buffer, self.bytesRead, 0, remainingBytesToRead);
|
||||
// Slice the overflow into a new buffer that we will then re-parse
|
||||
data = data.slice(remainingBytesToRead);
|
||||
|
||||
// Emit current complete message
|
||||
try {
|
||||
var emitBuffer = self.buffer;
|
||||
// Reset state of buffer
|
||||
self.buffer = null;
|
||||
self.sizeOfMessage = 0;
|
||||
self.bytesRead = 0;
|
||||
self.stubBuffer = null;
|
||||
// Emit the buffer
|
||||
self.messageHandler(new Response(self.bson, emitBuffer, self.responseOptions), self);
|
||||
} catch(err) {
|
||||
var errorObject = {err:"socketHandler", trace:err, bin:self.buffer, parseState:{
|
||||
sizeOfMessage:self.sizeOfMessage,
|
||||
bytesRead:self.bytesRead,
|
||||
stubBuffer:self.stubBuffer}};
|
||||
// We got a parse Error fire it off then keep going
|
||||
self.emit("parseError", errorObject, self);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Stub buffer is kept in case we don't get enough bytes to determine the
|
||||
// size of the message (< 4 bytes)
|
||||
if(self.stubBuffer != null && self.stubBuffer.length > 0) {
|
||||
// If we have enough bytes to determine the message size let's do it
|
||||
if(self.stubBuffer.length + data.length > 4) {
|
||||
// Prepad the data
|
||||
var newData = new Buffer(self.stubBuffer.length + data.length);
|
||||
self.stubBuffer.copy(newData, 0);
|
||||
data.copy(newData, self.stubBuffer.length);
|
||||
// Reassign for parsing
|
||||
data = newData;
|
||||
|
||||
// Reset state of buffer
|
||||
self.buffer = null;
|
||||
self.sizeOfMessage = 0;
|
||||
self.bytesRead = 0;
|
||||
self.stubBuffer = null;
|
||||
|
||||
} else {
|
||||
|
||||
// Add the the bytes to the stub buffer
|
||||
var newStubBuffer = new Buffer(self.stubBuffer.length + data.length);
|
||||
// Copy existing stub buffer
|
||||
self.stubBuffer.copy(newStubBuffer, 0);
|
||||
// Copy missing part of the data
|
||||
data.copy(newStubBuffer, self.stubBuffer.length);
|
||||
// Exit parsing loop
|
||||
data = new Buffer(0);
|
||||
}
|
||||
} else {
|
||||
if(data.length > 4) {
|
||||
// Retrieve the message size
|
||||
// var sizeOfMessage = data.readUInt32LE(0);
|
||||
var sizeOfMessage = data[0] | data[1] << 8 | data[2] << 16 | data[3] << 24;
|
||||
// If we have a negative sizeOfMessage emit error and return
|
||||
if(sizeOfMessage < 0 || sizeOfMessage > self.maxBsonMessageSize) {
|
||||
errorObject = {err:"socketHandler", trace:'', bin:self.buffer, parseState:{
|
||||
sizeOfMessage: sizeOfMessage,
|
||||
bytesRead: self.bytesRead,
|
||||
stubBuffer: self.stubBuffer}};
|
||||
// We got a parse Error fire it off then keep going
|
||||
self.emit("parseError", errorObject, self);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure that the size of message is larger than 0 and less than the max allowed
|
||||
if(sizeOfMessage > 4 && sizeOfMessage < self.maxBsonMessageSize && sizeOfMessage > data.length) {
|
||||
self.buffer = new Buffer(sizeOfMessage);
|
||||
// Copy all the data into the buffer
|
||||
data.copy(self.buffer, 0);
|
||||
// Update bytes read
|
||||
self.bytesRead = data.length;
|
||||
// Update sizeOfMessage
|
||||
self.sizeOfMessage = sizeOfMessage;
|
||||
// Ensure stub buffer is null
|
||||
self.stubBuffer = null;
|
||||
// Exit parsing loop
|
||||
data = new Buffer(0);
|
||||
|
||||
} else if(sizeOfMessage > 4 && sizeOfMessage < self.maxBsonMessageSize && sizeOfMessage == data.length) {
|
||||
try {
|
||||
emitBuffer = data;
|
||||
// Reset state of buffer
|
||||
self.buffer = null;
|
||||
self.sizeOfMessage = 0;
|
||||
self.bytesRead = 0;
|
||||
self.stubBuffer = null;
|
||||
// Exit parsing loop
|
||||
data = new Buffer(0);
|
||||
// Emit the message
|
||||
self.messageHandler(new Response(self.bson, emitBuffer, self.responseOptions), self);
|
||||
} catch (err) {
|
||||
self.emit("parseError", err, self);
|
||||
}
|
||||
} else if(sizeOfMessage <= 4 || sizeOfMessage > self.maxBsonMessageSize) {
|
||||
errorObject = {err:"socketHandler", trace:null, bin:data, parseState:{
|
||||
sizeOfMessage:sizeOfMessage,
|
||||
bytesRead:0,
|
||||
buffer:null,
|
||||
stubBuffer:null}};
|
||||
// We got a parse Error fire it off then keep going
|
||||
self.emit("parseError", errorObject, self);
|
||||
|
||||
// Clear out the state of the parser
|
||||
self.buffer = null;
|
||||
self.sizeOfMessage = 0;
|
||||
self.bytesRead = 0;
|
||||
self.stubBuffer = null;
|
||||
// Exit parsing loop
|
||||
data = new Buffer(0);
|
||||
} else {
|
||||
emitBuffer = data.slice(0, sizeOfMessage);
|
||||
// Reset state of buffer
|
||||
self.buffer = null;
|
||||
self.sizeOfMessage = 0;
|
||||
self.bytesRead = 0;
|
||||
self.stubBuffer = null;
|
||||
// Copy rest of message
|
||||
data = data.slice(sizeOfMessage);
|
||||
// Emit the message
|
||||
self.messageHandler(new Response(self.bson, emitBuffer, self.responseOptions), self);
|
||||
}
|
||||
} else {
|
||||
// Create a buffer that contains the space for the non-complete message
|
||||
self.stubBuffer = new Buffer(data.length)
|
||||
// Copy the data to the stub buffer
|
||||
data.copy(self.stubBuffer, 0);
|
||||
// Exit parsing loop
|
||||
data = new Buffer(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// List of socket level valid ssl options
|
||||
var legalSslSocketOptions = ['pfx', 'key', 'passphrase', 'cert', 'ca', 'ciphers'
|
||||
, 'NPNProtocols', 'ALPNProtocols', 'servername', 'ecdhCurve'
|
||||
, 'secureProtocol', 'secureContext', 'session'
|
||||
, 'minDHSize'];
|
||||
|
||||
function merge(options1, options2) {
|
||||
// Merge in any allowed ssl options
|
||||
for(var name in options2) {
|
||||
if(options2[name] != null && legalSslSocketOptions.indexOf(name) != -1) {
|
||||
options1[name] = options2[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect
|
||||
* @method
|
||||
*/
|
||||
Connection.prototype.connect = function(_options) {
|
||||
var self = this;
|
||||
_options = _options || {};
|
||||
// Set the connections
|
||||
if(connectionAccounting) addConnection(this.id, this);
|
||||
// Check if we are overriding the promoteLongs
|
||||
if(typeof _options.promoteLongs == 'boolean') {
|
||||
self.responseOptions.promoteLongs = _options.promoteLongs;
|
||||
self.responseOptions.promoteValues = _options.promoteValues;
|
||||
self.responseOptions.promoteBuffers = _options.promoteBuffers;
|
||||
}
|
||||
|
||||
// Create new connection instance
|
||||
var connection_options;
|
||||
if (self.domainSocket) {
|
||||
connection_options = {path: self.host};
|
||||
} else {
|
||||
connection_options = {port: self.port, host: self.host};
|
||||
if (self.family !== void 0) {
|
||||
connection_options.family = self.family;
|
||||
}
|
||||
}
|
||||
self.connection = net.createConnection(connection_options);
|
||||
|
||||
// Set the options for the connection
|
||||
self.connection.setKeepAlive(self.keepAlive, self.keepAliveInitialDelay);
|
||||
self.connection.setTimeout(self.connectionTimeout);
|
||||
self.connection.setNoDelay(self.noDelay);
|
||||
|
||||
// If we have ssl enabled
|
||||
if(self.ssl) {
|
||||
var sslOptions = {
|
||||
socket: self.connection
|
||||
, rejectUnauthorized: self.rejectUnauthorized
|
||||
}
|
||||
|
||||
// Merge in options
|
||||
merge(sslOptions, this.options);
|
||||
merge(sslOptions, _options);
|
||||
|
||||
// Set options for ssl
|
||||
if(self.ca) sslOptions.ca = self.ca;
|
||||
if(self.crl) sslOptions.crl = self.crl;
|
||||
if(self.cert) sslOptions.cert = self.cert;
|
||||
if(self.key) sslOptions.key = self.key;
|
||||
if(self.passphrase) sslOptions.passphrase = self.passphrase;
|
||||
|
||||
// Override checkServerIdentity behavior
|
||||
if(self.checkServerIdentity == false) {
|
||||
// Skip the identiy check by retuning undefined as per node documents
|
||||
// https://nodejs.org/api/tls.html#tls_tls_connect_options_callback
|
||||
sslOptions.checkServerIdentity = function() {
|
||||
return undefined;
|
||||
}
|
||||
} else if(typeof self.checkServerIdentity == 'function') {
|
||||
sslOptions.checkServerIdentity = self.checkServerIdentity;
|
||||
}
|
||||
|
||||
// Set default sni servername to be the same as host
|
||||
if(sslOptions.servername == null) {
|
||||
sslOptions.servername = self.host;
|
||||
}
|
||||
|
||||
// Attempt SSL connection
|
||||
self.connection = tls.connect(self.port, self.host, sslOptions, function() {
|
||||
// Error on auth or skip
|
||||
if(self.connection.authorizationError && self.rejectUnauthorized) {
|
||||
return self.emit("error", self.connection.authorizationError, self, {ssl:true});
|
||||
}
|
||||
|
||||
// Set socket timeout instead of connection timeout
|
||||
self.connection.setTimeout(self.socketTimeout);
|
||||
// We are done emit connect
|
||||
self.emit('connect', self);
|
||||
});
|
||||
self.connection.setTimeout(self.connectionTimeout);
|
||||
} else {
|
||||
self.connection.once('connect', function() {
|
||||
// Set socket timeout instead of connection timeout
|
||||
self.connection.setTimeout(self.socketTimeout);
|
||||
// Emit connect event
|
||||
self.emit('connect', self);
|
||||
});
|
||||
}
|
||||
|
||||
// Add handlers for events
|
||||
self.connection.once('error', errorHandler(self));
|
||||
self.connection.once('timeout', timeoutHandler(self));
|
||||
self.connection.once('close', closeHandler(self));
|
||||
self.connection.on('data', dataHandler(self));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unref this connection
|
||||
* @method
|
||||
* @return {boolean}
|
||||
*/
|
||||
Connection.prototype.unref = function() {
|
||||
if (this.connection) this.connection.unref();
|
||||
else {
|
||||
var self = this;
|
||||
this.once('connect', function() {
|
||||
self.connection.unref();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy connection
|
||||
* @method
|
||||
*/
|
||||
Connection.prototype.destroy = function() {
|
||||
// Set the connections
|
||||
if(connectionAccounting) deleteConnection(this.id);
|
||||
if(this.connection) {
|
||||
// Catch posssible exception thrown by node 0.10.x
|
||||
try { this.connection.end(); } catch (err) {}
|
||||
// Destroy connection
|
||||
this.connection.destroy();
|
||||
}
|
||||
|
||||
this.destroyed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write to connection
|
||||
* @method
|
||||
* @param {Command} command Command to write out need to implement toBin and toBinUnified
|
||||
*/
|
||||
Connection.prototype.write = function(buffer) {
|
||||
var i;
|
||||
// Debug Log
|
||||
if(this.logger.isDebug()) {
|
||||
if(!Array.isArray(buffer)) {
|
||||
this.logger.debug(f('writing buffer [%s] to %s:%s', buffer.toString('hex'), this.host, this.port));
|
||||
} else {
|
||||
for(i = 0; i < buffer.length; i++)
|
||||
this.logger.debug(f('writing buffer [%s] to %s:%s', buffer[i].toString('hex'), this.host, this.port));
|
||||
}
|
||||
}
|
||||
|
||||
// Double check that the connection is not destroyed
|
||||
if(this.connection.destroyed === false) {
|
||||
// Write out the command
|
||||
if(!Array.isArray(buffer)) {
|
||||
this.connection.write(buffer, 'binary');
|
||||
return true;
|
||||
}
|
||||
|
||||
// Iterate over all buffers and write them in order to the socket
|
||||
for(i = 0; i < buffer.length; i++) this.connection.write(buffer[i], 'binary');
|
||||
return true;
|
||||
}
|
||||
|
||||
// Connection is destroyed return write failed
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return id of connection as a string
|
||||
* @method
|
||||
* @return {string}
|
||||
*/
|
||||
Connection.prototype.toString = function() {
|
||||
return "" + this.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return json object of connection
|
||||
* @method
|
||||
* @return {object}
|
||||
*/
|
||||
Connection.prototype.toJSON = function() {
|
||||
return {id: this.id, host: this.host, port: this.port};
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the connection connected
|
||||
* @method
|
||||
* @return {boolean}
|
||||
*/
|
||||
Connection.prototype.isConnected = function() {
|
||||
if(this.destroyed) return false;
|
||||
return !this.connection.destroyed && this.connection.writable;
|
||||
}
|
||||
|
||||
/**
|
||||
* A server connect event, used to verify that the connection is up and running
|
||||
*
|
||||
* @event Connection#connect
|
||||
* @type {Connection}
|
||||
*/
|
||||
|
||||
/**
|
||||
* The server connection closed, all pool connections closed
|
||||
*
|
||||
* @event Connection#close
|
||||
* @type {Connection}
|
||||
*/
|
||||
|
||||
/**
|
||||
* The server connection caused an error, all pool connections closed
|
||||
*
|
||||
* @event Connection#error
|
||||
* @type {Connection}
|
||||
*/
|
||||
|
||||
/**
|
||||
* The server connection timed out, all pool connections closed
|
||||
*
|
||||
* @event Connection#timeout
|
||||
* @type {Connection}
|
||||
*/
|
||||
|
||||
/**
|
||||
* The driver experienced an invalid message, all pool connections closed
|
||||
*
|
||||
* @event Connection#parseError
|
||||
* @type {Connection}
|
||||
*/
|
||||
|
||||
module.exports = Connection;
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
"use strict";
|
||||
|
||||
var f = require('util').format
|
||||
, MongoError = require('../error');
|
||||
|
||||
// Filters for classes
|
||||
var classFilters = {};
|
||||
var filteredClasses = {};
|
||||
var level = null;
|
||||
// Save the process id
|
||||
var pid = process.pid;
|
||||
// current logger
|
||||
var currentLogger = null;
|
||||
|
||||
/**
|
||||
* Creates a new Logger instance
|
||||
* @class
|
||||
* @param {string} className The Class name associated with the logging instance
|
||||
* @param {object} [options=null] Optional settings.
|
||||
* @param {Function} [options.logger=null] Custom logger function;
|
||||
* @param {string} [options.loggerLevel=error] Override default global log level.
|
||||
* @return {Logger} a Logger instance.
|
||||
*/
|
||||
var Logger = function(className, options) {
|
||||
if(!(this instanceof Logger)) return new Logger(className, options);
|
||||
options = options || {};
|
||||
|
||||
// Current reference
|
||||
this.className = className;
|
||||
|
||||
// Current logger
|
||||
if(options.logger) {
|
||||
currentLogger = options.logger;
|
||||
} else if(currentLogger == null) {
|
||||
currentLogger = console.log;
|
||||
}
|
||||
|
||||
// Set level of logging, default is error
|
||||
if(options.loggerLevel) {
|
||||
level = options.loggerLevel || 'error';
|
||||
}
|
||||
|
||||
// Add all class names
|
||||
if(filteredClasses[this.className] == null) classFilters[this.className] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a message at the debug level
|
||||
* @method
|
||||
* @param {string} message The message to log
|
||||
* @param {object} object additional meta data to log
|
||||
* @return {null}
|
||||
*/
|
||||
Logger.prototype.debug = function(message, object) {
|
||||
if(this.isDebug()
|
||||
&& ((Object.keys(filteredClasses).length > 0 && filteredClasses[this.className])
|
||||
|| (Object.keys(filteredClasses).length == 0 && classFilters[this.className]))) {
|
||||
var dateTime = new Date().getTime();
|
||||
var msg = f("[%s-%s:%s] %s %s", 'DEBUG', this.className, pid, dateTime, message);
|
||||
var state = {
|
||||
type: 'debug', message: message, className: this.className, pid: pid, date: dateTime
|
||||
};
|
||||
if(object) state.meta = object;
|
||||
currentLogger(msg, state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a message at the warn level
|
||||
* @method
|
||||
* @param {string} message The message to log
|
||||
* @param {object} object additional meta data to log
|
||||
* @return {null}
|
||||
*/
|
||||
Logger.prototype.warn = function(message, object) {
|
||||
if(this.isWarn()
|
||||
&& ((Object.keys(filteredClasses).length > 0 && filteredClasses[this.className])
|
||||
|| (Object.keys(filteredClasses).length == 0 && classFilters[this.className]))) {
|
||||
var dateTime = new Date().getTime();
|
||||
var msg = f("[%s-%s:%s] %s %s", 'WARN', this.className, pid, dateTime, message);
|
||||
var state = {
|
||||
type: 'warn', message: message, className: this.className, pid: pid, date: dateTime
|
||||
};
|
||||
if(object) state.meta = object;
|
||||
currentLogger(msg, state);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Log a message at the info level
|
||||
* @method
|
||||
* @param {string} message The message to log
|
||||
* @param {object} object additional meta data to log
|
||||
* @return {null}
|
||||
*/
|
||||
Logger.prototype.info = function(message, object) {
|
||||
if(this.isInfo()
|
||||
&& ((Object.keys(filteredClasses).length > 0 && filteredClasses[this.className])
|
||||
|| (Object.keys(filteredClasses).length == 0 && classFilters[this.className]))) {
|
||||
var dateTime = new Date().getTime();
|
||||
var msg = f("[%s-%s:%s] %s %s", 'INFO', this.className, pid, dateTime, message);
|
||||
var state = {
|
||||
type: 'info', message: message, className: this.className, pid: pid, date: dateTime
|
||||
};
|
||||
if(object) state.meta = object;
|
||||
currentLogger(msg, state);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Log a message at the error level
|
||||
* @method
|
||||
* @param {string} message The message to log
|
||||
* @param {object} object additional meta data to log
|
||||
* @return {null}
|
||||
*/
|
||||
Logger.prototype.error = function(message, object) {
|
||||
if(this.isError()
|
||||
&& ((Object.keys(filteredClasses).length > 0 && filteredClasses[this.className])
|
||||
|| (Object.keys(filteredClasses).length == 0 && classFilters[this.className]))) {
|
||||
var dateTime = new Date().getTime();
|
||||
var msg = f("[%s-%s:%s] %s %s", 'ERROR', this.className, pid, dateTime, message);
|
||||
var state = {
|
||||
type: 'error', message: message, className: this.className, pid: pid, date: dateTime
|
||||
};
|
||||
if(object) state.meta = object;
|
||||
currentLogger(msg, state);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Is the logger set at info level
|
||||
* @method
|
||||
* @return {boolean}
|
||||
*/
|
||||
Logger.prototype.isInfo = function() {
|
||||
return level == 'info' || level == 'debug';
|
||||
},
|
||||
|
||||
/**
|
||||
* Is the logger set at error level
|
||||
* @method
|
||||
* @return {boolean}
|
||||
*/
|
||||
Logger.prototype.isError = function() {
|
||||
return level == 'error' || level == 'info' || level == 'debug';
|
||||
},
|
||||
|
||||
/**
|
||||
* Is the logger set at error level
|
||||
* @method
|
||||
* @return {boolean}
|
||||
*/
|
||||
Logger.prototype.isWarn = function() {
|
||||
return level == 'error' || level == 'warn' || level == 'info' || level == 'debug';
|
||||
},
|
||||
|
||||
/**
|
||||
* Is the logger set at debug level
|
||||
* @method
|
||||
* @return {boolean}
|
||||
*/
|
||||
Logger.prototype.isDebug = function() {
|
||||
return level == 'debug';
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the logger to default settings, error and no filtered classes
|
||||
* @method
|
||||
* @return {null}
|
||||
*/
|
||||
Logger.reset = function() {
|
||||
level = 'error';
|
||||
filteredClasses = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current logger function
|
||||
* @method
|
||||
* @return {function}
|
||||
*/
|
||||
Logger.currentLogger = function() {
|
||||
return currentLogger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current logger function
|
||||
* @method
|
||||
* @param {function} logger Logger function.
|
||||
* @return {null}
|
||||
*/
|
||||
Logger.setCurrentLogger = function(logger) {
|
||||
if(typeof logger != 'function') throw new MongoError("current logger must be a function");
|
||||
currentLogger = logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set what classes to log.
|
||||
* @method
|
||||
* @param {string} type The type of filter (currently only class)
|
||||
* @param {string[]} values The filters to apply
|
||||
* @return {null}
|
||||
*/
|
||||
Logger.filter = function(type, values) {
|
||||
if(type == 'class' && Array.isArray(values)) {
|
||||
filteredClasses = {};
|
||||
|
||||
values.forEach(function(x) {
|
||||
filteredClasses[x] = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current log level
|
||||
* @method
|
||||
* @param {string} level Set current log level (debug, info, error)
|
||||
* @return {null}
|
||||
*/
|
||||
Logger.setLevel = function(_level) {
|
||||
if(_level != 'info' && _level != 'error' && _level != 'debug' && _level != 'warn') {
|
||||
throw new Error(f("%s is an illegal logging level", _level));
|
||||
}
|
||||
|
||||
level = _level;
|
||||
}
|
||||
|
||||
module.exports = Logger;
|
||||
+1387
File diff suppressed because it is too large
Load Diff
+86
@@ -0,0 +1,86 @@
|
||||
"use strict";
|
||||
|
||||
var f = require('util').format,
|
||||
require_optional = require('require_optional');
|
||||
|
||||
// Set property function
|
||||
var setProperty = function(obj, prop, flag, values) {
|
||||
Object.defineProperty(obj, prop.name, {
|
||||
enumerable:true,
|
||||
set: function(value) {
|
||||
if(typeof value != 'boolean') throw new Error(f("%s required a boolean", prop.name));
|
||||
// Flip the bit to 1
|
||||
if(value == true) values.flags |= flag;
|
||||
// Flip the bit to 0 if it's set, otherwise ignore
|
||||
if(value == false && (values.flags & flag) == flag) values.flags ^= flag;
|
||||
prop.value = value;
|
||||
}
|
||||
, get: function() { return prop.value; }
|
||||
});
|
||||
}
|
||||
|
||||
// Set property function
|
||||
var getProperty = function(obj, propName, fieldName, values, func) {
|
||||
Object.defineProperty(obj, propName, {
|
||||
enumerable:true,
|
||||
get: function() {
|
||||
// Not parsed yet, parse it
|
||||
if(values[fieldName] == null && obj.isParsed && !obj.isParsed()) {
|
||||
obj.parse();
|
||||
}
|
||||
|
||||
// Do we have a post processing function
|
||||
if(typeof func == 'function') return func(values[fieldName]);
|
||||
// Return raw value
|
||||
return values[fieldName];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Set simple property
|
||||
var getSingleProperty = function(obj, name, value) {
|
||||
Object.defineProperty(obj, name, {
|
||||
enumerable:true,
|
||||
get: function() {
|
||||
return value
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Shallow copy
|
||||
var copy = function(fObj, tObj) {
|
||||
tObj = tObj || {};
|
||||
for(var name in fObj) tObj[name] = fObj[name];
|
||||
return tObj;
|
||||
}
|
||||
|
||||
var debugOptions = function(debugFields, options) {
|
||||
var finaloptions = {};
|
||||
debugFields.forEach(function(n) {
|
||||
finaloptions[n] = options[n];
|
||||
});
|
||||
|
||||
return finaloptions;
|
||||
}
|
||||
|
||||
var retrieveBSON = function() {
|
||||
var BSON = require('bson');
|
||||
BSON.native = false;
|
||||
|
||||
try {
|
||||
var optionalBSON = require_optional('bson-ext');
|
||||
if(optionalBSON) {
|
||||
optionalBSON.native = true;
|
||||
return optionalBSON;
|
||||
}
|
||||
} catch(err) {}
|
||||
|
||||
return BSON;
|
||||
}
|
||||
|
||||
exports.setProperty = setProperty;
|
||||
exports.getProperty = getProperty;
|
||||
exports.getSingleProperty = getSingleProperty;
|
||||
exports.copy = copy;
|
||||
exports.debugOptions = debugOptions;
|
||||
exports.retrieveBSON = retrieveBSON;
|
||||
+704
@@ -0,0 +1,704 @@
|
||||
"use strict";
|
||||
|
||||
var Logger = require('./connection/logger')
|
||||
, retrieveBSON = require('./connection/utils').retrieveBSON
|
||||
, MongoError = require('./error')
|
||||
, f = require('util').format;
|
||||
|
||||
var BSON = retrieveBSON(),
|
||||
Long = BSON.Long;
|
||||
|
||||
/**
|
||||
* This is a cursor results callback
|
||||
*
|
||||
* @callback resultCallback
|
||||
* @param {error} error An error object. Set to null if no error present
|
||||
* @param {object} document
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileOverview The **Cursor** class is an internal class that embodies a cursor on MongoDB
|
||||
* allowing for iteration over the results returned from the underlying query.
|
||||
*
|
||||
* **CURSORS Cannot directly be instantiated**
|
||||
* @example
|
||||
* var Server = require('mongodb-core').Server
|
||||
* , ReadPreference = require('mongodb-core').ReadPreference
|
||||
* , assert = require('assert');
|
||||
*
|
||||
* var server = new Server({host: 'localhost', port: 27017});
|
||||
* // Wait for the connection event
|
||||
* server.on('connect', function(server) {
|
||||
* assert.equal(null, err);
|
||||
*
|
||||
* // Execute the write
|
||||
* var cursor = _server.cursor('integration_tests.inserts_example4', {
|
||||
* find: 'integration_tests.example4'
|
||||
* , query: {a:1}
|
||||
* }, {
|
||||
* readPreference: new ReadPreference('secondary');
|
||||
* });
|
||||
*
|
||||
* // Get the first document
|
||||
* cursor.next(function(err, doc) {
|
||||
* assert.equal(null, err);
|
||||
* server.destroy();
|
||||
* });
|
||||
* });
|
||||
*
|
||||
* // Start connecting
|
||||
* server.connect();
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates a new Cursor, not to be used directly
|
||||
* @class
|
||||
* @param {object} bson An instance of the BSON parser
|
||||
* @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
|
||||
* @param {{object}|Long} cmd The selector (can be a command or a cursorId)
|
||||
* @param {object} [options=null] Optional settings.
|
||||
* @param {object} [options.batchSize=1000] Batchsize for the operation
|
||||
* @param {array} [options.documents=[]] Initial documents list for cursor
|
||||
* @param {object} [options.transforms=null] Transform methods for the cursor results
|
||||
* @param {function} [options.transforms.query] Transform the value returned from the initial query
|
||||
* @param {function} [options.transforms.doc] Transform each document returned from Cursor.prototype.next
|
||||
* @param {object} topology The server topology instance.
|
||||
* @param {object} topologyOptions The server topology options.
|
||||
* @return {Cursor} A cursor instance
|
||||
* @property {number} cursorBatchSize The current cursorBatchSize for the cursor
|
||||
* @property {number} cursorLimit The current cursorLimit for the cursor
|
||||
* @property {number} cursorSkip The current cursorSkip for the cursor
|
||||
*/
|
||||
var Cursor = function(bson, ns, cmd, options, topology, topologyOptions) {
|
||||
options = options || {};
|
||||
|
||||
// Cursor pool
|
||||
this.pool = null;
|
||||
// Cursor server
|
||||
this.server = null;
|
||||
|
||||
// Do we have a not connected handler
|
||||
this.disconnectHandler = options.disconnectHandler;
|
||||
|
||||
// Set local values
|
||||
this.bson = bson;
|
||||
this.ns = ns;
|
||||
this.cmd = cmd;
|
||||
this.options = options;
|
||||
this.topology = topology;
|
||||
|
||||
// All internal state
|
||||
this.cursorState = {
|
||||
cursorId: null
|
||||
, cmd: cmd
|
||||
, documents: options.documents || []
|
||||
, cursorIndex: 0
|
||||
, dead: false
|
||||
, killed: false
|
||||
, init: false
|
||||
, notified: false
|
||||
, limit: options.limit || cmd.limit || 0
|
||||
, skip: options.skip || cmd.skip || 0
|
||||
, batchSize: options.batchSize || cmd.batchSize || 1000
|
||||
, currentLimit: 0
|
||||
// Result field name if not a cursor (contains the array of results)
|
||||
, transforms: options.transforms
|
||||
}
|
||||
|
||||
// Add promoteLong to cursor state
|
||||
if(typeof topologyOptions.promoteLongs == 'boolean') {
|
||||
this.cursorState.promoteLongs = topologyOptions.promoteLongs;
|
||||
} else if(typeof options.promoteLongs == 'boolean') {
|
||||
this.cursorState.promoteLongs = options.promoteLongs;
|
||||
}
|
||||
|
||||
// Add promoteValues to cursor state
|
||||
if(typeof topologyOptions.promoteValues == 'boolean') {
|
||||
this.cursorState.promoteValues = topologyOptions.promoteValues;
|
||||
} else if(typeof options.promoteValues == 'boolean') {
|
||||
this.cursorState.promoteValues = options.promoteValues;
|
||||
}
|
||||
|
||||
// Add promoteBuffers to cursor state
|
||||
if(typeof topologyOptions.promoteBuffers == 'boolean') {
|
||||
this.cursorState.promoteBuffers = topologyOptions.promoteBuffers;
|
||||
} else if(typeof options.promoteBuffers == 'boolean') {
|
||||
this.cursorState.promoteBuffers = options.promoteBuffers;
|
||||
}
|
||||
|
||||
// Logger
|
||||
this.logger = Logger('Cursor', topologyOptions);
|
||||
|
||||
//
|
||||
// Did we pass in a cursor id
|
||||
if(typeof cmd == 'number') {
|
||||
this.cursorState.cursorId = Long.fromNumber(cmd);
|
||||
this.cursorState.lastCursorId = this.cursorState.cursorId;
|
||||
} else if(cmd instanceof Long) {
|
||||
this.cursorState.cursorId = cmd;
|
||||
this.cursorState.lastCursorId = cmd;
|
||||
}
|
||||
}
|
||||
|
||||
Cursor.prototype.setCursorBatchSize = function(value) {
|
||||
this.cursorState.batchSize = value;
|
||||
}
|
||||
|
||||
Cursor.prototype.cursorBatchSize = function() {
|
||||
return this.cursorState.batchSize;
|
||||
}
|
||||
|
||||
Cursor.prototype.setCursorLimit = function(value) {
|
||||
this.cursorState.limit = value;
|
||||
}
|
||||
|
||||
Cursor.prototype.cursorLimit = function() {
|
||||
return this.cursorState.limit;
|
||||
}
|
||||
|
||||
Cursor.prototype.setCursorSkip = function(value) {
|
||||
this.cursorState.skip = value;
|
||||
}
|
||||
|
||||
Cursor.prototype.cursorSkip = function() {
|
||||
return this.cursorState.skip;
|
||||
}
|
||||
|
||||
//
|
||||
// Handle callback (including any exceptions thrown)
|
||||
var handleCallback = function(callback, err, result) {
|
||||
try {
|
||||
callback(err, result);
|
||||
} catch(err) {
|
||||
process.nextTick(function() {
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Internal methods
|
||||
Cursor.prototype._find = function(callback) {
|
||||
var self = this;
|
||||
|
||||
if(self.logger.isDebug()) {
|
||||
self.logger.debug(f('issue initial query [%s] with flags [%s]'
|
||||
, JSON.stringify(self.cmd)
|
||||
, JSON.stringify(self.query)));
|
||||
}
|
||||
|
||||
var queryCallback = function(err, r) {
|
||||
if(err) return callback(err);
|
||||
|
||||
// Get the raw message
|
||||
var result = r.message;
|
||||
|
||||
// Query failure bit set
|
||||
if(result.queryFailure) {
|
||||
return callback(MongoError.create(result.documents[0]), null);
|
||||
}
|
||||
|
||||
// Check if we have a command cursor
|
||||
if(Array.isArray(result.documents) && result.documents.length == 1
|
||||
&& (!self.cmd.find || (self.cmd.find && self.cmd.virtual == false))
|
||||
&& (result.documents[0].cursor != 'string'
|
||||
|| result.documents[0]['$err']
|
||||
|| result.documents[0]['errmsg']
|
||||
|| Array.isArray(result.documents[0].result))
|
||||
) {
|
||||
|
||||
// We have a an error document return the error
|
||||
if(result.documents[0]['$err']
|
||||
|| result.documents[0]['errmsg']) {
|
||||
return callback(MongoError.create(result.documents[0]), null);
|
||||
}
|
||||
|
||||
// We have a cursor document
|
||||
if(result.documents[0].cursor != null
|
||||
&& typeof result.documents[0].cursor != 'string') {
|
||||
var id = result.documents[0].cursor.id;
|
||||
// If we have a namespace change set the new namespace for getmores
|
||||
if(result.documents[0].cursor.ns) {
|
||||
self.ns = result.documents[0].cursor.ns;
|
||||
}
|
||||
// Promote id to long if needed
|
||||
self.cursorState.cursorId = typeof id == 'number' ? Long.fromNumber(id) : id;
|
||||
self.cursorState.lastCursorId = self.cursorState.cursorId;
|
||||
// If we have a firstBatch set it
|
||||
if(Array.isArray(result.documents[0].cursor.firstBatch)) {
|
||||
self.cursorState.documents = result.documents[0].cursor.firstBatch;//.reverse();
|
||||
}
|
||||
|
||||
// Return after processing command cursor
|
||||
return callback(null, null);
|
||||
}
|
||||
|
||||
if(Array.isArray(result.documents[0].result)) {
|
||||
self.cursorState.documents = result.documents[0].result;
|
||||
self.cursorState.cursorId = Long.ZERO;
|
||||
return callback(null, null);
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise fall back to regular find path
|
||||
self.cursorState.cursorId = result.cursorId;
|
||||
self.cursorState.documents = result.documents;
|
||||
self.cursorState.lastCursorId = result.cursorId;
|
||||
|
||||
// Transform the results with passed in transformation method if provided
|
||||
if(self.cursorState.transforms && typeof self.cursorState.transforms.query == 'function') {
|
||||
self.cursorState.documents = self.cursorState.transforms.query(result);
|
||||
}
|
||||
|
||||
// Return callback
|
||||
callback(null, null);
|
||||
}
|
||||
|
||||
// Options passed to the pool
|
||||
var queryOptions = {};
|
||||
|
||||
// If we have a raw query decorate the function
|
||||
if(self.options.raw || self.cmd.raw) {
|
||||
// queryCallback.raw = self.options.raw || self.cmd.raw;
|
||||
queryOptions.raw = self.options.raw || self.cmd.raw;
|
||||
}
|
||||
|
||||
// Do we have documentsReturnedIn set on the query
|
||||
if(typeof self.query.documentsReturnedIn == 'string') {
|
||||
// queryCallback.documentsReturnedIn = self.query.documentsReturnedIn;
|
||||
queryOptions.documentsReturnedIn = self.query.documentsReturnedIn;
|
||||
}
|
||||
|
||||
// Add promote Long value if defined
|
||||
if(typeof self.cursorState.promoteLongs == 'boolean') {
|
||||
queryOptions.promoteLongs = self.cursorState.promoteLongs;
|
||||
}
|
||||
|
||||
// Add promote values if defined
|
||||
if(typeof self.cursorState.promoteValues == 'boolean') {
|
||||
queryOptions.promoteValues = self.cursorState.promoteValues;
|
||||
}
|
||||
|
||||
// Add promote values if defined
|
||||
if(typeof self.cursorState.promoteBuffers == 'boolean') {
|
||||
queryOptions.promoteBuffers = self.cursorState.promoteBuffers;
|
||||
}
|
||||
// Write the initial command out
|
||||
self.server.s.pool.write(self.query, queryOptions, queryCallback);
|
||||
}
|
||||
|
||||
Cursor.prototype._getmore = function(callback) {
|
||||
if(this.logger.isDebug()) this.logger.debug(f('schedule getMore call for query [%s]', JSON.stringify(this.query)))
|
||||
// Determine if it's a raw query
|
||||
var raw = this.options.raw || this.cmd.raw;
|
||||
|
||||
// Set the current batchSize
|
||||
var batchSize = this.cursorState.batchSize;
|
||||
if(this.cursorState.limit > 0
|
||||
&& ((this.cursorState.currentLimit + batchSize) > this.cursorState.limit)) {
|
||||
batchSize = this.cursorState.limit - this.cursorState.currentLimit;
|
||||
}
|
||||
|
||||
// Default pool
|
||||
var pool = this.server.s.pool;
|
||||
|
||||
// We have a wire protocol handler
|
||||
this.server.wireProtocolHandler.getMore(this.bson, this.ns, this.cursorState, batchSize, raw, pool, this.options, callback);
|
||||
}
|
||||
|
||||
Cursor.prototype._killcursor = function(callback) {
|
||||
// Set cursor to dead
|
||||
this.cursorState.dead = true;
|
||||
this.cursorState.killed = true;
|
||||
// Remove documents
|
||||
this.cursorState.documents = [];
|
||||
|
||||
// If no cursor id just return
|
||||
if(this.cursorState.cursorId == null || this.cursorState.cursorId.isZero() || this.cursorState.init == false) {
|
||||
if(callback) callback(null, null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Default pool
|
||||
var pool = this.server.s.pool;
|
||||
// Execute command
|
||||
this.server.wireProtocolHandler.killCursor(this.bson, this.ns, this.cursorState.cursorId, pool, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone the cursor
|
||||
* @method
|
||||
* @return {Cursor}
|
||||
*/
|
||||
Cursor.prototype.clone = function() {
|
||||
return this.topology.cursor(this.ns, this.cmd, this.options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the cursor is dead
|
||||
* @method
|
||||
* @return {boolean} A boolean signifying if the cursor is dead or not
|
||||
*/
|
||||
Cursor.prototype.isDead = function() {
|
||||
return this.cursorState.dead == true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the cursor was killed by the application
|
||||
* @method
|
||||
* @return {boolean} A boolean signifying if the cursor was killed by the application
|
||||
*/
|
||||
Cursor.prototype.isKilled = function() {
|
||||
return this.cursorState.killed == true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the cursor notified it's caller about it's death
|
||||
* @method
|
||||
* @return {boolean} A boolean signifying if the cursor notified the callback
|
||||
*/
|
||||
Cursor.prototype.isNotified = function() {
|
||||
return this.cursorState.notified == true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current buffered documents length
|
||||
* @method
|
||||
* @return {number} The number of items in the buffered documents
|
||||
*/
|
||||
Cursor.prototype.bufferedCount = function() {
|
||||
return this.cursorState.documents.length - this.cursorState.cursorIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current buffered documents
|
||||
* @method
|
||||
* @return {Array} An array of buffered documents
|
||||
*/
|
||||
Cursor.prototype.readBufferedDocuments = function(number) {
|
||||
var unreadDocumentsLength = this.cursorState.documents.length - this.cursorState.cursorIndex;
|
||||
var length = number < unreadDocumentsLength ? number : unreadDocumentsLength;
|
||||
var elements = this.cursorState.documents.slice(this.cursorState.cursorIndex, this.cursorState.cursorIndex + length);
|
||||
|
||||
// Transform the doc with passed in transformation method if provided
|
||||
if(this.cursorState.transforms && typeof this.cursorState.transforms.doc == 'function') {
|
||||
// Transform all the elements
|
||||
for(var i = 0; i < elements.length; i++) {
|
||||
elements[i] = this.cursorState.transforms.doc(elements[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure we do not return any more documents than the limit imposed
|
||||
// Just return the number of elements up to the limit
|
||||
if(this.cursorState.limit > 0 && (this.cursorState.currentLimit + elements.length) > this.cursorState.limit) {
|
||||
elements = elements.slice(0, (this.cursorState.limit - this.cursorState.currentLimit));
|
||||
this.kill();
|
||||
}
|
||||
|
||||
// Adjust current limit
|
||||
this.cursorState.currentLimit = this.cursorState.currentLimit + elements.length;
|
||||
this.cursorState.cursorIndex = this.cursorState.cursorIndex + elements.length;
|
||||
|
||||
// Return elements
|
||||
return elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Kill the cursor
|
||||
* @method
|
||||
* @param {resultCallback} callback A callback function
|
||||
*/
|
||||
Cursor.prototype.kill = function(callback) {
|
||||
this._killcursor(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the cursor
|
||||
* @method
|
||||
* @return {null}
|
||||
*/
|
||||
Cursor.prototype.rewind = function() {
|
||||
if(this.cursorState.init) {
|
||||
if(!this.cursorState.dead) {
|
||||
this.kill();
|
||||
}
|
||||
|
||||
this.cursorState.currentLimit = 0;
|
||||
this.cursorState.init = false;
|
||||
this.cursorState.dead = false;
|
||||
this.cursorState.killed = false;
|
||||
this.cursorState.notified = false;
|
||||
this.cursorState.documents = [];
|
||||
this.cursorState.cursorId = null;
|
||||
this.cursorState.cursorIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate if the pool is dead and return error
|
||||
*/
|
||||
var isConnectionDead = function(self, callback) {
|
||||
if(self.pool
|
||||
&& self.pool.isDestroyed()) {
|
||||
self.cursorState.notified = true;
|
||||
self.cursorState.killed = true;
|
||||
self.cursorState.documents = [];
|
||||
self.cursorState.cursorIndex = 0;
|
||||
callback(MongoError.create(f('connection to host %s:%s was destroyed', self.pool.host, self.pool.port)))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate if the cursor is dead but was not explicitly killed by user
|
||||
*/
|
||||
var isCursorDeadButNotkilled = function(self, callback) {
|
||||
// Cursor is dead but not marked killed, return null
|
||||
if(self.cursorState.dead && !self.cursorState.killed) {
|
||||
self.cursorState.notified = true;
|
||||
self.cursorState.killed = true;
|
||||
self.cursorState.documents = [];
|
||||
self.cursorState.cursorIndex = 0;
|
||||
handleCallback(callback, null, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate if the cursor is dead and was killed by user
|
||||
*/
|
||||
var isCursorDeadAndKilled = function(self, callback) {
|
||||
if(self.cursorState.dead && self.cursorState.killed) {
|
||||
handleCallback(callback, MongoError.create('cursor is dead'));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate if the cursor was killed by the user
|
||||
*/
|
||||
var isCursorKilled = function(self, callback) {
|
||||
if(self.cursorState.killed) {
|
||||
self.cursorState.notified = true;
|
||||
self.cursorState.documents = [];
|
||||
self.cursorState.cursorIndex = 0;
|
||||
handleCallback(callback, null, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark cursor as being dead and notified
|
||||
*/
|
||||
var setCursorDeadAndNotified = function(self, callback) {
|
||||
self.cursorState.dead = true;
|
||||
self.cursorState.notified = true;
|
||||
self.cursorState.documents = [];
|
||||
self.cursorState.cursorIndex = 0;
|
||||
handleCallback(callback, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark cursor as being notified
|
||||
*/
|
||||
var setCursorNotified = function(self, callback) {
|
||||
self.cursorState.notified = true;
|
||||
self.cursorState.documents = [];
|
||||
self.cursorState.cursorIndex = 0;
|
||||
handleCallback(callback, null, null);
|
||||
}
|
||||
|
||||
var nextFunction = function(self, callback) {
|
||||
// We have notified about it
|
||||
if(self.cursorState.notified) {
|
||||
return callback(new Error('cursor is exhausted'));
|
||||
}
|
||||
|
||||
// Cursor is killed return null
|
||||
if(isCursorKilled(self, callback)) return;
|
||||
|
||||
// Cursor is dead but not marked killed, return null
|
||||
if(isCursorDeadButNotkilled(self, callback)) return;
|
||||
|
||||
// We have a dead and killed cursor, attempting to call next should error
|
||||
if(isCursorDeadAndKilled(self, callback)) return;
|
||||
|
||||
// We have just started the cursor
|
||||
if(!self.cursorState.init) {
|
||||
// Topology is not connected, save the call in the provided store to be
|
||||
// Executed at some point when the handler deems it's reconnected
|
||||
if(!self.topology.isConnected(self.options)) {
|
||||
// Only need this for single server, because repl sets and mongos
|
||||
// will always continue trying to reconnect
|
||||
if (self.topology._type === 'server' && !self.topology.s.options.reconnect) {
|
||||
// Reconnect is disabled, so we'll never reconnect
|
||||
return callback(new MongoError('no connection available'));
|
||||
}
|
||||
if (self.disconnectHandler != null) {
|
||||
if (self.topology.isDestroyed()) {
|
||||
// Topology was destroyed, so don't try to wait for it to reconnect
|
||||
return callback(new MongoError('Topology was destroyed'));
|
||||
}
|
||||
|
||||
return self.disconnectHandler.addObjectAndMethod('cursor', self, 'next', [callback], callback);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
self.server = self.topology.getServer(self.options);
|
||||
} catch(err) {
|
||||
// Handle the error and add object to next method call
|
||||
if(self.disconnectHandler != null) {
|
||||
return self.disconnectHandler.addObjectAndMethod('cursor', self, 'next', [callback], callback);
|
||||
}
|
||||
|
||||
// Otherwise return the error
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
// Set as init
|
||||
self.cursorState.init = true;
|
||||
|
||||
// Server does not support server
|
||||
if(self.cmd
|
||||
&& self.cmd.collation
|
||||
&& self.server.ismaster.maxWireVersion < 5) {
|
||||
return callback(new MongoError(f('server %s does not support collation', self.server.name)));
|
||||
}
|
||||
|
||||
try {
|
||||
self.query = self.server.wireProtocolHandler.command(self.bson, self.ns, self.cmd, self.cursorState, self.topology, self.options);
|
||||
} catch(err) {
|
||||
return callback(err);
|
||||
}
|
||||
}
|
||||
|
||||
// If we don't have a cursorId execute the first query
|
||||
if(self.cursorState.cursorId == null) {
|
||||
// Check if pool is dead and return if not possible to
|
||||
// execute the query against the db
|
||||
if(isConnectionDead(self, callback)) return;
|
||||
|
||||
// Check if topology is destroyed
|
||||
if(self.topology.isDestroyed()) return callback(new MongoError('connection destroyed, not possible to instantiate cursor'));
|
||||
|
||||
// query, cmd, options, cursorState, callback
|
||||
self._find(function(err) {
|
||||
if(err) return handleCallback(callback, err, null);
|
||||
|
||||
if(self.cursorState.documents.length == 0
|
||||
&& self.cursorState.cursorId && self.cursorState.cursorId.isZero()
|
||||
&& !self.cmd.tailable && !self.cmd.awaitData) {
|
||||
return setCursorNotified(self, callback);
|
||||
}
|
||||
|
||||
nextFunction(self, callback);
|
||||
});
|
||||
} else if(self.cursorState.limit > 0 && self.cursorState.currentLimit >= self.cursorState.limit) {
|
||||
// Ensure we kill the cursor on the server
|
||||
self.kill();
|
||||
// Set cursor in dead and notified state
|
||||
return setCursorDeadAndNotified(self, callback);
|
||||
} else if(self.cursorState.cursorIndex == self.cursorState.documents.length
|
||||
&& !Long.ZERO.equals(self.cursorState.cursorId)) {
|
||||
// Ensure an empty cursor state
|
||||
self.cursorState.documents = [];
|
||||
self.cursorState.cursorIndex = 0;
|
||||
|
||||
// Check if topology is destroyed
|
||||
if(self.topology.isDestroyed()) return callback(new MongoError('connection destroyed, not possible to instantiate cursor'));
|
||||
|
||||
// Check if connection is dead and return if not possible to
|
||||
// execute a getmore on this connection
|
||||
if(isConnectionDead(self, callback)) return;
|
||||
|
||||
// Execute the next get more
|
||||
self._getmore(function(err, doc, connection) {
|
||||
if(err) return handleCallback(callback, err);
|
||||
|
||||
// Save the returned connection to ensure all getMore's fire over the same connection
|
||||
self.connection = connection;
|
||||
|
||||
// Tailable cursor getMore result, notify owner about it
|
||||
// No attempt is made here to retry, this is left to the user of the
|
||||
// core module to handle to keep core simple
|
||||
if(self.cursorState.documents.length == 0
|
||||
&& self.cmd.tailable && Long.ZERO.equals(self.cursorState.cursorId)) {
|
||||
// No more documents in the tailed cursor
|
||||
return handleCallback(callback, MongoError.create({
|
||||
message: 'No more documents in tailed cursor'
|
||||
, tailable: self.cmd.tailable
|
||||
, awaitData: self.cmd.awaitData
|
||||
}));
|
||||
} else if(self.cursorState.documents.length == 0
|
||||
&& self.cmd.tailable && !Long.ZERO.equals(self.cursorState.cursorId)) {
|
||||
return nextFunction(self, callback);
|
||||
}
|
||||
|
||||
if(self.cursorState.limit > 0 && self.cursorState.currentLimit >= self.cursorState.limit) {
|
||||
return setCursorDeadAndNotified(self, callback);
|
||||
}
|
||||
|
||||
nextFunction(self, callback);
|
||||
});
|
||||
} else if(self.cursorState.documents.length == self.cursorState.cursorIndex
|
||||
&& self.cmd.tailable && Long.ZERO.equals(self.cursorState.cursorId)) {
|
||||
return handleCallback(callback, MongoError.create({
|
||||
message: 'No more documents in tailed cursor'
|
||||
, tailable: self.cmd.tailable
|
||||
, awaitData: self.cmd.awaitData
|
||||
}));
|
||||
} else if(self.cursorState.documents.length == self.cursorState.cursorIndex
|
||||
&& Long.ZERO.equals(self.cursorState.cursorId)) {
|
||||
setCursorDeadAndNotified(self, callback);
|
||||
} else {
|
||||
if(self.cursorState.limit > 0 && self.cursorState.currentLimit >= self.cursorState.limit) {
|
||||
// Ensure we kill the cursor on the server
|
||||
self.kill();
|
||||
// Set cursor in dead and notified state
|
||||
return setCursorDeadAndNotified(self, callback);
|
||||
}
|
||||
|
||||
// Increment the current cursor limit
|
||||
self.cursorState.currentLimit += 1;
|
||||
|
||||
// Get the document
|
||||
var doc = self.cursorState.documents[self.cursorState.cursorIndex++];
|
||||
|
||||
// Doc overflow
|
||||
if(!doc || doc.$err) {
|
||||
// Ensure we kill the cursor on the server
|
||||
self.kill();
|
||||
// Set cursor in dead and notified state
|
||||
return setCursorDeadAndNotified(self, function() {
|
||||
handleCallback(callback, new MongoError(doc ? doc.$err : undefined));
|
||||
});
|
||||
}
|
||||
|
||||
// Transform the doc with passed in transformation method if provided
|
||||
if(self.cursorState.transforms && typeof self.cursorState.transforms.doc == 'function') {
|
||||
doc = self.cursorState.transforms.doc(doc);
|
||||
}
|
||||
|
||||
// Return the document
|
||||
handleCallback(callback, null, doc);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the next document from the cursor
|
||||
* @method
|
||||
* @param {resultCallback} callback A callback function
|
||||
*/
|
||||
Cursor.prototype.next = function(callback) {
|
||||
nextFunction(this, callback);
|
||||
}
|
||||
|
||||
module.exports = Cursor;
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Creates a new MongoError
|
||||
* @class
|
||||
* @augments Error
|
||||
* @param {string} message The error message
|
||||
* @return {MongoError} A MongoError instance
|
||||
*/
|
||||
function MongoError(message) {
|
||||
this.name = 'MongoError';
|
||||
this.message = message;
|
||||
Error.captureStackTrace(this, MongoError);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new MongoError object
|
||||
* @method
|
||||
* @param {object} options The error options
|
||||
* @return {MongoError} A MongoError instance
|
||||
*/
|
||||
MongoError.create = function(options) {
|
||||
var err = null;
|
||||
|
||||
if(options instanceof Error) {
|
||||
err = new MongoError(options.message);
|
||||
err.stack = options.stack;
|
||||
} else if(typeof options == 'string') {
|
||||
err = new MongoError(options);
|
||||
} else {
|
||||
err = new MongoError(options.message || options.errmsg || options.$err || "n/a");
|
||||
// Other options
|
||||
for(var name in options) {
|
||||
err[name] = options[name];
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
// Extend JavaScript error
|
||||
MongoError.prototype = new Error;
|
||||
|
||||
module.exports = MongoError;
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
var fs = require('fs');
|
||||
|
||||
/* Note: because this plugin uses process.on('uncaughtException'), only one
|
||||
* of these can exist at any given time. This plugin and anything else that
|
||||
* uses process.on('uncaughtException') will conflict. */
|
||||
exports.attachToRunner = function(runner, outputFile) {
|
||||
var smokeOutput = { results : [] };
|
||||
var runningTests = {};
|
||||
|
||||
var integraPlugin = {
|
||||
beforeTest: function(test, callback) {
|
||||
test.startTime = Date.now();
|
||||
runningTests[test.name] = test;
|
||||
callback();
|
||||
},
|
||||
afterTest: function(test, callback) {
|
||||
smokeOutput.results.push({
|
||||
status: test.status,
|
||||
start: test.startTime,
|
||||
end: Date.now(),
|
||||
test_file: test.name,
|
||||
exit_code: 0,
|
||||
url: ""
|
||||
});
|
||||
delete runningTests[test.name];
|
||||
callback();
|
||||
},
|
||||
beforeExit: function(obj, callback) {
|
||||
fs.writeFile(outputFile, JSON.stringify(smokeOutput), function() {
|
||||
callback();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// In case of exception, make sure we write file
|
||||
process.on('uncaughtException', function(err) {
|
||||
// Mark all currently running tests as failed
|
||||
for (var testName in runningTests) {
|
||||
smokeOutput.results.push({
|
||||
status: "fail",
|
||||
start: runningTests[testName].startTime,
|
||||
end: Date.now(),
|
||||
test_file: testName,
|
||||
exit_code: 0,
|
||||
url: ""
|
||||
});
|
||||
}
|
||||
|
||||
// write file
|
||||
fs.writeFileSync(outputFile, JSON.stringify(smokeOutput));
|
||||
|
||||
// Standard NodeJS uncaught exception handler
|
||||
console.error(err.stack);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
runner.plugin(integraPlugin);
|
||||
return integraPlugin;
|
||||
};
|
||||
+1307
File diff suppressed because it is too large
Load Diff
+118
@@ -0,0 +1,118 @@
|
||||
"use strict";
|
||||
|
||||
var needSlaveOk = ['primaryPreferred', 'secondary', 'secondaryPreferred', 'nearest'];
|
||||
|
||||
/**
|
||||
* @fileOverview The **ReadPreference** class is a class that represents a MongoDB ReadPreference and is
|
||||
* used to construct connections.
|
||||
*
|
||||
* @example
|
||||
* var ReplSet = require('mongodb-core').ReplSet
|
||||
* , ReadPreference = require('mongodb-core').ReadPreference
|
||||
* , assert = require('assert');
|
||||
*
|
||||
* var server = new ReplSet([{host: 'localhost', port: 30000}], {setName: 'rs'});
|
||||
* // Wait for the connection event
|
||||
* server.on('connect', function(server) {
|
||||
* var cursor = server.cursor('db.test'
|
||||
* , {find: 'db.test', query: {}}
|
||||
* , {readPreference: new ReadPreference('secondary')});
|
||||
* cursor.next(function(err, doc) {
|
||||
* server.destroy();
|
||||
* });
|
||||
* });
|
||||
*
|
||||
* // Start connecting
|
||||
* server.connect();
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates a new Pool instance
|
||||
* @class
|
||||
* @param {string} preference A string describing the preference (primary|primaryPreferred|secondary|secondaryPreferred|nearest)
|
||||
* @param {array} tags The tags object
|
||||
* @param {object} [options] Additional read preference options
|
||||
* @param {number} [options.maxStalenessSeconds] Max Secondary Read Stalleness in Seconds, Minimum value is 90 seconds.
|
||||
* @property {string} preference The preference string (primary|primaryPreferred|secondary|secondaryPreferred|nearest)
|
||||
* @property {array} tags The tags object
|
||||
* @property {object} options Additional read preference options
|
||||
* @property {number} maxStalenessSeconds MaxStalenessSeconds value for the read preference
|
||||
* @return {ReadPreference}
|
||||
*/
|
||||
var ReadPreference = function(preference, tags, options) {
|
||||
this.preference = preference;
|
||||
this.tags = tags;
|
||||
this.options = options;
|
||||
|
||||
// Add the maxStalenessSeconds value to the read Preference
|
||||
if(this.options && this.options.maxStalenessSeconds != null) {
|
||||
this.options = options;
|
||||
this.maxStalenessSeconds = this.options.maxStalenessSeconds >= 0
|
||||
? this.options.maxStalenessSeconds : null;
|
||||
} else if(tags && typeof tags == 'object') {
|
||||
this.options = tags, tags = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This needs slaveOk bit set
|
||||
* @method
|
||||
* @return {boolean}
|
||||
*/
|
||||
ReadPreference.prototype.slaveOk = function() {
|
||||
return needSlaveOk.indexOf(this.preference) != -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Are the two read preference equal
|
||||
* @method
|
||||
* @return {boolean}
|
||||
*/
|
||||
ReadPreference.prototype.equals = function(readPreference) {
|
||||
return readPreference.preference == this.preference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return JSON representation
|
||||
* @method
|
||||
* @return {Object}
|
||||
*/
|
||||
ReadPreference.prototype.toJSON = function() {
|
||||
var readPreference = {mode: this.preference};
|
||||
if(Array.isArray(this.tags)) readPreference.tags = this.tags;
|
||||
if(this.maxStalenessSeconds) readPreference.maxStalenessSeconds = this.maxStalenessSeconds;
|
||||
return readPreference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary read preference
|
||||
* @method
|
||||
* @return {ReadPreference}
|
||||
*/
|
||||
ReadPreference.primary = new ReadPreference('primary');
|
||||
/**
|
||||
* Primary Preferred read preference
|
||||
* @method
|
||||
* @return {ReadPreference}
|
||||
*/
|
||||
ReadPreference.primaryPreferred = new ReadPreference('primaryPreferred');
|
||||
/**
|
||||
* Secondary read preference
|
||||
* @method
|
||||
* @return {ReadPreference}
|
||||
*/
|
||||
ReadPreference.secondary = new ReadPreference('secondary');
|
||||
/**
|
||||
* Secondary Preferred read preference
|
||||
* @method
|
||||
* @return {ReadPreference}
|
||||
*/
|
||||
ReadPreference.secondaryPreferred = new ReadPreference('secondaryPreferred');
|
||||
/**
|
||||
* Nearest read preference
|
||||
* @method
|
||||
* @return {ReadPreference}
|
||||
*/
|
||||
ReadPreference.nearest = new ReadPreference('nearest');
|
||||
|
||||
module.exports = ReadPreference;
|
||||
+1473
File diff suppressed because it is too large
Load Diff
+1001
File diff suppressed because it is too large
Load Diff
+880
@@ -0,0 +1,880 @@
|
||||
"use strict"
|
||||
|
||||
var inherits = require('util').inherits,
|
||||
f = require('util').format,
|
||||
EventEmitter = require('events').EventEmitter,
|
||||
ReadPreference = require('./read_preference'),
|
||||
Logger = require('../connection/logger'),
|
||||
debugOptions = require('../connection/utils').debugOptions,
|
||||
retrieveBSON = require('../connection/utils').retrieveBSON,
|
||||
Pool = require('../connection/pool'),
|
||||
Query = require('../connection/commands').Query,
|
||||
MongoError = require('../error'),
|
||||
PreTwoSixWireProtocolSupport = require('../wireprotocol/2_4_support'),
|
||||
TwoSixWireProtocolSupport = require('../wireprotocol/2_6_support'),
|
||||
ThreeTwoWireProtocolSupport = require('../wireprotocol/3_2_support'),
|
||||
BasicCursor = require('../cursor'),
|
||||
sdam = require('./shared'),
|
||||
assign = require('../utils').assign,
|
||||
createClientInfo = require('./shared').createClientInfo;
|
||||
|
||||
// Used for filtering out fields for loggin
|
||||
var debugFields = ['reconnect', 'reconnectTries', 'reconnectInterval', 'emitError', 'cursorFactory', 'host'
|
||||
, 'port', 'size', 'keepAlive', 'keepAliveInitialDelay', 'noDelay', 'connectionTimeout', 'checkServerIdentity'
|
||||
, 'socketTimeout', 'singleBufferSerializtion', 'ssl', 'ca', 'crl', 'cert', 'key', 'rejectUnauthorized', 'promoteLongs', 'promoteValues'
|
||||
, 'promoteBuffers', 'servername'];
|
||||
|
||||
// Server instance id
|
||||
var id = 0;
|
||||
var serverAccounting = false;
|
||||
var servers = {};
|
||||
var BSON = retrieveBSON();
|
||||
|
||||
/**
|
||||
* Creates a new Server instance
|
||||
* @class
|
||||
* @param {boolean} [options.reconnect=true] Server will attempt to reconnect on loss of connection
|
||||
* @param {number} [options.reconnectTries=30] Server attempt to reconnect #times
|
||||
* @param {number} [options.reconnectInterval=1000] Server will wait # milliseconds between retries
|
||||
* @param {number} [options.monitoring=true] Enable the server state monitoring (calling ismaster at monitoringInterval)
|
||||
* @param {number} [options.monitoringInterval=5000] The interval of calling ismaster when monitoring is enabled.
|
||||
* @param {Cursor} [options.cursorFactory=Cursor] The cursor factory class used for all query cursors
|
||||
* @param {string} options.host The server host
|
||||
* @param {number} options.port The server port
|
||||
* @param {number} [options.size=5] Server connection pool size
|
||||
* @param {boolean} [options.keepAlive=true] TCP Connection keep alive enabled
|
||||
* @param {number} [options.keepAliveInitialDelay=300000] Initial delay before TCP keep alive enabled
|
||||
* @param {boolean} [options.noDelay=true] TCP Connection no delay
|
||||
* @param {number} [options.connectionTimeout=30000] TCP Connection timeout setting
|
||||
* @param {number} [options.socketTimeout=360000] TCP Socket timeout setting
|
||||
* @param {boolean} [options.ssl=false] Use SSL for connection
|
||||
* @param {boolean|function} [options.checkServerIdentity=true] Ensure we check server identify during SSL, set to false to disable checking. Only works for Node 0.12.x or higher. You can pass in a boolean or your own checkServerIdentity override function.
|
||||
* @param {Buffer} [options.ca] SSL Certificate store binary buffer
|
||||
* @param {Buffer} [options.crl] SSL Certificate revocation store binary buffer
|
||||
* @param {Buffer} [options.cert] SSL Certificate binary buffer
|
||||
* @param {Buffer} [options.key] SSL Key file binary buffer
|
||||
* @param {string} [options.passphrase] SSL Certificate pass phrase
|
||||
* @param {boolean} [options.rejectUnauthorized=true] Reject unauthorized server certificates
|
||||
* @param {string} [options.servername=null] String containing the server name requested via TLS SNI.
|
||||
* @param {boolean} [options.promoteLongs=true] Convert Long values from the db into Numbers if they fit into 53 bits
|
||||
* @param {boolean} [options.promoteValues=true] Promotes BSON values to native types where possible, set to false to only receive wrapper types.
|
||||
* @param {boolean} [options.promoteBuffers=false] Promotes Binary BSON values to native Node Buffers.
|
||||
* @param {string} [options.appname=null] Application name, passed in on ismaster call and logged in mongod server logs. Maximum size 128 bytes.
|
||||
* @param {boolean} [options.domainsEnabled=false] Enable the wrapping of the callback in the current domain, disabled by default to avoid perf hit.
|
||||
* @return {Server} A cursor instance
|
||||
* @fires Server#connect
|
||||
* @fires Server#close
|
||||
* @fires Server#error
|
||||
* @fires Server#timeout
|
||||
* @fires Server#parseError
|
||||
* @fires Server#reconnect
|
||||
* @fires Server#reconnectFailed
|
||||
* @fires Server#serverHeartbeatStarted
|
||||
* @fires Server#serverHeartbeatSucceeded
|
||||
* @fires Server#serverHeartbeatFailed
|
||||
* @fires Server#topologyOpening
|
||||
* @fires Server#topologyClosed
|
||||
* @fires Server#topologyDescriptionChanged
|
||||
* @property {string} type the topology type.
|
||||
* @property {string} parserType the parser type used (c++ or js).
|
||||
*/
|
||||
var Server = function(options) {
|
||||
options = options || {};
|
||||
|
||||
// Add event listener
|
||||
EventEmitter.call(this);
|
||||
|
||||
// Server instance id
|
||||
this.id = id++;
|
||||
|
||||
// Internal state
|
||||
this.s = {
|
||||
// Options
|
||||
options: options,
|
||||
// Logger
|
||||
logger: Logger('Server', options),
|
||||
// Factory overrides
|
||||
Cursor: options.cursorFactory || BasicCursor,
|
||||
// BSON instance
|
||||
bson: options.bson || new BSON([BSON.Binary, BSON.Code, BSON.DBRef, BSON.Decimal128,
|
||||
BSON.Double, BSON.Int32, BSON.Long, BSON.Map, BSON.MaxKey, BSON.MinKey,
|
||||
BSON.ObjectId, BSON.BSONRegExp, BSON.Symbol, BSON.Timestamp]),
|
||||
// Pool
|
||||
pool: null,
|
||||
// Disconnect handler
|
||||
disconnectHandler: options.disconnectHandler,
|
||||
// Monitor thread (keeps the connection alive)
|
||||
monitoring: typeof options.monitoring == 'boolean' ? options.monitoring : true,
|
||||
// Is the server in a topology
|
||||
inTopology: typeof options.inTopology == 'boolean' ? options.inTopology : false,
|
||||
// Monitoring timeout
|
||||
monitoringInterval: typeof options.monitoringInterval == 'number'
|
||||
? options.monitoringInterval
|
||||
: 5000,
|
||||
// Topology id
|
||||
topologyId: -1
|
||||
}
|
||||
|
||||
// Curent ismaster
|
||||
this.ismaster = null;
|
||||
// Current ping time
|
||||
this.lastIsMasterMS = -1;
|
||||
// The monitoringProcessId
|
||||
this.monitoringProcessId = null;
|
||||
// Initial connection
|
||||
this.initalConnect = true;
|
||||
// Wire protocol handler, default to oldest known protocol handler
|
||||
// this gets changed when the first ismaster is called.
|
||||
this.wireProtocolHandler = new PreTwoSixWireProtocolSupport();
|
||||
// Default type
|
||||
this._type = 'server';
|
||||
// Set the client info
|
||||
this.clientInfo = createClientInfo(options);
|
||||
|
||||
// Max Stalleness values
|
||||
// last time we updated the ismaster state
|
||||
this.lastUpdateTime = 0;
|
||||
// Last write time
|
||||
this.lastWriteDate = 0;
|
||||
// Stalleness
|
||||
this.staleness = 0;
|
||||
}
|
||||
|
||||
inherits(Server, EventEmitter);
|
||||
|
||||
Object.defineProperty(Server.prototype, 'type', {
|
||||
enumerable:true, get: function() { return this._type; }
|
||||
});
|
||||
|
||||
Object.defineProperty(Server.prototype, 'parserType', {
|
||||
enumerable:true, get: function() {
|
||||
return BSON.native ? "c++" : "js";
|
||||
}
|
||||
});
|
||||
|
||||
Server.enableServerAccounting = function() {
|
||||
serverAccounting = true;
|
||||
servers = {};
|
||||
}
|
||||
|
||||
Server.disableServerAccounting = function() {
|
||||
serverAccounting = false;
|
||||
}
|
||||
|
||||
Server.servers = function() {
|
||||
return servers;
|
||||
}
|
||||
|
||||
Object.defineProperty(Server.prototype, 'name', {
|
||||
enumerable:true,
|
||||
get: function() { return this.s.options.host + ":" + this.s.options.port; }
|
||||
});
|
||||
|
||||
function configureWireProtocolHandler(self, ismaster) {
|
||||
// 3.2 wire protocol handler
|
||||
if(ismaster.maxWireVersion >= 4) {
|
||||
return new ThreeTwoWireProtocolSupport(new TwoSixWireProtocolSupport());
|
||||
}
|
||||
|
||||
// 2.6 wire protocol handler
|
||||
if(ismaster.maxWireVersion >= 2) {
|
||||
return new TwoSixWireProtocolSupport();
|
||||
}
|
||||
|
||||
// 2.4 or earlier wire protocol handler
|
||||
return new PreTwoSixWireProtocolSupport();
|
||||
}
|
||||
|
||||
function disconnectHandler(self, type, ns, cmd, options, callback) {
|
||||
// Topology is not connected, save the call in the provided store to be
|
||||
// Executed at some point when the handler deems it's reconnected
|
||||
if(!self.s.pool.isConnected() && self.s.options.reconnect && self.s.disconnectHandler != null && !options.monitoring) {
|
||||
self.s.disconnectHandler.add(type, ns, cmd, options, callback);
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we have no connection error
|
||||
if(!self.s.pool.isConnected()) {
|
||||
callback(MongoError.create(f("no connection available to server %s", self.name)));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function monitoringProcess(self) {
|
||||
return function() {
|
||||
// Pool was destroyed do not continue process
|
||||
if(self.s.pool.isDestroyed()) return;
|
||||
// Emit monitoring Process event
|
||||
self.emit('monitoring', self);
|
||||
// Perform ismaster call
|
||||
// Query options
|
||||
var queryOptions = { numberToSkip: 0, numberToReturn: -1, checkKeys: false, slaveOk: true };
|
||||
// Create a query instance
|
||||
var query = new Query(self.s.bson, 'admin.$cmd', {ismaster:true}, queryOptions);
|
||||
// Get start time
|
||||
var start = new Date().getTime();
|
||||
|
||||
// Execute the ismaster query
|
||||
self.s.pool.write(query, {
|
||||
socketTimeout: (typeof self.s.options.connectionTimeout !== 'number') ? 2000 : self.s.options.connectionTimeout,
|
||||
monitoring: true,
|
||||
}, function(err, result) {
|
||||
// Set initial lastIsMasterMS
|
||||
self.lastIsMasterMS = new Date().getTime() - start;
|
||||
if(self.s.pool.isDestroyed()) return;
|
||||
// Update the ismaster view if we have a result
|
||||
if(result) {
|
||||
self.ismaster = result.result;
|
||||
}
|
||||
// Re-schedule the monitoring process
|
||||
self.monitoringProcessId = setTimeout(monitoringProcess(self), self.s.monitoringInterval);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var eventHandler = function(self, event) {
|
||||
return function(err) {
|
||||
// Log information of received information if in info mode
|
||||
if(self.s.logger.isInfo()) {
|
||||
var object = err instanceof MongoError ? JSON.stringify(err) : {}
|
||||
self.s.logger.info(f('server %s fired event %s out with message %s'
|
||||
, self.name, event, object));
|
||||
}
|
||||
|
||||
// Handle connect event
|
||||
if(event == 'connect') {
|
||||
// Issue an ismaster command at connect
|
||||
// Query options
|
||||
var queryOptions = { numberToSkip: 0, numberToReturn: -1, checkKeys: false, slaveOk: true };
|
||||
// Create a query instance
|
||||
var query = new Query(self.s.bson, 'admin.$cmd', {ismaster:true, client: self.clientInfo}, queryOptions);
|
||||
// Get start time
|
||||
var start = new Date().getTime();
|
||||
// Execute the ismaster query
|
||||
self.s.pool.write(query, {
|
||||
socketTimeout: self.s.options.connectionTimeout || 2000,
|
||||
}, function(err, result) {
|
||||
// Set initial lastIsMasterMS
|
||||
self.lastIsMasterMS = new Date().getTime() - start;
|
||||
if(err) {
|
||||
self.destroy();
|
||||
if(self.listeners('error').length > 0) self.emit('error', err);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure no error emitted after initial connect when reconnecting
|
||||
self.initalConnect = false;
|
||||
// Save the ismaster
|
||||
self.ismaster = result.result;
|
||||
|
||||
// It's a proxy change the type so
|
||||
// the wireprotocol will send $readPreference
|
||||
if(self.ismaster.msg == 'isdbgrid') {
|
||||
self._type = 'mongos';
|
||||
}
|
||||
// Add the correct wire protocol handler
|
||||
self.wireProtocolHandler = configureWireProtocolHandler(self, self.ismaster);
|
||||
// Have we defined self monitoring
|
||||
if(self.s.monitoring) {
|
||||
self.monitoringProcessId = setTimeout(monitoringProcess(self), self.s.monitoringInterval);
|
||||
}
|
||||
|
||||
// Emit server description changed if something listening
|
||||
sdam.emitServerDescriptionChanged(self, {
|
||||
address: self.name, arbiters: [], hosts: [], passives: [], type: sdam.getTopologyType(self)
|
||||
});
|
||||
|
||||
if(!self.s.inTopology) {
|
||||
// Emit topology description changed if something listening
|
||||
sdam.emitTopologyDescriptionChanged(self, {
|
||||
topologyType: 'Single', servers: [{address: self.name, arbiters: [], hosts: [], passives: [], type: sdam.getTopologyType(self)}]
|
||||
});
|
||||
}
|
||||
|
||||
// Log the ismaster if available
|
||||
if(self.s.logger.isInfo()) {
|
||||
self.s.logger.info(f('server %s connected with ismaster [%s]', self.name, JSON.stringify(self.ismaster)));
|
||||
}
|
||||
|
||||
// Emit connect
|
||||
self.emit('connect', self);
|
||||
});
|
||||
} else if(event == 'error' || event == 'parseError'
|
||||
|| event == 'close' || event == 'timeout' || event == 'reconnect'
|
||||
|| event == 'attemptReconnect' || 'reconnectFailed') {
|
||||
// Remove server instance from accounting
|
||||
if(serverAccounting && ['close', 'timeout', 'error', 'parseError', 'reconnectFailed'].indexOf(event) != -1) {
|
||||
// Emit toplogy opening event if not in topology
|
||||
if(!self.s.inTopology) {
|
||||
self.emit('topologyOpening', { topologyId: self.id });
|
||||
}
|
||||
|
||||
delete servers[self.id];
|
||||
}
|
||||
|
||||
if (event === 'close') {
|
||||
// Closing emits a server description changed event going to unknown.
|
||||
sdam.emitServerDescriptionChanged(self, {
|
||||
address: self.name, arbiters: [], hosts: [], passives: [], type: 'Unknown'
|
||||
});
|
||||
}
|
||||
|
||||
// Reconnect failed return error
|
||||
if(event == 'reconnectFailed') {
|
||||
self.emit('reconnectFailed', err);
|
||||
// Emit error if any listeners
|
||||
if(self.listeners('error').length > 0) {
|
||||
self.emit('error', err);
|
||||
}
|
||||
// Terminate
|
||||
return;
|
||||
}
|
||||
|
||||
// On first connect fail
|
||||
if(self.s.pool.state == 'disconnected' && self.initalConnect && ['close', 'timeout', 'error', 'parseError'].indexOf(event) != -1) {
|
||||
self.initalConnect = false;
|
||||
return self.emit('error', new MongoError(f('failed to connect to server [%s] on first connect [%s]', self.name, err)));
|
||||
}
|
||||
|
||||
// Reconnect event, emit the server
|
||||
if(event == 'reconnect') {
|
||||
// Reconnecting emits a server description changed event going from unknown to the
|
||||
// current server type.
|
||||
sdam.emitServerDescriptionChanged(self, {
|
||||
address: self.name, arbiters: [], hosts: [], passives: [], type: sdam.getTopologyType(self)
|
||||
});
|
||||
return self.emit(event, self);
|
||||
}
|
||||
|
||||
// Emit the event
|
||||
self.emit(event, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate server connect
|
||||
* @method
|
||||
* @param {array} [options.auth=null] Array of auth options to apply on connect
|
||||
*/
|
||||
Server.prototype.connect = function(options) {
|
||||
var self = this;
|
||||
options = options || {};
|
||||
|
||||
// Set the connections
|
||||
if(serverAccounting) servers[this.id] = this;
|
||||
|
||||
// Do not allow connect to be called on anything that's not disconnected
|
||||
if(self.s.pool && !self.s.pool.isDisconnected() && !self.s.pool.isDestroyed()) {
|
||||
throw MongoError.create(f('server instance in invalid state %s', self.s.pool.state));
|
||||
}
|
||||
|
||||
// Create a pool
|
||||
self.s.pool = new Pool(assign(self.s.options, options, {bson: this.s.bson}));
|
||||
|
||||
// Set up listeners
|
||||
self.s.pool.on('close', eventHandler(self, 'close'));
|
||||
self.s.pool.on('error', eventHandler(self, 'error'));
|
||||
self.s.pool.on('timeout', eventHandler(self, 'timeout'));
|
||||
self.s.pool.on('parseError', eventHandler(self, 'parseError'));
|
||||
self.s.pool.on('connect', eventHandler(self, 'connect'));
|
||||
self.s.pool.on('reconnect', eventHandler(self, 'reconnect'));
|
||||
self.s.pool.on('reconnectFailed', eventHandler(self, 'reconnectFailed'));
|
||||
|
||||
// Emit toplogy opening event if not in topology
|
||||
if(!self.s.inTopology) {
|
||||
this.emit('topologyOpening', { topologyId: self.id });
|
||||
}
|
||||
|
||||
// Emit opening server event
|
||||
self.emit('serverOpening', {
|
||||
topologyId: self.s.topologyId != -1 ? self.s.topologyId : self.id,
|
||||
address: self.name
|
||||
});
|
||||
|
||||
// Connect with optional auth settings
|
||||
if(options.auth) {
|
||||
self.s.pool.connect.apply(self.s.pool, options.auth);
|
||||
} else {
|
||||
self.s.pool.connect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the server description
|
||||
* @method
|
||||
* @return {object}
|
||||
*/
|
||||
Server.prototype.getDescription = function() {
|
||||
var ismaster = this.ismaster || {};
|
||||
var description = {
|
||||
type: sdam.getTopologyType(this),
|
||||
address: this.name,
|
||||
};
|
||||
|
||||
// Add fields if available
|
||||
if(ismaster.hosts) description.hosts = ismaster.hosts;
|
||||
if(ismaster.arbiters) description.arbiters = ismaster.arbiters;
|
||||
if(ismaster.passives) description.passives = ismaster.passives;
|
||||
if(ismaster.setName) description.setName = ismaster.setName;
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last known ismaster document for this server
|
||||
* @method
|
||||
* @return {object}
|
||||
*/
|
||||
Server.prototype.lastIsMaster = function() {
|
||||
return this.ismaster;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unref all connections belong to this server
|
||||
* @method
|
||||
*/
|
||||
Server.prototype.unref = function() {
|
||||
this.s.pool.unref();
|
||||
}
|
||||
|
||||
/**
|
||||
* Figure out if the server is connected
|
||||
* @method
|
||||
* @return {boolean}
|
||||
*/
|
||||
Server.prototype.isConnected = function() {
|
||||
if(!this.s.pool) return false;
|
||||
return this.s.pool.isConnected();
|
||||
}
|
||||
|
||||
/**
|
||||
* Figure out if the server instance was destroyed by calling destroy
|
||||
* @method
|
||||
* @return {boolean}
|
||||
*/
|
||||
Server.prototype.isDestroyed = function() {
|
||||
if(!this.s.pool) return false;
|
||||
return this.s.pool.isDestroyed();
|
||||
}
|
||||
|
||||
function basicWriteValidations(self) {
|
||||
if(!self.s.pool) return MongoError.create('server instance is not connected');
|
||||
if(self.s.pool.isDestroyed()) return MongoError.create('server instance pool was destroyed');
|
||||
}
|
||||
|
||||
function basicReadValidations(self, options) {
|
||||
basicWriteValidations(self, options);
|
||||
|
||||
if(options.readPreference && !(options.readPreference instanceof ReadPreference)) {
|
||||
throw new Error("readPreference must be an instance of ReadPreference");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a command
|
||||
* @method
|
||||
* @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
|
||||
* @param {object} cmd The command hash
|
||||
* @param {ReadPreference} [options.readPreference] Specify read preference if command supports it
|
||||
* @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized.
|
||||
* @param {Boolean} [options.checkKeys=false] Specify if the bson parser should validate keys.
|
||||
* @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
|
||||
* @param {Boolean} [options.fullResult=false] Return the full envelope instead of just the result document.
|
||||
* @param {opResultCallback} callback A callback function
|
||||
*/
|
||||
Server.prototype.command = function(ns, cmd, options, callback) {
|
||||
var self = this;
|
||||
if(typeof options == 'function') callback = options, options = {}, options = options || {};
|
||||
var result = basicReadValidations(self, options);
|
||||
if(result) return callback(result);
|
||||
|
||||
// Clone the options
|
||||
options = assign({}, options, { wireProtocolCommand: false });
|
||||
|
||||
// Debug log
|
||||
if(self.s.logger.isDebug()) self.s.logger.debug(f('executing command [%s] against %s', JSON.stringify({
|
||||
ns: ns, cmd: cmd, options: debugOptions(debugFields, options)
|
||||
}), self.name));
|
||||
|
||||
// If we are not connected or have a disconnectHandler specified
|
||||
if(disconnectHandler(self, 'command', ns, cmd, options, callback)) return;
|
||||
|
||||
// Check if we have collation support
|
||||
if(this.ismaster && this.ismaster.maxWireVersion < 5 && cmd.collation) {
|
||||
return callback(new MongoError(f('server %s does not support collation', this.name)));
|
||||
}
|
||||
|
||||
// Query options
|
||||
var queryOptions = {
|
||||
numberToSkip: 0,
|
||||
numberToReturn: -1,
|
||||
checkKeys: typeof options.checkKeys == 'boolean' ? options.checkKeys: false,
|
||||
serializeFunctions: typeof options.serializeFunctions == 'boolean' ? options.serializeFunctions : false,
|
||||
ignoreUndefined: typeof options.ignoreUndefined == 'boolean' ? options.ignoreUndefined : false
|
||||
};
|
||||
|
||||
// Are we executing against a specific topology
|
||||
var topology = options.topology || {};
|
||||
// Create the query object
|
||||
var query = self.wireProtocolHandler.command(self.s.bson, ns, cmd, {}, topology, options);
|
||||
// Set slave OK of the query
|
||||
query.slaveOk = options.readPreference ? options.readPreference.slaveOk() : false;
|
||||
|
||||
// Write options
|
||||
var writeOptions = {
|
||||
raw: typeof options.raw == 'boolean' ? options.raw : false,
|
||||
promoteLongs: typeof options.promoteLongs == 'boolean' ? options.promoteLongs : true,
|
||||
promoteValues: typeof options.promoteValues == 'boolean' ? options.promoteValues : true,
|
||||
promoteBuffers: typeof options.promoteBuffers == 'boolean' ? options.promoteBuffers : false,
|
||||
command: true,
|
||||
monitoring: typeof options.monitoring == 'boolean' ? options.monitoring : false,
|
||||
fullResult: typeof options.fullResult == 'boolean' ? options.fullResult : false,
|
||||
requestId: query.requestId,
|
||||
socketTimeout: typeof options.socketTimeout == 'number' ? options.socketTimeout : null,
|
||||
};
|
||||
|
||||
// Write the operation to the pool
|
||||
self.s.pool.write(query, writeOptions, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert one or more documents
|
||||
* @method
|
||||
* @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
|
||||
* @param {array} ops An array of documents to insert
|
||||
* @param {boolean} [options.ordered=true] Execute in order or out of order
|
||||
* @param {object} [options.writeConcern={}] Write concern for the operation
|
||||
* @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized.
|
||||
* @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
|
||||
* @param {opResultCallback} callback A callback function
|
||||
*/
|
||||
Server.prototype.insert = function(ns, ops, options, callback) {
|
||||
var self = this;
|
||||
if(typeof options == 'function') callback = options, options = {}, options = options || {};
|
||||
var result = basicWriteValidations(self, options);
|
||||
if(result) return callback(result);
|
||||
|
||||
// If we are not connected or have a disconnectHandler specified
|
||||
if(disconnectHandler(self, 'insert', ns, ops, options, callback)) return;
|
||||
|
||||
// Setup the docs as an array
|
||||
ops = Array.isArray(ops) ? ops : [ops];
|
||||
|
||||
// Execute write
|
||||
return self.wireProtocolHandler.insert(self.s.pool, self.ismaster, ns, self.s.bson, ops, options, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform one or more update operations
|
||||
* @method
|
||||
* @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
|
||||
* @param {array} ops An array of updates
|
||||
* @param {boolean} [options.ordered=true] Execute in order or out of order
|
||||
* @param {object} [options.writeConcern={}] Write concern for the operation
|
||||
* @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized.
|
||||
* @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
|
||||
* @param {opResultCallback} callback A callback function
|
||||
*/
|
||||
Server.prototype.update = function(ns, ops, options, callback) {
|
||||
var self = this;
|
||||
if(typeof options == 'function') callback = options, options = {}, options = options || {};
|
||||
var result = basicWriteValidations(self, options);
|
||||
if(result) return callback(result);
|
||||
|
||||
// If we are not connected or have a disconnectHandler specified
|
||||
if(disconnectHandler(self, 'update', ns, ops, options, callback)) return;
|
||||
|
||||
// Check if we have collation support
|
||||
if(this.ismaster && this.ismaster.maxWireVersion < 5 && options.collation) {
|
||||
return callback(new MongoError(f('server %s does not support collation', this.name)));
|
||||
}
|
||||
|
||||
// Setup the docs as an array
|
||||
ops = Array.isArray(ops) ? ops : [ops];
|
||||
// Execute write
|
||||
return self.wireProtocolHandler.update(self.s.pool, self.ismaster, ns, self.s.bson, ops, options, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform one or more remove operations
|
||||
* @method
|
||||
* @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
|
||||
* @param {array} ops An array of removes
|
||||
* @param {boolean} [options.ordered=true] Execute in order or out of order
|
||||
* @param {object} [options.writeConcern={}] Write concern for the operation
|
||||
* @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized.
|
||||
* @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
|
||||
* @param {opResultCallback} callback A callback function
|
||||
*/
|
||||
Server.prototype.remove = function(ns, ops, options, callback) {
|
||||
var self = this;
|
||||
if(typeof options == 'function') callback = options, options = {}, options = options || {};
|
||||
var result = basicWriteValidations(self, options);
|
||||
if(result) return callback(result);
|
||||
|
||||
// If we are not connected or have a disconnectHandler specified
|
||||
if(disconnectHandler(self, 'remove', ns, ops, options, callback)) return;
|
||||
|
||||
// Check if we have collation support
|
||||
if(this.ismaster && this.ismaster.maxWireVersion < 5 && options.collation) {
|
||||
return callback(new MongoError(f('server %s does not support collation', this.name)));
|
||||
}
|
||||
|
||||
// Setup the docs as an array
|
||||
ops = Array.isArray(ops) ? ops : [ops];
|
||||
// Execute write
|
||||
return self.wireProtocolHandler.remove(self.s.pool, self.ismaster, ns, self.s.bson, ops, options, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new cursor
|
||||
* @method
|
||||
* @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
|
||||
* @param {{object}|{Long}} cmd Can be either a command returning a cursor or a cursorId
|
||||
* @param {object} [options.batchSize=0] Batchsize for the operation
|
||||
* @param {array} [options.documents=[]] Initial documents list for cursor
|
||||
* @param {ReadPreference} [options.readPreference] Specify read preference if command supports it
|
||||
* @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized.
|
||||
* @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
|
||||
* @param {opResultCallback} callback A callback function
|
||||
*/
|
||||
Server.prototype.cursor = function(ns, cmd, cursorOptions) {
|
||||
var s = this.s;
|
||||
cursorOptions = cursorOptions || {};
|
||||
// Set up final cursor type
|
||||
var FinalCursor = cursorOptions.cursorFactory || s.Cursor;
|
||||
// Return the cursor
|
||||
return new FinalCursor(s.bson, ns, cmd, cursorOptions, this, s.options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout from a database
|
||||
* @method
|
||||
* @param {string} db The db we are logging out from
|
||||
* @param {authResultCallback} callback A callback function
|
||||
*/
|
||||
Server.prototype.logout = function(dbName, callback) {
|
||||
this.s.pool.logout(dbName, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate using a specified mechanism
|
||||
* @method
|
||||
* @param {string} mechanism The Auth mechanism we are invoking
|
||||
* @param {string} db The db we are invoking the mechanism against
|
||||
* @param {...object} param Parameters for the specific mechanism
|
||||
* @param {authResultCallback} callback A callback function
|
||||
*/
|
||||
Server.prototype.auth = function(mechanism, db) {
|
||||
var self = this;
|
||||
|
||||
// If we have the default mechanism we pick mechanism based on the wire
|
||||
// protocol max version. If it's >= 3 then scram-sha1 otherwise mongodb-cr
|
||||
if(mechanism == 'default' && self.ismaster && self.ismaster.maxWireVersion >= 3) {
|
||||
mechanism = 'scram-sha-1';
|
||||
} else if(mechanism == 'default') {
|
||||
mechanism = 'mongocr';
|
||||
}
|
||||
|
||||
// Slice all the arguments off
|
||||
var args = Array.prototype.slice.call(arguments, 0);
|
||||
// Set the mechanism
|
||||
args[0] = mechanism;
|
||||
// Get the callback
|
||||
var callback = args[args.length - 1];
|
||||
|
||||
// If we are not connected or have a disconnectHandler specified
|
||||
if(disconnectHandler(self, 'auth', db, args, {}, callback)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not authenticate if we are an arbiter
|
||||
if(this.lastIsMaster() && this.lastIsMaster().arbiterOnly) {
|
||||
return callback(null, true);
|
||||
}
|
||||
|
||||
// Apply the arguments to the pool
|
||||
self.s.pool.auth.apply(self.s.pool, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two server instances
|
||||
* @method
|
||||
* @param {Server} server Server to compare equality against
|
||||
* @return {boolean}
|
||||
*/
|
||||
Server.prototype.equals = function(server) {
|
||||
if(typeof server == 'string') return this.name.toLowerCase() == server.toLowerCase();
|
||||
if(server.name) return this.name.toLowerCase() == server.name.toLowerCase();
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* All raw connections
|
||||
* @method
|
||||
* @return {Connection[]}
|
||||
*/
|
||||
Server.prototype.connections = function() {
|
||||
return this.s.pool.allConnections();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get server
|
||||
* @method
|
||||
* @return {Server}
|
||||
*/
|
||||
Server.prototype.getServer = function() {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connection
|
||||
* @method
|
||||
* @return {Connection}
|
||||
*/
|
||||
Server.prototype.getConnection = function() {
|
||||
return this.s.pool.get();
|
||||
}
|
||||
|
||||
var listeners = ['close', 'error', 'timeout', 'parseError', 'connect'];
|
||||
|
||||
/**
|
||||
* Destroy the server connection
|
||||
* @method
|
||||
* @param {boolean} [options.emitClose=false] Emit close event on destroy
|
||||
* @param {boolean} [options.emitDestroy=false] Emit destroy event on destroy
|
||||
* @param {boolean} [options.force=false] Force destroy the pool
|
||||
*/
|
||||
Server.prototype.destroy = function(options) {
|
||||
options = options || {};
|
||||
var self = this;
|
||||
|
||||
// Set the connections
|
||||
if(serverAccounting) delete servers[this.id];
|
||||
|
||||
// Destroy the monitoring process if any
|
||||
if(this.monitoringProcessId) {
|
||||
clearTimeout(this.monitoringProcessId);
|
||||
}
|
||||
|
||||
// No pool, return
|
||||
if(!self.s.pool) return;
|
||||
|
||||
// Emit close event
|
||||
if(options.emitClose) {
|
||||
self.emit('close', self);
|
||||
}
|
||||
|
||||
// Emit destroy event
|
||||
if(options.emitDestroy) {
|
||||
self.emit('destroy', self);
|
||||
}
|
||||
|
||||
// Remove all listeners
|
||||
listeners.forEach(function(event) {
|
||||
self.s.pool.removeAllListeners(event);
|
||||
});
|
||||
|
||||
// Emit opening server event
|
||||
if(self.listeners('serverClosed').length > 0) self.emit('serverClosed', {
|
||||
topologyId: self.s.topologyId != -1 ? self.s.topologyId : self.id, address: self.name
|
||||
});
|
||||
|
||||
// Emit toplogy opening event if not in topology
|
||||
if(self.listeners('topologyClosed').length > 0 && !self.s.inTopology) {
|
||||
self.emit('topologyClosed', { topologyId: self.id });
|
||||
}
|
||||
|
||||
if(self.s.logger.isDebug()) {
|
||||
self.s.logger.debug(f('destroy called on server %s', self.name));
|
||||
}
|
||||
|
||||
// Destroy the pool
|
||||
this.s.pool.destroy(options.force);
|
||||
}
|
||||
|
||||
/**
|
||||
* A server connect event, used to verify that the connection is up and running
|
||||
*
|
||||
* @event Server#connect
|
||||
* @type {Server}
|
||||
*/
|
||||
|
||||
/**
|
||||
* A server reconnect event, used to verify that the server topology has reconnected
|
||||
*
|
||||
* @event Server#reconnect
|
||||
* @type {Server}
|
||||
*/
|
||||
|
||||
/**
|
||||
* A server opening SDAM monitoring event
|
||||
*
|
||||
* @event Server#serverOpening
|
||||
* @type {object}
|
||||
*/
|
||||
|
||||
/**
|
||||
* A server closed SDAM monitoring event
|
||||
*
|
||||
* @event Server#serverClosed
|
||||
* @type {object}
|
||||
*/
|
||||
|
||||
/**
|
||||
* A server description SDAM change monitoring event
|
||||
*
|
||||
* @event Server#serverDescriptionChanged
|
||||
* @type {object}
|
||||
*/
|
||||
|
||||
/**
|
||||
* A topology open SDAM event
|
||||
*
|
||||
* @event Server#topologyOpening
|
||||
* @type {object}
|
||||
*/
|
||||
|
||||
/**
|
||||
* A topology closed SDAM event
|
||||
*
|
||||
* @event Server#topologyClosed
|
||||
* @type {object}
|
||||
*/
|
||||
|
||||
/**
|
||||
* A topology structure SDAM change event
|
||||
*
|
||||
* @event Server#topologyDescriptionChanged
|
||||
* @type {object}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Server reconnect failed
|
||||
*
|
||||
* @event Server#reconnectFailed
|
||||
* @type {Error}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Server connection pool closed
|
||||
*
|
||||
* @event Server#close
|
||||
* @type {object}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Server connection pool caused an error
|
||||
*
|
||||
* @event Server#error
|
||||
* @type {Error}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Server destroyed was called
|
||||
*
|
||||
* @event Server#destroy
|
||||
* @type {Server}
|
||||
*/
|
||||
|
||||
module.exports = Server;
|
||||
+334
@@ -0,0 +1,334 @@
|
||||
"use strict"
|
||||
|
||||
var os = require('os'),
|
||||
f = require('util').format;
|
||||
|
||||
/**
|
||||
* Emit event if it exists
|
||||
* @method
|
||||
*/
|
||||
function emitSDAMEvent(self, event, description) {
|
||||
if(self.listeners(event).length > 0) {
|
||||
self.emit(event, description);
|
||||
}
|
||||
}
|
||||
|
||||
// Get package.json variable
|
||||
var driverVersion = require('../../package.json').version;
|
||||
var nodejsversion = f('Node.js %s, %s', process.version, os.endianness());
|
||||
var type = os.type();
|
||||
var name = process.platform;
|
||||
var architecture = process.arch;
|
||||
var release = os.release();
|
||||
|
||||
function createClientInfo(options) {
|
||||
// Build default client information
|
||||
var clientInfo = options.clientInfo ? clone(options.clientInfo) : {
|
||||
driver: {
|
||||
name: "nodejs-core",
|
||||
version: driverVersion
|
||||
},
|
||||
os: {
|
||||
type: type,
|
||||
name: name,
|
||||
architecture: architecture,
|
||||
version: release
|
||||
}
|
||||
}
|
||||
|
||||
// Is platform specified
|
||||
if(clientInfo.platform && clientInfo.platform.indexOf('mongodb-core') == -1) {
|
||||
clientInfo.platform = f('%s, mongodb-core: %s', clientInfo.platform, driverVersion);
|
||||
} else if(!clientInfo.platform){
|
||||
clientInfo.platform = nodejsversion;
|
||||
}
|
||||
|
||||
// Do we have an application specific string
|
||||
if(options.appname) {
|
||||
// Cut at 128 bytes
|
||||
var buffer = new Buffer(options.appname);
|
||||
// Return the truncated appname
|
||||
var appname = buffer.length > 128 ? buffer.slice(0, 128).toString('utf8') : options.appname;
|
||||
// Add to the clientInfo
|
||||
clientInfo.application = { name: appname };
|
||||
}
|
||||
|
||||
return clientInfo;
|
||||
}
|
||||
|
||||
function clone(object) {
|
||||
return JSON.parse(JSON.stringify(object));
|
||||
}
|
||||
|
||||
var getPreviousDescription = function(self) {
|
||||
if(!self.s.serverDescription) {
|
||||
self.s.serverDescription = {
|
||||
address: self.name,
|
||||
arbiters: [], hosts: [], passives: [], type: 'Unknown'
|
||||
}
|
||||
}
|
||||
|
||||
return self.s.serverDescription;
|
||||
}
|
||||
|
||||
var emitServerDescriptionChanged = function(self, description) {
|
||||
if(self.listeners('serverDescriptionChanged').length > 0) {
|
||||
// Emit the server description changed events
|
||||
self.emit('serverDescriptionChanged', {
|
||||
topologyId: self.s.topologyId != -1 ? self.s.topologyId : self.id, address: self.name,
|
||||
previousDescription: getPreviousDescription(self),
|
||||
newDescription: description
|
||||
});
|
||||
|
||||
self.s.serverDescription = description;
|
||||
}
|
||||
}
|
||||
|
||||
var getPreviousTopologyDescription = function(self) {
|
||||
if(!self.s.topologyDescription) {
|
||||
self.s.topologyDescription = {
|
||||
topologyType: 'Unknown',
|
||||
servers: [{
|
||||
address: self.name, arbiters: [], hosts: [], passives: [], type: 'Unknown'
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
return self.s.topologyDescription;
|
||||
}
|
||||
|
||||
var emitTopologyDescriptionChanged = function(self, description) {
|
||||
if(self.listeners('topologyDescriptionChanged').length > 0) {
|
||||
// Emit the server description changed events
|
||||
self.emit('topologyDescriptionChanged', {
|
||||
topologyId: self.s.topologyId != -1 ? self.s.topologyId : self.id, address: self.name,
|
||||
previousDescription: getPreviousTopologyDescription(self),
|
||||
newDescription: description
|
||||
});
|
||||
|
||||
self.s.serverDescription = description;
|
||||
}
|
||||
}
|
||||
|
||||
var changedIsMaster = function(self, currentIsmaster, ismaster) {
|
||||
var currentType = getTopologyType(self, currentIsmaster);
|
||||
var newType = getTopologyType(self, ismaster);
|
||||
if(newType != currentType) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
var getTopologyType = function(self, ismaster) {
|
||||
if(!ismaster) {
|
||||
ismaster = self.ismaster;
|
||||
}
|
||||
|
||||
if(!ismaster) return 'Unknown';
|
||||
if(ismaster.ismaster && ismaster.msg == 'isdbgrid') return 'Mongos';
|
||||
if(ismaster.ismaster && !ismaster.hosts) return 'Standalone';
|
||||
if(ismaster.ismaster) return 'RSPrimary';
|
||||
if(ismaster.secondary) return 'RSSecondary';
|
||||
if(ismaster.arbiterOnly) return 'RSArbiter';
|
||||
return 'Unknown';
|
||||
}
|
||||
|
||||
var inquireServerState = function(self) {
|
||||
return function(callback) {
|
||||
if(self.s.state == 'destroyed') return;
|
||||
// Record response time
|
||||
var start = new Date().getTime();
|
||||
|
||||
// emitSDAMEvent
|
||||
emitSDAMEvent(self, 'serverHeartbeatStarted', { connectionId: self.name });
|
||||
|
||||
// Attempt to execute ismaster command
|
||||
self.command('admin.$cmd', { ismaster:true }, { monitoring:true }, function(err, r) {
|
||||
if(!err) {
|
||||
// Legacy event sender
|
||||
self.emit('ismaster', r, self);
|
||||
|
||||
// Calculate latencyMS
|
||||
var latencyMS = new Date().getTime() - start;
|
||||
|
||||
// Server heart beat event
|
||||
emitSDAMEvent(self, 'serverHeartbeatSucceeded', { durationMS: latencyMS, reply: r.result, connectionId: self.name });
|
||||
|
||||
// Did the server change
|
||||
if(changedIsMaster(self, self.s.ismaster, r.result)) {
|
||||
// Emit server description changed if something listening
|
||||
emitServerDescriptionChanged(self, {
|
||||
address: self.name, arbiters: [], hosts: [], passives: [], type: !self.s.inTopology ? 'Standalone' : getTopologyType(self)
|
||||
});
|
||||
}
|
||||
|
||||
// Updat ismaster view
|
||||
self.s.ismaster = r.result;
|
||||
|
||||
// Set server response time
|
||||
self.s.isMasterLatencyMS = latencyMS;
|
||||
} else {
|
||||
emitSDAMEvent(self, 'serverHeartbeatFailed', { durationMS: latencyMS, failure: err, connectionId: self.name });
|
||||
}
|
||||
|
||||
// Peforming an ismaster monitoring callback operation
|
||||
if(typeof callback == 'function') {
|
||||
return callback(err, r);
|
||||
}
|
||||
|
||||
// Perform another sweep
|
||||
self.s.inquireServerStateTimeout = setTimeout(inquireServerState(self), self.s.haInterval);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Clone the options
|
||||
var cloneOptions = function(options) {
|
||||
var opts = {};
|
||||
for(var name in options) {
|
||||
opts[name] = options[name];
|
||||
}
|
||||
return opts;
|
||||
}
|
||||
|
||||
function Interval(fn, time) {
|
||||
var timer = false;
|
||||
|
||||
this.start = function () {
|
||||
if (!this.isRunning()) {
|
||||
timer = setInterval(fn, time);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
this.stop = function () {
|
||||
clearInterval(timer);
|
||||
timer = false;
|
||||
return this;
|
||||
};
|
||||
|
||||
this.isRunning = function () {
|
||||
return timer !== false;
|
||||
};
|
||||
}
|
||||
|
||||
function Timeout(fn, time) {
|
||||
var timer = false;
|
||||
|
||||
this.start = function () {
|
||||
if (!this.isRunning()) {
|
||||
timer = setTimeout(function() {
|
||||
fn();
|
||||
if (timer && timer._called === undefined) {
|
||||
// The artificial _called is set here for compatibility with node.js 0.10.x/0.12.x versions
|
||||
timer._called = true;
|
||||
}
|
||||
}, time);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
this.stop = function () {
|
||||
clearTimeout(timer);
|
||||
timer = false;
|
||||
return this;
|
||||
};
|
||||
|
||||
this.isRunning = function () {
|
||||
if(timer && timer._called) return false;
|
||||
return timer !== false;
|
||||
};
|
||||
}
|
||||
|
||||
function diff(previous, current) {
|
||||
// Difference document
|
||||
var diff = {
|
||||
servers: []
|
||||
}
|
||||
|
||||
// Previous entry
|
||||
if(!previous) {
|
||||
previous = { servers: [] };
|
||||
}
|
||||
|
||||
// Check if we have any previous servers missing in the current ones
|
||||
for(var i = 0; i < previous.servers.length; i++) {
|
||||
var found = false;
|
||||
|
||||
for(var j = 0; j < current.servers.length; j++) {
|
||||
if(current.servers[j].address.toLowerCase()
|
||||
=== previous.servers[i].address.toLowerCase()) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!found) {
|
||||
// Add to the diff
|
||||
diff.servers.push({
|
||||
address: previous.servers[i].address,
|
||||
from: previous.servers[i].type,
|
||||
to: 'Unknown',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Check if there are any severs that don't exist
|
||||
for(var j = 0; j < current.servers.length; j++) {
|
||||
var found = false;
|
||||
|
||||
// Go over all the previous servers
|
||||
for(var i = 0; i < previous.servers.length; i++) {
|
||||
if(previous.servers[i].address.toLowerCase()
|
||||
=== current.servers[j].address.toLowerCase()) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the server to the diff
|
||||
if(!found) {
|
||||
diff.servers.push({
|
||||
address: current.servers[j].address,
|
||||
from: 'Unknown',
|
||||
to: current.servers[j].type,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Got through all the servers
|
||||
for(var i = 0; i < previous.servers.length; i++) {
|
||||
var prevServer = previous.servers[i];
|
||||
|
||||
// Go through all current servers
|
||||
for(var j = 0; j < current.servers.length; j++) {
|
||||
var currServer = current.servers[j];
|
||||
|
||||
// Matching server
|
||||
if(prevServer.address.toLowerCase() === currServer.address.toLowerCase()) {
|
||||
// We had a change in state
|
||||
if(prevServer.type != currServer.type) {
|
||||
diff.servers.push({
|
||||
address: prevServer.address,
|
||||
from: prevServer.type,
|
||||
to: currServer.type
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return difference
|
||||
return diff;
|
||||
}
|
||||
|
||||
module.exports.inquireServerState = inquireServerState
|
||||
module.exports.getTopologyType = getTopologyType;
|
||||
module.exports.emitServerDescriptionChanged = emitServerDescriptionChanged;
|
||||
module.exports.emitTopologyDescriptionChanged = emitTopologyDescriptionChanged;
|
||||
module.exports.cloneOptions = cloneOptions;
|
||||
module.exports.createClientInfo = createClientInfo;
|
||||
module.exports.clone = clone;
|
||||
module.exports.diff = diff;
|
||||
module.exports.Interval = Interval;
|
||||
module.exports.Timeout = Timeout;
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copy the values of all enumerable own properties from one or more
|
||||
* source objects to a target object. It will return the target object.
|
||||
*/
|
||||
var assign = Object.assign ? Object.assign : function assign(target) {
|
||||
if (target === undefined || target === null) {
|
||||
throw new TypeError('Cannot convert first argument to object');
|
||||
}
|
||||
|
||||
var to = Object(target);
|
||||
for (var i = 1; i < arguments.length; i++) {
|
||||
var nextSource = arguments[i];
|
||||
if (nextSource === undefined || nextSource === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var keysArray = Object.keys(Object(nextSource));
|
||||
for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
|
||||
var nextKey = keysArray[nextIndex];
|
||||
var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
|
||||
if (desc !== undefined && desc.enumerable) {
|
||||
to[nextKey] = nextSource[nextKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return to;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
assign: assign
|
||||
};
|
||||
+570
@@ -0,0 +1,570 @@
|
||||
"use strict";
|
||||
|
||||
var Insert = require('./commands').Insert
|
||||
, Update = require('./commands').Update
|
||||
, Remove = require('./commands').Remove
|
||||
, copy = require('../connection/utils').copy
|
||||
, retrieveBSON = require('../connection/utils').retrieveBSON
|
||||
, KillCursor = require('../connection/commands').KillCursor
|
||||
, GetMore = require('../connection/commands').GetMore
|
||||
, Query = require('../connection/commands').Query
|
||||
, f = require('util').format
|
||||
, CommandResult = require('../connection/command_result')
|
||||
, MongoError = require('../error')
|
||||
, getReadPreference = require('./shared').getReadPreference;
|
||||
|
||||
var BSON = retrieveBSON(),
|
||||
Long = BSON.Long;
|
||||
|
||||
// Write concern fields
|
||||
var writeConcernFields = ['w', 'wtimeout', 'j', 'fsync'];
|
||||
|
||||
var WireProtocol = function() {}
|
||||
|
||||
//
|
||||
// Needs to support legacy mass insert as well as ordered/unordered legacy
|
||||
// emulation
|
||||
//
|
||||
WireProtocol.prototype.insert = function(pool, ismaster, ns, bson, ops, options, callback) {
|
||||
options = options || {};
|
||||
// Default is ordered execution
|
||||
var ordered = typeof options.ordered == 'boolean' ? options.ordered : true;
|
||||
ops = Array.isArray(ops) ? ops :[ops];
|
||||
|
||||
// If we have more than a 1000 ops fails
|
||||
if(ops.length > 1000) return callback(new MongoError("exceeded maximum write batch size of 1000"));
|
||||
|
||||
// Write concern
|
||||
var writeConcern = options.writeConcern || {w:1};
|
||||
|
||||
// We are unordered
|
||||
if(!ordered || writeConcern.w == 0) {
|
||||
return executeUnordered('insert', Insert, ismaster, ns, bson, pool, ops, options, callback);
|
||||
}
|
||||
|
||||
return executeOrdered('insert', Insert, ismaster, ns, bson, pool, ops, options, callback);
|
||||
}
|
||||
|
||||
WireProtocol.prototype.update = function(pool, ismaster, ns, bson, ops, options, callback) {
|
||||
options = options || {};
|
||||
// Default is ordered execution
|
||||
var ordered = typeof options.ordered == 'boolean' ? options.ordered : true;
|
||||
ops = Array.isArray(ops) ? ops :[ops];
|
||||
|
||||
// Write concern
|
||||
var writeConcern = options.writeConcern || {w:1};
|
||||
|
||||
// We are unordered
|
||||
if(!ordered || writeConcern.w == 0) {
|
||||
return executeUnordered('update', Update, ismaster, ns, bson, pool, ops, options, callback);
|
||||
}
|
||||
|
||||
return executeOrdered('update', Update, ismaster, ns, bson, pool, ops, options, callback);
|
||||
}
|
||||
|
||||
WireProtocol.prototype.remove = function(pool, ismaster, ns, bson, ops, options, callback) {
|
||||
options = options || {};
|
||||
// Default is ordered execution
|
||||
var ordered = typeof options.ordered == 'boolean' ? options.ordered : true;
|
||||
ops = Array.isArray(ops) ? ops :[ops];
|
||||
|
||||
// Write concern
|
||||
var writeConcern = options.writeConcern || {w:1};
|
||||
|
||||
// We are unordered
|
||||
if(!ordered || writeConcern.w == 0) {
|
||||
return executeUnordered('remove', Remove, ismaster, ns, bson, pool, ops, options, callback);
|
||||
}
|
||||
|
||||
return executeOrdered('remove', Remove, ismaster, ns, bson, pool, ops, options, callback);
|
||||
}
|
||||
|
||||
WireProtocol.prototype.killCursor = function(bson, ns, cursorId, pool, callback) {
|
||||
// Create a kill cursor command
|
||||
var killCursor = new KillCursor(bson, [cursorId]);
|
||||
// Execute the kill cursor command
|
||||
if(pool && pool.isConnected()) {
|
||||
pool.write(killCursor, {
|
||||
immediateRelease:true, noResponse: true
|
||||
});
|
||||
}
|
||||
|
||||
// Callback
|
||||
if(typeof callback == 'function') callback(null, null);
|
||||
}
|
||||
|
||||
WireProtocol.prototype.getMore = function(bson, ns, cursorState, batchSize, raw, connection, options, callback) {
|
||||
// Create getMore command
|
||||
var getMore = new GetMore(bson, ns, cursorState.cursorId, {numberToReturn: batchSize});
|
||||
|
||||
// Query callback
|
||||
var queryCallback = function(err, result) {
|
||||
if(err) return callback(err);
|
||||
// Get the raw message
|
||||
var r = result.message;
|
||||
|
||||
// If we have a timed out query or a cursor that was killed
|
||||
if((r.responseFlags & (1 << 0)) != 0) {
|
||||
return callback(new MongoError("cursor does not exist, was killed or timed out"), null);
|
||||
}
|
||||
|
||||
// Ensure we have a Long valie cursor id
|
||||
var cursorId = typeof r.cursorId == 'number'
|
||||
? Long.fromNumber(r.cursorId)
|
||||
: r.cursorId;
|
||||
|
||||
// Set all the values
|
||||
cursorState.documents = r.documents;
|
||||
cursorState.cursorId = cursorId;
|
||||
|
||||
// Return
|
||||
callback(null, null, r.connection);
|
||||
}
|
||||
|
||||
// Contains any query options
|
||||
var queryOptions = {};
|
||||
|
||||
// If we have a raw query decorate the function
|
||||
if(raw) {
|
||||
queryOptions.raw = raw;
|
||||
}
|
||||
|
||||
// Check if we need to promote longs
|
||||
if(typeof cursorState.promoteLongs == 'boolean') {
|
||||
queryOptions.promoteLongs = cursorState.promoteLongs;
|
||||
}
|
||||
|
||||
if(typeof cursorState.promoteValues == 'boolean') {
|
||||
queryOptions.promoteValues = cursorState.promoteValues;
|
||||
}
|
||||
|
||||
if(typeof cursorState.promoteBuffers == 'boolean') {
|
||||
queryOptions.promoteBuffers = cursorState.promoteBuffers;
|
||||
}
|
||||
|
||||
// Write out the getMore command
|
||||
connection.write(getMore, queryOptions, queryCallback);
|
||||
}
|
||||
|
||||
WireProtocol.prototype.command = function(bson, ns, cmd, cursorState, topology, options) {
|
||||
// Establish type of command
|
||||
if(cmd.find) {
|
||||
return setupClassicFind(bson, ns, cmd, cursorState, topology, options)
|
||||
} else if(cursorState.cursorId != null) {
|
||||
return;
|
||||
} else if(cmd) {
|
||||
return setupCommand(bson, ns, cmd, cursorState, topology, options);
|
||||
} else {
|
||||
throw new MongoError(f("command %s does not return a cursor", JSON.stringify(cmd)));
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Execute a find command
|
||||
var setupClassicFind = function(bson, ns, cmd, cursorState, topology, options) {
|
||||
// Ensure we have at least some options
|
||||
options = options || {};
|
||||
// Get the readPreference
|
||||
var readPreference = getReadPreference(cmd, options);
|
||||
// Set the optional batchSize
|
||||
cursorState.batchSize = cmd.batchSize || cursorState.batchSize;
|
||||
var numberToReturn = 0;
|
||||
|
||||
// Unpack the limit and batchSize values
|
||||
if(cursorState.limit == 0) {
|
||||
numberToReturn = cursorState.batchSize;
|
||||
} else if(cursorState.limit < 0 || cursorState.limit < cursorState.batchSize || (cursorState.limit > 0 && cursorState.batchSize == 0)) {
|
||||
numberToReturn = cursorState.limit;
|
||||
} else {
|
||||
numberToReturn = cursorState.batchSize;
|
||||
}
|
||||
|
||||
var numberToSkip = cursorState.skip || 0;
|
||||
// Build actual find command
|
||||
var findCmd = {};
|
||||
// Using special modifier
|
||||
var usesSpecialModifier = false;
|
||||
|
||||
// We have a Mongos topology, check if we need to add a readPreference
|
||||
if(topology.type == 'mongos' && readPreference) {
|
||||
findCmd['$readPreference'] = readPreference.toJSON();
|
||||
usesSpecialModifier = true;
|
||||
}
|
||||
|
||||
// Add special modifiers to the query
|
||||
if(cmd.sort) findCmd['orderby'] = cmd.sort, usesSpecialModifier = true;
|
||||
if(cmd.hint) findCmd['$hint'] = cmd.hint, usesSpecialModifier = true;
|
||||
if(cmd.snapshot) findCmd['$snapshot'] = cmd.snapshot, usesSpecialModifier = true;
|
||||
if(cmd.returnKey) findCmd['$returnKey'] = cmd.returnKey, usesSpecialModifier = true;
|
||||
if(cmd.maxScan) findCmd['$maxScan'] = cmd.maxScan, usesSpecialModifier = true;
|
||||
if(cmd.min) findCmd['$min'] = cmd.min, usesSpecialModifier = true;
|
||||
if(cmd.max) findCmd['$max'] = cmd.max, usesSpecialModifier = true;
|
||||
if(cmd.showDiskLoc) findCmd['$showDiskLoc'] = cmd.showDiskLoc, usesSpecialModifier = true;
|
||||
if(cmd.comment) findCmd['$comment'] = cmd.comment, usesSpecialModifier = true;
|
||||
if(cmd.maxTimeMS) findCmd['$maxTimeMS'] = cmd.maxTimeMS, usesSpecialModifier = true;
|
||||
|
||||
if(cmd.explain) {
|
||||
// nToReturn must be 0 (match all) or negative (match N and close cursor)
|
||||
// nToReturn > 0 will give explain results equivalent to limit(0)
|
||||
numberToReturn = -Math.abs(cmd.limit || 0);
|
||||
usesSpecialModifier = true;
|
||||
findCmd['$explain'] = true;
|
||||
}
|
||||
|
||||
// If we have a special modifier
|
||||
if(usesSpecialModifier) {
|
||||
findCmd['$query'] = cmd.query;
|
||||
} else {
|
||||
findCmd = cmd.query;
|
||||
}
|
||||
|
||||
// Throw on majority readConcern passed in
|
||||
if(cmd.readConcern && cmd.readConcern.level != 'local') {
|
||||
throw new MongoError(f('server find command does not support a readConcern level of %s', cmd.readConcern.level));
|
||||
}
|
||||
|
||||
// Remove readConcern, ensure no failing commands
|
||||
if(cmd.readConcern) {
|
||||
cmd = copy(cmd);
|
||||
delete cmd['readConcern'];
|
||||
}
|
||||
|
||||
// Set up the serialize and ignoreUndefined fields
|
||||
var serializeFunctions = typeof options.serializeFunctions == 'boolean'
|
||||
? options.serializeFunctions : false;
|
||||
var ignoreUndefined = typeof options.ignoreUndefined == 'boolean'
|
||||
? options.ignoreUndefined : false;
|
||||
|
||||
// Build Query object
|
||||
var query = new Query(bson, ns, findCmd, {
|
||||
numberToSkip: numberToSkip, numberToReturn: numberToReturn
|
||||
, checkKeys: false, returnFieldSelector: cmd.fields
|
||||
, serializeFunctions: serializeFunctions, ignoreUndefined: ignoreUndefined
|
||||
});
|
||||
|
||||
// Set query flags
|
||||
query.slaveOk = readPreference.slaveOk();
|
||||
|
||||
// Set up the option bits for wire protocol
|
||||
if(typeof cmd.tailable == 'boolean') query.tailable = cmd.tailable;
|
||||
if(typeof cmd.oplogReplay == 'boolean') query.oplogReplay = cmd.oplogReplay;
|
||||
if(typeof cmd.noCursorTimeout == 'boolean') query.noCursorTimeout = cmd.noCursorTimeout;
|
||||
if(typeof cmd.awaitData == 'boolean') query.awaitData = cmd.awaitData;
|
||||
if(typeof cmd.partial == 'boolean') query.partial = cmd.partial;
|
||||
// Return the query
|
||||
return query;
|
||||
}
|
||||
|
||||
//
|
||||
// Set up a command cursor
|
||||
var setupCommand = function(bson, ns, cmd, cursorState, topology, options) {
|
||||
// Set empty options object
|
||||
options = options || {}
|
||||
// Get the readPreference
|
||||
var readPreference = getReadPreference(cmd, options);
|
||||
// Final query
|
||||
var finalCmd = {};
|
||||
for(var name in cmd) {
|
||||
finalCmd[name] = cmd[name];
|
||||
}
|
||||
|
||||
// Build command namespace
|
||||
var parts = ns.split(/\./);
|
||||
|
||||
// Throw on majority readConcern passed in
|
||||
if(cmd.readConcern && cmd.readConcern.level != 'local') {
|
||||
throw new MongoError(f('server %s command does not support a readConcern level of %s', JSON.stringify(cmd), cmd.readConcern.level));
|
||||
}
|
||||
|
||||
// Remove readConcern, ensure no failing commands
|
||||
if(cmd.readConcern) delete cmd['readConcern'];
|
||||
|
||||
// Serialize functions
|
||||
var serializeFunctions = typeof options.serializeFunctions == 'boolean'
|
||||
? options.serializeFunctions : false;
|
||||
|
||||
// Set up the serialize and ignoreUndefined fields
|
||||
var ignoreUndefined = typeof options.ignoreUndefined == 'boolean'
|
||||
? options.ignoreUndefined : false;
|
||||
|
||||
// We have a Mongos topology, check if we need to add a readPreference
|
||||
if(topology.type == 'mongos'
|
||||
&& readPreference
|
||||
&& readPreference.preference != 'primary') {
|
||||
finalCmd = {
|
||||
'$query': finalCmd,
|
||||
'$readPreference': readPreference.toJSON()
|
||||
};
|
||||
}
|
||||
|
||||
// Build Query object
|
||||
var query = new Query(bson, f('%s.$cmd', parts.shift()), finalCmd, {
|
||||
numberToSkip: 0, numberToReturn: -1
|
||||
, checkKeys: false, serializeFunctions: serializeFunctions
|
||||
, ignoreUndefined: ignoreUndefined
|
||||
});
|
||||
|
||||
// Set query flags
|
||||
query.slaveOk = readPreference.slaveOk();
|
||||
|
||||
// Return the query
|
||||
return query;
|
||||
}
|
||||
|
||||
var hasWriteConcern = function(writeConcern) {
|
||||
if(writeConcern.w
|
||||
|| writeConcern.wtimeout
|
||||
|| writeConcern.j == true
|
||||
|| writeConcern.fsync == true
|
||||
|| Object.keys(writeConcern).length == 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
var cloneWriteConcern = function(writeConcern) {
|
||||
var wc = {};
|
||||
if(writeConcern.w != null) wc.w = writeConcern.w;
|
||||
if(writeConcern.wtimeout != null) wc.wtimeout = writeConcern.wtimeout;
|
||||
if(writeConcern.j != null) wc.j = writeConcern.j;
|
||||
if(writeConcern.fsync != null) wc.fsync = writeConcern.fsync;
|
||||
return wc;
|
||||
}
|
||||
|
||||
//
|
||||
// Aggregate up all the results
|
||||
//
|
||||
var aggregateWriteOperationResults = function(opType, ops, results, connection) {
|
||||
var finalResult = { ok: 1, n: 0 }
|
||||
if(opType == 'update') {
|
||||
finalResult.nModified = 0;
|
||||
}
|
||||
|
||||
// Map all the results coming back
|
||||
for(var i = 0; i < results.length; i++) {
|
||||
var result = results[i];
|
||||
var op = ops[i];
|
||||
|
||||
if((result.upserted || (result.updatedExisting == false)) && finalResult.upserted == null) {
|
||||
finalResult.upserted = [];
|
||||
}
|
||||
|
||||
// Push the upserted document to the list of upserted values
|
||||
if(result.upserted) {
|
||||
finalResult.upserted.push({index: i, _id: result.upserted});
|
||||
}
|
||||
|
||||
// We have an upsert where we passed in a _id
|
||||
if(result.updatedExisting == false && result.n == 1 && result.upserted == null) {
|
||||
finalResult.upserted.push({index: i, _id: op.q._id});
|
||||
} else if(result.updatedExisting == true) {
|
||||
finalResult.nModified += result.n;
|
||||
}
|
||||
|
||||
// We have an insert command
|
||||
if(result.ok == 1 && opType == 'insert' && result.err == null) {
|
||||
finalResult.n = finalResult.n + 1;
|
||||
}
|
||||
|
||||
// We have a command error
|
||||
if(result != null && result.ok == 0 || result.err || result.errmsg) {
|
||||
if(result.ok == 0) finalResult.ok = 0;
|
||||
finalResult.code = result.code;
|
||||
finalResult.errmsg = result.errmsg || result.err || result.errMsg;
|
||||
|
||||
// Check if we have a write error
|
||||
if(result.code == 11000
|
||||
|| result.code == 11001
|
||||
|| result.code == 12582
|
||||
|| result.code == 16544
|
||||
|| result.code == 16538
|
||||
|| result.code == 16542
|
||||
|| result.code == 14
|
||||
|| result.code == 13511) {
|
||||
if(finalResult.writeErrors == null) finalResult.writeErrors = [];
|
||||
finalResult.writeErrors.push({
|
||||
index: i
|
||||
, code: result.code
|
||||
, errmsg: result.errmsg || result.err || result.errMsg
|
||||
});
|
||||
} else {
|
||||
finalResult.writeConcernError = {
|
||||
code: result.code
|
||||
, errmsg: result.errmsg || result.err || result.errMsg
|
||||
}
|
||||
}
|
||||
} else if(typeof result.n == 'number') {
|
||||
finalResult.n += result.n;
|
||||
} else {
|
||||
finalResult.n += 1;
|
||||
}
|
||||
|
||||
// Result as expected
|
||||
if(result != null && result.lastOp) finalResult.lastOp = result.lastOp;
|
||||
}
|
||||
|
||||
// Return finalResult aggregated results
|
||||
return new CommandResult(finalResult, connection);
|
||||
}
|
||||
|
||||
//
|
||||
// Execute all inserts in an ordered manner
|
||||
//
|
||||
var executeOrdered = function(opType ,command, ismaster, ns, bson, pool, ops, options, callback) {
|
||||
var _ops = ops.slice(0);
|
||||
// Collect all the getLastErrors
|
||||
var getLastErrors = [];
|
||||
// Execute an operation
|
||||
var executeOp = function(list, _callback) {
|
||||
// No more items in the list
|
||||
if(list.length == 0) {
|
||||
return process.nextTick(function() {
|
||||
_callback(null, aggregateWriteOperationResults(opType, ops, getLastErrors, null));
|
||||
});
|
||||
}
|
||||
|
||||
// Get the first operation
|
||||
var doc = list.shift();
|
||||
// Create an insert command
|
||||
var op = new command(Query.getRequestId(), ismaster, bson, ns, [doc], options);
|
||||
// Write concern
|
||||
var optionWriteConcern = options.writeConcern || {w:1};
|
||||
// Final write concern
|
||||
var writeConcern = cloneWriteConcern(optionWriteConcern);
|
||||
|
||||
// Get the db name
|
||||
var db = ns.split('.').shift();
|
||||
|
||||
try {
|
||||
// Add binary message to list of commands to execute
|
||||
var commands = [op];
|
||||
|
||||
// Add getLastOrdered
|
||||
var getLastErrorCmd = {getlasterror: 1};
|
||||
// Merge all the fields
|
||||
for(var i = 0; i < writeConcernFields.length; i++) {
|
||||
if(writeConcern[writeConcernFields[i]] != null) {
|
||||
getLastErrorCmd[writeConcernFields[i]] = writeConcern[writeConcernFields[i]];
|
||||
}
|
||||
}
|
||||
|
||||
// Create a getLastError command
|
||||
var getLastErrorOp = new Query(bson, f("%s.$cmd", db), getLastErrorCmd, {numberToReturn: -1});
|
||||
// Add getLastError command to list of ops to execute
|
||||
commands.push(getLastErrorOp);
|
||||
|
||||
// getLastError callback
|
||||
var getLastErrorCallback = function(err, result) {
|
||||
if(err) return callback(err);
|
||||
// Get the document
|
||||
var doc = result.result;
|
||||
// Save the getLastError document
|
||||
getLastErrors.push(doc);
|
||||
|
||||
// If we have an error terminate
|
||||
if(doc.ok == 0 || doc.err || doc.errmsg) {
|
||||
return callback(null, aggregateWriteOperationResults(opType, ops, getLastErrors, result.connection));
|
||||
}
|
||||
|
||||
// Execute the next op in the list
|
||||
executeOp(list, callback);
|
||||
}
|
||||
|
||||
// Write both commands out at the same time
|
||||
pool.write(commands, getLastErrorCallback);
|
||||
} catch(err) {
|
||||
// We have a serialization error, rewrite as a write error to have same behavior as modern
|
||||
// write commands
|
||||
getLastErrors.push({ ok: 1, errmsg: typeof err == 'string' ? err : err.message, code: 14 });
|
||||
// Return due to an error
|
||||
process.nextTick(function() {
|
||||
_callback(null, aggregateWriteOperationResults(opType, ops, getLastErrors, null));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Execute the operations
|
||||
executeOp(_ops, callback);
|
||||
}
|
||||
|
||||
var executeUnordered = function(opType, command, ismaster, ns, bson, pool, ops, options, callback) {
|
||||
// Total operations to write
|
||||
var totalOps = ops.length;
|
||||
// Collect all the getLastErrors
|
||||
var getLastErrors = [];
|
||||
// Write concern
|
||||
var optionWriteConcern = options.writeConcern || {w:1};
|
||||
// Final write concern
|
||||
var writeConcern = cloneWriteConcern(optionWriteConcern);
|
||||
// Driver level error
|
||||
var error;
|
||||
|
||||
// Execute all the operations
|
||||
for(var i = 0; i < ops.length; i++) {
|
||||
// Create an insert command
|
||||
var op = new command(Query.getRequestId(), ismaster, bson, ns, [ops[i]], options);
|
||||
// Get db name
|
||||
var db = ns.split('.').shift();
|
||||
|
||||
try {
|
||||
// Add binary message to list of commands to execute
|
||||
var commands = [op];
|
||||
|
||||
// If write concern 0 don't fire getLastError
|
||||
if(hasWriteConcern(writeConcern)) {
|
||||
var getLastErrorCmd = {getlasterror: 1};
|
||||
// Merge all the fields
|
||||
for(var j = 0; j < writeConcernFields.length; j++) {
|
||||
if(writeConcern[writeConcernFields[j]] != null)
|
||||
getLastErrorCmd[writeConcernFields[j]] = writeConcern[writeConcernFields[j]];
|
||||
}
|
||||
|
||||
// Create a getLastError command
|
||||
var getLastErrorOp = new Query(bson, f("%s.$cmd", db), getLastErrorCmd, {numberToReturn: -1});
|
||||
// Add getLastError command to list of ops to execute
|
||||
commands.push(getLastErrorOp);
|
||||
|
||||
// Give the result from getLastError the right index
|
||||
var callbackOp = function(_index) {
|
||||
return function(err, result) {
|
||||
if(err) error = err;
|
||||
// Update the number of operations executed
|
||||
totalOps = totalOps - 1;
|
||||
// Save the getLastError document
|
||||
if(!err) getLastErrors[_index] = result.result;
|
||||
// Check if we are done
|
||||
if(totalOps == 0) {
|
||||
process.nextTick(function() {
|
||||
if(error) return callback(error);
|
||||
callback(null, aggregateWriteOperationResults(opType, ops, getLastErrors, result.connection));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write both commands out at the same time
|
||||
pool.write(commands, callbackOp(i));
|
||||
} else {
|
||||
pool.write(commands, {immediateRelease:true, noResponse:true});
|
||||
}
|
||||
} catch(err) {
|
||||
// Update the number of operations executed
|
||||
totalOps = totalOps - 1;
|
||||
// We have a serialization error, rewrite as a write error to have same behavior as modern
|
||||
// write commands
|
||||
getLastErrors[i] = { ok: 1, errmsg: typeof err == 'string' ? err : err.message, code: 14 };
|
||||
// Check if we are done
|
||||
if(totalOps == 0) {
|
||||
callback(null, aggregateWriteOperationResults(opType, ops, getLastErrors, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Empty w:0 return
|
||||
if(writeConcern
|
||||
&& writeConcern.w == 0 && callback) {
|
||||
callback(null, new CommandResult({ok:1}, null));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WireProtocol;
|
||||
+334
@@ -0,0 +1,334 @@
|
||||
"use strict";
|
||||
|
||||
var copy = require('../connection/utils').copy
|
||||
, retrieveBSON = require('../connection/utils').retrieveBSON
|
||||
, KillCursor = require('../connection/commands').KillCursor
|
||||
, GetMore = require('../connection/commands').GetMore
|
||||
, Query = require('../connection/commands').Query
|
||||
, f = require('util').format
|
||||
, MongoError = require('../error')
|
||||
, getReadPreference = require('./shared').getReadPreference;
|
||||
|
||||
var BSON = retrieveBSON(),
|
||||
Long = BSON.Long;
|
||||
|
||||
var WireProtocol = function() {}
|
||||
|
||||
//
|
||||
// Execute a write operation
|
||||
var executeWrite = function(pool, bson, type, opsField, ns, ops, options, callback) {
|
||||
if(ops.length == 0) throw new MongoError("insert must contain at least one document");
|
||||
if(typeof options == 'function') {
|
||||
callback = options;
|
||||
options = {};
|
||||
options = options || {};
|
||||
}
|
||||
|
||||
// Split the ns up to get db and collection
|
||||
var p = ns.split(".");
|
||||
var d = p.shift();
|
||||
// Options
|
||||
var ordered = typeof options.ordered == 'boolean' ? options.ordered : true;
|
||||
var writeConcern = options.writeConcern;
|
||||
|
||||
// return skeleton
|
||||
var writeCommand = {};
|
||||
writeCommand[type] = p.join('.');
|
||||
writeCommand[opsField] = ops;
|
||||
writeCommand.ordered = ordered;
|
||||
|
||||
// Did we specify a write concern
|
||||
if(writeConcern && Object.keys(writeConcern).length > 0) {
|
||||
writeCommand.writeConcern = writeConcern;
|
||||
}
|
||||
|
||||
// Do we have bypassDocumentValidation set, then enable it on the write command
|
||||
if(typeof options.bypassDocumentValidation == 'boolean') {
|
||||
writeCommand.bypassDocumentValidation = options.bypassDocumentValidation;
|
||||
}
|
||||
|
||||
// Options object
|
||||
var opts = { command: true };
|
||||
var queryOptions = { checkKeys : false, numberToSkip: 0, numberToReturn: 1 };
|
||||
if(type == 'insert') queryOptions.checkKeys = true;
|
||||
if(typeof options.checkKeys == 'boolean') queryOptions.checkKeys = options.checkKeys;
|
||||
// Ensure we support serialization of functions
|
||||
if(options.serializeFunctions) queryOptions.serializeFunctions = options.serializeFunctions;
|
||||
// Do not serialize the undefined fields
|
||||
if(options.ignoreUndefined) queryOptions.ignoreUndefined = options.ignoreUndefined;
|
||||
|
||||
try {
|
||||
// Create write command
|
||||
var cmd = new Query(bson, f("%s.$cmd", d), writeCommand, queryOptions);
|
||||
// Execute command
|
||||
pool.write(cmd, opts, callback);
|
||||
} catch(err) {
|
||||
callback(err);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Needs to support legacy mass insert as well as ordered/unordered legacy
|
||||
// emulation
|
||||
//
|
||||
WireProtocol.prototype.insert = function(pool, ismaster, ns, bson, ops, options, callback) {
|
||||
executeWrite(pool, bson, 'insert', 'documents', ns, ops, options, callback);
|
||||
}
|
||||
|
||||
WireProtocol.prototype.update = function(pool, ismaster, ns, bson, ops, options, callback) {
|
||||
executeWrite(pool, bson, 'update', 'updates', ns, ops, options, callback);
|
||||
}
|
||||
|
||||
WireProtocol.prototype.remove = function(pool, ismaster, ns, bson, ops, options, callback) {
|
||||
executeWrite(pool, bson, 'delete', 'deletes', ns, ops, options, callback);
|
||||
}
|
||||
|
||||
WireProtocol.prototype.killCursor = function(bson, ns, cursorId, pool, callback) {
|
||||
// Create a kill cursor command
|
||||
var killCursor = new KillCursor(bson, [cursorId]);
|
||||
// Execute the kill cursor command
|
||||
if(pool && pool.isConnected()) {
|
||||
pool.write(killCursor, {
|
||||
immediateRelease:true, noResponse: true
|
||||
});
|
||||
}
|
||||
|
||||
// Callback
|
||||
if(typeof callback == 'function') callback(null, null);
|
||||
}
|
||||
|
||||
WireProtocol.prototype.getMore = function(bson, ns, cursorState, batchSize, raw, connection, options, callback) {
|
||||
// Create getMore command
|
||||
var getMore = new GetMore(bson, ns, cursorState.cursorId, {numberToReturn: batchSize});
|
||||
|
||||
// Query callback
|
||||
var queryCallback = function(err, result) {
|
||||
if(err) return callback(err);
|
||||
// Get the raw message
|
||||
var r = result.message;
|
||||
|
||||
// If we have a timed out query or a cursor that was killed
|
||||
if((r.responseFlags & (1 << 0)) != 0) {
|
||||
return callback(new MongoError("cursor does not exist, was killed or timed out"), null);
|
||||
}
|
||||
|
||||
// Ensure we have a Long valie cursor id
|
||||
var cursorId = typeof r.cursorId == 'number'
|
||||
? Long.fromNumber(r.cursorId)
|
||||
: r.cursorId;
|
||||
|
||||
// Set all the values
|
||||
cursorState.documents = r.documents;
|
||||
cursorState.cursorId = cursorId;
|
||||
|
||||
// Return
|
||||
callback(null, null, r.connection);
|
||||
}
|
||||
|
||||
// Contains any query options
|
||||
var queryOptions = {};
|
||||
|
||||
// If we have a raw query decorate the function
|
||||
if(raw) {
|
||||
queryOptions.raw = raw;
|
||||
}
|
||||
|
||||
// Check if we need to promote longs
|
||||
if(typeof cursorState.promoteLongs == 'boolean') {
|
||||
queryOptions.promoteLongs = cursorState.promoteLongs;
|
||||
}
|
||||
|
||||
if(typeof cursorState.promoteValues == 'boolean') {
|
||||
queryOptions.promoteValues = cursorState.promoteValues;
|
||||
}
|
||||
|
||||
if(typeof cursorState.promoteBuffers == 'boolean') {
|
||||
queryOptions.promoteBuffers = cursorState.promoteBuffers;
|
||||
}
|
||||
|
||||
// Write out the getMore command
|
||||
connection.write(getMore, queryOptions, queryCallback);
|
||||
}
|
||||
|
||||
WireProtocol.prototype.command = function(bson, ns, cmd, cursorState, topology, options) {
|
||||
// Establish type of command
|
||||
if(cmd.find) {
|
||||
return setupClassicFind(bson, ns, cmd, cursorState, topology, options)
|
||||
} else if(cursorState.cursorId != null) {
|
||||
return;
|
||||
} else if(cmd) {
|
||||
return setupCommand(bson, ns, cmd, cursorState, topology, options);
|
||||
} else {
|
||||
throw new MongoError(f("command %s does not return a cursor", JSON.stringify(cmd)));
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Execute a find command
|
||||
var setupClassicFind = function(bson, ns, cmd, cursorState, topology, options) {
|
||||
// Ensure we have at least some options
|
||||
options = options || {};
|
||||
// Get the readPreference
|
||||
var readPreference = getReadPreference(cmd, options);
|
||||
// Set the optional batchSize
|
||||
cursorState.batchSize = cmd.batchSize || cursorState.batchSize;
|
||||
var numberToReturn = 0;
|
||||
|
||||
// Unpack the limit and batchSize values
|
||||
if(cursorState.limit == 0) {
|
||||
numberToReturn = cursorState.batchSize;
|
||||
} else if(cursorState.limit < 0 || cursorState.limit < cursorState.batchSize || (cursorState.limit > 0 && cursorState.batchSize == 0)) {
|
||||
numberToReturn = cursorState.limit;
|
||||
} else {
|
||||
numberToReturn = cursorState.batchSize;
|
||||
}
|
||||
|
||||
var numberToSkip = cursorState.skip || 0;
|
||||
// Build actual find command
|
||||
var findCmd = {};
|
||||
// Using special modifier
|
||||
var usesSpecialModifier = false;
|
||||
|
||||
// We have a Mongos topology, check if we need to add a readPreference
|
||||
if(topology.type == 'mongos' && readPreference) {
|
||||
findCmd['$readPreference'] = readPreference.toJSON();
|
||||
usesSpecialModifier = true;
|
||||
}
|
||||
|
||||
// Add special modifiers to the query
|
||||
if(cmd.sort) findCmd['orderby'] = cmd.sort, usesSpecialModifier = true;
|
||||
if(cmd.hint) findCmd['$hint'] = cmd.hint, usesSpecialModifier = true;
|
||||
if(cmd.snapshot) findCmd['$snapshot'] = cmd.snapshot, usesSpecialModifier = true;
|
||||
if(cmd.returnKey) findCmd['$returnKey'] = cmd.returnKey, usesSpecialModifier = true;
|
||||
if(cmd.maxScan) findCmd['$maxScan'] = cmd.maxScan, usesSpecialModifier = true;
|
||||
if(cmd.min) findCmd['$min'] = cmd.min, usesSpecialModifier = true;
|
||||
if(cmd.max) findCmd['$max'] = cmd.max, usesSpecialModifier = true;
|
||||
if(cmd.showDiskLoc) findCmd['$showDiskLoc'] = cmd.showDiskLoc, usesSpecialModifier = true;
|
||||
if(cmd.comment) findCmd['$comment'] = cmd.comment, usesSpecialModifier = true;
|
||||
if(cmd.maxTimeMS) findCmd['$maxTimeMS'] = cmd.maxTimeMS, usesSpecialModifier = true;
|
||||
|
||||
if(cmd.explain) {
|
||||
// nToReturn must be 0 (match all) or negative (match N and close cursor)
|
||||
// nToReturn > 0 will give explain results equivalent to limit(0)
|
||||
numberToReturn = -Math.abs(cmd.limit || 0);
|
||||
usesSpecialModifier = true;
|
||||
findCmd['$explain'] = true;
|
||||
}
|
||||
|
||||
// If we have a special modifier
|
||||
if(usesSpecialModifier) {
|
||||
findCmd['$query'] = cmd.query;
|
||||
} else {
|
||||
findCmd = cmd.query;
|
||||
}
|
||||
|
||||
// Throw on majority readConcern passed in
|
||||
if(cmd.readConcern && cmd.readConcern.level != 'local') {
|
||||
throw new MongoError(f('server find command does not support a readConcern level of %s', cmd.readConcern.level));
|
||||
}
|
||||
|
||||
// Remove readConcern, ensure no failing commands
|
||||
if(cmd.readConcern) {
|
||||
cmd = copy(cmd);
|
||||
delete cmd['readConcern'];
|
||||
}
|
||||
|
||||
// Serialize functions
|
||||
var serializeFunctions = typeof options.serializeFunctions == 'boolean'
|
||||
? options.serializeFunctions : false;
|
||||
var ignoreUndefined = typeof options.ignoreUndefined == 'boolean'
|
||||
? options.ignoreUndefined : false;
|
||||
|
||||
// Build Query object
|
||||
var query = new Query(bson, ns, findCmd, {
|
||||
numberToSkip: numberToSkip, numberToReturn: numberToReturn
|
||||
, checkKeys: false, returnFieldSelector: cmd.fields
|
||||
, serializeFunctions: serializeFunctions
|
||||
, ignoreUndefined: ignoreUndefined
|
||||
});
|
||||
|
||||
// Set query flags
|
||||
query.slaveOk = readPreference.slaveOk();
|
||||
|
||||
// Set up the option bits for wire protocol
|
||||
if(typeof cmd.tailable == 'boolean') {
|
||||
query.tailable = cmd.tailable;
|
||||
}
|
||||
|
||||
if(typeof cmd.oplogReplay == 'boolean') {
|
||||
query.oplogReplay = cmd.oplogReplay;
|
||||
}
|
||||
|
||||
if(typeof cmd.noCursorTimeout == 'boolean') {
|
||||
query.noCursorTimeout = cmd.noCursorTimeout;
|
||||
}
|
||||
|
||||
if(typeof cmd.awaitData == 'boolean') {
|
||||
query.awaitData = cmd.awaitData;
|
||||
}
|
||||
|
||||
if(typeof cmd.partial == 'boolean') {
|
||||
query.partial = cmd.partial;
|
||||
}
|
||||
|
||||
// Return the query
|
||||
return query;
|
||||
}
|
||||
|
||||
//
|
||||
// Set up a command cursor
|
||||
var setupCommand = function(bson, ns, cmd, cursorState, topology, options) {
|
||||
// Set empty options object
|
||||
options = options || {}
|
||||
// Get the readPreference
|
||||
var readPreference = getReadPreference(cmd, options);
|
||||
|
||||
// Final query
|
||||
var finalCmd = {};
|
||||
for(var name in cmd) {
|
||||
finalCmd[name] = cmd[name];
|
||||
}
|
||||
|
||||
// Build command namespace
|
||||
var parts = ns.split(/\./);
|
||||
|
||||
// Serialize functions
|
||||
var serializeFunctions = typeof options.serializeFunctions == 'boolean'
|
||||
? options.serializeFunctions : false;
|
||||
|
||||
var ignoreUndefined = typeof options.ignoreUndefined == 'boolean'
|
||||
? options.ignoreUndefined : false;
|
||||
|
||||
// Throw on majority readConcern passed in
|
||||
if(cmd.readConcern && cmd.readConcern.level != 'local') {
|
||||
throw new MongoError(f('server %s command does not support a readConcern level of %s', JSON.stringify(cmd), cmd.readConcern.level));
|
||||
}
|
||||
|
||||
// Remove readConcern, ensure no failing commands
|
||||
if(cmd.readConcern) delete cmd['readConcern'];
|
||||
|
||||
// We have a Mongos topology, check if we need to add a readPreference
|
||||
if(topology.type == 'mongos'
|
||||
&& readPreference
|
||||
&& readPreference.preference != 'primary') {
|
||||
finalCmd = {
|
||||
'$query': finalCmd,
|
||||
'$readPreference': readPreference.toJSON()
|
||||
};
|
||||
}
|
||||
|
||||
// Build Query object
|
||||
var query = new Query(bson, f('%s.$cmd', parts.shift()), finalCmd, {
|
||||
numberToSkip: 0, numberToReturn: -1
|
||||
, checkKeys: false, serializeFunctions: serializeFunctions
|
||||
, ignoreUndefined: ignoreUndefined
|
||||
});
|
||||
|
||||
// Set query flags
|
||||
query.slaveOk = readPreference.slaveOk();
|
||||
|
||||
// Return the query
|
||||
return query;
|
||||
}
|
||||
|
||||
module.exports = WireProtocol;
|
||||
+549
@@ -0,0 +1,549 @@
|
||||
"use strict";
|
||||
|
||||
var Query = require('../connection/commands').Query
|
||||
, retrieveBSON = require('../connection/utils').retrieveBSON
|
||||
, f = require('util').format
|
||||
, MongoError = require('../error')
|
||||
, getReadPreference = require('./shared').getReadPreference;
|
||||
|
||||
var BSON = retrieveBSON(),
|
||||
Long = BSON.Long;
|
||||
|
||||
var WireProtocol = function(legacyWireProtocol) {
|
||||
this.legacyWireProtocol = legacyWireProtocol;
|
||||
}
|
||||
|
||||
//
|
||||
// Execute a write operation
|
||||
var executeWrite = function(pool, bson, type, opsField, ns, ops, options, callback) {
|
||||
if(ops.length == 0) throw new MongoError("insert must contain at least one document");
|
||||
if(typeof options == 'function') {
|
||||
callback = options;
|
||||
options = {};
|
||||
options = options || {};
|
||||
}
|
||||
|
||||
// Split the ns up to get db and collection
|
||||
var p = ns.split(".");
|
||||
var d = p.shift();
|
||||
// Options
|
||||
var ordered = typeof options.ordered == 'boolean' ? options.ordered : true;
|
||||
var writeConcern = options.writeConcern;
|
||||
|
||||
// return skeleton
|
||||
var writeCommand = {};
|
||||
writeCommand[type] = p.join('.');
|
||||
writeCommand[opsField] = ops;
|
||||
writeCommand.ordered = ordered;
|
||||
|
||||
// Did we specify a write concern
|
||||
if(writeConcern && Object.keys(writeConcern).length > 0) {
|
||||
writeCommand.writeConcern = writeConcern;
|
||||
}
|
||||
|
||||
// If we have collation passed in
|
||||
if(options.collation) {
|
||||
for(var i = 0; i < writeCommand[opsField].length; i++) {
|
||||
if(!writeCommand[opsField][i].collation) {
|
||||
writeCommand[opsField][i].collation = options.collation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Do we have bypassDocumentValidation set, then enable it on the write command
|
||||
if(typeof options.bypassDocumentValidation == 'boolean') {
|
||||
writeCommand.bypassDocumentValidation = options.bypassDocumentValidation;
|
||||
}
|
||||
|
||||
// Options object
|
||||
var opts = { command: true };
|
||||
var queryOptions = { checkKeys : false, numberToSkip: 0, numberToReturn: 1 };
|
||||
if(type == 'insert') queryOptions.checkKeys = true;
|
||||
if(typeof options.checkKeys == 'boolean') queryOptions.checkKeys = options.checkKeys;
|
||||
|
||||
// Ensure we support serialization of functions
|
||||
if(options.serializeFunctions) queryOptions.serializeFunctions = options.serializeFunctions;
|
||||
// Do not serialize the undefined fields
|
||||
if(options.ignoreUndefined) queryOptions.ignoreUndefined = options.ignoreUndefined;
|
||||
|
||||
try {
|
||||
// Create write command
|
||||
var cmd = new Query(bson, f("%s.$cmd", d), writeCommand, queryOptions);
|
||||
// Execute command
|
||||
pool.write(cmd, opts, callback);
|
||||
} catch(err) {
|
||||
callback(err);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Needs to support legacy mass insert as well as ordered/unordered legacy
|
||||
// emulation
|
||||
//
|
||||
WireProtocol.prototype.insert = function(pool, ismaster, ns, bson, ops, options, callback) {
|
||||
executeWrite(pool, bson, 'insert', 'documents', ns, ops, options, callback);
|
||||
}
|
||||
|
||||
WireProtocol.prototype.update = function(pool, ismaster, ns, bson, ops, options, callback) {
|
||||
executeWrite(pool, bson, 'update', 'updates', ns, ops, options, callback);
|
||||
}
|
||||
|
||||
WireProtocol.prototype.remove = function(pool, ismaster, ns, bson, ops, options, callback) {
|
||||
executeWrite(pool, bson, 'delete', 'deletes', ns, ops, options, callback);
|
||||
}
|
||||
|
||||
WireProtocol.prototype.killCursor = function(bson, ns, cursorId, pool, callback) {
|
||||
// Build command namespace
|
||||
var parts = ns.split(/\./);
|
||||
// Command namespace
|
||||
var commandns = f('%s.$cmd', parts.shift());
|
||||
// Create getMore command
|
||||
var killcursorCmd = {
|
||||
killCursors: parts.join('.'),
|
||||
cursors: [cursorId]
|
||||
}
|
||||
|
||||
// Build Query object
|
||||
var query = new Query(bson, commandns, killcursorCmd, {
|
||||
numberToSkip: 0, numberToReturn: -1
|
||||
, checkKeys: false, returnFieldSelector: null
|
||||
});
|
||||
|
||||
// Set query flags
|
||||
query.slaveOk = true;
|
||||
|
||||
// Kill cursor callback
|
||||
var killCursorCallback = function(err, result) {
|
||||
if(err) {
|
||||
if(typeof callback != 'function') return;
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
// Result
|
||||
var r = result.message;
|
||||
// If we have a timed out query or a cursor that was killed
|
||||
if((r.responseFlags & (1 << 0)) != 0) {
|
||||
if(typeof callback != 'function') return;
|
||||
return callback(new MongoError("cursor killed or timed out"), null);
|
||||
}
|
||||
|
||||
if(!Array.isArray(r.documents) || r.documents.length == 0) {
|
||||
if(typeof callback != 'function') return;
|
||||
return callback(new MongoError(f('invalid killCursors result returned for cursor id %s', cursorId)));
|
||||
}
|
||||
|
||||
// Return the result
|
||||
if(typeof callback == 'function') {
|
||||
callback(null, r.documents[0]);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute the kill cursor command
|
||||
if(pool && pool.isConnected()) {
|
||||
pool.write(query, {
|
||||
command: true
|
||||
}, killCursorCallback);
|
||||
}
|
||||
}
|
||||
|
||||
WireProtocol.prototype.getMore = function(bson, ns, cursorState, batchSize, raw, connection, options, callback) {
|
||||
options = options || {};
|
||||
// Build command namespace
|
||||
var parts = ns.split(/\./);
|
||||
// Command namespace
|
||||
var commandns = f('%s.$cmd', parts.shift());
|
||||
|
||||
// Create getMore command
|
||||
var getMoreCmd = {
|
||||
getMore: cursorState.cursorId,
|
||||
collection: parts.join('.'),
|
||||
batchSize: Math.abs(batchSize)
|
||||
}
|
||||
|
||||
if(cursorState.cmd.tailable
|
||||
&& typeof cursorState.cmd.maxAwaitTimeMS == 'number') {
|
||||
getMoreCmd.maxTimeMS = cursorState.cmd.maxAwaitTimeMS;
|
||||
}
|
||||
|
||||
// Build Query object
|
||||
var query = new Query(bson, commandns, getMoreCmd, {
|
||||
numberToSkip: 0, numberToReturn: -1
|
||||
, checkKeys: false, returnFieldSelector: null
|
||||
});
|
||||
|
||||
// Set query flags
|
||||
query.slaveOk = true;
|
||||
|
||||
// Query callback
|
||||
var queryCallback = function(err, result) {
|
||||
if(err) return callback(err);
|
||||
// Get the raw message
|
||||
var r = result.message;
|
||||
|
||||
// If we have a timed out query or a cursor that was killed
|
||||
if((r.responseFlags & (1 << 0)) != 0) {
|
||||
return callback(new MongoError("cursor killed or timed out"), null);
|
||||
}
|
||||
|
||||
// Raw, return all the extracted documents
|
||||
if(raw) {
|
||||
cursorState.documents = r.documents;
|
||||
cursorState.cursorId = r.cursorId;
|
||||
return callback(null, r.documents);
|
||||
}
|
||||
|
||||
// We have an error detected
|
||||
if(r.documents[0].ok == 0) {
|
||||
return callback(MongoError.create(r.documents[0]));
|
||||
}
|
||||
|
||||
// Ensure we have a Long valid cursor id
|
||||
var cursorId = typeof r.documents[0].cursor.id == 'number'
|
||||
? Long.fromNumber(r.documents[0].cursor.id)
|
||||
: r.documents[0].cursor.id;
|
||||
|
||||
// Set all the values
|
||||
cursorState.documents = r.documents[0].cursor.nextBatch;
|
||||
cursorState.cursorId = cursorId;
|
||||
|
||||
// Return the result
|
||||
callback(null, r.documents[0], r.connection);
|
||||
}
|
||||
|
||||
// Query options
|
||||
var queryOptions = { command: true };
|
||||
|
||||
// If we have a raw query decorate the function
|
||||
if(raw) {
|
||||
queryOptions.raw = raw;
|
||||
}
|
||||
|
||||
// Add the result field needed
|
||||
queryOptions.documentsReturnedIn = 'nextBatch';
|
||||
|
||||
// Check if we need to promote longs
|
||||
if(typeof cursorState.promoteLongs == 'boolean') {
|
||||
queryOptions.promoteLongs = cursorState.promoteLongs;
|
||||
}
|
||||
|
||||
if(typeof cursorState.promoteValues == 'boolean') {
|
||||
queryOptions.promoteValues = cursorState.promoteValues;
|
||||
}
|
||||
|
||||
if(typeof cursorState.promoteBuffers == 'boolean') {
|
||||
queryOptions.promoteBuffers = cursorState.promoteBuffers;
|
||||
}
|
||||
|
||||
// Write out the getMore command
|
||||
connection.write(query, queryOptions, queryCallback);
|
||||
}
|
||||
|
||||
WireProtocol.prototype.command = function(bson, ns, cmd, cursorState, topology, options) {
|
||||
options = options || {}
|
||||
// Check if this is a wire protocol command or not
|
||||
var wireProtocolCommand = typeof options.wireProtocolCommand == 'boolean' ? options.wireProtocolCommand : true;
|
||||
|
||||
// Establish type of command
|
||||
if(cmd.find && wireProtocolCommand) {
|
||||
// Create the find command
|
||||
var query = executeFindCommand(bson, ns, cmd, cursorState, topology, options)
|
||||
// Mark the cmd as virtual
|
||||
cmd.virtual = false;
|
||||
// Signal the documents are in the firstBatch value
|
||||
query.documentsReturnedIn = 'firstBatch';
|
||||
// Return the query
|
||||
return query;
|
||||
} else if(cursorState.cursorId != null) {
|
||||
return;
|
||||
} else if(cmd) {
|
||||
return setupCommand(bson, ns, cmd, cursorState, topology, options);
|
||||
} else {
|
||||
throw new MongoError(f("command %s does not return a cursor", JSON.stringify(cmd)));
|
||||
}
|
||||
}
|
||||
|
||||
// // Command
|
||||
// {
|
||||
// find: ns
|
||||
// , query: <object>
|
||||
// , limit: <n>
|
||||
// , fields: <object>
|
||||
// , skip: <n>
|
||||
// , hint: <string>
|
||||
// , explain: <boolean>
|
||||
// , snapshot: <boolean>
|
||||
// , batchSize: <n>
|
||||
// , returnKey: <boolean>
|
||||
// , maxScan: <n>
|
||||
// , min: <n>
|
||||
// , max: <n>
|
||||
// , showDiskLoc: <boolean>
|
||||
// , comment: <string>
|
||||
// , maxTimeMS: <n>
|
||||
// , raw: <boolean>
|
||||
// , readPreference: <ReadPreference>
|
||||
// , tailable: <boolean>
|
||||
// , oplogReplay: <boolean>
|
||||
// , noCursorTimeout: <boolean>
|
||||
// , awaitdata: <boolean>
|
||||
// , exhaust: <boolean>
|
||||
// , partial: <boolean>
|
||||
// }
|
||||
|
||||
// FIND/GETMORE SPEC
|
||||
// {
|
||||
// “find”: <string>,
|
||||
// “filter”: { ... },
|
||||
// “sort”: { ... },
|
||||
// “projection”: { ... },
|
||||
// “hint”: { ... },
|
||||
// “skip”: <int>,
|
||||
// “limit”: <int>,
|
||||
// “batchSize”: <int>,
|
||||
// “singleBatch”: <bool>,
|
||||
// “comment”: <string>,
|
||||
// “maxScan”: <int>,
|
||||
// “maxTimeMS”: <int>,
|
||||
// “max”: { ... },
|
||||
// “min”: { ... },
|
||||
// “returnKey”: <bool>,
|
||||
// “showRecordId”: <bool>,
|
||||
// “snapshot”: <bool>,
|
||||
// “tailable”: <bool>,
|
||||
// “oplogReplay”: <bool>,
|
||||
// “noCursorTimeout”: <bool>,
|
||||
// “awaitData”: <bool>,
|
||||
// “partial”: <bool>,
|
||||
// “$readPreference”: { ... }
|
||||
// }
|
||||
|
||||
//
|
||||
// Execute a find command
|
||||
var executeFindCommand = function(bson, ns, cmd, cursorState, topology, options) {
|
||||
// Ensure we have at least some options
|
||||
options = options || {};
|
||||
// Get the readPreference
|
||||
var readPreference = getReadPreference(cmd, options);
|
||||
// Set the optional batchSize
|
||||
cursorState.batchSize = cmd.batchSize || cursorState.batchSize;
|
||||
|
||||
// Build command namespace
|
||||
var parts = ns.split(/\./);
|
||||
// Command namespace
|
||||
var commandns = f('%s.$cmd', parts.shift());
|
||||
|
||||
// Build actual find command
|
||||
var findCmd = {
|
||||
find: parts.join('.')
|
||||
};
|
||||
|
||||
// I we provided a filter
|
||||
if(cmd.query) {
|
||||
// Check if the user is passing in the $query parameter
|
||||
if(cmd.query['$query']) {
|
||||
findCmd.filter = cmd.query['$query'];
|
||||
} else {
|
||||
findCmd.filter = cmd.query;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort value
|
||||
var sortValue = cmd.sort;
|
||||
|
||||
// Handle issue of sort being an Array
|
||||
if(Array.isArray(sortValue)) {
|
||||
var sortObject = {};
|
||||
|
||||
if(sortValue.length > 0 && !Array.isArray(sortValue[0])) {
|
||||
var sortDirection = sortValue[1];
|
||||
// Translate the sort order text
|
||||
if(sortDirection == 'asc') {
|
||||
sortDirection = 1;
|
||||
} else if(sortDirection == 'desc') {
|
||||
sortDirection = -1;
|
||||
}
|
||||
|
||||
// Set the sort order
|
||||
sortObject[sortValue[0]] = sortDirection;
|
||||
} else {
|
||||
for(var i = 0; i < sortValue.length; i++) {
|
||||
sortDirection = sortValue[i][1];
|
||||
// Translate the sort order text
|
||||
if(sortDirection == 'asc') {
|
||||
sortDirection = 1;
|
||||
} else if(sortDirection == 'desc') {
|
||||
sortDirection = -1;
|
||||
}
|
||||
|
||||
// Set the sort order
|
||||
sortObject[sortValue[i][0]] = sortDirection;
|
||||
}
|
||||
}
|
||||
|
||||
sortValue = sortObject;
|
||||
}
|
||||
|
||||
// Add sort to command
|
||||
if(cmd.sort) findCmd.sort = sortValue;
|
||||
// Add a projection to the command
|
||||
if(cmd.fields) findCmd.projection = cmd.fields;
|
||||
// Add a hint to the command
|
||||
if(cmd.hint) findCmd.hint = cmd.hint;
|
||||
// Add a skip
|
||||
if(cmd.skip) findCmd.skip = cmd.skip;
|
||||
// Add a limit
|
||||
if(cmd.limit) findCmd.limit = cmd.limit;
|
||||
|
||||
// Check if we wish to have a singleBatch
|
||||
if(cmd.limit < 0) {
|
||||
findCmd.limit = Math.abs(cmd.limit);
|
||||
findCmd.singleBatch = true;
|
||||
}
|
||||
|
||||
// Add a batchSize
|
||||
if(typeof cmd.batchSize == 'number') {
|
||||
if (cmd.batchSize < 0) {
|
||||
if (cmd.limit != 0 && Math.abs(cmd.batchSize) < Math.abs(cmd.limit)) {
|
||||
findCmd.limit = Math.abs(cmd.batchSize);
|
||||
}
|
||||
|
||||
findCmd.singleBatch = true;
|
||||
}
|
||||
|
||||
findCmd.batchSize = Math.abs(cmd.batchSize);
|
||||
}
|
||||
|
||||
// If we have comment set
|
||||
if(cmd.comment) findCmd.comment = cmd.comment;
|
||||
|
||||
// If we have maxScan
|
||||
if(cmd.maxScan) findCmd.maxScan = cmd.maxScan;
|
||||
|
||||
// If we have maxTimeMS set
|
||||
if(cmd.maxTimeMS) findCmd.maxTimeMS = cmd.maxTimeMS;
|
||||
|
||||
// If we have min
|
||||
if(cmd.min) findCmd.min = cmd.min;
|
||||
|
||||
// If we have max
|
||||
if(cmd.max) findCmd.max = cmd.max;
|
||||
|
||||
// If we have returnKey set
|
||||
if(cmd.returnKey) findCmd.returnKey = cmd.returnKey;
|
||||
|
||||
// If we have showDiskLoc set
|
||||
if(cmd.showDiskLoc) findCmd.showRecordId = cmd.showDiskLoc;
|
||||
|
||||
// If we have snapshot set
|
||||
if(cmd.snapshot) findCmd.snapshot = cmd.snapshot;
|
||||
|
||||
// If we have tailable set
|
||||
if(cmd.tailable) findCmd.tailable = cmd.tailable;
|
||||
|
||||
// If we have oplogReplay set
|
||||
if(cmd.oplogReplay) findCmd.oplogReplay = cmd.oplogReplay;
|
||||
|
||||
// If we have noCursorTimeout set
|
||||
if(cmd.noCursorTimeout) findCmd.noCursorTimeout = cmd.noCursorTimeout;
|
||||
|
||||
// If we have awaitData set
|
||||
if(cmd.awaitData) findCmd.awaitData = cmd.awaitData;
|
||||
if(cmd.awaitdata) findCmd.awaitData = cmd.awaitdata;
|
||||
|
||||
// If we have partial set
|
||||
if(cmd.partial) findCmd.partial = cmd.partial;
|
||||
|
||||
// If we have collation passed in
|
||||
if(cmd.collation) findCmd.collation = cmd.collation;
|
||||
|
||||
// If we have explain, we need to rewrite the find command
|
||||
// to wrap it in the explain command
|
||||
if(cmd.explain) {
|
||||
findCmd = {
|
||||
explain: findCmd
|
||||
}
|
||||
}
|
||||
|
||||
// Did we provide a readConcern
|
||||
if(cmd.readConcern) findCmd.readConcern = cmd.readConcern;
|
||||
|
||||
// Set up the serialize and ignoreUndefined fields
|
||||
var serializeFunctions = typeof options.serializeFunctions == 'boolean'
|
||||
? options.serializeFunctions : false;
|
||||
var ignoreUndefined = typeof options.ignoreUndefined == 'boolean'
|
||||
? options.ignoreUndefined : false;
|
||||
|
||||
// We have a Mongos topology, check if we need to add a readPreference
|
||||
if(topology.type == 'mongos'
|
||||
&& readPreference
|
||||
&& readPreference.preference != 'primary') {
|
||||
findCmd = {
|
||||
'$query': findCmd,
|
||||
'$readPreference': readPreference.toJSON()
|
||||
};
|
||||
}
|
||||
|
||||
// Build Query object
|
||||
var query = new Query(bson, commandns, findCmd, {
|
||||
numberToSkip: 0, numberToReturn: 1
|
||||
, checkKeys: false, returnFieldSelector: null
|
||||
, serializeFunctions: serializeFunctions, ignoreUndefined: ignoreUndefined
|
||||
});
|
||||
|
||||
// Set query flags
|
||||
query.slaveOk = readPreference.slaveOk();
|
||||
|
||||
// Return the query
|
||||
return query;
|
||||
}
|
||||
|
||||
//
|
||||
// Set up a command cursor
|
||||
var setupCommand = function(bson, ns, cmd, cursorState, topology, options) {
|
||||
// Set empty options object
|
||||
options = options || {}
|
||||
// Get the readPreference
|
||||
var readPreference = getReadPreference(cmd, options);
|
||||
|
||||
// Final query
|
||||
var finalCmd = {};
|
||||
for(var name in cmd) {
|
||||
finalCmd[name] = cmd[name];
|
||||
}
|
||||
|
||||
// Build command namespace
|
||||
var parts = ns.split(/\./);
|
||||
|
||||
// Serialize functions
|
||||
var serializeFunctions = typeof options.serializeFunctions == 'boolean'
|
||||
? options.serializeFunctions : false;
|
||||
|
||||
// Set up the serialize and ignoreUndefined fields
|
||||
var ignoreUndefined = typeof options.ignoreUndefined == 'boolean'
|
||||
? options.ignoreUndefined : false;
|
||||
|
||||
// We have a Mongos topology, check if we need to add a readPreference
|
||||
if(topology.type == 'mongos'
|
||||
&& readPreference
|
||||
&& readPreference.preference != 'primary') {
|
||||
finalCmd = {
|
||||
'$query': finalCmd,
|
||||
'$readPreference': readPreference.toJSON()
|
||||
};
|
||||
}
|
||||
|
||||
// Build Query object
|
||||
var query = new Query(bson, f('%s.$cmd', parts.shift()), finalCmd, {
|
||||
numberToSkip: 0, numberToReturn: -1
|
||||
, checkKeys: false, serializeFunctions: serializeFunctions
|
||||
, ignoreUndefined: ignoreUndefined
|
||||
});
|
||||
|
||||
// Set query flags
|
||||
query.slaveOk = readPreference.slaveOk();
|
||||
|
||||
// Return the query
|
||||
return query;
|
||||
}
|
||||
|
||||
module.exports = WireProtocol;
|
||||
+357
@@ -0,0 +1,357 @@
|
||||
"use strict";
|
||||
|
||||
var MongoError = require('../error');
|
||||
|
||||
// Wire command operation ids
|
||||
var OP_UPDATE = 2001;
|
||||
var OP_INSERT = 2002;
|
||||
var OP_DELETE = 2006;
|
||||
|
||||
var Insert = function(requestId, ismaster, bson, ns, documents, options) {
|
||||
// Basic options needed to be passed in
|
||||
if(ns == null) throw new MongoError("ns must be specified for query");
|
||||
if(!Array.isArray(documents) || documents.length == 0) throw new MongoError("documents array must contain at least one document to insert");
|
||||
|
||||
// Validate that we are not passing 0x00 in the collection name
|
||||
if(!!~ns.indexOf("\x00")) {
|
||||
throw new MongoError("namespace cannot contain a null character");
|
||||
}
|
||||
|
||||
// Set internal
|
||||
this.requestId = requestId;
|
||||
this.bson = bson;
|
||||
this.ns = ns;
|
||||
this.documents = documents;
|
||||
this.ismaster = ismaster;
|
||||
|
||||
// Ensure empty options
|
||||
options = options || {};
|
||||
|
||||
// Unpack options
|
||||
this.serializeFunctions = typeof options.serializeFunctions == 'boolean' ? options.serializeFunctions : false;
|
||||
this.ignoreUndefined = typeof options.ignoreUndefined == 'boolean' ? options.ignoreUndefined : false;
|
||||
this.checkKeys = typeof options.checkKeys == 'boolean' ? options.checkKeys : true;
|
||||
this.continueOnError = typeof options.continueOnError == 'boolean' ? options.continueOnError : false;
|
||||
// Set flags
|
||||
this.flags = this.continueOnError ? 1 : 0;
|
||||
}
|
||||
|
||||
// To Binary
|
||||
Insert.prototype.toBin = function() {
|
||||
// Contains all the buffers to be written
|
||||
var buffers = [];
|
||||
|
||||
// Header buffer
|
||||
var header = new Buffer(
|
||||
4 * 4 // Header
|
||||
+ 4 // Flags
|
||||
+ Buffer.byteLength(this.ns) + 1 // namespace
|
||||
);
|
||||
|
||||
// Add header to buffers
|
||||
buffers.push(header);
|
||||
|
||||
// Total length of the message
|
||||
var totalLength = header.length;
|
||||
|
||||
// Serialize all the documents
|
||||
for(var i = 0; i < this.documents.length; i++) {
|
||||
var buffer = this.bson.serialize(this.documents[i], {
|
||||
checkKeys: this.checkKeys,
|
||||
serializeFunctions: this.serializeFunctions,
|
||||
ignoreUndefined: this.ignoreUndefined,
|
||||
});
|
||||
|
||||
// Document is larger than maxBsonObjectSize, terminate serialization
|
||||
if(buffer.length > this.ismaster.maxBsonObjectSize) {
|
||||
throw new MongoError("Document exceeds maximum allowed bson size of " + this.ismaster.maxBsonObjectSize + " bytes");
|
||||
}
|
||||
|
||||
// Add to total length of wire protocol message
|
||||
totalLength = totalLength + buffer.length;
|
||||
// Add to buffer
|
||||
buffers.push(buffer);
|
||||
}
|
||||
|
||||
// Command is larger than maxMessageSizeBytes terminate serialization
|
||||
if(totalLength > this.ismaster.maxMessageSizeBytes) {
|
||||
throw new MongoError("Command exceeds maximum message size of " + this.ismaster.maxMessageSizeBytes + " bytes");
|
||||
}
|
||||
|
||||
// Add all the metadata
|
||||
var index = 0;
|
||||
|
||||
// Write header length
|
||||
header[index + 3] = (totalLength >> 24) & 0xff;
|
||||
header[index + 2] = (totalLength >> 16) & 0xff;
|
||||
header[index + 1] = (totalLength >> 8) & 0xff;
|
||||
header[index] = (totalLength) & 0xff;
|
||||
index = index + 4;
|
||||
|
||||
// Write header requestId
|
||||
header[index + 3] = (this.requestId >> 24) & 0xff;
|
||||
header[index + 2] = (this.requestId >> 16) & 0xff;
|
||||
header[index + 1] = (this.requestId >> 8) & 0xff;
|
||||
header[index] = (this.requestId) & 0xff;
|
||||
index = index + 4;
|
||||
|
||||
// No flags
|
||||
header[index + 3] = (0 >> 24) & 0xff;
|
||||
header[index + 2] = (0 >> 16) & 0xff;
|
||||
header[index + 1] = (0 >> 8) & 0xff;
|
||||
header[index] = (0) & 0xff;
|
||||
index = index + 4;
|
||||
|
||||
// Operation
|
||||
header[index + 3] = (OP_INSERT >> 24) & 0xff;
|
||||
header[index + 2] = (OP_INSERT >> 16) & 0xff;
|
||||
header[index + 1] = (OP_INSERT >> 8) & 0xff;
|
||||
header[index] = (OP_INSERT) & 0xff;
|
||||
index = index + 4;
|
||||
|
||||
// Flags
|
||||
header[index + 3] = (this.flags >> 24) & 0xff;
|
||||
header[index + 2] = (this.flags >> 16) & 0xff;
|
||||
header[index + 1] = (this.flags >> 8) & 0xff;
|
||||
header[index] = (this.flags) & 0xff;
|
||||
index = index + 4;
|
||||
|
||||
// Write collection name
|
||||
index = index + header.write(this.ns, index, 'utf8') + 1;
|
||||
header[index - 1] = 0;
|
||||
|
||||
// Return the buffers
|
||||
return buffers;
|
||||
}
|
||||
|
||||
var Update = function(requestId, ismaster, bson, ns, update, options) {
|
||||
// Basic options needed to be passed in
|
||||
if(ns == null) throw new MongoError("ns must be specified for query");
|
||||
|
||||
// Ensure empty options
|
||||
options = options || {};
|
||||
|
||||
// Set internal
|
||||
this.requestId = requestId;
|
||||
this.bson = bson;
|
||||
this.ns = ns;
|
||||
this.ismaster = ismaster;
|
||||
|
||||
// Unpack options
|
||||
this.serializeFunctions = typeof options.serializeFunctions == 'boolean' ? options.serializeFunctions : false;
|
||||
this.ignoreUndefined = typeof options.ignoreUndefined == 'boolean' ? options.ignoreUndefined : false;
|
||||
this.checkKeys = typeof options.checkKeys == 'boolean' ? options.checkKeys : false;
|
||||
|
||||
// Unpack the update document
|
||||
this.upsert = typeof update[0].upsert == 'boolean' ? update[0].upsert : false;
|
||||
this.multi = typeof update[0].multi == 'boolean' ? update[0].multi : false;
|
||||
this.q = update[0].q;
|
||||
this.u = update[0].u;
|
||||
|
||||
// Create flag value
|
||||
this.flags = this.upsert ? 1 : 0;
|
||||
this.flags = this.multi ? this.flags | 2 : this.flags;
|
||||
}
|
||||
|
||||
// To Binary
|
||||
Update.prototype.toBin = function() {
|
||||
// Contains all the buffers to be written
|
||||
var buffers = [];
|
||||
|
||||
// Header buffer
|
||||
var header = new Buffer(
|
||||
4 * 4 // Header
|
||||
+ 4 // ZERO
|
||||
+ Buffer.byteLength(this.ns) + 1 // namespace
|
||||
+ 4 // Flags
|
||||
);
|
||||
|
||||
// Add header to buffers
|
||||
buffers.push(header);
|
||||
|
||||
// Total length of the message
|
||||
var totalLength = header.length;
|
||||
|
||||
// Serialize the selector
|
||||
var selector = this.bson.serialize(this.q, {
|
||||
checkKeys: this.checkKeys,
|
||||
serializeFunctions: this.serializeFunctions,
|
||||
ignoreUndefined: this.ignoreUndefined,
|
||||
});
|
||||
buffers.push(selector);
|
||||
totalLength = totalLength + selector.length;
|
||||
|
||||
// Serialize the update
|
||||
var update = this.bson.serialize(this.u, {
|
||||
checkKeys: this.checkKeys,
|
||||
serializeFunctions: this.serializeFunctions,
|
||||
ignoreUndefined: this.ignoreUndefined,
|
||||
});
|
||||
buffers.push(update);
|
||||
totalLength = totalLength + update.length;
|
||||
|
||||
// Index in header buffer
|
||||
var index = 0;
|
||||
|
||||
// Write header length
|
||||
header[index + 3] = (totalLength >> 24) & 0xff;
|
||||
header[index + 2] = (totalLength >> 16) & 0xff;
|
||||
header[index + 1] = (totalLength >> 8) & 0xff;
|
||||
header[index] = (totalLength) & 0xff;
|
||||
index = index + 4;
|
||||
|
||||
// Write header requestId
|
||||
header[index + 3] = (this.requestId >> 24) & 0xff;
|
||||
header[index + 2] = (this.requestId >> 16) & 0xff;
|
||||
header[index + 1] = (this.requestId >> 8) & 0xff;
|
||||
header[index] = (this.requestId) & 0xff;
|
||||
index = index + 4;
|
||||
|
||||
// No flags
|
||||
header[index + 3] = (0 >> 24) & 0xff;
|
||||
header[index + 2] = (0 >> 16) & 0xff;
|
||||
header[index + 1] = (0 >> 8) & 0xff;
|
||||
header[index] = (0) & 0xff;
|
||||
index = index + 4;
|
||||
|
||||
// Operation
|
||||
header[index + 3] = (OP_UPDATE >> 24) & 0xff;
|
||||
header[index + 2] = (OP_UPDATE >> 16) & 0xff;
|
||||
header[index + 1] = (OP_UPDATE >> 8) & 0xff;
|
||||
header[index] = (OP_UPDATE) & 0xff;
|
||||
index = index + 4;
|
||||
|
||||
// Write ZERO
|
||||
header[index + 3] = (0 >> 24) & 0xff;
|
||||
header[index + 2] = (0 >> 16) & 0xff;
|
||||
header[index + 1] = (0 >> 8) & 0xff;
|
||||
header[index] = (0) & 0xff;
|
||||
index = index + 4;
|
||||
|
||||
// Write collection name
|
||||
index = index + header.write(this.ns, index, 'utf8') + 1;
|
||||
header[index - 1] = 0;
|
||||
|
||||
// Flags
|
||||
header[index + 3] = (this.flags >> 24) & 0xff;
|
||||
header[index + 2] = (this.flags >> 16) & 0xff;
|
||||
header[index + 1] = (this.flags >> 8) & 0xff;
|
||||
header[index] = (this.flags) & 0xff;
|
||||
index = index + 4;
|
||||
|
||||
// Return the buffers
|
||||
return buffers;
|
||||
}
|
||||
|
||||
var Remove = function(requestId, ismaster, bson, ns, remove, options) {
|
||||
// Basic options needed to be passed in
|
||||
if(ns == null) throw new MongoError("ns must be specified for query");
|
||||
|
||||
// Ensure empty options
|
||||
options = options || {};
|
||||
|
||||
// Set internal
|
||||
this.requestId = requestId;
|
||||
this.bson = bson;
|
||||
this.ns = ns;
|
||||
this.ismaster = ismaster;
|
||||
|
||||
// Unpack options
|
||||
this.serializeFunctions = typeof options.serializeFunctions == 'boolean' ? options.serializeFunctions : false;
|
||||
this.ignoreUndefined = typeof options.ignoreUndefined == 'boolean' ? options.ignoreUndefined : false;
|
||||
this.checkKeys = typeof options.checkKeys == 'boolean' ? options.checkKeys : false;
|
||||
|
||||
// Unpack the update document
|
||||
this.limit = typeof remove[0].limit == 'number' ? remove[0].limit : 1;
|
||||
this.q = remove[0].q;
|
||||
|
||||
// Create flag value
|
||||
this.flags = this.limit == 1 ? 1 : 0;
|
||||
}
|
||||
|
||||
// To Binary
|
||||
Remove.prototype.toBin = function() {
|
||||
// Contains all the buffers to be written
|
||||
var buffers = [];
|
||||
|
||||
// Header buffer
|
||||
var header = new Buffer(
|
||||
4 * 4 // Header
|
||||
+ 4 // ZERO
|
||||
+ Buffer.byteLength(this.ns) + 1 // namespace
|
||||
+ 4 // Flags
|
||||
);
|
||||
|
||||
// Add header to buffers
|
||||
buffers.push(header);
|
||||
|
||||
// Total length of the message
|
||||
var totalLength = header.length;
|
||||
|
||||
// Serialize the selector
|
||||
var selector = this.bson.serialize(this.q, {
|
||||
checkKeys: this.checkKeys,
|
||||
serializeFunctions: this.serializeFunctions,
|
||||
ignoreUndefined: this.ignoreUndefined,
|
||||
});
|
||||
buffers.push(selector);
|
||||
totalLength = totalLength + selector.length;
|
||||
|
||||
// Index in header buffer
|
||||
var index = 0;
|
||||
|
||||
// Write header length
|
||||
header[index + 3] = (totalLength >> 24) & 0xff;
|
||||
header[index + 2] = (totalLength >> 16) & 0xff;
|
||||
header[index + 1] = (totalLength >> 8) & 0xff;
|
||||
header[index] = (totalLength) & 0xff;
|
||||
index = index + 4;
|
||||
|
||||
// Write header requestId
|
||||
header[index + 3] = (this.requestId >> 24) & 0xff;
|
||||
header[index + 2] = (this.requestId >> 16) & 0xff;
|
||||
header[index + 1] = (this.requestId >> 8) & 0xff;
|
||||
header[index] = (this.requestId) & 0xff;
|
||||
index = index + 4;
|
||||
|
||||
// No flags
|
||||
header[index + 3] = (0 >> 24) & 0xff;
|
||||
header[index + 2] = (0 >> 16) & 0xff;
|
||||
header[index + 1] = (0 >> 8) & 0xff;
|
||||
header[index] = (0) & 0xff;
|
||||
index = index + 4;
|
||||
|
||||
// Operation
|
||||
header[index + 3] = (OP_DELETE >> 24) & 0xff;
|
||||
header[index + 2] = (OP_DELETE >> 16) & 0xff;
|
||||
header[index + 1] = (OP_DELETE >> 8) & 0xff;
|
||||
header[index] = (OP_DELETE) & 0xff;
|
||||
index = index + 4;
|
||||
|
||||
// Write ZERO
|
||||
header[index + 3] = (0 >> 24) & 0xff;
|
||||
header[index + 2] = (0 >> 16) & 0xff;
|
||||
header[index + 1] = (0 >> 8) & 0xff;
|
||||
header[index] = (0) & 0xff;
|
||||
index = index + 4;
|
||||
|
||||
// Write collection name
|
||||
index = index + header.write(this.ns, index, 'utf8') + 1;
|
||||
header[index - 1] = 0;
|
||||
|
||||
// Write ZERO
|
||||
header[index + 3] = (this.flags >> 24) & 0xff;
|
||||
header[index + 2] = (this.flags >> 16) & 0xff;
|
||||
header[index + 1] = (this.flags >> 8) & 0xff;
|
||||
header[index] = (this.flags) & 0xff;
|
||||
index = index + 4;
|
||||
|
||||
// Return the buffers
|
||||
return buffers;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
Insert: Insert
|
||||
, Update: Update
|
||||
, Remove: Remove
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
"use strict"
|
||||
|
||||
var ReadPreference = require('../topologies/read_preference'),
|
||||
MongoError = require('../error');
|
||||
|
||||
var getReadPreference = function(cmd, options) {
|
||||
// Default to command version of the readPreference
|
||||
var readPreference = cmd.readPreference || new ReadPreference('primary');
|
||||
// If we have an option readPreference override the command one
|
||||
if(options.readPreference) {
|
||||
readPreference = options.readPreference;
|
||||
}
|
||||
|
||||
if(typeof readPreference == 'string') {
|
||||
readPreference = new ReadPreference(readPreference);
|
||||
}
|
||||
|
||||
if(!(readPreference instanceof ReadPreference)) {
|
||||
throw new MongoError('readPreference must be a ReadPreference instance');
|
||||
}
|
||||
|
||||
return readPreference;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getReadPreference: getReadPreference
|
||||
}
|
||||
Reference in New Issue
Block a user