@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/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,9 @@ 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 },
232
+ get copyHeld() { return stream !== null && !stream.writableEnded },
227
233
  get acquiring() { return acquisition && !acquisition.owner.cancelled ? 1 : 0 },
228
234
  count: 0,
229
235
  id
@@ -268,8 +274,12 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
268
274
  return !query && sent.length === 0
269
275
  }
270
276
 
277
+ function accepting() {
278
+ return !query || (!barrier && sent.length < max_pipeline)
279
+ }
280
+
271
281
  function backoffMs() {
272
- return (typeof backoff === 'function' ? backoff(options.shared.retries) : backoff) * 1000
282
+ return backoffSeconds(backoff, options.shared.retries) * 1000
273
283
  }
274
284
 
275
285
  function standbyPass() {
@@ -446,7 +456,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
446
456
  queryError(pending, err)
447
457
  pending.cancelled && pending.cancelled.resolve()
448
458
  }
449
- query = results = errorResponse = chunk = nextWriteTimer = null
459
+ query = results = errorResponse = chunk = nextWriteTimer = barrier = null
450
460
  result = new Result()
451
461
  rows = 0
452
462
  nonce = serverSignature = null
@@ -510,8 +520,10 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
510
520
  }
511
521
 
512
522
  function submit(q) {
513
- if (stream)
514
- return queryError(q, Errors.generic('COPY_IN_PROGRESS', 'You cannot execute queries during copy'))
523
+ if (stream) {
524
+ queryError(q, Errors.generic('COPY_IN_PROGRESS', 'You cannot execute queries during copy'))
525
+ return true
526
+ }
515
527
 
516
528
  if (q.cancelled)
517
529
  return true
@@ -525,17 +537,21 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
525
537
  : (query = q, query.active = true)
526
538
 
527
539
  build(q)
528
- return write(toBuffer(q))
529
- && !q.describeFirst
530
- && !q.cursorFn
531
- && sent.length < max_pipeline
540
+ const written = write(toBuffer(q))
541
+ if (q.describeFirst || q.cursorFn || mayCopy(q))
542
+ barrier = q
543
+ return written && accepting()
532
544
  } catch (error) {
533
545
  sent.remove(q) || write(Sync)
534
546
  queryError(q, error)
535
- return sent.length < max_pipeline
547
+ return accepting()
536
548
  }
537
549
  }
538
550
 
551
+ function mayCopy(q) {
552
+ return q.options.simple && /\bcopy\b/i.test(q.string)
553
+ }
554
+
539
555
  function toBuffer(q) {
540
556
  if (q.parameters.length >= 65534)
541
557
  throw Errors.generic('MAX_PARAMETERS_EXCEEDED', 'Max number of parameters (65534) exceeded')
@@ -594,7 +610,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
594
610
  ? statements[q.signature]
595
611
  : { string, types, name: q.prepare ? statementId + statementCount++ : '' }
596
612
 
597
- typeof options.debug === 'function' && options.debug(id, string, parameters, types)
613
+ typeof options.debug === 'function' && callback(options.debug, id, string, parameters, types)
598
614
  }
599
615
 
600
616
  function write(x, fn) {
@@ -765,18 +781,21 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
765
781
  backendParameters[k] = v
766
782
  if (options.parameters[k] !== v) {
767
783
  options.parameters[k] = v
768
- onparameter && onparameter(k, v)
784
+ callback(onparameter, k, v)
769
785
  }
770
786
  }
771
787
 
772
- function ReadyForQuery() {
788
+ function ReadyForQuery(x) {
789
+ query === barrier && (barrier = null)
773
790
  if (query) {
774
791
  if (errorResponse) {
775
792
  if (query.initialization)
776
793
  return dispatch('initFailed', errorResponse)
794
+ query.prepared && retryRoutines.has(errorResponse.routine) && delete statements[query.signature]
777
795
  query.retried
778
796
  ? errored(query.retried)
779
- : query.prepared && retryRoutines.has(errorResponse.routine) && query.owner === connection.owner
797
+ : query.prepared && retryRoutines.has(errorResponse.routine) && query.owner === connection.owner && !barrier
798
+ && (x[5] !== 69 || endsAbortedState.test(query.string))
780
799
  ? retry(query, errorResponse)
781
800
  : errored(errorResponse)
782
801
  } else {
@@ -1059,7 +1078,6 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
1059
1078
  }
1060
1079
 
1061
1080
  function retry(q, error) {
1062
- delete statements[q.signature]
1063
1081
  q.retried = error
1064
1082
  execute(q)
1065
1083
  }
@@ -1070,7 +1088,8 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
1070
1088
 
1071
1089
  let index = 9
1072
1090
  while (x[index++] !== 0);
1073
- onnotify(
1091
+ callback(
1092
+ onnotify,
1074
1093
  x.toString('utf8', 9, index - 1),
1075
1094
  x.toString('utf8', index, x.length - 1)
1076
1095
  )
@@ -1116,6 +1135,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
1116
1135
  }
1117
1136
  })
1118
1137
  query.resolve(stream)
1138
+ connection.owner && connection.owner.next()
1119
1139
  }
