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.
Files changed (78) hide show
  1. package/CHANGELOG.md +66 -6
  2. package/README.md +9 -83
  3. package/connection/connection.js +13 -1
  4. package/connection/intl-connection.d.ts +5 -3
  5. package/connection/intl-connection.js +70 -20
  6. package/connection/pool.js +6 -0
  7. package/connection/prepared-statement.d.ts +11 -0
  8. package/connection/prepared-statement.js +19 -5
  9. package/constants.d.ts +60 -0
  10. package/constants.js +119 -1
  11. package/data-type-map.js +82 -0
  12. package/data-types/bit-type.d.ts +5 -0
  13. package/data-types/bit-type.js +91 -0
  14. package/data-types/box-type.js +9 -19
  15. package/data-types/circle-type.js +6 -16
  16. package/data-types/classes/geometric.d.ts +105 -0
  17. package/data-types/classes/geometric.js +197 -0
  18. package/data-types/classes/interval.d.ts +73 -0
  19. package/data-types/classes/interval.js +181 -0
  20. package/data-types/classes/numeric.d.ts +41 -0
  21. package/data-types/classes/numeric.js +51 -0
  22. package/data-types/classes/range.d.ts +74 -0
  23. package/data-types/classes/range.js +111 -0
  24. package/data-types/classes/type-oid.d.ts +34 -0
  25. package/data-types/classes/type-oid.js +45 -0
  26. package/data-types/inet-type.d.ts +5 -0
  27. package/data-types/inet-type.js +286 -0
  28. package/data-types/int2-vector-type.js +4 -1
  29. package/data-types/interval-type.d.ts +5 -0
  30. package/data-types/interval-type.js +79 -0
  31. package/data-types/jsonpath-type.d.ts +3 -0
  32. package/data-types/jsonpath-type.js +53 -0
  33. package/data-types/line-type.d.ts +3 -0
  34. package/data-types/line-type.js +41 -0
  35. package/data-types/lseg-type.js +9 -19
  36. package/data-types/macaddr-type.d.ts +5 -0
  37. package/data-types/macaddr-type.js +86 -0
  38. package/data-types/numeric-type.js +98 -69
  39. package/data-types/oid-vector-type.js +4 -1
  40. package/data-types/path-type.d.ts +5 -0
  41. package/data-types/path-type.js +139 -0
  42. package/data-types/pg-lsn-type.d.ts +3 -0
  43. package/data-types/pg-lsn-type.js +58 -0
  44. package/data-types/point-type.js +8 -13
  45. package/data-types/range-type.d.ts +23 -0
  46. package/data-types/range-type.js +342 -0
  47. package/data-types/snapshot-type.d.ts +5 -0
  48. package/data-types/snapshot-type.js +96 -0
  49. package/data-types/tid-type.d.ts +3 -0
  50. package/data-types/tid-type.js +50 -0
  51. package/data-types/timestamptz-type.js +9 -0
  52. package/data-types/timetz-type.d.ts +3 -0
  53. package/data-types/timetz-type.js +184 -0
  54. package/data-types/tsvector-type.d.ts +5 -0
  55. package/data-types/tsvector-type.js +216 -0
  56. package/data-types/xid-type.d.ts +7 -0
  57. package/data-types/xid-type.js +110 -0
  58. package/index.d.ts +4 -0
  59. package/index.js +4 -0
  60. package/interfaces/data-mapping-options.d.ts +26 -0
  61. package/interfaces/data-type.d.ts +8 -9
  62. package/package.json +1 -1
  63. package/protocol/connection-lost-error.d.ts +5 -3
  64. package/protocol/connection-lost-error.js +5 -3
  65. package/protocol/frontend.js +17 -0
  66. package/protocol/pg-socket.js +16 -2
  67. package/util/decode-binaryarray.js +8 -1
  68. package/util/encode-binaryarray.d.ts +12 -1
  69. package/util/encode-binaryarray.js +13 -2
  70. package/util/format-datetime.d.ts +18 -0
  71. package/util/format-datetime.js +33 -0
  72. package/util/parse-array.js +17 -2
  73. package/util/resolve-column-formats.d.ts +2 -1
  74. package/util/resolve-column-formats.js +35 -8
  75. package/util/sql-tag.js +15 -0
  76. package/util/stringify-for-sql.js +44 -1
  77. package/util/unspecified-param.d.ts +32 -0
  78. package/util/unspecified-param.js +40 -0
package/CHANGELOG.md CHANGED
@@ -1,6 +1,63 @@
1
1
  ## Changelog
2
2
 
3
- ### [v3.6.1](https://github.com/panates/postgrejs/compare/v3.6.0...v3.6.1) -
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 :small_orange_diamond: [DOCUMENTATION](https://www.postgrejs.com/) :small_orange_diamond: for detailed
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 | 56 | generic <sup>16</sup> | 14 |
208
- | Text decoders | 56 | 44 | 12 <sup>17</sup> |
209
- | Binary encoders | 56 | ❌ | ❌ |
210
- | Binary decoders | 56 | 16 | ❌ |
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
 
@@ -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 ? prm.oid : typeMap.determine(prm));
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". That is caught here: the entry is dropped and the query
198
- * runs again unprepared, so a migration against a live connection
199
- * recovers instead of failing every call from then on.
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". That is caught here: the entry is dropped and the query
621
- * runs again unprepared, so a migration against a live connection
622
- * recovers instead of failing every call from then on.
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?.code !== '0A000')
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
- if (!options.fetchAsString?.length && !this._earnsAName(key))
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 (0A000, "cached plan must not
1135
- // change result type"). Drop what this pipeline reused and run it
1136
- // again unprepared - once, since the second attempt has nothing
1137
- // cached left to reuse.
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:
@@ -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.emit('notice', msg);
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.emit('notice', msg);
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.emit('notice', msg);
331
+ this._emitNotice(msg);
318
332
  break;
319
333
  case Protocol.BackendMessageCode.ReadyForQuery:
320
334
  intoCon.transactionStatus = msg.status;