@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.
@@ -10,6 +10,8 @@ import { stringify, handleValue, addArrayType } from './types.js'
10
10
  import { Errors } from './errors.js'
11
11
  import Result from './result.js'
12
12
  import Queue from './queue.js'
13
+ import clampedTimeout from './timeout.js'
14
+ import { SSLRequest, tlsConfig, cancelRequest } from './transport.js'
13
15
  import { Query, CLOSE } from './query.js'
14
16
  import b from './bytes.js'
15
17
 
@@ -19,12 +21,12 @@ let uid = 1
19
21
 
20
22
  const Sync = b().S().end()
21
23
  , Flush = b().H().end()
22
- , SSLRequest = b().i32(8).i32(80877103).end(8)
23
24
  , ExecuteUnnamed = Buffer.concat([b().E().str(b.N).i32(0).end(), Sync])
24
25
  , DescribeUnnamed = b().D().str('S').str(b.N).end()
25
26
  , noop = () => { /* noop */ }
26
27
 
27
- const Phase = { Closed: 0, Backoff: 1, Opening: 2, Negotiating: 3, Authenticating: 4, Initializing: 5, Ready: 6, Draining: 7, Closing: 8 }
28
+ const Phase = { Closed: 0, Backoff: 1, Connecting: 2, Initializing: 3, Ready: 4, Draining: 5, Closing: 6 }
29
+ const phaseNames = Object.keys(Phase)
28
30
 
