UNPKG

ejdb

Version:

EJDB - Embedded JSON Database engine

760 lines (703 loc) 26.4 kB
/** * 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;