Changeset: 79f5ee55d327 for MonetDB
URL: http://dev.monetdb.org/hg/MonetDB?cmd=changeset;node=79f5ee55d327
Added Files:
        clients/nodejs/monetdb/README
        clients/nodejs/monetdb/mapiclient.js
        clients/nodejs/monetdb/package.json
Modified Files:
        clients/R/MonetDB.R/R/mapi.R
Branch: Oct2014
Log Message:

Node.JS connector import


diffs (truncated from 399 to 300 lines):

diff --git a/clients/R/MonetDB.R/R/mapi.R b/clients/R/MonetDB.R/R/mapi.R
--- a/clients/R/MonetDB.R/R/mapi.R
+++ b/clients/R/MonetDB.R/R/mapi.R
@@ -1,6 +1,5 @@
 # MAPI implementation for R
 
-PROTOCOL_v8 <- 8
 PROTOCOL_v9 <- 9
 MAX_PACKET_SIZE <- 8192
 
@@ -232,14 +231,12 @@ REPLY_SIZE    <- 100 # Apparently, -1 me
       # no need to check the returned values, as there is none. If we get no 
error, all is well.
       return(env)
     }
-    
   }
 }
 
 .mapiParseHeader <- function(line, stupidInverseColsRows=FALSE) {
-  tableinfo <- strsplit(line, " ", fixed=TRUE, useBytes=TRUE)
-  tableinfo <- tableinfo[[1]]
-  
+  tableinfo <- strsplit(line, " ", fixed=TRUE, useBytes=TRUE)[[1]]
+    
   id    <- as.numeric(tableinfo[2])
   if (!stupidInverseColsRows) {
     rows  <- as.numeric(tableinfo[3])
@@ -338,8 +335,7 @@ REPLY_SIZE    <- 100 # Apparently, -1 me
 .monetdbd.command <- function(passphrase, host="localhost", port=50000L, 
timeout=86400L) {
   socket <- .mapiConnect(host, port, timeout)
   .mapiAuthenticate(socket, "merovingian", "monetdb", passphrase, 
language="control")
-  .mapiWrite(socket, "#all status\n")
-  ret <- .mapiRead(socket)
+  ret <- .mapiRequest(socket, "#all status\n")
   .mapiDisconnect(socket)
   return (ret)
 }
diff --git a/clients/nodejs/monetdb/README b/clients/nodejs/monetdb/README
new file mode 100644
--- /dev/null
+++ b/clients/nodejs/monetdb/README
@@ -0,0 +1,11 @@
+This package connects node.js and MonetDB
+
+Example usage:
+
+var conn = require('monetdb').connect({'dbname':'mydb'} , function() {
+       console.log('connected');
+});
+
+conn.request('SELECT 1', function(response) {
+       console.log(response.success);
+});
\ No newline at end of file
diff --git a/clients/nodejs/monetdb/mapiclient.js 
b/clients/nodejs/monetdb/mapiclient.js
new file mode 100644
--- /dev/null
+++ b/clients/nodejs/monetdb/mapiclient.js
@@ -0,0 +1,325 @@
+var net    = require('net');
+var crypto = require('crypto');
+
+function MonetDBConnection(options, conncallback) {
+       console.log(options)
+       this.state = 'new';
+       this.options = options; 
+       this.read_leftover = 0;
+       this.read_final = false;
+       this.read_str = '';
+       this.read_callback = undefined;
+       this.mapi_blocksize = 8192;
+
+       this.queryqueue = [];
+       var thizz = this;
+       this.socket = net.connect(options.port, options.host, function() {
+               thizz.state = 'connected';
+       });
+       this.socket.on('data', function(data) {
+               thizz.handleInput(data);
+       });
+       this.socket.on('end', function() {
+               thizz.state = 'disconnected';
+       });
+       this.socket.on('error',function(x) {
+               // TODO: how should we handle this? 
+               console.log(x);
+       });
+       /* some setup */
+       this.request('Xreply_size -1', undefined, true);
+       this.request('Xauto_commit 1', undefined, true);
+       /* get server environment into connector */
+       this.request('SELECT * FROM env()', function(x) {
+               thizz.env = {};
+               x.data.forEach(function(l) { 
+                       thizz.env[l.name] = l.value;
+                });
+       });
+       this.request('SELECT 42', function(x) {
+               conncallback();
+       });
+}
+
+MonetDBConnection.prototype.request = function(message, callback, raw) {
+       if (!raw) {
+               message = 's'+message+';';
+       }
+       this.queryqueue.push({'message' : message , 'callback' : callback})
+}
+
+MonetDBConnection.prototype.nextOp = function() {
+       if (this.queryqueue.length < 1) {
+               return;
+       }
+       var op = this.queryqueue.shift();
+       this.sendMessage(op.message);
+       this.read_callback = op.callback;
+}      
+
+MonetDBConnection.prototype.handleInput = function(data) {
+       /* we need to read a header obviously */
+       if (this.read_leftover == 0) {
+               var hdr = data.readUInt16LE(0);
+               this.read_leftover = (hdr >> 1);
+               this.read_final = (hdr & 1) == 1;
+               data = data.slice(2);
+       }
+       if (this.options.debug) 
+               console.log('reading ' + this.read_leftover + ' bytes, final=' 
+ this.read_final);
+
+       /* what is in the buffer is not necessary the entire block */
+       var read_cnt = Math.min(data.length, this.read_leftover);
+       this.read_str = this.read_str + data.toString('utf8', 0, read_cnt);
+       this.read_leftover -= read_cnt;
+
+       /* if there is something left to read, we will be called again */
+       if (this.read_leftover > 0) {
+               return;
+       }
+
+       /* pass on reassembled messages */
+       if (this.read_leftover == 0 && this.read_final) {
+               this.handleMessage(this.read_str);
+               this.read_str = '';
+       }
+
+       /* also, the buffer might contain more blocks or parts thereof */
+       if (data.length > read_cnt) {
+               var leftover = new Buffer(data.length - read_cnt);
+               data.copy(leftover, 0, read_cnt, data.length);
+               this.handleInput(leftover);
+       }
+
+};
+
+
+// TODO: make blocking work here.
+MonetDBConnection.prototype.sendMessage = function(message) {
+       if (this.options.debug) 
+               console.log('TX: '+message);
+
+       var buf = new Buffer(message,'utf8');
+       var final = 0;
+       while (final == 0) {
+               var bs = Math.min(buf.length, this.mapi_blocksize - 2)
+               var sendbuf = buf.slice(0, bs);
+
+               buf = buf.slice(bs + 1);
+               if (buf.length == 0) {
+                       final = 1;
+               }
+
+               if (this.options.debug)
+                       console.log('writing ' + bs + ' bytes, final=' + final);
+
+               var hdrbuf = new Buffer(2);
+               hdrbuf.writeInt16LE((bs << 1) | final, 0);
+               this.socket.write(Buffer.concat([hdrbuf, sendbuf]));
+       }
+}
+
+/* In theory, the server tells us which hashes he likes. 
+   In practice, we know he always likes sha512 , so... */
+function sha512(str) {
+       return crypto.createHash('sha512').update(str).digest('hex');
+}
+
+MonetDBConnection.prototype.handleMessage = function(message) {
+       if (this.options.debug)
+               console.log('RX ['+this.state+']: '+message);
+
+       /* prompt, good */
+       if (message == '') {
+               this.state = 'ready';
+               this.nextOp();
+               return;
+       }
+
+       /* monetdbd redirect, ignore. We will get another challenge soon */
+       if (message.charAt(0) == '^') {
+               return;
+       }
+
+       if (this.state == 'connected') {
+               // means we get the challenge from the server
+               var authch = message.split(':');
+               var salt   = authch[0];
+               var dbname = authch[1];
+               var pwhash = sha512(sha512(this.options.password) + salt)
+               var response = 'LIT:' + this.options.user + ':{SHA512}' + 
pwhash + ':' +
+                       this.options.language + ':' + this.options.dbname + ':';
+               this.sendMessage(response);
+               return;
+       }
+
+       var response = {};
+
+       /* error message */
+       if (message.charAt(0) == '!') {
+               response.success = false;
+               response.error = message.substring(1);
+       }
+
+       /* query result */
+       if (message.charAt(0) == '&') {
+               response = _parseresponse(message);
+               response.success = true;
+       }
+
+       if (this.read_callback != undefined) {
+               this.read_callback(response);
+               this.read_callback = undefined;
+       }
+       this.nextOp();
+       
+}
+
+function _parsetuples(names, types, lines) {
+       var state = 'INCRAP';
+       var resultarr = [];
+       for (li in lines) {
+               var line = lines[li];
+               var resultline = {};
+               var tokenStart = 2;
+               var endQuote = 0;
+               var valPtr = '';
+               var cCol = 0;
+
+               /* mostly adapted from clients/R/MonetDB.R/src/mapisplit.c */
+               for (var curPos = tokenStart; curPos<line.length-1; curPos++) {
+                       var chr = line.charAt(curPos);
+                       switch (state) {
+                       case 'INCRAP':
+                               if (chr != '\t' && chr != ',') {
+                                       tokenStart = curPos;
+                                       if (chr == '"') {
+                                               state = 'INQUOTES';
+                                               tokenStart++;
+                                       } else {
+                                               state = 'INTOKEN';
+                                       }
+                               }
+                               break;
+                       case 'INTOKEN':
+                               if (chr == ',' || curPos == line.length - 2) {
+                                       var tokenLen = curPos - tokenStart - 
endQuote;
+                                       valPtr = line.substring(tokenStart, 
tokenStart + tokenLen);
+                                       if (tokenLen < 1 || valPtr == 'NULL') {
+                                               resultline[names[cCol]] = 
undefined;
+
+                                       } else {
+                                               switch(types[cCol]) {
+                                                       case 'boolean':
+                                                               
resultline[names[cCol]] = valPtr == 'true';
+                                                               break;
+                                                       case 'tinyint':
+                                                       case 'smallint':
+                                                       case 'int':
+                                                       case 'wrd':
+                                                       case 'bigint':
+                                                               
resultline[names[cCol]] = parseInt(valPtr);
+                                                               break
+                                                       case 'real':
+                                                       case 'double':
+                                                       case 'decimal':
+                                                               
resultline[names[cCol]] = parseFloat(valPtr);
+                                                               break
+                                                       default:
+                                                               
resultline[names[cCol]] = valPtr;
+                                                               break;
+                                               }
+                                       }
+                                       cCol++;
+                                       endQuote = 0;
+                                       state = 'INCRAP';
+                               }
+                               break;
+                       case 'ESCAPED':
+                               state = 'INQUOTES';
+                               break;
+                       case 'INQUOTES':
_______________________________________________
checkin-list mailing list
[email protected]
https://www.monetdb.org/mailman/listinfo/checkin-list

Reply via email to