Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion packages/pg/lib/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ class Client extends EventEmitter {
this._connected = false
this._connectionError = false
this._queryable = true
this._unqueryableReason = null
this._activeQuery = null
this._txStatus = null

Expand Down Expand Up @@ -418,6 +419,7 @@ class Client extends EventEmitter {
return this._handleErrorWhileConnecting(err)
}
this._queryable = false
this._unqueryableReason = this._unqueryableReason || err
this._errorAllQueries(err)
this.emit('error', err)
}
Expand Down Expand Up @@ -764,7 +766,12 @@ class Client extends EventEmitter {

if (!this._queryable) {
process.nextTick(() => {
query.handleError(new Error('Client has encountered a connection error and is not queryable'), this.connection)
query.handleError(
new Error('Client has encountered a connection error and is not queryable', {
cause: this._unqueryableReason,
}),
this.connection
)
})
return result
}
Expand Down
9 changes: 8 additions & 1 deletion packages/pg/lib/native/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const Client = (module.exports = function (config) {
this._connecting = false
this._connected = false
this._queryable = true
this._unqueryableReason = null
this.pipeline = Boolean(config.pipeline)
this._pipelineInFlight = false

Expand Down Expand Up @@ -110,6 +111,7 @@ Client.prototype._connect = function (cb) {
// handle connection errors from the native layer
self.native.on('error', function (err) {
self._queryable = false
self._unqueryableReason = self._unqueryableReason || err
self._errorAllQueries(err)
self.emit('error', err)
})
Expand Down Expand Up @@ -223,7 +225,11 @@ Client.prototype.query = function (config, values, callback) {
if (!this._queryable) {
query.native = this.native
process.nextTick(() => {
query.handleError(new Error('Client has encountered a connection error and is not queryable'))
query.handleError(
new Error('Client has encountered a connection error and is not queryable', {
cause: this._unqueryableReason,
})
)
})
return result
}
Expand Down Expand Up @@ -358,6 +364,7 @@ Client.prototype._pulsePipelinedQueryQueue = function () {
// for the life of the process.
self._connected = false
self._queryable = false
self._unqueryableReason = self._unqueryableReason || err
for (let i = 0; i < nativeQueries.length; i++) {
const q = nativeQueries[i]
q.native = self.native
Expand Down
28 changes: 28 additions & 0 deletions packages/pg/test/integration/gh-issues/3852-tests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use strict'
const helper = require('../test-helper')
const assert = require('assert')
const suite = new helper.Suite()

// libpq does not report a FATAL error while no query is running
if (helper.args.native) {
return
}

suite.test('not queryable error has the idle-in-transaction timeout as cause', async () => {
const client = new helper.pg.Client(helper.config)
const errors = []
client.on('error', (err) => errors.push(err))
const ended = new Promise((resolve) => client.once('end', resolve))
await client.connect()
await client.query("SET idle_in_transaction_session_timeout = '100ms'")
await client.query('BEGIN')
await ended

const timeoutError = errors[0]
assert.equal(timeoutError.code, '25P03')
await assert.rejects(client.query('SELECT 1'), (err) => {
assert.equal(err.message, 'Client has encountered a connection error and is not queryable')
assert.strictEqual(err.cause, timeoutError)
return true
})
})
23 changes: 23 additions & 0 deletions packages/pg/test/native/unqueryable-error-cause-tests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use strict'
const helper = require('./../test-helper')
const Client = require('./../../lib/native')
const suite = new helper.Suite()
const assert = require('assert')

suite.test('not queryable error keeps the error that made the client unqueryable as cause', async function () {
const client = new Client(helper.config)
await client.connect()

const connectionError = new Error('server closed the connection unexpectedly')
client.on('error', function (err) {
assert.strictEqual(err, connectionError)
})
client.native.emit('error', connectionError)

await assert.rejects(client.query('SELECT 1'), function (err) {
assert.equal(err.message, 'Client has encountered a connection error and is not queryable')
assert.strictEqual(err.cause, connectionError)
return true
})
await client.end()
})
26 changes: 26 additions & 0 deletions packages/pg/test/unit/client/unqueryable-error-cause-tests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use strict'
const helper = require('./test-helper')
const assert = require('assert')
const suite = new helper.Suite()
const test = suite.test.bind(suite)

test('not queryable error keeps the error that made the client unqueryable as cause', function (done) {
const client = helper.client()
client.connection.emit('readyForQuery')

const fatal = new Error('terminating connection due to idle-in-transaction timeout')
fatal.severity = 'FATAL'
fatal.code = '25P03'

client.on('error', function (err) {
assert.strictEqual(err, fatal)
})
client.connection.emit('errorMessage', fatal)

client.query('SELECT 1', function (err) {
assert.equal(err.message, 'Client has encountered a connection error and is not queryable')
assert.strictEqual(err.cause, fatal)
assert.equal(err.cause.code, '25P03')
done()
})
})
Loading