@steadybrew/postgresjs 4.0.0-rc1 → 4.1.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/cf/src/errors.js CHANGED
@@ -13,10 +13,10 @@ export const Errors = {
13
13
  notSupported
14
14
  }
15
15
 
16
- function connection(x, options, socket) {
16
+ function connection(x, options, socket, cause) {
17
17
  const { host, port } = socket || options
18
18
  const error = Object.assign(
19
- new Error(('write ' + x + ' ' + (options.path || (host + ':' + port)))),
19
+ new Error(('write ' + x + ' ' + (options.path || (host + ':' + port))), cause && { cause }),
20
20
  {
21
21
  code: x,
22
22
  errno: x,
package/cf/src/index.js CHANGED
@@ -21,12 +21,16 @@ import {
21
21
  } from './types.js'
22
22
 
23
23
  import Connection from './connection.js'
24
+ import { cancelRequest } from './transport.js'
25
+ import clampedTimeout from './timeout.js'
24
26
  import { Query, CLOSE } from './query.js'
25
27
  import Queue from './queue.js'
26
28
  import { Errors, PostgresError } from './errors.js'
27
29
  import Subscribe from './subscribe.js'
28
30
  import largeObject from './large.js'
29
31
 
32
+ const Lease = { Active: 0, Settling: 1, Released: 2, Discarded: 3, Closed: 4 }
33
+
30
34
  Object.assign(Postgres, {
31
35
  PostgresError,
32
36
  toPascal,
@@ -53,6 +57,7 @@ function Postgres(a, b) {
53
57
  , subscribe = options.no_subscribe || Subscribe(Postgres, { ...options })
54
58
 
55
59
  let ending = false
60
+ let reconnecting = null
56
61
 
57
62
  const queries = Queue()
58
63
  , connecting = Queue()
@@ -206,9 +211,10 @@ function Postgres(a, b) {
206
211
  if (ending)
207
212
  throw Errors.connection('CONNECTION_ENDED', options)
208
213
 
209
- const l = { connection: null, closed: null, onclose: null, reserved: null, queue: Queue() }
210
- open.length
211
- ? own(open.shift(), l)
214
+ const l = { state: null, connection: null, error: null, onclose: null, queue: Queue() }
215
+ const idle = takeOpen()
216
+ idle
217
+ ? own(idle, l)
212
218
  : auto && !closed.length && busy.length
213
219
  ? own(busy.shift(), l)
214
220
  : await new Promise((resolve, reject) => {
@@ -222,50 +228,53 @@ function Postgres(a, b) {
222
228
 
223
229
  function own(c, l) {
224
230
  l.connection = c
225
- c.lease = l
226
- move(c, reserved)
227
- c.reserved = l.reserved = () => l.queue.length
228
- ? c.execute(l.queue.shift())
229
- : move(c, reserved)
230
- c.reserved.release = true
231
- return c
232
- }
233
-
234
- function valid(l) {
235
- return !l.closed && l.connection.lease === l && l.connection.reserved === l.reserved
231
+ l.state = Lease.Active
232
+ c.owner = l
233
+ l.next = () => {
234
+ while (l.queue.length) {
235
+ if (!c.execute(l.queue.shift()))
236
+ return
237
+ }
238
+ move(c, reserved)
239
+ }
240
+ return move(c, reserved)
236
241
  }
237
242
 
238
- function send(l, q) {
243
+ function send(l, q, expected = Lease.Active) {
239
244
  const c = l.connection
240
- valid(l)
245
+ l.state === expected
241
246
  ? c.queue === full
242
247
  ? l.queue.push(q)
243
248
  : c.execute(q) || move(c, full)
244
- : q.reject(gone(l))
249
+ : q.reject(leaseError(l.state))
245
250
  }
246
251
 
247
- function gone(l) {
248
- return Errors.connection(l.closed && l.closed.code === 'CONNECTION_ENDED' ? 'CONNECTION_ENDED' : 'CONNECTION_CLOSED', options)
252
+ function leaseError(state) {
253
+ return Errors.connection(state === Lease.Closed ? 'CONNECTION_CLOSED' : 'CONNECTION_ENDED', options)
249
254
  }
250
255
 
251
- function finish(l) {
252
- const c = l.connection
253
- if (l.closed || c.lease !== l)
254
- return false
256
+ function settle(l, outcome, err) {
257
+ if (l.state === Lease.Released || l.state === Lease.Discarded || l.state === Lease.Closed)
258
+ return
255
259
 
256
- const held = c.reserved === l.reserved
257
- c.lease = null
258
- l.closed = Errors.connection('CONNECTION_ENDED', options)
259
- held && (c.reserved = null)
260
+ const c = l.connection
261
+ l.state = outcome
262
+ c.owner = null
260
263
  while (l.queue.length)
261
- l.queue.shift().reject(gone(l))
262
- return held
264
+ l.queue.shift().reject(leaseError(outcome))
265
+
266
+ if (outcome === Lease.Closed) {
267
+ l.error = err || Errors.connection('CONNECTION_CLOSED', options)
268
+ return l.onclose && l.onclose(l.error)
269
+ }
270
+
271
+ outcome === Lease.Released ? c.release() : c.terminate()
263
272
  }
264
273
 
265
274
  async function reserve() {
266
275
  const l = await lease(false)
267
276
  const sql = Sql(q => send(l, q))
268
- sql.release = () => finish(l) && l.connection.release()
277
+ sql.release = () => settle(l, Lease.Released)
269
278
  return sql
270
279
  }
271
280
 
@@ -274,23 +283,33 @@ function Postgres(a, b) {
274
283
  const l = await lease(true)
275
284
  let savepoints = 0
276
285
  , prepare = null
277
- , settled = false
278
286
 
279
287
  try {
280
288
  const closing = new Promise((_, reject) => l.onclose = reject)
281
289
  return await Promise.race([transaction(), closing])
282
290
  } finally {
283
- finish(l) && (settled ? l.connection.release() : l.connection.terminate())
291
+ settle(l, Lease.Discarded)
284
292
  }
285
293
 
286
294
  async function transaction() {
287
295
  await Sql(q => send(l, q)).unsafe('begin ' + options.replace(/[^a-z ]/ig, ''), []).execute().catch(e => {
288
- e instanceof PostgresError && (settled = true)
296
+ e instanceof PostgresError && settle(l, Lease.Released)
289
297
  throw e
290
298
  })
291
299
  return scope(fn)
292
300
  }
293
301
 
302
+ async function finish(statement) {
303
+ l.state = Lease.Settling
304
+ try {
305
+ await statement(Sql(q => send(l, q, Lease.Settling)))
306
+ } catch (e) {
307
+ settle(l, Lease.Discarded)
308
+ throw e
309
+ }
310
+ settle(l, Lease.Released)
311
+ }
312
+
294
313
  async function scope(fn, name) {
295
314
  const sql = Sql(handler)
296
315
  sql.savepoint = savepoint
@@ -313,23 +332,20 @@ function Postgres(a, b) {
313
332
  if (uncaughtError)
314
333
  throw uncaughtError
315
334
  } catch (e) {
316
- l.closed || await (name
335
+ l.state === Lease.Active && await (name
317
336
  ? sql`rollback to ${ sql(name) }`
318
- : sql`rollback`
337
+ : finish(final => final`rollback`)
319
338
  )
320
- name || (settled = true)
321
339
  throw e instanceof PostgresError && e.code === '25P02' && uncaughtError || e
322
340
  }
323
341
 
324
- if (l.closed)
325
- throw l.closed
342
+ if (l.state !== Lease.Active)
343
+ throw l.error || leaseError(l.state)
326
344
 
327
- if (!name) {
328
- prepare
329
- ? await sql`prepare transaction ${ sql.unsafe(prepare) }`
330
- : await sql`commit`
331
- settled = true
332
- }
345
+ name || await finish(final => prepare
346
+ ? final`prepare transaction ${ final.unsafe(prepare) }`
347
+ : final`commit`
348
+ )
333
349
 
334
350
  return result
335
351
 
@@ -373,8 +389,9 @@ function Postgres(a, b) {
373
389
  if (ending)
374
390
  return query.reject(Errors.connection('CONNECTION_ENDED', options, options))
375
391
 
376
- if (open.length)
377
- return go(open.shift(), query)
392
+ const idle = takeOpen()
393
+ if (idle)
394
+ return go(idle, query)
378
395
 
379
396
  if (closed.length)
380
397
  return connect(closed.shift(), query)
@@ -384,6 +401,18 @@ function Postgres(a, b) {
384
401
  : queries.push(query)
385
402
  }
386
403
 
404
+ function takeOpen() {
405
+ while (open.length) {
406
+ const c = open.shift()
407
+ if (!c.expired())
408
+ return c
409
+
410
+ c.terminate()
411
+ }
412
+
413
+ return null
414
+ }
415
+
387
416
  function go(c, query) {
388
417
  return c.execute(query)
389
418
  ? move(c, busy)
@@ -391,17 +420,18 @@ function Postgres(a, b) {
391
420
  }
392
421
 
393
422
  function cancel(query) {
423
+ if (query.state && query.active)
424
+ return cancelRequest(options, query.state)
425
+
394
426
  return new Promise((resolve, reject) => {
395
- query.state
396
- ? query.active
397
- ? Connection(options).cancel(query.state, resolve, reject)
398
- : query.cancelled = { resolve, reject }
399
- : (
400
- queries.remove(query),
401
- query.cancelled = true,
402
- query.reject(Errors.generic('57014', 'canceling statement due to user request')),
403
- resolve()
404
- )
427
+ if (query.state) {
428
+ query.cancelled = { resolve, reject }
429
+ } else {
430
+ queries.remove(query)
431
+ query.cancelled = true
432
+ query.reject(Errors.generic('57014', 'canceling statement due to user request'))
433
+ resolve()
434
+ }
405
435
  })
406
436
  }
407
437
 
@@ -412,11 +442,8 @@ function Postgres(a, b) {
412
442
  await 1
413
443
  let timer
414
444
  return ending = Promise.resolve().then(() => Promise.race([
415
- new Promise(r => timeout !== null && (timer = setTimeout(() => destroy(r), timeout * 1000))),
416
- Promise.all(connections.map(c => c.end()).concat(
417
- listen.sql ? listen.sql.end({ timeout: 0 }) : [],
418
- subscribe.sql ? subscribe.sql.end({ timeout: 0 }) : []
419
- ))
445
+ new Promise(r => timeout !== null && (timer = clampedTimeout(() => destroy(r), timeout * 1000))),
446
+ endConnections()
420
447
  ])).then(() => {
421
448
  clearTimeout(timer)
422
449
  while (queries.length)
@@ -424,6 +451,15 @@ function Postgres(a, b) {
424
451
  })
425
452
  }
426
453
 
454
+ async function endConnections() {
455
+ await Promise.all(connections.map(c => c.end()).concat(
456
+ listen.sql ? listen.sql.end({ timeout: 0 }) : [],
457
+ subscribe.sql ? subscribe.sql.end({ timeout: 0 }) : []
458
+ ))
459
+ if (reconnecting)
460
+ await reconnecting.end()
461
+ }
462
+
427
463
  async function close() {
428
464
  await Promise.all(connections.map(c => c.end()))
429
465
  }
@@ -447,18 +483,18 @@ function Postgres(a, b) {
447
483
 
448
484
  function onopen(c, initial) {
449
485
  if (initial && initial.reserve)
450
- return initial.reserve(move(c, reserved))
486
+ return initial.reserve(c)
451
487
 
452
- if (queries.length === 0)
488
+ if (!initial && !queries.length)
453
489
  return move(c, open)
454
490
 
455
491
  let max = Math.ceil(queries.length / (connecting.length + 1))
456
- , ready = true
492
+ , ready = !initial || c.execute(initial)
457
493
 
458
494
  while (ready && queries.length && max-- > 0) {
459
495
  const query = queries.shift()
460
496
  if (query.reserve)
461
- return query.reserve(move(c, reserved))
497
+ return query.reserve(c)
462
498
 
463
499
  ready = c.execute(query)
464
500
  }
@@ -480,27 +516,38 @@ function Postgres(a, b) {
480
516
  ? query.reject(Errors.connection('CONNECTION_ENDED', options))
481
517
  : (drained = true, ready = c.execute(query))
482
518
  }
519
+ drained && move(c, ended)
483
520
  return drained
484
521
  }
485
522
 
486
- function abandon(l, e) {
487
- l.connection.lease = null
488
- l.closed = e || Errors.connection('CONNECTION_CLOSED', options)
489
- while (l.queue.length)
490
- l.queue.shift().reject(gone(l))
491
- l.onclose && l.onclose(e)
492
- }
493
-
494
523
  function onclose(c, e) {
495
524
  move(c, closed)
496
- c.reserved = null
497
- c.lease && abandon(c.lease, e)
525
+ c.owner && settle(c.owner, Lease.Closed, e)
498
526
  options.onclose && options.onclose(c.id)
499
- if (ending) {
500
- while (queries.length)
501
- queries.shift().reject(e)
502
- } else if (queries.length && c.queue === closed) {
527
+ if (ending)
528
+ return closedWhileEnding(c, e)
529
+
530
+ if (queries.length && c.queue === closed)
503
531
  connect(c, queries.shift())
532
+ }
533
+
534
+ function closedWhileEnding(c, e) {
535
+ const reconnectFailed = c === reconnecting
536
+ if (connections.some(x => x !== c && x.queue !== closed))
537
+ return
538
+
539
+ while (queries.length) {
540
+ const query = queries.shift()
541
+ if (query.reserve) {
542
+ query.reject(Errors.connection('CONNECTION_ENDED', options))
543
+ } else if (reconnectFailed) {
544
+ query.reject(e)
545
+ } else {
546
+ reconnecting = c
547
+ connect(c, query)
548
+ c.end()
549
+ return
550
+ }
504
551
  }
505
552
  }
506
553
  }
@@ -573,7 +620,7 @@ function parseOptions(a, b) {
573
620
  socket : o.socket,
574
621
  transform : parseTransform(o.transform || { undefined: undefined }),
575
622
  parameters : {},
576
- shared : { retries: 0, typeArrayMap: {} },
623
+ shared : { retries: 0, typeArrayMap: new Map() },
577
624
  ...mergeUserTypes(o.types)
578
625
  }
579
626
 
package/cf/src/query.js CHANGED
@@ -23,10 +23,19 @@ export class Query extends Promise {
23
23
  this.state = null
24
24
  this.statement = null
25
25
 
26
- this.resolve = x => (this.active = false, resolve(x))
27
- this.reject = x => (this.active = false, reject(x))
26
+ this.resolve = x => {
27
+ this.active = false
28
+ this.settled = true
29
+ resolve(x)
30
+ }
31
+ this.reject = x => {
32
+ this.active = false
33
+ this.settled = true
34
+ reject(x)
35
+ }
28
36
 
29
37
  this.active = false
38
+ this.settled = false
30
39
  this.cancelled = null
31
40
  this.executed = false
32
41
  this.signature = ''
@@ -50,7 +59,13 @@ export class Query extends Promise {
50
59
  }
51
60
 
52
61
  cancel() {
53
- return this.canceller && (this.canceller(this), this.canceller = null)
62
+ if (!this.canceller || this.settled)
63
+ return Promise.resolve()
64
+
65
+ const cancelled = this.canceller(this)
66
+ this.canceller = null
67
+ cancelled.catch(() => undefined)
68
+ return cancelled
54
69
  }
55
70
 
56
71
  simple() {
@@ -0,0 +1,5 @@
1
+ const maxDelay = 2 ** 31 - 1
2
+
3
+ export default function clampedTimeout(fn, ms) {
4
+ return setTimeout(fn, Math.min(ms, maxDelay))
5
+ }
@@ -0,0 +1,88 @@
1
+ import { net } from '../polyfills.js'
2
+ import { tls } from '../polyfills.js'
3
+ import b from './bytes.js'
4
+ import clampedTimeout from './timeout.js'
5
+
6
+ export const SSLRequest = b().i32(8).i32(80877103).end(8)
7
+
8
+ export function tlsConfig(options, raw) {
9
+ const { ssl, sslnegotiation } = options
10
+ const config = {
11
+ socket: raw,
12
+ servername: net.isIP(raw.host) ? undefined : raw.host
13
+ }
14
+
15
+ if (sslnegotiation === 'direct')
16
+ config.ALPNProtocols = ['postgresql']
17
+
18
+ if (ssl === 'require' || ssl === 'allow' || ssl === 'prefer')
19
+ config.rejectUnauthorized = false
20
+ else if (typeof ssl === 'object')
21
+ Object.assign(config, ssl)
22
+
23
+ return config
24
+ }
25
+
26
+ export function cancelRequest(options, backend) {
27
+ return Promise.resolve()
28
+ .then(() => options.socket ? options.socket(options) : new net.Socket())
29
+ .then(socket => sendCancel(options, backend, socket))
30
+ }
31
+
32
+ function sendCancel(options, { pid, secret }, s) {
33
+ const { ssl, sslnegotiation, host, port } = options
34
+ const request = b().i32(16).i32(80877102).i32(pid).i32(secret).end(16)
35
+
36
+ return new Promise((resolve, reject) => {
37
+ let timeout = null
38
+ const watch = x => {
39
+ x.once('error', reject)
40
+ x.on('error', () => undefined)
41
+ x.once('close', () => (clearTimeout(timeout), resolve()))
42
+ }
43
+ const upgrade = () => {
44
+ try {
45
+ const raw = s
46
+ const config = tlsConfig(options, raw)
47
+ raw.removeAllListeners()
48
+ s = tls.connect(config)
49
+ watch(s)
50
+ s.once('secureConnect', () => s.write(request))
51
+ } catch (error) {
52
+ clearTimeout(timeout)
53
+ s.destroy()
54
+ reject(error)
55
+ }
56
+ }
57
+ const start = !ssl
58
+ ? () => s.write(request)
59
+ : sslnegotiation === 'direct'
60
+ ? upgrade
61
+ : () => {
62
+ s.once('data', x => x[0] === 83 || ssl !== 'prefer' ? upgrade() : s.write(request))
63
+ s.write(SSLRequest)
64
+ }
65
+
66
+ try {
67
+ watch(s)
68
+ options.connect_timeout && (timeout = clampedTimeout(() => s.destroy(), options.connect_timeout * 1000))
69
+
70
+ if (options.socket)
71
+ return start()
72
+
73
+ s.once('connect', start)
74
+
75
+ if (options.path)
76
+ return s.connect(options.path)
77
+
78
+ s.ssl = ssl
79
+ s.connect(port[0], host[0])
80
+ s.host = host[0]
81
+ s.port = port[0]
82
+ } catch (error) {
83
+ clearTimeout(timeout)
84
+ reject(error)
85
+ }
86
+ })
87
+ }
88
+
package/cf/src/types.js CHANGED
@@ -87,7 +87,7 @@ export function handleValue(x, parameters, types, options) {
87
87
  return '$' + (types.push(
88
88
  x instanceof Parameter
89
89
  ? (parameters.push(x.value), x.array
90
- ? x.array[x.type || inferType(x.value)] || x.type || firstIsString(x.value)
90
+ ? x.array.get(x.type || inferType(x.value)) || x.type || firstIsString(x.value)
91
91
  : x.type
92
92
  )
93
93
  : (parameters.push(x), inferType(x))
@@ -200,7 +200,7 @@ export function addDefaultArrayTypes(options) {
200
200
  }
201
201
 
202
202
  export function addArrayType(options, oid, typarray) {
203
- options.shared.typeArrayMap[oid] = typarray
203
+ options.shared.typeArrayMap.set(oid, typarray)
204
204
  if (!options.parsers[typarray]) {
205
205
  const parser = options.parsers[oid]
206
206
  options.parsers[typarray] = xs => arrayParser(xs, parser, typarray)