postgrejs 2.23.1 → 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.
- package/CHANGELOG.md +152 -1
- package/LICENSE +26 -17
- package/README.md +195 -175
- package/connection/connection.d.ts +152 -13
- package/connection/connection.js +296 -49
- package/connection/copy-stream.d.ts +88 -0
- package/connection/copy-stream.js +244 -0
- package/connection/cursor.js +20 -13
- package/connection/intl-connection.d.ts +68 -3
- package/connection/intl-connection.js +414 -57
- package/connection/large-object.d.ts +65 -0
- package/connection/large-object.js +151 -0
- package/connection/logical-replication.d.ts +121 -0
- package/connection/logical-replication.js +279 -0
- package/connection/pool.d.ts +61 -4
- package/connection/pool.js +177 -29
- package/connection/portal.d.ts +18 -4
- package/connection/portal.js +53 -59
- package/connection/prepared-statement.d.ts +22 -1
- package/connection/prepared-statement.js +117 -97
- package/data-type-map.js +4 -2
- package/data-types/bool-type.js +14 -10
- package/data-types/box-type.js +15 -8
- package/data-types/bytea-type.js +9 -5
- package/data-types/char-type.js +11 -5
- package/data-types/circle-type.js +15 -7
- package/data-types/date-type.js +25 -19
- package/data-types/float4-type.js +10 -3
- package/data-types/float8-type.js +12 -3
- package/data-types/int2-type.js +9 -4
- package/data-types/int2-vector-type.js +9 -4
- package/data-types/int4-type.js +11 -5
- package/data-types/int8-type.js +22 -7
- package/data-types/json-type.js +17 -8
- package/data-types/jsonb-type.js +20 -14
- package/data-types/lseg-type.js +16 -8
- package/data-types/numeric-type.d.ts +8 -0
- package/data-types/numeric-type.js +112 -8
- package/data-types/oid-type.d.ts +0 -1
- package/data-types/oid-type.js +9 -10
- package/data-types/oid-vector-type.d.ts +9 -0
- package/data-types/oid-vector-type.js +51 -0
- package/data-types/point-type.js +14 -6
- package/data-types/time-type.js +20 -13
- package/data-types/timestamp-type.js +32 -25
- package/data-types/timestamptz-type.js +33 -21
- package/data-types/uuid-type.js +21 -11
- package/data-types/varchar-type.js +16 -4
- package/index.d.ts +5 -0
- package/index.js +5 -0
- package/interfaces/data-type.d.ts +5 -3
- package/interfaces/database-connection-params.d.ts +95 -0
- package/interfaces/query-options.d.ts +17 -0
- package/interfaces/script-execute-options.d.ts +17 -0
- package/package.json +2 -3
- package/protocol/backend.d.ts +6 -1
- package/protocol/backend.js +70 -45
- package/protocol/buffer-reader.d.ts +0 -2
- package/protocol/buffer-reader.js +3 -10
- package/protocol/cert-signature.d.ts +12 -0
- package/protocol/cert-signature.js +114 -0
- package/protocol/frontend.d.ts +16 -0
- package/protocol/frontend.js +57 -6
- package/protocol/pg-socket.d.ts +220 -9
- package/protocol/pg-socket.js +539 -102
- package/protocol/pgoutput.d.ts +76 -0
- package/protocol/pgoutput.js +155 -0
- package/protocol/protocol.d.ts +3 -4
- package/protocol/protocol.js +1 -1
- package/protocol/sasl.d.ts +11 -1
- package/protocol/sasl.js +35 -4
- package/protocol/smart-buffer.d.ts +0 -2
- package/protocol/smart-buffer.js +11 -16
- package/types.d.ts +5 -3
- package/util/abort-signal.d.ts +24 -0
- package/util/abort-signal.js +63 -0
- package/util/array-calculatedim.js +3 -1
- package/util/connection-config.js +77 -3
- package/util/decode-binaryarray.d.ts +1 -1
- package/util/decode-binaryarray.js +15 -6
- package/util/encode-binaryarray.js +7 -3
- package/util/escape-identifier.d.ts +14 -0
- package/util/escape-identifier.js +32 -0
- package/util/escape-literal.js +3 -0
- package/util/fast-parseint.d.ts +1 -0
- package/util/fast-parseint.js +12 -17
- package/util/format-datetime.d.ts +10 -0
- package/util/format-datetime.js +71 -0
- package/util/get-parsers.js +31 -10
- package/util/parse-array.js +2 -2
- package/util/parse-bytea.d.ts +2 -0
- package/util/parse-bytea.js +83 -0
- package/util/parse-datetime.d.ts +3 -1
- package/util/parse-datetime.js +86 -17
- package/util/parse-row.d.ts +3 -1
- package/util/parse-row.js +60 -5
- package/util/parse-time.js +8 -1
- package/util/sql-tag.d.ts +60 -0
- package/util/sql-tag.js +202 -0
- package/util/starts-transaction.d.ts +38 -0
- package/util/starts-transaction.js +148 -0
- package/util/stringify-arrayliteral.js +13 -1
- package/util/wrap-row-description.js +13 -4
- package/util/convert-row-to-object.d.ts +0 -2
- package/util/convert-row-to-object.js +0 -9
|
@@ -1,24 +1,40 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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 (
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
276
|
+
this.runningQueryCount++;
|
|
277
|
+
return await this.socket.sendQueryMessage(sql, (code, msg, done) => {
|
|
189
278
|
switch (code) {
|
|
190
|
-
case Protocol.BackendMessageCode.
|
|
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
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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 =
|
|
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 =
|
|
329
|
+
currentStart = performance.now();
|
|
231
330
|
break;
|
|
232
331
|
case Protocol.BackendMessageCode.ReadyForQuery:
|
|
233
332
|
this.transactionStatus = msg.status;
|
|
234
|
-
|
|
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
|
+
}
|