diff --git a/packages/pg/lib/client.js b/packages/pg/lib/client.js index 7a2fc9a64..8958aa2d9 100644 --- a/packages/pg/lib/client.js +++ b/packages/pg/lib/client.js @@ -437,6 +437,16 @@ class Client extends EventEmitter { this._activeQuery = null if (activeQuery.name) { delete this.connection.submittedNamedStatements[activeQuery.name] + // pipelined queries already on the wire skipped their own Parse relying on this one. + // If it failed before parseComplete the backend will answer them with + // 'prepared statement does not exist', so hand them the original error instead. + if (!this.connection.parsedStatements[activeQuery.name]) { + for (const query of this._sentQueryQueue) { + if (query.name === activeQuery.name) { + query._pipelineParseError = msg + } + } + } } activeQuery.handleError(msg, this.connection) } diff --git a/packages/pg/lib/query.js b/packages/pg/lib/query.js index 6b9214199..eea7b6fce 100644 --- a/packages/pg/lib/query.js +++ b/packages/pg/lib/query.js @@ -120,6 +120,10 @@ class Query extends EventEmitter { } handleError(err, connection) { + // the named statement this query relied on was never created (26000): report why + if (this._pipelineParseError && err && err.code === '26000') { + err = this._pipelineParseError + } // need to sync after error during a prepared statement if (this._canceledDueToError) { err = this._canceledDueToError diff --git a/packages/pg/test/integration/client/pipelining-tests.js b/packages/pg/test/integration/client/pipelining-tests.js index 957047d7d..1d9d24833 100644 --- a/packages/pg/test/integration/client/pipelining-tests.js +++ b/packages/pg/test/integration/client/pipelining-tests.js @@ -123,6 +123,29 @@ suite.test( } ) +// Two pipelined queries share a statement name and the parse fails: the second skipped +// its own Parse, so the backend answers 'prepared statement does not exist'. The client +// must report the original parse error to it instead. +suite.test( + 'named statement parse failure reports the real error to the queries behind it', + !helper.args.native && + async function () { + const client = helper.client(undefined, { pipeline: true }) + + const results = await Promise.allSettled([ + client.query({ name: 'shared-bad', text: 'SELECT no_such_column_xyz' }), + client.query({ name: 'shared-bad', text: 'SELECT no_such_column_xyz' }), + ]) + + assert.equal(results[0].status, 'rejected') + assert.equal(results[1].status, 'rejected') + assert.ok(results[0].reason.message.includes('no_such_column_xyz'), results[0].reason.message) + assert.ok(results[1].reason.message.includes('no_such_column_xyz'), results[1].reason.message) + + await client.end() + } +) + // #14: query_timeout with pipelining // When an already-sent pipelined query times out, the connection is destroyed // to unblock the pipeline — subsequent queries error rather than hanging.