postgrejs 3.1.3 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/CHANGELOG.md +42 -1
  2. package/README.md +25 -9
  3. package/connection/cursor.d.ts +2 -1
  4. package/connection/cursor.js +6 -6
  5. package/connection/intl-connection.js +10 -14
  6. package/connection/prepared-statement.js +2 -1
  7. package/data-types/bool-type.js +0 -1
  8. package/data-types/box-type.js +0 -1
  9. package/data-types/bytea-type.js +5 -3
  10. package/data-types/char-type.js +2 -2
  11. package/data-types/circle-type.js +0 -1
  12. package/data-types/date-type.js +1 -2
  13. package/data-types/float4-type.js +2 -4
  14. package/data-types/float8-type.js +2 -5
  15. package/data-types/int2-type.js +0 -1
  16. package/data-types/int2-vector-type.js +3 -2
  17. package/data-types/int4-type.js +0 -1
  18. package/data-types/int8-type.js +0 -1
  19. package/data-types/json-type.js +2 -2
  20. package/data-types/jsonb-type.js +6 -5
  21. package/data-types/lseg-type.js +0 -1
  22. package/data-types/numeric-type.js +2 -4
  23. package/data-types/oid-type.js +0 -1
  24. package/data-types/oid-vector-type.js +3 -2
  25. package/data-types/point-type.js +0 -1
  26. package/data-types/time-type.js +1 -2
  27. package/data-types/timestamp-type.js +13 -3
  28. package/data-types/timestamptz-type.js +27 -10
  29. package/data-types/uuid-type.js +1 -2
  30. package/data-types/varchar-type.js +2 -2
  31. package/index.d.ts +1 -0
  32. package/index.js +1 -0
  33. package/interfaces/command-result.d.ts +3 -2
  34. package/interfaces/data-type.d.ts +0 -1
  35. package/interfaces/query-options.d.ts +12 -0
  36. package/interfaces/script-execute-options.d.ts +12 -0
  37. package/package.json +14 -2
  38. package/protocol/backend.js +6 -6
  39. package/protocol/buffer-reader.d.ts +7 -12
  40. package/protocol/buffer-reader.js +11 -58
  41. package/protocol/frontend.js +86 -96
  42. package/protocol/pg-socket.d.ts +6 -33
  43. package/protocol/pg-socket.js +19 -74
  44. package/protocol/pgoutput.js +2 -2
  45. package/protocol/smart-buffer.d.ts +11 -29
  46. package/protocol/smart-buffer.js +19 -162
  47. package/types.d.ts +13 -1
  48. package/util/bigint-methods.d.ts +0 -1
  49. package/util/bigint-methods.js +0 -22
  50. package/util/decode-binaryarray.d.ts +1 -1
  51. package/util/decode-binaryarray.js +7 -7
  52. package/util/encode-binaryarray.js +6 -6
  53. package/util/fast-parsefloat.d.ts +15 -0
  54. package/util/fast-parsefloat.js +141 -0
  55. package/util/get-parsers.js +4 -8
  56. package/util/parse-datetime.js +7 -0
  57. package/util/parse-pg-timestamp.d.ts +31 -0
  58. package/util/parse-pg-timestamp.js +199 -0
  59. package/util/parse-row.js +120 -0
  60. package/util/row-decoder.d.ts +49 -0
  61. package/util/row-decoder.js +63 -0
package/CHANGELOG.md CHANGED
@@ -1,6 +1,47 @@
1
1
  ## Changelog
2
2
 
