Skip to content

Reading an empty array ('{}') throws RangeError: Invalid array length and leaves the connection unusable #129

Closed

Description

@ledyba

Note: I tracked this down with the help of Claude Code (an AI coding assistant). I ran the repro below myself, and the analysis was checked against the ts-postgres and PostgreSQL sources linked here.

Version: ts-postgres 2.0.4 (the same code is still on master), Node.js v22.23.4, PostgreSQL 13

Repro

import { connect } from 'ts-postgres';

const client = await connect({ /* ... */ });
console.log((await client.query("SELECT '{a}'::text[] AS a")).rows); // [ [ [ 'a' ] ] ]

try {
  await client.query("SELECT '{}'::text[] AS a");
} catch (e) {
  console.log(e.message); // "Invalid array length"
}
console.log(client.closed); // false

await client.query('SELECT 1'); // rejects with "Stream not writable"

Log output:

ts-postgres: RangeError: Invalid array length
ts-postgres: Cancelled query due to an internal error

Expected: [ [ [] ] ], i.e. the column decodes to an empty array, and the connection stays usable.

Actual:

  1. The query rejects with RangeError: Invalid array length.
  2. The client destroys its stream (this.stream.destroy() in receive()), but client.closed stays false. A pool that checks closed to validate connections, like generic-pool with validate: c => !c.closed, keeps handing out the dead connection. Every later query on it fails with Stream not writable.

Cause

PostgreSQL represents an empty array as a zero-dimensional array (“an empty (zero-dimensional) array”, 8.15.3. Accessing Arrays), so in the binary format it arrives as ndim = 0 with no dimension headers. The decoder in readRowData computes

const dimCount = buffer.readInt32BE(offset) - 1;

which is -1 for an empty array. That is not 0, so it takes the multi-dimensional branch and calls new Array(dimCount), i.e. new Array(-1).

Suggested fix: when ndim is 0, return [] before branching on dimCount. Separately, it would help if closed became true after the stream is destroyed on an internal error.

Workaround: select the column through array_to_json(col). It comes back as json, so an empty array decodes to [] and NULL to null.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions