@steadybrew/postgresjs 4.3.0 → 4.4.1
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 +18 -2
- package/cf/src/connection.js +59 -19
- package/cf/src/errors.js +8 -0
- package/cf/src/index.js +61 -34
- package/cf/src/subscribe.js +127 -47
- package/cjs/src/connection.js +59 -19
- package/cjs/src/errors.js +8 -0
- package/cjs/src/index.js +61 -34
- package/cjs/src/subscribe.js +127 -47
- package/package.json +1 -1
- package/src/connection.js +59 -19
- package/src/errors.js +8 -0
- package/src/index.js +61 -34
- package/src/subscribe.js +127 -47
- package/types/index.d.ts +9 -9
package/README.md
CHANGED
|
@@ -592,6 +592,8 @@ for await (const chunk of readableStream) {
|
|
|
592
592
|
|
|
593
593
|
> **NOTE** This is a low-level API which does not provide any type safety. To make this work, you must match your [`copy query` parameters](https://www.postgresql.org/docs/14/sql-copy.html) correctly to your [Node.js stream read or write](https://nodejs.org/api/stream.html) code. Ensure [Node.js stream backpressure](https://nodejs.org/en/learn/modules/backpressuring-in-streams) is handled correctly to avoid memory exhaustion.
|
|
594
594
|
|
|
595
|
+
A `COPY` holds its connection until it finishes. Postgres.js cannot tell that a simple-protocol query is a `COPY` until the server answers, so it sends nothing behind a simple query whose text contains the word `copy` (`sql.unsafe()` or `sql.file()` without parameters, `.simple()`, `.readable()`, `.writable()`). Queries sent through `sql` meanwhile run on another connection or wait for one. When every connection is running a `COPY` that has not been ended, for example with `max: 1`, queries, `sql.reserve()` and `sql.begin()` through `sql` reject with `COPY_IN_PROGRESS` instead, because only that `COPY` finishing could free a connection. Statements on a `sql.reserve()` or `sql.begin()` handle reject with `COPY_IN_PROGRESS` while its `COPY` runs.
|
|
596
|
+
|
|
595
597
|
### Canceling Queries in Progress
|
|
596
598
|
|
|
597
599
|
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.
|
|
@@ -869,7 +871,7 @@ const sql = postgres({
|
|
|
869
871
|
When you call `.listen`, a dedicated connection will be created to ensure that you receive notifications instantly. This connection will be used for any further calls to `.listen`. The connection will automatically reconnect according to a backoff reconnection pattern to not overload the database server.
|
|
870
872
|
|
|
871
873
|
### Listen `await sql.listen(channel, onnotify, [onlisten]) -> { state }`
|
|
872
|
-
`.listen` takes the channel name, a function to handle each notify, and an optional function to run every time listen is registered and ready (happens on initial connect and reconnects). It returns a promise which resolves once the `LISTEN` query to Postgres completes, or if there is already a listener active.
|
|
874
|
+
`.listen` takes the channel name, a function to handle each notify, and an optional function to run every time listen is registered and ready (happens on initial connect and reconnects). It returns a promise which resolves once the `LISTEN` query to Postgres completes, or if there is already a listener active. If either function throws, the error is rethrown as an uncaught exception, and the other listeners and the returned promise are not affected.
|
|
873
875
|
|
|
874
876
|
```js
|
|
875
877
|
await sql.listen('news', payload => {
|
|
@@ -922,10 +924,17 @@ const { unsubscribe } = await sql.subscribe(
|
|
|
922
924
|
},
|
|
923
925
|
() => {
|
|
924
926
|
// Callback on initial connect and potential reconnects
|
|
927
|
+
},
|
|
928
|
+
(error) => {
|
|
929
|
+
// Callback on connection errors and changes that could not be decoded
|
|
925
930
|
}
|
|
926
931
|
)
|
|
927
932
|
```
|
|
928
933
|
|
|
934
|
+
If any of these callbacks throws, the error is rethrown as an uncaught exception, and other subscribers and the replication stream are not affected.
|
|
935
|
+
|
|
936
|
+
`onerror` is called with errors of the replication connection, after which the subscription reconnects on a new replication slot and calls `onsubscribe` again (these are also logged with `console.error`, and changes committed while it was disconnected are not delivered; if the reconnect itself fails, it is retried with backoff, its error is logged with `console.error` and passed to `onerror`, and it never ends the process), and with errors thrown while a change is decoded by a [transform](#data-transformation) or a custom type parser. Such a change is skipped, and the replication stream and later changes carry on. A decode error goes only to the subscriptions that would have received the change, matched by operation and table; a subscription filtered by key gets every skipped change of its table, as the key is read from the decoded row, even if the table has no primary key. If a column name transform throws, each change of that table is skipped and reported until the server describes the table again, for example after `ALTER TABLE`. If one of the matching subscriptions has no `onerror`, the error is rethrown once as an uncaught exception, which ends the process under Node's default handling. `onerror` stays registered across reconnects until `unsubscribe()`.
|
|
937
|
+
|
|
929
938
|
### Subscribe pattern
|
|
930
939
|
|
|
931
940
|
You can subscribe to specific operations, tables, or even rows with primary keys.
|
|
@@ -1051,6 +1060,8 @@ const sql = postgres('postgres://username:password@host:port/database', {
|
|
|
1051
1060
|
})
|
|
1052
1061
|
```
|
|
1053
1062
|
|
|
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.
|
|
1064
|
+
|
|
1054
1065
|
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.
|
|
1055
1066
|
|
|
1056
1067
|
### Dynamic passwords
|
|
@@ -1457,7 +1468,12 @@ If the only attempt times out, the error is `CONNECT_TIMEOUT`. Once earlier atte
|
|
|
1457
1468
|
##### COPY_IN_PROGRESS
|
|
1458
1469
|
> You cannot execute queries during copy
|
|
1459
1470
|
|
|
1460
|
-
This error is thrown
|
|
1471
|
+
This error is thrown for a statement on a `sql.reserve()` or `sql.begin()` handle while its copy operation (writable / readable) runs, including statements sent before the server started it. A query, `sql.reserve()` or `sql.begin()` through `sql` gets it when every connection is running a copy operation whose writable has not been ended; otherwise it waits for a connection.
|
|
1472
|
+
|
|
1473
|
+
##### TRANSACTION_ROLLED_BACK
|
|
1474
|
+
> The server rolled the transaction back instead of committing it
|
|
1475
|
+
|
|
1476
|
+
This error is thrown by `sql.begin()` when the server answers the final `COMMIT` (or `PREPARE TRANSACTION`) with `ROLLBACK`, because the transaction was aborted by a statement whose error the callback did not surface, such as a failing query that was not awaited. Nothing was committed. When such a query's error was already recorded, it is the error's `cause`.
|
|
1461
1477
|
|
|
1462
1478
|
## TypeScript support
|
|
1463
1479
|
|
package/cf/src/connection.js
CHANGED
|
@@ -7,7 +7,7 @@ import Stream from 'node:stream'
|
|
|
7
7
|
import { performance } from '../polyfills.js'
|
|
8
8
|
|
|
9
9
|
import { stringify, handleValue, addArrayType } from './types.js'
|
|
10
|
-
import { Errors } from './errors.js'
|
|
10
|
+
import { Errors, callback } from './errors.js'
|
|
11
11
|
import Result from './result.js'
|
|
12
12
|
import Queue from './queue.js'
|
|
13
13
|
import clampedTimeout from './timeout.js'
|
|
@@ -15,7 +15,7 @@ import { SSLRequest, tlsConfig, cancelRequest } from './transport.js'
|
|
|
15
15
|
import { Query, CLOSE } from './query.js'
|
|
16
16
|
import b from './bytes.js'
|
|
17
17
|
|
|
18
|
-
export
|
|
18
|
+
export { Connection }
|
|
19
19
|
|
|
20
20
|
let uid = 1
|
|
21
21
|
|
|
@@ -28,6 +28,8 @@ const Sync = b().S().end()
|
|
|
28
28
|
const Phase = { Closed: 0, Backoff: 1, Connecting: 2, Initializing: 3, Ready: 4, Draining: 5, Closing: 6 }
|
|
29
29
|
const phaseNames = Object.keys(Phase)
|
|
30
30
|
|
|
31
|
+
const endsAbortedState = /^\s*(rollback|abort)\b/i
|
|
32
|
+
|
|
31
33
|
const retryRoutines = new Set([
|
|
32
34
|
'FetchPreparedStatement',
|
|
33
35
|
'RevalidateCachedQuery',
|
|
@@ -112,6 +114,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
112
114
|
, chunk = null
|
|
113
115
|
, nonce = null
|
|
114
116
|
, query = null
|
|
117
|
+
, barrier = null
|
|
115
118
|
, final = null
|
|
116
119
|
|
|
117
120
|
const check = globalThis[Symbol.for('postgres.js:check')]
|
|
@@ -226,6 +229,9 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
226
229
|
owner: null,
|
|
227
230
|
get active() { return query ? 1 : 0 },
|
|
228
231
|
get pipelined() { return sent.length },
|
|
232
|
+
get accepting() { return accepting() },
|
|
233
|
+
get copying() { return stream !== null },
|
|
234
|
+
get copyHeld() { return stream !== null && !stream.writableEnded },
|
|
229
235
|
get acquiring() { return acquisition && !acquisition.owner.cancelled ? 1 : 0 },
|
|
230
236
|
count: 0,
|
|
231
237
|
id
|
|
@@ -270,8 +276,12 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
270
276
|
return !query && sent.length === 0
|
|
271
277
|
}
|
|
272
278
|
|
|
279
|
+
function accepting() {
|
|
280
|
+
return !query || (!barrier && sent.length < max_pipeline)
|
|
281
|
+
}
|
|
282
|
+
|
|
273
283
|
function backoffMs() {
|
|
274
|
-
return (
|
|
284
|
+
return backoffSeconds(backoff, options.shared.retries) * 1000
|
|
275
285
|
}
|
|
276
286
|
|
|
277
287
|
function standbyPass() {
|
|
@@ -448,7 +458,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
448
458
|
queryError(pending, err)
|
|
449
459
|
pending.cancelled && pending.cancelled.resolve()
|
|
450
460
|
}
|
|
451
|
-
query = results = errorResponse = chunk = nextWriteTimer = null
|
|
461
|
+
query = results = errorResponse = chunk = nextWriteTimer = barrier = null
|
|
452
462
|
result = new Result()
|
|
453
463
|
rows = 0
|
|
454
464
|
nonce = serverSignature = null
|
|
@@ -512,8 +522,10 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
512
522
|
}
|
|
513
523
|
|
|
514
524
|
function submit(q) {
|
|
515
|
-
if (stream)
|
|
516
|
-
|
|
525
|
+
if (stream) {
|
|
526
|
+
queryError(q, Errors.generic('COPY_IN_PROGRESS', 'You cannot execute queries during copy'))
|
|
527
|
+
return true
|
|
528
|
+
}
|
|
517
529
|
|
|
518
530
|
if (q.cancelled)
|
|
519
531
|
return true
|
|
@@ -527,17 +539,21 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
527
539
|
: (query = q, query.active = true)
|
|
528
540
|
|
|
529
541
|
build(q)
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
542
|
+
const written = write(toBuffer(q))
|
|
543
|
+
if (q.describeFirst || q.cursorFn || mayCopy(q))
|
|
544
|
+
barrier = q
|
|
545
|
+
return written && accepting()
|
|
534
546
|
} catch (error) {
|
|
535
547
|
sent.remove(q) || write(Sync)
|
|
536
548
|
queryError(q, error)
|
|
537
|
-
return
|
|
549
|
+
return accepting()
|
|
538
550
|
}
|
|
539
551
|
}
|
|
540
552
|
|
|
553
|
+
function mayCopy(q) {
|
|
554
|
+
return q.options.simple && /\bcopy\b/i.test(q.string)
|
|
555
|
+
}
|
|
556
|
+
|
|
541
557
|
function toBuffer(q) {
|
|
542
558
|
if (q.parameters.length >= 65534)
|
|
543
559
|
throw Errors.generic('MAX_PARAMETERS_EXCEEDED', 'Max number of parameters (65534) exceeded')
|
|
@@ -596,7 +612,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
596
612
|
? statements[q.signature]
|
|
597
613
|
: { string, types, name: q.prepare ? statementId + statementCount++ : '' }
|
|
598
614
|
|
|
599
|
-
typeof options.debug === 'function' && options.debug
|
|
615
|
+
typeof options.debug === 'function' && callback(options.debug, id, string, parameters, types)
|
|
600
616
|
}
|
|
601
617
|
|
|
602
618
|
function write(x, fn) {
|
|
@@ -767,18 +783,21 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
767
783
|
backendParameters[k] = v
|
|
768
784
|
if (options.parameters[k] !== v) {
|
|
769
785
|
options.parameters[k] = v
|
|
770
|
-
onparameter
|
|
786
|
+
callback(onparameter, k, v)
|
|
771
787
|
}
|
|
772
788
|
}
|
|
773
789
|
|
|
774
|
-
function ReadyForQuery() {
|
|
790
|
+
function ReadyForQuery(x) {
|
|
791
|
+
query === barrier && (barrier = null)
|
|
775
792
|
if (query) {
|
|
776
793
|
if (errorResponse) {
|
|
777
794
|
if (query.initialization)
|
|
778
795
|
return dispatch('initFailed', errorResponse)
|
|
796
|
+
query.prepared && retryRoutines.has(errorResponse.routine) && delete statements[query.signature]
|
|
779
797
|
query.retried
|
|
780
798
|
? errored(query.retried)
|
|
781
|
-
: query.prepared && retryRoutines.has(errorResponse.routine) && query.owner === connection.owner
|
|
799
|
+
: query.prepared && retryRoutines.has(errorResponse.routine) && query.owner === connection.owner && !barrier
|
|
800
|
+
&& (x[5] !== 69 || endsAbortedState.test(query.string))
|
|
782
801
|
? retry(query, errorResponse)
|
|
783
802
|
: errored(errorResponse)
|
|
784
803
|
} else {
|
|
@@ -1061,7 +1080,6 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
1061
1080
|
}
|
|
1062
1081
|
|
|
1063
1082
|
function retry(q, error) {
|
|
1064
|
-
delete statements[q.signature]
|
|
1065
1083
|
q.retried = error
|
|
1066
1084
|
execute(q)
|
|
1067
1085
|
}
|
|
@@ -1072,7 +1090,8 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
1072
1090
|
|
|
1073
1091
|
let index = 9
|
|
1074
1092
|
while (x[index++] !== 0);
|
|
1075
|
-
|
|
1093
|
+
callback(
|
|
1094
|
+
onnotify,
|
|
1076
1095
|
x.toString('utf8', 9, index - 1),
|
|
1077
1096
|
x.toString('utf8', index, x.length - 1)
|
|
1078
1097
|
)
|
|
@@ -1118,6 +1137,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
1118
1137
|
}
|
|
1119
1138
|
})
|
|
1120
1139
|
query.resolve(stream)
|
|
1140
|
+
connection.owner && connection.owner.next()
|
|
1121
1141
|
}
|
|
1122
1142
|
|
|
1123
1143
|
function CopyOutResponse() {
|
|
@@ -1125,6 +1145,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
1125
1145
|
read() { socket.resume() }
|
|
1126
1146
|
})
|
|
1127
1147
|
query.resolve(stream)
|
|
1148
|
+
connection.owner && connection.owner.next()
|
|
1128
1149
|
}
|
|
1129
1150
|
|
|
1130
1151
|
/* c8 ignore next 3 */
|
|
@@ -1147,6 +1168,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
1147
1168
|
}
|
|
1148
1169
|
})
|
|
1149
1170
|
query.resolve(stream)
|
|
1171
|
+
connection.owner && connection.owner.next()
|
|
1150
1172
|
}
|
|
1151
1173
|
|
|
1152
1174
|
function CopyData(x) {
|
|
@@ -1159,10 +1181,12 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
1159
1181
|
}
|
|
1160
1182
|
|
|
1161
1183
|
function NoticeResponse(x) {
|
|
1184
|
+
if (onnotice === false)
|
|
1185
|
+
return
|
|
1186
|
+
|
|
1162
1187
|
onnotice
|
|
1163
|
-
? onnotice
|
|
1188
|
+
? callback(onnotice, parseError(x))
|
|
1164
1189
|
: console.log(parseError(x)) // eslint-disable-line
|
|
1165
|
-
|
|
1166
1190
|
}
|
|
1167
1191
|
|
|
1168
1192
|
/* c8 ignore next 3 */
|
|
@@ -1313,3 +1337,19 @@ function timer(fn, seconds) {
|
|
|
1313
1337
|
fn.apply(null, args)
|
|
1314
1338
|
}
|
|
1315
1339
|
}
|
|
1340
|
+
|
|
1341
|
+
export function defaultBackoff(retries) {
|
|
1342
|
+
return (0.5 + Math.random() / 2) * Math.min(3 ** retries / 100, 20)
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
export function backoffSeconds(backoff, retries) {
|
|
1346
|
+
if (typeof backoff !== 'function')
|
|
1347
|
+
return backoff
|
|
1348
|
+
|
|
1349
|
+
try {
|
|
1350
|
+
return backoff(retries)
|
|
1351
|
+
} catch (e) {
|
|
1352
|
+
callback(() => { throw e })
|
|
1353
|
+
return defaultBackoff(retries)
|
|
1354
|
+
}
|
|
1355
|
+
}
|
package/cf/src/errors.js
CHANGED
package/cf/src/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
Parameter,
|
|
10
10
|
Identifier,
|
|
11
11
|
Builder,
|
|
12
|
+
escapeIdentifier,
|
|
12
13
|
toPascal,
|
|
13
14
|
pascal,
|
|
14
15
|
toCamel,
|
|
@@ -20,12 +21,12 @@ import {
|
|
|
20
21
|
fromKebab
|
|
21
22
|
} from './types.js'
|
|
22
23
|
|
|
23
|
-
import Connection from './connection.js'
|
|
24
|
+
import { Connection, defaultBackoff } from './connection.js'
|
|
24
25
|
import { cancelRequest } from './transport.js'
|
|
25
26
|
import clampedTimeout from './timeout.js'
|
|
26
27
|
import { Query, CLOSE } from './query.js'
|
|
27
28
|
import Queue from './queue.js'
|
|
28
|
-
import { Errors, PostgresError } from './errors.js'
|
|
29
|
+
import { Errors, PostgresError, callback } from './errors.js'
|
|
29
30
|
import Subscribe from './subscribe.js'
|
|
30
31
|
import largeObject from './large.js'
|
|
31
32
|
|
|
@@ -219,7 +220,7 @@ function Postgres(a, b) {
|
|
|
219
220
|
})
|
|
220
221
|
},
|
|
221
222
|
onnotify(c, x) {
|
|
222
|
-
c in listen.channels && listen.channels[c].listeners.forEach(l => l.fn
|
|
223
|
+
c in listen.channels && listen.channels[c].listeners.forEach(l => callback(l.fn, x))
|
|
223
224
|
}
|
|
224
225
|
}))
|
|
225
226
|
|
|
@@ -229,7 +230,7 @@ function Postgres(a, b) {
|
|
|
229
230
|
if (exists) {
|
|
230
231
|
channels[name].listeners.push(listener)
|
|
231
232
|
const result = await channels[name].result
|
|
232
|
-
listener.onlisten
|
|
233
|
+
callback(listener.onlisten)
|
|
233
234
|
return { state: result.state, unlisten }
|
|
234
235
|
}
|
|
235
236
|
|
|
@@ -237,7 +238,7 @@ function Postgres(a, b) {
|
|
|
237
238
|
sql.unsafe('"' + name.replace(/"/g, '""') + '"')
|
|
238
239
|
}`, listeners: [listener] }
|
|
239
240
|
const result = await channels[name].result
|
|
240
|
-
listener.onlisten
|
|
241
|
+
callback(listener.onlisten)
|
|
241
242
|
return { state: result.state, unlisten }
|
|
242
243
|
|
|
243
244
|
async function unlisten() {
|
|
@@ -273,7 +274,9 @@ function Postgres(a, b) {
|
|
|
273
274
|
const query = { reserve: c => resolve(own(c, l)), reject }
|
|
274
275
|
closed.length
|
|
275
276
|
? connect(closed.shift(), query)
|
|
276
|
-
:
|
|
277
|
+
: allHeldByCopy()
|
|
278
|
+
? reject(copyInProgress())
|
|
279
|
+
: queries.push(query)
|
|
277
280
|
})
|
|
278
281
|
return l
|
|
279
282
|
}
|
|
@@ -295,9 +298,9 @@ function Postgres(a, b) {
|
|
|
295
298
|
function send(l, q, expected = Lease.Active) {
|
|
296
299
|
const c = l.connection
|
|
297
300
|
l.state === expected
|
|
298
|
-
? c.queue
|
|
299
|
-
? (q
|
|
300
|
-
:
|
|
301
|
+
? c.copying || (c.queue !== full && c.accepting)
|
|
302
|
+
? c.execute(q) || move(c, full)
|
|
303
|
+
: (move(c, full), q.queue = l.queue, l.queue.push(q))
|
|
301
304
|
: q.reject(leaseError(l.state))
|
|
302
305
|
}
|
|
303
306
|
|
|
@@ -353,14 +356,16 @@ function Postgres(a, b) {
|
|
|
353
356
|
}
|
|
354
357
|
|
|
355
358
|
async function finish(statement) {
|
|
359
|
+
let result
|
|
356
360
|
l.state = Lease.Settling
|
|
357
361
|
try {
|
|
358
|
-
await statement(Sql(q => send(l, q, Lease.Settling)))
|
|
362
|
+
result = await statement(Sql(q => send(l, q, Lease.Settling)))
|
|
359
363
|
} catch (e) {
|
|
360
364
|
settle(l, Lease.Discarded)
|
|
361
365
|
throw e
|
|
362
366
|
}
|
|
363
367
|
settle(l, Lease.Released)
|
|
368
|
+
return result
|
|
364
369
|
}
|
|
365
370
|
|
|
366
371
|
async function scope(fn, name) {
|
|
@@ -375,7 +380,7 @@ function Postgres(a, b) {
|
|
|
375
380
|
let uncaughtError
|
|
376
381
|
, result
|
|
377
382
|
|
|
378
|
-
name && await sql
|
|
383
|
+
name && await sql.unsafe('savepoint ' + escapeIdentifier(name))
|
|
379
384
|
try {
|
|
380
385
|
result = await new Promise((resolve, reject) => {
|
|
381
386
|
const x = fn(sql)
|
|
@@ -386,8 +391,8 @@ function Postgres(a, b) {
|
|
|
386
391
|
throw uncaughtError
|
|
387
392
|
} catch (e) {
|
|
388
393
|
l.state === Lease.Active && await (name
|
|
389
|
-
? sql
|
|
390
|
-
: finish(final => final
|
|
394
|
+
? sql.unsafe('rollback to ' + escapeIdentifier(name))
|
|
395
|
+
: finish(final => final.unsafe('rollback'))
|
|
391
396
|
)
|
|
392
397
|
throw e instanceof PostgresError && e.code === '25P02' && uncaughtError || e
|
|
393
398
|
}
|
|
@@ -395,10 +400,18 @@ function Postgres(a, b) {
|
|
|
395
400
|
if (l.state !== Lease.Active)
|
|
396
401
|
throw l.error || leaseError(l.state)
|
|
397
402
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
403
|
+
if (!name) {
|
|
404
|
+
const done = await finish(final => prepare
|
|
405
|
+
? final.unsafe('prepare transaction ' + prepare)
|
|
406
|
+
: final.unsafe('commit')
|
|
407
|
+
)
|
|
408
|
+
if (done.command === 'ROLLBACK') {
|
|
409
|
+
throw Object.assign(
|
|
410
|
+
Errors.generic('TRANSACTION_ROLLED_BACK', 'The server rolled the transaction back instead of committing it'),
|
|
411
|
+
uncaughtError && { cause: uncaughtError }
|
|
412
|
+
)
|
|
413
|
+
}
|
|
414
|
+
}
|
|
402
415
|
|
|
403
416
|
return result
|
|
404
417
|
|
|
@@ -427,14 +440,6 @@ function Postgres(a, b) {
|
|
|
427
440
|
return c
|
|
428
441
|
}
|
|
429
442
|
|
|
430
|
-
function callback(fn, ...args) {
|
|
431
|
-
try {
|
|
432
|
-
fn && fn(...args)
|
|
433
|
-
} catch (e) {
|
|
434
|
-
queueMicrotask(() => { throw e })
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
|
|
438
443
|
function json(x) {
|
|
439
444
|
return new Parameter(x, 3802)
|
|
440
445
|
}
|
|
@@ -457,9 +462,32 @@ function Postgres(a, b) {
|
|
|
457
462
|
if (closed.length)
|
|
458
463
|
return connect(closed.shift(), query)
|
|
459
464
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
465
|
+
const c = takeBusy()
|
|
466
|
+
c
|
|
467
|
+
? go(c, query)
|
|
468
|
+
: allHeldByCopy()
|
|
469
|
+
? query.reject(copyInProgress())
|
|
470
|
+
: queries.push(query)
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function allHeldByCopy() {
|
|
474
|
+
return connections.every(c => c.copyHeld)
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function copyInProgress() {
|
|
478
|
+
return Errors.generic('COPY_IN_PROGRESS', 'You cannot execute queries during copy')
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function takeBusy() {
|
|
482
|
+
while (busy.length) {
|
|
483
|
+
const c = busy.shift()
|
|
484
|
+
if (c.accepting)
|
|
485
|
+
return c
|
|
486
|
+
|
|
487
|
+
move(c, full)
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return null
|
|
463
491
|
}
|
|
464
492
|
|
|
465
493
|
function takeOpen() {
|
|
@@ -546,6 +574,9 @@ function Postgres(a, b) {
|
|
|
546
574
|
if (initial && initial.reserve)
|
|
547
575
|
return initial.reserve(c)
|
|
548
576
|
|
|
577
|
+
if (!initial && !c.accepting)
|
|
578
|
+
return move(c, full)
|
|
579
|
+
|
|
549
580
|
if (!initial && !queries.length)
|
|
550
581
|
return move(c, open)
|
|
551
582
|
|
|
@@ -584,7 +615,7 @@ function Postgres(a, b) {
|
|
|
584
615
|
function onclose(c, e) {
|
|
585
616
|
move(c, closed)
|
|
586
617
|
c.owner && settle(c.owner, Lease.Closed, e)
|
|
587
|
-
options.onclose
|
|
618
|
+
callback(options.onclose, c.id)
|
|
588
619
|
if (ending)
|
|
589
620
|
return closedWhileEnding(c, e)
|
|
590
621
|
|
|
@@ -639,7 +670,7 @@ function parseOptions(a, b) {
|
|
|
639
670
|
connect_timeout : 30,
|
|
640
671
|
max_lifetime : max_lifetime,
|
|
641
672
|
max_pipeline : 100,
|
|
642
|
-
backoff :
|
|
673
|
+
backoff : defaultBackoff,
|
|
643
674
|
keep_alive : 60,
|
|
644
675
|
prepare : true,
|
|
645
676
|
debug : false,
|
|
@@ -698,10 +729,6 @@ function tsa(o, url, env) {
|
|
|
698
729
|
throw new Error('target_session_attrs ' + x + ' is not supported')
|
|
699
730
|
}
|
|
700
731
|
|
|
701
|
-
function backoff(retries) {
|
|
702
|
-
return (0.5 + Math.random() / 2) * Math.min(3 ** retries / 100, 20)
|
|
703
|
-
}
|
|
704
|
-
|
|
705
732
|
function max_lifetime() {
|
|
706
733
|
return 60 * (30 + Math.random() * 30)
|
|
707
734
|
}
|