@steadybrew/postgresjs 4.4.0 → 4.5.0-rc1

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.
@@ -4,6 +4,7 @@ import { defaultBackoff, backoffSeconds } from './connection.js'
4
4
 
5
5
  const noop = () => { /* noop */ }
6
6
  , commands = { 68: 'delete', 73: 'insert', 85: 'update' }
7
+ , statusInterval = 1000
7
8
 
8
9
  export default function Subscribe(postgres, options) {
9
10
  const subscribers = new Map()
@@ -152,6 +153,10 @@ export default function Subscribe(postgres, options) {
152
153
  stream.on('error', reportConnection)
153
154
  stream.on('close', sql.close)
154
155
 
156
+ // A walsender can miss the wakeup for a commit and sleep until its timeout; a status update wakes it.
157
+ const status = setInterval(() => stream.writable && pong(), statusInterval)
158
+ stream.on('close', () => clearInterval(status))
159
+
155
160
  return { stream, state: xs.state }
156
161
 
157
162
  function data(x) {
package/cf/src/types.js CHANGED
@@ -249,6 +249,11 @@ export const inferType = function inferType(x) {
249
249
  )
250
250
  }
251
251
 
252
+ export function clientSerialize(x) {
253
+ const proto = typeof x === 'object' && Object.getPrototypeOf(x)
254
+ return proto === Object.prototype || proto === null ? JSON.stringify(x) : '' + x
255
+ }
256
+
252
257
  const escapeBackslash = /\\/g
253
258
  const escapeQuote = /"/g
254
259
 
@@ -4,7 +4,7 @@ const crypto = require('crypto')
4
4
  const Stream = require('stream')
5
5
  const { performance } = require('perf_hooks')
6
6
 
7
- const { stringify, handleValue, addArrayType } = require('./types.js')
7
+ const { stringify, handleValue, addArrayType, clientSerialize } = require('./types.js')
8
8
  const { Errors, callback } = require('./errors.js')
9
9
  const Result = require('./result.js')
10
10
  const Queue = require('./queue.js')
@@ -214,7 +214,10 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
214
214
  const connection = {
215
215
  queue: queues.closed,
216
216
  idleTimer,
217
- connect: request => dispatch('acquire', request),
217
+ // Sentry's hook wraps connect and reads the connection from this, so it must stay a method
218
+ connect(request) {
219
+ dispatch('acquire', request)
220
+ },
218
221
  terminate: () => dispatch('terminate'),
219
222
  execute,
220
223
  release: () => dispatch('release'),
@@ -229,6 +232,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
229
232
  get pipelined() { return sent.length },
230
233
  get accepting() { return accepting() },
231
234
  get copying() { return stream !== null },
235
+ get copyHeld() { return stream !== null && !stream.writableEnded },
232
236
  get acquiring() { return acquisition && !acquisition.owner.cancelled ? 1 : 0 },
233
237
  count: 0,
234
238
  id
@@ -537,7 +541,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
537
541
 
538
542
  build(q)
539
543
  const written = write(toBuffer(q))
540
- if (q.describeFirst || q.cursorFn || mayCopy(q))
544
+ if (q.describeFirst || q.cursorFn || mayCopy(q) || (q.prepare && !q.prepared && !q.options.simple && q.parameters.length))
541
545
  barrier = q
542
546
  return written && accepting()
543
547
  } catch (error) {
@@ -575,7 +579,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
575
579
 
576
580
  function prepared(q) {
577
581
  return Buffer.concat([
578
- Bind(q.parameters, q.statement.types, q.statement.name, q.cursorName),
582
+ Bind(q),
579
583
  q.cursorFn
580
584
  ? Execute('', q.cursorRows)
581
585
  : ExecuteUnnamed
@@ -603,8 +607,11 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
603
607
  q.signature = q.prepare && types + string
604
608
  q.onlyDescribe && (delete statements[q.signature])
605
609
  q.parameters = q.parameters || parameters
610
+ q.inferred = options.parameter_types === 'server' ? null : types.slice()
606
611
  q.prepared = q.prepare && q.signature in statements
607
- q.describeFirst = q.onlyDescribe || (parameters.length && !q.prepared)
612
+ q.describeFirst = q.onlyDescribe || (!q.prepared && types.some((x, i) =>
613
+ !x && (options.parameter_types !== 'client' || Array.isArray(q.parameters[i]))
614
+ ))
608
615
  q.statement = q.prepared
609
616
  ? statements[q.signature]
610
617
  : { string, types, name: q.prepare ? statementId + statementCount++ : '' }
@@ -688,7 +695,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
688
695
  err = new Error(err)
689
696
 
690
697
  'query' in err || 'parameters' in err || Object.defineProperties(err, {
691
- stack: { value: err.stack + query.origin.replace(/.*\n/, '\n'), enumerable: options.debug },
698
+ stack: { value: err.stack + query.origin.replace(/.*\n/, '\n'), enumerable: options.debug, writable: true, configurable: true },
692
699
  query: { value: query.string, enumerable: options.debug },
693
700
  parameters: { value: query.parameters, enumerable: options.debug },
694
701
  args: { value: query.args, enumerable: options.debug },
@@ -1212,20 +1219,21 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
1212
1219
  }
1213
1220
 
1214
1221
  /* Messages */
1215
- function Bind(parameters, types, statement = '', portal = '') {
1222
+ function Bind(q) {
1223
+ const differences = []
1216
1224
  let prev
1217
1225
  , type
1218
1226
 
1219
- b().B().str(portal + b.N).str(statement + b.N).i16(0).i16(parameters.length)
1227
+ b().B().str((q.cursorName || '') + b.N).str(q.statement.name + b.N).i16(0).i16(q.parameters.length)
1220
1228
 
1221
- parameters.forEach((x, i) => {
1229
+ q.parameters.forEach((x, i) => {
1222
1230
  if (x === null)
1223
1231
  return b.i32(0xFFFFFFFF)
1224
1232
 
1225
- type = types[i]
1226
- x = type in options.serializers
1227
- ? options.serializers[type](x)
1228
- : '' + x
1233
+ type = q.statement.types[i]
1234
+ x = !q.inferred || q.inferred[i] || Array.isArray(x)
1235
+ ? serialize(x, type)
1236
+ : options.parameter_types === 'client' ? clientSerialize(x) : compare(x, type, i, differences)
1229
1237
 
1230
1238
  prev = b.i
1231
1239
  b.inc(4).str(x).i32(b.i - prev - 4, prev)
@@ -1233,7 +1241,27 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
1233
1241
 
1234
1242
  b.i16(0)
1235
1243
 
1236
- return b.end()
1244
+ const message = b.end()
1245
+ differences.length && callback(options.ontypemismatch || console.warn, { query: q.string, parameters: differences }) // eslint-disable-line
1246
+ return message
1247
+ }
1248
+
1249
+ function serialize(x, type) {
1250
+ return type in options.serializers
1251
+ ? options.serializers[type](x)
1252
+ : '' + x
1253
+ }
1254
+
1255
+ function compare(x, type, i, differences) {
1256
+ const serverText = serialize(x, type)
1257
+ let clientText
1258
+ try {
1259
+ clientText = clientSerialize(x)
1260
+ } catch (error) {
1261
+ clientText = error
1262
+ }
1263
+ serverText === clientText || differences.push({ position: i + 1, oid: type, typeof: typeof x, ...options.debug && { serverText, clientText } })
1264
+ return serverText
1237
1265
  }
1238
1266
 
1239
1267
  function Parse(str, parameters, types, name = '') {
package/cjs/src/index.js CHANGED
@@ -86,7 +86,8 @@ function Postgres(a, b) {
86
86
  listen,
87
87
  begin,
88
88
  close,
89
- end
89
+ end,
90
+ ...Symbol.asyncDispose && { [Symbol.asyncDispose]: () => end() }
90
91
  })
91
92
 
92
93
  return sql
@@ -273,7 +274,9 @@ function Postgres(a, b) {
273
274
  const query = { reserve: c => resolve(own(c, l)), reject }
274
275
  closed.length
275
276
  ? connect(closed.shift(), query)
276
- : queries.push(query)
277
+ : allHeldByCopy()
278
+ ? reject(copyInProgress())
279
+ : queries.push(query)
277
280
  })
278
281
  return l
279
282
  }
@@ -325,10 +328,17 @@ function Postgres(a, b) {
325
328
 
326
329
  async function reserve() {
327
330
  const l = await lease(false)
328
- const sql = Sql(q => send(l, q))
331
+ const sql = Sql(handler)
329
332
  sql.release = () => settle(l, Lease.Released)
333
+ Symbol.dispose && (sql[Symbol.dispose] = () => sql.release())
334
+ Symbol.asyncDispose && (sql[Symbol.asyncDispose] = async() => sql.release())
330
335
  sql.stats = stats
331
336
  return sql
337
+
338
+ // dd-trace starts query spans only inside function declarations named handler
339
+ function handler(q) {
340
+ send(l, q)
341
+ }
332
342
  }
333
343
 
334
344
  async function begin(options, fn) {
@@ -345,7 +355,7 @@ function Postgres(a, b) {
345
355
  }
346
356
 
347
357
  async function transaction() {
348
- await Sql(q => send(l, q)).unsafe('begin ' + options.replace(/[^a-z ]/ig, ''), []).execute().catch(e => {
358
+ await Sql(handler).unsafe('begin ' + options.replace(/[^a-z ]/ig, ''), []).execute().catch(e => {
349
359
  e instanceof PostgresError && settle(l, Lease.Released)
350
360
  throw e
351
361
  })
@@ -356,7 +366,7 @@ function Postgres(a, b) {
356
366
  let result
357
367
  l.state = Lease.Settling
358
368
  try {
359
- result = await statement(Sql(q => send(l, q, Lease.Settling)))
369
+ result = await statement(Sql(q => handler(q, Lease.Settling)))
360
370
  } catch (e) {
361
371
  settle(l, Lease.Discarded)
362
372
  throw e
@@ -366,7 +376,7 @@ function Postgres(a, b) {
366
376
  }
367
377
 
368
378
  async function scope(fn, name) {
369
- const sql = Sql(handler)
379
+ const sql = Sql(track)
370
380
  sql.savepoint = savepoint
371
381
  sql.prepare = x => {
372
382
  if (typeof x !== 'string' || !x || x.includes('\0'))
@@ -420,11 +430,16 @@ function Postgres(a, b) {
420
430
  return scope(fn, 's' + savepoints++ + (name ? '_' + name : ''))
421
431
  }
422
432
 
423
- function handler(q) {
433
+ function track(q) {
424
434
  q.catch(e => uncaughtError || (uncaughtError = e))
425
- send(l, q)
435
+ handler(q)
426
436
  }
427
437
  }
438
+
439
+ // dd-trace starts query spans only inside function declarations named handler
440
+ function handler(q, expected) {
441
+ send(l, q, expected)
442
+ }
428
443
  }
429
444
 
430
445
  function move(c, queue) {
@@ -462,7 +477,17 @@ function Postgres(a, b) {
462
477
  const c = takeBusy()
463
478
  c
464
479
  ? go(c, query)
465
- : queries.push(query)
480
+ : allHeldByCopy()
481
+ ? query.reject(copyInProgress())
482
+ : queries.push(query)
483
+ }
484
+
485
+ function allHeldByCopy() {
486
+ return connections.every(c => c.copyHeld)
487
+ }
488
+
489
+ function copyInProgress() {
490
+ return Errors.generic('COPY_IN_PROGRESS', 'You cannot execute queries during copy')
466
491
  }
467
492
 
468
493
  function takeBusy() {
@@ -662,6 +687,7 @@ function parseOptions(a, b) {
662
687
  prepare : true,
663
688
  debug : false,
664
689
  fetch_types : true,
690
+ parameter_types : 'server',
665
691
  publications : 'alltables',
666
692
  target_session_attrs: null
667
693
  }
@@ -692,11 +718,13 @@ function parseOptions(a, b) {
692
718
  },
693
719
  types : o.types || {},
694
720
  target_session_attrs: tsa(o, url, env),
721
+ parameter_types : parameterTypes(o, url, env),
695
722
  onnotice : o.onnotice,
696
723
  onnotify : o.onnotify,
697
724
  onclose : o.onclose,
698
725
  onidle : o.onidle,
699
726
  onparameter : o.onparameter,
727
+ ontypemismatch : o.ontypemismatch,
700
728
  socket : o.socket,
701
729
  transform : parseTransform(o.transform || { undefined: undefined }),
702
730
  parameters : {},
@@ -716,6 +744,14 @@ function tsa(o, url, env) {
716
744
  throw new Error('target_session_attrs ' + x + ' is not supported')
717
745
  }
718
746
 
747
+ function parameterTypes(o, url, env) {
748
+ const x = o.parameter_types || url.searchParams.get('parameter_types') || env.PGPARAMETER_TYPES || 'server'
749
+ if (['server', 'client', 'warn'].includes(x))
750
+ return x
751
+
752
+ throw new Error('parameter_types ' + x + ' is not supported')
753
+ }
754
+
719
755
  function max_lifetime() {
720
756
  return 60 * (30 + Math.random() * 30)
721
757
  }
package/cjs/src/query.js CHANGED
@@ -23,16 +23,9 @@ const Query = module.exports.Query = class Query extends Promise {
23
23
  this.state = null
24
24
  this.statement = null
25
25
 
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
- }
26
+ // dd-trace's load-time rewrite only accepts single-expression arrows here
27
+ this.resolve = x => (this.active = false, this.settled = true, resolve(x))
28
+ this.reject = x => (this.active = false, this.settled = true, reject(x))
36
29
 
37
30
  this.active = false
38
31
  this.settled = false
@@ -3,6 +3,7 @@ const { defaultBackoff, backoffSeconds } = require('./connection.js')
3
3
 
4
4
  const noop = () => { /* noop */ }
5
5
  , commands = { 68: 'delete', 73: 'insert', 85: 'update' }
6
+ , statusInterval = 1000
6
7
 
7
8
  module.exports = Subscribe;function Subscribe(postgres, options) {
8
9
  const subscribers = new Map()
@@ -151,6 +152,10 @@ module.exports = Subscribe;function Subscribe(postgres, options) {
151
152
  stream.on('error', reportConnection)
152
153
  stream.on('close', sql.close)
153
154
 
155
+ // A walsender can miss the wakeup for a commit and sleep until its timeout; a status update wakes it.
156
+ const status = setInterval(() => stream.writable && pong(), statusInterval)
157
+ stream.on('close', () => clearInterval(status))
158
+
154
159
  return { stream, state: xs.state }
155
160
 
156
161
  function data(x) {
package/cjs/src/types.js CHANGED
@@ -248,6 +248,11 @@ const inferType = module.exports.inferType = function inferType(x) {
248
248
  )
249
249
  }
250
250
 
251
+ module.exports.clientSerialize = clientSerialize;function clientSerialize(x) {
252
+ const proto = typeof x === 'object' && Object.getPrototypeOf(x)
253
+ return proto === Object.prototype || proto === null ? JSON.stringify(x) : '' + x
254
+ }
255
+
251
256
  const escapeBackslash = /\\/g
252
257
  const escapeQuote = /"/g
253
258
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@steadybrew/postgresjs",
3
- "version": "4.4.0",
3
+ "version": "4.5.0-rc1",
4
4
  "description": "PostgreSQL client for Node.js and Deno, independently maintained by Steady Brew",
5
5
  "type": "module",
6
6
  "module": "src/index.js",
@@ -24,7 +24,7 @@
24
24
  "types": "types/index.d.ts",
25
25
  "typings": "types/index.d.ts",
26
26
  "engines": {
27
- "node": ">=24"
27
+ "node": ">=22"
28
28
  },
29
29
  "scripts": {
30
30
  "build": "npm run build:cjs && npm run build:deno && npm run build:cf",
@@ -34,6 +34,7 @@
34
34
  "test": "npm run test:startup && npm run test:esm && npm run test:cjs && npm run test:deno",
35
35
  "test:esm": "node tests-startup/run.js --integration && node tests/index.js",
36
36
  "test:startup": "node tests-startup/run.js",
37
+ "test:tracing": "node tests-tracing/run.js",
37
38
  "test:cjs": "npm run build:cjs && node tests-startup/run.js --integration --cjs && cd cjs/tests && node index.js && cd ../../",
38
39
  "test:deno": "npm run test:deno-socket && npm run build:deno && cd deno/tests && deno run --no-lock --allow-all --unsafely-ignore-certificate-errors index.js && cd ../../",
39
40
  "lint": "eslint src && eslint tests && eslint tests-startup --ext .js,.mjs",
package/src/connection.js CHANGED
@@ -4,7 +4,7 @@ import crypto from 'crypto'
4
4
  import Stream from 'stream'
5
5
  import { performance } from 'perf_hooks'
6
6
 
7
- import { stringify, handleValue, addArrayType } from './types.js'
7
+ import { stringify, handleValue, addArrayType, clientSerialize } from './types.js'
8
8
  import { Errors, callback } from './errors.js'
9
9
  import Result from './result.js'
10
10
  import Queue from './queue.js'
@@ -214,7 +214,10 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
214
214
  const connection = {
215
215
  queue: queues.closed,
216
216
  idleTimer,
217
- connect: request => dispatch('acquire', request),
217
+ // Sentry's hook wraps connect and reads the connection from this, so it must stay a method
218
+ connect(request) {
219
+ dispatch('acquire', request)
220
+ },
218
221
  terminate: () => dispatch('terminate'),
219
222
  execute,
220
223
  release: () => dispatch('release'),
@@ -229,6 +232,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
229
232
  get pipelined() { return sent.length },
230
233
  get accepting() { return accepting() },
231
234
  get copying() { return stream !== null },
235
+ get copyHeld() { return stream !== null && !stream.writableEnded },
232
236
  get acquiring() { return acquisition && !acquisition.owner.cancelled ? 1 : 0 },
233
237
  count: 0,
234
238
  id
@@ -537,7 +541,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
537
541
 
538
542
  build(q)
539
543
  const written = write(toBuffer(q))
540
- if (q.describeFirst || q.cursorFn || mayCopy(q))
544
+ if (q.describeFirst || q.cursorFn || mayCopy(q) || (q.prepare && !q.prepared && !q.options.simple && q.parameters.length))
541
545
  barrier = q
542
546
  return written && accepting()
543
547
  } catch (error) {
@@ -575,7 +579,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
575
579
 
576
580
  function prepared(q) {
577
581
  return Buffer.concat([
578
- Bind(q.parameters, q.statement.types, q.statement.name, q.cursorName),
582
+ Bind(q),
579
583
  q.cursorFn
580
584
  ? Execute('', q.cursorRows)
581
585
  : ExecuteUnnamed
@@ -603,8 +607,11 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
603
607
  q.signature = q.prepare && types + string
604
608
  q.onlyDescribe && (delete statements[q.signature])
605
609
  q.parameters = q.parameters || parameters
610
+ q.inferred = options.parameter_types === 'server' ? null : types.slice()
606
611
  q.prepared = q.prepare && q.signature in statements
607
- q.describeFirst = q.onlyDescribe || (parameters.length && !q.prepared)
612
+ q.describeFirst = q.onlyDescribe || (!q.prepared && types.some((x, i) =>
613
+ !x && (options.parameter_types !== 'client' || Array.isArray(q.parameters[i]))
614
+ ))
608
615
  q.statement = q.prepared
609
616
  ? statements[q.signature]
610
617
  : { string, types, name: q.prepare ? statementId + statementCount++ : '' }
@@ -688,7 +695,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
688
695
  err = new Error(err)
689
696
 
690
697
  'query' in err || 'parameters' in err || Object.defineProperties(err, {
691
- stack: { value: err.stack + query.origin.replace(/.*\n/, '\n'), enumerable: options.debug },
698
+ stack: { value: err.stack + query.origin.replace(/.*\n/, '\n'), enumerable: options.debug, writable: true, configurable: true },
692
699
  query: { value: query.string, enumerable: options.debug },
693
700
  parameters: { value: query.parameters, enumerable: options.debug },
694
701
  args: { value: query.args, enumerable: options.debug },
@@ -1212,20 +1219,21 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
1212
1219
  }
1213
1220
 
1214
1221
  /* Messages */
1215
- function Bind(parameters, types, statement = '', portal = '') {
1222
+ function Bind(q) {
1223
+ const differences = []
1216
1224
  let prev
1217
1225
  , type
1218
1226
 
1219
- b().B().str(portal + b.N).str(statement + b.N).i16(0).i16(parameters.length)
1227
+ b().B().str((q.cursorName || '') + b.N).str(q.statement.name + b.N).i16(0).i16(q.parameters.length)
1220
1228
 
1221
- parameters.forEach((x, i) => {
1229
+ q.parameters.forEach((x, i) => {
1222
1230
  if (x === null)
1223
1231
  return b.i32(0xFFFFFFFF)
1224
1232
 
1225
- type = types[i]
1226
- x = type in options.serializers
1227
- ? options.serializers[type](x)
1228
- : '' + x
1233
+ type = q.statement.types[i]
1234
+ x = !q.inferred || q.inferred[i] || Array.isArray(x)
1235
+ ? serialize(x, type)
1236
+ : options.parameter_types === 'client' ? clientSerialize(x) : compare(x, type, i, differences)
1229
1237
 
1230
1238
  prev = b.i
1231
1239
  b.inc(4).str(x).i32(b.i - prev - 4, prev)
@@ -1233,7 +1241,27 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
1233
1241
 
1234
1242
  b.i16(0)
1235
1243
 
1236
- return b.end()
1244
+ const message = b.end()
1245
+ differences.length && callback(options.ontypemismatch || console.warn, { query: q.string, parameters: differences }) // eslint-disable-line
1246
+ return message
1247
+ }
1248
+
1249
+ function serialize(x, type) {
1250
+ return type in options.serializers
1251
+ ? options.serializers[type](x)
1252
+ : '' + x
1253
+ }
1254
+
1255
+ function compare(x, type, i, differences) {
1256
+ const serverText = serialize(x, type)
1257
+ let clientText
1258
+ try {
1259
+ clientText = clientSerialize(x)
1260
+ } catch (error) {
1261
+ clientText = error
1262
+ }
1263
+ serverText === clientText || differences.push({ position: i + 1, oid: type, typeof: typeof x, ...options.debug && { serverText, clientText } })
1264
+ return serverText
1237
1265
  }
1238
1266
 
1239
1267
  function Parse(str, parameters, types, name = '') {
package/src/index.js CHANGED
@@ -86,7 +86,8 @@ function Postgres(a, b) {
86
86
  listen,
87
87
  begin,
88
88
  close,
89
- end
89
+ end,
90
+ ...Symbol.asyncDispose && { [Symbol.asyncDispose]: () => end() }
90
91
  })
91
92
 
92
93
  return sql
@@ -273,7 +274,9 @@ function Postgres(a, b) {
273
274
  const query = { reserve: c => resolve(own(c, l)), reject }
274
275
  closed.length
275
276
  ? connect(closed.shift(), query)
276
- : queries.push(query)
277
+ : allHeldByCopy()
278
+ ? reject(copyInProgress())
279
+ : queries.push(query)
277
280
  })
278
281
  return l
279
282
  }
@@ -325,10 +328,17 @@ function Postgres(a, b) {
325
328
 
326
329
  async function reserve() {
327
330
  const l = await lease(false)
328
- const sql = Sql(q => send(l, q))
331
+ const sql = Sql(handler)
329
332
  sql.release = () => settle(l, Lease.Released)
333
+ Symbol.dispose && (sql[Symbol.dispose] = () => sql.release())
334
+ Symbol.asyncDispose && (sql[Symbol.asyncDispose] = async() => sql.release())
330
335
  sql.stats = stats
331
336
  return sql
337
+
338
+ // dd-trace starts query spans only inside function declarations named handler
339
+ function handler(q) {
340
+ send(l, q)
341
+ }
332
342
  }
333
343
 
334
344
  async function begin(options, fn) {
@@ -345,7 +355,7 @@ function Postgres(a, b) {
345
355
  }
346
356
 
347
357
  async function transaction() {
348
- await Sql(q => send(l, q)).unsafe('begin ' + options.replace(/[^a-z ]/ig, ''), []).execute().catch(e => {
358
+ await Sql(handler).unsafe('begin ' + options.replace(/[^a-z ]/ig, ''), []).execute().catch(e => {
349
359
  e instanceof PostgresError && settle(l, Lease.Released)
350
360
  throw e
351
361
  })
@@ -356,7 +366,7 @@ function Postgres(a, b) {
356
366
  let result
357
367
  l.state = Lease.Settling
358
368
  try {
359
- result = await statement(Sql(q => send(l, q, Lease.Settling)))
369
+ result = await statement(Sql(q => handler(q, Lease.Settling)))
360
370
  } catch (e) {
361
371
  settle(l, Lease.Discarded)
362
372
  throw e
@@ -366,7 +376,7 @@ function Postgres(a, b) {
366
376
  }
367
377
 
368
378
  async function scope(fn, name) {
369
- const sql = Sql(handler)
379
+ const sql = Sql(track)
370
380
  sql.savepoint = savepoint
371
381
  sql.prepare = x => {
372
382
  if (typeof x !== 'string' || !x || x.includes('\0'))
@@ -420,11 +430,16 @@ function Postgres(a, b) {
420
430
  return scope(fn, 's' + savepoints++ + (name ? '_' + name : ''))
421
431
  }
422
432
 
423
- function handler(q) {
433
+ function track(q) {
424
434
  q.catch(e => uncaughtError || (uncaughtError = e))
425
- send(l, q)
435
+ handler(q)
426
436
  }
427
437
  }
438
+
439
+ // dd-trace starts query spans only inside function declarations named handler
440
+ function handler(q, expected) {
441
+ send(l, q, expected)
442
+ }
428
443
  }
429
444
 
430
445
  function move(c, queue) {
@@ -462,7 +477,17 @@ function Postgres(a, b) {
462
477
  const c = takeBusy()
463
478
  c
464
479
  ? go(c, query)
465
- : queries.push(query)
480
+ : allHeldByCopy()
481
+ ? query.reject(copyInProgress())
482
+ : queries.push(query)
483
+ }
484
+
485
+ function allHeldByCopy() {
486
+ return connections.every(c => c.copyHeld)
487
+ }
488
+
489
+ function copyInProgress() {
490
+ return Errors.generic('COPY_IN_PROGRESS', 'You cannot execute queries during copy')
466
491
  }
467
492
 
468
493
  function takeBusy() {
@@ -662,6 +687,7 @@ function parseOptions(a, b) {
662
687
  prepare : true,
663
688
  debug : false,
664
689
  fetch_types : true,
690
+ parameter_types : 'server',
665
691
  publications : 'alltables',
666
692
  target_session_attrs: null
667
693
  }
@@ -692,11 +718,13 @@ function parseOptions(a, b) {
692
718
  },
693
719
  types : o.types || {},
694
720
  target_session_attrs: tsa(o, url, env),
721
+ parameter_types : parameterTypes(o, url, env),
695
722
  onnotice : o.onnotice,
696
723
  onnotify : o.onnotify,
697
724
  onclose : o.onclose,
698
725
  onidle : o.onidle,
699
726
  onparameter : o.onparameter,
727
+ ontypemismatch : o.ontypemismatch,
700
728
  socket : o.socket,
701
729
  transform : parseTransform(o.transform || { undefined: undefined }),
702
730
  parameters : {},
@@ -716,6 +744,14 @@ function tsa(o, url, env) {
716
744
  throw new Error('target_session_attrs ' + x + ' is not supported')
717
745
  }
718
746
 
747
+ function parameterTypes(o, url, env) {
748
+ const x = o.parameter_types || url.searchParams.get('parameter_types') || env.PGPARAMETER_TYPES || 'server'
749
+ if (['server', 'client', 'warn'].includes(x))
750
+ return x
751
+
752
+ throw new Error('parameter_types ' + x + ' is not supported')
753
+ }
754
+
719
755
  function max_lifetime() {
720
756
  return 60 * (30 + Math.random() * 30)
721
757
  }
package/src/query.js CHANGED
@@ -23,16 +23,9 @@ export class Query extends Promise {
23
23
  this.state = null
24
24
  this.statement = null
25
25
 
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
- }
26
+ // dd-trace's load-time rewrite only accepts single-expression arrows here
27
+ this.resolve = x => (this.active = false, this.settled = true, resolve(x))
28
+ this.reject = x => (this.active = false, this.settled = true, reject(x))
36
29
 
37
30
  this.active = false
38
31
  this.settled = false
package/src/subscribe.js CHANGED
@@ -3,6 +3,7 @@ import { defaultBackoff, backoffSeconds } from './connection.js'
3
3
 
4
4
  const noop = () => { /* noop */ }
5
5
  , commands = { 68: 'delete', 73: 'insert', 85: 'update' }
6
+ , statusInterval = 1000
6
7
 
7
8
  export default function Subscribe(postgres, options) {
8
9
  const subscribers = new Map()
@@ -151,6 +152,10 @@ export default function Subscribe(postgres, options) {
151
152
  stream.on('error', reportConnection)
152
153
  stream.on('close', sql.close)
153
154
 
155
+ // A walsender can miss the wakeup for a commit and sleep until its timeout; a status update wakes it.
156
+ const status = setInterval(() => stream.writable && pong(), statusInterval)
157
+ stream.on('close', () => clearInterval(status))
158
+
154
159
  return { stream, state: xs.state }
155
160
 
156
161
  function data(x) {