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