ejdb
Version:
EJDB - Embedded JSON Database engine
760 lines (703 loc) • 26.4 kB
JavaScript
/**
* EJDB nodejs binding.
* @module {EJDB} ejdb
*/
/**************************************************************************************************
* NodeJS API for EJDB database library http://ejdb.org
* Copyright (C) 2012-2015 Softmotions Ltd <info@softmotions.com>
*
* This file is part of EJDB.
* EJDB is free software; you can redistribute it and/or modify it under the terms of
* the GNU Lesser General Public License as published by the Free Software Foundation; either
* version 2.1 of the License or any later version. EJDB is distributed in the hope
* that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
* You should have received a copy of the GNU Lesser General Public License along with EJDB;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307 USA.
*************************************************************************************************/
var binary = require('node-pre-gyp');
var path = require('path');
var ejdblib_path = binary.find(path.resolve(path.join(__dirname, './package.json')));
var ejdblib = require(ejdblib_path);
var EJDBImpl = ejdblib.NodeEJDB;
const DEFAULT_OPEN_MODE = (ejdblib.JBOWRITER | ejdblib.JBOCREAT);
/**
* The nodejs database wrapper.
* @class
* @memberOf ejdb
*/
var EJDB = function() {
Object.defineProperty(this, "_impl", {
value : new EJDBImpl(),
configurable : false,
enumerable : false,
writable : false
});
return this;
};
for (var k in ejdblib) { //Export constants
if (k.indexOf("JB") === 0) {
EJDB[k] = ejdblib[k];
}
}
/**
* EJDB default open mode: `JBOWRITER | JBOCREAT`
*/
EJDB.DEFAULT_OPEN_MODE = DEFAULT_OPEN_MODE;
/**
* Open database.
* Return database instance handle object .
*
* Default open mode: JBOWRITER | JBOCREAT
*
* Depending on `cb` parameter is passed to this function will be
* either async or blocking.
*
* @param {String} dbFile Database main file name
* @param {Number} openMode [JBOWRITER | JBOCREAT | ..] Bitmask of open modes:
* - `JBOREADER` Open as a reader.
* - `JBOWRITER` Open as a writer.
* - `JBOCREAT` Create db if it not exists
* - `JBOTRUNC` Truncate db.
* @param {Function} [cb] Callback called with error and EJDB object arguments.
* @returns {EJDB} EJDB database instance.
*/
EJDB.open = function(dbFile, openMode, cb) {
var db = new EJDB();
var mode = (openMode > 0) ? openMode : DEFAULT_OPEN_MODE;
if (cb) {
db._impl.open(dbFile, mode, function(err) {
cb(err, db);
});
return;
}
db._impl.open(dbFile, mode);
return db;
};
/**
* Returns true if argument is valid object id (OID) string.
* @param {String} oid Object id.
* @return {Boolean}
*/
EJDB.isValidOID = function(oid) {
if (typeof oid !== "string" || oid.length !== 24) {
return false;
}
var i = 0;
//noinspection StatementWithEmptyBodyJS
for (; ((oid[i] >= 0x30 && oid[i] <= 0x39) || /* 1 - 9 */
(oid[i] >= 0x61 && oid[i] <= 0x66)); /* a - f */
++i);
return (i === 24);
};
/**
* Close a database.
* If database was not opened it does nothing.
*
* Depending on if cb parameter is passed this function is either async or
* blocking.
*
* @param {Function} [cb] Callback called with error argument.
*/
EJDB.prototype.close = function(cb) {
return this._impl.close(cb);
};
/**
* Check if database is in opened state.
*/
EJDB.prototype.isOpen = function() {
return this._impl.isOpen();
};
/**
* Return existing collection or create new collection if it was not created before.
* Collection options `copts`
* are applied only for newly created collection.
* For existing collections `copts` takes no effect.
*
* Collection options (copts):
* {
* "cachedrecords" : Max number of cached records in shared memory segment. Default: 0
* "records" : Estimated number of records in this collection. Default: 65535.
* "large" : Specifies that the size of the database can be larger than 2GB. Default: false
* "compressed" : If true collection records will be compressed with DEFLATE compression. Default: false.
* }
*
* Depending on if cb parameter is passed this function is either async or
* blocking.
*
* @param {String} cname Name of collection.
* @param {Object} [copts] Collection options.
* @param {Function} [cb] Callback called with an error argument.
* @return {*}
*/
EJDB.prototype.ensureCollection = function(cname, copts, cb) {
if (arguments.length === 2 && typeof copts === 'function') {
cb = copts;
copts = {};
}
return this._impl.ensureCollection(cname, copts || {}, cb);
};
/**
* Please use #dropCollection
* @deprecated Will be removed in v1.1 //todo
*/
EJDB.prototype.removeCollection = function(cname, prune, cb) {
this.dropCollection.apply(this, arguments);
};
/**
* Drop collection.
*
* Call variations:
* - dropCollection(cname)
* - dropCollection(cname, cb)
* - dropCollection(cname, prune, cb)
*
* @param {String} cname Name of collection.
* @param {Boolean} [prune=false] If true the collection data will erased from disk.
* @param {Function} [cb] Callback function with arguments: (error)
*/
EJDB.prototype.dropCollection = function(cname, prune, cb) {
if (arguments.length == 2) {
cb = prune;
prune = false;
}
if (!cb) {
cb = function() {
};
}
//noinspection JSDeprecatedSymbols
return this._impl.removeCollection(cname, !!prune, cb);
};
/**
* Save/update specified in `jsarr` JSON objects in the collection identified by `cname`.
* If collection with `cname` does not exists it will be created.
*
* Every persistent object has unique identifier (OID) placed in the `_id` property.
* If a saved object does not have `_id` it will be autogenerated.
* To identify and update object it should contains the `_id` property.
*
* If `cb` is not provided this function will be synchronous.
*
* Calling variations:
* - save(cname, <json object>|<Array of json objects>)
* - 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)
*
* NOTE: Field names of passed JSON objects may not contain `$` and `.` characters,
* error condition will be fired in this case.
*
* @param {String} cname Collection name.
* @param {Array|Object} jsarr Signle JSON object or array of JSON objects to save
* @param {Object?} opts Optional options obj.
* If opts.merge == true saved object will be merged with who's
* already persisted in db.
* @param {Function} [cb] Callback function with arguments: (error, {Array} of OIDs of saved objects)
* @return {Array} of OIDs of saved objects in synchronous mode otherwise returns {undefined}.
*/
EJDB.prototype.save = function(cname, jsarr, opts, cb) {
if (!jsarr) {
return [];
}
if (!Array.isArray(jsarr)) {
jsarr = [jsarr];
}
if (typeof opts == "function") {
cb = opts;
opts = null;
}
var postprocess = function(oids) {
//Assign _id property for newly created objects
for (var i = jsarr.length - 1; i >= 0; --i) {
var so = jsarr[i];
if (so != null && so["_id"] !== oids[i]) {
so["_id"] = oids[i];
}
}
};
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);
});
}
};
/**
* Retrieve JSON object identified by OID.
* If callback is not provided this function will be synchronous.
* If callback is provided this function returns {undefined}.
*
* @param {String} cname Name of collection
* @param {String} oid Object identifier (OID)
* @param {Function} [cb] Callback function with arguments: (error, obj)
* `obj`: Retrieved JSON object or `null` if it is not found.
* @return JSON object or {null} if it is not found in synchronous mode otherwise return {undefined}.
*/
EJDB.prototype.load = function(cname, oid, cb) {
return this._impl.load(cname, oid, cb);
};
/**
* Remove JSON object identified nby `oid` from the collection `cname`.
* 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)
* @return {undefined}
*/
EJDB.prototype.remove = function(cname, oid, cb) {
return this._impl.remove(cname, oid, cb);
};
/*
* - (cname, [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;
var i = 0;
cname = args[i++];
if (typeof cname !== "string") {
throw new Error("Collection name 'cname' argument must be specified");
}
var next = args[i++];
if (typeof next === "function") {
cb = next;
} else {
qobj = next;
}
next = args[i++];
if (next !== undefined) {
if (Array.isArray(next)) {
orarr = next;
next = args[i++];
} else if (typeof next === "object") {
hints = next;
orarr = null;
next = args[i++];
}
if (!hints && typeof next === "object") {
hints = next;
next = args[i++];
}
if (typeof next === "function") {
cb = next;
}
}
return [cname, (qobj || {}), (orarr || []), (hints || {}), (cb || null)];
}
/**
* Execute query on collection.
*
* Execute query on collection of documents.
* @see See the complete query language specification: http://ejdb.org/doc/ql/ql.html
*
* To traverse selected records cursor object is used:
* - Cursor#next() Move cursor to the next record and returns true if next record exists.
* - Cursor#hasNext() Returns true if cursor can be placed to the next record.
* - Cursor#field(name) Retrieve value of the specified field of the current JSON object record.
* - Cursor#object() Retrieve whole JSON object with all fields.
* - Cursor#reset() Reset cursor to its initial state.
* - Cursor#length Read-only property: Number of records placed into cursor.
* - Cursor#pos Read/Write property: You can set cursor position: 0 <= pos < length
* - Cursor#close() Closes cursor and free cursor resources. Cursor cant be used in closed state.
*
* Call variations of find():
* - find(cname, [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} [qobjarr] 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:
* `cursor`: Cursor object to traverse 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]
var qa = parseQueryArgs(arguments);
return this._impl.query(qa[0], [qa[1]].concat(qa[2], qa[3]),
(qa[3]["$onlycount"] ? ejdblib.JBQRYCOUNT : 0),
qa[4]);
};
/**
* Retrieve a first found document matched to the specified query.
* If callback is not provided this function will be synchronous.
*
* Call variations of findOne():
* - findOne(cname, [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:
* `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]
var qa = parseQueryArgs(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 ret = null;
var cursor = this._impl.query(qa[0], [qa[1]].concat(qa[2], qa[3]), 0, cb);
if (cursor && typeof cursor === "object") {
if (cursor.next()) {
ret = cursor.object();
}
cursor.close();
}
return ret;
}
};
/**
* Execute ejdb database command.
*
* See http://ejdb.org/doc/iexp/index.html#cmd
*
* If callback is not provided this function will be synchronous.
*
* @param {Object} cmd BSON command spec.
* @param {Function} [cb] Callback function with arguments: (error, obj) where:
* `obj`: Command response JSON object.
* @return Command response JSON object if callback is not provided.
*/
EJDB.prototype.command = function(cmd, cb) {
if (cb) {
this._impl.command(cmd, 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 ret = null;
var cursor = this._impl.command(cmd);
if (cursor && typeof cursor === "object") {
if (cursor.next()) {
ret = cursor.object();
}
cursor.close();
}
return ret;
}
};
/**
* Convenient method to execute update queries.
*
* Call variations of update():
* 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:
* `count`: The number of updated records.
*
* @return If callback is provided returns {undefined}.
* If no callback is provided returns {Number} of updated objects.
*/
EJDB.prototype.update = function() {
//[cname, qobj, orarr, hints, cb]
var qa = parseQueryArgs(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);
}
};
/**
* Convenient count(*) operation.
*
* Call variations of count():
* - count(cname, [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:
* `count`: Number of matching records.
*
* @return If callback is provided returns {undefined}.
* If no callback is provided returns {Number} of matched object.
*/
EJDB.prototype.count = function() {
//[cname, qobj, orarr, hints, cb]
var qa = parseQueryArgs(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);
}
};
/**
* Synchronize entire database and all of its collections with disk.
* If callback is not provided this function will be synchronous.
* @param {Function} [cb] Optional callback function. Callback args: (error)
*/
EJDB.prototype.sync = function(cb) {
return this._impl.sync(cb);
};
/**
* DROP indexes of all types for JSON field path.
* If callback is not provided this function will be synchronous.
* @param {String} cname Name of collection
* @param {String} path JSON field path
* @param {Function} [cb] Optional callback function. Callback args: (error)
*/
EJDB.prototype.dropIndexes = function(cname, path, cb) {
return this._impl.setIndex(cname, path, ejdblib.JBIDXDROPALL, cb);
};
/**
* OPTIMIZE indexes of all types for JSON field path.
* Performs B+ tree index file optimization.
* If callback is not provided this function will be synchronous.
* @param {String} cname Name of collection
* @param {String} path JSON field path
* @param {Function} [cb] Optional callback function. Callback args: (error)
*/
EJDB.prototype.optimizeIndexes = function(cname, path, cb) {
return this._impl.setIndex(cname, path, ejdblib.JBIDXOP, cb);
};
/**
* Ensure index presence of String type for JSON field path.
* If callback is not provided this function will be synchronous.
* @param {String} cname Name of collection
* @param {String} path JSON field path
* @param {Function} [cb] Optional callback function. Callback args: (error)
*/
EJDB.prototype.ensureStringIndex = function(cname, path, cb) {
return this._impl.setIndex(cname, path, ejdblib.JBIDXSTR, cb);
};
/**
* Rebuild index of String type for JSON field path.
* If callback is not provided this function will be synchronous.
* @param {String} cname Name of collection
* @param {String} path JSON field path
* @param {Function} [cb] Optional callback function. Callback args: (error)
*/
EJDB.prototype.rebuildStringIndex = function(cname, path, cb) {
return this._impl.setIndex(cname, path, ejdblib.JBIDXSTR | ejdblib.JBIDXREBLD, cb);
};
/**
* Drop index of String type for JSON field path.
* If callback is not provided this function will be synchronous.
* @param {String} cname Name of collection
* @param {String} path JSON field path
* @param {Function} [cb] Optional callback function. Callback args: (error)
*/
EJDB.prototype.dropStringIndex = function(cname, path, cb) {
return this._impl.setIndex(cname, path, ejdblib.JBIDXSTR | ejdblib.JBIDXDROP, cb);
};
/**
* Ensure case insensitive String index for JSON field path.
* If callback is not provided this function will be synchronous.
* @param {String} cname Name of collection
* @param {String} path JSON field path
* @param {Function} [cb] Optional callback function. Callback args: (error)
*/
EJDB.prototype.ensureIStringIndex = function(cname, path, cb) {
return this._impl.setIndex(cname, path, ejdblib.JBIDXISTR, cb);
};
/**
* Rebuild case insensitive String index for JSON field path.
* If callback is not provided this function will be synchronous.
* @param {String} cname Name of collection
* @param {String} path JSON field path
* @param {Function} [cb] Optional callback function. Callback args: (error)
*/
EJDB.prototype.rebuildIStringIndex = function(cname, path, cb) {
return this._impl.setIndex(cname, path, ejdblib.JBIDXISTR | ejdblib.JBIDXREBLD, cb);
};
/**
* Drop case insensitive String index for JSON field path.
* If callback is not provided this function will be synchronous.
* @param {String} cname Name of collection
* @param {String} path JSON field path
* @param {Function} [cb] Optional callback function. Callback args: (error)
*/
EJDB.prototype.dropIStringIndex = function(cname, path, cb) {
return this._impl.setIndex(cname, path, ejdblib.JBIDXISTR | ejdblib.JBIDXDROP, cb);
};
/**
* Ensure index presence of Number type for JSON field path.
* If callback is not provided this function will be synchronous.
* @param {String} cname Name of collection
* @param {String} path JSON field path
* @param {Function} [cb] Optional callback function. Callback args: (error)
*/
EJDB.prototype.ensureNumberIndex = function(cname, path, cb) {
return this._impl.setIndex(cname, path, ejdblib.JBIDXNUM, cb);
};
/**
* Rebuild index of Number type for JSON field path.
* If callback is not provided this function will be synchronous.
* @param {String} cname Name of collection
* @param {String} path JSON field path
* @param {Function} [cb] Optional callback function. Callback args: (error)
*/
EJDB.prototype.rebuildNumberIndex = function(cname, path, cb) {
return this._impl.setIndex(cname, path, ejdblib.JBIDXNUM | ejdblib.JBIDXREBLD, cb);
};
/**
* Drop index of Number type for JSON field path.
* If callback is not provided this function will be synchronous.
* @param {String} cname Name of collection
* @param {String} path JSON field path
* @param {Function} [cb] Optional callback function. Callback args: (error)
*/
EJDB.prototype.dropNumberIndex = function(cname, path, cb) {
return this._impl.setIndex(cname, path, ejdblib.JBIDXNUM | ejdblib.JBIDXDROP, cb);
};
/**
* Ensure index presence of Array type for JSON field path.
* If callback is not provided this function will be synchronous.
* @param {String} cname Name of collection
* @param {String} path JSON field path
* @param {Function} [cb] Optional callback function. Callback args: (error)
*/
EJDB.prototype.ensureArrayIndex = function(cname, path, cb) {
return this._impl.setIndex(cname, path, ejdblib.JBIDXARR, cb);
};
/**
* Rebuild index of Array type for JSON field path.
* If callback is not provided this function will be synchronous.
* @param {String} cname Name of collection
* @param {String} path JSON field path
* @param {Function} [cb] Optional callback function. Callback args: (error)
*/
EJDB.prototype.rebuildArrayIndex = function(cname, path, cb) {
return this._impl.setIndex(cname, path, ejdblib.JBIDXARR | ejdblib.JBIDXREBLD, cb);
};
/**
* Drop index of Array type for JSON field path.
* If callback is not provided this function will be synchronous.
* @param {String} cname Name of collection
* @param {String} path JSON field path
* @param {Function} [cb] Optional callback function. Callback args: (error)
*/
EJDB.prototype.dropArrayIndex = function(cname, path, cb) {
return this._impl.setIndex(cname, path, ejdblib.JBIDXARR | ejdblib.JBIDXDROP, cb);
};
/**
* Get description of EJDB database and its collections.
*/
EJDB.prototype.getDBMeta = function() {
return this._impl.dbMeta();
};
/**
* Begin collection transaction.
* If callback is not provided this function will be synchronous.
* @param {String} cname Name of collection
* @param {Function} [cb] Optional callback function. Callback args: (error)
*/
EJDB.prototype.beginTransaction = function(cname, cb) {
return this._impl._txctl(cname, 8/*cmdTxBegin*/, cb);
};
/**
* Commit collection transaction.
* If callback is not provided this function will be synchronous.
* @param {String} cname Name of collection
* @param {Function} [cb] Optional callback function. Callback args: (error)
*/
EJDB.prototype.commitTransaction = function(cname, cb) {
return this._impl._txctl(cname, 10/*cmdTxCommit*/, cb);
};
/**
* Abort collection transaction.
* If callback is not provided this function will be synchronous.
* @param {String} cname Name of collection
* @param {Function} [cb] Optional callback function. Callback args: (error)
*/
EJDB.prototype.rollbackTransaction = function(cname, cb) {
return this._impl._txctl(cname, 9/*cmdTxAbort*/, cb);
};
/**
* Get collection transaction status.
* If callback is not provided this function will be synchronous.
* @param {String} cname Name of collection
* @param {Function} [cb] Optional callback function. Callback args: (error, isTxActive)
* @return If no callback provided it will return {true} if transaction active otherwise {false}
* or {undefined} if callback presented.
*/
EJDB.prototype.getTransactionStatus = function(cname, cb) {
return this._impl._txctl(cname, 11/*cmdTxStatus*/, cb);
};
module.exports = EJDB;