@steadybrew/postgresjs 4.4.1 → 4.5.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/README.md +271 -20
- package/cf/src/connection.js +62 -19
- package/cf/src/errors.js +2 -2
- package/cf/src/index.js +37 -9
- package/cf/src/query.js +4 -10
- package/cf/src/types.js +7 -2
- package/cjs/src/connection.js +62 -19
- package/cjs/src/errors.js +2 -2
- package/cjs/src/index.js +37 -9
- package/cjs/src/query.js +4 -10
- package/cjs/src/types.js +7 -2
- package/package.json +5 -4
- package/src/connection.js +62 -19
- package/src/errors.js +2 -2
- package/src/index.js +37 -9
- package/src/query.js +4 -10
- package/src/types.js +7 -2
- package/types/index.d.ts +37 -1
- package/types/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# Postgres.js by Steady Brew
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A maintained fork of [Postgres.js](https://github.com/porsager/postgres), the PostgreSQL client created by [Rasmus Porsager](https://github.com/porsager), with the same API.
|
|
4
|
+
|
|
5
|
+
- **Fixes reported Postgres.js bugs:** 40 open upstream issues, from queries receiving another query's rows to hung pools and crashed processes, each covered by a regression test. See [the list](#fixed-postgresjs-issues).
|
|
6
|
+
- **Built for serverless and connection poolers:** bounded connection retries with backoff, idle connections replaced when a frozen instance resumes, pool statistics from `sql.stats()`, and an adapter for Vercel Fluid. Query timeouts and cancellable pool waits are next on the [roadmap](ROADMAP.md).
|
|
7
|
+
- **Switch with one line:** your code and the libraries that import `postgres` keep working. See [Switching from Postgres.js](#switching-from-postgresjs).
|
|
4
8
|
|
|
5
9
|
---
|
|
6
10
|
|
|
@@ -13,33 +17,142 @@ This is an independently maintained fork of [Postgres.js](https://github.com/por
|
|
|
13
17
|
|
|
14
18
|
<br clear="all">
|
|
15
19
|
|
|
16
|
-
## Compatibility
|
|
20
|
+
## Compatibility
|
|
21
|
+
|
|
22
|
+
[](https://github.com/steadybrew/postgresjs/actions/workflows/test.yml?query=branch%3Amain)
|
|
23
|
+
|
|
24
|
+
**Version 4 runs on Node.js 22 or newer** and supports PostgreSQL 15–18. Every release passes the full test matrix before it is published.
|
|
25
|
+
|
|
26
|
+
| Runtime | PostgreSQL | Notes |
|
|
27
|
+
| --- | --- | --- |
|
|
28
|
+
| Node 22, 24 and 26, ESM and CJS | 15–18 | |
|
|
29
|
+
| Deno 1.46.3 | 17 | No Deno 2 claim |
|
|
30
|
+
| Cloudflare Workers (workerd) | 17 | A "Stream was cancelled" rejection on socket close remains ([#1196](https://github.com/porsager/postgres/issues/1196), [#1202](https://github.com/porsager/postgres/issues/1202)) |
|
|
31
|
+
| Bun | | Best effort, not tested in CI |
|
|
32
|
+
|
|
33
|
+
[The test workflow](https://github.com/steadybrew/postgresjs/blob/main/.github/workflows/test.yml) defines which pairs run on each push. Supabase PostgreSQL and Supabase Edge are separate environments; Deno 1.46.3 coverage does not imply Deno 2 or hosted Supabase Edge.
|
|
34
|
+
|
|
35
|
+
## Switching from Postgres.js
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
npm install postgres@npm:@steadybrew/postgresjs@^4
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
This installs the fork under the name `postgres`, so `import postgres from 'postgres'` stays as it is, and so do libraries that import it, such as `drizzle-orm/postgres-js`, `drizzle-kit` and `kysely-postgres-js`. Then read [Upgrading to 4.0.0](#upgrading-to-400) for the behaviour changes.
|
|
17
42
|
|
|
18
|
-
|
|
43
|
+
Tracing: from 4.5.0, dd-trace records the same database spans as with Postgres.js when the fork is installed under the `postgres` name. Sentry's automatic integration instruments `postgres` versions below 4 only, so widen its range before Sentry loads:
|
|
19
44
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
| Node 26, ESM and CJS | 15, 16, 17, 18 | 4 | Passed release-candidate CI |
|
|
24
|
-
| Deno 1.46.3 | 17 | 1 | Passed release-candidate CI; no Deno 2 claim |
|
|
25
|
-
| Cloudflare workerd, Wrangler 4.123.0 | 17 | 1 | Passed release-candidate CI; known "Stream was cancelled" rejection remains |
|
|
26
|
-
| Bun export | No dedicated target | 0 | Retained as best effort; unverified, without dedicated CI |
|
|
45
|
+
```js
|
|
46
|
+
// sentry-postgres.mjs
|
|
47
|
+
import { SENTRY_RUNTIME_INSTRUMENTATIONS } from '@sentry/server-utils/orchestrion/config'
|
|
27
48
|
|
|
28
|
-
|
|
49
|
+
for (const instrumentation of SENTRY_RUNTIME_INSTRUMENTATIONS) {
|
|
50
|
+
if (instrumentation.module.name === 'postgres')
|
|
51
|
+
instrumentation.module.versionRange = '>=3.0.0 <5'
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
node --import ./sentry-postgres.mjs --import @sentry/node/import app.js
|
|
57
|
+
```
|
|
29
58
|
|
|
30
|
-
|
|
59
|
+
This covers Sentry's runtime hook, not its bundler plugins. With pnpm, add `@sentry/server-utils` at the same version as `@sentry/node` so the file can import it.
|
|
31
60
|
|
|
32
61
|
## Upgrading to 4.0.0
|
|
33
62
|
|
|
34
|
-
Version 4.0.0 fixes connection startup, reservations, reconnects, shutdown, transaction ownership, and built-in array handling. When upgrading from `@steadybrew/postgresjs` 3.x:
|
|
63
|
+
Version 4.0.0 fixes connection startup, reservations, reconnects, shutdown, transaction ownership, and built-in array handling. When upgrading from Postgres.js 3.4 or `@steadybrew/postgresjs` 3.x:
|
|
35
64
|
|
|
36
|
-
- Upgrade to **Node.js
|
|
65
|
+
- Upgrade to **Node.js 22 or newer** before installing.
|
|
37
66
|
- Keep queries within the reservation or transaction that owns them. Reserved handles used after `release()`, and transaction handles used after completion, reject with `CONNECTION_ENDED`. A handle whose connection has closed cannot run queries on a reconnected session.
|
|
38
67
|
- Pass a non-empty string without NUL characters to transaction `sql.prepare(name)`. Invalid names throw `INVALID_TRANSACTION_NAME`.
|
|
39
68
|
- If you use `fetch_types: false`, review code that expects built-in arrays as raw strings: supported built-in arrays are now decoded as JavaScript arrays. SQL `NULL` elements become JavaScript `null`; quoted `"NULL"` text remains a string.
|
|
40
69
|
|
|
41
70
|
See the [changelog](./CHANGELOG.md#breaking-changes-and-migration) for the full migration notes and fixes.
|
|
42
71
|
|
|
72
|
+
## Fixed Postgres.js issues
|
|
73
|
+
|
|
74
|
+
Bugs and feature requests from [porsager/postgres](https://github.com/porsager/postgres) that this fork fixes, whether reported in an issue or in a pull request suggesting a fix, and the version that fixed them. Each fix has a test that reproduces the report, except [#1100](https://github.com/porsager/postgres/issues/1100), which rests on a production measurement, and [#1154](https://github.com/porsager/postgres/issues/1154), whose test reproduces the reported stack without a known trigger. The [changelog](./CHANGELOG.md) has the details.
|
|
75
|
+
|
|
76
|
+
<details>
|
|
77
|
+
<summary>54 bugs and 7 feature requests</summary>
|
|
78
|
+
|
|
79
|
+
**Wrong results and data safety**
|
|
80
|
+
|
|
81
|
+
| Issue | What happens now | Since |
|
|
82
|
+
| --- | --- | --- |
|
|
83
|
+
| [#1082](https://github.com/porsager/postgres/issues/1082), [#729](https://github.com/porsager/postgres/issues/729) | A query whose values fail to serialize rejects on its own; the query running alongside no longer gets its error, and the next query no longer gets another query's rows | 4.3.0 |
|
|
84
|
+
| [#1216](https://github.com/porsager/postgres/issues/1216) | A transaction handle whose connection closed rejects with `CONNECTION_CLOSED` instead of running inside the next user's transaction, so it can't read their rows through row-level security. | 4.0.0 |
|
|
85
|
+
| [#1248](https://github.com/porsager/postgres/issues/1248) | Statements in a `sql.begin()` callback after its connection closes reject instead of running outside the transaction | 4.0.0 |
|
|
86
|
+
| [#1204](https://github.com/porsager/postgres/issues/1204) | A transaction's connection is not handed to another `sql.begin()` when its socket drains late, so one transaction's `COMMIT` can't commit another's unfinished writes. | 4.0.0 |
|
|
87
|
+
| [#1217](https://github.com/porsager/postgres/issues/1217) | `sql.begin()` rejects with `TRANSACTION_ROLLED_BACK` when the server rolled back the COMMIT | 4.4.0 |
|
|
88
|
+
| [#1249](https://github.com/porsager/postgres/issues/1249), [#1226](https://github.com/porsager/postgres/issues/1226), [#1223](https://github.com/porsager/postgres/issues/1223) | The first query after a reconnect no longer rejects with an error from the closed session | 4.0.0 |
|
|
89
|
+
| [#789](https://github.com/porsager/postgres/issues/789) | `sql.array()` infers the right array type on first use | 4.0.0 |
|
|
90
|
+
| [#1049](https://github.com/porsager/postgres/issues/1049), [#1124](https://github.com/porsager/postgres/issues/1124) | `NULL` elements in arrays parse as `null`, not `NaN` or the string `"NULL"` | 4.0.0 |
|
|
91
|
+
| [#1252](https://github.com/porsager/postgres/issues/1252) | Cancelling a pipelined query that hasn't started no longer gives the next query its result | 4.1.0 |
|
|
92
|
+
| [#1253](https://github.com/porsager/postgres/issues/1253), [#1181](https://github.com/porsager/postgres/issues/1181), [#1119](https://github.com/porsager/postgres/issues/1119) | A query no longer gets empty slots at the start of its result after the previous query on the connection failed or was cancelled partway through its rows | 4.5.0 |
|
|
93
|
+
|
|
94
|
+
**Hangs**
|
|
95
|
+
|
|
96
|
+
| Issue | What happens now | Since |
|
|
97
|
+
| --- | --- | --- |
|
|
98
|
+
| [#751](https://github.com/porsager/postgres/issues/751), [#1219](https://github.com/porsager/postgres/issues/1219), [#1203](https://github.com/porsager/postgres/issues/1203) | `sql.reserve()` on a pool that hasn't opened a connection yet resolves, with or without `fetch_types` and on Cloudflare Workers | 4.0.0 |
|
|
99
|
+
| [#1195](https://github.com/porsager/postgres/issues/1195), [#1199](https://github.com/porsager/postgres/issues/1199) | A reservation survives its connection being terminated by the server, and the pool recovers after it is released | 4.0.0 |
|
|
100
|
+
| [#1186](https://github.com/porsager/postgres/issues/1186) | Queries queued on a reserved or transaction handle settle when its connection closes | 4.0.0 |
|
|
101
|
+
| [#1097](https://github.com/porsager/postgres/issues/1097), [#1130](https://github.com/porsager/postgres/issues/1130), [#1242](https://github.com/porsager/postgres/issues/1242), [#861](https://github.com/porsager/postgres/issues/861) | `sql.end()` resolves after a disconnect, after a failed query and after a connection closes inside `sql.begin()`, and waits for queries queued in the pool | 4.0.0 |
|
|
102
|
+
| [#1246](https://github.com/porsager/postgres/issues/1246) | A query sent between a socket error and its close settles, and a transaction reset mid-statement no longer loses a pool connection | 4.1.0 |
|
|
103
|
+
| [#1193](https://github.com/porsager/postgres/issues/1193) | A connection closed during startup settles its first query instead of reconnecting in a tight loop | 4.0.0 |
|
|
104
|
+
| [#1234](https://github.com/porsager/postgres/issues/1234) | Concurrent queries in `sql.begin()` after a column type change roll the transaction back and return the connection to the pool | 4.4.0 |
|
|
105
|
+
| [#1255](https://github.com/porsager/postgres/issues/1255), [#1261](https://github.com/porsager/postgres/issues/1261) | A reserved connection whose `max_lifetime` passed is closed when released, and `sql.end()` called before the release resolves | 4.0.0 |
|
|
106
|
+
| [#1016](https://github.com/porsager/postgres/pull/1016) | Queries no longer hang after a `COPY ... TO STDOUT` whose reader fell behind | 4.5.0 |
|
|
107
|
+
|
|
108
|
+
**Crashes**
|
|
109
|
+
|
|
110
|
+
| Issue | What happens now | Since |
|
|
111
|
+
| --- | --- | --- |
|
|
112
|
+
| [#1154](https://github.com/porsager/postgres/issues/1154), [#1208](https://github.com/porsager/postgres/issues/1208), [#1133](https://github.com/porsager/postgres/issues/1133) | A reserved or transaction connection that the server or network closes rejects its later statements, and `sql.begin()`'s own `ROLLBACK` or `COMMIT`, with `CONNECTION_CLOSED` instead of crashing the process with `Cannot read properties of null (reading 'write')`. | 4.0.0 |
|
|
113
|
+
| [#1192](https://github.com/porsager/postgres/issues/1192), [#1205](https://github.com/porsager/postgres/issues/1205), [#1086](https://github.com/porsager/postgres/issues/1086) | A failed or cancelled type query at startup rejects the query instead of crashing the process | 4.0.0 |
|
|
114
|
+
| [#1088](https://github.com/porsager/postgres/issues/1088) | Timers no longer fail on Cloudflare Workers | 4.0.0 |
|
|
115
|
+
| [#1227](https://github.com/porsager/postgres/issues/1227) | Assigning `err.stack` on a query error no longer throws on Node 22 and newer | 4.5.0 |
|
|
116
|
+
| [#1009](https://github.com/porsager/postgres/pull/1009) | The `camel` and `pascal` transforms no longer throw on a column name ending with an underscore | 4.5.0 |
|
|
117
|
+
|
|
118
|
+
**Reconnects and failover**
|
|
119
|
+
|
|
120
|
+
| Issue | What happens now | Since |
|
|
121
|
+
| --- | --- | --- |
|
|
122
|
+
| [#1179](https://github.com/porsager/postgres/issues/1179) | Failed connection attempts back off instead of the whole pool retrying at once | 4.0.0 |
|
|
123
|
+
| [#1250](https://github.com/porsager/postgres/issues/1250) | A connection closed with a statement still buffered reconnects, instead of failing every later statement with `CONNECT_TIMEOUT` | 4.0.0 |
|
|
124
|
+
| [#1174](https://github.com/porsager/postgres/issues/1174), [#815](https://github.com/porsager/postgres/issues/815), [#988](https://github.com/porsager/postgres/issues/988) | With several hosts, retries move to the next host, a connect timeout fails over, and `prefer-standby` falls back to a primary | 4.0.0 |
|
|
125
|
+
|
|
126
|
+
**Transactions, notices and subscriptions**
|
|
127
|
+
|
|
128
|
+
| Issue | What happens now | Since |
|
|
129
|
+
| --- | --- | --- |
|
|
130
|
+
| [#823](https://github.com/porsager/postgres/issues/823), [#1189](https://github.com/porsager/postgres/issues/1189), [#1210](https://github.com/porsager/postgres/issues/1210) | `sql.begin()` no longer fails with `UNSAFE_TRANSACTION` or sends a second `BEGIN` on a busy connection when transactions and queries run concurrently, at the pipeline limit, under write backpressure or with `max_pipeline: 0` | 4.0.0 |
|
|
131
|
+
| [#1063](https://github.com/porsager/postgres/issues/1063) | `onnotice: false` silences notices | 4.4.0 |
|
|
132
|
+
| [#1087](https://github.com/porsager/postgres/issues/1087) | `sql.subscribe()` no longer adds a listener on each subscribe or loses changes after a transform throws | 4.4.0 |
|
|
133
|
+
|
|
134
|
+
**TypeScript**
|
|
135
|
+
|
|
136
|
+
| Issue | What happens now | Since |
|
|
137
|
+
| --- | --- | --- |
|
|
138
|
+
| [#1158](https://github.com/porsager/postgres/issues/1158), [#1177](https://github.com/porsager/postgres/pull/1177) | `sslnegotiation` is in the option types | 4.5.0 |
|
|
139
|
+
| [#968](https://github.com/porsager/postgres/pull/968) | `socket` is in the option types | 4.5.0 |
|
|
140
|
+
| [#703](https://github.com/porsager/postgres/issues/703), [#1194](https://github.com/porsager/postgres/pull/1194) | `new PostgresError('message')` sets the message, as with `Error`, and the types declare both the message and the fields forms | 4.5.0 |
|
|
141
|
+
|
|
142
|
+
**Feature requests**
|
|
143
|
+
|
|
144
|
+
| Issue | What you get | Since |
|
|
145
|
+
| --- | --- | --- |
|
|
146
|
+
| [#443](https://github.com/porsager/postgres/issues/443), [#908](https://github.com/porsager/postgres/issues/908) | `sql.stats()` reports connections in use and idle, and queries waiting | 4.3.0 |
|
|
147
|
+
| [#1100](https://github.com/porsager/postgres/issues/1100) | An `onidle` callback and the `@steadybrew/postgresjs/vercel` adapter | 4.2.0 |
|
|
148
|
+
| [#1164](https://github.com/porsager/postgres/issues/1164) | Built-in array types are decoded, and plain JavaScript arrays are sent, with `fetch_types: false` | 4.0.0 |
|
|
149
|
+
| [#886](https://github.com/porsager/postgres/issues/886), [#1094](https://github.com/porsager/postgres/issues/1094) | `await using` ends a pool and releases a reserved connection when it goes out of scope | 4.5.0 |
|
|
150
|
+
| [#1225](https://github.com/porsager/postgres/issues/1225) | Queries with values in one round trip instead of two, opt-in with `parameter_types: 'client'` | 4.5.0 |
|
|
151
|
+
|
|
152
|
+
Bun isn't tested here, so Bun-only reports such as [#1066](https://github.com/porsager/postgres/issues/1066) aren't listed; the same crash on Node is [#1208](https://github.com/porsager/postgres/issues/1208).
|
|
153
|
+
|
|
154
|
+
</details>
|
|
155
|
+
|
|
43
156
|
## Getting started
|
|
44
157
|
|
|
45
158
|
<br>
|
|
@@ -113,6 +226,7 @@ const { default: postgres } = await import('@steadybrew/postgresjs')
|
|
|
113
226
|
* [Listen & notify](#listen--notify)
|
|
114
227
|
* [Realtime subscribe](#realtime-subscribe)
|
|
115
228
|
* [Numbers, bigint, numeric](#numbers-bigint-numeric)
|
|
229
|
+
* [Parameter types](#parameter-types)
|
|
116
230
|
* [Result Array](#result-array)
|
|
117
231
|
* [Connection details](#connection-details)
|
|
118
232
|
* [Custom Types](#custom-types)
|
|
@@ -151,7 +265,7 @@ Postgres.js utilizes [Tagged template functions](https://developer.mozilla.org/e
|
|
|
151
265
|
1. **Enforcing** safe query generation
|
|
152
266
|
2. Giving the ` sql`` ` function powerful [utility](#dynamic-inserts) and [query building](#building-queries) features.
|
|
153
267
|
|
|
154
|
-
Any generic value will be serialized
|
|
268
|
+
Any generic value will be serialized as described in [Parameter types](#parameter-types), and replaced by a PostgreSQL protocol placeholder `$1, $2, ...`. The values are then sent separately to PostgreSQL, which handles escaping & casting.
|
|
155
269
|
|
|
156
270
|
All queries will return a `Result` array, with objects mapping column names to each row.
|
|
157
271
|
|
|
@@ -599,11 +713,13 @@ A `COPY` holds its connection until it finishes. Postgres.js cannot tell that a
|
|
|
599
713
|
Postgres.js supports, [canceling queries in progress](https://www.postgresql.org/docs/7.1/protocol-protocol.html#AEN39000). It works by opening a new connection with a protocol level startup message to cancel the current query running on a specific connection. That means there is no guarantee that the query will be canceled, and due to the possible race conditions it might even result in canceling another query. This is fine for long running queries, but in the case of high load and fast queries it might be better to simply ignore results instead of canceling.
|
|
600
714
|
|
|
601
715
|
```js
|
|
602
|
-
const query = sql`select pg_sleep
|
|
716
|
+
const query = sql`select pg_sleep(100)`.execute()
|
|
603
717
|
setTimeout(() => query.cancel(), 100)
|
|
604
718
|
const result = await query
|
|
605
719
|
```
|
|
606
720
|
|
|
721
|
+
A cancelled query rejects with code `57014`. A cancel can arrive too late, in which case the query may still complete, or fail with its own error. `query.cancel()` resolves once the cancel request has been sent. A cancelled query you don't await does not cause an unhandled rejection.
|
|
722
|
+
|
|
607
723
|
### Execute
|
|
608
724
|
|
|
609
725
|
#### ```await sql``.execute()```
|
|
@@ -977,6 +1093,103 @@ const sql = postgres({
|
|
|
977
1093
|
|
|
978
1094
|
There is currently no guaranteed way to handle `numeric` / `decimal` types in native Javascript. **These [and similar] types will be returned as a `string`**. The best way in this case is to use [custom types](#custom-types).
|
|
979
1095
|
|
|
1096
|
+
## Parameter types
|
|
1097
|
+
|
|
1098
|
+
A `$1` placeholder has a type that PostgreSQL infers from the SQL around it (the column it is compared to or inserted into, a function argument, a cast). Postgres.js has to turn each JavaScript value into text for that type, and `parameter_types` chooses how.
|
|
1099
|
+
|
|
1100
|
+
Some values carry a type Postgres.js can tell from the JavaScript value: `Date`, `Buffer` / `Uint8Array`, booleans, `bigint`, and anything wrapped in `sql.typed(value, oid)`, `sql.types.*`, `sql.json` or `sql.array` with elements. They are serialized for that type and sent in a single round trip, in every mode. Everything else (strings, numbers, plain objects, `sql.typed(value)` without an OID, an empty `sql.array([])`, arrays) has no such type. `null` is always SQL `NULL`.
|
|
1101
|
+
|
|
1102
|
+
### `parameter_types: 'server'` (default)
|
|
1103
|
+
|
|
1104
|
+
For a value whose type Postgres.js cannot tell, it first asks PostgreSQL for the type of the placeholder, which costs an extra round trip on every query with `prepare: false` and the first time each connection runs that SQL with `prepare: true`, and then serializes the value for that type. This has consequences worth knowing:
|
|
1105
|
+
|
|
1106
|
+
- a string sent to a `json` or `jsonb` placeholder is sent as JSON, so `'{"a":1}'` is stored as a JSON string, not an object
|
|
1107
|
+
- a string or number sent to a `date`, `timestamp` or `timestamptz` placeholder goes through `new Date(x).toISOString()`, where a date-only string such as `'2020-01-02'` is read as UTC and a date-and-time string without an offset such as `'2020-01-02T10:00'` is read in the Node.js process time zone (`TZ`)
|
|
1108
|
+
- anything other than `true` sent to a `boolean` placeholder is stored as `false`, including the strings `'true'` and `'t'` and the number `1`
|
|
1109
|
+
- a string sent to a `bytea` placeholder is sent as hex of its UTF-8 bytes
|
|
1110
|
+
- a custom type registered in `types` with a `from` list runs its `serialize` function
|
|
1111
|
+
|
|
1112
|
+
### `parameter_types: 'client'`
|
|
1113
|
+
|
|
1114
|
+
The JavaScript value decides how it is serialized and PostgreSQL parses the text, so a query with values is one round trip:
|
|
1115
|
+
|
|
1116
|
+
- a string is sent unchanged as text
|
|
1117
|
+
- a number is sent as its JavaScript text (`'' + n`)
|
|
1118
|
+
- a plain object (prototype `Object.prototype` or `null`) is sent as `JSON.stringify(x)`
|
|
1119
|
+
- any other object (Decimal, Luxon, moment, URL, class instances) is sent as `'' + x`
|
|
1120
|
+
- `null` is SQL `NULL`; `undefined` is handled as before
|
|
1121
|
+
- a value with a type Postgres.js can tell from the JavaScript value is serialized for that type, as with `'server'`
|
|
1122
|
+
- an array whose elements have no such type still makes Postgres.js ask PostgreSQL for the placeholder's type first, because only PostgreSQL knows whether the placeholder is an array, JSON or a custom type; that query takes the extra round trip
|
|
1123
|
+
|
|
1124
|
+
What this changes compared to `'server'`:
|
|
1125
|
+
|
|
1126
|
+
| Value | Type PostgreSQL infers for the placeholder | `'server'` | `'client'` |
|
|
1127
|
+
| --- | --- | --- | --- |
|
|
1128
|
+
| string | json, jsonb | sent as JSON: stores a JSON string | parsed as JSON; text that is not JSON is rejected |
|
|
1129
|
+
| string | boolean | always `false` | PostgreSQL boolean input (`'t'`, `'yes'`, `'on'`, `'1'` are true); invalid text is rejected |
|
|
1130
|
+
| string | date, timestamp, timestamptz | `new Date(s).toISOString()`: date-only strings are read as UTC, date-and-time strings without an offset in the Node.js process time zone (`TZ`) | parsed by PostgreSQL in the PostgreSQL session's `TimeZone` setting; `date` and `timestamp` ignore an explicit offset |
|
|
1131
|
+
| string | bytea | hex of the UTF-8 bytes | bytea text input; differs only for strings containing backslashes |
|
|
1132
|
+
| string, number | a custom type registered in `types` with a `from` list | the type's `serialize` | sent unchanged as text |
|
|
1133
|
+
| number | json, jsonb | `JSON.stringify` (`NaN` and `Infinity` become `null`) | the number's text (`NaN` and `Infinity` are rejected) |
|
|
1134
|
+
| number | boolean | `false` | `1` is true, `0` is false, anything else is rejected |
|
|
1135
|
+
| number | date, timestamp, timestamptz | `new Date(ms)` | rejected, except `Infinity`; a 6- or 8-digit number is read as a `YYMMDD` or `YYYYMMDD` date |
|
|
1136
|
+
| plain object | json, jsonb | `JSON.stringify` or your json `serialize` | `JSON.stringify` |
|
|
1137
|
+
| plain object | text | `[object Object]` | JSON text |
|
|
1138
|
+
| plain object | a custom type registered in `types` with a `from` list | the type's `serialize` | JSON text, normally rejected |
|
|
1139
|
+
| class instance | json, jsonb | `JSON.stringify`, so `toJSON` applies | `'' + x` |
|
|
1140
|
+
| class instance | date, timestamp, timestamptz | `new Date(x)` | `'' + x`, normally rejected |
|
|
1141
|
+
|
|
1142
|
+
`time`, `timetz` and `interval` placeholders have no serializer in either mode: strings and numbers are sent unchanged as text.
|
|
1143
|
+
|
|
1144
|
+
In practice:
|
|
1145
|
+
|
|
1146
|
+
- pass `new Date(ms)` for epoch numbers, and set `connection: { timezone }` so date strings are read in the time zone you expect
|
|
1147
|
+
- pass `sql.json(s)` for a JSON string value, and `sql.types.name(v)` or `sql.typed(v, oid)` for a custom type; a custom type's `serialize` also runs for a whole array bound to that type, never for a plain value
|
|
1148
|
+
- `NaN`, `Infinity` and `-0` are sent as `NaN`, `Infinity` and `0`, and PostgreSQL's input rules for the placeholder's type decide
|
|
1149
|
+
|
|
1150
|
+
Run `'warn'` first to see which of your queries are affected.
|
|
1151
|
+
|
|
1152
|
+
**Planned for 5.0:** `'client'` becomes the default. Pass `parameter_types: 'server'` to keep the 4.x behaviour.
|
|
1153
|
+
|
|
1154
|
+
### `parameter_types: 'warn'`
|
|
1155
|
+
|
|
1156
|
+
`'warn'` behaves exactly like `'server'`, and also reports every query in which a value would be sent differently by `'client'`. The report goes to `ontypemismatch`, or to `console.warn` when it is not set:
|
|
1157
|
+
|
|
1158
|
+
```js
|
|
1159
|
+
const sql = postgres({
|
|
1160
|
+
parameter_types: 'warn',
|
|
1161
|
+
ontypemismatch: ({ query, parameters }) => {
|
|
1162
|
+
// parameters: [{ position: 2, oid: 3802, typeof: 'string' }]
|
|
1163
|
+
// position is the $n number, oid the type PostgreSQL inferred for the placeholder
|
|
1164
|
+
}
|
|
1165
|
+
})
|
|
1166
|
+
```
|
|
1167
|
+
|
|
1168
|
+
With `debug: true` each entry also carries `serverText` and `clientText`, the two texts that were compared; `clientText` is the error when the `'client'` mode would throw. A query is reported on every execution that has a difference; sample in `ontypemismatch` if that is too much. `'warn'` serializes each untyped value twice and compares the strings. The option can also be set with `?parameter_types=warn` in the connection URL or `PGPARAMETER_TYPES`.
|
|
1169
|
+
|
|
1170
|
+
`'warn'` compares the serialized text, not the stored value, so a report does not always mean the stored value would change.
|
|
1171
|
+
|
|
1172
|
+
These reported cases store the same value under both modes:
|
|
1173
|
+
|
|
1174
|
+
- `0`, `'0'`, `'false'`, `'no'` and `'off'` sent to a `boolean` placeholder (the texts differ, both store `false`)
|
|
1175
|
+
- a date string with an explicit offset sent to a `timestamptz` placeholder, as long as it has no more than millisecond precision (`toISOString()` truncates microseconds, so a string with microseconds stores a different value under `'server'`)
|
|
1176
|
+
- a string without backslashes sent to a `bytea` placeholder
|
|
1177
|
+
|
|
1178
|
+
These reported cases store a different value, or are rejected, under `'client'`:
|
|
1179
|
+
|
|
1180
|
+
- `1`, `'t'`, `'true'`, `'yes'`, `'on'` and `'1'` sent to a `boolean` placeholder (stored `false` today, `true` under `'client'`)
|
|
1181
|
+
- a date string with an explicit offset sent to a `timestamp` or `date` placeholder (`'server'` converts to UTC first, `'client'` drops the offset)
|
|
1182
|
+
- a string sent to a `json` or `jsonb` placeholder, a plain object sent to a `text` placeholder, and a number sent to a date, timestamp or boolean placeholder
|
|
1183
|
+
- a string or number sent to a custom type registered in `types` with a `from` list, which skips its `serialize`
|
|
1184
|
+
|
|
1185
|
+
Date strings without an explicit offset:
|
|
1186
|
+
|
|
1187
|
+
| String | Placeholder | Stored the same under both modes when |
|
|
1188
|
+
| --- | --- | --- |
|
|
1189
|
+
| date and time, such as `'2020-01-02T10:00'` | `timestamptz` | the Node.js process time zone and the PostgreSQL session's `TimeZone` setting match |
|
|
1190
|
+
| date only, such as `'2020-01-02'` | `timestamptz` | the PostgreSQL session's `TimeZone` setting is UTC |
|
|
1191
|
+
| date and time | `timestamp` or `date` | the Node.js process runs in UTC (`'server'` stores the UTC wall time, `'client'` stores the text as written) |
|
|
1192
|
+
|
|
980
1193
|
## Result Array
|
|
981
1194
|
|
|
982
1195
|
The `Result` Array returned from queries is a custom array allowing for easy destructuring or passing on directly to JSON.stringify or general Array usage. It includes the following properties.
|
|
@@ -1053,14 +1266,16 @@ const sql = postgres('postgres://username:password@host:port/database', {
|
|
|
1053
1266
|
application_name : 'postgres.js', // Default application_name
|
|
1054
1267
|
... // Other connection parameters, see https://www.postgresql.org/docs/current/runtime-config-client.html
|
|
1055
1268
|
},
|
|
1056
|
-
target_session_attrs : null, //
|
|
1057
|
-
//
|
|
1269
|
+
target_session_attrs : null, // Which kind of server to connect to, see
|
|
1270
|
+
// Multi-host connections below
|
|
1058
1271
|
fetch_types : true, // Automatically fetches types on connect
|
|
1059
1272
|
// on initial connection.
|
|
1273
|
+
parameter_types : 'server', // 'client' or 'warn', see Parameter types (PGPARAMETER_TYPES)
|
|
1274
|
+
ontypemismatch : fn, // Called by parameter_types: 'warn', default console.warn
|
|
1060
1275
|
})
|
|
1061
1276
|
```
|
|
1062
1277
|
|
|
1063
|
-
The `onnotice`, `onparameter`, `onnotify`, `onclose`, `onidle` and `debug` callbacks passed in the options are called synchronously from inside the driver. If one throws, the error is rethrown as an uncaught exception and the connection and its queries are not affected; under Node's default handling that ends the process.
|
|
1278
|
+
The `onnotice`, `onparameter`, `ontypemismatch`, `onnotify`, `onclose`, `onidle` and `debug` callbacks passed in the options are called synchronously from inside the driver. If one throws, the error is rethrown as an uncaught exception and the connection and its queries are not affected; under Node's default handling that ends the process.
|
|
1064
1279
|
|
|
1065
1280
|
Note that `max_lifetime = 60 * (30 + Math.random() * 30)` by default. This resolves to an interval between 30 and 60 minutes to optimize for the benefits of prepared statements **and** working nicely with Linux's OOM killer.
|
|
1066
1281
|
|
|
@@ -1099,7 +1314,20 @@ Multiple connection strings can be passed to `postgres()` in the form of `postgr
|
|
|
1099
1314
|
|
|
1100
1315
|
Connections will be attempted in order of the specified hosts/ports. On a successful connection, all retries will be reset. This ensures that hosts can come up and down seamlessly.
|
|
1101
1316
|
|
|
1102
|
-
|
|
1317
|
+
`target_session_attrs` (or `PGTARGETSESSIONATTRS`) picks which kind of server to connect to. The values and their meaning are libpq's:
|
|
1318
|
+
|
|
1319
|
+
| Value | Connects to |
|
|
1320
|
+
|---|---|
|
|
1321
|
+
| `null` (default) | any server |
|
|
1322
|
+
| `'read-write'` | a server that accepts writes: not a hot standby, and `default_transaction_read_only` off |
|
|
1323
|
+
| `'primary'` | a server that is not a hot standby, even if `default_transaction_read_only` is on |
|
|
1324
|
+
| `'read-only'` | a hot standby, or a server with `default_transaction_read_only` on |
|
|
1325
|
+
| `'standby'` | a hot standby |
|
|
1326
|
+
| `'prefer-standby'` | a hot standby if any host is one, otherwise any server |
|
|
1327
|
+
|
|
1328
|
+
During a failover or an upgrade, `'read-write'` and `'primary'` wait for the promoted server instead of connecting to a standby whose writes would fail. Read traffic that should keep using standbys during the switch belongs on `'prefer-standby'` or no `target_session_attrs` at all.
|
|
1329
|
+
|
|
1330
|
+
With several hosts, a host that answers but is the wrong kind is skipped and the next one is tried at once; if every host answers and none matches, the connection fails immediately with `TARGET_SESSION_ATTRS`. With a single host, or when some host fails to connect, it keeps retrying, bounded by `connect_timeout` per host, because a failover may be in progress.
|
|
1103
1331
|
|
|
1104
1332
|
### The Connection Pool
|
|
1105
1333
|
|
|
@@ -1276,11 +1504,14 @@ const sql = postgres()
|
|
|
1276
1504
|
| `application_name` | `PGAPPNAME` |
|
|
1277
1505
|
| `idle_timeout` | `PGIDLE_TIMEOUT` |
|
|
1278
1506
|
| `connect_timeout` | `PGCONNECT_TIMEOUT` |
|
|
1507
|
+
| `parameter_types` | `PGPARAMETER_TYPES` |
|
|
1279
1508
|
|
|
1280
1509
|
### Prepared statements
|
|
1281
1510
|
|
|
1282
1511
|
Prepared statements will automatically be created for any queries where it can be inferred that the query is static. This can be disabled by using the `prepare: false` option. For instance — this is useful when [using PGBouncer in `transaction mode`](https://github.com/porsager/postgres/issues/93#issuecomment-656290493).
|
|
1283
1512
|
|
|
1513
|
+
With `prepare: false`, `parameter_types: 'client'` sends queries with values in one round trip, unless a value is an untyped array; see [Parameter types](#parameter-types).
|
|
1514
|
+
|
|
1284
1515
|
**update**: [since 1.21.0](https://www.pgbouncer.org/2023/10/pgbouncer-1-21-0)
|
|
1285
1516
|
PGBouncer supports protocol-level named prepared statements when [configured
|
|
1286
1517
|
properly](https://www.pgbouncer.org/config.html#max_prepared_statements)
|
|
@@ -1326,6 +1557,8 @@ const [custom] = await sql`
|
|
|
1326
1557
|
|
|
1327
1558
|
```
|
|
1328
1559
|
|
|
1560
|
+
With `parameter_types: 'client'` a type's `serialize` runs for `sql.typed.name(v)` and for a whole array bound to that type, not for a plain value; see [Parameter types](#parameter-types).
|
|
1561
|
+
|
|
1329
1562
|
### Custom socket
|
|
1330
1563
|
|
|
1331
1564
|
Easily do in-process ssh tunneling to your database by providing a custom socket for Postgres.js to use. The function (optionally async) must return a socket-like duplex stream.
|
|
@@ -1357,6 +1590,17 @@ To ensure proper teardown and cleanup on server restarts use `await sql.end()` b
|
|
|
1357
1590
|
|
|
1358
1591
|
Calling `sql.end()` will reject new queries and return a Promise which resolves when all queries are finished and the underlying connections are closed. If a `{ timeout }` option is provided any pending queries will be rejected once the timeout (in seconds) is reached and the connections will be destroyed.
|
|
1359
1592
|
|
|
1593
|
+
With explicit resource management the pool ends itself at the end of a scope:
|
|
1594
|
+
|
|
1595
|
+
```js
|
|
1596
|
+
async function main() {
|
|
1597
|
+
await using sql = postgres()
|
|
1598
|
+
await sql`select 1`
|
|
1599
|
+
}
|
|
1600
|
+
```
|
|
1601
|
+
|
|
1602
|
+
This calls `sql.end()` when the scope exits, so it suits scripts and jobs, not a shared client at a module's top level. The syntax needs Node.js 24+ or TypeScript 5.2+.
|
|
1603
|
+
|
|
1360
1604
|
#### Sample shutdown using [Prexit](https://github.com/porsager/prexit)
|
|
1361
1605
|
|
|
1362
1606
|
```js
|
|
@@ -1384,6 +1628,13 @@ await reserved.release()
|
|
|
1384
1628
|
|
|
1385
1629
|
Once you have finished with the reserved connection, call `release` to add it back to the pool.
|
|
1386
1630
|
|
|
1631
|
+
```js
|
|
1632
|
+
using reserved = await sql.reserve()
|
|
1633
|
+
await reserved`select * from users`
|
|
1634
|
+
```
|
|
1635
|
+
|
|
1636
|
+
A `using` or `await using` declaration calls `release()` when the scope exits.
|
|
1637
|
+
|
|
1387
1638
|
After release, queries through that reserved handle reject with `CONNECTION_ENDED`. Call `sql.reserve()` again to acquire a new handle. A reserved handle whose connection has closed cannot be reused after reconnect.
|
|
1388
1639
|
|
|
1389
1640
|
## Error handling
|
package/cf/src/connection.js
CHANGED
|
@@ -6,7 +6,7 @@ import { crypto } from '../polyfills.js'
|
|
|
6
6
|
import Stream from 'node:stream'
|
|
7
7
|
import { performance } from '../polyfills.js'
|
|
8
8
|
|
|
9
|
-
import { stringify, handleValue, addArrayType } from './types.js'
|
|
9
|
+
import { stringify, handleValue, addArrayType, clientSerialize } from './types.js'
|
|
10
10
|
import { Errors, callback } from './errors.js'
|
|
11
11
|
import Result from './result.js'
|
|
12
12
|
import Queue from './queue.js'
|
|
@@ -106,6 +106,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
106
106
|
, hostIndex = 0
|
|
107
107
|
, length = 0
|
|
108
108
|
, rows = 0
|
|
109
|
+
, copyPaused = false
|
|
109
110
|
, serverSignature = null
|
|
110
111
|
, nextWriteTimer = null
|
|
111
112
|
, incomings = null
|
|
@@ -216,7 +217,10 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
216
217
|
const connection = {
|
|
217
218
|
queue: queues.closed,
|
|
218
219
|
idleTimer,
|
|
219
|
-
connect
|
|
220
|
+
// Sentry's hook wraps connect and reads the connection from this, so it must stay a method
|
|
221
|
+
connect(request) {
|
|
222
|
+
dispatch('acquire', request)
|
|
223
|
+
},
|
|
220
224
|
terminate: () => dispatch('terminate'),
|
|
221
225
|
execute,
|
|
222
226
|
release: () => dispatch('release'),
|
|
@@ -461,6 +465,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
461
465
|
query = results = errorResponse = chunk = nextWriteTimer = barrier = null
|
|
462
466
|
result = new Result()
|
|
463
467
|
rows = 0
|
|
468
|
+
copyPaused = false
|
|
464
469
|
nonce = serverSignature = null
|
|
465
470
|
}
|
|
466
471
|
|
|
@@ -540,7 +545,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
540
545
|
|
|
541
546
|
build(q)
|
|
542
547
|
const written = write(toBuffer(q))
|
|
543
|
-
if (q.describeFirst || q.cursorFn || mayCopy(q))
|
|
548
|
+
if (q.describeFirst || q.cursorFn || mayCopy(q) || (q.prepare && !q.prepared && !q.options.simple && q.parameters.length))
|
|
544
549
|
barrier = q
|
|
545
550
|
return written && accepting()
|
|
546
551
|
} catch (error) {
|
|
@@ -578,7 +583,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
578
583
|
|
|
579
584
|
function prepared(q) {
|
|
580
585
|
return Buffer.concat([
|
|
581
|
-
Bind(q
|
|
586
|
+
Bind(q),
|
|
582
587
|
q.cursorFn
|
|
583
588
|
? Execute('', q.cursorRows)
|
|
584
589
|
: ExecuteUnnamed
|
|
@@ -606,8 +611,11 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
606
611
|
q.signature = q.prepare && types + string
|
|
607
612
|
q.onlyDescribe && (delete statements[q.signature])
|
|
608
613
|
q.parameters = q.parameters || parameters
|
|
614
|
+
q.inferred = options.parameter_types === 'server' ? null : types.slice()
|
|
609
615
|
q.prepared = q.prepare && q.signature in statements
|
|
610
|
-
q.describeFirst = q.onlyDescribe || (
|
|
616
|
+
q.describeFirst = q.onlyDescribe || (!q.prepared && types.some((x, i) =>
|
|
617
|
+
!x && (options.parameter_types !== 'client' || Array.isArray(q.parameters[i]))
|
|
618
|
+
))
|
|
611
619
|
q.statement = q.prepared
|
|
612
620
|
? statements[q.signature]
|
|
613
621
|
: { string, types, name: q.prepare ? statementId + statementCount++ : '' }
|
|
@@ -680,6 +688,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
680
688
|
|
|
681
689
|
function errored(err) {
|
|
682
690
|
stream && (stream.destroy(err), stream = null)
|
|
691
|
+
resumeCopy()
|
|
683
692
|
query && queryError(query, err)
|
|
684
693
|
}
|
|
685
694
|
|
|
@@ -691,7 +700,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
691
700
|
err = new Error(err)
|
|
692
701
|
|
|
693
702
|
'query' in err || 'parameters' in err || Object.defineProperties(err, {
|
|
694
|
-
stack: { value: err.stack + query.origin.replace(/.*\n/, '\n'), enumerable: options.debug },
|
|
703
|
+
stack: { value: err.stack + query.origin.replace(/.*\n/, '\n'), enumerable: options.debug, writable: true, configurable: true },
|
|
695
704
|
query: { value: query.string, enumerable: options.debug },
|
|
696
705
|
parameters: { value: query.parameters, enumerable: options.debug },
|
|
697
706
|
args: { value: query.args, enumerable: options.debug },
|
|
@@ -796,7 +805,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
796
805
|
query.prepared && retryRoutines.has(errorResponse.routine) && delete statements[query.signature]
|
|
797
806
|
query.retried
|
|
798
807
|
? errored(query.retried)
|
|
799
|
-
: query.prepared && retryRoutines.has(errorResponse.routine) && query.owner === connection.owner && !barrier
|
|
808
|
+
: query.prepared && retryRoutines.has(errorResponse.routine) && query.owner === connection.owner && !barrier && !query.cancelled
|
|
800
809
|
&& (x[5] !== 69 || endsAbortedState.test(query.string))
|
|
801
810
|
? retry(query, errorResponse)
|
|
802
811
|
: errored(errorResponse)
|
|
@@ -810,6 +819,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
810
819
|
|
|
811
820
|
query = results = errorResponse = null
|
|
812
821
|
result = new Result()
|
|
822
|
+
rows = 0
|
|
813
823
|
|
|
814
824
|
dispatch('ready')
|
|
815
825
|
}
|
|
@@ -1042,8 +1052,9 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
1042
1052
|
|
|
1043
1053
|
function mismatchReason(x, xs) {
|
|
1044
1054
|
return (
|
|
1055
|
+
(x === 'read-write' && xs.in_hot_standby === 'on' && 'is a standby') ||
|
|
1045
1056
|
(x === 'read-write' && xs.default_transaction_read_only === 'on' && 'is read-only') ||
|
|
1046
|
-
(x === 'read-only' && xs.default_transaction_read_only === 'off' && 'is read-write') ||
|
|
1057
|
+
(x === 'read-only' && xs.in_hot_standby === 'off' && xs.default_transaction_read_only === 'off' && 'is read-write') ||
|
|
1047
1058
|
(x === 'primary' && xs.in_hot_standby === 'on' && 'is a standby') ||
|
|
1048
1059
|
(x === 'standby' && xs.in_hot_standby === 'off' && 'is a primary') ||
|
|
1049
1060
|
(x === 'prefer-standby' && xs.in_hot_standby === 'off' && standbyPass() && 'is a primary') ||
|
|
@@ -1142,7 +1153,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
1142
1153
|
|
|
1143
1154
|
function CopyOutResponse() {
|
|
1144
1155
|
stream = new Stream.Readable({
|
|
1145
|
-
read() { socket.resume() }
|
|
1156
|
+
read() { copyPaused = false; socket.resume() }
|
|
1146
1157
|
})
|
|
1147
1158
|
query.resolve(stream)
|
|
1148
1159
|
connection.owner && connection.owner.next()
|
|
@@ -1152,7 +1163,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
1152
1163
|
function CopyBothResponse() {
|
|
1153
1164
|
stream = new Stream.Duplex({
|
|
1154
1165
|
autoDestroy: true,
|
|
1155
|
-
read() { socket.resume() },
|
|
1166
|
+
read() { copyPaused = false; socket.resume() },
|
|
1156
1167
|
/* c8 ignore next 11 */
|
|
1157
1168
|
write(chunk, encoding, callback) {
|
|
1158
1169
|
socket.write(b().d().raw(chunk).end(), callback)
|
|
@@ -1172,12 +1183,23 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
1172
1183
|
}
|
|
1173
1184
|
|
|
1174
1185
|
function CopyData(x) {
|
|
1175
|
-
stream &&
|
|
1186
|
+
if (stream && !stream.push(x.subarray(5))) {
|
|
1187
|
+
copyPaused = true
|
|
1188
|
+
socket.pause()
|
|
1189
|
+
}
|
|
1176
1190
|
}
|
|
1177
1191
|
|
|
1178
1192
|
function CopyDone() {
|
|
1179
1193
|
stream && stream.push(null)
|
|
1180
1194
|
stream = null
|
|
1195
|
+
resumeCopy()
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
function resumeCopy() {
|
|
1199
|
+
if (copyPaused) {
|
|
1200
|
+
copyPaused = false
|
|
1201
|
+
socket.resume()
|
|
1202
|
+
}
|
|
1181
1203
|
}
|
|
1182
1204
|
|
|
1183
1205
|
function NoticeResponse(x) {
|
|
@@ -1215,20 +1237,21 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
1215
1237
|
}
|
|
1216
1238
|
|
|
1217
1239
|
/* Messages */
|
|
1218
|
-
function Bind(
|
|
1240
|
+
function Bind(q) {
|
|
1241
|
+
const differences = []
|
|
1219
1242
|
let prev
|
|
1220
1243
|
, type
|
|
1221
1244
|
|
|
1222
|
-
b().B().str(
|
|
1245
|
+
b().B().str((q.cursorName || '') + b.N).str(q.statement.name + b.N).i16(0).i16(q.parameters.length)
|
|
1223
1246
|
|
|
1224
|
-
parameters.forEach((x, i) => {
|
|
1247
|
+
q.parameters.forEach((x, i) => {
|
|
1225
1248
|
if (x === null)
|
|
1226
1249
|
return b.i32(0xFFFFFFFF)
|
|
1227
1250
|
|
|
1228
|
-
type = types[i]
|
|
1229
|
-
x =
|
|
1230
|
-
?
|
|
1231
|
-
: ''
|
|
1251
|
+
type = q.statement.types[i]
|
|
1252
|
+
x = !q.inferred || q.inferred[i] || Array.isArray(x)
|
|
1253
|
+
? serialize(x, type)
|
|
1254
|
+
: options.parameter_types === 'client' ? clientSerialize(x) : compare(x, type, i, differences)
|
|
1232
1255
|
|
|
1233
1256
|
prev = b.i
|
|
1234
1257
|
b.inc(4).str(x).i32(b.i - prev - 4, prev)
|
|
@@ -1236,7 +1259,27 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
1236
1259
|
|
|
1237
1260
|
b.i16(0)
|
|
1238
1261
|
|
|
1239
|
-
|
|
1262
|
+
const message = b.end()
|
|
1263
|
+
differences.length && callback(options.ontypemismatch || console.warn, { query: q.string, parameters: differences }) // eslint-disable-line
|
|
1264
|
+
return message
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
function serialize(x, type) {
|
|
1268
|
+
return type in options.serializers
|
|
1269
|
+
? options.serializers[type](x)
|
|
1270
|
+
: '' + x
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
function compare(x, type, i, differences) {
|
|
1274
|
+
const serverText = serialize(x, type)
|
|
1275
|
+
let clientText
|
|
1276
|
+
try {
|
|
1277
|
+
clientText = clientSerialize(x)
|
|
1278
|
+
} catch (error) {
|
|
1279
|
+
clientText = error
|
|
1280
|
+
}
|
|
1281
|
+
serverText === clientText || differences.push({ position: i + 1, oid: type, typeof: typeof x, ...options.debug && { serverText, clientText } })
|
|
1282
|
+
return serverText
|
|
1240
1283
|
}
|
|
1241
1284
|
|
|
1242
1285
|
function Parse(str, parameters, types, name = '') {
|
package/cf/src/errors.js
CHANGED