From 92cf6d6ab44bf0b3fd3d84d8e1f9dbbff3eb9204 Mon Sep 17 00:00:00 2001 From: adam Date: Fri, 2 Nov 2012 18:01:29 +0700 Subject: [PATCH] #1 --- node/Makefile | 3 +- node/ejdb.js | 29 +++++- node/ejdb_native.cc | 196 ++++++++++++++++++++++++++++++++++---- node/nbproject/configurations.xml | 16 ---- node/tests/t1.js | 42 +++++++- node/tests/t2.js | 41 ++++++++ tcejdb/ejdb.h | 3 +- 7 files changed, 287 insertions(+), 43 deletions(-) create mode 100644 node/tests/t2.js diff --git a/node/Makefile b/node/Makefile index 16a6411..2392cb6 100644 --- a/node/Makefile +++ b/node/Makefile @@ -1,4 +1,5 @@ +dummy:; all: npm build . @@ -6,4 +7,4 @@ all: clean: rm -rf ./build -.PHONY: all clean \ No newline at end of file +.PHONY: all clean dummy \ No newline at end of file diff --git a/node/ejdb.js b/node/ejdb.js index 500d19a..4df9ae1 100644 --- a/node/ejdb.js +++ b/node/ejdb.js @@ -1,8 +1,7 @@ - var ejdblib; try { ejdblib = require("../build/Release/ejdb_native.node"); -} catch(e) { +} catch (e) { console.error("Warning: Using the DEBUG version of EJDB nodejs binding"); ejdblib = require("../build/Debug/ejdb_native.node"); } @@ -29,6 +28,32 @@ EJDB.prototype.close = function() { return this._impl.close(); }; +EJDB.prototype.isOpen = function() { + return this._impl.isOpen(); +}; + +EJDB.prototype.ensureCollection = function(cname, copts) { + return this._impl.ensureCollection(cname, copts || {}); +}; + +EJDB.prototype.rmCollection = function(cname, prune) { + return this._impl.rmCollection(cname, !!prune); +}; + +EJDB.prototype.save = function(cname, jsarr, cb) { + if (!jsarr) { + return; + } + if (!cb) { + cb = function() { + }; + } + if (jsarr.constructor !== Array) { + jsarr = [jsarr]; + } + return this._impl.save(cname, jsarr, cb); +}; + module.exports = EJDB; diff --git a/node/ejdb_native.cc b/node/ejdb_native.cc index d2603e5..b2b0fab 100644 --- a/node/ejdb_native.cc +++ b/node/ejdb_native.cc @@ -20,13 +20,92 @@ using namespace v8; static const int CMD_RET_ERROR = 1; #define DEFINE_INT64_CONSTANT(target, constant) \ - (target)->Set(v8::String::NewSymbol(#constant), \ - v8::Number::New((int64_t) constant), \ - static_cast( \ - v8::ReadOnly|v8::DontDelete)) + (target)->Set(String::NewSymbol(#constant), \ + Number::New((int64_t) constant), \ + static_cast( \ + ReadOnly|DontDelete)) namespace ejdb { + /////////////////////////////////////////////////////////////////////////// + // Some symbols // + /////////////////////////////////////////////////////////////////////////// + + + static Persistent sym_large; + static Persistent sym_compressed; + static Persistent sym_records; + static Persistent sym_cachedrecords; + + + /////////////////////////////////////////////////////////////////////////// + // Fetch functions // + /////////////////////////////////////////////////////////////////////////// + + enum eFetchStatus { + FETCH_NONE, + FETCH_DEFAULT, + FETCH_VAL + }; + + char* fetch_string_data(Handle sobj, eFetchStatus* fs, const char* def) { + HandleScope scope; + if (sobj->IsNull() || sobj->IsUndefined()) { + if (fs) { + *fs = FETCH_DEFAULT; + } + return def ? strdup(def) : strdup(""); + } + String::Utf8Value value(sobj); + const char* data = *value; + if (fs) { + *fs = FETCH_VAL; + } + return data ? strdup(data) : strdup(""); + } + + int64_t fetch_int_data(Handle sobj, eFetchStatus* fs, int64_t def) { + HandleScope scope; + if (!(sobj->IsNumber() || sobj->IsInt32() || sobj->IsUint32())) { + if (fs) { + *fs = FETCH_DEFAULT; + } + return def; + } + if (fs) { + *fs = FETCH_VAL; + } + return sobj->ToNumber()->IntegerValue(); + } + + bool fetch_bool_data(Handle sobj, eFetchStatus* fs, bool def) { + HandleScope scope; + if (sobj->IsNull() || sobj->IsUndefined()) { + if (fs) { + *fs = FETCH_DEFAULT; + } + return def; + } + if (fs) { + *fs = FETCH_VAL; + } + return sobj->BooleanValue(); + } + + double fetch_real_data(Handle sobj, eFetchStatus* fs, double def) { + HandleScope scope; + if (!(sobj->IsNumber() || sobj->IsInt32())) { + if (fs) { + *fs = FETCH_DEFAULT; + } + return def; + } + if (fs) { + *fs = FETCH_VAL; + } + return sobj->ToNumber()->NumberValue(); + } + typedef struct { Handle traversed; } TBSONCTX; @@ -257,10 +336,10 @@ namespace ejdb { if (pv->IsString()) { String::Utf8Value val(pv); bson_append_string(bs, *spn, *val); - } else if (pv->IsUint32()) { - bson_append_long(bs, *spn, pv->Uint32Value()); } else if (pv->IsInt32()) { bson_append_int(bs, *spn, pv->Int32Value()); + } else if (pv->IsUint32()) { + bson_append_long(bs, *spn, pv->Uint32Value()); } else if (pv->IsNumber()) { bson_append_double(bs, *spn, pv->NumberValue()); } else if (pv->IsNull()) { @@ -394,14 +473,14 @@ namespace ejdb { } static void s_exec_cmd_eio(uv_work_t *req) { - EJBTask *task = static_cast (req->data); + EJBTask *task = (EJBTask*) (req->data); NodeEJDB *njb = task->wrapped; assert(njb); njb->exec_cmd(task); } static void s_exec_cmd_eio_after(uv_work_t *req) { - EJBTask *task = static_cast (req->data); + EJBTask *task = (EJBTask*) (req->data); NodeEJDB *njb = task->wrapped; assert(njb); njb->exec_cmd_after(task); @@ -441,15 +520,19 @@ namespace ejdb { HandleScope scope; REQ_ARGS(3); REQ_STR_ARG(0, cname); //Collection name - REQ_ARR_ARG(1, oarr); //Array of JAVA objects + REQ_ARR_ARG(1, oarr); //Array of JSON objects REQ_FUN_ARG(2, cb); //Callback BSONCmdData *cmdata = new BSONCmdData(*cname); for (uint32_t i = 0; i < oarr->Length(); ++i) { Local v = oarr->Get(i); - if (!v->IsObject()) continue; + if (!v->IsObject()) { + cmdata->bsons.push_back(NULL); + continue; + } bson *bs = bson_create(); assert(bs); + bson_init(bs); toBSON(Handle::Cast(v), bs); if (bs->err) { Local msg = String::New(bs->errstr ? bs->errstr : "BSON creation failed"); @@ -509,6 +592,57 @@ namespace ejdb { return scope.Close(args.This()); } + static Handle s_ecode(const Arguments& args) { + HandleScope scope; + NodeEJDB *njb = ObjectWrap::Unwrap< NodeEJDB > (args.This()); + if (!njb->m_jb) { + return scope.Close(ThrowException(Exception::Error(String::New("Operation on closed EJDB instance")))); + } + return scope.Close(Integer::New(ejdbecode(njb->m_jb))); + } + + static Handle s_ensure_collection(const Arguments& args) { + HandleScope scope; + REQ_STR_ARG(0, cname); + REQ_OBJ_ARG(1, copts); + NodeEJDB *njb = ObjectWrap::Unwrap< NodeEJDB > (args.This()); + if (!ejdbisopen(njb->m_jb)) { + return scope.Close(ThrowException(Exception::Error(String::New("Operation on closed EJDB instance")))); + } + EJCOLLOPTS jcopts; + memset(&jcopts, 0, sizeof (jcopts)); + jcopts.cachedrecords = fetch_int_data(copts->Get(sym_cachedrecords), NULL, 0); + jcopts.compressed = fetch_bool_data(copts->Get(sym_compressed), NULL, false); + jcopts.large = fetch_bool_data(copts->Get(sym_large), NULL, false); + jcopts.records = fetch_int_data(copts->Get(sym_records), NULL, 0); + EJCOLL *coll = ejdbcreatecoll(njb->m_jb, *cname, &jcopts); + if (!coll) { + return scope.Close(ThrowException(Exception::Error(String::New(njb->_jb_error_msg())))); + } + return scope.Close(args.This()); + } + + static Handle s_rm_collection(const Arguments& args) { + HandleScope scope; + REQ_STR_ARG(0, cname); + REQ_VAL_ARG(1, prune); + NodeEJDB *njb = ObjectWrap::Unwrap< NodeEJDB > (args.This()); + if (!ejdbisopen(njb->m_jb)) { + return scope.Close(ThrowException(Exception::Error(String::New("Operation on closed EJDB instance")))); + } + if (!ejdbrmcoll(njb->m_jb, *cname, prune->BooleanValue())) { + return scope.Close(ThrowException(Exception::Error(String::New(njb->_jb_error_msg())))); + } + return scope.Close(args.This()); + } + + static Handle s_is_open(const Arguments& args) { + HandleScope scope; + NodeEJDB *njb = ObjectWrap::Unwrap< NodeEJDB > (args.This()); + return scope.Close(Boolean::New(ejdbisopen(njb->m_jb))); + } + + /////////////////////////////////////////////////////////////////////////// // Instance methods // /////////////////////////////////////////////////////////////////////////// @@ -541,7 +675,6 @@ namespace ejdb { save_after((BSONCmdTask*) task); break; } - delete task; } void save(BSONCmdTask *task) { @@ -559,10 +692,14 @@ namespace ejdb { std::vector::iterator it; for (it = cmdata->bsons.begin(); it < cmdata->bsons.end(); it++) { - bson *bs = *(it); - assert(bs); bson_oid_t oid; - if (!ejdbsavebson(coll, bs, &oid)) { + bson *bs = *(it); + if (!bs) { + //Zero OID + oid.ints[0] = 0; + oid.ints[1] = 0; + oid.ints[2] = 0; + } else if (!ejdbsavebson(coll, bs, &oid)) { task->cmd_ret = CMD_RET_ERROR; task->cmd_ret_msg = _jb_error_msg(); break; @@ -583,10 +720,14 @@ namespace ejdb { std::vector::iterator it; int32_t c = 0; for (it = task->cmd_data->ids.begin(); it < task->cmd_data->ids.end(); it++) { - char oidhex[25]; bson_oid_t& oid = *it; - bson_oid_to_string(&oid, oidhex); - oids->Set(Integer::New(c++), String::New(oidhex)); + if (oid.ints[0] || oid.ints[1] || oid.ints[2]) { + char oidhex[25]; + bson_oid_to_string(&oid, oidhex); + oids->Set(Integer::New(c++), String::New(oidhex)); + } else { + oids->Set(Integer::New(c++), Null()); + } } argv[1] = oids; TryCatch try_catch; @@ -727,6 +868,14 @@ finish: static void Init(Handle target) { HandleScope scope; + + //Symbols + sym_large = NODE_PSYMBOL("large"); + sym_compressed = NODE_PSYMBOL("compressed"); + sym_records = NODE_PSYMBOL("records"); + sym_cachedrecords = NODE_PSYMBOL("cachedrecords"); + + Local t = FunctionTemplate::New(s_new_object); constructor_template = Persistent::New(t); constructor_template->InstanceTemplate()->SetInternalFieldCount(1); @@ -753,13 +902,18 @@ finish: //Misc NODE_DEFINE_CONSTANT(target, JBQRYCOUNT); - //Symbols - target->Set(v8::String::NewSymbol("NodeEJDB"), constructor_template->GetFunction()); - NODE_SET_PROTOTYPE_METHOD(constructor_template, "close", s_close); NODE_SET_PROTOTYPE_METHOD(constructor_template, "save", s_save); NODE_SET_PROTOTYPE_METHOD(constructor_template, "load", s_load); NODE_SET_PROTOTYPE_METHOD(constructor_template, "query", s_query); + NODE_SET_PROTOTYPE_METHOD(constructor_template, "lastError", s_ecode); + NODE_SET_PROTOTYPE_METHOD(constructor_template, "ensureCollection", s_ensure_collection); + NODE_SET_PROTOTYPE_METHOD(constructor_template, "rmCollection", s_rm_collection); + NODE_SET_PROTOTYPE_METHOD(constructor_template, "isOpen", s_is_open); + + + //Symbols + target->Set(String::NewSymbol("NodeEJDB"), constructor_template->GetFunction()); } void Ref() { @@ -1013,7 +1167,7 @@ finish: } } - void Init(v8::Handle target) { + void Init(Handle target) { #ifdef __unix setlocale(LC_ALL, "en_US.UTF-8"); //todo review it #endif diff --git a/node/nbproject/configurations.xml b/node/nbproject/configurations.xml index f27e283..bd5be3b 100644 --- a/node/nbproject/configurations.xml +++ b/node/nbproject/configurations.xml @@ -2,22 +2,6 @@ - - - - - - - - - - - - - - - - diff --git a/node/tests/t1.js b/node/tests/t1.js index 6b240d0..d2fb056 100644 --- a/node/tests/t1.js +++ b/node/tests/t1.js @@ -1,6 +1,44 @@ +var fs = require("fs"); var EJDB = require("../ejdb.js"); module.exports.testOpenClose = function(test) { - var jb = EJDB.open("var/tdb1"); + test.ok(EJDB.JBOWRITER); + test.ok(EJDB.JBOCREAT); + test.ok(EJDB.JBOTRUNC); + var jb = EJDB.open("var/tdbt11", EJDB.JBOWRITER | EJDB.JBOCREAT | EJDB.JBOTRUNC); + test.ok(jb); + test.equal(jb.isOpen(), true); + jb.close(); + test.equal(jb.isOpen(), false); + test.ok(fs.existsSync("var/tdbt11")); test.done(); -} \ No newline at end of file +}; + + +module.exports.testEnsureCollection = function(test) { + var jb = EJDB.open("var/tdbt12", EJDB.JBOWRITER | EJDB.JBOCREAT | EJDB.JBOTRUNC); + test.equal(jb.isOpen(), true); + var c1opts = { + cachedrecords : 10000, + compressed : true, + large : true, + records : 1000000 + }; + jb.ensureCollection("c1", c1opts); + test.ok(fs.existsSync("var/tdbt12_c1")); + jb.rmCollection("c1", true); + test.ok(!fs.existsSync("var/tdbt12_c1")); + jb.close(); + var err = null; + try { + jb.rmCollection("c1", true); + } catch (e) { + err = e; + } + test.ok(err); + test.done(); +}; + + + + diff --git a/node/tests/t2.js b/node/tests/t2.js new file mode 100644 index 0000000..52eaa86 --- /dev/null +++ b/node/tests/t2.js @@ -0,0 +1,41 @@ +var fs = require("fs"); +var EJDB = require("../ejdb.js"); + + +var jb = null; + +module.exports.testSetup = function(test) { + jb = EJDB.open("var/tdbt2", EJDB.JBOWRITER | EJDB.JBOCREAT | EJDB.JBOTRUNC); + test.done(); +}; + +module.exports.testSaveLoad = function(test) { + test.ok(jb.isOpen()); + var parrot1 = { + "name" : "Grenny", + "type" : "African Grey", + "male" : true, + "age" : 1, + "likes" : ["green color", "night", "toys"] + }; + var parrot2 = { + "name" : "Bounty", + "type" : "Cockatoo", + "male" : false, + "age" : 15, + "likes" : ["sugar cane"] + }; + jb.save("parrots", [parrot1, parrot2], function(err, oids) { + test.ifError(err); + test.ok(oids); + test.equal(oids.length, 2); + test.done(); + }); +}; + +module.exports.testClose = function(test) { + test.ok(jb); + jb.close(); + test.done(); +}; + diff --git a/tcejdb/ejdb.h b/tcejdb/ejdb.h index 78b1fbf..6aefa5e 100644 --- a/tcejdb/ejdb.h +++ b/tcejdb/ejdb.h @@ -144,7 +144,8 @@ EJDB_EXPORT EJCOLL* ejdbgetcoll(EJDB *jb, const char* colname); * * @param jb EJDB handle. * @param colname Name of collection. - * @param opts Options to be applied for newly created collection. + * @param opts Options are applied only for newly created collection. + * For existing collections it takes no effect. * @return Collection handle or NULL if error. */ EJDB_EXPORT EJCOLL* ejdbcreatecoll(EJDB *jb, const char* colname, EJCOLLOPTS *opts); -- 2.7.4