postgrejs 2.23.0 → 3.0.1

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 (109) hide show
  1. package/CHANGELOG.md +162 -1
  2. package/LICENSE +26 -17
  3. package/README.md +195 -175
  4. package/connection/connection.d.ts +152 -13
  5. package/connection/connection.js +296 -49
  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 +68 -3
  10. package/connection/intl-connection.js +414 -57
  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 +117 -97
  21. package/data-type-map.d.ts +2 -2
  22. package/data-type-map.js +4 -2
  23. package/data-types/bool-type.js +14 -10
  24. package/data-types/box-type.js +15 -8
  25. package/data-types/bytea-type.js +9 -5
  26. package/data-types/char-type.js +11 -5
  27. package/data-types/circle-type.js +15 -7
  28. package/data-types/date-type.js +25 -19
  29. package/data-types/float4-type.js +10 -3
  30. package/data-types/float8-type.js +12 -3
  31. package/data-types/int2-type.js +9 -4
  32. package/data-types/int2-vector-type.js +10 -4
  33. package/data-types/int4-type.js +11 -5
  34. package/data-types/int8-type.js +22 -7
  35. package/data-types/json-type.js +17 -8
  36. package/data-types/jsonb-type.js +20 -14
  37. package/data-types/lseg-type.js +16 -8
  38. package/data-types/numeric-type.d.ts +8 -0
  39. package/data-types/numeric-type.js +112 -8
  40. package/data-types/oid-type.d.ts +0 -1
  41. package/data-types/oid-type.js +9 -10
  42. package/data-types/oid-vector-type.d.ts +9 -0
  43. package/data-types/oid-vector-type.js +51 -0
  44. package/data-types/point-type.js +14 -6
  45. package/data-types/time-type.js +20 -13
  46. package/data-types/timestamp-type.js +32 -25
  47. package/data-types/timestamptz-type.d.ts +1 -1
  48. package/data-types/timestamptz-type.js +34 -21
  49. package/data-types/uuid-type.js +21 -11
  50. package/data-types/varchar-type.js +16 -4
  51. package/index.d.ts +5 -0
  52. package/index.js +5 -0
  53. package/interfaces/data-type.d.ts +5 -3
  54. package/interfaces/database-connection-params.d.ts +95 -0
  55. package/interfaces/query-options.d.ts +17 -0
  56. package/interfaces/script-execute-options.d.ts +17 -0
  57. package/package.json +3 -4
  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/database-error.js +1 -0
  65. package/protocol/frontend.d.ts +16 -0
  66. package/protocol/frontend.js +58 -6
  67. package/protocol/pg-socket.d.ts +221 -10
  68. package/protocol/pg-socket.js +539 -102
  69. package/protocol/pgoutput.d.ts +76 -0
  70. package/protocol/pgoutput.js +155 -0
  71. package/protocol/protocol.d.ts +3 -4
  72. package/protocol/protocol.js +1 -1
  73. package/protocol/sasl.d.ts +11 -1
  74. package/protocol/sasl.js +35 -4
  75. package/protocol/smart-buffer.d.ts +0 -2
  76. package/protocol/smart-buffer.js +11 -16
  77. package/types.d.ts +5 -3
  78. package/util/abort-signal.d.ts +24 -0
  79. package/util/abort-signal.js +63 -0
  80. package/util/array-calculatedim.js +3 -1
  81. package/util/bigint-methods.js +1 -1
  82. package/util/connection-config.js +77 -3
  83. package/util/decode-binaryarray.d.ts +1 -1
  84. package/util/decode-binaryarray.js +15 -6
  85. package/util/encode-binaryarray.js +7 -3
  86. package/util/escape-identifier.d.ts +14 -0
  87. package/util/escape-identifier.js +32 -0
  88. package/util/escape-literal.js +3 -0
  89. package/util/fast-parseint.d.ts +1 -0
  90. package/util/fast-parseint.js +12 -17
  91. package/util/format-datetime.d.ts +10 -0
  92. package/util/format-datetime.js +71 -0
  93. package/util/get-parsers.js +31 -10
  94. package/util/parse-array.js +2 -2
  95. package/util/parse-bytea.d.ts +2 -0
  96. package/util/parse-bytea.js +83 -0
  97. package/util/parse-datetime.d.ts +3 -1
  98. package/util/parse-datetime.js +86 -17
  99. package/util/parse-row.d.ts +3 -1
  100. package/util/parse-row.js +60 -5
  101. package/util/parse-time.js +8 -1
  102. package/util/sql-tag.d.ts +60 -0
  103. package/util/sql-tag.js +202 -0
  104. package/util/starts-transaction.d.ts +38 -0
  105. package/util/starts-transaction.js +148 -0
  106. package/util/stringify-arrayliteral.js +13 -1
  107. package/util/wrap-row-description.js +13 -4
  108. package/util/convert-row-to-object.d.ts +0 -2
  109. package/util/convert-row-to-object.js +0 -9
