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.
- package/CHANGELOG.md +171 -1
- package/LICENSE +26 -17
- package/README.md +195 -175
- package/connection/connection.d.ts +169 -16
- package/connection/connection.js +318 -56
- 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 +113 -5
- package/connection/intl-connection.js +500 -70
- 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 +129 -109
- 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 +118 -9
- 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/command-result.d.ts +2 -1
- package/interfaces/data-type.d.ts +5 -3
- package/interfaces/database-connection-params.d.ts +108 -0
- package/interfaces/query-options.d.ts +23 -0
- package/interfaces/script-execute-options.d.ts +23 -0
- package/interfaces/script-result.d.ts +3 -2
- 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 +70 -11
- 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 +36 -5
- 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 +34 -0
- package/util/abort-signal.js +76 -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,41 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if (
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
188
|
-
|
|
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.
|
|
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
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
387
|
+
if (timingEnabled)
|
|
388
|
+
current.executeTime = performance.now() - currentStart;
|
|
223
389
|
if (current.rows)
|
|
224
390
|
current.rowType =
|
|
225
|
-
|
|
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
|
-
|
|
396
|
+
if (timingEnabled)
|
|
397
|
+
currentStart = performance.now();
|
|
231
398
|
break;
|
|
232
399
|
case Protocol.BackendMessageCode.ReadyForQuery:
|
|
233
400
|
this.transactionStatus = msg.status;
|
|
234
|
-
|
|
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;
|