postgrejs 3.4.0 → 3.6.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 +43 -1
- package/README.md +29 -4
- package/connection/connection.d.ts +37 -0
- package/connection/connection.js +85 -7
- package/connection/cursor.d.ts +8 -0
- package/connection/cursor.js +39 -1
- package/connection/intl-connection.d.ts +6 -1
- package/connection/intl-connection.js +284 -122
- package/connection/pool.d.ts +16 -0
- package/connection/pool.js +52 -6
- package/connection/portal.d.ts +1 -1
- package/connection/portal.js +3 -2
- package/connection/prepared-statement.js +8 -4
- package/data-type-map.d.ts +11 -0
- package/data-type-map.js +20 -2
- package/data-types/char-type.js +15 -0
- package/data-types/date-type.js +4 -18
- package/data-types/float4-type.js +79 -1
- package/data-types/json-type.js +7 -12
- package/data-types/jsonb-type.js +7 -12
- package/data-types/time-type.js +2 -15
- package/data-types/timestamp-type.js +8 -29
- package/data-types/timestamptz-type.js +7 -31
- package/interfaces/batch-result.d.ts +1 -1
- package/interfaces/command-result.d.ts +21 -1
- package/interfaces/data-mapping-options.d.ts +16 -0
- package/interfaces/data-type.d.ts +11 -0
- package/interfaces/query-options.d.ts +25 -3
- package/package.json +1 -1
- package/protocol/frontend.d.ts +7 -0
- package/protocol/frontend.js +5 -3
- package/protocol/pg-socket.d.ts +2 -2
- package/protocol/pg-socket.js +12 -6
- package/util/channel-name.d.ts +24 -0
- package/util/channel-name.js +44 -0
- package/util/get-parsers.d.ts +2 -1
- package/util/get-parsers.js +16 -1
- package/util/resolve-column-formats.d.ts +33 -0
- package/util/resolve-column-formats.js +67 -0
- package/util/wrap-row-description.d.ts +2 -1
- package/util/wrap-row-description.js +7 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,48 @@
|
|
|
1
1
|
## Changelog
|
|
2
2
|
|
|
3
|
-
### [v3.
|
|
3
|
+
### [v3.6.0](https://github.com/panates/postgrejs/compare/v3.5.0...v3.6.0) -
|
|
4
|
+
|
|
5
|
+
#### 🚀 New Features
|
|
6
|
+
|
|
7
|
+
- feat: make fetchAsString request text from the server @Eray Hanoğlu
|
|
8
|
+
|
|
9
|
+
#### 🪲 Fixes
|
|
10
|
+
|
|
11
|
+
- fix: decode float4 as the number the server would print @Eray Hanoğlu
|
|
12
|
+
- fix: report rowsAffected for MERGE @Eray Hanoğlu
|
|
13
|
+
- fix: quote LISTEN channel names instead of restricting them @Eray Hanoğlu
|
|
14
|
+
- fix: stop a pooled cursor's connection going back into the pool @Eray Hanoğlu
|
|
15
|
+
- fix: stop inferring "char" for one-character strings @Eray Hanoğlu
|
|
16
|
+
- fix: copy both indexes when a DataTypeMap is built from another @Eray Hanoğlu
|
|
17
|
+
- fix: emit 'notification' once per NOTIFY @Eray Hanoğlu
|
|
18
|
+
- fix: accept single-character savepoint names @Eray Hanoğlu
|
|
19
|
+
|
|
20
|
+
#### 📖 Documentation Changes
|
|
21
|
+
|
|
22
|
+
- docs: record what 3.6 changes for existing code @Eray Hanoğlu
|
|
23
|
+
|
|
24
|
+
#### 💬 General Changes
|
|
25
|
+
|
|
26
|
+
- feat!: fetch every row by default instead of the first 100 @Eray Hanoğlu
|
|
27
|
+
|
|
28
|
+
### [v3.5.0](https://github.com/panates/postgrejs/compare/v3.4.0...v3.5.0) - 16 September 2026
|
|
29
|
+
|
|
30
|
+
#### 🚀 New Features
|
|
31
|
+
|
|
32
|
+
- feat: add transaction(fn) and let a Cursor be iterated with for await @Eray Hanoğlu
|
|
33
|
+
|
|
34
|
+
#### 📖 Documentation Changes
|
|
35
|
+
|
|
36
|
+
- docs: refresh Unit of Work after pipeline() started reusing statements @Eray Hanoğlu
|
|
37
|
+
- docs: record scoped transactions and cursor iteration in the README @Eray Hanoğlu
|
|
38
|
+
- docs: stop signing commits on the assistant's behalf @Eray Hanoğlu
|
|
39
|
+
- docs: note that pipeline() reuses cached prepared statements @Eray Hanoğlu
|
|
40
|
+
|
|
41
|
+
#### ⚡️ Performance Improvments
|
|
42
|
+
|
|
43
|
+
- perf: let pipeline() bind to the connection's cached statements @Eray Hanoğlu
|
|
44
|
+
|
|
45
|
+
### [v3.4.0](https://github.com/panates/postgrejs/compare/v3.3.0...v3.4.0) - 16 September 2026
|
|
4
46
|
|
|
5
47
|
- feat: add PreparedStatement.executeBatch() for multi-set execution
|
|
6
48
|
|
package/README.md
CHANGED
|
@@ -59,6 +59,9 @@ $ npm install postgrejs --save
|
|
|
59
59
|
Please read :small_orange_diamond: [DOCUMENTATION](https://www.postgrejs.com/) :small_orange_diamond: for detailed
|
|
60
60
|
usage.
|
|
61
61
|
|
|
62
|
+
Upgrading from 3.5? See [doc/MIGRATION-v3.5-to-v3.6.md](doc/MIGRATION-v3.5-to-v3.6.md) - `query()` no longer stops at
|
|
63
|
+
100 rows, and `fetchAsString` now returns the server's own text.
|
|
64
|
+
|
|
62
65
|
## Library Overview
|
|
63
66
|
|
|
64
67
|
- **Language:** Pure JavaScript, with no native/binary dependencies to compile or ship.
|
|
@@ -82,12 +85,23 @@ usage.
|
|
|
82
85
|
statement leaves the transaction usable instead of aborting the whole block. The `SAVEPOINT`/`RELEASE` pair travels
|
|
83
86
|
in the statement's own round trip rather than costing one each - 36µs for the pair, against 507µs sent separately.
|
|
84
87
|
`rollbackOnError: false` to opt out.
|
|
85
|
-
- **
|
|
88
|
+
- **Complete Results:** `query()` returns every row the statement produced. A `fetchCount` limit is a limit rather
|
|
89
|
+
than a silent cap: the result carries `suspended: true` when the server stopped at it, so a prefix can never be
|
|
90
|
+
mistaken for the whole answer. `0` means unlimited, as it does in the protocol.
|
|
91
|
+
- **Cursors:** Features fast double-link cache cursors for efficient data retrieval, iterable with `for await` a row
|
|
92
|
+
at a time and closed when the loop ends - by exhaustion, a `break`, or a throw. A cursor reads through a portal, and
|
|
93
|
+
a portal lives as long as the transaction that created it - `Pool.query()` keeps its connection out of the pool
|
|
94
|
+
until the cursor closes, and on a bare `Connection` open the cursor inside a transaction if anything else will run
|
|
95
|
+
on that connection meanwhile.
|
|
96
|
+
- **Scoped Transactions:** `transaction(fn)` commits when the callback returns and rolls back when it throws, on a
|
|
97
|
+
connection or straight from the pool. A call made while a transaction is already open takes a savepoint rather than
|
|
98
|
+
a second `BEGIN`, so a nested scope that fails rolls back its own work and leaves the outer transaction standing.
|
|
86
99
|
- **Batch Execution:** `executeBatch()` runs one prepared statement over many parameter sets under a single `Sync`,
|
|
87
100
|
reporting each set's row count - 1000 updates in 20ms where the same calls pipelined individually take 188ms.
|
|
88
101
|
- **Notifications:** High-level implementation for PostgreSQL notifications (LISTEN/NOTIFY), enabling real-time data
|
|
89
|
-
updates.
|
|
90
|
-
- **Extensibility:** Extensible data-types and type mapping to accommodate custom requirements.
|
|
102
|
+
updates. Channel names are quoted rather than pattern-matched, so any name PostgreSQL accepts works.
|
|
103
|
+
- **Extensibility:** Extensible data-types and type mapping to accommodate custom requirements. A type can set
|
|
104
|
+
`inferrable: false` to stay out of parameter type inference while still decoding its own columns.
|
|
91
105
|
- **Parameter Binding:** Bind parameters with OID mappings for precise and efficient query execution.
|
|
92
106
|
- **Array Handling:** Supports multidimensional arrays with fast binary encoding/decoding.
|
|
93
107
|
- **Performance Optimization:** Low memory utilization and boosted performance through the use of shared buffers.
|
|
@@ -99,7 +113,8 @@ usage.
|
|
|
99
113
|
- **Query Pipelining:** Pooled queries can share connections so a burst is not capped by pool size - opt-in per call.
|
|
100
114
|
`pipeline()` goes further for a known set of statements: several different ones travel under a single `Sync`, so they
|
|
101
115
|
cost one round trip instead of one each and commit or roll back together - around 2x faster than the same calls
|
|
102
|
-
through `Promise.all()`, which is already pipelined.
|
|
116
|
+
through `Promise.all()`, which is already pipelined. Statements the connection has run before bind to their cached
|
|
117
|
+
prepared names, so a repeated set sends no `Parse` at all - worth 2.5x on twenty statements.
|
|
103
118
|
- **Dynamic SQL:** A `sql` tag builds statements from composable fragments - values become parameters, names are quoted,
|
|
104
119
|
and `sql.values()`/`sql.set()` write INSERT and UPDATE clauses from objects.
|
|
105
120
|
- **Multiple Hosts:** A connection can list several servers and pick one by role
|
|
@@ -114,6 +129,10 @@ usage.
|
|
|
114
129
|
`rollbackPrepared()`, from any connection.
|
|
115
130
|
- **Cancellation:** Any call takes an `AbortSignal`, which also gives per-query timeouts via `AbortSignal.timeout()`.
|
|
116
131
|
- **Flexible Data Retrieval:** Can return both array and object rows to suit different data processing needs.
|
|
132
|
+
- **Values as Text:** `fetchAsString: [DataTypeOIDs.int8]` asks the server for those columns in its own text format
|
|
133
|
+
and hands the bytes back unparsed - so `count(*)` arrives as `'3'`, one consistent type where a decoded `int8` is a
|
|
134
|
+
`number` or a `BigInt` depending on magnitude. Any OID, arrays included. The string is PostgreSQL's own rendering
|
|
135
|
+
rather than one reconstructed from the binary value, so it cannot drift from what the server would print.
|
|
117
136
|
- **Resource Management:** Auto disposal of resources with the "using" syntax
|
|
118
137
|
([TC39 Explicit Resource Management](https://github.com/tc39/proposal-explicit-resource-management)), ensuring
|
|
119
138
|
efficient resource cleanup.
|
|
@@ -162,6 +181,7 @@ in · 🟡 partial or needs a separate package · ❌ not supported.
|
|
|
162
181
|
| Parameter type casting | ✅ | 🟡 <sup>10</sup> | ✅ |
|
|
163
182
|
| Prepared statements <sup>24</sup> | ✅ automatic | ✅ manual | ✅ automatic |
|
|
164
183
|
| Statement-level rollback <sup>25</sup> | ✅ automatic | ❌ | 🟡 manual |
|
|
184
|
+
| Scoped transaction helper <sup>26</sup> | ✅ nests | ❌ | ✅ |
|
|
165
185
|
| Batch execution <sup>21</sup> | ✅ | ❌ | ❌ |
|
|
166
186
|
| Multi-statement round trip <sup>23</sup> | ✅ | ❌ | ❌ |
|
|
167
187
|
| Multi-statement scripts | ✅ | ✅ | ✅ |
|
|
@@ -265,6 +285,11 @@ in · 🟡 partial or needs a separate package · ❌ not supported.
|
|
|
265
285
|
trip for each. postgres.js has savepoints, but as a scope the caller opens around a callback (`savepoint(name, fn)`
|
|
266
286
|
in `src/index.js`), so a statement is protected only where someone wrapped it; pg has no savepoint handling in
|
|
267
287
|
`lib/` at all, so a failed statement leaves the block aborted until the caller rolls back themselves.
|
|
288
|
+
- <sup>26</sup> Whether the library runs a callback inside a transaction for you, committing or rolling back on the way
|
|
289
|
+
out. PostgreJS has `transaction(fn)` on both a connection and the pool, and a call made inside one already open takes
|
|
290
|
+
a savepoint, so nesting needs nothing from the caller. postgres.js has `sql.begin(fn)` (`src/index.js`), where a
|
|
291
|
+
nested scope is opened explicitly with `sql.savepoint(fn)` instead. pg has no such helper in `lib/` - `BEGIN` and
|
|
292
|
+
`COMMIT` are statements the caller sends, and getting a transaction onto one connection is the caller's problem too.
|
|
268
293
|
|
|
269
294
|
|
|
270
295
|
## Benchmarks
|
|
@@ -282,6 +282,34 @@ export declare class Connection extends SafeEventEmitter implements AsyncDisposa
|
|
|
282
282
|
* Starts a transaction
|
|
283
283
|
*/
|
|
284
284
|
startTransaction(): Promise<void>;
|
|
285
|
+
/**
|
|
286
|
+
* Runs `fn` inside a transaction and commits when it returns, or rolls
|
|
287
|
+
* back and rethrows when it throws - the try/catch every caller of
|
|
288
|
+
* startTransaction()/commit()/rollback() ends up writing by hand.
|
|
289
|
+
*
|
|
290
|
+
* ```ts
|
|
291
|
+
* const id = await connection.transaction(async tx => {
|
|
292
|
+
* const r = await tx.query(
|
|
293
|
+
* 'insert into orders (total) values ($1) returning id',
|
|
294
|
+
* { params: [total] },
|
|
295
|
+
* );
|
|
296
|
+
* await tx.query('update stock set n = n - 1 where sku = $1', {
|
|
297
|
+
* params: [sku],
|
|
298
|
+
* });
|
|
299
|
+
* return r.rows![0][0];
|
|
300
|
+
* });
|
|
301
|
+
* ```
|
|
302
|
+
*
|
|
303
|
+
* A call made while a transaction is already open becomes a savepoint
|
|
304
|
+
* rather than a second BEGIN, so an inner scope that fails rolls back
|
|
305
|
+
* its own work and leaves the outer transaction to decide what to do -
|
|
306
|
+
* instead of taking everything down with it, which is what a shared
|
|
307
|
+
* BEGIN/ROLLBACK pair would do.
|
|
308
|
+
*
|
|
309
|
+
* `fn` is handed this same connection: every statement it runs on it is
|
|
310
|
+
* inside the transaction, and one it runs on another connection is not.
|
|
311
|
+
*/
|
|
312
|
+
transaction<T>(fn: (connection: this) => Promise<T>): Promise<T>;
|
|
285
313
|
/**
|
|
286
314
|
* Commits current transaction.
|
|
287
315
|
* @param immediate - Commits right away, ignoring how many nested
|
|
@@ -383,6 +411,15 @@ export declare class Connection extends SafeEventEmitter implements AsyncDisposa
|
|
|
383
411
|
* microtask tick on top of the one `promise`/`.catch()` already goes
|
|
384
412
|
* through - paid on every single query this wraps, disabled or not.
|
|
385
413
|
*/
|
|
414
|
+
/**
|
|
415
|
+
* The nested half of transaction(): a savepoint of its own, released on
|
|
416
|
+
* success and rolled back to on failure.
|
|
417
|
+
*
|
|
418
|
+
* The `inTransaction` checks are re-read rather than assumed - `fn` is
|
|
419
|
+
* free to commit or roll back the outer transaction itself, and there is
|
|
420
|
+
* no savepoint left to release once it has.
|
|
421
|
+
*/
|
|
422
|
+
private _savepointScope;
|
|
386
423
|
protected _captureErrorStack<T>(promise: Promise<T>, entry?: (...args: any[]) => any, asyncErrorHandling?: boolean): Promise<T>;
|
|
387
424
|
[Symbol.asyncDispose](): Promise<void>;
|
|
388
425
|
}
|
package/connection/connection.js
CHANGED
|
@@ -2,12 +2,16 @@ import { ConnectionState, DataTypeOIDs } from '../constants.js';
|
|
|
2
2
|
import { GlobalTypeMap } from '../data-type-map.js';
|
|
3
3
|
import { SafeEventEmitter } from '../safe-event-emitter.js';
|
|
4
4
|
import { withAbortSignal } from '../util/abort-signal.js';
|
|
5
|
+
import { normalizeChannelName } from '../util/channel-name.js';
|
|
6
|
+
import { escapeIdentifier } from '../util/escape-identifier.js';
|
|
5
7
|
import { QueryRequest } from '../util/sql-tag.js';
|
|
6
8
|
import { BindParam } from './bind-param.js';
|
|
7
9
|
import { IntlConnection } from './intl-connection.js';
|
|
8
10
|
import { LargeObject, LargeObjectMode } from './large-object.js';
|
|
9
11
|
import { PreparedStatement } from './prepared-statement.js';
|
|
10
12
|
const CAPTURE_STACK_TRACE_LIMIT = 5;
|
|
13
|
+
/** Names the savepoint a nested transaction() scope takes. */
|
|
14
|
+
let transactionScopeCounter = 0;
|
|
11
15
|
export class Connection extends SafeEventEmitter {
|
|
12
16
|
_pool;
|
|
13
17
|
_intlCon;
|
|
@@ -477,6 +481,50 @@ export class Connection extends SafeEventEmitter {
|
|
|
477
481
|
startTransaction() {
|
|
478
482
|
return this._captureErrorStack(this._intlCon.startTransaction());
|
|
479
483
|
}
|
|
484
|
+
/**
|
|
485
|
+
* Runs `fn` inside a transaction and commits when it returns, or rolls
|
|
486
|
+
* back and rethrows when it throws - the try/catch every caller of
|
|
487
|
+
* startTransaction()/commit()/rollback() ends up writing by hand.
|
|
488
|
+
*
|
|
489
|
+
* ```ts
|
|
490
|
+
* const id = await connection.transaction(async tx => {
|
|
491
|
+
* const r = await tx.query(
|
|
492
|
+
* 'insert into orders (total) values ($1) returning id',
|
|
493
|
+
* { params: [total] },
|
|
494
|
+
* );
|
|
495
|
+
* await tx.query('update stock set n = n - 1 where sku = $1', {
|
|
496
|
+
* params: [sku],
|
|
497
|
+
* });
|
|
498
|
+
* return r.rows![0][0];
|
|
499
|
+
* });
|
|
500
|
+
* ```
|
|
501
|
+
*
|
|
502
|
+
* A call made while a transaction is already open becomes a savepoint
|
|
503
|
+
* rather than a second BEGIN, so an inner scope that fails rolls back
|
|
504
|
+
* its own work and leaves the outer transaction to decide what to do -
|
|
505
|
+
* instead of taking everything down with it, which is what a shared
|
|
506
|
+
* BEGIN/ROLLBACK pair would do.
|
|
507
|
+
*
|
|
508
|
+
* `fn` is handed this same connection: every statement it runs on it is
|
|
509
|
+
* inside the transaction, and one it runs on another connection is not.
|
|
510
|
+
*/
|
|
511
|
+
async transaction(fn) {
|
|
512
|
+
if (this._intlCon.inTransaction)
|
|
513
|
+
return await this._savepointScope(fn);
|
|
514
|
+
await this.startTransaction();
|
|
515
|
+
try {
|
|
516
|
+
const result = await fn(this);
|
|
517
|
+
await this.commit();
|
|
518
|
+
return result;
|
|
519
|
+
}
|
|
520
|
+
catch (e) {
|
|
521
|
+
// A rollback that fails in turn must not replace the error that
|
|
522
|
+
// caused it: the connection is already in trouble, and the original
|
|
523
|
+
// is what the caller needs to see.
|
|
524
|
+
await this.rollback().catch(() => undefined);
|
|
525
|
+
throw e;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
480
528
|
/**
|
|
481
529
|
* Commits current transaction.
|
|
482
530
|
* @param immediate - Commits right away, ignoring how many nested
|
|
@@ -553,8 +601,7 @@ export class Connection extends SafeEventEmitter {
|
|
|
553
601
|
return this._captureErrorStack(this._intlCon.releaseSavepoint(name, immediate));
|
|
554
602
|
}
|
|
555
603
|
async listen(channel, callback) {
|
|
556
|
-
|
|
557
|
-
throw new TypeError(`Invalid channel name`);
|
|
604
|
+
channel = normalizeChannelName(channel);
|
|
558
605
|
if (!this._notificationListeners) {
|
|
559
606
|
this._notificationListeners = new SafeEventEmitter();
|
|
560
607
|
this._intlCon.on('notification', (msg) => this._handleNotification(msg));
|
|
@@ -567,14 +614,13 @@ export class Connection extends SafeEventEmitter {
|
|
|
567
614
|
const alreadyListening = !!this._notificationListeners.listenerCount(channel);
|
|
568
615
|
this._notificationListeners.on(channel, callback);
|
|
569
616
|
if (!alreadyListening)
|
|
570
|
-
await this._captureErrorStack(this.query('LISTEN ' + channel));
|
|
617
|
+
await this._captureErrorStack(this.query('LISTEN ' + escapeIdentifier(channel)));
|
|
571
618
|
}
|
|
572
619
|
async unListen(channel) {
|
|
573
|
-
|
|
574
|
-
throw new TypeError(`Invalid channel name`);
|
|
620
|
+
channel = normalizeChannelName(channel);
|
|
575
621
|
if (this._notificationListeners?.listenerCount(channel)) {
|
|
576
622
|
this._notificationListeners?.removeAllListeners(channel);
|
|
577
|
-
await this._captureErrorStack(this.query('UNLISTEN ' + channel));
|
|
623
|
+
await this._captureErrorStack(this.query('UNLISTEN ' + escapeIdentifier(channel)));
|
|
578
624
|
}
|
|
579
625
|
}
|
|
580
626
|
async unListenAll() {
|
|
@@ -636,7 +682,13 @@ export class Connection extends SafeEventEmitter {
|
|
|
636
682
|
return new LargeObject(this, oid, Number(opened.rows?.[0][0]), ownsTransaction);
|
|
637
683
|
}
|
|
638
684
|
_handleNotification(msg) {
|
|
639
|
-
this.emit('notification', msg)
|
|
685
|
+
// Deliberately no `this.emit('notification', msg)` here:
|
|
686
|
+
// IntlConnection.emit() re-emits every event on its owner, which is
|
|
687
|
+
// this Connection, so the raw event has already been delivered by the
|
|
688
|
+
// time this runs. Emitting it again fired 'notification' twice for one
|
|
689
|
+
// NOTIFY - but only once listen() had been called, since that is what
|
|
690
|
+
// installs this handler, so the same listener saw one event or two
|
|
691
|
+
// depending on whether anything else had subscribed.
|
|
640
692
|
this._notificationListeners?.emit(msg.channel, msg);
|
|
641
693
|
}
|
|
642
694
|
async _close() {
|
|
@@ -695,6 +747,32 @@ export class Connection extends SafeEventEmitter {
|
|
|
695
747
|
* microtask tick on top of the one `promise`/`.catch()` already goes
|
|
696
748
|
* through - paid on every single query this wraps, disabled or not.
|
|
697
749
|
*/
|
|
750
|
+
/**
|
|
751
|
+
* The nested half of transaction(): a savepoint of its own, released on
|
|
752
|
+
* success and rolled back to on failure.
|
|
753
|
+
*
|
|
754
|
+
* The `inTransaction` checks are re-read rather than assumed - `fn` is
|
|
755
|
+
* free to commit or roll back the outer transaction itself, and there is
|
|
756
|
+
* no savepoint left to release once it has.
|
|
757
|
+
*/
|
|
758
|
+
async _savepointScope(fn) {
|
|
759
|
+
const name = 'txscope' + ++transactionScopeCounter;
|
|
760
|
+
await this._captureErrorStack(this._intlCon.execute('SAVEPOINT ' + name));
|
|
761
|
+
try {
|
|
762
|
+
const result = await fn(this);
|
|
763
|
+
if (this._intlCon.inTransaction)
|
|
764
|
+
await this._intlCon.execute('RELEASE SAVEPOINT ' + name);
|
|
765
|
+
return result;
|
|
766
|
+
}
|
|
767
|
+
catch (e) {
|
|
768
|
+
if (this._intlCon.inTransaction) {
|
|
769
|
+
await this._intlCon
|
|
770
|
+
.execute('ROLLBACK TO SAVEPOINT ' + name + '; RELEASE SAVEPOINT ' + name)
|
|
771
|
+
.catch(() => undefined);
|
|
772
|
+
}
|
|
773
|
+
throw e;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
698
776
|
_captureErrorStack(promise, entry, asyncErrorHandling) {
|
|
699
777
|
const enabled = asyncErrorHandling ?? this._intlCon.config.asyncErrorHandling ?? true;
|
|
700
778
|
if (!enabled)
|
package/connection/cursor.d.ts
CHANGED
|
@@ -21,5 +21,13 @@ export declare class Cursor extends SafeEventEmitter implements AsyncDisposable
|
|
|
21
21
|
fetch(nRows: number): Promise<Row[]>;
|
|
22
22
|
close(): Promise<void>;
|
|
23
23
|
private _fetchRows;
|
|
24
|
+
/**
|
|
25
|
+
* Iterates the remaining rows, fetching a batch at a time, and closes
|
|
26
|
+
* the cursor when the loop ends - whether it ran out of rows, broke
|
|
27
|
+
* early, or the body threw. Nothing is decoded ahead of what is asked
|
|
28
|
+
* for, so this streams a result larger than memory the same way next()
|
|
29
|
+
* does one row at a time.
|
|
30
|
+
*/
|
|
31
|
+
[Symbol.asyncIterator](): AsyncIterableIterator<Row>;
|
|
24
32
|
[Symbol.asyncDispose](): Promise<void>;
|
|
25
33
|
}
|
package/connection/cursor.js
CHANGED
|
@@ -69,7 +69,28 @@ export class Cursor extends SafeEventEmitter {
|
|
|
69
69
|
await this._taskQueue
|
|
70
70
|
.enqueue(async () => {
|
|
71
71
|
const queryOptions = this._queryOptions;
|
|
72
|
-
|
|
72
|
+
let r;
|
|
73
|
+
try {
|
|
74
|
+
r = await portal.execute(queryOptions.fetchCount ?? 100);
|
|
75
|
+
}
|
|
76
|
+
catch (e) {
|
|
77
|
+
// 34000 here means the portal is gone, and the only thing that
|
|
78
|
+
// takes a portal away mid-cursor is another statement on this
|
|
79
|
+
// connection: its Sync ends the implicit transaction the portal
|
|
80
|
+
// lives in. The server's own message says nothing about why, and
|
|
81
|
+
// a caller cannot act on it without knowing. Closing first keeps
|
|
82
|
+
// the statement from leaking and lets whoever is holding the
|
|
83
|
+
// connection for this cursor (Pool.query()) have it back.
|
|
84
|
+
if (e?.code === '34000') {
|
|
85
|
+
await this.close().catch(() => undefined);
|
|
86
|
+
e.message +=
|
|
87
|
+
' - the portal was destroyed by another statement running on' +
|
|
88
|
+
' the same connection, which ends the implicit transaction it' +
|
|
89
|
+
' lives in. Open the cursor inside an explicit transaction, or' +
|
|
90
|
+
' give it a connection of its own.';
|
|
91
|
+
}
|
|
92
|
+
throw e;
|
|
93
|
+
}
|
|
73
94
|
if (r && r.rows && r.rows.length) {
|
|
74
95
|
const rows = r.rows;
|
|
75
96
|
if (this._parsers) {
|
|
@@ -92,6 +113,23 @@ export class Cursor extends SafeEventEmitter {
|
|
|
92
113
|
})
|
|
93
114
|
.toPromise();
|
|
94
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Iterates the remaining rows, fetching a batch at a time, and closes
|
|
118
|
+
* the cursor when the loop ends - whether it ran out of rows, broke
|
|
119
|
+
* early, or the body threw. Nothing is decoded ahead of what is asked
|
|
120
|
+
* for, so this streams a result larger than memory the same way next()
|
|
121
|
+
* does one row at a time.
|
|
122
|
+
*/
|
|
123
|
+
async *[Symbol.asyncIterator]() {
|
|
124
|
+
try {
|
|
125
|
+
let row;
|
|
126
|
+
while ((row = await this.next()) !== undefined)
|
|
127
|
+
yield row;
|
|
128
|
+
}
|
|
129
|
+
finally {
|
|
130
|
+
await this.close();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
95
133
|
[Symbol.asyncDispose]() {
|
|
96
134
|
return this.close();
|
|
97
135
|
}
|
|
@@ -207,6 +207,11 @@ export declare class IntlConnection extends SafeEventEmitter {
|
|
|
207
207
|
*/
|
|
208
208
|
protected _inlineSavepointFor(sql: string, options: QueryOptions): Maybe<string>;
|
|
209
209
|
protected _queryCached(sql: string, paramTypes: Maybe<Maybe<OID>[]>, params: Maybe<Maybe<any>[]>, options: QueryOptions, savepoint: Maybe<string>): Promise<QueryResult>;
|
|
210
|
+
/**
|
|
211
|
+
* Counts one more use of this SQL and says whether it has now been seen
|
|
212
|
+
* often enough to be worth preparing under a name.
|
|
213
|
+
*/
|
|
214
|
+
protected _earnsAName(key: string): boolean;
|
|
210
215
|
/** Closes the least recently used statement once the cache is full. */
|
|
211
216
|
protected _evictPreparedIfFull(): Promise<void>;
|
|
212
217
|
/**
|
|
@@ -240,7 +245,7 @@ export declare class IntlConnection extends SafeEventEmitter {
|
|
|
240
245
|
* so repeated executes of the same statement don't rebuild them.
|
|
241
246
|
* Shared by executeReused() and executeBatchReused().
|
|
242
247
|
*/
|
|
243
|
-
protected _resolveReusedParsers(cachedFields: Protocol.RowDescription[], typeMap: DataTypeMap, options: QueryOptions): {
|
|
248
|
+
protected _resolveReusedParsers(cachedFields: Protocol.RowDescription[], typeMap: DataTypeMap, options: QueryOptions, columnFormat: Protocol.DataFormat | Protocol.DataFormat[]): {
|
|
244
249
|
parsers: AnyParseFunction[];
|
|
245
250
|
resultFields: FieldInfo[];
|
|
246
251
|
};
|