From da8026df9d0631412a495d18b17de2f61a2a5422 Mon Sep 17 00:00:00 2001 From: brianc Date: Thu, 28 Oct 2010 19:09:40 -0500 Subject: [PATCH] prepared statements moved forward a good deal --- lib/client.js | 29 ++-- lib/connection.js | 1 + .../client/prepared-statement-tests.js | 137 +++++++++++++++++- test/unit/client/prepared-statement-tests.js | 80 ++++++---- 4 files changed, 205 insertions(+), 42 deletions(-) diff --git a/lib/client.js b/lib/client.js index 35d97b0af..f1917ef1d 100644 --- a/lib/client.js +++ b/lib/client.js @@ -77,7 +77,7 @@ p.pulseQueryQueue = function() { p.query = function(config) { //can take in strings or config objects - var query = new Query(config.text ? config : { text: config }); + var query = new Query((config.text || config.name) ? config : { text: config }); this.queryQueue.push(query); this.pulseQueryQueue(); return query; @@ -102,6 +102,7 @@ var Query = function(config) { this.rowDescription = null; EventEmitter.call(this); }; + sys.inherits(Query, EventEmitter); var p = Query.prototype; @@ -132,16 +133,13 @@ p.submit = function(connection) { }); }; +p.hasBeenParsed = function(connection) { + return this.name && connection.parsedStatements[this.name]; +}; + p.prepare = function(connection) { var self = this; - connection.parse({ - text: self.text, - name: self.name, - types: self.types - }); - connection.flush(); - var onParseComplete = function() { connection.bind({ portal: self.name, @@ -151,7 +149,20 @@ p.prepare = function(connection) { connection.flush(); }; - connection.once('parseComplete', onParseComplete); + + if(this.hasBeenParsed(connection)) { + onParseComplete(); + } else { + connection.parsedStatements[this.name] = true; + connection.parse({ + text: self.text, + name: self.name, + types: self.types + }); + connection.flush(); + connection.once('parseComplete', onParseComplete); + } + var onBindComplete = function() { connection.describe({ diff --git a/lib/connection.js b/lib/connection.js index ef390c462..e27265aeb 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -15,6 +15,7 @@ var Connection = function(config) { this.buffer = null; this.offset = null; this.encoding = 'utf8'; + this.parsedStatements = {}; }; sys.inherits(Connection, EventEmitter); diff --git a/test/integration/client/prepared-statement-tests.js b/test/integration/client/prepared-statement-tests.js index c8b49e711..ccdc308e4 100644 --- a/test/integration/client/prepared-statement-tests.js +++ b/test/integration/client/prepared-statement-tests.js @@ -1,6 +1,6 @@ var helper = require(__dirname +'/test-helper'); -test("simple prepared statement", function(){ +test("simple, unnamed prepared statement", function(){ var client = helper.client(); var query = client.query({ @@ -16,3 +16,138 @@ test("simple prepared statement", function(){ client.end(); }); }); + +test("named prepared statement", function() { + + var client = helper.client(); + var queryName = "user by age and like name"; + var query = client.query({ + text: 'select name from person where age <= $1 and name LIKE $2', + values: [20, 'Bri%'], + name: queryName + }); + + var parseCount = 0; + client.connection.on('parseComplete', function() { + parseCount++; + }); + + assert.raises(query, 'row', function(row) { + assert.equal(row.fields[0], 'Brian'); + }); + + assert.raises(query, 'end', function() { + test("query was parsed", function() { + assert.equal(parseCount, 1); + }); + + test("with same name & text", function() { + var cachedQuery = client.query({ + text: 'select name from person where age <= $1 and name LIKE $2', + name: queryName, + values: [10, 'A%'] + }); + + assert.raises(cachedQuery, 'row', function(row) { + assert.equal(row.fields[0], 'Aaron'); + }); + + assert.raises(cachedQuery, 'end', function() { + test("query was only parsed one time", function() { + assert.equal(parseCount, 1, "Should not have reparsed query"); + }); + }); + }); + + test("with same name, but the query text not even there batman!", function() { + var q = client.query({ + name: queryName, + values: [30, '%n%'] + }); + + test("gets first row", function() { + + assert.raises(q, 'row', function(row) { + assert.equal(row.fields[0], "Aaron"); + + test("gets second row", function() { + + assert.raises(q, 'row', function(row) { + assert.equal(row.fields[0], "Brian"); + }); + }); + + }); + }); + + test("only parsed query once total", function() { + assert.equal(parseCount, 1); + q.on('end', function() { + client.end(); + }); + }); + + }); + }); + +}); + +test("prepared statements on different clients", function() { + var statementName = "differ"; + var statement1 = "select count(*) from person"; + var statement2 = "select count(*) from person where age < $1"; + + var client1Finished = false; + var client2Finished = false; + + var client1 = helper.client(); + + var client2 = helper.client(); + + test("client 1 execution", function() { + + var query = client1.query({ + name: statementName, + text: statement1 + }); + test('gets right data back', function() { + assert.raises(query, 'row', function(row) { + assert.equal(row.fields[0], 26); + }); + }); + + assert.raises(query, 'end', function() { + if(client2Finished) { + client1.end(); + client2.end(); + } else { + client1Finished = true; + } + }); + + }); + + test('client 2 execution', function() { + var query = client2.query({ + name: statementName, + text: statement2, + values: [11] + }); + + test('gets right data', function() { + assert.raises(query, 'row', function(row) { + assert.equal(row.fields[0], 1); + }); + }); + + assert.raises(query, 'end', function() { + if(client1Finished) { + client1.end(); + client2.end(); + } else { + client2Finished = true; + } + }); + }); + +}); diff --git a/test/unit/client/prepared-statement-tests.js b/test/unit/client/prepared-statement-tests.js index 657901c74..f51d3b717 100644 --- a/test/unit/client/prepared-statement-tests.js +++ b/test/unit/client/prepared-statement-tests.js @@ -3,68 +3,84 @@ var helper = require(__dirname + '/test-helper'); var client = helper.client(); var con = client.connection; var parseArg = null; -con.parse = function(query) { - parseArg = query; +con.parse = function(arg) { + parseArg = arg; + process.nextTick(function() { + con.emit('parseComplete'); + }); }; var bindArg = null; con.bind = function(arg) { bindArg = arg; - this.emit('bindComplete'); + process.nextTick(function(){ + con.emit('bindComplete'); + }); }; var executeArg = null; con.execute = function(arg) { executeArg = arg; - this.emit('rowData',{ fields: [] }); - this.emit('commandComplete'); + process.nextTick(function() { + con.emit('rowData',{ fields: [] }); + con.emit('commandComplete'); + }); }; var describeArg = null; con.describe = function(arg) { describeArg = arg; - this.emit('rowDescription', { fields: [] }); + process.nextTick(function() { + con.emit('rowDescription', { fields: [] }); + }); }; -var syncCalled = true; +var syncCalled = false; +con.flush = function() { +}; con.sync = function() { - syncCalled = false; - this.emit('readyForQuery') + syncCalled = true; + process.nextTick(function() { + con.emit('readyForQuery'); + }); }; test('bound command', function() { test('simple, unnamed bound command', function() { - return false; + assert.ok(client.connection.emit('readyForQuery')); + var query = client.query({ text: 'select * where name = $1', - parameters: ['hi'] + values: ['hi'] }); - test('parse argument', function() { - assert.equal(parseArg.name, null); - assert.equal(parseArg.text, 'select * where name = $1'); - assert.equal(parseArg.types, null); - }); + assert.raises(query,'end', function() { + test('parse argument', function() { + assert.equal(parseArg.name, null); + assert.equal(parseArg.text, 'select * where name = $1'); + assert.equal(parseArg.types, null); + }); - test('bind argument', function() { - assert.equal(bindArg.statement, null); - assert.equal(bindArg.portal, null); - assert.length(bindArg.values, 1); - assert.equal(bindArg.values[0], 'hi') - }); + test('bind argument', function() { + assert.equal(bindArg.statement, null); + assert.equal(bindArg.portal, null); + assert.length(bindArg.values, 1); + assert.equal(bindArg.values[0], 'hi') + }); - test('describe argument', function() { - assert.equal(describeArg, null); - }); + test('describe argument', function() { + assert.equal(describeArg.type, 'P'); + assert.equal(describeArg.name, ""); + }); - test('execute argument', function() { - assert.equal(executeArg.portal, null); - assert.equal(executeArg.rows, null); - }); + test('execute argument', function() { + assert.equal(executeArg.portal, null); + assert.equal(executeArg.rows, null); + }); - test('sync called', function() { - assert.ok(syncCalled); + test('sync called', function() { + assert.ok(syncCalled); + }); }); - }); });