postgrejs 3.6.0 → 3.7.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 +64 -6
- package/README.md +9 -79
- package/connection/intl-connection.d.ts +5 -3
- package/connection/intl-connection.js +47 -17
- package/connection/pool.d.ts +22 -0
- package/connection/pool.js +45 -3
- package/connection/prepared-statement.js +2 -2
- package/constants.d.ts +60 -0
- package/constants.js +119 -1
- package/data-type-map.js +82 -0
- package/data-types/bit-type.d.ts +5 -0
- package/data-types/bit-type.js +91 -0
- package/data-types/box-type.js +9 -19
- package/data-types/circle-type.js +6 -16
- package/data-types/classes/geometric.d.ts +105 -0
- package/data-types/classes/geometric.js +197 -0
- package/data-types/classes/interval.d.ts +73 -0
- package/data-types/classes/interval.js +181 -0
- package/data-types/classes/numeric.d.ts +37 -0
- package/data-types/classes/numeric.js +47 -0
- package/data-types/classes/range.d.ts +74 -0
- package/data-types/classes/range.js +111 -0
- package/data-types/classes/type-oid.d.ts +34 -0
- package/data-types/classes/type-oid.js +45 -0
- package/data-types/inet-type.d.ts +5 -0
- package/data-types/inet-type.js +286 -0
- package/data-types/interval-type.d.ts +5 -0
- package/data-types/interval-type.js +79 -0
- package/data-types/jsonpath-type.d.ts +3 -0
- package/data-types/jsonpath-type.js +53 -0
- package/data-types/line-type.d.ts +3 -0
- package/data-types/line-type.js +41 -0
- package/data-types/lseg-type.js +9 -19
- package/data-types/macaddr-type.d.ts +5 -0
- package/data-types/macaddr-type.js +86 -0
- package/data-types/numeric-type.js +98 -69
- package/data-types/path-type.d.ts +5 -0
- package/data-types/path-type.js +139 -0
- package/data-types/pg-lsn-type.d.ts +3 -0
- package/data-types/pg-lsn-type.js +58 -0
- package/data-types/point-type.js +8 -13
- package/data-types/range-type.d.ts +23 -0
- package/data-types/range-type.js +342 -0
- package/data-types/snapshot-type.d.ts +5 -0
- package/data-types/snapshot-type.js +96 -0
- package/data-types/tid-type.d.ts +3 -0
- package/data-types/tid-type.js +50 -0
- package/data-types/timetz-type.d.ts +3 -0
- package/data-types/timetz-type.js +184 -0
- package/data-types/tsvector-type.d.ts +5 -0
- package/data-types/tsvector-type.js +216 -0
- package/data-types/xid-type.d.ts +7 -0
- package/data-types/xid-type.js +110 -0
- package/index.d.ts +5 -0
- package/index.js +5 -0
- package/interfaces/data-mapping-options.d.ts +26 -0
- package/interfaces/data-type.d.ts +8 -9
- package/package.json +1 -1
- package/protocol/connection-lost-error.d.ts +42 -0
- package/protocol/connection-lost-error.js +56 -0
- package/protocol/pg-socket.d.ts +7 -0
- package/protocol/pg-socket.js +41 -3
- package/util/parse-array.js +17 -2
- package/util/resolve-column-formats.d.ts +2 -1
- package/util/resolve-column-formats.js +35 -8
- package/util/stringify-for-sql.js +37 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,65 @@
|
|
|
1
1
|
## Changelog
|
|
2
2
|
|
|
3
|
-
### [v3.
|
|
3
|
+
### [v3.7.0](https://github.com/panates/postgrejs/compare/v3.6.1...v3.7.0) - 20 September 2026
|
|
4
|
+
|
|
5
|
+
#### Breaking Changes :warning:
|
|
6
|
+
|
|
7
|
+
- feat!: decode a numeric that a double cannot hold into a Numeric @Eray Hanoğlu
|
|
8
|
+
- build: let the changelog template see a breaking change @Eray Hanoğlu
|
|
9
|
+
|
|
10
|
+
#### 🚀 New Features
|
|
11
|
+
|
|
12
|
+
- feat: decode the network types @Eray Hanoğlu
|
|
13
|
+
- feat: decode line, path and polygon @Eray Hanoğlu
|
|
14
|
+
- feat: decode the system columns, and name the types that cannot be @Eray Hanoğlu
|
|
15
|
+
- feat: decode the range and multirange types @Eray Hanoğlu
|
|
16
|
+
- feat: decode the geometric types into classes of their own @Eray Hanoğlu
|
|
17
|
+
- feat: decode tsvector and tsquery @Eray Hanoğlu
|
|
18
|
+
- feat: decode interval @Eray Hanoğlu
|
|
19
|
+
- feat: decode bit, varbit and jsonpath @Eray Hanoğlu
|
|
20
|
+
- feat: decode timetz @Eray Hanoğlu
|
|
21
|
+
- feat: decode refcursor, pg_node_tree and the snapshot types @Eray Hanoğlu
|
|
22
|
+
- feat: add unknownTypesAsString @Eray Hanoğlu
|
|
23
|
+
- feat: let a decoded value carry the type it came from @Eray Hanoğlu
|
|
24
|
+
- feat: reject the in-flight query with ConnectionLostError too @Eray Hanoğlu
|
|
25
|
+
|
|
26
|
+
#### 🪲 Fixes
|
|
27
|
+
|
|
28
|
+
- fix: write an object as its own SQL literal, not as json @Eray Hanoğlu
|
|
29
|
+
- fix: complete the type OID table, and check it against the catalog @Eray Hanoğlu
|
|
30
|
+
- fix: recover when a recreated type invalidates a cached statement @Eray Hanoğlu
|
|
31
|
+
- fix: keep a quoted empty element when parsing an array literal @Eray Hanoğlu
|
|
32
|
+
|
|
33
|
+
#### 📖 Documentation Changes
|
|
34
|
+
|
|
35
|
+
- docs: cut the Features list back down @Eray Hanoğlu
|
|
36
|
+
- docs: drop the Features section @Eray Hanoğlu
|
|
37
|
+
- docs: bring the data type counts up to what is registered @Eray Hanoğlu
|
|
38
|
+
- docs: give the documentation link its own heading @Eray Hanoğlu
|
|
39
|
+
|
|
40
|
+
#### ⚡️ Performance Improvments
|
|
41
|
+
|
|
42
|
+
- perf: build a numeric's digits without a concatenation per digit @Eray Hanoğlu
|
|
43
|
+
|
|
44
|
+
### [v3.6.1](https://github.com/panates/postgrejs/compare/v3.6.0...v3.6.1) - 20 September 2026
|
|
45
|
+
|
|
46
|
+
#### 🚀 New Features
|
|
47
|
+
|
|
48
|
+
- feat: report a pooled connection that dies @Eray Hanoğlu
|
|
49
|
+
|
|
50
|
+
#### 🛠 Refactoring and Updates
|
|
51
|
+
|
|
52
|
+
- refactor: give a lost connection its own error type @Eray Hanoğlu
|
|
53
|
+
|
|
54
|
+
#### 💬 General Changes
|
|
55
|
+
|
|
56
|
+
- ci: pin the runner image and stop naming the PostgreSQL it ships @Eray Hanoğlu
|
|
57
|
+
|
|
58
|
+
### [v3.6.0](https://github.com/panates/postgrejs/compare/v3.5.0...v3.6.0) - 20 September 2026
|
|
59
|
+
|
|
60
|
+
#### Breaking Changes :warning:
|
|
61
|
+
|
|
62
|
+
- feat!: fetch every row by default instead of the first 100 @Eray Hanoğlu
|
|
4
63
|
|
|
5
64
|
#### 🚀 New Features
|
|
6
65
|
|
|
@@ -21,10 +80,6 @@
|
|
|
21
80
|
|
|
22
81
|
- docs: record what 3.6 changes for existing code @Eray Hanoğlu
|
|
23
82
|
|
|
24
|
-
#### 💬 General Changes
|
|
25
|
-
|
|
26
|
-
- feat!: fetch every row by default instead of the first 100 @Eray Hanoğlu
|
|
27
|
-
|
|
28
83
|
### [v3.5.0](https://github.com/panates/postgrejs/compare/v3.4.0...v3.5.0) - 16 September 2026
|
|
29
84
|
|
|
30
85
|
#### 🚀 New Features
|
|
@@ -81,6 +136,10 @@
|
|
|
81
136
|
|
|
82
137
|
### [v3.3.0](https://github.com/panates/postgrejs/compare/v3.2.0...v3.3.0) - 14 September 2026
|
|
83
138
|
|
|
139
|
+
#### Breaking Changes :warning:
|
|
140
|
+
|
|
141
|
+
- feat!: give decodeBinary the value's length, not a slice of it @Eray Hanoğlu
|
|
142
|
+
|
|
84
143
|
#### 🚀 New Features
|
|
85
144
|
|
|
86
145
|
- feat: add first-class Bun support with dedicated benchmark suite @Eray Hanoğlu
|
|
@@ -109,7 +168,6 @@
|
|
|
109
168
|
#### 💬 General Changes
|
|
110
169
|
|
|
111
170
|
- bench: report near-ties as ties instead of ranking them @Eray Hanoğlu
|
|
112
|
-
- feat!: give decodeBinary the value's length, not a slice of it @Eray Hanoğlu
|
|
113
171
|
- bench: stop measuring type-declaration policy in the Extended Query scenarios @Eray Hanoğlu
|
|
114
172
|
- bench: stop one stalled repeat from making every result a tie @Eray Hanoğlu
|
|
115
173
|
- bench: document why concurrent Simple Query varies its literal @Eray Hanoğlu
|
package/README.md
CHANGED
|
@@ -56,8 +56,7 @@ $ npm install postgrejs --save
|
|
|
56
56
|
|
|
57
57
|
## Documentation
|
|
58
58
|
|
|
59
|
-
Please read
|
|
60
|
-
usage.
|
|
59
|
+
### Please read 🔸 [DOCUMENTATION](https://www.postgrejs.com/) 🔸 for detailed usage
|
|
61
60
|
|
|
62
61
|
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
62
|
100 rows, and `fetchAsString` now returns the server's own text.
|
|
@@ -71,78 +70,6 @@ Upgrading from 3.5? See [doc/MIGRATION-v3.5-to-v3.6.md](doc/MIGRATION-v3.5-to-v3
|
|
|
71
70
|
- **Rigorously tested:** A test suite covering the wire protocol, every data type, and connection-handling edge case,
|
|
72
71
|
run on every push against PostgreSQL 12 through 18.
|
|
73
72
|
|
|
74
|
-
## Features
|
|
75
|
-
|
|
76
|
-
- **Connection Management:** Supports both single connection and advanced pooling, providing scalability and efficient
|
|
77
|
-
resource management.
|
|
78
|
-
- **Binary Wire Protocol:** Implements the full binary wire protocol for all PostgreSQL data types, ensuring robust and
|
|
79
|
-
efficient data handling.
|
|
80
|
-
- **Prepared Statements:** Named prepared statements for optimized query execution, and a per-connection cache that
|
|
81
|
-
reuses one automatically for SQL the connection has run before - a repeated query costs `Bind`/`Execute` instead of
|
|
82
|
-
parsing again, worth 3.2x on fifty concurrent calls and 2.9x again on a query inside an open transaction. On by
|
|
83
|
-
default, `prepare: false` to opt out.
|
|
84
|
-
- **Statement-Level Rollback:** Inside a transaction each statement runs under a savepoint of its own, so a failed
|
|
85
|
-
statement leaves the transaction usable instead of aborting the whole block. The `SAVEPOINT`/`RELEASE` pair travels
|
|
86
|
-
in the statement's own round trip rather than costing one each - 36µs for the pair, against 507µs sent separately.
|
|
87
|
-
`rollbackOnError: false` to opt out.
|
|
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.
|
|
99
|
-
- **Batch Execution:** `executeBatch()` runs one prepared statement over many parameter sets under a single `Sync`,
|
|
100
|
-
reporting each set's row count - 1000 updates in 20ms where the same calls pipelined individually take 188ms.
|
|
101
|
-
- **Notifications:** High-level implementation for PostgreSQL notifications (LISTEN/NOTIFY), enabling real-time data
|
|
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.
|
|
105
|
-
- **Parameter Binding:** Bind parameters with OID mappings for precise and efficient query execution.
|
|
106
|
-
- **Array Handling:** Supports multidimensional arrays with fast binary encoding/decoding.
|
|
107
|
-
- **Performance Optimization:** Low memory utilization and boosted performance through the use of shared buffers.
|
|
108
|
-
- **Authorization:** Supports various password algorithms including Clear text, MD5, and SASL, ensuring secure
|
|
109
|
-
authentication.
|
|
110
|
-
- **Bulk Import/Export:** `COPY TO STDOUT` and `COPY FROM STDIN` as Node streams, with backpressure in both directions,
|
|
111
|
-
plus `copyFromRows()`, which encodes rows straight into binary `COPY` - around 4x faster than the CSV equivalent and
|
|
112
|
-
no text escaping to get wrong. Takes arrays or objects from anything iterable, so a file larger than memory streams in.
|
|
113
|
-
- **Query Pipelining:** Pooled queries can share connections so a burst is not capped by pool size - opt-in per call.
|
|
114
|
-
`pipeline()` goes further for a known set of statements: several different ones travel under a single `Sync`, so they
|
|
115
|
-
cost one round trip instead of one each and commit or roll back together - around 2x faster than the same calls
|
|
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.
|
|
118
|
-
- **Dynamic SQL:** A `sql` tag builds statements from composable fragments - values become parameters, names are quoted,
|
|
119
|
-
and `sql.values()`/`sql.set()` write INSERT and UPDATE clauses from objects.
|
|
120
|
-
- **Multiple Hosts:** A connection can list several servers and pick one by role
|
|
121
|
-
(`target_session_attrs`), so a cluster that has failed over is found on the next connect.
|
|
122
|
-
- **Large Objects:** File-like access to binary data stored outside the row - seek, partial reads, streams - for
|
|
123
|
-
values past what a `bytea` column can hold.
|
|
124
|
-
- **Logical Replication:** `LogicalReplication` streams committed row changes as an async iterable, decoding
|
|
125
|
-
`pgoutput` itself, with client-side filtering and positions confirmed as you consume.
|
|
126
|
-
- **Channel Binding:** SCRAM authentication binds itself to the TLS channel when the server offers it, the way libpq
|
|
127
|
-
does by default, so a relayed login is detected even where the certificate is not verified.
|
|
128
|
-
- **Two-phase commit:** `prepareTransaction()` leaves a transaction waiting under a name for `commitPrepared()`/
|
|
129
|
-
`rollbackPrepared()`, from any connection.
|
|
130
|
-
- **Cancellation:** Any call takes an `AbortSignal`, which also gives per-query timeouts via `AbortSignal.timeout()`.
|
|
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.
|
|
136
|
-
- **Resource Management:** Auto disposal of resources with the "using" syntax
|
|
137
|
-
([TC39 Explicit Resource Management](https://github.com/tc39/proposal-explicit-resource-management)), ensuring
|
|
138
|
-
efficient resource cleanup.
|
|
139
|
-
- **Long Cancel Key:** Opt in to protocol 3.2 (PostgreSQL 18+) with `longCancelKey`, so a `cancel()` in progress can't
|
|
140
|
-
be forged by an attacker guessing a short secret key.
|
|
141
|
-
- **Legacy Function Call Protocol:** `callFunction()` calls a function by OID directly over the wire, bypassing SQL
|
|
142
|
-
entirely - kept for protocol completeness even though `SELECT func(...)` covers the same ground.
|
|
143
|
-
- **Graceful Protocol Renegotiation:** A server that doesn't recognize a requested protocol version or startup option
|
|
144
|
-
reports back instead of erroring out, surfaced on `Connection.protocolNegotiation`.
|
|
145
|
-
|
|
146
73
|
## Feature Comparison
|
|
147
74
|
|
|
148
75
|
How PostgreJS compares to [`pg`](https://github.com/brianc/node-postgres) (node-postgres) and
|
|
@@ -200,10 +127,10 @@ in · 🟡 partial or needs a separate package · ❌ not supported.
|
|
|
200
127
|
| Multiple hosts | ✅ | ❌ | ✅ |
|
|
201
128
|
| LISTEN/NOTIFY | ✅ | 🟡 <sup>15</sup> | ✅ |
|
|
202
129
|
| ***Data types*** | | | |
|
|
203
|
-
| Text encoders |
|
|
204
|
-
| Text decoders |
|
|
205
|
-
| Binary encoders |
|
|
206
|
-
| Binary decoders |
|
|
130
|
+
| Text encoders | 125 | generic <sup>16</sup> | 14 |
|
|
131
|
+
| Text decoders | 125 | 44 | 12 <sup>17</sup> |
|
|
132
|
+
| Binary encoders | 121 <sup>27</sup> | ❌ | ❌ |
|
|
133
|
+
| Binary decoders | 125 | 16 | ❌ |
|
|
207
134
|
| Multidimensional arrays | ✅ binary | 🟡 text <sup>18</sup> | 🟡 text |
|
|
208
135
|
| ***Security*** | | | |
|
|
209
136
|
| SSL/TLS | ✅ | ✅ | ✅ |
|
|
@@ -290,7 +217,10 @@ in · 🟡 partial or needs a separate package · ❌ not supported.
|
|
|
290
217
|
a savepoint, so nesting needs nothing from the caller. postgres.js has `sql.begin(fn)` (`src/index.js`), where a
|
|
291
218
|
nested scope is opened explicitly with `sql.savepoint(fn)` instead. pg has no such helper in `lib/` - `BEGIN` and
|
|
292
219
|
`COMMIT` are statements the caller sends, and getting a transaction onto one connection is the caller's problem too.
|
|
293
|
-
|
|
220
|
+
- <sup>27</sup> Every registered type but `tsvector` and `tsquery`, which have none deliberately: the server's own
|
|
221
|
+
input parser is what sorts and deduplicates a vector and what defines a query's grammar, so encoding either here
|
|
222
|
+
would make the same text mean one thing written as a literal and another bound as a parameter. They are sent as
|
|
223
|
+
text instead, which is exact.
|
|
294
224
|
|
|
295
225
|
## Benchmarks
|
|
296
226
|
|
|
@@ -194,9 +194,11 @@ export declare class IntlConnection extends SafeEventEmitter {
|
|
|
194
194
|
*
|
|
195
195
|
* A cached statement can be invalidated under us - `alter table` between
|
|
196
196
|
* two calls makes PostgreSQL answer 0A000, "cached plan must not change
|
|
197
|
-
* result type"
|
|
198
|
-
*
|
|
199
|
-
*
|
|
197
|
+
* result type", and a dropped and recreated type makes it answer XX000,
|
|
198
|
+
* "cache lookup failed for type". Both are caught here (see
|
|
199
|
+
* isStaleCachedPlan): the entry is dropped and the query runs again
|
|
200
|
+
* unprepared, so a migration against a live connection recovers instead
|
|
201
|
+
* of failing every call from then on.
|
|
200
202
|
*/
|
|
201
203
|
queryCached(sql: string, paramTypes: Maybe<Maybe<OID>[]>, params: Maybe<Maybe<any>[]>, options: QueryOptions): Promise<QueryResult>;
|
|
202
204
|
/**
|
|
@@ -28,6 +28,30 @@ function columnFormatsEqual(a, b) {
|
|
|
28
28
|
return false;
|
|
29
29
|
return true;
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Whether the server is saying that a statement this connection had under
|
|
33
|
+
* a name can no longer be executed as it stands, so the cache entry is
|
|
34
|
+
* stale and the query is worth running again unprepared.
|
|
35
|
+
*
|
|
36
|
+
* Two SQLSTATEs say it. `0A000` is the documented one - "cached plan must
|
|
37
|
+
* not change result type", after an `alter table`. `XX000` is what a
|
|
38
|
+
* dropped and recreated *type* produces ("cache lookup failed for type
|
|
39
|
+
* <oid>"): it is the server's catch-all internal_error rather than
|
|
40
|
+
* something specific, but it is rare enough in ordinary operation that
|
|
41
|
+
* retrying it costs nothing, and the alternative - matching the English
|
|
42
|
+
* message - breaks under a different `lc_messages` or a server version
|
|
43
|
+
* that rewords it.
|
|
44
|
+
*
|
|
45
|
+
* Deliberately narrow. Dropping the entry on *any* failure would also
|
|
46
|
+
* self-heal, but it would retire a perfectly good prepared statement
|
|
47
|
+
* every time an ordinary error came back - a unique violation on a hot
|
|
48
|
+
* upsert would keep costing the statement its name - and re-running a
|
|
49
|
+
* statement that failed for its own reasons buys nothing.
|
|
50
|
+
*/
|
|
51
|
+
function isStaleCachedPlan(e) {
|
|
52
|
+
const code = e?.code;
|
|
53
|
+
return code === '0A000' || code === 'XX000';
|
|
54
|
+
}
|
|
31
55
|
/**
|
|
32
56
|
* Whether a command tag's row count describes rows the statement changed,
|
|
33
57
|
* rather than rows it returned or moved. SELECT, FETCH, MOVE and COPY all
|
|
@@ -96,7 +120,9 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
96
120
|
this._config = Object.freeze(getConnectionConfig(config));
|
|
97
121
|
this.socket = new PgSocket(this._config);
|
|
98
122
|
this.socket.on('error', err => this._onError(err));
|
|
99
|
-
|
|
123
|
+
// The reason travels with it: 'close' on its own cannot tell an
|
|
124
|
+
// ordinary shutdown from the backend being terminated under us.
|
|
125
|
+
this.socket.on('close', (reason) => this.emit('close', reason));
|
|
100
126
|
this.socket.on('notification', payload => this.emit('notification', payload));
|
|
101
127
|
this.socket.on('connecting', () => this.emit('connecting'));
|
|
102
128
|
this._onErrorSavePoint = 'SP_' + Math.round(Math.random() * 100000000);
|
|
@@ -163,7 +189,7 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
163
189
|
// IntlConnection TWICE per close() call - confirmed live: a single
|
|
164
190
|
// close() left two listeners registered on the far side (e.g. two
|
|
165
191
|
// NOTIFY deliveries after Pool's reconnect re-subscribed twice).
|
|
166
|
-
this.socket.once('close', resolve);
|
|
192
|
+
this.socket.once('close', () => resolve());
|
|
167
193
|
this.socket.sendTerminateMessage(() => {
|
|
168
194
|
this.socket.close();
|
|
169
195
|
});
|
|
@@ -615,9 +641,11 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
615
641
|
*
|
|
616
642
|
* A cached statement can be invalidated under us - `alter table` between
|
|
617
643
|
* two calls makes PostgreSQL answer 0A000, "cached plan must not change
|
|
618
|
-
* result type"
|
|
619
|
-
*
|
|
620
|
-
*
|
|
644
|
+
* result type", and a dropped and recreated type makes it answer XX000,
|
|
645
|
+
* "cache lookup failed for type". Both are caught here (see
|
|
646
|
+
* isStaleCachedPlan): the entry is dropped and the query runs again
|
|
647
|
+
* unprepared, so a migration against a live connection recovers instead
|
|
648
|
+
* of failing every call from then on.
|
|
621
649
|
*/
|
|
622
650
|
async queryCached(sql, paramTypes, params, options) {
|
|
623
651
|
const savepoint = this._inlineSavepointFor(sql, options);
|
|
@@ -661,7 +689,7 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
661
689
|
return await this.executeReused(cached.name, cached.fields, paramTypes, params, options, savepoint);
|
|
662
690
|
}
|
|
663
691
|
catch (e) {
|
|
664
|
-
if (e
|
|
692
|
+
if (!isStaleCachedPlan(e))
|
|
665
693
|
throw e;
|
|
666
694
|
this._preparedCache.delete(key);
|
|
667
695
|
// ROLLBACK TO leaves the savepoint itself in place, so the retry
|
|
@@ -681,7 +709,10 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
681
709
|
// Parse+Describe round trip per connection instead of leaving every
|
|
682
710
|
// call until the statement earns a name asking for the whole row as
|
|
683
711
|
// text (what queryOnce() falls back to).
|
|
684
|
-
|
|
712
|
+
// unknownTypesAsString needs the columns known before the Bind for
|
|
713
|
+
// the same reason fetchAsString does, so it earns a name the same way.
|
|
714
|
+
const needsFields = !!options.fetchAsString?.length || !!options.unknownTypesAsString;
|
|
715
|
+
if (!needsFields && !this._earnsAName(key))
|
|
685
716
|
return this.queryOnce(sql, paramTypes, params, options, savepoint);
|
|
686
717
|
const name = 'C_' + ++this._preparedCounter;
|
|
687
718
|
// A Parse that fails caches nothing and reports itself; the statement
|
|
@@ -769,7 +800,7 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
769
800
|
// _queryCached() prepares a fetchAsString query on first sight
|
|
770
801
|
// precisely so it can send per-column codes from the second call on
|
|
771
802
|
// (from the first, for every connection that has seen the SQL).
|
|
772
|
-
const columnFormat = options.fetchAsString?.length
|
|
803
|
+
const columnFormat = options.fetchAsString?.length || options.unknownTypesAsString
|
|
773
804
|
? DataFormat.text
|
|
774
805
|
: options.columnFormat != null
|
|
775
806
|
? options.columnFormat
|
|
@@ -1027,8 +1058,8 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
1027
1058
|
// per-column codes; the rest fall back to asking for the whole row
|
|
1028
1059
|
// as text, exactly as queryOnce() does and for the same reason.
|
|
1029
1060
|
plan.columnFormat = plan.fields
|
|
1030
|
-
? resolveColumnFormats(plan.fields, options)
|
|
1031
|
-
: options.fetchAsString?.length
|
|
1061
|
+
? resolveColumnFormats(plan.fields, options, typeMap)
|
|
1062
|
+
: options.fetchAsString?.length || options.unknownTypesAsString
|
|
1032
1063
|
? DataFormat.text
|
|
1033
1064
|
: options.columnFormat != null
|
|
1034
1065
|
? options.columnFormat
|
|
@@ -1129,11 +1160,10 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
1129
1160
|
}
|
|
1130
1161
|
catch (e) {
|
|
1131
1162
|
// A cached statement can be invalidated between two pipelines the
|
|
1132
|
-
// same way a cached single query can
|
|
1133
|
-
//
|
|
1134
|
-
//
|
|
1135
|
-
|
|
1136
|
-
if (e?.code !== '0A000' || !cachedKeys.length)
|
|
1163
|
+
// same way a cached single query can - see isStaleCachedPlan().
|
|
1164
|
+
// Drop what this pipeline reused and run it again unprepared -
|
|
1165
|
+
// once, since the second attempt has nothing cached left to reuse.
|
|
1166
|
+
if (!isStaleCachedPlan(e) || !cachedKeys.length)
|
|
1137
1167
|
throw e;
|
|
1138
1168
|
for (const k of cachedKeys)
|
|
1139
1169
|
this._preparedCache.delete(k);
|
|
@@ -1178,7 +1208,7 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
1178
1208
|
const rowDecoder = resolveRowDecoder(options);
|
|
1179
1209
|
let parsers;
|
|
1180
1210
|
let resultFields;
|
|
1181
|
-
const columnFormat = resolveColumnFormats(cachedFields, options);
|
|
1211
|
+
const columnFormat = resolveColumnFormats(cachedFields, options, typeMap);
|
|
1182
1212
|
if (cachedFields) {
|
|
1183
1213
|
const resolved = this._resolveReusedParsers(cachedFields, typeMap, options, columnFormat);
|
|
1184
1214
|
parsers = resolved.parsers;
|
|
@@ -1289,7 +1319,7 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
1289
1319
|
// RowDescription is already in hand, so fetchAsString's OID list can
|
|
1290
1320
|
// become the per-column result format codes this Bind carries, with
|
|
1291
1321
|
// no extra round trip to find out what the columns are.
|
|
1292
|
-
const columnFormat = resolveColumnFormats(cachedFields, options);
|
|
1322
|
+
const columnFormat = resolveColumnFormats(cachedFields, options, typeMap);
|
|
1293
1323
|
if (cachedFields) {
|
|
1294
1324
|
const resolved = this._resolveReusedParsers(cachedFields, typeMap, options, columnFormat);
|
|
1295
1325
|
parsers = resolved.parsers;
|
package/connection/pool.d.ts
CHANGED
|
@@ -37,6 +37,14 @@ export declare class Pool extends SafeEventEmitter {
|
|
|
37
37
|
protected readonly _notificationListeners: SafeEventEmitter;
|
|
38
38
|
protected _notificationConnection?: Connection;
|
|
39
39
|
protected readonly _pipelineSlots: PipelineSlot[];
|
|
40
|
+
/**
|
|
41
|
+
* Why a connection is about to be destroyed, for the ones that were not
|
|
42
|
+
* destroyed on purpose. lightning-pool's own 'destroy' carries the
|
|
43
|
+
* resource and nothing else, so the reason is recorded on the way in -
|
|
44
|
+
* when the connection reports its close - and read back out when the
|
|
45
|
+
* pool reports the removal.
|
|
46
|
+
*/
|
|
47
|
+
protected readonly _destroyReasons: WeakMap<IntlConnection, Error>;
|
|
40
48
|
protected _pipelineMaxQueries: number;
|
|
41
49
|
protected _pipelineMaxConnections: number;
|
|
42
50
|
readonly config: PoolConfiguration;
|
|
@@ -91,6 +99,20 @@ export declare class Pool extends SafeEventEmitter {
|
|
|
91
99
|
listen(channel: string, callback: NotificationCallback): Promise<void>;
|
|
92
100
|
unListen(channel: string): Promise<void>;
|
|
93
101
|
unListenAll(): Promise<void>;
|
|
102
|
+
/**
|
|
103
|
+
* A pooled connection reported that its socket closed. `reason` is set
|
|
104
|
+
* only when that was not asked for - see PgSocket._handleClose().
|
|
105
|
+
*/
|
|
106
|
+
protected _onConnectionClosed(intlCon: IntlConnection, reason?: Error): void;
|
|
107
|
+
/**
|
|
108
|
+
* The pool has removed a connection. `destroy` fires the same way for an
|
|
109
|
+
* ordinary eviction (idle timeout, close(), a failed validate) as for a
|
|
110
|
+
* connection that died, so the reason is what tells them apart - an
|
|
111
|
+
* admin kill, a failover and a network fault were otherwise
|
|
112
|
+
* indistinguishable from a timeout, with nothing at all reported when
|
|
113
|
+
* the connection had no query in flight to reject.
|
|
114
|
+
*/
|
|
115
|
+
protected _onConnectionDestroyed(intlCon: IntlConnection): void;
|
|
94
116
|
protected _canPipeline(pipeline: boolean | undefined, sql: string, autoCommit: boolean | undefined, signal: AbortSignal | undefined): boolean;
|
|
95
117
|
/**
|
|
96
118
|
* Picks the pooled connection a pipelined query should ride on, or
|
package/connection/pool.js
CHANGED
|
@@ -13,6 +13,14 @@ export class Pool extends SafeEventEmitter {
|
|
|
13
13
|
_notificationListeners = new SafeEventEmitter();
|
|
14
14
|
_notificationConnection;
|
|
15
15
|
_pipelineSlots = [];
|
|
16
|
+
/**
|
|
17
|
+
* Why a connection is about to be destroyed, for the ones that were not
|
|
18
|
+
* destroyed on purpose. lightning-pool's own 'destroy' carries the
|
|
19
|
+
* resource and nothing else, so the reason is recorded on the way in -
|
|
20
|
+
* when the connection reports its close - and read back out when the
|
|
21
|
+
* pool reports the removal.
|
|
22
|
+
*/
|
|
23
|
+
_destroyReasons = new WeakMap();
|
|
16
24
|
_pipelineMaxQueries;
|
|
17
25
|
_pipelineMaxConnections;
|
|
18
26
|
config;
|
|
@@ -46,7 +54,7 @@ export class Pool extends SafeEventEmitter {
|
|
|
46
54
|
/* c8 ignore stop */
|
|
47
55
|
const intlCon = new IntlConnection(cfg);
|
|
48
56
|
await intlCon.connect();
|
|
49
|
-
intlCon.on('close', () => this.
|
|
57
|
+
intlCon.on('close', (reason) => this._onConnectionClosed(intlCon, reason));
|
|
50
58
|
/* c8 ignore start */
|
|
51
59
|
if (this.listenerCount('debug')) {
|
|
52
60
|
this.emit('debug', {
|
|
@@ -82,7 +90,7 @@ export class Pool extends SafeEventEmitter {
|
|
|
82
90
|
/* c8 ignore stop */
|
|
83
91
|
intlCon.owner = undefined;
|
|
84
92
|
intlCon.removeAllListeners();
|
|
85
|
-
intlCon.once('close', () => this.
|
|
93
|
+
intlCon.once('close', (reason) => this._onConnectionClosed(intlCon, reason));
|
|
86
94
|
intlCon._refCount = 0;
|
|
87
95
|
},
|
|
88
96
|
validate: async (intlCon) => {
|
|
@@ -104,7 +112,7 @@ export class Pool extends SafeEventEmitter {
|
|
|
104
112
|
this._pool.on('return', (...args) => this.emit('release', ...args));
|
|
105
113
|
this._pool.on('error', (...args) => this.emit('error', ...args));
|
|
106
114
|
this._pool.on('acquire', (...args) => this.emit('acquire', ...args));
|
|
107
|
-
this._pool.on('destroy', (
|
|
115
|
+
this._pool.on('destroy', (intlCon) => this._onConnectionDestroyed(intlCon));
|
|
108
116
|
}
|
|
109
117
|
/**
|
|
110
118
|
* Returns the number of connections that are currently acquired
|
|
@@ -313,6 +321,40 @@ export class Pool extends SafeEventEmitter {
|
|
|
313
321
|
await conn.close();
|
|
314
322
|
}
|
|
315
323
|
}
|
|
324
|
+
/**
|
|
325
|
+
* A pooled connection reported that its socket closed. `reason` is set
|
|
326
|
+
* only when that was not asked for - see PgSocket._handleClose().
|
|
327
|
+
*/
|
|
328
|
+
_onConnectionClosed(intlCon, reason) {
|
|
329
|
+
if (reason)
|
|
330
|
+
this._destroyReasons.set(intlCon, reason);
|
|
331
|
+
this._pool.destroy(intlCon);
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* The pool has removed a connection. `destroy` fires the same way for an
|
|
335
|
+
* ordinary eviction (idle timeout, close(), a failed validate) as for a
|
|
336
|
+
* connection that died, so the reason is what tells them apart - an
|
|
337
|
+
* admin kill, a failover and a network fault were otherwise
|
|
338
|
+
* indistinguishable from a timeout, with nothing at all reported when
|
|
339
|
+
* the connection had no query in flight to reject.
|
|
340
|
+
*/
|
|
341
|
+
_onConnectionDestroyed(intlCon) {
|
|
342
|
+
const reason = this._destroyReasons.get(intlCon);
|
|
343
|
+
if (reason)
|
|
344
|
+
this._destroyReasons.delete(intlCon);
|
|
345
|
+
this.emit('destroy', intlCon, reason);
|
|
346
|
+
if (!reason)
|
|
347
|
+
return;
|
|
348
|
+
// Reported on 'error' as well, which is where pg puts a pooled
|
|
349
|
+
// connection dying and where code ported from it listens. Safe to do
|
|
350
|
+
// unconditionally here: SafeEventEmitter drops an 'error' that has no
|
|
351
|
+
// listener instead of throwing, so this cannot turn a recovery the
|
|
352
|
+
// pool already made into a crashed process. The error names the pid
|
|
353
|
+
// and carries the socket error as its cause; no second argument, so
|
|
354
|
+
// the existing `(err, meta)` shape lightning-pool emits for a
|
|
355
|
+
// connection that could not be created stays unambiguous.
|
|
356
|
+
this.emit('error', reason);
|
|
357
|
+
}
|
|
316
358
|
_canPipeline(pipeline, sql, autoCommit, signal) {
|
|
317
359
|
return (pipeline === true &&
|
|
318
360
|
!signal &&
|
|
@@ -211,9 +211,9 @@ export class PreparedStatement extends SafeEventEmitter {
|
|
|
211
211
|
// prepare() already fetched this statement's RowDescription, so
|
|
212
212
|
// fetchAsString's OID list can become per-column result format
|
|
213
213
|
// codes on the Bind below with no round trip of its own.
|
|
214
|
-
const columnFormat = resolveColumnFormats(this._fields, options);
|
|
215
|
-
const fields = await portal.bindAndRetrieveFields(options.params, options, columnFormat);
|
|
216
214
|
const typeMap = options.typeMap || GlobalTypeMap;
|
|
215
|
+
const columnFormat = resolveColumnFormats(this._fields, options, typeMap);
|
|
216
|
+
const fields = await portal.bindAndRetrieveFields(options.params, options, columnFormat);
|
|
217
217
|
const parsers = getParsers(typeMap, fields, options);
|
|
218
218
|
const resultFields = wrapRowDescription(typeMap, fields, columnFormat, options);
|
|
219
219
|
const result = {
|
package/constants.d.ts
CHANGED
|
@@ -27,6 +27,7 @@ export declare const DataTypeOIDs: {
|
|
|
27
27
|
oidvector: number;
|
|
28
28
|
json: number;
|
|
29
29
|
xml: number;
|
|
30
|
+
pg_node_tree: number;
|
|
30
31
|
point: number;
|
|
31
32
|
lseg: number;
|
|
32
33
|
path: number;
|
|
@@ -42,6 +43,7 @@ export declare const DataTypeOIDs: {
|
|
|
42
43
|
money: number;
|
|
43
44
|
macaddr: number;
|
|
44
45
|
inet: number;
|
|
46
|
+
aclitem: number;
|
|
45
47
|
bpchar: number;
|
|
46
48
|
varchar: number;
|
|
47
49
|
date: number;
|
|
@@ -70,14 +72,40 @@ export declare const DataTypeOIDs: {
|
|
|
70
72
|
anyelement: number;
|
|
71
73
|
anynonarray: number;
|
|
72
74
|
uuid: number;
|
|
75
|
+
txid_snapshot: number;
|
|
76
|
+
pg_lsn: number;
|
|
77
|
+
pg_ndistinct: number;
|
|
78
|
+
pg_dependencies: number;
|
|
79
|
+
tsvector: number;
|
|
80
|
+
tsquery: number;
|
|
81
|
+
gtsvector: number;
|
|
82
|
+
regconfig: number;
|
|
83
|
+
regdictionary: number;
|
|
73
84
|
jsonb: number;
|
|
74
85
|
anyrange: number;
|
|
75
86
|
int4range: number;
|
|
76
87
|
numrange: number;
|
|
77
88
|
tsrange: number;
|
|
89
|
+
tstzrange: number;
|
|
90
|
+
/** @deprecated Misspelling of `tstzrange`, kept so it keeps resolving. */
|
|
78
91
|
rstzrange: number;
|
|
79
92
|
daterange: number;
|
|
80
93
|
int8range: number;
|
|
94
|
+
jsonpath: number;
|
|
95
|
+
regnamespace: number;
|
|
96
|
+
regrole: number;
|
|
97
|
+
regcollation: number;
|
|
98
|
+
int4multirange: number;
|
|
99
|
+
nummultirange: number;
|
|
100
|
+
tsmultirange: number;
|
|
101
|
+
tstzmultirange: number;
|
|
102
|
+
datemultirange: number;
|
|
103
|
+
int8multirange: number;
|
|
104
|
+
pg_brin_bloom_summary: number;
|
|
105
|
+
pg_brin_minmax_multi_summary: number;
|
|
106
|
+
pg_mcv_list: number;
|
|
107
|
+
pg_snapshot: number;
|
|
108
|
+
xid8: number;
|
|
81
109
|
_xml: number;
|
|
82
110
|
_json: number;
|
|
83
111
|
_xid8: number;
|
|
@@ -110,6 +138,7 @@ export declare const DataTypeOIDs: {
|
|
|
110
138
|
_float8: number;
|
|
111
139
|
_polygon: number;
|
|
112
140
|
_oid: number;
|
|
141
|
+
_aclitem: number;
|
|
113
142
|
_macaddr: number;
|
|
114
143
|
_inet: number;
|
|
115
144
|
_timestamp: number;
|
|
@@ -122,7 +151,38 @@ export declare const DataTypeOIDs: {
|
|
|
122
151
|
_timetz: number;
|
|
123
152
|
_bit: number;
|
|
124
153
|
_varbit: number;
|
|
154
|
+
_refcursor: number;
|
|
155
|
+
_regprocedure: number;
|
|
156
|
+
_regoper: number;
|
|
157
|
+
_regoperator: number;
|
|
158
|
+
_regclass: number;
|
|
159
|
+
_regtype: number;
|
|
160
|
+
_record: number;
|
|
161
|
+
_txid_snapshot: number;
|
|
125
162
|
_uuid: number;
|
|
163
|
+
_pg_lsn: number;
|
|
164
|
+
_tsvector: number;
|
|
165
|
+
_gtsvector: number;
|
|
166
|
+
_tsquery: number;
|
|
167
|
+
_regconfig: number;
|
|
168
|
+
_regdictionary: number;
|
|
126
169
|
_jsonb: number;
|
|
170
|
+
_int4range: number;
|
|
171
|
+
_numrange: number;
|
|
172
|
+
_tsrange: number;
|
|
173
|
+
_tstzrange: number;
|
|
174
|
+
_daterange: number;
|
|
175
|
+
_int8range: number;
|
|
176
|
+
_jsonpath: number;
|
|
177
|
+
_regnamespace: number;
|
|
178
|
+
_regrole: number;
|
|
179
|
+
_regcollation: number;
|
|
180
|
+
_pg_snapshot: number;
|
|
181
|
+
_int4multirange: number;
|
|
182
|
+
_nummultirange: number;
|
|
183
|
+
_tsmultirange: number;
|
|
184
|
+
_tstzmultirange: number;
|
|
185
|
+
_datemultirange: number;
|
|
186
|
+
_int8multirange: number;
|
|
127
187
|
};
|
|
128
188
|
export declare const DataTypeNames: Record<string, string>;
|