/** ** ============================== ** O O O OOOO ** O O O O O O ** O O O O O O ** OOOO OOOO O OOO OOOO ** O O O O O O O ** O O O O O O O ** OOOO OOOO O O OOOO ** ============================== ** Dr. Stefan Bosse http://www.bsslab.de ** ** COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED ** BY THE AUTHOR(S). ** THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED, ** MODIFIED, OR OTHERWISE USED IN A CONTEXT ** OUTSIDE OF THE SOFTWARE SYSTEM. ** ** $AUTHORS: Stefan Bosse ** $INITIAL: (C) 2006-2018 bLAB ** $CREATED: 09/02/16 by sbosse. ** $VERSION: 1.6.3 ** ** $INFO: ** ** JavaScript AIOS: SQL Service API ** ** Sqlc: SQLD Database Client Module using named FIFO pipes OR built-in SQL server (see below) ** ** path_in: the write to SQLD channel (SQLD input) ** path_out: the read from SQLD channel (SQLD output) ** ** ** Sqld: SQL Database Server Module (and client interface) ** ------------------------------------------------------- ** ** Using built-in sqlite3 module accessing a database file (jx: embedded, node.js: native module) ** ** Example: ** ** db = sql('/var/db/sensors.sql',{mode:'r+'}); // or in memory ** db = sql(':memory:',{mode:'r+'}) ** ** db.init(); ** db.createTable('sensors',{name:'',value:0, unit:''}); ** db.insertTable('sensors',{name:'current',value:1.0, unit:'A'}); ** db.insertTable('sensors',{name:'voltage',value:12.1, unit:'V'}); ** db.readTable('sensors',function (res) { ** print('callback',res); ** }); ** print(db.readTable('sensors')); ** print(db.select('sensors','*')); ** print('finished') ** ** Synchronous Version (using blocking IO, readFileSync) ** ** TODO: Merge with dbS!!!!! ** Options: Sync/Async/Server ... ** ** $ENDOFINFO */ var Io = Require('com/io'); var Comp = Require('com/compat'); var Fs = Require('fs'); var current={}; var Aios=none; var NL='\n'; var options = { version:'1.6.3' } function await() { if (jxcore) jxcore.utils.jump(); } function wakeup() { if (jxcore) jxcore.utils.continue(); } function exists(path) { try { var fd=Fs.openSync(path,'r'); Fs.close(fd); return true; } catch (e) { return false } } /* Some hacks for wine/win32 node.js/nw.js */ var Buffer = require('buffer').Buffer; function tryReadSync(fd, buffer, pos, len) { var threw = true; var bytesRead; try { bytesRead = Fs.readSync(fd, buffer, pos, len); threw = false; } finally { if (threw) fs.closeSync(fd); } return bytesRead; } function readFileSync(path,encoding) { var fd = Fs.openSync(path, 'r', 666), bytesRead,buffer,pos=0, buffers=[]; do { buffer = Buffer(8192); bytesRead = tryReadSync(fd, buffer, 0, 8192); if (bytesRead !== 0) { buffers.push(buffer.slice(0, bytesRead)); } pos += bytesRead; } while (bytesRead !== 0); Fs.closeSync(fd); buffer = Buffer.concat(buffers, pos); if (encoding) buffer = buffer.toString(encoding); return buffer; } function sleep(time) { var stop = new Date().getTime(); while(new Date().getTime() < stop + time) { ; } } function writeSync(path,str) { var n=0; var fd = Fs.openSync(path,'r+'); n=Fs.writeSync(fd, str); Fs.closeSync(fd); return n; } /******************* SQLC ************************/ var sqlc = function (path,chan) { this.path=path; // Client -> Server this.input=none; // Server -> Client this.output=none; this.chan=chan; this.id='SQLC'; this.log=function (msg) { ((Aios && Aios.print)||Io.log)('[SQLC] '+msg); } this.todo=[]; } /** Return string list of comma seperated value list * + Strip string delimeters '' * */ sqlc.prototype.args = function (msg) { var args=Comp.string.split(',',msg); return Comp.array.filtermap(args, function (arg) { if (arg=='') return none; else if (Comp.string.get(arg,0)=="'") return Comp.string.trim(arg,1,1); else return arg; }); }; /** Create a numeric matrix * * typeof @header = * [] is type interface provider for all rows; * */ sqlc.prototype.createMatrix = function (matname,header,callback) { var repl, intf='', line='', sep='', self=this; if (!this.connected) return callback?callback(false):false; if (header.length==0) return false; Comp.array.iter(header,function (col,i) { intf += (sep+'c'+(i+1)+(Comp.obj.isNumber(col)?' integer':' varchar(32)')); sep=','; }); sep=''; function done(_repl) { repl=_repl[0]; if (!repl || self.err(repl)) {current.error=repl;return 0;} else return 1; } return this.seq([ ['exec','drop table if exists '+matname, done], ['exec','create table '+matname+' ('+intf+')', done] ],callback); } /** Create a generic table * * typeof @header = {} is type and name interface provider for all rows; * */ sqlc.prototype.createTable = function (tabname,header,callback) { var repl, intf='', sep='', self=this; if (!this.connected) return callback?callback(false):false; // if (header.length==0) return false; Comp.obj.iter(header,function (attr,key) { intf += (sep+key+(Comp.obj.isNumber(attr)?' integer':' varchar(32)')); sep=','; }); function done(_repl) { repl=_repl[0]; if (!repl || self.err(repl)) {current.error=repl;return 0;} else return 1; } return this.seq([ ['exec','drop table if exists '+tabname, done], ['exec','create table '+tabname+' ('+intf+')', done], ],callback); } /** Check end message * */ sqlc.prototype.end = function (msg) { return msg.indexOf('END') == 0 } /** Check error message * */ sqlc.prototype.err = function (msg) { if (!msg) return false; if (Comp.obj.isObject(msg)) return msg.errno != undefined; if (Comp.obj.isString(msg)) return msg.indexOf('ERR') == 0 || msg.indexOf('Error') == 0; return false; } /** Execute: send a request and wait for reply. */ sqlc.prototype.exec = function (cmd,callback) { var n,str='',fd; if (!this.connected) return callback?callback(none):none; n = Fs.writeSync(this.input,cmd+NL); if (n<=0) { if (callback) { callback(none); return;} else return none; } str = readFileSync(this.output,'utf8'); if (callback) callback(str?Comp.array.filter(Comp.string.split('\n',str),function(line) {return line!=''}):none); else return str?Comp.array.filter(Comp.string.split('\n',str),function(line) {return line!=''}):none; }; /** GET ROW operation * @fmt: {}|none * @callback: function () -> string [] | * [] | none * */ sqlc.prototype.get = function (fmt,callback) { var row,self=this,repl; if (!this.connected) return callback?callback(none):none; function done(_repl) { repl=_repl?_repl[0]:none; if (!repl || self.err(repl)) {current.error=repl; row=none; return 0;} else return 1; } this.seq([ ['exec','get row',function (_repl) { var cols,i,p; if (!row) row=[]; if (done(_repl) && !self.end(repl)) { repl=Comp.string.replace_all('\n','',repl); cols=Comp.string.split(',',repl); if (fmt) { i=0;row=[]; for(p in fmt) { switch (fmt[p]) { case 'string': row.push(cols[i]); break; case 'number': row.push(Number(cols[i])); break; default: row.push(cols[i]); } i++; } } else row=cols; } return 1; }] ], callback?function (res) { if (res) callback(row); else callback(none);}:wakeup); if (callback) return; else if (row) return row; else { await(); return row } } sqlc.prototype.getError = function () { var err=current.error; current.error=undefined; return current.error || 'OK'} /** Setup client-server connection. * Only the input stream is opened (used for sending data to the SQLD server). * */ sqlc.prototype.init = function (callback) { var path_in = Comp.printf.sprintf("%sI%s%d",this.path,this.chan<10?'0':'',this.chan), path_out = Comp.printf.sprintf("%sO%s%d",this.path,this.chan<10?'0':'',this.chan), stat,repl,self=this; this.id = Comp.printf.sprintf("[SQLC%s%d]",this.chan<10?'0':'',this.chan); this.log (Comp.printf.sprintf("%s Connecting to server channel %s...", this.id,path_in)); if (!exists(path_in) || !exists(path_out)) { this.log (Comp.printf.sprintf("%s Connecting to server channel %s failed: %s", this.id,path_in,'No such file(s)')); if (callback) {callback(false); return;} else return false; } try { this.input = Fs.openSync(path_in,'r+'); } catch (e) { this.log (Comp.printf.sprintf("%s Connecting to server channel %s failed: %s", this.id,path_in,e)); if (callback) {callback(false); return;} else return false; } // this.input = path_in; this.output = path_out; this.connected = true; return this.seq([ ['set','nl',function (repl) {return (repl && !self.err(repl[0]))}], ['set','csv',function (repl) {return (repl && !self.err(repl[0]))}] ], callback); } /** Insert operation * */ sqlc.prototype.insert = function (tbl,row,callback) { var repl, line='', sep='', self=this; if (!this.connected) return callback?callback(false):false; if (Comp.obj.isArray(row)) Comp.array.iter(row,function (col,i) {line += sep+(Comp.obj.isNumber(col)?int(col):"'"+col+"'"); sep=',';}); else if (Comp.obj.isObject(row)) Comp.obj.iter(row,function (attr,key) {line += sep+(Comp.obj.isNumber(attr)?int(attr):"'"+attr+"'"); sep=',';}); else throw 'sql.insert: row neither array nor object!'; function done(_repl) { repl=_repl[0]; if (!repl || self.err(repl)) {current.error=repl;return 0;} else return 1; } return this.seq([ ['exec','insert into '+tbl+' values ('+line+')', done] ], callback); } /** Insert a matrix row * */ sqlc.prototype.insertMatrix = function (matname,row,callback) { var repl, line='', sep='', self=this; if (this.connected) return callback?callback(false):false; Comp.array.iter(row,function (col,i) {line += sep+(Comp.obj.isNumber(col)?int(col):"'"+col+"'"); sep=',';}); function done(_repl) { repl=_repl[0]; if (!repl || self.err(repl)) {current.error=repl;return 0;} else return 1; } return this.seq([ ['exec','insert into '+matname+' values ('+line+')', done] ], callback); } sqlc.prototype.insertTable = sqlc.prototype.insert; /** Read a matrix; return [][] * */ sqlc.prototype.readMatrix = function (matname,callback) { var mat,repl,cols, self=this; if (!this.connected) return callback?callback(none):none; function done(_repl) { repl=_repl[0]; if (!repl || self.err(repl)) {current.error=repl; mat=none; return 0;} else return 1; } function doneSelect(_repl,_rows) { repl=_repl[0]; if (_rows) { mat=_rows; return 2 }; // Maybe we got already all rows? if (!repl || self.err(repl)) {current.error=repl; tbl=none; return 0;} else return 1; } this.seq([ ['exec','select * from '+matname,doneSelect], ['exec','get row',function (_repl) { if (done(_repl)) { if (!mat) mat=[]; if (rows) { print('done'); mat=rows; return true /* all done */ }; if (!self.end(repl)) { cols=_; if (typeof repl == 'string') { repl=Comp.string.replace_all('\n','',repl); var cols=Comp.array.map(Comp.string.split(',',repl),function (col) { return Number(col); }); } else if (Comp.obj.isArray(repl)) { // Array cols=repl; // !!! } mat.push(cols); return -1; } else return 1; } else return 0; }] ], callback?function (res) { if (res) callback(mat); else callback(none);}:wakeup); if (callback) return; else if (mat) return mat; else { await(); return mat }; } /** Read a generic table; return {}[] * */ sqlc.prototype.readTable = function (tblname,callback) { var tbl,intf=[],repl,cols, self=this; if (!this.connected) return callback?callback(none):none; function done(_repl) { repl=_repl[0]; if (!repl || self.err(repl)) {current.error=repl; tbl=none; return false;} else return true; } function doneSelect(_repl,_rows) { repl=_repl[0]; // Maybe we got already all rows? if (_rows) { tbl=_rows; return 2}; if (!repl || self.err(repl)) {current.error=repl; tbl=none; return 0;} else return 1; } this.seq([ // TODO: Check SQLC API; this is only valid for SQLD! ['get',"select * from sqlite_master where type='table' and name='"+tblname+"'",function (_repl) { var tokens; if (done(_repl)) { if (repl.sql) { tokens=repl.sql.match(/\((.+)\)/)[1].split(','); tokens.forEach(function (token) { var cols=token.split(' '); if (cols.length==2) { intf.push({tag:cols[0],type:self.sqlType2native(cols[1])}); } else intf.push(null); }); return !Comp.obj.isEmpty(intf)?1:0; } } else return 0; }], ['exec','select * from '+tblname,doneSelect], ['exec','get row',function (_repl) { var o={}; if (!tbl) tbl=[]; if (done(_repl)) { if (!self.end(repl)) { cols=_; if (typeof repl == 'string') { repl=Comp.string.replace_all('\n','',repl); repl.split(',').forEach(function (e,i) { var io=intf[i]; if (io) o[io.tag]=io.type=='number'?Number(e):e; }); } else if (Comp.obj.isArray(repl)) { // Array repl.forEach(function (e,i) { var io=intf[i]; if (io) o(io.tag)=io.type=='number'?Number(e):e; }); } tbl.push(o); return -1; } else return 1; } else return 0; }] ], callback?function (res) { if (res) callback(tbl); else callback(none);}:wakeup); if (callback) return; else if (tbl) return tbl; else { await(); return tbl } } /** SELECT operation * tbl: string * vars?: string | string [] * cond: string * callback: function () -> status string * */ sqlc.prototype.select = function (tbl,vars,cond,callback) { var self=this,repl,stat; function done(_repl) { repl=_repl?_repl[0]:none; if (!repl || self.err(repl)) {current.error=repl; return 0;} else return 1; } if (vars==undefined) vars='*'; stat = this.seq([ ['exec',Comp.printf.sprintf('select %s from %s%s', Comp.obj.isArray(vars)?Comp.printf.list(vars):vars, tbl, cond?(' '+cond):''),done] ],callback?callback:wakeup); if (!callback) await(); return stat; } /** Execute a SQL operation sequence ** todo format: [op: string, ** args?: string, ** result: function returning boolean (false: break (error), true: next, _:loop)] ** callback: function () -> status */ sqlc.prototype.seq = function (todo,callback) { var l=todo,self=this,status,res, next; if (callback) { // Async non.block. function Todo() { if (self.todo.length>0) { var f = Comp.array.head(self.todo); self.error=undefined; f(_,function () { self.todo = Comp.array.tail(self.todo); Todo(); }); } } next=function (loop,finalize) { if (l.length==0 && !loop) { callback(status); if (finalize) finalize() } else { var hd; if (!loop) { hd= Comp.array.head(l); l = Comp.array.tail(l); } else hd=loop; switch (hd[0]) { case 'set': self.set(hd[1],function (repl) { status=hd[2](repl); if (status==1) next(_,finalize); else callback(status); }); break; case 'exec': self.exec(hd[1],function (repl) { status=hd[2](repl); if (status==1) next(_,finalize); else if (status==-1) next(hd,finalize); else callback(status); }); break; } } } self.todo.push(next); if (self.todo.length==1) Todo(); return; } else { // Sync block. next=function (loop) { if (l.length==0 && !loop) return status; else { if (!loop) { hd= Comp.array.head(l); l = Comp.array.tail(l); } else hd=loop; switch (hd[0]) { case 'set': status=self.set(hd[1]); if (status==1) next(); else return status; break; case 'exec': res=self.exec(hd[1]); status=hd[2](res); if (status==1) next(); else if (status==-1) next(hd); else return status; break; } } } return next(); } }; /** Set a SQLD flag (nl,csv,..). * */ sqlc.prototype.set = function (flag,callback) { var n,fd,str=''; if (!this.connected) return callback?callback(false):false; n=Fs.writeSync(this.input, 'set '+flag); if (n<=0) { if (callback) { callback(none); return;} else return none; } str = readFileSync(this.output,'utf8'); if (callback) callback(str?Comp.array.filter(Comp.string.split('\n',str),function(line) {return line!=''}):none); else return str?Comp.array.filter(Comp.string.split('\n',str),function(line) {return line!=''}):none; }; sqlc.prototype.sqlType2native = function (str) { if (str=='integer') return 'number'; if (str.indexOf('varchar')==0) return 'string'; } /** Write a matrix [][] (create + insert values) * */ sqlc.prototype.writeMatrix = function (matname,matrix,callback) { var repl, line='', self=this, intf='', sep='', i=0, row; if (!this.connected) return callback?callback(false):false; if (matrix.length==0) return false; Comp.array.iter(matrix[0],function (col,i) { intf += sep+'c'+(i+1)+(Comp.obj.isNumber(col)?' integer':' varchar(32)'); sep=','; }); row=matrix[0]; Comp.array.iter(row,function (col,i) { line += sep+(Comp.obj.isNumber(col)?int(col):"'"+col+"'"); sep=','; }); function done(_repl) { repl=_repl[0]; if (!repl || self.err(repl)) {current.error=repl;return 0;} else return 1; } seq=[ ['exec','drop table if exists '+matname,done], ['exec','create table '+matname+' ('+intf+')',done] ]; for(i=0;i status */ sqld.prototype.seq = function (todo,callback) { var l=todo,self=this,status,res,row,rows,cols, next; if (callback) { // Async non.block. function Todo() { if (self.todo.length>0) { var f = Comp.array.head(self.todo); f(_,function () { self.todo = Comp.array.tail(self.todo); Todo(); }); } } next=function (loop,finalize) { var p; if (l.length==0 && !loop) { callback(status); if (finalize) finalize()} else { var hd; if (!loop) { hd= l.shift(); } else hd=loop; switch (hd[0]) { case 'exec': if (hd[1]=='get row') { if (rows && rows.length) { row=rows.shift(); cols=[]; for(p in row) cols.push(row[p]); status=hd[2]([cols.join(',')]); } else status=hd[2](['END']); if (status==1) next(_,finalize); else if (status==-1) next(hd,finalize); else callback(status); } else if (hd[1].indexOf('select') == 0) { self.db.all(hd[1],[],function (repl,_rows) { rows=_rows; if (repl!=null && !Comp.obj.isArray(repl)) repl=[repl]; if (repl==null) repl=['OK']; status=hd[2](repl,_rows); if (status==1) next(_,finalize); else if (status==-1) next(hd,finalize); else if (status==2) { l=[]; next(_,finalize) } else callback(status); }); } else { self.db.run(hd[1],[],function (repl) { rows=_rows; if (repl!=null && !Comp.obj.isArray(repl)) repl=[repl]; if (repl==null) repl=['OK']; status=hd[2](repl); if (status==1) next(_,finalize); else if (status==-1) next(hd,finalize); else if (status==2) { l=[]; next(_,finalize) } else callback(status); }); } break; case 'get': self.db.get(hd[1], function (err, table) { var repl; if (err) repl=[err]; else repl=[table]; status=hd[2](repl); if (status==1) next(_,finalize); else if (status==-1) next(hd,finalize); else callback(status); }); break; } } } self.todo.push(next); if (self.todo.length==1) Todo(); return; } else { // Sync block., limited usability next=function (loop) { if (l.length==0 && !loop) return status; else { if (!loop) { hd=l.shift(); } else hd=loop; switch (hd[0]) { case 'exec': // w/o callback we get no status immediately! res='OK'; self.db.run(hd[1], function (err) { if (err) self.log(err) }); status=hd[2](res); if (status==1) return next(); else if (status==-1) return next(hd); else if (status==2) { l=[]; next() } else return status; break; } } } return next(); } }; /** SELECT operation returning rows (implicit GET ROW)!!!! * tbl: string * vars?: string | string [] * cond: string * callback: function () -> status string * */ sqld.prototype.select = function (tbl,vars,cond,callback) { var self=this,repl,stat,rows; function done(_repl,_rows) { repl=_repl?_repl[0]:none; rows=_rows; if (!repl || self.err(repl)) {current.error=repl; return 0;} else return 1; } if (vars==undefined) vars='*'; stat = this.seq([ ['exec',Comp.printf.sprintf('select %s from %s%s', Comp.obj.isArray(vars)?Comp.printf.list(vars):vars, tbl, cond?(' '+cond):''),done] ],callback?callback:wakeup); if (!callback) await(); return rows||stat; } sqld.prototype.sqlType2native = sqlc.prototype.sqlType2native; /** Write a matrix [][] (create + insert values) * */ sqld.prototype.writeMatrix = sqlc.prototype.writeMatrix; var Sqld = function (file,options) { var obj = new sqld(file,options); return obj; }; module.exports = { current:function (module) { current=module.current; Aios=module; }, Sqlc:Sqlc, Sqld:Sqld };