From 5133ced9b3a670e65256ed4ddea20bd1ffdc9696 Mon Sep 17 00:00:00 2001 From: Petri Lehtinen Date: Fri, 6 Sep 2019 11:56:54 +0300 Subject: [PATCH] Add support for describing prepared statements --- lib/client.js | 5 ++ lib/connection.js | 14 +++++ lib/query.js | 18 +++++++ lib/result.js | 11 ++++ .../client/prepared-statement-tests.js | 25 +++++++++ test/unit/client/prepared-statement-tests.js | 51 +++++++++++++++++++ 6 files changed, 124 insertions(+) diff --git a/lib/client.js b/lib/client.js index cca5e66e8..e793fe773 100644 --- a/lib/client.js +++ b/lib/client.js @@ -308,6 +308,11 @@ Client.prototype._attachListeners = function (con) { self.activeQuery.handleDataRow(msg) }) + // delegate paramDescription to active query + con.on('paramDescription', function (msg) { + self.activeQuery.handleParamDescription(msg, con) + }) + // delegate portalSuspended to active query // eslint-disable-next-line no-unused-vars con.on('portalSuspended', function (msg) { diff --git a/lib/connection.js b/lib/connection.js index 48d65d25f..41a23a7ec 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -424,6 +424,9 @@ Connection.prototype.parseMessage = function (buffer) { case 0x73: // s return new Message('portalSuspended', length) + case 0x74: // t + return this.parset(buffer, length) + case 0x47: // G return this.parseG(buffer, length) @@ -561,6 +564,17 @@ Connection.prototype.parseField = function (buffer) { return field } +Connection.prototype.parset = function (buffer, length) { + var msg = new Message('paramDescription', length) + msg.paramCount = this.parseInt16(buffer) + var params = [] + for (var i = 0; i < msg.paramCount; i++) { + params.push(this.parseInt32(buffer)) + } + msg.params = params + return msg +} + var DATA_ROW = 'dataRow' var DataRowMessage = function (length, fieldCount) { this.name = DATA_ROW diff --git a/lib/query.js b/lib/query.js index 250e8950d..9ce5ae537 100644 --- a/lib/query.js +++ b/lib/query.js @@ -25,6 +25,7 @@ var Query = function (config, values, callback) { this.types = config.types this.name = config.name this.binary = config.binary + this.describe = config.describe // use unique portal name each time this.portal = config.portal || '' this.callback = config.callback @@ -47,6 +48,8 @@ util.inherits(Query, EventEmitter) Query.prototype.requiresPreparation = function () { // named queries must always be prepared if (this.name) { return true } + // always prepare if describing a query + if (this.describe) { return true } // always prepare if there are max number of rows expected per // portal execution if (this.rows) { return true } @@ -99,6 +102,11 @@ Query.prototype.handleDataRow = function (msg) { } } +Query.prototype.handleParamDescription = function (msg, con) { + this._result.addParams(msg.params) + con.sync() +} + Query.prototype.handleCommandComplete = function (msg, con) { this._checkForMultirow() this._result.addCommandComplete(msg) @@ -193,6 +201,16 @@ Query.prototype.prepare = function (connection) { }, true) } + if (self.describe) { + // if describe is set, the query is not executed + connection.describe({ + type: 'S', + name: self.name + }) + connection.flush() + return + } + if (self.values) { try { self.values = self.values.map(utils.prepareValue) diff --git a/lib/result.js b/lib/result.js index 088298bc4..9fa779f8d 100644 --- a/lib/result.js +++ b/lib/result.js @@ -18,6 +18,7 @@ var Result = function (rowMode, types) { this.oid = null this.rows = [] this.fields = [] + this.params = [] this._parsers = [] this._types = types this.RowCtor = null @@ -100,4 +101,14 @@ Result.prototype.addFields = function (fieldDescriptions) { } } +Result.prototype.addParams = function (paramDescriptions) { + if (this.params.length) { + this.params = [] + } + for (var i = 0; i < paramDescriptions.length; i++) { + var desc = paramDescriptions[i] + this.params.push(desc) + } +} + module.exports = Result diff --git a/test/integration/client/prepared-statement-tests.js b/test/integration/client/prepared-statement-tests.js index 76654eaa3..6e351f1a9 100644 --- a/test/integration/client/prepared-statement-tests.js +++ b/test/integration/client/prepared-statement-tests.js @@ -151,3 +151,28 @@ var suite = new helper.Suite() suite.test('cleanup', () => client.end()) })() + +;(function () { + var client = helper.client() + client.on('drain', client.end.bind(client)) + + suite.test('describe', function (done) { + var query = client.query(new Query({ + text: 'SELECT id, name, age FROM person WHERE age > $1', + describe: true + }, (err, res) => { + assert.deepEqual(res.params, [23]) + assert.deepEqual( + res.fields.map(field => ({ name: field.name, type: field.dataTypeID })), + [ + { name: 'id', type: 23 }, + { name: 'name', type: 1043 }, + { name: 'age', type: 23 } + ] + ) + done() + })) + }) + + suite.test('cleanup', () => client.end()) +})() diff --git a/test/unit/client/prepared-statement-tests.js b/test/unit/client/prepared-statement-tests.js index 08db8860b..95bd90424 100644 --- a/test/unit/client/prepared-statement-tests.js +++ b/test/unit/client/prepared-statement-tests.js @@ -153,3 +153,54 @@ test('prepared statement with explicit portal', function () { }) }) }) + +var describeClient = helper.client() +var describeCon = describeClient.connection +describeCon.parse = function (arg) { + process.nextTick(function () { + describeCon.emit('parseComplete') + }) +} +var describeDescribeArg = null +describeCon.describe = function (arg) { + describeDescribeArg = arg +} + +var describeFlushCalled = false +describeCon.flush = function () { + describeFlushCalled = true + process.nextTick(function () { + describeCon.emit('paramDescription', { params: [] }) + describeCon.emit('rowDescription', { fields: [] }) + }) +} + +var describeSyncCalled = false +describeCon.sync = function () { + process.nextTick(function () { + describeSyncCalled = true + describeCon.emit('readyForQuery') + }) +} + +test('describe prepared statement', function () { + assert.ok(describeClient.connection.emit('readyForQuery')) + + var query = describeClient.query(new Query({ + text: 'select * from X where name = $1', + describe: true + })) + + assert.emits(query, 'end', function () { + test('describe argument', function () { + assert.equal(describeDescribeArg.type, 'S') + assert.equal(describeDescribeArg.name, undefined) + }) + test('flush called', function () { + assert.ok(describeFlushCalled) + }) + test('sync called', function () { + assert.ok(describeSyncCalled) + }) + }) +})