postgrejs 3.1.2 → 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.
- package/CHANGELOG.md +48 -1
- package/README.md +25 -9
- package/connection/cursor.d.ts +2 -1
- package/connection/cursor.js +6 -6
- package/connection/intl-connection.d.ts +7 -0
- package/connection/intl-connection.js +18 -14
- package/connection/prepared-statement.js +2 -1
- package/data-types/bool-type.js +0 -1
- package/data-types/box-type.js +0 -1
- package/data-types/bytea-type.js +5 -3
- package/data-types/char-type.js +2 -2
- package/data-types/circle-type.js +0 -1
- package/data-types/date-type.js +1 -2
- package/data-types/float4-type.js +2 -4
- package/data-types/float8-type.js +2 -5
- package/data-types/int2-type.js +0 -1
- package/data-types/int2-vector-type.js +3 -2
- package/data-types/int4-type.js +0 -1
- package/data-types/int8-type.js +0 -1
- package/data-types/json-type.js +2 -2
- package/data-types/jsonb-type.js +6 -5
- package/data-types/lseg-type.js +0 -1
- package/data-types/numeric-type.js +2 -4
- package/data-types/oid-type.js +0 -1
- package/data-types/oid-vector-type.js +3 -2
- package/data-types/point-type.js +0 -1
- package/data-types/time-type.js +1 -2
- package/data-types/timestamp-type.js +13 -3
- package/data-types/timestamptz-type.js +27 -10
- package/data-types/uuid-type.js +1 -2
- package/data-types/varchar-type.js +2 -2
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/interfaces/command-result.d.ts +3 -2
- package/interfaces/data-type.d.ts +0 -1
- package/interfaces/query-options.d.ts +12 -0
- package/interfaces/script-execute-options.d.ts +12 -0
- package/package.json +14 -2
- package/protocol/backend.js +6 -6
- package/protocol/buffer-reader.d.ts +7 -12
- package/protocol/buffer-reader.js +11 -58
- package/protocol/frontend.js +86 -96
- package/protocol/pg-socket.d.ts +6 -33
- package/protocol/pg-socket.js +19 -74
- package/protocol/pgoutput.js +2 -2
- package/protocol/smart-buffer.d.ts +11 -29
- package/protocol/smart-buffer.js +19 -162
- package/types.d.ts +13 -1
- package/util/bigint-methods.d.ts +0 -1
- package/util/bigint-methods.js +0 -22
- package/util/decode-binaryarray.d.ts +1 -1
- package/util/decode-binaryarray.js +7 -7
- package/util/encode-binaryarray.js +6 -6
- package/util/fast-parsefloat.d.ts +15 -0
- package/util/fast-parsefloat.js +141 -0
- package/util/get-parsers.js +4 -8
- package/util/parse-datetime.js +7 -0
- package/util/parse-pg-timestamp.d.ts +31 -0
- package/util/parse-pg-timestamp.js +199 -0
- package/util/parse-row.js +120 -0
- package/util/row-decoder.d.ts +49 -0
- package/util/row-decoder.js +63 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,53 @@
|
|
|
1
1
|
## Changelog
|
|
2
2
|
|
|
3
|
-
### [v3.
|
|
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
|
|
45
|
+
|
|
46
|
+
#### 🪲 Fixes
|
|
47
|
+
|
|
48
|
+
- fix: reset transaction depth after prepareTransaction() @Eray Hanoğlu
|
|
49
|
+
|
|
50
|
+
### [v3.1.2](https://github.com/panates/postgrejs/compare/v3.1.1...v3.1.2) - 11 September 2026
|
|
4
51
|
|
|
5
52
|
#### 💬 General Changes
|
|
6
53
|
|
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
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
##
|
|
244
|
+
## Runtime Compatibility
|
|
232
245
|
|
|
233
|
-
-
|
|
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/
|
|
261
|
+
[coveralls-image]: https://img.shields.io/coveralls/panates/postgrejs/dev.svg
|
|
246
262
|
[coveralls-url]: https://coveralls.io/r/panates/postgrejs
|
package/connection/cursor.d.ts
CHANGED
|
@@ -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[]>;
|
package/connection/cursor.js
CHANGED
|
@@ -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 {
|
|
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
|
|
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] =
|
|
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);
|
|
@@ -101,6 +101,13 @@ export declare class IntlConnection extends SafeEventEmitter {
|
|
|
101
101
|
*
|
|
102
102
|
* Requires `max_prepared_transactions` above zero on the server, which is
|
|
103
103
|
* not the default; the server's own error says so if it is not.
|
|
104
|
+
*
|
|
105
|
+
* Resets `_transactionDepth` like commit()/rollback() do: PREPARE
|
|
106
|
+
* TRANSACTION ends the session's transaction on the wire regardless of
|
|
107
|
+
* how many nested startTransaction() calls led up to it, so a stale,
|
|
108
|
+
* nonzero depth left behind here would make a later, unrelated commit()
|
|
109
|
+
* on this same connection treat itself as still nested and silently skip
|
|
110
|
+
* sending COMMIT.
|
|
104
111
|
*/
|
|
105
112
|
prepareTransaction(name: string): Promise<void>;
|
|
106
113
|
/** Commits a transaction left waiting by prepareTransaction(). */
|
|
@@ -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 {
|
|
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;
|
|
@@ -293,9 +293,17 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
293
293
|
*
|
|
294
294
|
* Requires `max_prepared_transactions` above zero on the server, which is
|
|
295
295
|
* not the default; the server's own error says so if it is not.
|
|
296
|
+
*
|
|
297
|
+
* Resets `_transactionDepth` like commit()/rollback() do: PREPARE
|
|
298
|
+
* TRANSACTION ends the session's transaction on the wire regardless of
|
|
299
|
+
* how many nested startTransaction() calls led up to it, so a stale,
|
|
300
|
+
* nonzero depth left behind here would make a later, unrelated commit()
|
|
301
|
+
* on this same connection treat itself as still nested and silently skip
|
|
302
|
+
* sending COMMIT.
|
|
296
303
|
*/
|
|
297
304
|
async prepareTransaction(name) {
|
|
298
305
|
await this._execute('PREPARE TRANSACTION ' + escapeLiteral(name));
|
|
306
|
+
this._transactionDepth = 0;
|
|
299
307
|
}
|
|
300
308
|
/** Commits a transaction left waiting by prepareTransaction(). */
|
|
301
309
|
async commitPrepared(name) {
|
|
@@ -390,6 +398,7 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
390
398
|
let fields;
|
|
391
399
|
let error;
|
|
392
400
|
const typeMap = options.typeMap || GlobalTypeMap;
|
|
401
|
+
const rowDecoder = resolveRowDecoder(options);
|
|
393
402
|
this.runningQueryCount++;
|
|
394
403
|
return await this.socket.sendQueryMessage(sql, (code, msg, done) => {
|
|
395
404
|
switch (code) {
|
|
@@ -418,9 +427,7 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
418
427
|
break;
|
|
419
428
|
case Protocol.BackendMessageCode.DataRow:
|
|
420
429
|
{
|
|
421
|
-
const row =
|
|
422
|
-
? parseObjectRow(parsers, msg.data, msg.columnCount, options, current.fields)
|
|
423
|
-
: parseRow(parsers, msg.data, msg.columnCount, options);
|
|
430
|
+
const row = rowDecoder.decode(parsers, msg.data, msg.columnCount, options, current.fields);
|
|
424
431
|
if (cb)
|
|
425
432
|
cb('row', row);
|
|
426
433
|
current.rows = current.rows || [];
|
|
@@ -438,8 +445,7 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
438
445
|
if (timingEnabled)
|
|
439
446
|
current.executeTime = performance.now() - currentStart;
|
|
440
447
|
if (current.rows)
|
|
441
|
-
current.rowType =
|
|
442
|
-
options.objectRows && current.fields ? 'object' : 'array';
|
|
448
|
+
current.rowType = resolveRowType(options);
|
|
443
449
|
result.results.push(current);
|
|
444
450
|
if (cb)
|
|
445
451
|
cb('command-complete', current);
|
|
@@ -489,6 +495,7 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
489
495
|
let resultFields;
|
|
490
496
|
let commandTag;
|
|
491
497
|
let error;
|
|
498
|
+
const rowDecoder = resolveRowDecoder(options);
|
|
492
499
|
this.runningQueryCount++;
|
|
493
500
|
await this.socket
|
|
494
501
|
.sendExtendedQueryMessages({
|
|
@@ -508,7 +515,7 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
508
515
|
parsers = getParsers(typeMap, msg.fields);
|
|
509
516
|
resultFields = wrapRowDescription(typeMap, msg.fields, options.columnFormat || DEFAULT_COLUMN_FORMAT);
|
|
510
517
|
result.fields = resultFields;
|
|
511
|
-
result.rowType = options
|
|
518
|
+
result.rowType = resolveRowType(options);
|
|
512
519
|
break;
|
|
513
520
|
case Protocol.BackendMessageCode.DataRow:
|
|
514
521
|
rows.push(msg);
|
|
@@ -539,9 +546,7 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
539
546
|
const l = rows.length;
|
|
540
547
|
let i;
|
|
541
548
|
for (i = 0; i < l; i++) {
|
|
542
|
-
rows[i] = options
|
|
543
|
-
? parseObjectRow(parsers, rows[i].data, rows[i].columnCount, options, resultFields)
|
|
544
|
-
: parseRow(parsers, rows[i].data, rows[i].columnCount, options);
|
|
549
|
+
rows[i] = rowDecoder.decode(parsers, rows[i].data, rows[i].columnCount, options, resultFields);
|
|
545
550
|
}
|
|
546
551
|
}
|
|
547
552
|
if (result.command === 'DELETE' ||
|
|
@@ -628,6 +633,7 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
628
633
|
let resultFields;
|
|
629
634
|
let commandTag;
|
|
630
635
|
let error;
|
|
636
|
+
const rowDecoder = resolveRowDecoder(options);
|
|
631
637
|
if (cachedFields) {
|
|
632
638
|
const columnFormat = options.columnFormat != null
|
|
633
639
|
? options.columnFormat
|
|
@@ -656,7 +662,7 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
656
662
|
});
|
|
657
663
|
}
|
|
658
664
|
result.fields = resultFields;
|
|
659
|
-
result.rowType = options
|
|
665
|
+
result.rowType = resolveRowType(options);
|
|
660
666
|
}
|
|
661
667
|
this.runningQueryCount++;
|
|
662
668
|
await this.socket
|
|
@@ -704,9 +710,7 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
704
710
|
const l = rows.length;
|
|
705
711
|
let i;
|
|
706
712
|
for (i = 0; i < l; i++) {
|
|
707
|
-
rows[i] = options
|
|
708
|
-
? parseObjectRow(parsers, rows[i].data, rows[i].columnCount, options, resultFields)
|
|
709
|
-
: parseRow(parsers, rows[i].data, rows[i].columnCount, options);
|
|
713
|
+
rows[i] = rowDecoder.decode(parsers, rows[i].data, rows[i].columnCount, options, resultFields);
|
|
710
714
|
}
|
|
711
715
|
}
|
|
712
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
|
|
128
|
+
rowType: resolveRowType(options),
|
|
128
129
|
cursor: new Cursor(this, portal, resultFields, parsers, options),
|
|
129
130
|
};
|
|
130
131
|
this._refCount++;
|
package/data-types/bool-type.js
CHANGED
|
@@ -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.
|
package/data-types/box-type.js
CHANGED
package/data-types/bytea-type.js
CHANGED
|
@@ -9,10 +9,12 @@ export const ByteaType = {
|
|
|
9
9
|
return '\\x' + v.toString('hex');
|
|
10
10
|
},
|
|
11
11
|
encodeBinary(buf, v) {
|
|
12
|
-
buf.
|
|
12
|
+
buf.writeBytes(v);
|
|
13
13
|
},
|
|
14
|
-
decodeBinary(v, offset = 0) {
|
|
15
|
-
|
|
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,
|
package/data-types/char-type.js
CHANGED
|
@@ -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;
|
package/data-types/date-type.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
},
|
package/data-types/int2-type.js
CHANGED
|
@@ -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,
|
|
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);
|
package/data-types/int4-type.js
CHANGED
package/data-types/int8-type.js
CHANGED
package/data-types/json-type.js
CHANGED
|
@@ -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;
|
package/data-types/jsonb-type.js
CHANGED
|
@@ -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
|
-
|
|
20
|
-
|
|
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;
|
package/data-types/lseg-type.js
CHANGED
|
@@ -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
|
-
|
|
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
|
},
|
package/data-types/oid-type.js
CHANGED
|
@@ -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,
|
|
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);
|
package/data-types/point-type.js
CHANGED
package/data-types/time-type.js
CHANGED
|
@@ -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
|
-
//
|
|
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) {
|