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