@steadybrew/postgresjs 4.2.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')]
@@ -222,6 +225,11 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
222
225
  },
223
226
  expired: () => idleTimer.expired() || lifeTimer.expired(),
224
227
  owner: null,
228
+ get active() { return query ? 1 : 0 },
229
+ get pipelined() { return sent.length },
230
+ get accepting() { return accepting() },
231
+ get copying() { return stream !== null },
232
+ get acquiring() { return acquisition && !acquisition.owner.cancelled ? 1 : 0 },
225
233
  count: 0,
226
234
  id
227
235
  }
@@ -265,8 +273,12 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
265
273
  return !query && sent.length === 0
266
274
  }
267
275
 
276
+ function accepting() {
277
+ return !query || (!barrier && sent.length < max_pipeline)
278
+ }
279
+
268
280
  function backoffMs() {
269
- return (typeof backoff === 'function' ? backoff(options.shared.retries) : backoff) * 1000
281
+ return backoffSeconds(backoff, options.shared.retries) * 1000
270
282
  }
271
283
 
272
284
  function standbyPass() {
@@ -443,7 +455,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
443
455
  queryError(pending, err)
444
456
  pending.cancelled && pending.cancelled.resolve()
445
457
  }
446
- query = results = errorResponse = chunk = nextWriteTimer = null
458
+ query = results = errorResponse = chunk = nextWriteTimer = barrier = null
447
459
  result = new Result()
448
460
  rows = 0
449
461
  nonce = serverSignature = null
@@ -507,8 +519,10 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
507
519
  }
508
520
 
509
521
  function submit(q) {
510
- if (stream)
511
- 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
+ }
512
526
 
513
527
  if (q.cancelled)
514
528
  return true
@@ -522,17 +536,21 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
522
536
  : (query = q, query.active = true)
523
537
 
524
538
  build(q)
525
- return write(toBuffer(q))
526
- && !q.describeFirst
527
- && !q.cursorFn
528
- && 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()
529
543
  } catch (error) {
530
- sent.length === 0 && write(Sync)
531
- errored(error)
532
- return true
544
+ sent.remove(q) || write(Sync)
545
+ queryError(q, error)
546
+ return accepting()
533
547
  }
534
548
  }
535
549
 
550
+ function mayCopy(q) {
551
+ return q.options.simple && /\bcopy\b/i.test(q.string)
552
+ }
553
+
536
554
  function toBuffer(q) {
537
555
  if (q.parameters.length >= 65534)
538
556
  throw Errors.generic('MAX_PARAMETERS_EXCEEDED', 'Max number of parameters (65534) exceeded')
@@ -591,7 +609,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
591
609
  ? statements[q.signature]
592
610
  : { string, types, name: q.prepare ? statementId + statementCount++ : '' }
593
611
 
594
- typeof options.debug === 'function' && options.debug(id, string, parameters, types)
612
+ typeof options.debug === 'function' && callback(options.debug, id, string, parameters, types)
595
613
  }
596
614
 
597
615
  function write(x, fn) {
@@ -762,18 +780,21 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
762
780
  backendParameters[k] = v
763
781
  if (options.parameters[k] !== v) {
764
782
  options.parameters[k] = v
765
- onparameter && onparameter(k, v)
783
+ callback(onparameter, k, v)
766
784
  }
767
785
  }
768
786
 
769
- function ReadyForQuery() {
787
+ function ReadyForQuery(x) {
788
+ query === barrier && (barrier = null)
770
789
  if (query) {
771
790
  if (errorResponse) {
772
791
  if (query.initialization)
773
792
  return dispatch('initFailed', errorResponse)
793
+ query.prepared && retryRoutines.has(errorResponse.routine) && delete statements[query.signature]
774
794
  query.retried
775
795
  ? errored(query.retried)
776
- : 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))
777
798
  ? retry(query, errorResponse)
778
799
  : errored(errorResponse)
779
800
  } else {
@@ -1056,7 +1077,6 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
1056
1077
  }
1057
1078
 
1058
1079
  function retry(q, error) {
1059
- delete statements[q.signature]
1060
1080
  q.retried = error
1061
1081
  execute(q)
1062
1082
  }
@@ -1067,7 +1087,8 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
1067
1087
 
1068
1088
  let index = 9
1069
1089
  while (x[index++] !== 0);
1070
- onnotify(
1090
+ callback(
1091
+ onnotify,
1071
1092
  x.toString('utf8', 9, index - 1),
1072
1093
  x.toString('utf8', index, x.length - 1)
1073
1094
  )
@@ -1113,6 +1134,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
1113
1134
  }
1114
1135
  })
1115
1136
  query.resolve(stream)
1137
+ connection.owner && connection.owner.next()
1116
1138
  }
1117
1139
 
1118
1140
  function CopyOutResponse() {
@@ -1120,6 +1142,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
1120
1142
  read() { socket.resume() }
1121
1143
  })
