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.
Files changed (66) hide show
  1. package/CHANGELOG.md +64 -6
  2. package/README.md +9 -79
  3. package/connection/intl-connection.d.ts +5 -3
  4. package/connection/intl-connection.js +47 -17
  5. package/connection/pool.d.ts +22 -0
  6. package/connection/pool.js +45 -3
  7. package/connection/prepared-statement.js +2 -2
  8. package/constants.d.ts +60 -0
  9. package/constants.js +119 -1
  10. package/data-type-map.js +82 -0
  11. package/data-types/bit-type.d.ts +5 -0
  12. package/data-types/bit-type.js +91 -0
  13. package/data-types/box-type.js +9 -19
  14. package/data-types/circle-type.js +6 -16
  15. package/data-types/classes/geometric.d.ts +105 -0
  16. package/data-types/classes/geometric.js +197 -0
  17. package/data-types/classes/interval.d.ts +73 -0
  18. package/data-types/classes/interval.js +181 -0
  19. package/data-types/classes/numeric.d.ts +37 -0
  20. package/data-types/classes/numeric.js +47 -0
  21. package/data-types/classes/range.d.ts +74 -0
  22. package/data-types/classes/range.js +111 -0
  23. package/data-types/classes/type-oid.d.ts +34 -0
  24. package/data-types/classes/type-oid.js +45 -0
  25. package/data-types/inet-type.d.ts +5 -0
  26. package/data-types/inet-type.js +286 -0
  27. package/data-types/interval-type.d.ts +5 -0
  28. package/data-types/interval-type.js +79 -0
  29. package/data-types/jsonpath-type.d.ts +3 -0
  30. package/data-types/jsonpath-type.js +53 -0
  31. package/data-types/line-type.d.ts +3 -0
  32. package/data-types/line-type.js +41 -0
  33. package/data-types/lseg-type.js +9 -19
  34. package/data-types/macaddr-type.d.ts +5 -0
  35. package/data-types/macaddr-type.js +86 -0
  36. package/data-types/numeric-type.js +98 -69
  37. package/data-types/path-type.d.ts +5 -0
  38. package/data-types/path-type.js +139 -0
  39. package/data-types/pg-lsn-type.d.ts +3 -0
  40. package/data-types/pg-lsn-type.js +58 -0
  41. package/data-types/point-type.js +8 -13
  42. package/data-types/range-type.d.ts +23 -0
  43. package/data-types/range-type.js +342 -0
  44. package/data-types/snapshot-type.d.ts +5 -0
  45. package/data-types/snapshot-type.js +96 -0
  46. package/data-types/tid-type.d.ts +3 -0
  47. package/data-types/tid-type.js +50 -0
  48. package/data-types/timetz-type.d.ts +3 -0
  49. package/data-types/timetz-type.js +184 -0
  50. package/data-types/tsvector-type.d.ts +5 -0
  51. package/data-types/tsvector-type.js +216 -0
  52. package/data-types/xid-type.d.ts +7 -0
  53. package/data-types/xid-type.js +110 -0
  54. package/index.d.ts +5 -0
  55. package/index.js +5 -0
  56. package/interfaces/data-mapping-options.d.ts +26 -0
  57. package/interfaces/data-type.d.ts +8 -9
  58. package/package.json +1 -1
  59. package/protocol/connection-lost-error.d.ts +42 -0
  60. package/protocol/connection-lost-error.js +56 -0
  61. package/protocol/pg-socket.d.ts +7 -0
  62. package/protocol/pg-socket.js +41 -3
  63. package/util/parse-array.js +17 -2
  64. package/util/resolve-column-formats.d.ts +2 -1
  65. package/util/resolve-column-formats.js +35 -8
  66. package/util/stringify-for-sql.js +37 -1
package/CHANGELOG.md CHANGED
@@ -1,6 +1,65 @@
1
1
  ## Changelog
2
2
 
3
- ### [v3.6.0](https://github.com/panates/postgrejs/compare/v3.5.0...v3.6.0) -
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 :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,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 | 56 | generic <sup>16</sup> | 14 |
204
- | Text decoders | 56 | 44 | 12 <sup>17</sup> |
205
- | Binary encoders | 56 | ❌ | ❌ |
206
- | 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 | ❌ |
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". 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
@@ -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
- this.socket.on('close', () => this.emit('close'));
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". That is caught here: the entry is dropped and the query
619
- * runs again unprepared, so a migration against a live connection
620
- * recovers instead of failing every call from then on.
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?.code !== '0A000')
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
- if (!options.fetchAsString?.length && !this._earnsAName(key))
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 (0A000, "cached plan must not
1133
- // change result type"). Drop what this pipeline reused and run it
1134
- // again unprepared - once, since the second attempt has nothing
1135
- // cached left to reuse.
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;
@@ -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
@@ -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._pool.destroy(intlCon));
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._pool.destroy(intlCon));
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', (...args) => this.emit('destroy', ...args));
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>;