├── .gitignore ├── run-tests.sh ├── update-mongo-c-driver.sh ├── tests ├── test_many_insert_async.js ├── test_many_insert.js ├── test_readme_test.js ├── test_mongo.js └── test_bson.js ├── src ├── bson.h ├── bson.cc └── mongo.cc ├── wscript ├── lib └── mongodb.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .dbshell 2 | .lock-wscript 3 | build 4 | mongo.node 5 | mongo-c-driver 6 | -------------------------------------------------------------------------------- /run-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | for f in `ls tests/test_*.js`; do 4 | node $f 5 | done 6 | -------------------------------------------------------------------------------- /update-mongo-c-driver.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ ! -d mongo-c-driver ]; then 4 | git clone git://github.com/orlandov/mongo-c-driver.git 5 | fi 6 | 7 | cd mongo-c-driver 8 | git fetch 9 | git rebase origin/master 10 | 11 | scons --c99 12 | -------------------------------------------------------------------------------- /tests/test_many_insert_async.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | assert = require('assert'); 4 | deepEqual = assert.deepEqual; 5 | 6 | require.paths.push("lib"); 7 | var mongodb = require('mongodb'); 8 | var sys = require('sys'); 9 | var mongo = new mongodb.MongoDB(); 10 | mongo.addListener('connection', function( ) { 11 | var test = mongo.getCollection( 'widgets' ); 12 | test.remove(); 13 | 14 | var i = 1000; 15 | function doInserts() { 16 | if (!i--) return; 17 | test.insert( { i: i } ); 18 | process.nextTick(arguments.callee); 19 | } 20 | 21 | doInserts(); 22 | }); 23 | 24 | mongo.addListener('close', function() { 25 | sys.puts("Tests done!"); 26 | }); 27 | 28 | mongo.connect( { 29 | hostname: '127.0.0.1', 30 | port: 27017, 31 | db: '__node_mongodb_test' 32 | } ); 33 | -------------------------------------------------------------------------------- /tests/test_many_insert.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // thanks to sifu@github for the testcase 4 | 5 | assert = require('assert'); 6 | deepEqual = assert.deepEqual; 7 | 8 | require.paths.push("lib"); 9 | var mongodb = require('mongodb'); 10 | var sys = require('sys'); 11 | var mongo = new mongodb.MongoDB(); 12 | mongo.addListener('connection', function( ) { 13 | var test = mongo.getCollection( 'widgets' ); 14 | test.remove(); 15 | 16 | var i = 1000; 17 | while( i-- ) { 18 | test.insert( { i: i } ); 19 | } 20 | 21 | test.count(null, function (count) { 22 | deepEqual(count, 1000); 23 | mongo.close(); 24 | }); 25 | }); 26 | 27 | mongo.addListener('close', function() { 28 | sys.puts("Tests done!"); 29 | }); 30 | 31 | mongo.connect( { 32 | hostname: '127.0.0.1', 33 | port: 27017, 34 | db: '__node_mongodb_test' 35 | } ); 36 | -------------------------------------------------------------------------------- /src/bson.h: -------------------------------------------------------------------------------- 1 | #ifndef NODE_BSON_H 2 | #define NODE_BSON_H 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace v8; 8 | 9 | class ObjectID : public node::ObjectWrap { 10 | public: 11 | static v8::Persistent constructor_template; 12 | 13 | static void Initialize(Handle target); 14 | 15 | ObjectID() : ObjectWrap() {} 16 | ~ObjectID() {} 17 | 18 | static Handle ToString(const Arguments &args); 19 | 20 | bson_oid_t get() { return oid; } 21 | void str(char *); 22 | protected: 23 | static Handle New(const Arguments& args); 24 | 25 | ObjectID(const char *hex) : node::ObjectWrap() { 26 | bson_oid_from_string(&oid, hex); 27 | } 28 | 29 | private: 30 | 31 | bson_oid_t oid; 32 | }; 33 | 34 | // v8 wrappers 35 | Handle encode(const Arguments &args); 36 | Handle decode(const Arguments &args); 37 | 38 | v8::Local decodeObjectStr(const char *); 39 | bson encodeObject(const v8::Local element); 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /wscript: -------------------------------------------------------------------------------- 1 | import Options 2 | from os import unlink, symlink, popen 3 | from os.path import exists, abspath 4 | from shutil import copy 5 | 6 | srcdir = '.' 7 | blddir = 'build' 8 | VERSION = '0.0.1' 9 | 10 | def set_options(opt): 11 | opt.tool_options('compiler_cxx') 12 | 13 | def configure(conf): 14 | conf.check_tool('compiler_cxx') 15 | conf.check_tool('node_addon') 16 | 17 | conf.env.append_value("LIBPATH_BSON", abspath("./mongo-c-driver/")) 18 | conf.env.append_value("LIB_BSON", "bson") 19 | conf.env.append_value("CPPPATH_BSON", abspath("./mongo-c-driver/src")) 20 | 21 | conf.env.append_value("LIBPATH_MONGO", abspath("./mongo-c-driver/")) 22 | conf.env.append_value("LIB_MONGO", "mongoc") 23 | conf.env.append_value("CPPPATH_MONGO", abspath("./mongo-c-driver/src")) 24 | 25 | def build(bld): 26 | mongo = bld.new_task_gen('cxx', 'shlib', 'node_addon') 27 | mongo.cxxflags = "-g" 28 | mongo.target = 'mongo' 29 | mongo.source = "src/mongo.cc src/bson.cc" 30 | mongo.uselib = "MONGO BSON" 31 | 32 | def shutdown(): 33 | # HACK to get binding.node out of build directory. 34 | # better way to do this? 35 | if Options.commands['clean']: 36 | if exists('mongo.node'): unlink('mongo.node') 37 | else: 38 | if exists('build/default/mongo.node') and not exists('mongo.node'): 39 | copy('build/default/mongo.node', 'lib/mongo.node') 40 | -------------------------------------------------------------------------------- /tests/test_readme_test.js: -------------------------------------------------------------------------------- 1 | var sys = require("sys"); 2 | var mongodb = require("../lib/mongodb"); 3 | 4 | var mongo = new mongodb.MongoDB(); 5 | 6 | mongo.addListener("close", function () { 7 | sys.puts("Closing connection!"); 8 | }); 9 | 10 | mongo.addListener("connection", function () { 11 | var widgets = mongo.getCollection('widgets'); 12 | 13 | mongo.getCollections(function (collections) { 14 | sys.puts("the collections in the db are " + JSON.stringify(collections)); 15 | }); 16 | 17 | // remove widgets with shazbot > 0 18 | widgets.remove({ shazbot: { "$gt": 0 } }); 19 | 20 | // actually, just remove all widgets 21 | widgets.remove(); 22 | 23 | widgets.count(null, function(count) { 24 | widgets.insert({ foo: 1, shazbot: 1 }); 25 | widgets.insert({ bar: "a", shazbot: 2 }); 26 | widgets.insert({ baz: 42.5, shazbot: 0 }); 27 | 28 | // count all the widgets 29 | widgets.count(null, function (count) { 30 | sys.puts("there are " + count + " widgets"); 31 | }); 32 | 33 | // count widgets with shazbot > 0 34 | widgets.count({ shazbot: { "$gt": 0 } }, function (count) { 35 | sys.puts(count + " widget shazbots are > 0"); 36 | }); 37 | 38 | // count shazbots less than or equal to 1 39 | widgets.count({ shazbot: { "$lte": 1 } }, function (count) { 40 | sys.puts(2 == count); 41 | }); 42 | 43 | // return all widgets 44 | widgets.find(null, null, function (results) { 45 | // ... 46 | }); 47 | 48 | // return widgets with shazbot > 0 49 | widgets.find({ shazbot: { "$gt": 0 } }, null, function (results) { 50 | // ... 51 | }); 52 | 53 | // return only the shazbot field of every widget 54 | widgets.find({}, { "shazbot": true }, function (results) { 55 | // update shazbot of first document with shazbot 0 to 420 56 | widgets.update({ shazbot: 0 }, { shazbot: 420 }); 57 | 58 | widgets.find(null, null, function (results) { 59 | results.forEach(function(r) { 60 | // ... 61 | }); 62 | 63 | // close the connection 64 | mongo.close(); 65 | }); 66 | }); 67 | }); 68 | }); 69 | 70 | mongo.connect({ 71 | hostname: '127.0.0.1', 72 | port: 27017, 73 | db: 'mylittledb' 74 | }); 75 | -------------------------------------------------------------------------------- /lib/mongodb.js: -------------------------------------------------------------------------------- 1 | var sys = require("sys"), 2 | mongo = require("../lib/mongo"); 3 | 4 | function Collection(mongo, db, name) { 5 | this.mongo = mongo; 6 | this.ns = db + "." + name; 7 | this.db = db; 8 | this.name = name; 9 | } 10 | 11 | Collection.prototype.find = function(query, fields, callback) { 12 | this.mongo.addQuery(callback, this.ns, query, fields); 13 | } 14 | 15 | jjj = JSON.stringify 16 | 17 | Collection.prototype.insert = function(obj) { 18 | this.mongo.connection.insert(this.ns, obj); 19 | } 20 | 21 | Collection.prototype.update = function(cond, obj) { 22 | this.mongo.connection.update(this.ns, cond, obj); 23 | } 24 | 25 | Collection.prototype.remove = function(query) { 26 | this.mongo.connection.remove(this.ns, query); 27 | } 28 | 29 | Collection.prototype.find_one = function(query, fields, ns, callback) { 30 | this.mongo.addQuery(function (results) { 31 | // XXX what if result.Length < 1 32 | callback(results[0]); 33 | }, ns || this.ns, query, fields, 1); 34 | } 35 | 36 | Collection.prototype.count = function(query, callback) { 37 | ns = this.db + ".$cmd"; 38 | var cmd = { 39 | "count": this.name, 40 | "query": query 41 | } 42 | 43 | this.find_one(cmd, {}, ns, function (result) { 44 | callback(result.n); 45 | }); 46 | } 47 | 48 | function MongoDB() { 49 | this.myID = Math.random(); 50 | this.connection = new mongo.Connection(); 51 | 52 | var self = this; 53 | 54 | this.connection.addListener("close", function () { 55 | self.emit("close"); 56 | }); 57 | 58 | this.connection.addListener("ready", function () { 59 | self.dispatch(); 60 | }); 61 | 62 | this.connection.addListener("connection", function () { 63 | self.emit("connection", self); 64 | }); 65 | 66 | this.connection.addListener("result", function(result) { 67 | var callback = self.currentQuery[0]; 68 | callback(result); 69 | self.currentQuery = null; 70 | }); 71 | } 72 | 73 | sys.inherits(MongoDB, process.EventEmitter); 74 | 75 | MongoDB.prototype.connect = function(args) { 76 | this.queries = []; 77 | this.hostname = args.hostname || "127.0.0.1"; 78 | this.port = args.port || 27017; 79 | this.db = args.db; 80 | 81 | this.connection.connect(this.hostname, this.port); 82 | } 83 | 84 | MongoDB.prototype.close = function() { 85 | this.connection.close(); 86 | } 87 | 88 | MongoDB.prototype.addQuery = function(callback, ns, query, fields, limit, skip ) { 89 | var q = [ callback, ns ]; 90 | if (query) q.push(query); 91 | if (fields) q.push(fields); 92 | if (limit) q.push(limit); 93 | if (skip) q.push(skip); 94 | this.queries.push(q); 95 | } 96 | 97 | MongoDB.prototype.dispatch = function() { 98 | if (this.currentQuery || !this.queries.length) return; 99 | this.currentQuery = this.queries.shift(); 100 | this.connection.find.apply(this.connection, this.currentQuery.slice(1)); 101 | } 102 | 103 | MongoDB.prototype.getCollection = function(name) { 104 | return new Collection(this, this.db, name); 105 | } 106 | 107 | MongoDB.prototype.getCollections = function(callback) { 108 | this.addQuery(function (results) { 109 | var collections = []; 110 | results.forEach(function (r) { 111 | if (r.name.indexOf("$") != -1) 112 | return; 113 | collections.push(r.name.slice(r.name.indexOf(".")+1)); 114 | }); 115 | callback(collections); 116 | }, this.db + ".system.namespaces"); 117 | 118 | } 119 | 120 | exports.MongoDB = MongoDB; 121 | exports.ObjectID = mongo.ObjectID 122 | -------------------------------------------------------------------------------- /tests/test_mongo.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | assert = require('assert'); 4 | ok = assert.ok; 5 | equal = assert.equal; 6 | throws = assert.throws; 7 | deepEqual = assert.deepEqual; 8 | notEqual = assert.notEqual; 9 | 10 | var sys = require("sys"); 11 | 12 | require.paths.push("lib"); 13 | 14 | var mongodb = require("mongodb"); 15 | 16 | var oid_hex = "123456789012345678901234"; 17 | 18 | var mongo = new mongodb.MongoDB(); 19 | 20 | var cb = 0; 21 | 22 | mongo.addListener("close", function () { 23 | equal(cb, 11); 24 | sys.puts("Tests done!"); 25 | }); 26 | 27 | mongo.addListener("connection", function () { 28 | cb++; 29 | mongo.getCollections(function (collections) { 30 | cb++; 31 | collections.sort(); 32 | /*deepEqual(collections, ["system.indexes", "widgets"]);*/ 33 | }); 34 | var widgets = mongo.getCollection('widgets'); 35 | 36 | throws("widgets.remove([1,2])"); 37 | throws("widgets.remove(1)"); 38 | 39 | widgets.remove(); 40 | 41 | widgets.count(null, function(count) { 42 | cb++; 43 | equal(count, 0); 44 | 45 | widgets.insert({ _id: new mongodb.ObjectID(oid_hex), "dude": "lebowski" }); 46 | 47 | widgets.insert({ foo: 1, shazbot: 1 }); 48 | widgets.insert({ bar: "a", shazbot: 2 }); 49 | widgets.insert({ baz: 42.5, shazbot: 0 }); 50 | widgets.insert({ baz: 420, bucket: [1, 2] }); 51 | 52 | widgets.find({ baz: 420 },null,function (result) { 53 | cb++; 54 | deepEqual(result[0].bucket, [1, 2]); 55 | }); 56 | 57 | widgets.find_one({ _id: new mongodb.ObjectID(oid_hex) }, null, null, function (result) { 58 | cb++; 59 | equal(result._id.toString(), oid_hex); 60 | equal(result.dude, "lebowski"); 61 | }); 62 | 63 | widgets.count(null, function (count) { 64 | cb++; 65 | equal(5, count); 66 | }); 67 | 68 | widgets.count({ shazbot: { "$lte": 1 } }, function (count) { 69 | cb++; 70 | equal(2, count); 71 | }); 72 | 73 | widgets.find(null, null, function (results) { 74 | cb++; 75 | equal(5, results.length); 76 | }); 77 | 78 | widgets.find({ shazbot: { "$gt": 0 } }, null, function (results) { 79 | cb++; 80 | equal(results.length, 2); 81 | results.forEach(function (r) { 82 | // check that we had a validish oid 83 | ok(r['_id'].toString().length == 24); 84 | equal(r['baz'], undefined); 85 | }); 86 | }); 87 | 88 | widgets.find({}, { "shazbot": true }, function (results) { 89 | cb++; 90 | var shazbots = []; 91 | results.forEach(function (r) { 92 | shazbots.push(r.shazbot); 93 | equal(r['foo'], undefined); 94 | equal(r['bar'], undefined); 95 | equal(r['baz'], undefined); 96 | }); 97 | shazbots.sort(); 98 | // doesn't work, fields is broken? 99 | /*deepEqual(shazbots, [0, 1, 2]);*/ 100 | 101 | widgets.update({ shazbot: 0 }, { shazbot: 420 }); 102 | 103 | widgets.find({ shazbot: {"$lt": 1000}}, null, function (results) { 104 | cb++; 105 | for (var i = 0; i < results.length; i++) { 106 | notEqual(results[i].shazbot, 0); 107 | } 108 | 109 | mongo.close(); 110 | }); 111 | }); 112 | }); 113 | }); 114 | 115 | mongo.connect({ 116 | hostname: '127.0.0.1', 117 | port: 27017, 118 | db: 'node_mongodb_test' 119 | }); 120 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | NAME 2 | ---- 3 | 4 | node-mongodb - An asynchronous Node interface to MongoDB 5 | 6 | SYNOPSYS 7 | -------- 8 | 9 | var sys = require("sys"); 10 | var mongodb = require("../lib/mongodb"); 11 | 12 | var mongo = new mongodb.MongoDB(); 13 | 14 | mongo.addListener("close", function () { 15 | sys.puts("Closing connection!"); 16 | }); 17 | 18 | mongo.addListener("connection", function () { 19 | var widgets = mongo.getCollection('widgets'); 20 | 21 | mongo.getCollections(function (collections) { 22 | sys.puts("the collections in the db are " + JSON.stringify(collections)); 23 | }); 24 | 25 | // remove widgets with shazbot > 0 26 | widgets.remove({ shazbot: { "$gt": 0 } }); 27 | 28 | // actually, just remove all widgets 29 | widgets.remove(); 30 | 31 | widgets.count(null, function(count) { 32 | widgets.insert({ foo: 1, shazbot: 1 }); 33 | widgets.insert({ bar: "a", shazbot: 2 }); 34 | widgets.insert({ baz: 42.5, shazbot: 0 }); 35 | 36 | // count all the widgets 37 | widgets.count(null, function (count) { 38 | sys.puts("there are " + count + " widgets"); 39 | }); 40 | 41 | // count widgets with shazbot > 0 42 | widgets.count({ shazbot: { "$gt": 0 } }, function (count) { 43 | sys.puts(count + " widget shazbots are > 0"); 44 | }); 45 | 46 | // count shazbots less than or equal to 1 47 | widgets.count({ shazbot: { "$lte": 1 } }, function (count) { 48 | sys.puts(2 == count); 49 | }); 50 | 51 | // return all widgets 52 | widgets.find(null, null, function (results) { 53 | // ... 54 | }); 55 | 56 | // return widgets with shazbot > 0 57 | widgets.find({ shazbot: { "$gt": 0 } }, null, function (results) { 58 | // ... 59 | }); 60 | 61 | // return only the shazbot field of every widget 62 | widgets.find({}, { "shazbot": true }, function (results) { 63 | // update shazbot of first document with shazbot 0 to 420 64 | widgets.update({ shazbot: 0 }, { shazbot: 420 }); 65 | 66 | widgets.find(null, null, function (results) { 67 | results.forEach(function(r) { 68 | // ... 69 | }); 70 | 71 | // close the connection 72 | mongo.close(); 73 | }); 74 | }); 75 | }); 76 | }); 77 | 78 | mongo.connect({ 79 | hostname: '127.0.0.1', 80 | port: 27017, 81 | db: 'mylittledb' 82 | }); 83 | 84 | DESCRIPTION 85 | ----------- 86 | 87 | This is an attempt at MongoDB bindings for Node. The important thing here is 88 | to ensure that we never let ourselves or any libraries block on IO. As such, 89 | I've tried to do my best to make sure that connect() and recv() never block, 90 | but there may be bugs. The MongoDB C drivers are used to interface with the 91 | database, but some core functions needed to be rewritten to operate in a 92 | non-blocking manner. 93 | 94 | Installation 95 | ------------ 96 | 97 | - Make sure you have git installed. 98 | - ./update-mongo-c-driver.sh 99 | - node-waf configure build 100 | - ./run-tests.sh 101 | 102 | BUGS 103 | ---- 104 | 105 | This package is EXPERIMENTAL, with emphasis on MENTAL. I am working on this in 106 | my spare time to learn the Node, v8 and MongoDB API's. 107 | 108 | The error handling in this extension needs to be improved substantially. Be 109 | warned. 110 | 111 | I would appreciate any and all patches, suggestions and constructive 112 | criticisms. 113 | 114 | ACKNOWLEDGEMENTS 115 | ---------------- 116 | 117 | - ryah's Node postgres driver was the foundation for this extension 118 | - MongoDB C drivers 119 | - The people in #node.js and #mongodb on freenode for answering my questions 120 | 121 | SEE ALSO 122 | -------- 123 | 124 | - http://github.com/ry/node_postgres 125 | - http://github.com/mongodb/mongo-c-driver 126 | 127 | AUTHOR 128 | ------ 129 | 130 | Orlando Vazquez (ovazquez@gmail.com) 131 | -------------------------------------------------------------------------------- /tests/test_bson.js: -------------------------------------------------------------------------------- 1 | require.paths.push("lib"); 2 | 3 | var sys = require("sys"), 4 | mongo = require("mongo"), 5 | mongodb = require("mongodb"), 6 | assert = require("assert"); 7 | 8 | ok = assert.ok; 9 | equal = assert.equal; 10 | throws = assert.throws; 11 | deepEqual = assert.deepEqual; 12 | 13 | var bson = { encode: mongo.encode, decode: mongo.decode }; 14 | 15 | /* OID */ 16 | var oid_hex = "123456789012345678901234"; 17 | var oid = new mongodb.ObjectID(oid_hex); 18 | equal(oid.toString(), oid_hex); 19 | 20 | var mongo = new mongodb.MongoDB(); 21 | 22 | throws(function () { new mongodb.ObjectID("12345"); }); 23 | throws(function () { new mongodb.ObjectID(); }); 24 | throws(function () { new mongodb.ObjectID([1,2]); }); 25 | throws(function () { new mongodb.ObjectID(1); }); 26 | 27 | var comparisons = [ 28 | // strings 29 | [ 30 | { "hello": "world" }, 31 | "\x16\x00\x00\x00\x02hello\x00\x06\x00\x00\x00world\x00\x00" 32 | ], 33 | [ 34 | { "javascript": "rocks", "mongodb": "rocks" }, 35 | "\x2e\x00\x00\x00" + // size 36 | "\x02javascript\x00" + // key 37 | "\x06\x00\x00\x00rocks\x00" + // value 38 | "\x02mongodb\x00" + // key 39 | "\x06\x00\x00\x00rocks\x00" + // value 40 | "\x00" 41 | ], 42 | 43 | // number 44 | [ 45 | { "hello": 1 }, 46 | "\x10\x00\x00\x00" + // size 47 | "\x10hello\x00" + // key 48 | "\x01\x00\x00\x00" + // value 49 | "\x00" 50 | ], 51 | [ 52 | { "hello": 4.20 }, 53 | "\x14\x00\x00\x00" + // size 54 | "\x01hello\x00" + // key 55 | "\xcd\xcc\xcc\xcc\xcc\xcc\x10\x40" + 56 | "\x00" 57 | ], 58 | 59 | // boolean 60 | [ 61 | { "hello": true }, 62 | "\x0d\x00\x00\x00" + // size 63 | "\x08hello\x00" + // key 64 | "\x01" + // value 65 | "\x00" 66 | ], 67 | [ 68 | { "hello": false }, 69 | "\x0d\x00\x00\x00" + // size 70 | "\x08hello\x00" + // key 71 | "\x00" + // value 72 | "\x00" 73 | ], 74 | 75 | // arrays 76 | [ 77 | { 'mahbucket': [ 'foo', 'bar', 'baz' ] }, 78 | "\x36\x00\x00\x00" + // size 79 | "\x04mahbucket\0" + // type, key 80 | "\x26\x00\x00\x00" + 81 | "\x02"+"0\0" + // item 0 82 | "\x04\x00\x00\x00foo\x00" + 83 | "\x02"+"1\0" + // item 1 84 | "\x04\x00\x00\x00bar\x00" + 85 | "\x02"+"2\0" + // item 2 86 | "\x04\x00\x00\x00baz\x00" + 87 | "\x00" + 88 | "\x00" 89 | ], 90 | [ 91 | { 'mahbucket': [ { 'foo': 'bar' } ] }, 92 | "\x2a\x00\x00\x00" + // size 93 | "\x04mahbucket\0" + // type, key 94 | "\x1a\x00\x00\x00" + 95 | "\x03"+"0\0" + // item 0 96 | "\x12\x00\x00\x00" + // size 97 | "\x02foo\x00" + // key 98 | "\x04\x00\x00\x00bar\x00" + // value 99 | "\x00" + 100 | "\x00" + 101 | "\x00" 102 | ], 103 | [ 104 | { 'mahbucket': [ [ "foo", "bar" ], ["baz", "qux"] ] }, 105 | "\x51\x00\x00\x00" + // size 106 | "\x04mahbucket\0" + // type, key 107 | 108 | "\x41\x00\x00\x00" + // size of top level array 109 | 110 | "\x04"+"0\0" + // first sub array 111 | 112 | "\x1b\x00\x00\x00" + // size of inner array 113 | "\x02"+"0\0" + // item 0 114 | "\x04\x00\x00\x00foo\x00" + 115 | "\x02"+"1\0" + // item 1 116 | "\x04\x00\x00\x00bar\x00" + 117 | "\x00" + 118 | 119 | "\x04"+"1\0" + // second sub array 120 | 121 | "\x1b\x00\x00\x00" + // size of inner array 122 | "\x02"+"0\0" + // item 0 123 | "\x04\x00\x00\x00baz\x00" + 124 | "\x02"+"1\0" + // item 1 125 | "\x04\x00\x00\x00qux\x00" + 126 | "\x00" + 127 | 128 | "\x00" + 129 | "\x00" 130 | ], 131 | 132 | // nested objects 133 | [ 134 | { "great-old-ones": { "cthulhu": true } }, 135 | "\x24\x00\x00\x00" + // size 136 | "\x03great-old-ones\0" + // type, key 137 | // value: 138 | "\x0f\x00\x00\x00" + // size 139 | "\x08cthulhu\x00" + // type, key 140 | "\x01" + // value 141 | "\x00" + // eoo 142 | "\x00" 143 | ], 144 | ]; 145 | 146 | // Test decoding and encoding of objects 147 | var i = 0; 148 | comparisons.forEach(function (comp) { 149 | sys.puts("test #" + i++); 150 | deepEqual(bson.encode(comp[0]), comp[1]); 151 | deepEqual(bson.decode(comp[1]), comp[0]); 152 | }); 153 | -------------------------------------------------------------------------------- /src/bson.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | extern "C" { 6 | #define MONGO_HAVE_STDINT 7 | #include 8 | } 9 | 10 | #include "bson.h" 11 | 12 | using namespace std; 13 | using namespace v8; 14 | 15 | Persistent ObjectID::constructor_template; 16 | 17 | void ObjectID::Initialize(Handle target) { 18 | HandleScope scope; 19 | 20 | Local t = FunctionTemplate::New(ObjectID::New); 21 | constructor_template = Persistent::New(t); 22 | constructor_template->InstanceTemplate()->SetInternalFieldCount(1); 23 | constructor_template->SetClassName(String::NewSymbol("ObjectID")); 24 | 25 | NODE_SET_PROTOTYPE_METHOD(ObjectID::constructor_template, "toString", ObjectID::ToString); 26 | 27 | target->Set(String::NewSymbol("ObjectID"), constructor_template->GetFunction()); 28 | } 29 | 30 | Handle ObjectID::New(const Arguments &args) { 31 | HandleScope scope; 32 | 33 | if (args.Length() < 1 34 | || !args[0]->IsString() 35 | || (args[0]->IsString() 36 | && args[0]->ToString()->Length() != 24)) { 37 | return ThrowException(Exception::Error( 38 | String::New("Argument must be 24 character hex string"))); 39 | } 40 | 41 | String::Utf8Value hex(args[0]->ToString()); 42 | 43 | // XXX where should this be deleted? 44 | ObjectID *o = new ObjectID((const char *) *hex); 45 | o->Wrap(args.This()); 46 | return args.This(); 47 | } 48 | 49 | void ObjectID::str(char *str) { 50 | bson_oid_to_string(&oid, str); 51 | } 52 | 53 | Handle 54 | ObjectID::ToString(const Arguments &args) { 55 | ObjectID *o = ObjectWrap::Unwrap(args.This()); 56 | 57 | HandleScope scope; 58 | char hex[25]; 59 | o->str(hex); 60 | return String::New(hex); 61 | } 62 | 63 | const char * 64 | ToCString(const String::Utf8Value& value) { 65 | return *value ? *value : ""; 66 | } 67 | 68 | inline void 69 | encodeString(bson_buffer *bb, const char *name, const Local element) { 70 | String::Utf8Value v(element); 71 | const char *value(ToCString(v)); 72 | bson_append_string(bb, name, value); 73 | } 74 | 75 | inline void 76 | encodeNumber(bson_buffer *bb, const char *name, const Local element) { 77 | double value(element->NumberValue()); 78 | bson_append_double(bb, name, value); 79 | } 80 | 81 | inline void 82 | encodeInteger(bson_buffer *bb, const char *name, const Local element) { 83 | int value(element->NumberValue()); 84 | bson_append_int(bb, name, value); 85 | } 86 | 87 | inline void 88 | encodeBoolean(bson_buffer *bb, const char *name, const Local element) { 89 | bool value(element->IsTrue()); 90 | bson_append_bool(bb, name, value); 91 | } 92 | 93 | void 94 | encodeObjectID(bson_buffer *bb, const char *name, const Local element) { 95 | // get at the delicious wrapped object centre 96 | Local obj = element->ToObject(); 97 | assert(!obj.IsEmpty()); 98 | assert(obj->InternalFieldCount() > 0); 99 | ObjectID *o = static_cast(Handle::Cast( 100 | obj->GetInternalField(0))->Value()); 101 | bson_oid_t oid; 102 | char oid_hex[25]; 103 | o->str(oid_hex); 104 | bson_oid_from_string(&oid, oid_hex); 105 | bson_append_oid(bb, name, &oid); 106 | } 107 | 108 | void 109 | encodeArray(bson_buffer *bb, const char *name, const Local element) { 110 | Local a = Array::Cast(*element); 111 | bson_buffer *arr = bson_append_start_array(bb, name); 112 | 113 | for (int i = 0, l=a->Length(); i < l; i++) { 114 | Local val = a->Get(Number::New(i)); 115 | stringstream keybuf; 116 | string keyval; 117 | keybuf << i << endl; 118 | keybuf >> keyval; 119 | 120 | if (val->IsString()) { 121 | encodeString(arr, keyval.c_str(), val); 122 | } 123 | else if (val->IsInt32()) { 124 | encodeInteger(arr, keyval.c_str(), val); 125 | } 126 | else if (val->IsNumber()) { 127 | encodeNumber(arr, keyval.c_str(), val); 128 | } 129 | else if (val->IsBoolean()) { 130 | encodeBoolean(arr, keyval.c_str(), val); 131 | } 132 | else if (val->IsArray()) { 133 | encodeArray(arr, keyval.c_str(), val); 134 | } 135 | else if (val->IsObject()) { 136 | bson bson(encodeObject(val)); 137 | bson_append_bson(arr, keyval.c_str(), &bson); 138 | bson_destroy(&bson); 139 | } 140 | } 141 | bson_append_finish_object(arr); 142 | } 143 | 144 | bson encodeObject(const Local element) { 145 | HandleScope scope; 146 | bson_buffer bb; 147 | bson_buffer_init(&bb); 148 | 149 | Local object = element->ToObject(); 150 | Local properties = object->GetPropertyNames(); 151 | 152 | for (int i = 0; i < properties->Length(); i++) { 153 | // get the property name and value 154 | Local prop_name = properties->Get(Integer::New(i)); 155 | Local prop_val = object->Get(prop_name->ToString()); 156 | 157 | // convert the property name to a c string 158 | String::Utf8Value n(prop_name); 159 | const char *pname = ToCString(n); 160 | 161 | // append property using appropriate appender 162 | if (prop_val->IsString()) { 163 | encodeString(&bb, pname, prop_val); 164 | } 165 | else if (prop_val->IsInt32()) { 166 | encodeInteger(&bb, pname, prop_val); 167 | } 168 | else if (prop_val->IsNumber()) { 169 | encodeNumber(&bb, pname, prop_val); 170 | } 171 | else if (prop_val->IsBoolean()) { 172 | encodeBoolean(&bb, pname, prop_val); 173 | } 174 | else if (prop_val->IsArray()) { 175 | encodeArray(&bb, pname, prop_val); 176 | } 177 | else if (prop_val->IsObject() 178 | && ObjectID::constructor_template->HasInstance(prop_val)) { 179 | encodeObjectID(&bb, pname, prop_val); 180 | } 181 | else if (prop_val->IsObject()) { 182 | bson bson(encodeObject(prop_val)); 183 | bson_append_bson(&bb, pname, &bson); 184 | bson_destroy(&bson); 185 | } 186 | } 187 | 188 | bson bson; 189 | bson_from_buffer(&bson, &bb); 190 | 191 | return bson; 192 | } 193 | 194 | Handle 195 | encode(const Arguments &args) { 196 | // TODO assert args.length > 0 197 | // TODO assert args.type == Object 198 | HandleScope scope; 199 | 200 | bson bson(encodeObject(args[0])); 201 | Handle ret = node::Encode(bson.data, bson_size(&bson), node::BINARY); 202 | bson_destroy(&bson); 203 | return ret; 204 | } 205 | 206 | // Decoding functions 207 | 208 | Handle 209 | decodeString(bson_iterator *i) { 210 | HandleScope scope; 211 | const char *val = bson_iterator_string(i); 212 | Local str = String::New(val); 213 | return scope.Close(str); 214 | } 215 | 216 | Handle 217 | decodeObject(bson_iterator *i) { 218 | HandleScope scope; 219 | bson bson; 220 | bson_iterator_subobject(i, &bson); 221 | Handle sub = decodeObjectStr(bson.data); 222 | return scope.Close(sub); 223 | } 224 | 225 | Handle 226 | decodeObjectID(bson_iterator *i) { 227 | HandleScope scope; 228 | char hex_oid[25]; 229 | bson_oid_t *oid = bson_iterator_oid(i); 230 | bson_oid_to_string(oid, hex_oid); 231 | Handle argv[1]; 232 | argv[0] = String::New(hex_oid); 233 | 234 | Handle obj = 235 | ObjectID::constructor_template->GetFunction()->NewInstance(1, argv); 236 | 237 | return scope.Close(obj); 238 | } 239 | 240 | Handle 241 | decodeDouble(bson_iterator *i) { 242 | HandleScope scope; 243 | double val = bson_iterator_double_raw(i); 244 | Local obj = Number::New(val); 245 | return scope.Close(obj); 246 | } 247 | 248 | Handle 249 | decodeInteger(bson_iterator *i) { 250 | HandleScope scope; 251 | double val = bson_iterator_int_raw(i); 252 | Local obj = Integer::New(val); 253 | return scope.Close(obj); 254 | } 255 | 256 | Handle 257 | decodeBool(bson_iterator *i) { 258 | HandleScope scope; 259 | bson_bool_t val = bson_iterator_bool(i); 260 | Handle obj = Boolean::New(val); 261 | return scope.Close(obj); 262 | } 263 | 264 | Handle 265 | decodeArray(bson_iterator *it) { 266 | HandleScope scope; 267 | bson_iterator sub; 268 | bson_iterator_subiterator(it, &sub); 269 | Local obj = Array::New(); 270 | 271 | for (int i = 0; bson_iterator_next(&sub); i++) { 272 | bson_type type = bson_iterator_type(&sub); 273 | 274 | const char *key = bson_iterator_key(&sub); 275 | 276 | switch (type) { 277 | case bson_string: 278 | obj->Set(Number::New(i), decodeString(&sub)); 279 | break; 280 | case bson_array: 281 | obj->Set(Number::New(i), decodeArray(&sub)); 282 | break; 283 | case bson_object: 284 | obj->Set(Number::New(i), decodeObject(&sub)); 285 | break; 286 | case bson_oid: 287 | obj->Set(Number::New(i), decodeObjectID(&sub)); 288 | break; 289 | case bson_double: 290 | obj->Set(Number::New(i), decodeDouble(&sub)); 291 | break; 292 | case bson_int: 293 | obj->Set(Number::New(i), decodeInteger(&sub)); 294 | break; 295 | case bson_bool: 296 | obj->Set(Number::New(i), decodeBool(&sub)); 297 | break; 298 | } 299 | } 300 | 301 | return scope.Close(obj); 302 | } 303 | 304 | Local 305 | decodeObjectIterator(bson_iterator *it) { 306 | HandleScope scope; 307 | Local obj = Object::New(); 308 | while (bson_iterator_next(it)) { 309 | bson_type type = bson_iterator_type(it); 310 | const char *key = bson_iterator_key(it); 311 | 312 | switch (type) { 313 | case bson_string: 314 | obj->Set(String::New(key), decodeString(it)); 315 | break; 316 | 317 | case bson_array: 318 | obj->Set(String::New(key), decodeArray(it)); 319 | break; 320 | 321 | case bson_object: 322 | obj->Set(String::New(key), decodeObject(it)); 323 | break; 324 | 325 | case bson_oid: 326 | obj->Set(String::New(key), decodeObjectID(it)); 327 | break; 328 | 329 | case bson_double: 330 | obj->Set(String::New(key), decodeDouble(it)); 331 | break; 332 | 333 | case bson_int: 334 | obj->Set(String::New(key), decodeInteger(it)); 335 | break; 336 | 337 | case bson_bool: 338 | obj->Set(String::New(key), decodeBool(it)); 339 | break; 340 | } 341 | } 342 | 343 | return scope.Close(obj); 344 | } 345 | 346 | Local 347 | decodeObjectStr(const char *buf) { 348 | HandleScope scope; 349 | 350 | bson_iterator it; 351 | bson_iterator_init(&it, buf); 352 | 353 | Handle obj = decodeObjectIterator(&it); 354 | return scope.Close(obj); 355 | } 356 | 357 | Handle 358 | decodeObject(const Local str) { 359 | HandleScope scope; 360 | size_t buflen = str->ToString()->Length(); 361 | char buf[buflen]; 362 | node::DecodeWrite(buf, buflen, str, node::BINARY); 363 | return decodeObjectStr(buf); 364 | } 365 | 366 | Handle 367 | decode(const Arguments &args) { 368 | HandleScope scope; 369 | return decodeObject(args[0]); 370 | } 371 | -------------------------------------------------------------------------------- /src/mongo.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | extern "C" { 15 | #define MONGO_HAVE_STDINT 16 | #include 17 | #include 18 | #include 19 | } 20 | #include "bson.h" 21 | 22 | #define DEBUGMODE 0 23 | #define pdebug(...) do{if(DEBUGMODE)printf(__VA_ARGS__);}while(0) 24 | 25 | const int chunk_size(4094); 26 | const int headerSize(sizeof(mongo_header) + sizeof(mongo_reply_fields)); 27 | 28 | using namespace v8; 29 | 30 | void setNonBlocking(int sock) { 31 | int sockflags = fcntl(sock, F_GETFL, 0); 32 | fcntl(sock, F_SETFL, sockflags | O_NONBLOCK); 33 | } 34 | 35 | class Connection : public node::EventEmitter { 36 | public: 37 | 38 | static void 39 | Initialize (Handle target) { 40 | HandleScope scope; 41 | 42 | Local t = FunctionTemplate::New(Connection::New); 43 | 44 | t->Inherit(node::EventEmitter::constructor_template); 45 | t->InstanceTemplate()->SetInternalFieldCount(1); 46 | 47 | NODE_SET_PROTOTYPE_METHOD(t, "connect", Connect); 48 | NODE_SET_PROTOTYPE_METHOD(t, "close", Close); 49 | NODE_SET_PROTOTYPE_METHOD(t, "find", Find); 50 | NODE_SET_PROTOTYPE_METHOD(t, "insert", Insert); 51 | NODE_SET_PROTOTYPE_METHOD(t, "update", Update); 52 | NODE_SET_PROTOTYPE_METHOD(t, "remove", Remove); 53 | 54 | target->Set(String::NewSymbol("Connection"), t->GetFunction()); 55 | } 56 | 57 | void StartReadWatcher() { 58 | pdebug("*** Starting read watcher\n"); 59 | ev_io_start(EV_DEFAULT_ &read_watcher); 60 | } 61 | 62 | void StopReadWatcher() { 63 | pdebug("*** Stopping read watcher\n"); 64 | ev_io_stop(EV_DEFAULT_ &read_watcher); 65 | } 66 | 67 | void StartWriteWatcher() { 68 | pdebug("*** Starting write watcher\n"); 69 | ev_io_start(EV_DEFAULT_ &write_watcher); 70 | } 71 | 72 | void StopWriteWatcher() { 73 | pdebug("*** Stopping write watcher\n"); 74 | ev_io_stop(EV_DEFAULT_ &write_watcher); 75 | } 76 | 77 | void StartConnectWatcher() { 78 | pdebug("*** Starting connect watcher\n"); 79 | ev_io_start(EV_DEFAULT_ &connect_watcher); 80 | } 81 | 82 | void StopConnectWatcher() { 83 | pdebug("*** Stopping connect watcher\n"); 84 | ev_io_stop(EV_DEFAULT_ &connect_watcher); 85 | } 86 | 87 | void CreateConnection(mongo_connection_options *options) { 88 | // MONGO_INIT_EXCEPTION(&conn->exception); 89 | 90 | conn->left_opts = (mongo_connection_options *)bson_malloc(sizeof(mongo_connection_options)); 91 | conn->right_opts = NULL; 92 | 93 | if ( options ){ 94 | memcpy( conn->left_opts , options , sizeof( mongo_connection_options ) ); 95 | } else { 96 | strcpy( conn->left_opts->host , "127.0.0.1" ); 97 | conn->left_opts->port = 27017; 98 | } 99 | 100 | MongoCreateSocket(); 101 | } 102 | 103 | void MongoCreateSocket() { 104 | conn->sock = 0; 105 | conn->connected = 0; 106 | 107 | memset(conn->sa.sin_zero, 0, sizeof(conn->sa.sin_zero)); 108 | conn->sa.sin_family = AF_INET; 109 | conn->sa.sin_port = htons(conn->left_opts->port); 110 | conn->sa.sin_addr.s_addr = inet_addr(conn->left_opts->host); 111 | conn->addressSize = sizeof(conn->sa); 112 | 113 | conn->sock = socket( AF_INET, SOCK_STREAM, 0 ); 114 | if (conn->sock <= 0){ 115 | //return mongo_conn_no_socket; 116 | // throw exception here? 117 | } 118 | 119 | setNonBlocking(conn->sock); 120 | int res = connect(conn->sock, (struct sockaddr*) &conn->sa, conn->addressSize); 121 | 122 | // make sure we've gotten a non-blocking connection 123 | assert(res < 0); 124 | assert(errno == EINPROGRESS); 125 | 126 | ev_io_set(&connect_watcher, conn->sock, EV_WRITE); 127 | StartConnectWatcher(); 128 | } 129 | 130 | void Connected() { 131 | StopConnectWatcher(); 132 | setsockopt( conn->sock, IPPROTO_TCP, TCP_NODELAY, (char *) &one, sizeof(one) ); 133 | 134 | conn->connected = 1; 135 | 136 | Emit(String::New("connection"), 0, NULL); 137 | } 138 | 139 | void Connect(const char *host, const int32_t port) { 140 | HandleScope scope; 141 | 142 | mongo_connection_options opts; 143 | memcpy(opts.host, host, strlen(host)+1); 144 | opts.host[strlen(host)] = '\0'; 145 | opts.port = port; 146 | 147 | CreateConnection(&opts); 148 | 149 | setNonBlocking(conn->sock); 150 | 151 | ev_io_set(&read_watcher, conn->sock, EV_READ); 152 | ev_io_set(&write_watcher, conn->sock, EV_WRITE); 153 | 154 | Ref(); 155 | StartWriteWatcher(); 156 | } 157 | 158 | void Close() { 159 | pdebug("--- in Close()\n"); 160 | HandleScope scope; 161 | close = true; 162 | } 163 | 164 | void reallyClose() { 165 | HandleScope scope; 166 | StopWriteWatcher(); 167 | StopReadWatcher(); 168 | 169 | if (writebuf) { 170 | delete [] writebuf; 171 | writebuf = NULL; 172 | writebuflen = 0; 173 | } 174 | 175 | if (buf) { 176 | delete [] buf; 177 | buf = NULL; 178 | buflen = 0; 179 | } 180 | 181 | buf = writebuf = NULL; 182 | 183 | mongo_destroy(conn); 184 | 185 | Emit(String::New("close"), 0, NULL); 186 | 187 | Unref(); 188 | } 189 | 190 | void CheckBuffer() { 191 | if (buflen < headerSize) return; 192 | 193 | mongo_header reply_head; 194 | mongo_reply_fields reply_fields; 195 | 196 | memcpy(&reply_head, bufptr, sizeof(reply_head)); 197 | bufptr += sizeof(reply_head); 198 | memcpy(&reply_fields, bufptr, sizeof(reply_fields)); 199 | bufptr += sizeof(reply_fields); 200 | 201 | int len; 202 | bson_little_endian32(&len, &reply_head.len); 203 | 204 | if (len-buflen == 0) { 205 | // we've gotten the full response 206 | ParseReply(reply_head, reply_fields); 207 | 208 | delete [] buf; 209 | buf = bufptr = NULL; 210 | buflen = 0; 211 | 212 | StopReadWatcher(); 213 | StartWriteWatcher(); 214 | } 215 | } 216 | 217 | void ParseReply(mongo_header reply_head, mongo_reply_fields reply_fields) { 218 | HandleScope scope; 219 | 220 | int len; 221 | bson_little_endian32(&len, &reply_head.len); 222 | 223 | char replybuf[len]; 224 | 225 | mongo_reply *reply = reinterpret_cast(replybuf); 226 | 227 | reply->head.len = len; 228 | bson_little_endian32(&reply->head.id, &reply_head.id); 229 | bson_little_endian32(&reply->head.responseTo, &reply_head.responseTo); 230 | bson_little_endian32(&reply->head.op, &reply_head.op); 231 | 232 | bson_little_endian32(&reply->fields.flag, &reply_fields.flag); 233 | bson_little_endian64(&reply->fields.cursorID, &reply_fields.cursorID); 234 | bson_little_endian32(&reply->fields.start, &reply_fields.start); 235 | bson_little_endian32(&reply->fields.num, &reply_fields.num); 236 | 237 | memcpy(&reply->objs, bufptr, len-headerSize); 238 | 239 | cursor->mm = reply; 240 | cursor->current.data = NULL; 241 | 242 | for (int i = results->Length(); AdvanceCursor(); i++){ 243 | Local val = decodeObjectStr(cursor->current.data); 244 | results->Set(Integer::New(i), val); 245 | } 246 | 247 | // if this is the last cursor 248 | if (!cursor->mm || ! reply_fields.cursorID) { 249 | FreeCursor(); 250 | get_more = false; 251 | EmitResults(); 252 | results.Dispose(); 253 | results.Clear(); 254 | results = Persistent::New(Array::New()); 255 | } 256 | } 257 | 258 | void FreeCursor() { 259 | free((void*)cursor->ns); 260 | free(cursor); 261 | cursor = NULL; 262 | } 263 | 264 | void EmitResults() { 265 | Emit(String::New("result"), 1, reinterpret_cast *>(&results)); 266 | } 267 | 268 | bool AdvanceCursor() { 269 | char* bson_addr; 270 | 271 | /* no data */ 272 | if (!cursor->mm || cursor->mm->fields.num == 0) 273 | return false; 274 | 275 | /* first */ 276 | if (cursor->current.data == NULL){ 277 | bson_init(&cursor->current, &cursor->mm->objs, 0); 278 | return true; 279 | } 280 | 281 | // new cursor position 282 | bson_addr = cursor->current.data + bson_size(&cursor->current); 283 | 284 | if (bson_addr >= ((char*)cursor->mm + cursor->mm->head.len)){ 285 | // current cursor is out of data 286 | get_more = true; 287 | 288 | // indicate that this is the last result 289 | return false; 290 | } else { 291 | // advance cursor by one object 292 | bson_init(&cursor->current, bson_addr, 0); 293 | 294 | return true; 295 | } 296 | return false; 297 | } 298 | 299 | void BufferMessageToSend(mongo_message *mm) { 300 | mongo_header head; 301 | bson_little_endian32(&head.len, &mm->head.len); 302 | bson_little_endian32(&head.id, &mm->head.id); 303 | bson_little_endian32(&head.responseTo, &mm->head.responseTo); 304 | bson_little_endian32(&head.op, &mm->head.op); 305 | 306 | int size = mm->head.len; 307 | pdebug("buffering message of size %d\n", size); 308 | 309 | char *tmp = new char[writebuflen+size]; 310 | 311 | if (writebuf) { 312 | memcpy(tmp, writebuf, writebuflen); 313 | } 314 | 315 | memcpy(tmp+writebuflen, &head, sizeof(head)); 316 | memcpy(tmp+writebuflen+sizeof(head), &mm->data, size-sizeof(head)); 317 | free(mm); 318 | 319 | int ptrdelta = writebufptr - writebuf; 320 | 321 | if (writebuf) { 322 | delete [] writebuf; 323 | } 324 | 325 | writebuflen = writebuflen + size; 326 | writebuf = tmp; 327 | writebufptr = tmp + ptrdelta; 328 | pdebug("write buf is of size %d\n", writebuflen); 329 | pdebug("est lenRem = %d\n", writebuflen-ptrdelta); 330 | pdebug("wbuf diff = %d\n", ptrdelta); 331 | StartWriteWatcher(); 332 | } 333 | 334 | void WriteSendBuffer() { 335 | pdebug("going to write buffer\n"); 336 | 337 | int sock = conn->sock; 338 | int lenRemaining = writebuflen-(writebufptr-writebuf); 339 | 340 | pdebug("remaining: %d\n", lenRemaining); 341 | while (lenRemaining) { 342 | pdebug("trying to write %d\n", lenRemaining); 343 | int sent = write(sock, writebufptr, lenRemaining); 344 | pdebug("write = %d\n", sent); 345 | if (sent == -1) { 346 | if (errno == EAGAIN) { 347 | // we need to set the write watcher again and continue 348 | // later 349 | pdebug("EAGAIN\n"); 350 | 351 | StartWriteWatcher(); 352 | return; 353 | } 354 | else { 355 | pdebug("errorno was %d\n", errno); 356 | } 357 | } 358 | writebufptr += sent; 359 | lenRemaining -= sent; 360 | } 361 | if (!lenRemaining) { 362 | delete [] writebuf; 363 | writebufptr = writebuf = NULL; 364 | writebuflen = 0; 365 | } 366 | pdebug("done! write buf is of size %d\n", writebuflen); 367 | pdebug("done! est lenRem = %d\n", writebuflen-(writebufptr-writebuf)); 368 | pdebug("done! wbuf diff = %d\n", (writebufptr-writebuf)); 369 | StopWriteWatcher(); 370 | } 371 | 372 | void ConsumeInput() { 373 | char *tmp; 374 | char readbuf[chunk_size]; 375 | int32_t readbuflen; 376 | 377 | for (;;) { 378 | readbuflen = read(conn->sock, readbuf, chunk_size); 379 | 380 | if (readbuflen == -1 && errno == EAGAIN) { 381 | // no more input to consume 382 | pdebug("len == -1 && errno == EAGAIN\n"); 383 | return; 384 | } 385 | else if (readbuflen <= 0) { 386 | // socket problem? 387 | pdebug("length error on read %d errno = %d\n", readbuflen, errno); 388 | } 389 | else { 390 | tmp = static_cast(new char[buflen+readbuflen]); 391 | memset(tmp, 0, buflen+readbuflen); 392 | 393 | if (buf) { 394 | memcpy(tmp, buf, buflen); 395 | } 396 | memcpy(tmp+buflen, readbuf, readbuflen); 397 | if (buf) { 398 | delete [] buf; 399 | } 400 | buflen = buflen + readbuflen; 401 | bufptr = tmp + (bufptr - buf); 402 | buf = tmp; 403 | break; 404 | } 405 | } 406 | } 407 | 408 | void RequestMore() { 409 | HandleScope scope; 410 | 411 | char* data; 412 | int sl = strlen(cursor->ns)+1; 413 | mongo_message * mm = mongo_message_create(16 /*header*/ 414 | +4 /*ZERO*/ 415 | +sl 416 | +4 /*numToReturn*/ 417 | +8 /*cursorID*/ 418 | , 0, 0, mongo_op_get_more); 419 | data = &mm->data; 420 | data = mongo_data_append32(data, &zero); 421 | data = mongo_data_append(data, cursor->ns, sl); 422 | data = mongo_data_append32(data, &zero); 423 | data = mongo_data_append64(data, &(cursor->mm->fields.cursorID)); 424 | 425 | BufferMessageToSend(mm); 426 | } 427 | 428 | 429 | bool Find(const char *ns, bson *query=0, bson *query_fields=0, 430 | int nToReturn=0, int nToSkip=0, int options=0) { 431 | StartReadWatcher(); 432 | assert(!close); 433 | cursor = static_cast( 434 | bson_malloc(sizeof(mongo_cursor))); 435 | 436 | int sl = strlen(ns)+1; 437 | cursor->ns = static_cast(bson_malloc(sl)); 438 | 439 | memcpy(static_cast(const_cast(cursor->ns)), ns, sl); 440 | cursor->conn = conn; 441 | 442 | char * data; 443 | mongo_message * mm = mongo_message_create( 16 + /* header */ 444 | 4 + /* options */ 445 | sl + /* ns */ 446 | 4 + 4 + /* skip,return */ 447 | bson_size( query ) + 448 | bson_size( query_fields ) , 449 | 0 , 0 , mongo_op_query ); 450 | 451 | data = &mm->data; 452 | data = mongo_data_append32(data, &options); 453 | data = mongo_data_append(data, ns, strlen(ns)+ 1); 454 | data = mongo_data_append32(data, &nToSkip); 455 | data = mongo_data_append32(data, &nToReturn); 456 | data = mongo_data_append(data, query->data, bson_size(query)); 457 | if (query_fields) 458 | data = mongo_data_append(data, query_fields->data, bson_size(query_fields)); 459 | 460 | bson_fatal_msg((data == ((char*)mm) + mm->head.len), "query building fail!"); 461 | 462 | BufferMessageToSend(mm); 463 | } 464 | 465 | void Insert(const char *ns, bson obj) { 466 | char * data; 467 | mongo_message *mm = mongo_message_create( 16 /* header */ 468 | + 4 /* ZERO */ 469 | + strlen(ns) 470 | + 1 + bson_size(&obj) 471 | , 0, 0, mongo_op_insert); 472 | 473 | data = &mm->data; 474 | data = mongo_data_append32(data, &zero); 475 | data = mongo_data_append(data, ns, strlen(ns) + 1); 476 | data = mongo_data_append(data, obj.data, bson_size(&obj)); 477 | 478 | BufferMessageToSend(mm); 479 | } 480 | 481 | void Remove(const char *ns, bson cond) { 482 | char * data; 483 | mongo_message * mm = mongo_message_create( 16 /* header */ 484 | + 4 /* ZERO */ 485 | + strlen(ns) + 1 486 | + 4 /* ZERO */ 487 | + bson_size(&cond) 488 | , 0 , 0 , mongo_op_delete ); 489 | 490 | data = &mm->data; 491 | data = mongo_data_append32(data, &zero); 492 | data = mongo_data_append(data, ns, strlen(ns) + 1); 493 | data = mongo_data_append32(data, &zero); 494 | data = mongo_data_append(data, cond.data, bson_size(&cond)); 495 | BufferMessageToSend(mm); 496 | } 497 | 498 | void Update(const char *ns, bson cond, bson op, int flags=0) { 499 | char * data; 500 | mongo_message * mm = mongo_message_create( 16 /* header */ 501 | + 4 /* ZERO */ 502 | + strlen(ns) + 1 503 | + 4 /* flags */ 504 | + bson_size(&cond) 505 | + bson_size(&op) 506 | , 0 , 0 , mongo_op_update ); 507 | 508 | data = &mm->data; 509 | data = mongo_data_append32(data, &zero); 510 | data = mongo_data_append(data, ns, strlen(ns) + 1); 511 | data = mongo_data_append32(data, &flags); 512 | data = mongo_data_append(data, cond.data, bson_size(&cond)); 513 | data = mongo_data_append(data, op.data, bson_size(&op)); 514 | 515 | BufferMessageToSend(mm); 516 | } 517 | 518 | protected: 519 | 520 | static Handle 521 | New(const Arguments& args) { 522 | HandleScope scope; 523 | 524 | // XXX where should this be deleted? 525 | Connection *connection = new Connection(); 526 | connection->Wrap(args.This()); 527 | return args.This(); 528 | } 529 | 530 | ~Connection() { 531 | } 532 | 533 | Connection() : node::EventEmitter() { 534 | HandleScope scope; 535 | results = Persistent::New(Array::New()); 536 | 537 | close = false; 538 | cursor = false; 539 | get_more = false; 540 | buflen = writebuflen = 0; 541 | buf = bufptr = writebuf = writebufptr = NULL; 542 | 543 | ev_init(&read_watcher, io_event); 544 | read_watcher.data = this; 545 | ev_init(&write_watcher, io_event); 546 | write_watcher.data = this; 547 | ev_init(&connect_watcher, connect_event); 548 | connect_watcher.data = this; 549 | } 550 | 551 | static Handle 552 | Connect(const Arguments &args) { 553 | HandleScope scope; 554 | Connection *connection = ObjectWrap::Unwrap(args.This()); 555 | 556 | // XXX check args.Length 557 | String::Utf8Value host(args[0]->ToString()); 558 | connection->Connect(*host, args[1]->Int32Value()); 559 | 560 | return Undefined(); 561 | } 562 | 563 | static Handle 564 | Close(const Arguments &args) { 565 | HandleScope scope; 566 | Connection *connection = ObjectWrap::Unwrap(args.This()); 567 | 568 | connection->Close(); 569 | 570 | return Undefined(); 571 | } 572 | 573 | static Handle 574 | Find(const Arguments &args) { 575 | HandleScope scope; 576 | Connection *connection = ObjectWrap::Unwrap(args.This()); 577 | 578 | // TODO assert ns != undefined (args.Length > 0) 579 | String::Utf8Value ns(args[0]->ToString()); 580 | 581 | bson query_bson; 582 | bson query_fields_bson; 583 | int nToReturn(0), nToSkip(0); 584 | 585 | if (args.Length() > 1 && !args[1]->IsUndefined()) { 586 | Local query(args[1]->ToObject()); 587 | query_bson = encodeObject(query); 588 | } 589 | else { 590 | bson_empty(&query_bson); 591 | } 592 | 593 | if (args.Length() > 2 && !args[2]->IsUndefined()) { 594 | Local query_fields(args[2]->ToObject()); 595 | query_fields_bson = encodeObject(query_fields); 596 | } 597 | else { 598 | bson_empty(&query_fields_bson); 599 | } 600 | 601 | if (args.Length() > 3 && !args[3]->IsUndefined()) { 602 | nToReturn = args[3]->Int32Value(); 603 | } 604 | 605 | if (args.Length() > 4 && !args[4]->IsUndefined()) { 606 | nToSkip = args[4]->Int32Value(); 607 | } 608 | 609 | connection->Find(*ns, &query_bson, &query_fields_bson, nToReturn, nToSkip); 610 | 611 | bson_destroy(&query_bson); 612 | bson_destroy(&query_fields_bson); 613 | return Undefined(); 614 | } 615 | 616 | static Handle 617 | Insert(const Arguments &args) { 618 | HandleScope scope; 619 | Connection *connection = ObjectWrap::Unwrap(args.This()); 620 | 621 | String::Utf8Value ns(args[0]->ToString()); 622 | // TODO assert ns != undefined (args.Length > 0) 623 | 624 | bson obj; 625 | 626 | // XXX check args > 1 627 | Local query(args[1]->ToObject()); 628 | obj = encodeObject(query); 629 | 630 | connection->Insert(*ns, obj); 631 | 632 | bson_destroy(&obj); 633 | return Undefined(); 634 | } 635 | 636 | static Handle 637 | Update(const Arguments &args) { 638 | HandleScope scope; 639 | Connection *connection = ObjectWrap::Unwrap(args.This()); 640 | 641 | String::Utf8Value ns(args[0]->ToString()); 642 | // TODO assert ns != undefined (args.Length > 0) 643 | 644 | bson cond; 645 | bson obj; 646 | 647 | if (args.Length() > 1 && !args[1]->IsUndefined()) { 648 | Local query(args[1]->ToObject()); 649 | cond = encodeObject(query); 650 | } 651 | else { 652 | bson_empty(&cond); 653 | } 654 | 655 | if (args.Length() > 2 && !args[2]->IsUndefined()) { 656 | Local query(args[2]->ToObject()); 657 | obj = encodeObject(query); 658 | } 659 | else { 660 | bson_empty(&obj); 661 | } 662 | 663 | connection->Update(*ns, cond, obj); 664 | 665 | bson_destroy(&cond); 666 | bson_destroy(&obj); 667 | return Undefined(); 668 | } 669 | 670 | static Handle 671 | Remove(const Arguments &args) { 672 | HandleScope scope; 673 | Connection *connection = ObjectWrap::Unwrap(args.This()); 674 | if (!args[0]->IsString()) { 675 | return ThrowException( 676 | Exception::Error( 677 | String::New("ns must be specified"))); 678 | } 679 | String::Utf8Value ns(args[0]->ToString()); 680 | 681 | 682 | bson cond; 683 | if (args.Length() > 1 && args[1]->IsObject()) { 684 | Local query(args[1]->ToObject()); 685 | cond = encodeObject(query); 686 | } 687 | else if (args.Length() > 1 && args[1]->IsUndefined()) { 688 | bson_empty(&cond); 689 | } 690 | else if (args.Length() > 1 && !args[1]->IsObject()) { 691 | return ThrowException( 692 | Exception::Error( 693 | String::New("Condition must be an object"))); 694 | } 695 | 696 | connection->Remove(*ns, cond); 697 | 698 | bson_destroy(&cond); 699 | return Undefined(); 700 | } 701 | 702 | void Event(EV_P_ ev_io *w, int revents) { 703 | if (!conn->connected) { 704 | StopReadWatcher(); 705 | StopWriteWatcher(); 706 | return; 707 | }; 708 | pdebug("event %d %d\n", conn->connected, close ? 1 : 0); 709 | if (revents & EV_READ) { 710 | pdebug("!!! got a read event\n"); 711 | StopReadWatcher(); 712 | ConsumeInput(); 713 | CheckBuffer(); 714 | } 715 | if (revents & EV_WRITE) { 716 | pdebug("!!! got a write event\n"); 717 | pdebug("!!! writebuflen = %d\n", writebuflen); 718 | if (writebuflen) { 719 | pdebug("things to write\n"); 720 | WriteSendBuffer(); 721 | } 722 | else { 723 | StopWriteWatcher(); 724 | } 725 | 726 | if (get_more) { 727 | RequestMore(); 728 | } 729 | else { 730 | Emit(String::New("ready"), 0, NULL); 731 | } 732 | } 733 | if (close) { 734 | pdebug("!!! really closing %d\n", close); 735 | reallyClose(); 736 | close = false; 737 | } 738 | if (revents & EV_ERROR) { 739 | pdebug("!!! got an error event\n"); 740 | } 741 | } 742 | 743 | private: 744 | 745 | static void 746 | connect_event(EV_P_ ev_io *w, int revents) { 747 | pdebug("!!! got a connect event\n"); 748 | Connection *connection = static_cast(w->data); 749 | connection->Connected(); 750 | } 751 | 752 | static void 753 | io_event (EV_P_ ev_io *w, int revents) { 754 | Connection *connection = static_cast(w->data); 755 | connection->Event(w, revents); 756 | } 757 | 758 | mongo_connection conn[1]; 759 | 760 | // states 761 | bool get_more; 762 | bool close; 763 | 764 | mongo_cursor *cursor; 765 | 766 | Persistent results; 767 | 768 | char *buf; 769 | char *bufptr; 770 | int buflen; 771 | 772 | char *writebuf; 773 | char *writebufptr; 774 | int writebuflen; 775 | 776 | ev_io read_watcher; 777 | ev_io write_watcher; 778 | ev_io connect_watcher; 779 | }; 780 | 781 | extern "C" void 782 | init (Handle target) { 783 | HandleScope scope; 784 | 785 | target->Set( 786 | String::New("encode"), 787 | FunctionTemplate::New(encode)->GetFunction()); 788 | target->Set( 789 | String::New("decode"), 790 | FunctionTemplate::New(decode)->GetFunction()); 791 | ObjectID::Initialize(target); 792 | Connection::Initialize(target); 793 | } 794 | --------------------------------------------------------------------------------