diff --git a/lib/native/query.js b/lib/native/query.js index c1a769154..dbcd56041 100644 --- a/lib/native/query.js +++ b/lib/native/query.js @@ -10,7 +10,6 @@ var EventEmitter = require('events').EventEmitter var util = require('util') var utils = require('../utils') -var NativeResult = require('./result') var NativeQuery = module.exports = function (config, values, callback) { EventEmitter.call(this) @@ -91,7 +90,7 @@ NativeQuery.prototype.submit = function (client) { this.native = client.native client.native.arrayMode = this._arrayMode - var after = function (err, rows) { + var after = function (err, rows, results) { client.native.arrayMode = false setImmediate(function () { self.emit('_done') @@ -102,22 +101,26 @@ NativeQuery.prototype.submit = function (client) { return self.handleError(err) } - var result = new NativeResult() - result.addCommandComplete(self.native.pq) - result.rows = rows - // emit row events for each row in the result if (self._emitRowEvents) { - rows.forEach(function (row) { - self.emit('row', row, result) - }) + if (results.length > 1) { + rows.forEach((rowOfRows, i) => { + rowOfRows.forEach(row => { + self.emit('row', row, results[i]) + }) + }) + } else { + rows.forEach(function (row) { + self.emit('row', row, results) + }) + } } // handle successful result self.state = 'end' - self.emit('end', result) + self.emit('end', results) if (self.callback) { - self.callback(null, result) + self.callback(null, results) } } diff --git a/lib/native/result.js b/lib/native/result.js deleted file mode 100644 index ae9a97a1e..000000000 --- a/lib/native/result.js +++ /dev/null @@ -1,37 +0,0 @@ -'use strict' -/** - * Copyright (c) 2010-2017 Brian Carlson (brian.m.carlson@gmail.com) - * All rights reserved. - * - * This source code is licensed under the MIT license found in the - * README.md file in the root directory of this source tree. - */ - -var NativeResult = module.exports = function (pq) { - this.command = null - this.rowCount = 0 - this.rows = null - this.fields = null -} - -NativeResult.prototype.addCommandComplete = function (pq) { - this.command = pq.cmdStatus().split(' ')[0] - this.rowCount = parseInt(pq.cmdTuples(), 10) - var nfields = pq.nfields() - if (nfields < 1) return - - this.fields = [] - for (var i = 0; i < nfields; i++) { - this.fields.push({ - name: pq.fname(i), - dataTypeID: pq.ftype(i) - }) - } -} - -NativeResult.prototype.addRow = function (row) { - // This is empty to ensure pg code doesn't break when switching to pg-native - // pg-native loads all rows into the final result object by default. - // This is because libpg loads all rows into memory before passing the result - // to pg-native. -} diff --git a/lib/query.js b/lib/query.js index 89bde080b..4d483ac7a 100644 --- a/lib/query.js +++ b/lib/query.js @@ -29,10 +29,14 @@ var Query = function (config, values, callback) { // use unique portal name each time this.portal = config.portal || '' this.callback = config.callback + this._rowMode = config.rowMode if (process.domain && config.callback) { this.callback = process.domain.bind(config.callback) } - this._result = new Result(config.rowMode, config.types) + this._result = new Result(this._rowMode, this.types) + + // potential for multiple results + this._results = this._result this.isPreparedStatement = false this._canceledDueToError = false this._promise = null @@ -54,10 +58,24 @@ Query.prototype.requiresPreparation = function () { return this.values.length > 0 } +Query.prototype._checkForMultirow = function () { + // if we already have a result with a command property + // then we've already executed one query in a multi-statement simple query + // turn our results into an array of results + if (this._result.command) { + if (!Array.isArray(this._results)) { + this._results = [this._result] + } + this._result = new Result(this._rowMode, this.types) + this._results.push(this._result) + } +} + // associates row metadata from the supplied // message with this query object // metadata used when parsing row results Query.prototype.handleRowDescription = function (msg) { + this._checkForMultirow() this._result.addFields(msg.fields) this._accumulateRows = this.callback || !this.listeners('row').length } @@ -83,6 +101,7 @@ Query.prototype.handleDataRow = function (msg) { } Query.prototype.handleCommandComplete = function (msg, con) { + this._checkForMultirow() this._result.addCommandComplete(msg) // need to sync after each command complete of a prepared statement if (this.isPreparedStatement) { @@ -104,9 +123,9 @@ Query.prototype.handleReadyForQuery = function (con) { return this.handleError(this._canceledDueToError, con) } if (this.callback) { - this.callback(null, this._result) + this.callback(null, this._results) } - this.emit('end', this._result) + this.emit('end', this._results) } Query.prototype.handleError = function (err, connection) { diff --git a/package.json b/package.json index 070689726..9935b9cda 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "eslint-plugin-standard": "3.0.1", "pg-copy-streams": "0.3.0" }, - "minNativeVersion": "1.7.0", + "minNativeVersion": "2.0.0", "scripts": { "test": "make test-all" }, diff --git a/test/integration/client/multiple-results-tests.js b/test/integration/client/multiple-results-tests.js new file mode 100644 index 000000000..01dd9eaed --- /dev/null +++ b/test/integration/client/multiple-results-tests.js @@ -0,0 +1,69 @@ +'use strict' +const assert = require('assert') +const co = require('co') + +const helper = require('./test-helper') + +const suite = new helper.Suite('multiple result sets') + +suite.test('two select results work', co.wrap(function * () { + const client = new helper.Client() + yield client.connect() + + const results = yield client.query(`SELECT 'foo'::text as name; SELECT 'bar'::text as baz`) + assert(Array.isArray(results)) + + assert.equal(results[0].fields[0].name, 'name') + assert.deepEqual(results[0].rows, [{ name: 'foo' }]) + + assert.equal(results[1].fields[0].name, 'baz') + assert.deepEqual(results[1].rows, [{ baz: 'bar' }]) + + return client.end() +})) + +suite.test('multiple selects work', co.wrap(function * () { + const client = new helper.Client() + yield client.connect() + + const text = ` + SELECT * FROM generate_series(2, 4) as foo; + SELECT * FROM generate_series(8, 10) as bar; + SELECT * FROM generate_series(20, 22) as baz; + ` + + const results = yield client.query(text) + assert(Array.isArray(results)) + + assert.equal(results[0].fields[0].name, 'foo') + assert.deepEqual(results[0].rows, [{ foo: 2 }, { foo: 3 }, { foo: 4 }]) + + assert.equal(results[1].fields[0].name, 'bar') + assert.deepEqual(results[1].rows, [{ bar: 8 }, { bar: 9 }, { bar: 10 }]) + + assert.equal(results[2].fields[0].name, 'baz') + assert.deepEqual(results[2].rows, [{ baz: 20 }, { baz: 21 }, { baz: 22 }]) + + assert.equal(results.length, 3) + + return client.end() +})) + +suite.test('mixed queries and statements', co.wrap(function * () { + const client = new helper.Client() + yield client.connect() + + const text = ` + CREATE TEMP TABLE weather(type text); + INSERT INTO weather(type) VALUES ('rain'); + SELECT * FROM weather; + ` + + const results = yield client.query(text) + assert(Array.isArray(results)) + assert.equal(results[0].command, 'CREATE') + assert.equal(results[1].command, 'INSERT') + assert.equal(results[2].command, 'SELECT') + + return client.end() +})) diff --git a/test/integration/client/simple-query-tests.js b/test/integration/client/simple-query-tests.js index e46958f2d..d7f1916a5 100644 --- a/test/integration/client/simple-query-tests.js +++ b/test/integration/client/simple-query-tests.js @@ -49,16 +49,17 @@ test('prepared statements do not mutate params', function () { client.on('drain', client.end.bind(client)) + const rows = [] query.on('row', function (row, result) { assert.ok(result) - result.addRow(row) + rows.push(row) }) query.on('end', function (result) { - assert.lengthIs(result.rows, 26, 'result returned wrong number of rows') - assert.lengthIs(result.rows, result.rowCount) - assert.equal(result.rows[0].name, 'Aaron') - assert.equal(result.rows[25].name, 'Zanzabar') + assert.lengthIs(rows, 26, 'result returned wrong number of rows') + assert.lengthIs(rows, result.rowCount) + assert.equal(rows[0].name, 'Aaron') + assert.equal(rows[25].name, 'Zanzabar') }) })