ejdb
Version:
EJDB - Embedded JSON Database engine
423 lines (374 loc) • 16.4 kB
JavaScript
var cdb = null; //current DB desc
const maxInspectRows = 10;
const maxInspectDepth = 10;
var useColors = true;
var quiet = false;
var cmd = null;
var pkg = require("../package.json");
//Parse aguments
(function () {
var args = process.argv;
for (var i = 2; i < args.length; ++i) {
var a = args[i];
if (["--help", "-h"].indexOf(a) !== -1) {
help();
} else if (["--no-colors", "-n"].indexOf(a) !== -1) {
useColors = false;
} else if (["--quiet", "-q"].indexOf(a) !== -1) {
quiet = true;
} else if (["--cmd", "-c"].indexOf(a) !== -1) {
cmd = a;
} else if (i === args.length - 1) { //last arg
cmd = "db.open('" + a + "')"; //todo review
}
}
})();
function help() {
var h = [];
h.push("EJDB CLI v" + pkg.version);
h.push("usage: ejdb [options] [dbfile]");
h.push("options:");
h.push("\t-h --help\tshow this help tip");
h.push("\t-n --no-colors\tdo not use colored output");
h.push("\t-q --quiet\trun in quiet output mode");
h.push("\t-c --cmd\trun specified javascript command");
console.error(h.join("\n"));
process.exit(0);
}
if (!quiet) {
console.log("Welcome to EJDB CLI v" + pkg.version);
}
var util = require("util");
var path = require("path");
var EJDB = require("../ejdb.js");
var clinspect = require("../clinspect.js");
// help messages (for methods with collection)
var helpGetters = {
save : function (selected) {
return "(" + (!selected ? "cname, " : "") + "object|array of object, [opts], [cb]) Save/update specified JSON objects in the collection."
},
load : function (selected) {
return "(" + (!selected ? "cname, " : "") + "oid, [cb]) Loads object identified by OID from the collection"
},
remove : function (selected) {
return "(" + (!selected ? "cname, " : "") + "oid, [cb]) Removes object from the collection"
},
find : function (selected) {
return "(" + (!selected ? "cname, " : "") + "[qobj], [qobjarr], [hints], [cb]) Execute query on collection"
},
findOne : function (selected) {
return "(" + (!selected ? "cname, " : "") + "[qobj], [qobjarr], [hints], [cb]) Retrive one object from the collection"
},
update : function (selected) {
return "(" + (!selected ? "cname, " : "") + "[qobj], [qobjarr], [hints], [cb]) Perform update query on collection"
},
count : function (selected) {
return "(" + (!selected ? "cname, " : "") + "[qobj], [qobjarr], [hints], [cb]) Convenient count(*) operation"
},
dropIndexes : function (selected) {
return "(" + (!selected ? "cname, " : "") + "path, [cb]) Drop indexes of all types for JSON field path"
},
optimizeIndexes : function (selected) {
return "(" + (!selected ? "cname, " : "") + "path, [cb]) Optimize indexes of all types for JSON field path"
},
ensureStringIndex : function (selected) {
return "(" + (!selected ? "cname, " : "") + "path, [cb]) Ensure String index for JSON field path"
},
rebuildStringIndex : function (selected) {
return "(" + (!selected ? "cname, " : "") + "path, [cb])"
},
dropStringIndex : function (selected) {
return "(" + (!selected ? "cname, " : "") + "path, [cb])"
},
ensureIStringIndex : function (selected) {
return "(" + (!selected ? "cname, " : "") + "path, [cb]) Ensure case insensitive String index for JSON field path"
},
rebuildIStringIndex : function (selected) {
return "(" + (!selected ? "cname, " : "") + "path, [cb])"
},
dropIStringIndex : function (selected) {
return "(" + (!selected ? "cname, " : "") + "path, [cb])"
},
ensureNumberIndex : function (selected) {
return "(" + (!selected ? "cname, " : "") + "path, [cb]) Ensure index presence of Number type for JSON field path"
},
rebuildNumberIndex : function (selected) {
return "(" + (!selected ? "cname, " : "") + "path, [cb])"
},
dropNumberIndex : function (selected) {
return "(" + (!selected ? "cname, " : "") + "path, [cb])"
},
ensureArrayIndex : function (selected) {
return "(" + (!selected ? "cname, " : "") + "path, [cb]) Ensure index presence of Array type for JSON field path"
},
rebuildArrayIndex : function (selected) {
return "(" + (!selected ? "cname, " : "") + "path, [cb])"
},
dropArrayIndex : function (selected) {
return "(" + (!selected ? "cname, " : "") + "path, [cb])"
}
};
//Init help hints
Object.defineProperty(EJDB.open, "_help_", {value : "(dbFile, [openMode], [cb]) Open database"});
Object.defineProperty(EJDB.prototype.close, "_help_", {value : "([cb]) Close database"});
Object.defineProperty(EJDB.prototype.isOpen, "_help_", {value : "Check if database in opened state"});
Object.defineProperty(EJDB.prototype.ensureCollection, "_help_", {value : "(cname, [copts], [cb]) Creates new collection if it does't exists"});
Object.defineProperty(EJDB.prototype.dropCollection, "_help_", {value : "(cname, [prune], [cb]) Drop collection, " +
"if `prune` is true collection db files will be erased from disk."});
Object.defineProperty(EJDB.prototype.getDBMeta, "_help_", {value : "Get description of EJDB database and its collections"});
Object.defineProperty(EJDB.prototype.sync, "_help_", {value : "Synchronize entire EJDB database with disk"});
Object.defineProperty(EJDB.prototype.save, "_help_", {value : helpGetters.save()});
Object.defineProperty(EJDB.prototype.load, "_help_", {value : helpGetters.load()});
Object.defineProperty(EJDB.prototype.remove, "_help_", {value : helpGetters.remove()});
Object.defineProperty(EJDB.prototype.find, "_help_", {value : helpGetters.find()});
Object.defineProperty(EJDB.prototype.findOne, "_help_", {value : helpGetters.findOne()});
Object.defineProperty(EJDB.prototype.update, "_help_", {value : helpGetters.update()});
Object.defineProperty(EJDB.prototype.count, "_help_", {value : helpGetters.count()});
Object.defineProperty(EJDB.prototype.dropIndexes, "_help_", {value : helpGetters.dropIndexes()});
Object.defineProperty(EJDB.prototype.optimizeIndexes, "_help_", {value : helpGetters.optimizeIndexes()});
Object.defineProperty(EJDB.prototype.ensureStringIndex, "_help_", {value : helpGetters.ensureStringIndex()});
Object.defineProperty(EJDB.prototype.rebuildStringIndex, "_help_", {value : helpGetters.rebuildStringIndex()});
Object.defineProperty(EJDB.prototype.dropStringIndex, "_help_", {value : helpGetters.dropStringIndex()});
Object.defineProperty(EJDB.prototype.ensureIStringIndex, "_help_", {value : helpGetters.ensureIStringIndex()});
Object.defineProperty(EJDB.prototype.rebuildIStringIndex, "_help_", {value : helpGetters.rebuildIStringIndex()});
Object.defineProperty(EJDB.prototype.dropIStringIndex, "_help_", {value : helpGetters.dropIStringIndex()});
Object.defineProperty(EJDB.prototype.ensureNumberIndex, "_help_", {value : helpGetters.ensureNumberIndex()});
Object.defineProperty(EJDB.prototype.rebuildNumberIndex, "_help_", {value : helpGetters.rebuildNumberIndex()});
Object.defineProperty(EJDB.prototype.dropNumberIndex, "_help_", {value : helpGetters.dropNumberIndex()});
Object.defineProperty(EJDB.prototype.ensureArrayIndex, "_help_", {value : helpGetters.ensureArrayIndex()});
Object.defineProperty(EJDB.prototype.rebuildArrayIndex, "_help_", {value : helpGetters.rebuildArrayIndex()});
Object.defineProperty(EJDB.prototype.dropArrayIndex, "_help_", {value : helpGetters.dropArrayIndex()});
Object.defineProperty(EJDB.prototype.beginTransaction, "_help_", {value : "Begin collection transaction"});
Object.defineProperty(EJDB.prototype.commitTransaction, "_help_", {value : "Commit collection transaction"});
Object.defineProperty(EJDB.prototype.rollbackTransaction, "_help_", {value : "Rollback collection transaction"});
Object.defineProperty(EJDB.prototype.getTransactionStatus, "_help_", {value : "Get collection transaction status"});
// collection controllers history (for merge)
var cchistory = [];
// bind collections controllers
// dbctrl - db controller
// forcerebind - if <code>true</code> force rebind all collection controllers, otherwise check added/deleted collections
var bindColCtls = function(dbctrl, forcerebind) {
var octrls = forcerebind ? [] : cchistory;
var nctrls = [];
var dbMeta = cdb.jb.getDBMeta();
if (dbMeta && dbMeta.collections) {
for (var j = 0; j < dbMeta.collections.length; ++j) {
var collection = dbMeta.collections[j];
var ci;
if ((ci = octrls.indexOf(collection.name)) != -1) {
nctrls.push(collection.name);
octrls.splice(ci, 1);
} else if (!dbctrl[collection.name]){
nctrls.push(collection.name);
dbctrl[collection.name] = colctl(dbctrl, collection.name);
}
}
}
for (var i = 0; i < octrls.length; ++i) {
delete dbctrl[octrls[i]];
}
// save current known collections
cchistory = nctrls;
};
// collection controller (creation function)
var colctl = function (db, cname) {
// build arguments function: add <cname> as first argument
var buildargs = function (args) {
var result = [cname];
// args is Object, we need to iterate all fields with numeric key for collecting arguments
for (var i = 0; args[i]; ++i) {
result.push(args[i]);
}
return result;
};
// method names for creating aliases (db.<method>(cname, ...) -> db.cname.<method>(...))
var mnames = [
"save", "load", "remove", "find", "findOne", "update", "count",
"dropCollection", "dropIndexes", "optimizeIndexes",
"ensureStringIndex", "rebuildStringIndex", "dropStringIndex",
"ensureIStringIndex", "rebuildIStringIndex", "dropIStringIndex",
"ensureNumberIndex", "rebuildNumberIndex", "dropNumberIndex",
"ensureArrayIndex", "rebuildArrayIndex", "dropArrayIndex"
];
// bind method alias
var mbind = function (mname) {
return function () {
return db[mname].apply(db, buildargs(arguments));
}
};
// collection controller impl
var colctlimpl = {
inspect : function() {
return '\u001b[' + 36 + 'm' + "[Collection]" + '\u001b[' + 39 + 'm';
}
};
var mname;
// wrap methods
for (var i = 0; i < mnames.length; ++i) {
mname = mnames[i];
colctlimpl[mname] = mbind(mname);
if (helpGetters[mname]) {
Object.defineProperty(colctlimpl[mname], '_help_', {value : helpGetters[mname](true)});
}
}
return colctlimpl
};
repl = require("repl").start({
prompt : "ejdb> ",
input : process.stdin,
output : process.stdout,
terminal : true,
writer : function (obj) {
return clinspect.inspect(obj, maxInspectDepth, useColors)
}
});
//console.log("MF=" + module.filename);
var dbctl = {
open : function (dbpath) {
if (dbpath == null) {
return error("No file path specified");
}
if (cdb) {
return error("Database already opened: " + cdb.dbpath);
}
dbpath = path.resolve(dbpath);
cdb = {
dbpath : dbpath,
jb : EJDB.open(dbpath)
};
syncdbctx();
return dbstatus(cdb);
},
status : function () {
syncdbctx();
return dbstatus(cdb);
},
close : function () {
if (!cdb || !cdb.jb) {
return error("Database already closed");
}
try {
cdb.jb.close();
} finally {
cdb = null;
}
syncdbctx();
}
};
Object.defineProperty(dbctl.open, "_help_", {value : EJDB.open._help_});
Object.defineProperty(dbctl.close, "_help_", {value : EJDB.prototype.close._help_});
Object.defineProperty(dbctl.status, "_help_", {value : "Get current database status"});
repl.on("exit", function () {
dbctl.close();
console.log("Bye!");
});
function dbstatus(cdb) {
if (cdb) {
return cdb.jb.getDBMeta();
} else {
return {};
}
}
function syncdbctx() {
var db = {};
repl.resetContext();
if (cdb && cdb.jb) {
db.__proto__ = cdb.jb;
db.close = dbctl.close;
db.status = dbctl.status;
db.find = function () {
var ret = cdb.jb.find.apply(cdb.jb, arguments);
if (typeof ret === "object") {
if (!quiet) {
println("Found " + ret.length + " records");
}
for (var i = 0; ret.next() && i < maxInspectRows; ++i) {
println(repl.writer(ret.object()));
}
ret.reset();
if (ret.length > maxInspectRows) {
if (!quiet) {
println("Shown only first " + maxInspectRows);
}
}
if (!quiet) {
println("\nReturned cursor:");
}
}
// rebind collection controlles if need
if (!db[arguments[0]]) {
bindColCtls(db);
}
return ret;
};
Object.defineProperty(db.find, "_help_", {value : EJDB.prototype.find._help_});
// db - db controller
// mname - method name
// frc - force rebind collections controllers. if <code>false</code> db meta will be reloaded only if method executes on unknown collection.
// argl - arguments count (for register callback as last argument)
var dbbind = function(db, mname, frc) {
return function() {
var cname = arguments[0];
var args = [cname];
// copy all arguments except first and last
for(var i = 1; i < arguments.length - 1; ++i) {
args.push(arguments[i]);
}
// creating callback with collection rebuilding
var ccb = function(rcb) {
return function() {
if (frc || !db[cname]) {
bindColCtls(db);
}
if (rcb) {
rcb.apply(this, arguments);
}
}
};
if (arguments.length > 1) {
if (typeof arguments[arguments.length - 1] === 'function') {
// wrap existing callback
args.push(ccb(arguments[arguments.length - 1]));
} else {
// adding registering callback after last argument
args.push(arguments[arguments.length - 1]);
args.push(ccb());
}
} else {
args.push(ccb());
}
return cdb.jb[mname].apply(cdb.jb, args);
}
};
var rbmnames, j;
// reload collections statuses for some methods (rebind collection controllers if need, force)
rbmnames = ["ensureCollection", "dropCollection"];
for (j = 0; j < rbmnames.length; ++j) {
db[rbmnames[j]] = dbbind(db, rbmnames[j], true);
Object.defineProperty(db[rbmnames[j]], "_help_", {value : EJDB.prototype[rbmnames[j]]._help_});
}
// reload collections statuses for some methods (rebind collection controllers if need, non force)
rbmnames = ["save", "update"];
for (j = 0; j < rbmnames.length; ++j) {
db[rbmnames[j]] = dbbind(db, rbmnames[j]);
Object.defineProperty(db[rbmnames[j]], "_help_", {value : EJDB.prototype[rbmnames[j]]._help_});
}
// bind collection controllers for all known collections
bindColCtls(db, true);
} else {
db.__proto__ = dbctl;
}
repl.context.db = db;
repl.context.EJDB = EJDB;
}
function println(msg) {
repl.outputStream.write(msg + "\n");
}
function error(msg) {
return "ERROR: " + msg;
}
syncdbctx();
if (cmd) {
repl.rli.write(cmd + "\n");
}