postgrejs 2.23.1 → 3.0.2

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.
Files changed (107) hide show
  1. package/CHANGELOG.md +171 -1
  2. package/LICENSE +26 -17
  3. package/README.md +195 -175
  4. package/connection/connection.d.ts +169 -16
  5. package/connection/connection.js +318 -56
  6. package/connection/copy-stream.d.ts +88 -0
  7. package/connection/copy-stream.js +244 -0
  8. package/connection/cursor.js +20 -13
  9. package/connection/intl-connection.d.ts +113 -5
  10. package/connection/intl-connection.js +500 -70
  11. package/connection/large-object.d.ts +65 -0
  12. package/connection/large-object.js +151 -0
  13. package/connection/logical-replication.d.ts +121 -0
  14. package/connection/logical-replication.js +279 -0
  15. package/connection/pool.d.ts +61 -4
  16. package/connection/pool.js +177 -29
  17. package/connection/portal.d.ts +18 -4
  18. package/connection/portal.js +53 -59
  19. package/connection/prepared-statement.d.ts +22 -1
  20. package/connection/prepared-statement.js +129 -109
  21. package/data-type-map.js +4 -2
  22. package/data-types/bool-type.js +14 -10
  23. package/data-types/box-type.js +15 -8
  24. package/data-types/bytea-type.js +9 -5
  25. package/data-types/char-type.js +11 -5
  26. package/data-types/circle-type.js +15 -7
  27. package/data-types/date-type.js +25 -19
  28. package/data-types/float4-type.js +10 -3
  29. package/data-types/float8-type.js +12 -3
  30. package/data-types/int2-type.js +9 -4
  31. package/data-types/int2-vector-type.js +9 -4
  32. package/data-types/int4-type.js +11 -5
  33. package/data-types/int8-type.js +22 -7
  34. package/data-types/json-type.js +17 -8
  35. package/data-types/jsonb-type.js +20 -14
  36. package/data-types/lseg-type.js +16 -8
  37. package/data-types/numeric-type.d.ts +8 -0
  38. package/data-types/numeric-type.js +118 -9
  39. package/data-types/oid-type.d.ts +0 -1
  40. package/data-types/oid-type.js +9 -10
  41. package/data-types/oid-vector-type.d.ts +9 -0
  42. package/data-types/oid-vector-type.js +51 -0
  43. package/data-types/point-type.js +14 -6
  44. package/data-types/time-type.js +20 -13
  45. package/data-types/timestamp-type.js +32 -25
  46. package/data-types/timestamptz-type.js +33 -21
  47. package/data-types/uuid-type.js +21 -11
  48. package/data-types/varchar-type.js +16 -4
  49. package/index.d.ts +5 -0
  50. package/index.js +5 -0
  51. package/interfaces/command-result.d.ts +2 -1
  52. package/interfaces/data-type.d.ts +5 -3
  53. package/interfaces/database-connection-params.d.ts +108 -0
  54. package/interfaces/query-options.d.ts +23 -0
  55. package/interfaces/script-execute-options.d.ts +23 -0
  56. package/interfaces/script-result.d.ts +3 -2
  57. package/package.json +2 -3
  58. package/protocol/backend.d.ts +6 -1
  59. package/protocol/backend.js +70 -45
  60. package/protocol/buffer-reader.d.ts +0 -2
  61. package/protocol/buffer-reader.js +3 -10
  62. package/protocol/cert-signature.d.ts +12 -0
  63. package/protocol/cert-signature.js +114 -0
  64. package/protocol/frontend.d.ts +16 -0
  65. package/protocol/frontend.js +70 -11
  66. package/protocol/pg-socket.d.ts +220 -9
  67. package/protocol/pg-socket.js +539 -102
  68. package/protocol/pgoutput.d.ts +76 -0
  69. package/protocol/pgoutput.js +155 -0
  70. package/protocol/protocol.d.ts +3 -4
  71. package/protocol/protocol.js +1 -1
  72. package/protocol/sasl.d.ts +11 -1
  73. package/protocol/sasl.js +36 -5
  74. package/protocol/smart-buffer.d.ts +0 -2
  75. package/protocol/smart-buffer.js +11 -16
  76. package/types.d.ts +5 -3
  77. package/util/abort-signal.d.ts +34 -0
  78. package/util/abort-signal.js +76 -0
  79. package/util/array-calculatedim.js +3 -1
  80. package/util/connection-config.js +77 -3
  81. package/util/decode-binaryarray.d.ts +1 -1
  82. package/util/decode-binaryarray.js +15 -6
  83. package/util/encode-binaryarray.js +7 -3
  84. package/util/escape-identifier.d.ts +14 -0
  85. package/util/escape-identifier.js +32 -0
  86. package/util/escape-literal.js +3 -0
  87. package/util/fast-parseint.d.ts +1 -0
  88. package/util/fast-parseint.js +12 -17
  89. package/util/format-datetime.d.ts +10 -0
  90. package/util/format-datetime.js +71 -0
  91. package/util/get-parsers.js +31 -10
  92. package/util/parse-array.js +2 -2
  93. package/util/parse-bytea.d.ts +2 -0
  94. package/util/parse-bytea.js +83 -0
  95. package/util/parse-datetime.d.ts +3 -1
  96. package/util/parse-datetime.js +86 -17
  97. package/util/parse-row.d.ts +3 -1
  98. package/util/parse-row.js +60 -5
  99. package/util/parse-time.js +8 -1
  100. package/util/sql-tag.d.ts +60 -0
  101. package/util/sql-tag.js +202 -0
  102. package/util/starts-transaction.d.ts +38 -0
  103. package/util/starts-transaction.js +148 -0
  104. package/util/stringify-arrayliteral.js +13 -1
  105. package/util/wrap-row-description.js +13 -4
  106. package/util/convert-row-to-object.d.ts +0 -2
  107. package/util/convert-row-to-object.js +0 -9
