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