Skip to content

Errored QueryStream causes Client to throw on message receive #1674

@rkaw92

Description

@rkaw92

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 using knex)

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 like 8b0fb8cb-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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions