@steadybrew/postgresjs 4.3.0 → 4.4.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/src/connection.js CHANGED
@@ -5,7 +5,7 @@ import Stream from 'stream'
5
5
  import { performance } from 'perf_hooks'
6
6
 
7
7
  import { stringify, handleValue, addArrayType } from './types.js'
8
- import { Errors } from './errors.js'
8
+ import { Errors, callback } from './errors.js'
9
9
  import Result from './result.js'
10
10
  import Queue from './queue.js'
11
11
  import clampedTimeout from './timeout.js'
@@ -13,7 +13,7 @@ import { SSLRequest, tlsConfig, cancelRequest } from './transport.js'
13
13
  import { Query, CLOSE } from './query.js'
14
14
  import b from './bytes.js'
15
15
 
16
- export default Connection
16
+ export { Connection }
17
17
 
18
18
  let uid = 1
19
19
 
@@ -26,6 +26,8 @@ const Sync = b().S().end()
26
26
  const Phase = { Closed: 0, Backoff: 1, Connecting: 2, Initializing: 3, Ready: 4, Draining: 5, Closing: 6 }
27
27
  const phaseNames = Object.keys(Phase)
28
28
 
29
+ const endsAbortedState = /^\s*(rollback|abort)\b/i
30
+
29
31
  const retryRoutines = new Set([
30
32
  'FetchPreparedStatement',
31
33
  'RevalidateCachedQuery',
@@ -110,6 +112,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
110
112
  , chunk = null
111
113
  , nonce = null
112
114
  , query = null
115
+ , barrier = null
113
116
  , final = null
114
117
 
115
118
  const check = globalThis[Symbol.for('postgres.js:check')]
@@ -224,6 +227,8 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
224
227
  owner: null,
225
228
  get active() { return query ? 1 : 0 },
226
229
  get pipelined() { return sent.length },
230
+ get accepting() { return accepting() },
231
+ get copying() { return stream !== null },
227
232
  get acquiring() { return acquisition && !acquisition.owner.cancelled ? 1 : 0 },
228
233
  count: 0,
229
234
  id
@@ -268,8 +273,12 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
268
273
  return !query && sent.length === 0
269
274
  }
270
275
 
276
+ function accepting() {
277
+ return !query || (!barrier && sent.length < max_pipeline)
278
+ }
279
+
271
280
  function backoffMs() {
272
- return (typeof backoff === 'function' ? backoff(options.shared.retries) : backoff) * 1000
281
+ return backoffSeconds(backoff, options.shared.retries) * 1000
273
282
  }
274
283
 
275
284
  function standbyPass() {
@@ -446,7 +455,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
446
455
  queryError(pending, err)
447
456
  pending.cancelled && pending.cancelled.resolve()
448
457
  }
449
- query = results = errorResponse = chunk = nextWriteTimer = null
458
+ query = results = errorResponse = chunk = nextWriteTimer = barrier = null
450
459
  result = new Result()
451
460
  rows = 0
452
461
  nonce = serverSignature = null
@@ -510,8 +519,10 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
510
519
  }
511
520
 
512
521
  function submit(q) {
513
- if (stream)
514
- return queryError(q, Errors.generic('COPY_IN_PROGRESS', 'You cannot execute queries during copy'))
522
+ if (stream) {
523
+ queryError(q, Errors.generic('COPY_IN_PROGRESS', 'You cannot execute queries during copy'))
524
+ return true
525
+ }
515
526
 
516
527
  if (q.cancelled)
517
528
  return true
@@ -525,17 +536,21 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
525
536
  : (query = q, query.active = true)
526
537
 
527
538
  build(q)
528
- return write(toBuffer(q))
529
- && !q.describeFirst
530
- && !q.cursorFn
531
- && sent.length < max_pipeline
539
+ const written = write(toBuffer(q))
540
+ if (q.describeFirst || q.cursorFn || mayCopy(q))
541
+ barrier = q
542
+ return written && accepting()
532
543
  } catch (error) {
533
544
  sent.remove(q) || write(Sync)
534
545
  queryError(q, error)
535
- return sent.length < max_pipeline
546
+ return accepting()
536
547
  }
537
548
  }
538
549
 