@@ -1,24 +1,40 @@
1
- import { TaskQueue } from 'power-tasks';
1
+ import { performance } from 'node:perf_hooks';
2
2
  import { coerceToBoolean } from 'putil-varhelpers';
3
- import { ConnectionState } from '../constants.js';
3
+ import { ConnectionState, DEFAULT_COLUMN_FORMAT } from '../constants.js';
4
4
  import { GlobalTypeMap } from '../data-type-map.js';
5
5
  import { PgSocket } from '../protocol/pg-socket.js';
6
6
  import { Protocol } from '../protocol/protocol.js';
7
7
  import { SafeEventEmitter } from '../safe-event-emitter.js';
8
8
  import { getConnectionConfig } from '../util/connection-config.js';
9
- import { convertRowToObject } from '../util/convert-row-to-object.js';
10
9
  import { escapeLiteral } from '../util/escape-literal.js';
11
10
  import { getParsers } from '../util/get-parsers.js';
12
- import { parseRow } from '../util/parse-row.js';
11
+ import { parseObjectRow, parseRow } from '../util/parse-row.js';
13
12
  import { wrapRowDescription } from '../util/wrap-row-description.js';
13
+ import { CopyFromStream, CopyToStream } from './copy-stream.js';
14
14
  const DataFormat = Protocol.DataFormat;