1120
1140
 
1121
1141
  function CopyOutResponse() {
@@ -1123,6 +1143,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
1123
1143
  read() { socket.resume() }
1124
1144
  })
1125
1145
  query.resolve(stream)
1146
+ connection.owner && connection.owner.next()
1126
1147
  }
1127
1148
 
1128
1149
  /* c8 ignore next 3 */
@@ -1145,6 +1166,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
1145
1166
  }
1146
1167
  })
1147
1168
  query.resolve(stream)
1169
+ connection.owner && connection.owner.next()
1148
1170
  }
1149
1171
 
1150
1172
  function CopyData(x) {
@@ -1157,10 +1179,12 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
1157
1179
  }
1158
1180
 
1159
1181
  function NoticeResponse(x) {
1182
+ if (onnotice === false)
1183
+ return
1184
+
1160
1185
  onnotice
1161
- ? onnotice(parseError(x))
1186
+ ? callback(onnotice, parseError(x))
1162
1187
  : console.log(parseError(x)) // eslint-disable-line
1163
-
1164
1188
  }
1165
1189
 
1166
1190
  /* c8 ignore next 3 */
@@ -1311,3 +1335,19 @@ function timer(fn, seconds) {
1311
1335
  fn.apply(null, args)
1312
1336
  }
1313
1337
  }
1338
+
1339
+ export function defaultBackoff(retries) {
1340
+ return (0.5 + Math.random() / 2) * Math.min(3 ** retries / 100, 20)
1341
+ }
1342
+
1343
+ export function backoffSeconds(backoff, retries) {
1344
+ if (typeof backoff !== 'function')
1345
+ return backoff
1346
+
1347
+ try {
1348
+ return backoff(retries)
1349
+ } catch (e) {
1350
+ callback(() => { throw e })
1351
+ return defaultBackoff(retries)
1352
+ }
1353
+ }
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() {
@@ -272,7 +273,9 @@ function Postgres(a, b) {
272
273
  const query = { reserve: c => resolve(own(c, l)), reject }
273
274
  closed.length
274
275
  ? connect(closed.shift(), query)
275
- : queries.push(query)
276
+ : allHeldByCopy()
277
+ ? reject(copyInProgress())
278
+ : queries.push(query)
276
279
  })
277
280
  return l
278
281
  }