29
31
  const retryRoutines = new Set([
30
32
  'FetchPreparedStatement',
@@ -76,18 +78,20 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
76
78
  const sent = Queue()
77
79
  , id = uid++
78
80
  , backend = { pid: null, secret: null }
79
- , idleTimer = timer(end, options.idle_timeout)
80
- , lifeTimer = timer(end, options.max_lifetime)
81
+ , idleTimer = timer(() => dispatch('end'), options.idle_timeout)
82
+ , lifeTimer = timer(() => dispatch('end'), options.max_lifetime)
81
83
 
82
84
  let socket = null
83
85
  , phase = Phase.Closed
84
86
  , generation = 0
85
87
  , acquisition = null
86
- , endRequested = false
87
88
  , endWaiters = []
88
89
  , inheritedBackoff = null
89
90
  , backoffTimer = null
90
- , connectTimer = null
91
+ , backoffUntil = 0
92
+ , attemptTimer = null
93
+ , deadlineTimer = null
94
+ , closeTimer = null
91
95
  , errorResponse = null
92
96
  , result = new Result()
93
97
  , incoming = Buffer.alloc(0)
@@ -110,38 +114,153 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
110
114
  , query = null
111
115
  , final = null
112
116
 
117
+ const check = globalThis[Symbol.for('postgres.js:check')]
118
+ let depth = 0
119
+
120
+ const starting = {
121
+ acquire: refuse,
122
+ deadline: expire,
123
+ attemptTimeout: () => afterFailure(Errors.connection('CONNECT_TIMEOUT', options, socket), 'timeout'),
124
+ socketError: err => afterFailure(err, 'socket'),
125
+ socketClose: () => afterFailure(errorResponse || Errors.connection('CONNECTION_CLOSED', options, socket), 'dropped'),
126
+ protocolError: err => afterFailure(err, 'protocol'),
127
+ authFailed: enterClosed,
128
+ writable: noop,
129
+ end: () => endAcquisition(null),
130
+ terminate: () => enterClosed(Errors.connection('CONNECTION_DESTROYED', options, socket))
131
+ }
132
+
133
+ const on = {
134
+ [Phase.Closed]: {
135
+ acquire,
136
+ execute: refuse,
137
+ end: () => endWaiters.splice(0).forEach(resolve => resolve()),
138
+ terminate: noop
139
+ },
140
+ [Phase.Backoff]: {
141
+ acquire: refuse,
142
+ execute: refuse,
143
+ backoffElapsed: enterConnecting,
144
+ deadline: expire,
145
+ end: () => (inheritedBackoff = { at: performance.now(), delay: backoffUntil - performance.now() }, endAcquisition(acquisition.lastError)),
146
+ terminate: () => enterClosed(Errors.connection('CONNECTION_DESTROYED', options, socket))
147
+ },
148
+ [Phase.Connecting]: {
149
+ ...starting,
150
+ execute: refuse,
151
+ socketCreated: attach,
152
+ factoryFailed: err => afterFailure(err, 'factory'),
153
+ socketReady: () => ssl ? negotiate() : authenticate(),
154
+ sslReply: x => x[0] === 83
155
+ ? upgrade()
156
+ : ssl === 'prefer'
157
+ ? authenticate()
158
+ : afterFailure(Errors.generic('SSL_NOT_SUPPORTED', 'The server does not support SSL connections'), 'protocol'),
159
+ secure: authenticate,
160
+ serverError: err => afterFailure(err, 'rejected'),
161
+ ready: () => (transition(Phase.Initializing), initialized())
162
+ },
163
+ [Phase.Initializing]: {
164
+ ...starting,
165
+ execute: q => q.initialization ? submit(q) : refuse(q),
166
+ serverError: err => afterFailure(err, 'dropped'),
167
+ initFailed: enterClosed,
168
+ ready: initialized
169
+ },
170
+ [Phase.Ready]: {
171
+ acquire: refuse,
172
+ execute: submit,
173
+ ready: () => pipelined() || (connection.owner ? connection.owner.next() : onopen(connection)),
174
+ serverError: lost,
175
+ socketError: lost,
176
+ socketClose: hadError => lost(closedBy(errorResponse), hadError),
177
+ protocolError: errored,
178
+ writable: () => !query && !connection.owner && onopen(connection),
179
+ end: () => idle() && !connection.owner
180
+ ? closing()
181
+ : (transition(Phase.Draining), connection.owner || onend(connection)),
182
+ terminate: () => idle() ? enterClosed() : enterClosed(Errors.connection('CONNECTION_DESTROYED', options, socket)),
183
+ release: () => onopen(connection)
184
+ },
185
+ [Phase.Draining]: {
186
+ acquire: refuse,
187
+ execute: submit,
188
+ ready: () => pipelined() || (connection.owner ? connection.owner.next() : drained()),
189
+ serverError: lost,
190
+ socketError: lost,
191
+ socketClose: hadError => lost(closedBy(errorResponse), hadError),
192
+ protocolError: errored,
193
+ writable: noop,
194
+ end: noop,
195
+ terminate: () => idle() ? enterClosed() : enterClosed(Errors.connection('CONNECTION_DESTROYED', options, socket)),
196
+ release: () => idle() ? drained() : onend(connection)
197
+ },
198
+ [Phase.Closing]: {
199
+ acquire: refuse,
200
+ execute: refuse,
201
+ ready: noop,
202
+ serverError: noop,
203
+ socketError: noop,
204
+ socketClose: hadError => hadError ? lost(Errors.connection('CONNECTION_CLOSED', options, socket)) : enterClosed(),
205
+ closeTimeout: () => enterClosed(),
206
+ protocolError: errored,
207
+ writable: noop,
208
+ end: noop,
209
+ terminate: () => enterClosed()
210
+ }
211
+ }
212
+
113
213
  const connection = {
114
214
  queue: queues.closed,
115
215
  idleTimer,
116
- connect: acquire,
117
- terminate,
216
+ connect: request => dispatch('acquire', request),
217
+ terminate: () => dispatch('terminate'),
118
218
  execute,
119
- cancel,
120
- release,
121
- end,
219
+ release: () => dispatch('release'),
220
+ end: () => {
221
+ const done = new Promise(resolve => endWaiters.push(resolve))
222
+ dispatch('end')
223
+ return done
224
+ },
225
+ expired: () => idleTimer.expired() || lifeTimer.expired(),
226
+ owner: null,
122
227
  count: 0,
123
228
  id
124
229
  }
125
230
 
231
+ check && Object.defineProperty(connection, Symbol.for('postgres.js:phase'), { get: () => phaseNames[phase] })
232
+ check && check('created', connection, queues)
233
+
126
234
  queues.closed && queues.closed.push(connection)
127
235
 
128
236
  return connection
129
237
 
130
238
  function transition(next) {
239
+ check && check('edge', connection, phaseNames[phase], phaseNames[next])
131
240
  generation++
132
241
  phase = next
133
242
  }
134
243
 
135
- function starting() {
136
- return phase >= Phase.Opening && phase <= Phase.Initializing
244
+ function dispatch(event, arg) {
245
+ const handler = on[phase][event]
246
+ if (!handler)
247
+ return check && check('unhandled', connection, phaseNames[phase], event)
248
+
249
+ depth++
250
+ try {
251
+ return handler(arg)
252
+ } finally {
253
+ depth--
254
+ check && depth === 0 && check('settled', connection, queues)
255
+ }
137
256
  }
138
257
 
139
- function held() {
140
- return connection.reserved || connection.queue === queues.reserved
258
+ function refuse(request) {
259
+ queryError(request, Errors.connection('CONNECTION_CLOSED', options))
141
260
  }
142
261
 
143
262
  function drained() {
144
- ondrain(connection) || closing(true)
263
+ ondrain(connection) || closing()
145
264
  }
146
265
 
147
266
  function idle() {
@@ -152,47 +271,38 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
152
271
  return (typeof backoff === 'function' ? backoff(options.shared.retries) : backoff) * 1000
153
272
  }
154
273
 
155
- function acquire(owner) {
156
- if (phase !== Phase.Closed)
157
- return queryError(owner, Errors.connection('CONNECTION_CLOSED', options))
274
+ function standbyPass() {
275
+ return target_session_attrs === 'prefer-standby' && host.length > 1 && acquisition.pass === 'standby'
276
+ }
158
277
 
159
- acquisition = { owner, attempts: 0, hostsTried: 0, lastError: null, deadline: null, expired: false }
278
+ function acquire(owner) {
279
+ acquisition = { owner, ending: false, pass: 'standby', hostsTried: 0, attempting: null, mismatches: [], lastError: null }
160
280
  const wait = inheritedBackoff ? inheritedBackoff.at + inheritedBackoff.delay - performance.now() : 0
161
281
  inheritedBackoff = null
162
- wait > 0 ? enterBackoff(wait) : enterOpening()
282
+ wait > 0 ? enterBackoff(wait) : enterConnecting()
163
283
  }
164
284
 
165
285
  function enterBackoff(ms) {
166
286
  transition(Phase.Backoff)
167
- const a = acquisition
168
- const attempt = generation
169
- const left = a.deadline === null ? Infinity : Math.max(0, a.deadline - performance.now())
170
- a.expired = left <= ms
171
- backoffTimer = setTimeout(() => {
172
- backoffTimer = null
173
- attempt === generation && (a.expired
174
- ? enterClosed(a.lastError || Errors.connection('CONNECT_TIMEOUT', options))
175
- : enterOpening())
176
- }, Math.min(ms, left))
177
- }
178
-
179
- function enterOpening() {
180
- transition(Phase.Opening)
287
+ backoffUntil = performance.now() + ms
288
+ backoffTimer = clampedTimeout(() => (backoffTimer = null, dispatch('backoffElapsed')), ms)
289
+ }
290
+
291
+ function enterConnecting() {
292
+ transition(Phase.Connecting)
181
293
  const a = acquisition
182
294
  const attempt = generation
183
295
  const ms = options.connect_timeout * 1000
184
296
  if (ms) {
185
- const now = performance.now()
186
- a.deadline === null && (a.deadline = now + ms * host.length)
187
- const left = a.deadline - now
188
- connectTimer = setTimeout(() => connectTimedOut(left <= ms), Math.min(ms, left))
297
+ deadlineTimer === null && (deadlineTimer = clampedTimeout(() => (deadlineTimer = null, dispatch('deadline')), ms * host.length))
298
+ host.length > 1 && (attemptTimer = clampedTimeout(() => (attemptTimer = null, dispatch('attemptTimeout')), ms))
189
299
  }
190
300
  a.hostsTried++
191
301
  backendParameters = {}
192
302
  Promise.resolve()
193
303
  .then(() => options.socket ? options.socket(options) : new net.Socket())
194
- .then(created => attempt === generation ? attach(created) : dispose(created))
195
- .catch(err => attempt === generation && fail(err))
304
+ .then(created => attempt === generation ? dispatch('socketCreated', created) : dispose(created))
305
+ .catch(err => attempt === generation && dispatch('factoryFailed', err))
196
306
  }
197
307
 
198
308
  function dispose(created) {
@@ -200,16 +310,21 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
200
310
  created.destroy()
201
311
  }
202
312
 
313
+ function listen(s) {
314
+ s.on('error', err => dispatch('socketError', err))
315
+ s.on('close', hadError => dispatch('socketClose', hadError))
316
+ s.on('drain', () => dispatch('writable'))
317
+ }
318
+
203
319
  function attach(created) {
320
+ listen(created)
204
321
  socket = created
205
- created.on('error', error)
206
- created.on('close', closed)
207
- created.on('drain', drain)
322
+ acquisition.attempting = options.socket ? 'custom socket' : options.path || host[hostIndex] + ':' + port[hostIndex]
208
323
 
209
324
  if (options.socket)
210
- return ssl ? negotiate() : authenticate()
325
+ return dispatch('socketReady')
211
326
 
212
- created.on('connect', ssl ? negotiate : authenticate)
327
+ created.on('connect', () => dispatch('socketReady'))
213
328
 
214
329
  if (options.path)
215
330
  return created.connect(options.path)
@@ -223,110 +338,96 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
223
338
  }
224
339
 
225
340
  function negotiate() {
226
- transition(Phase.Negotiating)
227
341
  if (sslnegotiation === 'direct')
228
342
  return upgrade()
229
343
 
230
- const attempt = generation
231
- socket.once('data', x => attempt === generation && (
232
- x[0] === 83
233
- ? upgrade()
234
- : ssl === 'prefer'
235
- ? authenticate()
236
- : fail(Errors.generic('SSL_NOT_SUPPORTED', 'The server does not support SSL connections'))
237
- ))
344
+ socket.once('data', x => dispatch('sslReply', x))
238
345
  write(SSLRequest)
239
346
  }
240
347
 
241
348
  function upgrade() {
242
349
  try {
243
350
  const raw = socket
244
- const config = tlsConfig(raw)
351
+ const config = tlsConfig(options, raw)
245
352
  raw.removeAllListeners()
246
353
  socket = tls.connect(config)
247
- socket.on('secureConnect', authenticate)
248
- socket.on('error', error)
249
- socket.on('close', closed)
250
- socket.on('drain', drain)
354
+ socket.on('secureConnect', () => dispatch('secure'))
355
+ listen(socket)
251
356
  } catch (err) {
252
- fail(err)
357
+ afterFailure(err, 'protocol')
253
358
  }
254
359
  }
255
360
 
256
- function tlsConfig(raw) {
257
- const config = {
258
- socket: raw,
259
- servername: net.isIP(raw.host) ? undefined : raw.host
260
- }
261
-
262
- if (sslnegotiation === 'direct')
263
- config.ALPNProtocols = ['postgresql']
264
-
265
- if (ssl === 'require' || ssl === 'allow' || ssl === 'prefer')
266
- config.rejectUnauthorized = false
267
- else if (typeof ssl === 'object')
268
- Object.assign(config, ssl)
269
-
270
- return config
271
- }
272
-
273
361
  function authenticate() {
274
- transition(Phase.Authenticating)
275
362
  try {
276
363
  statements = {}
277
364
  needsTypes = options.fetch_types
278
365
  statementId = Math.random().toString(36).slice(2)
279
366
  statementCount = 1
280
- lifeTimer.start()
281
367
  socket.on('data', data)
282
368
  keep_alive && socket.setKeepAlive && socket.setKeepAlive(true, 1000 * keep_alive)
283
369
  write(StartupMessage())
284
370
  } catch (err) {
285
- fail(err)
371
+ afterFailure(err, 'protocol')
286
372
  }
287
373
  }
288
374
 
289
- function connectTimedOut(expiring) {
290
- connectTimer = null
291
- if (!starting())
292
- return
293
- expiring && (acquisition.expired = true)
294
- fail(Errors.connection('CONNECT_TIMEOUT', options, socket), true)
375
+ function expire() {
376
+ enterClosed(acquisition.lastError || Errors.connection('CONNECT_TIMEOUT', options, socket))
295
377
  }
296
378
 
297
- function fail(err, persist = host.length > 1) {
379
+ function mismatched(reason) {
298
380
  const a = acquisition
299
- a.attempts++
300
- a.lastError = err && err.code === 'CONNECT_TIMEOUT' && a.lastError ? a.lastError : err
301
- clearTimeout(connectTimer)
302
- connectTimer = null
303
- detach()
304
- resetSocketState(err)
305
-
306
- if (endRequested)
307
- return enterClosed(err)
308
-
309
- if (a.expired || (a.deadline !== null && performance.now() >= a.deadline))
310
- return enterClosed(a.lastError)
381
+ a.mismatches.push(a.attempting + ' ' + reason)
382
+ afterFailure(closedBy(Errors.generic('TARGET_SESSION_ATTRS',
383
+ 'No host matched target_session_attrs=' + target_session_attrs + ': ' + a.mismatches.join(', '))), 'mismatch')
384
+ }
311
385
 
312
- if (a.hostsTried < host.length)
313
- return enterBackoff(0)
386
+ function afterFailure(err, cause) {
387
+ const a = acquisition
388
+ a.lastError = cause === 'timeout' && a.lastError ? a.lastError : err
314
389
 
315
- options.shared.retries++
316
- if (!persist) {
390
+ if (a.ending) {
317
391
  inheritedBackoff = { at: performance.now(), delay: backoffMs() }
318
392
  return enterClosed(err)
319
393
  }
320
394
 
321
- a.hostsTried = 0
322
- enterBackoff(backoffMs())
395
+ let delay = 0
396
+ if (a.hostsTried === host.length) {
397
+ a.hostsTried = 0
398
+ const mismatches = a.mismatches
399
+ a.mismatches = []
400
+ if (standbyPass()) {
401
+ a.pass = 'any'
402
+ } else {
403
+ a.pass = 'standby'
404
+ options.shared.retries++
405
+ if (host.length > 1 && mismatches.length === host.length) {
406
+ inheritedBackoff = { at: performance.now(), delay: backoffMs() }
407
+ return enterClosed(err)
408
+ }
409
+
410
+ // A single endpoint that is read-only now is often mid-failover behind one DNS name, so retry until the deadline.
411
+ if (host.length === 1 && cause !== 'dropped' && cause !== 'mismatch') {
412
+ inheritedBackoff = { at: performance.now(), delay: backoffMs() }
413
+ return enterClosed(err)
414
+ }
415
+
416
+ delay = backoffMs()
417
+ }
418
+ }
419
+
420
+ clearTimeout(attemptTimer)
421
+ attemptTimer = null
422
+ endSession(err)
423
+ enterBackoff(delay)
323
424
  }
324
425
 
325
- function detach() {
326
- clearTimeout(connectTimer)
327
- connectTimer = null
328
- idleTimer.cancel()
426
+ function endSession(err) {
329
427
  lifeTimer.cancel()
428
+ clearTimeout(closeTimer)
429
+ closeTimer = null
430
+ clearImmediate(nextWriteTimer)
330
431
  incoming = Buffer.alloc(0)
331
432
  remaining = 0
332
433
  incomings = null
@@ -336,117 +437,85 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
336
437
  socket.destroy()
337
438
  socket = null
338
439
  }
440
+ stream && (stream.destroy(err), stream = null)
441
+ final && (final(err), final = null)
442
+ query && queryError(query, err)
443
+ while (sent.length) {
444
+ const pending = sent.shift()
445
+ queryError(pending, err)
446
+ pending.cancelled && pending.cancelled.resolve()
447
+ }
448
+ query = results = errorResponse = chunk = nextWriteTimer = null
449
+ result = new Result()
450
+ rows = 0
451
+ nonce = serverSignature = null
339
452
  }
340
453
 
341
- function enterClosed(err = Errors.connection('CONNECTION_CLOSED', options, socket)) {
342
- if (phase === Phase.Closed)
343
- return
344
-
454
+ function enterClosed(err = Errors.connection('CONNECTION_CLOSED', options, socket), reported = err) {
345
455
  const a = acquisition
346
456
  const waiters = endWaiters
347
- clearTimeout(backoffTimer)
348
- backoffTimer = null
349
- detach()
350
- resetSocketState(err)
457
+ clearAcquisitionTimers()
458
+ endSession(err)
351
459
  acquisition = null
352
- endRequested = false
353
460
  endWaiters = []
354
461
  transition(Phase.Closed)
355
462
  a && queryError(a.owner, err)
356
463
  waiters.forEach(resolve => resolve())
357
- onclose(connection, err)
464
+ onclose(connection, reported)
465
+ }
466
+
467
+ function clearAcquisitionTimers() {
468
+ clearTimeout(attemptTimer)
469
+ attemptTimer = null
470
+ clearTimeout(deadlineTimer)
471
+ deadlineTimer = null
472
+ clearTimeout(backoffTimer)
473
+ backoffTimer = null
358
474
  }
359
475
 
360
- function closing(quit) {
476
+ function closing() {
361
477
  transition(Phase.Closing)
362
- idleTimer.cancel()
478
+ onend(connection)
363
479
  lifeTimer.cancel()
364
- quit && socket.readyState === 'open'
365
- ? socket.end(b().X().end())
366
- : socket.destroy()
480
+ if (socket.readyState === 'open') {
481
+ socket.end(b().X().end())
482
+ const ms = options.connect_timeout * 1000
483
+ ms && (closeTimer = clampedTimeout(() => (closeTimer = null, dispatch('closeTimeout')), ms))
484
+ } else {
485
+ socket.destroy()
486
+ }
367
487
  }
368
488
 
369
489
  function handoff() {
370
- const owner = acquisition.owner
490
+ const { owner, ending } = acquisition
371
491
  transition(Phase.Ready)
372
- clearTimeout(connectTimer)
373
- connectTimer = null
492
+ clearAcquisitionTimers()
374
493
  acquisition = null
375
494
  options.shared.retries = 0
495
+ lifeTimer.start()
376
496
 
377
- if (endRequested) {
378
- endRequested = false
379
- if (owner.reserve)
380
- return (queryError(owner, Errors.connection('CONNECTION_ENDED', options)), closing(true))
497
+ if (ending) {
381
498
  if (owner.cancelled)
382
- return closing(true)
499
+ return closing()
383
500
  transition(Phase.Draining)
384
501
  return execute(owner)
385
502
  }
386
503
 
387
- owner.reserve
388
- ? onopen(connection, owner)
389
- : owner.cancelled
390
- ? onopen(connection)
391
- : execute(owner)
392
- }
393
-
394
- async function cancel({ pid, secret }, resolve, reject) {
395
- let timeout = null
396
- try {
397
- let s = await Promise.resolve(options.socket ? options.socket(options) : new net.Socket())
398
- const request = b().i32(16).i32(80877102).i32(pid).i32(secret).end(16)
399
- const watch = x => {
400
- x.once('error', reject)
401
- x.on('error', noop)
402
- x.once('close', () => (clearTimeout(timeout), resolve()))
403
- }
404
- const upgrade = () => {
405
- const raw = s
406
- const config = tlsConfig(raw)
407
- raw.removeAllListeners()
408
- s = tls.connect(config)
409
- watch(s)
410
- s.once('secureConnect', () => s.write(request))
411
- }
412
- const start = !ssl
413
- ? () => s.write(request)
414
- : sslnegotiation === 'direct'
415
- ? upgrade
416
- : () => {
417
- s.once('data', x => x[0] === 83 || ssl !== 'prefer' ? upgrade() : s.write(request))
418
- s.write(SSLRequest)
419
- }
420
-
421
- watch(s)
422
- options.connect_timeout && (timeout = setTimeout(() => s.destroy(), options.connect_timeout * 1000))
423
-
424
- if (options.socket)
425
- return start()
426
-
427
- s.once('connect', start)
428
-
429
- if (options.path)
430
- return s.connect(options.path)
431
-
432
- s.ssl = ssl
433
- s.connect(port[0], host[0])
434
- s.host = host[0]
435
- s.port = port[0]
436
- } catch (error) {
437
- reject(error)
438
- }
504
+ onopen(connection, owner.cancelled ? undefined : owner)
439
505
  }
440
506
 
441
507
  function execute(q) {
442
- if (phase !== Phase.Ready && phase !== Phase.Draining && !(phase === Phase.Initializing && q.initialization))
443
- return queryError(q, Errors.connection('CONNECTION_CLOSED', options))
508
+ return dispatch('execute', q)
509
+ }
444
510
 
511
+ function submit(q) {
445
512
  if (stream)
446
513
  return queryError(q, Errors.generic('COPY_IN_PROGRESS', 'You cannot execute queries during copy'))
447
514
 
448
515
  if (q.cancelled)
449
- return
516
+ return true
517
+
518
+ q.owner === undefined && (q.owner = connection.owner)
450
519
 
451
520
  try {
452
521
  q.state = backend
@@ -542,24 +611,6 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
542
611
  return x
543
612
  }
544
613
 
545
- function resetSocketState(err) {
546
- clearImmediate(nextWriteTimer)
547
- stream && (stream.destroy(err), stream = null)
548
- final && (final(err), final = null)
549
- query && queryError(query, err)
550
- while (sent.length)
551
- queryError(sent.shift(), err)
552
- query = results = errorResponse = chunk = nextWriteTimer = null
553
- result = new Result()
554
- rows = 0
555
- nonce = serverSignature = null
556
- }
557
-
558
- /* c8 ignore next 3 */
559
- function drain() {
560
- phase === Phase.Ready && !query && !held() && onopen(connection)
561
- }
562
-
563
614
  function data(x) {
564
615
  if (incomings) {
565
616
  incomings.push(x)
@@ -587,7 +638,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
587
638
  handle(incoming.subarray(0, length + 1))
588
639
  } catch (e) {
589
640
  query && (query.cursorFn || query.describeFirst) && write(Sync)
590
- starting() ? fail(e) : errored(e)
641
+ dispatch('protocolError', e)
591
642
  }
592
643
  if (socket !== source)
593
644
  return
@@ -597,22 +648,15 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
597
648
  }
598
649
  }
599
650
 
600
- function error(err) {
601
- if (starting())
602
- return fail(err)
603
-
604
- if (phase === Phase.Ready || phase === Phase.Draining)
605
- socketFailed(err)
651
+ function lost(err, counted = true) {
652
+ counted && options.shared.retries++
653
+ inheritedBackoff = { at: performance.now(), delay: backoffMs() }
654
+ enterClosed(err, err.code === 'CONNECTION_CLOSED' ? err : closedBy(err))
606
655
  }
607
656
 
608
- function socketFailed(err) {
609
- errored(err)
610
- while (sent.length)
611
- queryError(sent.shift(), err)
612
- query = null
613
- options.shared.retries++
614
- inheritedBackoff = { at: performance.now(), delay: backoffMs() }
615
- closing(false)
657
+ // CONNECTION_CLOSED stays the code callers retry on; the FATAL, socket error or target_session_attrs mismatch is its cause.
658
+ function closedBy(cause) {
659
+ return Errors.connection('CONNECTION_CLOSED', options, socket, cause)
616
660
  }
617
661
 
618
662
  function errored(err) {
@@ -637,68 +681,16 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
637
681
  query.reject(err)
638
682
  }
639
683
 
640
- function end() {
641
- if (phase === Phase.Closed)
642
- return Promise.resolve()
643
-
644
- const done = new Promise(resolve => endWaiters.push(resolve))
645
-
646
- if (phase === Phase.Closing || phase === Phase.Draining)
647
- return done
648
-
649
- if (phase === Phase.Ready) {
650
- !held() && onend(connection)
651
- idle() && !held()
652
- ? closing(true)
653
- : transition(Phase.Draining)
654
- return done
655
- }
656
-
657
- if (acquisition.owner.reserve)
684
+ function endAcquisition(err) {
685
+ const a = acquisition
686
+ if (a.owner.reserve)
658
687
  enterClosed(Errors.connection('CONNECTION_ENDED', options))
659
- else if (phase === Phase.Backoff && acquisition.attempts > 0)
660
- enterClosed(acquisition.lastError)
661
- else if (!endRequested)
662
- (endRequested = true, onend(connection))
663
-
664
- return done
665
- }
666
-
667
- function terminate() {
668
- if (phase === Phase.Closed)
669
- return
670
-
671
- const destroyed = Errors.connection('CONNECTION_DESTROYED', options, socket)
672
- if (phase === Phase.Closing)
673
- return socket.destroy()
674
-
675
- if (phase !== Phase.Ready && phase !== Phase.Draining)
676
- return enterClosed(destroyed)
677
-
678
- const inflight = !idle()
679
- resetSocketState(destroyed)
680
- enterClosed(inflight ? destroyed : undefined)
681
- }
682
-
683
- function release() {
684
- phase === Phase.Ready
685
- ? onopen(connection)
686
- : phase === Phase.Draining && idle() && drained()
688
+ else if (err)
689
+ enterClosed(err)
690
+ else if (!a.ending)
691
+ (a.ending = true, onend(connection))
687
692
  }
688
693
 
689
- function closed(hadError) {
690
- const err = Errors.connection('CONNECTION_CLOSED', options, socket)
691
- if (starting())
692
- return fail(errorResponse || err, true)
693
-
694
- if (!inheritedBackoff && (phase !== Phase.Closing || hadError)) {
695
- hadError && options.shared.retries++
696
- inheritedBackoff = { at: performance.now(), delay: backoffMs() }
697
- }
698
- enterClosed(err)
699
- }
700
-
701
-
702
694
  /* Handlers */
703
695
  function handle(xs, x = xs[0]) {
704
696
  (
@@ -776,14 +768,14 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
776
768
  }
777
769
  }
778
770
 
779
- function ReadyForQuery(x) {
771
+ function ReadyForQuery() {
780
772
  if (query) {
781
773
  if (errorResponse) {
782
774
  if (query.initialization)
783
- return enterClosed(errorResponse)
775
+ return dispatch('initFailed', errorResponse)
784
776
  query.retried
785
777
  ? errored(query.retried)
786
- : query.prepared && retryRoutines.has(errorResponse.routine)
778
+ : query.prepared && retryRoutines.has(errorResponse.routine) && query.owner === connection.owner
787
779
  ? retry(query, errorResponse)
788
780
  : errored(errorResponse)
789
781
  } else {
@@ -797,37 +789,26 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
797
789
  query = results = errorResponse = null
798
790
  result = new Result()
799
791
 
800
- if (phase === Phase.Authenticating || phase === Phase.Initializing)
801
- return initialized()
802
-
803
- if (phase === Phase.Closing)
804
- return
805
-
806
- while (sent.length && (query = sent.shift()) && (query.active = true, query.cancelled))
807
- Connection(options).cancel(query.state, query.cancelled.resolve, query.cancelled.reject)
792
+ dispatch('ready')
793
+ }
808
794
 
809
- if (query)
810
- return
795
+ function pipelined() {
796
+ query = sent.length ? sent.shift() : null
797
+ if (query) {
798
+ query.active = true
799
+ query.cancelled && cancelRequest(options, query.state).then(query.cancelled.resolve, query.cancelled.reject)
800
+ }
811
801
 
812
- connection.reserved
813
- ? !connection.reserved.release && x[5] === 73
814
- ? phase === Phase.Draining
815
- ? drained()
816
- : (connection.reserved = null, onopen(connection))
817
- : connection.reserved()
818
- : phase === Phase.Draining
819
- ? drained()
820
- : onopen(connection)
802
+ return query
821
803
  }
822
804
 
823
805
  function initialized() {
824
806
  if (target_session_attrs) {
825
807
  if (!backendParameters.in_hot_standby || !backendParameters.default_transaction_read_only)
826
808
  return fetchState()
827
- if (tryNext(target_session_attrs, backendParameters)) {
828
- acquisition.mismatch = true
829
- return fail(Errors.connection('CONNECTION_CLOSED', options, socket), true)
830
- }
809
+ const reason = mismatchReason(target_session_attrs, backendParameters)
810
+ if (reason)
811
+ return mismatched(reason)
831
812
  }
832
813
 
833
814
  if (needsTypes)
@@ -851,7 +832,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
851
832
 
852
833
  final && (final(), final = null)
853
834
 
854
- if (result.command === 'BEGIN' && max !== 1 && !connection.reserved)
835
+ if (result.command === 'BEGIN' && max !== 1 && !query.owner)
855
836
  return errored(Errors.generic('UNSAFE_TRANSACTION', 'Only use sql.begin, sql.reserved or max: 1'))
856
837
 
857
838
  if (query.options.simple)
@@ -929,7 +910,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
929
910
  type === 12 ? SASLFinal :
930
911
  type !== 0 ? UnknownAuth :
931
912
  noop
932
- )(x, type, attempt)).catch(err => attempt === generation && enterClosed(err))
913
+ )(x, type, attempt)).catch(err => attempt === generation && dispatch('authFailed', err))
933
914
  }
934
915
 
935
916
  /* c8 ignore next 5 */
@@ -1003,7 +984,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
1003
984
  if (x.toString('utf8', 9).split(b.N, 1)[0].slice(2) === serverSignature)
1004
985
  return
1005
986
  /* c8 ignore next 5 */
1006
- enterClosed(Errors.generic('SASL_SIGNATURE_MISMATCH', 'The server did not return the correct signature'))
987
+ dispatch('authFailed', Errors.generic('SASL_SIGNATURE_MISMATCH', 'The server did not return the correct signature'))
1007
988
  }
1008
989
 
1009
990
  function Pass() {
@@ -1037,13 +1018,14 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
1037
1018
  `, types => types.forEach(({ oid, typarray }) => addArrayType(options, oid, typarray)))
1038
1019
  }
1039
1020
 
1040
- function tryNext(x, xs) {
1021
+ function mismatchReason(x, xs) {
1041
1022
  return (
1042
- (x === 'read-write' && xs.default_transaction_read_only === 'on') ||
1043
- (x === 'read-only' && xs.default_transaction_read_only === 'off') ||
1044
- (x === 'primary' && xs.in_hot_standby === 'on') ||
1045
- (x === 'standby' && xs.in_hot_standby === 'off') ||
1046
- (x === 'prefer-standby' && xs.in_hot_standby === 'off' && host.length > 1 && !acquisition.mismatch)
1023
+ (x === 'read-write' && xs.default_transaction_read_only === 'on' && 'is read-only') ||
1024
+ (x === 'read-only' && xs.default_transaction_read_only === 'off' && 'is read-write') ||
1025
+ (x === 'primary' && xs.in_hot_standby === 'on' && 'is a standby') ||
1026
+ (x === 'standby' && xs.in_hot_standby === 'off' && 'is a primary') ||
1027
+ (x === 'prefer-standby' && xs.in_hot_standby === 'off' && standbyPass() && 'is a primary') ||
1028
+ null
1047
1029
  )
1048
1030
  }
1049
1031
 
@@ -1058,7 +1040,6 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
1058
1040
  }
1059
1041
 
1060
1042
  function initialize(string, resolve, simple = false) {
1061
- transition(Phase.Initializing)
1062
1043
  const attempt = generation
1063
1044
  const q = new Query([string], [], q => attempt === generation
1064
1045
  ? execute(q)
@@ -1072,10 +1053,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
1072
1053
  (query.cursorFn || query.describeFirst) && write(Sync)
1073
1054
  errorResponse = Errors.postgres(parseError(x))
1074
1055
  } else {
1075
- const err = Errors.postgres(parseError(x))
1076
- phase === Phase.Ready || phase === Phase.Draining
1077
- ? socketFailed(err)
1078
- : phase === Phase.Authenticating && fail(err)
1056
+ dispatch('serverError', Errors.postgres(parseError(x)))
1079
1057
  }
1080
1058
  }
1081
1059
 
@@ -1307,21 +1285,28 @@ function xor(a, b) {
1307
1285
  function timer(fn, seconds) {
1308
1286
  seconds = typeof seconds === 'function' ? seconds() : seconds
1309
1287
  if (!seconds)
1310
- return { cancel: noop, start: noop }
1288
+ return { cancel: noop, start: noop, expired: () => false }
1311
1289
 
1312
1290
  let timer
1291
+ , due = null
1313
1292
  return {
1314
1293
  cancel() {
1315
1294
  timer && (clearTimeout(timer), timer = null)
1295
+ due = null
1316
1296
  },
1317
1297
  start(...args) {
1318
1298
  timer && clearTimeout(timer)
1319
- timer = setTimeout(() => done(args), seconds * 1000)
1299
+ due = Date.now() + seconds * 1000
1300
+ timer = clampedTimeout(() => done(args), seconds * 1000)
1301
+ },
1302
+ expired() {
1303
+ return due !== null && Date.now() >= due
1320
1304
  }
1321
1305
  }
1322
1306
 
1323
1307
  function done(args) {
1324
1308
  timer = null
1309
+ due = null
1325
1310
  fn.apply(null, args)
1326
1311
  }
1327
1312
  }