1122
1144
  query.resolve(stream)
1145
+ connection.owner && connection.owner.next()
1123
1146
  }
1124
1147
 
1125
1148
  /* c8 ignore next 3 */
@@ -1142,6 +1165,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
1142
1165
  }
1143
1166
  })
1144
1167
  query.resolve(stream)
1168
+ connection.owner && connection.owner.next()
1145
1169
  }
1146
1170
 
1147
1171
  function CopyData(x) {
@@ -1154,10 +1178,12 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
1154
1178
  }
1155
1179
 
1156
1180
  function NoticeResponse(x) {
1181
+ if (onnotice === false)
1182
+ return
1183
+
1157
1184
  onnotice
1158
- ? onnotice(parseError(x))
1185
+ ? callback(onnotice, parseError(x))
1159
1186
  : console.log(parseError(x)) // eslint-disable-line
1160
-
1161
1187
  }
1162
1188
 
1163
1189
  /* c8 ignore next 3 */
@@ -1308,3 +1334,19 @@ function timer(fn, seconds) {
1308
1334
  fn.apply(null, args)
1309
1335
  }
1310
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
 
@@ -81,6 +82,7 @@ function Postgres(a, b) {
81
82
  PostgresError,
82
83
  options,
83
84
  reserve,
85
+ stats,
84
86
  listen,
85
87
  begin,
86
88
  close,
@@ -150,6 +152,56 @@ function Postgres(a, b) {
150
152
  }
151
153
  }
152
154
 
155
+ function stats() {
156
+ let idleCount = 0
157
+ , busyCount = 0
158
+ , reservedCount = 0
159
+ , connectingCount = 0
160
+ , closingCount = 0
161
+ , executingCount = 0
162
+ , waitingCount = queries.length
163
+
164
+ for (const c of connections) {
165
+ executingCount += c.active
166
+ waitingCount += c.pipelined + c.acquiring + (c.owner ? c.owner.queue.length : 0)
167
+ c.queue === open
168
+ ? idleCount++
169
+ : c.queue === connecting
170
+ ? connectingCount++
171
+ : c.queue === ended
172
+ ? closingCount++
173
+ : c.queue === reserved
174
+ ? reservedCount++
175
+ : (c.queue === busy || c.queue === full) && (c.owner ? reservedCount++ : busyCount++)
176
+ }
177
+
178
+ const totalCount = idleCount + busyCount + reservedCount + connectingCount
179
+
180
+ return {
181
+ max: options.max,
182
+ totalCount,
183
+ idleCount,
184
+ waitingCount,
185
+ activeCount: busyCount + reservedCount,
186
+ availableCount: idleCount + options.max - totalCount - closingCount,
187
+ busyCount,
188
+ reservedCount,
189
+ connectingCount,
190
+ closingCount,
191
+ executingCount,
192
+ listenCount: pooled(listen.sql),
193
+ subscribeCount: pooled(subscribe.sql)
194
+ }
195
+ }
196
+
197
+ function pooled(sql) {
198
+ if (!sql)
199
+ return 0
200
+
201
+ const s = sql.stats()
202
+ return s.totalCount + s.closingCount
203
+ }
204
+
153
205
  async function listen(name, fn, onlisten) {
154
206
  const listener = { fn, onlisten }
155
207
 
@@ -167,7 +219,7 @@ function Postgres(a, b) {
167
219
  })
168
220
  },
169
221
  onnotify(c, x) {
170
- 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))
171
223
  }
172
224
  }))
173
225
 
