2010-11-03 13:27:11 +08:00
|
|
|
var EventEmitter = require('events').EventEmitter;
|
2010-11-04 05:47:26 +08:00
|
|
|
var sys = require('sys');var sys = require('sys');
|
2011-01-19 12:39:07 +08:00
|
|
|
var Result = require(__dirname + "/result");
|
2010-11-03 13:27:11 +08:00
|
|
|
|
|
|
|
var Query = function(config) {
|
|
|
|
this.text = config.text;
|
|
|
|
this.values = config.values;
|
|
|
|
this.rows = config.rows;
|
|
|
|
this.types = config.types;
|
|
|
|
this.name = config.name;
|
|
|
|
//for code clarity purposes we'll declare this here though it's not
|
|
|
|
//set or used until a rowDescription message comes in
|
|
|
|
this.rowDescription = null;
|
2010-11-15 14:10:21 +08:00
|
|
|
this.callback = config.callback;
|
2010-11-03 13:27:11 +08:00
|
|
|
EventEmitter.call(this);
|
|
|
|
};
|
|
|
|
|
|
|
|
sys.inherits(Query, EventEmitter);
|
|
|
|
var p = Query.prototype;
|
|
|
|
|
|
|
|
p.requiresPreparation = function() {
|
|
|
|
return (this.values || 0).length > 0 || this.name || this.rows;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
var noParse = function(val) {
|
|
|
|
return val;
|
|
|
|
};
|
|
|
|
|
2011-01-24 09:01:28 +08:00
|
|
|
//creates datarow metatdata from the supplied
|
|
|
|
//data row information
|
|
|
|
var buildDataRowMetadata = function(msg, converters, names) {
|
|
|
|
var len = msg.fields.length;
|
|
|
|
for(var i = 0; i < len; i++) {
|
|
|
|
var field = msg.fields[i];
|
|
|
|
var dataTypeId = field.dataTypeID;
|
|
|
|
names[i] = field.name;
|
|
|
|
switch(dataTypeId) {
|
|
|
|
case 20:
|
2011-01-29 08:19:33 +08:00
|
|
|
converters[i] = parseBinaryInt64;
|
|
|
|
break;
|
2011-01-24 09:01:28 +08:00
|
|
|
case 21:
|
2011-01-29 08:19:33 +08:00
|
|
|
converters[i] = parseBinaryInt16;
|
|
|
|
break;
|
2011-01-24 09:01:28 +08:00
|
|
|
case 23:
|
2011-01-29 08:19:33 +08:00
|
|
|
converters[i] = parseBinaryInt32;
|
|
|
|
break;
|
2011-01-24 09:01:28 +08:00
|
|
|
case 26:
|
2011-01-29 08:19:33 +08:00
|
|
|
converters[i] = parseBinaryInt64;
|
2011-01-24 09:01:28 +08:00
|
|
|
break;
|
|
|
|
case 1700:
|
|
|
|
case 700:
|
2011-01-29 08:19:33 +08:00
|
|
|
converters[i] = parseBinaryFloat32;
|
2011-01-24 09:01:28 +08:00
|
|
|
case 701:
|
2011-01-29 08:19:33 +08:00
|
|
|
converters[i] = parseBinaryFloat64;
|
2011-01-24 09:01:28 +08:00
|
|
|
break;
|
|
|
|
case 16:
|
|
|
|
converters[i] = function(val) {
|
2011-01-29 08:19:33 +08:00
|
|
|
return val == 1;
|
2011-01-24 09:01:28 +08:00
|
|
|
};
|
|
|
|
break;
|
|
|
|
case 1114:
|
|
|
|
case 1184:
|
2011-01-29 08:19:33 +08:00
|
|
|
converters[i] = parseDate;
|
|
|
|
break;
|
|
|
|
case 1007:
|
|
|
|
case 1008:
|
|
|
|
converters[i] = arrayParser,
|
2011-01-24 09:01:28 +08:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
converters[i] = dataTypeParsers[dataTypeId] || noParse;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2010-11-03 13:27:11 +08:00
|
|
|
p.submit = function(connection) {
|
|
|
|
var self = this;
|
|
|
|
if(this.requiresPreparation()) {
|
|
|
|
this.prepare(connection);
|
|
|
|
} else {
|
|
|
|
connection.query(this.text);
|
|
|
|
}
|
2011-01-24 09:01:28 +08:00
|
|
|
|
2010-11-04 13:21:29 +08:00
|
|
|
var converters = [];
|
|
|
|
var names = [];
|
2010-11-03 13:27:11 +08:00
|
|
|
var handleRowDescription = function(msg) {
|
2011-01-24 09:01:28 +08:00
|
|
|
buildDataRowMetadata(msg, converters, names);
|
2010-11-03 13:27:11 +08:00
|
|
|
};
|
2011-01-24 09:01:28 +08:00
|
|
|
|
|
|
|
var result = new Result();
|
|
|
|
|
2010-11-03 13:27:11 +08:00
|
|
|
var handleDatarow = function(msg) {
|
2011-01-19 13:03:24 +08:00
|
|
|
var row = {};
|
2010-11-03 13:27:11 +08:00
|
|
|
for(var i = 0; i < msg.fields.length; i++) {
|
2010-11-04 12:06:07 +08:00
|
|
|
var rawValue = msg.fields[i];
|
2011-01-19 13:03:24 +08:00
|
|
|
row[names[i]] = rawValue === null ? null : converters[i](rawValue);
|
2010-11-03 13:27:11 +08:00
|
|
|
}
|
2011-01-19 13:03:24 +08:00
|
|
|
self.emit('row', row);
|
2010-11-15 14:42:38 +08:00
|
|
|
|
2011-01-19 13:03:24 +08:00
|
|
|
//if there is a callback collect rows
|
2010-11-15 14:10:21 +08:00
|
|
|
if(self.callback) {
|
2011-01-19 13:03:24 +08:00
|
|
|
result.addRow(row);
|
2010-11-15 14:10:21 +08:00
|
|
|
}
|
2010-11-03 13:27:11 +08:00
|
|
|
};
|
2010-11-15 06:50:38 +08:00
|
|
|
|
2011-01-19 13:03:24 +08:00
|
|
|
var onCommandComplete = function(msg) {
|
|
|
|
result.addCommandComplete(msg);
|
|
|
|
};
|
2010-12-03 07:47:54 +08:00
|
|
|
|
2010-11-15 06:50:38 +08:00
|
|
|
var onError = function(err) {
|
2010-11-03 13:27:11 +08:00
|
|
|
//remove all listeners
|
2011-01-24 09:01:28 +08:00
|
|
|
removeListeners();
|
2010-11-15 14:10:21 +08:00
|
|
|
if(self.callback) {
|
|
|
|
self.callback(err);
|
2010-12-17 13:50:36 +08:00
|
|
|
} else {
|
|
|
|
self.emit('error', err);
|
2010-11-15 14:10:21 +08:00
|
|
|
}
|
2010-11-15 06:50:38 +08:00
|
|
|
self.emit('end');
|
|
|
|
};
|
|
|
|
|
|
|
|
var onReadyForQuery = function() {
|
2011-01-24 09:01:28 +08:00
|
|
|
removeListeners();
|
|
|
|
if(self.callback) {
|
|
|
|
self.callback(null, result);
|
|
|
|
}
|
|
|
|
self.emit('end', result);
|
|
|
|
};
|
|
|
|
|
|
|
|
var removeListeners = function() {
|
2010-11-15 07:53:49 +08:00
|
|
|
//remove all listeners
|
2010-11-03 13:27:11 +08:00
|
|
|
connection.removeListener('rowDescription', handleRowDescription);
|
|
|
|
connection.removeListener('dataRow', handleDatarow);
|
2010-11-15 06:50:38 +08:00
|
|
|
connection.removeListener('readyForQuery', onReadyForQuery);
|
2011-01-19 13:03:24 +08:00
|
|
|
connection.removeListener('commandComplete', onCommandComplete);
|
2010-11-15 06:50:38 +08:00
|
|
|
connection.removeListener('error', onError);
|
|
|
|
};
|
|
|
|
|
|
|
|
connection.on('rowDescription', handleRowDescription);
|
|
|
|
connection.on('dataRow', handleDatarow);
|
|
|
|
connection.on('readyForQuery', onReadyForQuery);
|
2011-01-19 13:03:24 +08:00
|
|
|
connection.on('commandComplete', onCommandComplete);
|
2010-11-15 06:50:38 +08:00
|
|
|
connection.on('error', onError);
|
2010-11-03 13:27:11 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
p.hasBeenParsed = function(connection) {
|
|
|
|
return this.name && connection.parsedStatements[this.name];
|
|
|
|
};
|
|
|
|
|
|
|
|
p.prepare = function(connection) {
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
if(!this.hasBeenParsed(connection)) {
|
|
|
|
connection.parse({
|
|
|
|
text: self.text,
|
|
|
|
name: self.name,
|
|
|
|
types: self.types
|
|
|
|
});
|
2011-01-24 09:01:28 +08:00
|
|
|
connection.parsedStatements[this.name] = true;
|
2010-11-03 13:27:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
//TODO is there some btter way to prepare values for the database?
|
|
|
|
if(self.values) {
|
|
|
|
self.values = self.values.map(function(val) {
|
|
|
|
return (val instanceof Date) ? JSON.stringify(val) : val;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
//http://developer.postgresql.org/pgdocs/postgres/protocol-flow.html#PROTOCOL-FLOW-EXT-QUERY
|
|
|
|
connection.bind({
|
|
|
|
portal: self.name,
|
|
|
|
statement: self.name,
|
|
|
|
values: self.values
|
|
|
|
});
|
|
|
|
|
|
|
|
connection.describe({
|
|
|
|
type: 'P',
|
|
|
|
name: self.name || ""
|
|
|
|
});
|
|
|
|
|
2010-11-15 07:53:49 +08:00
|
|
|
var getRows = function() {
|
|
|
|
connection.execute({
|
|
|
|
portal: self.name,
|
|
|
|
rows: self.rows
|
|
|
|
});
|
|
|
|
connection.flush();
|
|
|
|
};
|
2010-11-03 13:27:11 +08:00
|
|
|
|
2010-11-15 07:53:49 +08:00
|
|
|
getRows();
|
2010-11-03 13:27:11 +08:00
|
|
|
|
|
|
|
var onCommandComplete = function() {
|
2010-11-15 07:53:49 +08:00
|
|
|
connection.removeListener('error', onCommandComplete);
|
2010-11-15 06:50:38 +08:00
|
|
|
connection.removeListener('commandComplete', onCommandComplete);
|
2010-11-15 07:53:49 +08:00
|
|
|
connection.removeListener('portalSuspended', getRows);
|
2010-11-15 06:50:38 +08:00
|
|
|
connection.sync();
|
|
|
|
};
|
|
|
|
|
2010-11-15 07:53:49 +08:00
|
|
|
connection.on('portalSuspended', getRows);
|
2010-11-03 13:27:11 +08:00
|
|
|
|
2010-11-15 06:50:38 +08:00
|
|
|
connection.on('commandComplete', onCommandComplete);
|
2010-11-15 07:53:49 +08:00
|
|
|
connection.on('error', onCommandComplete);
|
2010-11-03 13:27:11 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
var dateParser = function(isoDate) {
|
2011-01-24 13:58:03 +08:00
|
|
|
//TODO this could do w/ a refactor
|
|
|
|
|
|
|
|
var dateMatcher = /(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})(\.\d{1,})?/;
|
|
|
|
|
|
|
|
var match = dateMatcher.exec(isoDate);
|
2010-11-03 13:27:11 +08:00
|
|
|
var year = match[1];
|
2010-12-11 19:03:29 +08:00
|
|
|
var month = parseInt(match[2],10)-1;
|
2010-11-03 13:27:11 +08:00
|
|
|
var day = match[3];
|
2011-01-24 13:58:03 +08:00
|
|
|
var hour = parseInt(match[4],10);
|
|
|
|
var min = parseInt(match[5],10);
|
|
|
|
var seconds = parseInt(match[6], 10);
|
|
|
|
|
|
|
|
var miliString = match[7];
|
|
|
|
var mili = 0;
|
|
|
|
if(miliString) {
|
|
|
|
mili = 1000 * parseFloat(miliString);
|
|
|
|
}
|
2010-11-03 13:27:11 +08:00
|
|
|
|
2011-01-24 13:58:03 +08:00
|
|
|
var tZone = /([Z|+\-])(\d{2})?(\d{2})?/.exec(isoDate.split(' ')[1]);
|
2010-11-03 13:27:11 +08:00
|
|
|
//minutes to adjust for timezone
|
|
|
|
var tzAdjust = 0;
|
2011-01-24 13:58:03 +08:00
|
|
|
|
2010-11-03 13:27:11 +08:00
|
|
|
if(tZone) {
|
|
|
|
var type = tZone[1];
|
|
|
|
switch(type) {
|
|
|
|
case 'Z': break;
|
|
|
|
case '-':
|
2010-12-11 19:03:29 +08:00
|
|
|
tzAdjust = -(((parseInt(tZone[2],10)*60)+(parseInt(tZone[3]||0,10))));
|
2010-11-03 13:27:11 +08:00
|
|
|
break;
|
|
|
|
case '+':
|
2010-12-11 19:03:29 +08:00
|
|
|
tzAdjust = (((parseInt(tZone[2],10)*60)+(parseInt(tZone[3]||0,10))));
|
2010-11-03 13:27:11 +08:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new Error("Unidentifed tZone part " + type);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var utcOffset = Date.UTC(year, month, day, hour, min, seconds, mili);
|
|
|
|
|
|
|
|
var date = new Date(utcOffset - (tzAdjust * 60* 1000));
|
|
|
|
return date;
|
|
|
|
};
|
|
|
|
|
2011-01-27 22:10:45 +08:00
|
|
|
function shl(a,b) {
|
|
|
|
// Copyright (c) 1996 Henri Torgemane. All Rights Reserved.
|
|
|
|
// fix for crappy <<
|
|
|
|
for (var i=0;i<b;i++) {
|
|
|
|
a=a%0x80000000;
|
|
|
|
if (a&0x40000000==0x40000000)
|
|
|
|
{
|
|
|
|
a-=0x40000000;
|
|
|
|
a*=2;
|
|
|
|
a+=0x80000000;
|
|
|
|
} else
|
|
|
|
a*=2;
|
|
|
|
};
|
|
|
|
|
|
|
|
return a;
|
|
|
|
}
|
|
|
|
|
|
|
|
var parseFloat = function(data, precisionBits, exponentBits) {
|
|
|
|
var bias = Math.pow(2, exponentBits - 1) - 1;
|
|
|
|
var sign = parseBits(data, 1);
|
|
|
|
var exponent = parseBits(data, exponentBits, 1);
|
|
|
|
|
|
|
|
if (exponent == 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
// parse mantissa
|
|
|
|
var precisionBitsCounter = 1;
|
|
|
|
var parsePrecisionBits = function(lastValue, newValue, bits) {
|
|
|
|
if (lastValue == 0) {
|
|
|
|
lastValue = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (var i = 1; i <= bits; i++) {
|
|
|
|
precisionBitsCounter /= 2;
|
|
|
|
if ((newValue & (0x1 << (bits - i))) > 0) {
|
|
|
|
lastValue += precisionBitsCounter;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return lastValue;
|
|
|
|
};
|
|
|
|
|
|
|
|
var mantissa = parseBits(data, precisionBits, exponentBits + 1, parsePrecisionBits);
|
|
|
|
|
|
|
|
// special cases
|
|
|
|
if (exponent == (Math.pow(2, exponentBits + 1) - 1)) {
|
|
|
|
if (mantissa == 0) {
|
|
|
|
return (sign == 0) ? Infinity : -Infinity;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NaN;
|
|
|
|
}
|
|
|
|
|
|
|
|
// normale number
|
|
|
|
return ((sign == 0) ? 1 : -1) * Math.pow(2, exponent - bias) * mantissa;
|
|
|
|
};
|
|
|
|
|
|
|
|
var parseBits = function(data, bits, offset, callback) {
|
|
|
|
offset = offset || 0;
|
|
|
|
callback = callback || function(lastValue, newValue, bits) { return (lastValue * Math.pow(2, bits)) + newValue; };
|
|
|
|
var offsetBytes = offset >> 3;
|
|
|
|
|
|
|
|
// read first (maybe partial) byte
|
|
|
|
var mask = 0xff;
|
|
|
|
var firstBits = 8 - (offset % 8);
|
|
|
|
if (bits < firstBits) {
|
|
|
|
mask = (0xff << (8 - bits)) & 0xff;
|
|
|
|
firstBits = bits;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (offset) {
|
|
|
|
mask = mask >> (offset % 8);
|
|
|
|
}
|
|
|
|
var result = callback(0, data[offsetBytes] & mask, firstBits);
|
|
|
|
|
|
|
|
// read bytes
|
|
|
|
var bytes = (bits + offset) >> 3;
|
|
|
|
for (var i = offsetBytes + 1; i < bytes; i++) {
|
|
|
|
result = callback(result, data[i], 8);
|
|
|
|
}
|
|
|
|
|
|
|
|
// bits to read, that are not a complete byte
|
|
|
|
var lastBits = (bits + offset) % 8;
|
|
|
|
if (lastBits > 0) {
|
|
|
|
result = callback(result, data[bytes] >> (8 - lastBits), lastBits);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
var parseBinaryInt64 = function(value) {
|
|
|
|
return parseBits(value, 64);
|
|
|
|
}
|
|
|
|
|
|
|
|
var parseBinaryInt32 = function(value) {
|
|
|
|
return parseBits(value, 32);
|
|
|
|
}
|
|
|
|
|
|
|
|
var parseBinaryInt16 = function(value) {
|
|
|
|
return parseBits(value, 16);
|
|
|
|
}
|
|
|
|
|
|
|
|
var parseBinaryFloat32 = function(value) {
|
|
|
|
return parseFloat(value, 23, 8);
|
|
|
|
}
|
|
|
|
|
|
|
|
var parseBinaryFloat64 = function(value) {
|
|
|
|
return parseFloat(value, 52, 11);
|
|
|
|
}
|
|
|
|
|
2011-01-28 04:11:42 +08:00
|
|
|
var parseDate = function(value) {
|
|
|
|
var sign = parseBits(value, 1);
|
|
|
|
var rawValue = parseBits(value, 63, 1);
|
|
|
|
|
|
|
|
// discard usecs and shift from 2000 to 1970
|
2011-01-29 01:16:12 +08:00
|
|
|
var result = new Date((((sign == 0) ? 1 : -1) * rawValue / 1000) + 946684800000);
|
|
|
|
|
|
|
|
// add microseconds to the date
|
|
|
|
result.usec = rawValue % 1000;
|
|
|
|
result.getMicroSeconds = function() {
|
|
|
|
return this.usec;
|
|
|
|
};
|
|
|
|
result.setMicroSeconds = function(value) {
|
|
|
|
this.usec = value;
|
|
|
|
};
|
|
|
|
result.getUTCMicroSeconds = function() {
|
|
|
|
return this.usec;
|
|
|
|
};
|
|
|
|
|
|
|
|
return result;
|
2011-01-28 04:11:42 +08:00
|
|
|
}
|
|
|
|
|
2011-01-29 01:06:05 +08:00
|
|
|
var arrayParser = function(value) {
|
|
|
|
var dim = parseBits(value, 32);
|
|
|
|
|
|
|
|
var flags = parseBits(value, 32, 32);
|
|
|
|
var elementType = parseBits(value, 32, 64);
|
|
|
|
|
|
|
|
var offset = 96;
|
|
|
|
var dims = new Array();
|
|
|
|
for (var i = 0; i < dim; i++) {
|
|
|
|
// parse dimension
|
|
|
|
dims[i] = parseBits(value, 32, offset);
|
|
|
|
offset += 32;
|
|
|
|
|
|
|
|
// ignore lower bounds
|
|
|
|
offset += 32;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
var parseElement = function(elementType) {
|
|
|
|
// parse content length
|
|
|
|
var length = parseBits(value, 32, offset);
|
|
|
|
offset += 32;
|
|
|
|
|
|
|
|
// parse null values
|
|
|
|
if (length == 0xffffffff) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (elementType == 0x17) {
|
|
|
|
// int
|
|
|
|
var result = parseBits(value, length * 8, offset);
|
|
|
|
offset += length * 8;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
else if (elementType == 0x19) {
|
|
|
|
// string
|
|
|
|
var result = value.toString('utf8', offset >> 3, (offset += (length << 3)) >> 3);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
console.log("ERROR: ElementType not implemented: " + elementType);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
var parseArray = function(dimension, elementType) {
|
|
|
|
var array = new Array();
|
|
|
|
|
|
|
|
if (dimension.length > 1) {
|
|
|
|
var count = dimension.shift();
|
|
|
|
for (var i = 0; i < count; i++) {
|
|
|
|
array[i] = parseArray(dimension, elementType);
|
|
|
|
}
|
|
|
|
dimension.unshift(count);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
for (var i = 0; i < dimension[0]; i++) {
|
|
|
|
array[i] = parseElement(elementType);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return array;
|
|
|
|
}
|
|
|
|
|
|
|
|
return parseArray(dims, elementType);
|
|
|
|
};
|
|
|
|
|
2010-12-11 19:03:29 +08:00
|
|
|
// To help we test dateParser
|
|
|
|
Query.dateParser = dateParser;
|
|
|
|
|
2010-11-03 13:27:11 +08:00
|
|
|
var dataTypeParsers = {
|
|
|
|
};
|
|
|
|
|
|
|
|
module.exports = Query;
|