From 8eaa1f086853082ced0c654310d053b4e3c7dd78 Mon Sep 17 00:00:00 2001 From: Brian Downing Date: Mon, 4 May 2015 04:03:20 -0500 Subject: [PATCH 1/2] Add test for long (>63 character) prepared statement names Since the name is truncated by Postgres, the second query will result in a "prepared statement already exists" error. --- .../client/prepared-statement-tests.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/integration/client/prepared-statement-tests.js b/test/integration/client/prepared-statement-tests.js index 478cd007c..21c093502 100644 --- a/test/integration/client/prepared-statement-tests.js +++ b/test/integration/client/prepared-statement-tests.js @@ -211,4 +211,20 @@ test('prepared statement', function() { checkForResults(query); }) + test('with long name', function() { + var queryA = client.query({ + name: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + text: 'SELECT name FROM zoom ORDER BY name', + rows: 1000 + }) + checkForResults(queryA); + + var queryB = client.query({ + name: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab', + text: 'SELECT name FROM zoom ORDER BY name', + rows: 1000 + }) + checkForResults(queryB); + }) + }) From 96bea73cc1cf530425dc2d42426a5f295e473740 Mon Sep 17 00:00:00 2001 From: Brian Downing Date: Mon, 4 May 2015 04:15:56 -0500 Subject: [PATCH 2/2] Use cached short names in place of long prepared statement names This allows for prepared statement names of any length. In particular it lets one safely use the SQL query itself as the name. --- lib/connection.js | 27 +++++++++++++++++++++++---- lib/native/index.js | 11 +++++++++++ lib/native/query.js | 14 +++++++++----- 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/lib/connection.js b/lib/connection.js index f11ecf05c..ac038bf30 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -19,6 +19,8 @@ var Connection = function(config) { this.parsedStatements = {}; this.writer = new Writer(); this.ssl = config.ssl || false; + this.longNameI = 0; + this.longNameMap = {}; this._ending = false; this._mode = TEXT_MODE; this._emitMessage = false; @@ -189,6 +191,15 @@ Connection.prototype.query = function(text) { this.stream.write(this.writer.addCString(text).flush(0x51)); }; +Connection.prototype.getShortName = function(longName) { + var shortName = this.longNameMap[longName]; + if (!shortName) { + shortName = '__node_pg_short_name_' + (++this.longNameI) + '__'; + this.longNameMap[longName] = shortName; + } + return shortName; +}; + //send parse message //"more" === true to buffer the message until flush() is called Connection.prototype.parse = function(query, more) { @@ -198,12 +209,16 @@ Connection.prototype.parse = function(query, more) { // types: ['int8', 'bool'] } //normalize missing query names to allow for null - query.name = query.name || ''; + var query_name = query.name || ''; + //shorten long (>63 character) names + if (query_name.length > 63) { + query_name = this.getShortName(query_name); + } //normalize null type array query.types = query.types || []; var len = query.types.length; var buffer = this.writer - .addCString(query.name) //name of query + .addCString(query_name) //name of query .addCString(query.text) //actual query text .addInt16(len); for(var i = 0; i < len; i++) { @@ -220,7 +235,11 @@ Connection.prototype.bind = function(config, more) { //normalize config config = config || {}; config.portal = config.portal || ''; - config.statement = config.statement || ''; + var config_statement = config.statement || ''; + //shorten long (>63 character) names + if (config_statement.length > 63) { + config_statement = this.getShortName(config_statement); + } config.binary = config.binary || false; var values = config.values || []; var len = values.length; @@ -229,7 +248,7 @@ Connection.prototype.bind = function(config, more) { useBinary |= values[j] instanceof Buffer; var buffer = this.writer .addCString(config.portal) - .addCString(config.statement); + .addCString(config_statement); if (!useBinary) buffer.addInt16(0); else { diff --git a/lib/native/index.js b/lib/native/index.js index 7569c4d47..8a8e28d4e 100644 --- a/lib/native/index.js +++ b/lib/native/index.js @@ -36,6 +36,8 @@ var Client = module.exports = function(config) { //a hash to hold named queries this.namedQueries = {}; + this.longNameI = 0; + this.longNameMap = {}; }; util.inherits(Client, EventEmitter); @@ -191,3 +193,12 @@ Client.prototype.setTypeParser = function(oid, format, parseFn) { Client.prototype.getTypeParser = function(oid, format) { return this._types.getTypeParser(oid, format); }; + +Client.prototype.getShortName = function(longName) { + var shortName = this.longNameMap[longName]; + if (!shortName) { + shortName = '__node_pg_native_short_name_' + (++this.longNameI) + '__'; + this.longNameMap[longName] = shortName; + } + return shortName; +}; diff --git a/lib/native/query.js b/lib/native/query.js index 29b77ba96..051965f6c 100644 --- a/lib/native/query.js +++ b/lib/native/query.js @@ -86,17 +86,21 @@ NativeQuery.prototype.submit = function(client) { //named query if(this.name) { var values = (this.values||[]).map(utils.prepareValue); + var this_name = this.name; + if(this_name.length > 63) { + this_name = client.getShortName(this_name); + } //check if the client has already executed this named query //if so...just execute it again - skip the planning phase - if(client.namedQueries[this.name]) { - return this.native.execute(this.name, values, after); + if(client.namedQueries[this_name]) { + return this.native.execute(this_name, values, after); } //plan the named query the first time, then execute it - return this.native.prepare(this.name, this.text, values.length, function(err) { + return this.native.prepare(this_name, this.text, values.length, function(err) { if(err) return after(err); - client.namedQueries[self.name] = true; - return self.native.execute(self.name, values, after); + client.namedQueries[this_name] = true; + return self.native.execute(this_name, values, after); }); } else if(this.values) {