@@ -294,9 +297,9 @@ function Postgres(a, b) {
294
297
  function send(l, q, expected = Lease.Active) {
295
298
  const c = l.connection
296
299
  l.state === expected
297
- ? c.queue === full
298
- ? (q.queue = l.queue, l.queue.push(q))
299
- : c.execute(q) || move(c, full)
300
+ ? c.copying || (c.queue !== full && c.accepting)
301
+ ? c.execute(q) || move(c, full)
302
+ : (move(c, full), q.queue = l.queue, l.queue.push(q))
300
303
  : q.reject(leaseError(l.state))
301
304
  }
302
305
 
@@ -352,14 +355,16 @@ function Postgres(a, b) {
352
355
  }
353
356
 
354
357
  async function finish(statement) {
358
+ let result
355
359
  l.state = Lease.Settling
356
360
  try {
357
- await statement(Sql(q => send(l, q, Lease.Settling)))
361
+ result = await statement(Sql(q => send(l, q, Lease.Settling)))
358
362
  } catch (e) {
359
363
  settle(l, Lease.Discarded)
360
364
  throw e
361
365
  }
362
366
  settle(l, Lease.Released)
367
+ return result
363
368
  }
364
369
 
365
370
  async function scope(fn, name) {
@@ -374,7 +379,7 @@ function Postgres(a, b) {
374
379
  let uncaughtError
375
380
  , result
376
381
 
377
- name && await sql`savepoint ${ sql(name) }`
382
+ name && await sql.unsafe('savepoint ' + escapeIdentifier(name))
378
383
  try {
379
384
  result = await new Promise((resolve, reject) => {
380
385
  const x = fn(sql)
@@ -385,8 +390,8 @@ function Postgres(a, b) {
385
390
  throw uncaughtError
386
391
  } catch (e) {
387
392
  l.state === Lease.Active && await (name
388
- ? sql`rollback to ${ sql(name) }`
389
- : finish(final => final`rollback`)
393
+ ? sql.unsafe('rollback to ' + escapeIdentifier(name))
394
+ : finish(final => final.unsafe('rollback'))
390
395
  )
391
396
  throw e instanceof PostgresError && e.code === '25P02' && uncaughtError || e
392
397
  }
@@ -394,10 +399,18 @@ function Postgres(a, b) {
394
399
  if (l.state !== Lease.Active)
395
400
  throw l.error || leaseError(l.state)
396
401
 
397
- name || await finish(final => prepare
398
- ? final`prepare transaction ${ final.unsafe(prepare) }`
399
- : final`commit`
400
- )
402
+ if (!name) {
403
+ const done = await finish(final => prepare
404
+ ? final.unsafe('prepare transaction ' + prepare)
405
+ : final.unsafe('commit')
406
+ )
407
+ if (done.command === 'ROLLBACK') {
408
+ throw Object.assign(
409
+ Errors.generic('TRANSACTION_ROLLED_BACK', 'The server rolled the transaction back instead of committing it'),
410
+ uncaughtError && { cause: uncaughtError }
411
+ )
412
+ }
413
+ }
401
414
 
402
415
  return result
403
416
 
@@ -426,14 +439,6 @@ function Postgres(a, b) {
426
439
  return c
427
440
  }
428
441
 
429
- function callback(fn, ...args) {
430
- try {
431
- fn && fn(...args)
432
- } catch (e) {
433
- queueMicrotask(() => { throw e })
434
- }
435
- }
436
-
437
442
  function json(x) {
438
443
  return new Parameter(x, 3802)
439
444
  }
@@ -456,9 +461,32 @@ function Postgres(a, b) {
456
461
  if (closed.length)
457
462
  return connect(closed.shift(), query)
458
463
 
459
- busy.length
460
- ? go(busy.shift(), query)
461
- : queries.push(query)
464
+ const c = takeBusy()
465
+ c
466
+ ? go(c, query)
467
+ : allHeldByCopy()
468
+ ? query.reject(copyInProgress())
469
+ : queries.push(query)
470
+ }
471
+
472
+ function allHeldByCopy() {
473
+ return connections.every(c => c.copyHeld)
474
+ }
475
+
476
+ function copyInProgress() {
477
+ return Errors.generic('COPY_IN_PROGRESS', 'You cannot execute queries during copy')
478
+ }
479
+
480
+ function takeBusy() {
481
+ while (busy.length) {
482
+ const c = busy.shift()
483
+ if (c.accepting)
484
+ return c
485
+
486
+ move(c, full)
487
+ }
488
+
489
+ return null
462
490
  }
463
491
 
464
492
  function takeOpen() {
@@ -545,6 +573,9 @@ function Postgres(a, b) {
545
573
  if (initial && initial.reserve)
546
574
  return initial.reserve(c)
547
575
 
576
+ if (!initial && !c.accepting)
577
+ return move(c, full)
578
+
548
579
  if (!initial && !queries.length)
549
580
  return move(c, open)
550
581
 
@@ -583,7 +614,7 @@ function Postgres(a, b) {
583
614
  function onclose(c, e) {
584
615
  move(c, closed)
585
616
  c.owner && settle(c.owner, Lease.Closed, e)
586
- options.onclose && options.onclose(c.id)
617
+ callback(options.onclose, c.id)
587
618
  if (ending)
588
619
  return closedWhileEnding(c, e)
589
620
 
@@ -638,7 +669,7 @@ function parseOptions(a, b) {
638
669
  connect_timeout : 30,
639
670
  max_lifetime : max_lifetime,
640
671
  max_pipeline : 100,
641
- backoff : backoff,
672
+ backoff : defaultBackoff,
642
673
  keep_alive : 60,
643
674
  prepare : true,
644
675
  debug : false,
@@ -697,10 +728,6 @@ function tsa(o, url, env) {
697
728
  throw new Error('target_session_attrs ' + x + ' is not supported')
698
729
  }
699
730
 
700
- function backoff(retries) {
701
- return (0.5 + Math.random() / 2) * Math.min(3 ** retries / 100, 20)
702
- }
703
-
704
731
  function max_lifetime() {
705
732
  return 60 * (30 + Math.random() * 30)
706
733
  }