From 8b9399338b60f98c5796337859f50f795508871b Mon Sep 17 00:00:00 2001 From: Prince Jain Date: Fri, 23 Oct 2026 00:08:09 +0530 Subject: [PATCH] fix(pg): settle a query once when bind serialization throws When a parameter fails to serialize, connection.bind() throws before any bind message reaches the wire. The catch reported the error through the callback immediately, but the query stayed active, so the server's ReadyForQuery answer to our sync settled the same query a second time as a success with zero rows. Defer the error through the existing _canceledDueToError channel so handleReadyForQuery reports it exactly once. Fixes #3820 --- packages/pg/lib/query.js | 5 +++- .../test/unit/client/throw-in-bind-tests.js | 23 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/packages/pg/lib/query.js b/packages/pg/lib/query.js index 3b93cb5ba..6d8838eda 100644 --- a/packages/pg/lib/query.js +++ b/packages/pg/lib/query.js @@ -239,7 +239,10 @@ class Query extends EventEmitter { connection.close({ type: 'S', name: this.name }) connection.sync() - this.handleError(err, connection) + // defer the error until readyForQuery so the query settles exactly + // once; reporting it here would let handleReadyForQuery settle the + // same query again as a success when the server answers our sync + this._canceledDueToError = err return } diff --git a/packages/pg/test/unit/client/throw-in-bind-tests.js b/packages/pg/test/unit/client/throw-in-bind-tests.js index 8b460b9e4..1c5c6e624 100644 --- a/packages/pg/test/unit/client/throw-in-bind-tests.js +++ b/packages/pg/test/unit/client/throw-in-bind-tests.js @@ -52,6 +52,8 @@ suite.test('calls callback with error when bind throws', function (done) { }, }) ) + // the server answers the sync sent after the failed bind + con.emit('readyForQuery') }) suite.test('emits error event when bind throws (no callback)', function (done) { @@ -67,6 +69,7 @@ suite.test('emits error event when bind throws (no callback)', function (done) { done() }) client.query(query) + con.emit('readyForQuery') }) suite.test('send close when bind throws', function (done) { @@ -83,4 +86,24 @@ suite.test('send close when bind throws', function (done) { }, }) ) + con.emit('readyForQuery') +}) + +suite.test('settles the query exactly once when bind throws', function (done) { + const { client, con } = setupClient() + con.emit('readyForQuery') + let calls = 0 + client.query( + new Query({ + text: 'select $1', + values: ['x'], + callback: function () { + calls++ + }, + }) + ) + // the server answers the sync sent after the failed bind + con.emit('readyForQuery') + assert.equal(calls, 1, 'callback should be called exactly once') + done() })