pg-native: unhandled libpq result status drops the result and the server's error
まだ誰も着手していません。
評価
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 初心者へのやさしさ
- 58/100
- issue の種類
- バグ
- 明瞭さ
- おおむね明確
- 活発さ
- 活発
- 技術スタック
- javascript, node.js, postgresql
調査の方向性
まず、native クライアントと pure-JS クライアントで、提示されている COPY … TO STDOUT のケースを再現します。packages/pg-native/index.js の _emitResult、_read、_onReadyForQuery 付近を読み、その後 packages/pg/lib/native/query.js の after() 付近を読みます。未処理の libpq ステータスによって undefined の成功結果が生成されなくなり、サーバーエラーがクエリの callback に到達すれば完了です。
索引モデルが issue の本文から書いたものです。
説明
Disclaimer
This is a real issue we saw in our production environment via sequelize v6, we used claude + 2 agents to generate this report, I saw no AI/slop policy, feel free to do whatever you feel like based on this.
Summary
client.query(sql, cb) on the native client can call cb(null, undefined) — no error, no result — so anything that reads res.rows throws. The pure-JS client returns a Result for the same query.
Versions: pg 8.23.0, pg-native 3.9.0, libpq 1.11.0, Node 26.7.0. Reproduced on PostgreSQL 17.11 and CockroachDB 26.2.5 with identical output. Line numbers below are from master; the published 3.9.0 tarball is offset by ~17 lines around _onReadyForQuery.
Repro
COPY … TO STDOUT through client.query() hits it every time, no patching needed. This runs the same query through both drivers:
const CONN = process.argv[2] || 'postgres://postgres@localhost:5432/postgres';
const SQL = 'COPY (SELECT 1) TO STDOUT';
const native = require('pg').native;
const { Client: JsClient } = require('pg');
function run(Client, label) {
return new Promise((resolve) => {
const client = new Client(CONN);
client.connect((connErr) => {
if (connErr) return resolve(console.log(`[${label}] connect failed: ${connErr.message}`));
client.query(SQL, (err, res) => {
console.log(`[${label}] err = ${err && err.message}`);
console.log(`[${label}] res = ${res && res.constructor.name}`);
try {
console.log(`[${label}] res.rows = ${JSON.stringify(res.rows)}`);
} catch (e) {
console.log(`[${label}] res.rows THREW ${e.constructor.name}: ${e.message}`);
}
client.end(() => resolve());
});
});
});
}
(async () => {
await run(JsClient, 'js ');
await run(native.Client, 'native');
})();
[js ] err = null
[js ] res = Result
[js ] res.rows = []
[native] err = null
[native] res = undefined
[native] res.rows THREW TypeError: Cannot read properties of undefined (reading 'rows')
What's happening
_emitResult (packages/pg-native/index.js:181) emits a result event for three statuses only — PGRES_TUPLES_OK, PGRES_COMMAND_OK, PGRES_EMPTY_QUERY. COPY_OUT/COPY_BOTH break out without one, and the default: branch sends the error to the client through _readError rather than to the query. Either way _read reaches emit('readyForQuery') (:248) with _resultCount === 0, and _onReadyForQuery (:490) passes that straight through:
const results = this._results // undefined: no 'result' event fired
cb(err, rows || [], results)
pg's NativeQuery then reads the falsy err as success and calls back with undefined (packages/pg/lib/native/query.js:121).
The case that actually bit us
COPY is just the convenient reproduction. The default: branch is the damaging one, because the server's error goes to the client's error event and never reaches the query — so you lose the result and the explanation. We spent a while on a Sequelize crash that looked like this:
TypeError: Cannot read properties of undefined (reading 'rows')
at Query.run (sequelize/lib/dialects/postgres/query.js:62)
The transaction was left aborted, every later statement failed with current transaction is aborted, commands ignored until end of transaction block, and the error explaining why was gone. On the JS driver the same server condition arrives as an ordinary error.
Possible fixes
- In
_onReadyForQuery, when there's no result and no_queryError, call back with an error instead ofundefined. - In the
default:branch, set_queryErrorso the server's message reaches the caller. - Or defensively in
pg'safter()(packages/pg/lib/native/query.js:121): treat a missingresultsas an error.
Happy to open a PR if one of those looks like the right shape.
Possibly related, and I can file it separately: at packages/pg-native/index.js:214, if pq.consumeInput() returns false, _read calls _readError() and returns before emit('readyForQuery') — the in-flight query's callback never fires and the promise hangs. I saw this by cancelling a running statement server-side (on CockroachDB; haven't rechecked on PostgreSQL).
- 主要言語
- JavaScript
- スター
- 13.2k
- フォーク
- 1.4k
- 平均マージ
- 6日 15時間
- マージ済み PR(30日)
- 6
コントリビューションガイド
このリポジトリのコントリビューションガイドは索引されていません
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
brianc/node-postgres のほかの issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
brianc/node-postgres#3770 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
brianc/node-postgres#3716 · コメント 1 件 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
brianc/node-postgres#3631 · コメント 1 件 ·
-
難易度 1/5 1〜3時間 初心者へのやさしさ 62/100
brianc/node-postgres#2857 ·
-
難易度 1/5 1時間未満 初心者へのやさしさ 68/100
brianc/node-postgres#2433 ·
brianc/node-postgres の issue をすべて見る
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 65/100
antfu-collective/icones#398 ·
-
ECmail.com オープン
難易度 1/5 1時間未満 初心者へのやさしさ 90/100
wesbos/burner-email-providers#554 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 65/100
radiantearth/stac-browser#1023 ·
-
HMR stops working オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
-
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
components-web-app/docs#92 ·