diff --git a/packages/pg/lib/client.js b/packages/pg/lib/client.js index 76906712b..d177a24bf 100644 --- a/packages/pg/lib/client.js +++ b/packages/pg/lib/client.js @@ -332,6 +332,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/packages/pg/lib/connection.js b/packages/pg/lib/connection.js index c3f30aa0f..8efb2a141 100644 --- a/packages/pg/lib/connection.js +++ b/packages/pg/lib/connection.js @@ -403,6 +403,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) @@ -540,6 +543,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/packages/pg/lib/query.js b/packages/pg/lib/query.js index 2392b710e..69bcf387b 100644 --- a/packages/pg/lib/query.js +++ b/packages/pg/lib/query.js @@ -24,6 +24,7 @@ class Query extends EventEmitter { 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 @@ -45,6 +46,10 @@ class Query extends EventEmitter { 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) { @@ -103,6 +108,11 @@ class Query extends EventEmitter { } } + handleParamDescription(msg, con) { + this._result.addParams(msg.params) + con.sync() + } + handleCommandComplete(msg, con) { this._checkForMultirow() this._result.addCommandComplete(msg) @@ -202,6 +212,16 @@ class Query extends EventEmitter { ) } + if (this.describe) { + // if describe is set, the query is not executed + connection.describe({ + type: 'S', + name: this.name, + }) + connection.flush() + return + } + if (this.values) { try { this.values = this.values.map(utils.prepareValue) diff --git a/packages/pg/lib/result.js b/packages/pg/lib/result.js index 233455b06..220b6ae3b 100644 --- a/packages/pg/lib/result.js +++ b/packages/pg/lib/result.js @@ -18,6 +18,7 @@ var Result = function (rowMode, types) { this.oid = null this.rows = [] this.fields = [] + this.params = [] this._parsers = undefined this._types = types this.RowCtor = null @@ -102,4 +103,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/packages/pg/test/integration/client/prepared-statement-tests.js b/packages/pg/test/integration/client/prepared-statement-tests.js index 48d12f899..920048cf5 100644 --- a/packages/pg/test/integration/client/prepared-statement-tests.js +++ b/packages/pg/test/integration/client/prepared-statement-tests.js @@ -176,3 +176,32 @@ 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) { + client.query( + new Query( + { + text: 'SELECT id, name, age FROM person WHERE age > $1', + describe: true, + }, + (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/packages/pg/test/unit/client/prepared-statement-tests.js b/packages/pg/test/unit/client/prepared-statement-tests.js index 2499808f7..2294d913c 100644 --- a/packages/pg/test/unit/client/prepared-statement-tests.js +++ b/packages/pg/test/unit/client/prepared-statement-tests.js @@ -155,3 +155,56 @@ 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) + }) + }) +})