3
- ### [v3.1.3](https://github.com/panates/postgrejs/compare/v3.1.2...v3.1.3) -
3
+ ### [v3.3.0](https://github.com/panates/postgrejs/compare/v3.2.0...v3.3.0) -
4
+
5
+ #### 🚀 New Features
6
+
7
+ - feat: add first-class Bun support with dedicated benchmark suite @Eray Hanoğlu
8
+ - feat: add pluggable RowDecoder for custom row decoding @Eray Hanoğlu
9
+
10
+ #### 🪲 Fixes
11
+
12
+ - fix: return the actual instant when decoding timestamptz from binary @Eray Hanoğlu
13
+ - fix: point coverage badge at dev instead of the abandoned master branch @Eray Hanoğlu
14
+
15
+ #### 📖 Documentation Changes
16
+
17
+ - docs: refresh both benchmark reports on 3.3.0 @Eray Hanoğlu
18
+ - docs: document Bun support and the Bun benchmark report in the README @Eray Hanoğlu
19
+ - docs: cover the Bun benchmarks in the README's speed section too @Eray Hanoğlu
20
+ - docs: document RowDecoder's data-immutability contract @Eray Hanoğlu
21
+
22
+ #### ⚡️ Performance Improvments
23
+
24
+ - perf: decode timestamps from the wire bytes instead of a string @Eray Hanoğlu
25
+ - perf: build object rows from a compiled literal instead of per-column assignment @Eray Hanoğlu
26
+ - perf: decode float and numeric text without materialising a string @Eray Hanoğlu
27
+ - perf: decode PostgreSQL's own timestamp text shape without the engine parser @Eray Hanoğlu
28
+ - perf: write protocol messages immediately instead of deferring a tick @Eray Hanoğlu
29
+
30
+ #### 💬 General Changes
31
+
32
+ - bench: report near-ties as ties instead of ranking them @Eray Hanoğlu
33
+ - feat!: give decodeBinary the value's length, not a slice of it @Eray Hanoğlu
34
+ - bench: stop measuring type-declaration policy in the Extended Query scenarios @Eray Hanoğlu
35
+ - bench: stop one stalled repeat from making every result a tie @Eray Hanoğlu
36
+ - bench: document why concurrent Simple Query varies its literal @Eray Hanoğlu
37
+
38
+ ### [v3.2.0](https://github.com/panates/postgrejs/compare/v3.1.3...v3.2.0) - 12 September 2026
39
+
40
+ #### 🛠 Refactoring and Updates
41
+
42
+ - refactor: build SmartBuffer/BufferReader on top of flexy-buffer @Eray Hanoğlu
43
+
44
+ ### [v3.1.3](https://github.com/panates/postgrejs/compare/v3.1.2...v3.1.3) - 11 September 2026
4
45
 
5
46
  #### 🪲 Fixes
6
47
 
package/README.md CHANGED
@@ -12,7 +12,7 @@
12
12
 
13
13
  ## Why PostgreJS?
14
14
 
15
- **PostgreJS** is a PostgreSQL driver for Node.js built from the wire protocol up - no `libpq`, no native bindings,
15
+ **PostgreJS** is a PostgreSQL driver for Node.js and Bun built from the wire protocol up - no `libpq`, no native bindings,
16
16
  just TypeScript talking directly to PostgreSQL. That from-scratch design is also what makes it fast and light: every
17
17
  byte on the wire is handled by code written for exactly that purpose, with a binary-first protocol, shared buffers,
18
18
  and row/column decoding pipelines built to avoid unnecessary allocation, instead of generic string plumbing bolted
@@ -22,11 +22,14 @@ onto a client meant for text.
22
22
 
23
23
  The numbers back it up. In PostgreJS's own benchmark suite - run head-to-head against
