postgrejs 3.6.1 → 3.8.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 +66 -6
- package/README.md +9 -83
- package/connection/connection.js +13 -1
- package/connection/intl-connection.d.ts +5 -3
- package/connection/intl-connection.js +70 -20
- package/connection/pool.js +6 -0
- package/connection/prepared-statement.d.ts +11 -0
- package/connection/prepared-statement.js +19 -5
- 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 +41 -0
- package/data-types/classes/numeric.js +51 -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/int2-vector-type.js +4 -1
- 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/oid-vector-type.js +4 -1
- 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/timestamptz-type.js +9 -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 +4 -0
- package/index.js +4 -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 +5 -3
- package/protocol/connection-lost-error.js +5 -3
- package/protocol/frontend.js +17 -0
- package/protocol/pg-socket.js +16 -2
- package/util/decode-binaryarray.js +8 -1
- package/util/encode-binaryarray.d.ts +12 -1
- package/util/encode-binaryarray.js +13 -2
- package/util/format-datetime.d.ts +18 -0
- package/util/format-datetime.js +33 -0
- 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/sql-tag.js +15 -0
- package/util/stringify-for-sql.js +44 -1
- package/util/unspecified-param.d.ts +32 -0
- package/util/unspecified-param.js +40 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,63 @@
|
|
|
1
1
|
## Changelog
|
|
2
2
|
|
|
3
|
-
### [v3.
|
|
3
|
+
### [v3.8.0](https://github.com/panates/postgrejs/compare/v3.7.0...v3.8.0) -
|
|
4
|
+
|
|
5
|
+
#### 🚀 New Features
|
|
6
|
+
|
|
7
|
+
- feat: deliver server notices to the connection @Eray Hanoğlu
|
|
8
|
+
|
|
9
|
+
#### 🪲 Fixes
|
|
10
|
+
|
|
11
|
+
- fix: send a string parameter unspecified, so the column decides @Eray Hanoğlu
|
|
12
|
+
- fix: send a Date parameter unspecified, so the column decides @Eray Hanoğlu
|
|
13
|
+
- fix: write a lower bound of 1 for an ordinary binary array @Eray Hanoğlu
|
|
14
|
+
|
|
15
|
+
#### 🧪 Changes to Test Assests
|
|
16
|
+
|
|
17
|
+
- test: skip the types PostgreSQL 12 does not have @Eray Hanoğlu
|
|
18
|
+
|
|
19
|
+
### [v3.7.0](https://github.com/panates/postgrejs/compare/v3.6.1...v3.7.0) - 20 September 2026
|
|
20
|
+
|
|
21
|
+
#### Breaking Changes :warning:
|
|
22
|
+
|
|
23
|
+
- feat!: decode a numeric that a double cannot hold into a Numeric @Eray Hanoğlu
|
|
24
|
+
- build: let the changelog template see a breaking change @Eray Hanoğlu
|
|
25
|
+
|
|
26
|
+
#### 🚀 New Features
|
|
27
|
+
|
|
28
|
+
- feat: decode the network types @Eray Hanoğlu
|
|
29
|
+
- feat: decode line, path and polygon @Eray Hanoğlu
|
|
30
|
+
- feat: decode the system columns, and name the types that cannot be @Eray Hanoğlu
|
|
31
|
+
- feat: decode the range and multirange types @Eray Hanoğlu
|
|
32
|
+
- feat: decode the geometric types into classes of their own @Eray Hanoğlu
|
|
33
|
+
- feat: decode tsvector and tsquery @Eray Hanoğlu
|
|
34
|
+
- feat: decode interval @Eray Hanoğlu
|
|
35
|
+
- feat: decode bit, varbit and jsonpath @Eray Hanoğlu
|
|
36
|
+
- feat: decode timetz @Eray Hanoğlu
|
|
37
|
+
- feat: decode refcursor, pg_node_tree and the snapshot types @Eray Hanoğlu
|
|
38
|
+
- feat: add unknownTypesAsString @Eray Hanoğlu
|
|
39
|
+
- feat: let a decoded value carry the type it came from @Eray Hanoğlu
|
|
40
|
+
- feat: reject the in-flight query with ConnectionLostError too @Eray Hanoğlu
|
|
41
|
+
|
|
42
|
+
#### 🪲 Fixes
|
|
43
|
+
|
|
44
|
+
- fix: write an object as its own SQL literal, not as json @Eray Hanoğlu
|
|
45
|
+
- fix: complete the type OID table, and check it against the catalog @Eray Hanoğlu
|
|
46
|
+
- fix: recover when a recreated type invalidates a cached statement @Eray Hanoğlu
|
|
47
|
+
- fix: keep a quoted empty element when parsing an array literal @Eray Hanoğlu
|
|
48
|
+
|
|
49
|
+
#### 📖 Documentation Changes
|
|
50
|
+
|
|
51
|
+
- docs: cut the Features list back down @Eray Hanoğlu
|
|
52
|
+
- docs: drop the Features section @Eray Hanoğlu
|
|
53
|
+
- docs: bring the data type counts up to what is registered @Eray Hanoğlu
|
|
54
|
+
- docs: give the documentation link its own heading @Eray Hanoğlu
|
|
55
|
+
|
|
56
|
+
#### ⚡️ Performance Improvments
|
|
57
|
+
|
|
58
|
+
- perf: build a numeric's digits without a concatenation per digit @Eray Hanoğlu
|
|
59
|
+
|
|
60
|
+
### [v3.6.1](https://github.com/panates/postgrejs/compare/v3.6.0...v3.6.1) - 20 September 2026
|
|
4
61
|
|
|
5
62
|
#### 🚀 New Features
|
|
6
63
|
|
|
@@ -16,6 +73,10 @@
|
|
|
16
73
|
|
|
17
74
|
### [v3.6.0](https://github.com/panates/postgrejs/compare/v3.5.0...v3.6.0) - 20 September 2026
|
|
18
75
|
|
|
76
|
+
#### Breaking Changes :warning:
|
|
77
|
+
|
|
78
|
+
- feat!: fetch every row by default instead of the first 100 @Eray Hanoğlu
|
|
79
|
+
|
|
19
80
|
#### 🚀 New Features
|
|
20
81
|
|
|
21
82
|
- feat: make fetchAsString request text from the server @Eray Hanoğlu
|
|
@@ -35,10 +96,6 @@
|
|
|
35
96
|
|
|
36
97
|
- docs: record what 3.6 changes for existing code @Eray Hanoğlu
|
|
37
98
|
|
|
38
|
-
#### 💬 General Changes
|
|
39
|
-
|
|
40
|
-
- feat!: fetch every row by default instead of the first 100 @Eray Hanoğlu
|
|
41
|
-
|
|
42
99
|
### [v3.5.0](https://github.com/panates/postgrejs/compare/v3.4.0...v3.5.0) - 16 September 2026
|
|
43
100
|
|
|
44
101
|
#### 🚀 New Features
|
|
@@ -95,6 +152,10 @@
|
|
|
95
152
|
|
|
96
153
|
### [v3.3.0](https://github.com/panates/postgrejs/compare/v3.2.0...v3.3.0) - 14 September 2026
|
|
97
154
|
|
|
155
|
+
#### Breaking Changes :warning:
|
|
156
|
+
|
|
157
|
+
- feat!: give decodeBinary the value's length, not a slice of it @Eray Hanoğlu
|
|
158
|
+
|
|
98
159
|
#### 🚀 New Features
|
|
99
160
|
|
|
100
161
|
- feat: add first-class Bun support with dedicated benchmark suite @Eray Hanoğlu
|
|
@@ -123,7 +184,6 @@
|
|
|
123
184
|
#### 💬 General Changes
|
|
124
185
|
|
|
125
186
|
- bench: report near-ties as ties instead of ranking them @Eray Hanoğlu
|
|
126
|
-
- feat!: give decodeBinary the value's length, not a slice of it @Eray Hanoğlu
|
|
127
187
|
- bench: stop measuring type-declaration policy in the Extended Query scenarios @Eray Hanoğlu
|
|
128
188
|
- bench: stop one stalled repeat from making every result a tie @Eray Hanoğlu
|
|
129
189
|
- 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,82 +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. A connection that dies rather than being closed says so: `'close'` carries the reason, and a
|
|
78
|
-
pooled one is reported on the pool's `'destroy'` and `'error'` events - so an admin kill or a failover is
|
|
79
|
-
distinguishable from an ordinary eviction, including when the connection was sitting idle with no query to reject.
|
|
80
|
-
The reason is a `ConnectionLostError` carrying the backend's `processID`, and `code` `'08006'` so branching on it
|
|
81
|
-
needs no `instanceof`.
|
|
82
|
-
- **Binary Wire Protocol:** Implements the full binary wire protocol for all PostgreSQL data types, ensuring robust and
|
|
83
|
-
efficient data handling.
|
|
84
|
-
- **Prepared Statements:** Named prepared statements for optimized query execution, and a per-connection cache that
|
|
85
|
-
reuses one automatically for SQL the connection has run before - a repeated query costs `Bind`/`Execute` instead of
|
|
86
|
-
parsing again, worth 3.2x on fifty concurrent calls and 2.9x again on a query inside an open transaction. On by
|
|
87
|
-
default, `prepare: false` to opt out.
|
|
88
|
-
- **Statement-Level Rollback:** Inside a transaction each statement runs under a savepoint of its own, so a failed
|
|
89
|
-
statement leaves the transaction usable instead of aborting the whole block. The `SAVEPOINT`/`RELEASE` pair travels
|
|
90
|
-
in the statement's own round trip rather than costing one each - 36µs for the pair, against 507µs sent separately.
|
|
91
|
-
`rollbackOnError: false` to opt out.
|
|
92
|
-
- **Complete Results:** `query()` returns every row the statement produced. A `fetchCount` limit is a limit rather
|
|
93
|
-
than a silent cap: the result carries `suspended: true` when the server stopped at it, so a prefix can never be
|
|
94
|
-
mistaken for the whole answer. `0` means unlimited, as it does in the protocol.
|
|
95
|
-
- **Cursors:** Features fast double-link cache cursors for efficient data retrieval, iterable with `for await` a row
|
|
96
|
-
at a time and closed when the loop ends - by exhaustion, a `break`, or a throw. A cursor reads through a portal, and
|
|
97
|
-
a portal lives as long as the transaction that created it - `Pool.query()` keeps its connection out of the pool
|
|
98
|
-
until the cursor closes, and on a bare `Connection` open the cursor inside a transaction if anything else will run
|
|
99
|
-
on that connection meanwhile.
|
|
100
|
-
- **Scoped Transactions:** `transaction(fn)` commits when the callback returns and rolls back when it throws, on a
|
|
101
|
-
connection or straight from the pool. A call made while a transaction is already open takes a savepoint rather than
|
|
102
|
-
a second `BEGIN`, so a nested scope that fails rolls back its own work and leaves the outer transaction standing.
|
|
103
|
-
- **Batch Execution:** `executeBatch()` runs one prepared statement over many parameter sets under a single `Sync`,
|
|
104
|
-
reporting each set's row count - 1000 updates in 20ms where the same calls pipelined individually take 188ms.
|
|
105
|
-
- **Notifications:** High-level implementation for PostgreSQL notifications (LISTEN/NOTIFY), enabling real-time data
|
|
106
|
-
updates. Channel names are quoted rather than pattern-matched, so any name PostgreSQL accepts works.
|
|
107
|
-
- **Extensibility:** Extensible data-types and type mapping to accommodate custom requirements. A type can set
|
|
108
|
-
`inferrable: false` to stay out of parameter type inference while still decoding its own columns.
|
|
109
|
-
- **Parameter Binding:** Bind parameters with OID mappings for precise and efficient query execution.
|
|
110
|
-
- **Array Handling:** Supports multidimensional arrays with fast binary encoding/decoding.
|
|
111
|
-
- **Performance Optimization:** Low memory utilization and boosted performance through the use of shared buffers.
|
|
112
|
-
- **Authorization:** Supports various password algorithms including Clear text, MD5, and SASL, ensuring secure
|
|
113
|
-
authentication.
|
|
114
|
-
- **Bulk Import/Export:** `COPY TO STDOUT` and `COPY FROM STDIN` as Node streams, with backpressure in both directions,
|
|
115
|
-
plus `copyFromRows()`, which encodes rows straight into binary `COPY` - around 4x faster than the CSV equivalent and
|
|
116
|
-
no text escaping to get wrong. Takes arrays or objects from anything iterable, so a file larger than memory streams in.
|
|
117
|
-
- **Query Pipelining:** Pooled queries can share connections so a burst is not capped by pool size - opt-in per call.
|
|
118
|
-
`pipeline()` goes further for a known set of statements: several different ones travel under a single `Sync`, so they
|
|
119
|
-
cost one round trip instead of one each and commit or roll back together - around 2x faster than the same calls
|
|
120
|
-
through `Promise.all()`, which is already pipelined. Statements the connection has run before bind to their cached
|
|
121
|
-
prepared names, so a repeated set sends no `Parse` at all - worth 2.5x on twenty statements.
|
|
122
|
-
- **Dynamic SQL:** A `sql` tag builds statements from composable fragments - values become parameters, names are quoted,
|
|
123
|
-
and `sql.values()`/`sql.set()` write INSERT and UPDATE clauses from objects.
|
|
124
|
-
- **Multiple Hosts:** A connection can list several servers and pick one by role
|
|
125
|
-
(`target_session_attrs`), so a cluster that has failed over is found on the next connect.
|
|
126
|
-
- **Large Objects:** File-like access to binary data stored outside the row - seek, partial reads, streams - for
|
|
127
|
-
values past what a `bytea` column can hold.
|
|
128
|
-
- **Logical Replication:** `LogicalReplication` streams committed row changes as an async iterable, decoding
|
|
129
|
-
`pgoutput` itself, with client-side filtering and positions confirmed as you consume.
|
|
130
|
-
- **Channel Binding:** SCRAM authentication binds itself to the TLS channel when the server offers it, the way libpq
|
|
131
|
-
does by default, so a relayed login is detected even where the certificate is not verified.
|
|
132
|
-
- **Two-phase commit:** `prepareTransaction()` leaves a transaction waiting under a name for `commitPrepared()`/
|
|
133
|
-
`rollbackPrepared()`, from any connection.
|
|
134
|
-
- **Cancellation:** Any call takes an `AbortSignal`, which also gives per-query timeouts via `AbortSignal.timeout()`.
|
|
135
|
-
- **Flexible Data Retrieval:** Can return both array and object rows to suit different data processing needs.
|
|
136
|
-
- **Values as Text:** `fetchAsString: [DataTypeOIDs.int8]` asks the server for those columns in its own text format
|
|
137
|
-
and hands the bytes back unparsed - so `count(*)` arrives as `'3'`, one consistent type where a decoded `int8` is a
|
|
138
|
-
`number` or a `BigInt` depending on magnitude. Any OID, arrays included. The string is PostgreSQL's own rendering
|
|
139
|
-
rather than one reconstructed from the binary value, so it cannot drift from what the server would print.
|
|
140
|
-
- **Resource Management:** Auto disposal of resources with the "using" syntax
|
|
141
|
-
([TC39 Explicit Resource Management](https://github.com/tc39/proposal-explicit-resource-management)), ensuring
|
|
142
|
-
efficient resource cleanup.
|
|
143
|
-
- **Long Cancel Key:** Opt in to protocol 3.2 (PostgreSQL 18+) with `longCancelKey`, so a `cancel()` in progress can't
|
|
144
|
-
be forged by an attacker guessing a short secret key.
|
|
145
|
-
- **Legacy Function Call Protocol:** `callFunction()` calls a function by OID directly over the wire, bypassing SQL
|
|
146
|
-
entirely - kept for protocol completeness even though `SELECT func(...)` covers the same ground.
|
|
147
|
-
- **Graceful Protocol Renegotiation:** A server that doesn't recognize a requested protocol version or startup option
|
|
148
|
-
reports back instead of erroring out, surfaced on `Connection.protocolNegotiation`.
|
|
149
|
-
|
|
150
73
|
## Feature Comparison
|
|
151
74
|
|
|
152
75
|
How PostgreJS compares to [`pg`](https://github.com/brianc/node-postgres) (node-postgres) and
|
|
@@ -204,10 +127,10 @@ in · 🟡 partial or needs a separate package · ❌ not supported.
|
|
|
204
127
|
| Multiple hosts | ✅ | ❌ | ✅ |
|
|
205
128
|
| LISTEN/NOTIFY | ✅ | 🟡 <sup>15</sup> | ✅ |
|
|
206
129
|
| ***Data types*** | | | |
|
|
207
|
-
| Text encoders |
|
|
208
|
-
| Text decoders |
|
|
209
|
-
| Binary encoders |
|
|
210
|
-
| 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 | ❌ |
|
|
211
134
|
| Multidimensional arrays | ✅ binary | 🟡 text <sup>18</sup> | 🟡 text |
|
|
212
135
|
| ***Security*** | | | |
|
|
213
136
|
| SSL/TLS | ✅ | ✅ | ✅ |
|
|
@@ -294,7 +217,10 @@ in · 🟡 partial or needs a separate package · ❌ not supported.
|
|
|
294
217
|
a savepoint, so nesting needs nothing from the caller. postgres.js has `sql.begin(fn)` (`src/index.js`), where a
|
|
295
218
|
nested scope is opened explicitly with `sql.savepoint(fn)` instead. pg has no such helper in `lib/` - `BEGIN` and
|
|
296
219
|
`COMMIT` are statements the caller sends, and getting a transaction onto one connection is the caller's problem too.
|
|
297
|
-
|
|
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.
|
|
298
224
|
|
|
299
225
|
## Benchmarks
|
|
300
226
|
|
package/connection/connection.js
CHANGED
|
@@ -5,6 +5,7 @@ import { withAbortSignal } from '../util/abort-signal.js';
|
|
|
5
5
|
import { normalizeChannelName } from '../util/channel-name.js';
|
|
6
6
|
import { escapeIdentifier } from '../util/escape-identifier.js';
|
|
7
7
|
import { QueryRequest } from '../util/sql-tag.js';
|
|
8
|
+
import { isUnspecifiedParam } from '../util/unspecified-param.js';
|
|
8
9
|
import { BindParam } from './bind-param.js';
|
|
9
10
|
import { IntlConnection } from './intl-connection.js';
|
|
10
11
|
import { LargeObject, LargeObjectMode } from './large-object.js';
|
|
@@ -631,7 +632,18 @@ export class Connection extends SafeEventEmitter {
|
|
|
631
632
|
}
|
|
632
633
|
async _query(sql, options) {
|
|
633
634
|
const typeMap = options?.typeMap || GlobalTypeMap;
|
|
634
|
-
const paramTypes = options?.params?.map(prm => prm instanceof BindParam
|
|
635
|
+
const paramTypes = options?.params?.map(prm => prm instanceof BindParam
|
|
636
|
+
? prm.oid
|
|
637
|
+
: // A Date and a string go out with no declared type, so the
|
|
638
|
+
// server resolves each from where it lands - neither can say
|
|
639
|
+
// what it is, and naming a type for them is what made a `Date`
|
|
640
|
+
// move by the client's offset and a string unusable anywhere a
|
|
641
|
+
// `varchar` is not what the context wanted. See
|
|
642
|
+
// `isUnspecifiedParam()` for what that costs. The text they go
|
|
643
|
+
// out as is written in getBindMessage().
|
|
644
|
+
isUnspecifiedParam(prm)
|
|
645
|
+
? 0
|
|
646
|
+
: typeMap.determine(prm));
|
|
635
647
|
const effectiveAutoCommit = options?.autoCommit != null
|
|
636
648
|
? options.autoCommit
|
|
637
649
|
: this._intlCon.config.autoCommit;
|
|
@@ -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
|
|
@@ -100,6 +124,10 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
100
124
|
// ordinary shutdown from the backend being terminated under us.
|
|
101
125
|
this.socket.on('close', (reason) => this.emit('close', reason));
|
|
102
126
|
this.socket.on('notification', payload => this.emit('notification', payload));
|
|
127
|
+
// Covers a notice that arrives while no statement is running. One
|
|
128
|
+
// raised *by* a statement is dispatched to that statement's own
|
|
129
|
+
// message loop instead, which is why each of those forwards it too.
|
|
130
|
+
this.socket.on('notice', payload => this.emit('notice', payload));
|
|
103
131
|
this.socket.on('connecting', () => this.emit('connecting'));
|
|
104
132
|
this._onErrorSavePoint = 'SP_' + Math.round(Math.random() * 100000000);
|
|
105
133
|
}
|
|
@@ -537,6 +565,9 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
537
565
|
new Error('COPY TO STDOUT is not supported by execute() - use copyTo() instead');
|
|
538
566
|
break;
|
|
539
567
|
case Protocol.BackendMessageCode.NoticeResponse:
|
|
568
|
+
// Not an error: handed over, and the loop carries on.
|
|
569
|
+
this.emit('notice', msg);
|
|
570
|
+
break;
|
|
540
571
|
case Protocol.BackendMessageCode.EmptyQueryResponse:
|
|
541
572
|
break;
|
|
542
573
|
case Protocol.BackendMessageCode.RowDescription:
|
|
@@ -617,9 +648,11 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
617
648
|
*
|
|
618
649
|
* A cached statement can be invalidated under us - `alter table` between
|
|
619
650
|
* two calls makes PostgreSQL answer 0A000, "cached plan must not change
|
|
620
|
-
* result type"
|
|
621
|
-
*
|
|
622
|
-
*
|
|
651
|
+
* result type", and a dropped and recreated type makes it answer XX000,
|
|
652
|
+
* "cache lookup failed for type". Both are caught here (see
|
|
653
|
+
* isStaleCachedPlan): the entry is dropped and the query runs again
|
|
654
|
+
* unprepared, so a migration against a live connection recovers instead
|
|
655
|
+
* of failing every call from then on.
|
|
623
656
|
*/
|
|
624
657
|
async queryCached(sql, paramTypes, params, options) {
|
|
625
658
|
const savepoint = this._inlineSavepointFor(sql, options);
|
|
@@ -663,7 +696,7 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
663
696
|
return await this.executeReused(cached.name, cached.fields, paramTypes, params, options, savepoint);
|
|
664
697
|
}
|
|
665
698
|
catch (e) {
|
|
666
|
-
if (e
|
|
699
|
+
if (!isStaleCachedPlan(e))
|
|
667
700
|
throw e;
|
|
668
701
|
this._preparedCache.delete(key);
|
|
669
702
|
// ROLLBACK TO leaves the savepoint itself in place, so the retry
|
|
@@ -683,7 +716,10 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
683
716
|
// Parse+Describe round trip per connection instead of leaving every
|
|
684
717
|
// call until the statement earns a name asking for the whole row as
|
|
685
718
|
// text (what queryOnce() falls back to).
|
|
686
|
-
|
|
719
|
+
// unknownTypesAsString needs the columns known before the Bind for
|
|
720
|
+
// the same reason fetchAsString does, so it earns a name the same way.
|
|
721
|
+
const needsFields = !!options.fetchAsString?.length || !!options.unknownTypesAsString;
|
|
722
|
+
if (!needsFields && !this._earnsAName(key))
|
|
687
723
|
return this.queryOnce(sql, paramTypes, params, options, savepoint);
|
|
688
724
|
const name = 'C_' + ++this._preparedCounter;
|
|
689
725
|
// A Parse that fails caches nothing and reports itself; the statement
|
|
@@ -771,7 +807,7 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
771
807
|
// _queryCached() prepares a fetchAsString query on first sight
|
|
772
808
|
// precisely so it can send per-column codes from the second call on
|
|
773
809
|
// (from the first, for every connection that has seen the SQL).
|
|
774
|
-
const columnFormat = options.fetchAsString?.length
|
|
810
|
+
const columnFormat = options.fetchAsString?.length || options.unknownTypesAsString
|
|
775
811
|
? DataFormat.text
|
|
776
812
|
: options.columnFormat != null
|
|
777
813
|
? options.columnFormat
|
|
@@ -796,10 +832,13 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
796
832
|
after: savepoint ? 'RELEASE ' + savepoint : undefined,
|
|
797
833
|
}, (code, msg, done) => {
|
|
798
834
|
switch (code) {
|
|
835
|
+
case Protocol.BackendMessageCode.NoticeResponse:
|
|
836
|
+
// Not an error: handed over, and the loop carries on.
|
|
837
|
+
this.emit('notice', msg);
|
|
838
|
+
break;
|
|
799
839
|
case Protocol.BackendMessageCode.ParseComplete:
|
|
800
840
|
case Protocol.BackendMessageCode.BindComplete:
|
|
801
841
|
case Protocol.BackendMessageCode.NoData:
|
|
802
|
-
case Protocol.BackendMessageCode.NoticeResponse:
|
|
803
842
|
break;
|
|
804
843
|
// The server stopped at the fetchCount limit rather than
|
|
805
844
|
// running out of rows. The Sync that closes this call
|
|
@@ -881,10 +920,13 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
881
920
|
describe: { type: 'S', name: statementName },
|
|
882
921
|
}, (code, msg, done) => {
|
|
883
922
|
switch (code) {
|
|
923
|
+
case Protocol.BackendMessageCode.NoticeResponse:
|
|
924
|
+
// Not an error: handed over, and the loop carries on.
|
|
925
|
+
this.emit('notice', msg);
|
|
926
|
+
break;
|
|
884
927
|
case Protocol.BackendMessageCode.ParseComplete:
|
|
885
928
|
case Protocol.BackendMessageCode.ParameterDescription:
|
|
886
929
|
case Protocol.BackendMessageCode.NoData:
|
|
887
|
-
case Protocol.BackendMessageCode.NoticeResponse:
|
|
888
930
|
break;
|
|
889
931
|
case Protocol.BackendMessageCode.RowDescription:
|
|
890
932
|
fields = msg.fields;
|
|
@@ -1029,8 +1071,8 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
1029
1071
|
// per-column codes; the rest fall back to asking for the whole row
|
|
1030
1072
|
// as text, exactly as queryOnce() does and for the same reason.
|
|
1031
1073
|
plan.columnFormat = plan.fields
|
|
1032
|
-
? resolveColumnFormats(plan.fields, options)
|
|
1033
|
-
: options.fetchAsString?.length
|
|
1074
|
+
? resolveColumnFormats(plan.fields, options, typeMap)
|
|
1075
|
+
: options.fetchAsString?.length || options.unknownTypesAsString
|
|
1034
1076
|
? DataFormat.text
|
|
1035
1077
|
: options.columnFormat != null
|
|
1036
1078
|
? options.columnFormat
|
|
@@ -1062,9 +1104,12 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
1062
1104
|
await this.socket
|
|
1063
1105
|
.sendPipelineMessages({ statements }, (code, msg, done) => {
|
|
1064
1106
|
switch (code) {
|
|
1107
|
+
case Protocol.BackendMessageCode.NoticeResponse:
|
|
1108
|
+
// Not an error: handed over, and the loop carries on.
|
|
1109
|
+
this.emit('notice', msg);
|
|
1110
|
+
break;
|
|
1065
1111
|
case Protocol.BackendMessageCode.ParseComplete:
|
|
1066
1112
|
case Protocol.BackendMessageCode.BindComplete:
|
|
1067
|
-
case Protocol.BackendMessageCode.NoticeResponse:
|
|
1068
1113
|
break;
|
|
1069
1114
|
case Protocol.BackendMessageCode.NoData:
|
|
1070
1115
|
pendingFields = undefined;
|
|
@@ -1131,11 +1176,10 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
1131
1176
|
}
|
|
1132
1177
|
catch (e) {
|
|
1133
1178
|
// A cached statement can be invalidated between two pipelines the
|
|
1134
|
-
// same way a cached single query can
|
|
1135
|
-
//
|
|
1136
|
-
//
|
|
1137
|
-
|
|
1138
|
-
if (e?.code !== '0A000' || !cachedKeys.length)
|
|
1179
|
+
// same way a cached single query can - see isStaleCachedPlan().
|
|
1180
|
+
// Drop what this pipeline reused and run it again unprepared -
|
|
1181
|
+
// once, since the second attempt has nothing cached left to reuse.
|
|
1182
|
+
if (!isStaleCachedPlan(e) || !cachedKeys.length)
|
|
1139
1183
|
throw e;
|
|
1140
1184
|
for (const k of cachedKeys)
|
|
1141
1185
|
this._preparedCache.delete(k);
|
|
@@ -1180,7 +1224,7 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
1180
1224
|
const rowDecoder = resolveRowDecoder(options);
|
|
1181
1225
|
let parsers;
|
|
1182
1226
|
let resultFields;
|
|
1183
|
-
const columnFormat = resolveColumnFormats(cachedFields, options);
|
|
1227
|
+
const columnFormat = resolveColumnFormats(cachedFields, options, typeMap);
|
|
1184
1228
|
if (cachedFields) {
|
|
1185
1229
|
const resolved = this._resolveReusedParsers(cachedFields, typeMap, options, columnFormat);
|
|
1186
1230
|
parsers = resolved.parsers;
|
|
@@ -1206,8 +1250,11 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
1206
1250
|
execute: { fetchCount: 0 },
|
|
1207
1251
|
}, (code, msg, done) => {
|
|
1208
1252
|
switch (code) {
|
|
1209
|
-
case Protocol.BackendMessageCode.BindComplete:
|
|
1210
1253
|
case Protocol.BackendMessageCode.NoticeResponse:
|
|
1254
|
+
// Not an error: handed over, and the loop carries on.
|
|
1255
|
+
this.emit('notice', msg);
|
|
1256
|
+
break;
|
|
1257
|
+
case Protocol.BackendMessageCode.BindComplete:
|
|
1211
1258
|
break;
|
|
1212
1259
|
case Protocol.BackendMessageCode.DataRow:
|
|
1213
1260
|
(pendingRows || (pendingRows = [])).push(msg);
|
|
@@ -1291,7 +1338,7 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
1291
1338
|
// RowDescription is already in hand, so fetchAsString's OID list can
|
|
1292
1339
|
// become the per-column result format codes this Bind carries, with
|
|
1293
1340
|
// no extra round trip to find out what the columns are.
|
|
1294
|
-
const columnFormat = resolveColumnFormats(cachedFields, options);
|
|
1341
|
+
const columnFormat = resolveColumnFormats(cachedFields, options, typeMap);
|
|
1295
1342
|
if (cachedFields) {
|
|
1296
1343
|
const resolved = this._resolveReusedParsers(cachedFields, typeMap, options, columnFormat);
|
|
1297
1344
|
parsers = resolved.parsers;
|
|
@@ -1320,9 +1367,12 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
1320
1367
|
after: savepoint ? 'RELEASE ' + savepoint : undefined,
|
|
1321
1368
|
}, (code, msg, done) => {
|
|
1322
1369
|
switch (code) {
|
|
1370
|
+
case Protocol.BackendMessageCode.NoticeResponse:
|
|
1371
|
+
// Not an error: handed over, and the loop carries on.
|
|
1372
|
+
this.emit('notice', msg);
|
|
1373
|
+
break;
|
|
1323
1374
|
case Protocol.BackendMessageCode.ParseComplete:
|
|
1324
1375
|
case Protocol.BackendMessageCode.BindComplete:
|
|
1325
|
-
case Protocol.BackendMessageCode.NoticeResponse:
|
|
1326
1376
|
break;
|
|
1327
1377
|
// See queryOnce() for what a suspended portal means here.
|
|
1328
1378
|
case Protocol.BackendMessageCode.PortalSuspended:
|
package/connection/pool.js
CHANGED
|
@@ -157,6 +157,12 @@ export class Pool extends SafeEventEmitter {
|
|
|
157
157
|
connection.on('execute', (...args) => this.emit('execute', ...args));
|
|
158
158
|
if (this.listenerCount('query'))
|
|
159
159
|
connection.on('query', (...args) => this.emit('query', ...args));
|
|
160
|
+
// Forwarded with the connection that raised it, the way 'destroy'
|
|
161
|
+
// names its own subject: a caller using pool.query() never sees the
|
|
162
|
+
// Connection, so without this a notice raised by their statement has
|
|
163
|
+
// nowhere to go at all.
|
|
164
|
+
if (this.listenerCount('notice'))
|
|
165
|
+
connection.on('notice', msg => this.emit('notice', msg, connection));
|
|
160
166
|
return connection;
|
|
161
167
|
}
|
|
162
168
|
/**
|
|
@@ -68,6 +68,17 @@ export declare class PreparedStatement extends SafeEventEmitter implements Async
|
|
|
68
68
|
* takes, minus `fetchCount` and `cursor`, which a batch cannot honour.
|
|
69
69
|
*/
|
|
70
70
|
executeBatch(paramSets: any[][], options?: QueryOptions): Promise<BatchResult>;
|
|
71
|
+
/**
|
|
72
|
+
* A notice reaches both this statement and the connection it ran on.
|
|
73
|
+
*
|
|
74
|
+
* These are the three loops the statement keeps for its own close and
|
|
75
|
+
* cancel traffic; an `execute()` runs through the connection's loop
|
|
76
|
+
* instead and is forwarded there. Without this second hop a notice
|
|
77
|
+
* arriving on one of these three would be the only kind a caller
|
|
78
|
+
* holding a `Connection` never saw. Hard to provoke - `DEALLOCATE`
|
|
79
|
+
* does not normally raise one - which is why there is no test for it.
|
|
80
|
+
*/
|
|
81
|
+
protected _emitNotice(msg: any): void;
|
|
71
82
|
protected _executeBatchWithTransaction(paramSets: any[][], options: QueryOptions): Promise<BatchResult>;
|
|
72
83
|
protected _executeWithTransaction(options?: QueryOptions): Promise<QueryResult>;
|
|
73
84
|
/**
|
|
@@ -114,6 +114,20 @@ export class PreparedStatement extends SafeEventEmitter {
|
|
|
114
114
|
return withAbortSignal(options.signal, () => intlCon.cancel(), () => this._executeBatchWithTransaction(paramSets, options));
|
|
115
115
|
return this._executeBatchWithTransaction(paramSets, options);
|
|
116
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* A notice reaches both this statement and the connection it ran on.
|
|
119
|
+
*
|
|
120
|
+
* These are the three loops the statement keeps for its own close and
|
|
121
|
+
* cancel traffic; an `execute()` runs through the connection's loop
|
|
122
|
+
* instead and is forwarded there. Without this second hop a notice
|
|
123
|
+
* arriving on one of these three would be the only kind a caller
|
|
124
|
+
* holding a `Connection` never saw. Hard to provoke - `DEALLOCATE`
|
|
125
|
+
* does not normally raise one - which is why there is no test for it.
|
|
126
|
+
*/
|
|
127
|
+
_emitNotice(msg) {
|
|
128
|
+
this.emit('notice', msg);
|
|
129
|
+
getIntlConnection(this.connection).emit('notice', msg);
|
|
130
|
+
}
|
|
117
131
|
_executeBatchWithTransaction(paramSets, options) {
|
|
118
132
|
const intlCon = getIntlConnection(this.connection);
|
|
119
133
|
return this._withTransaction(options, () => intlCon.executeBatchReused(this.name, this._fields, this.paramTypes, paramSets, options));
|
|
@@ -211,9 +225,9 @@ export class PreparedStatement extends SafeEventEmitter {
|
|
|
211
225
|
// prepare() already fetched this statement's RowDescription, so
|
|
212
226
|
// fetchAsString's OID list can become per-column result format
|
|
213
227
|
// 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
228
|
const typeMap = options.typeMap || GlobalTypeMap;
|
|
229
|
+
const columnFormat = resolveColumnFormats(this._fields, options, typeMap);
|
|
230
|
+
const fields = await portal.bindAndRetrieveFields(options.params, options, columnFormat);
|
|
217
231
|
const parsers = getParsers(typeMap, fields, options);
|
|
218
232
|
const resultFields = wrapRowDescription(typeMap, fields, columnFormat, options);
|
|
219
233
|
const result = {
|
|
@@ -268,7 +282,7 @@ export class PreparedStatement extends SafeEventEmitter {
|
|
|
268
282
|
}, (code, msg, done) => {
|
|
269
283
|
switch (code) {
|
|
270
284
|
case Protocol.BackendMessageCode.NoticeResponse:
|
|
271
|
-
this.
|
|
285
|
+
this._emitNotice(msg);
|
|
272
286
|
break;
|
|
273
287
|
case Protocol.BackendMessageCode.CloseComplete:
|
|
274
288
|
// Arrives twice (once per Close) - neither call is terminal.
|
|
@@ -299,7 +313,7 @@ export class PreparedStatement extends SafeEventEmitter {
|
|
|
299
313
|
const closePromise = socket.sendCloseMessage({ type: 'S', name: this.name }, (code, msg, done) => {
|
|
300
314
|
switch (code) {
|
|
301
315
|
case Protocol.BackendMessageCode.NoticeResponse:
|
|
302
|
-
this.
|
|
316
|
+
this._emitNotice(msg);
|
|
303
317
|
break;
|
|
304
318
|
case Protocol.BackendMessageCode.CloseComplete:
|
|
305
319
|
done();
|
|
@@ -314,7 +328,7 @@ export class PreparedStatement extends SafeEventEmitter {
|
|
|
314
328
|
const syncPromise = socket.sendSyncMessage((code, msg, done) => {
|
|
315
329
|
switch (code) {
|
|
316
330
|
case Protocol.BackendMessageCode.NoticeResponse:
|
|
317
|
-
this.
|
|
331
|
+
this._emitNotice(msg);
|
|
318
332
|
break;
|
|
319
333
|
case Protocol.BackendMessageCode.ReadyForQuery:
|
|
320
334
|
intoCon.transactionStatus = msg.status;
|