@steadybrew/postgresjs 3.4.9 → 4.0.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/index.js CHANGED
@@ -3,6 +3,7 @@ import fs from 'fs'
3
3
 
4
4
  import {
5
5
  mergeUserTypes,
6
+ addDefaultArrayTypes,
6
7
  inferType,
7
8
  Parameter,
8
9
  Identifier,
@@ -62,7 +63,7 @@ function Postgres(a, b) {
62
63
  , full = Queue()
63
64
  , queues = { connecting, reserved, closed, ended, open, busy, full }
64
65
 
65
- const connections = [...Array(options.max)].map(() => Connection(options, queues, { onopen, onend, onclose }))
66
+ const connections = [...Array(options.max)].map(() => Connection(options, queues, { onopen, onend, onclose, ondrain }))
66
67
 
67
68
  const sql = Sql(handler)
68
69
 
@@ -200,58 +201,104 @@ function Postgres(a, b) {
200
201
  return await sql`select pg_notify(${ channel }, ${ '' + payload })`
201
202
  }
202
203
 
203
- async function reserve() {
204
- const queue = Queue()
205
- const c = open.length
206
- ? open.shift()
207
- : await new Promise((resolve, reject) => {
208
- const query = { reserve: resolve, reject }
209
- queries.push(query)
210
- closed.length && connect(closed.shift(), query)
211
- })
204
+ async function lease(auto) {
205
+ if (ending)
206
+ throw Errors.connection('CONNECTION_ENDED', options)
207
+
208
+ const l = { connection: null, closed: null, onclose: null, reserved: null, queue: Queue() }
209
+ open.length
210
+ ? own(open.shift(), l)
211
+ : auto && !closed.length && busy.length
212
+ ? own(busy.shift(), l)
213
+ : await new Promise((resolve, reject) => {
214
+ const query = { reserve: c => resolve(own(c, l)), reject }
215
+ closed.length
216
+ ? connect(closed.shift(), query)
217
+ : queries.push(query)
218
+ })
219
+ return l
220
+ }
212
221
 
222
+ function own(c, l) {
223
+ l.connection = c
224
+ c.lease = l
213
225
  move(c, reserved)
214
- c.reserved = () => queue.length
215
- ? c.execute(queue.shift())
226
+ c.reserved = l.reserved = () => l.queue.length
227
+ ? c.execute(l.queue.shift())
216
228
  : move(c, reserved)
217
229
  c.reserved.release = true
230
+ return c
231
+ }
218
232
 
219
- const sql = Sql(handler)
220
- sql.release = () => {
221
- c.reserved = null
222
- onopen(c)
223
- }
224
-
225
- return sql
233
+ function valid(l) {
234
+ return !l.closed && l.connection.lease === l && l.connection.reserved === l.reserved
235
+ }
226
236
 
227
- function handler(q) {
228
- c.queue === full
229
- ? queue.push(q)
237
+ function send(l, q) {
238
+ const c = l.connection
239
+ valid(l)
240
+ ? c.queue === full
241
+ ? l.queue.push(q)
230
242
  : c.execute(q) || move(c, full)
231
- }
243
+ : q.reject(gone(l))
244
+ }
245
+
246
+ function gone(l) {
247
+ return Errors.connection(l.closed && l.closed.code === 'CONNECTION_ENDED' ? 'CONNECTION_ENDED' : 'CONNECTION_CLOSED', options)
248
+ }
249
+
250
+ function finish(l) {
251
+ const c = l.connection
252
+ if (l.closed || c.lease !== l)
253
+ return false
254
+
255
+ const held = c.reserved === l.reserved
256
+ c.lease = null
257
+ l.closed = Errors.connection('CONNECTION_ENDED', options)
258
+ held && (c.reserved = null)
259
+ while (l.queue.length)
260
+ l.queue.shift().reject(gone(l))
261
+ return held
262
+ }
263
+
264
+ async function reserve() {
265
+ const l = await lease(false)
266
+ const sql = Sql(q => send(l, q))
267
+ sql.release = () => finish(l) && l.connection.release()
268
+ return sql
232
269
  }
233
270
 
234
271
  async function begin(options, fn) {
235
272
  !fn && (fn = options, options = '')
236
- const queries = Queue()
273
+ const l = await lease(true)
237
274
  let savepoints = 0
238
- , connection
239
275
  , prepare = null
276
+ , settled = false
240
277
 
241
278
  try {
242
- await sql.unsafe('begin ' + options.replace(/[^a-z ]/ig, ''), [], { onexecute }).execute()
243
- return await Promise.race([
244
- scope(connection, fn),
245
- new Promise((_, reject) => connection.onclose = reject)
246
- ])
247
- } catch (error) {
248
- throw error
279
+ const closing = new Promise((_, reject) => l.onclose = reject)
280
+ return await Promise.race([transaction(), closing])
281
+ } finally {
282
+ finish(l) && (settled ? l.connection.release() : l.connection.terminate())
283
+ }
284
+
285
+ async function transaction() {
286
+ await Sql(q => send(l, q)).unsafe('begin ' + options.replace(/[^a-z ]/ig, ''), []).execute().catch(e => {
287
+ e instanceof PostgresError && (settled = true)
288
+ throw e
289
+ })
290
+ return scope(fn)
249
291
  }
250
292
 
251
- async function scope(c, fn, name) {
293
+ async function scope(fn, name) {
252
294
  const sql = Sql(handler)
253
295
  sql.savepoint = savepoint
254
- sql.prepare = x => prepare = x.replace(/[^a-z0-9$-_. ]/gi)
296
+ sql.prepare = x => {
297
+ if (typeof x !== 'string' || !x || x.includes('\0'))
298
+ throw Errors.generic('INVALID_TRANSACTION_NAME', 'sql.prepare requires a non-empty string without null bytes')
299
+
300
+ prepare = 'E\'' + x.replace(/\\/g, '\\\\').replace(/'/g, '\'\'') + '\''
301
+ }
255
302
  let uncaughtError
256
303
  , result
257
304
 
@@ -265,17 +312,22 @@ function Postgres(a, b) {
265
312
  if (uncaughtError)
266
313
  throw uncaughtError
267
314
  } catch (e) {
268
- await (name
315
+ l.closed || await (name
269
316
  ? sql`rollback to ${ sql(name) }`
270
317
  : sql`rollback`
271
318
  )
319
+ name || (settled = true)
272
320
  throw e instanceof PostgresError && e.code === '25P02' && uncaughtError || e
273
321
  }
274
322
 
323
+ if (l.closed)
324
+ throw l.closed
325
+
275
326
  if (!name) {
276
327
  prepare
277
- ? await sql`prepare transaction '${ sql.unsafe(prepare) }'`
328
+ ? await sql`prepare transaction ${ sql.unsafe(prepare) }`
278
329
  : await sql`commit`
330
+ settled = true
279
331
  }
280
332
 
281
333
  return result
@@ -285,24 +337,14 @@ function Postgres(a, b) {
285
337
  return savepoint(sql => sql.apply(sql, arguments))
286
338
 
287
339
  arguments.length === 1 && (fn = name, name = null)
288
- return scope(c, fn, 's' + savepoints++ + (name ? '_' + name : ''))
340
+ return scope(fn, 's' + savepoints++ + (name ? '_' + name : ''))
289
341
  }
290
342
 
291
343
  function handler(q) {
292
344
  q.catch(e => uncaughtError || (uncaughtError = e))
293
- c.queue === full
294
- ? queries.push(q)
295
- : c.execute(q) || move(c, full)
345
+ send(l, q)
296
346
  }
297
347
  }
298
-
299
- function onexecute(c) {
300
- connection = c
301
- move(c, reserved)
302
- c.reserved = () => queries.length
303
- ? c.execute(queries.shift())
304
- : move(c, reserved)
305
- }
306
348
  }
307
349
 
308
350
  function move(c, queue) {
@@ -368,13 +410,17 @@ function Postgres(a, b) {
368
410
 
369
411
  await 1
370
412
  let timer
371
- return ending = Promise.race([
372
- new Promise(r => timeout !== null && (timer = setTimeout(destroy, timeout * 1000, r))),
413
+ return ending = Promise.resolve().then(() => Promise.race([
414
+ new Promise(r => timeout !== null && (timer = setTimeout(() => destroy(r), timeout * 1000))),
373
415
  Promise.all(connections.map(c => c.end()).concat(
374
416
  listen.sql ? listen.sql.end({ timeout: 0 }) : [],
375
417
  subscribe.sql ? subscribe.sql.end({ timeout: 0 }) : []
376
418
  ))
377
- ]).then(() => clearTimeout(timer))
419
+ ])).then(() => {
420
+ clearTimeout(timer)
421
+ while (queries.length)
422
+ queries.shift().reject(Errors.connection('CONNECTION_CLOSED', options))
423
+ })
378
424
  }
379
425
 
380
426
  async function close() {
@@ -382,9 +428,9 @@ function Postgres(a, b) {
382
428
  }
383
429
 
384
430
  async function destroy(resolve) {
385
- await Promise.all(connections.map(c => c.terminate()))
386
431
  while (queries.length)
387
432
  queries.shift().reject(Errors.connection('CONNECTION_DESTROYED', options))
433
+ await Promise.all(connections.map(c => c.terminate()))
388
434
  resolve()
389
435
  }
390
436
 
@@ -398,7 +444,10 @@ function Postgres(a, b) {
398
444
  move(c, ended)
399
445
  }
400
446
 
401
- function onopen(c) {
447
+ function onopen(c, initial) {
448
+ if (initial && initial.reserve)
449
+ return initial.reserve(move(c, reserved))
450
+
402
451
  if (queries.length === 0)
403
452
  return move(c, open)
404
453
 
@@ -408,7 +457,7 @@ function Postgres(a, b) {
408
457
  while (ready && queries.length && max-- > 0) {
409
458
  const query = queries.shift()
410
459
  if (query.reserve)
411
- return query.reserve(c)
460
+ return query.reserve(move(c, reserved))
412
461
 
413
462
  ready = c.execute(query)
414
463
  }
@@ -418,12 +467,40 @@ function Postgres(a, b) {
418
467
  : move(c, full)
419
468
  }
420
469
 
470
+ function ondrain(c) {
471
+ if (!ending)
472
+ return false
473
+
474
+ let drained = false
475
+ let ready = true
476
+ while (ready && queries.length) {
477
+ const query = queries.shift()
478
+ query.reserve
479
+ ? query.reject(Errors.connection('CONNECTION_ENDED', options))
480
+ : (drained = true, ready = c.execute(query))
481
+ }
482
+ return drained
483
+ }
484
+
485
+ function abandon(l, e) {
486
+ l.connection.lease = null
487
+ l.closed = e || Errors.connection('CONNECTION_CLOSED', options)
488
+ while (l.queue.length)
489
+ l.queue.shift().reject(gone(l))
490
+ l.onclose && l.onclose(e)
491
+ }
492
+
421
493
  function onclose(c, e) {
422
494
  move(c, closed)
423
495
  c.reserved = null
424
- c.onclose && (c.onclose(e), c.onclose = null)
496
+ c.lease && abandon(c.lease, e)
425
497
  options.onclose && options.onclose(c.id)
426
- queries.length && connect(c, queries.shift())
498
+ if (ending) {
499
+ while (queries.length)
500
+ queries.shift().reject(e)
501
+ } else if (queries.length && c.queue === closed) {
502
+ connect(c, queries.shift())
503
+ }
427
504
  }
428
505
  }
429
506
 
@@ -462,7 +539,7 @@ function parseOptions(a, b) {
462
539
  target_session_attrs: null
463
540
  }
464
541
 
465
- return {
542
+ const options = {
466
543
  host : Array.isArray(host) ? host : host.split(',').map(x => x.split(':')[0]),
467
544
  port : Array.isArray(port) ? port : host.split(',').map(x => parseInt(x.split(':')[1] || port)),
468
545
  path : o.path || host.indexOf('/') > -1 && host + '/.s.PGSQL.' + port,
@@ -498,6 +575,9 @@ function parseOptions(a, b) {
498
575
  shared : { retries: 0, typeArrayMap: {} },
499
576
  ...mergeUserTypes(o.types)
500
577
  }
578
+
579
+ !options.fetch_types && addDefaultArrayTypes(options)
580
+ return options
501
581
  }
502
582
 
503
583
  function tsa(o, url, env) {
package/src/types.js CHANGED
@@ -190,6 +190,25 @@ function firstIsString(x) {
190
190
  return typeof x === 'string' ? 1009 : 0
191
191
  }
192
192
 
193
+ const builtInArrays = [[16, 1000], [17, 1001], [18, 1002], [19, 1003], [20, 1016], [21, 1005], [23, 1007], [25, 1009],
194
+ [26, 1028], [114, 199], [700, 1021], [701, 1022], [1042, 1014], [1043, 1015], [1082, 1182],
195
+ [1083, 1183], [1114, 1115], [1184, 1185], [1186, 1187], [1700, 1231], [2950, 2951], [3802, 3807]]
196
+
197
+ export function addDefaultArrayTypes(options) {
198
+ builtInArrays.forEach(([oid, typarray]) => addArrayType(options, oid, typarray))
199
+ }
200
+
201
+ export function addArrayType(options, oid, typarray) {
202
+ options.shared.typeArrayMap[oid] = typarray
203
+ if (!options.parsers[typarray]) {
204
+ const parser = options.parsers[oid]
205
+ options.parsers[typarray] = xs => arrayParser(xs, parser, typarray)
206
+ options.parsers[typarray].array = true
207
+ }
208
+ if (!options.serializers[typarray])
209
+ options.serializers[typarray] = xs => arraySerializer(xs, options.serializers[oid], options, typarray)
210
+ }
211
+
193
212
  export const mergeUserTypes = function(types) {
194
213
  const user = typeHandlers(types || {})
195
214
  return {
@@ -278,6 +297,10 @@ export const arrayParser = function arrayParser(x, parser, typarray) {
278
297
  return arrayParserLoop(arrayParserState, x, parser, typarray)
279
298
  }
280
299
 
300
+ function parseArrayValue(value, parser) {
301
+ return value === 'NULL' ? null : parser ? parser(value) : value
302
+ }
303
+
281
304
  function arrayParserLoop(s, x, parser, typarray) {
282
305
  const xs = []
283
306
  // Only _box (1020) has the ';' delimiter for arrays, all other types use the ',' delimiter
@@ -302,16 +325,16 @@ function arrayParserLoop(s, x, parser, typarray) {
302
325
  xs.push(arrayParserLoop(s, x, parser, typarray))
303
326
  } else if (s.char === '}') {
304
327
  s.quoted = false
305
- s.last < s.i && xs.push(parser ? parser(x.slice(s.last, s.i)) : x.slice(s.last, s.i))
328
+ s.last < s.i && xs.push(parseArrayValue(x.slice(s.last, s.i), parser))
306
329
  s.last = s.i + 1
307
330
  break
308
331
  } else if (s.char === delimiter && s.p !== '}' && s.p !== '"') {
309
- xs.push(parser ? parser(x.slice(s.last, s.i)) : x.slice(s.last, s.i))
332
+ xs.push(parseArrayValue(x.slice(s.last, s.i), parser))
310
333
  s.last = s.i + 1
311
334
  }
312
335
  s.p = s.char
313
336
  }
314
- s.last < s.i && xs.push(parser ? parser(x.slice(s.last, s.i + 1)) : x.slice(s.last, s.i + 1))
337
+ s.last < s.i && xs.push(parseArrayValue(x.slice(s.last, s.i + 1), parser))
315
338
  return xs
316
339
  }
317
340