24
24
  [`pg`](https://github.com/brianc/node-postgres) (node-postgres) and [`postgres`](https://github.com/porsager/postgres)
25
- (postgres.js) on identical workloads - PostgreJS opens a connection up to **3x faster** than postgres.js and
26
- **2x faster** than pg, pushes pooled queries through up to **6.6x faster** than postgres.js, and fetches large
27
- result sets nearly **5x faster** than pg. It's also the only one of the three with a complete binary wire protocol
28
- across every data type, rather than falling back to text for most of them. See
29
- [`doc/BENCHMARKS.md`](doc/BENCHMARKS.md) for the full methodology and every scenario.
25
+ (postgres.js) on identical workloads - PostgreJS opens a connection up to **3x faster** than postgres.js, pushes
26
+ pooled queries through up to **6.6x faster** than postgres.js, and fetches large result sets nearly **5x faster**
27
+ than pg. It's also the only one of the three Node drivers with a complete binary wire protocol across every data
28
+ type, rather than falling back to text for most of them. See [`doc/BENCHMARKS.md`](doc/BENCHMARKS.md) for the full
29
+ methodology and every scenario.
30
+
31
+ The same suite runs under Bun, where Bun's own built-in `Bun.sql` joins the comparison as a fourth client - see
32
+ [`doc/BENCHMARKS-bun.md`](doc/BENCHMARKS-bun.md).
30
33
 
31
34
  ### 🪶 Small Footprint
32
35
 
@@ -223,14 +226,27 @@ concurrency, each library run through its own idiomatic fast path. The numbers t
223
226
  machine via `npm run bench` against the repo's own
224
227
  `docker/docker-compose.yml` Postgres instance; see [`benchmark/README.md`](./benchmark/README.md) for details.
225
228
 
229
+ The same suite runs under Bun via `npm run bench:bun`, where it also measures Bun's own built-in `Bun.sql` client as
230
+ a fourth library. Those results live in [`doc/BENCHMARKS-bun.md`](doc/BENCHMARKS-bun.md) and are deliberately kept
231
+ in a separate report rather than merged into the Node one: GC instrumentation, cursor streaming and binary-format
232
+ control all differ enough between the two runtimes that a single table would conflate a library difference with a
233
+ runtime difference.
234
+
235
+ Both reports list libraries in a fixed order rather than fastest-first, and mark every result statistically tied
236
+ with the leader rather than bolding a lone winner - several scenarios separate the leading drivers by about a
237
+ percent, which is less than the run-to-run spread of the measurement itself.
238
+
226
239
  ## Support
227
240
 
228
241
  You can report bugs and discuss features on the [GitHub issues](https://github.com/panates/postgrejs/issues) page When
229
242
  you open an issue please provide version of NodeJS and PostgreSQL server.
230
243
 
231
- ## Node Compatibility
244
+ ## Runtime Compatibility
232
245
 
233
- - node >= 20.x
246
+ - Node.js >= 20.x
247
+ - Bun - the same test suite runs under Bun in CI against PostgreSQL 18 on every push, so Bun is a supported target
248
+ rather than an untested coincidence. (Coverage is collected on the Node matrix only: `c8` instruments V8's
249
+ coverage APIs, which JavaScriptCore doesn't have.)
234
250
 
235
251
  ## License
236
252
 
@@ -242,5 +258,5 @@ PostgreJS is available under the [BSD 3-Clause](LICENSE) license.
242
258
  [downloads-url]: https://npmjs.org/package/postgrejs
243
259
  [ci-test-image]: https://github.com/panates/postgrejs/actions/workflows/test.yml/badge.svg
244
260
  [ci-test-url]: https://github.com/panates/postgrejs/actions/workflows/test.yml
245
- [coveralls-image]: https://img.shields.io/coveralls/panates/postgrejs/master.svg
261
+ [coveralls-image]: https://img.shields.io/coveralls/panates/postgrejs/dev.svg
246
262
  [coveralls-url]: https://coveralls.io/r/panates/postgrejs
@@ -9,12 +9,13 @@ export declare class Cursor extends SafeEventEmitter implements AsyncDisposable
9
9
  private readonly _portal;
10
10
  private readonly _parsers;
11
11
  private readonly _queryOptions;
12
+ private readonly _rowDecoder;
12
13
  private _taskQueue;
13
14
  private _rows;
14
15
  private _closed;
15
16
  readonly fields: FieldInfo[];
16
17
  constructor(statement: PreparedStatement, portal: Portal, fields: FieldInfo[], parsers: AnyParseFunction[], queryOptions: QueryOptions);
17
- get rowType(): 'array' | 'object';
18
+ get rowType(): 'array' | 'object' | 'custom';
18
19
  get isClosed(): boolean;
19
20
  next(): Promise<Maybe<Row>>;
20
21
  fetch(nRows: number): Promise<Row[]>;
@@ -1,12 +1,13 @@
1
1
  import DoublyLinked from 'doublylinked';
2
2
  import { TaskQueue } from 'power-tasks';
3
3
  import { SafeEventEmitter } from '../safe-event-emitter.js';
4
- import { parseObjectRow, parseRow } from '../util/parse-row.js';
4
+ import { resolveRowDecoder, resolveRowType } from '../util/row-decoder.js';
5
5
  export class Cursor extends SafeEventEmitter {
6
6
  _statement;
7
7
  _portal;
8
8
  _parsers;
9
9
  _queryOptions;
10
+ _rowDecoder;
10
11
  _taskQueue = new TaskQueue({ concurrency: 1 });
11
12
  _rows = new DoublyLinked();
12
13
  _closed = false;
@@ -18,9 +19,10 @@ export class Cursor extends SafeEventEmitter {
18
19
  this._parsers = parsers;
19
20
  this._queryOptions = queryOptions;
20
21
  this.fields = fields;
22
+ this._rowDecoder = resolveRowDecoder(queryOptions);
21
23
  }
22
24
  get rowType() {
23
- return this._queryOptions.objectRows ? 'object' : 'array';
25
+ return resolveRowType(this._queryOptions);
24
26
  }
25
27
  get isClosed() {
26
28
  return this._closed;
@@ -71,16 +73,14 @@ export class Cursor extends SafeEventEmitter {
71
73
  if (r && r.rows && r.rows.length) {
72
74
  const rows = r.rows;
73
75
  if (this._parsers) {
74
- const objectRows = queryOptions.objectRows;
75
76
  const fields = this.fields;
76
77
  const parsers = this._parsers;
78
+ const rowDecoder = this._rowDecoder;
77
79
  const rowLen = rows.length;
78
80
  let i;
79
81
  for (i = 0; i < rowLen; i++) {
80
82
  const { data, columnCount } = rows[i];
81
- rows[i] = objectRows
82
- ? parseObjectRow(parsers, data, columnCount, this._queryOptions, fields)
83
- : parseRow(parsers, data, columnCount, this._queryOptions);
83
+ rows[i] = rowDecoder.decode(parsers, data, columnCount, this._queryOptions, fields);
84
84
  }
85
85
  }
86
86
  this._rows.push(...rows);
@@ -7,7 +7,7 @@ import { SafeEventEmitter } from '../safe-event-emitter.js';
7
7
  import { getConnectionConfig } from '../util/connection-config.js';
8
8
  import { escapeLiteral } from '../util/escape-literal.js';
9
9
  import { getParsers } from '../util/get-parsers.js';
10
- import { parseObjectRow, parseRow } from '../util/parse-row.js';
10
+ import { resolveRowDecoder, resolveRowType } from '../util/row-decoder.js';
11
11
  import { wrapRowDescription } from '../util/wrap-row-description.js';
12
12
  import { CopyFromStream, CopyToStream } from './copy-stream.js';
13
13
  const DataFormat = Protocol.DataFormat;
@@ -398,6 +398,7 @@ export class IntlConnection extends SafeEventEmitter {
398
398
  let fields;
399
399
  let error;
400
400
  const typeMap = options.typeMap || GlobalTypeMap;
401
+ const rowDecoder = resolveRowDecoder(options);
401
402
  this.runningQueryCount++;
402
403
  return await this.socket.sendQueryMessage(sql, (code, msg, done) => {
403
404
  switch (code) {
@@ -426,9 +427,7 @@ export class IntlConnection extends SafeEventEmitter {
426
427
  break;
427
428
  case Protocol.BackendMessageCode.DataRow:
428
429
  {
429
- const row = options.objectRows && current.fields
430
- ? parseObjectRow(parsers, msg.data, msg.columnCount, options, current.fields)
431
- : parseRow(parsers, msg.data, msg.columnCount, options);
430
+ const row = rowDecoder.decode(parsers, msg.data, msg.columnCount, options, current.fields);
432
431
  if (cb)
433
432
  cb('row', row);
434
433
  current.rows = current.rows || [];
@@ -446,8 +445,7 @@ export class IntlConnection extends SafeEventEmitter {
446
445
  if (timingEnabled)
447
446
  current.executeTime = performance.now() - currentStart;
448
447
  if (current.rows)
449
- current.rowType =
450
- options.objectRows && current.fields ? 'object' : 'array';
448
+ current.rowType = resolveRowType(options);
451
449
  result.results.push(current);
452
450
  if (cb)
453
451
  cb('command-complete', current);
@@ -497,6 +495,7 @@ export class IntlConnection extends SafeEventEmitter {
497
495
  let resultFields;
498
496
  let commandTag;
499
497
  let error;
498
+ const rowDecoder = resolveRowDecoder(options);
500
499
  this.runningQueryCount++;
501
500
  await this.socket
502
501
  .sendExtendedQueryMessages({
@@ -516,7 +515,7 @@ export class IntlConnection extends SafeEventEmitter {
516
515
  parsers = getParsers(typeMap, msg.fields);
517
516
  resultFields = wrapRowDescription(typeMap, msg.fields, options.columnFormat || DEFAULT_COLUMN_FORMAT);
518
517
  result.fields = resultFields;
519
- result.rowType = options.objectRows ? 'object' : 'array';
518
+ result.rowType = resolveRowType(options);
520
519
  break;
521
520
  case Protocol.BackendMessageCode.DataRow:
522
521
  rows.push(msg);
@@ -547,9 +546,7 @@ export class IntlConnection extends SafeEventEmitter {
547
546
  const l = rows.length;
548
547
  let i;
549
548
  for (i = 0; i < l; i++) {
550
- rows[i] = options.objectRows
551
- ? parseObjectRow(parsers, rows[i].data, rows[i].columnCount, options, resultFields)
552
- : parseRow(parsers, rows[i].data, rows[i].columnCount, options);
549
+ rows[i] = rowDecoder.decode(parsers, rows[i].data, rows[i].columnCount, options, resultFields);
553
550
  }
554
551
  }
555
552
  if (result.command === 'DELETE' ||
@@ -636,6 +633,7 @@ export class IntlConnection extends SafeEventEmitter {
636
633
  let resultFields;
637
634
  let commandTag;
638
635
  let error;
636
+ const rowDecoder = resolveRowDecoder(options);
639
637
  if (cachedFields) {
640
638
  const columnFormat = options.columnFormat != null
641
639
  ? options.columnFormat
@@ -664,7 +662,7 @@ export class IntlConnection extends SafeEventEmitter {
664
662
  });
665
663
  }
666
664
  result.fields = resultFields;
667
- result.rowType = options.objectRows ? 'object' : 'array';
665
+ result.rowType = resolveRowType(options);
668
666
  }
669
667
  this.runningQueryCount++;
670
668
  await this.socket
@@ -712,9 +710,7 @@ export class IntlConnection extends SafeEventEmitter {
712
710
  const l = rows.length;
713
711
  let i;
714
712
  for (i = 0; i < l; i++) {
715
- rows[i] = options.objectRows
716
- ? parseObjectRow(parsers, rows[i].data, rows[i].columnCount, options, resultFields)
717
- : parseRow(parsers, rows[i].data, rows[i].columnCount, options);
713
+ rows[i] = rowDecoder.decode(parsers, rows[i].data, rows[i].columnCount, options, resultFields);
718
714
  }
719
715
  }
720
716
  if (result.command === 'DELETE' ||
@@ -4,6 +4,7 @@ import { Protocol } from '../protocol/protocol.js';
4
4
  import { SafeEventEmitter } from '../safe-event-emitter.js';
5
5
  import { withAbortSignal } from '../util/abort-signal.js';
6
6
  import { getParsers } from '../util/get-parsers.js';
7
+ import { resolveRowType } from '../util/row-decoder.js';
7
8
  import { wrapRowDescription } from '../util/wrap-row-description.js';
8
9
  import { Cursor } from './cursor.js';
9
10
  import { getIntlConnection } from './intl-connection.js';
@@ -124,7 +125,7 @@ export class PreparedStatement extends SafeEventEmitter {
124
125
  const result = {
125
126
  command: undefined,
126
127
  fields: resultFields,
127
- rowType: options.objectRows ? 'object' : 'array',
128
+ rowType: resolveRowType(options),
128
129
  cursor: new Cursor(this, portal, resultFields, parsers, options),
129
130
  };
130
131
  this._refCount++;
@@ -3,7 +3,6 @@ export const BoolType = {
3
3
  name: 'bool',
4
4
  oid: DataTypeOIDs.bool,
5
5
  jsType: 'boolean',
6
- fixedBinarySize: 1,
7
6
  // 't'/'f', the form decodeText above reads and the server itself
8
7
  // emits - encodeText and decodeText are a public pair and have to stay
9
8
  // each other's inverse.
@@ -7,7 +7,6 @@ export const BoxType = {
7
7
  oid: DataTypeOIDs.box,
8
8
  jsType: 'object',
9
9
  arraySeparator: ';',
10
- fixedBinarySize: 32,
11
10
  encodeText(v) {
12
11
  return `(${v.x1},${v.y1}),(${v.x2},${v.y2})`;
13
12
  },
@@ -9,10 +9,12 @@ export const ByteaType = {
9
9
  return '\\x' + v.toString('hex');
10
10
  },
11
11
  encodeBinary(buf, v) {
12
- buf.writeBuffer(v);
12
+ buf.writeBytes(v);
13
13
  },
14
- decodeBinary(v, offset = 0) {
15
- return offset ? v.subarray(offset) : v;
14
+ decodeBinary(v, offset = 0, len) {
15
+ // A view, not a copy - the value handed back is the caller's own
16
+ // window onto the wire bytes, which is what bytea has always returned.
17
+ return v.subarray(offset, offset + len);
16
18
  },
17
19
  decodeText: parseBytea,
18
20
  decodeTextBuffer: parseByteaBuffer,
@@ -9,8 +9,8 @@ export const CharType = {
9
9
  encodeBinary(buf, v) {
10
10
  buf.writeString((v ? '' + v : ' ')[0], 'utf8');
11
11
  },
12
- decodeBinary(v, offset = 0) {
13
- return v.toString('utf8', offset);
12
+ decodeBinary(v, offset = 0, len) {
13
+ return v.toString('utf8', offset, offset + len);
14
14
  },
15
15
  decodeText(v) {
16
16
  return v;
@@ -7,7 +7,6 @@ export const CircleType = {
7
7
  name: 'circle',
8
8
  oid: DataTypeOIDs.circle,
9
9
  jsType: 'object',
10
- fixedBinarySize: 24,
11
10
  encodeText(v) {
12
11
  return `<(${v.x},${v.y}),${v.r}>`;
13
12
  },
@@ -6,7 +6,6 @@ export const DateType = {
6
6
  name: 'date',
7
7
  oid: DataTypeOIDs.date,
8
8
  jsType: 'Date',
9
- fixedBinarySize: 4,
10
9
  encodeText(v, options) {
11
10
  return formatDate(v, options);
12
11
  },
@@ -30,7 +29,7 @@ export const DateType = {
30
29
  const t = Math.trunc(n + Number.EPSILON);
31
30
  buf.writeInt32BE(t);
32
31
  },
33
- decodeBinary(v, offset = 0, options) {
32
+ decodeBinary(v, offset = 0, _len, options) {
34
33
  const fetchAsString = options.fetchAsString?.includes(DataTypeOIDs.date);
35
34
  const t = v.readInt32BE(offset);
36
35
  if (t === 0x7fffffff)
@@ -1,9 +1,9 @@
1
1
  import { DataTypeOIDs } from '../constants.js';
2
+ import { fastParseFloatBuffer } from '../util/fast-parsefloat.js';
2
3
  export const Float4Type = {
3
4
  name: 'float4',
4
5
  oid: DataTypeOIDs.float4,
5
6
  jsType: 'number',
6
- fixedBinarySize: 4,
7
7
  encodeText(v) {
8
8
  return '' + v;
9
9
  },
@@ -14,9 +14,7 @@ export const Float4Type = {
14
14
  return v.readFloatBE(offset);
15
15
  },
16
16
  decodeText: parseFloat,
17
- decodeTextBuffer(buf, offset, len) {
18
- return parseFloat(buf.toString('latin1', offset, offset + len));
19
- },
17
+ decodeTextBuffer: fastParseFloatBuffer,
20
18
  isType(v) {
21
19
  return typeof v === 'number';
22
20
  },
@@ -1,9 +1,9 @@
1
1
  import { DataTypeOIDs } from '../constants.js';
2
+ import { fastParseFloatBuffer } from '../util/fast-parsefloat.js';
2
3
  export const Float8Type = {
3
4
  name: 'float8',
4
5
  oid: DataTypeOIDs.float8,
5
6
  jsType: 'number',
6
- fixedBinarySize: 8,
7
7
  // Infinity/NaN stringify to the words PostgreSQL itself uses.
8
8
  encodeText(v) {
9
9
  return '' + v;
@@ -15,10 +15,7 @@ export const Float8Type = {
15
15
  return v.readDoubleBE(offset);
16
16
  },
17
17
  decodeText: parseFloat,
18
- // See float4-type.ts's decodeTextBuffer comment - same rationale.
19
- decodeTextBuffer(buf, offset, len) {
20
- return parseFloat(buf.toString('latin1', offset, offset + len));
21
- },
18
+ decodeTextBuffer: fastParseFloatBuffer,
22
19
  isType(v) {
23
20
  return typeof v === 'number';
24
21
  },
@@ -4,7 +4,6 @@ export const Int2Type = {
4
4
  name: 'int2',
5
5
  oid: DataTypeOIDs.int2,
6
6
  jsType: 'number',
7
- fixedBinarySize: 2,
8
7
  encodeText(v) {
9
8
  return '' + v;
10
9
  },
@@ -15,8 +15,9 @@ export const Int2VectorType = {
15
15
  encodeCalculateDim(v) {
16
16
  return [v.length];
17
17
  },
18
- decodeBinary(v) {
19
- return decodeBinaryArray(v, 0, b => b.readInt16BE()) || undefined;
18
+ decodeBinary(v, offset = 0) {
19
+ return (decodeBinaryArray(v, offset, (b, off) => b.readInt16BE(off)) ||
20
+ undefined);
20
21
  },
21
22
  decodeText(str) {
22
23
  return str.split(' ').map(fastParseInt);
@@ -4,7 +4,6 @@ export const Int4Type = {
4
4
  name: 'int4',
5
5
  oid: DataTypeOIDs.int4,
6
6
  jsType: 'number',
7
- fixedBinarySize: 4,
8
7
  encodeText(v) {
9
8
  return '' + v;
10
9
  },
@@ -6,7 +6,6 @@ export const Int8Type = {
6
6
  name: 'int8',
7
7
  oid: DataTypeOIDs.int8,
8
8
  jsType: 'BigInt',
9
- fixedBinarySize: 8,
10
9
  encodeText(v) {
11
10
  return '' + v;
12
11
  },
@@ -3,8 +3,8 @@ export const JsonType = {
3
3
  name: 'json',
4
4
  oid: DataTypeOIDs.json,
5
5
  jsType: 'string',
6
- decodeBinary(v, offset = 0, options) {
7
- const content = v.toString('utf8', offset);
6
+ decodeBinary(v, offset = 0, len, options) {
7
+ const content = v.toString('utf8', offset, offset + len);
8
8
  const fetchAsString = options.fetchAsString?.includes(DataTypeOIDs.json);
9
9
  if (fetchAsString)
10
10
  return content;
@@ -1,5 +1,4 @@
1
1
  import { DataTypeOIDs } from '../constants.js';
2
- import { BufferReader } from '../protocol/buffer-reader.js';
3
2
  export const JsonbType = {
4
3
  name: 'jsonb',
5
4
  oid: DataTypeOIDs.jsonb,
@@ -15,12 +14,14 @@ export const JsonbType = {
15
14
  buf.writeUInt8(1);
16
15
  buf.writeString(JsonbType.encodeText(v, {}), 'utf8');
17
16
  },
18
- decodeBinary(v, offset = 0, options) {
19
- const buf = new BufferReader(offset ? v.subarray(offset) : v);
20
- if (buf.readUInt8() !== 1)
17
+ decodeBinary(v, offset = 0, len, options) {
18
+ // jsonb's binary form is a one-byte version followed by the JSON text,
19
+ // which carries no terminator of its own - so its end is whatever
20
+ // `len` says, not the end of the buffer it happens to sit in.
21
+ if (v[offset] !== 1)
21
22
  throw new Error('Unexpected Jsonb version value in header');
23
+ const content = v.toString('utf8', offset + 1, offset + len);
22
24
  const fetchAsString = options.fetchAsString?.includes(DataTypeOIDs.jsonb);
23
- const content = buf.readLString(buf.length - buf.offset);
24
25
  if (fetchAsString)
25
26
  return content;
26
27
  return content ? JSON.parse(content) : undefined;
@@ -7,7 +7,6 @@ export const LsegType = {
7
7
  name: 'lseg',
8
8
  oid: DataTypeOIDs.lseg,
9
9
  jsType: 'object',
10
- fixedBinarySize: 32,
11
10
  encodeText(v) {
12
11
  return `[(${v.x1},${v.y1}),(${v.x2},${v.y2})]`;
13
12
  },
@@ -1,4 +1,5 @@
1
1
  import { DataTypeOIDs } from '../constants.js';
2
+ import { fastParseFloatBuffer } from '../util/fast-parsefloat.js';
2
3
  const NUMERIC_NEG = 0x4000;
3
4
  const NUMERIC_NAN = 0xc000;
4
5
  const NUMERIC_PINF = 0xd000;
@@ -109,10 +110,7 @@ export const NumericType = {
109
110
  return '' + n;
110
111
  },
111
112
  decodeText: parseFloat,
112
- // See float4-type.ts's decodeTextBuffer comment - same rationale.
113
- decodeTextBuffer(buf, offset, len) {
114
- return parseFloat(buf.toString('latin1', offset, offset + len));
115
- },
113
+ decodeTextBuffer: fastParseFloatBuffer,
116
114
  isType(v) {
117
115
  return typeof v === 'number';
118
116
  },
@@ -4,7 +4,6 @@ export const OidType = {
4
4
  name: 'oid',
5
5
  oid: DataTypeOIDs.oid,
6
6
  jsType: 'number',
7
- fixedBinarySize: 4,
8
7
  encodeText(v) {
9
8
  return '' + v;
10
9
  },
@@ -21,8 +21,9 @@ export const OidVectorType = {
21
21
  encodeCalculateDim(v) {
22
22
  return [v.length];
23
23
  },
24
- decodeBinary(v) {
25
- return decodeBinaryArray(v, 0, b => b.readUInt32BE()) || undefined;
24
+ decodeBinary(v, offset = 0) {
25
+ return (decodeBinaryArray(v, offset, (b, off) => b.readUInt32BE(off)) ||
26
+ undefined);
26
27
  },
27
28
  decodeText(str) {
28
29
  return str.split(' ').map(fastParseInt);
@@ -5,7 +5,6 @@ export const PointType = {
5
5
  name: 'point',
6
6
  oid: DataTypeOIDs.point,
7
7
  jsType: 'object',
8
- fixedBinarySize: 16,
9
8
  encodeText(v) {
10
9
  return `(${v.x},${v.y})`;
11
10
  },
@@ -6,7 +6,6 @@ export const TimeType = {
6
6
  name: 'time',
7
7
  oid: DataTypeOIDs.time,
8
8
  jsType: 'string',
9
- fixedBinarySize: 8,
10
9
  encodeText(v, options) {
11
10
  return formatTime(v, options);
12
11
  },
@@ -25,7 +24,7 @@ export const TimeType = {
25
24
  buf.writeInt32BE(hi);
26
25
  buf.writeUInt32BE(lo);
27
26
  },
28
- decodeBinary(v, offset = 0, options) {
27
+ decodeBinary(v, offset = 0, _len, options) {
29
28
  const fetchAsString = options.fetchAsString?.includes(DataTypeOIDs.time);
30
29
  const hi = v.readInt32BE(offset);
31
30
  const lo = v.readUInt32BE(offset + 4);
@@ -1,13 +1,13 @@
1
1
  import { DataTypeOIDs } from '../constants.js';
2
2
  import { formatTimestamp } from '../util/format-datetime.js';
3
3
  import { parseDateTime } from '../util/parse-datetime.js';
4
+ import { parsePgTimestampBuffer } from '../util/parse-pg-timestamp.js';
4
5
  const timeShift = 946684800000;
5
6
  const timeMul = 4294967296;
6
7
  export const TimestampType = {
7
8
  name: 'timestamp',
8
9
  oid: DataTypeOIDs.timestamp,
9
10
  jsType: 'Date',
10
- fixedBinarySize: 8,
11
11
  encodeText(v, options) {
12
12
  return formatTimestamp(v, options);
13
13
  },
@@ -36,7 +36,7 @@ export const TimestampType = {
36
36
  buf.writeInt32BE(hi);
37
37
  buf.writeUInt32BE(lo);
38
38
  },
39
- decodeBinary(v, offset = 0, options) {
39
+ decodeBinary(v, offset = 0, _len, options) {
40
40
  const fetchAsString = options.fetchAsString?.includes(DataTypeOIDs.timestamp);
41
41
  const hi = v.readInt32BE(offset);
42
42
  const lo = v.readUInt32BE(offset + 4);
@@ -56,8 +56,18 @@ export const TimestampType = {
56
56
  return v;
57
57
  return parseDateTime(v, options.utcDates);
58
58
  },
59
- // See date-type.ts's decodeTextBuffer comment - same rationale.
59
+ // Reads PostgreSQL's own timestamp shape straight from the wire bytes -
60
+ // see timestamptz-type.ts's decodeTextBuffer for why. The two guards are
61
+ // what decodeText()/parseDateTime() would have applied anyway: with
62
+ // fetchAsString the raw string is the answer, and with utcDates the
63
+ // string has to go through the regex path that reads it as UTC.
60
64
  decodeTextBuffer(buf, offset, len, options) {
65
+ if (!options.utcDates &&
66
+ !options.fetchAsString?.includes(DataTypeOIDs.timestamp)) {
67
+ const d = parsePgTimestampBuffer(buf, offset, offset + len);
68
+ if (d !== undefined)
69
+ return d;
70
+ }
61
71
  return TimestampType.decodeText(buf.toString('latin1', offset, offset + len), options);
62
72
  },
63
73
  isType(v) {