550
+ function mayCopy(q) {
551
+ return q.options.simple && /\bcopy\b/i.test(q.string)
552
+ }
553
+
539
554
  function toBuffer(q) {
540
555
  if (q.parameters.length >= 65534)
541
556
  throw Errors.generic('MAX_PARAMETERS_EXCEEDED', 'Max number of parameters (65534) exceeded')
@@ -594,7 +609,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
594
609
  ? statements[q.signature]
595
610
  : { string, types, name: q.prepare ? statementId + statementCount++ : '' }
596
611
 
597
- typeof options.debug === 'function' && options.debug(id, string, parameters, types)
612
+ typeof options.debug === 'function' && callback(options.debug, id, string, parameters, types)
598
613
  }
599
614
 
600
615
  function write(x, fn) {
@@ -765,18 +780,21 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
765
780
  backendParameters[k] = v
766
781
  if (options.parameters[k] !== v) {
767
782
  options.parameters[k] = v
768
- onparameter && onparameter(k, v)
783
+ callback(onparameter, k, v)
769
784
  }
770
785
  }
771
786
 
772
- function ReadyForQuery() {
787
+ function ReadyForQuery(x) {
788
+ query === barrier && (barrier = null)
773
789
  if (query) {
774
790
  if (errorResponse) {
775
791
  if (query.initialization)
776
792
  return dispatch('initFailed', errorResponse)
793
+ query.prepared && retryRoutines.has(errorResponse.routine) && delete statements[query.signature]
777
794
  query.retried
778
795
  ? errored(query.retried)
779
- : query.prepared && retryRoutines.has(errorResponse.routine) && query.owner === connection.owner
796
+ : query.prepared && retryRoutines.has(errorResponse.routine) && query.owner === connection.owner && !barrier
797
+ && (x[5] !== 69 || endsAbortedState.test(query.string))
780
798
  ? retry(query, errorResponse)
781
799
  : errored(errorResponse)
782
800
  } else {
@@ -1059,7 +1077,6 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
1059
1077
  }
1060
1078
 
1061
1079
  function retry(q, error) {
1062
- delete statements[q.signature]
1063
1080
  q.retried = error
1064
1081
  execute(q)
1065
1082
  }
@@ -1070,7 +1087,8 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
1070
1087
 
1071
1088
  let index = 9
1072
1089
  while (x[index++] !== 0);
1073
- onnotify(
1090
+ callback(
1091
+ onnotify,
1074
1092
  x.toString('utf8', 9, index - 1),
1075
1093
  x.toString('utf8', index, x.length - 1)
1076
1094
  )
@@ -1116,6 +1134,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
1116
1134
  }
1117
1135
  })
1118
1136
  query.resolve(stream)
1137
+ connection.owner && connection.owner.next()
1119
1138
  }
1120
1139
 
1121
1140
  function CopyOutResponse() {
@@ -1123,6 +1142,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
1123
1142
  read() { socket.resume() }
1124
1143
  })
1125
1144
  query.resolve(stream)
1145
+ connection.owner && connection.owner.next()
1126
1146
  }
1127
1147
 
1128
1148
  /* c8 ignore next 3 */
@@ -1145,6 +1165,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
1145
1165
  }
1146
1166
  })
1147
1167
  query.resolve(stream)
1168
+ connection.owner && connection.owner.next()
1148
1169
  }
1149
1170
 
1150
1171
  function CopyData(x) {
@@ -1157,10 +1178,12 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
1157
1178
  }
1158
1179
 
1159
1180
  function NoticeResponse(x) {
1181
+ if (onnotice === false)
1182
+ return
1183
+
1160
1184
  onnotice
1161
- ? onnotice(parseError(x))
1185
+ ? callback(onnotice, parseError(x))
1162
1186
  : console.log(parseError(x)) // eslint-disable-line
1163
-
1164
1187
  }
1165
1188
 
1166
1189
  /* c8 ignore next 3 */
@@ -1311,3 +1334,19 @@ function timer(fn, seconds) {
1311
1334
  fn.apply(null, args)
1312
1335
  }
1313
1336
  }
