From 915edd69dcaf532dfe02a6ba083f511ed02996a3 Mon Sep 17 00:00:00 2001 From: sstent Date: Mon, 7 Jan 2013 15:37:18 -0500 Subject: [PATCH] removing ned for second mongo --- app.js | 8 +- node_modules/mongoskin/History.md | 14 + node_modules/mongoskin/Readme.md | 40 +- node_modules/mongoskin/examples/update.js | 2 +- .../mongoskin/node_modules/mongodb/index.js | 0 .../node_modules/mongodb/lib/mongodb/admin.js | 34 +- .../mongodb/lib/mongodb/collection.js | 320 ++- .../lib/mongodb/commands/db_command.js | 64 +- .../lib/mongodb/connection/connection.js | 10 +- .../lib/mongodb/connection/connection_pool.js | 20 +- .../lib/mongodb/connection/repl_set.js | 615 +++-- .../mongodb/lib/mongodb/connection/server.js | 130 +- .../connection/strategies/ping_strategy.js | 10 +- .../strategies/statistics_strategy.js | 2 +- .../mongodb/lib/mongodb/cursor.js | 212 +- .../mongodb/lib/mongodb/cursorstream.js | 24 +- .../node_modules/mongodb/lib/mongodb/db.js | 760 +++--- .../mongodb/lib/mongodb/gridfs/grid.js | 29 +- .../mongodb/lib/mongodb/gridfs/gridstore.js | 73 +- .../mongodb/lib/mongodb/gridfs/readstream.js | 18 +- .../node_modules/mongodb/lib/mongodb/index.js | 132 +- .../node_modules/mongodb/lib/mongodb/utils.js | 23 + .../mongodb/node_modules/bson/ext/bson.cc | 2036 +++++++++-------- .../mongodb/node_modules/bson/ext/bson.h | 546 ++--- .../mongodb/node_modules/bson/package.json | 7 +- .../node_modules/mongodb/package.json | 17 +- node_modules/mongoskin/package.json | 16 +- 27 files changed, 2841 insertions(+), 2321 deletions(-) mode change 100644 => 100755 node_modules/mongoskin/node_modules/mongodb/index.js mode change 100644 => 100755 node_modules/mongoskin/node_modules/mongodb/package.json diff --git a/app.js b/app.js index 4836f66..a82c7f7 100644 --- a/app.js +++ b/app.js @@ -4,9 +4,9 @@ */ var fs = require('fs'); var path = require('path'); -var mongo = require('mongodb'); +var mongo = require('mongoskin'); var BSON = mongo.BSONPure; -var db = require('mongoskin').db('localhost:27017/test'); +var db = mongo.db('localhost:27017/test'); var testcollection = db.collection('testcollection'); var exercisecollection = db.collection('exercisecollection'); var expressocollection = db.collection('expressocollection'); @@ -16,8 +16,8 @@ var formidable = require('formidable'); var xml2js = require('xml2js'); var parser = new xml2js.Parser(); var dateFormat = require('dateformat'); -var nodeGarminConnect = require('./node-garmin-connect'); -var garminClient = new nodeGarminConnect(); +//var nodeGarminConnect = require('./node-garmin-connect'); +//var garminClient = new nodeGarminConnect(); var app = require('http').createServer(function handler(request, response) { diff --git a/node_modules/mongoskin/History.md b/node_modules/mongoskin/History.md index 6a6ee24..25af581 100644 --- a/node_modules/mongoskin/History.md +++ b/node_modules/mongoskin/History.md @@ -1,3 +1,17 @@ + +0.5.0 / 2012-12-29 +================== + + * fixed unsafe mode warnning log + * Merge pull request #84 from kingpearl/master + * MongoDB 1.2.x support + * Merge pull request #73 from jockster/master + * Merge pull request #75 from voke/patch-1 + * Fix typo + * fixed bind() test cases; + * Minor error in readme. Now updated + * Updated readme according to issue #72 + 0.3.4 / 2011-03-24 * fix global leaks diff --git a/node_modules/mongoskin/Readme.md b/node_modules/mongoskin/Readme.md index 4156d11..ba39ce9 100644 --- a/node_modules/mongoskin/Readme.md +++ b/node_modules/mongoskin/Readme.md @@ -1,6 +1,6 @@ -# mongoskin +# mongoskin [![Build Status](https://secure.travis-ci.org/kissjs/node-mongoskin.png)](http://travis-ci.org/kissjs/node-mongoskin) -[![Build Status](https://secure.travis-ci.org/kissjs/node-mongoskin.png)](http://travis-ci.org/kissjs/node-mongoskin) +![logo](https://raw.github.com/kissjs/node-mongoskin/master/logo.png) This project is a wrapper of [node-mongodb-native](https://github.com/mongodb/node-mongodb-native). The api is same to node-mongodb-native, please see the [document](http://mongodb.github.com/node-mongodb-native/) first. @@ -14,11 +14,12 @@ The api is same to node-mongodb-native, please see the [document](http://mongodb * >= 0.9.8 < 1.0.0: mongodb have bug, it will throw a `TypeError: object is not a function` when connection open error. -* 1.0.x -* 1.1.x +* 1.0.x +* 1.1.x +* 1.2.x ```bash -$ make test-version +$ make test ``` @@ -129,7 +130,7 @@ mongo.db('localhost:27017/testdb').collection('blog').find().toArray(function (e Server options and BSON options -------- You can also set `auto_reconnect` options querystring. -And native_parser options will automatically set if native_parser is avariable. +And native_parser options will automatically set if native_parser is available. ```js var mongo = require('mongoskin'); @@ -172,6 +173,10 @@ It is very useful if you want to use MVC patterns with nodejs and mongodb. You can also invoke collection by properties after bind, it could simplfy your `require`. +To keep your code in line with DRY principles, it's possible to create your own +data layer by for example, setting up your own validators and/or default values +inside the MVC methods as shown below in the config example + ```js db.bind('posts', { findTop10 : function (fn) { @@ -180,8 +185,26 @@ db.bind('posts', { removeTagWith : function (tag, fn) { this.remove({tags:tag},fn); } + } }); +db.bind('settings', { + + getAll: function(user, fn) { + + this.find({user: user}).toArray(function(err, settings) { + + // We want to set a default currency from within our app instead of storing it + // for every user + settings.currency = (typeof settings.currency !== "undefined") ? settings.currency : 'USD'; + + fn(err, settings); + + }); + } +}); + + db.bind('comments'); db.collection('posts').removeTagWith('delete', function (err, replies) { @@ -245,7 +268,8 @@ var db = mongoskin.db([ '192.168.0.2:27017/?auto_reconnect=true', '192.168.0.3:27017/?auto_reconnect=true' ], { - database: 'testdb' + database: 'testdb', + safe: true }, { connectArbiter: false, socketOptions: { @@ -587,6 +611,8 @@ collection.group([], {}, {"count":0}, "function (obj, prev) { prev.count++; }", Options: upsert - true/false (perform upsert operation) multi - true/false (update all documents matching spec) + strict - true/false (perform check if the operation failed, required extra call to db) + Deprecated Options: safe - true/false (perform check if the operation failed, required extra call to db) **/ ``` diff --git a/node_modules/mongoskin/examples/update.js b/node_modules/mongoskin/examples/update.js index 9a048ce..080d84e 100644 --- a/node_modules/mongoskin/examples/update.js +++ b/node_modules/mongoskin/examples/update.js @@ -3,7 +3,7 @@ var articles = db.collection('articles'); articles.insert({foo: 'bar', val: 'val1'}, function(err, result) { console.log(result); - articles.update({foo:'bar'}, {foo: 'bar', val:'val2'}, {safe: true}, function(err, result) { + articles.update({foo:'bar'}, {foo: 'bar', val:'val2'}, {strict: true}, function(err, result) { console.log(result); articles.find({foo: 'bar'}).toArray(function(err, docs){ diff --git a/node_modules/mongoskin/node_modules/mongodb/index.js b/node_modules/mongoskin/node_modules/mongodb/index.js old mode 100644 new mode 100755 diff --git a/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/admin.js b/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/admin.js index f02369c..2183cf9 100644 --- a/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/admin.js +++ b/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/admin.js @@ -21,7 +21,7 @@ function Admin(db) { * Retrieve the server information for the current * instance of the db client * - * @param {Function} callback Callback function of format `function(err, result) {}`. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from buildInfo or null if an error occured. * @return {null} Returns no result * @api public */ @@ -33,7 +33,7 @@ Admin.prototype.buildInfo = function(callback) { * Retrieve the server information for the current * instance of the db client * - * @param {Function} callback Callback function of format `function(err, result) {}`. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from serverInfo or null if an error occured. * @return {null} Returns no result * @api private */ @@ -47,7 +47,7 @@ Admin.prototype.serverInfo = function(callback) { /** * Retrieve this db's server status. * - * @param {Function} callback returns the server status. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from serverStatus or null if an error occured. * @return {null} * @api public */ @@ -67,7 +67,7 @@ Admin.prototype.serverStatus = function(callback) { /** * Retrieve the current profiling Level for MongoDB * - * @param {Function} callback Callback function of format `function(err, result) {}`. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from profilingLevel or null if an error occured. * @return {null} Returns no result * @api public */ @@ -92,7 +92,7 @@ Admin.prototype.profilingLevel = function(callback) { /** * Ping the MongoDB server and retrieve results * - * @param {Function} callback Callback function of format `function(err, result) {}`. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from ping or null if an error occured. * @return {null} Returns no result * @api public */ @@ -109,7 +109,7 @@ Admin.prototype.ping = function(options, callback) { * * @param {String} username The user name for the authentication. * @param {String} password The password for the authentication. - * @param {Function} callback Callback function of format `function(err, result) {}`. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from authenticate or null if an error occured. * @return {null} Returns no result * @api public */ @@ -123,7 +123,7 @@ Admin.prototype.authenticate = function(username, password, callback) { * Logout current authenticated user * * @param {Object} [options] Optional parameters to the command. - * @param {Function} callback Callback function of format `function(err, result) {}`. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from logout or null if an error occured. * @return {null} Returns no result * @api public */ @@ -143,7 +143,7 @@ Admin.prototype.logout = function(callback) { * @param {String} username The user name for the authentication. * @param {String} password The password for the authentication. * @param {Object} [options] additional options during update. - * @param {Function} callback Callback function of format `function(err, result) {}`. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from addUser or null if an error occured. * @return {null} Returns no result * @api public */ @@ -167,7 +167,7 @@ Admin.prototype.addUser = function(username, password, options, callback) { * * @param {String} username The user name for the authentication. * @param {Object} [options] additional options during update. - * @param {Function} callback Callback function of format `function(err, result) {}`. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from removeUser or null if an error occured. * @return {null} Returns no result * @api public */ @@ -187,7 +187,7 @@ Admin.prototype.removeUser = function(username, options, callback) { * Set the current profiling level of MongoDB * * @param {String} level The new profiling level (off, slow_only, all) - * @param {Function} callback Callback function of format `function(err, result) {}`. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from setProfilingLevel or null if an error occured. * @return {null} Returns no result * @api public */ @@ -221,15 +221,13 @@ Admin.prototype.setProfilingLevel = function(level, callback) { /** * Retrive the current profiling information for MongoDB * - * @param {Function} callback Callback function of format `function(err, result) {}`. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from profilingInfo or null if an error occured. * @return {null} Returns no result * @api public */ Admin.prototype.profilingInfo = function(callback) { try { - new Cursor(this.db, new Collection(this.db, DbCommand.SYSTEM_PROFILE_COLLECTION), {}, null, null, null - , null, null, null, null, null, null, null, null, null, null - , null, null, null, null, null, null, null, null, 'admin').toArray(function(err, items) { + new Cursor(this.db, new Collection(this.db, DbCommand.SYSTEM_PROFILE_COLLECTION), {}, {}, {dbName: 'admin'}).toArray(function(err, items) { return callback(err, items); }); } catch (err) { @@ -242,7 +240,7 @@ Admin.prototype.profilingInfo = function(callback) { * * @param {Object} command A command object `{ping:1}`. * @param {Object} [options] Optional parameters to the command. - * @param {Function} callback Callback function of format `function(err, result) {}`. + * @param {Function} callback this will be called after executing this method. The command always return the whole result of the command as the second parameter. * @return {null} Returns no result * @api public */ @@ -264,7 +262,7 @@ Admin.prototype.command = function(command, options, callback) { * * @param {String} collectionName The name of the collection to validate. * @param {Object} [options] Optional parameters to the command. - * @param {Function} callback Callback function of format `function(err, result) {}`. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from validateCollection or null if an error occured. * @return {null} Returns no result * @api public */ @@ -304,7 +302,7 @@ Admin.prototype.validateCollection = function(collectionName, options, callback) /** * List the available databases * - * @param {Function} callback Callback function of format `function(err, result) {}`. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from listDatabases or null if an error occured. * @return {null} Returns no result * @api public */ @@ -319,7 +317,7 @@ Admin.prototype.listDatabases = function(callback) { /** * Get ReplicaSet status * - * @param {Function} callback returns the replica set status (if available). + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from replSetGetStatus or null if an error occured. * @return {null} * @api public */ diff --git a/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/collection.js b/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/collection.js index b66f279..60c6030 100644 --- a/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/collection.js +++ b/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/collection.js @@ -67,13 +67,19 @@ function Collection (db, collectionName, pkFactory, options) { * Inserts a single document or a an array of documents into MongoDB. * * Options - * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. +* - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write + * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) + * - **fsync**, (Boolean, default:false) write waits for fsync before returning + * - **journal**, (Boolean, default:false) write waits for journal sync before returning * - **continueOnError/keepGoing** {Boolean, default:false}, keep inserting documents even if one document has an error, *mongodb 1.9.1 >*. * - **serializeFunctions** {Boolean, default:false}, serialize functions on the document. + * + * Deprecated Options + * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. * * @param {Array|Object} docs * @param {Object} [options] optional options for insert command - * @param {Function} [callback] optional callback for the function, must be provided when using `safe` mode + * @param {Function} [callback] optional callback for the function, must be provided when using a writeconcern * @return {null} * @api public */ @@ -112,12 +118,18 @@ var checkCollectionName = function checkCollectionName (collectionName) { * Removes documents specified by `selector` from the db. * * Options - * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. +* - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write + * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) + * - **fsync**, (Boolean, default:false) write waits for fsync before returning + * - **journal**, (Boolean, default:false) write waits for journal sync before returning * - **single** {Boolean, default:false}, removes the first document found. + * + * Deprecated Options + * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. * * @param {Object} [selector] optional select, no selector is equivalent to removing all documents. * @param {Object} [options] additional options during remove. - * @param {Function} [callback] must be provided if you performing a safe remove + * @param {Function} [callback] must be provided if you performing a remove with a writeconcern * @return {null} * @api public */ @@ -153,14 +165,9 @@ Collection.prototype.remove = function remove(selector, options, callback) { , flags); var self = this; - var errorOptions = options.safe != null ? options.safe : null; - errorOptions = errorOptions == null && this.opts.safe != null ? this.opts.safe : errorOptions; - errorOptions = errorOptions == null && this.db.safe != null ? this.db.safe : errorOptions; - - // If we have a write concern set and no callback throw error - if(errorOptions && errorOptions['safe'] != false && typeof callback !== 'function') throw new Error("safe cannot be used without a callback"); + var errorOptions = _getWriteConcern(self, options, callback); // Execute the command, do not add a callback as it's async - if(errorOptions && errorOptions != false) { + if(_hasWriteConcern(errorOptions) && typeof callback == 'function') { // Insert options var commandOptions = {read:false}; // If we have safe set set async to false @@ -188,6 +195,8 @@ Collection.prototype.remove = function remove(selector, options, callback) { callback(null, error[0].n); } }); + } else if(_hasWriteConcern(errorOptions) && callback == null) { + throw new Error("Cannot use a writeConcern without a provided callback"); } else { var result = this.db._executeRemoveCommand(deleteCommand); // If no callback just return @@ -271,7 +280,7 @@ var insertAll = function insertAll (self, docs, options, callback) { , dbName + "." + self.collectionName, true, insertFlags); // Add the documents and decorate them with id's if they have none - for (var index = 0, len = docs.length; index < len; ++index) { + for(var index = 0, len = docs.length; index < len; ++index) { var doc = docs[index]; // Add id to each document if it's not already defined @@ -283,17 +292,11 @@ var insertAll = function insertAll (self, docs, options, callback) { } // Collect errorOptions - var errorOptions = options.safe != null ? options.safe : null; - errorOptions = errorOptions == null && self.opts.safe != null ? self.opts.safe : errorOptions; - errorOptions = errorOptions == null && self.db.safe != null ? self.db.safe : errorOptions; - - // If we have a write concern set and no callback throw error - if(errorOptions && errorOptions['safe'] != false && typeof callback !== 'function') throw new Error("safe cannot be used without a callback"); - + var errorOptions = _getWriteConcern(self, options, callback); // Default command options var commandOptions = {}; // If safe is defined check for error message - if(errorOptions && errorOptions != false) { + if(_hasWriteConcern(errorOptions) && typeof callback == 'function') { // Insert options commandOptions['read'] = false; // If we have safe set set async to false @@ -322,8 +325,11 @@ var insertAll = function insertAll (self, docs, options, callback) { callback(null, docs); } }); + } else if(_hasWriteConcern(errorOptions) && callback == null) { + throw new Error("Cannot use a writeConcern without a provided callback"); } else { - var result = self.db._executeInsertCommand(insertCommand, commandOptions, callback); + // Execute the call without a write concern + var result = self.db._executeInsertCommand(insertCommand, commandOptions); // If no callback just return if(!callback) return; // If error return error @@ -340,6 +346,12 @@ var insertAll = function insertAll (self, docs, options, callback) { * operators and update instead for more efficient operations. * * Options +* - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write + * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) + * - **fsync**, (Boolean, default:false) write waits for fsync before returning + * - **journal**, (Boolean, default:false) write waits for journal sync before returning + * + * Deprecated Options * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. * * @param {Object} [doc] the document to save @@ -352,16 +364,15 @@ Collection.prototype.save = function save(doc, options, callback) { if('function' === typeof options) callback = options, options = null; if(options == null) options = {}; if(!('function' === typeof callback)) callback = null; - - var errorOptions = options.safe != null ? options.safe : false; - errorOptions = errorOptions == null && this.opts.safe != null ? this.opts.safe : errorOptions; // Extract the id, if we have one we need to do a update command var id = doc['_id']; + var commandOptions = _getWriteConcern(this, options, callback); if(id) { - this.update({ _id: id }, doc, { upsert: true, safe: errorOptions }, callback); + commandOptions.upsert = true; + this.update({ _id: id }, doc, commandOptions, callback); } else { - this.insert(doc, { safe: errorOptions }, callback && function (err, docs) { + this.insert(doc, commandOptions, callback && function (err, docs) { if (err) return callback(err, null); if (Array.isArray(docs)) { @@ -377,15 +388,21 @@ Collection.prototype.save = function save(doc, options, callback) { * Updates documents. * * Options - * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. +* - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write + * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) + * - **fsync**, (Boolean, default:false) write waits for fsync before returning + * - **journal**, (Boolean, default:false) write waits for journal sync before returning * - **upsert** {Boolean, default:false}, perform an upsert operation. * - **multi** {Boolean, default:false}, update all documents matching the selector. * - **serializeFunctions** {Boolean, default:false}, serialize functions on the document. + * + * Deprecated Options + * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. * * @param {Object} selector the query to select the document/documents to be updated * @param {Object} document the fields/vals to be updated, or in the case of an upsert operation, inserted. * @param {Object} [options] additional options during update. - * @param {Function} [callback] must be provided if you performing a safe update + * @param {Function} [callback] must be provided if you performing an update with a writeconcern * @return {null} * @api public */ @@ -418,21 +435,15 @@ Collection.prototype.update = function update(selector, document, options, callb var self = this; // Unpack the error options if any - var errorOptions = (options && options.safe != null) ? options.safe : null; - errorOptions = errorOptions == null && this.opts.safe != null ? this.opts.safe : errorOptions; - errorOptions = errorOptions == null && this.db.safe != null ? this.db.safe : errorOptions; - - // If we have a write concern set and no callback throw error - if(errorOptions && errorOptions['safe'] != false && typeof callback !== 'function') throw new Error("safe cannot be used without a callback"); - - // If we are executing in safe mode or safe both the update and the safe command must happen on the same line - if(errorOptions && errorOptions != false) { + var errorOptions = _getWriteConcern(this, options, callback); + // If safe is defined check for error message + if(_hasWriteConcern(errorOptions) && typeof callback == 'function') { // Insert options var commandOptions = {read:false}; // If we have safe set set async to false if(errorOptions == null) commandOptions['async'] = true; // Set safe option - commandOptions['safe'] = true; + commandOptions['safe'] = errorOptions; // If we have an error option if(typeof errorOptions == 'object') { var keys = Object.keys(errorOptions); @@ -455,6 +466,8 @@ Collection.prototype.update = function update(selector, document, options, callb callback(null, error[0].n, error[0]); } }); + } else if(_hasWriteConcern(errorOptions) && callback == null) { + throw new Error("Cannot use a writeConcern without a provided callback"); } else { // Execute update var result = this.db._executeUpdateCommand(updateCommand); @@ -478,7 +491,7 @@ Collection.prototype.update = function update(selector, document, options, callb * @param {String} key key to run distinct against. * @param {Object} [query] option query to narrow the returned objects. * @param {Object} [options] additional options during update. - * @param {Function} callback must be provided. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from distinct or null if an error occured. * @return {null} * @api public */ @@ -516,7 +529,7 @@ Collection.prototype.distinct = function distinct(key, query, options, callback) * * @param {Object} [query] query to filter by before performing count. * @param {Object} [options] additional options during count. - * @param {Function} callback must be provided. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the count method or null if an error occured. * @return {null} * @api public */ @@ -567,7 +580,7 @@ Collection.prototype.count = function count (query, options, callback) { /** * Drop the collection * - * @param {Function} [callback] provide a callback to be notified when command finished executing + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the drop method or null if an error occured. * @return {null} * @api public */ @@ -579,16 +592,22 @@ Collection.prototype.drop = function drop(callback) { * Find and update a document. * * Options - * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. +* - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write + * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) + * - **fsync**, (Boolean, default:false) write waits for fsync before returning + * - **journal**, (Boolean, default:false) write waits for journal sync before returning * - **remove** {Boolean, default:false}, set to true to remove the object before returning. * - **upsert** {Boolean, default:false}, perform an upsert operation. * - **new** {Boolean, default:false}, set to true if you want to return the modified object rather than the original. Ignored for remove. + * + * Deprecated Options + * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. * * @param {Object} query query object to locate the object to modify * @param {Array} sort - if multiple docs match, choose the first one in the specified sort order as the object to manipulate * @param {Object} doc - the fields/vals to be updated * @param {Object} [options] additional options during update. - * @param {Function} [callback] returns results. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the findAndModify method or null if an error occured. * @return {null} * @api public */ @@ -627,9 +646,7 @@ Collection.prototype.findAndModify = function findAndModify (query, sort, doc, o } // Unpack the error options if any - var errorOptions = (options && options.safe != null) ? options.safe : null; - errorOptions = errorOptions == null && this.opts.safe != null ? this.opts.safe : errorOptions; - errorOptions = errorOptions == null && this.db.safe != null ? this.db.safe : errorOptions; + var errorOptions = _getWriteConcern(this, options, callback); // If we have j, w or something else do the getLast Error path if(errorOptions != null && typeof errorOptions == 'object') { @@ -676,12 +693,18 @@ Collection.prototype.findAndModify = function findAndModify (query, sort, doc, o * Find and remove a document * * Options +* - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write + * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) + * - **fsync**, (Boolean, default:false) write waits for fsync before returning + * - **journal**, (Boolean, default:false) write waits for journal sync before returning + * + * Deprecated Options * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. * * @param {Object} query query object to locate the object to modify * @param {Array} sort - if multiple docs match, choose the first one in the specified sort order as the object to manipulate * @param {Object} [options] additional options during update. - * @param {Function} [callback] returns results. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the findAndRemove method or null if an error occured. * @return {null} * @api public */ @@ -696,10 +719,11 @@ Collection.prototype.findAndRemove = function(query, sort, options, callback) { this.findAndModify(query, sort, null, options, callback); } -var testForFields = {'limit' : 1, 'sort' : 1, 'fields' : 1, 'skip' : 1, 'hint' : 1, 'explain' : 1, 'snapshot' : 1 - , 'timeout' : 1, 'tailable' : 1, 'batchSize' : 1, 'raw' : 1, 'read' : 1 - , 'returnKey' : 1, 'maxScan' : 1, 'min' : 1, 'max' : 1, 'showDiskLoc' : 1, 'comment' : 1, 'dbName' : 1, 'exhaust': 1 - , 'tailableRetryInterval': 1}; +var testForFields = { + limit: 1, sort: 1, fields:1, skip: 1, hint: 1, explain: 1, snapshot: 1, timeout: 1, tailable: 1, tailableRetryInterval: 1 + , numberOfRetries: 1, awaitdata: 1, exhaust: 1, batchSize: 1, returnKey: 1, maxScan: 1, min: 1, max: 1, showDiskLoc: 1 + , comment: 1, raw: 1, readPreference: 1, numberOfRetries: 1, partial: 1, read: 1, dbName: 1 +}; /** * Creates a cursor for a query that can be used to iterate over results from MongoDB @@ -741,7 +765,7 @@ var testForFields = {'limit' : 1, 'sort' : 1, 'fields' : 1, 'skip' : 1, 'hint' : * * @param {Object} query query object to locate the object to modify * @param {Object} [options] additional options during update. - * @param {Function} [callback] optional callback for cursor. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the find method or null if an error occured. * @return {Cursor} returns a cursor to the query * @api public */ @@ -860,15 +884,9 @@ Collection.prototype.find = function find () { // callback for backward compatibility if(callback) { // TODO refactor Cursor args - callback(null, new Cursor(this.db, this, selector, fields, o.skip, o.limit - , o.sort, o.hint, o.explain, o.snapshot, o.timeout, o.tailable, o.batchSize - , o.slaveOk, o.raw, o.read, o.returnKey, o.maxScan, o.min, o.max, o.showDiskLoc, o.comment, o.awaitdata - , o.numberOfRetries, o.dbName, o.tailableRetryInterval, o.exhaust)); + callback(null, new Cursor(this.db, this, selector, fields, o)); } else { - return new Cursor(this.db, this, selector, fields, o.skip, o.limit - , o.sort, o.hint, o.explain, o.snapshot, o.timeout, o.tailable, o.batchSize - , o.slaveOk, o.raw, o.read, o.returnKey, o.maxScan, o.min, o.max, o.showDiskLoc, o.comment, o.awaitdata - , o.numberOfRetries, o.dbName, o.tailableRetryInterval, o.exhaust); + return new Cursor(this.db, this, selector, fields, o); } }; @@ -941,7 +959,7 @@ var normalizeHintField = function normalizeHintField(hint) { * * @param {Object} query query object to locate the object to modify * @param {Object} [options] additional options during update. - * @param {Function} [callback] optional callback for cursor. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the findOne method or null if an error occured. * @return {Cursor} returns a cursor to the query * @api public */ @@ -951,10 +969,9 @@ Collection.prototype.findOne = function findOne () { var callback = args.pop(); var cursor = this.find.apply(this, args).limit(-1).batchSize(1); // Return the item - cursor.toArray(function(err, items) { - if(err != null) return callback(err instanceof Error ? err : self.db.wrap(new Error(err)), null); - if(items.length == 1) return callback(null, items[0]); - callback(null, null); + cursor.nextObject(function(err, item) { + if(err != null) return callback(err instanceof Error ? err : self.db.wrap(err), null); + callback(null, item); }); }; @@ -962,7 +979,10 @@ Collection.prototype.findOne = function findOne () { * Creates an index on the collection. * * Options - * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a +* - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write + * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) + * - **fsync**, (Boolean, default:false) write waits for fsync before returning + * - **journal**, (Boolean, default:false) write waits for journal sync before returning * - **unique** {Boolean, default:false}, creates an unique index. * - **sparse** {Boolean, default:false}, creates a sparse index. * - **background** {Boolean, default:false}, creates the index in the background, yielding whenever possible. @@ -972,10 +992,13 @@ Collection.prototype.findOne = function findOne () { * - **v** {Number}, specify the format version of the indexes. * - **expireAfterSeconds** {Number}, allows you to expire data on indexes applied to a data (MongoDB 2.2 or higher) * - **name** {String}, override the autogenerated index name (useful if the resulting name is larger than 128 bytes) + * + * Deprecated Options + * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. * * @param {Object} fieldOrSpec fieldOrSpec that defines the index. * @param {Object} [options] additional options during update. - * @param {Function} callback for results. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the createIndex method or null if an error occured. * @return {null} * @api public */ @@ -988,13 +1011,7 @@ Collection.prototype.createIndex = function createIndex (fieldOrSpec, options, c options = options == null ? {} : options; // Collect errorOptions - var errorOptions = options.safe != null ? options.safe : null; - errorOptions = errorOptions == null && this.opts.safe != null ? this.opts.safe : errorOptions; - errorOptions = errorOptions == null && this.db.safe != null ? this.db.safe : errorOptions; - - // If we have a write concern set and no callback throw error - if(errorOptions != null && errorOptions != false && (typeof callback !== 'function' && typeof options !== 'function')) throw new Error("safe cannot be used without a callback"); - + var errorOptions = _getWriteConcern(this, options, callback); // Execute create index this.db.createIndex(this.collectionName, fieldOrSpec, options, callback); }; @@ -1003,7 +1020,10 @@ Collection.prototype.createIndex = function createIndex (fieldOrSpec, options, c * Ensures that an index exists, if it does not it creates it * * Options - * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a +* - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write + * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) + * - **fsync**, (Boolean, default:false) write waits for fsync before returning + * - **journal**, (Boolean, default:false) write waits for journal sync before returning * - **unique** {Boolean, default:false}, creates an unique index. * - **sparse** {Boolean, default:false}, creates a sparse index. * - **background** {Boolean, default:false}, creates the index in the background, yielding whenever possible. @@ -1013,10 +1033,13 @@ Collection.prototype.createIndex = function createIndex (fieldOrSpec, options, c * - **v** {Number}, specify the format version of the indexes. * - **expireAfterSeconds** {Number}, allows you to expire data on indexes applied to a data (MongoDB 2.2 or higher) * - **name** {String}, override the autogenerated index name (useful if the resulting name is larger than 128 bytes) + * + * Deprecated Options + * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. * * @param {Object} fieldOrSpec fieldOrSpec that defines the index. * @param {Object} [options] additional options during update. - * @param {Function} callback for results. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the ensureIndex method or null if an error occured. * @return {null} * @api public */ @@ -1031,14 +1054,6 @@ Collection.prototype.ensureIndex = function ensureIndex (fieldOrSpec, options, c options = {}; } - // Collect errorOptions - var errorOptions = options.safe != null ? options.safe : null; - errorOptions = errorOptions == null && this.opts.safe != null ? this.opts.safe : errorOptions; - errorOptions = errorOptions == null && this.db.safe != null ? this.db.safe : errorOptions; - - // If we have a write concern set and no callback throw error - if(errorOptions != null && errorOptions != false && (typeof callback !== 'function' && typeof options !== 'function')) throw new Error("safe cannot be used without a callback"); - // Execute create index this.db.ensureIndex(this.collectionName, fieldOrSpec, options, callback); }; @@ -1050,7 +1065,7 @@ Collection.prototype.ensureIndex = function ensureIndex (fieldOrSpec, options, c * - **full** {Boolean, default:false}, returns the full raw index information. * * @param {Object} [options] additional options during update. - * @param {Function} callback returns the index information. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the indexInformation method or null if an error occured. * @return {null} * @api public */ @@ -1067,7 +1082,7 @@ Collection.prototype.indexInformation = function indexInformation (options, call * Drops an index from this collection. * * @param {String} name - * @param {Function} callback returns the results. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the dropIndex method or null if an error occured. * @return {null} * @api public */ @@ -1078,7 +1093,7 @@ Collection.prototype.dropIndex = function dropIndex (name, callback) { /** * Drops all indexes from this collection. * - * @param {Function} callback returns the results. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the dropAllIndexes method or null if an error occured. * @return {null} * @api public */ @@ -1098,7 +1113,7 @@ Collection.prototype.dropAllIndexes = function dropIndexes (callback) { * Drops all indexes from this collection. * * @deprecated - * @param {Function} callback returns the results. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the dropIndexes method or null if an error occured. * @return {null} * @api private */ @@ -1108,7 +1123,7 @@ Collection.prototype.dropIndexes = Collection.prototype.dropAllIndexes; * Reindex all indexes on the collection * Warning: reIndex is a blocking operation (indexes are rebuilt in the foreground) and will be slow for large collections. * - * @param {Function} callback returns the results. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the reIndex method or null if an error occured. * @return {null} * @api public **/ @@ -1134,7 +1149,7 @@ Collection.prototype.reIndex = function(callback) { * @param {Function|String} map the mapping function. * @param {Function|String} reduce the reduce function. * @param {Objects} [options] options for the map reduce job. - * @param {Function} callback returns the result of the map reduce job, (error, results, [stats]) + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the mapReduce method or null if an error occured. * @return {null} * @api public */ @@ -1165,7 +1180,11 @@ Collection.prototype.mapReduce = function mapReduce (map, reduce, options, callb // Add any other options passed in for (var name in options) { - mapCommandHash[name] = options[name]; + if ('scope' == name) { + mapCommandHash[name] = processScope(options[name]); + } else { + mapCommandHash[name] = options[name]; + } } // Set read preference if we set one @@ -1222,6 +1241,30 @@ Collection.prototype.mapReduce = function mapReduce (map, reduce, options, callb }); }; +/** + * Functions that are passed as scope args must + * be converted to Code instances. + * @ignore + */ +function processScope (scope) { + if (!utils.isObject(scope)) { + return scope; + } + + var keys = Object.keys(scope); + var i = keys.length; + var key; + + while (i--) { + key = keys[i]; + if ('function' == typeof scope[key]) { + scope[key] = new Code(String(scope[key])); + } + } + + return scope; +} + /** * Group function helper * @ignore @@ -1267,7 +1310,7 @@ var groupFunction = function () { * @param {Function|Code} finalize an optional function to be run on each item in the result set just before the item is returned. * @param {Boolean} command specify if you wish to run using the internal group command or using eval, default is true. * @param {Object} [options] additional options during update. - * @param {Function} callback returns the results. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the group method or null if an error occured. * @return {null} * @api public */ @@ -1371,7 +1414,7 @@ Collection.prototype.group = function group(keys, condition, initial, reduce, fi /** * Returns the options of the collection. * - * @param {Function} callback returns option results. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the options method or null if an error occured. * @return {null} * @api public */ @@ -1387,7 +1430,7 @@ Collection.prototype.options = function options(callback) { /** * Returns if the collection is a capped collection * - * @param {Function} callback returns if collection is capped. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the isCapped method or null if an error occured. * @return {null} * @api public */ @@ -1405,7 +1448,7 @@ Collection.prototype.isCapped = function isCapped(callback) { * Checks if one or more indexes exist on the collection * * @param {String|Array} indexNames check if one or more indexes exist on the collection. - * @param {Function} callback returns if the indexes exist. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the indexExists method or null if an error occured. * @return {null} * @api public */ @@ -1445,7 +1488,7 @@ Collection.prototype.indexExists = function indexExists(indexes, callback) { * @param {Number} x point to search on the x axis, ensure the indexes are ordered in the same order. * @param {Number} y point to search on the y axis, ensure the indexes are ordered in the same order. * @param {Objects} [options] options for the map reduce job. - * @param {Function} callback returns matching documents. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the geoNear method or null if an error occured. * @return {null} * @api public */ @@ -1486,7 +1529,7 @@ Collection.prototype.geoNear = function geoNear(x, y, options, callback) { * @param {Number} x point to search on the x axis, ensure the indexes are ordered in the same order. * @param {Number} y point to search on the y axis, ensure the indexes are ordered in the same order. * @param {Objects} [options] options for the map reduce job. - * @param {Function} callback returns matching documents. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the geoHaystackSearch method or null if an error occured. * @return {null} * @api public */ @@ -1515,7 +1558,7 @@ Collection.prototype.geoHaystackSearch = function geoHaystackSearch(x, y, option /** * Retrieve all the indexes on the collection. * - * @param {Function} callback returns index information. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the indexes method or null if an error occured. * @return {null} * @api public */ @@ -1532,7 +1575,7 @@ Collection.prototype.indexes = function indexes(callback) { * * @param {Array} array containing all the aggregation framework commands for the execution. * @param {Object} [options] additional options during update. - * @param {Function} callback returns matching documents. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the aggregate method or null if an error occured. * @return {null} * @api public */ @@ -1541,8 +1584,10 @@ Collection.prototype.aggregate = function(pipeline, options, callback) { var args = Array.prototype.slice.call(arguments, 0); callback = args.pop(); var self = this; - // Get the right options - options = args[args.length - 1].explain ? args.pop() : {} + + // If we have any of the supported options in the options object + var opts = args[args.length - 1]; + options = opts.readPreference || opts.explain ? args.pop() : {} // Convert operations to an array if(!Array.isArray(args[0])) { @@ -1582,7 +1627,7 @@ Collection.prototype.aggregate = function(pipeline, options, callback) { * - **readPreference** {String}, the preferred read preference ((Server.PRIMARY, Server.PRIMARY_PREFERRED, Server.SECONDARY, Server.SECONDARY_PREFERRED, Server.NEAREST). * * @param {Objects} [options] options for the stats command. - * @param {Function} callback returns statistical information for the collection. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the stats method or null if an error occured. * @return {null} * @api public */ @@ -1617,20 +1662,65 @@ Object.defineProperty(Collection.prototype, "hint", { } }); +/** + * @ignore + */ +var _hasWriteConcern = function(errorOptions) { + return errorOptions == true + || errorOptions.w > 0 + || errorOptions.w == 'majority' + || errorOptions.j == true + || errorOptions.journal == true + || errorOptions.fsync == true +} + +/** + * @ignore + */ +var _setWriteConcernHash = function(options) { + var finalOptions = {}; + if(options.w != null) finalOptions.w = options.w; + if(options.journal == true) finalOptions.j = options.journal; + if(options.j == true) finalOptions.j = options.j; + if(options.fsync == true) finalOptions.fsync = options.fsync; + if(options.wtimeout != null) finalOptions.wtimeout = options.wtimeout; + return finalOptions; +} + +/** + * @ignore + */ +var _getWriteConcern = function(self, options, callback) { + // Final options + var finalOptions = {w:1}; + // Local options verification + if(options.w != null || typeof options.j == 'boolean' || typeof options.journal == 'boolean' || typeof options.fsync == 'boolean') { + finalOptions = _setWriteConcernHash(options); + } else if(typeof options.safe == "boolean") { + finalOptions = {w: (options.safe ? 1 : 0)}; + } else if(options.safe != null && typeof options.safe == 'object') { + finalOptions = _setWriteConcernHash(options.safe); + } else if(self.opts.w != null || typeof self.opts.j == 'boolean' || typeof self.opts.journal == 'boolean' || typeof self.opts.fsync == 'boolean') { + finalOptions = _setWriteConcernHash(self.opts); + } else if(typeof self.opts.safe == "boolean") { + finalOptions = {w: (self.opts.safe ? 1 : 0)}; + } else if(self.db.safe.w != null || typeof self.db.safe.j == 'boolean' || typeof self.db.safe.journal == 'boolean' || typeof self.db.safe.fsync == 'boolean') { + finalOptions = _setWriteConcernHash(self.db.safe); + } else if(self.db.options.w != null || typeof self.db.options.j == 'boolean' || typeof self.db.options.journal == 'boolean' || typeof self.db.options.fsync == 'boolean') { + finalOptions = _setWriteConcernHash(self.db.options); + } else if(typeof self.db.safe == "boolean") { + finalOptions = {w: (self.db.safe ? 1 : 0)}; + } + + // Ensure we don't have an invalid combination of write concerns + if(finalOptions.w < 1 + && (finalOptions.journal == true || finalOptions.j == true || finalOptions.fsync == true)) throw new Error("No acknowlegement using w < 1 cannot be combined with journal:true or fsync:true"); + + // Return the options + return finalOptions; +} + /** * Expose. */ exports.Collection = Collection; - - - - - - - - - - - - - diff --git a/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/commands/db_command.js b/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/commands/db_command.js index 87438e8..b1a798d 100644 --- a/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/commands/db_command.js +++ b/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/commands/db_command.js @@ -1,6 +1,7 @@ var QueryCommand = require('./query_command').QueryCommand, InsertCommand = require('./insert_command').InsertCommand, inherits = require('util').inherits, + utils = require('../utils'), crypto = require('crypto'); /** @@ -107,6 +108,11 @@ DbCommand.createGetLastErrorCommand = function(options, db) { } } + // Special case for w == 1, remove the w + if(1 == command.w) { + delete command.w; + } + // Execute command return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, command, null); }; @@ -127,28 +133,36 @@ DbCommand.createCreateIndexCommand = function(db, collectionName, fieldOrSpec, o var keys; // Get all the fields accordingly - if (fieldOrSpec.constructor === String) { // 'type' + if('string' == typeof fieldOrSpec) { + // 'type' indexes.push(fieldOrSpec + '_' + 1); fieldHash[fieldOrSpec] = 1; - } else if (fieldOrSpec.constructor === Array) { // [{location:'2d'}, ...] + + } else if(utils.isArray(fieldOrSpec)) { + fieldOrSpec.forEach(function(f) { - if (f.constructor === String) { // [{location:'2d'}, 'type'] + if('string' == typeof f) { + // [{location:'2d'}, 'type'] indexes.push(f + '_' + 1); fieldHash[f] = 1; - } else if (f.constructor === Array) { // [['location', '2d'],['type', 1]] + } else if(utils.isArray(f)) { + // [['location', '2d'],['type', 1]] indexes.push(f[0] + '_' + (f[1] || 1)); fieldHash[f[0]] = f[1] || 1; - } else if (f.constructor === Object) { // [{location:'2d'}, {type:1}] + } else if(utils.isObject(f)) { + // [{location:'2d'}, {type:1}] keys = Object.keys(f); keys.forEach(function(k) { indexes.push(k + '_' + f[k]); fieldHash[k] = f[k]; - }); + }); } else { - // undefined + // undefined (ignore) } }); - } else if (fieldOrSpec.constructor === Object) { // {location:'2d', type:1} + + } else if(utils.isObject(fieldOrSpec)) { + // {location:'2d', type:1} keys = Object.keys(fieldOrSpec); keys.forEach(function(key) { indexes.push(key + '_' + fieldOrSpec[key]); @@ -157,26 +171,38 @@ DbCommand.createCreateIndexCommand = function(db, collectionName, fieldOrSpec, o } // Generate the index name - var indexName = typeof options.name == 'string' ? options.name : indexes.join("_"); - // Build the selector - var selector = {'ns':(db.databaseName + "." + collectionName), 'key':fieldHash, 'name':indexName}; + var indexName = typeof options.name == 'string' + ? options.name + : indexes.join("_"); + + var selector = { + 'ns': db.databaseName + "." + collectionName, + 'key': fieldHash, + 'name': indexName + } // Ensure we have a correct finalUnique - var finalUnique = options == null || 'object' === typeof options ? false : options; + var finalUnique = options == null || 'object' === typeof options + ? false + : options; + // Set up options - options = options == null || typeof options == 'boolean' ? {} : options; + options = options == null || typeof options == 'boolean' + ? {} + : options; // Add all the options var keys = Object.keys(options); - // Add all the fields to the selector for(var i = 0; i < keys.length; i++) { selector[keys[i]] = options[keys[i]]; } - // If we don't have the unique property set on the selector - if(selector['unique'] == null) selector['unique'] = finalUnique; - // Create the insert command for the index and return the document - return new InsertCommand(db, db.databaseName + "." + DbCommand.SYSTEM_INDEX_COLLECTION, false).add(selector); + if(selector['unique'] == null) + selector['unique'] = finalUnique; + + var name = db.databaseName + "." + DbCommand.SYSTEM_INDEX_COLLECTION; + var cmd = new InsertCommand(db, name, false); + return cmd.add(selector); }; DbCommand.logoutCommand = function(db, command_hash, options) { @@ -211,4 +237,4 @@ DbCommand.createAdminDbCommandSlaveOk = function(db, command_hash) { DbCommand.createDbSlaveOkCommand = function(db, command_hash, options) { return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT | QueryCommand.OPTS_SLAVE, 0, -1, command_hash, null, options); -}; \ No newline at end of file +}; diff --git a/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/connection/connection.js b/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/connection/connection.js index ec90560..f1c333a 100644 --- a/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/connection/connection.js +++ b/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/connection/connection.js @@ -89,10 +89,10 @@ Connection.prototype.start = function() { } } else { // Create new connection instance - if(!this.domainSocket) { - this.connection = net.createConnection(this.socketOptions.port, this.socketOptions.host); + if(this.domainSocket) { + this.connection = net.createConnection(this.socketOptions.host); } else { - this.connection = net.createConnection(this.socketOptions.host); + this.connection = net.createConnection(this.socketOptions.port, this.socketOptions.host); } if(this.logger != null && this.logger.doDebug){ this.logger.debug("opened connection", this.socketOptions); @@ -139,7 +139,7 @@ Connection.prototype.write = function(command, callback) { if(!this.socketOptions['disableDriverBSONSizeCheck'] && binaryCommand.length > this.maxBsonSize) return callback(new Error("Document exceeds maximal allowed bson size of " + this.maxBsonSize + " bytes")); if(this.logger != null && this.logger.doDebug) - this.logger.debug("writing command to mongodb", binaryCommand); + this.logger.debug("writing command to mongodb", {binary: binaryCommand, json: command[i]}); var r = this.writeSteam.write(binaryCommand); } @@ -149,7 +149,7 @@ Connection.prototype.write = function(command, callback) { return callback(new Error("Document exceeds maximal allowed bson size of " + this.maxBsonSize + " bytes")); if(this.logger != null && this.logger.doDebug) - this.logger.debug("writing command to mongodb", binaryCommand); + this.logger.debug("writing command to mongodb", {binary: binaryCommand, json: command}); var r = this.writeSteam.write(binaryCommand); } diff --git a/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/connection/connection_pool.js b/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/connection/connection_pool.js index 299024b..9bbb88e 100644 --- a/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/connection/connection_pool.js +++ b/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/connection/connection_pool.js @@ -7,9 +7,13 @@ var utils = require('./connection_utils'), Connection = require("./connection").Connection; var ConnectionPool = exports.ConnectionPool = function(host, port, poolSize, bson, socketOptions) { - if(typeof host !== 'string' || typeof port !== 'number') throw "host and port must be specified [" + host + ":" + port + "]"; + if(typeof host !== 'string') { + throw new Error("host must be specified [" + host + "]"); + } + // Set up event emitter EventEmitter.call(this); + // Keep all options for the socket in a specific collection allowing the user to specify the // Wished upon socket connection parameters this.socketOptions = typeof socketOptions === 'object' ? socketOptions : {}; @@ -22,7 +26,11 @@ var ConnectionPool = exports.ConnectionPool = function(host, port, poolSize, bso this.minPoolSize = Math.floor(this.poolSize / 2) + 1; // Check if the host is a socket - if(host.match(/^\//)) this.socketOptions.domainSocket = true; + if(host.match(/^\//)) { + this.socketOptions.domainSocket = true; + } else if(typeof port !== 'number') { + throw new Error("port must be specified [" + port + "]"); + } // Set default settings for the socket options utils.setIntegerParameter(this.socketOptions, 'timeout', 0); @@ -96,9 +104,8 @@ var _connect = function(_self) { _self.emit("error", err); } - // Set disconnected + connection.close(); _self._poolState = 'disconnected'; - // Stop _self.stop(); }); @@ -120,9 +127,8 @@ var _connect = function(_self) { if(_self._poolState !== 'disconnected' && _self.listeners("timeout").length > 0) { _self.emit("timeout", err); } - // Set disconnected - _self._poolState = 'disconnected'; - // Stop + + connection.close(); _self.stop(); }); diff --git a/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/connection/repl_set.js b/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/connection/repl_set.js index 2a4bf1a..a471e89 100644 --- a/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/connection/repl_set.js +++ b/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/connection/repl_set.js @@ -1,14 +1,14 @@ -var Connection = require('./connection').Connection, +var Connection = require('./connection').Connection, ReadPreference = require('./read_preference').ReadPreference, DbCommand = require('../commands/db_command').DbCommand, MongoReply = require('../responses/mongo_reply').MongoReply, debug = require('util').debug, - EventEmitter = require('events').EventEmitter, inherits = require('util').inherits, inspect = require('util').inspect, Server = require('./server').Server, PingStrategy = require('./strategies/ping_strategy').PingStrategy, - StatisticsStrategy = require('./strategies/statistics_strategy').StatisticsStrategy; + StatisticsStrategy = require('./strategies/statistics_strategy').StatisticsStrategy, + Base = require('./base').Base; const STATE_STARTING_PHASE_1 = 0; const STATE_PRIMARY = 1; @@ -35,6 +35,7 @@ const STATE_ROLLBACK = 9; * - **strategy** {String, default:null}, selection strategy for reads choose between (ping and statistical, default is round-robin) * - **secondaryAcceptableLatencyMS** {Number, default:15}, sets the range of servers to pick when using NEAREST (lowest ping ms + the latency fence, ex: range of 1 to (1 + 15) ms) * - **connectArbiter** {Boolean, default:false}, sets if the driver should connect to arbiters or not. + * - **logger** {Object, default:null}, an object representing a logger that you want to use, needs to support functions debug, log, error **({error:function(message, object) {}, log:function(message, object) {}, debug:function(message, object) {}})**. * * @class Represents a Replicaset Configuration * @param {Array} list of server objects participating in the replicaset. @@ -48,7 +49,7 @@ var ReplSet = exports.ReplSet = function(servers, options) { return new ReplSet(servers, options); // Set up event emitter - EventEmitter.call(this); + Base.call(this); // Ensure no Mongos's for(var i = 0; i < servers.length; i++) { @@ -89,6 +90,8 @@ var ReplSet = exports.ReplSet = function(servers, options) { this._numberOfServersLeftToInitialize = 0; // Do we record server stats or not this.recordQueryStats = false; + // Update health try server + this.updateHealthServerTry = 0; // Get the readPreference var readPreference = this.options['readPreference']; @@ -106,6 +109,12 @@ var ReplSet = exports.ReplSet = function(servers, options) { this._readPreference = null; } + // Ensure read_secondary is set correctly + if(!this.readSecondary) + this.readSecondary = this._readPreference == ReadPreference.PRIMARY + || this._readPreference == false + || this._readPreference == null ? false : true; + // Strategy for picking a secondary this.secondaryAcceptableLatencyMS = this.options['secondaryAcceptableLatencyMS'] == null ? 15 : this.options['secondaryAcceptableLatencyMS']; this.strategy = this.options['strategy']; @@ -115,7 +124,6 @@ var ReplSet = exports.ReplSet = function(servers, options) { if(this.strategy == 'ping') { // Create a new instance this.strategyInstance = new PingStrategy(this, this.secondaryAcceptableLatencyMS); - this.strategyInstance.start(); } else if(this.strategy == 'statistical') { // Set strategy as statistical this.strategyInstance = new StatisticsStrategy(this); @@ -174,8 +182,12 @@ var ReplSet = exports.ReplSet = function(servers, options) { // How often are we checking for new servers in the replicaset this.replicasetStatusCheckInterval = this.options['haInterval'] == null ? 1000 : this.options['haInterval']; this._replicasetTimeoutId = null; + // Connection timeout - this._connectTimeoutMS = 1000; + this._connectTimeoutMS = this.socketOptions.connectTimeoutMS + ? this.socketOptions.connectTimeoutMS + : 1000; + // Current list of servers to test this.pingCandidateServers = []; @@ -186,7 +198,7 @@ var ReplSet = exports.ReplSet = function(servers, options) { /** * @ignore */ -inherits(ReplSet, EventEmitter); +inherits(ReplSet, Base); /** * @ignore @@ -219,8 +231,20 @@ ReplSet.prototype.isMongos = function() { /** * @ignore */ -ReplSet.prototype.isConnected = function() { - return this.primary != null && this._state.master != null && this._state.master.isConnected(); +ReplSet.prototype.isConnected = function(read) { + if(read == null || read == ReadPreference.PRIMARY || read == false) + return this.primary != null && this._state.master != null && this._state.master.isConnected(); + + if((read == ReadPreference.PRIMARY_PREFERRED || read == ReadPreference.SECONDARY_PREFERRED || read == ReadPreference.NEAREST) + && ((this.primary != null && this._state.master != null && this._state.master.isConnected()) + || (this._state && this._state.secondaries && Object.keys(this._state.secondaries).length > 0))) { + return true; + } else if(read == ReadPreference.SECONDARY) { + return this._state && this._state.secondaries && Object.keys(this._state.secondaries).length > 0; + } + + // No valid connection return false + return false; } /** @@ -242,25 +266,13 @@ ReplSet.prototype.isPrimary = function(config) { */ ReplSet.prototype.isReadPrimary = ReplSet.prototype.isPrimary; -/** - * @ignore - **/ -ReplSet.prototype._checkReplicaSet = function() { - if(!this.haEnabled) return false; - var currentTime = new Date().getTime(); - if((currentTime - this.lastReplicaSetTime) >= this.replicasetStatusCheckInterval) { - this.lastReplicaSetTime = currentTime; - return true; - } else { - return false; - } -} - /** * @ignore */ ReplSet.prototype.allServerInstances = function() { var self = this; + // If no state yet return empty + if(!self._state) return []; // Close all the servers (concatenate entire list of servers first for ease) var allServers = self._state.master != null ? [self._state.master] : []; @@ -290,153 +302,274 @@ ReplSet.prototype.allServerInstances = function() { } /** + * Enables high availability pings. + * * @ignore */ -var __executeAllCallbacksWithError = function(dbInstance, error) { - var keys = Object.keys(dbInstance._callBackStore._notReplied); - // Iterate over all callbacks - for(var i = 0; i < keys.length; i++) { - // Delete info object - delete dbInstance._callBackStore._notReplied[keys[i]]; - // Emit the error - dbInstance._callBackStore.emit(keys[i], error); +ReplSet.prototype._enableHA = function () { + var self = this; + return check(); + + function ping () { + if("disconnected" == self._serverState) return; + + if(Object.keys(self._state.addresses).length == 0) return; + var selectedServer = self._state.addresses[Object.keys(self._state.addresses)[self.updateHealthServerTry++]]; + if(self.updateHealthServerTry >= Object.keys(self._state.addresses).length) self.updateHealthServerTry = 0; + if(selectedServer == null) return check(); + + // If we have an active db instance + if(self.dbInstances.length > 0) { + var db = self.dbInstances[0]; + + // Create a new master connection + var _server = new Server(selectedServer.host, selectedServer.port, { + auto_reconnect: false, + returnIsMasterResults: true, + slaveOk: true, + poolSize: 1, + socketOptions: { connectTimeoutMS: self._connectTimeoutMS } + }); + + // Connect using the new _server connection to not impact the driver + // behavior on any errors we could possibly run into + _server.connect(db, function(err, result, _server) { + if(err) { + if(_server.close) _server.close(); + return check(); + } + + // Create is master command + var cmd = DbCommand.createIsMasterCommand(db); + // Execute is master command + db._executeQueryCommand(cmd, {failFast:true, connection: _server.checkoutWriter()}, function(err, res) { + // Close the connection used + _server.close(); + // If error let's set perform another check + if(err) return check(); + // Validate the replicaset + self._validateReplicaset(res, db.auths, function() { + check(); + }); + }); + }); + } + } + + function check () { + self._haTimer = setTimeout(ping, self.replicasetStatusCheckInterval); } } /** * @ignore */ -ReplSet.prototype._validateReplicaset = function(result, auths) { +ReplSet.prototype._validateReplicaset = function(result, auths, cb) { var self = this; - // For each member we need to check if we have a new connection that needs to be established - var members = result['documents'][0]['members']; - // Get members - var members = Array.isArray(result['documents'][0]['members']) ? result['documents'][0]['members'] : []; - // The total members we check - var serversToConnectList = {}; + var res = result.documents[0]; - // Iterate over all the members and see if we need to reconnect - for(var i = 0, jlen = members.length; i < jlen; i++) { - var member = members[i]; + // manage master node changes + if(res.primary && self._state.master && self._state.master.name != res.primary) { + // Delete master record so we can rediscover it + delete self._state.addresses[self._state.master.name]; - if(member['health'] != 0 - && null == self._state['addresses'][member['name']] - && null == serversToConnectList[member['name']]) { - if (member['stateStr'] == 'ARBITER' && self.connectArbiter != true) { - continue; - } - // Split the server string - var parts = member.name.split(/:/); - if(parts.length == 1) { - parts = [parts[0], Connection.DEFAULT_PORT]; - } + // TODO existing issue? this seems to only work if + // we already have a connection to the new primary. - // Default empty socket options object - var socketOptions = {host:parts[0], port:parseInt(parts[1], 10)}; - // If a socket option object exists clone it - if(self.socketOptions != null) { - var keys = Object.keys(self.socketOptions); - for(var k = 0; k < keys.length;k++) socketOptions[keys[i]] = self.socketOptions[keys[i]]; - } + // Update information on new primary + // add as master, remove from secondary + var newMaster = self._state.addresses[res.primary]; + newMaster.isMasterDoc.ismaster = true; + newMaster.isMasterDoc.secondary = false; + self._state.master = newMaster; + delete self._state.secondaries[res.primary]; + } - // Create a new server instance - var newServer = new Server(parts[0], parseInt(parts[1], 10), {auto_reconnect:false, 'socketOptions':socketOptions - , logger:self.logger, ssl:self.ssl, poolSize:self.poolSize}); - // Set the replicaset instance - newServer.replicasetInstance = self; + // discover new hosts + var hosts = []; - // Add handlers - newServer.on("close", _handler("close", self)); - newServer.on("error", _handler("error", self)); - newServer.on("timeout", _handler("timeout", self)); - // Add to list of server connection target - serversToConnectList[member['name']] = newServer; - } else if(member['stateStr'] == 'PRIMARY' && self._state.master['name'] != member['name']) { - // Delete master record so we can rediscover it - delete self._state['addresses'][self._state.master['name']]; - // Update inormation on new primary - var newMaster = self._state.addresses[member['name']]; - newMaster.isMasterDoc.ismaster = true; - newMaster.isMasterDoc.secondary = false; - self._state.master = newMaster; - // Remove from secondaries - delete self._state.secondaries[member['name']]; - newMaster = null; + for(var i = 0; i < res.hosts.length; ++i) { + var host = res.hosts[i]; + if (host == res.me) continue; + if (!(self._state.addresses[host] || ~hosts.indexOf(host))) { + // we dont already have a connection to this host and aren't + // already planning on connecting. + hosts.push(host); } } - // All servers we want to connect to - var serverKeys = Object.keys(serversToConnectList); - // For all remaining servers on the list connect - while(serverKeys.length > 0) { - var _serverKey = serverKeys.pop(); - // Fetch the server - var _server = serversToConnectList[_serverKey]; - // Add a new server to the total number of servers that need to initialized before we are done - //var newServerCallback = self.connectionHandler(_server); - var newServerCallback = _connectHandler(self, null, _server) - // Connect To the new server - _server.connect(self.db, {returnIsMasterResults: true, eventReceiver:newServer}, function(err, result, _server) { - if(err == null && result != null) { - // Fetch the myState - var document = result.documents[0]; - // Remove from list until - if(document.ismaster || document.secondary || document.arbiterOnly) { - process.nextTick(function() { - // Apply any auths - if(Array.isArray(auths) && auths.length > 0) { - // Get number of auths we need to execute - var numberOfAuths = auths.length; - // Apply all auths - for(var i = 0; i < auths.length; i++) { - self.db.authenticate(auths[i].username, auths[i].password, {'authdb':auths[i].authdb}, function(err, authenticated) { - numberOfAuths = numberOfAuths - 1; - // If we have no more authentications to replay - if(numberOfAuths == 0) { - newServerCallback(err, result, _server); - } - }); - } - } else { - newServerCallback(err, result, _server); - } - }); - } else { - _server.close(); - } - } else { - _server.close(); - } - }); + connectTo(hosts, auths, self, cb); +} + +/** + * Create connections to all `hosts` firing `cb` after + * connections are attempted for all `hosts`. + * + * @param {Array} hosts + * @param {Array} [auths] + * @param {ReplSet} replset + * @param {Function} cb + * @ignore + */ +function connectTo (hosts, auths, replset, cb) { + var pending = hosts.length; + if (!pending) return cb(); + + for(var i = 0; i < hosts.length; ++i) { + connectToHost(hosts[i], auths, replset, handle); + } + + function handle () { + --pending; + if (0 === pending) cb(); } } -var _handler = function(event, self) { - return function(err, server) { - // Check if we have a secondary server - if(self._state.master && self._state.master.name == server.name) { - // Force close - self.close(); - // Error out all callbacks - __executeAllCallbacksWithError(self.db, err); - } else if(self._state.master - && (self._state.secondaries[server.name] != null - || self._state.arbiters[server.name] != null - || self._state.passives[server.name] != null)) { +/** + * Attempts connection to `host` and authenticates with optional `auth` + * for the given `replset` firing `cb` when finished. + * + * @param {String} host + * @param {Array} auths + * @param {ReplSet} replset + * @param {Function} cb + * @ignore + */ +function connectToHost (host, auths, replset, cb) { + var server = createServer(host, replset); - delete self._state.secondaries[server.name]; - delete self._state.arbiters[server.name]; - delete self._state.passives[server.name]; - delete self._state.addresses[server.name]; + var options = { + returnIsMasterResults: true, + eventReceiver: server + } + + server.connect(replset.db, options, function(err, result) { + var doc = result && result.documents && result.documents[0]; + + if (err || !doc) { + server.close(); + return cb(err, result, server); } - // If it's a primary we need to close the set to reconnect - if(self._state.master && self._state.master.host == server.host && self._state.master.port == server.port) { - // If we have app listeners on close event - if(self.db.listeners(event).length > 0) { - self.db.emit(event, err); + if(!(doc.ismaster || doc.secondary || doc.arbiterOnly)) { + server.close(); + return cb(null, result, server); + } + + // if host is an arbiter, disconnect if not configured for it + if(doc.arbiterOnly && !replset.connectArbiter) { + server.close(); + return cb(null, result, server); + } + + // create handler for successful connections + var handleConnect = _connectHandler(replset, null, server); + function complete () { + handleConnect(err, result); + cb(); + } + + // authenticate if necessary + if(!(Array.isArray(auths) && auths.length > 0)) { + return complete(); + } + + var pending = auths.length; + + var connections = server.allRawConnections(); + var pendingAuthConn = connections.length; + for(var x = 0; x 0) { + self.db.emit(event, err); + } + + // If it's the primary close all connections + if(self._state.master + && self._state.master.host == server.host + && self._state.master.port == server.port) { + // return self.close(); + self._state.master = null; + } } } @@ -444,6 +577,7 @@ var _connectHandler = function(self, candidateServers, instanceServer) { return function(err, result) { // We are disconnected stop attempting reconnect or connect if(self._serverState == 'disconnected') return instanceServer.close(); + // If no error handle isMaster if(err == null && result.documents[0].hosts != null) { // Fetch the isMaster command result @@ -479,7 +613,10 @@ var _connectHandler = function(self, candidateServers, instanceServer) { if (oldServer && oldServer !== instanceServer) oldServer.close(); delete self._state.addresses[userProvidedServerString]; - if (self._state.addresses[me] && self._state.addresses[me] !== instanceServer) self._state.addresses[me].close(); + if (self._state.addresses[me] && self._state.addresses[me] !== instanceServer) { + self._state.addresses[me].close(); + } + self._state.addresses[me] = instanceServer; // Let's add the server to our list of server types @@ -499,6 +636,7 @@ var _connectHandler = function(self, candidateServers, instanceServer) { instanceServer.name = me; // Add tag info instanceServer.tags = tags; + // Add the handlers to the instance instanceServer.on("close", _handler("close", self)); instanceServer.on("error", _handler("error", self)); @@ -539,19 +677,25 @@ var _connectHandler = function(self, candidateServers, instanceServer) { // Attempt to connect to the next server if(Array.isArray(candidateServers) && candidateServers.length > 0) { var server = candidateServers.pop(); + // Get server addresses var addresses = self._state.addresses; + // Default empty socket options object var socketOptions = {}; + + // Set fast connect timeout + socketOptions['connectTimeoutMS'] = self._connectTimeoutMS; + // If a socket option object exists clone it if(self.socketOptions != null && typeof self.socketOptions === 'object') { var keys = Object.keys(self.socketOptions); for(var j = 0; j < keys.length;j++) socketOptions[keys[j]] = self.socketOptions[keys[j]]; } + // If ssl is specified - if(self.ssl) serverConnections[i].ssl = true; - // Set fast connect timeout - socketOptions['connectTimeoutMS'] = self._connectTimeoutMS + if(self.ssl) server.ssl = true; + // Add host information to socket options socketOptions['host'] = server.host; socketOptions['port'] = server.port; @@ -579,6 +723,10 @@ var _connectHandler = function(self, candidateServers, instanceServer) { return self.emit("connectionError", new Error("no primary server found in set")) } else{ + if (self.strategyInstance) { + self.strategyInstance.start(); + } + self.emit("fullsetup", null, self.db, self); self.emit("open", null, self.db, self); } @@ -586,6 +734,23 @@ var _connectHandler = function(self, candidateServers, instanceServer) { } } +/** + * Interval state object constructor + * + * @ignore + */ +ReplSet.State = function ReplSetState () { + this.errorMessages = []; + this.secondaries = {}; + this.addresses = {}; + this.arbiters = {}; + this.passives = {}; + this.members = []; + this.errors = {}; + this.setName = null; + this.master = null; +} + /** * @ignore */ @@ -597,14 +762,18 @@ ReplSet.prototype.connect = function(parent, options, callback) { // Ensure it's all closed self.close(); + // Set connecting status this.db = parent; this._serverState = 'connecting'; this._callbackList = []; - this._state = {'master':null, 'secondaries':{}, 'arbiters':{}, 'passives':{} - , 'errors':{}, 'addresses':{}, 'setName':null, 'errorMessages':[], 'members':[]}; + + this._state = new ReplSet.State(); + // Ensure parent can do a slave query if it's set - parent.slaveOk = this.slaveOk ? this.slaveOk : parent.slaveOk; + parent.slaveOk = this.slaveOk + ? this.slaveOk + : parent.slaveOk; // Remove any listeners this.removeAllListeners("fullsetup"); @@ -612,11 +781,8 @@ ReplSet.prototype.connect = function(parent, options, callback) { // Add primary found event handler this.once("fullsetup", function() { - // Set state connected - self._serverState = 'connected'; - // Emit the fullsetup and open event - parent.emit("open", null, self.db, self); - parent.emit("fullsetup", null, self.db, self); + self._handleOnFullSetup(parent); + // Callback if(typeof callback == 'function') { var internalCallback = callback; @@ -640,31 +806,13 @@ ReplSet.prototype.connect = function(parent, options, callback) { // Get server addresses var addresses = this._state.addresses; - // Default empty socket options object - var socketOptions = {}; - // If a socket option object exists clone it - if(this.socketOptions != null && typeof this.socketOptions === 'object') { - var keys = Object.keys(this.socketOptions); - for(var j = 0; j < keys.length;j++) socketOptions[keys[j]] = this.socketOptions[keys[j]]; - } - // If ssl is specified - if(this.ssl) serverConnections[i].ssl = true; - // Set fast connect timeout - socketOptions['connectTimeoutMS'] = this._connectTimeoutMS - // De-duplicate any servers - var server; + var server, key; for(var i = 0; i < this.servers.length; i++) { server = this.servers[i]; - // Add host information to socket options - socketOptions['host'] = server.host; - socketOptions['port'] = server.port; - server.socketOptions = socketOptions; - server.replicasetInstance = this; - server.enableRecordQueryStats(this.recordQueryStats); - // If server does not exist set it - if(addresses[server.host + ":" + server.port] == null) { - addresses[server.host + ":" + server.port] = server; + key = server.host + ":" + server.port; + if(null == addresses[key]) { + addresses[key] = server; } } @@ -672,11 +820,45 @@ ReplSet.prototype.connect = function(parent, options, callback) { var candidateServers = []; var keys = Object.keys(addresses); for(var i = 0; i < keys.length; i++) { - candidateServers.push(addresses[keys[i]]); + server = addresses[keys[i]]; + server.assignReplicaSet(this); + candidateServers.push(server); } + // Let's connect to the first one on the list server = candidateServers.pop(); - server.connect(parent, {returnIsMasterResults: true, eventReceiver:server}, _connectHandler(this, candidateServers, server)); + var opts = { + returnIsMasterResults: true, + eventReceiver: server + } + server.connect(parent, opts, _connectHandler(this, candidateServers, server)); +} + +/** + * Handles the first `fullsetup` event of this ReplSet. + * + * @param {Db} parent + * @ignore + */ +ReplSet.prototype._handleOnFullSetup = function (parent) { + this._serverState = 'connected'; + + // Emit the fullsetup and open event + parent.emit("open", null, this.db, this); + parent.emit("fullsetup", null, this.db, this); + + if(!this.haEnabled) return; + this._enableHA(); +} + +/** + * Disables high availability pings. + * + * @ignore + */ +ReplSet.prototype._disableHA = function () { + clearTimeout(this._haTimer); + this._haTimer = undefined; } /** @@ -694,26 +876,28 @@ ReplSet.prototype.checkoutWriter = function() { */ var pickFirstConnectedSecondary = function pickFirstConnectedSecondary(self, tags) { var keys = Object.keys(self._state.secondaries); - var connection = null; + var connection; // Find first available reader if any for(var i = 0; i < keys.length; i++) { connection = self._state.secondaries[keys[i]].checkoutReader(); - if(connection != null) break; + if(connection) return connection; } // If we still have a null, read from primary if it's not secondary only if(self._readPreference == ReadPreference.SECONDARY_PREFERRED) { connection = self._state.master.checkoutReader(); + if(connection) return connection; } - if(connection == null) { - var preferenceName = self._readPreference == ReadPreference.SECONDARY_PREFERRED ? 'secondary' : self._readPreference; - return new Error("No replica set member available for query with ReadPreference " + preferenceName + " and tags " + JSON.stringify(tags)); - } + var preferenceName = self._readPreference == ReadPreference.SECONDARY_PREFERRED + ? 'secondary' + : self._readPreference; - // Return the connection - return connection; + // console.log("================================================================ pickFirstConnectedSecondary :::: ") + + return new Error("No replica set member available for query with ReadPreference " + + preferenceName + " and tags " + JSON.stringify(tags)); } /** @@ -769,6 +953,10 @@ var _pickFromTags = function(self, tags) { */ ReplSet.prototype.checkoutReader = function(readPreference, tags) { var connection = null; + // console.log("============================ checkoutReader") + // console.dir(readPreference) + // console.log(arguments.callee.caller.toString()) + // If we have a read preference object unpack it if(typeof readPreference == 'object' && readPreference['_type'] == 'ReadPreference') { // Validate if the object is using a valid mode @@ -783,6 +971,11 @@ ReplSet.prototype.checkoutReader = function(readPreference, tags) { // Set up our read Preference, allowing us to override the readPreference var finalReadPreference = readPreference != null ? readPreference : this._readPreference; finalReadPreference = finalReadPreference == true ? ReadPreference.SECONDARY_PREFERRED : finalReadPreference; + finalReadPreference = finalReadPreference == null ? ReadPreference.PRIMARY : finalReadPreference; + // finalReadPreference = 'primary'; + + // console.log("============================ finalReadPreference: " + finalReadPreference) + // console.dir(finalReadPreference) // If we are reading from a primary if(finalReadPreference == 'primary') { @@ -812,14 +1005,7 @@ ReplSet.prototype.checkoutReader = function(readPreference, tags) { return new Error("No replica set members available for query"); } } else { - // Pick a secondary using round robin - var keys = Object.keys(this._state.secondaries); - this._currentServerChoice = this._currentServerChoice % keys.length; - var key = keys[this._currentServerChoice++]; - // Fetch a connectio - connection = this._state.secondaries[key] != null ? this._state.secondaries[key].checkoutReader() : null; - // If connection is null fallback to first available secondary - connection = connection == null ? pickFirstConnectedSecondary(this, tags) : connection; + connection = _roundRobin(this, tags); } } else if(finalReadPreference == ReadPreference.PRIMARY_PREFERRED) { // Check if there is a primary available and return that if possible @@ -835,17 +1021,11 @@ ReplSet.prototype.checkoutReader = function(readPreference, tags) { return new Error("No replica set members available for query"); } } else { - // Pick a secondary using round robin - var keys = Object.keys(this._state.secondaries); - this._currentServerChoice = this._currentServerChoice % keys.length; - var key = keys[this._currentServerChoice++]; - // Fetch a connectio - connection = this._state.secondaries[key] != null ? this._state.secondaries[key].checkoutReader() : null; - // If connection is null fallback to first available secondary - connection = connection == null ? pickFirstConnectedSecondary(this, tags) : connection; + connection = _roundRobin(this, tags); } } } else if(finalReadPreference == ReadPreference.SECONDARY_PREFERRED && tags == null && Object.keys(this._state.secondaries).length == 0) { + // console.log("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ SECONDARY_PREFERRED") connection = this.checkoutWriter(); // If no connection return an error if(connection == null) { @@ -874,6 +1054,7 @@ ReplSet.prototype.checkoutReader = function(readPreference, tags) { } else if(finalReadPreference == ReadPreference.NEAREST && this.strategyInstance == null) { return new Error("A strategy for calculating nearness must be enabled such as ping or statistical"); } else if(finalReadPreference == ReadPreference.SECONDARY && Object.keys(this._state.secondaries).length == 0) { + // console.log("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ SECONDARY") if(tags != null && typeof tags == 'object') { var preferenceName = finalReadPreference == ReadPreference.SECONDARY ? 'secondary' : finalReadPreference; connection = new Error("No replica set member available for query with ReadPreference " + preferenceName + " and tags " + JSON.stringify(tags)); @@ -888,6 +1069,27 @@ ReplSet.prototype.checkoutReader = function(readPreference, tags) { return connection; } +/** + * Pick a secondary using round robin + * + * @ignore + */ +function _roundRobin (replset, tags) { + var keys = Object.keys(replset._state.secondaries); + var key = keys[replset._currentServerChoice++ % keys.length]; + + var conn = null != replset._state.secondaries[key] + ? replset._state.secondaries[key].checkoutReader() + : null; + + // If connection is null fallback to first available secondary + if (null == conn) { + conn = pickFirstConnectedSecondary(replset, tags); + } + + return conn; +} + /** * @ignore */ @@ -899,9 +1101,8 @@ ReplSet.prototype.allRawConnections = function() { var allMasterConnections = this._state.master.connectionPool.getAllConnections(); // Add all connections to list allConnections = allConnections.concat(allMasterConnections); - // If we have read secondary let's add all secondary servers - if(this.readSecondary && Object.keys(this._state.secondaries).length > 0) { + if(Object.keys(this._state.secondaries).length > 0) { // Get all the keys var keys = Object.keys(this._state.secondaries); // For each of the secondaries grab the connections diff --git a/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/connection/server.js b/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/connection/server.js index 1d93d11..50635ec 100644 --- a/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/connection/server.js +++ b/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/connection/server.js @@ -4,6 +4,8 @@ var Connection = require('./connection').Connection, MongoReply = require('../responses/mongo_reply').MongoReply, ConnectionPool = require('./connection_pool').ConnectionPool, EventEmitter = require('events').EventEmitter, + Base = require('./base').Base, + utils = require('../utils'), inherits = require('util').inherits; /** @@ -13,7 +15,7 @@ var Connection = require('./connection').Connection, * - **readPreference** {String, default:null}, set's the read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST) * - **ssl** {Boolean, default:false}, use ssl connection (needs to have a mongod server with ssl support) * - **slaveOk** {Boolean, default:false}, legacy option allowing reads from secondary, use **readPrefrence** instead. - * - **poolSize** {Number, default:1}, number of connections in the connection pool, set to 1 as default for legacy reasons. + * - **poolSize** {Number, default:5}, number of connections in the connection pool, set to 5 as default for legacy reasons. * - **socketOptions** {Object, default:null}, an object containing socket options to use (noDelay:(boolean), keepAlive:(number), connectTimeoutMS:(number), socketTimeoutMS:(number)) * - **logger** {Object, default:null}, an object representing a logger that you want to use, needs to support functions debug, log, error **({error:function(message, object) {}, log:function(message, object) {}, debug:function(message, object) {}})**. * - **auto_reconnect** {Boolean, default:false}, reconnect on error. @@ -25,10 +27,12 @@ var Connection = require('./connection').Connection, * @param {Object} [options] optional options for insert command */ function Server(host, port, options) { - // Set up event emitter - EventEmitter.call(this); // Set up Server instance if(!(this instanceof Server)) return new Server(host, port, options); + + // Set up event emitter + Base.call(this); + // Ensure correct values if(port != null && typeof port == 'object') { options = port; @@ -45,16 +49,19 @@ function Server(host, port, options) { this.poolSize = this.options.poolSize == null ? 5 : this.options.poolSize; this.disableDriverBSONSizeCheck = this.options.disableDriverBSONSizeCheck != null ? this.options.disableDriverBSONSizeCheck : false; this.ssl = this.options.ssl == null ? false : this.options.ssl; - this.slaveOk = this.options["slave_ok"]; + this.slaveOk = this.options["slave_ok"] ? this.options["slave_ok"] : this.options["slaveOk"]; this._used = false; + this.replicasetInstance = null; // Get the readPreference var readPreference = this.options['readPreference']; + // If readPreference is an object get the mode string + var validateReadPreference = readPreference != null && typeof readPreference == 'object' ? readPreference.mode : readPreference; // Read preference setting - if(readPreference != null) { - if(readPreference != ReadPreference.PRIMARY && readPreference != ReadPreference.SECONDARY && readPreference != ReadPreference.NEAREST - && readPreference != ReadPreference.SECONDARY_PREFERRED && readPreference != ReadPreference.PRIMARY_PREFERRED) { - throw new Error("Illegal readPreference mode specified, " + readPreference); + if(validateReadPreference != null) { + if(validateReadPreference != ReadPreference.PRIMARY && validateReadPreference != ReadPreference.SECONDARY && validateReadPreference != ReadPreference.NEAREST + && validateReadPreference != ReadPreference.SECONDARY_PREFERRED && validateReadPreference != ReadPreference.PRIMARY_PREFERRED) { + throw new Error("Illegal readPreference mode specified, " + validateReadPreference); } // Set read Preference @@ -95,8 +102,7 @@ function Server(host, port, options) { /** * @ignore */ -// Inherit simple event emitter -inherits(Server, EventEmitter); +inherits(Server, Base); // // Deprecated, USE ReadPreferences class @@ -142,7 +148,7 @@ Server.prototype.close = function(callback) { // Set server status as disconnected this._serverState = 'disconnected'; // Peform callback if present - if(typeof callback === 'function') callback(); + if(typeof callback === 'function') callback(null); }; /** @@ -163,10 +169,46 @@ Server.prototype.allServerInstances = function() { * @ignore */ Server.prototype.isSetMember = function() { - return this['replicasetInstance'] != null || this['mongosInstance'] != null; + return this.replicasetInstance != null || this.mongosInstance != null; } /** + * Assigns a replica set to this `server`. + * + * @param {ReplSet} replset + * @ignore + */ +Server.prototype.assignReplicaSet = function (replset) { + this.replicasetInstance = replset; + this.inheritReplSetOptionsFrom(replset); + this.enableRecordQueryStats(replset.recordQueryStats); +} + +/** + * Takes needed options from `replset` and overwrites + * our own options. + * + * @param {ReplSet} replset + * @ignore + */ +Server.prototype.inheritReplSetOptionsFrom = function (replset) { + this.socketOptions = {}; + this.socketOptions.connectTimeoutMS = replset._connectTimeoutMS; + + if(replset.ssl) + this.socketOptions.ssl = true; + + // If a socket option object exists clone it + if(utils.isObject(replset.socketOptions)) { + var keys = Object.keys(replset.socketOptions); + for(var i = 0; i < keys.length; i++) + this.socketOptions[keys[i]] = replset.socketOptions[keys[i]]; + } +} + +/** + * Opens this server connection. + * * @ignore */ Server.prototype.connect = function(dbInstance, options, callback) { @@ -291,8 +333,9 @@ Server.prototype.connect = function(dbInstance, options, callback) { } // The command executed another request, log the handler again under that request id - if(mongoReply.requestId > 0 && mongoReply.cursorId.toString() != "0" && callbackInfo.info && callbackInfo.info.exhaust) { - dbInstance._reRegisterHandler(mongoReply.requestId, callbackInfo); + if(mongoReply.requestId > 0 && mongoReply.cursorId.toString() != "0" + && callbackInfo && callbackInfo.info && callbackInfo.info.exhaust) { + dbInstance._reRegisterHandler(mongoReply.requestId, callbackInfo); } // Only execute callback if we have a caller @@ -317,6 +360,8 @@ Server.prototype.connect = function(dbInstance, options, callback) { // Parse the body mongoReply.parseBody(message, connectionPool.bson, callbackInfo.info.raw, function(err) { + // console.log("+++++++++++++++++++++++++++++++++++++++ recieved message") + // console.dir(message) if(err != null) { // If pool connection is already closed if(server._serverState === 'disconnected') return; @@ -342,7 +387,7 @@ Server.prototype.connect = function(dbInstance, options, callback) { // If we are a single server connection fire errors correctly if(!server.isSetMember()) { // Fire all callback errors - _fireCallbackErrors(server, new Error("connection closed due to parseError")); + server.__executeAllCallbacksWithError(new Error("connection closed due to parseError")); // Emit error _emitAcrossAllDbInstances(server, eventReceiver, "parseError", server, null, true); } @@ -355,6 +400,7 @@ Server.prototype.connect = function(dbInstance, options, callback) { // If we have an error let's execute the callback and clean up all other // chained commands var firstResult = mongoReply && mongoReply.documents; + // Check for an error, if we have one let's trigger the callback and clean up // The chained callbacks if(firstResult[0].err != null || firstResult[0].errmsg != null) { @@ -387,6 +433,8 @@ Server.prototype.connect = function(dbInstance, options, callback) { } else if(callbackInfo && callbackInfo.callback && callbackInfo.info) { // Parse the body mongoReply.parseBody(message, connectionPool.bson, callbackInfo.info.raw, function(err) { + // console.log("+++++++++++++++++++++++++++++++++++++++ recieved message") + // console.dir(message) if(err != null) { // If pool connection is already closed if(server._serverState === 'disconnected') return; @@ -412,7 +460,7 @@ Server.prototype.connect = function(dbInstance, options, callback) { // If we are a single server connection fire errors correctly if(!server.isSetMember()) { // Fire all callback errors - _fireCallbackErrors(server, new Error("connection closed due to parseError")); + server.__executeAllCallbacksWithError(new Error("connection closed due to parseError")); // Emit error _emitAcrossAllDbInstances(server, eventReceiver, "parseError", server, null, true); } @@ -461,7 +509,7 @@ Server.prototype.connect = function(dbInstance, options, callback) { // If we are a single server connection fire errors correctly if(!server.isSetMember()) { // Fire all callback errors - _fireCallbackErrors(server, err); + server.__executeAllCallbacksWithError(err); // Emit error _emitAcrossAllDbInstances(server, eventReceiver, "timeout", err, server, true); } @@ -489,7 +537,7 @@ Server.prototype.connect = function(dbInstance, options, callback) { // If we are a single server connection fire errors correctly if(!server.isSetMember()) { // Fire all callback errors - _fireCallbackErrors(server, new Error(message && message.err ? message.err : message)); + server.__executeAllCallbacksWithError(new Error(message && message.err ? message.err : message)); // Emit error _emitAcrossAllDbInstances(server, eventReceiver, "error", new Error(message && message.err ? message.err : message), server, true); } @@ -517,7 +565,7 @@ Server.prototype.connect = function(dbInstance, options, callback) { // If we are a single server connection fire errors correctly if(!server.isSetMember()) { // Fire all callback errors - _fireCallbackErrors(server, new Error("connection closed")); + server.__executeAllCallbacksWithError(new Error("connection closed")); // Emit error _emitAcrossAllDbInstances(server, eventReceiver, "close", server, null, true); } @@ -546,7 +594,7 @@ Server.prototype.connect = function(dbInstance, options, callback) { // If we are a single server connection fire errors correctly if(!server.isSetMember()) { // Fire all callback errors - _fireCallbackErrors(server, new Error("connection closed due to parseError")); + server.__executeAllCallbacksWithError(new Error("connection closed due to parseError")); // Emit error _emitAcrossAllDbInstances(server, eventReceiver, "parseError", server, null, true); } @@ -556,45 +604,6 @@ Server.prototype.connect = function(dbInstance, options, callback) { connectionPool.start(); } -/** - * Fire all the errors - * @ignore - */ -var _fireCallbackErrors = function(server, err) { - // Locate all the possible callbacks that need to return - for(var i = 0; i < server.dbInstances.length; i++) { - // Fetch the db Instance - var dbInstance = server.dbInstances[i]; - // Check all callbacks - var keys = Object.keys(dbInstance._callBackStore._notReplied); - // For each key check if it's a callback that needs to be returned - for(var j = 0; j < keys.length; j++) { - var info = dbInstance._callBackStore._notReplied[keys[j]]; - // Check if we have a chained command (findAndModify) - if(info && info['chained'] && Array.isArray(info['chained']) && info['chained'].length > 0) { - var chained = info['chained']; - // Only callback once and the last one is the right one - var finalCallback = chained.pop(); - if(info.connection.socketOptions.host === server.host && info.connection.socketOptions.port === server.port) { - dbInstance._callBackStore.emit(finalCallback, err, null); - } - - // Put back the final callback to ensure we don't call all commands in the chain - chained.push(finalCallback); - - // Remove all chained callbacks - for(var i = 0; i < chained.length; i++) { - delete dbInstance._callBackStore._notReplied[chained[i]]; - } - } else { - if(info && info.connection.socketOptions.host === server.host && info.connection.socketOptions.port === server.port) { - dbInstance._callBackStore.emit(keys[j], err, null); - } - } - } - } -} - /** * @ignore */ @@ -649,6 +658,8 @@ var canCheckoutWriter = function(self, read) { * @ignore */ Server.prototype.checkoutWriter = function(read) { + // console.log("===================== checkoutWriter :: " + read) + // console.dir(this.isMasterDoc) if(read == true) return this.connectionPool.checkoutConnection(); // Check if are allowed to do a checkout (if we try to use an arbiter f.ex) var result = canCheckoutWriter(this, read); @@ -687,6 +698,7 @@ var canCheckoutReader = function(self) { * @ignore */ Server.prototype.checkoutReader = function() { + // console.log("===================== checkoutReader") // Check if are allowed to do a checkout (if we try to use an arbiter f.ex) var result = canCheckoutReader(this); // If the result is null check out a writer diff --git a/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/connection/strategies/ping_strategy.js b/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/connection/strategies/ping_strategy.js index 71db4ca..4fefd78 100644 --- a/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/connection/strategies/ping_strategy.js +++ b/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/connection/strategies/ping_strategy.js @@ -144,13 +144,13 @@ PingStrategy.prototype._pingServer = function(callback) { new function(serverInstance) { var options = { poolSize: 1, timeout: 500, auto_reconnect: false }; var server = new Server(serverInstance.host, serverInstance.port, options); - var db = new self.Db(self.replicaset.db.databaseName, server); + var db = new self.Db(self.replicaset.db.databaseName, server, { safe: true }); db.on("error", done); // Open the db instance db.open(function(err, _db) { - if(err) return done(_db); + if(err) return done(err, _db); // Startup time of the command var startTime = Date.now(); @@ -161,13 +161,13 @@ PingStrategy.prototype._pingServer = function(callback) { serverInstance.runtimeStats['pingMs'] = Date.now() - startTime; } - done(_db); + done(null, _db); }) }) - function done (_db) { + function done (err, _db) { // Close connection - _db.close(true); + if (_db) _db.close(true); // Adjust the number of checks numberOfEntries--; diff --git a/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/connection/strategies/statistics_strategy.js b/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/connection/strategies/statistics_strategy.js index 7d01230..2e87dbd 100644 --- a/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/connection/strategies/statistics_strategy.js +++ b/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/connection/strategies/statistics_strategy.js @@ -7,7 +7,7 @@ var StatisticsStrategy = exports.StatisticsStrategy = function(replicaset) { // Starts any needed code StatisticsStrategy.prototype.start = function(callback) { - callback(null, null); + callback && callback(null, null); } StatisticsStrategy.prototype.stop = function(callback) { diff --git a/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/cursor.js b/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/cursor.js index 1ef57e9..f66be71 100644 --- a/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/cursor.js +++ b/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/cursor.js @@ -11,73 +11,77 @@ var QueryCommand = require('./commands/query_command').QueryCommand, * using find. This cursor object is unidirectional and cannot traverse backwards. Clients should not be creating a cursor directly, * but use find to acquire a cursor. * + * Options + * - **skip** {Number} skip number of documents to skip. + * - **limit** {Number}, limit the number of results to return. -1 has a special meaning and is used by Db.eval. A value of 1 will also be treated as if it were -1. + * - **sort** {Array | Object}, set to sort the documents coming back from the query. Array of indexes, [['a', 1]] etc. + * - **hint** {Object}, hint force the query to use a specific index. + * - **explain** {Boolean}, explain return the explaination of the query. + * - **snapshot** {Boolean}, snapshot Snapshot mode assures no duplicates are returned. + * - **timeout** {Boolean}, timeout allow the query to timeout. + * - **tailable** {Boolean}, tailable allow the cursor to be tailable. + * - **awaitdata** {Boolean}, awaitdata allow the cursor to wait for data, only applicable for tailable cursor. + * - **batchSize** {Number}, batchSize the number of the subset of results to request the database to return for every request. This should initially be greater than 1 otherwise the database will automatically close the cursor. The batch size can be set to 1 with cursorInstance.batchSize after performing the initial query to the database. + * - **raw** {Boolean}, raw return all query documents as raw buffers (default false). + * - **read** {Boolean}, read specify override of read from source (primary/secondary). + * - **slaveOk** {Boolean}, slaveOk, sets the slaveOk flag on the query wire protocol for secondaries. + * - **returnKey** {Boolean}, returnKey only return the index key. + * - **maxScan** {Number}, maxScan limit the number of items to scan. + * - **min** {Number}, min set index bounds. + * - **max** {Number}, max set index bounds. + * - **showDiskLoc** {Boolean}, showDiskLoc show disk location of results. + * - **comment** {String}, comment you can put a $comment field on a query to make looking in the profiler logs simpler. + * - **numberOfRetries** {Number}, numberOfRetries if using awaidata specifies the number of times to retry on timeout. + * - **dbName** {String}, dbName override the default dbName. + * - **tailableRetryInterval** {Number}, tailableRetryInterval specify the miliseconds between getMores on tailable cursor. + * - **exhaust** {Boolean}, exhaust have the server send all the documents at once as getMore packets. + * - **partial** {Boolean}, partial have the sharded system return a partial result from mongos. + * * @class Represents a Cursor. * @param {Db} db the database object to work with. * @param {Collection} collection the collection to query. * @param {Object} selector the query selector. * @param {Object} fields an object containing what fields to include or exclude from objects returned. - * @param {Number} skip number of documents to skip. - * @param {Number} limit the number of results to return. -1 has a special meaning and is used by Db.eval. A value of 1 will also be treated as if it were -1. - * @param {String|Array|Object} sort the required sorting for the query. - * @param {Object} hint force the query to use a specific index. - * @param {Boolean} explain return the explaination of the query. - * @param {Boolean} snapshot Snapshot mode assures no duplicates are returned. - * @param {Boolean} timeout allow the query to timeout. - * @param {Boolean} tailable allow the cursor to be tailable. - * @param {Boolean} awaitdata allow the cursor to wait for data, only applicable for tailable cursor. - * @param {Number} batchSize the number of the subset of results to request the database to return for every request. This should initially be greater than 1 otherwise the database will automatically close the cursor. The batch size can be set to 1 with cursorInstance.batchSize after performing the initial query to the database. - * @param {Boolean} raw return all query documents as raw buffers (default false). - * @param {Boolean} read specify override of read from source (primary/secondary). - * @param {Boolean} returnKey only return the index key. - * @param {Number} maxScan limit the number of items to scan. - * @param {Number} min set index bounds. - * @param {Number} max set index bounds. - * @param {Boolean} showDiskLoc show disk location of results. - * @param {String} comment you can put a $comment field on a query to make looking in the profiler logs simpler. - * @param {Number} numberOfRetries if using awaidata specifies the number of times to retry on timeout. - * @param {String} dbName override the default dbName. - * @param {Number} tailableRetryInterval specify the miliseconds between getMores on tailable cursor. - * @param {Boolean} exhaust have the server send all the documents at once as getMore packets. - * @param {Boolean} partial have the sharded system return a partial result from mongos. - */ -function Cursor(db, collection, selector, fields, skip, limit - , sort, hint, explain, snapshot, timeout, tailable, batchSize, slaveOk, raw, read - , returnKey, maxScan, min, max, showDiskLoc, comment, awaitdata, numberOfRetries, dbName, tailableRetryInterval, exhaust, partial) { + * @param {Object} [options] additional options for the collection. +*/ +function Cursor(db, collection, selector, fields, options) { this.db = db; this.collection = collection; this.selector = selector; this.fields = fields; - this.skipValue = skip == null ? 0 : skip; - this.limitValue = limit == null ? 0 : limit; - this.sortValue = sort; - this.hint = hint; - this.explainValue = explain; - this.snapshot = snapshot; - this.timeout = timeout == null ? true : timeout; - this.tailable = tailable; - this.awaitdata = awaitdata; - this.numberOfRetries = numberOfRetries == null ? 5 : numberOfRetries; + options = !options ? {} : options; + + this.skipValue = options.skip == null ? 0 : options.skip; + this.limitValue = options.limit == null ? 0 : options.limit; + this.sortValue = options.sort; + this.hint = options.hint; + this.explainValue = options.explain; + this.snapshot = options.snapshot; + this.timeout = options.timeout == null ? true : options.timeout; + this.tailable = options.tailable; + this.awaitdata = options.awaitdata; + this.numberOfRetries = options.numberOfRetries == null ? 5 : options.numberOfRetries; this.currentNumberOfRetries = this.numberOfRetries; - this.batchSizeValue = batchSize == null ? 0 : batchSize; - this.slaveOk = slaveOk == null ? collection.slaveOk : slaveOk; - this.raw = raw == null ? false : raw; - this.read = read == null ? ReadPreference.PRIMARY : read; - this.returnKey = returnKey; - this.maxScan = maxScan; - this.min = min; - this.max = max; - this.showDiskLoc = showDiskLoc; - this.comment = comment; - this.tailableRetryInterval = tailableRetryInterval || 100; - this.exhaust = exhaust || false; - this.partial = partial || false; + this.batchSizeValue = options.batchSize == null ? 0 : options.batchSize; + this.slaveOk = options.slaveOk == null ? collection.slaveOk : options.slaveOk; + this.raw = options.raw == null ? false : options.raw; + this.read = options.read == null ? ReadPreference.PRIMARY : options.read; + this.returnKey = options.returnKey; + this.maxScan = options.maxScan; + this.min = options.min; + this.max = options.max; + this.showDiskLoc = options.showDiskLoc; + this.comment = options.comment; + this.tailableRetryInterval = options.tailableRetryInterval || 100; + this.exhaust = options.exhaust || false; + this.partial = options.partial || false; this.totalNumberOfRecords = 0; this.items = []; this.cursorId = Long.fromInt(0); // This name - this.dbName = dbName; + this.dbName = options.dbName; // State variables for the cursor this.state = Cursor.INIT; @@ -126,7 +130,7 @@ Cursor.prototype.rewind = function() { * results when this cursor had been previouly accessed. In that case, * cursor.rewind() can be used to reset the cursor. * - * @param {Function} callback This will be called after executing this method successfully. The first paramter will contain the Error object if an error occured, or null otherwise. The second paramter will contain an array of BSON deserialized objects as a result of the query. + * @param {Function} callback This will be called after executing this method successfully. The first parameter will contain the Error object if an error occured, or null otherwise. The second parameter will contain an array of BSON deserialized objects as a result of the query. * @return {null} * @api public */ @@ -168,7 +172,7 @@ Cursor.prototype.toArray = function(callback) { * at any given time if batch size is specified. Otherwise, the caller is responsible * for making sure that the entire result can fit the memory. * - * @param {Function} callback this will be called for while iterating every document of the query result. The first paramter will contain the Error object if an error occured, or null otherwise. While the second paramter will contain the document. + * @param {Function} callback this will be called for while iterating every document of the query result. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the document. * @return {null} * @api public */ @@ -204,7 +208,7 @@ Cursor.prototype.each = function(callback) { /** * Determines how many result the query for this cursor will return * - * @param {Function} callback this will be after executing this method. The first paramter will contain the Error object if an error occured, or null otherwise. While the second paramter will contain the number of results or null if an error occured. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the number of results or null if an error occured. * @return {null} * @api public */ @@ -293,15 +297,20 @@ Cursor.prototype.limit = function(limit, callback) { */ Cursor.prototype.setReadPreference = function(readPreference, tags, callback) { if(typeof tags == 'function') callback = tags; - callback = callback || function() {}; + + var _mode = readPreference != null && typeof readPreference == 'object' ? readPreference.mode : readPreference; if(this.queryRun == true || this.state == Cursor.CLOSED) { + if(callback == null) throw new Error("Cannot change read preference on executed query or closed cursor"); callback(new Error("Cannot change read preference on executed query or closed cursor")); - } else if(readPreference == null && readPreference != 'primary' - && readPreference != 'secondaryOnly' && readPreference != 'secondary') { - callback(new Error("only readPreference of primary, secondary or secondaryOnly supported")); + } else if(_mode != null && _mode != 'primary' + && _mode != 'secondaryOnly' && _mode != 'secondary' + && _mode != 'nearest' && _mode != 'primaryPreferred' && _mode != 'secondaryPreferred') { + if(callback == null) throw new Error("only readPreference of primary, secondary, secondaryPreferred, primaryPreferred or nearest supported"); + callback(new Error("only readPreference of primary, secondary, secondaryPreferred, primaryPreferred or nearest supported")); } else { this.read = readPreference; + if(callback != null) callback(null, this); } return this; @@ -497,7 +506,10 @@ Cursor.prototype.nextObject = function(callback) { return callback(err, null); } + // Execute command var commandHandler = function(err, result) { + self.state = Cursor.OPEN; + // console.dir(err) if(err != null && result == null) return callback(err, null); if(!err && result.documents[0] && result.documents[0]['$err']) { @@ -556,6 +568,12 @@ Cursor.prototype.nextObject = function(callback) { var getMore = function(self, callback) { var limit = 0; + if(self.state == Cursor.GET_MORE) return callback(null, null); + + // Set get more in progress + self.state = Cursor.GET_MORE; + + // Set options if (!self.tailable && self.limitValue > 0) { limit = self.limitValue - self.totalNumberOfRecords; if (limit < 1) { @@ -563,6 +581,7 @@ var getMore = function(self, callback) { return; } } + try { var getMoreCommand = new GetMoreCommand( self.db @@ -576,6 +595,9 @@ var getMore = function(self, callback) { // Execute the command self.db._executeQueryCommand(getMoreCommand, options, function(err, result) { + // Get more done + self.state = Cursor.OPEN; + try { if(err != null) { return callback(err, null); @@ -629,6 +651,9 @@ var getMore = function(self, callback) { getMoreCommand = null; } catch(err) { + // Get more done + self.state = Cursor.OPEN; + var handleClose = function() { callback(err, null); }; @@ -647,11 +672,62 @@ var getMore = function(self, callback) { */ Cursor.prototype.explain = function(callback) { var limit = (-1)*Math.abs(this.limitValue); + + // * - **skip** {Number} skip number of documents to skip. + // * - **limit** {Number}, limit the number of results to return. -1 has a special meaning and is used by Db.eval. A value of 1 will also be treated as if it were -1. + // * - **hint** {Object}, hint force the query to use a specific index. + // * - **explain** {Boolean}, explain return the explaination of the query. + // * - **slaveOk** {Boolean}, slaveOk, sets the slaveOk flag on the query wire protocol for secondaries. + // * - **snapshot** {Boolean}, snapshot Snapshot mode assures no duplicates are returned. + // * - **timeout** {Boolean}, timeout allow the query to timeout. + // * - **tailable** {Boolean}, tailable allow the cursor to be tailable. + // * - **awaitdata** {Boolean}, awaitdata allow the cursor to wait for data, only applicable for tailable cursor. + // * - **batchSize** {Number}, batchSize the number of the subset of results to request the database to return for every request. This should initially be greater than 1 otherwise the database will automatically close the cursor. The batch size can be set to 1 with cursorInstance.batchSize after performing the initial query to the database. + // * - **raw** {Boolean}, raw return all query documents as raw buffers (default false). + // * - **read** {Boolean}, read specify override of read from source (primary/secondary). + // * - **returnKey** {Boolean}, returnKey only return the index key. + // * - **maxScan** {Number}, maxScan limit the number of items to scan. + // * - **min** {Number}, min set index bounds. + // * - **max** {Number}, max set index bounds. + // * - **showDiskLoc** {Boolean}, showDiskLoc show disk location of results. + // * - **comment** {String}, comment you can put a $comment field on a query to make looking in the profiler logs simpler. + // * - **numberOfRetries** {Number}, numberOfRetries if using awaidata specifies the number of times to retry on timeout. + // * - **dbName** {String}, dbName override the default dbName. + // * - **tailableRetryInterval** {Number}, tailableRetryInterval specify the miliseconds between getMores on tailable cursor. + // * - **exhaust** {Boolean}, exhaust have the server send all the documents at once as getMore packets. + // * - **partial** {Boolean}, partial have the sharded system return a partial result from mongos. + + // * - **sort** {Array | Object}, set to sort the documents coming back from the query. Array of indexes, [['a', 1]] etc. + +// function Cursor(db, collection, selector, fields, skip, limit +// - , sort, hint, explain, snapshot, timeout, tailable, batchSize, slaveOk, raw, read +// - , returnKey, maxScan, min, max, showDiskLoc, comment, awaitdata, numberOfRetries, dbName, tailableRetry + // Create a new cursor and fetch the plan - var cursor = new Cursor(this.db, this.collection, this.selector, this.fields, this.skipValue, limit - , this.sortValue, this.hint, true, this.snapshot, this.timeout, this.tailable, this.batchSizeValue - , this.slaveOk, this.raw, this.read, this.returnKey, this.maxScan, this.min, this.max, this.showDiskLoc - , this.comment, this.awaitdata, this.numberOfRetries, this.dbName); + var cursor = new Cursor(this.db, this.collection, this.selector, this.fields, { + skip: this.skipValue + , limit:limit + , sort: this.sortValue + , hint: this.hint + , explain: true + , snapshot: this.snapshot + , timeout: this.timeout + , tailable: this.tailable + , batchSize: this.batchSizeValue + , slaveOk: this.slaveOk + , raw: this.raw + , read: this.read + , returnKey: this.returnKey + , maxScan: this.maxScan + , min: this.min + , max: this.max + , showDiskLoc: this.showDiskLoc + , comment: this.comment + , awaitdata: this.awaitdata + , numberOfRetries: this.numberOfRetries + , dbName: this.dbName + }); + // Fetch the explaination document cursor.nextObject(function(err, item) { if(err != null) return callback(err, null); @@ -761,7 +837,8 @@ Cursor.prototype.close = function(callback) { if(this.cursorId instanceof Long && this.cursorId.greaterThan(Long.fromInt(0))) { try { var command = new KillCursorCommand(this.db, [this.cursorId]); - this.db._executeQueryCommand(command, {read:self.read, raw:self.raw, connection:self.connection}, null); + // Added an empty callback to ensure we don't throw any null exceptions + this.db._executeQueryCommand(command, {read:self.read, raw:self.raw, connection:self.connection}, function() {}); } catch(err) {} } @@ -811,8 +888,15 @@ Cursor.OPEN = 1; **/ Cursor.CLOSED = 2; +/** + * Cursor performing a get more + * + * @classconstant OPEN + **/ +Cursor.GET_MORE = 3; + /** * @ignore * @api private */ -exports.Cursor = Cursor; +exports.Cursor = Cursor; \ No newline at end of file diff --git a/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/cursorstream.js b/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/cursorstream.js index 35a80de..ee0cd80 100644 --- a/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/cursorstream.js +++ b/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/cursorstream.js @@ -70,13 +70,11 @@ CursorStream.prototype._next = function () { if (this.paused || this._destroyed) return; var self = this; - - // nextTick is necessary to avoid stack overflows when - // dealing with large result sets. - process.nextTick(function () { + // Get the next object + process.nextTick(function() { self._cursor.nextObject(function (err, doc) { self._onNextObject(err, doc); - }); + }); }); } @@ -86,16 +84,16 @@ CursorStream.prototype._next = function () { * @api private */ CursorStream.prototype._onNextObject = function (err, doc) { - if (err) return this.destroy(err); + if(err) return this.destroy(err); // when doc is null we hit the end of the cursor - if (!doc) { + if(!doc && (this._cursor.state == 1 || this._cursor.state == 2)) { this.emit('end') return this.destroy(); + } else if(doc) { + this.emit('data', doc); + this._next(); } - - this.emit('data', doc); - this._next(); } /** @@ -114,8 +112,14 @@ CursorStream.prototype.pause = function () { */ CursorStream.prototype.resume = function () { var self = this; + + // Don't do anything if we are not paused + if(!this.paused) return; + if(!this._cursor.state == 3) return; + process.nextTick(function() { self.paused = false; + // Only trigger more fetching if the cursor is open self._next(); }) } diff --git a/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/db.js b/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/db.js index a77aaf5..024f65f 100644 --- a/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/db.js +++ b/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/db.js @@ -14,7 +14,8 @@ var QueryCommand = require('./commands/query_command').QueryCommand, Cursor = require('./cursor').Cursor, EventEmitter = require('events').EventEmitter, inherits = require('util').inherits, - crypto = require('crypto'); + crypto = require('crypto'), + parse = require('./connection/url_parser').parse; /** * Internal class for callback storage @@ -36,7 +37,10 @@ inherits(CallbackStore, EventEmitter); * Create a new Db instance. * * Options - * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, execute insert with a getLastError command returning the result of the insert command. + * - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write + * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) + * - **fsync**, (Boolean, default:false) write waits for fsync before returning + * - **journal**, (Boolean, default:false) write waits for journal sync before returning * - **readPreference** {String}, the prefered read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). * - **native_parser** {Boolean, default:false}, use c++ bson parser. * - **forceServerObjectId** {Boolean, default:false}, force server to create _id fields instead of client. @@ -44,19 +48,19 @@ inherits(CallbackStore, EventEmitter); * - **serializeFunctions** {Boolean, default:false}, serialize functions. * - **raw** {Boolean, default:false}, peform operations using raw bson buffers. * - **recordQueryStats** {Boolean, default:false}, record query statistics during execution. - * - **reaper** {Boolean, default:false}, enables the reaper, timing out calls that never return. - * - **reaperInterval** {Number, default:10000}, number of miliseconds between reaper wakups. - * - **reaperTimeout** {Number, default:30000}, the amount of time before a callback times out. * - **retryMiliSeconds** {Number, default:5000}, number of miliseconds between retries. * - **numberOfRetries** {Number, default:5}, number of retries off connection. + * - **logger** {Object, default:null}, an object representing a logger that you want to use, needs to support functions debug, log, error **({error:function(message, object) {}, log:function(message, object) {}, debug:function(message, object) {}})**. + * + * Deprecated Options + * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. * - * @class Represents a Collection + * @class Represents a Db * @param {String} databaseName name of the database. * @param {Object} serverConfig server config object. * @param {Object} [options] additional options for the collection. */ function Db(databaseName, serverConfig, options) { - if(!(this instanceof Db)) return new Db(databaseName, serverConfig, options); EventEmitter.call(this); @@ -67,8 +71,9 @@ function Db(databaseName, serverConfig, options) { this._applicationClosed = false; // Fetch the override flag if any var overrideUsedFlag = this.options['override_used_flag'] == null ? false : this.options['override_used_flag']; + // Verify that nobody is using this config - if(!overrideUsedFlag && typeof this.serverConfig == 'object' && this.serverConfig._isUsed()) { + if(!overrideUsedFlag && this.serverConfig != null && typeof this.serverConfig == 'object' && this.serverConfig._isUsed && this.serverConfig._isUsed()) { throw new Error("A Server or ReplSet instance cannot be shared across multiple Db instances"); } else if(!overrideUsedFlag && typeof this.serverConfig == 'object'){ // Set being used @@ -82,7 +87,7 @@ function Db(databaseName, serverConfig, options) { try { this.native_parser = this.options.native_parser; // The bson lib - var bsonLib = this.bsonLib = this.options.native_parser ? require('bson').BSONNative : new require('bson').BSONPure; + var bsonLib = this.bsonLib = this.options.native_parser ? require('bson').BSONNative : require('bson').BSONPure; // Fetch the serializer object var BSON = bsonLib.BSON; // Create a new instance @@ -107,10 +112,18 @@ function Db(databaseName, serverConfig, options) { this.safe = this.options.safe == null ? false : this.options.safe; // If we have not specified a "safe mode" we just print a warning to the console - if(this.options.safe == null) { + if(this.options.safe == null && this.options.w == null && this.options.journal == null && this.options.fsync == null) { console.log("========================================================================================"); - console.log("= Please ensure that you set the default safe variable to one of the ="); - console.log("= allowed values of [true | false | {j:true} | {w:n, wtimeout:n} | {fsync:true}] ="); + console.log("= Please ensure that you set the default write concern for the database by setting ="); + console.log("= one of the options ="); + console.log("= ="); + console.log("= w: (value of > -1 or the string 'majority'), where < 1 means ="); + console.log("= no write acknowlegement ="); + console.log("= journal: true/false, wait for flush to journal before acknowlegement ="); + console.log("= fsync: true/false, wait for flush to file system before acknowlegement ="); + console.log("= ="); + console.log("= For backward compatibility safe is still supported and ="); + console.log("= allows values of [true | false | {j:true} | {w:n, wtimeout:n} | {fsync:true}] ="); console.log("= the default value is false which means the driver receives does not ="); console.log("= return the information of the success/error of the insert/update/remove ="); console.log("= ="); @@ -118,7 +131,7 @@ function Db(databaseName, serverConfig, options) { console.log("= ="); console.log("= http://www.mongodb.org/display/DOCS/getLastError+Command ="); console.log("= ="); - console.log("= The default of false will change to true in the near future ="); + console.log("= The default of no acknowlegement will change in the very near future ="); console.log("= ="); console.log("= This message will disappear when the default safe is set on the driver Db ="); console.log("========================================================================================"); @@ -166,69 +179,14 @@ function Db(databaseName, serverConfig, options) { this.serverConfig.enableRecordQueryStats(true); } - // Reaper enable setting - this.reaperEnabled = this.options.reaper != null ? this.options.reaper : false; - this._lastReaperTimestamp = new Date().getTime(); - // Retry information this.retryMiliSeconds = this.options.retryMiliSeconds != null ? this.options.retryMiliSeconds : 1000; this.numberOfRetries = this.options.numberOfRetries != null ? this.options.numberOfRetries : 60; - // Reaper information - this.reaperInterval = this.options.reaperInterval != null ? this.options.reaperInterval : 10000; - this.reaperTimeout = this.options.reaperTimeout != null ? this.options.reaperTimeout : 30000; - // Set default read preference if any this.readPreference = this.options.readPreference; }; -/** - * The reaper cleans up any callbacks that have not returned inside the space set by - * the parameter reaperTimeout, it will only attempt to reap if the time since last reap - * is bigger or equal to the reaperInterval value - * @ignore - */ -var reaper = function(dbInstance, reaperInterval, reaperTimeout) { - // Get current time, compare to reaper interval - var currentTime = new Date().getTime(); - // Now calculate current time difference to check if it's time to reap - if((currentTime - dbInstance._lastReaperTimestamp) >= reaperInterval) { - // Save current timestamp for next reaper iteration - dbInstance._lastReaperTimestamp = currentTime; - // Get all non-replied to messages - var keys = Object.keys(dbInstance._callBackStore._notReplied); - // Iterate over all callbacks - for(var i = 0; i < keys.length; i++) { - // Fetch the current key - var key = keys[i]; - // Get info element - var info = dbInstance._callBackStore._notReplied[key]; - // If it's timed out let's remove the callback and return an error - if((currentTime - info.start) > reaperTimeout) { - // Cleanup - delete dbInstance._callBackStore._notReplied[key]; - // Perform callback in next Tick - process.nextTick(function() { - if(dbInstance._callBackStore - && dbInstance._callBackStore.listeners(key).length > 0 - && typeof dbInstance._callBackStore.listeners(key)[0] == 'function') { - dbInstance._callBackStore.emit(key, new Error("operation timed out"), null); - } else if(dbInstance._callBackStore - && dbInstance._callBackStore.listeners(key).length > 0) { - console.log("================================================= _callBackStore listener not a function"); - console.dir(dbInstance._callBackStore.listeners(key)); - } - }); - } - } - // Return reaping was done - return true; - } else { - // No reaping done - return false; - } -} - /** * @ignore */ @@ -250,7 +208,7 @@ inherits(Db, EventEmitter); /** * Initialize the database connection. * - * @param {Function} callback returns index information. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the index information or null if an error occured. * @return {null} * @api public */ @@ -270,6 +228,7 @@ Db.prototype.open = function(callback) { // Set that db has been opened this.openCalled = true; + // Set the status of the server self._state = 'connecting'; // Set up connections @@ -283,6 +242,8 @@ Db.prototype.open = function(callback) { } // Set the status of the server self._state = 'connected'; + // If we have queued up commands execute a command to trigger replays + if(self.commands.length > 0) _execute_queued_command(self); // Callback return callback(null, self); }); @@ -291,6 +252,24 @@ Db.prototype.open = function(callback) { } }; +// Execute any baked up commands +var _execute_queued_command = function(self) { + // Execute any backed up commands + process.nextTick(function() { + // Execute any backed up commands + while(self.commands.length > 0) { + // Fetch the command + var command = self.commands.shift(); + // Execute based on type + if(command['type'] == 'query') { + __executeQueryCommand(self, command['db_command'], command['options'], command['callback']); + } else if(command['type'] == 'insert') { + __executeInsertCommand(self, command['db_command'], command['options'], command['callback']); + } + } + }); +} + /** * Create a new Db instance sharing the current socket connections. * @@ -327,7 +306,7 @@ Db.prototype.db = function(dbName) { * Close the current db connection, including all the child db instances. Emits close event if no callback is provided. * * @param {Boolean} [forceClose] connection can never be reused. - * @param {Function} [callback] returns the results. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results or null if an error occured. * @return {null} * @api public */ @@ -354,7 +333,7 @@ Db.prototype.close = function(forceClose, callback) { var server = allServerInstances[0]; // For all db instances signal all db instances if(Array.isArray(server.dbInstances) && server.dbInstances.length > 1) { - for(var i = 0; i < server.dbInstances.length; i++) { + for(var i = 0; i < server.dbInstances.length; i++) { var dbInstance = server.dbInstances[i]; // Check if it's our current db instance and skip if it is if(dbInstance.databaseName !== self.databaseName && dbInstance.tag !== self.tag) { @@ -389,7 +368,7 @@ Db.prototype.admin = function(callback) { * Returns a cursor to all the collection information. * * @param {String} [collectionName] the collection name we wish to retrieve the information from. - * @param {Function} callback returns option results. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the options or null if an error occured. * @return {null} * @api public */ @@ -417,7 +396,7 @@ Db.prototype.collectionsInfo = function(collectionName, callback) { * * @param {String} [collectionName] the collection name we wish to filter by. * @param {Object} [options] additional options during update. - * @param {Function} callback returns option results. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the collection names or null if an error occured. * @return {null} * @api public */ @@ -461,15 +440,23 @@ Db.prototype.collectionNames = function(collectionName, options, callback) { * Fetch a specific collection (containing the actual collection information) * * Options - * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. +* - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write + * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) + * - **fsync**, (Boolean, default:false) write waits for fsync before returning + * - **journal**, (Boolean, default:false) write waits for journal sync before returning * - **serializeFunctions** {Boolean, default:false}, serialize functions on the document. * - **raw** {Boolean, default:false}, perform all operations using raw bson objects. * - **pkFactory** {Object}, object overriding the basic ObjectID primary key generation. * - **readPreference** {String}, the prefered read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). + * - **strict**, (Boolean, default:false) throws and error if collection already exists + * + * Deprecated Options + * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. + * * * @param {String} collectionName the collection name we wish to access. * @param {Object} [options] returns option results. - * @param {Function} [callback] returns the results. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the collection or null if an error occured. * @return {null} * @api public */ @@ -477,7 +464,8 @@ Db.prototype.collection = function(collectionName, options, callback) { var self = this; if(typeof options === "function") { callback = options; options = {}; } // Execute safe - if(options && options.safe) { + + if(options && (options.strict)) { self.collectionNames(collectionName, function(err, collections) { if(err != null) return callback(err, null); @@ -511,7 +499,7 @@ Db.prototype.collection = function(collectionName, options, callback) { /** * Fetch all collections for the current db. * - * @param {Function} callback returns the results. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the collections or null if an error occured. * @return {null} * @api public */ @@ -538,7 +526,7 @@ Db.prototype.collections = function(callback) { * @param {Code} code javascript to execute on server. * @param {Object|Array} [parameters] the parameters for the call. * @param {Object} [options] the options - * @param {Function} callback returns the results. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from eval or null if an error occured. * @return {null} * @api public */ @@ -591,7 +579,7 @@ Db.prototype.eval = function(code, parameters, options, callback) { * Dereference a dbref, against a db * * @param {DBRef} dbRef db reference object we wish to resolve. - * @param {Function} callback returns the results. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from dereference or null if an error occured. * @return {null} * @api public */ @@ -609,7 +597,7 @@ Db.prototype.dereference = function(dbRef, callback) { /** * Logout user from server, fire off on all connections and remove all auth info * - * @param {Function} callback returns the results. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from logout or null if an error occured. * @return {null} * @api public */ @@ -653,7 +641,7 @@ Db.prototype.logout = function(options, callback) { * @param {String} username username. * @param {String} password password. * @param {Object} [options] the options - * @param {Function} callback returns the results. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from authentication or null if an error occured. * @return {null} * @api public */ @@ -667,14 +655,22 @@ Db.prototype.authenticate = function(username, password, options, callback) { // the default db to authenticate against is 'this' // if authententicate is called from a retry context, it may be another one, like admin var authdb = options.authdb ? options.authdb : self.databaseName; - // Push the new auth if we have no previous record - // Get the amount of connections in the pool to ensure we have authenticated all comments - var numberOfConnections = this.serverConfig.allRawConnections().length; + + var numberOfConnections = 0; var errorObject = null; + if(options['connection'] != null) { + //if a connection was explicitly passed on options, then we have only one... + numberOfConnections = 1; + } else { + // Get the amount of connections in the pool to ensure we have authenticated all comments + numberOfConnections = this.serverConfig.allRawConnections().length; + options['onAll'] = true; + } + // Execute all four - this._executeQueryCommand(DbCommand.createGetNonceCommand(self), {onAll:true}, function(err, result, connection) { + this._executeQueryCommand(DbCommand.createGetNonceCommand(self), options, function(err, result, connection) { // Execute on all the connections if(err == null) { // Nonce used to make authentication request with md5 hash @@ -713,12 +709,18 @@ Db.prototype.authenticate = function(username, password, options, callback) { * Add a user to the database. * * Options +* - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write + * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) + * - **fsync**, (Boolean, default:false) write waits for fsync before returning + * - **journal**, (Boolean, default:false) write waits for journal sync before returning + * + * Deprecated Options * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. * * @param {String} username username. * @param {String} password password. * @param {Object} [options] additional options during update. - * @param {Function} callback returns the results. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from addUser or null if an error occured. * @return {null} * @api public */ @@ -729,12 +731,11 @@ Db.prototype.addUser = function(username, password, options, callback) { options = args.length ? args.shift() : {}; // Figure out the safe mode settings - var safe = self.safe != null && self.safe == false ? true : self.safe; + var safe = self.safe != null && self.safe == false ? {w: 1} : self.safe; // Override with options passed in if applicable safe = options != null && options['safe'] != null ? options['safe'] : safe; // Ensure it's at least set to safe - safe = safe == null ? true : safe; - + safe = safe == null ? {w: 1} : safe; // Use node md5 generator var md5 = crypto.createHash('md5'); // Generate keys used for authentication @@ -750,8 +751,12 @@ Db.prototype.addUser = function(username, password, options, callback) { collection.find({user: username}, {dbName: options['dbName']}).toArray(function(err, documents) { // We got an error (f.ex not authorized) if(err != null) return callback(err, null); + // Add command keys + var commandOptions = safe; + commandOptions.dbName = options['dbName']; + commandOptions.upsert = true; // We have a user, let's update the password or upsert if not - collection.update({user: username},{$set: {user: username, pwd: userPassword}}, {safe:safe, dbName: options['dbName'], upsert:true}, function(err, results) { + collection.update({user: username},{$set: {user: username, pwd: userPassword}}, commandOptions, function(err, results) { if(count == 0 && err) { callback(null, [{user:username, pwd:userPassword}]); } else if(err) { @@ -768,11 +773,17 @@ Db.prototype.addUser = function(username, password, options, callback) { * Remove a user from a database * * Options +* - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write + * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) + * - **fsync**, (Boolean, default:false) write waits for fsync before returning + * - **journal**, (Boolean, default:false) write waits for journal sync before returning + * + * Deprecated Options * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. * * @param {String} username username. * @param {Object} [options] additional options during update. - * @param {Function} callback returns the results. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from removeUser or null if an error occured. * @return {null} * @api public */ @@ -783,17 +794,21 @@ Db.prototype.removeUser = function(username, options, callback) { options = args.length ? args.shift() : {}; // Figure out the safe mode settings - var safe = self.safe != null && self.safe == false ? true : self.safe; + var safe = self.safe != null && self.safe == false ? {w: 1} : self.safe; // Override with options passed in if applicable safe = options != null && options['safe'] != null ? options['safe'] : safe; // Ensure it's at least set to safe - safe = safe == null ? true : safe; + safe = safe == null ? {w: 1} : safe; // Fetch a user collection var collection = this.collection(DbCommand.SYSTEM_USER_COLLECTION); collection.findOne({user: username}, {dbName: options['dbName']}, function(err, user) { if(user != null) { - collection.remove({user: username}, {safe:safe, dbName: options['dbName']}, function(err, result) { + // Add command keys + var commandOptions = safe; + commandOptions.dbName = options['dbName']; + + collection.remove({user: username}, commandOptions, function(err, result) { callback(err, true); }); } else { @@ -806,19 +821,26 @@ Db.prototype.removeUser = function(username, options, callback) { * Creates a collection on a server pre-allocating space, need to create f.ex capped collections. * * Options - * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. +* - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write + * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) + * - **fsync**, (Boolean, default:false) write waits for fsync before returning + * - **journal**, (Boolean, default:false) write waits for journal sync before returning * - **serializeFunctions** {Boolean, default:false}, serialize functions on the document. * - **raw** {Boolean, default:false}, perform all operations using raw bson objects. * - **pkFactory** {Object}, object overriding the basic ObjectID primary key generation. * - **capped** {Boolean, default:false}, create a capped collection. * - **size** {Number}, the size of the capped collection in bytes. * - **max** {Number}, the maximum number of documents in the capped collection. - * - **autoIndexId** {Boolean, default:false}, create an index on the _id field of the document, not created automatically on capped collections. + * - **autoIndexId** {Boolean, default:true}, create an index on the _id field of the document, True by default on MongoDB 2.2 or higher off for version < 2.2. * - **readPreference** {String}, the prefered read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). + * - **strict**, (Boolean, default:false) throws and error if collection already exists + * + * Deprecated Options + * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. * * @param {String} collectionName the collection name we wish to access. * @param {Object} [options] returns option results. - * @param {Function} callback returns the results. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from createCollection or null if an error occured. * @return {null} * @api public */ @@ -829,11 +851,11 @@ Db.prototype.createCollection = function(collectionName, options, callback) { var self = this; // Figure out the safe mode settings - var safe = self.safe != null && self.safe == false ? true : self.safe; + var safe = self.safe != null && self.safe == false ? {w: 1} : self.safe; // Override with options passed in if applicable safe = options != null && options['safe'] != null ? options['safe'] : safe; // Ensure it's at least set to safe - safe = safe == null ? true : safe; + safe = safe == null ? {w: 1} : safe; // Check if we have the name this.collectionNames(collectionName, function(err, collections) { @@ -845,7 +867,7 @@ Db.prototype.createCollection = function(collectionName, options, callback) { }); // If the collection exists either throw an exception (if db in safe mode) or return the existing collection - if(found && ((options && options.safe))) { + if(found && options && options.strict) { return callback(new Error("Collection " + collectionName + " already exists. Currently in safe mode."), null); } else if(found){ try { @@ -878,7 +900,7 @@ Db.prototype.createCollection = function(collectionName, options, callback) { * Execute a command hash against MongoDB. This lets you acess any commands not available through the api on the server. * * @param {Object} selector the command hash to send to the server, ex: {ping:1}. - * @param {Function} callback returns the results. + * @param {Function} callback this will be called after executing this method. The command always return the whole result of the command as the second parameter. * @return {null} * @api public */ @@ -889,8 +911,9 @@ Db.prototype.command = function(selector, options, callback) { // Set up the options var cursor = new Cursor(this - , new Collection(this, DbCommand.SYSTEM_COMMAND_COLLECTION), selector, {}, 0, -1, null, null, null, null, QueryCommand.OPTS_NO_CURSOR_TIMEOUT - , null, null, null, null, null, null, null, null, null, null, null, null, null, options['dbName']); + , new Collection(this, DbCommand.SYSTEM_COMMAND_COLLECTION), selector, {}, { + limit: -1, timeout: QueryCommand.OPTS_NO_CURSOR_TIMEOUT, dbName: options['dbName'] + }); // Set read preference if we set one var readPreference = options['readPreference'] ? options['readPreference'] : false; @@ -915,7 +938,7 @@ Db.prototype.command = function(selector, options, callback) { * Drop a collection from the database, removing it permanently. New accesses will create a new collection. * * @param {String} collectionName the name of the collection we wish to drop. - * @param {Function} callback returns the results. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from dropCollection or null if an error occured. * @return {null} * @api public */ @@ -937,7 +960,7 @@ Db.prototype.dropCollection = function(collectionName, callback) { * * @param {String} fromCollection the name of the current collection we wish to rename. * @param {String} toCollection the new name of the collection. - * @param {Function} callback returns the results. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from renameCollection or null if an error occured. * @return {null} * @api public */ @@ -968,7 +991,7 @@ Db.prototype.renameCollection = function(fromCollection, toCollection, callback) * * @param {Object} [options] returns option results. * @param {Object} [connectionOptions] returns option results. - * @param {Function} callback returns the results. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from lastError or null if an error occured. * @return {null} * @api public */ @@ -1000,7 +1023,7 @@ Db.prototype.lastStatus = Db.prototype.lastError; * - **connection** {Connection}, fire the getLastError down a specific connection. * * @param {Object} [options] returns option results. - * @param {Function} callback returns the results. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from previousErrors or null if an error occured. * @return {null} * @api public */ @@ -1042,7 +1065,7 @@ Db.prototype.executeDbAdminCommand = function(command_hash, options, callback) { * - **connection** {Connection}, fire the getLastError down a specific connection. * * @param {Object} [options] returns option results. - * @param {Function} callback returns the results. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from resetErrorHistory or null if an error occured. * @return {null} * @api public */ @@ -1061,7 +1084,10 @@ Db.prototype.resetErrorHistory = function(options, callback) { * Creates an index on the collection. * * Options - * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a +* - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write + * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) + * - **fsync**, (Boolean, default:false) write waits for fsync before returning + * - **journal**, (Boolean, default:false) write waits for journal sync before returning * - **unique** {Boolean, default:false}, creates an unique index. * - **sparse** {Boolean, default:false}, creates a sparse index. * - **background** {Boolean, default:false}, creates the index in the background, yielding whenever possible. @@ -1071,11 +1097,15 @@ Db.prototype.resetErrorHistory = function(options, callback) { * - **v** {Number}, specify the format version of the indexes. * - **expireAfterSeconds** {Number}, allows you to expire data on indexes applied to a data (MongoDB 2.2 or higher) * - **name** {String}, override the autogenerated index name (useful if the resulting name is larger than 128 bytes) + * + * Deprecated Options + * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. + * * * @param {String} collectionName name of the collection to create the index on. * @param {Object} fieldOrSpec fieldOrSpec that defines the index. * @param {Object} [options] additional options during update. - * @param {Function} callback for results. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from createIndex or null if an error occured. * @return {null} * @api public */ @@ -1087,20 +1117,15 @@ Db.prototype.createIndex = function(collectionName, fieldOrSpec, options, callba options = typeof callback === 'function' ? options : callback; options = options == null ? {} : options; - // Collect errorOptions - var errorOptions = options.safe != null ? options.safe : null; - errorOptions = errorOptions == null && self.safe != null ? self.safe : errorOptions; - - // If we have a write concern set and no callback throw error - if(errorOptions != null && errorOptions != false && (typeof callback !== 'function' && typeof options !== 'function')) throw new Error("safe cannot be used without a callback"); - + // Get the error options + var errorOptions = _getWriteConcern(this, options, callback); // Create command var command = DbCommand.createCreateIndexCommand(this, collectionName, fieldOrSpec, options); // Default command options var commandOptions = {}; // If we have error conditions set handle them - if(errorOptions && errorOptions != false) { + if(_hasWriteConcern(errorOptions) && typeof callback == 'function') { // Insert options commandOptions['read'] = false; // If we have safe set set async to false @@ -1127,6 +1152,8 @@ Db.prototype.createIndex = function(collectionName, fieldOrSpec, options, callba callback(null, command.documents[0].name); } }); + } else if(_hasWriteConcern(errorOptions) && callback == null) { + throw new Error("Cannot use a writeConcern without a provided callback"); } else { // Execute insert command var result = this._executeInsertCommand(command, commandOptions); @@ -1145,7 +1172,10 @@ Db.prototype.createIndex = function(collectionName, fieldOrSpec, options, callba * Ensures that an index exists, if it does not it creates it * * Options - * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a +* - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write + * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) + * - **fsync**, (Boolean, default:false) write waits for fsync before returning + * - **journal**, (Boolean, default:false) write waits for journal sync before returning * - **unique** {Boolean, default:false}, creates an unique index. * - **sparse** {Boolean, default:false}, creates a sparse index. * - **background** {Boolean, default:false}, creates the index in the background, yielding whenever possible. @@ -1155,11 +1185,14 @@ Db.prototype.createIndex = function(collectionName, fieldOrSpec, options, callba * - **v** {Number}, specify the format version of the indexes. * - **expireAfterSeconds** {Number}, allows you to expire data on indexes applied to a data (MongoDB 2.2 or higher) * - **name** {String}, override the autogenerated index name (useful if the resulting name is larger than 128 bytes) + * + * Deprecated Options + * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. * * @param {String} collectionName name of the collection to create the index on. * @param {Object} fieldOrSpec fieldOrSpec that defines the index. * @param {Object} [options] additional options during update. - * @param {Function} callback for results. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from ensureIndex or null if an error occured. * @return {null} * @api public */ @@ -1175,13 +1208,11 @@ Db.prototype.ensureIndex = function(collectionName, fieldOrSpec, options, callba options = {}; } - // Collect errorOptions - var errorOptions = options.safe != null ? options.safe : null; - errorOptions = errorOptions == null && self.safe != null ? self.safe : errorOptions; - - // If we have a write concern set and no callback throw error - if(errorOptions != null && errorOptions != false && (typeof callback !== 'function' && typeof options !== 'function')) throw new Error("safe cannot be used without a callback"); - + // Get the error options + var errorOptions = _getWriteConcern(this, options, callback); + // Make sure we don't try to do a write concern without a callback + if(_hasWriteConcern(errorOptions) && callback == null) + throw new Error("Cannot use a writeConcern without a provided callback"); // Create command var command = DbCommand.createCreateIndexCommand(this, collectionName, fieldOrSpec, options); var index_name = command.documents[0].name; @@ -1194,14 +1225,12 @@ Db.prototype.ensureIndex = function(collectionName, fieldOrSpec, options, callba if(!collectionInfo[index_name]) { // If we have error conditions set handle them - if(errorOptions && errorOptions != false) { + if(_hasWriteConcern(errorOptions) && typeof callback == 'function') { // Insert options commandOptions['read'] = false; // If we have safe set set async to false if(errorOptions == null) commandOptions['async'] = true; - // Set safe option - commandOptions['safe'] = errorOptions; // If we have an error option if(typeof errorOptions == 'object') { var keys = Object.keys(errorOptions); @@ -1210,6 +1239,11 @@ Db.prototype.ensureIndex = function(collectionName, fieldOrSpec, options, callba } } + if(typeof callback === 'function' + && commandOptions.w < 1 && !commandOptions.fsync && !commandOptions.journal) { + commandOptions.w = 1; + } + self._executeInsertCommand(command, commandOptions, function(err, result) { // Only callback if we have one specified if(typeof callback === 'function') { @@ -1248,7 +1282,7 @@ Db.prototype.ensureIndex = function(collectionName, fieldOrSpec, options, callba * - **readPreference** {String}, the prefered read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). * * @param {Object} [options] additional options during update. - * @param {Function} callback for results. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from cursorInfo or null if an error occured. * @return {null} * @api public */ @@ -1267,7 +1301,7 @@ Db.prototype.cursorInfo = function(options, callback) { * * @param {String} collectionName the name of the collection where the command will drop an index. * @param {String} indexName name of the index to drop. - * @param {Function} callback for results. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from dropIndex or null if an error occured. * @return {null} * @api public */ @@ -1280,7 +1314,7 @@ Db.prototype.dropIndex = function(collectionName, indexName, callback) { * Warning: reIndex is a blocking operation (indexes are rebuilt in the foreground) and will be slow for large collections. * * @param {String} collectionName the name of the collection. - * @param {Function} callback returns the results. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from reIndex or null if an error occured. * @api public **/ Db.prototype.reIndex = function(collectionName, callback) { @@ -1304,7 +1338,7 @@ Db.prototype.reIndex = function(collectionName, callback) { * * @param {String} collectionName the name of the collection. * @param {Object} [options] additional options during update. - * @param {Function} callback returns the index information. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from indexInformation or null if an error occured. * @return {null} * @api public */ @@ -1357,7 +1391,7 @@ Db.prototype.indexInformation = function(collectionName, options, callback) { /** * Drop a database. * - * @param {Function} callback returns the index information. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from dropDatabase or null if an error occured. * @return {null} * @api public */ @@ -1385,7 +1419,7 @@ Db.prototype.dropDatabase = function(callback) { * - **readPreference** {String}, the preferred read preference ((Server.PRIMARY, Server.PRIMARY_PREFERRED, Server.SECONDARY, Server.SECONDARY_PREFERRED, Server.NEAREST). * * @param {Objects} [options] options for the stats command - * @param {Function} callback returns statistical information for the db. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from stats or null if an error occured. * @return {null} * @api public */ @@ -1535,6 +1569,10 @@ var __executeQueryCommand = function(self, db_command, options, callback) { if(typeof callback === 'function' && !onAll) { // Override connection if we passed in a specific connection var connection = specifiedConnection != null ? specifiedConnection : null; + // connection = connection != null && connection.connected != null ? connection : null; + + if(connection instanceof Error) return callback(connection, null); + // Fetch either a reader or writer dependent on the specified read option if no connection // was passed in if(connection == null) { @@ -1548,9 +1586,6 @@ var __executeQueryCommand = function(self, db_command, options, callback) { return callback(connection); } - // Perform reaping of any dead connection - if(self.reaperEnabled) reaper(self, self.reaperInterval, self.reaperTimeout); - // Exhaust Option var exhaust = options.exhaust || false; @@ -1571,8 +1606,7 @@ var __executeQueryCommand = function(self, db_command, options, callback) { for(var i = 0; i < connections.length; i++) { // Fetch a connection var connection = connections[i]; - // Override connection if needed - connection = specifiedConnection != null ? specifiedConnection : connection; + // Ensure we have a valid connection if(connection == null) { return callback(new Error("no open connections")); @@ -1665,21 +1699,8 @@ var __retryCommandOnFailure = function(self, retryInMilliseconds, numberOfTimes, } else { // Execute command command(_self, _db_command, _options, _callback); - - // Execute any backed up commands - process.nextTick(function() { - // Execute any backed up commands - while(_self.commands.length > 0) { - // Fetch the command - var command = _self.commands.shift(); - // Execute based on type - if(command['type'] == 'query') { - __executeQueryCommand(_self, command['db_command'], command['options'], command['callback']); - } else if(command['type'] == 'insert') { - __executeInsertCommand(_self, command['db_command'], command['options'], command['callback']); - } - } - }); + // Execute all the commands + if(_self.commands.length > 0) _execute_queued_command(_self); } } }); @@ -1735,64 +1756,58 @@ Db.prototype._executeQueryCommand = function(db_command, options, callback) { } // fast fail option used for HA, no retry - var failFast = options['failFast'] != null ? options['failFast'] : false; + var failFast = options['failFast'] != null + ? options['failFast'] + : false; + // Check if the user force closed the command if(this._applicationClosed) { - if(typeof callback == 'function') { - return callback(new Error("db closed by application"), null); + var err = new Error("db closed by application"); + if('function' == typeof callback) { + return callback(err, null); } else { - throw new Error("db closed by application"); + throw err; } } + var config = this.serverConfig; // If the pool is not connected, attemp to reconnect to send the message - if(this._state == 'connecting' && this.serverConfig.autoReconnect && !failFast) { - process.nextTick(function() { - self.commands.push({type:'query', 'db_command':db_command, 'options':options, 'callback':callback}); + if(this._state == 'connecting' && config.autoReconnect && !failFast) { + return process.nextTick(function() { + self.commands.push({ + type: 'query', + db_command: db_command, + options: options, + callback: callback + }); }) - } else if(!this.serverConfig.isConnected() && this.serverConfig.autoReconnect && !failFast) { - this._state = 'connecting'; - // Retry command - __retryCommandOnFailure(this, this.retryMiliSeconds, this.numberOfRetries, __executeQueryCommand, db_command, options, callback); - } else if(!this.serverConfig.isConnected() && !this.serverConfig.autoReconnect && callback) { - // Fire an error to the callback if we are not connected and don't do reconnect - callback(new Error("no open connections"), null); - } else { - // If we have a - if(this.serverConfig instanceof ReplSet && this.serverConfig._checkReplicaSet()) { - // Execute the command in waiting - __executeQueryCommand(self, db_command, options, function(err, result, connection) { - if(!err) { - process.nextTick(function() { - // Force close if we are disconnected - if(self._state == 'disconnected') { - self.close(); - return; - } - - var replSetGetStatusCommand = DbCommand.createAdminDbCommandSlaveOk(self, {replSetGetStatus:1}, {}); - // Do a freaking ping - __executeQueryCommand(self, replSetGetStatusCommand, {readPreference:ReadPreference.SECONDARY_PREFERRED}, function(_replerr, _replresult) { - // Force close if we are disconnected - if(self._state == 'disconnected') { - self.close(true); - return; - } - - // Handle the HA - if(_replerr == null) { - self.serverConfig._validateReplicaset(_replresult, self.auths); - } - }) - }) - } - // Call the original method - callback(err, result, connection) - }) - } else { - __executeQueryCommand(self, db_command, options, callback) - } } + + if(!failFast && !config.isConnected(options.read) && config.autoReconnect + && (options.read == null + || options.read == false + || options.read == ReadPreference.PRIMARY + || config.checkoutReader() == null)) { + this._state = 'connecting'; + return __retryCommandOnFailure(this, + this.retryMiliSeconds, + this.numberOfRetries, + __executeQueryCommand, + db_command, + options, + callback); + } + + if(!config.isConnected(options.read) && !config.autoReconnect && callback) { + // Fire an error to the callback if we are not connected + // and don't reconnect. + return callback(new Error("no open connections"), null); + } + + __executeQueryCommand(self, db_command, options, function (err, result, conn) { + callback(err, result, conn); + }); + }; /** @@ -1817,13 +1832,12 @@ var __executeInsertCommand = function(self, db_command, options, callback) { return callback(connection); } - // We are expecting a check right after the actual operation - if(safe != null && safe != false) { + var errorOptions = _getWriteConcern(self, options, callback); + if(errorOptions.w > 0 || errorOptions.w == 'majority' || errorOptions.j || errorOptions.journal || errorOptions.fsync) { // db command is now an array of commands (original command + lastError) db_command = [db_command, DbCommand.createGetLastErrorCommand(safe, self)]; - // Register the handler in the data structure - self._registerHandler(db_command[1], raw, connection, callback); + self._registerHandler(db_command[1], raw, connection, callback); } } @@ -1834,17 +1848,18 @@ var __executeInsertCommand = function(self, db_command, options, callback) { if(connection == null && typeof callback == 'function') return callback(new Error("no primary server found"), null); // Write the message out - connection.write(db_command, function(err) { + connection.write(db_command, function(err) { // Return the callback if it's not a safe operation and the callback is defined if(typeof callback === 'function' && (safe == null || safe == false)) { - // Perform reaping - if(self.reaperEnabled) reaper(self, self.reaperInterval, self.reaperTimeout); // Perform the callback callback(err, null); - } else if(typeof callback === 'function'){ + } else if(typeof callback === 'function') { // Call the handler with an error self._callHandler(db_command[1].getRequestId(), null, err); - } else { + } else if(typeof callback == 'function' && safe && safe.w == -1) { + // Call the handler with no error + self._callHandler(db_command[1].getRequestId(), null, null); + } else if(!safe && safe.w == -1) { self.emit("error", err); } }); @@ -1889,28 +1904,7 @@ Db.prototype._executeInsertCommand = function(db_command, options, callback) { // Fire an error to the callback if we are not connected and don't do reconnect if(callback) callback(new Error("no open connections"), null); } else { - // If we have a - if(this.serverConfig instanceof ReplSet && this.serverConfig._checkReplicaSet()) { - // Execute insert command - __executeInsertCommand(self, db_command, options, callback) - - var replSetGetStatusCommand = DbCommand.createAdminDbCommandSlaveOk(self, {replSetGetStatus:1}, {}); - // Do a freaking ping - __executeQueryCommand(self, replSetGetStatusCommand, {readPreference:ReadPreference.SECONDARY_PREFERRED}, function(_replerr, _replresult) { - // Force close if we are disconnected - if(self._state == 'disconnected') { - self.close(true); - return; - } - - // Handle the HA - if(_replerr == null) { - self.serverConfig._validateReplicaset(_replresult, self.auths); - } - }) - } else { - __executeInsertCommand(self, db_command, options, callback) - } + __executeInsertCommand(self, db_command, options, callback); } } @@ -1938,7 +1932,7 @@ Db.prototype.wrap = function(error) { e.name = 'MongoError'; // Get all object keys - var keys = Object.keys(error); + var keys = typeof error == 'object' ? Object.keys(error) : []; // Populate error object with properties for(var i = 0; i < keys.length; i++) { e[keys[i]] = error[keys[i]]; @@ -1961,10 +1955,14 @@ Db.DEFAULT_URL = 'mongodb://localhost:27017/default'; * * Options * - **uri_decode_auth** {Boolean, default:false} uri decode the user name and password for authentication + * - **db** {Object, default: null} a hash off options to set on the db object, see **Db constructor** + * - **server** {Object, default: null} a hash off options to set on the server objects, see **Server** constructor** + * - **replSet** {Object, default: null} a hash off options to set on the replSet object, see **ReplSet** constructor** + * - **mongos** {Object, default: null} a hash off options to set on the mongos object, see **Mongos** constructor** * * @param {String} url connection url for MongoDB. * @param {Object} [options] optional options for insert command - * @param {Function} callback callback returns the initialized db. + * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the db instance or null if an error occured. * @return {null} * @api public */ @@ -1974,112 +1972,146 @@ Db.connect = function(url, options, callback) { options = args.length ? args.shift() : null; options = options || {}; var serverOptions = options.server || {}; + var mongosOptions = options.mongos || {}; var replSetServersOptions = options.replSet || options.replSetServers || {}; var dbOptions = options.db || {}; - // Ensure empty socket option field - serverOptions.socketOptions = serverOptions.socketOptions || {}; - replSetServersOptions.socketOptions = serverOptions.socketOptions || {}; - - // Match the url format - var urlRE = new RegExp('^mongo(?:db)?://(?:|([^@/]*)@)([^@/]*)(?:|/([^?]*)(?:|\\?([^?]*)))$'); - var match = (url || Db.DEFAULT_URL).match(urlRE); - if (!match) - throw Error("URL must be in the format mongodb://user:pass@host:port/dbname"); - - var authPart = match[1] || ''; - var auth = authPart.split(':', 2); - if(options['uri_decode_auth']){ - auth[0] = decodeURIComponent(auth[0]); - if(auth[1]){ - auth[1] = decodeURIComponent(auth[1]); - } - } - - var hostPart = match[2]; - var dbname = match[3] || 'default'; - var urlOptions = (match[4] || '').split(/[&;]/); - - // Ugh, we have to figure out which options go to which constructor manually. - urlOptions.forEach(function(opt) { - if(!opt) return; - var splitOpt = opt.split('='), name = splitOpt[0], value = splitOpt[1]; - - // Options implementations - switch(name) { - case 'slaveOk': - case 'slave_ok': - serverOptions.slave_ok = (value == 'true'); - break; - case 'poolSize': - serverOptions.poolSize = Number(value); - break; - case 'autoReconnect': - case 'auto_reconnect': - serverOptions.auto_reconnect = (value == 'true'); - break; - case 'ssl': - serverOptions.ssl = (value == 'true'); - break; - case 'replicaSet': - case 'rs_name': - replSetServersOptions.rs_name = value; - break; - case 'reconnectWait': - replSetServersOptions.reconnectWait = Number(value); - break; - case 'retries': - replSetServersOptions.retries = Number(value); - break; - case 'readSecondary': - case 'read_secondary': - replSetServersOptions.retries = Number(value); - break; - case 'safe': - dbOptions.safe = (value == 'true'); - break; - case 'nativeParser': - case 'native_parser': - dbOptions.native_parser = (value == 'true'); - break; - case 'safe': - dbOptions.safe = (value == 'true'); - break; - case 'connectTimeoutMS': - serverOptions.socketOptions.connectTimeoutMS = Number(value); - replSetServersOptions.socketOptions.connectTimeoutMS = Number(value); - break; - case 'socketTimeoutMS': - serverOptions.socketOptions.socketTimeoutMS = Number(value); - replSetServersOptions.socketOptions.socketTimeoutMS = Number(value); - break; - default: - break; - } - }); - - var servers = hostPart.split(',').map(function(h) { - var hostPort = h.split(':', 2); - return new Server(hostPort[0] || 'localhost', hostPort[1] != null ? parseInt(hostPort[1]) : 27017, serverOptions); - }); - - var server; - if (servers.length == 1) { - server = servers[0]; - } else { - server = new ReplSet(servers, replSetServersOptions); - } - - var db = new Db(dbname, server, dbOptions); - if(options.noOpen) - return db; - // If callback is null throw an exception if(callback == null) throw new Error("no callback function provided"); + // Parse the string + var object = parse(url); + // Merge in any options for db in options object + if(dbOptions) { + for(var name in dbOptions) object.db_options[name] = dbOptions[name]; + } + + // Merge in any options for server in options object + if(serverOptions) { + for(var name in serverOptions) object.server_options[name] = serverOptions[name]; + } + + // Merge in any replicaset server options + if(replSetServersOptions) { + for(var name in replSetServersOptions) object.rs_options[name] = replSetServersOptions[name]; + } + + // Merge in any replicaset server options + if(mongosOptions) { + for(var name in mongosOptions) object.mongos_options[name] = mongosOptions[name]; + } + + // We need to ensure that the list of servers are only either direct members or mongos + // they cannot be a mix of monogs and mongod's + var totalNumberOfServers = object.servers.length; + var totalNumberOfMongosServers = 0; + var totalNumberOfMongodServers = 0; + var serverConfig = null; + + // Failure modes + if(object.servers.length == 0) throw new Error("connection string must contain at least one seed host"); + + // If we have no db setting for the native parser try to set the c++ one first + object.db_options.native_parser = _setNativeParser(object.db_options); + // If no auto_reconnect is set, set it to true as default for single servers + if(typeof object.server_options.auto_reconnect != 'boolean') { + object.server_options.auto_reconnect = true; + } + + // If we have more than a server, it could be replicaset or mongos list + // need to verify that it's one or the other and fail if it's a mix + // Connect to all servers and run ismaster + for(var i = 0; i < object.servers.length; i++) { + // Set up the Server object + var _server = object.servers[i].domain_socket + ? new Server(object.servers[i].domain_socket, {socketOptions:{connectTimeoutMS:1000}, auto_reconnect:false}) + : new Server(object.servers[i].host, object.servers[i].port, {socketOptions:{connectTimeoutMS:1000}, auto_reconnect:false}); + + // Attempt connect + new Db(object.dbName, _server, {safe:false, native_parser:false}).open(function(err, db) { + // Update number of servers + totalNumberOfServers = totalNumberOfServers - 1; + // If no error do the correct checks + if(!err) { + // Close the connection + db.close(true); + var isMasterDoc = db.serverConfig.isMasterDoc; + // Check what type of server we have + if(isMasterDoc.setName) totalNumberOfMongodServers++; + if(isMasterDoc.msg && isMasterDoc.msg == "isdbgrid") totalNumberOfMongosServers++; + } + + if(totalNumberOfServers == 0) { + // If we have a mix of mongod and mongos, throw an error + if(totalNumberOfMongosServers > 0 && totalNumberOfMongodServers > 0) + return callback(new Error("cannot combine a list of replicaset seeds and mongos seeds")); + + if(totalNumberOfMongodServers == 0 && object.servers.length == 1) { + var obj = object.servers[0]; + serverConfig = obj.domain_socket ? + new Server(obj.domain_socket, object.server_options) + : new Server(obj.host, obj.port, object.server_options); + } else if(totalNumberOfMongodServers > 0) { + serverConfig = new ReplSet(object.servers.map(function(serverObj) { + return new Server(serverObj.host, serverObj.port, object.server_options); + }), object.rs_options); + } else if(totalNumberOfMongosServers > 0) { + serverConfig = new Mongos(object.servers.map(function(serverObj) { + return new Server(serverObj.host, serverObj.port, object.server_options); + }), object.mongos_options); + } + + if(serverConfig == null) return callback(new Error("Could not locate any valid servers in initial seed list")); + // Set up all options etc and connect to the database + _finishConnecting(serverConfig, object, options, callback) + } + }); + } +} + +var _setNativeParser = function(db_options) { + if(typeof db_options.native_parser == 'boolean') return db_options.native_parser; + + try { + require('bson').BSONNative.BSON; + return true; + } catch(err) { + return false; + } +} + +var _finishConnecting = function(serverConfig, object, options, callback) { + // Safe settings + var safe = {}; + // Build the safe parameter if needed + if(object.db_options.journal) safe.j = object.db_options.journal; + if(object.db_options.w) safe.w = object.db_options.w; + if(object.db_options.fsync) safe.fsync = object.db_options.fsync; + if(object.db_options.wtimeoutMS) safe.wtimeout = object.db_options.wtimeoutMS; + + // If we have a read Preference set + if(object.db_options.read_preference) { + var readPreference = new ReadPreference(object.db_options.read_preference); + // If we have the tags set up + if(object.db_options.read_preference_tags) + readPreference = new ReadPreference(object.db_options.read_preference, object.db_options.read_preference_tags); + // Add the read preference + object.db_options.readPreference = readPreference; + } + + // No safe mode if no keys + if(Object.keys(safe).length == 0) safe = false; + // Add the safe object + object.db_options.safe = safe; + // Set up the db options + var db = new Db(object.dbName, serverConfig, object.db_options); + // Don't open the connection + if(options.noOpen) return db; + + // Open the db db.open(function(err, db){ - if(err == null && authPart){ - db.authenticate(auth[0], auth[1], function(err, success){ + if(err == null && object.auth){ + db.authenticate(object.auth.user, object.auth.password, function(err, success){ if(success){ callback(null, db); } else { @@ -2102,6 +2134,60 @@ Object.defineProperty(Db.prototype, "state", { enumerable: true } }); +/** + * @ignore + */ +var _hasWriteConcern = function(errorOptions) { + return errorOptions == true + || errorOptions.w > 0 + || errorOptions.w == 'majority' + || errorOptions.j == true + || errorOptions.journal == true + || errorOptions.fsync == true +} + +/** + * @ignore + */ +var _setWriteConcernHash = function(options) { + var finalOptions = {}; + if(options.w != null) finalOptions.w = options.w; + if(options.journal == true) finalOptions.j = options.journal; + if(options.j == true) finalOptions.j = options.j; + if(options.fsync == true) finalOptions.fsync = options.fsync; + if(options.wtimeout != null) finalOptions.wtimeout = options.wtimeout; + return finalOptions; +} + +/** + * @ignore + */ +var _getWriteConcern = function(self, options, callback) { + // Final options + var finalOptions = {w:1}; + // Local options verification + if(options.w != null || typeof options.j == 'boolean' || typeof options.journal == 'boolean' || typeof options.fsync == 'boolean') { + finalOptions = _setWriteConcernHash(options); + } else if(options.safe != null && typeof options.safe == 'object') { + finalOptions = _setWriteConcernHash(options.safe); + } else if(typeof options.safe == "boolean") { + finalOptions = {w: (options.safe ? 1 : 0)}; + } else if(self.options.w != null || typeof self.options.j == 'boolean' || typeof self.options.journal == 'boolean' || typeof self.options.fsync == 'boolean') { + finalOptions = _setWriteConcernHash(self.options); + } else if(self.safe.w != null || typeof self.safe.j == 'boolean' || typeof self.safe.journal == 'boolean' || typeof self.safe.fsync == 'boolean') { + finalOptions = _setWriteConcernHash(self.safe); + } else if(typeof self.safe == "boolean") { + finalOptions = {w: (self.safe ? 1 : 0)}; + } + + // Ensure we don't have an invalid combination of write concerns + if(finalOptions.w < 1 + && (finalOptions.journal == true || finalOptions.j == true || finalOptions.fsync == true)) throw new Error("No acknowlegement using w < 1 cannot be combined with journal:true or fsync:true"); + + // Return the options + return finalOptions; +} + /** * Legacy support * diff --git a/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/gridfs/grid.js b/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/gridfs/grid.js index 25ce49e..aa695b7 100644 --- a/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/gridfs/grid.js +++ b/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/gridfs/grid.js @@ -20,9 +20,16 @@ function Grid(db, fsName) { /** * Puts binary data to the grid * + * Options + * - **_id** {Any}, unique id for this file + * - **root** {String}, root collection to use. Defaults to **{GridStore.DEFAULT_ROOT_COLLECTION}**. + * - **content_type** {String}, mime type of the file. Defaults to **{GridStore.DEFAULT_CONTENT_TYPE}**. + * - **chunk_size** {Number}, size for the chunk. Defaults to **{Chunk.DEFAULT_CHUNK_SIZE}**. + * - **metadata** {Object}, arbitrary data the user wants to store. + * * @param {Buffer} data buffer with Binary Data. * @param {Object} [options] the options for the files. - * @callback {Function} this will be called after this method is executed. The first parameter will contain an Error object if an error occured or null otherwise. The second parameter will contain a reference to this object. + * @param {Function} callback this will be called after this method is executed. The first parameter will contain an Error object if an error occured or null otherwise. The second parameter will contain a reference to this object. * @return {null} * @api public */ @@ -37,9 +44,11 @@ Grid.prototype.put = function(data, options, callback) { // Return if we don't have a buffer object as data if(!(Buffer.isBuffer(data))) return callback(new Error("Data object must be a buffer object"), null); // Get filename if we are using it - var filename = options['filename']; + var filename = options['filename'] || null; + // Get id if we are using it + var id = options['_id'] || null; // Create gridstore - var gridStore = new GridStore(this.db, filename, "w", options); + var gridStore = new GridStore(this.db, id, filename, "w", options); gridStore.open(function(err, gridStore) { if(err) return callback(err, null); @@ -57,16 +66,14 @@ Grid.prototype.put = function(data, options, callback) { /** * Get binary data to the grid * - * @param {ObjectID} id ObjectID for file. - * @callback {Function} this will be called after this method is executed. The first parameter will contain an Error object if an error occured or null otherwise. The second parameter will contain a reference to this object. + * @param {Any} id for file. + * @param {Function} callback this will be called after this method is executed. The first parameter will contain an Error object if an error occured or null otherwise. The second parameter will contain a reference to this object. * @return {null} * @api public */ Grid.prototype.get = function(id, callback) { - // Validate that we have a valid ObjectId - if(!(id instanceof ObjectID)) return callback(new Error("Not a valid ObjectID", null)); // Create gridstore - var gridStore = new GridStore(this.db, id, "r", {root:this.fsName}); + var gridStore = new GridStore(this.db, id, null, "r", {root:this.fsName}); gridStore.open(function(err, gridStore) { if(err) return callback(err, null); @@ -80,14 +87,12 @@ Grid.prototype.get = function(id, callback) { /** * Delete file from grid * - * @param {ObjectID} id ObjectID for file. - * @callback {Function} this will be called after this method is executed. The first parameter will contain an Error object if an error occured or null otherwise. The second parameter will contain a reference to this object. + * @param {Any} id for file. + * @param {Function} callback this will be called after this method is executed. The first parameter will contain an Error object if an error occured or null otherwise. The second parameter will contain a reference to this object. * @return {null} * @api public */ Grid.prototype.delete = function(id, callback) { - // Validate that we have a valid ObjectId - if(!(id instanceof ObjectID)) return callback(new Error("Not a valid ObjectID", null)); // Create gridstore GridStore.unlink(this.db, id, {root:this.fsName}, function(err, result) { if(err) return callback(err, false); diff --git a/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/gridfs/gridstore.js b/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/gridfs/gridstore.js index fdd8429..586a491 100644 --- a/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/gridfs/gridstore.js +++ b/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/gridfs/gridstore.js @@ -35,10 +35,10 @@ var REFERENCE_BY_FILENAME = 0, * * @class Represents the GridStore. * @param {Db} db A database instance to interact with. - * @param {ObjectID} id an unique ObjectID for this file - * @param {String} [filename] optional a filename for this file, no unique constrain on the field + * @param {Any} [id] optional unique id for this file + * @param {String} [filename] optional filename for this file, no unique constrain on the field * @param {String} mode set the mode for this file. - * @param {Object} options optional properties to specify. Recognized keys: + * @param {Object} options optional properties to specify. * @return {GridStore} */ var GridStore = function GridStore(db, id, filename, mode, options) { @@ -56,34 +56,52 @@ var GridStore = function GridStore(db, id, filename, mode, options) { } // Handle options - if(options == null) options = {}; + if(typeof options === 'undefined') options = {}; // Handle mode - if(mode == null) { + if(typeof mode === 'undefined') { mode = filename; - filename = null; + filename = undefined; } else if(typeof mode == 'object') { options = mode; mode = filename; - filename = null; + filename = undefined; } + if(id instanceof ObjectID) { + this.referenceBy = REFERENCE_BY_ID; + this.fileId = id; + this.filename = filename; + } else if(typeof filename == 'undefined') { + this.referenceBy = REFERENCE_BY_FILENAME; + this.filename = id; + if (mode.indexOf('w') != null) { + this.fileId = new ObjectID(); + } + } else { + this.referenceBy = REFERENCE_BY_ID; + this.fileId = id; + this.filename = filename; + } + + /* // Handle id if(id instanceof ObjectID && (typeof filename == 'string' || filename == null)) { - this.referenceBy = 1; + this.referenceBy = REFERENCE_BY_ID; this.fileId = id; this.filename = filename; } else if(!(id instanceof ObjectID) && typeof id == 'string' && mode.indexOf("w") != null) { - this.referenceBy = 0; + this.referenceBy = REFERENCE_BY_FILENAME; this.fileId = new ObjectID(); this.filename = id; } else if(!(id instanceof ObjectID) && typeof id == 'string' && mode.indexOf("r") != null) { - this.referenceBy = 0; - this.filename = filename; + this.referenceBy = REFERENCE_BY_FILENAME; + this.filename = id; } else { - this.referenceBy = 1; + this.referenceBy = REFERENCE_BY_ID; this.fileId = id; this.filename = filename; } + */ // Set up the rest this.mode = mode == null ? "r" : mode; @@ -92,8 +110,6 @@ var GridStore = function GridStore(db, id, filename, mode, options) { this.position = 0; // Set default chunk size this.internalChunkSize = this.options['chunkSize'] == null ? Chunk.DEFAULT_CHUNK_SIZE : this.options['chunkSize']; - // Previous chunk size - this.previousChunkSize = 0; } /** @@ -198,7 +214,8 @@ var _open = function(self, callback) { self.length = 0; } else { self.length = 0; - return error(new Error((self.referenceBy == REFERENCE_BY_ID ? self.fileId.toHexString() : self.filename) + " does not exist", self)); + var txtId = self.fileId instanceof ObjectID ? self.fileId.toHexString() : self.fileId; + return error(new Error((self.referenceBy == REFERENCE_BY_ID ? txtId : self.filename) + " does not exist", self)); } // Process the mode of the object @@ -442,19 +459,19 @@ var writeBuffer = function(self, buffer, close, callback) { * @api private */ var buildMongoObject = function(self, callback) { - // Keeps the final chunk number - var chunkNumber = 0; - var previousChunkSize = self.previousChunkSize; - // Get the correct chunk Number, if we have an empty chunk return the previous chunk number - if(null != self.currentChunk && self.currentChunk.chunkNumber > 0 && self.currentChunk.position == 0) { - chunkNumber = self.currentChunk.chunkNumber - 1; - } else { - chunkNumber = self.currentChunk.chunkNumber; - previousChunkSize = self.currentChunk.position; - } + // // Keeps the final chunk number + // var chunkNumber = 0; + // var previousChunkSize = 0; + // // Get the correct chunk Number, if we have an empty chunk return the previous chunk number + // if(null != self.currentChunk && self.currentChunk.chunkNumber > 0 && self.currentChunk.position == 0) { + // chunkNumber = self.currentChunk.chunkNumber - 1; + // } else { + // chunkNumber = self.currentChunk.chunkNumber; + // previousChunkSize = self.currentChunk.position; + // } - // Calcuate the length - var length = self.currentChunk != null ? (chunkNumber * self.chunkSize + previousChunkSize) : 0; + // // Calcuate the length + // var length = self.currentChunk != null ? (chunkNumber * self.chunkSize + previousChunkSize) : 0; var mongoObject = { '_id': self.fileId, 'filename': self.filename, @@ -1381,6 +1398,8 @@ var _read = function _read(self) { } self.resume = function () { + if(!self.paused) return; + self.paused = false; stream.resume(); self.readable = stream.readable; diff --git a/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/gridfs/readstream.js b/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/gridfs/readstream.js index 239b483..ebb09bd 100644 --- a/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/gridfs/readstream.js +++ b/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/gridfs/readstream.js @@ -26,7 +26,7 @@ function ReadStream(autoclose, gstore) { this.finalLength = gstore.length - gstore.position; this.completedLength = 0; - this.currentChunkNumber = 0; + this.currentChunkNumber = gstore.currentChunk.chunkNumber; this.paused = false; this.readable = true; @@ -35,6 +35,9 @@ function ReadStream(autoclose, gstore) { // Calculate the number of chunks this.numberOfChunks = Math.ceil(gstore.length/gstore.chunkSize); + + // This seek start position inside the current chunk + this.seekStartPosition = gstore.position - (this.currentChunkNumber * gstore.chunkSize); var self = this; process.nextTick(function() { @@ -81,7 +84,18 @@ ReadStream.prototype._execute = function() { last = true; } - var data = gstore.currentChunk.readSlice(gstore.currentChunk.length()); + // Data setup + var data = null; + + // Read a slice (with seek set if none) + if(this.seekStartPosition > 0 && (gstore.currentChunk.length() - this.seekStartPosition) > 0) { + data = gstore.currentChunk.readSlice(gstore.currentChunk.length() - this.seekStartPosition); + this.seekStartPosition = 0; + } else { + data = gstore.currentChunk.readSlice(gstore.currentChunk.length()); + } + + // Return the data if(data != null && gstore.currentChunk.chunkNumber == self.currentChunkNumber) { self.currentChunkNumber = self.currentChunkNumber + 1; self.completedLength += data.length; diff --git a/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/index.js b/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/index.js index 014f3d2..6a2b727 100644 --- a/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/index.js +++ b/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/index.js @@ -6,14 +6,6 @@ try { } [ 'commands/base_command' - , 'commands/db_command' - , 'commands/delete_command' - , 'commands/get_more_command' - , 'commands/insert_command' - , 'commands/kill_cursor_command' - , 'commands/query_command' - , 'commands/update_command' - , 'responses/mongo_reply' , 'admin' , 'collection' , 'connection/read_preference' @@ -21,8 +13,10 @@ try { , 'connection/server' , 'connection/mongos' , 'connection/repl_set' + , 'mongo_client' , 'cursor' , 'db' + , 'mongo_client' , 'gridfs/grid' , 'gridfs/chunk' , 'gridfs/gridstore'].forEach(function (path) { @@ -48,110 +42,28 @@ try { // Add BSON Parser exports.BSON = require('bson').BSONPure.BSON; + }); -// Exports all the classes for the PURE JS BSON Parser -exports.pure = function() { - var classes = {}; - // Map all the classes - [ 'commands/base_command' - , 'commands/db_command' - , 'commands/delete_command' - , 'commands/get_more_command' - , 'commands/insert_command' - , 'commands/kill_cursor_command' - , 'commands/query_command' - , 'commands/update_command' - , 'responses/mongo_reply' - , 'admin' - , 'collection' - , 'connection/read_preference' - , 'connection/connection' - , 'connection/server' - , 'connection/mongos' - , 'connection/repl_set' - , 'cursor' - , 'db' - , 'gridfs/grid' - , 'gridfs/chunk' - , 'gridfs/gridstore'].forEach(function (path) { - var module = require('./' + path); - for (var i in module) { - classes[i] = module[i]; - } - }); - - // backwards compat - classes.ReplSetServers = exports.ReplSet; - - // Add BSON Classes - classes.Binary = require('bson').Binary; - classes.Code = require('bson').Code; - classes.DBRef = require('bson').DBRef; - classes.Double = require('bson').Double; - classes.Long = require('bson').Long; - classes.MinKey = require('bson').MinKey; - classes.MaxKey = require('bson').MaxKey; - classes.ObjectID = require('bson').ObjectID; - classes.Symbol = require('bson').Symbol; - classes.Timestamp = require('bson').Timestamp; - - // Add BSON Parser - classes.BSON = require('bson').BSONPure.BSON; - - // Return classes list - return classes; +// Get the Db object +var Db = require('./db').Db; +// Set up the connect function +var connect = Db.connect; +var obj = connect; +// Map all values to the exports value +for(var name in exports) { + obj[name] = exports[name]; } -// Exports all the classes for the PURE JS BSON Parser -exports.native = function() { - var classes = {}; - // Map all the classes - [ 'commands/base_command' - , 'commands/db_command' - , 'commands/delete_command' - , 'commands/get_more_command' - , 'commands/insert_command' - , 'commands/kill_cursor_command' - , 'commands/query_command' - , 'commands/update_command' - , 'responses/mongo_reply' - , 'admin' - , 'collection' - , 'connection/read_preference' - , 'connection/connection' - , 'connection/server' - , 'connection/mongos' - , 'connection/repl_set' - , 'cursor' - , 'db' - , 'gridfs/grid' - , 'gridfs/chunk' - , 'gridfs/gridstore'].forEach(function (path) { - var module = require('./' + path); - for (var i in module) { - classes[i] = module[i]; - } - }); - - // Add BSON Classes - classes.Binary = require('bson').Binary; - classes.Code = require('bson').Code; - classes.DBRef = require('bson').DBRef; - classes.Double = require('bson').Double; - classes.Long = require('bson').Long; - classes.MinKey = require('bson').MinKey; - classes.MaxKey = require('bson').MaxKey; - classes.ObjectID = require('bson').ObjectID; - classes.Symbol = require('bson').Symbol; - classes.Timestamp = require('bson').Timestamp; - - // backwards compat - classes.ReplSetServers = exports.ReplSet; - - // Add BSON Parser - classes.BSON = require('bson').BSONNative.BSON; - - // Return classes list - return classes; +// Add the pure and native backward compatible functions +exports.pure = exports.native = function() { + return obj; } + +// Map all values to the exports value +for(var name in exports) { + connect[name] = exports[name]; +} + +// Set our exports to be the connect function +module.exports = connect; \ No newline at end of file diff --git a/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/utils.js b/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/utils.js index 07a0ab0..120a584 100644 --- a/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/utils.js +++ b/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/utils.js @@ -72,3 +72,26 @@ exports.decodeUInt32 = function(array, index) { exports.decodeUInt8 = function(array, index) { return array[index]; } + +/** + * Context insensitive type checks + */ + +var toString = Object.prototype.toString; + +exports.isObject = function (arg) { + return '[object Object]' == toString.call(arg) +} + +exports.isArray = function (arg) { + return Array.isArray(arg) || + 'object' == typeof arg && '[object Array]' == toString.call(arg) +} + +exports.isDate = function (arg) { + return 'object' == typeof arg && '[object Date]' == toString.call(arg) +} + +exports.isRegExp = function (arg) { + return 'object' == typeof arg && '[object RegExp]' == toString.call(arg) +} diff --git a/node_modules/mongoskin/node_modules/mongodb/node_modules/bson/ext/bson.cc b/node_modules/mongoskin/node_modules/mongodb/node_modules/bson/ext/bson.cc index 26dfe04..4996375 100644 --- a/node_modules/mongoskin/node_modules/mongodb/node_modules/bson/ext/bson.cc +++ b/node_modules/mongoskin/node_modules/mongodb/node_modules/bson/ext/bson.cc @@ -1,1016 +1,1020 @@ -//=========================================================================== - -#include -#include -#include -#include -#include - -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunused-parameter" -#endif - -#include - -// this and the above block must be around the v8.h header otherwise -// v8 is not happy -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - -#include -#include -#include - -#include -#include -#include -#include - -#include "bson.h" - -using namespace v8; -using namespace node; - -//=========================================================================== - -void DataStream::WriteObjectId(const Handle& object, const Handle& key) -{ - uint16_t buffer[12]; - object->Get(key)->ToString()->Write(buffer, 0, 12); - for(uint32_t i = 0; i < 12; ++i) - { - *p++ = (char) buffer[i]; - } -} - -void ThrowAllocatedStringException(size_t allocationSize, const char* format, ...) -{ - va_list args; - va_start(args, format); - char* string = (char*) malloc(allocationSize); - vsprintf(string, format, args); - va_end(args); - - throw string; -} - -void DataStream::CheckKey(const Local& keyName) -{ - size_t keyLength = keyName->Utf8Length(); - if(keyLength == 0) return; - - char* keyStringBuffer = (char*) alloca(keyLength+1); - keyName->WriteUtf8(keyStringBuffer); - - if(keyStringBuffer[0] == '$') - { - ThrowAllocatedStringException(64+keyLength, "key %s must not start with '$'", keyStringBuffer); - } - - if(strchr(keyStringBuffer, '.') != NULL) - { - ThrowAllocatedStringException(64+keyLength, "key %s must not contain '.'", keyStringBuffer); - } -} - -template void BSONSerializer::SerializeDocument(const Handle& value) -{ - void* documentSize = this->BeginWriteSize(); - Local object = bson->GetSerializeObject(value); - - // Get the object property names - #if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION < 6 - Local propertyNames = object->GetPropertyNames(); - #else - Local propertyNames = object->GetOwnPropertyNames(); - #endif - - // Length of the property - int propertyLength = propertyNames->Length(); - for(int i = 0; i < propertyLength; ++i) - { - const Local& propertyName = propertyNames->Get(i)->ToString(); - if(checkKeys) this->CheckKey(propertyName); - - const Local& propertyValue = object->Get(propertyName); - - if(serializeFunctions || !propertyValue->IsFunction()) - { - void* typeLocation = this->BeginWriteType(); - this->WriteString(propertyName); - SerializeValue(typeLocation, propertyValue); - } - } - - this->WriteByte(0); - this->CommitSize(documentSize); -} - -template void BSONSerializer::SerializeArray(const Handle& value) -{ - void* documentSize = this->BeginWriteSize(); - - Local array = Local::Cast(value->ToObject()); - uint32_t arrayLength = array->Length(); - - for(uint32_t i = 0; i < arrayLength; ++i) - { - void* typeLocation = this->BeginWriteType(); - this->WriteUInt32String(i); - SerializeValue(typeLocation, array->Get(i)); - } - - this->WriteByte(0); - this->CommitSize(documentSize); -} - -// This is templated so that we can use this function to both count the number of bytes, and to serialize those bytes. -// The template approach eliminates almost all of the inspection of values unless they're required (eg. string lengths) -// and ensures that there is always consistency between bytes counted and bytes written by design. -template void BSONSerializer::SerializeValue(void* typeLocation, const Handle& value) -{ - if(value->IsNumber()) - { - double doubleValue = value->NumberValue(); - int intValue = (int) doubleValue; - if(intValue == doubleValue) - { - this->CommitType(typeLocation, BSON_TYPE_INT); - this->WriteInt32(intValue); - } - else - { - this->CommitType(typeLocation, BSON_TYPE_NUMBER); - this->WriteDouble(doubleValue); - } - } - else if(value->IsString()) - { - this->CommitType(typeLocation, BSON_TYPE_STRING); - this->WriteLengthPrefixedString(value->ToString()); - } - else if(value->IsBoolean()) - { - this->CommitType(typeLocation, BSON_TYPE_BOOLEAN); - this->WriteBool(value); - } - else if(value->IsArray()) - { - this->CommitType(typeLocation, BSON_TYPE_ARRAY); - SerializeArray(value); - } - else if(value->IsDate()) - { - this->CommitType(typeLocation, BSON_TYPE_DATE); - this->WriteInt64(value); - } - else if(value->IsRegExp()) - { - this->CommitType(typeLocation, BSON_TYPE_REGEXP); - const Handle& regExp = Handle::Cast(value); - - this->WriteString(regExp->GetSource()); - - int flags = regExp->GetFlags(); - if(flags & RegExp::kGlobal) this->WriteByte('s'); - if(flags & RegExp::kIgnoreCase) this->WriteByte('i'); - if(flags & RegExp::kMultiline) this->WriteByte('m'); - this->WriteByte(0); - } - else if(value->IsFunction()) - { - this->CommitType(typeLocation, BSON_TYPE_CODE); - this->WriteLengthPrefixedString(value->ToString()); - } - else if(value->IsObject()) - { - const Local& object = value->ToObject(); - if(object->Has(bson->_bsontypeString)) - { - const Local& constructorString = object->GetConstructorName(); - if(bson->longString->StrictEquals(constructorString)) - { - this->CommitType(typeLocation, BSON_TYPE_LONG); - this->WriteInt32(object, bson->_longLowString); - this->WriteInt32(object, bson->_longHighString); - } - else if(bson->timestampString->StrictEquals(constructorString)) - { - this->CommitType(typeLocation, BSON_TYPE_TIMESTAMP); - this->WriteInt32(object, bson->_longLowString); - this->WriteInt32(object, bson->_longHighString); - } - else if(bson->objectIDString->StrictEquals(constructorString)) - { - this->CommitType(typeLocation, BSON_TYPE_OID); - this->WriteObjectId(object, bson->_objectIDidString); - } - else if(bson->binaryString->StrictEquals(constructorString)) - { - this->CommitType(typeLocation, BSON_TYPE_BINARY); - - uint32_t length = object->Get(bson->_binaryPositionString)->Uint32Value(); - Local bufferObj = object->Get(bson->_binaryBufferString)->ToObject(); - - this->WriteInt32(length); - this->WriteByte(object, bson->_binarySubTypeString); // write subtype - this->WriteData(Buffer::Data(bufferObj), length); - } - else if(bson->doubleString->StrictEquals(constructorString)) - { - this->CommitType(typeLocation, BSON_TYPE_NUMBER); - this->WriteDouble(object, bson->_doubleValueString); - } - else if(bson->symbolString->StrictEquals(constructorString)) - { - this->CommitType(typeLocation, BSON_TYPE_SYMBOL); - this->WriteLengthPrefixedString(object->Get(bson->_symbolValueString)->ToString()); - } - else if(bson->codeString->StrictEquals(constructorString)) - { - const Local& function = object->Get(bson->_codeCodeString)->ToString(); - const Local& scope = object->Get(bson->_codeScopeString)->ToObject(); - - // For Node < 0.6.X use the GetPropertyNames - #if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION < 6 - uint32_t propertyNameLength = scope->GetPropertyNames()->Length(); - #else - uint32_t propertyNameLength = scope->GetOwnPropertyNames()->Length(); - #endif - - if(propertyNameLength > 0) - { - this->CommitType(typeLocation, BSON_TYPE_CODE_W_SCOPE); - void* codeWidthScopeSize = this->BeginWriteSize(); - this->WriteLengthPrefixedString(function->ToString()); - SerializeDocument(scope); - this->CommitSize(codeWidthScopeSize); - } - else - { - this->CommitType(typeLocation, BSON_TYPE_CODE); - this->WriteLengthPrefixedString(function->ToString()); - } - } - else if(bson->dbrefString->StrictEquals(constructorString)) - { - this->CommitType(typeLocation, BSON_TYPE_OBJECT); - - void* dbRefSize = this->BeginWriteSize(); - - void* refType = this->BeginWriteType(); - this->WriteData("$ref", 5); - SerializeValue(refType, object->Get(bson->_dbRefNamespaceString)); - - void* idType = this->BeginWriteType(); - this->WriteData("$id", 4); - SerializeValue(idType, object->Get(bson->_dbRefOidString)); - - const Local& refDbValue = object->Get(bson->_dbRefDbString); - if(!refDbValue->IsUndefined()) - { - void* dbType = this->BeginWriteType(); - this->WriteData("$db", 4); - SerializeValue(dbType, refDbValue); - } - - this->WriteByte(0); - this->CommitSize(dbRefSize); - } - else if(bson->minKeyString->StrictEquals(constructorString)) - { - this->CommitType(typeLocation, BSON_TYPE_MIN_KEY); - } - else if(bson->maxKeyString->StrictEquals(constructorString)) - { - this->CommitType(typeLocation, BSON_TYPE_MAX_KEY); - } - } - else if(Buffer::HasInstance(value)) - { - this->CommitType(typeLocation, BSON_TYPE_BINARY); - - #if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION < 3 - Buffer *buffer = ObjectWrap::Unwrap(value->ToObject()); - uint32_t length = object->length(); - #else - uint32_t length = Buffer::Length(value->ToObject()); - #endif - - this->WriteInt32(length); - this->WriteByte(0); - this->WriteData(Buffer::Data(value->ToObject()), length); - } - else - { - this->CommitType(typeLocation, BSON_TYPE_OBJECT); - SerializeDocument(value); - } - } - else if(value->IsNull() || value->IsUndefined()) - { - this->CommitType(typeLocation, BSON_TYPE_NULL); - } -} - -// Data points to start of element list, length is length of entire document including '\0' but excluding initial size -BSONDeserializer::BSONDeserializer(BSON* aBson, char* data, size_t length) -: bson(aBson), - pStart(data), - p(data), - pEnd(data + length - 1) -{ - if(*pEnd != '\0') ThrowAllocatedStringException(64, "Missing end of document marker '\\0'"); -} - -BSONDeserializer::BSONDeserializer(BSONDeserializer& parentSerializer, size_t length) -: bson(parentSerializer.bson), - pStart(parentSerializer.p), - p(parentSerializer.p), - pEnd(parentSerializer.p + length - 1) -{ - parentSerializer.p += length; - if(pEnd > parentSerializer.pEnd) ThrowAllocatedStringException(64, "Child document exceeds parent's bounds"); - if(*pEnd != '\0') ThrowAllocatedStringException(64, "Missing end of document marker '\\0'"); -} - -Local BSONDeserializer::ReadCString() -{ - char* start = p; - while(*p++) { } - return String::New(start, (int32_t) (p-start-1) ); -} - -int32_t BSONDeserializer::ReadRegexOptions() -{ - int32_t options = 0; - for(;;) - { - switch(*p++) - { - case '\0': return options; - case 's': options |= RegExp::kGlobal; break; - case 'i': options |= RegExp::kIgnoreCase; break; - case 'm': options |= RegExp::kMultiline; break; - } - } -} - -uint32_t BSONDeserializer::ReadIntegerString() -{ - uint32_t value = 0; - while(*p) - { - if(*p < '0' || *p > '9') ThrowAllocatedStringException(64, "Invalid key for array"); - value = value * 10 + *p++ - '0'; - } - ++p; - return value; -} - -Local BSONDeserializer::ReadString() -{ - uint32_t length = ReadUInt32(); - char* start = p; - p += length; - return String::New(start, length-1); -} - -Local BSONDeserializer::ReadObjectId() -{ - uint16_t objectId[12]; - for(size_t i = 0; i < 12; ++i) - { - objectId[i] = *reinterpret_cast(p++); - } - return String::New(objectId, 12); -} - -Handle BSONDeserializer::DeserializeDocument() -{ - uint32_t length = ReadUInt32(); - if(length < 5) ThrowAllocatedStringException(64, "Bad BSON: Document is less than 5 bytes"); - - BSONDeserializer documentDeserializer(*this, length-4); - return documentDeserializer.DeserializeDocumentInternal(); -} - -Handle BSONDeserializer::DeserializeDocumentInternal() -{ - Local returnObject = Object::New(); - - while(HasMoreData()) - { - BsonType type = (BsonType) ReadByte(); - const Local& name = ReadCString(); - const Handle& value = DeserializeValue(type); - returnObject->ForceSet(name, value); - } - if(p != pEnd) ThrowAllocatedStringException(64, "Bad BSON Document: Serialize consumed unexpected number of bytes"); - - // From JavaScript: - // if(object['$id'] != null) object = new DBRef(object['$ref'], object['$id'], object['$db']); - if(returnObject->Has(bson->_dbRefIdRefString)) - { - Local argv[] = { returnObject->Get(bson->_dbRefRefString), returnObject->Get(bson->_dbRefIdRefString), returnObject->Get(bson->_dbRefDbRefString) }; - return bson->dbrefConstructor->NewInstance(3, argv); - } - else - { - return returnObject; - } -} - -Handle BSONDeserializer::DeserializeArray() -{ - uint32_t length = ReadUInt32(); - if(length < 5) ThrowAllocatedStringException(64, "Bad BSON: Array Document is less than 5 bytes"); - - BSONDeserializer documentDeserializer(*this, length-4); - return documentDeserializer.DeserializeArrayInternal(); -} - -Handle BSONDeserializer::DeserializeArrayInternal() -{ - Local returnArray = Array::New(); - - while(HasMoreData()) - { - BsonType type = (BsonType) ReadByte(); - uint32_t index = ReadIntegerString(); - const Handle& value = DeserializeValue(type); - returnArray->Set(index, value); - } - if(p != pEnd) ThrowAllocatedStringException(64, "Bad BSON Array: Serialize consumed unexpected number of bytes"); - - return returnArray; -} - -Handle BSONDeserializer::DeserializeValue(BsonType type) -{ - switch(type) - { - case BSON_TYPE_STRING: - return ReadString(); - - case BSON_TYPE_INT: - return Integer::New(ReadInt32()); - - case BSON_TYPE_NUMBER: - return Number::New(ReadDouble()); - - case BSON_TYPE_NULL: - return Null(); - - case BSON_TYPE_UNDEFINED: - return Undefined(); - - case BSON_TYPE_TIMESTAMP: - { - int32_t lowBits = ReadInt32(); - int32_t highBits = ReadInt32(); - Local argv[] = { Int32::New(lowBits), Int32::New(highBits) }; - return bson->timestampConstructor->NewInstance(2, argv); - } - - case BSON_TYPE_BOOLEAN: - return (ReadByte() != 0) ? True() : False(); - - case BSON_TYPE_REGEXP: - { - const Local& regex = ReadCString(); - int32_t options = ReadRegexOptions(); - return RegExp::New(regex, (RegExp::Flags) options); - } - - case BSON_TYPE_CODE: - { - const Local& code = ReadString(); - const Local& scope = Object::New(); - Local argv[] = { code, scope }; - return bson->codeConstructor->NewInstance(2, argv); - } - - case BSON_TYPE_CODE_W_SCOPE: - { - ReadUInt32(); - const Local& code = ReadString(); - const Handle& scope = DeserializeDocument(); - Local argv[] = { code, scope->ToObject() }; - return bson->codeConstructor->NewInstance(2, argv); - } - - case BSON_TYPE_OID: - { - Local argv[] = { ReadObjectId() }; - return bson->objectIDConstructor->NewInstance(1, argv); - } - - case BSON_TYPE_BINARY: - { - uint32_t length = ReadUInt32(); - uint32_t subType = ReadByte(); - Buffer* buffer = Buffer::New(p, length); - p += length; - - Handle argv[] = { buffer->handle_, Uint32::New(subType) }; - return bson->binaryConstructor->NewInstance(2, argv); - } - - case BSON_TYPE_LONG: - { - // Read 32 bit integers - int32_t lowBits = (int32_t) ReadInt32(); - int32_t highBits = (int32_t) ReadInt32(); - - // If value is < 2^53 and >-2^53 - if((highBits < 0x200000 || (highBits == 0x200000 && lowBits == 0)) && highBits >= -0x200000) { - // Adjust the pointer and read as 64 bit value - p -= 8; - // Read the 64 bit value - int64_t finalValue = (int64_t) ReadInt64(); - return Number::New(finalValue); - } - - Local argv[] = { Int32::New(lowBits), Int32::New(highBits) }; - return bson->longConstructor->NewInstance(2, argv); - } - - case BSON_TYPE_DATE: - return Date::New((double) ReadInt64()); - - case BSON_TYPE_ARRAY: - return DeserializeArray(); - - case BSON_TYPE_OBJECT: - return DeserializeDocument(); - - case BSON_TYPE_SYMBOL: - { - const Local& string = ReadString(); - Local argv[] = { string }; - return bson->symbolConstructor->NewInstance(1, argv); - } - - case BSON_TYPE_MIN_KEY: - return bson->minKeyConstructor->NewInstance(); - - case BSON_TYPE_MAX_KEY: - return bson->maxKeyConstructor->NewInstance(); - - default: - ThrowAllocatedStringException(64, "Unhandled BSON Type: %d", type); - } - - return v8::Null(); -} - - -static Handle VException(const char *msg) -{ - HandleScope scope; - return ThrowException(Exception::Error(String::New(msg))); -} - -Persistent BSON::constructor_template; - -BSON::BSON() : ObjectWrap() -{ - // Setup pre-allocated comparision objects - _bsontypeString = Persistent::New(String::New("_bsontype")); - _longLowString = Persistent::New(String::New("low_")); - _longHighString = Persistent::New(String::New("high_")); - _objectIDidString = Persistent::New(String::New("id")); - _binaryPositionString = Persistent::New(String::New("position")); - _binarySubTypeString = Persistent::New(String::New("sub_type")); - _binaryBufferString = Persistent::New(String::New("buffer")); - _doubleValueString = Persistent::New(String::New("value")); - _symbolValueString = Persistent::New(String::New("value")); - _dbRefRefString = Persistent::New(String::New("$ref")); - _dbRefIdRefString = Persistent::New(String::New("$id")); - _dbRefDbRefString = Persistent::New(String::New("$db")); - _dbRefNamespaceString = Persistent::New(String::New("namespace")); - _dbRefDbString = Persistent::New(String::New("db")); - _dbRefOidString = Persistent::New(String::New("oid")); - _codeCodeString = Persistent::New(String::New("code")); - _codeScopeString = Persistent::New(String::New("scope")); - _toBSONString = Persistent::New(String::New("toBSON")); - - longString = Persistent::New(String::New("Long")); - objectIDString = Persistent::New(String::New("ObjectID")); - binaryString = Persistent::New(String::New("Binary")); - codeString = Persistent::New(String::New("Code")); - dbrefString = Persistent::New(String::New("DBRef")); - symbolString = Persistent::New(String::New("Symbol")); - doubleString = Persistent::New(String::New("Double")); - timestampString = Persistent::New(String::New("Timestamp")); - minKeyString = Persistent::New(String::New("MinKey")); - maxKeyString = Persistent::New(String::New("MaxKey")); -} - -void BSON::Initialize(v8::Handle target) -{ - // Grab the scope of the call from Node - HandleScope scope; - // Define a new function template - Local t = FunctionTemplate::New(New); - constructor_template = Persistent::New(t); - constructor_template->InstanceTemplate()->SetInternalFieldCount(1); - constructor_template->SetClassName(String::NewSymbol("BSON")); - - // Instance methods - NODE_SET_PROTOTYPE_METHOD(constructor_template, "calculateObjectSize", CalculateObjectSize); - NODE_SET_PROTOTYPE_METHOD(constructor_template, "serialize", BSONSerialize); - NODE_SET_PROTOTYPE_METHOD(constructor_template, "serializeWithBufferAndIndex", SerializeWithBufferAndIndex); - NODE_SET_PROTOTYPE_METHOD(constructor_template, "deserialize", BSONDeserialize); - NODE_SET_PROTOTYPE_METHOD(constructor_template, "deserializeStream", BSONDeserializeStream); - - target->ForceSet(String::NewSymbol("BSON"), constructor_template->GetFunction()); -} - -// Create a new instance of BSON and passing it the existing context -Handle BSON::New(const Arguments &args) -{ - HandleScope scope; - - // Check that we have an array - if(args.Length() == 1 && args[0]->IsArray()) - { - // Cast the array to a local reference - Local array = Local::Cast(args[0]); - - if(array->Length() > 0) - { - // Create a bson object instance and return it - BSON *bson = new BSON(); - - uint32_t foundClassesMask = 0; - - // Iterate over all entries to save the instantiate funtions - for(uint32_t i = 0; i < array->Length(); i++) - { - // Let's get a reference to the function - Local func = Local::Cast(array->Get(i)); - Local functionName = func->GetName()->ToString(); - - // Save the functions making them persistant handles (they don't get collected) - if(functionName->StrictEquals(bson->longString)) - { - bson->longConstructor = Persistent::New(func); - foundClassesMask |= 1; - } - else if(functionName->StrictEquals(bson->objectIDString)) - { - bson->objectIDConstructor = Persistent::New(func); - foundClassesMask |= 2; - } - else if(functionName->StrictEquals(bson->binaryString)) - { - bson->binaryConstructor = Persistent::New(func); - foundClassesMask |= 4; - } - else if(functionName->StrictEquals(bson->codeString)) - { - bson->codeConstructor = Persistent::New(func); - foundClassesMask |= 8; - } - else if(functionName->StrictEquals(bson->dbrefString)) - { - bson->dbrefConstructor = Persistent::New(func); - foundClassesMask |= 0x10; - } - else if(functionName->StrictEquals(bson->symbolString)) - { - bson->symbolConstructor = Persistent::New(func); - foundClassesMask |= 0x20; - } - else if(functionName->StrictEquals(bson->doubleString)) - { - bson->doubleConstructor = Persistent::New(func); - foundClassesMask |= 0x40; - } - else if(functionName->StrictEquals(bson->timestampString)) - { - bson->timestampConstructor = Persistent::New(func); - foundClassesMask |= 0x80; - } - else if(functionName->StrictEquals(bson->minKeyString)) - { - bson->minKeyConstructor = Persistent::New(func); - foundClassesMask |= 0x100; - } - else if(functionName->StrictEquals(bson->maxKeyString)) - { - bson->maxKeyConstructor = Persistent::New(func); - foundClassesMask |= 0x200; - } - } - - // Check if we have the right number of constructors otherwise throw an error - if(foundClassesMask != 0x3ff) - { - delete bson; - return VException("Missing function constructor for either [Long/ObjectID/Binary/Code/DbRef/Symbol/Double/Timestamp/MinKey/MaxKey]"); - } - else - { - bson->Wrap(args.This()); - return args.This(); - } - } - else - { - return VException("No types passed in"); - } - } - else - { - return VException("Argument passed in must be an array of types"); - } -} - -//------------------------------------------------------------------------------------------------ -//------------------------------------------------------------------------------------------------ -//------------------------------------------------------------------------------------------------ -//------------------------------------------------------------------------------------------------ - -Handle BSON::BSONDeserialize(const Arguments &args) -{ - HandleScope scope; - - // Ensure that we have an parameter - if(Buffer::HasInstance(args[0]) && args.Length() > 1) return VException("One argument required - buffer1."); - if(args[0]->IsString() && args.Length() > 1) return VException("One argument required - string1."); - // Throw an exception if the argument is not of type Buffer - if(!Buffer::HasInstance(args[0]) && !args[0]->IsString()) return VException("Argument must be a Buffer or String."); - - // Define pointer to data - Local obj = args[0]->ToObject(); - - // Unpack the BSON parser instance - BSON *bson = ObjectWrap::Unwrap(args.This()); - - // If we passed in a buffer, let's unpack it, otherwise let's unpack the string - if(Buffer::HasInstance(obj)) - { -#if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION < 3 - Buffer *buffer = ObjectWrap::Unwrap(obj); - char* data = buffer->data(); - size_t length = buffer->length(); -#else - char* data = Buffer::Data(obj); - size_t length = Buffer::Length(obj); -#endif - - // Validate that we have at least 5 bytes - if(length < 5) return VException("corrupt bson message < 5 bytes long"); - - try - { - BSONDeserializer deserializer(bson, data, length); - return deserializer.DeserializeDocument(); - } - catch(char* exception) - { - Handle error = VException(exception); - free(exception); - return error; - } - - } - else - { - // The length of the data for this encoding - ssize_t len = DecodeBytes(args[0], BINARY); - - // Validate that we have at least 5 bytes - if(len < 5) return VException("corrupt bson message < 5 bytes long"); - - // Let's define the buffer size - char* data = (char *)malloc(len); - DecodeWrite(data, len, args[0], BINARY); - - try - { - BSONDeserializer deserializer(bson, data, len); - Handle result = deserializer.DeserializeDocument(); - free(data); - return result; - - } - catch(char* exception) - { - Handle error = VException(exception); - free(exception); - free(data); - return error; - } - } -} - -Local BSON::GetSerializeObject(const Handle& argValue) -{ - Local object = argValue->ToObject(); - if(object->Has(_toBSONString)) - { - const Local& toBSON = object->Get(_toBSONString); - if(!toBSON->IsFunction()) ThrowAllocatedStringException(64, "toBSON is not a function"); - - Local result = Local::Cast(toBSON)->Call(object, 0, NULL); - if(!result->IsObject()) ThrowAllocatedStringException(64, "toBSON function did not return an object"); - return result->ToObject(); - } - else - { - return object; - } -} - -Handle BSON::BSONSerialize(const Arguments &args) -{ - HandleScope scope; - - if(args.Length() == 1 && !args[0]->IsObject()) return VException("One, two or tree arguments required - [object] or [object, boolean] or [object, boolean, boolean]"); - if(args.Length() == 2 && !args[0]->IsObject() && !args[1]->IsBoolean()) return VException("One, two or tree arguments required - [object] or [object, boolean] or [object, boolean, boolean]"); - if(args.Length() == 3 && !args[0]->IsObject() && !args[1]->IsBoolean() && !args[2]->IsBoolean()) return VException("One, two or tree arguments required - [object] or [object, boolean] or [object, boolean, boolean]"); - if(args.Length() == 4 && !args[0]->IsObject() && !args[1]->IsBoolean() && !args[2]->IsBoolean() && !args[3]->IsBoolean()) return VException("One, two or tree arguments required - [object] or [object, boolean] or [object, boolean, boolean] or [object, boolean, boolean, boolean]"); - if(args.Length() > 4) return VException("One, two, tree or four arguments required - [object] or [object, boolean] or [object, boolean, boolean] or [object, boolean, boolean, boolean]"); - - // Unpack the BSON parser instance - BSON *bson = ObjectWrap::Unwrap(args.This()); - - // Calculate the total size of the document in binary form to ensure we only allocate memory once - // With serialize function - bool serializeFunctions = (args.Length() >= 4) && args[3]->BooleanValue(); - - char *serialized_object = NULL; - size_t object_size; - try - { - Local object = bson->GetSerializeObject(args[0]); - - BSONSerializer counter(bson, false, serializeFunctions); - counter.SerializeDocument(object); - object_size = counter.GetSerializeSize(); - - // Allocate the memory needed for the serialization - serialized_object = (char *)malloc(object_size); - - // Check if we have a boolean value - bool checkKeys = args.Length() >= 3 && args[1]->IsBoolean() && args[1]->BooleanValue(); - BSONSerializer data(bson, checkKeys, serializeFunctions, serialized_object); - data.SerializeDocument(object); - } - catch(char *err_msg) - { - free(serialized_object); - Handle error = VException(err_msg); - free(err_msg); - return error; - } - - // If we have 3 arguments - if(args.Length() == 3 || args.Length() == 4) - { - Buffer *buffer = Buffer::New(serialized_object, object_size); - free(serialized_object); - return scope.Close(buffer->handle_); - } - else - { - Local bin_value = Encode(serialized_object, object_size, BINARY)->ToString(); - free(serialized_object); - return bin_value; - } -} - -Handle BSON::CalculateObjectSize(const Arguments &args) -{ - HandleScope scope; - // Ensure we have a valid object - if(args.Length() == 1 && !args[0]->IsObject()) return VException("One argument required - [object]"); - if(args.Length() == 2 && !args[0]->IsObject() && !args[1]->IsBoolean()) return VException("Two arguments required - [object, boolean]"); - if(args.Length() > 3) return VException("One or two arguments required - [object] or [object, boolean]"); - - // Unpack the BSON parser instance - BSON *bson = ObjectWrap::Unwrap(args.This()); - bool serializeFunctions = (args.Length() >= 2) && args[1]->BooleanValue(); - BSONSerializer countSerializer(bson, false, serializeFunctions); - countSerializer.SerializeDocument(args[0]); - - // Return the object size - return scope.Close(Uint32::New((uint32_t) countSerializer.GetSerializeSize())); -} - -Handle BSON::SerializeWithBufferAndIndex(const Arguments &args) -{ - HandleScope scope; - - //BSON.serializeWithBufferAndIndex = function serializeWithBufferAndIndex(object, ->, buffer, index) { - // Ensure we have the correct values - if(args.Length() > 5) return VException("Four or five parameters required [object, boolean, Buffer, int] or [object, boolean, Buffer, int, boolean]"); - if(args.Length() == 4 && !args[0]->IsObject() && !args[1]->IsBoolean() && !Buffer::HasInstance(args[2]) && !args[3]->IsUint32()) return VException("Four parameters required [object, boolean, Buffer, int]"); - if(args.Length() == 5 && !args[0]->IsObject() && !args[1]->IsBoolean() && !Buffer::HasInstance(args[2]) && !args[3]->IsUint32() && !args[4]->IsBoolean()) return VException("Four parameters required [object, boolean, Buffer, int, boolean]"); - - uint32_t index; - size_t object_size; - - try - { - BSON *bson = ObjectWrap::Unwrap(args.This()); - - Local obj = args[2]->ToObject(); - char* data = Buffer::Data(obj); - size_t length = Buffer::Length(obj); - - index = args[3]->Uint32Value(); - bool checkKeys = args.Length() >= 4 && args[1]->IsBoolean() && args[1]->BooleanValue(); - bool serializeFunctions = (args.Length() == 5) && args[4]->BooleanValue(); - - BSONSerializer dataSerializer(bson, checkKeys, serializeFunctions, data+index); - dataSerializer.SerializeDocument(bson->GetSerializeObject(args[0])); - object_size = dataSerializer.GetSerializeSize(); - - if(object_size + index > length) return VException("Serious error - overflowed buffer!!"); - } - catch(char *exception) - { - Handle error = VException(exception); - free(exception); - return error; - } - - return scope.Close(Uint32::New((uint32_t) (index + object_size - 1))); -} - -Handle BSON::BSONDeserializeStream(const Arguments &args) -{ - HandleScope scope; - - // At least 3 arguments required - if(args.Length() < 5) return VException("Arguments required (Buffer(data), Number(index in data), Number(number of documents to deserialize), Array(results), Number(index in the array), Object(optional))"); - - // If the number of argumets equals 3 - if(args.Length() >= 5) - { - if(!Buffer::HasInstance(args[0])) return VException("First argument must be Buffer instance"); - if(!args[1]->IsUint32()) return VException("Second argument must be a positive index number"); - if(!args[2]->IsUint32()) return VException("Third argument must be a positive number of documents to deserialize"); - if(!args[3]->IsArray()) return VException("Fourth argument must be an array the size of documents to deserialize"); - if(!args[4]->IsUint32()) return VException("Sixth argument must be a positive index number"); - } - - // If we have 4 arguments - if(args.Length() == 6 && !args[5]->IsObject()) return VException("Fifth argument must be an object with options"); - - // Define pointer to data - Local obj = args[0]->ToObject(); - uint32_t numberOfDocuments = args[2]->Uint32Value(); - uint32_t index = args[1]->Uint32Value(); - uint32_t resultIndex = args[4]->Uint32Value(); - - // Unpack the BSON parser instance - BSON *bson = ObjectWrap::Unwrap(args.This()); - - // Unpack the buffer variable -#if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION < 3 - Buffer *buffer = ObjectWrap::Unwrap(obj); - char* data = buffer->data(); - size_t length = buffer->length(); -#else - char* data = Buffer::Data(obj); - size_t length = Buffer::Length(obj); -#endif - - // Fetch the documents - Local documents = args[3]->ToObject(); - - BSONDeserializer deserializer(bson, data+index, length-index); - for(uint32_t i = 0; i < numberOfDocuments; i++) - { - try - { - documents->Set(i + resultIndex, deserializer.DeserializeDocument()); - } - catch (char* exception) - { - Handle error = VException(exception); - free(exception); - return error; - } - } - - // Return new index of parsing - return scope.Close(Uint32::New((uint32_t) (index + deserializer.GetSerializeSize()))); -} - -// Exporting function -extern "C" void init(Handle target) -{ - HandleScope scope; - BSON::Initialize(target); -} - -NODE_MODULE(bson, BSON::Initialize); +//=========================================================================== + +#include +#include +#include +#include +#include + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-parameter" +#endif + +#include + +// this and the above block must be around the v8.h header otherwise +// v8 is not happy +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#include +#include +#include + +#include +#include +#include +#include + +#ifdef __sun + #include +#endif + +#include "bson.h" + +using namespace v8; +using namespace node; + +//=========================================================================== + +void DataStream::WriteObjectId(const Handle& object, const Handle& key) +{ + uint16_t buffer[12]; + object->Get(key)->ToString()->Write(buffer, 0, 12); + for(uint32_t i = 0; i < 12; ++i) + { + *p++ = (char) buffer[i]; + } +} + +void ThrowAllocatedStringException(size_t allocationSize, const char* format, ...) +{ + va_list args; + va_start(args, format); + char* string = (char*) malloc(allocationSize); + vsprintf(string, format, args); + va_end(args); + + throw string; +} + +void DataStream::CheckKey(const Local& keyName) +{ + size_t keyLength = keyName->Utf8Length(); + if(keyLength == 0) return; + + char* keyStringBuffer = (char*) alloca(keyLength+1); + keyName->WriteUtf8(keyStringBuffer); + + if(keyStringBuffer[0] == '$') + { + ThrowAllocatedStringException(64+keyLength, "key %s must not start with '$'", keyStringBuffer); + } + + if(strchr(keyStringBuffer, '.') != NULL) + { + ThrowAllocatedStringException(64+keyLength, "key %s must not contain '.'", keyStringBuffer); + } +} + +template void BSONSerializer::SerializeDocument(const Handle& value) +{ + void* documentSize = this->BeginWriteSize(); + Local object = bson->GetSerializeObject(value); + + // Get the object property names + #if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION < 6 + Local propertyNames = object->GetPropertyNames(); + #else + Local propertyNames = object->GetOwnPropertyNames(); + #endif + + // Length of the property + int propertyLength = propertyNames->Length(); + for(int i = 0; i < propertyLength; ++i) + { + const Local& propertyName = propertyNames->Get(i)->ToString(); + if(checkKeys) this->CheckKey(propertyName); + + const Local& propertyValue = object->Get(propertyName); + + if(serializeFunctions || !propertyValue->IsFunction()) + { + void* typeLocation = this->BeginWriteType(); + this->WriteString(propertyName); + SerializeValue(typeLocation, propertyValue); + } + } + + this->WriteByte(0); + this->CommitSize(documentSize); +} + +template void BSONSerializer::SerializeArray(const Handle& value) +{ + void* documentSize = this->BeginWriteSize(); + + Local array = Local::Cast(value->ToObject()); + uint32_t arrayLength = array->Length(); + + for(uint32_t i = 0; i < arrayLength; ++i) + { + void* typeLocation = this->BeginWriteType(); + this->WriteUInt32String(i); + SerializeValue(typeLocation, array->Get(i)); + } + + this->WriteByte(0); + this->CommitSize(documentSize); +} + +// This is templated so that we can use this function to both count the number of bytes, and to serialize those bytes. +// The template approach eliminates almost all of the inspection of values unless they're required (eg. string lengths) +// and ensures that there is always consistency between bytes counted and bytes written by design. +template void BSONSerializer::SerializeValue(void* typeLocation, const Handle& value) +{ + if(value->IsNumber()) + { + double doubleValue = value->NumberValue(); + int intValue = (int) doubleValue; + if(intValue == doubleValue) + { + this->CommitType(typeLocation, BSON_TYPE_INT); + this->WriteInt32(intValue); + } + else + { + this->CommitType(typeLocation, BSON_TYPE_NUMBER); + this->WriteDouble(doubleValue); + } + } + else if(value->IsString()) + { + this->CommitType(typeLocation, BSON_TYPE_STRING); + this->WriteLengthPrefixedString(value->ToString()); + } + else if(value->IsBoolean()) + { + this->CommitType(typeLocation, BSON_TYPE_BOOLEAN); + this->WriteBool(value); + } + else if(value->IsArray()) + { + this->CommitType(typeLocation, BSON_TYPE_ARRAY); + SerializeArray(value); + } + else if(value->IsDate()) + { + this->CommitType(typeLocation, BSON_TYPE_DATE); + this->WriteInt64(value); + } + else if(value->IsRegExp()) + { + this->CommitType(typeLocation, BSON_TYPE_REGEXP); + const Handle& regExp = Handle::Cast(value); + + this->WriteString(regExp->GetSource()); + + int flags = regExp->GetFlags(); + if(flags & RegExp::kGlobal) this->WriteByte('s'); + if(flags & RegExp::kIgnoreCase) this->WriteByte('i'); + if(flags & RegExp::kMultiline) this->WriteByte('m'); + this->WriteByte(0); + } + else if(value->IsFunction()) + { + this->CommitType(typeLocation, BSON_TYPE_CODE); + this->WriteLengthPrefixedString(value->ToString()); + } + else if(value->IsObject()) + { + const Local& object = value->ToObject(); + if(object->Has(bson->_bsontypeString)) + { + const Local& constructorString = object->GetConstructorName(); + if(bson->longString->StrictEquals(constructorString)) + { + this->CommitType(typeLocation, BSON_TYPE_LONG); + this->WriteInt32(object, bson->_longLowString); + this->WriteInt32(object, bson->_longHighString); + } + else if(bson->timestampString->StrictEquals(constructorString)) + { + this->CommitType(typeLocation, BSON_TYPE_TIMESTAMP); + this->WriteInt32(object, bson->_longLowString); + this->WriteInt32(object, bson->_longHighString); + } + else if(bson->objectIDString->StrictEquals(constructorString)) + { + this->CommitType(typeLocation, BSON_TYPE_OID); + this->WriteObjectId(object, bson->_objectIDidString); + } + else if(bson->binaryString->StrictEquals(constructorString)) + { + this->CommitType(typeLocation, BSON_TYPE_BINARY); + + uint32_t length = object->Get(bson->_binaryPositionString)->Uint32Value(); + Local bufferObj = object->Get(bson->_binaryBufferString)->ToObject(); + + this->WriteInt32(length); + this->WriteByte(object, bson->_binarySubTypeString); // write subtype + this->WriteData(Buffer::Data(bufferObj), length); + } + else if(bson->doubleString->StrictEquals(constructorString)) + { + this->CommitType(typeLocation, BSON_TYPE_NUMBER); + this->WriteDouble(object, bson->_doubleValueString); + } + else if(bson->symbolString->StrictEquals(constructorString)) + { + this->CommitType(typeLocation, BSON_TYPE_SYMBOL); + this->WriteLengthPrefixedString(object->Get(bson->_symbolValueString)->ToString()); + } + else if(bson->codeString->StrictEquals(constructorString)) + { + const Local& function = object->Get(bson->_codeCodeString)->ToString(); + const Local& scope = object->Get(bson->_codeScopeString)->ToObject(); + + // For Node < 0.6.X use the GetPropertyNames + #if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION < 6 + uint32_t propertyNameLength = scope->GetPropertyNames()->Length(); + #else + uint32_t propertyNameLength = scope->GetOwnPropertyNames()->Length(); + #endif + + if(propertyNameLength > 0) + { + this->CommitType(typeLocation, BSON_TYPE_CODE_W_SCOPE); + void* codeWidthScopeSize = this->BeginWriteSize(); + this->WriteLengthPrefixedString(function->ToString()); + SerializeDocument(scope); + this->CommitSize(codeWidthScopeSize); + } + else + { + this->CommitType(typeLocation, BSON_TYPE_CODE); + this->WriteLengthPrefixedString(function->ToString()); + } + } + else if(bson->dbrefString->StrictEquals(constructorString)) + { + this->CommitType(typeLocation, BSON_TYPE_OBJECT); + + void* dbRefSize = this->BeginWriteSize(); + + void* refType = this->BeginWriteType(); + this->WriteData("$ref", 5); + SerializeValue(refType, object->Get(bson->_dbRefNamespaceString)); + + void* idType = this->BeginWriteType(); + this->WriteData("$id", 4); + SerializeValue(idType, object->Get(bson->_dbRefOidString)); + + const Local& refDbValue = object->Get(bson->_dbRefDbString); + if(!refDbValue->IsUndefined()) + { + void* dbType = this->BeginWriteType(); + this->WriteData("$db", 4); + SerializeValue(dbType, refDbValue); + } + + this->WriteByte(0); + this->CommitSize(dbRefSize); + } + else if(bson->minKeyString->StrictEquals(constructorString)) + { + this->CommitType(typeLocation, BSON_TYPE_MIN_KEY); + } + else if(bson->maxKeyString->StrictEquals(constructorString)) + { + this->CommitType(typeLocation, BSON_TYPE_MAX_KEY); + } + } + else if(Buffer::HasInstance(value)) + { + this->CommitType(typeLocation, BSON_TYPE_BINARY); + + #if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION < 3 + Buffer *buffer = ObjectWrap::Unwrap(value->ToObject()); + uint32_t length = object->length(); + #else + uint32_t length = Buffer::Length(value->ToObject()); + #endif + + this->WriteInt32(length); + this->WriteByte(0); + this->WriteData(Buffer::Data(value->ToObject()), length); + } + else + { + this->CommitType(typeLocation, BSON_TYPE_OBJECT); + SerializeDocument(value); + } + } + else if(value->IsNull() || value->IsUndefined()) + { + this->CommitType(typeLocation, BSON_TYPE_NULL); + } +} + +// Data points to start of element list, length is length of entire document including '\0' but excluding initial size +BSONDeserializer::BSONDeserializer(BSON* aBson, char* data, size_t length) +: bson(aBson), + pStart(data), + p(data), + pEnd(data + length - 1) +{ + if(*pEnd != '\0') ThrowAllocatedStringException(64, "Missing end of document marker '\\0'"); +} + +BSONDeserializer::BSONDeserializer(BSONDeserializer& parentSerializer, size_t length) +: bson(parentSerializer.bson), + pStart(parentSerializer.p), + p(parentSerializer.p), + pEnd(parentSerializer.p + length - 1) +{ + parentSerializer.p += length; + if(pEnd > parentSerializer.pEnd) ThrowAllocatedStringException(64, "Child document exceeds parent's bounds"); + if(*pEnd != '\0') ThrowAllocatedStringException(64, "Missing end of document marker '\\0'"); +} + +Local BSONDeserializer::ReadCString() +{ + char* start = p; + while(*p++) { } + return String::New(start, (int32_t) (p-start-1) ); +} + +int32_t BSONDeserializer::ReadRegexOptions() +{ + int32_t options = 0; + for(;;) + { + switch(*p++) + { + case '\0': return options; + case 's': options |= RegExp::kGlobal; break; + case 'i': options |= RegExp::kIgnoreCase; break; + case 'm': options |= RegExp::kMultiline; break; + } + } +} + +uint32_t BSONDeserializer::ReadIntegerString() +{ + uint32_t value = 0; + while(*p) + { + if(*p < '0' || *p > '9') ThrowAllocatedStringException(64, "Invalid key for array"); + value = value * 10 + *p++ - '0'; + } + ++p; + return value; +} + +Local BSONDeserializer::ReadString() +{ + uint32_t length = ReadUInt32(); + char* start = p; + p += length; + return String::New(start, length-1); +} + +Local BSONDeserializer::ReadObjectId() +{ + uint16_t objectId[12]; + for(size_t i = 0; i < 12; ++i) + { + objectId[i] = *reinterpret_cast(p++); + } + return String::New(objectId, 12); +} + +Handle BSONDeserializer::DeserializeDocument() +{ + uint32_t length = ReadUInt32(); + if(length < 5) ThrowAllocatedStringException(64, "Bad BSON: Document is less than 5 bytes"); + + BSONDeserializer documentDeserializer(*this, length-4); + return documentDeserializer.DeserializeDocumentInternal(); +} + +Handle BSONDeserializer::DeserializeDocumentInternal() +{ + Local returnObject = Object::New(); + + while(HasMoreData()) + { + BsonType type = (BsonType) ReadByte(); + const Local& name = ReadCString(); + const Handle& value = DeserializeValue(type); + returnObject->ForceSet(name, value); + } + if(p != pEnd) ThrowAllocatedStringException(64, "Bad BSON Document: Serialize consumed unexpected number of bytes"); + + // From JavaScript: + // if(object['$id'] != null) object = new DBRef(object['$ref'], object['$id'], object['$db']); + if(returnObject->Has(bson->_dbRefIdRefString)) + { + Local argv[] = { returnObject->Get(bson->_dbRefRefString), returnObject->Get(bson->_dbRefIdRefString), returnObject->Get(bson->_dbRefDbRefString) }; + return bson->dbrefConstructor->NewInstance(3, argv); + } + else + { + return returnObject; + } +} + +Handle BSONDeserializer::DeserializeArray() +{ + uint32_t length = ReadUInt32(); + if(length < 5) ThrowAllocatedStringException(64, "Bad BSON: Array Document is less than 5 bytes"); + + BSONDeserializer documentDeserializer(*this, length-4); + return documentDeserializer.DeserializeArrayInternal(); +} + +Handle BSONDeserializer::DeserializeArrayInternal() +{ + Local returnArray = Array::New(); + + while(HasMoreData()) + { + BsonType type = (BsonType) ReadByte(); + uint32_t index = ReadIntegerString(); + const Handle& value = DeserializeValue(type); + returnArray->Set(index, value); + } + if(p != pEnd) ThrowAllocatedStringException(64, "Bad BSON Array: Serialize consumed unexpected number of bytes"); + + return returnArray; +} + +Handle BSONDeserializer::DeserializeValue(BsonType type) +{ + switch(type) + { + case BSON_TYPE_STRING: + return ReadString(); + + case BSON_TYPE_INT: + return Integer::New(ReadInt32()); + + case BSON_TYPE_NUMBER: + return Number::New(ReadDouble()); + + case BSON_TYPE_NULL: + return Null(); + + case BSON_TYPE_UNDEFINED: + return Undefined(); + + case BSON_TYPE_TIMESTAMP: + { + int32_t lowBits = ReadInt32(); + int32_t highBits = ReadInt32(); + Local argv[] = { Int32::New(lowBits), Int32::New(highBits) }; + return bson->timestampConstructor->NewInstance(2, argv); + } + + case BSON_TYPE_BOOLEAN: + return (ReadByte() != 0) ? True() : False(); + + case BSON_TYPE_REGEXP: + { + const Local& regex = ReadCString(); + int32_t options = ReadRegexOptions(); + return RegExp::New(regex, (RegExp::Flags) options); + } + + case BSON_TYPE_CODE: + { + const Local& code = ReadString(); + const Local& scope = Object::New(); + Local argv[] = { code, scope }; + return bson->codeConstructor->NewInstance(2, argv); + } + + case BSON_TYPE_CODE_W_SCOPE: + { + ReadUInt32(); + const Local& code = ReadString(); + const Handle& scope = DeserializeDocument(); + Local argv[] = { code, scope->ToObject() }; + return bson->codeConstructor->NewInstance(2, argv); + } + + case BSON_TYPE_OID: + { + Local argv[] = { ReadObjectId() }; + return bson->objectIDConstructor->NewInstance(1, argv); + } + + case BSON_TYPE_BINARY: + { + uint32_t length = ReadUInt32(); + uint32_t subType = ReadByte(); + Buffer* buffer = Buffer::New(p, length); + p += length; + + Handle argv[] = { buffer->handle_, Uint32::New(subType) }; + return bson->binaryConstructor->NewInstance(2, argv); + } + + case BSON_TYPE_LONG: + { + // Read 32 bit integers + int32_t lowBits = (int32_t) ReadInt32(); + int32_t highBits = (int32_t) ReadInt32(); + + // If value is < 2^53 and >-2^53 + if((highBits < 0x200000 || (highBits == 0x200000 && lowBits == 0)) && highBits >= -0x200000) { + // Adjust the pointer and read as 64 bit value + p -= 8; + // Read the 64 bit value + int64_t finalValue = (int64_t) ReadInt64(); + return Number::New(finalValue); + } + + Local argv[] = { Int32::New(lowBits), Int32::New(highBits) }; + return bson->longConstructor->NewInstance(2, argv); + } + + case BSON_TYPE_DATE: + return Date::New((double) ReadInt64()); + + case BSON_TYPE_ARRAY: + return DeserializeArray(); + + case BSON_TYPE_OBJECT: + return DeserializeDocument(); + + case BSON_TYPE_SYMBOL: + { + const Local& string = ReadString(); + Local argv[] = { string }; + return bson->symbolConstructor->NewInstance(1, argv); + } + + case BSON_TYPE_MIN_KEY: + return bson->minKeyConstructor->NewInstance(); + + case BSON_TYPE_MAX_KEY: + return bson->maxKeyConstructor->NewInstance(); + + default: + ThrowAllocatedStringException(64, "Unhandled BSON Type: %d", type); + } + + return v8::Null(); +} + + +static Handle VException(const char *msg) +{ + HandleScope scope; + return ThrowException(Exception::Error(String::New(msg))); +} + +Persistent BSON::constructor_template; + +BSON::BSON() : ObjectWrap() +{ + // Setup pre-allocated comparision objects + _bsontypeString = Persistent::New(String::New("_bsontype")); + _longLowString = Persistent::New(String::New("low_")); + _longHighString = Persistent::New(String::New("high_")); + _objectIDidString = Persistent::New(String::New("id")); + _binaryPositionString = Persistent::New(String::New("position")); + _binarySubTypeString = Persistent::New(String::New("sub_type")); + _binaryBufferString = Persistent::New(String::New("buffer")); + _doubleValueString = Persistent::New(String::New("value")); + _symbolValueString = Persistent::New(String::New("value")); + _dbRefRefString = Persistent::New(String::New("$ref")); + _dbRefIdRefString = Persistent::New(String::New("$id")); + _dbRefDbRefString = Persistent::New(String::New("$db")); + _dbRefNamespaceString = Persistent::New(String::New("namespace")); + _dbRefDbString = Persistent::New(String::New("db")); + _dbRefOidString = Persistent::New(String::New("oid")); + _codeCodeString = Persistent::New(String::New("code")); + _codeScopeString = Persistent::New(String::New("scope")); + _toBSONString = Persistent::New(String::New("toBSON")); + + longString = Persistent::New(String::New("Long")); + objectIDString = Persistent::New(String::New("ObjectID")); + binaryString = Persistent::New(String::New("Binary")); + codeString = Persistent::New(String::New("Code")); + dbrefString = Persistent::New(String::New("DBRef")); + symbolString = Persistent::New(String::New("Symbol")); + doubleString = Persistent::New(String::New("Double")); + timestampString = Persistent::New(String::New("Timestamp")); + minKeyString = Persistent::New(String::New("MinKey")); + maxKeyString = Persistent::New(String::New("MaxKey")); +} + +void BSON::Initialize(v8::Handle target) +{ + // Grab the scope of the call from Node + HandleScope scope; + // Define a new function template + Local t = FunctionTemplate::New(New); + constructor_template = Persistent::New(t); + constructor_template->InstanceTemplate()->SetInternalFieldCount(1); + constructor_template->SetClassName(String::NewSymbol("BSON")); + + // Instance methods + NODE_SET_PROTOTYPE_METHOD(constructor_template, "calculateObjectSize", CalculateObjectSize); + NODE_SET_PROTOTYPE_METHOD(constructor_template, "serialize", BSONSerialize); + NODE_SET_PROTOTYPE_METHOD(constructor_template, "serializeWithBufferAndIndex", SerializeWithBufferAndIndex); + NODE_SET_PROTOTYPE_METHOD(constructor_template, "deserialize", BSONDeserialize); + NODE_SET_PROTOTYPE_METHOD(constructor_template, "deserializeStream", BSONDeserializeStream); + + target->ForceSet(String::NewSymbol("BSON"), constructor_template->GetFunction()); +} + +// Create a new instance of BSON and passing it the existing context +Handle BSON::New(const Arguments &args) +{ + HandleScope scope; + + // Check that we have an array + if(args.Length() == 1 && args[0]->IsArray()) + { + // Cast the array to a local reference + Local array = Local::Cast(args[0]); + + if(array->Length() > 0) + { + // Create a bson object instance and return it + BSON *bson = new BSON(); + + uint32_t foundClassesMask = 0; + + // Iterate over all entries to save the instantiate funtions + for(uint32_t i = 0; i < array->Length(); i++) + { + // Let's get a reference to the function + Local func = Local::Cast(array->Get(i)); + Local functionName = func->GetName()->ToString(); + + // Save the functions making them persistant handles (they don't get collected) + if(functionName->StrictEquals(bson->longString)) + { + bson->longConstructor = Persistent::New(func); + foundClassesMask |= 1; + } + else if(functionName->StrictEquals(bson->objectIDString)) + { + bson->objectIDConstructor = Persistent::New(func); + foundClassesMask |= 2; + } + else if(functionName->StrictEquals(bson->binaryString)) + { + bson->binaryConstructor = Persistent::New(func); + foundClassesMask |= 4; + } + else if(functionName->StrictEquals(bson->codeString)) + { + bson->codeConstructor = Persistent::New(func); + foundClassesMask |= 8; + } + else if(functionName->StrictEquals(bson->dbrefString)) + { + bson->dbrefConstructor = Persistent::New(func); + foundClassesMask |= 0x10; + } + else if(functionName->StrictEquals(bson->symbolString)) + { + bson->symbolConstructor = Persistent::New(func); + foundClassesMask |= 0x20; + } + else if(functionName->StrictEquals(bson->doubleString)) + { + bson->doubleConstructor = Persistent::New(func); + foundClassesMask |= 0x40; + } + else if(functionName->StrictEquals(bson->timestampString)) + { + bson->timestampConstructor = Persistent::New(func); + foundClassesMask |= 0x80; + } + else if(functionName->StrictEquals(bson->minKeyString)) + { + bson->minKeyConstructor = Persistent::New(func); + foundClassesMask |= 0x100; + } + else if(functionName->StrictEquals(bson->maxKeyString)) + { + bson->maxKeyConstructor = Persistent::New(func); + foundClassesMask |= 0x200; + } + } + + // Check if we have the right number of constructors otherwise throw an error + if(foundClassesMask != 0x3ff) + { + delete bson; + return VException("Missing function constructor for either [Long/ObjectID/Binary/Code/DbRef/Symbol/Double/Timestamp/MinKey/MaxKey]"); + } + else + { + bson->Wrap(args.This()); + return args.This(); + } + } + else + { + return VException("No types passed in"); + } + } + else + { + return VException("Argument passed in must be an array of types"); + } +} + +//------------------------------------------------------------------------------------------------ +//------------------------------------------------------------------------------------------------ +//------------------------------------------------------------------------------------------------ +//------------------------------------------------------------------------------------------------ + +Handle BSON::BSONDeserialize(const Arguments &args) +{ + HandleScope scope; + + // Ensure that we have an parameter + if(Buffer::HasInstance(args[0]) && args.Length() > 1) return VException("One argument required - buffer1."); + if(args[0]->IsString() && args.Length() > 1) return VException("One argument required - string1."); + // Throw an exception if the argument is not of type Buffer + if(!Buffer::HasInstance(args[0]) && !args[0]->IsString()) return VException("Argument must be a Buffer or String."); + + // Define pointer to data + Local obj = args[0]->ToObject(); + + // Unpack the BSON parser instance + BSON *bson = ObjectWrap::Unwrap(args.This()); + + // If we passed in a buffer, let's unpack it, otherwise let's unpack the string + if(Buffer::HasInstance(obj)) + { +#if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION < 3 + Buffer *buffer = ObjectWrap::Unwrap(obj); + char* data = buffer->data(); + size_t length = buffer->length(); +#else + char* data = Buffer::Data(obj); + size_t length = Buffer::Length(obj); +#endif + + // Validate that we have at least 5 bytes + if(length < 5) return VException("corrupt bson message < 5 bytes long"); + + try + { + BSONDeserializer deserializer(bson, data, length); + return deserializer.DeserializeDocument(); + } + catch(char* exception) + { + Handle error = VException(exception); + free(exception); + return error; + } + + } + else + { + // The length of the data for this encoding + ssize_t len = DecodeBytes(args[0], BINARY); + + // Validate that we have at least 5 bytes + if(len < 5) return VException("corrupt bson message < 5 bytes long"); + + // Let's define the buffer size + char* data = (char *)malloc(len); + DecodeWrite(data, len, args[0], BINARY); + + try + { + BSONDeserializer deserializer(bson, data, len); + Handle result = deserializer.DeserializeDocument(); + free(data); + return result; + + } + catch(char* exception) + { + Handle error = VException(exception); + free(exception); + free(data); + return error; + } + } +} + +Local BSON::GetSerializeObject(const Handle& argValue) +{ + Local object = argValue->ToObject(); + if(object->Has(_toBSONString)) + { + const Local& toBSON = object->Get(_toBSONString); + if(!toBSON->IsFunction()) ThrowAllocatedStringException(64, "toBSON is not a function"); + + Local result = Local::Cast(toBSON)->Call(object, 0, NULL); + if(!result->IsObject()) ThrowAllocatedStringException(64, "toBSON function did not return an object"); + return result->ToObject(); + } + else + { + return object; + } +} + +Handle BSON::BSONSerialize(const Arguments &args) +{ + HandleScope scope; + + if(args.Length() == 1 && !args[0]->IsObject()) return VException("One, two or tree arguments required - [object] or [object, boolean] or [object, boolean, boolean]"); + if(args.Length() == 2 && !args[0]->IsObject() && !args[1]->IsBoolean()) return VException("One, two or tree arguments required - [object] or [object, boolean] or [object, boolean, boolean]"); + if(args.Length() == 3 && !args[0]->IsObject() && !args[1]->IsBoolean() && !args[2]->IsBoolean()) return VException("One, two or tree arguments required - [object] or [object, boolean] or [object, boolean, boolean]"); + if(args.Length() == 4 && !args[0]->IsObject() && !args[1]->IsBoolean() && !args[2]->IsBoolean() && !args[3]->IsBoolean()) return VException("One, two or tree arguments required - [object] or [object, boolean] or [object, boolean, boolean] or [object, boolean, boolean, boolean]"); + if(args.Length() > 4) return VException("One, two, tree or four arguments required - [object] or [object, boolean] or [object, boolean, boolean] or [object, boolean, boolean, boolean]"); + + // Unpack the BSON parser instance + BSON *bson = ObjectWrap::Unwrap(args.This()); + + // Calculate the total size of the document in binary form to ensure we only allocate memory once + // With serialize function + bool serializeFunctions = (args.Length() >= 4) && args[3]->BooleanValue(); + + char *serialized_object = NULL; + size_t object_size; + try + { + Local object = bson->GetSerializeObject(args[0]); + + BSONSerializer counter(bson, false, serializeFunctions); + counter.SerializeDocument(object); + object_size = counter.GetSerializeSize(); + + // Allocate the memory needed for the serialization + serialized_object = (char *)malloc(object_size); + + // Check if we have a boolean value + bool checkKeys = args.Length() >= 3 && args[1]->IsBoolean() && args[1]->BooleanValue(); + BSONSerializer data(bson, checkKeys, serializeFunctions, serialized_object); + data.SerializeDocument(object); + } + catch(char *err_msg) + { + free(serialized_object); + Handle error = VException(err_msg); + free(err_msg); + return error; + } + + // If we have 3 arguments + if(args.Length() == 3 || args.Length() == 4) + { + Buffer *buffer = Buffer::New(serialized_object, object_size); + free(serialized_object); + return scope.Close(buffer->handle_); + } + else + { + Local bin_value = Encode(serialized_object, object_size, BINARY)->ToString(); + free(serialized_object); + return bin_value; + } +} + +Handle BSON::CalculateObjectSize(const Arguments &args) +{ + HandleScope scope; + // Ensure we have a valid object + if(args.Length() == 1 && !args[0]->IsObject()) return VException("One argument required - [object]"); + if(args.Length() == 2 && !args[0]->IsObject() && !args[1]->IsBoolean()) return VException("Two arguments required - [object, boolean]"); + if(args.Length() > 3) return VException("One or two arguments required - [object] or [object, boolean]"); + + // Unpack the BSON parser instance + BSON *bson = ObjectWrap::Unwrap(args.This()); + bool serializeFunctions = (args.Length() >= 2) && args[1]->BooleanValue(); + BSONSerializer countSerializer(bson, false, serializeFunctions); + countSerializer.SerializeDocument(args[0]); + + // Return the object size + return scope.Close(Uint32::New((uint32_t) countSerializer.GetSerializeSize())); +} + +Handle BSON::SerializeWithBufferAndIndex(const Arguments &args) +{ + HandleScope scope; + + //BSON.serializeWithBufferAndIndex = function serializeWithBufferAndIndex(object, ->, buffer, index) { + // Ensure we have the correct values + if(args.Length() > 5) return VException("Four or five parameters required [object, boolean, Buffer, int] or [object, boolean, Buffer, int, boolean]"); + if(args.Length() == 4 && !args[0]->IsObject() && !args[1]->IsBoolean() && !Buffer::HasInstance(args[2]) && !args[3]->IsUint32()) return VException("Four parameters required [object, boolean, Buffer, int]"); + if(args.Length() == 5 && !args[0]->IsObject() && !args[1]->IsBoolean() && !Buffer::HasInstance(args[2]) && !args[3]->IsUint32() && !args[4]->IsBoolean()) return VException("Four parameters required [object, boolean, Buffer, int, boolean]"); + + uint32_t index; + size_t object_size; + + try + { + BSON *bson = ObjectWrap::Unwrap(args.This()); + + Local obj = args[2]->ToObject(); + char* data = Buffer::Data(obj); + size_t length = Buffer::Length(obj); + + index = args[3]->Uint32Value(); + bool checkKeys = args.Length() >= 4 && args[1]->IsBoolean() && args[1]->BooleanValue(); + bool serializeFunctions = (args.Length() == 5) && args[4]->BooleanValue(); + + BSONSerializer dataSerializer(bson, checkKeys, serializeFunctions, data+index); + dataSerializer.SerializeDocument(bson->GetSerializeObject(args[0])); + object_size = dataSerializer.GetSerializeSize(); + + if(object_size + index > length) return VException("Serious error - overflowed buffer!!"); + } + catch(char *exception) + { + Handle error = VException(exception); + free(exception); + return error; + } + + return scope.Close(Uint32::New((uint32_t) (index + object_size - 1))); +} + +Handle BSON::BSONDeserializeStream(const Arguments &args) +{ + HandleScope scope; + + // At least 3 arguments required + if(args.Length() < 5) return VException("Arguments required (Buffer(data), Number(index in data), Number(number of documents to deserialize), Array(results), Number(index in the array), Object(optional))"); + + // If the number of argumets equals 3 + if(args.Length() >= 5) + { + if(!Buffer::HasInstance(args[0])) return VException("First argument must be Buffer instance"); + if(!args[1]->IsUint32()) return VException("Second argument must be a positive index number"); + if(!args[2]->IsUint32()) return VException("Third argument must be a positive number of documents to deserialize"); + if(!args[3]->IsArray()) return VException("Fourth argument must be an array the size of documents to deserialize"); + if(!args[4]->IsUint32()) return VException("Sixth argument must be a positive index number"); + } + + // If we have 4 arguments + if(args.Length() == 6 && !args[5]->IsObject()) return VException("Fifth argument must be an object with options"); + + // Define pointer to data + Local obj = args[0]->ToObject(); + uint32_t numberOfDocuments = args[2]->Uint32Value(); + uint32_t index = args[1]->Uint32Value(); + uint32_t resultIndex = args[4]->Uint32Value(); + + // Unpack the BSON parser instance + BSON *bson = ObjectWrap::Unwrap(args.This()); + + // Unpack the buffer variable +#if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION < 3 + Buffer *buffer = ObjectWrap::Unwrap(obj); + char* data = buffer->data(); + size_t length = buffer->length(); +#else + char* data = Buffer::Data(obj); + size_t length = Buffer::Length(obj); +#endif + + // Fetch the documents + Local documents = args[3]->ToObject(); + + BSONDeserializer deserializer(bson, data+index, length-index); + for(uint32_t i = 0; i < numberOfDocuments; i++) + { + try + { + documents->Set(i + resultIndex, deserializer.DeserializeDocument()); + } + catch (char* exception) + { + Handle error = VException(exception); + free(exception); + return error; + } + } + + // Return new index of parsing + return scope.Close(Uint32::New((uint32_t) (index + deserializer.GetSerializeSize()))); +} + +// Exporting function +extern "C" void init(Handle target) +{ + HandleScope scope; + BSON::Initialize(target); +} + +NODE_MODULE(bson, BSON::Initialize); diff --git a/node_modules/mongoskin/node_modules/mongodb/node_modules/bson/ext/bson.h b/node_modules/mongoskin/node_modules/mongodb/node_modules/bson/ext/bson.h index 580abd4..72ae8cc 100644 --- a/node_modules/mongoskin/node_modules/mongodb/node_modules/bson/ext/bson.h +++ b/node_modules/mongoskin/node_modules/mongodb/node_modules/bson/ext/bson.h @@ -1,273 +1,273 @@ -//=========================================================================== - -#ifndef BSON_H_ -#define BSON_H_ - -//=========================================================================== - -#define USE_MISALIGNED_MEMORY_ACCESS 1 - -#include -#include -#include - -using namespace v8; -using namespace node; - -//=========================================================================== - -enum BsonType -{ - BSON_TYPE_NUMBER = 1, - BSON_TYPE_STRING = 2, - BSON_TYPE_OBJECT = 3, - BSON_TYPE_ARRAY = 4, - BSON_TYPE_BINARY = 5, - BSON_TYPE_UNDEFINED = 6, - BSON_TYPE_OID = 7, - BSON_TYPE_BOOLEAN = 8, - BSON_TYPE_DATE = 9, - BSON_TYPE_NULL = 10, - BSON_TYPE_REGEXP = 11, - BSON_TYPE_CODE = 13, - BSON_TYPE_SYMBOL = 14, - BSON_TYPE_CODE_W_SCOPE = 15, - BSON_TYPE_INT = 16, - BSON_TYPE_TIMESTAMP = 17, - BSON_TYPE_LONG = 18, - BSON_TYPE_MAX_KEY = 0x7f, - BSON_TYPE_MIN_KEY = 0xff -}; - -//=========================================================================== - -template class BSONSerializer; - -class BSON : public ObjectWrap { -public: - BSON(); - ~BSON() {} - - static void Initialize(Handle target); - static Handle BSONDeserializeStream(const Arguments &args); - - // JS based objects - static Handle BSONSerialize(const Arguments &args); - static Handle BSONDeserialize(const Arguments &args); - - // Calculate size of function - static Handle CalculateObjectSize(const Arguments &args); - static Handle SerializeWithBufferAndIndex(const Arguments &args); - - // Constructor used for creating new BSON objects from C++ - static Persistent constructor_template; - -private: - static Handle New(const Arguments &args); - static Handle deserialize(BSON *bson, char *data, uint32_t dataLength, uint32_t startIndex, bool is_array_item); - - // BSON type instantiate functions - Persistent longConstructor; - Persistent objectIDConstructor; - Persistent binaryConstructor; - Persistent codeConstructor; - Persistent dbrefConstructor; - Persistent symbolConstructor; - Persistent doubleConstructor; - Persistent timestampConstructor; - Persistent minKeyConstructor; - Persistent maxKeyConstructor; - - // Equality Objects - Persistent longString; - Persistent objectIDString; - Persistent binaryString; - Persistent codeString; - Persistent dbrefString; - Persistent symbolString; - Persistent doubleString; - Persistent timestampString; - Persistent minKeyString; - Persistent maxKeyString; - - // Equality speed up comparison objects - Persistent _bsontypeString; - Persistent _longLowString; - Persistent _longHighString; - Persistent _objectIDidString; - Persistent _binaryPositionString; - Persistent _binarySubTypeString; - Persistent _binaryBufferString; - Persistent _doubleValueString; - Persistent _symbolValueString; - - Persistent _dbRefRefString; - Persistent _dbRefIdRefString; - Persistent _dbRefDbRefString; - Persistent _dbRefNamespaceString; - Persistent _dbRefDbString; - Persistent _dbRefOidString; - - Persistent _codeCodeString; - Persistent _codeScopeString; - Persistent _toBSONString; - - Local GetSerializeObject(const Handle& object); - - template friend class BSONSerializer; - friend class BSONDeserializer; -}; - -//=========================================================================== - -class CountStream -{ -public: - CountStream() : count(0) { } - - void WriteByte(int value) { ++count; } - void WriteByte(const Handle&, const Handle&) { ++count; } - void WriteBool(const Handle& value) { ++count; } - void WriteInt32(int32_t value) { count += 4; } - void WriteInt32(const Handle& value) { count += 4; } - void WriteInt32(const Handle& object, const Handle& key) { count += 4; } - void WriteInt64(int64_t value) { count += 8; } - void WriteInt64(const Handle& value) { count += 8; } - void WriteDouble(double value) { count += 8; } - void WriteDouble(const Handle& value) { count += 8; } - void WriteDouble(const Handle&, const Handle&) { count += 8; } - void WriteUInt32String(uint32_t name) { char buffer[32]; count += sprintf(buffer, "%u", name) + 1; } - void WriteLengthPrefixedString(const Local& value) { count += value->Utf8Length()+5; } - void WriteObjectId(const Handle& object, const Handle& key) { count += 12; } - void WriteString(const Local& value) { count += value->Utf8Length() + 1; } // This returns the number of bytes exclusive of the NULL terminator - void WriteData(const char* data, size_t length) { count += length; } - - void* BeginWriteType() { ++count; return NULL; } - void CommitType(void*, BsonType) { } - void* BeginWriteSize() { count += 4; return NULL; } - void CommitSize(void*) { } - - size_t GetSerializeSize() const { return count; } - - // Do nothing. CheckKey is implemented for DataStream - void CheckKey(const Local&) { } - -private: - size_t count; -}; - -class DataStream -{ -public: - DataStream(char* aDestinationBuffer) : destinationBuffer(aDestinationBuffer), p(aDestinationBuffer) { } - - void WriteByte(int value) { *p++ = value; } - void WriteByte(const Handle& object, const Handle& key) { *p++ = object->Get(key)->Int32Value(); } -#if USE_MISALIGNED_MEMORY_ACCESS - void WriteInt32(int32_t value) { *reinterpret_cast(p) = value; p += 4; } - void WriteInt64(int64_t value) { *reinterpret_cast(p) = value; p += 8; } - void WriteDouble(double value) { *reinterpret_cast(p) = value; p += 8; } -#else - void WriteInt32(int32_t value) { memcpy(p, &value, 4); p += 4; } - void WriteInt64(int64_t value) { memcpy(p, &value, 8); p += 8; } - void WriteDouble(double value) { memcpy(p, &value, 8); p += 8; } -#endif - void WriteBool(const Handle& value) { WriteByte(value->BooleanValue() ? 1 : 0); } - void WriteInt32(const Handle& value) { WriteInt32(value->Int32Value()); } - void WriteInt32(const Handle& object, const Handle& key) { WriteInt32(object->Get(key)); } - void WriteInt64(const Handle& value) { WriteInt64(value->IntegerValue()); } - void WriteDouble(const Handle& value) { WriteDouble(value->NumberValue()); } - void WriteDouble(const Handle& object, const Handle& key) { WriteDouble(object->Get(key)); } - void WriteUInt32String(uint32_t name) { p += sprintf(p, "%u", name) + 1; } - void WriteLengthPrefixedString(const Local& value) { WriteInt32(value->Utf8Length()+1); WriteString(value); } - void WriteObjectId(const Handle& object, const Handle& key); - void WriteString(const Local& value) { p += value->WriteUtf8(p); } // This returns the number of bytes inclusive of the NULL terminator. - void WriteData(const char* data, size_t length) { memcpy(p, data, length); p += length; } - - void* BeginWriteType() { void* returnValue = p; p++; return returnValue; } - void CommitType(void* beginPoint, BsonType value) { *reinterpret_cast(beginPoint) = value; } - void* BeginWriteSize() { void* returnValue = p; p += 4; return returnValue; } - -#if USE_MISALIGNED_MEMORY_ACCESS - void CommitSize(void* beginPoint) { *reinterpret_cast(beginPoint) = (int32_t) (p - (char*) beginPoint); } -#else - void CommitSize(void* beginPoint) { int32_t value = (int32_t) (p - (char*) beginPoint); memcpy(beginPoint, &value, 4); } -#endif - - size_t GetSerializeSize() const { return p - destinationBuffer; } - - void CheckKey(const Local& keyName); - -protected: - char *const destinationBuffer; // base, never changes - char* p; // cursor into buffer -}; - -template class BSONSerializer : public T -{ -private: - typedef T Inherited; - -public: - BSONSerializer(BSON* aBson, bool aCheckKeys, bool aSerializeFunctions) : Inherited(), checkKeys(aCheckKeys), serializeFunctions(aSerializeFunctions), bson(aBson) { } - BSONSerializer(BSON* aBson, bool aCheckKeys, bool aSerializeFunctions, char* parentParam) : Inherited(parentParam), checkKeys(aCheckKeys), serializeFunctions(aSerializeFunctions), bson(aBson) { } - - void SerializeDocument(const Handle& value); - void SerializeArray(const Handle& value); - void SerializeValue(void* typeLocation, const Handle& value); - -private: - bool checkKeys; - bool serializeFunctions; - BSON* bson; -}; - -//=========================================================================== - -class BSONDeserializer -{ -public: - BSONDeserializer(BSON* aBson, char* data, size_t length); - BSONDeserializer(BSONDeserializer& parentSerializer, size_t length); - - Handle DeserializeDocument(); - - bool HasMoreData() const { return p < pEnd; } - Local ReadCString(); - uint32_t ReadIntegerString(); - int32_t ReadRegexOptions(); - Local ReadString(); - Local ReadObjectId(); - - unsigned char ReadByte() { return *reinterpret_cast(p++); } -#if USE_MISALIGNED_MEMORY_ACCESS - int32_t ReadInt32() { int32_t returnValue = *reinterpret_cast(p); p += 4; return returnValue; } - uint32_t ReadUInt32() { uint32_t returnValue = *reinterpret_cast(p); p += 4; return returnValue; } - int64_t ReadInt64() { int64_t returnValue = *reinterpret_cast(p); p += 8; return returnValue; } - double ReadDouble() { double returnValue = *reinterpret_cast(p); p += 8; return returnValue; } -#else - int32_t ReadInt32() { int32_t returnValue; memcpy(&returnValue, p, 4); p += 4; return returnValue; } - uint32_t ReadUInt32() { uint32_t returnValue; memcpy(&returnValue, p, 4); p += 4; return returnValue; } - int64_t ReadInt64() { int64_t returnValue; memcpy(&returnValue, p, 8); p += 8; return returnValue; } - double ReadDouble() { double returnValue; memcpy(&returnValue, p, 8); p += 8; return returnValue; } -#endif - - size_t GetSerializeSize() const { return p - pStart; } - -private: - Handle DeserializeArray(); - Handle DeserializeValue(BsonType type); - Handle DeserializeDocumentInternal(); - Handle DeserializeArrayInternal(); - - BSON* bson; - char* const pStart; - char* p; - char* const pEnd; -}; - -//=========================================================================== - -#endif // BSON_H_ - -//=========================================================================== +//=========================================================================== + +#ifndef BSON_H_ +#define BSON_H_ + +//=========================================================================== + +#define USE_MISALIGNED_MEMORY_ACCESS 1 + +#include +#include +#include + +using namespace v8; +using namespace node; + +//=========================================================================== + +enum BsonType +{ + BSON_TYPE_NUMBER = 1, + BSON_TYPE_STRING = 2, + BSON_TYPE_OBJECT = 3, + BSON_TYPE_ARRAY = 4, + BSON_TYPE_BINARY = 5, + BSON_TYPE_UNDEFINED = 6, + BSON_TYPE_OID = 7, + BSON_TYPE_BOOLEAN = 8, + BSON_TYPE_DATE = 9, + BSON_TYPE_NULL = 10, + BSON_TYPE_REGEXP = 11, + BSON_TYPE_CODE = 13, + BSON_TYPE_SYMBOL = 14, + BSON_TYPE_CODE_W_SCOPE = 15, + BSON_TYPE_INT = 16, + BSON_TYPE_TIMESTAMP = 17, + BSON_TYPE_LONG = 18, + BSON_TYPE_MAX_KEY = 0x7f, + BSON_TYPE_MIN_KEY = 0xff +}; + +//=========================================================================== + +template class BSONSerializer; + +class BSON : public ObjectWrap { +public: + BSON(); + ~BSON() {} + + static void Initialize(Handle target); + static Handle BSONDeserializeStream(const Arguments &args); + + // JS based objects + static Handle BSONSerialize(const Arguments &args); + static Handle BSONDeserialize(const Arguments &args); + + // Calculate size of function + static Handle CalculateObjectSize(const Arguments &args); + static Handle SerializeWithBufferAndIndex(const Arguments &args); + + // Constructor used for creating new BSON objects from C++ + static Persistent constructor_template; + +private: + static Handle New(const Arguments &args); + static Handle deserialize(BSON *bson, char *data, uint32_t dataLength, uint32_t startIndex, bool is_array_item); + + // BSON type instantiate functions + Persistent longConstructor; + Persistent objectIDConstructor; + Persistent binaryConstructor; + Persistent codeConstructor; + Persistent dbrefConstructor; + Persistent symbolConstructor; + Persistent doubleConstructor; + Persistent timestampConstructor; + Persistent minKeyConstructor; + Persistent maxKeyConstructor; + + // Equality Objects + Persistent longString; + Persistent objectIDString; + Persistent binaryString; + Persistent codeString; + Persistent dbrefString; + Persistent symbolString; + Persistent doubleString; + Persistent timestampString; + Persistent minKeyString; + Persistent maxKeyString; + + // Equality speed up comparison objects + Persistent _bsontypeString; + Persistent _longLowString; + Persistent _longHighString; + Persistent _objectIDidString; + Persistent _binaryPositionString; + Persistent _binarySubTypeString; + Persistent _binaryBufferString; + Persistent _doubleValueString; + Persistent _symbolValueString; + + Persistent _dbRefRefString; + Persistent _dbRefIdRefString; + Persistent _dbRefDbRefString; + Persistent _dbRefNamespaceString; + Persistent _dbRefDbString; + Persistent _dbRefOidString; + + Persistent _codeCodeString; + Persistent _codeScopeString; + Persistent _toBSONString; + + Local GetSerializeObject(const Handle& object); + + template friend class BSONSerializer; + friend class BSONDeserializer; +}; + +//=========================================================================== + +class CountStream +{ +public: + CountStream() : count(0) { } + + void WriteByte(int value) { ++count; } + void WriteByte(const Handle&, const Handle&) { ++count; } + void WriteBool(const Handle& value) { ++count; } + void WriteInt32(int32_t value) { count += 4; } + void WriteInt32(const Handle& value) { count += 4; } + void WriteInt32(const Handle& object, const Handle& key) { count += 4; } + void WriteInt64(int64_t value) { count += 8; } + void WriteInt64(const Handle& value) { count += 8; } + void WriteDouble(double value) { count += 8; } + void WriteDouble(const Handle& value) { count += 8; } + void WriteDouble(const Handle&, const Handle&) { count += 8; } + void WriteUInt32String(uint32_t name) { char buffer[32]; count += sprintf(buffer, "%u", name) + 1; } + void WriteLengthPrefixedString(const Local& value) { count += value->Utf8Length()+5; } + void WriteObjectId(const Handle& object, const Handle& key) { count += 12; } + void WriteString(const Local& value) { count += value->Utf8Length() + 1; } // This returns the number of bytes exclusive of the NULL terminator + void WriteData(const char* data, size_t length) { count += length; } + + void* BeginWriteType() { ++count; return NULL; } + void CommitType(void*, BsonType) { } + void* BeginWriteSize() { count += 4; return NULL; } + void CommitSize(void*) { } + + size_t GetSerializeSize() const { return count; } + + // Do nothing. CheckKey is implemented for DataStream + void CheckKey(const Local&) { } + +private: + size_t count; +}; + +class DataStream +{ +public: + DataStream(char* aDestinationBuffer) : destinationBuffer(aDestinationBuffer), p(aDestinationBuffer) { } + + void WriteByte(int value) { *p++ = value; } + void WriteByte(const Handle& object, const Handle& key) { *p++ = object->Get(key)->Int32Value(); } +#if USE_MISALIGNED_MEMORY_ACCESS + void WriteInt32(int32_t value) { *reinterpret_cast(p) = value; p += 4; } + void WriteInt64(int64_t value) { *reinterpret_cast(p) = value; p += 8; } + void WriteDouble(double value) { *reinterpret_cast(p) = value; p += 8; } +#else + void WriteInt32(int32_t value) { memcpy(p, &value, 4); p += 4; } + void WriteInt64(int64_t value) { memcpy(p, &value, 8); p += 8; } + void WriteDouble(double value) { memcpy(p, &value, 8); p += 8; } +#endif + void WriteBool(const Handle& value) { WriteByte(value->BooleanValue() ? 1 : 0); } + void WriteInt32(const Handle& value) { WriteInt32(value->Int32Value()); } + void WriteInt32(const Handle& object, const Handle& key) { WriteInt32(object->Get(key)); } + void WriteInt64(const Handle& value) { WriteInt64(value->IntegerValue()); } + void WriteDouble(const Handle& value) { WriteDouble(value->NumberValue()); } + void WriteDouble(const Handle& object, const Handle& key) { WriteDouble(object->Get(key)); } + void WriteUInt32String(uint32_t name) { p += sprintf(p, "%u", name) + 1; } + void WriteLengthPrefixedString(const Local& value) { WriteInt32(value->Utf8Length()+1); WriteString(value); } + void WriteObjectId(const Handle& object, const Handle& key); + void WriteString(const Local& value) { p += value->WriteUtf8(p); } // This returns the number of bytes inclusive of the NULL terminator. + void WriteData(const char* data, size_t length) { memcpy(p, data, length); p += length; } + + void* BeginWriteType() { void* returnValue = p; p++; return returnValue; } + void CommitType(void* beginPoint, BsonType value) { *reinterpret_cast(beginPoint) = value; } + void* BeginWriteSize() { void* returnValue = p; p += 4; return returnValue; } + +#if USE_MISALIGNED_MEMORY_ACCESS + void CommitSize(void* beginPoint) { *reinterpret_cast(beginPoint) = (int32_t) (p - (char*) beginPoint); } +#else + void CommitSize(void* beginPoint) { int32_t value = (int32_t) (p - (char*) beginPoint); memcpy(beginPoint, &value, 4); } +#endif + + size_t GetSerializeSize() const { return p - destinationBuffer; } + + void CheckKey(const Local& keyName); + +protected: + char *const destinationBuffer; // base, never changes + char* p; // cursor into buffer +}; + +template class BSONSerializer : public T +{ +private: + typedef T Inherited; + +public: + BSONSerializer(BSON* aBson, bool aCheckKeys, bool aSerializeFunctions) : Inherited(), checkKeys(aCheckKeys), serializeFunctions(aSerializeFunctions), bson(aBson) { } + BSONSerializer(BSON* aBson, bool aCheckKeys, bool aSerializeFunctions, char* parentParam) : Inherited(parentParam), checkKeys(aCheckKeys), serializeFunctions(aSerializeFunctions), bson(aBson) { } + + void SerializeDocument(const Handle& value); + void SerializeArray(const Handle& value); + void SerializeValue(void* typeLocation, const Handle& value); + +private: + bool checkKeys; + bool serializeFunctions; + BSON* bson; +}; + +//=========================================================================== + +class BSONDeserializer +{ +public: + BSONDeserializer(BSON* aBson, char* data, size_t length); + BSONDeserializer(BSONDeserializer& parentSerializer, size_t length); + + Handle DeserializeDocument(); + + bool HasMoreData() const { return p < pEnd; } + Local ReadCString(); + uint32_t ReadIntegerString(); + int32_t ReadRegexOptions(); + Local ReadString(); + Local ReadObjectId(); + + unsigned char ReadByte() { return *reinterpret_cast(p++); } +#if USE_MISALIGNED_MEMORY_ACCESS + int32_t ReadInt32() { int32_t returnValue = *reinterpret_cast(p); p += 4; return returnValue; } + uint32_t ReadUInt32() { uint32_t returnValue = *reinterpret_cast(p); p += 4; return returnValue; } + int64_t ReadInt64() { int64_t returnValue = *reinterpret_cast(p); p += 8; return returnValue; } + double ReadDouble() { double returnValue = *reinterpret_cast(p); p += 8; return returnValue; } +#else + int32_t ReadInt32() { int32_t returnValue; memcpy(&returnValue, p, 4); p += 4; return returnValue; } + uint32_t ReadUInt32() { uint32_t returnValue; memcpy(&returnValue, p, 4); p += 4; return returnValue; } + int64_t ReadInt64() { int64_t returnValue; memcpy(&returnValue, p, 8); p += 8; return returnValue; } + double ReadDouble() { double returnValue; memcpy(&returnValue, p, 8); p += 8; return returnValue; } +#endif + + size_t GetSerializeSize() const { return p - pStart; } + +private: + Handle DeserializeArray(); + Handle DeserializeValue(BsonType type); + Handle DeserializeDocumentInternal(); + Handle DeserializeArrayInternal(); + + BSON* bson; + char* const pStart; + char* p; + char* const pEnd; +}; + +//=========================================================================== + +#endif // BSON_H_ + +//=========================================================================== diff --git a/node_modules/mongoskin/node_modules/mongodb/node_modules/bson/package.json b/node_modules/mongoskin/node_modules/mongodb/node_modules/bson/package.json index 4b97fe3..fb36ede 100644 --- a/node_modules/mongoskin/node_modules/mongodb/node_modules/bson/package.json +++ b/node_modules/mongoskin/node_modules/mongodb/node_modules/bson/package.json @@ -48,11 +48,8 @@ "dependencies": {}, "optionalDependencies": {}, "_engineSupported": true, - "_npmVersion": "1.1.21", - "_nodeVersion": "v0.6.18", + "_npmVersion": "1.1.24", + "_nodeVersion": "v0.6.19", "_defaultsLoaded": true, - "dist": { - "shasum": "d6954ee604c768436a1956ad487ea2bd571910da" - }, "_from": "bson@0.1.5" } diff --git a/node_modules/mongoskin/node_modules/mongodb/package.json b/node_modules/mongoskin/node_modules/mongodb/package.json old mode 100644 new mode 100755 index 528784a..9f45c06 --- a/node_modules/mongoskin/node_modules/mongodb/package.json +++ b/node_modules/mongoskin/node_modules/mongodb/package.json @@ -7,7 +7,7 @@ "driver", "db" ], - "version": "1.1.11", + "version": "1.2.8", "author": { "name": "Christian Amor Kvalheim", "email": "christkv@gmail.com" @@ -165,6 +165,9 @@ }, { "name": "Roman Shtylman" + }, + { + "name": "Matt Self" } ], "repository": { @@ -193,6 +196,7 @@ "native": false }, "main": "./lib/mongodb/index", + "homepage": "http://mongodb.github.com/node-mongodb-native/", "directories": { "lib": "./lib/mongodb" }, @@ -208,14 +212,11 @@ "url": "http://www.apache.org/licenses/LICENSE-2.0" } ], - "_id": "mongodb@1.1.11", + "_id": "mongodb@1.2.8", "optionalDependencies": {}, "_engineSupported": true, - "_npmVersion": "1.1.21", - "_nodeVersion": "v0.6.18", + "_npmVersion": "1.1.24", + "_nodeVersion": "v0.6.19", "_defaultsLoaded": true, - "dist": { - "shasum": "51f813dad01721d585f3588f4233920d23d5169e" - }, - "_from": "mongodb@< 1.2.0" + "_from": "mongodb@1.2.x" } diff --git a/node_modules/mongoskin/package.json b/node_modules/mongoskin/package.json index cf1d326..983150c 100644 --- a/node_modules/mongoskin/package.json +++ b/node_modules/mongoskin/package.json @@ -1,7 +1,7 @@ { "name": "mongoskin", "description": "The future layer above node-mongodb-native", - "version": "0.4.4", + "version": "0.5.0", "author": { "name": "Gui Lin", "email": "guileen@gmail.com" @@ -24,21 +24,22 @@ "node": ">= 0.4.0" }, "dependencies": { - "mongodb": "< 1.2.0" + "mongodb": "1.2.x" }, "devDependencies": { "mocha": "*", + "jscover": "*", "should": "*" }, "scripts": { - "test": "make test-version" + "test": "make test" }, "directories": { "example": "./examples", "lib": "./lib/mongoskin" }, "license": "MIT", - "_id": "mongoskin@0.4.4", + "_id": "mongoskin@0.5.0", "contributors": [ { "name": "Gui Lin", @@ -103,11 +104,8 @@ ], "optionalDependencies": {}, "_engineSupported": true, - "_npmVersion": "1.1.21", - "_nodeVersion": "v0.6.18", + "_npmVersion": "1.1.24", + "_nodeVersion": "v0.6.19", "_defaultsLoaded": true, - "dist": { - "shasum": "dec69b0cbcbcb30f3327e1fc26f6f29ad35781bc" - }, "_from": "mongoskin@>= 0.0.1" }