import { RowData } from "joist-core"; import pg from "pg"; //#region src/drivers/WireRowData.d.ts /** * A lazy wire-row {@link RowData} over raw Postgres `DataRow` payload bytes. * * Each query produces its own `WireRowData` — one query, one result; results are never combined * or appended across queries, and the payload is read-only after the query completes (entity * mutations go into `InstanceData.data`, never back into the row bytes). * * Rows are kept in their row-major wire format (`int16 fieldCount` + length-prefixed cells) as * zero-copy views over the parser's own immutable buffers: `addRow` records each DataRow's * `(bytes, offset, length)` by reference, deduping the socket chunk that consecutive rows * share. (Our pg-protocol patch guarantees message bytes are never rewritten — chunks parse in * place and straddling messages get a buffer of their own; see `patchPgProtocol.ts`.) A * `row × column` cell is decoded on first field access by scanning the row's length-prefixed * cells to the column's ordinal ("row-lazy" decode, see JS-ROW-STORE-DESIGN.md §3/C2). This is * deferred decoding of row-major data, not a columnar layout. * * Because chunks are whole socket reads, retaining any row pins its ~64KiB chunk (plus whatever * protocol frames share it); `finalize`'s compaction copies retained rows out into an * exact-size owned buffer when enough of the payload was dropped. * * Text-format cells go through the same active text parsers node-postgres would resolve for * the query (i.e. honoring pool/client `TypeOverrides`), so they parse identically to classic * rows. Binary-format cells decode via `wireBinaryParser`: wire bytes -> value directly for * default-parsed scalar types (no intermediate string), else rendered to pg's canonical text * and fed through the active text parser for parity. (Classic node-postgres cannot do binary * at all — it round-trips cells through a UTF-8 string, corrupting bytes >= 0x80.) * * Because decoding is deferred, a custom parser that throws will do so on first field access * (or `toRow`/`toRows`), not while awaiting the query; `id` and inheritance-discriminator cells * still decode during hydration. * * After hydration, `finalize` trims unused capacity and, when some rows were not retained (i.e. * their entities were already in the identity map), compacts the payload down to only the * retained rows, so retained memory tracks live entities rather than query history. * * Small results deliberately stay lazy — there is NO row-count threshold below which we * materialize to a `PojoRowData` instead. Measured (benchmark-rowdata-small.ts, 40-col rows): * for the typical sparse access pattern (~6 of 40 columns read), keeping the lazy result wins at * every size including a single row (n=1: 4.3µs vs 7.6µs; n=1000: 0.76ms vs 3.0ms), because * materialization eagerly decodes every column while lazy faults only what is read. Even full * column coverage no longer flips the winner: with binary decode and `#readCell`'s adaptive * scan cursor, reading all ~36 columns of every row measures ~parity with classic end-to-end * (benchmark-lazy-parsing.ts), and small finds (n <= 10) are statistically identical. */ declare class WireRowData implements RowData { #private; get rowCount(): number; /** The DataRow payload bytes currently indexed; drops when compaction discards rows. */ get payloadBytes(): number; /** The bytes currently held by payload chunks + the row-slice table, i.e. for benchmarks. */ get memoryBytes(): number; get(rowIndex: number, columnName: string): any; /** Materializes one row as a POJO, i.e. for debugging and differential tests; values are not cached. */ toRow(rowIndex: number): any; /** Materializes classic POJO rows, i.e. for `afterFind` observation or debugging; not cached. */ toRows(): any[]; /** * Resolves each column's decoder from the query's RowDescription. * * Binary-format fields decode through Joist's own binary registry only (see * `setBinaryTypeParser`) — `pg.types`/`TypeOverrides` are never consulted, and a column whose * oid has no registered binary parser fails the query here, before any rows arrive, rather * than guessing at a lossy decoding. Text-format fields (the `binary: false` escape hatch) * decode by utf8-slicing the cell into the active text parser, resolved via `getTypeParser` * (i.e. the pool/client `TypeOverrides` chain) or the global registry. */ setRowDescription(fields: Array<{ name: string; dataTypeID: number; format?: string; }>, getTypeParser?: (dataTypeID: number) => (value: any) => any): void; /** * Records one DataRow payload *by reference* (zero-copy); called synchronously from the wire * parser, whose patched buffer management guarantees the bytes are never rewritten. * * Consecutive rows usually share one socket chunk, so `bytes` is deduped against the last * chunk ref; retaining any row of a chunk pins the whole chunk, which `finalize`'s compaction * resolves by copying retained rows out when enough of the payload was dropped. */ addRow(bytes: Buffer, offset: number, payloadLength: number): void; /** Marks `rowIndex` as retained by a hydrated entity; unmarked rows can be compacted away. */ retain(rowIndex: number): void; /** * Trims unused capacity, and compacts down to only `retain`-ed rows when enough rows were not * retained to be worth the copy. * * Compaction exists to release pinned socket buffers to the GC: rows are zero-copy views into * whole ~64KiB chunks, so keeping any row alive pins its entire chunk — and unretained rows * are typically duplicates whose entities were *already in memory* (identity-map hits that * keep their original `rowData`), making the newly-arrived bytes dead weight. Copying the * retained rows into one exact-size owned buffer lets every chunk reference drop. * * The copy re-copies every retained byte, so it only pays off when it buys back a meaningful * fraction of the payload: we compact when the dropped rows hold more than 20% of the payload * bytes, and otherwise just trim, accepting the (bounded) leftover bytes. Called once after * hydration + sidecar reads (`_tags`, preload aggregates) are complete; retained entities keep * their original `rowIndex`, and un-compacted unretained rows simply remain readable-but-unused. */ finalize(): void; } declare function executeRowDataQuery(client: pg.PoolClient, sql: string, bindings: readonly any[], opts?: { binary?: boolean; }): Promise; /** Returns whether `client` supports the lazy row-data query path, before submitting anything. */ declare function isRowDataCapableClient(client: unknown): client is pg.PoolClient; //#endregion export { WireRowData, executeRowDataQuery, isRowDataCapableClient }; //# sourceMappingURL=WireRowData.d.mts.map