* If a saved object does not have `_id` it will be autogenerated.
* To identify and update object it should contains `_id` property.
*
+ * If callback is not provided this function will be synchronous.
+ *
* Call variations:
- * - save(cname, <json object>|<Array of json objects>, [cb])
- * - save(cname, <json object>|<Array of json objects>, [options], [cb])
+ * - save(cname, <json object>|<Array of json objects>, options)
+ * - save(cname, <json object>|<Array of json objects>, cb)
+ * - save(cname, <json object>|<Array of json objects>, options, cb)
*
* @param {String} cname Name of collection.
* @param {Array|Object} jsarr Signle JSON object or array of JSON objects to save
* @param {Function} [cb] Callback function with arguments: (error, {Array} of OIDs for saved objects)
+ * @return {Array} of OIDs for saved objects in synchronous mode otherwise undefined.
*/
EJDB.prototype.save = function(cname, jsarr, opts, cb) {
if (!jsarr) {
- return;
+ return [];
}
if (jsarr.constructor !== Array) {
jsarr = [jsarr];
cb = opts;
opts = null;
}
- return this._impl.save(cname, jsarr, (opts || {}), function(err, oids) {
- if (err) {
- if (cb) {
- cb(err);
- }
- return;
- }
+ var postprocess = function(oids) {
//Assign _id property for newly created objects
for (var i = jsarr.length - 1; i >= 0; --i) {
var so = jsarr[i];
so["_id"] = oids[i];
}
}
- if (cb) {
- cb(err, oids);
- }
- });
+ };
+ if (cb == null) {
+ postprocess(this._impl.save(cname, jsarr, (opts || {})));
+ return jsarr;
+ } else {
+ return this._impl.save(cname, jsarr, (opts || {}), function(err, oids) {
+ if (err) {
+ cb(err);
+ return;
+ }
+ postprocess(oids);
+ cb(null, oids);
+ });
+ }
};
/**
* Loads JSON object identified by OID from the collection.
+ * If callback is not provided this function will be synchronous.
*
* @param {String} cname Name of collection
* @param {String} oid Object identifier (OID)
- * @param {Function} cb Callback function with arguments: (error, obj)
+ * @param {Function} [cb] Callback function with arguments: (error, obj)
* `obj`: Retrieved JSON object or NULL if it is not found.
+ * @return Retrieved JSON object or NULL if it is not found in synchronous mode otherwise undefined.
*/
EJDB.prototype.load = function(cname, oid, cb) {
return this._impl.load(cname, oid, cb);
/**
* Removes JSON object from the collection.
+ * If callback is not provided this function will be synchronous.
*
* @param {String} cname Name of collection
* @param {String} oid Object identifier (OID)
- * @param {Function} cb Callback function with arguments: (error)
+ * @param {Function} [cb] Callback function with arguments: (error)
+ * @return {undefined}
*/
EJDB.prototype.remove = function(cname, oid, cb) {
return this._impl.remove(cname, oid, cb);
};
+
+/*
+ * - (cname, qobj, [cb])
+ * - (cname, qobj, hints, [cb])
+ * - (cname, qobj, qobjarr, [cb])
+ * - (cname, qobj, qobjarr, hints, [cb])
+ */
+function parseQueryArgs(args) {
+ var cname, qobj, orarr, hints, cb;
+ cname = args.shift();
+ if (typeof cname !== "string") {
+ throw new Error("Collection name 'cname' argument must be specified");
+ }
+ qobj = args.shift();
+ var next = args.shift();
+ if (next !== undefined) {
+ if (next.constructor === Array) {
+ orarr = next;
+ next = args.shift();
+ } else if (typeof next === "object") {
+ hints = next;
+ orarr = null;
+ next = args.shift();
+ }
+ if (!hints && typeof next === "object") {
+ hints = next;
+ next = args.shift();
+ }
+ if (typeof next === "function") {
+ cb = next;
+ }
+ } else {
+ orarr = hints = cb = null;
+ }
+ return [cname, (qobj || {}), (orarr || []), (hints || {}), (cb || null)];
+}
+
/**
* Execute query on collection.
*
*
* NOTE: It is better to execute update queries with `$onlycount=true` hint flag
* or use the special `update()` method to avoid unnecessarily data fetching.
- *
* NOTE: Negate operations: $not and $nin not using indexes
- * so they can be slow in comparison to other matching operations.
- *
+ * so they can be slow in comparison to other matching operations.
* NOTE: Only one index can be used in search query operation.
+ * NOTE: If callback is not provided this function will be synchronous.
*
* QUERY HINTS (specified by `hints` argument):
* - $max Maximum number in the result set
* - Cursor#close() Closes cursor and free cursor resources. Cursor cant be used in closed state.
*
* Call variations of find():
- * - find(cname, qobj, cb)
- * - find(cname, qobj, hints, cb)
- * - find(cname, qobj, qobjarr, cb)
- * - find(cname, qobj, qobjarr, hints, cb)
+ * - find(cname, qobj, [cb])
+ * - find(cname, qobj, hints, [cb])
+ * - find(cname, qobj, qobjarr, [cb])
+ * - find(cname, qobj, qobjarr, hints, [cb])
*
* @param {String} cname Name of collection
* @param {Object} qobj Main JSON query object
* @param {Array} [orarr] Array of additional OR query objects (joined with OR predicate).
* @param {Object} [hints] JSON object with query hints.
- * @param {Function} cb Callback function with arguments: (error, cursor, count) where:
+ * @param {Function} [cb] Callback function with arguments: (error, cursor, count) where:
* `cursor`: Cursor object to traverse records
- * `count`: Total number of selected records
+ * `count`: Total number of selected records.
+ * @return If callback is provided returns undefined.
+ * If no callback and $onlycount hint is set returns count number.
+ * If no callback and no $onlycount hint returns cursor object.
+ *
*/
-EJDB.prototype.find = function(cname, qobj, orarr, hints, cb) {
- if (arguments.length == 4) {
- cb = hints;
- if (orarr && orarr.constructor === Array) {
- hints = {};
- } else {
- hints = orarr;
- orarr = [];
- }
- } else if (arguments.length == 3) {
- cb = orarr;
- orarr = [];
- hints = {};
- }
- if (typeof cb !== "function") {
- throw new Error("Callback 'cb' argument must be specified");
- }
- if (typeof cname !== "string") {
- throw new Error("Collection name 'cname' argument must be specified");
- }
- if (!hints || typeof hints !== "object") {
- hints = {};
- }
- if (!qobj || typeof qobj !== "object") {
- qobj = {};
- }
- return this._impl.query(cname,
- [qobj].concat(orarr, hints),
- (hints["$onlycount"] ? ejdblib.JBQRYCOUNT : 0),
- cb);
+EJDB.prototype.find = function() {
+ //[cname, qobj, orarr, hints, cb]
+ var qa = parseQueryArgs(Array.prototype.slice.call(arguments));
+ return this._impl.query(qa[0], [qa[1]].concat(qa[2], qa[3]),
+ (qa[3]["$onlycount"] ? ejdblib.JBQRYCOUNT : 0),
+ qa[4]);
};
/**
* Same as #find() but retrieves only one matching JSON object.
+ * If callback is not provided this function will be synchronous.
*
* Call variations of findOne():
- * - findOne(cname, qobj, cb)
- * - findOne(cname, qobj, hints, cb)
- * - findOne(cname, qobj, qobjarr, cb)
- * - findOne(cname, qobj, qobjarr, hints, cb)
+ * - findOne(cname, qobj, [cb])
+ * - findOne(cname, qobj, hints, [cb])
+ * - findOne(cname, qobj, qobjarr, [cb])
+ * - findOne(cname, qobj, qobjarr, hints, [cb])
*
* @param {String} cname Name of collection
* @param {Object} qobj Main JSON query object
* @param {Array} [orarr] Array of additional OR query objects (joined with OR predicate).
* @param {Object} [hints] JSON object with query hints.
- * @param {Function} cb Callback function with arguments: (error, obj) where:
+ * @param {Function} [cb] Callback function with arguments: (error, obj) where:
* `obj`: Retrieved JSON object or NULL if it is not found.
+ * @return If callback is provided returns undefined.
+ * If no callback is provided returns found object or null.
*/
-EJDB.prototype.findOne = function(cname, qobj, orarr, hints, cb) {
- if (arguments.length == 4) {
- cb = hints;
- if (orarr && orarr.constructor === Array) {
- hints = {};
- } else {
- hints = orarr;
- orarr = [];
+EJDB.prototype.findOne = function() {
+ //[cname, qobj, orarr, hints, cb]
+ var qa = parseQueryArgs(Array.prototype.slice.call(arguments));
+ qa[3]["$max"] = 1;
+ var cb = qa[4];
+ if (cb) {
+ return this._impl.query(qa[0], [qa[1]].concat(qa[2], qa[3]), 0,
+ function(err, cursor) {
+ if (err) {
+ cb(err);
+ return;
+ }
+ if (cursor.next()) {
+ try {
+ cb(null, cursor.object());
+ } finally {
+ cursor.close();
+ }
+ } else {
+ cb(null, null);
+ }
+ });
+ } else {
+ var cursor = this._impl.query(qa[0], [qa[1]].concat(qa[2], qa[3]), 0, cb);
+ var ret = null;
+ if (cursor.next()) {
+ ret = cursor.object();
}
- } else if (arguments.length == 3) {
- cb = orarr;
- orarr = [];
- hints = {};
- }
- if (typeof cb !== "function") {
- throw new Error("Callback 'cb' argument must be specified");
+ cursor.close();
+ return ret;
}
- if (typeof cname !== "string") {
- throw new Error("Collection name 'cname' argument must be specified");
- }
- if (!hints || typeof hints !== "object") {
- hints = {};
- }
- if (!qobj || typeof qobj !== "object") {
- qobj = {};
- }
- hints["$max"] = 1;
- return this._impl.query(cname, [qobj].concat(orarr, hints), 0,
- function(err, cursor) {
- if (err) {
- cb(err);
- return;
- }
- if (cursor.next()) {
- try {
- cb(null, cursor.object());
- } finally {
- cursor.close();
- }
- } else {
- cb(null, null);
- }
- });
};
/**
* Convenient method to execute update queries.
- * The `$set` and `$inc` operations are supported.
+ * If callback is not provided this function will be synchronous.
*
- * `$set` Field set operation:
- * - {some fields for selection, '$set' : {'field1' : {obj}, ..., 'field1' : {obj}}}
- * `$inc` Increment operation. Only number types are supported.
- * - {some fields for selection, '$inc' : {'field1' : number, ..., 'field1' : {number}}
+ * The following update operations are supported:
+ * $set Field set operation.
+ * - {.., '$set' : {'field1' : val1, 'fieldN' : valN}}
+ * $inc Increment operation. Only number types are supported.
+ * - {.., '$inc' : {'field1' : number, ..., 'field1' : number}
+ * $dropall In-place record removal operation.
+ * - {.., '$dropall' : true}
+ * $addToSet Atomically adds value to the array only if its not in the array already.
+ * If containing array is missing it will be created.
+ * - {.., '$addToSet' : {'json.field.path' : val1, 'json.field.pathN' : valN, ...}}
+ * $pull Atomically removes all occurrences of value from field, if field is an array.
+ * - {.., '$pull' : {'json.field.path' : val1, 'json.field.pathN' : valN, ...}}
*
* Call variations of update():
- * update(cname, qobj, cb)
- * update(cname, qobj, hints, cb)
- * update(cname, qobj, qobjarr, cb)
- * update(cname, qobj, qobjarr, hints, cb)
+ * update(cname, qobj, [cb])
+ * update(cname, qobj, hints, [cb])
+ * update(cname, qobj, qobjarr, [cb])
+ * update(cname, qobj, qobjarr, hints, [cb])
*
* @param {String} cname Name of collection
* @param {Object} qobj Main JSON query object
* @param {Array} [orarr] Array of additional OR query objects (joined with OR predicate).
* @param {Object} [hints] JSON object with query hints.
- * @param {Function} cb Callback function with arguments: (error, count) where:
+ * @param {Function} [cb] Callback function with arguments: (error, count) where:
* `count`: The number of updated records.
+ *
+ * @return If callback is provided returns undefined.
+ * If no callback is provided returns count of updated object.
*/
-EJDB.prototype.update = function(cname, qobj, orarr, hints, cb) {
- if (arguments.length == 4) {
- cb = hints;
- if (orarr && orarr.constructor === Array) {
- hints = {};
- } else {
- hints = orarr;
- orarr = [];
- }
- } else if (arguments.length == 3) {
- cb = orarr;
- orarr = [];
- hints = {};
- }
- if (typeof cb !== "function") {
- cb = null;
- }
- if (typeof cname !== "string") {
- throw new Error("Collection name 'cname' argument must be specified");
- }
- if (!hints || typeof hints !== "object") {
- hints = {};
- }
- if (!qobj || typeof qobj !== "object") {
- qobj = {};
+EJDB.prototype.update = function() {
+ //[cname, qobj, orarr, hints, cb]
+ var qa = parseQueryArgs(Array.prototype.slice.call(arguments));
+ var cb = qa[4];
+ if (cb) {
+ return this._impl.query(qa[0], [qa[1]].concat(qa[2], qa[3]), ejdblib.JBQRYCOUNT,
+ function(err, cursor, count, log) {
+ if (err) {
+ cb(err, null, log);
+ return;
+ }
+ cb(null, count, log);
+ });
+ } else {
+ return this._impl.query(qa[0], [qa[1]].concat(qa[2], qa[3]), ejdblib.JBQRYCOUNT, cb);
}
- return this._impl.query(cname,
- [qobj].concat(orarr, hints),
- ejdblib.JBQRYCOUNT,
- function(err, cursor, count, log) {
- if (err) {
- cb(err, null, log);
- return;
- }
- cb(null, count, log);
- });
};
/**
* Convenient count(*) operation.
*
* Call variations of count():
- * - count(cname, qobj, cb)
- * - count(cname, qobj, hints, cb)
- * - count(cname, qobj, qobjarr, cb)
- * - count(cname, qobj, qobjarr, hints, cb)
+ * - count(cname, qobj, [cb])
+ * - count(cname, qobj, hints, [cb])
+ * - count(cname, qobj, qobjarr, [cb])
+ * - count(cname, qobj, qobjarr, hints, [cb])
*
* @param {String} cname Name of collection
* @param {Object} qobj Main JSON query object
* @param {Array} [orarr] Array of additional OR query objects (joined with OR predicate).
* @param {Object} [hints] JSON object with query hints.
- * @param {Function} cb Callback function with arguments: (error, count) where:
+ * @param {Function} [cb] Callback function with arguments: (error, count) where:
* `count`: Number of matching records.
+ * @return If callback is provided returns undefined.
+ * If no callback is provided returns count of updated object.
*/
-EJDB.prototype.count = function(cname, qobj, orarr, hints, cb) {
- if (arguments.length == 4) {
- cb = hints;
- if (orarr && orarr.constructor === Array) {
- hints = {};
- } else {
- hints = orarr;
- orarr = [];
- }
- } else if (arguments.length == 3) {
- cb = orarr;
- orarr = [];
- hints = {};
- }
- if (typeof cb !== "function") {
- throw new Error("Callback 'cb' argument must be specified");
- }
- if (typeof cname !== "string") {
- throw new Error("Collection name 'cname' argument must be specified");
- }
- if (!hints || typeof hints !== "object") {
- hints = {};
- }
- if (!qobj || typeof qobj !== "object") {
- qobj = {};
+EJDB.prototype.count = function() {
+ //[cname, qobj, orarr, hints, cb]
+ var qa = parseQueryArgs(Array.prototype.slice.call(arguments));
+ var cb = qa[4];
+ if (cb) {
+ return this._impl.query(qa[0], [qa[1]].concat(qa[2], qa[3]), ejdblib.JBQRYCOUNT,
+ function(err, cursor, count, log) {
+ if (err) {
+ cb(err, null, log);
+ return;
+ }
+ cb(null, count, log);
+ });
+ } else {
+ return this._impl.query(qa[0], [qa[1]].concat(qa[2], qa[3]), ejdblib.JBQRYCOUNT, cb);
}
- return this._impl.query(cname,
- [qobj].concat(orarr, hints),
- ejdblib.JBQRYCOUNT,
- function(err, cursor, count) {
- if (err) {
- cb(err);
- return;
- }
- cursor.close();
- cb(null, count);
- });
};
-
/**
* Synchronize entire EJDB database and
* all its collections with storage.
V8ObjSet::iterator it = ctx->tset.find(obj);
if (it != ctx->tset.end()) {
bs->err = BSON_ERROR_ANY;
- bs->errstr = strdup("Circular object reference");
+ bs->errstr = strdup("Converting circular structure to JSON");
return;
}
ctx->nlevel++;
REQ_ARGS(3);
REQ_STR_ARG(0, cname); //Collection name
REQ_STR_ARG(1, soid); //String OID
- REQ_FUN_ARG(2, cb); //Callback
- if (soid.length() != 24) {
+ if (!ejdbisvalidoidstr(*soid)) {
return scope.Close(ThrowException(Exception::Error(String::New("Argument 2: Invalid OID string"))));
}
+ Local<Function> cb;
bson_oid_t oid;
bson_oid_from_string(&oid, *soid);
BSONCmdData *cmdata = new BSONCmdData(*cname);
cmdata->ref = oid;
NodeEJDB *njb = ObjectWrap::Unwrap< NodeEJDB > (args.This());
- BSONCmdTask *task = new BSONCmdTask(cb, njb, cmdLoad, cmdata, BSONCmdTask::delete_val);
- uv_queue_work(uv_default_loop(), &task->uv_work, s_exec_cmd_eio, s_exec_cmd_eio_after);
- return scope.Close(args.This());
+ if (args[2]->IsFunction()) {
+ cb = Local<Function>::Cast(args[2]);
+ BSONCmdTask *task = new BSONCmdTask(cb, njb, cmdLoad, cmdata, BSONCmdTask::delete_val);
+ uv_queue_work(uv_default_loop(), &task->uv_work, s_exec_cmd_eio, s_exec_cmd_eio_after);
+ return scope.Close(args.This());
+ } else {
+ BSONCmdTask task(cb, njb, cmdLoad, cmdata, BSONCmdTask::delete_val);
+ njb->load(&task);
+ return scope.Close(njb->load_after(&task));
+ }
}
static Handle<Value> s_remove(const Arguments& args) {
REQ_ARGS(3);
REQ_STR_ARG(0, cname); //Collection name
REQ_STR_ARG(1, soid); //String OID
- REQ_FUN_ARG(2, cb); //Callback
- if (soid.length() != 24) {
+ if (!ejdbisvalidoidstr(*soid)) {
return scope.Close(ThrowException(Exception::Error(String::New("Argument 2: Invalid OID string"))));
}
+ Local<Function> cb;
bson_oid_t oid;
bson_oid_from_string(&oid, *soid);
BSONCmdData *cmdata = new BSONCmdData(*cname);
cmdata->ref = oid;
NodeEJDB *njb = ObjectWrap::Unwrap< NodeEJDB > (args.This());
- BSONCmdTask *task = new BSONCmdTask(cb, njb, cmdRemove, cmdata, BSONCmdTask::delete_val);
- uv_queue_work(uv_default_loop(), &task->uv_work, s_exec_cmd_eio, s_exec_cmd_eio_after);
- return scope.Close(args.This());
+ if (args[2]->IsFunction()) {
+ cb = Local<Function>::Cast(args[2]);
+ BSONCmdTask *task = new BSONCmdTask(cb, njb, cmdRemove, cmdata, BSONCmdTask::delete_val);
+ uv_queue_work(uv_default_loop(), &task->uv_work, s_exec_cmd_eio, s_exec_cmd_eio_after);
+ return scope.Close(args.This());
+ } else {
+ BSONCmdTask task(cb, njb, cmdRemove, cmdata, BSONCmdTask::delete_val);
+ njb->remove(&task);
+ return scope.Close(njb->remove_after(&task));
+ }
}
static Handle<Value> s_save(const Arguments& args) {
HandleScope scope;
- REQ_ARGS(4);
+ REQ_ARGS(3);
REQ_STR_ARG(0, cname); //Collection name
REQ_ARR_ARG(1, oarr); //Array of JSON objects
REQ_OBJ_ARG(2, opts); //Options obj
- REQ_FUN_ARG(3, cb); //Callback
+ Local<Function> cb;
BSONCmdData *cmdata = new BSONCmdData(*cname);
for (uint32_t i = 0; i < oarr->Length(); ++i) {
Local<Value> v = oarr->Get(i);
cmdata->merge = true;
}
NodeEJDB *njb = ObjectWrap::Unwrap< NodeEJDB > (args.This());
- BSONCmdTask *task = new BSONCmdTask(cb, njb, cmdSave, cmdata, BSONCmdTask::delete_val);
- uv_queue_work(uv_default_loop(), &task->uv_work, s_exec_cmd_eio, s_exec_cmd_eio_after);
- return scope.Close(args.This());
+
+ if (args[3]->IsFunction()) { //callback provided
+ cb = Local<Function>::Cast(args[3]);
+ BSONCmdTask *task = new BSONCmdTask(cb, njb, cmdSave, cmdata, BSONCmdTask::delete_val);
+ uv_queue_work(uv_default_loop(), &task->uv_work, s_exec_cmd_eio, s_exec_cmd_eio_after);
+ return scope.Close(args.This());
+ } else {
+ BSONCmdTask task(cb, njb, cmdSave, cmdata, BSONCmdTask::delete_val);
+ njb->save(&task);
+ return scope.Close(njb->save_after(&task));
+ }
}
static Handle<Value> s_query(const Arguments& args) {
}
}
- void remove_after(BSONCmdTask *task) {
+ Handle<Value> remove_after(BSONCmdTask *task) {
HandleScope scope;
Local<Value> argv[1];
if (task->cmd_ret != 0) {
} else {
argv[0] = Local<Primitive>::New(Null());
}
- TryCatch try_catch;
- task->cb->Call(Context::GetCurrent()->Global(), 1, argv);
- if (try_catch.HasCaught()) {
- FatalException(try_catch);
+ if (task->cb.IsEmpty() || task->cb->IsNull() || task->cb->IsUndefined()) {
+ if (task->cmd_ret != 0)
+ return scope.Close(ThrowException(argv[0]));
+ else
+ return scope.Close(Undefined());
+ } else {
+ TryCatch try_catch;
+ task->cb->Call(Context::GetCurrent()->Global(), 1, argv);
+ if (try_catch.HasCaught()) {
+ FatalException(try_catch);
+ }
+ return scope.Close(Undefined());
}
}
}
}
- void save_after(BSONCmdTask *task) {
+ Handle<Value> save_after(BSONCmdTask *task) {
HandleScope scope;
Local<Value> argv[2];
if (task->cmd_ret != 0) {
}
}
argv[1] = oids;
- TryCatch try_catch;
- task->cb->Call(Context::GetCurrent()->Global(), 2, argv);
- if (try_catch.HasCaught()) {
- FatalException(try_catch);
+ if (task->cb.IsEmpty() || task->cb->IsNull() || task->cb->IsUndefined()) {
+ return (task->cmd_ret != 0) ? scope.Close(ThrowException(argv[0])) : scope.Close(argv[1]);
+ } else {
+ TryCatch try_catch;
+ task->cb->Call(Context::GetCurrent()->Global(), 2, argv);
+ if (try_catch.HasCaught()) {
+ FatalException(try_catch);
+ }
+ return scope.Close(Undefined());
}
}
cmdata->bsons.push_back(ejdbloadbson(coll, &task->cmd_data->ref));
}
- void load_after(BSONCmdTask *task) {
+ Handle<Value> load_after(BSONCmdTask *task) {
HandleScope scope;
Local<Value> argv[2];
if (task->cmd_ret != 0) {
} else {
argv[1] = Local<Primitive>::New(Null());
}
- TryCatch try_catch;
- task->cb->Call(Context::GetCurrent()->Global(), 2, argv);
- if (try_catch.HasCaught()) {
- FatalException(try_catch);
+ if (task->cb.IsEmpty() || task->cb->IsNull() || task->cb->IsUndefined()) {
+ return (task->cmd_ret != 0) ? scope.Close(ThrowException(argv[0])) : scope.Close(argv[1]);
+ } else {
+ TryCatch try_catch;
+ task->cb->Call(Context::GetCurrent()->Global(), 2, argv);
+ if (try_catch.HasCaught()) {
+ FatalException(try_catch);
+ }
+ return scope.Close(Undefined());
}
}