15
+ function columnFormatsEqual(a, b) {
16
+ if (a === b)
17
+ return true;
18
+ if (!Array.isArray(a) || !Array.isArray(b))
19
+ return false;
20
+ if (a.length !== b.length)
21
+ return false;
22
+ const l = a.length;
23
+ let i;
24
+ for (i = 0; i < l; i++)
25
+ if (a[i] !== b[i])
26
+ return false;
27
+ return true;
28
+ }
15
29
  export class IntlConnection extends SafeEventEmitter {
30
+ _executeReusedParserCache = new WeakMap();
16
31
  _refCount = 0;
17
32
  _config;
18
33
  _onErrorSavePoint;
19
34
  transactionStatus = 'I';
20
35
  socket;
21
- statementQueue = new TaskQueue({ concurrency: 1 });
36
+ owner;
37
+ runningQueryCount = 0;
22
38
  constructor(config) {
23
39
  super();
24
40
  this._config = Object.freeze(getConnectionConfig(config));
@@ -76,58 +92,60 @@ export class IntlConnection extends SafeEventEmitter {
76
92
  async close() {
77
93
  if (this.state === ConnectionState.CLOSED)
78
94
  return;
79
- this.statementQueue.clearQueue();
80
95
  return new Promise(resolve => {
81
96
  if (this.socket.state === ConnectionState.CLOSED)
82
97
  return;
98
+ // 'close' is not emitted here - the constructor's own
99
+ // `this.socket.on('close', () => this.emit('close'))` already
100
+ // forwards it once the socket's real close event fires. Emitting it
101
+ // again right here (synchronously, before socket.close()'s
102
+ // destroy() has actually completed) used to fire 'close' on this
103
+ // IntlConnection TWICE per close() call - confirmed live: a single
104
+ // close() left two listeners registered on the far side (e.g. two
105
+ // NOTIFY deliveries after Pool's reconnect re-subscribed twice).
83
106
  this.socket.once('close', resolve);
84
107
  this.socket.sendTerminateMessage(() => {
85
108
  this.socket.close();
86
- this.emit('close');
87
109
  });
88
110
  });
89
111
  }
90
112
  async execute(sql, options, cb) {
91
113
  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;
114
+ const transactionCommand = sql.match(/^(\bBEGIN\b|\bCOMMIT\b|\bSTART\b|\bROLLBACK|SAVEPOINT|RELEASE\b)/i);
115
+ let beginFirst = false;
116
+ let commitLast = false;
117
+ if (!transactionCommand) {
118
+ if (!this.inTransaction &&
119
+ (options?.autoCommit != null
120
+ ? options?.autoCommit
121
+ : this.config.autoCommit) === false) {
122
+ beginFirst = true;
106
123
  }
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;
123
- }
124
- catch (e) {
125
- if (this.inTransaction && rollbackOnError)
126
- await this._execute('ROLLBACK TO ' + this._onErrorSavePoint + ';');
127
- throw e;
124
+ if (this.inTransaction && options?.autoCommit)
125
+ commitLast = true;
126
+ }
127
+ if (beginFirst)
128
+ await this._execute('BEGIN');
129
+ const rollbackOnError = !transactionCommand &&
130
+ (options?.rollbackOnError != null
131
+ ? options.rollbackOnError
132
+ : coerceToBoolean(this.config.rollbackOnError, true));
133
+ if (this.inTransaction && rollbackOnError)
134
+ await this._execute('SAVEPOINT ' + this._onErrorSavePoint);
135
+ try {
136
+ const result = await this._execute(sql, options, cb);
137
+ if (commitLast)
138
+ await this._execute('COMMIT');
139
+ else if (this.inTransaction && rollbackOnError) {
140
+ await this._execute('RELEASE ' + this._onErrorSavePoint + ';');
128
141
  }
129
- })
130
- .toPromise();
142
+ return result;
143
+ }
144
+ catch (e) {
145
+ if (this.inTransaction && rollbackOnError)
146
+ await this._execute('ROLLBACK TO ' + this._onErrorSavePoint + ';');
147
+ throw e;
148
+ }
131
149
  }
132
150
  async startTransaction() {
133
151
  if (!this.inTransaction)
@@ -146,6 +164,31 @@ export class IntlConnection extends SafeEventEmitter {
146
164
  if (this.inTransaction)
147
165
  await this.execute('ROLLBACK');
148
166
  }
167
+ /**
168
+ * Ends the current transaction as a prepared one: it stops being tied to
169
+ * this session and waits under `name` until some connection - not
170
+ * necessarily this one, and not necessarily this process - finishes it
171
+ * with commitPrepared() or rollbackPrepared().
172
+ *
173
+ * Goes through _execute() rather than execute(): PREPARE TRANSACTION is
174
+ * not in the latter's list of transaction commands, so it would be
175
+ * wrapped in a savepoint whose RELEASE then fails, the session no longer
176
+ * being in a transaction by that point.
177
+ *
178
+ * Requires `max_prepared_transactions` above zero on the server, which is
179
+ * not the default; the server's own error says so if it is not.
180
+ */
181
+ async prepareTransaction(name) {
182
+ await this._execute('PREPARE TRANSACTION ' + escapeLiteral(name));
183
+ }
184
+ /** Commits a transaction left waiting by prepareTransaction(). */
185
+ async commitPrepared(name) {
186
+ await this._execute('COMMIT PREPARED ' + escapeLiteral(name));
187
+ }
188
+ /** Discards a transaction left waiting by prepareTransaction(). */
189
+ async rollbackPrepared(name) {
190
+ await this._execute('ROLLBACK PREPARED ' + escapeLiteral(name));
191
+ }
149
192
  async rollbackToSavepoint(name) {
150
193
  if (!(name && name.match(/^[a-zA-Z]\w+$/)))
151
194
  throw new Error(`Invalid savepoint "${name}"`);
@@ -156,11 +199,17 @@ export class IntlConnection extends SafeEventEmitter {
156
199
  throw new Error(`Invalid savepoint "${name}"`);
157
200
  await this.execute('RELEASE SAVEPOINT ' + name, { autoCommit: false });
158
201
  }
202
+ /** Asks the server to cancel whatever this session is running. */
203
+ cancel() {
204
+ return this.socket.cancel();
205
+ }
159
206
  ref() {
160
207
  this._refCount++;
161
208
  }
162
209
  unref() {
163
210
  this._refCount--;
211
+ if (!this._refCount)
212
+ this.emit('idle');
164
213
  return !this._refCount;
165
214
  }
166
215
  assertConnected() {
@@ -169,27 +218,79 @@ export class IntlConnection extends SafeEventEmitter {
169
218
  if (this.state === ConnectionState.CLOSED)
170
219
  throw new Error('Connection closed');
171
220
  }
221
+ /**
222
+ * Runs a COPY ... TO STDOUT and hands back its bytes as they arrive.
223
+ * Resolves once the server has accepted the copy, not once it has
224
+ * finished - the whole point is not to hold the export in memory.
225
+ */
226
+ async copyTo(sql) {
227
+ this.assertConnected();
228
+ this.ref();
229
+ this.runningQueryCount++;
230
+ const stream = new CopyToStream(this.socket);
231
+ this.socket
232
+ .sendQueryMessage(sql, stream.capture)
233
+ .catch(err => stream.fail(err))
234
+ .finally(() => {
235
+ this.runningQueryCount--;
236
+ this.unref();
237
+ });
238
+ await stream.waitStarted();
239
+ return stream;
240
+ }
241
+ /**
242
+ * Runs a COPY ... FROM STDIN and hands back a stream to feed it.
243
+ * Resolves once the server is ready for data.
244
+ */
245
+ async copyFrom(sql) {
246
+ this.assertConnected();
247
+ this.ref();
248
+ this.runningQueryCount++;
249
+ const stream = new CopyFromStream(this.socket);
250
+ this.socket
251
+ .sendQueryMessage(sql, stream.capture)
252
+ .catch(err => stream.fail(err))
253
+ .finally(() => {
254
+ this.runningQueryCount--;
255
+ this.unref();
256
+ });
257
+ await stream.waitStarted();
258
+ return stream;
259
+ }
172
260
  async _execute(sql, options, cb) {
173
261
  this.ref();
174
262
  try {
175
- const startTime = Date.now();
263
+ const startTime = performance.now();
176
264
  const result = {
177
265
  totalCommands: 0,
178
266
  totalTime: 0,
179
267
  results: [],
180
268
  };
181
269
  const opts = options || {};
182
- this.socket.sendQueryMessage(sql);
183
- let currentStart = Date.now();
270
+ let currentStart = startTime;
184
271
  let parsers;
185
272
  let current = { command: undefined };
186
273
  let fields;
274
+ let error;
187
275
  const typeMap = opts.typeMap || GlobalTypeMap;
188
- return await this.socket.capture(async (code, msg, done) => {
276
+ this.runningQueryCount++;
277
+ return await this.socket.sendQueryMessage(sql, (code, msg, done) => {
189
278
  switch (code) {
190
- case Protocol.BackendMessageCode.NoticeResponse:
279
+ case Protocol.BackendMessageCode.ErrorResponse:
280
+ error = msg;
281
+ break;
191
282
  case Protocol.BackendMessageCode.CopyInResponse:
283
+ error =
284
+ error ||
285
+ new Error('COPY FROM STDIN is not supported by execute() - use copyFrom() instead');
286
+ this.socket.sendCopyFail(error.message);
287
+ break;
192
288
  case Protocol.BackendMessageCode.CopyOutResponse:
289
+ error =
290
+ error ||
291
+ new Error('COPY TO STDOUT is not supported by execute() - use copyTo() instead');
292
+ break;
293
+ case Protocol.BackendMessageCode.NoticeResponse:
193
294
  case Protocol.BackendMessageCode.EmptyQueryResponse:
194
295
  break;
195
296
  case Protocol.BackendMessageCode.RowDescription:
@@ -200,11 +301,9 @@ export class IntlConnection extends SafeEventEmitter {
200
301
  break;
201
302
  case Protocol.BackendMessageCode.DataRow:
202
303
  {
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);
304
+ const row = opts.objectRows && current.fields
305
+ ? parseObjectRow(parsers, msg.data, msg.columnCount, opts, current.fields)
306
+ : parseRow(parsers, msg.data, msg.columnCount, opts);
208
307
  if (cb)
209
308
  cb('row', row);
210
309
  current.rows = current.rows || [];
@@ -219,7 +318,7 @@ export class IntlConnection extends SafeEventEmitter {
219
318
  current.command === 'UPDATE') {
220
319
  current.rowsAffected = msg.rowCount;
221
320
  }
222
- current.executeTime = Date.now() - currentStart;
321
+ current.executeTime = performance.now() - currentStart;
223
322
  if (current.rows)
224
323
  current.rowType =
225
324
  opts.objectRows && current.fields ? 'object' : 'array';
@@ -227,11 +326,15 @@ export class IntlConnection extends SafeEventEmitter {
227
326
  if (cb)
228
327
  cb('command-complete', current);
229
328
  current = { command: undefined };
230
- currentStart = Date.now();
329
+ currentStart = performance.now();
231
330
  break;
232
331
  case Protocol.BackendMessageCode.ReadyForQuery:
233
332
  this.transactionStatus = msg.status;
234
- result.totalTime = Date.now() - startTime;
333
+ if (error) {
334
+ done(error);
335
+ break;
336
+ }
337
+ result.totalTime = performance.now() - startTime;
235
338
  // Ignore COMMIT command that we added to sql
236
339
  result.totalCommands = result.results.length;
237
340
  done(undefined, result);
@@ -241,10 +344,264 @@ export class IntlConnection extends SafeEventEmitter {
241
344
  }
242
345
  });
243
346
  }
347
+ finally {
348
+ this.runningQueryCount--;
349
+ this.unref();
350
+ }
351
+ }
352
+ /**
353
+ * One-shot Extended Query fast path: Parse+Bind+Describe+Execute+Sync as
354
+ * a single round trip (unnamed statement/portal, no Close needed), used
355
+ * by Connection.query() instead of prepare()+PreparedStatement.execute()
356
+ * +close()'s 7 round trips whenever there's no cursor and no explicit/
357
+ * active transaction to wrap (see Connection.query() for that gating).
358
+ */
359
+ async queryOnce(sql, paramTypes, params, options) {
360
+ this.assertConnected();
361
+ this.ref();
362
+ try {
363
+ const typeMap = options.typeMap || GlobalTypeMap;
364
+ const startTime = performance.now();
365
+ const result = { command: undefined };
366
+ const rows = [];
367
+ let parsers;
368
+ let resultFields;
369
+ let commandTag;
370
+ let error;
371
+ this.runningQueryCount++;
372
+ await this.socket
373
+ .sendExtendedQueryMessages({
374
+ parse: { sql, paramTypes },
375
+ bind: { typeMap, paramTypes, params, queryOptions: options },
376
+ describe: { type: 'P' },
377
+ execute: { fetchCount: options.fetchCount || 100 },
378
+ }, (code, msg, done) => {
379
+ switch (code) {
380
+ case Protocol.BackendMessageCode.ParseComplete:
381
+ case Protocol.BackendMessageCode.BindComplete:
382
+ case Protocol.BackendMessageCode.NoData:
383
+ case Protocol.BackendMessageCode.NoticeResponse:
384
+ case Protocol.BackendMessageCode.PortalSuspended:
385
+ break;
386
+ case Protocol.BackendMessageCode.RowDescription:
387
+ parsers = getParsers(typeMap, msg.fields);
388
+ resultFields = wrapRowDescription(typeMap, msg.fields, options.columnFormat || DEFAULT_COLUMN_FORMAT);
389
+ result.fields = resultFields;
390
+ result.rowType = options.objectRows ? 'object' : 'array';
391
+ break;
392
+ case Protocol.BackendMessageCode.DataRow:
393
+ rows.push(msg);
394
+ break;
395
+ case Protocol.BackendMessageCode.CommandComplete:
396
+ commandTag = msg;
397
+ break;
398
+ case Protocol.BackendMessageCode.ErrorResponse:
399
+ error = msg;
400
+ break;
401
+ case Protocol.BackendMessageCode.ReadyForQuery:
402
+ this.transactionStatus = msg.status;
403
+ done(error);
404
+ break;
405
+ default:
406
+ done(new Error(`Server returned unexpected response message (${String.fromCharCode(code)})`));
407
+ }
408
+ })
409
+ .finally(() => {
410
+ this.runningQueryCount--;
411
+ });
412
+ if (commandTag?.command)
413
+ result.command = commandTag.command;
414
+ if (resultFields && parsers) {
415
+ if (!result.command)
416
+ result.command = 'SELECT';
417
+ result.rows = rows;
418
+ const l = rows.length;
419
+ let i;
420
+ for (i = 0; i < l; i++) {
421
+ rows[i] = options.objectRows
422
+ ? parseObjectRow(parsers, rows[i].data, rows[i].columnCount, options, resultFields)
423
+ : parseRow(parsers, rows[i].data, rows[i].columnCount, options);
424
+ }
425
+ }
426
+ if (result.command === 'DELETE' ||
427
+ result.command === 'INSERT' ||
428
+ result.command === 'UPDATE') {
429
+ result.rowsAffected = commandTag?.rowCount;
430
+ }
431
+ result.executeTime = performance.now() - startTime;
432
+ return result;
433
+ }
434
+ finally {
435
+ this.unref();
436
+ }
437
+ }
438
+ /**
439
+ * Parse+Describe(statement)+Sync as a single round trip - used by
440
+ * PreparedStatement.prepare() instead of a separately-awaited Parse then
441
+ * a separately-awaited Sync, and fetches the RowDescription/NoData in the
442
+ * same round trip so the caller can cache it (see executeReused()) rather
443
+ * than every later execute() re-Describing its own portal.
444
+ */
445
+ async prepareOnce(sql, paramTypes, statementName) {
446
+ this.assertConnected();
447
+ this.ref();
448
+ try {
449
+ let fields;
450
+ let error;
451
+ this.runningQueryCount++;
452
+ await this.socket
453
+ .sendPrepareMessages({
454
+ parse: { statement: statementName, sql, paramTypes },
455
+ describe: { type: 'S', name: statementName },
456
+ }, (code, msg, done) => {
457
+ switch (code) {
458
+ case Protocol.BackendMessageCode.ParseComplete:
459
+ case Protocol.BackendMessageCode.ParameterDescription:
460
+ case Protocol.BackendMessageCode.NoData:
461
+ case Protocol.BackendMessageCode.NoticeResponse:
462
+ break;
463
+ case Protocol.BackendMessageCode.RowDescription:
464
+ fields = msg.fields;
465
+ break;
466
+ case Protocol.BackendMessageCode.ErrorResponse:
467
+ // See queryOnce()'s ErrorResponse case for why done() must
468
+ // not be called here.
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
+ return { fields };
483
+ }
244
484
  finally {
245
485
  this.unref();
246
486
  }
247
487
  }
488
+ /**
489
+ * Bind+Execute+Sync as a single round trip against an unnamed portal, for
490
+ * an already-prepared (named) statement - used by
491
+ * PreparedStatement._execute()'s non-cursor path instead of the 4-round-
492
+ * trip Portal.bind()/retrieveFields()/execute()/close() sequence. Takes
493
+ * the RowDescription fields prepareOnce() already fetched instead of
494
+ * re-Describing a fresh portal on every call.
495
+ */
496
+ async executeReused(statementName, cachedFields, paramTypes, params, options) {
497
+ this.assertConnected();
498
+ this.ref();
499
+ try {
500
+ const typeMap = options.typeMap || GlobalTypeMap;
501
+ const startTime = performance.now();
502
+ const result = { command: undefined };
503
+ const rows = [];
504
+ let parsers;
505
+ let resultFields;
506
+ let commandTag;
507
+ let error;
508
+ if (cachedFields) {
509
+ const columnFormat = options.columnFormat != null
510
+ ? options.columnFormat
511
+ : DEFAULT_COLUMN_FORMAT;
512
+ const cached = this._executeReusedParserCache.get(cachedFields);
513
+ if (cached &&
514
+ cached.typeMap === typeMap &&
515
+ columnFormatsEqual(cached.columnFormat, columnFormat)) {
516
+ parsers = cached.parsers;
517
+ resultFields = cached.resultFields;
518
+ }
519
+ else {
520
+ const fields = cachedFields.map((f, i) => ({
521
+ ...f,
522
+ format: Array.isArray(columnFormat)
523
+ ? columnFormat[i]
524
+ : columnFormat,
525
+ }));
526
+ parsers = getParsers(typeMap, fields);
527
+ resultFields = wrapRowDescription(typeMap, fields, columnFormat);
528
+ this._executeReusedParserCache.set(cachedFields, {
529
+ typeMap,
530
+ columnFormat,
531
+ parsers,
532
+ resultFields,
533
+ });
534
+ }
535
+ result.fields = resultFields;
536
+ result.rowType = options.objectRows ? 'object' : 'array';
537
+ }
538
+ this.runningQueryCount++;
539
+ await this.socket
540
+ .sendBindExecuteMessages({
541
+ bind: {
542
+ typeMap,
543
+ statement: statementName,
544
+ paramTypes,
545
+ params,
546
+ queryOptions: options,
547
+ },
548
+ execute: { fetchCount: options.fetchCount || 100 },
549
+ }, (code, msg, done) => {
550
+ switch (code) {
551
+ case Protocol.BackendMessageCode.BindComplete:
552
+ case Protocol.BackendMessageCode.NoticeResponse:
553
+ case Protocol.BackendMessageCode.PortalSuspended:
554
+ break;
555
+ case Protocol.BackendMessageCode.DataRow:
556
+ rows.push(msg);
557
+ break;
558
+ case Protocol.BackendMessageCode.CommandComplete:
559
+ commandTag = msg;
560
+ break;
561
+ case Protocol.BackendMessageCode.ErrorResponse:
562
+ error = msg;
563
+ break;
564
+ case Protocol.BackendMessageCode.ReadyForQuery:
565
+ this.transactionStatus = msg.status;
566
+ done(error);
567
+ break;
568
+ default:
569
+ done(new Error(`Server returned unexpected response message (${String.fromCharCode(code)})`));
570
+ }
571
+ })
572
+ .finally(() => {
573
+ this.runningQueryCount--;
574
+ });
575
+ if (commandTag?.command)
576
+ result.command = commandTag.command;
577
+ if (resultFields && parsers) {
578
+ if (!result.command)
579
+ result.command = 'SELECT';
580
+ result.rows = rows;
581
+ const l = rows.length;
582
+ let i;
583
+ for (i = 0; i < l; i++) {
584
+ rows[i] = options.objectRows
585
+ ? parseObjectRow(parsers, rows[i].data, rows[i].columnCount, options, resultFields)
586
+ : parseRow(parsers, rows[i].data, rows[i].columnCount, options);
587
+ }
588
+ }
589
+ if (result.command === 'DELETE' ||
590
+ result.command === 'INSERT' ||
591
+ result.command === 'UPDATE') {
592
+ result.rowsAffected = commandTag?.rowCount;
593
+ }
594
+ result.executeTime = performance.now() - startTime;
595
+ return result;
596
+ }
597
+ finally {
598
+ this.unref();
599
+ }
600
+ }
601
+ emit(event, ...args) {
602
+ const handled = super.emit(event, ...args);
603
+ return this.owner ? this.owner.emit(event, ...args) || handled : handled;
604
+ }
248
605
  _onError(err) {
249
606
  if (this.socket.state !== ConnectionState.READY)
250
607
  return;
@@ -0,0 +1,65 @@
1
+ import { Readable, Writable } from 'node:stream';
2
+ import type { Connection } from './connection.js';
3
+ /** Open modes, as PostgreSQL's own INV_READ / INV_WRITE constants. */
4
+ export declare const LargeObjectMode: {
5
+ readonly read: 262144;
6
+ readonly write: 131072;
7
+ readonly readWrite: 393216;
8
+ };
9
+ export interface LargeObjectStreamOptions {
10
+ /**
11
+ * Bytes per round trip (default 65536). Each chunk is its own `loread`
12
+ * or `lowrite` call, so this trades memory against the number of
13
+ * queries.
14
+ */
15
+ chunkSize?: number;
16
+ }
17
+ /**
18
+ * A PostgreSQL large object: binary data stored outside the row it belongs
19
+ * to and reached through a file-like interface, so it can be seeked into
20
+ * and read a piece at a time rather than loaded whole the way a `bytea`
21
+ * column must be. Up to 4TB, against roughly 1GB for `bytea`.
22
+ *
23
+ * ```ts
24
+ * const lo = await connection.createLargeObject();
25
+ * await pipeline(fs.createReadStream('video.mp4'), lo.writable());
26
+ * await lo.close();
27
+ * // keep lo.oid somewhere - a large object belongs to no row
28
+ * ```
29
+ *
30
+ * The descriptor a large object is opened with only lives as long as the
31
+ * transaction that opened it, so one is started if the connection is not
32
+ * already in one, and close() commits that one - but never a transaction
33
+ * the caller started, which stays theirs to finish.
34
+ */
35
+ export declare class LargeObject {
36
+ readonly oid: number;
37
+ protected readonly _connection: Connection;
38
+ protected readonly _fd: number;
39
+ /** True when this object opened the transaction and so should commit it. */
40
+ protected readonly _ownsTransaction: boolean;
41
+ protected _closed: boolean;
42
+ constructor(connection: Connection, oid: number, fd: number, ownsTransaction: boolean);
43
+ /** Reads at most `length` bytes from the current position. */
44
+ read(length: number): Promise<Buffer>;
45
+ /** Writes at the current position and returns how many bytes went in. */
46
+ write(data: Buffer): Promise<number>;
47
+ /** Moves the read/write position. `whence`: 0 start, 1 current, 2 end. */
48
+ seek(offset: number | bigint, whence?: number): Promise<bigint>;
49
+ /** The current read/write position. */
50
+ tell(): Promise<bigint>;
51
+ /** Total size in bytes; leaves the position where it found it. */
52
+ size(): Promise<bigint>;
53
+ /** Cuts the object down to `length` bytes. */
54
+ truncate(length: number | bigint): Promise<void>;
55
+ /** Reads from the current position to the end. */
56
+ readable(options?: LargeObjectStreamOptions): Readable;
57
+ /** Writes from the current position onwards. */
58
+ writable(options?: LargeObjectStreamOptions): Writable;
59
+ /**
60
+ * Closes the descriptor, and commits the transaction if this object
61
+ * started it. Safe to call twice.
62
+ */
63
+ close(): Promise<void>;
64
+ protected _assertOpen(): void;
65
+ }