Skip to main content

RowDecoder

Extension point for turning one row's raw wire data into whatever shape a caller wants. Pass an instance of a RowDecoder subclass as the rowDecoder option to QueryOptions/ScriptExecuteOptions to take over row decoding entirely, for example to decode individual columns lazily, on first access, instead of eagerly decoding the whole row up front.

Two built-in subclasses cover the default behavior: ArrayRowDecoder (rows as Row arrays, the default) and ObjectRowDecoder (rows as <fieldName, value> objects, what objectRows: true has always produced). rowDecoder: 'array' / rowDecoder: 'object' select these by name without importing them.

Writing a custom decoder​

import { Connection, RowDecoder } from 'postgrejs';
import type { AnyParseFunction, DataMappingOptions, FieldInfo } from 'postgrejs';

class LazyRowDecoder extends RowDecoder {
decode(
parsers: AnyParseFunction[],
data: Buffer,
columnCount: number,
options: DataMappingOptions,
fields: FieldInfo[],
) {
// Retain `data` and decode columns only when they're actually read,
// instead of parsing every column up front.
return new LazyRow(parsers, data, columnCount, options, fields);
}
}

const connection = new Connection();
await connection.connect();
const result = await connection.query('select * from big_table', {
rowDecoder: new LazyRowDecoder(),
});
console.log(result.rowType); // 'custom'

Data-immutability contract​

data is never written to or reused by postgrejs once decode() is called with it, so it's safe to retain a reference to it (or a Buffer.subarray() of it) for as long as you want, e.g. to defer decoding a column until it's actually read. The only cost of doing so is memory: data is itself a zero-copy view into the socket's own read buffer (up to ~64KB), so holding onto even one row's data keeps that whole chunk alive, including every other row's bytes in it, until released.

rowType​

readonly rowType: 'array' | 'object' | 'custom'

What QueryResult.rowType/Cursor.rowType reports for rows this decoder produces: 'custom' on the base class, and overridden to 'array'/'object' on the two built-ins. A subclass that changes decode() but still produces the same shape its parent does (say, an ArrayRowDecoder subclass that only changes when columns are decoded, not their shape) inherits the right value without doing anything; a subclass whose decode() hands back something else (a lazy view, say) should override it to say so, since a caller reading rowType to decide how to index the result otherwise gets the wrong answer.

decode()​

abstract decode(parsers: AnyParseFunction[], data: Buffer, columnCount: number, options: DataMappingOptions, fields: FieldInfo[]): any

ArgumentTypeDescription
parsersAnyParseFunction[]One decode function per column, in column order
dataBufferThe row's own zero-copy buffer view
columnCountnumberNumber of columns in the row
optionsDataMappingOptionsThe query's data-mapping options (utcDates, fetchAsString, ...)
fieldsFieldInfo[]Column metadata, in column order

ArrayRowDecoder​

The default decoder. Decodes a row into a Row (array of values), same as parseRow(). Used when neither rowDecoder nor objectRows is set, and by rowDecoder: 'array'.

ObjectRowDecoder​

Decodes a row into a <fieldName, value> object, same as parseObjectRow(). Used by rowDecoder: 'object' and by the deprecated objectRows: true.

See also​