Changeset: dcae411fd381 for MonetDB URL: http://dev.monetdb.org/hg/MonetDB?cmd=changeset;node=dcae411fd381 Added Files: clients/nodejs/Tests/nodetest.js clients/nodejs/monetdb/README clients/nodejs/monetdb/mapiclient.js clients/nodejs/monetdb/package.json Modified Files: clients/R/MonetDB.R/R/mapi.R sql/backends/monet5/sql_scenario.c Branch: default Log Message:
Merge with Oct2014 branch. diffs (truncated from 529 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/Tests/nodetest.js b/clients/nodejs/Tests/nodetest.js new file mode 100644 --- /dev/null +++ b/clients/nodejs/Tests/nodetest.js @@ -0,0 +1,78 @@ +var monetdb = require('../monetdb'); +var assert = require('assert'); + +var dbport = parseInt(process.argv[2]); +var dbname = process.argv[3]; + +/* lets first check some failing connection attempts */ +monetdb.connect({host:'veryinvalidhostnamethathopefullyresolvesnowhere'}, function(resp) { + assert.equal(false,resp.success); + assert(resp.message.trim().length > 0); +}); + +monetdb.connect({dbname:'nonexist', port:dbport}, function(resp) { + assert.equal(false,resp.success); + assert(resp.message.trim().length > 0); +}); + +monetdb.connect({dbname:dbname, user:'nonexist', port:dbport}, function(resp) { + assert.equal(false,resp.success); + assert(resp.message.trim().length > 0); +}); + +/* now actually connect */ +var conn = monetdb.connect({dbname:dbname, port:dbport}, function(resp) { + assert.equal(true,resp.success); +}); + + +/* some querying */ +conn.query('start transaction'); + +conn.query('create table foo(a int, b float, c clob)'); +conn.query("insert into foo values (42,4.2,'42'),(43,4.3,'43'),(44,4.4,'44'),(45,4.5,'45')"); + +conn.query('select * from foo', function(res) { + assert.equal(true, res.success); + + assert.equal('table', res.type); + assert.equal(4, res.rows); + assert.equal(3, res.cols); + + assert.equal(3, res.structure.length); + + assert.equal('a', res.structure[0].column); + assert.equal('int', res.structure[0].type); + + assert.equal(4, res.data.length); + + assert.equal(42, res.data[0][res.structure[0].index]); + assert.equal(4.3, res.data[1][1]); + assert.equal('44', res.data[2][2]); +}); + +conn.query('delete from foo; drop table foo; rollback'); + +/* query that will force multi-block operations */ +function rep(str,n) { + ret = ''; + for (var i = 0; i< n; i++) { + ret += str; + } + return ret; +} +var longstr = rep('ABCDEFGHIJKLMNOP',10000); + +conn.query("SELECT '"+longstr+"'", function(res) { + assert.equal(true, res.success); + assert.equal(longstr,res.data[0][0]); + +}); + +/* failing query */ +conn.query('MEHR BIER', function(res) { + assert.equal(false,res.success); + assert(res.message.trim().length > 0); +}); + +conn.close(); \ No newline at end of file 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(response) { + if (response.success) console.log('connected'); +}); + +conn.request('SELECT 1', function(response) { + console.log(response); +}); \ 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,356 @@ +var net = require('net'); +var crypto = require('crypto'); + +function MonetDBConnection(options, conncallback) { + this.state = 'new'; + this.options = options; + this.read_leftover = 0; + this.read_final = false; + this.read_str = ''; + this.read_callback = undefined; + this.conn_callback = conncallback; + 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) { + if (conncallback != undefined) + conncallback({'success':false, 'message':x.toString()}); + }); + /* 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) { + if (this.conn_callback != undefined) + this.conn_callback({'success':true, 'message':'ok'}); + }); +} + +MonetDBConnection.prototype.request = +MonetDBConnection.prototype.query = function(message, callback, raw) { + if (!raw) { + message = 's'+message+';'; + } + this.queryqueue.push({'message' : message , 'callback' : callback}) +} + + +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') { + /* error message during authentication? */ + if (message.charAt(0) == '!') { + message = 'Error: '+message.substring(1,message.length-1); + if (this.conn_callback != undefined) + this.conn_callback({'success':false, 'message':message}); + return; + } + + // 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.message = message.substring(1,message.length-1); + } + + /* query result */ + if (message.charAt(0) == '&') { + response = _parseresponse(message); + response.success = true; + response.message = 'ok'; + } + + if (this.read_callback != undefined) { + this.read_callback(response); + this.read_callback = undefined; + } + this.nextOp(); +} + + +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); + } + +}; + _______________________________________________ checkin-list mailing list checkin-list@monetdb.org https://www.monetdb.org/mailman/listinfo/checkin-list