postgrejs 3.7.0 → 3.9.0
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 +53 -0
- package/README.md +50 -0
- package/connection/connection.d.ts +14 -2
- package/connection/connection.js +70 -10
- package/connection/intl-connection.d.ts +146 -5
- package/connection/intl-connection.js +485 -35
- package/connection/pool.d.ts +30 -6
- package/connection/pool.js +28 -3
- package/connection/prepared-statement.d.ts +11 -0
- package/connection/prepared-statement.js +39 -5
- package/data-type-map.d.ts +16 -0
- package/data-type-map.js +56 -12
- package/data-types/classes/numeric.d.ts +8 -4
- package/data-types/classes/numeric.js +8 -4
- package/data-types/date-type.js +2 -2
- package/data-types/int2-vector-type.js +4 -1
- package/data-types/money-type.d.ts +38 -0
- package/data-types/money-type.js +203 -0
- package/data-types/numeric-type.d.ts +2 -0
- package/data-types/numeric-type.js +1 -1
- package/data-types/oid-vector-type.js +4 -1
- package/data-types/timestamp-type.js +2 -2
- package/data-types/timestamptz-type.js +11 -2
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/interfaces/data-mapping-options.d.ts +27 -0
- package/interfaces/database-connection-params.d.ts +32 -5
- package/interfaces/query-options.d.ts +32 -0
- package/interfaces/script-execute-options.d.ts +32 -0
- package/interfaces/transaction-options.d.ts +27 -0
- package/interfaces/transaction-options.js +1 -0
- package/package.json +1 -1
- package/protocol/database-error.d.ts +15 -0
- package/protocol/database-error.js +19 -0
- package/protocol/frontend.js +17 -0
- package/protocol/pg-socket.js +7 -10
- package/util/array-leaf.d.ts +10 -0
- package/util/array-leaf.js +22 -0
- package/util/date-style.d.ts +47 -0
- package/util/date-style.js +123 -0
- package/util/decode-binaryarray.js +8 -1
- package/util/encode-binaryarray.d.ts +12 -1
- package/util/encode-binaryarray.js +13 -2
- package/util/format-datetime.d.ts +18 -0
- package/util/format-datetime.js +33 -0
- package/util/parse-datetime.d.ts +4 -3
- package/util/parse-datetime.js +34 -9
- package/util/resolve-column-formats.js +4 -0
- package/util/row-decoder.d.ts +24 -4
- package/util/row-decoder.js +25 -10
- package/util/sql-tag.js +16 -0
- package/util/stringify-for-sql.js +7 -0
- package/util/transaction-command.d.ts +7 -0
- package/util/transaction-command.js +32 -0
- package/util/unspecified-param.d.ts +27 -0
- package/util/unspecified-param.js +31 -0
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import { performance } from 'node:perf_hooks';
|
|
2
|
-
import { ConnectionState, DEFAULT_COLUMN_FORMAT } from '../constants.js';
|
|
2
|
+
import { ConnectionState, DataTypeOIDs, DEFAULT_COLUMN_FORMAT, } from '../constants.js';
|
|
3
3
|
import { GlobalTypeMap } from '../data-type-map.js';
|
|
4
|
+
import { parseMoneyFormat } from '../data-types/money-type.js';
|
|
4
5
|
import { DatabaseError } from '../protocol/database-error.js';
|
|
5
6
|
import { PgSocket } from '../protocol/pg-socket.js';
|
|
6
7
|
import { Protocol } from '../protocol/protocol.js';
|
|
7
8
|
import { SafeEventEmitter } from '../safe-event-emitter.js';
|
|
8
9
|
import { getConnectionConfig } from '../util/connection-config.js';
|
|
9
10
|
import { buildCopySql, buildProbeSql, writeCopyBinaryRows, } from '../util/copy-from-rows.js';
|
|
11
|
+
import { parseDateStyleSetting } from '../util/date-style.js';
|
|
10
12
|
import { escapeLiteral } from '../util/escape-literal.js';
|
|
11
13
|
import { getParsers } from '../util/get-parsers.js';
|
|
12
14
|
import { fetchAsStringEqual, resolveColumnFormats, } from '../util/resolve-column-formats.js';
|
|
13
|
-
import { resolveRowDecoder, resolveRowType } from '../util/row-decoder.js';
|
|
15
|
+
import { resolveRowDecoder, resolveRowType, } from '../util/row-decoder.js';
|
|
16
|
+
import { isTransactionCommand, refusesSavepoint, } from '../util/transaction-command.js';
|
|
14
17
|
import { wrapRowDescription } from '../util/wrap-row-description.js';
|
|
15
18
|
import { CopyFromStream, CopyToStream } from './copy-stream.js';
|
|
16
19
|
const DataFormat = Protocol.DataFormat;
|
|
@@ -77,6 +80,72 @@ function reportsRowsAffected(command) {
|
|
|
77
80
|
* it starts saving, which it makes back immediately.
|
|
78
81
|
*/
|
|
79
82
|
const PREPARE_AFTER_USES = 2;
|
|
83
|
+
/**
|
|
84
|
+
* Stands in for the command tag an empty statement never sends, so that
|
|
85
|
+
* the slot is taken and nothing behind it is mistaken for the caller's
|
|
86
|
+
* own command. Its empty name is what leaves `QueryResult.command`
|
|
87
|
+
* unset, which is what `pg` reports for the same statement.
|
|
88
|
+
*/
|
|
89
|
+
/** The connection-configuration keys that are per-statement defaults. */
|
|
90
|
+
const MAPPING_DEFAULT_KEYS = [
|
|
91
|
+
'typeMap',
|
|
92
|
+
'columnFormat',
|
|
93
|
+
'objectRows',
|
|
94
|
+
'rowDecoder',
|
|
95
|
+
'utcDates',
|
|
96
|
+
'fetchAsString',
|
|
97
|
+
'unknownTypesAsString',
|
|
98
|
+
'moneyFormat',
|
|
99
|
+
];
|
|
100
|
+
/**
|
|
101
|
+
* The mapping defaults out of a connection configuration, or undefined
|
|
102
|
+
* when it set none - so the merge can be skipped outright rather than
|
|
103
|
+
* walking an empty object on every statement.
|
|
104
|
+
*/
|
|
105
|
+
function pickMappingDefaults(config) {
|
|
106
|
+
let out;
|
|
107
|
+
let k;
|
|
108
|
+
let i;
|
|
109
|
+
for (i = 0; i < MAPPING_DEFAULT_KEYS.length; i++) {
|
|
110
|
+
k = MAPPING_DEFAULT_KEYS[i];
|
|
111
|
+
if (config[k] === undefined)
|
|
112
|
+
continue;
|
|
113
|
+
out = out || {};
|
|
114
|
+
out[k] = config[k];
|
|
115
|
+
}
|
|
116
|
+
return out && Object.freeze(out);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* The isolation levels PostgreSQL takes, as the SQL that names them.
|
|
120
|
+
*
|
|
121
|
+
* A table rather than the caller's own string: this text is
|
|
122
|
+
* concatenated into a statement, and nothing that reaches it may come
|
|
123
|
+
* from outside this file.
|
|
124
|
+
*/
|
|
125
|
+
const ISOLATION_LEVELS = {
|
|
126
|
+
serializable: 'SERIALIZABLE',
|
|
127
|
+
'repeatable read': 'REPEATABLE READ',
|
|
128
|
+
'read committed': 'READ COMMITTED',
|
|
129
|
+
'read uncommitted': 'READ UNCOMMITTED',
|
|
130
|
+
};
|
|
131
|
+
/** `BEGIN`, carrying whatever modes the caller asked for. */
|
|
132
|
+
function beginStatement(options) {
|
|
133
|
+
if (!options)
|
|
134
|
+
return 'BEGIN';
|
|
135
|
+
let sql = 'BEGIN';
|
|
136
|
+
if (options.isolationLevel != null) {
|
|
137
|
+
const level = ISOLATION_LEVELS[options.isolationLevel.toLowerCase()];
|
|
138
|
+
if (!level)
|
|
139
|
+
throw new TypeError(`Unknown transaction isolation level "${options.isolationLevel}"`);
|
|
140
|
+
sql += ' ISOLATION LEVEL ' + level;
|
|
141
|
+
}
|
|
142
|
+
if (options.readOnly != null)
|
|
143
|
+
sql += options.readOnly ? ' READ ONLY' : ' READ WRITE';
|
|
144
|
+
if (options.deferrable != null)
|
|
145
|
+
sql += options.deferrable ? ' DEFERRABLE' : ' NOT DEFERRABLE';
|
|
146
|
+
return sql;
|
|
147
|
+
}
|
|
148
|
+
const EMPTY_QUERY_TAG = { command: '' };
|
|
80
149
|
/**
|
|
81
150
|
* SQL that manages transaction boundaries itself, and so must never be
|
|
82
151
|
* wrapped in a savepoint or an implicit BEGIN/COMMIT of ours.
|
|
@@ -89,7 +158,6 @@ const PREPARE_AFTER_USES = 2;
|
|
|
89
158
|
function preparedCacheKey(sql, paramTypes) {
|
|
90
159
|
return paramTypes?.length ? sql + '\u0000' + paramTypes.join(',') : sql;
|
|
91
160
|
}
|
|
92
|
-
const TRANSACTION_COMMAND_PATTERN = /^(\bBEGIN\b|\bCOMMIT\b|\bSTART\b|\bROLLBACK|SAVEPOINT|RELEASE\b)/i;
|
|
93
161
|
export class IntlConnection extends SafeEventEmitter {
|
|
94
162
|
/**
|
|
95
163
|
* Server-side prepared statements this connection has built, keyed by
|
|
@@ -102,6 +170,15 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
102
170
|
* this is per connection and dies with it.
|
|
103
171
|
*/
|
|
104
172
|
_preparedCache = new Map();
|
|
173
|
+
/**
|
|
174
|
+
* The names being given out right now, one per SQL. A burst of the
|
|
175
|
+
* same statement arrives before any of it is prepared, and without
|
|
176
|
+
* this every call in the burst prepares its own copy: measured at 50
|
|
177
|
+
* concurrent `select $1::int4`, 25 Parse+Describe round trips and 25
|
|
178
|
+
* named statements left on the server that this map's one entry could
|
|
179
|
+
* never close again.
|
|
180
|
+
*/
|
|
181
|
+
_preparing = new Map();
|
|
105
182
|
/** SQL seen once but not yet prepared - see PREPARE_AFTER_USES. */
|
|
106
183
|
_preparedCandidates = new Map();
|
|
107
184
|
_preparedCounter = 0;
|
|
@@ -111,6 +188,33 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
111
188
|
_savepointDepths = new Map();
|
|
112
189
|
_config;
|
|
113
190
|
_onErrorSavePoint;
|
|
191
|
+
/**
|
|
192
|
+
* The data-mapping options this connection was configured with, or
|
|
193
|
+
* undefined when it was given none - which is the common case, and
|
|
194
|
+
* the one that must not pay for this.
|
|
195
|
+
*/
|
|
196
|
+
_mappingDefaults;
|
|
197
|
+
/** The session's DateStyle as last reported, and what it parsed to. */
|
|
198
|
+
_dateStyleRaw;
|
|
199
|
+
_dateStyle;
|
|
200
|
+
/** The server's own money rendering - see ensureMoneyFormat(). */
|
|
201
|
+
_moneyFormat;
|
|
202
|
+
_moneyFormatPromise;
|
|
203
|
+
/** Statements currently holding the wire - see enterWire(). */
|
|
204
|
+
_wireUsers = 0;
|
|
205
|
+
_wireIdleWaiters;
|
|
206
|
+
_wireLock;
|
|
207
|
+
/** Bound once: enterWire()'s fast path hands out this same function. */
|
|
208
|
+
_releaseWire = () => {
|
|
209
|
+
if (--this._wireUsers || !this._wireIdleWaiters)
|
|
210
|
+
return;
|
|
211
|
+
const waiters = this._wireIdleWaiters;
|
|
212
|
+
this._wireIdleWaiters = undefined;
|
|
213
|
+
let i;
|
|
214
|
+
const l = waiters.length;
|
|
215
|
+
for (i = 0; i < l; i++)
|
|
216
|
+
waiters[i]();
|
|
217
|
+
};
|
|
114
218
|
transactionStatus = 'I';
|
|
115
219
|
socket;
|
|
116
220
|
owner;
|
|
@@ -118,12 +222,17 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
118
222
|
constructor(config) {
|
|
119
223
|
super();
|
|
120
224
|
this._config = Object.freeze(getConnectionConfig(config));
|
|
225
|
+
this._mappingDefaults = pickMappingDefaults(this._config);
|
|
121
226
|
this.socket = new PgSocket(this._config);
|
|
122
227
|
this.socket.on('error', err => this._onError(err));
|
|
123
228
|
// The reason travels with it: 'close' on its own cannot tell an
|
|
124
229
|
// ordinary shutdown from the backend being terminated under us.
|
|
125
230
|
this.socket.on('close', (reason) => this.emit('close', reason));
|
|
126
231
|
this.socket.on('notification', payload => this.emit('notification', payload));
|
|
232
|
+
// Covers a notice that arrives while no statement is running. One
|
|
233
|
+
// raised *by* a statement is dispatched to that statement's own
|
|
234
|
+
// message loop instead, which is why each of those forwards it too.
|
|
235
|
+
this.socket.on('notice', payload => this.emit('notice', payload));
|
|
127
236
|
this.socket.on('connecting', () => this.emit('connecting'));
|
|
128
237
|
this._onErrorSavePoint = 'SP_' + Math.round(Math.random() * 100000000);
|
|
129
238
|
}
|
|
@@ -197,7 +306,7 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
197
306
|
}
|
|
198
307
|
async execute(sql, options = {}, cb) {
|
|
199
308
|
this.assertConnected();
|
|
200
|
-
const transactionCommand =
|
|
309
|
+
const transactionCommand = isTransactionCommand(sql);
|
|
201
310
|
let beginFirst = false;
|
|
202
311
|
let commitLast = false;
|
|
203
312
|
const { autoCommit } = options;
|
|
@@ -221,7 +330,10 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
221
330
|
// out from under it) - but ordered so the cheap local read still
|
|
222
331
|
// short-circuits the getter whenever rollbackOnError is already
|
|
223
332
|
// false, which it is outside a transaction.
|
|
224
|
-
const rollbackOnError =
|
|
333
|
+
const rollbackOnError =
|
|
334
|
+
// Not `transactionCommand`: `SET TRANSACTION` still wants the
|
|
335
|
+
// implicit BEGIN above, and only the savepoint is fatal to it.
|
|
336
|
+
!refusesSavepoint(sql) &&
|
|
225
337
|
this.inTransaction &&
|
|
226
338
|
(options.rollbackOnError ?? this.config.rollbackOnError ?? true);
|
|
227
339
|
if (rollbackOnError && this.inTransaction)
|
|
@@ -295,10 +407,27 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
295
407
|
* that point). A matching number of commit() calls is then needed to
|
|
296
408
|
* actually commit - see commit().
|
|
297
409
|
*/
|
|
298
|
-
async startTransaction() {
|
|
410
|
+
async startTransaction(options) {
|
|
411
|
+
// A nested call is a depth increment, not a second BEGIN - so modes
|
|
412
|
+
// it carries could only be applied to a transaction someone else
|
|
413
|
+
// opened. PostgreSQL would take them: a nested `BEGIN ISOLATION
|
|
414
|
+
// LEVEL SERIALIZABLE` before any query has run *silently changes the
|
|
415
|
+
// outer transaction's level* (measured, only a warning about the
|
|
416
|
+
// redundant BEGIN), and after one it raises 25001. Neither is what
|
|
417
|
+
// the caller of a nested scope means, so this says so instead.
|
|
418
|
+
// Built before anything is counted: an isolation level this client
|
|
419
|
+
// does not know throws here, and a start that never happened must
|
|
420
|
+
// not leave the depth one higher than the transactions there are.
|
|
421
|
+
const sql = beginStatement(options);
|
|
422
|
+
if (options && this._transactionDepth > 0)
|
|
423
|
+
throw new Error('startTransaction() cannot set transaction modes on a transaction ' +
|
|
424
|
+
'that is already open - they belong to the BEGIN that opens it');
|
|
425
|
+
if (this.inTransaction) {
|
|
426
|
+
this._transactionDepth++;
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
await this.execute(sql);
|
|
299
430
|
this._transactionDepth++;
|
|
300
|
-
if (!this.inTransaction)
|
|
301
|
-
await this.execute('BEGIN');
|
|
302
431
|
}
|
|
303
432
|
/**
|
|
304
433
|
* Starts a savepoint, or - if one under the same `name` is already open -
|
|
@@ -410,6 +539,92 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
410
539
|
cancel() {
|
|
411
540
|
return this.socket.cancel();
|
|
412
541
|
}
|
|
542
|
+
/**
|
|
543
|
+
* Claims the wire for one statement, and hands back the function that
|
|
544
|
+
* releases it.
|
|
545
|
+
*
|
|
546
|
+
* Several statements share it by default: PostgreSQL correlates
|
|
547
|
+
* responses to requests by order, so a connection can carry more than
|
|
548
|
+
* one at a time and a caller that does not wait for each reply is
|
|
549
|
+
* measurably faster. `exclusive` is `pipeline: false` - the statement
|
|
550
|
+
* waits until nothing else is running here, and nothing else starts
|
|
551
|
+
* until it releases.
|
|
552
|
+
*
|
|
553
|
+
* Returns the release function itself rather than a promise of one
|
|
554
|
+
* whenever it can be taken without waiting, which is every statement
|
|
555
|
+
* on a connection nobody has asked to serialise - the common case, and
|
|
556
|
+
* the one that must not pay for this.
|
|
557
|
+
*/
|
|
558
|
+
enterWire(exclusive) {
|
|
559
|
+
if (!exclusive && !this._wireLock) {
|
|
560
|
+
this._wireUsers++;
|
|
561
|
+
return this._releaseWire;
|
|
562
|
+
}
|
|
563
|
+
return this._enterWireQueued(!!exclusive);
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Asks the server how it renders money, once per connection.
|
|
567
|
+
*
|
|
568
|
+
* `money` travels as an int64 of the smallest currency unit, and how
|
|
569
|
+
* many of those make one unit is `lc_monetary` - which the server
|
|
570
|
+
* does not report among its startup parameters, so `1234` on the wire
|
|
571
|
+
* is $12.34, ¥1234 or 1.234 KWD depending on a setting only the server
|
|
572
|
+
* knows. Asking it to render a value it already knows is the whole
|
|
573
|
+
* answer, and needs no locale data on this side.
|
|
574
|
+
*
|
|
575
|
+
* `1` is the value asked for because it cannot carry a thousands
|
|
576
|
+
* separator: whatever separator comes back can only be the decimal
|
|
577
|
+
* one, and the digits after it can only be the fraction. `$1.00` is
|
|
578
|
+
* two, `¥1` is none.
|
|
579
|
+
*/
|
|
580
|
+
async ensureMoneyFormat() {
|
|
581
|
+
if (this._moneyFormat)
|
|
582
|
+
return;
|
|
583
|
+
if (!this._moneyFormatPromise) {
|
|
584
|
+
// Assigned before the query starts: the query goes through
|
|
585
|
+
// queryCached like any other, and would ask for the format again
|
|
586
|
+
// on its way out.
|
|
587
|
+
let done;
|
|
588
|
+
this._moneyFormatPromise = new Promise(r => (done = r));
|
|
589
|
+
this.queryCached("select '1'::money::text", undefined, undefined, {
|
|
590
|
+
columnFormat: DataFormat.text,
|
|
591
|
+
})
|
|
592
|
+
.then(r => {
|
|
593
|
+
const text = r.rows?.[0]?.[0];
|
|
594
|
+
if (typeof text === 'string')
|
|
595
|
+
this._moneyFormat = parseMoneyFormat(text);
|
|
596
|
+
})
|
|
597
|
+
.catch(() => undefined)
|
|
598
|
+
.then(done);
|
|
599
|
+
}
|
|
600
|
+
return this._moneyFormatPromise;
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Whether these columns need the server's money format and this
|
|
604
|
+
* connection has not asked for it yet.
|
|
605
|
+
*
|
|
606
|
+
* The scan costs a look at each column's OID, and only until the
|
|
607
|
+
* answer is in - from then on the first test ends it.
|
|
608
|
+
*/
|
|
609
|
+
needsMoneyFormat(fields, options) {
|
|
610
|
+
if (this._moneyFormat || !fields)
|
|
611
|
+
return false;
|
|
612
|
+
const asString = options?.fetchAsString;
|
|
613
|
+
const l = fields.length;
|
|
614
|
+
let i;
|
|
615
|
+
let oid;
|
|
616
|
+
for (i = 0; i < l; i++) {
|
|
617
|
+
oid = fields[i].dataTypeId;
|
|
618
|
+
if (oid !== DataTypeOIDs.money && oid !== DataTypeOIDs._money)
|
|
619
|
+
continue;
|
|
620
|
+
// Named by fetchAsString, so the value goes back exactly as the
|
|
621
|
+
// server rendered it and no decoder asks what a minor unit is.
|
|
622
|
+
if (asString?.includes(oid))
|
|
623
|
+
continue;
|
|
624
|
+
return true;
|
|
625
|
+
}
|
|
626
|
+
return false;
|
|
627
|
+
}
|
|
413
628
|
ref() {
|
|
414
629
|
this._refCount++;
|
|
415
630
|
}
|
|
@@ -528,6 +743,7 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
528
743
|
return stream;
|
|
529
744
|
}
|
|
530
745
|
async _execute(sql, options = {}, cb) {
|
|
746
|
+
options = this.withDefaults(options);
|
|
531
747
|
this.ref();
|
|
532
748
|
try {
|
|
533
749
|
const timingEnabled = options.timing ?? this.config.timing ?? false;
|
|
@@ -540,11 +756,13 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
540
756
|
let parsers;
|
|
541
757
|
let current = { command: undefined };
|
|
542
758
|
let fields;
|
|
759
|
+
const deferred = [];
|
|
760
|
+
let currentDeferred;
|
|
543
761
|
let error;
|
|
544
762
|
const typeMap = options.typeMap || GlobalTypeMap;
|
|
545
763
|
const rowDecoder = resolveRowDecoder(options);
|
|
546
764
|
this.runningQueryCount++;
|
|
547
|
-
|
|
765
|
+
const scriptResult = await this.socket.sendQueryMessage(sql, (code, msg, done) => {
|
|
548
766
|
switch (code) {
|
|
549
767
|
case Protocol.BackendMessageCode.ErrorResponse:
|
|
550
768
|
error = msg;
|
|
@@ -561,6 +779,9 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
561
779
|
new Error('COPY TO STDOUT is not supported by execute() - use copyTo() instead');
|
|
562
780
|
break;
|
|
563
781
|
case Protocol.BackendMessageCode.NoticeResponse:
|
|
782
|
+
// Not an error: handed over, and the loop carries on.
|
|
783
|
+
this.emit('notice', msg);
|
|
784
|
+
break;
|
|
564
785
|
case Protocol.BackendMessageCode.EmptyQueryResponse:
|
|
565
786
|
break;
|
|
566
787
|
case Protocol.BackendMessageCode.RowDescription:
|
|
@@ -571,9 +792,25 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
571
792
|
parsers = getParsers(typeMap, fields, options);
|
|
572
793
|
current.fields = wrapRowDescription(typeMap, fields, DataFormat.text, options);
|
|
573
794
|
current.rows = [];
|
|
795
|
+
currentDeferred = this.needsMoneyFormat(fields, options)
|
|
796
|
+
? {
|
|
797
|
+
target: current,
|
|
798
|
+
raw: [],
|
|
799
|
+
parsers,
|
|
800
|
+
fields: current.fields,
|
|
801
|
+
}
|
|
802
|
+
: undefined;
|
|
803
|
+
if (currentDeferred)
|
|
804
|
+
deferred.push(currentDeferred);
|
|
574
805
|
break;
|
|
575
806
|
case Protocol.BackendMessageCode.DataRow:
|
|
576
807
|
{
|
|
808
|
+
// Set aside rather than read against a scale nobody has
|
|
809
|
+
// confirmed - see _decodeDeferredRows().
|
|
810
|
+
if (currentDeferred) {
|
|
811
|
+
currentDeferred.raw.push(msg);
|
|
812
|
+
break;
|
|
813
|
+
}
|
|
577
814
|
const row = rowDecoder.decode(parsers, msg.data, msg.columnCount, options, current.fields);
|
|
578
815
|
if (cb)
|
|
579
816
|
cb('row', row);
|
|
@@ -582,6 +819,7 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
582
819
|
}
|
|
583
820
|
break;
|
|
584
821
|
case Protocol.BackendMessageCode.CommandComplete:
|
|
822
|
+
currentDeferred = undefined;
|
|
585
823
|
// Ignore BEGIN command that we added to sql
|
|
586
824
|
current.command = msg.command;
|
|
587
825
|
if (reportsRowsAffected(current.command))
|
|
@@ -613,6 +851,9 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
613
851
|
break;
|
|
614
852
|
}
|
|
615
853
|
});
|
|
854
|
+
if (deferred.length)
|
|
855
|
+
await this._decodeDeferredRows(deferred, rowDecoder, options);
|
|
856
|
+
return scriptResult;
|
|
616
857
|
}
|
|
617
858
|
finally {
|
|
618
859
|
this.runningQueryCount--;
|
|
@@ -648,9 +889,14 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
648
889
|
* of failing every call from then on.
|
|
649
890
|
*/
|
|
650
891
|
async queryCached(sql, paramTypes, params, options) {
|
|
892
|
+
// Read before the merge below: a connection-wide fetchAsString must
|
|
893
|
+
// not give every statement a name on first sight - see
|
|
894
|
+
// _queryCached().
|
|
895
|
+
const callWantsFields = !!options.fetchAsString?.length || !!options.unknownTypesAsString;
|
|
896
|
+
options = this.withDefaults(options);
|
|
651
897
|
const savepoint = this._inlineSavepointFor(sql, options);
|
|
652
898
|
try {
|
|
653
|
-
return await this._queryCached(sql, paramTypes, params, options, savepoint);
|
|
899
|
+
return await this._queryCached(sql, paramTypes, params, options, savepoint, callWantsFields);
|
|
654
900
|
}
|
|
655
901
|
catch (e) {
|
|
656
902
|
// PostgreSQL throws away everything between a failed statement and
|
|
@@ -666,16 +912,19 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
666
912
|
/**
|
|
667
913
|
* The savepoint rollbackOnError calls for, or undefined when this call
|
|
668
914
|
* needs none: outside a transaction there is nothing to roll back to,
|
|
669
|
-
*
|
|
670
|
-
* boundaries (wrapping COMMIT in a savepoint would be nonsense)
|
|
915
|
+
* a statement that is itself a transaction command manages its own
|
|
916
|
+
* boundaries (wrapping COMMIT in a savepoint would be nonsense), and
|
|
917
|
+
* PostgreSQL refuses `SET TRANSACTION` inside one outright.
|
|
671
918
|
*/
|
|
672
919
|
_inlineSavepointFor(sql, options) {
|
|
673
|
-
if (!this.inTransaction ||
|
|
920
|
+
if (!this.inTransaction || refusesSavepoint(sql))
|
|
674
921
|
return undefined;
|
|
675
922
|
const on = options.rollbackOnError ?? this.config.rollbackOnError ?? true;
|
|
676
923
|
return on ? this._onErrorSavePoint : undefined;
|
|
677
924
|
}
|
|
678
|
-
async _queryCached(sql, paramTypes, params, options, savepoint
|
|
925
|
+
async _queryCached(sql, paramTypes, params, options, savepoint,
|
|
926
|
+
/** Whether the CALL asked for text columns, not the connection. */
|
|
927
|
+
callWantsFields) {
|
|
679
928
|
const enabled = options.prepare ?? this.config.prepare ?? true;
|
|
680
929
|
if (!enabled)
|
|
681
930
|
return this.queryOnce(sql, paramTypes, params, options, savepoint);
|
|
@@ -711,18 +960,56 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
711
960
|
// text (what queryOnce() falls back to).
|
|
712
961
|
// unknownTypesAsString needs the columns known before the Bind for
|
|
713
962
|
// the same reason fetchAsString does, so it earns a name the same way.
|
|
714
|
-
|
|
963
|
+
// ...unless the caller already asked for the whole row as text, in
|
|
964
|
+
// which case there is nothing for either option to resolve and the
|
|
965
|
+
// Parse+Describe would buy nothing. An array of formats is not the
|
|
966
|
+
// same thing: it can be shorter than the row, and the columns it
|
|
967
|
+
// does not cover are binary.
|
|
968
|
+
//
|
|
969
|
+
// And only for a list the CALL carried. The same list set once on
|
|
970
|
+
// the connection applies to every statement, so naming each one on
|
|
971
|
+
// first sight would double the round trips of a workload full of
|
|
972
|
+
// one-shot SQL and leave a server-side statement behind for each -
|
|
973
|
+
// measured at 5 distinct statements, 5 round trips against 10. A
|
|
974
|
+
// connection-wide list takes queryOnce()'s whole-row-text fallback
|
|
975
|
+
// on a statement's first run instead, and per-column codes from its
|
|
976
|
+
// second, which is where the name arrives anyway.
|
|
977
|
+
const needsFields = callWantsFields && options.columnFormat !== DataFormat.text;
|
|
715
978
|
if (!needsFields && !this._earnsAName(key))
|
|
716
979
|
return this.queryOnce(sql, paramTypes, params, options, savepoint);
|
|
980
|
+
let preparing = this._preparing.get(key);
|
|
981
|
+
if (!preparing) {
|
|
982
|
+
// Registered before the first await inside, so everything that
|
|
983
|
+
// arrives while this one is in flight finds it rather than
|
|
984
|
+
// starting a second one.
|
|
985
|
+
preparing = this._prepareForCache(key, sql, paramTypes);
|
|
986
|
+
this._preparing.set(key, preparing);
|
|
987
|
+
}
|
|
988
|
+
else if (!needsFields) {
|
|
989
|
+
// Someone else is already naming it. Waiting would cost a round
|
|
990
|
+
// trip the one-shot path does not - the name is for the calls
|
|
991
|
+
// after this burst, not for this one.
|
|
992
|
+
return this.queryOnce(sql, paramTypes, params, options, savepoint);
|
|
993
|
+
}
|
|
994
|
+
const entry = await preparing;
|
|
995
|
+
return this.executeReused(entry.name, entry.fields, paramTypes, params, options, savepoint);
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* Gives this SQL a name and caches it, once, however many callers are
|
|
999
|
+
* waiting. A Parse that fails caches nothing and reports itself; the
|
|
1000
|
+
* statement is simply never reused. It runs outside the savepoint, as
|
|
1001
|
+
* the prepare() the transaction path used to do ahead of its own
|
|
1002
|
+
* wrapper.
|
|
1003
|
+
*/
|
|
1004
|
+
_prepareForCache(key, sql, paramTypes) {
|
|
717
1005
|
const name = 'C_' + ++this._preparedCounter;
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
this.
|
|
725
|
-
return this.executeReused(name, entry.fields, paramTypes, params, options, savepoint);
|
|
1006
|
+
return (async () => {
|
|
1007
|
+
const { fields } = await this.prepareOnce(sql, paramTypes, name);
|
|
1008
|
+
const entry = { name, fields };
|
|
1009
|
+
await this._evictPreparedIfFull();
|
|
1010
|
+
this._preparedCache.set(key, entry);
|
|
1011
|
+
return entry;
|
|
1012
|
+
})().finally(() => this._preparing.delete(key));
|
|
726
1013
|
}
|
|
727
1014
|
/**
|
|
728
1015
|
* Counts one more use of this SQL and says whether it has now been seen
|
|
@@ -775,6 +1062,7 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
775
1062
|
await Promise.all([closed, synced]);
|
|
776
1063
|
}
|
|
777
1064
|
async queryOnce(sql, paramTypes, params, options, savepoint) {
|
|
1065
|
+
options = this.withDefaults(options);
|
|
778
1066
|
this.assertConnected();
|
|
779
1067
|
this.ref();
|
|
780
1068
|
try {
|
|
@@ -825,10 +1113,13 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
825
1113
|
after: savepoint ? 'RELEASE ' + savepoint : undefined,
|
|
826
1114
|
}, (code, msg, done) => {
|
|
827
1115
|
switch (code) {
|
|
1116
|
+
case Protocol.BackendMessageCode.NoticeResponse:
|
|
1117
|
+
// Not an error: handed over, and the loop carries on.
|
|
1118
|
+
this.emit('notice', msg);
|
|
1119
|
+
break;
|
|
828
1120
|
case Protocol.BackendMessageCode.ParseComplete:
|
|
829
1121
|
case Protocol.BackendMessageCode.BindComplete:
|
|
830
1122
|
case Protocol.BackendMessageCode.NoData:
|
|
831
|
-
case Protocol.BackendMessageCode.NoticeResponse:
|
|
832
1123
|
break;
|
|
833
1124
|
// The server stopped at the fetchCount limit rather than
|
|
834
1125
|
// running out of rows. The Sync that closes this call
|
|
@@ -846,11 +1137,17 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
846
1137
|
case Protocol.BackendMessageCode.DataRow:
|
|
847
1138
|
rows.push(msg);
|
|
848
1139
|
break;
|
|
1140
|
+
// An empty statement - '' or nothing but a comment - answers
|
|
1141
|
+
// with this *instead of* a CommandComplete, so it has to take
|
|
1142
|
+
// the same slot: a RELEASE riding along behind it would
|
|
1143
|
+
// otherwise be read as the caller's own command. The sentinel
|
|
1144
|
+
// carries no command name, which is what `pg` reports too.
|
|
1145
|
+
case Protocol.BackendMessageCode.EmptyQueryResponse:
|
|
849
1146
|
case Protocol.BackendMessageCode.CommandComplete:
|
|
850
1147
|
if (leadingCommandTags)
|
|
851
1148
|
leadingCommandTags--;
|
|
852
1149
|
else if (!commandTag)
|
|
853
|
-
commandTag = msg;
|
|
1150
|
+
commandTag = msg || EMPTY_QUERY_TAG;
|
|
854
1151
|
break;
|
|
855
1152
|
case Protocol.BackendMessageCode.ErrorResponse:
|
|
856
1153
|
error = msg;
|
|
@@ -868,6 +1165,12 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
868
1165
|
});
|
|
869
1166
|
if (commandTag?.command)
|
|
870
1167
|
result.command = commandTag.command;
|
|
1168
|
+
// The rows are still raw here, so the one question money cannot
|
|
1169
|
+
// answer for itself can still be asked before they are read.
|
|
1170
|
+
if (this.needsMoneyFormat(resultFields, options)) {
|
|
1171
|
+
await this.ensureMoneyFormat();
|
|
1172
|
+
options = this.withDefaults(options);
|
|
1173
|
+
}
|
|
871
1174
|
if (resultFields && parsers) {
|
|
872
1175
|
if (!result.command)
|
|
873
1176
|
result.command = 'SELECT';
|
|
@@ -910,10 +1213,13 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
910
1213
|
describe: { type: 'S', name: statementName },
|
|
911
1214
|
}, (code, msg, done) => {
|
|
912
1215
|
switch (code) {
|
|
1216
|
+
case Protocol.BackendMessageCode.NoticeResponse:
|
|
1217
|
+
// Not an error: handed over, and the loop carries on.
|
|
1218
|
+
this.emit('notice', msg);
|
|
1219
|
+
break;
|
|
913
1220
|
case Protocol.BackendMessageCode.ParseComplete:
|
|
914
1221
|
case Protocol.BackendMessageCode.ParameterDescription:
|
|
915
1222
|
case Protocol.BackendMessageCode.NoData:
|
|
916
|
-
case Protocol.BackendMessageCode.NoticeResponse:
|
|
917
1223
|
break;
|
|
918
1224
|
case Protocol.BackendMessageCode.RowDescription:
|
|
919
1225
|
fields = msg.fields;
|
|
@@ -1002,6 +1308,7 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
1002
1308
|
* that arrive before one belong to the statement it closes.
|
|
1003
1309
|
*/
|
|
1004
1310
|
async executePipeline(requests, options) {
|
|
1311
|
+
options = this.withDefaults(options);
|
|
1005
1312
|
this.assertConnected();
|
|
1006
1313
|
this.ref();
|
|
1007
1314
|
try {
|
|
@@ -1012,6 +1319,7 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
1012
1319
|
const results = [];
|
|
1013
1320
|
let pendingFields;
|
|
1014
1321
|
let pendingRows;
|
|
1322
|
+
const deferred = [];
|
|
1015
1323
|
let error;
|
|
1016
1324
|
// Plan every statement against the prepared statement cache before
|
|
1017
1325
|
// writing anything: one that is already prepared binds to its name
|
|
@@ -1091,9 +1399,12 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
1091
1399
|
await this.socket
|
|
1092
1400
|
.sendPipelineMessages({ statements }, (code, msg, done) => {
|
|
1093
1401
|
switch (code) {
|
|
1402
|
+
case Protocol.BackendMessageCode.NoticeResponse:
|
|
1403
|
+
// Not an error: handed over, and the loop carries on.
|
|
1404
|
+
this.emit('notice', msg);
|
|
1405
|
+
break;
|
|
1094
1406
|
case Protocol.BackendMessageCode.ParseComplete:
|
|
1095
1407
|
case Protocol.BackendMessageCode.BindComplete:
|
|
1096
|
-
case Protocol.BackendMessageCode.NoticeResponse:
|
|
1097
1408
|
break;
|
|
1098
1409
|
case Protocol.BackendMessageCode.NoData:
|
|
1099
1410
|
pendingFields = undefined;
|
|
@@ -1122,12 +1433,24 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
1122
1433
|
if (!result.command)
|
|
1123
1434
|
result.command = 'SELECT';
|
|
1124
1435
|
const rows = pendingRows || [];
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1436
|
+
if (this.needsMoneyFormat(fields, options)) {
|
|
1437
|
+
// Set aside: see _decodeDeferredRows().
|
|
1438
|
+
result.rows = [];
|
|
1439
|
+
deferred.push({
|
|
1440
|
+
target: result,
|
|
1441
|
+
raw: rows,
|
|
1442
|
+
parsers,
|
|
1443
|
+
fields: resultFields,
|
|
1444
|
+
});
|
|
1445
|
+
}
|
|
1446
|
+
else {
|
|
1447
|
+
const l = rows.length;
|
|
1448
|
+
let i;
|
|
1449
|
+
for (i = 0; i < l; i++) {
|
|
1450
|
+
rows[i] = rowDecoder.decode(parsers, rows[i].data, rows[i].columnCount, options, resultFields);
|
|
1451
|
+
}
|
|
1452
|
+
result.rows = rows;
|
|
1129
1453
|
}
|
|
1130
|
-
result.rows = rows;
|
|
1131
1454
|
}
|
|
1132
1455
|
if (reportsRowsAffected(result.command))
|
|
1133
1456
|
result.rowsAffected = msg.rowCount;
|
|
@@ -1185,6 +1508,8 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
1185
1508
|
fields: promo.plan.fields,
|
|
1186
1509
|
});
|
|
1187
1510
|
}
|
|
1511
|
+
if (deferred.length)
|
|
1512
|
+
await this._decodeDeferredRows(deferred, rowDecoder, options);
|
|
1188
1513
|
if (timingEnabled) {
|
|
1189
1514
|
const elapsed = performance.now() - startTime;
|
|
1190
1515
|
const l = results.length;
|
|
@@ -1199,6 +1524,7 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
1199
1524
|
}
|
|
1200
1525
|
}
|
|
1201
1526
|
async executeBatchReused(statementName, cachedFields, paramTypes, paramSets, options) {
|
|
1527
|
+
options = this.withDefaults(options);
|
|
1202
1528
|
this.assertConnected();
|
|
1203
1529
|
this.ref();
|
|
1204
1530
|
try {
|
|
@@ -1234,8 +1560,11 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
1234
1560
|
execute: { fetchCount: 0 },
|
|
1235
1561
|
}, (code, msg, done) => {
|
|
1236
1562
|
switch (code) {
|
|
1237
|
-
case Protocol.BackendMessageCode.BindComplete:
|
|
1238
1563
|
case Protocol.BackendMessageCode.NoticeResponse:
|
|
1564
|
+
// Not an error: handed over, and the loop carries on.
|
|
1565
|
+
this.emit('notice', msg);
|
|
1566
|
+
break;
|
|
1567
|
+
case Protocol.BackendMessageCode.BindComplete:
|
|
1239
1568
|
break;
|
|
1240
1569
|
case Protocol.BackendMessageCode.DataRow:
|
|
1241
1570
|
(pendingRows || (pendingRows = [])).push(msg);
|
|
@@ -1301,6 +1630,7 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
1301
1630
|
}
|
|
1302
1631
|
}
|
|
1303
1632
|
async executeReused(statementName, cachedFields, paramTypes, params, options, savepoint) {
|
|
1633
|
+
options = this.withDefaults(options);
|
|
1304
1634
|
this.assertConnected();
|
|
1305
1635
|
this.ref();
|
|
1306
1636
|
try {
|
|
@@ -1348,9 +1678,12 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
1348
1678
|
after: savepoint ? 'RELEASE ' + savepoint : undefined,
|
|
1349
1679
|
}, (code, msg, done) => {
|
|
1350
1680
|
switch (code) {
|
|
1681
|
+
case Protocol.BackendMessageCode.NoticeResponse:
|
|
1682
|
+
// Not an error: handed over, and the loop carries on.
|
|
1683
|
+
this.emit('notice', msg);
|
|
1684
|
+
break;
|
|
1351
1685
|
case Protocol.BackendMessageCode.ParseComplete:
|
|
1352
1686
|
case Protocol.BackendMessageCode.BindComplete:
|
|
1353
|
-
case Protocol.BackendMessageCode.NoticeResponse:
|
|
1354
1687
|
break;
|
|
1355
1688
|
// See queryOnce() for what a suspended portal means here.
|
|
1356
1689
|
case Protocol.BackendMessageCode.PortalSuspended:
|
|
@@ -1359,6 +1692,8 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
1359
1692
|
case Protocol.BackendMessageCode.DataRow:
|
|
1360
1693
|
rows.push(msg);
|
|
1361
1694
|
break;
|
|
1695
|
+
// See queryOnce() for why an empty statement lands here.
|
|
1696
|
+
case Protocol.BackendMessageCode.EmptyQueryResponse:
|
|
1362
1697
|
case Protocol.BackendMessageCode.CommandComplete:
|
|
1363
1698
|
// Three statements can answer here when a savepoint rides
|
|
1364
1699
|
// along: SAVEPOINT's tag, then the caller's own, then
|
|
@@ -1367,7 +1702,7 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
1367
1702
|
if (leadingCommandTags)
|
|
1368
1703
|
leadingCommandTags--;
|
|
1369
1704
|
else if (!commandTag)
|
|
1370
|
-
commandTag = msg;
|
|
1705
|
+
commandTag = msg || EMPTY_QUERY_TAG;
|
|
1371
1706
|
break;
|
|
1372
1707
|
case Protocol.BackendMessageCode.ErrorResponse:
|
|
1373
1708
|
error = msg;
|
|
@@ -1385,6 +1720,12 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
1385
1720
|
});
|
|
1386
1721
|
if (commandTag?.command)
|
|
1387
1722
|
result.command = commandTag.command;
|
|
1723
|
+
// The rows are still raw here, so the one question money cannot
|
|
1724
|
+
// answer for itself can still be asked before they are read.
|
|
1725
|
+
if (this.needsMoneyFormat(resultFields, options)) {
|
|
1726
|
+
await this.ensureMoneyFormat();
|
|
1727
|
+
options = this.withDefaults(options);
|
|
1728
|
+
}
|
|
1388
1729
|
if (resultFields && parsers) {
|
|
1389
1730
|
if (!result.command)
|
|
1390
1731
|
result.command = 'SELECT';
|
|
@@ -1411,6 +1752,115 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
1411
1752
|
const handled = super.emit(event, ...args);
|
|
1412
1753
|
return this.owner ? this.owner.emit(event, ...args) || handled : handled;
|
|
1413
1754
|
}
|
|
1755
|
+
/**
|
|
1756
|
+
* The slow half of enterWire(): either an exclusive statement, or an
|
|
1757
|
+
* ordinary one arriving while an exclusive statement holds the wire.
|
|
1758
|
+
*
|
|
1759
|
+
* The claim itself is synchronous on purpose. Each waiter wakes in its
|
|
1760
|
+
* own microtask and runs to its next `await` without interruption, so
|
|
1761
|
+
* re-reading `_wireLock` and assigning it in the same stretch is what
|
|
1762
|
+
* makes two waiters unable to both believe they took it.
|
|
1763
|
+
*/
|
|
1764
|
+
async _enterWireQueued(exclusive) {
|
|
1765
|
+
while (this._wireLock)
|
|
1766
|
+
await this._wireLock;
|
|
1767
|
+
if (!exclusive) {
|
|
1768
|
+
this._wireUsers++;
|
|
1769
|
+
return this._releaseWire;
|
|
1770
|
+
}
|
|
1771
|
+
let unlock;
|
|
1772
|
+
this._wireLock = new Promise(resolve => (unlock = resolve));
|
|
1773
|
+
// Whatever was already in flight when the lock went up still has to
|
|
1774
|
+
// finish - the point of asking is not to be interleaved with it.
|
|
1775
|
+
if (this._wireUsers)
|
|
1776
|
+
await new Promise(resolve => (this._wireIdleWaiters || (this._wireIdleWaiters = [])).push(resolve));
|
|
1777
|
+
this._wireUsers++;
|
|
1778
|
+
return () => {
|
|
1779
|
+
this._wireLock = undefined;
|
|
1780
|
+
this._releaseWire();
|
|
1781
|
+
unlock();
|
|
1782
|
+
};
|
|
1783
|
+
}
|
|
1784
|
+
/**
|
|
1785
|
+
* `options` with whatever this connection can answer for it: the
|
|
1786
|
+
* data-mapping defaults it was configured with, and the money format
|
|
1787
|
+
* it has learned.
|
|
1788
|
+
*
|
|
1789
|
+
* At most one shallow copy per statement, and only when there is
|
|
1790
|
+
* something to fill in - a connection configured with nothing and no
|
|
1791
|
+
* money in sight hands the caller's own object straight back.
|
|
1792
|
+
*/
|
|
1793
|
+
/**
|
|
1794
|
+
* Rows left raw because a money column's scale was not in yet.
|
|
1795
|
+
*
|
|
1796
|
+
* Two paths decode inside the message loop, where there is no longer
|
|
1797
|
+
* anywhere to ask the server anything: the Simple Query protocol and
|
|
1798
|
+
* a pipeline. Rather than read those against a scale nobody
|
|
1799
|
+
* confirmed, they set the rows aside and this reads them once the
|
|
1800
|
+
* loop is over and the question can be asked.
|
|
1801
|
+
*/
|
|
1802
|
+
async _decodeDeferredRows(deferred, rowDecoder, options) {
|
|
1803
|
+
await this.ensureMoneyFormat();
|
|
1804
|
+
const opts = this.withDefaults(options);
|
|
1805
|
+
let d;
|
|
1806
|
+
let i;
|
|
1807
|
+
let k;
|
|
1808
|
+
for (i = 0; i < deferred.length; i++) {
|
|
1809
|
+
d = deferred[i];
|
|
1810
|
+
const l = d.raw.length;
|
|
1811
|
+
const out = new Array(l);
|
|
1812
|
+
for (k = 0; k < l; k++)
|
|
1813
|
+
out[k] = rowDecoder.decode(d.parsers, d.raw[k].data, d.raw[k].columnCount, opts, d.fields);
|
|
1814
|
+
d.target.rows = out;
|
|
1815
|
+
}
|
|
1816
|
+
return opts;
|
|
1817
|
+
}
|
|
1818
|
+
withDefaults(options) {
|
|
1819
|
+
const defaults = this._mappingDefaults;
|
|
1820
|
+
const money = this._moneyFormat;
|
|
1821
|
+
const dateStyle = this._currentDateStyle();
|
|
1822
|
+
if (!defaults && !money && !dateStyle)
|
|
1823
|
+
return options;
|
|
1824
|
+
let out;
|
|
1825
|
+
if (defaults) {
|
|
1826
|
+
let k;
|
|
1827
|
+
for (k in defaults) {
|
|
1828
|
+
// The call always wins; the connection only fills a gap.
|
|
1829
|
+
if (options[k] !== undefined)
|
|
1830
|
+
continue;
|
|
1831
|
+
out = out || { ...options };
|
|
1832
|
+
out[k] = defaults[k];
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
if (money && !(out || options).moneyFormat) {
|
|
1836
|
+
out = out || { ...options };
|
|
1837
|
+
out.moneyFormat = money;
|
|
1838
|
+
}
|
|
1839
|
+
if (dateStyle && !(out || options).dateStyle) {
|
|
1840
|
+
out = out || { ...options };
|
|
1841
|
+
out.dateStyle = dateStyle;
|
|
1842
|
+
}
|
|
1843
|
+
return out || options;
|
|
1844
|
+
}
|
|
1845
|
+
/**
|
|
1846
|
+
* The session's DateStyle, when it is not ISO.
|
|
1847
|
+
*
|
|
1848
|
+
* Read off the reported parameters rather than cached once: `SET
|
|
1849
|
+
* DateStyle` is answered with a fresh ParameterStatus, so the string
|
|
1850
|
+
* changes under us and a connection that changes it mid-session must
|
|
1851
|
+
* not go on decoding by the old one. The comparison is against the
|
|
1852
|
+
* same string the socket holds, so it costs a read and a pointer
|
|
1853
|
+
* compare while nothing changes - which is always, for the ISO
|
|
1854
|
+
* default that needs no parsing at all.
|
|
1855
|
+
*/
|
|
1856
|
+
_currentDateStyle() {
|
|
1857
|
+
const raw = this.socket.sessionParameters.DateStyle;
|
|
1858
|
+
if (raw !== this._dateStyleRaw) {
|
|
1859
|
+
this._dateStyleRaw = raw;
|
|
1860
|
+
this._dateStyle = parseDateStyleSetting(raw);
|
|
1861
|
+
}
|
|
1862
|
+
return this._dateStyle;
|
|
1863
|
+
}
|
|
1414
1864
|
_onError(err) {
|
|
1415
1865
|
if (this.socket.state !== ConnectionState.READY)
|
|
1416
1866
|
return;
|