@@ -177,7 +229,7 @@ function Postgres(a, b) {
177
229
  if (exists) {
178
230
  channels[name].listeners.push(listener)
179
231
  const result = await channels[name].result
180
- listener.onlisten && listener.onlisten()
232
+ callback(listener.onlisten)
181
233
  return { state: result.state, unlisten }
182
234
  }
183
235
 
@@ -185,7 +237,7 @@ function Postgres(a, b) {
185
237
  sql.unsafe('"' + name.replace(/"/g, '""') + '"')
186
238
  }`, listeners: [listener] }
187
239
  const result = await channels[name].result
188
- listener.onlisten && listener.onlisten()
240
+ callback(listener.onlisten)
189
241
  return { state: result.state, unlisten }
190
242
 
191
243
  async function unlisten() {
@@ -243,9 +295,9 @@ function Postgres(a, b) {
243
295
  function send(l, q, expected = Lease.Active) {
244
296
  const c = l.connection
245
297
  l.state === expected
246
- ? c.queue === full
247
- ? l.queue.push(q)
248
- : 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))
249
301
  : q.reject(leaseError(l.state))
250
302
  }
251
303
 
@@ -275,6 +327,7 @@ function Postgres(a, b) {
275
327
  const l = await lease(false)
276
328
  const sql = Sql(q => send(l, q))
277
329
  sql.release = () => settle(l, Lease.Released)
330
+ sql.stats = stats
278
331
  return sql
279
332
  }
280
333
 
@@ -300,14 +353,16 @@ function Postgres(a, b) {
300
353
  }
301
354
 
302
355
  async function finish(statement) {
356
+ let result
303
357
  l.state = Lease.Settling
304
358
  try {
305
- await statement(Sql(q => send(l, q, Lease.Settling)))
359
+ result = await statement(Sql(q => send(l, q, Lease.Settling)))
306
360
  } catch (e) {
307
361
  settle(l, Lease.Discarded)
308
362
  throw e
309
363
  }
310
364
  settle(l, Lease.Released)
365
+ return result
311
366
  }
312
367
 
313
368
  async function scope(fn, name) {
@@ -322,7 +377,7 @@ function Postgres(a, b) {
322
377
  let uncaughtError
323
378
  , result
324
379
 
325
- name && await sql`savepoint ${ sql(name) }`
380
+ name && await sql.unsafe('savepoint ' + escapeIdentifier(name))
326
381
  try {
327
382
  result = await new Promise((resolve, reject) => {
328
383
  const x = fn(sql)
@@ -333,8 +388,8 @@ function Postgres(a, b) {
333
388
  throw uncaughtError
334
389
  } catch (e) {
335
390
  l.state === Lease.Active && await (name
336
- ? sql`rollback to ${ sql(name) }`
337
- : finish(final => final`rollback`)
391
+ ? sql.unsafe('rollback to ' + escapeIdentifier(name))
392
+ : finish(final => final.unsafe('rollback'))
338
393
  )
339
394
  throw e instanceof PostgresError && e.code === '25P02' && uncaughtError || e
340
395
  }
@@ -342,10 +397,18 @@ function Postgres(a, b) {
342
397
  if (l.state !== Lease.Active)
343
398
  throw l.error || leaseError(l.state)
344
399
 
345
- name || await finish(final => prepare
346
- ? final`prepare transaction ${ final.unsafe(prepare) }`
347
- : final`commit`
348
- )
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
+ }
349
412
 
350
413
  return result
351
414
 
@@ -374,14 +437,6 @@ function Postgres(a, b) {
374
437
  return c
375
438
  }
376
439
 
377
- function callback(fn, ...args) {
378
- try {
379
- fn && fn(...args)
380
- } catch (e) {
381
- queueMicrotask(() => { throw e })
382
- }
383
- }
384
-
385
440
  function json(x) {
386
441
  return new Parameter(x, 3802)
387
442
  }
@@ -404,11 +459,24 @@ function Postgres(a, b) {
404
459
  if (closed.length)
405
460
  return connect(closed.shift(), query)
406
461
 
407
- busy.length
408
- ? go(busy.shift(), query)
462
+ const c = takeBusy()
463
+ c
464
+ ? go(c, query)
409
465
  : queries.push(query)
410
466
  }
411
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
+
412
480
  function takeOpen() {
413
481
  while (open.length) {
414
482
  const c = open.shift()
@@ -435,7 +503,7 @@ function Postgres(a, b) {
435
503
  if (query.state) {
436
504
  query.cancelled = { resolve, reject }
437
505
  } else {
438
- queries.remove(query)
506
+ (query.queue || queries).remove(query)
439
507
  query.cancelled = true
440
508
  query.reject(Errors.generic('57014', 'canceling statement due to user request'))
441
509
  resolve()
@@ -493,6 +561,9 @@ function Postgres(a, b) {
493
561
  if (initial && initial.reserve)
494
562
  return initial.reserve(c)
495
563
 
564
+ if (!initial && !c.accepting)
565
+ return move(c, full)
566
+
496
567
  if (!initial && !queries.length)
497
568
  return move(c, open)
498
569
 
@@ -531,7 +602,7 @@ function Postgres(a, b) {
531
602
  function onclose(c, e) {
532
603
  move(c, closed)
533
604
  c.owner && settle(c.owner, Lease.Closed, e)
534
- options.onclose && options.onclose(c.id)
605
+ callback(options.onclose, c.id)
535
606
  if (ending)
536
607
  return closedWhileEnding(c, e)
537
608
 
@@ -586,7 +657,7 @@ function parseOptions(a, b) {
586
657
  connect_timeout : 30,
587
658
  max_lifetime : max_lifetime,
588
659
  max_pipeline : 100,
589
- backoff : backoff,
660
+ backoff : defaultBackoff,
590
661
  keep_alive : 60,
591
662
  prepare : true,
592
663
  debug : false,
@@ -645,10 +716,6 @@ function tsa(o, url, env) {
645
716
  throw new Error('target_session_attrs ' + x + ' is not supported')
646
717
  }
647
718
 
648
- function backoff(retries) {
649
- return (0.5 + Math.random() / 2) * Math.min(3 ** retries / 100, 20)
650
- }
651
-
652
719
  function max_lifetime() {
653
720
  return 60 * (30 + Math.random() * 30)
654
721
  }