Comparing version 1.4.0 to 1.5.0
@@ -34,3 +34,3 @@ Virtual attributes | ||
.get( function () { | ||
return this.name.first + this.name.last; | ||
return this.name.first + ' ' + this.name.last; | ||
}); | ||
@@ -45,3 +45,3 @@ | ||
function () { | ||
return this.name.first + this.name.last; | ||
return this.name.first + ' ' + this.name.last; | ||
} | ||
@@ -59,3 +59,3 @@ | ||
.get( function () { | ||
return this.name.first + this.name.last; | ||
return this.name.first + ' ' + this.name.last; | ||
}) | ||
@@ -62,0 +62,0 @@ .set( function (setFullNameTo) { |
1.5.0 / 2011-06-27 | ||
=================== | ||
* changed; saving without a callback no longer ignores the error (@bnoguchi) | ||
* changed; hook-js version bump to 0.1.9 | ||
* changed; node-mongodb-native version bumped to 0.9.6.1 - When .remove() doesn't | ||
return an error, null is no longer passed. | ||
* fixed; two memory leaks (@justmoon) | ||
* added; sparse index support | ||
* added; more ObjectId conditionals (gt, lt, gte, lte) (@phillyqueso) | ||
* added; options are now passed in model#remote (@JerryLuke) | ||
1.4.0 / 2011-06-10 | ||
@@ -3,0 +15,0 @@ =================== |
@@ -79,2 +79,3 @@ | ||
} | ||
this.queue = []; | ||
return this; | ||
@@ -81,0 +82,0 @@ }; |
@@ -648,2 +648,4 @@ | ||
} | ||
}, function (err) { | ||
this.emit('error', err); | ||
}).pre('save', function validation (next) { | ||
@@ -650,0 +652,0 @@ return self.validate.call(self, next); |
@@ -96,20 +96,3 @@ | ||
if (!this.buffer) { | ||
// if the spec is an array, use -native (old way) | ||
if (fields.constructor != Object) | ||
return oldEnsureIndex.apply(this, arguments); | ||
fn = fn || noop; | ||
// transform fields dict into lame array | ||
var fieldsArr = []; | ||
for (var i in fields) | ||
fieldsArr.push([i, fields[i]]); | ||
if (options && Object.keys(options).length){ | ||
if (options.unique) | ||
return oldEnsureIndex.call(this, fieldsArr, true, fn); | ||
else if (fn != noop) | ||
fn(new Error('This driver only implements unique indexes')); | ||
} else | ||
oldEnsureIndex.call(this, fieldsArr, fn); | ||
return oldEnsureIndex.apply(this, arguments); | ||
} | ||
@@ -116,0 +99,0 @@ }; |
@@ -292,3 +292,3 @@ | ||
exports.version = '1.4.0'; | ||
exports.version = '1.5.0'; | ||
@@ -295,0 +295,0 @@ /** |
@@ -66,2 +66,12 @@ | ||
function makeSaveHandler(promise, self) { | ||
return function (err) { | ||
if (err) return promise.error(err); | ||
promise.complete(self); | ||
self.emit('save', self); | ||
promise = null; | ||
self = null; | ||
}; | ||
}; | ||
Model.prototype.save = function (fn) { | ||
@@ -72,7 +82,3 @@ var promise = new Promise(fn) | ||
function complete (err) { | ||
if (err) return promise.error(err); | ||
promise.complete(self); | ||
self.emit('save', self); | ||
}; | ||
var complete = makeSaveHandler(promise, self); | ||
@@ -255,2 +261,4 @@ // support for safe mode | ||
}); | ||
}, function (err) { | ||
this.emit('error', err); | ||
}); | ||
@@ -301,3 +309,4 @@ | ||
indexes.forEach(function (index) { | ||
self.collection.ensureIndex(index[0], index[1], function(){ | ||
self.collection.ensureIndex(index[0], index[1], function (err) { | ||
if (err) return self.emit(err); | ||
--count || self.emit('index'); | ||
@@ -304,0 +313,0 @@ }); |
@@ -881,6 +881,8 @@ var utils = require('./utils') | ||
this.op = 'remove'; | ||
var model = this.model; | ||
var model = this.model | ||
, options = this._optionsForExec(model); | ||
this.cast(model); | ||
var castQuery = this._conditions; | ||
model.collection.remove(castQuery, callback); | ||
model.collection.remove(castQuery, options, callback); | ||
return this; | ||
@@ -887,0 +889,0 @@ }; |
@@ -1,2 +0,1 @@ | ||
/** | ||
@@ -76,2 +75,6 @@ * Module dependencies. | ||
, '$nin': handleArray | ||
, '$gt': handleSingle | ||
, '$lt': handleSingle | ||
, '$gte': handleSingle | ||
, '$lte': handleSingle | ||
}; | ||
@@ -78,0 +81,0 @@ ObjectId.prototype.castForQuery = function ($conditional, val) { |
{ | ||
"name": "mongoose" | ||
, "description": "Mongoose MongoDB ORM" | ||
, "version": "1.4.0" | ||
, "version": "1.5.0" | ||
, "author": "Guillermo Rauch <guillermo@learnboost.com>" | ||
, "keywords": ["mongodb", "mongoose", "orm", "data", "datastore", "nosql"] | ||
, "dependencies": { | ||
"hooks": "0.1.7" | ||
"hooks": "0.1.9" | ||
} | ||
, "devDependencies": { | ||
"should": ">=0.2.1" | ||
"should": "0.2.1" | ||
} | ||
@@ -13,0 +13,0 @@ , "directories": { "lib": "./lib/mongoose" } |
216
README.md
@@ -11,21 +11,23 @@ Mongoose 1.0 | ||
var Comments = new Schema({ | ||
title : String | ||
, body : String | ||
, date : Date | ||
}); | ||
```javascript | ||
var Comments = new Schema({ | ||
title : String | ||
, body : String | ||
, date : Date | ||
}); | ||
var BlogPost = new Schema({ | ||
author : ObjectId | ||
, title : String | ||
, body : String | ||
, date : Date | ||
, comments : [Comments] | ||
, meta : { | ||
votes : Number | ||
, favs : Number | ||
} | ||
}); | ||
var BlogPost = new Schema({ | ||
author : ObjectId | ||
, title : String | ||
, body : String | ||
, date : Date | ||
, comments : [Comments] | ||
, meta : { | ||
votes : Number | ||
, favs : Number | ||
} | ||
}); | ||
mongoose.model('BlogPost', BlogPost); | ||
mongoose.model('BlogPost', BlogPost); | ||
``` | ||
@@ -36,15 +38,22 @@ ## Installation | ||
$ npm install mongoose | ||
```bash | ||
$ npm install mongoose | ||
``` | ||
Otherwise, you can check it in your repository and then expose it: | ||
$ git clone git@github.com:LearnBoost/mongoose.git support/mongoose/ | ||
```bash | ||
$ git clone git@github.com:LearnBoost/mongoose.git support/mongoose/ | ||
``` | ||
```javascript | ||
// in your code | ||
require.paths.unshift('support/mongoose/lib') | ||
``` | ||
// in your code | ||
require.paths.unshift('support/mongoose/lib') | ||
Then you can `require` it: | ||
Then you can require it: | ||
```javascript | ||
require('mongoose') | ||
``` | ||
require('mongoose') | ||
## Connecting to MongoDB | ||
@@ -59,5 +68,7 @@ | ||
var mongoose = require('mongoose'); | ||
```javascript | ||
var mongoose = require('mongoose'); | ||
mongoose.connect('mongodb://localhost/my_database'); | ||
mongoose.connect('mongodb://localhost/my_database'); | ||
``` | ||
@@ -76,11 +87,13 @@ Once connected, the `open` event is fired on the `Connection` instance. If | ||
var Schema = mongoose.Schema | ||
, ObjectId = Schema.ObjectId; | ||
```javascript | ||
var Schema = mongoose.Schema | ||
, ObjectId = Schema.ObjectId; | ||
var BlogPost = new Schema({ | ||
author : ObjectId | ||
, title : String | ||
, body : String | ||
, date : Date | ||
}); | ||
var BlogPost = new Schema({ | ||
author : ObjectId | ||
, title : String | ||
, body : String | ||
, date : Date | ||
}); | ||
``` | ||
@@ -102,19 +115,21 @@ Aside from defining the structure of your documents and the types of data you're | ||
var Comment = new Schema({ | ||
name : { type: String, default: 'hahaha' } | ||
, age : { type: Number, min: 18, index: true } | ||
, bio : { type: String, match: /[a-z]/ } | ||
, date : { type: Date, default: Date.now } | ||
}); | ||
```javascript | ||
var Comment = new Schema({ | ||
name : { type: String, default: 'hahaha' } | ||
, age : { type: Number, min: 18, index: true } | ||
, bio : { type: String, match: /[a-z]/ } | ||
, date : { type: Date, default: Date.now } | ||
}); | ||
// a setter | ||
Comment.path('name').set(function (v) { | ||
return v.capitalize(); | ||
}); | ||
// a setter | ||
Comment.path('name').set(function (v) { | ||
return v.capitalize(); | ||
}); | ||
// middleware | ||
Comment.pre('save', function (next) { | ||
notify(this.get('email')); | ||
next(); | ||
}); | ||
// middleware | ||
Comment.pre('save', function (next) { | ||
notify(this.get('email')); | ||
next(); | ||
}); | ||
``` | ||
@@ -129,17 +144,23 @@ Take a look at the example in `examples/schema.js` for an end-to-end example of | ||
var myModel = mongoose.model('ModelName'); | ||
```javascript | ||
var myModel = mongoose.model('ModelName'); | ||
``` | ||
We can then instantiate it, and save it: | ||
var instance = new myModel(); | ||
instance.my.key = 'hello'; | ||
instance.save(function (err) { | ||
// | ||
}); | ||
```javascript | ||
var instance = new myModel(); | ||
instance.my.key = 'hello'; | ||
instance.save(function (err) { | ||
// | ||
}); | ||
``` | ||
Or we can find documents from the same collection | ||
myModel.find({}, function (err, docs) { | ||
// docs.forEach | ||
}); | ||
```javascript | ||
myModel.find({}, function (err, docs) { | ||
// docs.forEach | ||
}); | ||
``` | ||
@@ -153,3 +174,5 @@ You can also `findOne`, `findById`, `update`, etc. For more details check out | ||
comments: [Comments] | ||
``` | ||
comments: [Comments] | ||
``` | ||
@@ -159,25 +182,29 @@ Where `Comments` is a `Schema` we created. This means that creating embedded | ||
// retrieve my model | ||
var BlogPost = mongoose.model('BlogPost'); | ||
```javascript | ||
// retrieve my model | ||
var BlogPost = mongoose.model('BlogPost'); | ||
// create a blog post | ||
var post = new BlogPost(); | ||
// create a blog post | ||
var post = new BlogPost(); | ||
// create a comment | ||
post.comments.push({ title: 'My comment' }); | ||
// create a comment | ||
post.comments.push({ title: 'My comment' }); | ||
post.save(function (err) { | ||
if (!err) console.log('Success!'); | ||
}); | ||
post.save(function (err) { | ||
if (!err) console.log('Success!'); | ||
}); | ||
``` | ||
The same goes for removing them: | ||
BlogPost.findById(myId, function (err, post) { | ||
if (!err) { | ||
post.comments[0].remove(); | ||
post.save(function (err) { | ||
// do something | ||
}); | ||
} | ||
```javascript | ||
BlogPost.findById(myId, function (err, post) { | ||
if (!err) { | ||
post.comments[0].remove(); | ||
post.save(function (err) { | ||
// do something | ||
}); | ||
} | ||
}); | ||
``` | ||
@@ -205,5 +232,7 @@ Embedded documents enjoy all the same features as your models. Defaults, | ||
.pre(method, function (next, methodArg1, methodArg2, ...) { | ||
// ... | ||
}) | ||
```javascript | ||
.pre(method, function (next, methodArg1, methodArg2, ...) { | ||
// ... | ||
}) | ||
``` | ||
@@ -219,8 +248,10 @@ They're executed one after the other, when each middleware calls `next`. | ||
Parallel middleware offer more fine-grained flow control, and are defined | ||
like | ||
like: | ||
```javascript | ||
.pre(method, true, function (next, done, methodArg1, methodArg2) { | ||
// ... | ||
}) | ||
``` | ||
.pre(method, true, function (next, done, methodArg1, methodArg2) { | ||
// ... | ||
}) | ||
Parallel middleware can `next()` immediately, but the final argument will be | ||
@@ -236,12 +267,14 @@ called when all the parallel middleware have called `done()`. | ||
schema.pre('save', function (next) { | ||
// something goes wrong | ||
next(new Error('something went wrong')); | ||
}); | ||
```javascript | ||
schema.pre('save', function (next) { | ||
// something goes wrong | ||
next(new Error('something went wrong')); | ||
}); | ||
// later... | ||
// later... | ||
myModel.save(function (err) { | ||
// err can come from a middleware | ||
}); | ||
myModel.save(function (err) { | ||
// err can come from a middleware | ||
}); | ||
``` | ||
@@ -310,2 +343,3 @@ ### Intercepting and mutating method arguments | ||
- [mongoose-dbref](https://github.com/goulash1971/mongoose-dbref) - Adds DBRef support | ||
- [mongoose-joins](https://github.com/goulash1971/mongoose-joins) - Adds simple join support | ||
@@ -312,0 +346,0 @@ ## Contributing to Mongoose |
@@ -10,3 +10,3 @@ require.paths.unshift('../lib'); | ||
GridStore = require('mongodb').GridStore, | ||
sys = require('sys'); | ||
sys = require('util'); | ||
@@ -13,0 +13,0 @@ var simulated_buffer = new Buffer(1024*1000*10).toString(); |
@@ -8,6 +8,7 @@ require.paths.unshift('../lib'); | ||
Collection = require('mongodb').Collection, | ||
sys = require('sys'); | ||
sys = require('util'), | ||
debug = require('util').debug; | ||
var BSON = require('bson'); | ||
var db = new Db('streaming_benchmark', new Server("127.0.0.1", 27017, {auto_reconnect: true}), {}) | ||
var db = new Db('streaming_benchmark', new Server("127.0.0.1", 27017, {auto_reconnect: true, poolSize:4}), {}) | ||
// Set native deserializer | ||
@@ -25,4 +26,4 @@ db.bson_deserializer = BSON; | ||
// Add documents | ||
for(var i = 0; i < 100000; i++) { | ||
// for(var i = 0; i < 1000; i++) { | ||
// for(var i = 0; i < 100000; i++) { | ||
for(var i = 0; i < 10000; i++) { | ||
collection.save({'i':i, 'a':i, 'c':i, 'd':{'i':i}}, function(err, result){}); | ||
@@ -43,3 +44,3 @@ } | ||
count++; | ||
if ((count%10000)==0) sys.puts("recs:" + count + " :: " + | ||
if ((count%1000)==0) sys.puts("recs:" + count + " :: " + | ||
((new Date().getTime() - started_at)/1000) + "seconds"); | ||
@@ -46,0 +47,0 @@ }); |
GLOBAL.DEBUG = true; | ||
sys = require("sys"); | ||
debug = require('util').debug, | ||
inspect = require('util').inspect, | ||
test = require("assert"); | ||
@@ -23,3 +25,3 @@ | ||
// Erase all records in collection | ||
collection.remove(function(err, collection) { | ||
collection.remove({}, function(err, r) { | ||
db.admin(function(err, admin) { | ||
@@ -39,3 +41,2 @@ | ||
cursor.toArray(function(err, items) { | ||
// Stop profiling | ||
@@ -47,5 +48,5 @@ admin.setProfilingLevel('off', function(err, level) { | ||
// Validate returns a hash if all is well or return an error has if there is a | ||
// Validate returns a hash if all is well or return an error hash if there is a | ||
// problem. | ||
admin.validatCollection(collection.collectionName, function(err, result) { | ||
admin.validateCollection(collection.collectionName, function(err, result) { | ||
sys.puts(result.result); | ||
@@ -52,0 +53,0 @@ db.close(); |
GLOBAL.DEBUG = true; | ||
sys = require("sys"); | ||
sys = require("sys"), | ||
debug = require('util').debug, | ||
inspect = require('util').inspect, | ||
test = require("assert"); | ||
@@ -9,3 +11,2 @@ | ||
Server = require('../lib/mongodb').Server, | ||
// BSON = require('../lib/mongodb').BSONPure; | ||
BSON = require('../lib/mongodb').BSONNative; | ||
@@ -20,5 +21,5 @@ | ||
db.dropDatabase(function(err, result) { | ||
db.collection('test', function(err, collection) { | ||
db.collection('test', function(err, collection) { | ||
// Erase all records from the collection, if any | ||
collection.remove(function(err, collection) { | ||
collection.remove({}, function(err, result) { | ||
// Insert 3 records | ||
@@ -25,0 +26,0 @@ for(var i = 0; i < 3; i++) { |
require.paths.unshift("../../lib"); | ||
var sys = require('sys'), | ||
var sys = require('util'), | ||
debug = require('util').debug, | ||
inspect = require('util').inspect, | ||
Buffer = require('buffer').Buffer, | ||
@@ -22,196 +24,212 @@ BSON = require('./bson').BSON, | ||
sys.puts("=== EXCEUTING TEST_BSON ==="); | ||
sys.puts("=== EXECUTING TEST_BSON ==="); | ||
// Long data type tests | ||
var l2_string = Long2.fromNumber(100); | ||
var l_string = Long.fromNumber(100); | ||
assert.equal(l_string.toNumber(), l2_string.toNumber()); | ||
// // Long data type tests | ||
// var l2_string = Long2.fromNumber(100); | ||
// var l_string = Long.fromNumber(100); | ||
// assert.equal(l_string.toNumber(), l2_string.toNumber()); | ||
// | ||
// var l2_string = Long2.fromNumber(9223372036854775807).toString(); | ||
// var l_string = Long.fromNumber(9223372036854775807).toString(); | ||
// assert.equal(l_string, l2_string); | ||
// | ||
// l2_string = Long2.fromNumber(9223372036800).toString(); | ||
// l_string = Long.fromNumber(9223372036800).toString(); | ||
// assert.equal(l_string, l2_string); | ||
// | ||
// l2_string = Long2.fromNumber(2355).toString(); | ||
// l_string = Long.fromNumber(2355).toString(); | ||
// assert.equal(l_string, l2_string); | ||
// | ||
// l_string = Long.fromNumber(-9223372036854775807).toString(); | ||
// l2_string = Long2.fromNumber(-9223372036854775807).toString(); | ||
// assert.equal(l_string, l2_string); | ||
// | ||
// l2_string = Long2.fromNumber(-2355).toString(); | ||
// l_string = Long.fromNumber(-2355).toString(); | ||
// assert.equal(l_string, l2_string); | ||
// | ||
// l2_string = Long2.fromNumber(-1).toString(); | ||
// l_string = Long.fromNumber(-1).toString(); | ||
// assert.equal(l_string, l2_string); | ||
// | ||
// l2_string = Long2.fromNumber(1).toString(); | ||
// l_string = Long.fromNumber(1).toString(); | ||
// assert.equal(l_string, l2_string); | ||
// | ||
// var a = Long2.fromNumber(10); | ||
// assert.equal(10, a); | ||
// | ||
// var a = Long2.fromNumber(9223372036854775807); | ||
// assert.equal(9223372036854775807, a); | ||
// | ||
// // Simple serialization and deserialization test for a Single String value | ||
// var simple_string_serialized = BSON.serialize({doc:'Serialize'}); | ||
// assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(new Buffer(simple_string_serialized, 'binary'))); | ||
// assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(simple_string_serialized)); | ||
// | ||
// // Simple integer serialization/deserialization test, including testing boundary conditions | ||
// var simple_string_serialized = BSON.serialize({doc:-1}); | ||
// assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(new Buffer(simple_string_serialized, 'binary'))); | ||
// assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(simple_string_serialized)); | ||
// | ||
// var simple_string_serialized = BSON.serialize({doc:2147483648}); | ||
// assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(new Buffer(simple_string_serialized, 'binary'))); | ||
// assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(simple_string_serialized)); | ||
// | ||
// var simple_string_serialized = BSON.serialize({doc:-2147483648}); | ||
// assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(new Buffer(simple_string_serialized, 'binary'))); | ||
// assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(simple_string_serialized)); | ||
// | ||
// // Simple serialization and deserialization test for a Long value | ||
// var simple_string_serialized = BSON.serialize({doc:Long2.fromNumber(9223372036854775807)}); | ||
// assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(new Buffer(simple_string_serialized, 'binary'))); | ||
// assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(simple_string_serialized)); | ||
// | ||
// var simple_string_serialized = BSON.serialize({doc:Long2.fromNumber(-9223372036854775807)}); | ||
// assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(new Buffer(simple_string_serialized, 'binary'))); | ||
// assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(simple_string_serialized)); | ||
// | ||
// // Simple serialization and deserialization for a Float value | ||
// var simple_string_serialized = BSON.serialize({doc:2222.3333}); | ||
// assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(new Buffer(simple_string_serialized, 'binary'))); | ||
// assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(simple_string_serialized)); | ||
// | ||
// var simple_string_serialized = BSON.serialize({doc:-2222.3333}); | ||
// assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(new Buffer(simple_string_serialized, 'binary'))); | ||
// assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(simple_string_serialized)); | ||
// | ||
// // Simple serialization and deserialization for a null value | ||
// var simple_string_serialized = BSON.serialize({doc:null}); | ||
// assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(new Buffer(simple_string_serialized, 'binary'))); | ||
// assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(simple_string_serialized)); | ||
// | ||
// // Simple serialization and deserialization for a boolean value | ||
// var simple_string_serialized = BSON.serialize({doc:true}); | ||
// assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(new Buffer(simple_string_serialized, 'binary'))); | ||
// assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(simple_string_serialized)); | ||
// | ||
// // Simple serialization and deserialization for a date value | ||
// var date = new Date(); | ||
// var simple_string_serialized = BSON.serialize({doc:date}); | ||
// assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(new Buffer(simple_string_serialized, 'binary'))); | ||
// assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(simple_string_serialized)); | ||
// | ||
// // Simple serialization and deserialization for a boolean value | ||
// var simple_string_serialized = BSON.serialize({doc:/abcd/mi}); | ||
// assert.equal(BSONJS.deserialize(simple_string_serialized).doc.toString(), BSON.deserialize(simple_string_serialized).doc.toString()); | ||
// assert.equal(BSONJS.deserialize(simple_string_serialized).doc.toString(), BSON.deserialize(new Buffer(simple_string_serialized, 'binary')).doc.toString()); | ||
// | ||
// var simple_string_serialized = BSON.serialize({doc:/abcd/}); | ||
// assert.equal(BSONJS.deserialize(simple_string_serialized).doc.toString(), BSON.deserialize(simple_string_serialized).doc.toString()); | ||
// assert.equal(BSONJS.deserialize(simple_string_serialized).doc.toString(), BSON.deserialize(new Buffer(simple_string_serialized, 'binary')).doc.toString()); | ||
// | ||
// // Simple serialization and deserialization for a objectId value | ||
// var simple_string_serialized = BSON.serialize({doc:new ObjectID2()}); | ||
// assert.deepEqual(BSONJS.deserialize(simple_string_serialized).doc.toString(), BSON.deserialize(new Buffer(simple_string_serialized, 'binary')).doc.toString()); | ||
// assert.deepEqual(BSONJS.deserialize(simple_string_serialized).doc.toString(), BSON.deserialize(simple_string_serialized).doc.toString()); | ||
// | ||
// // // Simple serialization and deserialization for a Binary value | ||
// var binary = new Binary2(); | ||
// var string = 'binstring' | ||
// for(var index = 0; index < string.length; index++) { binary.put(string.charAt(index)); } | ||
// var simple_string_serialized = BSON.serialize({doc:binary}); | ||
// assert.deepEqual(BSONJS.deserialize(simple_string_serialized).doc.value(), BSON.deserialize(new Buffer(simple_string_serialized, 'binary')).doc.value()); | ||
// assert.deepEqual(BSONJS.deserialize(simple_string_serialized).doc.value(), BSON.deserialize(simple_string_serialized).doc.value()); | ||
// | ||
// // Simple serialization and deserialization for a Code value | ||
// var code = new Code2('this.a > i', {'i': 1}); | ||
// var code2 = new Code('this.a > i', {'i': 1}); | ||
// var simple_string_serialized_2 = BSONJS.serialize({doc:code2}); | ||
// var simple_string_serialized = BSON.serialize({doc:code}); | ||
// assert.deepEqual(BSONJS.deserialize(simple_string_serialized_2).doc.scope, BSON.deserialize(new Buffer(simple_string_serialized, 'binary')).doc.scope); | ||
// assert.deepEqual(BSONJS.deserialize(simple_string_serialized_2).doc.code, BSON.deserialize(simple_string_serialized).doc.code); | ||
// | ||
// // Simple serialization and deserialization for an Object | ||
// var simple_string_serialized = BSON.serialize({doc:{a:1, b:{c:2}}}); | ||
// var simple_string_serialized_2 = BSONJS.serialize({doc:{a:1, b:{c:2}}}); | ||
// assert.deepEqual(BSONJS.deserialize(simple_string_serialized_2).doc, BSON.deserialize(new Buffer(simple_string_serialized, 'binary')).doc); | ||
// assert.deepEqual(BSONJS.deserialize(simple_string_serialized_2).doc, BSON.deserialize(simple_string_serialized).doc); | ||
// | ||
// // Simple serialization and deserialization for an Array | ||
// var simple_string_serialized = BSON.serialize({doc:[9, 9, 1, 2, 3, 1, 1, 1, 1, 1, 1, 1]}); | ||
// var simple_string_serialized_2 = BSONJS.serialize({doc:[9, 9, 1, 2, 3, 1, 1, 1, 1, 1, 1, 1]}); | ||
// assert.deepEqual(BSONJS.deserialize(simple_string_serialized_2).doc, BSON.deserialize(new Buffer(simple_string_serialized, 'binary')).doc); | ||
// assert.deepEqual(BSONJS.deserialize(simple_string_serialized_2).doc, BSON.deserialize(simple_string_serialized).doc); | ||
// | ||
// // Simple serialization and deserialization for a DBRef | ||
// var oid = new ObjectID2() | ||
// var simple_string_serialized = BSONJS.serialize({doc:new DBRef('namespace', oid, 'integration_tests_')}); | ||
// var simple_string_serialized_2 = BSON.serialize({doc:new DBRef2('namespace', oid, 'integration_tests_')}); | ||
// | ||
// // Ensure we have the same values for the dbref | ||
// var object_js = BSONJS.deserialize(simple_string_serialized_2); | ||
// var object_c = BSON.deserialize(simple_string_serialized); | ||
// assert.equal(object_js.doc.namespace, object_c.doc.namespace); | ||
// assert.equal(object_js.doc.oid.toHexString(), object_c.doc.oid.toHexString()); | ||
// assert.equal(object_js.doc.db, object_c.doc.db); | ||
// | ||
// // Serialized document | ||
// var bytes = [47,0,0,0,2,110,97,109,101,0,6,0,0,0,80,97,116,116,121,0,16,97,103,101,0,34,0,0,0,7,95,105,100,0,76,100,12,23,11,30,39,8,89,0,0,1,0]; | ||
// var serialized_data = ''; | ||
// // Convert to chars | ||
// for(var i = 0; i < bytes.length; i++) { | ||
// serialized_data = serialized_data + BinaryParser.fromByte(bytes[i]); | ||
// } | ||
// var object = BSON.deserialize(serialized_data); | ||
// assert.equal('Patty', object.name) | ||
// assert.equal(34, object.age) | ||
// assert.equal('4c640c170b1e270859000001', object._id.toHexString()) | ||
// | ||
// // Serialize utf8 | ||
// var doc = { "name" : "本荘由利地域に洪水警報", "name1" : "öüóőúéáűíÖÜÓŐÚÉÁŰÍ", "name2" : "abcdedede"}; | ||
// var simple_string_serialized = BSON.serialize(doc); | ||
// var object = BSON.deserialize(simple_string_serialized); | ||
// assert.equal(doc.name, object.name) | ||
// assert.equal(doc.name1, object.name1) | ||
// assert.equal(doc.name2, object.name2) | ||
// | ||
// // Serialize object with array | ||
// var doc = {b:[1, 2, 3]}; | ||
// var simple_string_serialized = BSON.serialize(doc); | ||
// var simple_string_serialized_2 = BSONJS.serialize(doc); | ||
// var object = BSON.deserialize(simple_string_serialized); | ||
// assert.deepEqual(doc, object) | ||
// | ||
// // Test equality of an object ID | ||
// var object_id = new ObjectID2(); | ||
// var object_id_2 = new ObjectID2(); | ||
// assert.ok(object_id.equals(object_id)); | ||
// assert.ok(!(object_id.equals(object_id_2))) | ||
var l2_string = Long2.fromNumber(9223372036854775807).toString(); | ||
var l_string = Long.fromNumber(9223372036854775807).toString(); | ||
assert.equal(l_string, l2_string); | ||
// Test same serialization for Object ID | ||
var object_id = new ObjectID(); | ||
var object_id2 = ObjectID2.createFromHexString(object_id.toString()) | ||
l2_string = Long2.fromNumber(9223372036800).toString(); | ||
l_string = Long.fromNumber(9223372036800).toString(); | ||
assert.equal(l_string, l2_string); | ||
var simple_string_serialized = BSONJS.serialize({doc:object_id}); | ||
var simple_string_serialized_2 = BSON.serialize({doc:object_id2}); | ||
l2_string = Long2.fromNumber(2355).toString(); | ||
l_string = Long.fromNumber(2355).toString(); | ||
assert.equal(l_string, l2_string); | ||
// assert.deepEqual(simple_string_serialized_2.toString(), simple_string_serialized_2.toString()); | ||
assert.equal(simple_string_serialized_2.length, simple_string_serialized.length); | ||
assert.deepEqual(simple_string_serialized, simple_string_serialized_2) | ||
var object = BSONJS.deserialize(simple_string_serialized_2); | ||
var object2 = BSON.deserialize(simple_string_serialized); | ||
assert.deepEqual(object, object2); | ||
l_string = Long.fromNumber(-9223372036854775807).toString(); | ||
l2_string = Long2.fromNumber(-9223372036854775807).toString(); | ||
assert.equal(l_string, l2_string); | ||
// Force garbage collect | ||
global.gc(); | ||
l2_string = Long2.fromNumber(-2355).toString(); | ||
l_string = Long.fromNumber(-2355).toString(); | ||
assert.equal(l_string, l2_string); | ||
l2_string = Long2.fromNumber(-1).toString(); | ||
l_string = Long.fromNumber(-1).toString(); | ||
assert.equal(l_string, l2_string); | ||
l2_string = Long2.fromNumber(1).toString(); | ||
l_string = Long.fromNumber(1).toString(); | ||
assert.equal(l_string, l2_string); | ||
var a = Long2.fromNumber(10); | ||
assert.equal(10, a); | ||
var a = Long2.fromNumber(9223372036854775807); | ||
assert.equal(9223372036854775807, a); | ||
// Simple serialization and deserialization test for a Single String value | ||
var simple_string_serialized = BSON.serialize({doc:'Serialize'}); | ||
assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(new Buffer(simple_string_serialized, 'binary'))); | ||
assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(simple_string_serialized)); | ||
// Simple integer serialization/deserialization test, including testing boundary conditions | ||
var simple_string_serialized = BSON.serialize({doc:-1}); | ||
assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(new Buffer(simple_string_serialized, 'binary'))); | ||
assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(simple_string_serialized)); | ||
var simple_string_serialized = BSON.serialize({doc:2147483648}); | ||
assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(new Buffer(simple_string_serialized, 'binary'))); | ||
assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(simple_string_serialized)); | ||
var simple_string_serialized = BSON.serialize({doc:-2147483648}); | ||
assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(new Buffer(simple_string_serialized, 'binary'))); | ||
assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(simple_string_serialized)); | ||
// Simple serialization and deserialization test for a Long value | ||
var simple_string_serialized = BSON.serialize({doc:Long2.fromNumber(9223372036854775807)}); | ||
assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(new Buffer(simple_string_serialized, 'binary'))); | ||
assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(simple_string_serialized)); | ||
var simple_string_serialized = BSON.serialize({doc:Long2.fromNumber(-9223372036854775807)}); | ||
assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(new Buffer(simple_string_serialized, 'binary'))); | ||
assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(simple_string_serialized)); | ||
// Simple serialization and deserialization for a Float value | ||
var simple_string_serialized = BSON.serialize({doc:2222.3333}); | ||
assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(new Buffer(simple_string_serialized, 'binary'))); | ||
assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(simple_string_serialized)); | ||
var simple_string_serialized = BSON.serialize({doc:-2222.3333}); | ||
assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(new Buffer(simple_string_serialized, 'binary'))); | ||
assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(simple_string_serialized)); | ||
// Simple serialization and deserialization for a null value | ||
var simple_string_serialized = BSON.serialize({doc:null}); | ||
assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(new Buffer(simple_string_serialized, 'binary'))); | ||
assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(simple_string_serialized)); | ||
// Simple serialization and deserialization for a boolean value | ||
var simple_string_serialized = BSON.serialize({doc:true}); | ||
assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(new Buffer(simple_string_serialized, 'binary'))); | ||
assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(simple_string_serialized)); | ||
// Simple serialization and deserialization for a date value | ||
var date = new Date(); | ||
var simple_string_serialized = BSON.serialize({doc:date}); | ||
assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(new Buffer(simple_string_serialized, 'binary'))); | ||
assert.deepEqual(BSONJS.deserialize(simple_string_serialized), BSON.deserialize(simple_string_serialized)); | ||
// Simple serialization and deserialization for a boolean value | ||
var simple_string_serialized = BSON.serialize({doc:/abcd/mi}); | ||
assert.equal(BSONJS.deserialize(simple_string_serialized).doc.toString(), BSON.deserialize(simple_string_serialized).doc.toString()); | ||
assert.equal(BSONJS.deserialize(simple_string_serialized).doc.toString(), BSON.deserialize(new Buffer(simple_string_serialized, 'binary')).doc.toString()); | ||
var simple_string_serialized = BSON.serialize({doc:/abcd/}); | ||
assert.equal(BSONJS.deserialize(simple_string_serialized).doc.toString(), BSON.deserialize(simple_string_serialized).doc.toString()); | ||
assert.equal(BSONJS.deserialize(simple_string_serialized).doc.toString(), BSON.deserialize(new Buffer(simple_string_serialized, 'binary')).doc.toString()); | ||
// Simple serialization and deserialization for a objectId value | ||
var simple_string_serialized = BSON.serialize({doc:new ObjectID2()}); | ||
assert.deepEqual(BSONJS.deserialize(simple_string_serialized).doc.toString(), BSON.deserialize(new Buffer(simple_string_serialized, 'binary')).doc.toString()); | ||
assert.deepEqual(BSONJS.deserialize(simple_string_serialized).doc.toString(), BSON.deserialize(simple_string_serialized).doc.toString()); | ||
// Simple serialization and deserialization for a Binary value | ||
var binary = new Binary2(); | ||
var string = 'binstring' | ||
for(var index = 0; index < string.length; index++) { binary.put(string.charAt(index)); } | ||
var simple_string_serialized = BSON.serialize({doc:binary}); | ||
assert.deepEqual(BSONJS.deserialize(simple_string_serialized).doc.value(), BSON.deserialize(new Buffer(simple_string_serialized, 'binary')).doc.value()); | ||
assert.deepEqual(BSONJS.deserialize(simple_string_serialized).doc.value(), BSON.deserialize(simple_string_serialized).doc.value()); | ||
// Simple serialization and deserialization for a Code value | ||
var code = new Code2('this.a > i', {'i': 1}); | ||
var code2 = new Code('this.a > i', {'i': 1}); | ||
var simple_string_serialized_2 = BSONJS.serialize({doc:code2}); | ||
var simple_string_serialized = BSON.serialize({doc:code}); | ||
assert.deepEqual(BSONJS.deserialize(simple_string_serialized_2).doc.scope, BSON.deserialize(new Buffer(simple_string_serialized, 'binary')).doc.scope); | ||
assert.deepEqual(BSONJS.deserialize(simple_string_serialized_2).doc.code, BSON.deserialize(simple_string_serialized).doc.code); | ||
// Simple serialization and deserialization for an Object | ||
var simple_string_serialized = BSON.serialize({doc:{a:1, b:{c:2}}}); | ||
var simple_string_serialized_2 = BSONJS.serialize({doc:{a:1, b:{c:2}}}); | ||
assert.deepEqual(BSONJS.deserialize(simple_string_serialized_2).doc, BSON.deserialize(new Buffer(simple_string_serialized, 'binary')).doc); | ||
assert.deepEqual(BSONJS.deserialize(simple_string_serialized_2).doc, BSON.deserialize(simple_string_serialized).doc); | ||
// Simple serialization and deserialization for an Array | ||
var simple_string_serialized = BSON.serialize({doc:[9, 9, 1, 2, 3, 1, 1, 1, 1, 1, 1, 1]}); | ||
var simple_string_serialized_2 = BSONJS.serialize({doc:[9, 9, 1, 2, 3, 1, 1, 1, 1, 1, 1, 1]}); | ||
assert.deepEqual(BSONJS.deserialize(simple_string_serialized_2).doc, BSON.deserialize(new Buffer(simple_string_serialized, 'binary')).doc); | ||
assert.deepEqual(BSONJS.deserialize(simple_string_serialized_2).doc, BSON.deserialize(simple_string_serialized).doc); | ||
// Simple serialization and deserialization for a DBRef | ||
var oid = new ObjectID2() | ||
var simple_string_serialized = BSONJS.serialize({doc:new DBRef('namespace', oid, 'integration_tests_')}); | ||
var simple_string_serialized_2 = BSON.serialize({doc:new DBRef2('namespace', oid, 'integration_tests_')}); | ||
// Ensure we have the same values for the dbref | ||
var object_js = BSONJS.deserialize(simple_string_serialized_2); | ||
var object_c = BSON.deserialize(simple_string_serialized); | ||
assert.equal(object_js.doc.namespace, object_c.doc.namespace); | ||
assert.equal(object_js.doc.oid.toHexString(), object_c.doc.oid.toHexString()); | ||
assert.equal(object_js.doc.db, object_c.doc.db); | ||
// Serialized document | ||
var bytes = [47,0,0,0,2,110,97,109,101,0,6,0,0,0,80,97,116,116,121,0,16,97,103,101,0,34,0,0,0,7,95,105,100,0,76,100,12,23,11,30,39,8,89,0,0,1,0]; | ||
var serialized_data = ''; | ||
// Convert to chars | ||
for(var i = 0; i < bytes.length; i++) { | ||
serialized_data = serialized_data + BinaryParser.fromByte(bytes[i]); | ||
} | ||
var object = BSON.deserialize(serialized_data); | ||
assert.equal('Patty', object.name) | ||
assert.equal(34, object.age) | ||
assert.equal('4c640c170b1e270859000001', object._id.toHexString()) | ||
// Serialize utf8 | ||
var doc = { "name" : "本荘由利地域に洪水警報", "name1" : "öüóőúéáűíÖÜÓŐÚÉÁŰÍ", "name2" : "abcdedede"}; | ||
var simple_string_serialized = BSON.serialize(doc); | ||
var object = BSON.deserialize(simple_string_serialized); | ||
assert.equal(doc.name, object.name) | ||
assert.equal(doc.name1, object.name1) | ||
assert.equal(doc.name2, object.name2) | ||
// Serialize object with array | ||
var doc = {b:[1, 2, 3]}; | ||
var simple_string_serialized = BSON.serialize(doc); | ||
var simple_string_serialized_2 = BSONJS.serialize(doc); | ||
var object = BSON.deserialize(simple_string_serialized); | ||
assert.deepEqual(doc, object) | ||
// Test equality of an object ID | ||
var object_id = new ObjectID2(); | ||
var object_id_2 = new ObjectID2(); | ||
assert.ok(object_id.equals(object_id)); | ||
assert.ok(!(object_id.equals(object_id_2))) | ||
require.paths.unshift("../../lib"); | ||
var sys = require('sys'), | ||
var sys = require('util'), | ||
fs = require('fs'), | ||
@@ -17,3 +17,3 @@ Buffer = require('buffer').Buffer, | ||
sys.puts("=== EXCEUTING TEST_FULL_BSON ==="); | ||
sys.puts("=== EXECUTING TEST_FULL_BSON ==="); | ||
@@ -169,3 +169,3 @@ // Should Correctly Deserialize object | ||
// Should Correctly Serialize and Deserialize a big Binary object | ||
var data = fs.readFileSync("../../integration/test_gs_weird_bug.png", 'binary'); | ||
var data = fs.readFileSync("../../test/gridstore/test_gs_weird_bug.png", 'binary'); | ||
var bin = new Binary() | ||
@@ -172,0 +172,0 @@ bin.write(data) |
var Collection = require('./collection').Collection, | ||
Cursor = require('./cursor').Cursor, | ||
DbCommand = require('./commands/db_command').DbCommand; | ||
DbCommand = require('./commands/db_command').DbCommand, | ||
debug = require('util').debug, | ||
inspect = require('util').inspect; | ||
@@ -12,16 +14,15 @@ var Admin = exports.Admin = function(db) { | ||
var command = {buildinfo:1}; | ||
var databaseName = self.db.databaseName; | ||
self.db.databaseName = 'admin'; | ||
this.db.executeDbCommand(command, function(err, doc) { | ||
this.command(command, function(err, doc) { | ||
if(err != null) return callback(err, null); | ||
return callback(null, doc.documents[0]); | ||
}); | ||
// Ensure change before event loop executes | ||
self.db.databaseName = databaseName; | ||
} | ||
Admin.prototype.profilingLevel = function(callback) { | ||
var self = this; | ||
var command = {profile:-1}; | ||
this.db.executeDbCommand(command, function(err, doc) { | ||
this.command(command, function(err, doc) { | ||
doc = doc.documents[0]; | ||
if(err == null && (doc.ok == 1 || doc.was.constructor == Numeric)) { | ||
@@ -44,5 +45,18 @@ var was = doc.was; | ||
Admin.prototype.authenticate = function(username, password, callback) { | ||
var self = this; | ||
var databaseName = this.db.databaseName; | ||
this.db.databaseName = 'admin'; | ||
this.db.authenticate(username, password, function(err, result) { | ||
self.db.databaseName = databaseName; | ||
return callback(err, result); | ||
}) | ||
} | ||
Admin.prototype.setProfilingLevel = function(level, callback) { | ||
var self = this; | ||
var command = {}; | ||
var profile = 0; | ||
if(level == "off") { | ||
@@ -55,13 +69,13 @@ profile = 0; | ||
} else { | ||
callback(new Error("Error: illegal profiling level value " + level)); | ||
return; | ||
return callback(new Error("Error: illegal profiling level value " + level)); | ||
} | ||
command['profile'] = profile; | ||
this.db.executeDbCommand(command, function(err, doc) { | ||
this.command(command, function(err, doc) { | ||
doc = doc.documents[0]; | ||
if(err == null && (doc.ok == 1 || doc.was.constructor == Numeric)) { | ||
callback(null, level); | ||
return callback(null, level); | ||
} else { | ||
err != null ? callback(err, null) : callback(new Error("Error with profile command"), null); | ||
return err != null ? callback(err, null) : callback(new Error("Error with profile command"), null); | ||
} | ||
@@ -72,24 +86,41 @@ }); | ||
Admin.prototype.profilingInfo = function(callback) { | ||
var self = this; | ||
var databaseName = this.db.databaseName; | ||
this.db.databaseName = 'admin'; | ||
new Cursor(this.db, new Collection(this.db, DbCommand.SYSTEM_PROFILE_COLLECTION), {}).toArray(function(err, items) { | ||
callback(err, items); | ||
}); | ||
return callback(err, items); | ||
}); | ||
self.db.databaseName = databaseName; | ||
}; | ||
Admin.prototype.validatCollection = function(collectionName, callback) { | ||
Admin.prototype.command = function(command, callback) { | ||
var self = this; | ||
// Execute a command | ||
this.db.executeDbAdminCommand(command, function(err, result) { | ||
// Ensure change before event loop executes | ||
return callback(err, result); | ||
}); | ||
} | ||
Admin.prototype.validateCollection = function(collectionName, callback) { | ||
var self = this; | ||
var command = {validate: collectionName}; | ||
this.db.executeDbCommand(command, function(err, doc) { | ||
if(err != null) return callback(err, null); | ||
doc = doc.documents[0]; | ||
if(err != null) { | ||
callback(err, null); | ||
} else if(doc.ok == 0) { | ||
callback(new Error("Error with validate command"), null); | ||
if(doc.ok == 0) { | ||
return callback(new Error("Error with validate command"), null); | ||
} else if(doc.result.constructor != String) { | ||
callback(new Error("Error with validation data"), null); | ||
return callback(new Error("Error with validation data"), null); | ||
} else if(doc.result.match(/exception|corrupt/) != null) { | ||
callback(new Error("Error: invalid collection " + collectionName), null); | ||
return callback(new Error("Error: invalid collection " + collectionName), null); | ||
} else { | ||
callback(null, doc); | ||
return callback(null, doc); | ||
} | ||
}); | ||
}; |
@@ -1,11 +0,22 @@ | ||
var sys = require('sys'); | ||
//+ Jonas Raoni Soares Silva | ||
//@ http://jsfromhell.com/classes/binary-parser [v1.0] | ||
/** | ||
* Module dependencies. | ||
*/ | ||
var sys = require('util'); | ||
/** | ||
* Binary Parser. | ||
* Jonas Raoni Soares Silva | ||
* http://jsfromhell.com/classes/binary-parser [v1.0] | ||
*/ | ||
var chr = String.fromCharCode; | ||
var maxBits = []; | ||
for(var i = 0; i<64; i++) | ||
for (var i = 0; i < 64; i++) { | ||
maxBits[i] = Math.pow(2, i); | ||
} | ||
var p = exports.BinaryParser = function( bigEndian, allowExceptions ){ | ||
function BinaryParser (bigEndian, allowExceptions) { | ||
this.bigEndian = bigEndian; | ||
@@ -15,138 +26,154 @@ this.allowExceptions = allowExceptions; | ||
var Buffer = exports.BinaryParser.Buffer = function( bigEndian, buffer ){ | ||
this.bigEndian = bigEndian || 0; | ||
this.buffer = []; | ||
this.setBuffer( buffer ); | ||
}; | ||
BinaryParser.warn = function warn (msg) { | ||
if (this.allowExceptions) { | ||
throw new Error(msg); | ||
} | ||
Buffer.prototype.setBuffer = function( data ){ | ||
if( data ){ | ||
for( var l, i = l = data.length, b = this.buffer = new Array( l ); i; b[l - i] = data.charCodeAt( --i ) ); | ||
this.bigEndian && b.reverse(); | ||
} | ||
return 1; | ||
}; | ||
Buffer.prototype.hasNeededBits = function( neededBits ){ | ||
return this.buffer.length >= -( -neededBits >> 3 ); | ||
}; | ||
BinaryParser.decodeFloat = function decodeFloat (data, precisionBits, exponentBits) { | ||
var b = new this.Buffer(this.bigEndian, data); | ||
Buffer.prototype.checkBuffer = function( neededBits ){ | ||
if( !this.hasNeededBits( neededBits ) ) | ||
throw new Error( "checkBuffer::missing bytes" ); | ||
}; | ||
b.checkBuffer(precisionBits + exponentBits + 1); | ||
Buffer.prototype.readBits = function( start, length ){ | ||
//shl fix: Henri Torgemane ~1996 (compressed by Jonas Raoni) | ||
function shl( a, b ){ | ||
for( ; b--; a = ( ( a %= 0x7fffffff + 1 ) & 0x40000000 ) == 0x40000000 ? a * 2 : ( a - 0x40000000 ) * 2 + 0x7fffffff + 1 ); | ||
return a; | ||
} | ||
if( start < 0 || length <= 0 ) | ||
return 0; | ||
this.checkBuffer( start + length ); | ||
for( var offsetLeft, offsetRight = start % 8, curByte = this.buffer.length - ( start >> 3 ) - 1, lastByte = this.buffer.length + ( -( start + length ) >> 3 ), diff = curByte - lastByte, sum = ( ( this.buffer[ curByte ] >> offsetRight ) & ( ( 1 << ( diff ? 8 - offsetRight : length ) ) - 1 ) ) + ( diff && ( offsetLeft = ( start + length ) % 8 ) ? ( this.buffer[ lastByte++ ] & ( ( 1 << offsetLeft ) - 1 ) ) << ( diff-- << 3 ) - offsetRight : 0 ); diff; sum += shl( this.buffer[ lastByte++ ], ( diff-- << 3 ) - offsetRight ) ); | ||
return sum; | ||
}; | ||
p.warn = function( msg ){ | ||
if( this.allowExceptions ) | ||
throw new Error( msg ); | ||
return 1; | ||
}; | ||
p.decodeFloat = function( data, precisionBits, exponentBits ){ | ||
var b = new this.Buffer( this.bigEndian, data ); | ||
b.checkBuffer( precisionBits + exponentBits + 1 ); | ||
//var bias = Math.pow( 2, exponentBits - 1 ) - 1, | ||
var bias = maxBits[exponentBits - 1] - 1, | ||
signal = b.readBits( precisionBits + exponentBits, 1 ), exponent = b.readBits( precisionBits, exponentBits ), significand = 0, | ||
divisor = 2, curByte = b.buffer.length + ( -precisionBits >> 3 ) - 1; | ||
do{ | ||
for( var byteValue = b.buffer[ ++curByte ], startBit = precisionBits % 8 || 8, mask = 1 << startBit; mask >>= 1; ( byteValue & mask ) && ( significand += 1 / divisor ), divisor *= 2 ); | ||
}while( precisionBits -= startBit ); | ||
var bias = maxBits[exponentBits - 1] - 1 | ||
, signal = b.readBits(precisionBits + exponentBits, 1) | ||
, exponent = b.readBits(precisionBits, exponentBits) | ||
, significand = 0 | ||
, divisor = 2 | ||
, curByte = b.buffer.length + (-precisionBits >> 3) - 1; | ||
do { | ||
for (var byteValue = b.buffer[ ++curByte ], startBit = precisionBits % 8 || 8, mask = 1 << startBit; mask >>= 1; ( byteValue & mask ) && ( significand += 1 / divisor ), divisor *= 2 ); | ||
} while (precisionBits -= startBit); | ||
return exponent == ( bias << 1 ) + 1 ? significand ? NaN : signal ? -Infinity : +Infinity : ( 1 + signal * -2 ) * ( exponent || significand ? !exponent ? Math.pow( 2, -bias + 1 ) * significand : Math.pow( 2, exponent - bias ) * ( 1 + significand ) : 0 ); | ||
}; | ||
p.decodeInt = function( data, bits, signed, forceBigEndian ){ | ||
var b = new this.Buffer( this.bigEndian||forceBigEndian, data ), x = b.readBits( 0, bits ), max = maxBits[bits]; //max = Math.pow( 2, bits ); | ||
return signed && x >= max / 2 ? x - max : x; | ||
BinaryParser.decodeInt = function decodeInt (data, bits, signed, forceBigEndian) { | ||
var b = new this.Buffer(this.bigEndian || forceBigEndian, data) | ||
, x = b.readBits(0, bits) | ||
, max = maxBits[bits]; //max = Math.pow( 2, bits ); | ||
return signed && x >= max / 2 | ||
? x - max | ||
: x; | ||
}; | ||
p.encodeFloat = function( data, precisionBits, exponentBits ){ | ||
//var bias = Math.pow( 2, exponentBits - 1 ) - 1, | ||
var bias = maxBits[exponentBits - 1] - 1, | ||
minExp = -bias + 1, maxExp = bias, minUnnormExp = minExp - precisionBits, | ||
status = isNaN( n = parseFloat( data ) ) || n == -Infinity || n == +Infinity ? n : 0, | ||
exp = 0, len = 2 * bias + 1 + precisionBits + 3, bin = new Array( len ), | ||
signal = ( n = status !== 0 ? 0 : n ) < 0, n = Math.abs( n ), intPart = Math.floor( n ), floatPart = n - intPart, | ||
i, lastBit, rounded, j, result; | ||
for( i = len; i; bin[--i] = 0 ); | ||
for( i = bias + 2; intPart && i; bin[--i] = intPart % 2, intPart = Math.floor( intPart / 2 ) ); | ||
for( i = bias + 1; floatPart > 0 && i; ( bin[++i] = ( ( floatPart *= 2 ) >= 1 ) - 0 ) && --floatPart ); | ||
for( i = -1; ++i < len && !bin[i]; ); | ||
if( bin[( lastBit = precisionBits - 1 + ( i = ( exp = bias + 1 - i ) >= minExp && exp <= maxExp ? i + 1 : bias + 1 - ( exp = minExp - 1 ) ) ) + 1] ){ | ||
if( !( rounded = bin[lastBit] ) ){ | ||
for( j = lastBit + 2; !rounded && j < len; rounded = bin[j++] ); | ||
BinaryParser.encodeFloat = function encodeFloat (data, precisionBits, exponentBits) { | ||
var bias = maxBits[exponentBits - 1] - 1 | ||
, minExp = -bias + 1 | ||
, maxExp = bias | ||
, minUnnormExp = minExp - precisionBits | ||
, n = parseFloat(data) | ||
, status = isNaN(n) || n == -Infinity || n == +Infinity ? n : 0 | ||
, exp = 0 | ||
, len = 2 * bias + 1 + precisionBits + 3 | ||
, bin = new Array(len) | ||
, signal = (n = status !== 0 ? 0 : n) < 0 | ||
, intPart = Math.floor(n = Math.abs(n)) | ||
, floatPart = n - intPart | ||
, lastBit | ||
, rounded | ||
, result | ||
, i | ||
, j; | ||
for (i = len; i; bin[--i] = 0); | ||
for (i = bias + 2; intPart && i; bin[--i] = intPart % 2, intPart = Math.floor(intPart / 2)); | ||
for (i = bias + 1; floatPart > 0 && i; (bin[++i] = ((floatPart *= 2) >= 1) - 0 ) && --floatPart); | ||
for (i = -1; ++i < len && !bin[i];); | ||
if (bin[(lastBit = precisionBits - 1 + (i = (exp = bias + 1 - i) >= minExp && exp <= maxExp ? i + 1 : bias + 1 - (exp = minExp - 1))) + 1]) { | ||
if (!(rounded = bin[lastBit])) { | ||
for (j = lastBit + 2; !rounded && j < len; rounded = bin[j++]); | ||
} | ||
for( j = lastBit + 1; rounded && --j >= 0; ( bin[j] = !bin[j] - 0 ) && ( rounded = 0 ) ); | ||
for (j = lastBit + 1; rounded && --j >= 0; (bin[j] = !bin[j] - 0) && (rounded = 0)); | ||
} | ||
for( i = i - 2 < 0 ? -1 : i - 3; ++i < len && !bin[i]; ); | ||
if( ( exp = bias + 1 - i ) >= minExp && exp <= maxExp ) | ||
for (i = i - 2 < 0 ? -1 : i - 3; ++i < len && !bin[i];); | ||
if ((exp = bias + 1 - i) >= minExp && exp <= maxExp) { | ||
++i; | ||
else if( exp < minExp ){ | ||
exp != bias + 1 - len && exp < minUnnormExp && this.warn( "encodeFloat::float underflow" ); | ||
i = bias + 1 - ( exp = minExp - 1 ); | ||
} else if (exp < minExp) { | ||
exp != bias + 1 - len && exp < minUnnormExp && this.warn("encodeFloat::float underflow"); | ||
i = bias + 1 - (exp = minExp - 1); | ||
} | ||
if( intPart || status !== 0 ){ | ||
this.warn( intPart ? "encodeFloat::float overflow" : "encodeFloat::" + status ); | ||
if (intPart || status !== 0) { | ||
this.warn(intPart ? "encodeFloat::float overflow" : "encodeFloat::" + status); | ||
exp = maxExp + 1; | ||
i = bias + 2; | ||
if( status == -Infinity ) | ||
if (status == -Infinity) { | ||
signal = 1; | ||
else if( isNaN( status ) ) | ||
} else if (isNaN(status)) { | ||
bin[i] = 1; | ||
} | ||
} | ||
for( n = Math.abs( exp + bias ), j = exponentBits + 1, result = ""; --j; result = ( n % 2 ) + result, n = n >>= 1 ); | ||
for( n = 0, j = 0, i = ( result = ( signal ? "1" : "0" ) + result + bin.slice( i, i + precisionBits ).join( "" ) ).length, r = []; i; j = ( j + 1 ) % 8 ){ | ||
n += ( 1 << j ) * result.charAt( --i ); | ||
if( j == 7 ){ | ||
r[r.length] = String.fromCharCode( n ); | ||
for (n = Math.abs(exp + bias), j = exponentBits + 1, result = ""; --j; result = (n % 2) + result, n = n >>= 1); | ||
for (n = 0, j = 0, i = (result = (signal ? "1" : "0") + result + bin.slice(i, i + precisionBits).join("")).length, r = []; i; j = (j + 1) % 8) { | ||
n += (1 << j) * result.charAt(--i); | ||
if (j == 7) { | ||
r[r.length] = String.fromCharCode(n); | ||
n = 0; | ||
} | ||
} | ||
r[r.length] = n ? String.fromCharCode( n ) : ""; | ||
return ( this.bigEndian ? r.reverse() : r ).join( "" ); | ||
r[r.length] = n | ||
? String.fromCharCode(n) | ||
: ""; | ||
return (this.bigEndian ? r.reverse() : r).join(""); | ||
}; | ||
p.encodeInt = function( data, bits, signed, forceBigEndian ){ | ||
//var max = Math.pow( 2, bits ); | ||
BinaryParser.encodeInt = function encodeInt (data, bits, signed, forceBigEndian) { | ||
var max = maxBits[bits]; | ||
( data >= max || data < -( max / 2 ) ) && this.warn( "encodeInt::overflow" ) && ( data = 0 ); | ||
data < 0 && ( data += max ); | ||
for( var r = []; data; r[r.length] = String.fromCharCode( data % 256 ), data = Math.floor( data / 256 ) ); | ||
for( bits = -( -bits >> 3 ) - r.length; bits--; r[r.length] = "\0" ); | ||
return ( (this.bigEndian||forceBigEndian) ? r.reverse() : r ).join( "" ); | ||
if (data >= max || data < -(max / 2)) { | ||
this.warn("encodeInt::overflow"); | ||
data = 0; | ||
} | ||
if (data < 0) { | ||
data += max; | ||
} | ||
for (var r = []; data; r[r.length] = String.fromCharCode(data % 256), data = Math.floor(data / 256)); | ||
for (bits = -(-bits >> 3) - r.length; bits--; r[r.length] = "\0"); | ||
return ((this.bigEndian || forceBigEndian) ? r.reverse() : r).join(""); | ||
}; | ||
p.toSmall = function( data ){ return this.decodeInt( data, 8, true ); }; | ||
p.fromSmall = function( data ){ return this.encodeInt( data, 8, true ); }; | ||
p.toByte = function( data ){ return this.decodeInt( data, 8, false ); }; | ||
p.fromByte = function( data ){ return this.encodeInt( data, 8, false ); }; | ||
p.toShort = function( data ){ return this.decodeInt( data, 16, true ); }; | ||
p.fromShort = function( data ){ return this.encodeInt( data, 16, true ); }; | ||
p.toWord = function( data ){ return this.decodeInt( data, 16, false ); }; | ||
p.fromWord = function( data ){ return this.encodeInt( data, 16, false ); }; | ||
p.toInt = function( data ){ return this.decodeInt( data, 32, true ); }; | ||
p.fromInt = function( data ){ return this.encodeInt( data, 32, true ); }; | ||
p.toLong = function( data ){ return this.decodeInt( data, 64, true ); }; | ||
p.fromLong = function( data ){ return this.encodeInt( data, 64, true ); }; | ||
p.toDWord = function( data ){ return this.decodeInt( data, 32, false ); }; | ||
p.fromDWord = function( data ){ return this.encodeInt( data, 32, false ); }; | ||
p.toQWord = function( data ){ return this.decodeInt( data, 64, true ); }; | ||
p.fromQWord = function( data ){ return this.encodeInt( data, 64, true ); }; | ||
p.toFloat = function( data ){ return this.decodeFloat( data, 23, 8 ); }; | ||
p.fromFloat = function( data ){ return this.encodeFloat( data, 23, 8 ); }; | ||
p.toDouble = function( data ){ return this.decodeFloat( data, 52, 11 ); }; | ||
p.fromDouble = function( data ){ return this.encodeFloat( data, 52, 11 ); }; | ||
BinaryParser.toSmall = function( data ){ return this.decodeInt( data, 8, true ); }; | ||
BinaryParser.fromSmall = function( data ){ return this.encodeInt( data, 8, true ); }; | ||
BinaryParser.toByte = function( data ){ return this.decodeInt( data, 8, false ); }; | ||
BinaryParser.fromByte = function( data ){ return this.encodeInt( data, 8, false ); }; | ||
BinaryParser.toShort = function( data ){ return this.decodeInt( data, 16, true ); }; | ||
BinaryParser.fromShort = function( data ){ return this.encodeInt( data, 16, true ); }; | ||
BinaryParser.toWord = function( data ){ return this.decodeInt( data, 16, false ); }; | ||
BinaryParser.fromWord = function( data ){ return this.encodeInt( data, 16, false ); }; | ||
BinaryParser.toInt = function( data ){ return this.decodeInt( data, 32, true ); }; | ||
BinaryParser.fromInt = function( data ){ return this.encodeInt( data, 32, true ); }; | ||
BinaryParser.toLong = function( data ){ return this.decodeInt( data, 64, true ); }; | ||
BinaryParser.fromLong = function( data ){ return this.encodeInt( data, 64, true ); }; | ||
BinaryParser.toDWord = function( data ){ return this.decodeInt( data, 32, false ); }; | ||
BinaryParser.fromDWord = function( data ){ return this.encodeInt( data, 32, false ); }; | ||
BinaryParser.toQWord = function( data ){ return this.decodeInt( data, 64, true ); }; | ||
BinaryParser.fromQWord = function( data ){ return this.encodeInt( data, 64, true ); }; | ||
BinaryParser.toFloat = function( data ){ return this.decodeFloat( data, 23, 8 ); }; | ||
BinaryParser.fromFloat = function( data ){ return this.encodeFloat( data, 23, 8 ); }; | ||
BinaryParser.toDouble = function( data ){ return this.decodeFloat( data, 52, 11 ); }; | ||
BinaryParser.fromDouble = function( data ){ return this.encodeFloat( data, 52, 11 ); }; | ||
// Factor out the encode so it can be shared by add_header and push_int32 | ||
p.encode_int32 = function(number) { | ||
BinaryParser.encode_int32 = function encode_int32 (number) { | ||
var a, b, c, d, unsigned; | ||
@@ -164,3 +191,3 @@ unsigned = (number < 0) ? (number + 0x100000000) : number; | ||
p.encode_int64 = function(number) { | ||
BinaryParser.encode_int64 = function encode_int64 (number) { | ||
var a, b, c, d, e, f, g, h, unsigned; | ||
@@ -187,82 +214,182 @@ unsigned = (number < 0) ? (number + 0x10000000000000000) : number; | ||
/** | ||
UTF8 methods | ||
**/ | ||
* UTF8 methods | ||
*/ | ||
// Take a raw binary string and return a utf8 string | ||
p.decode_utf8 = function(a) { | ||
var string = ""; | ||
var i = 0; | ||
var c, c1, c2, c3; | ||
c = c1 = c2 = 0; | ||
BinaryParser.decode_utf8 = function decode_utf8 (binaryStr) { | ||
var len = binaryStr.length | ||
, decoded = '' | ||
, i = 0 | ||
, c = 0 | ||
, c1 = 0 | ||
, c2 = 0 | ||
, c3; | ||
while ( i < a.length ) { | ||
c = a.charCodeAt(i); | ||
while (i < len) { | ||
c = binaryStr.charCodeAt(i); | ||
if (c < 128) { | ||
string += String.fromCharCode(c); | ||
decoded += String.fromCharCode(c); | ||
i++; | ||
} else if((c > 191) && (c < 224)) { | ||
c2 = a.charCodeAt(i+1); | ||
string += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); | ||
} else if ((c > 191) && (c < 224)) { | ||
c2 = binaryStr.charCodeAt(i+1); | ||
decoded += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); | ||
i += 2; | ||
} else { | ||
c2 = a.charCodeAt(i+1); | ||
c3 = a.charCodeAt(i+2); | ||
string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); | ||
c2 = binaryStr.charCodeAt(i+1); | ||
c3 = binaryStr.charCodeAt(i+2); | ||
decoded += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); | ||
i += 3; | ||
} | ||
} | ||
return string; | ||
return decoded; | ||
}; | ||
// Encode a cstring correctly | ||
p.encode_cstring = function(s) { | ||
return unescape(encodeURIComponent(s)) + p.fromByte(0); | ||
// Encode a cstring | ||
BinaryParser.encode_cstring = function encode_cstring (s) { | ||
return unescape(encodeURIComponent(s)) + BinaryParser.fromByte(0); | ||
}; | ||
// Take a utf8 string and return a binary string | ||
p.encode_utf8 = function(s) { | ||
var a=""; | ||
for (var n=0; n< s.length; n++) { | ||
var c=s.charCodeAt(n); | ||
if (c<128) { | ||
BinaryParser.encode_utf8 = function encode_utf8 (s) { | ||
var a = "" | ||
, c; | ||
for (var n = 0, len = s.length; n < len; n++) { | ||
c = s.charCodeAt(n); | ||
if (c < 128) { | ||
a += String.fromCharCode(c); | ||
} else if ((c>127)&&(c<2048)) { | ||
a += String.fromCharCode( (c>>6) | 192) ; | ||
a += String.fromCharCode( (c&63) | 128); | ||
} else if ((c > 127) && (c < 2048)) { | ||
a += String.fromCharCode((c>>6) | 192) ; | ||
a += String.fromCharCode((c&63) | 128); | ||
} else { | ||
a += String.fromCharCode( (c>>12) | 224); | ||
a += String.fromCharCode( ((c>>6) & 63) | 128); | ||
a += String.fromCharCode( (c&63) | 128); | ||
a += String.fromCharCode((c>>12) | 224); | ||
a += String.fromCharCode(((c>>6) & 63) | 128); | ||
a += String.fromCharCode((c&63) | 128); | ||
} | ||
} | ||
return a; | ||
}; | ||
p.hprint = function(s) { | ||
for (var i=0; i<s.length; i++) { | ||
if (s.charCodeAt(i)<32) { | ||
var number = s.charCodeAt(i) <= 15 ? "0" + s.charCodeAt(i).toString(16) : s.charCodeAt(i).toString(16); | ||
sys.debug(number+' : ');} | ||
else { | ||
var number = s.charCodeAt(i) <= 15 ? "0" + s.charCodeAt(i).toString(16) : s.charCodeAt(i).toString(16); | ||
sys.debug(number+' : '+ s.charAt(i));} | ||
BinaryParser.hprint = function hprint (s) { | ||
var number; | ||
for (var i = 0, len = s.length; i < len; i++) { | ||
if (s.charCodeAt(i) < 32) { | ||
number = s.charCodeAt(i) <= 15 | ||
? "0" + s.charCodeAt(i).toString(16) | ||
: s.charCodeAt(i).toString(16); | ||
process.stdout.write(number) | ||
} else { | ||
number = s.charCodeAt(i) <= 15 | ||
? "0" + s.charCodeAt(i).toString(16) | ||
: s.charCodeAt(i).toString(16); | ||
process.stdout.write(number) | ||
} | ||
} | ||
process.stdout.write("\n\n"); | ||
}; | ||
p.to_byte_array = function(s) { | ||
var array = []; | ||
for (var i=0; i<s.length; i++) { | ||
if (s.charCodeAt(i)<32) {array.push(s.charCodeAt(i));} | ||
else {array.push(s.charCodeAt(i))} | ||
} | ||
sys.puts(array); | ||
} | ||
BinaryParser.ilprint = function hprint (s) { | ||
var number; | ||
p.pprint = function(s) { | ||
for (var i=0; i<s.length; i++) { | ||
if (s.charCodeAt(i)<32) {sys.puts(s.charCodeAt(i)+' : ');} | ||
else {sys.puts(s.charCodeAt(i)+' : '+ s.charAt(i));} | ||
for (var i = 0, len = s.length; i < len; i++) { | ||
if (s.charCodeAt(i) < 32) { | ||
number = s.charCodeAt(i) <= 15 | ||
? "0" + s.charCodeAt(i).toString(10) | ||
: s.charCodeAt(i).toString(10); | ||
sys.debug(number+' : '); | ||
} else { | ||
number = s.charCodeAt(i) <= 15 | ||
? "0" + s.charCodeAt(i).toString(10) | ||
: s.charCodeAt(i).toString(10); | ||
sys.debug(number+' : '+ s.charAt(i)); | ||
} | ||
} | ||
}; | ||
BinaryParser.hlprint = function hprint (s) { | ||
var number; | ||
for (var i = 0, len = s.length; i < len; i++) { | ||
if (s.charCodeAt(i) < 32) { | ||
number = s.charCodeAt(i) <= 15 | ||
? "0" + s.charCodeAt(i).toString(16) | ||
: s.charCodeAt(i).toString(16); | ||
sys.debug(number+' : '); | ||
} else { | ||
number = s.charCodeAt(i) <= 15 | ||
? "0" + s.charCodeAt(i).toString(16) | ||
: s.charCodeAt(i).toString(16); | ||
sys.debug(number+' : '+ s.charAt(i)); | ||
} | ||
} | ||
}; | ||
/** | ||
* BinaryParser buffer constructor. | ||
*/ | ||
function BinaryParserBuffer (bigEndian, buffer) { | ||
this.bigEndian = bigEndian || 0; | ||
this.buffer = []; | ||
this.setBuffer(buffer); | ||
}; | ||
BinaryParserBuffer.prototype.setBuffer = function setBuffer (data) { | ||
var l, i, b; | ||
if (data) { | ||
i = l = data.length; | ||
b = this.buffer = new Array(l); | ||
for (; i; b[l - i] = data.charCodeAt(--i)); | ||
this.bigEndian && b.reverse(); | ||
} | ||
}; | ||
BinaryParserBuffer.prototype.hasNeededBits = function hasNeededBits (neededBits) { | ||
return this.buffer.length >= -(-neededBits >> 3); | ||
}; | ||
BinaryParserBuffer.prototype.checkBuffer = function checkBuffer (neededBits) { | ||
if (!this.hasNeededBits(neededBits)) { | ||
throw new Error("checkBuffer::missing bytes"); | ||
} | ||
}; | ||
BinaryParserBuffer.prototype.readBits = function readBits (start, length) { | ||
//shl fix: Henri Torgemane ~1996 (compressed by Jonas Raoni) | ||
function shl (a, b) { | ||
for (; b--; a = ((a %= 0x7fffffff + 1) & 0x40000000) == 0x40000000 ? a * 2 : (a - 0x40000000) * 2 + 0x7fffffff + 1); | ||
return a; | ||
} | ||
if (start < 0 || length <= 0) { | ||
return 0; | ||
} | ||
this.checkBuffer(start + length); | ||
var offsetLeft | ||
, offsetRight = start % 8 | ||
, curByte = this.buffer.length - ( start >> 3 ) - 1 | ||
, lastByte = this.buffer.length + ( -( start + length ) >> 3 ) | ||
, diff = curByte - lastByte | ||
, sum = ((this.buffer[ curByte ] >> offsetRight) & ((1 << (diff ? 8 - offsetRight : length)) - 1)) + (diff && (offsetLeft = (start + length) % 8) ? (this.buffer[lastByte++] & ((1 << offsetLeft) - 1)) << (diff-- << 3) - offsetRight : 0); | ||
for(; diff; sum += shl(this.buffer[lastByte++], (diff-- << 3) - offsetRight)); | ||
return sum; | ||
}; | ||
/** | ||
* Expose. | ||
*/ | ||
exports.BinaryParser = BinaryParser; | ||
BinaryParser.Buffer = BinaryParserBuffer; |
@@ -1,16 +0,18 @@ | ||
var BinaryParser = require('./binary_parser').BinaryParser, | ||
Integer = require('../goog/math/integer').Integer, | ||
Long = require('../goog/math/long').Long, | ||
Buffer = require('buffer').Buffer; | ||
// Alias a string function | ||
var chr = String.fromCharCode; | ||
/** | ||
* Module dependencies. | ||
*/ | ||
var BinaryParser = require('./binary_parser').BinaryParser | ||
, Long = require('../goog/math/long').Long | ||
, Timestamp = require('./timestamp').Timestamp | ||
, ObjectID = require('./objectid').ObjectID | ||
, Binary = require('./binary').Binary | ||
, debug = require('util').debug | ||
, inspect = require('util').inspect | ||
, inherits = require('util').inherits; | ||
var BSON = exports.BSON = function(){}, | ||
sys = require('sys'); | ||
/** | ||
* BSON constructor. | ||
*/ | ||
// Exports Long value | ||
exports.Long = Long; | ||
exports.Timestamp = Long; | ||
var Timestamp = Long | ||
function BSON () {}; | ||
@@ -38,2 +40,3 @@ // BSON MAX VALUES | ||
// BSON BINARY DATA SUBTYPES | ||
BSON.BSON_BINARY_SUBTYPE_DEFAULT = 0; | ||
BSON.BSON_BINARY_SUBTYPE_FUNCTION = 1; | ||
@@ -45,279 +48,482 @@ BSON.BSON_BINARY_SUBTYPE_BYTE_ARRAY = 2; | ||
BSON.serialize = function(data, checkKeys) { | ||
/** | ||
* Serialize `data` as BSON. | ||
* | ||
* @param {TODO} data | ||
* @param {Bool|null} checkKeys - TODO | ||
* @return {TODO} | ||
*/ | ||
BSON.serialize = function serialize (data, checkKeys) { | ||
return BSON.encodeValue('', null, data, true, checkKeys == null ? false : checkKeys); | ||
}; | ||
BSON.deserialize = function(data, is_array_item, returnData, returnArray) { | ||
/** | ||
* Deserialize `data` as BSON. | ||
* | ||
* @param {TODO} data | ||
* @param {Bool} is_array_item | ||
* @param {TODO} returnData | ||
* @param {TODO} returnArray | ||
* @return {TODO} | ||
*/ | ||
BSON.deserialize = function deserialize (data, is_array_item, returnData, returnArray) { | ||
// The return data | ||
var return_data = {}; | ||
var return_array = []; | ||
// Index of decoding in the binary file | ||
var index = 0; | ||
// Split of the first 4 characters to get the number of bytes | ||
var size = BinaryParser.toInt(data.substr(0, 4)); | ||
// Adjust index | ||
index = index + 4; | ||
while(index < data.length) { | ||
// Read the first byte indicating the type of object | ||
var type = BinaryParser.toSmall(data.substr(index, 1)); | ||
var insert_index = 0; | ||
// Adjust for the type of element | ||
index = index + 1; | ||
// If it's a string decode the value | ||
if(type == BSON.BSON_DATA_STRING) { | ||
// Read the null terminated string (indexof until first 0) | ||
var string_end_index = data.indexOf('\0', index); | ||
var string_name = data.substring(index, string_end_index); | ||
// Ajust index to point to the end of the string | ||
index = string_end_index + 1; | ||
// If we have an index read the array index | ||
if(is_array_item) insert_index = parseInt(string_name, 10); | ||
// Read the length of the string (next 4 bytes) | ||
var string_size = BinaryParser.toInt(data.substr(index, 4)); | ||
// Adjust the index to point at start of string | ||
index = index + 4; | ||
// Read the string | ||
var value = BinaryParser.decode_utf8(data.substr(index, string_size - 1)); | ||
// Adjust the index with the size of the string | ||
index = index + string_size; | ||
// Set the data on the object | ||
is_array_item ? return_array[insert_index] = value : return_data[string_name] = value; | ||
} else if(type == BSON.BSON_DATA_NULL) { | ||
// Read the null terminated string (indexof until first 0) | ||
var string_end_index = data.indexOf('\0', index); | ||
var string_name = data.substring(index, string_end_index); | ||
// Ajust index to point to the end of the string | ||
index = string_end_index + 1; | ||
// If we have an index read the array index | ||
if(is_array_item) insert_index = parseInt(string_name, 10); | ||
// Set the data on the object | ||
is_array_item ? return_array[insert_index] = null : return_data[string_name] = null; | ||
} else if(type == BSON.BSON_DATA_INT) { | ||
// Read the null terminated string (indexof until first 0) | ||
var string_end_index = data.indexOf('\0', index); | ||
var string_name = data.substring(index, string_end_index); | ||
// Ajust index to point to the end of the string | ||
index = string_end_index + 1; | ||
// If we have an index read the array index | ||
if(is_array_item) insert_index = parseInt(string_name, 10); | ||
// Read the number value | ||
var value = BinaryParser.toInt(data.substr(index, 4)); | ||
// Adjust the index with the size | ||
index = index + 4; | ||
// Set the data on the object | ||
is_array_item ? return_array[insert_index] = value : return_data[string_name] = value; | ||
} else if(type == BSON.BSON_DATA_LONG || type == BSON.BSON_DATA_TIMESTAMP) { | ||
// Read the null terminated string (indexof until first 0) | ||
var string_end_index = data.indexOf('\0', index); | ||
var string_name = data.substring(index, string_end_index); | ||
// Ajust index to point to the end of the string | ||
index = string_end_index + 1; | ||
// If we have an index read the array index | ||
if(is_array_item) insert_index = parseInt(string_name, 10); | ||
// Read the number value | ||
var low_bits = Integer.fromInt(BinaryParser.toInt(data.substr(index, 4))); | ||
var high_bits = Integer.fromInt(BinaryParser.toInt(data.substr(index + 4, 4))); | ||
// Create to integers | ||
var value = type == BSON.BSON_DATA_TIMESTAMP ? new Timestamp(low_bits, high_bits) : new Long(low_bits, high_bits); | ||
// Adjust the index with the size | ||
index = index + 8; | ||
// Set the data on the object | ||
is_array_item ? return_array[insert_index] = value : return_data[string_name] = value; | ||
} else if(type == BSON.BSON_DATA_NUMBER) { | ||
// Read the null terminated string (indexof until first 0) | ||
var string_end_index = data.indexOf('\0', index); | ||
var string_name = data.substring(index, string_end_index); | ||
// Ajust index to point to the end of the string | ||
index = string_end_index + 1; | ||
// If we have an index read the array index | ||
if(is_array_item) insert_index = parseInt(string_name, 10); | ||
// Read the number value | ||
var value = BinaryParser.toDouble(data.substr(index, 8)); | ||
// Adjust the index with the size | ||
index = index + 8; | ||
// Set the data on the object | ||
is_array_item ? return_array[insert_index] = value : return_data[string_name] = value; | ||
} else if(type == BSON.BSON_DATA_OBJECT) { | ||
// Read the null terminated string (indexof until first 0) | ||
var string_end_index = data.indexOf('\0', index); | ||
var string_name = data.substring(index, string_end_index); | ||
// Ajust index to point to the end of the string | ||
index = string_end_index + 1; | ||
// If we have an index read the array index | ||
if(is_array_item) insert_index = parseInt(string_name, 10); | ||
// Read the size of the object | ||
var object_size = BinaryParser.toInt(data.substr(index, 4)); | ||
// Do a substr based on the size and parse the sub object | ||
var object_data = data.substr(index, object_size); | ||
// Parse the object | ||
var value = BSON.deserialize(object_data, false); | ||
// Adjust the index for the next value | ||
index = index + object_size; | ||
// Set the data on the object | ||
is_array_item ? return_array[insert_index] = value : return_data[string_name] = value; | ||
} else if(type == BSON.BSON_DATA_ARRAY) { | ||
// Read the null terminated string (indexof until first 0) | ||
var string_end_index = data.indexOf('\0', index); | ||
var string_name = data.substring(index, string_end_index); | ||
// Ajust index to point to the end of the string | ||
index = string_end_index + 1; | ||
// If we have an index read the array index | ||
if(is_array_item) insert_index = parseInt(string_name, 10); | ||
// Read the size of the object | ||
var array_size = BinaryParser.toInt(data.substr(index, 4)); | ||
// Let's split off the data and parse all elements (keeping in mind the elements) | ||
var array_data = data.substr(index, array_size); | ||
// Parse the object | ||
var value = BSON.deserialize(array_data, true); | ||
// Adjust the index for the next value | ||
index = index + array_size; | ||
// Set the data on the object | ||
is_array_item ? return_array[insert_index] = value : return_data[string_name] = value; | ||
} else if(type == BSON.BSON_DATA_BOOLEAN) { | ||
// Read the null terminated string (indexof until first 0) | ||
var string_end_index = data.indexOf('\0', index); | ||
var string_name = data.substring(index, string_end_index); | ||
// Ajust index to point to the end of the string | ||
index = string_end_index + 1; | ||
// If we have an index read the array index | ||
if(is_array_item) insert_index = parseInt(string_name, 10); | ||
// Read the length of the string (next 4 bytes) | ||
var boolean_value = BinaryParser.toSmall(data.substr(index, 1)); | ||
var value = boolean_value == 1 ? true : false; | ||
// Adjust the index | ||
index = index + 1; | ||
// Set the data on the object | ||
is_array_item ? return_array[insert_index] = value : return_data[string_name] = value; | ||
} else if(type == BSON.BSON_DATA_DATE) { | ||
// Read the null terminated string (indexof until first 0) | ||
var string_end_index = data.indexOf('\0', index); | ||
var string_name = data.substring(index, string_end_index); | ||
// Ajust index to point to the end of the string | ||
index = string_end_index + 1; | ||
// If we have an index read the array index | ||
if(is_array_item) insert_index = parseInt(string_name, 10); | ||
// Read the number value | ||
var low_bits = Integer.fromInt(BinaryParser.toInt(data.substr(index, 4))); | ||
var high_bits = Integer.fromInt(BinaryParser.toInt(data.substr(index + 4, 4))); | ||
// Create to integers | ||
var value_in_seconds = new Long(low_bits, high_bits).toNumber(); | ||
// Calculate date with miliseconds | ||
var value = new Date(); | ||
value.setTime(value_in_seconds); | ||
// Adjust the index | ||
index = index + 8; | ||
// Set the data on the object | ||
is_array_item ? return_array[insert_index] = value : return_data[string_name] = value; | ||
} else if(type == BSON.BSON_DATA_OID) { | ||
// Read the null terminated string (indexof until first 0) | ||
var string_end_index = data.indexOf('\0', index); | ||
var string_name = data.substring(index, string_end_index); | ||
// Ajust index to point to the end of the string | ||
index = string_end_index + 1; | ||
// If we have an index read the array index | ||
if(is_array_item) insert_index = parseInt(string_name, 10); | ||
// Read the oid (12 bytes) | ||
var oid = data.substr(index, 12); | ||
// Calculate date with miliseconds | ||
var value = new exports.ObjectID(oid); | ||
// Adjust the index | ||
index = index + 12; | ||
// Set the data on the object | ||
is_array_item ? return_array[insert_index] = value : return_data[string_name] = value; | ||
} else if(type == BSON.BSON_DATA_CODE_W_SCOPE) { | ||
// Read the null terminated string (indexof until first 0) | ||
var string_end_index = data.indexOf('\0', index); | ||
var string_name = data.substring(index, string_end_index); | ||
// Ajust index to point to the end of the string | ||
index = string_end_index + 1; | ||
// If we have an index read the array index | ||
if(is_array_item) insert_index = parseInt(string_name, 10); | ||
// Unpack the integer sizes | ||
var total_code_size = BinaryParser.toInt(data.substr(index, 4)); | ||
index = index + 4; | ||
var string_size = BinaryParser.toInt(data.substr(index, 4)); | ||
index = index + 4; | ||
// Read the string + terminating null | ||
var code_string = BinaryParser.decode_utf8(data.substr(index, string_size - 1)); | ||
index = index + string_size; | ||
// Get the bson object | ||
var bson_object_size = total_code_size - string_size - 8; | ||
var bson_object_string = data.substr(index, bson_object_size); | ||
index = index + bson_object_size; | ||
// Parse the bson object | ||
var scope_object = BSON.deserialize(bson_object_string, false); | ||
// Create code object | ||
var value = new exports.Code(code_string, scope_object); | ||
// Set the data on the object | ||
is_array_item ? return_array[insert_index] = value : return_data[string_name] = value; | ||
} else if(type == BSON.BSON_DATA_REGEXP) { | ||
// Read the null terminated string (indexof until first 0) | ||
var string_end_index = data.indexOf('\0', index); | ||
var string_name = data.substring(index, string_end_index); | ||
// Ajust index to point to the end of the string | ||
index = string_end_index + 1; | ||
// If we have an index read the array index | ||
if(is_array_item) insert_index = parseInt(string_name, 10); | ||
// Read characters until end of regular expression | ||
var reg_exp_array = []; | ||
var chr = 1; | ||
var start_index = index | ||
while(BinaryParser.toByte((chr = data.charAt(index))) != 0) { | ||
index = index + 1 | ||
} | ||
// RegExp Expression | ||
reg_exp = data.substring(start_index, index) | ||
index = index + 1 | ||
// Read the options for the regular expression | ||
var options_array = []; | ||
while(BinaryParser.toByte((chr = data.charAt(index++))) != 0) { | ||
options_array.push(chr); | ||
} | ||
// Regular expression | ||
var value = new RegExp(BinaryParser.decode_utf8(reg_exp), options_array.join('')); | ||
// Set the data on the object | ||
is_array_item ? return_array[insert_index] = value : return_data[string_name] = value; | ||
} else if(type == BSON.BSON_DATA_BINARY) { | ||
// Read the null terminated string (indexof until first 0) | ||
var string_end_index = data.indexOf('\0', index); | ||
var string_name = data.substring(index, string_end_index); | ||
// Ajust index to point to the end of the string | ||
index = string_end_index + 1; | ||
// If we have an index read the array index | ||
if(is_array_item) insert_index = parseInt(string_name, 10); | ||
// The total number of bytes after subtype | ||
var total_number_of_bytes = BinaryParser.toInt(data.substr(index, 4)); | ||
index = index + 4; | ||
// Decode the subtype | ||
var sub_type = BinaryParser.toByte(data.substr(index, 1)); | ||
index = index + 1; | ||
// Binary object | ||
var value = new exports.Binary(); | ||
value.sub_type = sub_type; | ||
// Read the size of the binary | ||
var number_of_bytes = BinaryParser.toInt(data.substr(index, 4)); | ||
index = index + 4; | ||
// Read the next bytes into our Binary object | ||
var bin_data = data.substr(index, number_of_bytes); | ||
value.write(bin_data); | ||
// Adjust index with number of bytes | ||
index = index + number_of_bytes; | ||
// Set the data on the object | ||
is_array_item ? return_array[insert_index] = value : return_data[string_name] = value; | ||
switch (type) { | ||
case BSON.BSON_DATA_STRING: | ||
// Read the null terminated string (indexof until first 0) | ||
var string_end_index = data.indexOf('\0', index); | ||
var string_name = data.substring(index, string_end_index); | ||
// UTF-8 decode key name | ||
string_name = BinaryParser.decode_utf8(string_name); | ||
// | ||
// Ajust index to point to the end of the string | ||
index = string_end_index + 1; | ||
// Read the length of the string (next 4 bytes) | ||
var string_size = BinaryParser.toInt(data.substr(index, 4)); | ||
// Adjust the index to point at start of string | ||
index = index + 4; | ||
// Read the string | ||
var value = BinaryParser.decode_utf8(data.substr(index, string_size - 1)); | ||
// Adjust the index with the size of the string | ||
index = index + string_size; | ||
// Set the data on the object | ||
if (is_array_item) { | ||
return_array[parseInt(string_name, 10)] = value; | ||
} else { | ||
return_data[string_name] = value; | ||
} | ||
break; | ||
case BSON.BSON_DATA_NULL: | ||
// Read the null terminated string (indexof until first 0) | ||
var string_end_index = data.indexOf('\0', index); | ||
var string_name = data.substring(index, string_end_index); | ||
// UTF-8 decode key name | ||
// string_name = decodeURIComponent(escape(string_name)); | ||
string_name = BinaryParser.decode_utf8(string_name); | ||
// Ajust index to point to the end of the string | ||
index = string_end_index + 1; | ||
// Set the data on the object | ||
if (is_array_item) { | ||
return_array[parseInt(string_name, 10)] = null; | ||
} else { | ||
return_data[string_name] = null; | ||
} | ||
break; | ||
case BSON.BSON_DATA_INT: | ||
// Read the null terminated string (indexof until first 0) | ||
var string_end_index = data.indexOf('\0', index); | ||
var string_name = data.substring(index, string_end_index); | ||
// UTF-8 decode key name | ||
// string_name = decodeURIComponent(escape(string_name)); | ||
string_name = BinaryParser.decode_utf8(string_name); | ||
// Ajust index to point to the end of the string | ||
index = string_end_index + 1; | ||
// Read the number value | ||
var value = BinaryParser.toInt(data.substr(index, 4)); | ||
// Adjust the index with the size | ||
index = index + 4; | ||
if (is_array_item) { | ||
return_array[parseInt(string_name, 10)] = value; | ||
} else { | ||
return_data[string_name] = value; | ||
} | ||
break; | ||
case BSON.BSON_DATA_LONG: | ||
case BSON.BSON_DATA_TIMESTAMP: | ||
// Read the null terminated string (indexof until first 0) | ||
var string_end_index = data.indexOf('\0', index); | ||
var string_name = data.substring(index, string_end_index); | ||
// UTF-8 decode key name | ||
// string_name = decodeURIComponent(escape(string_name)); | ||
string_name = BinaryParser.decode_utf8(string_name); | ||
// Ajust index to point to the end of the string | ||
index = string_end_index + 1; | ||
// Read the number value | ||
var low_bits = BinaryParser.toInt(data.substr(index, 4)); | ||
var high_bits = BinaryParser.toInt(data.substr(index + 4, 4)); | ||
var value = new Long(low_bits, high_bits); | ||
// Adjust the index with the size | ||
index = index + 8; | ||
if (is_array_item) { | ||
return_array[parseInt(string_name, 10)] = value; | ||
} else { | ||
return_data[string_name] = value; | ||
} | ||
break; | ||
case BSON.BSON_DATA_NUMBER: | ||
// Read the null terminated string (indexof until first 0) | ||
var string_end_index = data.indexOf('\0', index); | ||
var string_name = data.substring(index, string_end_index); | ||
// UTF-8 decode key name | ||
// string_name = decodeURIComponent(escape(string_name)); | ||
string_name = BinaryParser.decode_utf8(string_name); | ||
// Ajust index to point to the end of the string | ||
index = string_end_index + 1; | ||
// Read the number value | ||
var value = BinaryParser.toDouble(data.substr(index, 8)); | ||
// Adjust the index with the size | ||
index = index + 8; | ||
if (is_array_item) { | ||
return_array[parseInt(string_name, 10)] = value; | ||
} else { | ||
return_data[string_name] = value; | ||
} | ||
break; | ||
case BSON.BSON_DATA_OBJECT: | ||
// Read the null terminated string (indexof until first 0) | ||
var string_end_index = data.indexOf('\0', index); | ||
var string_name = data.substring(index, string_end_index); | ||
// UTF-8 decode key name | ||
// string_name = decodeURIComponent(escape(string_name)); | ||
string_name = BinaryParser.decode_utf8(string_name); | ||
// Ajust index to point to the end of the string | ||
index = string_end_index + 1; | ||
// Read the size of the object | ||
var object_size = BinaryParser.toInt(data.substr(index, 4)); | ||
// Do a substr based on the size and parse the sub object | ||
var object_data = data.substr(index, object_size); | ||
// Parse the object | ||
var value = BSON.deserialize(object_data, false); | ||
// Adjust the index for the next value | ||
index = index + object_size; | ||
if (is_array_item) { | ||
return_array[parseInt(string_name, 10)] = value; | ||
} else { | ||
return_data[string_name] = value; | ||
} | ||
break; | ||
case BSON.BSON_DATA_ARRAY: | ||
// Read the null terminated string (indexof until first 0) | ||
var string_end_index = data.indexOf('\0', index); | ||
var string_name = data.substring(index, string_end_index); | ||
// UTF-8 decode key name | ||
// string_name = decodeURIComponent(escape(string_name)); | ||
string_name = BinaryParser.decode_utf8(string_name); | ||
// Ajust index to point to the end of the string | ||
index = string_end_index + 1; | ||
// Read the size of the object | ||
var array_size = BinaryParser.toInt(data.substr(index, 4)); | ||
// Let's split off the data and parse all elements (keeping in mind the elements) | ||
var array_data = data.substr(index, array_size); | ||
// Parse the object | ||
var value = BSON.deserialize(array_data, true); | ||
// Adjust the index for the next value | ||
index = index + array_size; | ||
if (is_array_item) { | ||
return_array[parseInt(string_name, 10)] = value; | ||
} else { | ||
return_data[string_name] = value; | ||
} | ||
break; | ||
case BSON.BSON_DATA_BOOLEAN: | ||
// Read the null terminated string (indexof until first 0) | ||
var string_end_index = data.indexOf('\0', index); | ||
var string_name = data.substring(index, string_end_index); | ||
// UTF-8 decode key name | ||
// string_name = decodeURIComponent(escape(string_name)); | ||
string_name = BinaryParser.decode_utf8(string_name); | ||
// Ajust index to point to the end of the string | ||
index = string_end_index + 1; | ||
// Read the length of the string (next 4 bytes) | ||
var boolean_value = BinaryParser.toSmall(data.substr(index, 1)); | ||
var value = 1 === boolean_value; | ||
// Adjust the index | ||
index = index + 1; | ||
// Set the data on the object | ||
if (is_array_item) { | ||
return_array[parseInt(string_name, 10)] = value; | ||
} else { | ||
return_data[string_name] = value; | ||
} | ||
break; | ||
case BSON.BSON_DATA_DATE: | ||
// Read the null terminated string (indexof until first 0) | ||
var string_end_index = data.indexOf('\0', index); | ||
var string_name = data.substring(index, string_end_index); | ||
// UTF-8 decode key name | ||
// string_name = decodeURIComponent(escape(string_name)); | ||
string_name = BinaryParser.decode_utf8(string_name); | ||
// Ajust index to point to the end of the string | ||
index = string_end_index + 1; | ||
// Read the number value | ||
var low_bits = BinaryParser.toInt(data.substr(index, 4)); | ||
var high_bits = BinaryParser.toInt(data.substr(index + 4, 4)); | ||
// Create to integers | ||
var value_in_seconds = new Long(low_bits, high_bits).toNumber(); | ||
// Calculate date with miliseconds | ||
var value = new Date(); | ||
value.setTime(value_in_seconds); | ||
// Adjust the index | ||
index = index + 8; | ||
if (is_array_item) { | ||
return_array[parseInt(string_name, 10)] = value; | ||
} else { | ||
return_data[string_name] = value; | ||
} | ||
break; | ||
case BSON.BSON_DATA_OID: | ||
// Read the null terminated string (indexof until first 0) | ||
var string_end_index = data.indexOf('\0', index); | ||
var string_name = data.substring(index, string_end_index); | ||
// UTF-8 decode key name | ||
// string_name = decodeURIComponent(escape(string_name)); | ||
string_name = BinaryParser.decode_utf8(string_name); | ||
// Ajust index to point to the end of the string | ||
index = string_end_index + 1; | ||
// Read the oid (12 bytes) | ||
var oid = data.substr(index, 12); | ||
// Calculate date with miliseconds | ||
var value = new ObjectID(oid); | ||
// Adjust the index | ||
index = index + 12; | ||
if (is_array_item) { | ||
return_array[parseInt(string_name, 10)] = value; | ||
} else { | ||
return_data[string_name] = value; | ||
} | ||
break; | ||
case BSON.BSON_DATA_CODE_W_SCOPE: | ||
// Read the null terminated string (indexof until first 0) | ||
var string_end_index = data.indexOf('\0', index); | ||
var string_name = data.substring(index, string_end_index); | ||
// UTF-8 decode key name | ||
// string_name = decodeURIComponent(escape(string_name)); | ||
string_name = BinaryParser.decode_utf8(string_name); | ||
// Ajust index to point to the end of the string | ||
index = string_end_index + 1; | ||
// Unpack the integer sizes | ||
var total_code_size = BinaryParser.toInt(data.substr(index, 4)); | ||
index = index + 4; | ||
var string_size = BinaryParser.toInt(data.substr(index, 4)); | ||
index = index + 4; | ||
// Read the string + terminating null | ||
var code_string = BinaryParser.decode_utf8(data.substr(index, string_size - 1)); | ||
index = index + string_size; | ||
// Get the bson object | ||
var bson_object_size = total_code_size - string_size - 8; | ||
var bson_object_string = data.substr(index, bson_object_size); | ||
index = index + bson_object_size; | ||
// Parse the bson object | ||
var scope_object = BSON.deserialize(bson_object_string, false); | ||
// Create code object | ||
var value = new exports.Code(code_string, scope_object); | ||
if (is_array_item) { | ||
return_array[parseInt(string_name, 10)] = value; | ||
} else { | ||
return_data[string_name] = value; | ||
} | ||
break; | ||
case BSON.BSON_DATA_REGEXP: | ||
// Read the null terminated string (indexof until first 0) | ||
var string_end_index = data.indexOf('\0', index); | ||
var string_name = data.substring(index, string_end_index); | ||
// UTF-8 decode key name | ||
// string_name = decodeURIComponent(escape(string_name)); | ||
string_name = BinaryParser.decode_utf8(string_name); | ||
// Ajust index to point to the end of the string | ||
index = string_end_index + 1; | ||
// Read characters until end of regular expression | ||
var reg_exp_array = []; | ||
var chr = 1; | ||
var start_index = index; | ||
while(BinaryParser.toByte((chr = data.charAt(index))) != 0) { | ||
++index; | ||
} | ||
// RegExp Expression | ||
reg_exp = data.substring(start_index, index); | ||
index = index + 1; | ||
// Read the options for the regular expression | ||
var options_array = []; | ||
while(BinaryParser.toByte((chr = data.charAt(index++))) != 0) { | ||
options_array.push(chr); | ||
} | ||
// Regular expression | ||
var value = new RegExp(BinaryParser.decode_utf8(reg_exp), options_array.join('')); | ||
if (is_array_item) { | ||
return_array[parseInt(string_name, 10)] = value; | ||
} else { | ||
return_data[string_name] = value; | ||
} | ||
break; | ||
case BSON.BSON_DATA_BINARY: | ||
// Read the null terminated string (indexof until first 0) | ||
var string_end_index = data.indexOf('\0', index); | ||
var string_name = data.substring(index, string_end_index); | ||
// UTF-8 decode key name | ||
string_name = BinaryParser.decode_utf8(string_name); | ||
// Ajust index to point to the end of the string | ||
index = string_end_index + 1; | ||
// Read the size of the binary | ||
var number_of_bytes = BinaryParser.toInt(data.substr(index, 4)); | ||
index = index + 4; | ||
// Decode the subtype | ||
var sub_type = BinaryParser.toByte(data.substr(index, 1)); | ||
index = index + 1; | ||
// Binary object | ||
var value = new Binary(); | ||
value.sub_type = sub_type; | ||
// Read the next bytes into our Binary object | ||
var bin_data = data.substr(index, number_of_bytes); | ||
value.write(bin_data); | ||
// Adjust index with number of bytes | ||
index = index + number_of_bytes; | ||
if (is_array_item) { | ||
return_array[parseInt(string_name, 10)] = value; | ||
} else { | ||
return_data[string_name] = value; | ||
} | ||
break; | ||
} | ||
} | ||
// Check if we have a db reference | ||
if(!is_array_item && return_data['$ref'] != null) { | ||
return_data = new exports.DBRef(return_data['$ref'], return_data['$id'], return_data['$db']); | ||
if(!is_array_item && null != return_data['$ref']) { | ||
return_data = new exports.DBRef( return_data['$ref'] | ||
, return_data['$id'] | ||
, return_data['$db']); | ||
} | ||
// Return the data | ||
return is_array_item ? return_array : return_data; | ||
return is_array_item | ||
? return_array | ||
: return_data; | ||
}; | ||
BSON.encodeString = function(string) { | ||
/** | ||
* Encode a string as BSON. | ||
* | ||
* @param {TODO} string | ||
* @return {TODO} | ||
*/ | ||
BSON.encodeString = function encodeString (string) { | ||
var encodedString = BinaryParser.encode_cstring(string); | ||
@@ -328,18 +534,50 @@ // Encode the string as binary with the length | ||
BSON.encodeInt = function(number) { | ||
return BinaryParser.fromInt(number.toInt()); | ||
/** | ||
* Encode an Int as BSON. | ||
* | ||
* @param {TODO} number | ||
* @return {TODO} | ||
*/ | ||
BSON.encodeInt = function encodeInt (number) { | ||
return BinaryParser.fromInt(number); | ||
}; | ||
BSON.encodeLong = function(number) { | ||
return BinaryParser.fromInt(number.getLowBits()) + BinaryParser.fromInt(number.getHighBits()); | ||
/** | ||
* Encode a Long as BSON. | ||
* | ||
* @param {TODO} number | ||
* @return {TODO} | ||
*/ | ||
BSON.encodeLong = function encodeLong (number) { | ||
return BinaryParser.fromInt(number.getLowBits()) | ||
+ BinaryParser.fromInt(number.getHighBits()); | ||
}; | ||
BSON.encodeFloat = function(number) { | ||
/** | ||
* Encode a Float as BSON. | ||
* | ||
* @param {TODO} number | ||
* @return {TODO} | ||
*/ | ||
BSON.encodeFloat = function encodeFloat (number) { | ||
return BinaryParser.fromDouble(number); | ||
}; | ||
BSON.encodeArray = function(array, checkKeys) { | ||
/** | ||
* Encode an array as BSON. | ||
* | ||
* @param {TODO} array | ||
* @param {Bool} checkKeys - TODO | ||
* @return {TODO} | ||
*/ | ||
BSON.encodeArray = function encodeArray (array, checkKeys) { | ||
var encoded_string = ''; | ||
var index = 0; | ||
var len = array.length; | ||
for(var index = 0; index < array.length; index++) { | ||
for(; index < len; ++index) { | ||
var index_string = new String(index) + BinaryParser.fromByte(0); | ||
@@ -353,130 +591,290 @@ var encoded_object = BSON.encodeValue('', null, array[index], false, checkKeys); | ||
BSON.encodeObject = function(object, checkKeys) { | ||
/** | ||
* Encode an object as BSON. | ||
* | ||
* @param {TODO} object | ||
* @param {Bool} checkKeys - TODO | ||
* @return {TODO} | ||
*/ | ||
BSON.encodeObject = function encodeObject (object, checkKeys) { | ||
var encoded_string = ''; | ||
// Let's fetch all the variables for the object and encode each | ||
for(var variable in object) { | ||
if(object[variable] == null || (object[variable] != null && object[variable].constructor != Function)) { | ||
encoded_string += BSON.encodeValue('', variable, object[variable], false, checkKeys); | ||
var keys = Object.keys(object); | ||
var len = keys.length; | ||
var key, val; | ||
for (var i = 0; i < len; ++i) { | ||
key = keys[i]; | ||
val = object[key]; | ||
if(null == val || (val != null && val.constructor != Function)) { | ||
encoded_string += BSON.encodeValue('', key, val, false, checkKeys); | ||
} | ||
} | ||
// Return the encoded string | ||
return encoded_string; | ||
}; | ||
BSON.encodeBoolean = function(value) { | ||
return value ? BinaryParser.fromSmall(1) : BinaryParser.fromSmall(0); | ||
/** | ||
* Encode a boolean as BSON. | ||
* | ||
* @param {TODO} bool | ||
* @return {TODO} | ||
*/ | ||
BSON.encodeBoolean = function encodeBoolean (bool) { | ||
return BinaryParser.fromSmall(bool ? 1 : 0); | ||
}; | ||
BSON.encodeDate = function(value) { | ||
var dateInMilis = Long.fromNumber(value.getTime()); | ||
return BinaryParser.fromInt(dateInMilis.getLowBits()) + BinaryParser.fromInt(dateInMilis.getHighBits()); | ||
/** | ||
* Encode a date as BSON. | ||
* | ||
* @param {TODO} date | ||
* @return {TODO} | ||
*/ | ||
BSON.encodeDate = function encodeDate (date) { | ||
var dateInMilis = Long.fromNumber(date.getTime()); | ||
return BinaryParser.fromInt(dateInMilis.getLowBits()) | ||
+ BinaryParser.fromInt(dateInMilis.getHighBits()); | ||
}; | ||
BSON.encodeOid = function(oid) { | ||
/** | ||
* Encode a ObjectId as BSON. | ||
* | ||
* @param {ObjectId} oid | ||
* @return {TODO} | ||
*/ | ||
BSON.encodeOid = function encodeOid (oid) { | ||
return oid.id; | ||
}; | ||
BSON.encodeCode = function(code, checkKeys) { | ||
// Get the code_string | ||
/** | ||
* Encode a CodeString as BSON. | ||
* | ||
* @param {TODO} code | ||
* @param {Bools} checkKeys | ||
* @return {TODO} | ||
*/ | ||
BSON.encodeCode = function encodeCode (code, checkKeys) { | ||
var code_string = BSON.encodeString(code.code); | ||
var scope = BinaryParser.fromInt(5) + BinaryParser.fromByte(0); | ||
// Encode the scope (a hash of values or ordered hash) | ||
if(code.scope != null) { | ||
if(null != code.scope) { | ||
scope = BSON.encodeValue('', null, code.scope, false, checkKeys); | ||
scope = scope.substring(1, scope.length); | ||
} | ||
// Calculate lengths | ||
var total_length = code_string.length - 4 + scope.length + 8; | ||
return BinaryParser.fromInt(total_length) + code_string + scope; | ||
}; | ||
BSON.encodeRegExp = function(regexp) { | ||
// Get regular expression | ||
var clean_regexp = regexp.toString().match(/\/.*\//, ''); | ||
/** | ||
* Encode a RegExp as BSON. | ||
* | ||
* @param {RegExp} regexp | ||
* @return {TODO} | ||
*/ | ||
BSON.encodeRegExp = function encodeRegExp (regexp) { | ||
var options_array = []; | ||
var str = regexp.toString(); | ||
var clean_regexp = str.match(/\/.*\//, ''); | ||
clean_regexp = clean_regexp[0].substring(1, clean_regexp[0].length - 1); | ||
var options = regexp.toString().substr(clean_regexp.length + 2); | ||
var options_array = []; | ||
var options = str.substr(clean_regexp.length + 2); | ||
// Extract all options that are legal and sort them alphabetically | ||
for(var index = 0; index < options.length; index++) { | ||
for(var index = 0, len = options.length; index < len; ++index) { | ||
var chr = options.charAt(index); | ||
if(chr == 'i' || chr == 'm' || chr == 'x') options_array.push(chr); | ||
if('i' == chr || 'm' == chr || 'x' == chr) { | ||
options_array.push(chr); | ||
} | ||
} | ||
// Don't need to sort the alphabetically as it's already done by javascript on creation of a Regexp obejct | ||
options = options_array.join(''); | ||
// Encode the regular expression | ||
return BinaryParser.encode_cstring(clean_regexp) + BinaryParser.encode_cstring(options); | ||
return BinaryParser.encode_cstring(clean_regexp) | ||
+ BinaryParser.encode_cstring(options); | ||
}; | ||
BSON.encodeBinary = function(binary) { | ||
/** | ||
* Encode a binary as BSON. | ||
* | ||
* @param {TODO} binary | ||
* @return {TODO} | ||
*/ | ||
BSON.encodeBinary = function encodeBinary (binary) { | ||
var data = binary.value(); | ||
return BinaryParser.fromByte(binary.sub_type) + BinaryParser.fromInt(data.length) + data; | ||
return BinaryParser.fromInt(data.length) + BinaryParser.fromByte(binary.sub_type) + data; | ||
}; | ||
BSON.encodeDBRef = function(dbref) { | ||
var ordered_values = {'$ref':dbref.namespace, '$id':dbref.oid}; | ||
if(dbref.db != null) ordered_values['$db'] = dbref.db; | ||
/** | ||
* Encode a DBRef as BSON. | ||
* | ||
* @param {TODO} dbref | ||
* @return {TODO} | ||
*/ | ||
BSON.encodeDBRef = function encodeDBRef (dbref) { | ||
var ordered_values = { | ||
'$ref': dbref.namespace | ||
, '$id' : dbref.oid | ||
}; | ||
if(null != dbref.db) { | ||
ordered_values['$db'] = dbref.db; | ||
} | ||
return BSON.encodeObject(ordered_values); | ||
}; | ||
BSON.checkKey = function(variable) { | ||
/** | ||
* Check if key name is valid. | ||
* | ||
* @param {TODO} key | ||
*/ | ||
BSON.checkKey = function checkKey (key) { | ||
if (!key.length) return; | ||
// Check if we have a legal key for the object | ||
if(variable.length > 0 && variable.substr(0, 1) == '$') { | ||
throw Error("key " + variable + " must not start with '$'"); | ||
} else if(variable.length > 0 && variable.indexOf('.') != -1) { | ||
throw Error("key " + variable + " must not contain '.'"); | ||
if ('$' == key[0]) { | ||
throw Error("key " + key + " must not start with '$'"); | ||
} else if (!!~key.indexOf('.')) { | ||
throw Error("key " + key + " must not contain '.'"); | ||
} | ||
}; | ||
BSON.toLong = function(low_bits, high_bits) { | ||
var low_bits = Integer.fromInt(low_bits); | ||
var high_bits = Integer.fromInt(high_bits); | ||
/** | ||
* Convert bits to Long. | ||
* | ||
* @param {int} low_bits | ||
* @param {int} high_bits | ||
* @return {Long} | ||
*/ | ||
BSON.toLong = function toLong (low_bits, high_bits) { | ||
// var low_bits = Integer.fromInt(low_bits); | ||
// var high_bits = Integer.fromInt(high_bits); | ||
return new Long(low_bits, high_bits); | ||
} | ||
}; | ||
BSON.toInt = function(value) { | ||
return Integer.fromNumber(value).toInt(); | ||
} | ||
/** | ||
* Converts value to an Integer. | ||
* | ||
* @param {value} | ||
* @return {Integer} | ||
*/ | ||
BSON.encodeValue = function(encoded_string, variable, value, top_level, checkKeys) { | ||
var variable_encoded = variable == null ? '' : BinaryParser.encode_cstring(variable); | ||
if(checkKeys && variable != null)BSON.checkKey(variable); | ||
BSON.toInt = function toInt (value) { | ||
return Math.floor(value); | ||
}; | ||
/** | ||
* Encodes value as BSON. | ||
* | ||
* @param {TODO} encoded_string | ||
* @param {TODO} variable | ||
* @param {TODO} value | ||
* @param {TODO} top_level | ||
* @param {TODO} checkKeys | ||
* @return {String} | ||
*/ | ||
BSON.encodeValue = function encodeValue (encoded_string, variable, value, top_level, checkKeys) { | ||
var toString = Object.prototype.toString; | ||
var variable_encoded = variable == null | ||
? '' | ||
: BinaryParser.encode_cstring(variable); | ||
if(checkKeys && variable != null) { | ||
BSON.checkKey(variable); | ||
} | ||
if(value == null) { | ||
encoded_string += BinaryParser.fromByte(BSON.BSON_DATA_NULL) + variable_encoded; | ||
encoded_string += BinaryParser.fromByte(BSON.BSON_DATA_NULL) | ||
+ variable_encoded; | ||
} else if(value.constructor == String) { | ||
encoded_string += BinaryParser.fromByte(BSON.BSON_DATA_STRING) + variable_encoded + BSON.encodeString(value); | ||
} else if(value instanceof Timestamp || Object.prototype.toString.call(value) == "[object Timestamp]") { | ||
encoded_string += BinaryParser.fromByte(BSON.BSON_DATA_TIMESTAMP) + variable_encoded + BSON.encodeLong(value); | ||
} else if(value instanceof Long || Object.prototype.toString.call(value) == "[object Long]") { | ||
encoded_string += BinaryParser.fromByte(BSON.BSON_DATA_LONG) + variable_encoded + BSON.encodeLong(value); | ||
encoded_string += BinaryParser.fromByte(BSON.BSON_DATA_STRING) | ||
+ variable_encoded | ||
+ BSON.encodeString(value); | ||
} else if(value instanceof Timestamp || toString.call(value) == "[object Timestamp]") { | ||
encoded_string += BinaryParser.fromByte(BSON.BSON_DATA_TIMESTAMP) | ||
+ variable_encoded | ||
+ BSON.encodeLong(value); | ||
} else if(value instanceof Long || toString.call(value) == "[object Long]") { | ||
encoded_string += BinaryParser.fromByte(BSON.BSON_DATA_LONG) | ||
+ variable_encoded | ||
+ BSON.encodeLong(value); | ||
} else if(value.constructor == Number && value === parseInt(value, 10)) { | ||
if(value > BSON.BSON_INT32_MAX || value < BSON.BSON_INT32_MIN) { | ||
encoded_string += BinaryParser.fromByte(BSON.BSON_DATA_LONG) + variable_encoded + BSON.encodeLong(Long.fromNumber(value)); | ||
if(value > BSON.BSON_INT32_MAX || value < BSON.BSON_INT32_MIN) { | ||
encoded_string += BinaryParser.fromByte(BSON.BSON_DATA_LONG) | ||
+ variable_encoded | ||
+ BSON.encodeLong(Long.fromNumber(value)); | ||
} else { | ||
encoded_string += BinaryParser.fromByte(BSON.BSON_DATA_INT) + variable_encoded + BSON.encodeInt(Integer.fromInt(value)); | ||
} | ||
encoded_string += BinaryParser.fromByte(BSON.BSON_DATA_INT) | ||
+ variable_encoded | ||
+ BSON.encodeInt(value); | ||
} | ||
} else if(value.constructor == Number) { | ||
encoded_string += BinaryParser.fromByte(BSON.BSON_DATA_NUMBER) + variable_encoded + BSON.encodeFloat(value); | ||
encoded_string += BinaryParser.fromByte(BSON.BSON_DATA_NUMBER) | ||
+ variable_encoded | ||
+ BSON.encodeFloat(value); | ||
} else if(Array.isArray(value)) { | ||
var object_string = BSON.encodeArray(value, checkKeys); | ||
encoded_string += BinaryParser.fromByte(BSON.BSON_DATA_ARRAY) + variable_encoded + BSON.encodeInt(Integer.fromInt(object_string.length + 4 + 1)) + object_string + BinaryParser.fromByte(0); | ||
} else if(Object.prototype.toString.call(value) === '[object Boolean]') { | ||
encoded_string += BinaryParser.fromByte(BSON.BSON_DATA_BOOLEAN) + variable_encoded + BSON.encodeBoolean(value); | ||
} else if(Object.prototype.toString.call(value) === '[object Date]') { | ||
encoded_string += BinaryParser.fromByte(BSON.BSON_DATA_DATE) + variable_encoded + BSON.encodeDate(value); | ||
} else if(Object.prototype.toString.call(value) === '[object RegExp]') { | ||
encoded_string += BinaryParser.fromByte(BSON.BSON_DATA_REGEXP) + variable_encoded + BSON.encodeRegExp(value); | ||
} else if(value instanceof ObjectID || (value.id && value.toHexString) || Object.prototype.toString.call(value) == "[object ObjectID]") { | ||
encoded_string += BinaryParser.fromByte(BSON.BSON_DATA_OID) + variable_encoded + BSON.encodeOid(value); | ||
} else if(value instanceof Code || Object.prototype.toString.call(value) == "[object Code]") { | ||
encoded_string += BinaryParser.fromByte(BSON.BSON_DATA_CODE_W_SCOPE) + variable_encoded + BSON.encodeCode(value, checkKeys); | ||
} else if(value instanceof Binary || Object.prototype.toString.call(value) == "[object Binary]") { | ||
var object_string = BSON.encodeBinary(value); | ||
encoded_string += BinaryParser.fromByte(BSON.BSON_DATA_BINARY) + variable_encoded + BSON.encodeInt(Integer.fromInt(object_string.length - 1)) + object_string; | ||
encoded_string += BinaryParser.fromByte(BSON.BSON_DATA_ARRAY) | ||
+ variable_encoded | ||
+ BSON.encodeInt(object_string.length + 4 + 1) | ||
+ object_string | ||
+ BinaryParser.fromByte(0); | ||
} else if(toString.call(value) === '[object Boolean]') { | ||
encoded_string += BinaryParser.fromByte(BSON.BSON_DATA_BOOLEAN) | ||
+ variable_encoded | ||
+ BSON.encodeBoolean(value); | ||
} else if(toString.call(value) === '[object Date]') { | ||
encoded_string += BinaryParser.fromByte(BSON.BSON_DATA_DATE) | ||
+ variable_encoded | ||
+ BSON.encodeDate(value); | ||
} else if(toString.call(value) === '[object RegExp]') { | ||
encoded_string += BinaryParser.fromByte(BSON.BSON_DATA_REGEXP) | ||
+ variable_encoded | ||
+ BSON.encodeRegExp(value); | ||
} else if(value instanceof ObjectID || | ||
(value.id && value.toHexString) || | ||
toString.call(value) === '[object ObjectID]') { | ||
encoded_string += BinaryParser.fromByte(BSON.BSON_DATA_OID) | ||
+ variable_encoded | ||
+ BSON.encodeOid(value); | ||
} else if(value instanceof Code || toString.call(value) == "[object Code]") { | ||
encoded_string += BinaryParser.fromByte(BSON.BSON_DATA_CODE_W_SCOPE) | ||
+ variable_encoded | ||
+ BSON.encodeCode(value, checkKeys); | ||
} else if(value instanceof Binary || toString.call(value) == "[object Binary]") { | ||
encoded_string += BinaryParser.fromByte(BSON.BSON_DATA_BINARY) | ||
+ variable_encoded | ||
+ BSON.encodeBinary(value); | ||
} else if(value instanceof DBRef) { | ||
var object_string = BSON.encodeDBRef(value); | ||
encoded_string += BinaryParser.fromByte(BSON.BSON_DATA_OBJECT) + variable_encoded + BSON.encodeInt(Integer.fromInt(object_string.length + 4 + 1)) + object_string + BinaryParser.fromByte(0); | ||
} else if(Object.prototype.toString.call(value) === '[object Object]') { | ||
encoded_string += BinaryParser.fromByte(BSON.BSON_DATA_OBJECT) | ||
+ variable_encoded | ||
+ BSON.encodeInt(object_string.length + 4 + 1) | ||
+ object_string | ||
+ BinaryParser.fromByte(0); | ||
} else if(toString.call(value) === '[object Object]') { | ||
var object_string = BSON.encodeObject(value, checkKeys); | ||
encoded_string += (!top_level ? BinaryParser.fromByte(BSON.BSON_DATA_OBJECT) : '') + variable_encoded + BSON.encodeInt(Integer.fromInt(object_string.length + 4 + 1)) + object_string + BinaryParser.fromByte(0); | ||
encoded_string += (!top_level ? BinaryParser.fromByte(BSON.BSON_DATA_OBJECT) : '') | ||
+ variable_encoded | ||
+ BSON.encodeInt(object_string.length + 4 + 1) | ||
+ object_string | ||
+ BinaryParser.fromByte(0); | ||
} | ||
@@ -487,3 +885,10 @@ | ||
var Code = exports.Code = function(code, scope) { | ||
/** | ||
* Code constructor. | ||
* | ||
* @param {TODO} code | ||
* @param {TODO} scope | ||
*/ | ||
function Code (code, scope) { | ||
this.code = code; | ||
@@ -494,85 +899,10 @@ this.scope = scope == null ? {} : scope; | ||
/** | ||
Object ID used to create object id's for the mongo requests | ||
**/ | ||
var ObjectID = exports.ObjectID = function(id) { | ||
if(id == null) this.id = this.generate(); | ||
else if( /^[0-9a-fA-F]{24}$/.test(id)) return ObjectID.createFromHexString(id); | ||
else this.id = id; | ||
}; | ||
* DBRef constructor. | ||
* | ||
* @param {TODO} namespace | ||
* @param {TODO} oid | ||
* @param {TODO} db | ||
*/ | ||
ObjectID.prototype.get_inc = function() { | ||
exports.ObjectID.index = (exports.ObjectID.index + 1) % 0xFFFFFF; | ||
return exports.ObjectID.index; | ||
}; | ||
/* Find the machine id. */ | ||
var MACHINE_ID = (function() { | ||
// Create a random 3-byte value (i.e. unique for this | ||
// process). Other drivers use a md5 of the machine id here, but | ||
// that would mean an asyc call to gethostname, so we don't bother. | ||
var rnd = parseInt(Math.random() * 0xffffff); | ||
return rnd; | ||
})(); | ||
ObjectID.prototype.generate = function() { | ||
var unixTime = parseInt((new Date()).getTime()/1000); | ||
var time4Bytes = BinaryParser.encodeInt(unixTime, 32, true, true); | ||
var machine3Bytes = BinaryParser.encodeInt(MACHINE_ID, 24, false); | ||
var pid2Bytes = BinaryParser.fromShort(process.pid); | ||
var index3Bytes = BinaryParser.encodeInt(this.get_inc(), 24, false, true); | ||
return time4Bytes + machine3Bytes + pid2Bytes + index3Bytes; | ||
}; | ||
ObjectID.prototype.toHexString = function() { | ||
var hexString = ''; | ||
for(var index = 0; index < this.id.length; index++) { | ||
var value = BinaryParser.toByte(this.id.substr(index, 1)); | ||
var number = value <= 15 ? "0" + value.toString(16) : value.toString(16); | ||
hexString = hexString + number; | ||
} | ||
return hexString; | ||
}; | ||
ObjectID.prototype.toString = function() { | ||
return this.toHexString(); | ||
}; | ||
ObjectID.prototype.inspect = function() { | ||
return this.toHexString(); | ||
} | ||
// accurate up to number of seconds | ||
ObjectID.prototype.__defineGetter__("generationTime", function() { | ||
return BinaryParser.decodeInt(this.id.substring(0,4), 32, true, true) * 1000; | ||
}) | ||
ObjectID.index = 0; | ||
ObjectID.createPk = function() { | ||
return new exports.ObjectID(); | ||
}; | ||
ObjectID.createFromHexString= function(hexString) { | ||
if(hexString.length > 12*2) throw "Id cannot be longer than 12 bytes"; | ||
var result= ""; | ||
for(var index=0 ; index < hexString.length; index+=2) { | ||
var string= hexString.substr(index, 2); | ||
var number= parseInt(string, 16); | ||
result+= BinaryParser.fromByte(number); | ||
} | ||
return new exports.ObjectID(result); | ||
}; | ||
ObjectID.prototype.toJSON = function() { | ||
return this.toHexString(); | ||
} | ||
ObjectID.prototype.equals = function(otherID) { | ||
return this.id == ((otherID instanceof ObjectID || otherID.toHexString) ? otherID.id : ObjectID.createFromHexString(otherID).id); | ||
} | ||
/** | ||
DBRef contains a db reference | ||
**/ | ||
var DBRef = exports.DBRef = function(namespace, oid, db) { | ||
function DBRef (namespace, oid, db) { | ||
this.namespace = namespace; | ||
@@ -583,63 +913,20 @@ this.oid = oid; | ||
DBRef.prototype.toJSON = function() { | ||
return JSON.stringify({ | ||
'$ref':this.namespace, | ||
'$id':this.oid, | ||
'$db':this.db == null ? '' : this.db | ||
}); | ||
} | ||
/** | ||
Contains the a binary stream of data | ||
**/ | ||
var Binary = exports.Binary = function(buffer) { | ||
this.sub_type = BSON.BSON_BINARY_SUBTYPE_BYTE_ARRAY; | ||
// Create a default sized binary in case non is passed in and prepare the size | ||
if(buffer) { | ||
this.buffer = buffer; | ||
this.position = buffer.length; | ||
} else { | ||
this.buffer = new Buffer(Binary.BUFFER_SIZE); | ||
this.position = 0; | ||
} | ||
}; | ||
* Expose. | ||
*/ | ||
Binary.BUFFER_SIZE = 256; | ||
Binary.prototype.put = function(byte_value) { | ||
if(this.buffer.length > this.position) { | ||
this.buffer[this.position++] = byte_value.charCodeAt(0); | ||
} else { | ||
// Create additional overflow buffer | ||
var buffer = new Buffer(Binary.BUFFER_SIZE + this.buffer.length); | ||
// Combine the two buffers together | ||
this.buffer.copy(buffer, 0, 0, this.buffer.length); | ||
this.buffer = buffer; | ||
this.buffer[this.position++] = byte_value.charCodeAt(0); | ||
} | ||
}; | ||
Binary.prototype.write = function(string, offset) { | ||
offset = offset ? offset : this.position; | ||
// If the buffer is to small let's extend the buffer | ||
if(this.buffer.length < offset + string.length) { | ||
var buffer = new Buffer(this.buffer.length + string.length); | ||
this.buffer.copy(buffer, 0, 0, this.buffer.length); | ||
// Assign the new buffer | ||
this.buffer = buffer; | ||
} | ||
// Write the content to the buffer | ||
if(string instanceof Buffer) { | ||
string.copy(this.buffer, offset, 0, string.length); | ||
} | ||
else { | ||
this.buffer.write(string, 'binary', offset); | ||
} | ||
this.position = offset + string.length; | ||
}; | ||
Binary.prototype.read = function(position, length) { | ||
length = length && length > 0 ? length : this.position; | ||
// Let's extract the string we want to read | ||
return this.buffer.toString('binary', position, position + length); | ||
}; | ||
Binary.prototype.value = function() { | ||
return this.buffer.toString('binary', 0, this.position); | ||
}; | ||
Binary.prototype.length = function() { | ||
return this.position; | ||
}; | ||
exports.Code = Code; | ||
exports.BSON = BSON; | ||
exports.DBRef = DBRef; | ||
exports.Binary = Binary; | ||
exports.ObjectID = ObjectID; | ||
exports.Long = Long; | ||
exports.Timestamp = Timestamp; |
@@ -1,28 +0,43 @@ | ||
var InsertCommand = require('./commands/insert_command').InsertCommand, | ||
QueryCommand = require('./commands/query_command').QueryCommand, | ||
DeleteCommand = require('./commands/delete_command').DeleteCommand, | ||
UpdateCommand = require('./commands/update_command').UpdateCommand, | ||
DbCommand = require('./commands/db_command').DbCommand, | ||
BinaryParser = require('./bson/binary_parser').BinaryParser, | ||
Cursor = require('./cursor').Cursor; | ||
/** | ||
* Module dependencies. | ||
*/ | ||
var InsertCommand = require('./commands/insert_command').InsertCommand | ||
, QueryCommand = require('./commands/query_command').QueryCommand | ||
, DeleteCommand = require('./commands/delete_command').DeleteCommand | ||
, UpdateCommand = require('./commands/update_command').UpdateCommand | ||
, DbCommand = require('./commands/db_command').DbCommand | ||
, BinaryParser = require('./bson/binary_parser').BinaryParser | ||
, Cursor = require('./cursor').Cursor | ||
, debug = require('util').debug | ||
, inspect = require('util').inspect; | ||
/** | ||
* Sort functions, Normalize and prepare sort parameters | ||
*/ | ||
/** | ||
Sort functions, Normalize and prepare sort parameters | ||
**/ | ||
var formatSortValue = function(sortDirection) { | ||
function formatSortValue (sortDirection) { | ||
var value = ("" + sortDirection).toLowerCase(); | ||
if(value == 'ascending' || value == 'asc' || value == 1) return 1; | ||
if(value == 'descending' || value == 'desc' || value == -1 ) return -1; | ||
throw new Error("Illegal sort clause, must be of the form " + | ||
"[['field1', '(ascending|descending)'], ['field2', '(ascending|descending)']]"); | ||
switch (value) { | ||
case 'ascending': | ||
case 'asc': | ||
case '1': | ||
return 1; | ||
case 'descending': | ||
case 'desc': | ||
case '-1': | ||
return -1; | ||
default: | ||
throw new Error("Illegal sort clause, must be of the form " | ||
+ "[['field1', '(ascending|descending)'], " | ||
+ "['field2', '(ascending|descending)']]"); | ||
} | ||
}; | ||
var formattedOrderClause = function(sortValue) { | ||
function formattedOrderClause (sortValue) { | ||
var orderBy = {}; | ||
var self = this; | ||
if(Array.isArray(sortValue)) { | ||
sortValue.forEach(function(sortElement) { | ||
if(sortElement.constructor == String) { | ||
if (Array.isArray(sortValue)) { | ||
sortValue.forEach(function (sortElement) { | ||
if (sortElement.constructor == String) { | ||
orderBy[sortElement] = 1; | ||
@@ -33,3 +48,3 @@ } else { | ||
}); | ||
} else if(sortValue.constructor == String) { | ||
} else if (sortValue.constructor == String) { | ||
orderBy[sortValue] = 1; | ||
@@ -40,2 +55,3 @@ } else { | ||
} | ||
return orderBy; | ||
@@ -45,158 +61,255 @@ }; | ||
/** | ||
Handles all the operations on objects in collections | ||
**/ | ||
var Collection = exports.Collection = function(db, collectionName, pkFactory) { | ||
* toString helper. | ||
*/ | ||
var toString = Object.prototype.toString; | ||
/** | ||
* Collection constructor. | ||
* | ||
* @param {Database} db | ||
* @param {String} collectionName | ||
* @param {Function} pkFactory | ||
*/ | ||
function Collection (db, collectionName, pkFactory, options) { | ||
this.checkCollectionName(collectionName); | ||
this.db = db; | ||
this.collectionName = collectionName; | ||
this.internalHint; | ||
this.pkFactory = pkFactory == null ? db.bson_serializer.ObjectID : pkFactory; | ||
// Add getter and setters | ||
this.__defineGetter__("hint", function() { return this.internalHint; }); | ||
this.__defineSetter__("hint", function(value) { this.internalHint = this.normalizeHintField(value); }); | ||
// Ensure the collection name is not illegal | ||
this.checkCollectionName(collectionName); | ||
this.opts = options != null && ('object' === typeof options) ? options : {}; | ||
this.slaveOk = options == null || options.slaveOk == null ? db.slaveOk : options.slaveOk; | ||
// debug("======================================================== options") | ||
// debug(inspect(options)) | ||
this.pkFactory = pkFactory == null | ||
? db.bson_serializer.ObjectID | ||
: pkFactory; | ||
Object.defineProperty(this, "hint", { | ||
enumerable: true | ||
, get: function () { | ||
return this.internalHint; | ||
} | ||
, set: function (v) { | ||
this.internalHint = this.normalizeHintField(v); | ||
} | ||
}); | ||
}; | ||
Collection.prototype.insert = function(docs, options, callback) { | ||
var args = Array.prototype.slice.call(arguments, 1); | ||
callback = args.pop(); | ||
if(Object.prototype.toString.call(callback) != '[object Function]') { | ||
args.push(callback) | ||
callback = null | ||
} | ||
options = args.length ? args.shift() : {}; | ||
/** | ||
* Inserts `docs` into the db. | ||
* | ||
* @param {Array|Object} docs | ||
* @param {Object} options (optional) | ||
* @param {Function} callback (optional) | ||
* @return {Collection} | ||
*/ | ||
Collection.prototype.insert = function insert (docs, options, callback) { | ||
if ('function' === typeof options) callback = options, options = {}; | ||
this.insertAll(Array.isArray(docs) ? docs : [docs], options, callback); | ||
return this; | ||
}; | ||
Collection.prototype.checkCollectionName = function(collectionName) { | ||
if (typeof collectionName != 'string') | ||
/** | ||
* Checks if `collectionName` is valid. | ||
* | ||
* @param {String} collectionName | ||
*/ | ||
Collection.prototype.checkCollectionName = function checkCollectionName (collectionName) { | ||
if ('string' !== typeof collectionName) { | ||
throw Error("collection name must be a String"); | ||
} | ||
if (!collectionName || collectionName.indexOf('..') != -1) | ||
if (!collectionName || collectionName.indexOf('..') != -1) { | ||
throw Error("collection names cannot be empty"); | ||
} | ||
if (collectionName.indexOf('$') != -1 && collectionName.match(/((^\$cmd)|(oplog\.\$main))/) == null) | ||
if (collectionName.indexOf('$') != -1 && | ||
collectionName.match(/((^\$cmd)|(oplog\.\$main))/) == null) { | ||
throw Error("collection names must not contain '$'"); | ||
} | ||
if (collectionName.match(/^\.|\.$/) != null) | ||
if (collectionName.match(/^\.|\.$/) != null) { | ||
throw Error("collection names must not start or end with '.'"); | ||
} | ||
}; | ||
Collection.prototype.remove = function(selector, options, callback) { | ||
var args = Array.prototype.slice.call(arguments, 0); | ||
callback = args.pop(); | ||
var removeSelector = args.length ? args.shift() : {}; | ||
options = args.length ? args.shift() : {}; | ||
/** | ||
* Removes documents specified by `selector` from the db. | ||
* @param {Object} selector (optional) | ||
* @param {Object} options (optional) | ||
* @param {Function} callback (optional) | ||
*/ | ||
// Generate selector for remove all if not available | ||
var deleteCommand = new DeleteCommand(this.db, this.db.databaseName + "." + this.collectionName, removeSelector); | ||
Collection.prototype.remove = function remove (selector, options, callback) { | ||
if ('function' === typeof selector) { | ||
callback = selector; | ||
selector = options = {}; | ||
} else if ('function' === typeof options) { | ||
callback = options; | ||
options = {}; | ||
} | ||
var deleteCommand = new DeleteCommand( | ||
this.db | ||
, this.db.databaseName + "." + this.collectionName | ||
, selector); | ||
// Execute the command, do not add a callback as it's async | ||
var result = this.db.executeCommand(deleteCommand); | ||
if(result instanceof Error) { | ||
if(callback != null) return callback(result, null); | ||
} | ||
// If safe mode check last error | ||
if(callback != null) { | ||
if(options.safe || this.db.strict) { | ||
this.db.error(function(err, error) { | ||
if(error[0].err) { | ||
callback(new Error(error[0].err), null); | ||
} else { | ||
callback(null, null); | ||
} | ||
}); | ||
} else { | ||
// Callback with no error check | ||
callback(null, this); | ||
if (options && options.safe || this.opts.safe != null || this.db.strict) { | ||
var errorOptions = options.safe != null ? options.safe : null; | ||
errorOptions = errorOptions == null && this.opts.safe != null ? this.opts.safe : errorOptions; | ||
errorOptions = errorOptions == null && this.db.strict != null ? this.db.strict : errorOptions; | ||
// Execute command with safe options (rolls up both command and safe command into one and executes them on the same connection) | ||
this.db.executeCommand(deleteCommand, {read:false, safe: errorOptions}, function (err, error) { | ||
error = error && error.documents; | ||
if(!callback) return; | ||
if (err) { | ||
callback(err); | ||
} else if (error[0].err) { | ||
callback(new Error(error[0].err)); | ||
} else { | ||
callback(null, error[0].n); | ||
} | ||
}); | ||
} else { | ||
var result = this.db.executeCommand(deleteCommand); | ||
// If no callback just return | ||
if (!callback) return; | ||
// If error return error | ||
if (result instanceof Error) { | ||
return callback(result); | ||
} | ||
// Otherwise just return | ||
return callback(); | ||
} | ||
}; | ||
Collection.prototype.rename = function(collectionName, callback) { | ||
/** | ||
* Renames the collection. | ||
* | ||
* @param {String} newName | ||
* @param {Function} callback | ||
*/ | ||
Collection.prototype.rename = function rename (newName, callback) { | ||
var self = this; | ||
try { | ||
this.checkCollectionName(collectionName); | ||
this.db.renameCollection(this.collectionName, collectionName, function(err, result) { | ||
if(err != null || result.documents[0].ok == 0) { | ||
err != null ? callback(err, null) : callback(new Error(result.documents[0].errmsg), null); | ||
this.checkCollectionName(newName); | ||
this.db.renameCollection(this.collectionName, newName, function (err, result) { | ||
if (err) { | ||
callback(err); | ||
} else if (result.documents[0].ok == 0) { | ||
callback(new Error(result.documents[0].errmsg)); | ||
} else { | ||
// Set collectionname to new one and return the collection | ||
self.db.collection(collectionName, callback); | ||
self.db.collection(newName, callback); | ||
} | ||
}); | ||
} catch(err) { | ||
callback(err, null); | ||
} catch (err) { | ||
callback(err); | ||
} | ||
}; | ||
Collection.prototype.insertAll = function(docs, options, callback) { | ||
var self = this; | ||
var error= null; | ||
var args = Array.prototype.slice.call(arguments, 1); | ||
callback = args.pop(); | ||
/** | ||
* Insert many docs into the db. | ||
* | ||
* @param {Array} docs | ||
* @param {Object} options (optional) | ||
* @param {Function} callback (optional) | ||
*/ | ||
if(Object.prototype.toString.call(callback) != '[object Function]') { | ||
args.push(callback) | ||
callback = null | ||
} | ||
Collection.prototype.insertAll = function insertAll (docs, options, callback) { | ||
if ('function' === typeof options) callback = options, options = {}; | ||
options = args.length ? args.shift() : {}; | ||
var insertCommand = new InsertCommand( | ||
this.db | ||
, this.db.databaseName + "." + this.collectionName); | ||
// List of all id's inserted | ||
var objects = []; | ||
// Create an insert command | ||
var insertCommand = new InsertCommand(this.db, this.db.databaseName + "." + this.collectionName); | ||
// Add id to each document if it's not already defined | ||
for(var index = 0; index < docs.length; index++) { | ||
// Add the documents and decorate them with id's if they have none | ||
for (var index = 0, len = docs.length; index < len; ++index) { | ||
var doc = docs[index]; | ||
// Add the id to the document | ||
var id = doc["_id"] == null ? this.pkFactory.createPk() : doc["_id"]; | ||
doc['_id'] = id; | ||
// Insert the document | ||
// Add id to each document if it's not already defined | ||
if (!doc['_id'] && this.db.forceServerObjectId != true) { | ||
doc['_id'] = this.pkFactory.createPk(); | ||
} | ||
insertCommand.add(doc); | ||
objects.push(doc); | ||
} | ||
// Execute the command | ||
var result = this.db.executeCommand(insertCommand); | ||
if(result instanceof Error) { | ||
if(callback != null) return callback(result, null); | ||
} | ||
// If safe is defined check for error message | ||
if(callback != null) { | ||
if(options.safe || this.db.strict) { | ||
this.db.error(function(err, error) { | ||
if(error[0].err) { | ||
if(callback != null) callback(new Error(error[0].err), null); | ||
} else { | ||
if(callback != null) callback(null, objects); | ||
} | ||
}); | ||
} else { | ||
if(callback != null) callback(null, objects); | ||
if (options != null && (options.safe == true || this.db.strict == true || this.opts.safe == true)) { | ||
var errorOptions = options.safe != null ? options.safe : null; | ||
errorOptions = errorOptions == null && this.opts.safe != null ? this.opts.safe : errorOptions; | ||
errorOptions = errorOptions == null && this.db.strict != null ? this.db.strict : errorOptions; | ||
// Insert options | ||
var insertOptions = {read:false, safe: errorOptions}; | ||
// If we have safe set set async to false | ||
if(errorOptions == null) insertOptions['async'] = true; | ||
// Execute command with safe options (rolls up both command and safe command into one and executes them on the same connection) | ||
this.db.executeCommand(insertCommand, insertOptions, function (err, error) { | ||
error = error && error.documents; | ||
if(!callback) return; | ||
if (err) { | ||
callback(err); | ||
} else if (error[0].err) { | ||
callback(new Error(error[0].err)); | ||
} else { | ||
callback(null, docs); | ||
} | ||
}); | ||
} else { | ||
var result = this.db.executeCommand(insertCommand); | ||
// If no callback just return | ||
if (!callback) return; | ||
// If error return error | ||
if (result instanceof Error) { | ||
return callback(result); | ||
} | ||
// Otherwise just return | ||
return callback(null, docs); | ||
} | ||
}; | ||
Collection.prototype.save = function(doc, options, callback) { | ||
/** | ||
* Save a document. | ||
* | ||
* @param {Object} doc | ||
* @param {Object} options (optional) | ||
* @param {Function} callback (optional) | ||
*/ | ||
Collection.prototype.save = function save (doc, options, callback) { | ||
var args = Array.prototype.slice.call(arguments, 1); | ||
callback = args.pop(); | ||
options = args.length ? args.shift() : {}; | ||
if(Object.prototype.toString.call(callback) != '[object Function]') { | ||
args.push(callback) | ||
callback = null | ||
} | ||
options = args.length ? args.shift() : null; | ||
var errorOptions = options.safe != null ? options.safe : false; | ||
errorOptions = errorOptions == null && this.opts.safe != null ? this.opts.safe : errorOptions; | ||
var id = doc['_id']; | ||
if(id != null) { | ||
this.update({'_id':id}, doc, {upsert: true, safe: options != null ? options.safe : false}, callback); | ||
if (id) { | ||
this.update({ _id: id }, doc, { upsert: true, safe: errorOptions }, callback); | ||
} else { | ||
this.insert(doc, {safe: options != null ? options.safe : false}, function(err, docs) { Array.isArray(docs) ? callback(err, docs[0]) : callback(err, docs); }); | ||
this.insert(doc, { safe: errorOptions }, callback && function (err, docs) { | ||
if (err) return callback(err, null); | ||
if (Array.isArray(docs)) { | ||
callback(err, docs[0]); | ||
} else { | ||
callback(err, docs); | ||
} | ||
}); | ||
} | ||
@@ -206,63 +319,57 @@ }; | ||
/** | ||
Update a single document in this collection. | ||
spec - a associcated array containing the fields that need to be present in | ||
the document for the update to succeed | ||
* Updates documents. | ||
* | ||
* By default updates only the first found doc. To update all matching | ||
* docs in the db, set options.multi to true. | ||
* | ||
* @param {Object} selector | ||
* @param {Object} document - the fields/vals to be updated, or in the case of | ||
* an upsert operation, inserted. | ||
* @param {Object} options (optional) | ||
* upsert - {bool} perform an upsert operation | ||
* multi - {bool} update all documents matching the selector | ||
* safe - {bool} check if the update failed (requires extra call to db) | ||
* @param {Function} callback (optional) | ||
*/ | ||
document - an associated array with the fields to be updated or in the case of | ||
a upsert operation the fields to be inserted. | ||
Collection.prototype.update = function update (selector, document, options, callback) { | ||
if('function' === typeof options) callback = options, options = null; | ||
Options: | ||
upsert - true/false (perform upsert operation) | ||
multi - true/false (update all documents matching spec) | ||
safe - true/false (perform check if the operation failed, required extra call to db) | ||
**/ | ||
Collection.prototype.update = function(spec, document, options, callback) { | ||
var args = Array.prototype.slice.call(arguments, 2); | ||
callback = args.pop(); | ||
var updateCommand = new UpdateCommand( | ||
this.db | ||
, this.db.databaseName + "." + this.collectionName | ||
, selector | ||
, document | ||
, options); | ||
if(Object.prototype.toString.call(callback) != '[object Function]') { | ||
args.push(callback) | ||
callback = null | ||
} | ||
options = args.length ? args.shift() : null; | ||
var safe = options == null || options.safe == null || options.safe == false ? false : true; | ||
var upsert = options == null || options.upsert == null || options.upsert == false ? false : true; | ||
// Create update command | ||
var updateCommand = new UpdateCommand(this.db, this.db.databaseName + "." + this.collectionName, spec, document, options); | ||
// Execute command | ||
var result = this.db.executeCommand(updateCommand);; | ||
if(result instanceof Error) { | ||
if(callback != null) return callback(result, null); | ||
} | ||
// If safe, we need to check for successful execution | ||
if(safe || this.db.strict) { | ||
this.db.error(function(err, error) { | ||
if(callback != null) { | ||
if (upsert) { | ||
// Document was either inserted or updated, simply pass on the error if one occurred. | ||
if (error[0].err) { | ||
if(callback != null) callback(new Error(error[0].err), null); | ||
} else { | ||
if(callback != null) callback(null, document); | ||
} | ||
} else { | ||
// Update only doesn't return an error when the record to be updated doesn't exist. | ||
if(error[0].updatedExisting == false) { | ||
if(callback != null) callback(new Error("Failed to update document"), null); | ||
} else { | ||
// If another kind of error occurred then report it to the callback function. | ||
if (error[0].err) { | ||
if(callback != null) callback(new Error(error[0].err), null); | ||
} else { | ||
if(callback != null) callback(null, document); | ||
} | ||
} | ||
} | ||
} | ||
}); | ||
// If we are executing in strict mode or safe both the update and the safe command must happen on the same line | ||
if (options && options.safe || this.db.strict || this.opts.safe) { | ||
// 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.strict != null ? this.db.strict : errorOptions; | ||
// Execute command with safe options (rolls up both command and safe command into one and executes them on the same connection) | ||
this.db.executeCommand(updateCommand, {read:false, safe: errorOptions}, function (err, error) { | ||
error = error && error.documents; | ||
if(!callback) return; | ||
if (err) { | ||
callback(err); | ||
} else if (error[0].err) { | ||
callback(new Error(error[0].err)); | ||
} else { | ||
callback(null, error[0].n); | ||
} | ||
}); | ||
} else { | ||
// Call back with ok if no error found | ||
if(callback != null) callback(null, document); | ||
var result = this.db.executeCommand(updateCommand); | ||
// If no callback just return | ||
if (!callback) return; | ||
// If error return error | ||
if (result instanceof Error) { | ||
return callback(result); | ||
} | ||
// Otherwise just return | ||
return callback(); | ||
} | ||
@@ -272,30 +379,65 @@ }; | ||
/** | ||
Fetch a distinct collection | ||
**/ | ||
Collection.prototype.distinct = function(key, query, callback) { | ||
var args = Array.prototype.slice.call(arguments, 1); | ||
callback = args.pop(); | ||
query = args.length ? args.shift() : {}; | ||
* Fetch a distinct collection | ||
* @param {String} key | ||
* @param {Object} query (optional) | ||
* @param {Function} callback (optional) | ||
*/ | ||
var mapCommandHash = {distinct:this.collectionName, key:key, query:query}; | ||
this.db.executeCommand(DbCommand.createDbCommand(this.db, mapCommandHash), function(err, result) { | ||
if(err == null && result.documents[0].ok == 1) { | ||
callback(null, result.documents[0].values); | ||
} else { | ||
err != null ? callback(err, null) : callback(new Error(result.documents[0].errmsg), null); | ||
Collection.prototype.distinct = function distinct (key, query, callback) { | ||
if ('function' === typeof query) callback = query, query = {}; | ||
var mapCommandHash = { | ||
distinct: this.collectionName | ||
, query: query | ||
, key: key | ||
}; | ||
var cmd = DbCommand.createDbCommand(this.db, mapCommandHash); | ||
this.db.executeCommand(cmd, {read:true}, function (err, result) { | ||
if (err) { | ||
return callback(err); | ||
} | ||
if (result.documents[0].ok != 1) { | ||
return callback(new Error(result.documents[0].errmsg)); | ||
} | ||
callback(null, result.documents[0].values); | ||
}); | ||
}; | ||
Collection.prototype.count = function(query, callback) { | ||
if(typeof query === "function") { callback = query; query = null; } | ||
var query_object = query == null ? {} : query; | ||
var final_query = {count: this.collectionName, query: query_object, fields: null}; | ||
var queryCommand = new QueryCommand(this.db, this.db.databaseName + ".$cmd", QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, final_query, null); | ||
// Execute the command | ||
this.db.executeCommand(queryCommand, function(err, result) { | ||
if(err == null && result.documents[0].ok == 1) { | ||
/** | ||
* Count number of matching documents in the db. | ||
* | ||
* @param {Object} query | ||
* @param {Function} callback | ||
*/ | ||
Collection.prototype.count = function count (query, callback) { | ||
if ('function' === typeof query) callback = query, query = {}; | ||
var final_query = { | ||
count: this.collectionName | ||
, query: query | ||
, fields: null | ||
}; | ||
var queryCommand = new QueryCommand( | ||
this.db | ||
, this.db.databaseName + ".$cmd" | ||
, QueryCommand.OPTS_NO_CURSOR_TIMEOUT | ||
, 0 | ||
, -1 | ||
, final_query | ||
, null | ||
); | ||
this.db.executeCommand(queryCommand, {read:true}, function (err, result) { | ||
if (err) { | ||
callback(err); | ||
} else if (result.documents[0].ok != 1) { | ||
callback(new Error(result.documents[0].errmsg)); | ||
} else { | ||
callback(null, result.documents[0].n); | ||
} else { | ||
err != null ? callback(err, null) : callback(new Error(result.documents[0].errmsg), null); | ||
} | ||
@@ -305,3 +447,9 @@ }); | ||
Collection.prototype.drop = function(callback) { | ||
/** | ||
* Drop this collection. | ||
* | ||
* @param {Function} callback | ||
*/ | ||
Collection.prototype.drop = function drop (callback) { | ||
this.db.dropCollection(this.collectionName, callback); | ||
@@ -311,45 +459,47 @@ }; | ||
/** | ||
Fetch and update a collection | ||
query: a filter for the query | ||
sort: if multiple docs match, choose the first one in the specified sort order as the object to manipulate | ||
update: an object describing the modifications to the documents selected by the query | ||
options: | ||
remove: set to a true to remove the object before returning | ||
new: set to true if you want to return the modified object rather than the original. Ignored for remove. | ||
upsert: true/false (perform upsert operation) | ||
**/ | ||
Collection.prototype.findAndModify = function(query, sort, update, options, callback) { | ||
* Find and update a document. | ||
* | ||
* @param {Object} query | ||
* @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 - | ||
* remove: {Bool} set to true to remove the object before returning | ||
* upsert: {Bool} perform an upsert operation | ||
* new: {Bool} set to true if you want to return the modified object | ||
* rather than the original. Ignored for remove. | ||
* @param {Function} callback | ||
*/ | ||
Collection.prototype.findAndModify = function findAndModify (query, sort, doc, options, callback) { | ||
var args = Array.prototype.slice.call(arguments, 1); | ||
callback = args.pop(); | ||
sort = args.length ? args.shift() : []; | ||
update = args.length ? args.shift() : null; | ||
doc = args.length ? args.shift() : null; | ||
options = args.length ? args.shift() : {}; | ||
// Format the sort object | ||
var sort_object = formattedOrderClause(sort); | ||
// Unpack the options | ||
var queryOptions = QueryCommand.OPTS_NONE; | ||
// Build the query object for the findAndModify | ||
var queryObject = { | ||
'findandmodify': this.collectionName, | ||
'query':query, | ||
'sort':sort_object | ||
} | ||
'findandmodify': this.collectionName | ||
, 'query': query | ||
, 'sort': formattedOrderClause(sort) | ||
}; | ||
queryObject['new'] = options['new'] ? 1 : 0; | ||
queryObject['remove'] = options.remove ? 1 : 0; | ||
queryObject['upsert'] = options.upsert ? 1 : 0; | ||
if (options.fields) queryObject['fields'] = options.fields; | ||
queryObject.new = options.new ? 1 : 0; | ||
queryObject.remove = options.remove ? 1 : 0; | ||
queryObject.upsert = options.upsert ? 1 : 0; | ||
// Set up the update if it exists | ||
if(update) queryObject['update'] = update; | ||
if (options.fields) { | ||
queryObject.fields = options.fields; | ||
} | ||
// Set up the sort | ||
if(!Array.isArray(sort) && sort.length == 0) queryObject['sort'] = sort_object; | ||
if (doc && !options.remove) { | ||
queryObject.update = doc; | ||
} | ||
if(options.remove) delete queryObject['update']; | ||
// Execute command | ||
this.db.executeDbCommand(queryObject, function(err, doc) { | ||
err ? callback(err, doc) : callback(err, doc.documents[0].value); | ||
this.db.executeDbCommand(queryObject, function (err, doc) { | ||
if (err) { | ||
callback(err, doc) | ||
} else { | ||
callback(err, doc.documents[0].value) | ||
} | ||
}); | ||
@@ -360,2 +510,3 @@ } | ||
* Various argument possibilities | ||
* TODO : combine/reduce # of possibilities | ||
* 1 callback? | ||
@@ -373,13 +524,15 @@ * 2 selector, callback?, | ||
*/ | ||
Collection.prototype.find = function() { | ||
var options, | ||
args = Array.prototype.slice.call(arguments, 0), | ||
has_callback = typeof args[args.length - 1] === 'function', | ||
has_weird_callback = typeof args[0] === 'function', | ||
callback = has_callback ? args.pop() : (has_weird_callback ? args.shift() : null), | ||
len = args.length, | ||
selector = (len >= 1) ? args[0] : {}, | ||
fields = (len >= 2) ? args[1] : undefined; | ||
if(len == 1 && has_weird_callback) { // backwards compat for callback?, options case | ||
Collection.prototype.find = function find () { | ||
var options | ||
, args = Array.prototype.slice.call(arguments, 0) | ||
, has_callback = typeof args[args.length - 1] === 'function' | ||
, has_weird_callback = typeof args[0] === 'function' | ||
, callback = has_callback ? args.pop() : (has_weird_callback ? args.shift() : null) | ||
, len = args.length | ||
, selector = len >= 1 ? args[0] : {} | ||
, fields = len >= 2 ? args[1] : undefined; | ||
if (len === 1 && has_weird_callback) { | ||
// backwards compat for callback?, options case | ||
selector = {}; | ||
@@ -389,21 +542,43 @@ options = args[0]; | ||
if(len == 2){ // backwards compat for options object | ||
var test = ['limit','sort','fields','skip','hint','explain','snapshot','timeout','tailable', 'batchSize'], | ||
idx = 0, l = test.length, is_option = false; | ||
while(!is_option && idx < l) if(test[idx] in fields ) is_option = true; else idx++; | ||
options = is_option ? fields : {}; | ||
if(is_option) fields = undefined; | ||
if (len === 2) { | ||
// backwards compat for options object | ||
var test = ['limit','sort','fields','skip','hint','explain','snapshot','timeout','tailable', 'batchSize'] | ||
, is_option = false; | ||
for (var idx = 0, l = test.length; idx < l; ++idx) { | ||
if (test[idx] in fields) { | ||
is_option = true; | ||
break; | ||
} | ||
} | ||
if (is_option) { | ||
options = fields; | ||
fields = undefined; | ||
} else { | ||
options = {}; | ||
} | ||
} | ||
if(len == 3) options = args[2]; | ||
if(options && options.fields){ | ||
if (3 === len) { | ||
options = args[2]; | ||
} | ||
if (options && options.fields) { | ||
fields = {}; | ||
if(options.fields.constructor == Array){ | ||
if(options.fields.length == 0) fields['_id'] = 1; | ||
else for(var i = 0, l = options.fields.length; i < l; i++) fields[options.fields[i]] = 1; | ||
if (Array.isArray(options.fields)) { | ||
if (!options.fields.length) { | ||
fields['_id'] = 1; | ||
} else { | ||
for (var i = 0, l = options.fields.length; i < l; i++) { | ||
fields[options.fields[i]] = 1; | ||
} | ||
} | ||
} else { | ||
fields = options.fields; | ||
} | ||
else fields = options.fields; | ||
} | ||
if(!options) options = {}; | ||
if (!options) options = {}; | ||
options.skip = len > 3 ? args[2] : options.skip ? options.skip : 0; | ||
@@ -413,3 +588,4 @@ options.limit = len > 3 ? args[3] : options.limit ? options.limit : 0; | ||
options.timeout = len == 5 ? args[4] : options.timeout ? options.timeout : undefined; | ||
options.slaveOk = options.slaveOk != null ? options.slaveOk : false; | ||
// If we have overridden slaveOk otherwise use the default db setting | ||
options.slaveOk = options.slaveOk != null ? options.slaveOk : this.db.slaveOk; | ||
@@ -420,2 +596,3 @@ var o = options; | ||
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)); | ||
@@ -427,75 +604,160 @@ } else { | ||
Collection.prototype.normalizeHintField = function(hint) { | ||
/** | ||
* Normalizes a `hint` argument. | ||
* | ||
* @param {String|Object|Array} hint | ||
* @return {Object} | ||
*/ | ||
Collection.prototype.normalizeHintField = function normalizeHintField (hint) { | ||
var finalHint = null; | ||
// Normalize the hint parameter | ||
if(hint != null && hint.constructor == String) { | ||
finalHint = {}; | ||
finalHint[hint] = 1; | ||
} else if(hint != null && hint.constructor == Object) { | ||
finalHint = {}; | ||
for(var name in hint) { finalHint[name] = hint[name]; } | ||
} else if(hint != null && hint.constructor == Array) { | ||
finalHint = {}; | ||
hint.forEach(function(param) { finalHint[param] = 1; }); | ||
if (null != hint) { | ||
switch (hint.constructor) { | ||
case String: | ||
finalHint = {}; | ||
finalHint[hint] = 1; | ||
break; | ||
case Object: | ||
finalHint = {}; | ||
for (var name in hint) { | ||
finalHint[name] = hint[name]; | ||
} | ||
break; | ||
case Array: | ||
finalHint = {}; | ||
hint.forEach(function(param) { | ||
finalHint[param] = 1; | ||
}); | ||
break; | ||
} | ||
} | ||
return finalHint; | ||
}; | ||
Collection.prototype.findOne = function(queryObject, options, callback) { | ||
var args = Array.prototype.slice.call(arguments, 0); | ||
callback = args.pop(); | ||
queryObject = args.length ? args.shift() : {}; | ||
options = args.length ? args.shift() : {}; | ||
/** | ||
* Finds one document. | ||
* | ||
* @param {Object} queryQbject | ||
* @param {Object} options | ||
* @param {Function} callback | ||
*/ | ||
var fields = options.fields; | ||
// Normalize fields filtering | ||
if(options && options.fields){ | ||
Collection.prototype.findOne = function findOne (queryObject, options, callback) { | ||
if ('function' === typeof queryObject) { | ||
callback = queryObject; | ||
queryQbject = {}; | ||
options = {}; | ||
} else if ('function' === typeof options) { | ||
callback = options; | ||
options = {}; | ||
} | ||
var fields; | ||
if (options.fields && Array.isArray(options.fields)) { | ||
fields = {}; | ||
if(options.fields.constructor == Array){ | ||
if(options.fields.length == 0) fields['_id'] = 1; | ||
else for(var i = 0, l = options.fields.length; i < l; i++) fields[options.fields[i]] = 1; | ||
if (0 === options.fields.length) { | ||
fields['_id'] = 1; | ||
} else { | ||
for (var i = 0, len = options.fields.length; i < len; ++i) { | ||
fields[options.fields[i]] = 1; | ||
} | ||
} | ||
else fields = options.fields; | ||
} else { | ||
fields = options.fields; | ||
} | ||
// Unpack the options | ||
var timeout = options.timeout != null ? options.timeout : QueryCommand.OPTS_NONE; | ||
var queryOptions = timeout; | ||
// Build final query | ||
var finalQueryObject = queryObject == null ? {} : queryObject; | ||
// Validate the type of query | ||
finalQueryObject = (finalQueryObject instanceof this.db.bson_serializer.ObjectID || Object.prototype.toString.call(finalQueryObject) === '[object ObjectID]') ? {'_id':finalQueryObject} : finalQueryObject; | ||
// Build special selector | ||
var specialSelector = {'query':finalQueryObject}; | ||
// Build full collection name | ||
var collectionName = (this.db.databaseName ? this.db.databaseName + "." : '') + this.collectionName; | ||
// Execute the command | ||
var queryCommand = new QueryCommand(this.db, collectionName, queryOptions, 0, 1, specialSelector, fields); | ||
this.db.executeCommand(queryCommand, function(err, result) { | ||
if(!err && result.documents[0] && result.documents[0]['$err']) return callback(result.documents[0]['$err'], null); | ||
callback(err, result.documents[0]); | ||
if (queryObject instanceof this.db.bson_serializer.ObjectID || | ||
'[object ObjectID]' === toString.call(queryObject)) { | ||
queryObject = { '_id': queryObject }; | ||
} | ||
var query = { 'query': queryObject }; | ||
var timeout = null != options.timeout | ||
? options.timeout | ||
: QueryCommand.OPTS_NONE; | ||
var collectionName = (this.db.databaseName ? this.db.databaseName + '.' : '') | ||
+ this.collectionName; | ||
var queryCommand = new QueryCommand( | ||
this.db | ||
, collectionName | ||
, timeout | ||
, 0 | ||
, 1 | ||
, query | ||
, fields); | ||
this.db.executeCommand(queryCommand, {read:true}, function (err, result) { | ||
if (!err && result.documents[0] && result.documents[0]['$err']) { | ||
return callback(result.documents[0]['$err']); | ||
} | ||
callback(err, result && result.documents && result.documents[0]); | ||
}); | ||
}; | ||
Collection.prototype.createIndex = function(fieldOrSpec, unique, callback) { | ||
this.db.createIndex(this.collectionName, fieldOrSpec, unique, callback); | ||
/** | ||
* Creates an index on this collection. | ||
* | ||
* @param {Object} fieldOrSpec | ||
* @param {Object} options | ||
* @param {Function} callback | ||
*/ | ||
Collection.prototype.createIndex = function createIndex (fieldOrSpec, options, callback) { | ||
this.db.createIndex(this.collectionName, fieldOrSpec, options, callback); | ||
}; | ||
Collection.prototype.ensureIndex = function(fieldOrSpec, unique, callback) { | ||
this.db.ensureIndex(this.collectionName, fieldOrSpec, unique, callback); | ||
/** | ||
* Ensures the index exists on this collection. | ||
* | ||
* @param {Object} fieldOrSpec | ||
* @param {Object} options | ||
* @param {Function} callback | ||
*/ | ||
Collection.prototype.ensureIndex = function ensureIndex (fieldOrSpec, options, callback) { | ||
this.db.ensureIndex(this.collectionName, fieldOrSpec, options, callback); | ||
}; | ||
Collection.prototype.indexInformation = function(callback) { | ||
/** | ||
* Retrieves this collections index info. | ||
* | ||
* @param {Function} callback | ||
*/ | ||
Collection.prototype.indexInformation = function indexInformation (callback) { | ||
this.db.indexInformation(this.collectionName, callback); | ||
}; | ||
Collection.prototype.dropIndex = function(indexName, callback) { | ||
this.db.dropIndex(this.collectionName, indexName, callback); | ||
/** | ||
* Drops an index from this collection. | ||
* | ||
* @param {String} name | ||
* @param {Function} callback | ||
*/ | ||
Collection.prototype.dropIndex = function dropIndex (name, callback) { | ||
this.db.dropIndex(this.collectionName, name, callback); | ||
}; | ||
Collection.prototype.dropIndexes = function(callback) { | ||
this.db.dropIndex(this.collectionName, "*", function(err, result) { | ||
if(err == null && result.documents[0].ok == 1) { | ||
/** | ||
* Drops all indexes from this collection. | ||
* | ||
* @param {Function} callback | ||
*/ | ||
Collection.prototype.dropIndexes = function dropIndexes (callback) { | ||
this.db.dropIndex(this.collectionName, '*', function (err, result) { | ||
if (err) { | ||
callback(err); | ||
} else if (1 == result.documents[0].ok) { | ||
callback(null, true); | ||
} else { | ||
err != null ? callback(err, null) : callback(new Error("map-reduce failed: " + result.documents[0].errmsg), false); | ||
callback(new Error("map-reduce failed: " + result.documents[0].errmsg), false); | ||
} | ||
@@ -505,49 +767,123 @@ }); | ||
Collection.prototype.mapReduce = function(map, reduce, options, callback) { | ||
var args = Array.prototype.slice.call(arguments, 2); | ||
callback = args.pop(); | ||
var self = this; | ||
// Parse version of server if available | ||
var version = this.db.version != null ? parseInt(this.db.version.replace(/\./g, '')) : 0; | ||
/** | ||
* Map reduce. | ||
* | ||
* @param {Function|String} map | ||
* @param {Function|String} reduce | ||
* @param {Objects} options | ||
* @param {Function} callback | ||
*/ | ||
Collection.prototype.mapReduce = function mapReduce (map, reduce, options, callback) { | ||
if ('function' === typeof options) callback = options, options = {}; | ||
// Set default to be inline if we are dealing with a v 1.7.6 > server | ||
if(version > 0 && version > 176) { | ||
options = args.length ? args.shift() : {out:'inline'}; | ||
if(options.out == null) options.out = 'inline'; | ||
var version = 'string' === typeof this.db.version | ||
? parseInt(this.db.version.replace(/\./g, '')) | ||
: 0; | ||
if (version > 0 && version > 176) { | ||
if (null == options.out) options.out = 'inline'; | ||
} | ||
if(Object.prototype.toString.call(map) === '[object Function]') map = map.toString(); | ||
if(Object.prototype.toString.call(reduce) === '[object Function]') reduce = reduce.toString(); | ||
// Build command object for execution | ||
var mapCommandHash = {mapreduce:this.collectionName, map:map, reduce:reduce}; | ||
if ('function' === typeof map) { | ||
map = map.toString(); | ||
} | ||
if ('function' === typeof reduce) { | ||
reduce = reduce.toString(); | ||
} | ||
if ('function' === typeof options.finalize) { | ||
options.finalize = options.finalize.toString(); | ||
} | ||
var mapCommandHash = { | ||
mapreduce: this.collectionName | ||
, map: map | ||
, reduce: reduce | ||
}; | ||
// Add any other options passed in | ||
for(var name in options) { | ||
for (var name in options) { | ||
mapCommandHash[name] = options[name]; | ||
} | ||
// Execute command against server | ||
this.db.executeCommand(DbCommand.createDbCommand(this.db, mapCommandHash), function(err, result) { | ||
if(err == null && result.documents[0].ok == 1) { | ||
//return the results, if the map/reduce is invoked with inline option | ||
if(result.documents[0].results) { | ||
return callback(err, result.documents[0].results); | ||
var self = this; | ||
var cmd = DbCommand.createDbCommand(this.db, mapCommandHash); | ||
this.db.executeCommand(cmd, {read:true}, function (err, result) { | ||
if (err) { | ||
return callback(err); | ||
} | ||
if (1 != result.documents[0].ok) { | ||
return callback(result.documents[0]); | ||
} | ||
// invoked with inline? | ||
if (result.documents[0].results) { | ||
return callback(null, result.documents[0].results); | ||
} | ||
// Create a collection object that wraps the result collection | ||
self.db.collection(result.documents[0].result, function (err, collection) { | ||
if (!options.include_statistics) { | ||
return callback(err, collection); | ||
} | ||
// Create a collection object that wraps the result collection | ||
self.db.collection(result.documents[0].result, function(err, collection) { | ||
if(options.include_statistics) { | ||
var stats = { | ||
processtime: result.documents[0].timeMillis, | ||
counts: result.documents[0].counts | ||
}; | ||
callback(err, collection, stats); | ||
} else { | ||
callback(err, collection); | ||
} | ||
}); | ||
} else { | ||
err != null ? callback(err, null, null) : callback(result.documents[0], null, null); | ||
} | ||
var stats = { | ||
processtime: result.documents[0].timeMillis | ||
, counts: result.documents[0].counts | ||
}; | ||
callback(err, collection, stats); | ||
}); | ||
}); | ||
}; | ||
Collection.prototype.group = function(keys, condition, initial, reduce, command, callback) { | ||
/** | ||
* Group function helper | ||
*/ | ||
var groupFunction = function () { | ||
var c = db[ns].find(condition); | ||
var map = new Map(); | ||
var reduce_function = reduce; | ||
while (c.hasNext()) { | ||
var obj = c.next(); | ||
var key = {}; | ||
for (var i = 0, len = keys.length; i < len; ++i) { | ||
var k = keys[i]; | ||
key[k] = obj[k]; | ||
} | ||
var aggObj = map.get(key); | ||
if (aggObj == null) { | ||
var newObj = Object.extend({}, key); | ||
aggObj = Object.extend(newObj, initial); | ||
map.put(key, aggObj); | ||
} | ||
reduce_function(obj, aggObj); | ||
} | ||
return { "result": map.values() }; | ||
}.toString(); | ||
/** | ||
* Group. | ||
* | ||
* @param {Object|Array|Function|Code} keys | ||
* @param {TODO} condition | ||
* @param {TODO} initial | ||
* @param {Function|Code} reduce | ||
* @param {Boolean} command | ||
* @param {Function} callback | ||
*/ | ||
Collection.prototype.group = function group (keys, condition, initial, reduce, command, callback) { | ||
var args = Array.prototype.slice.call(arguments, 3); | ||
@@ -558,24 +894,33 @@ callback = args.pop(); | ||
if(command) { | ||
var hash = {}, | ||
reduceFunction = reduce != null && reduce instanceof this.db.bson_serializer.Code ? reduce : new this.db.bson_serializer.Code(reduce); | ||
var Code = this.db.bson_serializer.Code | ||
if (!Array.isArray(keys) && keys instanceof Object && typeof(keys) !== 'function') { | ||
keys = Object.keys(keys); | ||
} | ||
if (reduce instanceof Function) { | ||
reduce = reduce.toString(); | ||
} | ||
if (command) { | ||
var reduceFunction = reduce instanceof Code | ||
? reduce | ||
: new Code(reduce); | ||
var selector = { | ||
group: | ||
{ | ||
'ns': this.collectionName, | ||
'$reduce': reduceFunction, | ||
'cond': condition, | ||
'initial': initial | ||
group: { | ||
'ns': this.collectionName | ||
, '$reduce': reduceFunction | ||
, 'cond': condition | ||
, 'initial': initial | ||
} | ||
}; | ||
if(keys.constructor == Function) | ||
{ | ||
var keyFunction = keys != null && keys instanceof this.db.bson_serializer.Code ? keys : new this.db.bson_serializer.Code(keys); | ||
selector.group.$keyf = keyFunction; | ||
} | ||
else | ||
{ | ||
keys.forEach(function(key) { | ||
if ('function' === typeof keys) { | ||
selector.group.$keyf = keys instanceof Code | ||
? keys | ||
: new Code(keys); | ||
} else { | ||
var hash = {}; | ||
keys.forEach(function (key) { | ||
hash[key] = 1; | ||
@@ -585,57 +930,33 @@ }); | ||
} | ||
this.db.executeCommand(DbCommand.createDbCommand(this.db, selector), function(err, result) { | ||
var cmd = DbCommand.createDbCommand(this.db, selector); | ||
this.db.executeCommand(cmd, {read:true}, function (err, result) { | ||
if (err) return callback(err); | ||
var document = result.documents[0]; | ||
if(err == null && document.retval != null) { | ||
callback(null, document.retval); | ||
} else { | ||
err != null ? callback(err, null) : callback(new Error("group command failed: " + document.errmsg), null); | ||
if (null == document.retval) { | ||
return callback(new Error("group command failed: " + document.errmsg)); | ||
} | ||
callback(null, document.retval); | ||
}); | ||
} else { | ||
// Create execution scope | ||
var scope = reduce != null && reduce instanceof this.db.bson_serializer.Code ? reduce.scope : {}; | ||
// Create scope for execution | ||
scope['ns'] = this.collectionName; | ||
scope['keys'] = keys; | ||
scope['condition'] = condition; | ||
scope['initial'] = initial; | ||
// Define group function | ||
var groupFunction = function() { | ||
var c = db[ns].find(condition); | ||
var map = new Map(); | ||
var reduce_function = reduce; | ||
while (c.hasNext()) { | ||
var obj = c.next(); | ||
var scope = reduce != null && reduce instanceof Code | ||
? reduce.scope | ||
: {}; | ||
var key = {}; | ||
for (var i = 0; i < keys.length; i++) { | ||
var k = keys[i]; | ||
key[k] = obj[k]; | ||
} | ||
scope.ns = this.collectionName; | ||
scope.keys = keys; | ||
scope.condition = condition; | ||
scope.initial = initial; | ||
var aggObj = map.get(key); | ||
if (aggObj == null) { | ||
var newObj = Object.extend({}, key); | ||
aggObj = Object.extend(newObj, initial); | ||
map.put(key, aggObj); | ||
} | ||
reduce_function(obj, aggObj); | ||
} | ||
return {"result": map.values()}; | ||
}; | ||
// Pass in the function text to execute within mongodb. | ||
var groupfn = groupFunction.replace(/ reduce;/, reduce.toString() + ';'); | ||
// Turn function into text and replace the "result" function of the grouping function | ||
var groupFunctionString = groupFunction.toString().replace(/ reduce;/, reduce.toString() + ';'); | ||
// Execute group | ||
this.db.eval(new this.db.bson_serializer.Code(groupFunctionString, scope), function(err, results) { | ||
if(err != null) { | ||
callback(err, null); | ||
} else { | ||
if(results.constructor == Object) { | ||
callback(err, results.result); | ||
} else { | ||
callback(err, results); | ||
} | ||
} | ||
this.db.eval(new Code(groupfn, scope), function (err, results) { | ||
if (err) return callback(err, null); | ||
callback(null, results.result || results); | ||
}); | ||
@@ -645,9 +966,21 @@ } | ||
Collection.prototype.options = function(callback) { | ||
this.db.collectionsInfo(this.collectionName, function(err, cursor) { | ||
// Fetch the object from the cursor | ||
cursor.nextObject(function(err, document) { | ||
callback(null, (document != null ? document.options : document)); | ||
/** | ||
* Options. | ||
* | ||
* @param {Function} callback | ||
*/ | ||
Collection.prototype.options = function options (callback) { | ||
this.db.collectionsInfo(this.collectionName, function (err, cursor) { | ||
if (err) return callback(err); | ||
cursor.nextObject(function (err, document) { | ||
callback(err, document && document.options || null); | ||
}); | ||
}); | ||
}; | ||
/** | ||
* Expose. | ||
*/ | ||
exports.Collection = Collection; |
var QueryCommand = require('./query_command').QueryCommand, | ||
InsertCommand = require('./insert_command').InsertCommand, | ||
MD5 = require('../crypto/md5').MD5, | ||
inherits = require('sys').inherits; | ||
inherits = require('util').inherits, | ||
debug = require('util').debug, | ||
inspect = require('util').inspect; | ||
@@ -46,6 +48,9 @@ /** | ||
var hash_password = MD5.hex_md5(username + ":mongo:" + password); | ||
// debug("=========================== hash_password :: " + hash_password) | ||
var key = MD5.hex_md5(nonce + username + hash_password); | ||
// debug("=========================== pre_hash_key :: " + (nonce + username + hash_password)) | ||
// debug("=========================== key :: " + key) | ||
var selector = {'authenticate':1, 'user':username, 'nonce':nonce, 'key':key}; | ||
// Create db command | ||
return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, selector, null); | ||
return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NONE, 0, -1, selector, null); | ||
}; | ||
@@ -77,9 +82,25 @@ | ||
DbCommand.createGetLastErrorCommand = function(db) { | ||
return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'getlasterror':1}, null); | ||
DbCommand.createGetLastErrorCommand = function(options, db) { | ||
var args = Array.prototype.slice.call(arguments, 0); | ||
db = args.pop(); | ||
options = args.length ? args.shift() : {}; | ||
// Final command | ||
var command = {'getlasterror':1}; | ||
// If we have an options Object let's merge in the fields (fsync/wtimeout/w) | ||
if('object' === typeof options) { | ||
for(var name in options) { | ||
command[name] = options[name] | ||
} | ||
} | ||
// debug("createGetLastErrorCommand :: " + inspect(command)) | ||
// Execute command | ||
return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, command, null); | ||
}; | ||
DbCommand.createGetLastStatusCommand = function(db) { | ||
return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'getlasterror':1}, null); | ||
}; | ||
DbCommand.createGetLastStatusCommand = DbCommand.createGetLastErrorCommand; | ||
// function(db) { | ||
// return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'getlasterror':1}, null); | ||
// }; | ||
@@ -94,7 +115,16 @@ DbCommand.createGetPreviousErrorsCommand = function(db) { | ||
DbCommand.createCreateIndexCommand = function(db, collectionName, fieldOrSpec, unique) { | ||
var finalUnique = unique == null ? false : unique; | ||
DbCommand.createCreateIndexCommand = function(db, collectionName, fieldOrSpec, options) { | ||
var finalUnique = options == null && !(options instanceof Object) ? false : options; | ||
var fieldHash = {}; | ||
var indexes = []; | ||
var keys; | ||
var sparse; | ||
var background; | ||
// If the options is a hash | ||
if(options instanceof Object) { | ||
finalUnique = options['unique'] != null ? options['unique'] : false; | ||
sparse = options['sparse'] != null ? options['sparse'] : false; | ||
background = options['background'] != null ? options['background'] : false; | ||
} | ||
@@ -136,3 +166,6 @@ // Get all the fields accordingly | ||
// Build the selector | ||
var selector = {'ns':(db.databaseName + "." + collectionName), 'unique':finalUnique, 'key':fieldHash, 'name':indexName}; | ||
var selector = {'ns':(db.databaseName + "." + collectionName), 'key':fieldHash, 'name':indexName}; | ||
selector['unique'] = finalUnique; | ||
selector['sparse'] = sparse; | ||
selector['background'] = background; | ||
// Create the insert command for the index and return the document | ||
@@ -153,1 +186,5 @@ return new InsertCommand(db, db.databaseName + "." + DbCommand.SYSTEM_INDEX_COLLECTION, false).add(selector); | ||
}; | ||
DbCommand.createAdminDbCommand = function(db, command_hash) { | ||
return new DbCommand(db, "admin." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, command_hash, null); | ||
}; |
var BaseCommand = require('./base_command').BaseCommand, | ||
BinaryParser = require('../bson/binary_parser').BinaryParser, | ||
inherits = require('sys').inherits; | ||
inherits = require('util').inherits; | ||
@@ -5,0 +5,0 @@ /** |
var BaseCommand = require('./base_command').BaseCommand, | ||
BinaryParser = require('../bson/binary_parser').BinaryParser, | ||
inherits = require('sys').inherits; | ||
inherits = require('util').inherits; | ||
@@ -5,0 +5,0 @@ /** |
var BaseCommand = require('./base_command').BaseCommand, | ||
BinaryParser = require('../bson/binary_parser').BinaryParser, | ||
inherits = require('sys').inherits; | ||
inherits = require('util').inherits, | ||
debug = require('util').debug, | ||
inspect = require('util').inspect; | ||
@@ -25,2 +27,3 @@ /** | ||
InsertCommand.prototype.getOpCode = function() { | ||
return BaseCommand.OP_INSERT; | ||
@@ -39,3 +42,3 @@ }; | ||
var command_string = ''; | ||
for(var i = 0; i < this.documents.length; i++) { | ||
for(var i = 0; i < this.documents.length; i++) { | ||
command_string = command_string + this.db.bson_serializer.BSON.serialize(this.documents[i], this.checkKeys); | ||
@@ -42,0 +45,0 @@ } |
var BaseCommand = require('./base_command').BaseCommand, | ||
BinaryParser = require('../bson/binary_parser').BinaryParser, | ||
inherits = require('sys').inherits; | ||
inherits = require('util').inherits; | ||
@@ -5,0 +5,0 @@ /** |
var BaseCommand = require('./base_command').BaseCommand, | ||
BinaryParser = require('../bson/binary_parser').BinaryParser, | ||
inherits = require('sys').inherits; | ||
inherits = require('util').inherits; | ||
@@ -54,2 +54,4 @@ /** | ||
QueryCommand.OPTS_OPLOG_REPLY = 8; | ||
QueryCommand.OPTS_NO_CURSOR_TIMEOUT = 16; | ||
QueryCommand.OPTS_NO_CURSOR_TIMEOUT = 16; | ||
QueryCommand.OPTS_AWAIT_DATA = 32; | ||
QueryCommand.OPTS_EXHAUST = 64; |
var BaseCommand = require('./base_command').BaseCommand, | ||
BinaryParser = require('../bson/binary_parser').BinaryParser, | ||
inherits = require('sys').inherits; | ||
inherits = require('util').inherits; | ||
@@ -5,0 +5,0 @@ /** |
var net = require('net'), | ||
debug = require('util').debug, | ||
inspect = require('util').inspect, | ||
EventEmitter = require("events").EventEmitter, | ||
BinaryParser = require('./bson/binary_parser').BinaryParser, | ||
inherits = require('sys').inherits; | ||
inherits = require('util').inherits, | ||
Server = require('./connections/server').Server; | ||
var Connection = exports.Connection = function(host, port, autoReconnect) { | ||
var Connection = exports.Connection = function(host, port, autoReconnect, options) { | ||
this.options = options == null ? {} : options; | ||
this.host = host; | ||
@@ -11,6 +15,7 @@ this.port = port; | ||
this.drained = true; | ||
// Fetch the poolsize | ||
this.poolSize = this.options["poolSize"] == null ? 1 : this.options["poolSize"]; | ||
// Reconnect buffer for messages | ||
this.messages = []; | ||
// Message sender | ||
var self = this; | ||
// Status messages | ||
@@ -21,2 +26,8 @@ this.sizeOfMessage = 0; | ||
this.stubBuffer = ''; | ||
this.connected = false; | ||
// Connection pool variables | ||
this.pool = []; | ||
this.poolByReference = {}; | ||
this.poolIndex = 0; | ||
}; | ||
@@ -26,56 +37,53 @@ | ||
// Functions to open the connection | ||
Connection.prototype.open = function() { | ||
// Assign variable to point to local scope object | ||
var self = this; | ||
// Create the associated connection | ||
this.connection = net.createConnection(this.port, this.host); | ||
// Set up the net client | ||
this.connection.setEncoding("binary"); | ||
// Add connnect listener | ||
this.connection.addListener("connect", function() { | ||
this.setEncoding("binary"); | ||
this.setTimeout(0); | ||
this.setNoDelay(); | ||
self.emit("connect"); | ||
}); | ||
this.connection.addListener("error", function(err) { | ||
self.emit("error", err); | ||
}); | ||
this.connection.addListener("timeout", function(err) { | ||
self.emit("timeout", err); | ||
}); | ||
// Add a close listener | ||
this.connection.addListener("close", function() { | ||
self.emit("close"); | ||
}); | ||
// Listener for receive data | ||
this.receiveListener = function(result) { | ||
var getConnection = function(self) { | ||
return self.pool[self.poolIndex++ % self.pool.length]; | ||
} | ||
// Setup the connection pool | ||
var setupConnectionPool = function(self, poolSize, reconnect) { | ||
// Pool off connections and status variables | ||
var connectionPool = []; | ||
var connectedTo = 0; | ||
var errors = 0; | ||
var connectionError = null; | ||
// | ||
// Listener that handles callbacks for the connection | ||
// Uses the internal object states to keep individual tcp connections seperate | ||
var receiveListener = function(result, fd) { | ||
fd = fd == null ? this.fd : fd; | ||
// Fetch the pool reference | ||
var conObj = self.poolByReference[fd]; | ||
// if(conObj == null) { | ||
// debug("================================================================ failed to find connection :: " + this.fd) | ||
// debug(inspect(self.poolByReference)) | ||
// } | ||
// Check if we have an unfinished message | ||
if(self.bytesRead > 0 && self.sizeOfMessage > 0) { | ||
if(conObj != null && conObj.bytesRead > 0 && conObj.sizeOfMessage > 0) { | ||
// Calculate remaing bytes to fetch | ||
var remainingBytes = self.sizeOfMessage - self.bytesRead; | ||
var remainingBytes = conObj.sizeOfMessage - conObj.bytesRead; | ||
// Check if we have multiple packet messages and save the pieces otherwise emit the message | ||
if(remainingBytes > result.length) { | ||
self.buffer = self.buffer + result; self.bytesRead = self.bytesRead + result.length; | ||
conObj.buffer = conObj.buffer + result; conObj.bytesRead = conObj.bytesRead + result.length; | ||
} else { | ||
// Cut off the remaining message | ||
self.buffer = self.buffer + result.substr(0, remainingBytes); | ||
conObj.buffer = conObj.buffer + result.substr(0, remainingBytes); | ||
// Emit the message | ||
self.emit("data", self.buffer); | ||
self.emit("data", conObj.buffer); | ||
// Reset the variables | ||
self.buffer = ''; self.bytesRead = 0; self.sizeOfMessage = 0; | ||
conObj.buffer = ''; conObj.bytesRead = 0; conObj.sizeOfMessage = 0; | ||
// If message is longer than the current one, keep parsing | ||
if(remainingBytes < result.length) { | ||
self.receiveListener(result.substr(remainingBytes, (result.length - remainingBytes))); | ||
// debug("--------------------------------------- remainingBytes < result.length :: " + this.fd) | ||
// receiveListener.call(this, result.substr(remainingBytes, (result.length - remainingBytes))); | ||
receiveListener(result.substr(remainingBytes, (result.length - remainingBytes)), fd); | ||
} | ||
} | ||
} else { | ||
if(self.stubBuffer.length > 0) { | ||
result = self.stubBuffer + result; | ||
self.stubBuffer = ''; | ||
} else if(conObj != null){ | ||
if(conObj.stubBuffer.length > 0) { | ||
result = conObj.stubBuffer + result; | ||
conObj.stubBuffer = ''; | ||
} | ||
@@ -87,3 +95,3 @@ | ||
if(sizeOfMessage > result.length) { | ||
self.buffer = self.buffer + result; self.bytesRead = result.length; self.sizeOfMessage = sizeOfMessage; | ||
conObj.buffer = conObj.buffer + result; conObj.bytesRead = result.length; conObj.sizeOfMessage = sizeOfMessage; | ||
} else if(sizeOfMessage == result.length) { | ||
@@ -93,67 +101,177 @@ self.emit("data", result); | ||
self.emit("data", result.substr(0, sizeOfMessage)); | ||
self.receiveListener(result.substr(sizeOfMessage, (result.length - sizeOfMessage))); | ||
// debug("--------------------------------------- sizeOfMessage < result.length :: " + this.fd) | ||
// receiveListener.call(this, result.substr(sizeOfMessage, (result.length - sizeOfMessage))); | ||
receiveListener(result.substr(sizeOfMessage, (result.length - sizeOfMessage)), fd); | ||
} | ||
} else { | ||
self.stubBuffer = result; | ||
conObj.stubBuffer = result; | ||
} | ||
} | ||
}; | ||
// Fill the pool | ||
for(var i = 0; i < poolSize; i++) { | ||
// Create the associated connection | ||
var connection = net.createConnection(self.port, self.host); | ||
// Set up the net client | ||
connection.setEncoding("binary"); | ||
// Add connnect listener | ||
connection.addListener("connect", function() { | ||
this.setEncoding("binary"); | ||
this.setTimeout(0); | ||
this.setNoDelay(); | ||
// Update number of connected to server | ||
connectedTo = connectedTo + 1; | ||
}); | ||
connection.addListener("error", function(err) { | ||
// Update number of errors | ||
errors = errors + 1; | ||
connectionError = err; | ||
}); | ||
connection.addListener("timeout", function(err) { | ||
// Update number of errors | ||
errors = errors + 1; | ||
connectionError = err; | ||
}); | ||
// Add a close listener | ||
connection.addListener("close", function() { | ||
self.emit("close"); | ||
}); | ||
// Add connection to the pool array | ||
connectionPool.push({"connection": connection, | ||
"sizeOfMessage": 0, | ||
"bytesRead": 0, | ||
"buffer": '', | ||
"stubBuffer": ''}); | ||
// Add the listener to the connection | ||
connection.addListener("data", receiveListener); | ||
} | ||
// Function that wait for connection to finish up | ||
var waitForConnections = function() { | ||
// Emit a connect message once all connections are up | ||
if(connectedTo == connectionPool.length) { | ||
if(reconnect == null || !reconnect) { | ||
self.connected = true; | ||
self.poolByReference = {}; | ||
// Save the connections by the fd reference | ||
self.pool.forEach(function(con) { | ||
self.poolByReference[con.connection.fd] = con; | ||
}); | ||
self.emit("connect"); | ||
} else { | ||
self.connected = false; | ||
self.emit("reconnect"); | ||
} | ||
} else if(errors + connectedTo == connectionPool.length) { | ||
if(reconnect == null || !reconnect) { | ||
self.connected = false; | ||
self.emit("error", connectionError); | ||
} else { | ||
self.connected = false; | ||
self.emit("reconnect"); | ||
} | ||
} else { | ||
process.nextTick(waitForConnections); | ||
} | ||
} | ||
// Wait until we are done connected to all pool entries before emitting connect signal | ||
process.nextTick(waitForConnections); | ||
// Return the pool | ||
return connectionPool; | ||
} | ||
// Add a receieved data connection | ||
this.connection.addListener("data", this.receiveListener); | ||
}; | ||
// Functions to open the connection | ||
Connection.prototype.open = function() { | ||
var self = this; | ||
// Create the pool with connections | ||
this.pool = setupConnectionPool(this, this.poolSize); | ||
} | ||
Connection.prototype.close = function() { | ||
if(this.connection) this.connection.end(); | ||
this.connected = false; | ||
// Close all entries in the pool | ||
for(var i = 0; i < this.pool.length; i++) { | ||
this.pool[i].connection.end(); | ||
} | ||
}; | ||
Connection.prototype.send = function(command) { | ||
Connection.prototype.send = function(command, rawConnection) { | ||
var self = this; | ||
// If we are executing the commnand on the entire pool | ||
var connection = null; | ||
// If we are forcing the use of a connection | ||
if(rawConnection != null) { | ||
connection = rawConnection; | ||
} else { | ||
connection = getConnection(self).connection; | ||
} | ||
// Check if the connection is closed | ||
try { | ||
if ( this.connection.readyState != "open" ) | ||
throw 'notConnected'; | ||
if(command.constructor == String) { | ||
this.connection.write(command, "binary"); | ||
if (connection.readyState != "open") { | ||
throw 'notConnected'; | ||
} | ||
// Send the command, if it's an array of commands execute them all on the same connection | ||
if(Array.isArray(command)) { | ||
for(var i = 0; i < command.length; i++) { | ||
// debug("========================================================================= command string") | ||
// BinaryParser.ilprint((command.constructor == String) ? command : command.toBinary()) | ||
connection.write((command[i].constructor == String) ? command[i] : command[i].toBinary(), "binary"); | ||
} | ||
} else { | ||
this.connection.write(command.toBinary(), "binary"); | ||
} | ||
// debug("========================================================================= command string") | ||
// BinaryParser.ilprint((command.constructor == String) ? command : command.toBinary()) | ||
connection.write((command.constructor == String) ? command : command.toBinary(), "binary"); | ||
} | ||
} catch(err) { | ||
// Check if the connection is closed | ||
if(this.connection.readyState != "open" && this.autoReconnect) { | ||
if(connection.readyState != "open" && self.autoReconnect) { | ||
// Add the message to the queue of messages to send | ||
this.messages.push(command); | ||
self.messages.push(command); | ||
// Initiate reconnect if no current running | ||
if(this.connection.currently_reconnecting == null) { | ||
this.connection.currently_reconnecting = true; | ||
// Create the associated connection | ||
var new_connection = net.createConnection(this.port, this.host); | ||
// Set up the net client | ||
new_connection.setEncoding("binary"); | ||
new_connection.addListener( "error", function( err ) { | ||
self.emit( "error", err ); | ||
self.connection.currently_reconnecting = null; | ||
}); | ||
// Add connnect listener | ||
new_connection.addListener("connect", function() { | ||
this.setEncoding("binary"); | ||
this.setTimeout(0); | ||
this.setNoDelay(); | ||
// Add the listener | ||
this.addListener("data", self.receiveListener); | ||
// assign the new ready connection | ||
self.connection = this; | ||
// send all the messages | ||
if(self.currently_reconnecting == null || self.currently_reconnecting == false) { | ||
self.currently_reconnecting = true; | ||
// Create the pool with connections | ||
self.pool = setupConnectionPool(self, self.poolSize, true); | ||
self.poolByReference = {}; | ||
// Save the connections by the fd reference | ||
self.pool.forEach(function(con) { | ||
self.poolByReference[con.connection.fd] = con; | ||
}) | ||
// Wait for a reconnect and send all the messages | ||
self.on("reconnect", function() { | ||
self.currently_reconnecting = false; | ||
// Fire the message again | ||
while(self.messages.length > 0) { | ||
var msg = self.messages.shift(); | ||
if(msg.constructor == String) { | ||
this.write(msg, "binary"); | ||
// Fetch a connection and resend messages | ||
connection = getConnection(self).connection; | ||
// Fetch the a message | ||
var command = self.messages.shift(); | ||
// Fire | ||
if(Array.isArray(command)) { | ||
for(var i = 0; i < command.length; i++) { | ||
connection.write((command[i].constructor == String) ? command[i] : command[i].toBinary(), "binary"); | ||
} | ||
} else { | ||
this.write(msg.toBinary(), "binary"); | ||
} | ||
// this.write(self.messages.shift().toBinary(), "binary"); | ||
} | ||
}); | ||
connection.write((command.constructor == String) ? command : command.toBinary(), "binary"); | ||
} | ||
} | ||
}) | ||
} | ||
} else { | ||
// Set connected to false | ||
self.connected = false; | ||
// Throw error | ||
throw err; | ||
@@ -163,2 +281,3 @@ } | ||
}; | ||
/** | ||
@@ -168,160 +287,20 @@ * Wrtie command without an attempt of reconnect | ||
*/ | ||
Connection.prototype.sendwithoutReconnect = function(command) { | ||
var self = this; | ||
var connection = this.connection; | ||
// Check if the connection is closed | ||
if ( this.connection.readyState != "open" ) { | ||
if (connection.readyState != "open") { | ||
throw new Error( 'Connection closed!' ); | ||
} | ||
try { | ||
this.connection.write(command.toBinary(), "binary"); | ||
connection.write(command.toBinary(), "binary"); | ||
} catch(err) { | ||
// no need to reconnect since called by latest master | ||
// and already went through send() function | ||
throw err; | ||
// no need to reconnect since called by latest master | ||
// and already went through send() function | ||
throw err; | ||
}; | ||
}; | ||
// Some basic defaults | ||
Connection.DEFAULT_PORT = 27017; | ||
var Server = exports.Server = function(host, port, options) { | ||
this.host = host; | ||
this.port = port; | ||
this.options = options == null ? {} : options; | ||
this.internalConnection; | ||
this.internalMaster = false; | ||
// Setters and getters | ||
this.__defineGetter__("autoReconnect", function() { return this.options['auto_reconnect'] == null ? false : this.options['auto_reconnect']; }); | ||
this.__defineGetter__("connection", function() { return this.internalConnection; }); | ||
this.__defineSetter__("connection", function(connection) { this.internalConnection = connection; }); | ||
this.__defineGetter__("master", function() { return this.internalMaster; }); | ||
this.__defineSetter__("master", function(value) { this.internalMaster = value; }); | ||
this.__defineGetter__("masterConnection", function() { return this.internalConnection; }); | ||
}; | ||
Server.prototype.close = function(callback) { | ||
this.connection.close(callback); | ||
}; | ||
// Server pair object used to support a failover connection set | ||
var ServerPair = exports.ServerPair = function(leftServer, rightServer) { | ||
if(leftServer == null || rightServer == null || !(leftServer instanceof Server) || !(rightServer instanceof Server)) { | ||
throw Error("Both left/right must be defined and off the type Server"); | ||
} | ||
this.leftServer = leftServer; | ||
this.rightServer = rightServer; | ||
// Containst the master server entry | ||
this.master = null; | ||
this.target = null; | ||
// Setters and getters | ||
this.__defineGetter__("autoReconnect", function() { | ||
if(this.target != null) return this.target.autoReconnect; | ||
if(this.masterConnection != null) return this.masterConnection.autoReconnect; | ||
}); | ||
this.__defineGetter__("masterConnection", function() { | ||
if(this.target != null && this.target instanceof Server) return this.target.masterConnection; | ||
if(this.leftServer.master) return this.leftServer.masterConnection; | ||
if(this.rightServer.master) return this.rightServer.masterConnection; | ||
return null; | ||
}); | ||
}; | ||
ServerPair.prototype.setTarget = function(target) { | ||
this.target = target; | ||
this.servers = []; | ||
}; | ||
ServerPair.MASTER = 0; | ||
ServerPair.SHADOW_MASTER = 1; | ||
// Server cluster (one master and multiple read slaves) | ||
var ServerCluster = exports.ServerCluster = function(servers) { | ||
// Containst the master server entry | ||
this.master = null; | ||
this.target = null; | ||
if(servers.constructor != Array || servers.length == 0) { | ||
throw Error("The parameter must be an array of servers and contain at least one server"); | ||
} else if(servers.constructor == Array || servers.length > 0) { | ||
var count = 0; | ||
servers.forEach(function(server) { | ||
if(server instanceof Server) count = count + 1; | ||
}); | ||
if(count < servers.length) { | ||
throw Error("All server entries must be of type Server"); | ||
} else { | ||
this.servers = servers; | ||
} | ||
} | ||
// Setters and getters | ||
this.__defineGetter__("autoReconnect", function() { | ||
if(this.target != null) return this.target.autoReconnect; | ||
if(this.masterConnection != null) return this.masterConnection.autoReconnect; | ||
}); | ||
this.__defineGetter__("masterConnection", function() { | ||
// Allow overriding to a specific connection | ||
if(this.target != null && this.target instanceof Server) { | ||
return this.target.masterConnection; | ||
} else { | ||
var finalServer = null; | ||
this.servers.forEach(function(server) { | ||
if(server.master == true) finalServer = server; | ||
}); | ||
return finalServer != null ? finalServer.masterConnection : finalServer; | ||
} | ||
}); | ||
}; | ||
ServerCluster.prototype.setTarget = function(target) { | ||
this.target = target; | ||
}; | ||
/** | ||
* ReplSetServers constructor provides master-slave functionality | ||
* | ||
* @param serverArr{Array of type Server} | ||
* @return constructor of ServerCluster | ||
* | ||
*/ | ||
var ReplSetServers = exports.ReplSetServers = function(servers) { | ||
// Contains the master server entry | ||
this.master = null; | ||
this.target = null; | ||
if(servers.constructor != Array || servers.length == 0) { | ||
throw Error("The parameter must be an array of servers and contain at least one server"); | ||
} else if(servers.constructor == Array || servers.length > 0) { | ||
var count = 0; | ||
servers.forEach(function(server) { | ||
if(server instanceof Server) count = count + 1; | ||
}); | ||
if(count < servers.length) { | ||
throw Error("All server entries must be of type Server"); | ||
} else { | ||
this.servers = servers; | ||
} | ||
} | ||
// Setters and getters | ||
this.__defineGetter__("autoReconnect", function() { | ||
if(this.target != null) return this.target.autoReconnect; | ||
if(this.masterConnection != null) return this.masterConnection.autoReconnect; | ||
}); | ||
this.__defineGetter__("masterConnection", function() { | ||
// Allow overriding to a specific connection | ||
if(this.target != null && this.target instanceof Server) { | ||
return this.target.masterConnection; | ||
} else { | ||
var finalServer = null; | ||
this.servers.forEach(function(server) { | ||
if(server.master == true && ( server.connection.connection.readyState == "open") ) finalServer = server; | ||
}); | ||
return finalServer != null ? finalServer.masterConnection : finalServer; | ||
} | ||
}); | ||
}; | ||
ReplSetServers.prototype.setTarget = function(target) { | ||
this.target = target; | ||
}; | ||
Connection.DEFAULT_PORT = 27017; |
var QueryCommand = require('./commands/query_command').QueryCommand, | ||
GetMoreCommand = require('./commands/get_more_command').GetMoreCommand, | ||
KillCursorCommand = require('./commands/kill_cursor_command').KillCursorCommand, | ||
Integer = require('./goog/math/integer').Integer, | ||
Long = require('./goog/math/long').Long; | ||
Long = require('./goog/math/long').Long, | ||
debug = require('util').debug, | ||
inspect = require('util').inspect; | ||
@@ -56,3 +57,3 @@ /** | ||
this.batchSizeValue = batchSize == null ? 0 : batchSize; | ||
this.slaveOk = slaveOk == null ? false : slaveOk; | ||
this.slaveOk = slaveOk == null ? collection.slaveOk : slaveOk; | ||
@@ -115,27 +116,23 @@ this.totalNumberOfRecords = 0; | ||
if (!callback) { | ||
if(!callback) { | ||
throw Error('callback is mandatory'); | ||
} | ||
try { | ||
if(this.tailable) { | ||
callback(new Error("Tailable cursor cannot be converted to array"), null); | ||
} else if(this.state != Cursor.CLOSED) { | ||
var items = []; | ||
this.each(function(err, item) { | ||
if (item != null) { | ||
items.push(item); | ||
} else { | ||
callback(err, items); | ||
if(this.tailable) { | ||
callback(new Error("Tailable cursor cannot be converted to array"), null); | ||
} else if(this.state != Cursor.CLOSED) { | ||
var items = []; | ||
items = null; | ||
} | ||
this.each(function(err, item) { | ||
if(err != null) return callback(err, null); | ||
item = null; | ||
}); | ||
} else { | ||
callback(new Error("Cursor is closed"), null); | ||
} | ||
} catch(err) { | ||
callback(new Error(err.toString()), null); | ||
if (item != null) { | ||
items.push(item); | ||
} else { | ||
callback(err, items); | ||
items = null; | ||
} | ||
}); | ||
} else { | ||
callback(new Error("Cursor is closed"), null); | ||
} | ||
@@ -173,2 +170,4 @@ }; | ||
self.nextObject(function(err, item) { | ||
if(err != null) return callback(err, null); | ||
if(item != null) { | ||
@@ -178,2 +177,3 @@ callback(null, item); | ||
} else { | ||
// Close the cursor if done | ||
self.state = Cursor.CLOSED; | ||
@@ -238,2 +238,3 @@ callback(err, null); | ||
if(typeof direction === "function") { callback = direction; direction = null; } | ||
if(this.tailable) { | ||
@@ -249,2 +250,3 @@ callback(new Error("Tailable cursor doesn't support sorting"), null); | ||
} | ||
this.sortValue = order; | ||
@@ -270,2 +272,3 @@ callback(null, this); | ||
callback = callback || function(){}; | ||
if(this.tailable) { | ||
@@ -283,2 +286,3 @@ callback(new Error("Tailable cursor doesn't support limit"), null); | ||
} | ||
return this; | ||
@@ -301,2 +305,3 @@ }; | ||
callback = callback || function(){}; | ||
if(this.tailable) { | ||
@@ -314,2 +319,3 @@ callback(new Error("Tailable cursor doesn't support skip"), null); | ||
} | ||
return this; | ||
@@ -332,2 +338,3 @@ }; | ||
callback = callback || function(){}; | ||
if(this.state == Cursor.CLOSED) { | ||
@@ -356,4 +363,3 @@ callback(new Error("Cursor is closed"), null); | ||
} | ||
} | ||
else { | ||
} else { | ||
requestedLimit = this.batchSizeValue; | ||
@@ -375,13 +381,12 @@ } | ||
if (!this.timeout) { | ||
queryOptions += QueryCommand.OPTS_NO_CURSOR_TIMEOUT; | ||
queryOptions |= QueryCommand.OPTS_NO_CURSOR_TIMEOUT; | ||
} | ||
if (this.tailable != null) { | ||
queryOptions += QueryCommand.OPTS_TAILABLE_CURSOR; | ||
this.skipValue = this.limitValue = 0; | ||
queryOptions |= QueryCommand.OPTS_TAILABLE_CURSOR; | ||
this.skipValue = this.limitValue = 0; | ||
} | ||
if (this.slaveOk) { | ||
queryOptions += QueryCommand.OPTS_SLAVE; | ||
queryOptions |= QueryCommand.OPTS_SLAVE; | ||
} | ||
// limitValue of -1 is a special case used by Db#eval | ||
@@ -467,19 +472,24 @@ var numberToReturn = this.limitValue == -1 ? -1 : this.limitRequest(); | ||
var commandHandler = function(err, result) { | ||
if(!err && result.documents[0] && result.documents[0]['$err']) { | ||
self.close(function() {callback(result.documents[0]['$err'], null);}); | ||
return; | ||
} | ||
self.queryRun = true; | ||
self.state = Cursor.OPEN; // Adjust the state of the cursor | ||
self.cursorId = result.cursorId; | ||
self.totalNumberOfRecords = result.numberReturned; | ||
// Add the new documents to the list of items | ||
self.items = self.items.concat(result.documents); | ||
self.nextObject(callback); | ||
result = null; | ||
try { | ||
if(err != null && result == null) return callback(err, null); | ||
if(!err && result.documents[0] && result.documents[0]['$err']) { | ||
// Let's keep the cursor open | ||
return self.close(function() {callback(result.documents[0]['$err'], null);}); | ||
} | ||
self.queryRun = true; | ||
self.state = Cursor.OPEN; // Adjust the state of the cursor | ||
self.cursorId = result.cursorId; | ||
self.totalNumberOfRecords = result.numberReturned; | ||
// Add the new documents to the list of items | ||
self.items = self.items.concat(result.documents); | ||
self.nextObject(callback); | ||
result = null; | ||
} catch(err) { | ||
callback(new Error(err.toString()), null); | ||
} | ||
}; | ||
self.db.executeCommand(self.generateQueryCommand(), commandHandler); | ||
self.db.executeCommand(self.generateQueryCommand(), {read:true}, commandHandler); | ||
commandHandler = null; | ||
@@ -494,3 +504,4 @@ } catch(err) { | ||
} else { | ||
self.close(function() {callback(null, null);}); | ||
// Force cursor to stay open | ||
return self.close(function() {callback(null, null);}); | ||
} | ||
@@ -521,33 +532,38 @@ } | ||
// Execute the command | ||
self.db.executeCommand(getMoreCommand, function(err, result) { | ||
self.db.executeCommand(getMoreCommand, {read:true}, function(err, result) { | ||
try { | ||
if(err != null) callback(err, null); | ||
self.cursorId = result.cursorId; | ||
self.totalNumberOfRecords += result.numberReturned; | ||
// Determine if there's more documents to fetch | ||
if(result.numberReturned > 0) { | ||
if (self.limitValue > 0) { | ||
var excessResult = self.totalNumberOfRecords - self.limitValue; | ||
self.cursorId = result.cursorId; | ||
self.totalNumberOfRecords += result.numberReturned; | ||
// Determine if there's more documents to fetch | ||
if(result.numberReturned > 0) { | ||
if (self.limitValue > 0) { | ||
var excessResult = self.totalNumberOfRecords - self.limitValue; | ||
if (excessResult > 0) { | ||
result.documents.splice(-1*excessResult, excessResult); | ||
if (excessResult > 0) { | ||
result.documents.splice(-1*excessResult, excessResult); | ||
} | ||
} | ||
self.items = self.items.concat(result.documents); | ||
callback(null, self.items.shift()); | ||
} else if(self.tailable) { | ||
self.getMoreTimer = setTimeout(function() {self.getMore(callback);}, 500); | ||
} else { | ||
self.close(function() {callback(null, null);}); | ||
} | ||
self.items = self.items.concat(result.documents); | ||
callback(null, self.items.shift()); | ||
} else if(self.tailable) { | ||
self.getMoreTimer = setTimeout(function() {self.getMore(callback);}, 500); | ||
} else { | ||
self.close(function() {callback(null, null);}); | ||
result = null; | ||
} catch(err) { | ||
callback(new Error(err.toString()), null); | ||
} | ||
result = null; | ||
}); | ||
getMoreCommand = null; | ||
getMoreCommand = null; | ||
} catch(err) { | ||
var handleClose = function() { | ||
var handleClose = function() { | ||
callback(new Error(err.toString()), null); | ||
}; | ||
self.close(handleClose); | ||
@@ -574,4 +590,6 @@ handleClose = null; | ||
cursor.nextObject(function(err, item) { | ||
if(err != null) return callback(err, null); | ||
// close the cursor | ||
cursor.close(function(err, result) { | ||
if(err != null) return callback(err, null); | ||
callback(null, item); | ||
@@ -599,3 +617,5 @@ }); | ||
function execute(command) { | ||
self.db.executeCommand(command, function(err,result) { | ||
self.db.executeCommand(command, {read:true}, function(err,result) { | ||
if(err != null) return callback(err, null); | ||
if (!self.queryRun && result) { | ||
@@ -607,2 +627,3 @@ self.queryRun = true; | ||
} | ||
if (result.documents && result.documents.length) { | ||
@@ -634,2 +655,3 @@ try { | ||
} | ||
return stream; | ||
@@ -652,3 +674,3 @@ }; | ||
var command = new KillCursorCommand(this.db, [this.cursorId]); | ||
this.db.executeCommand(command, null); | ||
this.db.executeCommand(command, {read:true}, null); | ||
} catch(err) {} | ||
@@ -658,6 +680,6 @@ } | ||
this.cursorId = self.db.bson_serializer.Long.fromInt(0); | ||
this.state = Cursor.CLOSED; | ||
this.state = Cursor.CLOSED; | ||
// callback for backward compatibility | ||
if (callback) { | ||
if(callback) { | ||
callback(null, this); | ||
@@ -664,0 +686,0 @@ } else { |
@@ -8,11 +8,10 @@ var QueryCommand = require('./commands/query_command').QueryCommand, | ||
Collection = require('./collection').Collection, | ||
Server = require('./connection').Server, | ||
ServerPair = require('./connection').ServerPair, | ||
ServerCluster = require('./connection').ServerCluster, | ||
ReplSetServers = require('./connection').ReplSetServers, | ||
Server = require('./connections/server').Server, | ||
ReplSetServers = require('./connections/repl_set_servers').ReplSetServers, | ||
Cursor = require('./cursor').Cursor, | ||
MD5 = require('./crypto/md5').MD5, | ||
EventEmitter = require('events').EventEmitter, | ||
inherits = require('sys').inherits, | ||
sys = require('sys'); | ||
inherits = require('util').inherits, | ||
debug = require('util').debug, | ||
inspect = require('util').inspect; | ||
@@ -23,20 +22,24 @@ var Db = exports.Db = function(databaseName, serverConfig, options) { | ||
this.serverConfig = serverConfig; | ||
this.options = options == null ? {} : options; | ||
this.options = options == null ? {} : options; | ||
// Contains all the connections for the db | ||
try { | ||
this.bson_serializer = this.options.native_parser ? require('../../external-libs/bson/bson') : require('./bson/bson'); | ||
this.bson_deserializer = this.options.native_parser ? require('../../external-libs/bson/bson') : require('./bson/bson'); | ||
var serializer = this.options.native_parser ? require('../../external-libs/bson') : require('./bson/bson'); | ||
this.bson_serializer = serializer; | ||
this.bson_deserializer = serializer; | ||
} catch (err) { | ||
// If we tried to instantiate the native driver | ||
throw "Native bson parser not compiled, please compile or avoud using native_parser=true"; | ||
throw "Native bson parser not compiled, please compile or avoid using native_parser=true"; | ||
} | ||
this.connections = []; | ||
// State of the db connection | ||
this.state = 'notConnected'; | ||
this.pkFactory = this.options.pk == null ? this.bson_serializer.ObjectID : this.options.pk; | ||
this.forceServerObjectId = this.options.forceServerObjectId != null ? this.options.forceServerObjectId : false; | ||
// Added strict | ||
this.strict = this.options.strict == null ? false : this.options.strict; | ||
this.notReplied ={}; | ||
this.slaveOk = false; | ||
this.isInitializing = true; | ||
this.auths = []; | ||
}; | ||
@@ -50,255 +53,31 @@ | ||
// Set up connections | ||
if(self.serverConfig instanceof Server) { | ||
self.serverConfig.connection = new Connection(self.serverConfig.host, self.serverConfig.port, self.serverConfig.autoReconnect); | ||
self.connections.push(self.serverConfig.connection); | ||
var server = self.serverConfig; | ||
self.serverConfig.connection.addListener("connect", function() { | ||
// Create a callback function for a given connection | ||
var connectCallback = function(err, reply) { | ||
if(err != null) { | ||
return callback(err, null); | ||
} else if(reply.documents[0].ismaster == 1) { | ||
self.serverConfig.master = true; | ||
} else if(reply.documents[0].ismaster == 0) { | ||
self.serverConfig.master = false; | ||
} | ||
// emit a message saying we got a master and are ready to go and change state to reflect it | ||
if(self.state == 'notConnected') { | ||
self.state = 'connected'; | ||
// | ||
// Call the server version function via admin to adapt to changes from 1.7.6 > | ||
self.admin(function(err, admindb) { | ||
admindb.serverInfo(function(err, doc) { | ||
if(err != null) return callback(err, null); | ||
// Store the db version | ||
self.version = doc.version; | ||
callback(null, self); | ||
}); | ||
}); | ||
} else { | ||
callback("connection already opened"); | ||
} | ||
}; | ||
// Create db command and Add the callback to the list of callbacks by the request id (mapping outgoing messages to correct callbacks) | ||
var db_command = DbCommand.createIsMasterCommand(self); | ||
self.addListener(db_command.getRequestId().toString(), connectCallback); | ||
self.notReplied[db_command.getRequestId().toString()] = this; | ||
// Let's send a request to identify the state of the server | ||
this.send(db_command); | ||
}); | ||
self.serverConfig.connection.addListener("data", function(message) { | ||
// Parse the data as a reply object | ||
var reply = new MongoReply(self, message); | ||
// Emit message | ||
self.emit(reply.responseTo.toString(), null, reply); | ||
// Remove the listener | ||
if ( self.notReplied[ reply.responseTo.toString()]) { | ||
delete self.notReplied[ reply.responseTo.toString()]; | ||
self.removeListener(reply.responseTo.toString(), self.listeners( reply.responseTo.toString())[0] ); | ||
} | ||
}); | ||
if(self.serverConfig instanceof Server || self.serverConfig instanceof ReplSetServers) { | ||
// Inner function for authentication | ||
var authenticateFunction = function(self, username, password) { | ||
return function() { | ||
self.authenticate(username, password, function(err, result) { | ||
// Just ignore the result for now | ||
}); | ||
} | ||
} | ||
self.serverConfig.connection.addListener("error", function(err) { | ||
if(self.listeners("error") != null && self.listeners("error").length > 0) self.emit("error", err); | ||
self.state = "notConnected" | ||
return callback(err, null); | ||
}); | ||
// Emit timeout and close events so the client using db can figure do proper error handling (emit contains the connection that triggered the event) | ||
self.serverConfig.connection.addListener("timeout", function() { self.emit("timeout", this); }); | ||
self.serverConfig.connection.addListener("close", function() { self.emit("close", this); }); | ||
// Open the connection | ||
self.serverConfig.connection.open(); | ||
} else if(self.serverConfig instanceof ServerPair || self.serverConfig instanceof ServerCluster) { | ||
var serverConnections = self.serverConfig instanceof ServerPair ? [self.serverConfig.leftServer, self.serverConfig.rightServer] : self.serverConfig.servers; | ||
var numberOfCheckedServers = 0; | ||
serverConnections.forEach(function(server) { | ||
server.connection = new Connection(server.host, server.port, server.autoReconnect); | ||
self.connections.push(server.connection); | ||
var handleServerConnection = function() { | ||
numberOfCheckedServers+=1; | ||
if(numberOfCheckedServers == serverConnections.length) { | ||
if(self.masterConnection) { | ||
// emit a message saying we got a master and are ready to go and change state to reflect it | ||
self.state = 'connected'; | ||
callback(null, self); | ||
} else { | ||
// emit error only when all servers are checked and connecting to them failed. | ||
self.state = "notConnected" | ||
callback(new Error("Failed connecting to any of the servers in the cluster"), null); | ||
} | ||
// Add a listener for the reconnect event | ||
self.on("reconnect", function() { | ||
// Number of current auths | ||
var authLength = self.auths.length; | ||
// // If we have any auths fire off the auth message to all the connections | ||
if(self.auths.length > 0) { | ||
for(var i = 0; i < authLength; i++) { | ||
authenticateFunction(self, self.auths[i].username, self.auths[i].password)(); | ||
} | ||
} | ||
}); | ||
server.connection.addListener("connect", function() { | ||
// Create a callback function for a given connection | ||
var connectCallback = function(err, reply) { | ||
if(err != null) { | ||
callback(err, null); | ||
} else { | ||
if(reply.documents[0].ismaster == 1) { | ||
// Locate the master connection and save it | ||
self.masterConnection = server.connection; | ||
server.master = true; | ||
} else { | ||
server.master = false; | ||
} | ||
handleServerConnection(); | ||
} | ||
}; | ||
// Create db command and Add the callback to the list of callbacks by the request id (mapping outgoing messages to correct callbacks) | ||
var db_command = DbCommand.createIsMasterCommand(self); | ||
self.addListener(db_command.getRequestId().toString(), connectCallback); | ||
// Let's send a request to identify the state of the server | ||
this.send(db_command); | ||
}); | ||
server.connection.addListener("data", function(message) { | ||
// Parse the data as a reply object | ||
var reply = new MongoReply(self, message); | ||
// Emit error if there is one | ||
reply.responseHasError ? self.emit(reply.responseTo.toString(), reply.documents[0], reply) : self.emit(reply.responseTo.toString(), null, reply); | ||
// Remove the listener | ||
self.removeListener(reply.responseTo.toString(), self.listeners(reply.responseTo.toString())[0]); | ||
}); | ||
server.connection.addListener("error", function(err) { | ||
handleServerConnection(); | ||
}); | ||
// Emit timeout and close events so the client using db can figure do proper error handling (emit contains the connection that triggered the event) | ||
server.connection.addListener("timeout", function() { self.emit("timeout", this); }); | ||
server.connection.addListener("close", function() { self.emit("close", this); }); | ||
// Open the connection | ||
server.connection.open(); | ||
self.serverConfig.connect(self, function(err, result) { | ||
if(err != null) return callback(err, null); | ||
// Callback | ||
return callback(null, self); | ||
}); | ||
} else if ( self.serverConfig instanceof ReplSetServers ) { | ||
var serverConnections = self.serverConfig instanceof ServerPair ? [self.serverConfig.leftServer, self.serverConfig.rightServer] : self.serverConfig.servers; | ||
var numberOfConnectedServers = 0; | ||
var numberOfErrorServers = 0; | ||
self.serverConfig.addresses = {}; | ||
var initServer = function(server) { | ||
self.serverConfig.addresses[ server.host + ':' + server.port ] = 1; | ||
server.connection = new Connection(server.host, server.port, server.autoReconnect); | ||
//console.log( 'Connect to ' + server.host + ':' + server.port ); | ||
self.connections.push(server.connection); | ||
server.connection.addListener("connect", function() { | ||
// Create a callback function for a given connection | ||
var connectCallback = function(err, reply) { | ||
if(err != null) { | ||
callback(err, null); | ||
} else { | ||
if(reply.documents[0].ismaster == 1) { | ||
// Locate the master connection and save it | ||
self.masterConnection = server.connection; | ||
server.master = true; | ||
} else { | ||
server.master = false; | ||
} | ||
if ( self.serverConfig instanceof ReplSetServers && ( reply.documents[0].hosts != undefined ) ) { | ||
var replicas = reply.documents[0].hosts; | ||
for( var i in replicas ) { | ||
if ( replicas[i] in self.serverConfig.addresses ) | ||
continue; | ||
self.serverConfig.addresses[ replicas[i] ] = 1; | ||
var ipAndPort = replicas[i].split(":"); | ||
var newServer = new Server( ipAndPort[0], parseInt( ipAndPort[1]), { auto_reconnect: true} ); | ||
console.log( 'Added ' + replicas[i] + ' to the replica set' ); | ||
serverConnections.push( newServer ); | ||
initServer( newServer ); | ||
} | ||
} | ||
// emit a message saying we got a master and are ready to go and change state to reflect it | ||
if(++numberOfConnectedServers == serverConnections.length && (self.state == 'notConnected')) { | ||
self.state = 'connected'; | ||
self.isInitializing = false; | ||
return callback(null, self); | ||
} | ||
if ( self.serverConfig instanceof ReplSetServers && server.master ) { | ||
//we have the master we are ok, wait for others (if any) to connect too | ||
self.state = 'connected'; | ||
} | ||
if ( self.serverConfig instanceof ReplSetServers && ( (numberOfConnectedServers + numberOfErrorServers ) == serverConnections.length )) { | ||
self.isInitializing = false; | ||
if ( self.state == 'connected' ) { | ||
return callback( null, self ); | ||
} else { | ||
return callback( new Error( 'No master available'), null ); | ||
} | ||
} | ||
} | ||
}; | ||
// Create db command and Add the callback to the list of callbacks by the request id (mapping outgoing messages to correct callbacks) | ||
var db_command = DbCommand.createIsMasterCommand(self); | ||
self.addListener(db_command.getRequestId().toString(), connectCallback); | ||
self.notReplied[db_command.getRequestId().toString()] = this; | ||
// Let's send a request to identify the state of the server | ||
this.send(db_command); | ||
server.connection.addListener("data", function(message) { | ||
// Parse the data as a reply object | ||
var reply = new MongoReply(self, message); | ||
// Emit error if there is one | ||
reply.responseHasError ? self.emit(reply.responseTo.toString(), reply.documents[0], reply) : self.emit(reply.responseTo.toString(), null, reply); | ||
// Remove the listener | ||
//if ( self.listeners(reply.responseTo.toString()).length ) | ||
if ( self.notReplied [ reply.responseTo.toString()] ) { | ||
delete self.notReplied[ reply.responseTo.toString()]; | ||
self.removeListener(reply.responseTo.toString(), self.listeners(reply.responseTo.toString())[0]); | ||
} | ||
}); | ||
}); | ||
server.connection.addListener("error", function(err) { | ||
if ( self.serverConfig instanceof ReplSetServers && self.isInitializing) { | ||
//we only have one error, if the rest are ok there is no problem | ||
numberOfErrorServers++; | ||
//console.log( server.host + ':' + server.port + ' down!!!'+ err ); | ||
if ( (numberOfErrorServers + numberOfConnectedServers) == serverConnections.length) { | ||
self.isInitializing = false; | ||
if ( self.state == 'connected' ) { | ||
return callback( null, self ); | ||
} else { | ||
return callback( new Error( 'No master available'), null ); | ||
} | ||
} | ||
} else if ( self.serverConfig instanceof ReplSetServers ) { | ||
for ( var i in self.notReplied ) { | ||
//console.log( 'delete event ' + i ); | ||
if ( self.notReplied[i] == this ) { | ||
delete self.notReplied[i]; | ||
self.emit( i, null, { documents: [{'$err':'Connection closed'}] } ); | ||
self.removeListener( i, self.listeners( i )[0]); | ||
} | ||
} | ||
} else { | ||
return callback(err, null); | ||
} | ||
}); | ||
// Emit timeout and close events so the client using db can figure do proper error handling (emit contains the connection that triggered the event) | ||
server.connection.addListener("timeout", function() { self.emit("timeout", this); }); | ||
server.connection.addListener("close", function() { self.emit("close", this); }); | ||
// Open the connection | ||
server.connection.open(); | ||
}; | ||
serverConnections.forEach( initServer ); | ||
} else { | ||
return callback(Error("Server parameter must be of type Server, ServerPair, ServerCluster or ReplSetServers"), null); | ||
return callback(Error("Server parameter must be of type Server or ReplSetServers"), null); | ||
} | ||
@@ -308,5 +87,6 @@ }; | ||
Db.prototype.close = function() { | ||
this.connections.forEach(function(connection) { | ||
connection.close(); | ||
}); | ||
// Remove all listeners | ||
this.removeAllListeners("reconnect"); | ||
// Close connection | ||
this.serverConfig.close(); | ||
// Clear out state of the connection | ||
@@ -317,2 +97,3 @@ this.state = "notConnected" | ||
Db.prototype.admin = function(callback) { | ||
if(callback == null) return new Admin(this); | ||
callback(null, new Admin(this)); | ||
@@ -348,3 +129,7 @@ }; | ||
self.collectionsInfo(collection_name, function(err, cursor) { | ||
if(err != null) return callback(err, null); | ||
cursor.toArray(function(err, documents) { | ||
if(err != null) return callback(err, null); | ||
// List of result documents that have been filtered | ||
@@ -366,16 +151,19 @@ var filtered_documents = []; | ||
**/ | ||
Db.prototype.collection = function(collectionName, callback) { | ||
Db.prototype.collection = function(collectionName, options, callback) { | ||
var self = this; | ||
if(typeof options === "function") { callback = options; options = {}; } | ||
try { | ||
if(self.strict) { | ||
if(options && options.safe || this.strict) { | ||
self.collectionNames(collectionName, function(err, collections) { | ||
if(err != null) return callback(err, null); | ||
if(collections.length == 0) { | ||
callback(new Error("Collection " + collectionName + " does not exist. Currently in strict mode."), null); | ||
return callback(new Error("Collection " + collectionName + " does not exist. Currently in strict mode."), null); | ||
} else { | ||
return callback(null, new Collection(self, collectionName, self.pkFactory)); | ||
return callback(null, new Collection(self, collectionName, self.pkFactory, options)); | ||
} | ||
}); | ||
} else { | ||
return callback(null, new Collection(self, collectionName, self.pkFactory)); | ||
return callback(null, new Collection(self, collectionName, self.pkFactory, options)); | ||
} | ||
@@ -394,5 +182,6 @@ } catch(err) { | ||
self.collectionNames(function(err, documents) { | ||
if(err != null) return callback(err, null); | ||
var collections = []; | ||
documents.forEach(function(document) { | ||
collections.push(new Collection(self, document.name.replace(self.databaseName + ".", ''))); | ||
collections.push(new Collection(self, document.name.replace(self.databaseName + ".", ''), self.pkFactory)); | ||
}); | ||
@@ -426,2 +215,4 @@ // Return the collection objects | ||
new Cursor(this, new Collection(this, DbCommand.SYSTEM_COMMAND_COLLECTION), selector, {}, 0, -1).nextObject(function(err, result) { | ||
if(err != null) return callback(err, null); | ||
if(result.ok == 1) { | ||
@@ -437,2 +228,4 @@ callback(null, result.retval); | ||
this.collection(dbRef.namespace, function(err, collection) { | ||
if(err != null) return callback(err, null); | ||
collection.findOne({'_id':dbRef.oid}, function(err, result) { | ||
@@ -449,19 +242,45 @@ callback(err, result); | ||
var self = this; | ||
// Execute command | ||
this.executeCommand(DbCommand.createGetNonceCommand(self), function(err, reply) { | ||
if(err == null) { | ||
// Nonce used to make authentication request with md5 hash | ||
var nonce = reply.documents[0].nonce; | ||
// Execute command | ||
self.executeCommand(DbCommand.createAuthenticationCommand(self, username, password, nonce), function(err, result) { | ||
if(err == null && result.documents[0].ok == 1) { | ||
callback(null, true); | ||
// Add the auth details to the connection for reconnects or update existing if any | ||
var found = false; | ||
for(var i = 0; i < self.auths.length; i++) { | ||
// If we have found an existing auth, update the password | ||
if(self.auths[i].username == username) { | ||
found = true; | ||
self.auths[i].password = password; | ||
} | ||
} | ||
// Push the new auth if we have no previous record | ||
if(!found) self.auths.push({'username':username, 'password':password}); | ||
// Figure out the number of times we need to trigger | ||
var rawConnections = self.serverConfig.allRawConnections(); | ||
var numberOfExpectedReturns = rawConnections.length; | ||
// Execute the commands | ||
for(var i = 0; i < numberOfExpectedReturns; i++) { | ||
// Execute command | ||
var createNonceCallback = function(index) { | ||
return function(err, reply) { | ||
if(err == null) { | ||
// Nonce used to make authentication request with md5 hash | ||
var nonce = reply.documents[0].nonce; | ||
// Execute command | ||
self.executeCommand(DbCommand.createAuthenticationCommand(self, username, password, nonce), {writer: rawConnections[index].connection}, function(err, result) { | ||
// Ajust the number of expected results | ||
numberOfExpectedReturns = numberOfExpectedReturns - 1; | ||
// If we are done let's evaluate | ||
if(numberOfExpectedReturns <= 0) { | ||
if(err == null && result.documents[0].ok == 1) { | ||
callback(null, true); | ||
} else { | ||
err != null ? callback(err, false) : callback(new Error(result.documents[0].errmsg), false); | ||
} | ||
} | ||
}); | ||
} else { | ||
err != null ? callback(err, false) : callback(new Error(result.documents[0].errmsg), false); | ||
callback(err, null); | ||
} | ||
}); | ||
} else { | ||
callback(err, null); | ||
} | ||
} | ||
}); | ||
this.executeCommand(DbCommand.createGetNonceCommand(self), {writer: rawConnections[i].connection}, createNonceCallback(i)); | ||
} | ||
}; | ||
@@ -477,3 +296,3 @@ | ||
// Insert the user into the system users collections | ||
collection.insert({user: username, pwd: userPassword}, function(err, documents) { | ||
collection.insert({user: username, pwd: userPassword}, {safe:true}, function(err, documents) { | ||
callback(err, documents); | ||
@@ -516,6 +335,7 @@ }); | ||
options = args.length ? args.shift() : null; | ||
var self = this; | ||
// Check if we have the name | ||
this.collectionNames(collectionName, function(err, collections) { | ||
if(err != null) return callback(err, null); | ||
var found = false; | ||
@@ -534,3 +354,3 @@ collections.forEach(function(collection) { | ||
// Create a new collection and return it | ||
self.executeCommand(DbCommand.createCreateCollectionCommand(self, collectionName, options), function(err, result) { | ||
self.executeCommand(DbCommand.createCreateCollectionCommand(self, collectionName, options), {read:false, safe:true}, function(err, result) { | ||
if(err == null && result.documents[0].ok == 1) { | ||
@@ -573,10 +393,12 @@ callback(null, new Collection(self, collectionName, self.pkFactory)); | ||
**/ | ||
Db.prototype.lastError = function(callback) { | ||
this.executeCommand(DbCommand.createGetLastErrorCommand(this), function(err, error) { | ||
callback(err, error.documents); | ||
Db.prototype.lastError = function(options, callback) { | ||
if ('function' === typeof options) callback = options, options = {}; | ||
this.executeCommand(DbCommand.createGetLastErrorCommand(options, this), function(err, error) { | ||
callback(err, error && error.documents); | ||
}); | ||
}; | ||
Db.prototype.error = function(callback) { | ||
this.lastError(callback); | ||
Db.prototype.error = function(options, callback) { | ||
this.lastError(options, callback); | ||
}; | ||
@@ -608,2 +430,9 @@ | ||
/** | ||
Runs a command on the database as admin | ||
**/ | ||
Db.prototype.executeDbAdminCommand = function(command_hash, callback) { | ||
this.executeCommand(DbCommand.createAdminDbCommand(this, command_hash), callback); | ||
}; | ||
/** | ||
Resets the error history of the mongo instance | ||
@@ -618,7 +447,15 @@ **/ | ||
**/ | ||
Db.prototype.createIndex = function(collectionName, fieldOrSpec, unique, callback) { | ||
if(callback == null) { callback = unique; unique = null; } | ||
var command = DbCommand.createCreateIndexCommand(this, collectionName, fieldOrSpec, unique); | ||
this.executeCommand(command, function(result) {}); | ||
callback(null, command.documents[0].name); | ||
Db.prototype.createIndex = function(collectionName, fieldOrSpec, options, callback) { | ||
if(callback == null) { callback = options; options = null; } | ||
var command = DbCommand.createCreateIndexCommand(this, collectionName, fieldOrSpec, options); | ||
this.executeCommand(command, {read:false, safe:true}, function(err, result) { | ||
if(err != null) return callback(err, null); | ||
result = result && result.documents; | ||
if (result[0].err) { | ||
callback(new Error(result[0].err)); | ||
} else { | ||
callback(null, command.documents[0].name); | ||
} | ||
}); | ||
}; | ||
@@ -629,5 +466,5 @@ | ||
**/ | ||
Db.prototype.ensureIndex = function(collectionName, fieldOrSpec, unique, callback) { | ||
if(callback == null) { callback = unique; unique = null; } | ||
var command = DbCommand.createCreateIndexCommand(this, collectionName, fieldOrSpec, unique); | ||
Db.prototype.ensureIndex = function(collectionName, fieldOrSpec, options, callback) { | ||
if(callback == null) { callback = options; options = null; } | ||
var command = DbCommand.createCreateIndexCommand(this, collectionName, fieldOrSpec, options); | ||
var index_name = command.documents[0].name; | ||
@@ -637,4 +474,16 @@ var self = this; | ||
this.indexInformation(collectionName, function(err, collectionInfo) { | ||
if(!collectionInfo[index_name]) self.executeCommand(command, function(result) {}); | ||
return callback(null, index_name); | ||
if(!collectionInfo[index_name]) { | ||
self.executeCommand(command, {read:false, safe:true}, function(err, result) { | ||
if(err != null) return callback(err, null); | ||
result = result && result.documents; | ||
if (result[0].err) { | ||
callback(new Error(result[0].err)); | ||
} else { | ||
callback(null, command.documents[0].name); | ||
} | ||
}); | ||
} else { | ||
return callback(null, index_name); | ||
} | ||
}); | ||
@@ -669,2 +518,4 @@ }; | ||
new Cursor(this, new Collection(this, DbCommand.SYSTEM_INDEX_COLLECTION), selector).each(function(err, index) { | ||
if(err != null) return callback(err, null); | ||
// Return the info when finished | ||
@@ -694,45 +545,110 @@ if(index == null) { | ||
**/ | ||
Db.prototype.executeCommand = function(db_command, callback) { | ||
Db.prototype.executeCommand = function(db_command, options, callback) { | ||
var self = this; | ||
var args = Array.prototype.slice.call(arguments, 1); | ||
callback = args.pop(); | ||
options = args.length ? args.shift() : {}; | ||
// Options unpacking | ||
var read = options['read'] != null ? options['read'] : false; | ||
var safe = options['safe'] != null ? options['safe'] : false; | ||
// Let's us pass in a writer to force the use of a connection (used for admin where we need to peform 2 calls against the same connection) | ||
var rawConnection = options['writer'] != null ? options['writer'] : null; | ||
// debug("===================================================== executeCommnad") | ||
// debug(" read :: " + read) | ||
// debug(" safe :: " + safe) | ||
// debug(" writer :: " + writer) | ||
var errorCommand = null; | ||
if(safe == true) { | ||
errorCommand = DbCommand.createGetLastErrorCommand(safe, this); | ||
} | ||
// If we have a callback execute | ||
if(callback instanceof Function) { | ||
var listenToCommand = errorCommand != null ? errorCommand : db_command; | ||
// Add the callback to the list of callbacks by the request id (mapping outgoing messages to correct callbacks) | ||
this.addListener(db_command.getRequestId().toString(), callback); | ||
if ( self.serverConfig.masterConnection != null ) { | ||
this.notReplied[db_command.getRequestId().toString()] = self.serverConfig.masterConnection; | ||
} | ||
this.on(listenToCommand.getRequestId().toString(), callback); | ||
if(self.serverConfig.primary != null) { | ||
this.notReplied[listenToCommand.getRequestId().toString()] = self.serverConfig.primary; | ||
} | ||
} | ||
// Correctly handle serialization errors | ||
var checkMasterHandler = function(err, reply, dbinstance){ | ||
if (err == null){ | ||
try{ | ||
if ( dbinstance.backup.server ) { // use slave this ONE time | ||
self.notReplied[db_command.getRequestId().toString()] = dbinstance.backup.server.connection; | ||
dbinstance.backup.server.connection.send( db_command); | ||
dbinstance.backup.server = null; | ||
} else { | ||
self.notReplied[db_command.getRequestId().toString()] = dbinstance.serverConfig.masterConnection; | ||
dbinstance.serverConfig.masterConnection.send(db_command); | ||
} | ||
} catch ( err ) { | ||
// Clean up callback if it exists | ||
if(this.notReplied[db_command.getRequestId().toString()] != null) { | ||
delete self.notReplied[db_command.getRequestId().toString()]; | ||
} | ||
if(callback instanceof Function) { | ||
return callback(err, null); | ||
} | ||
try{ | ||
// Attempt forcing a reconnect if we have a replicaset server | ||
if(self.serverConfig instanceof ReplSetServers && !self.serverConfig.isConnected()) { | ||
// Initialize | ||
self.isInitializing = true; | ||
// Number of retries | ||
var retries = self.serverConfig.retries; | ||
// Attempts a reconnect | ||
var reconnectAttempt = function() { | ||
// Try reconnect | ||
self.serverConfig.connect(self, function(err, result) { | ||
// debug("============================================================ reconnectAttemp") | ||
// debug("err :: " + inspect(err)) | ||
// Initialize | ||
self.isInitializing = true; | ||
// Set retries | ||
retries = retries - 1; | ||
// If we fail retry the connec | ||
if(err != null && retries > 0) { | ||
// Wait an try again | ||
setTimeout(reconnectAttempt, self.serverConfig.reconnectWait); | ||
} else { | ||
// Ensure we catch any errors happening and report them (especially important for replicaset servers) | ||
try { | ||
if(err != null && callback instanceof Function) return callback(err, null); | ||
// for the other instances fire the message | ||
// debug("=========================== attempt read :: 2 :: " + read) | ||
var writer = read ? self.serverConfig.checkoutReader() : self.serverConfig.checkoutWriter(); | ||
// If we got safe set | ||
if(errorCommand != null) { | ||
writer.send([db_command, errorCommand], rawConnection); | ||
} else { | ||
writer.send(db_command, rawConnection) | ||
} | ||
} catch (err) { | ||
// Set server config to disconnected if it's a replicaset | ||
if(self.serverConfig instanceof ReplSetServers && err == "notConnected") { | ||
// Just clear up all connections as we need to perform a complete reconnect for the call | ||
self.serverConfig.disconnect(); | ||
} | ||
// Signal an error | ||
if(!(err instanceof Error)) err = new Error(err); | ||
if(callback instanceof Function) { | ||
if(errorCommand != null) delete self.notReplied[errorCommand.getRequestId().toString()]; | ||
return callback(err, null); | ||
} | ||
} | ||
} | ||
}); | ||
} | ||
// Force a reconnect after self.serverConfig.reconnectWait seconds | ||
setTimeout(reconnectAttempt, self.serverConfig.reconnectWait); | ||
} else { | ||
// for the other instances fire the message | ||
var writer = read ? self.serverConfig.checkoutReader() : self.serverConfig.checkoutWriter(); | ||
// If we got safe set | ||
if(errorCommand != null) { | ||
writer.send([db_command, errorCommand], rawConnection); | ||
} else { | ||
writer.send(db_command, rawConnection) | ||
} | ||
} else { | ||
// XXX : LOOP!!!!!! | ||
setTimeout( self.checkMaster_(self, checkMasterHandler), 50 ); | ||
} | ||
} catch(err){ | ||
// Set server config to disconnected if it's a replicaset | ||
if(self.serverConfig instanceof ReplSetServers && err == "notConnected") { | ||
// Just clear up all connections as we need to perform a complete reconnect for the call | ||
self.serverConfig.disconnect(); | ||
} | ||
}; | ||
try{ | ||
self.serverConfig.masterConnection.send(db_command); | ||
} catch(err){ | ||
if(callback instanceof Function) { | ||
delete self.notReplied[db_command.getRequestId().toString()]; | ||
// Signal an error | ||
if(!(err instanceof Error)) err = new Error(err); | ||
if(callback instanceof Function) { | ||
if(errorCommand != null) delete self.notReplied[errorCommand.getRequestId().toString()]; | ||
return callback(err, null); | ||
@@ -742,3 +658,3 @@ } | ||
// Return error object | ||
return err; | ||
return err; | ||
} | ||
@@ -782,120 +698,1 @@ }; | ||
} | ||
/** | ||
* Checks for latest master by calling isMasterCommand on each server | ||
* of serverConfig | ||
* @param dbcopy{instance of db} | ||
* | ||
**/ | ||
Db.prototype.checkMaster_ = function(dbcopy, returnback) { | ||
var self = dbcopy; | ||
var hasReturned = false; | ||
var answers = 0; | ||
dbcopy.backup = {}; | ||
var servers = dbcopy.serverConfig.servers; | ||
if(Array.isArray(servers)) { | ||
for(var serveri = 0; serveri < servers.length; serveri++) { | ||
var server = servers[serveri]; | ||
server.master = false; | ||
if(server.connection.connection.readyState == "open" || server.connection.autoReconnect) { | ||
var db_cmnd = DbCommand.createIsMasterCommand(dbcopy); | ||
var connect_Callback = function(err, reply) { | ||
if(err != null) { | ||
if (!hasReturned && ( ++answers == dbcopy.serverConfig.servers.length)) { | ||
if (dbcopy.backup.server && dbcopy.backup.reply) { | ||
dbcopy.masterConnection = dbcopy.backup.server.connection; | ||
return returnback( null, dbcopy.backup.reply, dbcopy ); | ||
} else { | ||
return returnback( new Error( 'No master found' ) ); | ||
} | ||
} | ||
} else { | ||
if(reply.documents[0].ismaster == 1) { | ||
// Locate the master connection and save it | ||
dbcopy.masterConnection = server.connection; | ||
server.master = true; | ||
hasReturned = true; | ||
return returnback(null, reply, dbcopy); | ||
} else { | ||
server.master = false; | ||
// we may not have a master so we keep a secondary server, | ||
// that is able to respond, just in case | ||
dbcopy.backup.server = server; | ||
dbcopy.backup.reply = reply; | ||
if ( !hasReturned && ( ++answers == dbcopy.serverConfig.servers.length )) { | ||
if ( dbcopy.backup.server && dbcopy.backup.reply ) { | ||
dbcopy.masterConnection = dbcopy.backup.server.connection; | ||
return returnback( null, dbcopy.backup.reply, dbcopy ); | ||
} else { | ||
return returnback(new Error( 'No master found' )); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
dbcopy.addListener(db_cmnd.getRequestId().toString(), connect_Callback); | ||
self.notReplied[db_cmnd.getRequestId().toString()] = server.connection; | ||
if(server.connection.connection.readyState == "open") { | ||
server.connection.sendwithoutReconnect(db_cmnd); | ||
} else { | ||
// This if it's closed it may not have a listener | ||
// The listener is of general use so we need not use one for every command | ||
if (!server.connection.listeners("data").length) { | ||
server.connection.addListener("data", function(message) { | ||
// Parse the data as a reply object | ||
var reply = null; | ||
if ( message ) { | ||
reply = new MongoReply(self, message); | ||
} else { | ||
reply = {}; | ||
reply.responseHasError = true; | ||
reply.documents = ['Error connecting']; | ||
} | ||
// Emit error if there is one | ||
reply.responseHasError ? self.emit(reply.responseTo.toString(), reply.documents[0], reply) : self.emit(reply.responseTo.toString(), null, reply); | ||
// Remove the listener | ||
if(self.notReplied[ reply.responseTo.toString()]) { | ||
delete self.notReplied[ reply.responseTo.toString()]; | ||
self.removeListener(reply.responseTo.toString(), self.listeners( reply.responseTo.toString())[0]); | ||
} | ||
}); | ||
} | ||
if (server.connection.listeners("error").length == 0) { | ||
server.connection.addListener("error", function(err) { | ||
dbcopy.emit("error", err); | ||
server.master = false; | ||
}); | ||
} | ||
// Emit timeout and close events so the client using db can figure do proper error handling (emit contains the connection that triggered the event) | ||
if (server.connection.listeners("timeout").length == 0) { | ||
server.connection.addListener("timeout", function() { dbcopy.emit("timeout", this); }); | ||
} | ||
if (server.connection.listeners("close").length == 0) { | ||
server.connection.addListener("close", function() { dbcopy.emit("close", this); }); | ||
} | ||
server.connection.send(db_cmnd); | ||
} | ||
} else { | ||
server.master = false; | ||
if (!hasReturned && ( ++answers == dbcopy.serverConfig.servers.length)) { | ||
if (dbcopy.backup.server && dbcopy.backup.reply) { | ||
dbcopy.masterConnection = dbcopy.backup.server.connection; | ||
return returnback( null, dbcopy.backup.reply, dbcopy ); | ||
} else { | ||
return returnback( new Error( 'No master found' ) ); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} |
var BinaryParser = require('../bson/binary_parser').BinaryParser, | ||
sys = require('sys'); | ||
sys = require('util'), | ||
debug = require('util').debug, | ||
inspect = require('util').inspect; | ||
@@ -61,3 +63,4 @@ /** | ||
Chunk.prototype.write = function(data, callback) { | ||
this.data.write(data.toString('binary'), this.internalPosition); | ||
// this.data.write(data.toString('binary'), this.internalPosition); | ||
this.data.write(data, this.internalPosition); | ||
this.internalPosition = this.data.length(); | ||
@@ -92,4 +95,4 @@ callback(null, this); | ||
data = new Buffer(length); | ||
//todo there is performance degradation! we need direct Binary::write() into buffer with offset support! | ||
length = data.write(this.data.read(this.internalPosition, length), 'binary', 0); | ||
//length = data.write(this.data.read(this.internalPosition, length), 'binary', 0); | ||
length = this.data.readInto(data, this.internalPosition); | ||
} | ||
@@ -145,6 +148,7 @@ this.internalPosition = this.internalPosition + length; | ||
self.file.chunkCollection(function(err, collection) { | ||
collection.remove({'_id':self.objectId}, function(err, collection) { | ||
collection.remove({'_id':self.objectId}, {safe:true}, function(err, result) { | ||
if(self.data.length() > 0) { | ||
self.buildMongoObject(function(mongoObject) { | ||
collection.insert(mongoObject, function(collection) { | ||
collection.insert(mongoObject, {safe:true}, function(err, collection) { | ||
callback(null, self); | ||
@@ -151,0 +155,0 @@ }); |
@@ -13,10 +13,9 @@ /** | ||
DbCommand = require('../commands/db_command').DbCommand, | ||
Integer = require('../goog/math/integer').Integer, | ||
// ObjectID = require('../bson/bson').ObjectID, | ||
Buffer = require('buffer').Buffer, | ||
fs = require('fs'), | ||
util = require('util'), | ||
debug = require('util').debug, | ||
inspect = require('util').inspect, | ||
Stream = require('stream').Stream; | ||
/** | ||
@@ -96,3 +95,26 @@ * A class representation of a file stored in GridFS. | ||
var self = this; | ||
if((self.mode == "w" || self.mode == "w+") && self.db.serverConfig.primary != null) { | ||
// Get files collection | ||
self.collection(function(err, collection) { | ||
// Ensure index on files Collection | ||
collection.ensureIndex([['filename', 1], ['uploadDate', -1]], function(err, index) { | ||
// Get chunk collection | ||
self.chunkCollection(function(err, chunkCollection) { | ||
// Ensure index on chunk collection | ||
chunkCollection.ensureIndex([['files_id', 1], ['n', 1]], function(err, index) { | ||
self._open(callback); | ||
}); | ||
}); | ||
}); | ||
}); | ||
} else { | ||
self._open(callback); | ||
} | ||
} | ||
GridStore.prototype._open = function(callback) { | ||
var self = this; | ||
self.collection(function(err, collection) { | ||
@@ -127,5 +149,17 @@ if(err!==null) { | ||
if(self.mode == "r") { | ||
chunkCollection.ensureIndex([['files_id', 1], ['n', 1]], function(err, index) { | ||
self.nthChunk(0, function(err, chunk) { | ||
self.currentChunk = chunk; | ||
// chunkCollection.ensureIndex([['files_id', 1], ['n', 1]], function(err, index) { | ||
self.nthChunk(0, function(err, chunk) { | ||
self.currentChunk = chunk; | ||
self.position = 0; | ||
callback(null, self); | ||
}); | ||
// }); | ||
} else if(self.mode == "w") { | ||
self.chunkCollection(function(err, collection2) { | ||
// Delete any existing chunks | ||
self.deleteChunks(function(err, result) { | ||
self.currentChunk = new Chunk(self, {'n':0}); | ||
self.contentType = self.options['content_type'] == null ? self.contentType : self.options['content_type']; | ||
self.internalChunkSize = self.options['chunk_size'] == null ? self.internalChunkSize : self.options['chunk_size']; | ||
self.metadata = self.options['metadata'] == null ? self.metadata : self.options['metadata']; | ||
self.position = 0; | ||
@@ -135,31 +169,13 @@ callback(null, self); | ||
}); | ||
} else if(self.mode == "w") { | ||
self.chunkCollection(function(err, collection2) { | ||
// Create index for the chunks | ||
//chunkCollection.ensureIndex([['files_id', 1], ['n', 1]], function(err, index) { | ||
// Delete any existing chunks | ||
self.deleteChunks(function(err, result) { | ||
self.currentChunk = new Chunk(self, {'n':0}); | ||
self.contentType = self.options['content_type'] == null ? self.contentType : self.options['content_type']; | ||
self.internalChunkSize = self.options['chunk_size'] == null ? self.internalChunkSize : self.options['chunk_size']; | ||
self.metadata = self.options['metadata'] == null ? self.metadata : self.options['metadata']; | ||
self.position = 0; | ||
callback(null, self); | ||
}); | ||
//}); | ||
}); | ||
} else if(self.mode == "w+") { | ||
self.chunkCollection(function(err, collection) { | ||
// Create index for the chunks | ||
//chunkCollection.ensureIndex([['files_id', 1], ['n', 1]], function(err, index) { | ||
self.nthChunk(self.lastChunkNumber(), function(err, chunk) { | ||
// Set the current chunk | ||
self.currentChunk = chunk == null ? new Chunk(self, {'n':0}) : chunk; | ||
self.currentChunk.position = self.currentChunk.data.length(); | ||
self.metadata = self.options['metadata'] == null ? self.metadata : self.options['metadata']; | ||
self.position = self.length; | ||
callback(null, self); | ||
}); | ||
//}); | ||
}); | ||
self.nthChunk(self.lastChunkNumber(), function(err, chunk) { | ||
// Set the current chunk | ||
self.currentChunk = chunk == null ? new Chunk(self, {'n':0}) : chunk; | ||
self.currentChunk.position = self.currentChunk.data.length(); | ||
self.metadata = self.options['metadata'] == null ? self.metadata : self.options['metadata']; | ||
self.position = self.length; | ||
callback(null, self); | ||
}); | ||
}); | ||
} else { | ||
@@ -196,22 +212,56 @@ callback(new Error("Illegal mode " + self.mode), null); | ||
fs.fstat(file, function (err, stats) { | ||
var startIndices = []; | ||
for (var i = 0; i < stats.size; i += self.chunkSize) startIndices.push(i); | ||
startIndices.forEach(function (start, index, startIndices) { | ||
process.nextTick(function () { | ||
fs.read(file, self.chunkSize, start, 'binary', function (err, data, bytesRead) { | ||
var chunk = new Chunk(self, {n: index}); | ||
chunk.write(data, function (err, chunk) { | ||
chunk.save(function (err, result) { | ||
if (index == startIndices.length -1) { | ||
self.currentChunk = chunk; | ||
self.close(function (err, result) { | ||
callback(null, self); | ||
}); | ||
} | ||
}); | ||
var offset = 0; | ||
var index = 0; | ||
var numberOfChunksLeft = Math.min(stats.size / self.chunkSize); | ||
// Write a chunk | ||
var writeChunk = function() { | ||
fs.read(file, self.chunkSize, offset, 'binary', function(err, data, bytesRead) { | ||
offset = offset + bytesRead; | ||
// Create a new chunk for the data | ||
var chunk = new Chunk(self, {n:index++}); | ||
chunk.write(data, function(err, chunk) { | ||
chunk.save(function(err, result) { | ||
// Point to current chunk | ||
self.currentChunk = chunk; | ||
// debug("=========================== err :: " + err) | ||
// debug("=========================== err :: " + inspect(result)) | ||
// debug("============================= offset :: " + offset) | ||
if(offset >= stats.size) { | ||
fs.close(file); | ||
self.close(function(err, result) { | ||
return callback(null, self); | ||
}) | ||
} else { | ||
return process.nextTick(writeChunk); | ||
} | ||
}); | ||
}); | ||
}); | ||
}); | ||
} | ||
// Process the first write | ||
process.nextTick(writeChunk); | ||
// var startIndices = []; | ||
// for (var i = 0; i < stats.size; i += self.chunkSize) startIndices.push(i); | ||
// | ||
// startIndices.forEach(function (start, index, startIndices) { | ||
// process.nextTick(function () { | ||
// fs.read(file, self.chunkSize, start, 'binary', function (err, data, bytesRead) { | ||
// var chunk = new Chunk(self, {n: index}); | ||
// chunk.write(data, function (err, chunk) { | ||
// chunk.save(function (err, result) { | ||
// if (index == startIndices.length -1) { | ||
// self.currentChunk = chunk; | ||
// self.close(function (err, result) { | ||
// callback(null, self); | ||
// }); | ||
// } | ||
// }); | ||
// }); | ||
// }); | ||
// }); | ||
// }); | ||
}); | ||
@@ -245,4 +295,2 @@ }); | ||
if((self.currentChunk.position + string.length) > self.chunkSize) { | ||
// sys.puts("==============================================================1") | ||
var previousChunkNumber = self.currentChunk.chunkNumber; | ||
@@ -400,5 +448,5 @@ var leftOverDataSize = self.chunkSize - self.currentChunk.position; | ||
if(self.uploadDate != null) { | ||
files.remove({'_id':self.fileId}, function(err, collection) { | ||
files.remove({'_id':self.fileId}, {safe:true}, function(err, collection) { | ||
self.buildMongoObject(function(mongoObject) { | ||
files.save(mongoObject, function(err, doc) { | ||
files.save(mongoObject, {safe:true}, function(err, doc) { | ||
callback(err, doc); | ||
@@ -411,3 +459,3 @@ }); | ||
self.buildMongoObject(function(mongoObject) { | ||
files.save( mongoObject, function(err, doc) { | ||
files.save(mongoObject, {safe:true}, function(err, doc) { | ||
callback(err, doc); | ||
@@ -423,3 +471,3 @@ }); | ||
self.buildMongoObject(function(mongoObject) { | ||
files.save(mongoObject, function(err, doc) { | ||
files.save(mongoObject, {safe:true}, function(err, doc) { | ||
callback(err, doc); | ||
@@ -430,2 +478,4 @@ }); | ||
} | ||
} else if(self.mode[0] == "r") { | ||
callback(null, null); | ||
} else { | ||
@@ -501,3 +551,3 @@ callback(new Error("Illegal mode " + self.mode), null); | ||
} | ||
collection.remove({'files_id':self.fileId}, function(err, result) { | ||
collection.remove({'files_id':self.fileId}, {safe:true}, function(err, result) { | ||
callback(null, true); | ||
@@ -525,3 +575,3 @@ }); | ||
collection.remove({'_id':self.fileId}, function(err, collection) { | ||
collection.remove({'_id':self.fileId}, {safe:true}, function(err, collection) { | ||
callback(err, self); | ||
@@ -657,2 +707,3 @@ }); | ||
numberToRead = numberToRead - self.currentChunk.length(); | ||
// Load the next chunk and read some more | ||
@@ -839,3 +890,3 @@ self.nthChunk(self.currentChunk.chunkNumber + 1, function(err, chunk) { | ||
*/ | ||
GridStore.DEFAULT_CONTENT_TYPE = 'text/plain'; | ||
GridStore.DEFAULT_CONTENT_TYPE = 'binary/octet-stream'; | ||
/** | ||
@@ -1033,3 +1084,3 @@ * Seek mode where the given length is absolute. | ||
gridStore.collection(function(err, collection) { | ||
collection.remove({'_id':gridStore.fileId}, function(err, collection) { | ||
collection.remove({'_id':gridStore.fileId}, {safe:true}, function(err, collection) { | ||
callback(err, self); | ||
@@ -1083,3 +1134,3 @@ }); | ||
var data = gstore.currentChunk.read(toRead); | ||
var data = gstore.currentChunk.readSlice(toRead); | ||
if (data != null) { | ||
@@ -1086,0 +1137,0 @@ self.completedLength += data.length; |
@@ -1,27 +0,102 @@ | ||
var sys = require('sys') | ||
// // Add both the BSON Pure classes and the native ones | ||
var BSONPure = exports.BSONPure = require('./bson/bson'); | ||
var BSONNative = null | ||
try { | ||
BSONNative = exports.BSONNative = require('../../external-libs/bson/bson'); | ||
exports.BSONPure = require('./bson/bson'); | ||
exports.BSONNative = require('../../external-libs/bson/bson'); | ||
} catch(err) { | ||
// do nothing | ||
} | ||
[ | ||
'bson/binary_parser', | ||
'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', 'cursor', 'db', | ||
'goog/math/integer', 'goog/math/long', 'crypto/md5', | ||
'gridfs/chunk', 'gridfs/gridstore' | ||
].forEach(function(path){ | ||
[ 'bson/binary_parser' | ||
, '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' | ||
, 'connections/server' | ||
, 'connections/repl_set_servers' | ||
, 'connection' | ||
, 'cursor' | ||
, 'db' | ||
, 'goog/math/long' | ||
, 'crypto/md5' | ||
, 'gridfs/chunk' | ||
, 'gridfs/gridstore'].forEach(function (path) { | ||
var module = require('./' + path); | ||
for (var i in module) | ||
for (var i in module) { | ||
exports[i] = module[i]; | ||
} | ||
}); | ||
// Exports all the classes for the NATIVE JS BSON Parser | ||
exports.native = function() { | ||
var classes = {}; | ||
// Map all the classes | ||
[ 'bson/binary_parser' | ||
, '../../external-libs/bson/bson' | ||
, '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' | ||
, 'connections/server' | ||
, 'connections/repl_set_servers' | ||
, 'connection' | ||
, 'cursor' | ||
, 'db' | ||
, 'crypto/md5' | ||
, 'gridfs/chunk' | ||
, 'gridfs/gridstore'].forEach(function (path) { | ||
var module = require('./' + path); | ||
for (var i in module) { | ||
classes[i] = module[i]; | ||
} | ||
}); | ||
// Return classes list | ||
return classes; | ||
} | ||
// Exports all the classes for the PURE JS BSON Parser | ||
exports.pure = function() { | ||
var classes = {}; | ||
// Map all the classes | ||
[ 'bson/binary_parser' | ||
, './bson/bson' | ||
, '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' | ||
, 'connections/server' | ||
, 'connections/repl_set_servers' | ||
, 'connection' | ||
, 'cursor' | ||
, 'db' | ||
, 'crypto/md5' | ||
, 'gridfs/chunk' | ||
, 'gridfs/gridstore'].forEach(function (path) { | ||
var module = require('./' + path); | ||
for (var i in module) { | ||
classes[i] = module[i]; | ||
} | ||
}); | ||
// Return classes list | ||
return classes; | ||
} |
var BinaryParser = require('../bson/binary_parser').BinaryParser, | ||
Integer = require('../goog/math/integer').Integer, | ||
Long = require('../goog/math/long').Long; | ||
@@ -4,0 +3,0 @@ |
{ "name" : "mongodb" | ||
, "description" : "A node.js driver for MongoDB" | ||
, "version" : "0.9.3" | ||
, "version" : "0.9.6-1" | ||
, "author" : "Christian Amor Kvalheim <christkv@gmail.com>" | ||
@@ -5,0 +5,0 @@ , "contributors" : [ "Nathan White <nw@nwhite.net>", |
@@ -83,8 +83,12 @@ Install | ||
GridStore | ||
======== | ||
========= | ||
The GridStore class allows for storage of binary files in mongoDB using the mongoDB defined files and chunks collection definition. | ||
See the gridfs.js file under examples/ for how to use it or view the integration tests marked with test_gs_... | ||
For more information have a look at [Gridstore](https://github.com/christkv/node-mongodb-native/blob/master/docs/gridfs.md) | ||
Replicasets | ||
=========== | ||
For more information about how to connect to a replicaset have a look at [Replicasets](https://github.com/christkv/node-mongodb-native/blob/master/docs/replicaset.md) | ||
Notes | ||
@@ -158,4 +162,4 @@ ======== | ||
If this document doesn't answer your questions, see the source of | ||
[Collection](https://github.com/mongodb/mongo-python-driver/blob/master/pymongo/connection.py) | ||
or [Cursor](https://github.com/mongodb/mongo-python-driver/blob/master/pymongo/cursor.py), | ||
[Collection](https://github.com/christkv/node-mongodb-native/blob/master/lib/mongodb/collection.js) | ||
or [Cursor](https://github.com/christkv/node-mongodb-native/blob/master/lib/mongodb/cursor.js), | ||
or the documentation at MongoDB for query and update formats. | ||
@@ -162,0 +166,0 @@ |
@@ -8,2 +8,3 @@ | ||
, mongoose = start.mongoose | ||
, should = require('should') | ||
, Schema = mongoose.Schema; | ||
@@ -25,22 +26,41 @@ | ||
'test that trying to implement a sparse index throws an error': function () { | ||
var db = start() | ||
, NativeTestCollection = db.model('NativeDriverTest'); | ||
'test that trying to implement a sparse index works': function () { | ||
var db = start() | ||
, NativeTestCollection = db.model('NativeDriverTest'); | ||
NativeTestCollection.collection.ensureIndex({ title: 1 }, { sparse: true }, function (err) { | ||
err.should.be.an.instanceof(Error); | ||
/driver only implements/.test(err.message).should.be.true; | ||
NativeTestCollection.collection.ensureIndex({ title: 1 }, { sparse: true }, function (err) { | ||
should.strictEqual(!!err, false); | ||
NativeTestCollection.collection.getIndexes(function (err, indexes) { | ||
db.close(); | ||
should.strictEqual(!!err, false); | ||
indexes.should.be.instanceof(Object); | ||
indexes['title_1'].should.eql([['title', 1]]); | ||
}); | ||
}); | ||
}, | ||
'test that the -native traditional ensureIndex spec syntax for fields works': function () { | ||
var db = start() | ||
, NativeTestCollection = db.model('NativeDriverTest'); | ||
var db = start() | ||
, NativeTestCollection = db.model('NativeDriverTest'); | ||
NativeTestCollection.collection.ensureIndex([['a', 1]], function () { | ||
NativeTestCollection.collection.ensureIndex([['a', 1]], function () { | ||
db.close(); | ||
}); | ||
}, | ||
'unique index fails passes error': function () { | ||
var db = start() | ||
, schema = new Schema({ title: String }) | ||
, NativeTestCollection = db.model('NativeDriverTestUnique', schema) | ||
NativeTestCollection.create({ title: 'x' }, {title:'x'}, function (err) { | ||
should.strictEqual(!!err, false); | ||
NativeTestCollection.collection.ensureIndex({ title: 1 }, { unique: true }, function (err) { | ||
;/E11000 duplicate key error index/.test(err.message).should.equal(true); | ||
db.close(); | ||
}); | ||
}); | ||
} | ||
}; |
@@ -144,3 +144,3 @@ //Query.prototype.where(criteria, callback) | ||
UserNS.male.remove( function (err) { | ||
should.strictEqual(err, null); | ||
should.strictEqual(!!err, false); | ||
UserNS.male.find( function (err, found) { | ||
@@ -147,0 +147,0 @@ db.close(); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 4 instances in 1 package
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
Unidentified License
License(Experimental) Something that seems like a license was found, but its contents could not be matched with a known license.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
1928250
283
36132
383
2
80
166
11
+ Addedhooks@0.1.9(transitive)
- Removedhooks@0.1.7(transitive)
Updatedhooks@0.1.9