1337
+
1338
+ export function defaultBackoff(retries) {
1339
+ return (0.5 + Math.random() / 2) * Math.min(3 ** retries / 100, 20)
1340
+ }
1341
+
1342
+ export function backoffSeconds(backoff, retries) {
1343
+ if (typeof backoff !== 'function')
1344
+ return backoff
1345
+
1346
+ try {
1347
+ return backoff(retries)
1348
+ } catch (e) {
1349
+ callback(() => { throw e })
1350
+ return defaultBackoff(retries)
1351
+ }
1352
+ }
package/src/errors.js CHANGED
@@ -6,6 +6,14 @@ export class PostgresError extends Error {
6
6
  }
7
7
  }
8
8
 
9
+ export function callback(fn, ...args) {
10
+ try {
11
+ fn && fn(...args)
12
+ } catch (e) {
13
+ queueMicrotask(() => { throw e })
14
+ }
15
+ }
16
+
9
17
  export const Errors = {
10
18
  connection,
11
19
  postgres,
package/src/index.js CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  Parameter,
9
9
  Identifier,
10
10
  Builder,
11
+ escapeIdentifier,
11
12
  toPascal,
12
13
  pascal,
13
14
  toCamel,
@@ -19,12 +20,12 @@ import {
19
20
  fromKebab
20
21
  } from './types.js'
21
22
 
22
- import Connection from './connection.js'
23
+ import { Connection, defaultBackoff } from './connection.js'
23
24
  import { cancelRequest } from './transport.js'
24
25
  import clampedTimeout from './timeout.js'
25
26
  import { Query, CLOSE } from './query.js'
26
27
  import Queue from './queue.js'
27
- import { Errors, PostgresError } from './errors.js'
28
+ import { Errors, PostgresError, callback } from './errors.js'
28
29
  import Subscribe from './subscribe.js'
29
30
  import largeObject from './large.js'
30
31
 
@@ -218,7 +219,7 @@ function Postgres(a, b) {
218
219
  })
219
220
  },
220
221
  onnotify(c, x) {
221
- c in listen.channels && listen.channels[c].listeners.forEach(l => l.fn(x))
222
+ c in listen.channels && listen.channels[c].listeners.forEach(l => callback(l.fn, x))
222
223
  }
223
224
  }))
224
225
 
