UNPKG

jsmdb

Version:

JSMumps Database Component

826 lines (627 loc) 20.5 kB
/* * JSMumps Database API * * jsmdb.js: implementation of the API * * * Copyright (C) 2017, 2021 Coherent Logic Development LLC * * Author: John P. Willis <jpw@coherent-logic.com> * */ const logger = require('jsmlog'); const worker = require('./worker'); const nodem = require('nodem'); const fs = require('fs'); const readline = require('readline'); const util = require('util'); const SynQueue = require('./synqueue'); function JSMDB(opts) { var self = this; if (!(this instanceof JSMDB)) return new JSMDB(); this.locals = {}; this.exitNode = process.exit; process.exit = this.shutdown.bind(this); if(opts) { var workerCount = opts.workerCount || 1; this.logLevel = opts.logLevel || 3; } else { var workerCount = 1; this.logLevel = 3; var opts = {}; } this.logger = new logger.Logger({ logLevel: this.logLevel, moduleName: "jsmdb (parent, pid " + process.pid + ")" }); this.logger.debug("logLevel is " + this.logLevel); this.logger.debug("workerCount is " + workerCount); this.db = new nodem.Gtm(); var result = this.db.open(opts.nodemOptions || {}); if(!result.ok) { this.logger.error("error opening nodem"); } else { this.logger.debug("opened M database with pid " + result.pid); } this.workers = []; for(i = 0; i < workerCount; i++) { this.workers.push(new worker.Worker(this)); } this.logger.debug("workers.length is " + this.workers.length); process.on('SIGINT', (code) => { this.logger.info("user initiated parent process shutdown (SIGINT) with code", code); self.shutdown(); }); return this; } JSMDB.prototype.reconfigureNodem = function(nodemOptions) { this.db.configure(nodemOptions || {}); }; JSMDB.prototype.updateLocalSymbols = function (s) { this.locals = s; }; JSMDB.prototype.shutdown = function (exitCode) { var self = this; this.logger.info("closing master process database connection"); var result = this.db.close({resetTerminal: true}); if(result) { this.logger.error("error closing master process database connection"); } this.logger.info("sending CP_SHUTDOWN to " + this.workers.length + " worker processes"); for(i = 0; i < this.workers.length; i++) { try { this.workers[i].dispatch('CP_SHUTDOWN'); } catch (ex) { // do nothing } } setTimeout(() => { this.logger.info("sending SIGTERM to all remaining worker processes"); for(i = 0; i < this.workers.length; i++) { this.workers[i].worker.kill(); } this.logger.info("exiting"); self.exitNode(exitCode); }, 5000); }; JSMDB.prototype.nextFreeWorker = function () { for(var index in this.workers) { if(this.workers[index].free()) { this.logger.debug("worker process index " + index + " is free"); return this.workers[index].reserve(); } } this.logger.debug("no free workers found; creating a new worker"); // no free worker found... create a new one this.workers.push(new worker.Worker(this)); return this.workers[this.workers.length - 1]; }; JSMDB.prototype.dispatch = function (action, opts, callback) { var w = this.nextFreeWorker(); this.logger.debug("dispatching " + action + " to pid " + w.worker.pid); return w.dispatch(action, opts, callback); }; JSMDB.prototype.getObject = function(global, subscripts, callback) { this.dispatch('getObject', {global: global, subscripts: subscripts}, callback); }; JSMDB.prototype.getObjectSync = function(global, subscripts) { throw new Error("API not yet implemented"); }; JSMDB.prototype.setObject = function(global, subscripts, data, callback) { this.dispatch('setObject', {global: global, subscripts: subscripts, data: data}, callback); }; JSMDB.prototype.setObjectSync = function(global, subscripts, data) { throw new Error("API not yet implemented"); }; JSMDB.prototype.get = function(opts) { validateOpts(opts); if(opts.callback && !opts.childProcess) { return this.db.get(opts.global, ...(opts.subscripts || []), opts.onComplete); } else if(opts.childProcess) { if(opts.callback) { this.dispatch("get", {global: opts.global, subscripts: opts.subscripts || []}, opts.onComplete); } else { /* promise mode is default */ return new Promise((resolve, reject) => { this.dispatch("get", {global: opts.global, subscripts: opts.subscripts || []}, function (error, result) { if(error) reject(error); resolve(result.data); }); }); } } else if (opts.defer) { return opts.deferQueue.enqueue({ operation: "get", global: opts.global, subscripts: opts.subscripts || [] }); } else if (opts.synchronous) { return this.getSync(opts.global, opts.subscripts); } else { /* promise mode is default */ return new Promise((resolve, reject) => { this.db.get(opts.global, ...(opts.subscripts || []), function (error, result) { if(error) reject(error); resolve(result); }); }); } }; JSMDB.prototype.getSync = function(global, subscripts) { return this.db.get({global: global, subscripts: subscripts}); }; JSMDB.prototype.set = function(opts) { validateOpts(opts); if(opts.callback && !opts.childProcess) { return this.db.set(opts.global, ...(opts.subscripts || []), opts.data, opts.onComplete); } else if(opts.childProcess) { if(opts.callback) { this.dispatch("set", {global: opts.global, subscripts: opts.subscripts || [], data: opts.data}, opts.onComplete); } else { /* promise mode is default */ return new Promise((resolve, reject) => { this.dispatch("set", {global: opts.global, subscripts: opts.subscripts || [], data: opts.data}, function (error, result) { if(error) reject(error); resolve(result.data); }); }); } } else if (opts.defer) { return opts.deferQueue.enqueue({ operation: "set", global: opts.global, subscripts: opts.subscripts || [], data: opts.data }); } else if (opts.synchronous) { return this.setSync(opts.global, opts.subscripts, opts.data); } else { /* promise mode is default */ return new Promise((resolve, reject) => { this.db.set(opts.global, ...(opts.subscripts || []), opts.data, function (error, result) { if(error) reject(error); resolve(result); }); }); } }; JSMDB.prototype.setSync = function(global, subscripts, data) { return this.db.set({global: global, subscripts: subscripts, data: data}); }; JSMDB.prototype.merge = function(opts) { if(opts.callback && !opts.childProcess) { return this.db.merge({ to: { global: opts.to.global, subscripts: opts.to.subscripts }, from: { global: opts.from.global, subscripts: opts.from.subscripts } }, opts.onComplete); } else if(opts.childProcess) { if(opts.callback) { this.dispatch('merge', { to: { global: opts.to.global, subscripts: opts.to.subscripts }, from: { global: opts.from.global, subscripts: opts.from.subscripts } }, opts.onComplete); } else { return new Promise((resolve, reject) => { this.dispatch('merge', { to: { global: opts.to.global, subscripts: opts.to.subscripts }, from: { global: opts.from.global, subscripts: opts.from.subscripts } }, function (error, result) { if(error) reject(error); resolve(result.data); }); }); } } else if (opts.defer) { return opts.deferQueue.enqueue({ operation: "merge", from: opts.from, to: opts.to }); } else if (opts.synchronous) { return this.mergeSync(opts.from.global, opts.from.subscripts, opts.to.global, opts.to.subscripts); } else { /* promise mode is default */ return new Promise((resolve, reject) => { this.db.merge({ to: { global: opts.to.global, subscripts: opts.to.subscripts }, from: { global: opts.from.global, subscripts: opts.from.subscripts } }, function (error, result) { if(error) reject(error); resolve(result); }); }); } }; JSMDB.prototype.mergeSync = function(fromGlobal, fromSubscripts, toGlobal, toSubscripts) { return this.db.merge({ to: { global: toGlobal, subscripts: toSubscripts }, from: { global: fromGlobal, subscripts: fromSubscripts } }); }; JSMDB.prototype.kill = function(opts) { validateOpts(opts); if(opts.callback && !opts.childProcess) { return this.db.kill(opts.global, ...(opts.subscripts || []), opts.onComplete); } else if(opts.childProcess) { if(opts.callback) { this.dispatch('kill', {global: opts.global, subscripts: opts.subscripts || []}, opts.onComplete); } else { /* promise mode is default */ return new Promise((resolve, reject) => { this.dispatch('kill', {global: opts.global, subscripts: opts.subscripts || []}, function (error, result) { if(error) reject(error); resolve(result.data); }); }); } } else if(opts.defer) { return opts.deferQueue.enqueue({ operation: "kill", global: opts.global, subscripts: opts.subscripts || [], }); } else if(opts.synchronous) { return this.killSync(opts.global, opts.subscripts || []); } else { /* promise mode is default */ return new Promise((resolve, reject) => { this.db.kill(opts.global, ...(opts.subscripts || []), function (error, result) { if(error) reject(error); resolve(result); }); }); } }; JSMDB.prototype.killSync = function(global, subscripts) { return this.db.kill({global: global, subscripts: subscripts}); }; JSMDB.prototype.lock = function(opts) { validateOpts(opts); if(opts.callback && !opts.childProcess) { return this.db.lock({global: opts.global, subscripts: opts.subscripts || [], timeout: opts.timeout}, opts.onComplete); } else if(opts.childProcess) { if(opts.callback) { this.dispatch('lock', {global: opts.global, subscripts: opts.subscripts || [], timeout: opts.timeout}, opts.onComplete); } else { /* promise mode is default */ return new Promise((resolve, reject) => { this.dispatch("lock", {global: opts.global, subscripts: opts.subscripts || [], timeout: opts.timeout}, function (error, result) { if(error) reject(error); resolve(result.data); }); }); } } else if (opts.defer) { return opts.deferQueue.enqueue({ operation: "lock", global: opts.global, subscripts: opts.subscripts || [], timeout: opts.timeout }); } else if (opts.synchronous) { return this.lockSync(opts.global, ...(opts.subscripts || []), opts.timeout); } else { /* promise mode is default */ return new Promise((resolve, reject) => { this.db.lock(opts.global, ...(opts.subscripts || []), function (error, result) { if(error) reject(error); resolve(result); }); }); } }; JSMDB.prototype.lockSync = function(global, subscripts, timeout) { return this.db.lock({global: global, subscripts: subscripts, timeout: timeout}); }; JSMDB.prototype.unlock = function(opts) { validateOpts(opts); if(opts.callback && !opts.childProcess) { return this.db.unlock(opts.global, ...(opts.subscripts || []), opts.onComplete); } else if(opts.childProcess) { if(opts.callback) { this.dispatch('unlock', {global: opts.global, subscripts: opts.subscripts || []}, opts.onComplete); } else { /* promise mode is default */ return new Promise((resolve, reject) => { this.dispatch("unlock", {global: opts.global, subscripts: opts.subscripts || []}, function (error, result) { if(error) reject(error); resolve(result.data); }); }); } } else if (opts.defer) { return opts.deferQueue.enqueue({ operation: "unlock", global: opts.global, subscripts: opts.subscripts || [], }); } else if (opts.synchronous) { return this.unlockSync(opts.global, opts.subscripts || []); } else { /* promise mode is default */ return new Promise((resolve, reject) => { this.db.unlock(opts.global, ...(opts.subscripts || []), function (error, result) { if(error) reject(error); resolve(result); }); }); } }; JSMDB.prototype.unlockSync = function(global, subscripts) { return this.db.unlock({global: global, subscripts: subscripts}); }; JSMDB.prototype.data = function(opts) { validateOpts(opts); if(opts.callback && !opts.childProcess) { return this.db.data(opts.global, ...(opts.subscripts || []), opts.onComplete); } else if(opts.childProcess) { if(opts.callback) { this.dispatch("data", {global: opts.global, subscripts: opts.subscripts || []}, opts.onComplete); } else { return new Promise((resolve, reject) => { this.dispatch("data", {global: opts.global, subscripts: opts.subscripts || []}, function (error, result) { if(error) reject(error); resolve(result.data); }); }); } } else if(opts.defer) { return opts.deferQueue.enqueue({ operation: "data", global: opts.global, subscripts: opts.subscripts || [] }); } else if(opts.synchronous) { return this.dataSync(opts.global, opts.subscripts); } else { /* promise mode is default */ return new Promise((resolve, reject) => { this.db.data(opts.global, ...(opts.subscripts || []), function (error, result) { if(error) reject(error); resolve(result); }); }); } }; JSMDB.prototype.dataSync = function(global, subscripts) { return this.db.data({global: global, subscripts: subscripts}); }; JSMDB.prototype.order = function(opts) { validateOpts(opts); if(opts.callback && !opts.childProcess) { return this.db.order(opts.global, ...(opts.subscripts || []), opts.onComplete); } else if(opts.childProcess) { if(opts.callback) { this.dispatch("order", {global: opts.global, subscripts: opts.subscripts || []}, opts.onComplete); } else { /* promise mode is default */ return new Promise((resolve, reject) => { this.dispatch("order", {global: opts.global, subscripts: opts.subscripts || []}, function (error, result) { if(error) reject(error); resolve(result.data); }); }); } } else if(opts.defer) { return opts.deferQueue.enqueue({ operation: "order", global: opts.global, subscripts: opts.subscripts || [] }); } else if (opts.synchronous) { return this.orderSync(opts.global, ...(opts.subscripts || [])); } else { /* promise mode is default */ return new Promise((resolve, reject) => { this.db.order(opts.global, ...(opts.subscripts || []), function (error, result) { if(error) reject(error); resolve(result); }); }); } }; JSMDB.prototype.orderSync = function(global, subscripts) { return this.db.order({global: global, subscripts: subscripts}); }; JSMDB.prototype.query = function(opts) { validateOpts(opts); if(opts.callback && !opts.childProcess) { return this.db.query(opts.global, ...(opts.subscripts || []), opts.onComplete); } else if(opts.childProcess) { if(opts.callback) { this.dispatch("query", {global: opts.global, subscripts: opts.subscripts || []}, opts.onComplete); } else { /* promise mode is default */ return new Promise((resolve, reject) => { this.dispatch("query", {global: opts.global, subscripts: opts.subscripts || []}, function (error, result) { if(error) reject(error); resolve(result.data); }); }); } } else if(opts.defer) { return opts.deferQueue.enqueue({ operation: "query", global: opts.global, subscripts: opts.subscripts || [] }); } else if(opts.synchronous) { return this.querySync(opts.global, opts.subscripts || []); } else { /* promise mode is default */ return new Promise((resolve, reject) => { this.db.query(opts.global, ...(opts.subscripts || []), function (error, result) { if(error) reject(error); resolve(result); }); }); } }; JSMDB.prototype.querySync = function(global, subscripts) { return this.db.query({global: global, subscripts: subscripts}); }; JSMDB.prototype.import = function(routine, async) { if(!async) var async = false; routine = routine.replace("%", "_"); var dirs = process.env.gtmroutines.split(" "); var directories = []; var num = 0; for(var i = 0; i < dirs.length; i++) { if(dirs[i] !== "" && dirs[i].match(/.*\(.*\).*/)) { directories[num] = dirs[i].split("(")[1].split(")")[0]; num++; } } var file = ""; for(var i in directories) { this.logger.debug("routine(): scanning " + directories[i] + " for " + routine); file = directories[i] + "/" + routine + ".m"; if(fs.existsSync(file)) { this.logger.debug("routine(): found routine " + routine + " at " + file); break; } } this.logger.debug("routine(): parsing " + file); var fd = fs.openSync(file, "r"); var lines = fs.readFileSync(fd, "utf-8").split("\n"); this.logger.debug("routine(): read " + lines.length + " lines from " + file); var line = ""; var methods = {}; var hasParameterList = false; var parameterList = ""; for(line in lines) { if(lines[line].match(/^[%A-Za-z0-9]+/)) { var label = lines[line].split(" ")[0]; this.logger.debug("routine(): found label " + label + " at line " + (line + 1)); if(label.indexOf("(") > -1) { hasParameterList = true; var trueLabel = label.split("(")[0]; parameterList = label.split("(")[1].split(")")[0]; } else { hasParameterList = false; var trueLabel = label; } if(trueLabel.charAt(trueLabel.length - 1) === ":") continue; methods[trueLabel] = buildFunction(routine, trueLabel, this, async); } } return methods; }; function buildFunction(routine, label, instance, async) { if(async) { return function () { var args = Array.prototype.slice.call(arguments); var func = label + "^" + routine; var cb = args.pop(); instance.dispatch('function', {func: func, args: args}, cb); }; } else { return function () { var args = Array.prototype.slice.call(arguments); var func = label + "^" + routine; return instance.functionSync(func, args); }; } } function validateOpts(opts) { if(!opts) { throw new Error("Must pass an 'opts' object."); } if(typeof opts !== "object") { throw new Error("'opts' must be an object."); } } JSMDB.prototype.function = function(func, args, callback) { this.dispatch('function', {func: func, args: args}, callback); }; JSMDB.prototype.functionSync = function(func, args) { return this.db.function({function: func, arguments: args}); }; JSMDB.prototype.procedure = function(proc, args, callback) { this.dispatch('procedure', {proc: proc, args: args}, callback); }; JSMDB.prototype.procedureSync = function(proc, args) { return this.db.procedure({procedure: proc, arguments: args}); }; module.exports = JSMDB;