@@ -1,24 +1,41 @@
1
- import { TaskQueue } from 'power-tasks';
2
- import { coerceToBoolean } from 'putil-varhelpers';
3
- import { ConnectionState } from '../constants.js';
1
+ import { performance } from 'node:perf_hooks';
2
+ import { ConnectionState, DEFAULT_COLUMN_FORMAT } from '../constants.js';
4
3
  import { GlobalTypeMap } from '../data-type-map.js';
5
4
  import { PgSocket } from '../protocol/pg-socket.js';
6
5
  import { Protocol } from '../protocol/protocol.js';
7
6
  import { SafeEventEmitter } from '../safe-event-emitter.js';
8
7
  import { getConnectionConfig } from '../util/connection-config.js';
9
- import { convertRowToObject } from '../util/convert-row-to-object.js';
10
8
  import { escapeLiteral } from '../util/escape-literal.js';
11
9
  import { getParsers } from '../util/get-parsers.js';
12
- import { parseRow } from '../util/parse-row.js';
10
+ import { parseObjectRow, parseRow } from '../util/parse-row.js';
13
11
  import { wrapRowDescription } from '../util/wrap-row-description.js';
12
+ import { CopyFromStream, CopyToStream } from './copy-stream.js';
14
13
  const DataFormat = Protocol.DataFormat;
