diff --git a/lib/client.js b/lib/client.js index df3f1f1d..935c8878 100644 --- a/lib/client.js +++ b/lib/client.js @@ -2,7 +2,7 @@ var sys = require('sys'); var net = require('net'); var crypto = require('crypto'); var EventEmitter = require('events').EventEmitter; - +var Query = require(__dirname + '/query'); var utils = require(__dirname + '/utils'); var Connection = require(__dirname + '/connection'); @@ -15,10 +15,11 @@ var Client = function(config) { this.port = config.port || 5432; this.host = config.host; this.queryQueue = []; - this.connection = config.connection || new Connection({stream: config.stream || new net.Stream()}); this.queryQueue = []; this.password = config.password || ''; + + //internal references only declared here for clarity this.lastBuffer = false; this.lastOffset = 0; this.buffer = null; @@ -95,181 +96,4 @@ Client.md5 = function(string) { return crypto.createHash('md5').update(string).digest('hex'); }; -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; - EventEmitter.call(this); -}; - -sys.inherits(Query, EventEmitter); -var p = Query.prototype; - -p.requiresPreparation = function() { - return (this.values || 0).length > 0 || this.name || this.rows; -}; - -p.submit = function(connection) { - var self = this; - if(this.requiresPreparation()) { - this.prepare(connection); - } else { - connection.query(this.text); - } - var handleRowDescription = function(msg) { - self.onRowDescription(msg); - }; - var handleDatarow = function(msg) { - self.onDataRow(msg); - }; - connection.on('rowDescription', handleRowDescription); - connection.on('dataRow', handleDatarow); - connection.once('readyForQuery', function() { - //remove all listeners - connection.removeListener('rowDescription', handleRowDescription); - connection.removeListener('dataRow', handleDatarow); - self.emit('end'); - }); -}; - -p.hasBeenParsed = function(connection) { - return this.name && connection.parsedStatements[this.name]; -}; - -p.prepare = function(connection) { - var self = this; - var onParseComplete = function() { - 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 || "" - }); - connection.execute({ - portal: self.name, - rows: self.rows - }); - connection.flush(); - }; - - - if(this.hasBeenParsed(connection)) { - onParseComplete(); - } else { - connection.parsedStatements[this.name] = true; - connection.parse({ - text: self.text, - name: self.name, - types: self.types - }); - onParseComplete(); - } - - - //TODO support EmptyQueryResponse, ErrorResponse, and PortalSuspended - var onCommandComplete = function() { - connection.sync(); - }; - connection.once('commandComplete', onCommandComplete); - -}; - -var noParse = function(val) { - return val; -}; - -p.onRowDescription = function(msg) { - this.converters = msg.fields.map(function(field) { - return Client.dataTypeParser[field.dataTypeID] || noParse; - }); -}; - -//handles the raw 'dataRow' event from the connection does type coercion -p.onDataRow = function(msg) { - for(var i = 0; i < msg.fields.length; i++) { - if(msg.fields[i] !== null) { - msg.fields[i] = this.converters[i](msg.fields[i]); - } - } - this.emit('row', msg); -}; - -var dateParser = function(isoDate) { - //TODO find some regexp help - //this method works but it's ooglay - var split = isoDate.split(' '); - var dateMatcher = /(\d{4})-(\d{2})-(\d{2})/; - - var date = split[0]; - var time = split[1]; - var match = dateMatcher.exec(date); - var splitDate = date.split('-'); - var year = match[1]; - var month = parseInt(match[2])-1; - var day = match[3]; - - var splitTime = time.split(':'); - var hour = parseInt(splitTime[0]); - var min = splitTime[1]; - var end = splitTime[2]; - var seconds = /(\d{2})/.exec(end); - seconds = (seconds ? seconds[1] : 0); - var mili = /\.(\d{1,})/.exec(end); - mili = mili ? mili[1].slice(0,3) : 0; - var tZone = /([Z|+\-])(\d{2})?(\d{2})?/.exec(end); - //minutes to adjust for timezone - var tzAdjust = 0; - if(tZone) { - var type = tZone[1]; - switch(type) { - case 'Z': break; - case '-': - tzAdjust = -(((parseInt(tZone[2])*60)+(parseInt(tZone[3]||0)))); - break; - case '+': - tzAdjust = (((parseInt(tZone[2])*60)+(parseInt(tZone[3]||0)))); - 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; -}; - -Client.dataTypeParser = { - 20: parseInt, - 21: parseInt, - 23: parseInt, - 26: parseInt, - 1700: parseFloat, - 700: parseFloat, - 701: parseFloat, - 16: function(dbVal) { //boolean - return dbVal === 't'; - }, - // 1083: timeParser, - // 1266: timeParser, - 1114: dateParser, - 1184: dateParser -}; - -//end parsing methods module.exports = Client; diff --git a/lib/query.js b/lib/query.js new file mode 100644 index 00000000..94a4b26a --- /dev/null +++ b/lib/query.js @@ -0,0 +1,172 @@ +var EventEmitter = require('events').EventEmitter; + +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; + 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; +}; + +p.submit = function(connection) { + var self = this; + if(this.requiresPreparation()) { + this.prepare(connection); + } else { + connection.query(this.text); + } + var converters; + var handleRowDescription = function(msg) { + converters = msg.fields.map(function(field) { + return dataTypeParsers[field.dataTypeID] || noParse; + }); + }; + var handleDatarow = function(msg) { + for(var i = 0; i < msg.fields.length; i++) { + if(msg.fields[i] !== null) { + msg.fields[i] = converters[i](msg.fields[i]); + } + } + self.emit('row', msg); + }; + connection.on('rowDescription', handleRowDescription); + connection.on('dataRow', handleDatarow); + connection.once('readyForQuery', function() { + //remove all listeners + connection.removeListener('rowDescription', handleRowDescription); + connection.removeListener('dataRow', handleDatarow); + self.emit('end'); + }); +}; + +p.hasBeenParsed = function(connection) { + return this.name && connection.parsedStatements[this.name]; +}; + +p.prepare = function(connection) { + var self = this; + + if(!this.hasBeenParsed(connection)) { + connection.parsedStatements[this.name] = true; + connection.parse({ + text: self.text, + name: self.name, + types: self.types + }); + } + + //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 || "" + }); + + //TODO test for & support multpile row requests + connection.execute({ + portal: self.name, + rows: self.rows + }); + + connection.flush(); + + //TODO support EmptyQueryResponse, ErrorResponse, and PortalSuspended + var onCommandComplete = function() { + connection.sync(); + }; + + connection.once('commandComplete', onCommandComplete); +}; + +var dateParser = function(isoDate) { + //TODO find some regexp help + //this method works but it's ooglay + //if you wanna contribute...... ;) + var split = isoDate.split(' '); + var dateMatcher = /(\d{4})-(\d{2})-(\d{2})/; + + var date = split[0]; + var time = split[1]; + var match = dateMatcher.exec(date); + var splitDate = date.split('-'); + var year = match[1]; + var month = parseInt(match[2])-1; + var day = match[3]; + + var splitTime = time.split(':'); + var hour = parseInt(splitTime[0]); + var min = splitTime[1]; + var end = splitTime[2]; + var seconds = /(\d{2})/.exec(end); + seconds = (seconds ? seconds[1] : 0); + var mili = /\.(\d{1,})/.exec(end); + mili = mili ? mili[1].slice(0,3) : 0; + var tZone = /([Z|+\-])(\d{2})?(\d{2})?/.exec(end); + //minutes to adjust for timezone + var tzAdjust = 0; + if(tZone) { + var type = tZone[1]; + switch(type) { + case 'Z': break; + case '-': + tzAdjust = -(((parseInt(tZone[2])*60)+(parseInt(tZone[3]||0)))); + break; + case '+': + tzAdjust = (((parseInt(tZone[2])*60)+(parseInt(tZone[3]||0)))); + 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; +}; + +var dataTypeParsers = { + 20: parseInt, + 21: parseInt, + 23: parseInt, + 26: parseInt, + 1700: parseFloat, + 700: parseFloat, + 701: parseFloat, + 16: function(dbVal) { //boolean + return dbVal === 't'; + }, + 1114: dateParser, + 1184: dateParser +}; + + +module.exports = Query;