-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Description
Hi,
I have encountered a really bizarre issue when using knex
with pg
and query streams. A parameterized QueryStream which fails in the parsing stage (e.g. because of a column type / value type mismatch) breaks subsequent queries on the same connection - either by:
- throwing errors on incoming data packets from Postgres, terminating the Node process like this:
/home/thewanderer/Devel/Node/pg-prepared-statements/node_modules/pg/lib/client.js:212
self.activeQuery.handleRowDescription(msg)
^
TypeError: Cannot read property 'handleRowDescription' of null
at .<anonymous> (/home/thewanderer/Devel/Node/pg-prepared-statements/node_modules/pg/lib/client.js:212:21)
at emitOne (events.js:101:20)
at emit (events.js:188:7)
at Socket.<anonymous> (/home/thewanderer/Devel/Node/pg-prepared-statements/node_modules/pg/lib/connection.js:123:12)
at emitOne (events.js:96:13)
at Socket.emit (events.js:188:7)
at readableAddChunk (_stream_readable.js:177:18)
at Socket.Readable.push (_stream_readable.js:135:10)
at TCP.onread (net.js:542:20)
- or by messing with the output of subsequent queries - queries receive empty
result.rows
(this is much harder to reproduce and will not be covered here, but it occurs in one of our apps usingknex
)
Below are two ways to reproduce this issue, one using knex
and another simplified to bare pg
(which simulates how knex
interacts with pg
).
Test conditions:
- A table in the DB with a column of type "uuid":
CREATE TABLE events (id UUID);
- Note: if the table does not exist, this bug also occurs the same way.
[email protected]
[email protected]
[email protected]
(optional, for the knex-enabled test)PostgreSQL 9.6.7
(this probably does not matter)
'use strict';
function runKnex() {
const knex = require('knex');
const db = knex({
client: 'pg',
connection: {
host: process.env.PGHOST,
user: process.env.PGUSER,
password: process.env.PGPASSWORD,
database: process.env.PGDATABASE
},
pool: {
min: 1,
max: 1
}
});
const resultStream = db('events').select('*').where({ id: 'not a UUID' }).stream();
resultStream.on('data', function() {});
return db.raw('SELECT NOW() AS now').then(function(rows) {
console.log('now: %s', rows[0].now);
});
resultStream.on('error', function(error) {
console.error('stream error:', error);
});
}
function runPg() {
const Client = require('pg').Client;
const QueryStream = require('pg-query-stream');
const client = new Client();
return client.connect().then(function() {
const querySpec = new QueryStream('SELECT * FROM events WHERE id = $1', [ 'not a UUID' ]);
const rawStream = client.query(querySpec);
rawStream.on('error', function(error) {
console.log('stream error:', error);
rawStream.close();
});
rawStream.on('data', function() {});
client.query('SELECT NOW() AS now').then(function(result) {
console.log('now: %s', result.rows[0].now);
});
}).catch(function(error) {
console.error(error);
process.exit(5);
});
}
runPg();
// or alternatively:
// runKnex();
What should happen:
- A "stream error" should be displayed about invalid UUID input syntax (from the first query)
- The current date/time should be logged (from the second query)
What actually happens
- A stream error is displayed (from the first query)
- The process crashes with
TypeError: Cannot read property 'handleRowDescription' of null
, as described at the beginning of this bug report
It does not crash if:
- The
id
param is a valid UUID string like8b0fb8cb-b145-4e41-a847-dfe2f1a47aa1
(the second query then also runs OK) - We remove
rawStream.close();
from our stream error handler (but knex.js does it automatically)
Overall, I think some state machine inside pg.Client
breaks, and activeQuery
is set to null because of it, but I have not been able to pin-point exactly where or why it happens. It seems to be connected to closing the ReadableStream returned from client.query()
.
This is especially hard to debug when using pg
with a pool (such as knex.js's), because the error may go unnoticed until the same client object is re-acquired from the pool and used; this is why I opted for single-connection knex / pg examples above.