@@ -228,7 +229,7 @@ function Postgres(a, b) {
228
229
  if (exists) {
229
230
  channels[name].listeners.push(listener)
230
231
  const result = await channels[name].result
231
- listener.onlisten && listener.onlisten()
232
+ callback(listener.onlisten)
232
233
  return { state: result.state, unlisten }
233
234
  }
234
235
 
@@ -236,7 +237,7 @@ function Postgres(a, b) {
236
237
  sql.unsafe('"' + name.replace(/"/g, '""') + '"')
237
238
  }`, listeners: [listener] }
238
239
  const result = await channels[name].result
239
- listener.onlisten && listener.onlisten()
240
+ callback(listener.onlisten)
240
241
  return { state: result.state, unlisten }
241
242
 
242
243
  async function unlisten() {
@@ -294,9 +295,9 @@ function Postgres(a, b) {
294
295
  function send(l, q, expected = Lease.Active) {
295
296
  const c = l.connection
296
297
  l.state === expected
297
- ? c.queue === full
298
- ? (q.queue = l.queue, l.queue.push(q))
299
- : c.execute(q) || move(c, full)
298
+ ? c.copying || (c.queue !== full && c.accepting)
299
+ ? c.execute(q) || move(c, full)
300
+ : (move(c, full), q.queue = l.queue, l.queue.push(q))
300
301
  : q.reject(leaseError(l.state))
301
302
  }
302
303
 
@@ -352,14 +353,16 @@ function Postgres(a, b) {
352
353
  }
353
354
 
354
355
  async function finish(statement) {
356
+ let result
355
357
  l.state = Lease.Settling
356
358
  try {
357
- await statement(Sql(q => send(l, q, Lease.Settling)))
359
+ result = await statement(Sql(q => send(l, q, Lease.Settling)))
358
360
  } catch (e) {
359
361
  settle(l, Lease.Discarded)
360
362
  throw e
361
363
  }
362
364
  settle(l, Lease.Released)
365
+ return result
363
366
  }
364
367
 
365
368
  async function scope(fn, name) {
@@ -374,7 +377,7 @@ function Postgres(a, b) {
374
377
  let uncaughtError
375
378
  , result
376
379
 
377
- name && await sql`savepoint ${ sql(name) }`
380
+ name && await sql.unsafe('savepoint ' + escapeIdentifier(name))
378
381
  try {
379
382
  result = await new Promise((resolve, reject) => {
380
383
  const x = fn(sql)
@@ -385,8 +388,8 @@ function Postgres(a, b) {
385
388
  throw uncaughtError
386
389
  } catch (e) {
387
390
  l.state === Lease.Active && await (name
388
- ? sql`rollback to ${ sql(name) }`
389
- : finish(final => final`rollback`)
391
+ ? sql.unsafe('rollback to ' + escapeIdentifier(name))
392
+ : finish(final => final.unsafe('rollback'))
390
393
  )
391
394
  throw e instanceof PostgresError && e.code === '25P02' && uncaughtError || e
392
395
  }
@@ -394,10 +397,18 @@ function Postgres(a, b) {
394
397
  if (l.state !== Lease.Active)
395
398
  throw l.error || leaseError(l.state)
396
399
 
397
- name || await finish(final => prepare
398
- ? final`prepare transaction ${ final.unsafe(prepare) }`
399
- : final`commit`
400
- )
400
+ if (!name) {
401
+ const done = await finish(final => prepare
402
+ ? final.unsafe('prepare transaction ' + prepare)
403
+ : final.unsafe('commit')
404
+ )
405
+ if (done.command === 'ROLLBACK') {
406
+ throw Object.assign(
407
+ Errors.generic('TRANSACTION_ROLLED_BACK', 'The server rolled the transaction back instead of committing it'),
408
+ uncaughtError && { cause: uncaughtError }
409
+ )
410
+ }
411
+ }
401
412
 
402
413
  return result
403
414
 
@@ -426,14 +437,6 @@ function Postgres(a, b) {
426
437
  return c
427
438
  }
428
439
 
429
- function callback(fn, ...args) {
430
- try {
431
- fn && fn(...args)
432
- } catch (e) {
433
- queueMicrotask(() => { throw e })
434
- }
435
- }
436
-
437
440
  function json(x) {
438
441
  return new Parameter(x, 3802)
439
442
  }
@@ -456,11 +459,24 @@ function Postgres(a, b) {
456
459
  if (closed.length)
457
460
  return connect(closed.shift(), query)
458
461
 
459
- busy.length
460
- ? go(busy.shift(), query)
462
+ const c = takeBusy()
463
+ c
464
+ ? go(c, query)
461
465
  : queries.push(query)
462
466
  }
463
467
 
468
+ function takeBusy() {
469
+ while (busy.length) {
470
+ const c = busy.shift()
471
+ if (c.accepting)
472
+ return c
473
+
474
+ move(c, full)
475
+ }
476
+
477
+ return null
478
+ }
479
+
464
480
  function takeOpen() {
465
481
  while (open.length) {
466
482
  const c = open.shift()
@@ -545,6 +561,9 @@ function Postgres(a, b) {
545
561
  if (initial && initial.reserve)
546
562
  return initial.reserve(c)
547
563
 
564
+ if (!initial && !c.accepting)
565
+ return move(c, full)
566
+
548
567
  if (!initial && !queries.length)
549
568
  return move(c, open)
550
569
 
@@ -583,7 +602,7 @@ function Postgres(a, b) {
583
602
  function onclose(c, e) {
584
603
  move(c, closed)
585
604
  c.owner && settle(c.owner, Lease.Closed, e)
586
- options.onclose && options.onclose(c.id)
605
+ callback(options.onclose, c.id)
587
606
  if (ending)
588
607
  return closedWhileEnding(c, e)
589
608
 
@@ -638,7 +657,7 @@ function parseOptions(a, b) {
638
657
  connect_timeout : 30,
639
658
  max_lifetime : max_lifetime,
640
659
  max_pipeline : 100,
641
- backoff : backoff,
660
+ backoff : defaultBackoff,
642
661
  keep_alive : 60,
643
662
  prepare : true,
644
663
  debug : false,
@@ -697,10 +716,6 @@ function tsa(o, url, env) {
697
716
  throw new Error('target_session_attrs ' + x + ' is not supported')
698
717
  }
699
718
 
700
- function backoff(retries) {
701
- return (0.5 + Math.random() / 2) * Math.min(3 ** retries / 100, 20)
702
- }
703
-
704
719
  function max_lifetime() {
705
720
  return 60 * (30 + Math.random() * 30)
706
721
  }