14
+ function columnFormatsEqual(a, b) {
15
+ if (a === b)
16
+ return true;
17
+ if (!Array.isArray(a) || !Array.isArray(b))
18
+ return false;
19
+ if (a.length !== b.length)
20
+ return false;
21
+ const l = a.length;
22
+ let i;
23
+ for (i = 0; i < l; i++)
24
+ if (a[i] !== b[i])
25
+ return false;
26
+ return true;
27
+ }
15
28
  export class IntlConnection extends SafeEventEmitter {
29
+ _executeReusedParserCache = new WeakMap();
16
30
  _refCount = 0;
31
+ _transactionDepth = 0;
32
+ _savepointDepths = new Map();
17
33
  _config;
18
34
  _onErrorSavePoint;
19
35
  transactionStatus = 'I';
20
36
  socket;
21
- statementQueue = new TaskQueue({ concurrency: 1 });
37
+ owner;
38
+ runningQueryCount = 0;
22
39
  constructor(config) {
23
40
  super();
24
41
  this._config = Object.freeze(getConnectionConfig(config));
@@ -76,91 +93,190 @@ export class IntlConnection extends SafeEventEmitter {
76
93
  async close() {
77
94
  if (this.state === ConnectionState.CLOSED)
78
95
  return;
79
- this.statementQueue.clearQueue();
80
96
  return new Promise(resolve => {
81
97
  if (this.socket.state === ConnectionState.CLOSED)
82
98
  return;
99
+ // 'close' is not emitted here - the constructor's own
100
+ // `this.socket.on('close', () => this.emit('close'))` already
101
+ // forwards it once the socket's real close event fires. Emitting it
102
+ // again right here (synchronously, before socket.close()'s
103
+ // destroy() has actually completed) used to fire 'close' on this
104
+ // IntlConnection TWICE per close() call - confirmed live: a single
105
+ // close() left two listeners registered on the far side (e.g. two
106
+ // NOTIFY deliveries after Pool's reconnect re-subscribed twice).
83
107
  this.socket.once('close', resolve);
84
108
  this.socket.sendTerminateMessage(() => {
85
109
  this.socket.close();
86
- this.emit('close');
87
110
  });
88
111
  });
89
112
  }
90
- async execute(sql, options, cb) {
113
+ async execute(sql, options = {}, cb) {
91
114
  this.assertConnected();
92
- return this.statementQueue
93
- .enqueue(async () => {
94
- const transactionCommand = sql.match(/^(\bBEGIN\b|\bCOMMIT\b|\bSTART\b|\bROLLBACK|SAVEPOINT|RELEASE\b)/i);
95
- let beginFirst = false;
96
- let commitLast = false;
97
- if (!transactionCommand) {
98
- if (!this.inTransaction &&
99
- (options?.autoCommit != null
100
- ? options?.autoCommit
101
- : this.config.autoCommit) === false) {
102
- beginFirst = true;
103
- }
104
- if (this.inTransaction && options?.autoCommit)
105
- commitLast = true;
106
- }
107
- if (beginFirst)
108
- await this._execute('BEGIN');
109
- const rollbackOnError = !transactionCommand &&
110
- (options?.rollbackOnError != null
111
- ? options.rollbackOnError
112
- : coerceToBoolean(this.config.rollbackOnError, true));
113
- if (this.inTransaction && rollbackOnError)
114
- await this._execute('SAVEPOINT ' + this._onErrorSavePoint);
115
- try {
116
- const result = await this._execute(sql, options, cb);
117
- if (commitLast)
118
- await this._execute('COMMIT');
119
- else if (this.inTransaction && rollbackOnError) {
120
- await this._execute('RELEASE ' + this._onErrorSavePoint + ';');
121
- }
122
- return result;
115
+ const transactionCommand = sql.match(/^(\bBEGIN\b|\bCOMMIT\b|\bSTART\b|\bROLLBACK|SAVEPOINT|RELEASE\b)/i);
116
+ let beginFirst = false;
117
+ let commitLast = false;
118
+ const { autoCommit } = options;
119
+ if (!transactionCommand) {
120
+ if ((autoCommit != null ? autoCommit : this.config.autoCommit) === false &&
121
+ !this.inTransaction) {
122
+ beginFirst = true;
123
123
  }
124
- catch (e) {
125
- if (this.inTransaction && rollbackOnError)
126
- await this._execute('ROLLBACK TO ' + this._onErrorSavePoint + ';');
127
- throw e;
124
+ if (autoCommit && this.inTransaction)
125
+ commitLast = true;
126
+ }
127
+ if (beginFirst)
128
+ await this._execute('BEGIN');
129
+ // this.inTransaction goes first here, before the config fallback: the
130
+ // result is only ever consulted below when it's true, so checking it
131
+ // first short-circuits that read on every call made outside of a
132
+ // transaction, the common case. Below, `rollbackOnError &&
133
+ // this.inTransaction` deliberately puts it last instead - re-checking
134
+ // live state a plain local boolean can't capture (the awaited call in
135
+ // between may have committed, rolled back, or errored the transaction
136
+ // out from under it) - but ordered so the cheap local read still
137
+ // short-circuits the getter whenever rollbackOnError is already
138
+ // false, which it is outside a transaction.
139
+ const rollbackOnError = !transactionCommand &&
140
+ this.inTransaction &&
141
+ (options.rollbackOnError ?? this.config.rollbackOnError ?? true);
142
+ if (rollbackOnError && this.inTransaction)
143
+ await this._execute('SAVEPOINT ' + this._onErrorSavePoint);
144
+ try {
145
+ const result = await this._execute(sql, options, cb);
146
+ if (commitLast)
147
+ await this._execute('COMMIT');
148
+ else if (rollbackOnError && this.inTransaction) {
149
+ await this._execute('RELEASE ' + this._onErrorSavePoint + ';');
128
150
  }
129
- })
130
- .toPromise();
151
+ return result;
152
+ }
153
+ catch (e) {
154
+ if (rollbackOnError && this.inTransaction)
155
+ await this._execute('ROLLBACK TO ' + this._onErrorSavePoint + ';');
156
+ throw e;
157
+ }
131
158
  }
159
+ /**
160
+ * Starts a transaction, or - if one is already running - marks a nested
161
+ * level of it. Each call increments `_transactionDepth`; only the
162
+ * outermost one actually sends BEGIN (`inTransaction` is already false at
163
+ * that point). A matching number of commit() calls is then needed to
164
+ * actually commit - see commit().
165
+ */
132
166
  async startTransaction() {
167
+ this._transactionDepth++;
133
168
  if (!this.inTransaction)
134
169
  await this.execute('BEGIN');
135
170
  }
171
+ /**
172
+ * Starts a savepoint, or - if one under the same `name` is already open -
173
+ * marks a nested level of it. Mirrors startTransaction(): only the
174
+ * outermost call for a given name actually sends SAVEPOINT: a savepoint
175
+ * already exists under that name, re-declaring it would just stack a
176
+ * second, independent one with the same name on the server rather than
177
+ * nesting the existing one.
178
+ */
136
179
  async savepoint(name) {
137
180
  if (!(name && name.match(/^[a-zA-Z]\w+$/)))
138
181
  throw new Error(`Invalid savepoint "${name}"`);
139
- await this.execute('BEGIN; SAVEPOINT ' + name);
182
+ const depth = (this._savepointDepths.get(name) || 0) + 1;
183
+ this._savepointDepths.set(name, depth);
184
+ if (depth === 1)
185
+ await this.execute('BEGIN; SAVEPOINT ' + name);
140
186
  }
141
- async commit() {
187
+ /**
188
+ * Commits the transaction started by startTransaction() - or, when nested,
189
+ * just unwinds one level of `_transactionDepth`. The actual COMMIT is only
190
+ * sent once the depth reaches zero, i.e. once every startTransaction()
191
+ * call has a matching commit().
192
+ * @param immediate - Ignores `_transactionDepth` and commits right away,
193
+ * regardless of how many nested levels are still open.
194
+ */
195
+ async commit(immediate) {
196
+ if (!immediate && this._transactionDepth > 1) {
197
+ this._transactionDepth--;
198
+ return;
199
+ }
200
+ this._transactionDepth = 0;
142
201
  if (this.inTransaction)
143
202
  await this.execute('COMMIT');
144
203
  }
204
+ /**
205
+ * Releases the savepoint created by savepoint() - or, when nested, just
206
+ * unwinds one level of that name's depth. The actual RELEASE SAVEPOINT is
207
+ * only sent once that depth reaches zero.
208
+ * @param name - Name of the savepoint.
209
+ * @param immediate - Ignores the tracked depth and releases right away.
210
+ */
211
+ async releaseSavepoint(name, immediate) {
212
+ if (!(name && name.match(/^[a-zA-Z]\w+$/)))
213
+ throw new Error(`Invalid savepoint "${name}"`);
214
+ const depth = this._savepointDepths.get(name) || 0;
215
+ if (!immediate && depth > 1) {
216
+ this._savepointDepths.set(name, depth - 1);
217
+ return;
218
+ }
219
+ this._savepointDepths.delete(name);
220
+ await this.execute('RELEASE SAVEPOINT ' + name, { autoCommit: false });
221
+ }
222
+ /**
223
+ * Rolls back the whole transaction, regardless of `_transactionDepth` -
224
+ * a rollback ends the transaction outright, so there is nothing left for
225
+ * any pending, still-nested commit() call to unwind.
226
+ */
145
227
  async rollback() {
228
+ this._transactionDepth = 0;
146
229
  if (this.inTransaction)
147
230
  await this.execute('ROLLBACK');
148
231
  }
232
+ /**
233
+ * Ends the current transaction as a prepared one: it stops being tied to
234
+ * this session and waits under `name` until some connection - not
235
+ * necessarily this one, and not necessarily this process - finishes it
236
+ * with commitPrepared() or rollbackPrepared().
237
+ *
238
+ * Goes through _execute() rather than execute(): PREPARE TRANSACTION is
239
+ * not in the latter's list of transaction commands, so it would be
240
+ * wrapped in a savepoint whose RELEASE then fails, the session no longer
241
+ * being in a transaction by that point.
242
+ *
243
+ * Requires `max_prepared_transactions` above zero on the server, which is
244
+ * not the default; the server's own error says so if it is not.
245
+ */
246
+ async prepareTransaction(name) {
247
+ await this._execute('PREPARE TRANSACTION ' + escapeLiteral(name));
248
+ }
249
+ /** Commits a transaction left waiting by prepareTransaction(). */
250
+ async commitPrepared(name) {
251
+ await this._execute('COMMIT PREPARED ' + escapeLiteral(name));
252
+ }
253
+ /** Discards a transaction left waiting by prepareTransaction(). */
254
+ async rollbackPrepared(name) {
255
+ await this._execute('ROLLBACK PREPARED ' + escapeLiteral(name));
256
+ }
257
+ /**
258
+ * Rolls back to the given savepoint, regardless of its tracked depth - a
259
+ * rollback discards it (and everything after it) outright, so there is
260
+ * nothing left for any pending, still-nested releaseSavepoint() call to
261
+ * unwind.
262
+ */
149
263
  async rollbackToSavepoint(name) {
150
264
  if (!(name && name.match(/^[a-zA-Z]\w+$/)))
151
265
  throw new Error(`Invalid savepoint "${name}"`);
266
+ this._savepointDepths.delete(name);
152
267
  await this.execute('ROLLBACK TO SAVEPOINT ' + name, { autoCommit: false });
153
268
  }
154
- async releaseSavepoint(name) {
155
- if (!(name && name.match(/^[a-zA-Z]\w+$/)))
156
- throw new Error(`Invalid savepoint "${name}"`);
157
- await this.execute('RELEASE SAVEPOINT ' + name, { autoCommit: false });
269
+ /** Asks the server to cancel whatever this session is running. */
270
+ cancel() {
271
+ return this.socket.cancel();
158
272
  }
159
273
  ref() {
160
274
  this._refCount++;
161
275
  }
162
276
  unref() {
163
277
  this._refCount--;
278
+ if (!this._refCount)
279
+ this.emit('idle');
164
280
  return !this._refCount;
165
281
  }
166
282
  assertConnected() {
@@ -169,27 +285,78 @@ export class IntlConnection extends SafeEventEmitter {
169
285
  if (this.state === ConnectionState.CLOSED)
170
286
  throw new Error('Connection closed');
171
287
  }
172
- async _execute(sql, options, cb) {
288
+ /**
289
+ * Runs a COPY ... TO STDOUT and hands back its bytes as they arrive.
290
+ * Resolves once the server has accepted the copy, not once it has
291
+ * finished - the whole point is not to hold the export in memory.
292
+ */
293
+ async copyTo(sql) {
294
+ this.assertConnected();
295
+ this.ref();
296
+ this.runningQueryCount++;
297
+ const stream = new CopyToStream(this.socket);
298
+ this.socket
299
+ .sendQueryMessage(sql, stream.capture)
300
+ .catch(err => stream.fail(err))
301
+ .finally(() => {
302
+ this.runningQueryCount--;
303
+ this.unref();
304
+ });
305
+ await stream.waitStarted();
306
+ return stream;
307
+ }
308
+ /**
309
+ * Runs a COPY ... FROM STDIN and hands back a stream to feed it.
310
+ * Resolves once the server is ready for data.
311
+ */
312
+ async copyFrom(sql) {
313
+ this.assertConnected();
314
+ this.ref();
315
+ this.runningQueryCount++;
316
+ const stream = new CopyFromStream(this.socket);
317
+ this.socket
318
+ .sendQueryMessage(sql, stream.capture)
319
+ .catch(err => stream.fail(err))
320
+ .finally(() => {
321
+ this.runningQueryCount--;
322
+ this.unref();
323
+ });
324
+ await stream.waitStarted();
325
+ return stream;
326
+ }
327
+ async _execute(sql, options = {}, cb) {
173
328
  this.ref();
174
329
  try {
175
- const startTime = Date.now();
330
+ const timingEnabled = options.timing ?? this.config.timing ?? false;
331
+ const startTime = timingEnabled ? performance.now() : 0;
176
332
  const result = {
177
333
  totalCommands: 0,
178
- totalTime: 0,
179
334
  results: [],
180
335
  };
181
- const opts = options || {};
182
- this.socket.sendQueryMessage(sql);
183
- let currentStart = Date.now();
336
+ let currentStart = startTime;
184
337
  let parsers;
185
338
  let current = { command: undefined };
186
339
  let fields;
187
- const typeMap = opts.typeMap || GlobalTypeMap;
188
- return await this.socket.capture(async (code, msg, done) => {
340
+ let error;
341
+ const typeMap = options.typeMap || GlobalTypeMap;
342
+ this.runningQueryCount++;
343
+ return await this.socket.sendQueryMessage(sql, (code, msg, done) => {
189
344
  switch (code) {
190
- case Protocol.BackendMessageCode.NoticeResponse:
345
+ case Protocol.BackendMessageCode.ErrorResponse:
346
+ error = msg;
347
+ break;
191
348
  case Protocol.BackendMessageCode.CopyInResponse:
349
+ error =
350
+ error ||
351
+ new Error('COPY FROM STDIN is not supported by execute() - use copyFrom() instead');
352
+ this.socket.sendCopyFail(error.message);
353
+ break;
192
354
  case Protocol.BackendMessageCode.CopyOutResponse:
355
+ error =
356
+ error ||
357
+ new Error('COPY TO STDOUT is not supported by execute() - use copyTo() instead');
358
+ break;
359
+ case Protocol.BackendMessageCode.NoticeResponse:
193
360
  case Protocol.BackendMessageCode.EmptyQueryResponse:
194
361
  break;
195
362
  case Protocol.BackendMessageCode.RowDescription:
@@ -200,11 +367,9 @@ export class IntlConnection extends SafeEventEmitter {
200
367
  break;
201
368
  case Protocol.BackendMessageCode.DataRow:
202
369
  {
203
- let row = msg.columns.map((x) => x.toString('utf8'));
204
- // The null override assumes we can trust PG to always send the RowDescription first
205
- parseRow(parsers, row, opts);
206
- if (opts.objectRows && current.fields)
207
- row = convertRowToObject(current.fields, row);
370
+ const row = options.objectRows && current.fields
371
+ ? parseObjectRow(parsers, msg.data, msg.columnCount, options, current.fields)
372
+ : parseRow(parsers, msg.data, msg.columnCount, options);
208
373
  if (cb)
209
374
  cb('row', row);
210
375
  current.rows = current.rows || [];
@@ -219,19 +384,26 @@ export class IntlConnection extends SafeEventEmitter {
219
384
  current.command === 'UPDATE') {
220
385
  current.rowsAffected = msg.rowCount;
221
386
  }
222
- current.executeTime = Date.now() - currentStart;
387
+ if (timingEnabled)
388
+ current.executeTime = performance.now() - currentStart;
223
389
  if (current.rows)
224
390
  current.rowType =
225
- opts.objectRows && current.fields ? 'object' : 'array';
391
+ options.objectRows && current.fields ? 'object' : 'array';
226
392
  result.results.push(current);
227
393
  if (cb)
228
394
  cb('command-complete', current);
229
395
  current = { command: undefined };
230
- currentStart = Date.now();
396
+ if (timingEnabled)
397
+ currentStart = performance.now();
231
398
  break;
232
399
  case Protocol.BackendMessageCode.ReadyForQuery:
233
400
  this.transactionStatus = msg.status;
234
- result.totalTime = Date.now() - startTime;
401
+ if (error) {
402
+ done(error);
403
+ break;
404
+ }
405
+ if (timingEnabled)
406
+ result.totalTime = performance.now() - startTime;
235
407
  // Ignore COMMIT command that we added to sql
236
408
  result.totalCommands = result.results.length;
237
409
  done(undefined, result);
@@ -241,10 +413,268 @@ export class IntlConnection extends SafeEventEmitter {
241
413
  }
242
414
  });
243
415
  }
416
+ finally {
417
+ this.runningQueryCount--;
418
+ this.unref();
419
+ }
420
+ }
421
+ /**
422
+ * One-shot Extended Query fast path: Parse+Bind+Describe+Execute+Sync as
423
+ * a single round trip (unnamed statement/portal, no Close needed), used
424
+ * by Connection.query() instead of prepare()+PreparedStatement.execute()
425
+ * +close()'s 7 round trips whenever there's no cursor and no explicit/
426
+ * active transaction to wrap (see Connection.query() for that gating).
427
+ */
428
+ async queryOnce(sql, paramTypes, params, options) {
429
+ this.assertConnected();
430
+ this.ref();
431
+ try {
432
+ const typeMap = options.typeMap || GlobalTypeMap;
433
+ const timingEnabled = options.timing ?? this.config.timing ?? false;
434
+ const startTime = timingEnabled ? performance.now() : 0;
435
+ const result = { command: undefined };
436
+ const rows = [];
437
+ let parsers;
438
+ let resultFields;
439
+ let commandTag;
440
+ let error;
441
+ this.runningQueryCount++;
442
+ await this.socket
443
+ .sendExtendedQueryMessages({
444
+ parse: { sql, paramTypes },
445
+ bind: { typeMap, paramTypes, params, queryOptions: options },
446
+ describe: { type: 'P' },
447
+ execute: { fetchCount: options.fetchCount || 100 },
448
+ }, (code, msg, done) => {
449
+ switch (code) {
450
+ case Protocol.BackendMessageCode.ParseComplete:
451
+ case Protocol.BackendMessageCode.BindComplete:
452
+ case Protocol.BackendMessageCode.NoData:
453
+ case Protocol.BackendMessageCode.NoticeResponse:
454
+ case Protocol.BackendMessageCode.PortalSuspended:
455
+ break;
456
+ case Protocol.BackendMessageCode.RowDescription:
457
+ parsers = getParsers(typeMap, msg.fields);
458
+ resultFields = wrapRowDescription(typeMap, msg.fields, options.columnFormat || DEFAULT_COLUMN_FORMAT);
459
+ result.fields = resultFields;
460
+ result.rowType = options.objectRows ? 'object' : 'array';
461
+ break;
462
+ case Protocol.BackendMessageCode.DataRow:
463
+ rows.push(msg);
464
+ break;
465
+ case Protocol.BackendMessageCode.CommandComplete:
466
+ commandTag = msg;
467
+ break;
468
+ case Protocol.BackendMessageCode.ErrorResponse:
469
+ error = msg;
470
+ break;
471
+ case Protocol.BackendMessageCode.ReadyForQuery:
472
+ this.transactionStatus = msg.status;
473
+ done(error);
474
+ break;
475
+ default:
476
+ done(new Error(`Server returned unexpected response message (${String.fromCharCode(code)})`));
477
+ }
478
+ })
479
+ .finally(() => {
480
+ this.runningQueryCount--;
481
+ });
482
+ if (commandTag?.command)
483
+ result.command = commandTag.command;
484
+ if (resultFields && parsers) {
485
+ if (!result.command)
486
+ result.command = 'SELECT';
487
+ result.rows = rows;
488
+ const l = rows.length;
489
+ let i;
490
+ for (i = 0; i < l; i++) {
491
+ rows[i] = options.objectRows
492
+ ? parseObjectRow(parsers, rows[i].data, rows[i].columnCount, options, resultFields)
493
+ : parseRow(parsers, rows[i].data, rows[i].columnCount, options);
494
+ }
495
+ }
496
+ if (result.command === 'DELETE' ||
497
+ result.command === 'INSERT' ||
498
+ result.command === 'UPDATE') {
499
+ result.rowsAffected = commandTag?.rowCount;
500
+ }
501
+ if (timingEnabled)
502
+ result.executeTime = performance.now() - startTime;
503
+ return result;
504
+ }
505
+ finally {
506
+ this.unref();
507
+ }
508
+ }
509
+ /**
510
+ * Parse+Describe(statement)+Sync as a single round trip - used by
511
+ * PreparedStatement.prepare() instead of a separately-awaited Parse then
512
+ * a separately-awaited Sync, and fetches the RowDescription/NoData in the
513
+ * same round trip so the caller can cache it (see executeReused()) rather
514
+ * than every later execute() re-Describing its own portal.
515
+ */
516
+ async prepareOnce(sql, paramTypes, statementName) {
517
+ this.assertConnected();
518
+ this.ref();
519
+ try {
520
+ let fields;
521
+ let error;
522
+ this.runningQueryCount++;
523
+ await this.socket
524
+ .sendPrepareMessages({
525
+ parse: { statement: statementName, sql, paramTypes },
526
+ describe: { type: 'S', name: statementName },
527
+ }, (code, msg, done) => {
528
+ switch (code) {
529
+ case Protocol.BackendMessageCode.ParseComplete:
530
+ case Protocol.BackendMessageCode.ParameterDescription:
531
+ case Protocol.BackendMessageCode.NoData:
532
+ case Protocol.BackendMessageCode.NoticeResponse:
533
+ break;
534
+ case Protocol.BackendMessageCode.RowDescription:
535
+ fields = msg.fields;
536
+ break;
537
+ case Protocol.BackendMessageCode.ErrorResponse:
538
+ // See queryOnce()'s ErrorResponse case for why done() must
539
+ // not be called here.
540
+ error = msg;
541
+ break;
542
+ case Protocol.BackendMessageCode.ReadyForQuery:
543
+ this.transactionStatus = msg.status;
544
+ done(error);
545
+ break;
546
+ default:
547
+ done(new Error(`Server returned unexpected response message (${String.fromCharCode(code)})`));
548
+ }
549
+ })
550
+ .finally(() => {
551
+ this.runningQueryCount--;
552
+ });
553
+ return { fields };
554
+ }
244
555
  finally {
245
556
  this.unref();
246
557
  }
247
558
  }
559
+ /**
560
+ * Bind+Execute+Sync as a single round trip against an unnamed portal, for
561
+ * an already-prepared (named) statement - used by
562
+ * PreparedStatement._execute()'s non-cursor path instead of the 4-round-
563
+ * trip Portal.bind()/retrieveFields()/execute()/close() sequence. Takes
564
+ * the RowDescription fields prepareOnce() already fetched instead of
565
+ * re-Describing a fresh portal on every call.
566
+ */
567
+ async executeReused(statementName, cachedFields, paramTypes, params, options) {
568
+ this.assertConnected();
569
+ this.ref();
570
+ try {
571
+ const typeMap = options.typeMap || GlobalTypeMap;
572
+ const timingEnabled = options.timing ?? this.config.timing ?? false;
573
+ const startTime = timingEnabled ? performance.now() : 0;
574
+ const result = { command: undefined };
575
+ const rows = [];
576
+ let parsers;
577
+ let resultFields;
578
+ let commandTag;
579
+ let error;
580
+ if (cachedFields) {
581
+ const columnFormat = options.columnFormat != null
582
+ ? options.columnFormat
583
+ : DEFAULT_COLUMN_FORMAT;
584
+ const cached = this._executeReusedParserCache.get(cachedFields);
585
+ if (cached &&
586
+ cached.typeMap === typeMap &&
587
+ columnFormatsEqual(cached.columnFormat, columnFormat)) {
588
+ parsers = cached.parsers;
589
+ resultFields = cached.resultFields;
590
+ }
591
+ else {
592
+ const fields = cachedFields.map((f, i) => ({
593
+ ...f,
594
+ format: Array.isArray(columnFormat)
595
+ ? columnFormat[i]
596
+ : columnFormat,
597
+ }));
598
+ parsers = getParsers(typeMap, fields);
599
+ resultFields = wrapRowDescription(typeMap, fields, columnFormat);
600
+ this._executeReusedParserCache.set(cachedFields, {
601
+ typeMap,
602
+ columnFormat,
603
+ parsers,
604
+ resultFields,
605
+ });
606
+ }
607
+ result.fields = resultFields;
608
+ result.rowType = options.objectRows ? 'object' : 'array';
609
+ }
610
+ this.runningQueryCount++;
611
+ await this.socket
612
+ .sendBindExecuteMessages({
613
+ bind: {
614
+ typeMap,
615
+ statement: statementName,
616
+ paramTypes,
617
+ params,
618
+ queryOptions: options,
619
+ },
620
+ execute: { fetchCount: options.fetchCount || 100 },
621
+ }, (code, msg, done) => {
622
+ switch (code) {
623
+ case Protocol.BackendMessageCode.BindComplete:
624
+ case Protocol.BackendMessageCode.NoticeResponse:
625
+ case Protocol.BackendMessageCode.PortalSuspended:
626
+ break;
627
+ case Protocol.BackendMessageCode.DataRow:
628
+ rows.push(msg);
629
+ break;
630
+ case Protocol.BackendMessageCode.CommandComplete:
631
+ commandTag = msg;
632
+ break;
633
+ case Protocol.BackendMessageCode.ErrorResponse:
634
+ error = msg;
635
+ break;
636
+ case Protocol.BackendMessageCode.ReadyForQuery:
637
+ this.transactionStatus = msg.status;
638
+ done(error);
639
+ break;
640
+ default:
641
+ done(new Error(`Server returned unexpected response message (${String.fromCharCode(code)})`));
642
+ }
643
+ })
644
+ .finally(() => {
645
+ this.runningQueryCount--;
646
+ });
647
+ if (commandTag?.command)
648
+ result.command = commandTag.command;
649
+ if (resultFields && parsers) {
650
+ if (!result.command)
651
+ result.command = 'SELECT';
652
+ result.rows = rows;
653
+ const l = rows.length;
654
+ let i;
655
+ for (i = 0; i < l; i++) {
656
+ rows[i] = options.objectRows
657
+ ? parseObjectRow(parsers, rows[i].data, rows[i].columnCount, options, resultFields)
658
+ : parseRow(parsers, rows[i].data, rows[i].columnCount, options);
659
+ }
660
+ }
661
+ if (result.command === 'DELETE' ||
662
+ result.command === 'INSERT' ||
663
+ result.command === 'UPDATE') {
664
+ result.rowsAffected = commandTag?.rowCount;
665
+ }
666
+ if (timingEnabled)
667
+ result.executeTime = performance.now() - startTime;
668
+ return result;
669
+ }
670
+ finally {
671
+ this.unref();
672
+ }
673
+ }
674
+ emit(event, ...args) {
675
+ const handled = super.emit(event, ...args);
676
+ return this.owner ? this.owner.emit(event, ...args) || handled : handled;
677
+ }
248
678
  _onError(err) {
249
679
  if (this.socket.state !== ConnectionState.READY)
250
680
  return;