postgrejs 3.8.0 → 3.10.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 +59 -1
- package/README.md +50 -0
- package/connection/connection.d.ts +14 -2
- package/connection/connection.js +63 -11
- package/connection/intl-connection.d.ts +164 -6
- package/connection/intl-connection.js +558 -37
- package/connection/pool.d.ts +30 -6
- package/connection/pool.js +22 -3
- package/connection/prepared-statement.js +22 -2
- package/data-type-map.d.ts +16 -0
- package/data-type-map.js +56 -12
- package/data-types/circle-type.js +5 -2
- package/data-types/classes/geometric.d.ts +49 -7
- package/data-types/classes/geometric.js +59 -11
- package/data-types/classes/interval.d.ts +40 -16
- package/data-types/classes/interval.js +68 -40
- package/data-types/classes/numeric.d.ts +6 -0
- package/data-types/classes/numeric.js +8 -0
- package/data-types/classes/range.d.ts +6 -1
- package/data-types/classes/range.js +6 -1
- package/data-types/date-type.js +2 -2
- package/data-types/interval-type.js +1 -1
- package/data-types/money-type.d.ts +38 -0
- package/data-types/money-type.js +212 -0
- package/data-types/numeric-type.d.ts +2 -0
- package/data-types/numeric-type.js +22 -11
- package/data-types/temporal-types.d.ts +19 -0
- package/data-types/temporal-types.js +520 -0
- package/data-types/timestamp-type.js +2 -2
- package/data-types/timestamptz-type.js +2 -2
- package/index.d.ts +3 -0
- package/index.js +3 -0
- package/interfaces/data-mapping-options.d.ts +126 -4
- 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 +1 -1
- package/protocol/pg-socket.js +7 -10
- package/util/array-leaf.d.ts +10 -0
- package/util/array-leaf.js +22 -0
- package/util/connection-config.js +11 -1
- package/util/date-style.d.ts +47 -0
- package/util/date-style.js +123 -0
- package/util/decimal-as-string.d.ts +11 -0
- package/util/decimal-as-string.js +30 -0
- package/util/fetch-as-string.d.ts +33 -0
- package/util/fetch-as-string.js +122 -0
- package/util/get-parsers.js +18 -1
- package/util/parse-datetime.d.ts +4 -3
- package/util/parse-datetime.js +34 -9
- package/util/resolve-column-formats.d.ts +5 -11
- package/util/resolve-column-formats.js +28 -24
- package/util/row-decoder.d.ts +24 -4
- package/util/row-decoder.js +25 -10
- package/util/sql-tag.js +2 -1
- package/util/stringify-for-sql.js +6 -5
- package/util/temporal.d.ts +47 -0
- package/util/temporal.js +61 -0
- package/util/transaction-command.d.ts +7 -0
- package/util/transaction-command.js +32 -0
- package/util/unspecified-param.d.ts +18 -8
- package/util/unspecified-param.js +22 -14
|
@@ -1,16 +1,23 @@
|
|
|
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';
|
|
5
|
+
import { temporalTypeMap } from '../data-types/temporal-types.js';
|
|
4
6
|
import { DatabaseError } from '../protocol/database-error.js';
|
|
5
7
|
import { PgSocket } from '../protocol/pg-socket.js';
|
|
6
8
|
import { Protocol } from '../protocol/protocol.js';
|
|
7
9
|
import { SafeEventEmitter } from '../safe-event-emitter.js';
|
|
8
10
|
import { getConnectionConfig } from '../util/connection-config.js';
|
|
9
11
|
import { buildCopySql, buildProbeSql, writeCopyBinaryRows, } from '../util/copy-from-rows.js';
|
|
12
|
+
import { parseDateStyleSetting } from '../util/date-style.js';
|
|
13
|
+
import { validateDecimalAsString } from '../util/decimal-as-string.js';
|
|
10
14
|
import { escapeLiteral } from '../util/escape-literal.js';
|
|
15
|
+
import { fetchAsStringEqual, fetchAsStringNamesElement, fetchAsStringNamesOid, validateFetchAsString, } from '../util/fetch-as-string.js';
|
|
11
16
|
import { getParsers } from '../util/get-parsers.js';
|
|
12
|
-
import {
|
|
13
|
-
import { resolveRowDecoder, resolveRowType } from '../util/row-decoder.js';
|
|
17
|
+
import { resolveColumnFormats } from '../util/resolve-column-formats.js';
|
|
18
|
+
import { resolveRowDecoder, resolveRowType, } from '../util/row-decoder.js';
|
|
19
|
+
import { requireTimeZone } from '../util/temporal.js';
|
|
20
|
+
import { isTransactionCommand, refusesSavepoint, } from '../util/transaction-command.js';
|
|
14
21
|
import { wrapRowDescription } from '../util/wrap-row-description.js';
|
|
15
22
|
import { CopyFromStream, CopyToStream } from './copy-stream.js';
|
|
16
23
|
const DataFormat = Protocol.DataFormat;
|
|
@@ -77,6 +84,75 @@ function reportsRowsAffected(command) {
|
|
|
77
84
|
* it starts saving, which it makes back immediately.
|
|
78
85
|
*/
|
|
79
86
|
const PREPARE_AFTER_USES = 2;
|
|
87
|
+
/**
|
|
88
|
+
* Stands in for the command tag an empty statement never sends, so that
|
|
89
|
+
* the slot is taken and nothing behind it is mistaken for the caller's
|
|
90
|
+
* own command. Its empty name is what leaves `QueryResult.command`
|
|
91
|
+
* unset, which is what `pg` reports for the same statement.
|
|
92
|
+
*/
|
|
93
|
+
/** The connection-configuration keys that are per-statement defaults. */
|
|
94
|
+
const MAPPING_DEFAULT_KEYS = [
|
|
95
|
+
'typeMap',
|
|
96
|
+
'temporalTypes',
|
|
97
|
+
'timeZone',
|
|
98
|
+
'columnFormat',
|
|
99
|
+
'objectRows',
|
|
100
|
+
'rowDecoder',
|
|
101
|
+
'utcDates',
|
|
102
|
+
'fetchAsString',
|
|
103
|
+
'decimalAsString',
|
|
104
|
+
'unknownTypesAsString',
|
|
105
|
+
'moneyFormat',
|
|
106
|
+
];
|
|
107
|
+
/**
|
|
108
|
+
* The mapping defaults out of a connection configuration, or undefined
|
|
109
|
+
* when it set none - so the merge can be skipped outright rather than
|
|
110
|
+
* walking an empty object on every statement.
|
|
111
|
+
*/
|
|
112
|
+
function pickMappingDefaults(config) {
|
|
113
|
+
let out;
|
|
114
|
+
let k;
|
|
115
|
+
let i;
|
|
116
|
+
for (i = 0; i < MAPPING_DEFAULT_KEYS.length; i++) {
|
|
117
|
+
k = MAPPING_DEFAULT_KEYS[i];
|
|
118
|
+
if (config[k] === undefined)
|
|
119
|
+
continue;
|
|
120
|
+
out = out || {};
|
|
121
|
+
out[k] = config[k];
|
|
122
|
+
}
|
|
123
|
+
return out && Object.freeze(out);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The isolation levels PostgreSQL takes, as the SQL that names them.
|
|
127
|
+
*
|
|
128
|
+
* A table rather than the caller's own string: this text is
|
|
129
|
+
* concatenated into a statement, and nothing that reaches it may come
|
|
130
|
+
* from outside this file.
|
|
131
|
+
*/
|
|
132
|
+
const ISOLATION_LEVELS = {
|
|
133
|
+
serializable: 'SERIALIZABLE',
|
|
134
|
+
'repeatable read': 'REPEATABLE READ',
|
|
135
|
+
'read committed': 'READ COMMITTED',
|
|
136
|
+
'read uncommitted': 'READ UNCOMMITTED',
|
|
137
|
+
};
|
|
138
|
+
/** `BEGIN`, carrying whatever modes the caller asked for. */
|
|
139
|
+
function beginStatement(options) {
|
|
140
|
+
if (!options)
|
|
141
|
+
return 'BEGIN';
|
|
142
|
+
let sql = 'BEGIN';
|
|
143
|
+
if (options.isolationLevel != null) {
|
|
144
|
+
const level = ISOLATION_LEVELS[options.isolationLevel.toLowerCase()];
|
|
145
|
+
if (!level)
|
|
146
|
+
throw new TypeError(`Unknown transaction isolation level "${options.isolationLevel}"`);
|
|
147
|
+
sql += ' ISOLATION LEVEL ' + level;
|
|
148
|
+
}
|
|
149
|
+
if (options.readOnly != null)
|
|
150
|
+
sql += options.readOnly ? ' READ ONLY' : ' READ WRITE';
|
|
151
|
+
if (options.deferrable != null)
|
|
152
|
+
sql += options.deferrable ? ' DEFERRABLE' : ' NOT DEFERRABLE';
|
|
153
|
+
return sql;
|
|
154
|
+
}
|
|
155
|
+
const EMPTY_QUERY_TAG = { command: '' };
|
|
80
156
|
/**
|
|
81
157
|
* SQL that manages transaction boundaries itself, and so must never be
|
|
82
158
|
* wrapped in a savepoint or an implicit BEGIN/COMMIT of ours.
|
|
@@ -89,7 +165,6 @@ const PREPARE_AFTER_USES = 2;
|
|
|
89
165
|
function preparedCacheKey(sql, paramTypes) {
|
|
90
166
|
return paramTypes?.length ? sql + '\u0000' + paramTypes.join(',') : sql;
|
|
91
167
|
}
|
|
92
|
-
const TRANSACTION_COMMAND_PATTERN = /^(\bBEGIN\b|\bCOMMIT\b|\bSTART\b|\bROLLBACK|SAVEPOINT|RELEASE\b)/i;
|
|
93
168
|
export class IntlConnection extends SafeEventEmitter {
|
|
94
169
|
/**
|
|
95
170
|
* Server-side prepared statements this connection has built, keyed by
|
|
@@ -102,6 +177,15 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
102
177
|
* this is per connection and dies with it.
|
|
103
178
|
*/
|
|
104
179
|
_preparedCache = new Map();
|
|
180
|
+
/**
|
|
181
|
+
* The names being given out right now, one per SQL. A burst of the
|
|
182
|
+
* same statement arrives before any of it is prepared, and without
|
|
183
|
+
* this every call in the burst prepares its own copy: measured at 50
|
|
184
|
+
* concurrent `select $1::int4`, 25 Parse+Describe round trips and 25
|
|
185
|
+
* named statements left on the server that this map's one entry could
|
|
186
|
+
* never close again.
|
|
187
|
+
*/
|
|
188
|
+
_preparing = new Map();
|
|
105
189
|
/** SQL seen once but not yet prepared - see PREPARE_AFTER_USES. */
|
|
106
190
|
_preparedCandidates = new Map();
|
|
107
191
|
_preparedCounter = 0;
|
|
@@ -111,6 +195,38 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
111
195
|
_savepointDepths = new Map();
|
|
112
196
|
_config;
|
|
113
197
|
_onErrorSavePoint;
|
|
198
|
+
/**
|
|
199
|
+
* The data-mapping options this connection was configured with, or
|
|
200
|
+
* undefined when it was given none - which is the common case, and
|
|
201
|
+
* the one that must not pay for this.
|
|
202
|
+
*/
|
|
203
|
+
_mappingDefaults;
|
|
204
|
+
/** The session's DateStyle as last reported, and what it parsed to. */
|
|
205
|
+
_dateStyleRaw;
|
|
206
|
+
_dateStyle;
|
|
207
|
+
/** The session's TimeZone as last reported, once checked. */
|
|
208
|
+
_timeZoneRaw;
|
|
209
|
+
_timeZone;
|
|
210
|
+
/** The server's own money rendering - see ensureMoneyFormat(). */
|
|
211
|
+
_moneyFormat;
|
|
212
|
+
_moneyFormatPromise;
|
|
213
|
+
/** Whether the socket has been up, which is what makes a close a loss. */
|
|
214
|
+
_live = false;
|
|
215
|
+
/** Statements currently holding the wire - see enterWire(). */
|
|
216
|
+
_wireUsers = 0;
|
|
217
|
+
_wireIdleWaiters;
|
|
218
|
+
_wireLock;
|
|
219
|
+
/** Bound once: enterWire()'s fast path hands out this same function. */
|
|
220
|
+
_releaseWire = () => {
|
|
221
|
+
if (--this._wireUsers || !this._wireIdleWaiters)
|
|
222
|
+
return;
|
|
223
|
+
const waiters = this._wireIdleWaiters;
|
|
224
|
+
this._wireIdleWaiters = undefined;
|
|
225
|
+
let i;
|
|
226
|
+
const l = waiters.length;
|
|
227
|
+
for (i = 0; i < l; i++)
|
|
228
|
+
waiters[i]();
|
|
229
|
+
};
|
|
114
230
|
transactionStatus = 'I';
|
|
115
231
|
socket;
|
|
116
232
|
owner;
|
|
@@ -118,11 +234,37 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
118
234
|
constructor(config) {
|
|
119
235
|
super();
|
|
120
236
|
this._config = Object.freeze(getConnectionConfig(config));
|
|
237
|
+
this._mappingDefaults = pickMappingDefaults(this._config);
|
|
238
|
+
// Built here rather than at the first statement, so that a Temporal
|
|
239
|
+
// this runtime does not have, or an OID that is not one of the five,
|
|
240
|
+
// is reported before any I/O - and so the map every statement then
|
|
241
|
+
// reuses is made once.
|
|
242
|
+
if (this._mappingDefaults?.temporalTypes)
|
|
243
|
+
temporalTypeMap(this._mappingDefaults.temporalTypes, this._mappingDefaults.typeMap || GlobalTypeMap);
|
|
121
244
|
this.socket = new PgSocket(this._config);
|
|
122
|
-
this.socket.on('
|
|
245
|
+
this.socket.on('ready', () => {
|
|
246
|
+
this._live = true;
|
|
247
|
+
});
|
|
123
248
|
// The reason travels with it: 'close' on its own cannot tell an
|
|
124
249
|
// ordinary shutdown from the backend being terminated under us.
|
|
125
|
-
|
|
250
|
+
//
|
|
251
|
+
// A loss is reported on 'error' as well, which is where `pg` puts it
|
|
252
|
+
// and where code ported from it listens - `client.on('error')` was
|
|
253
|
+
// attached and never fired, because a terminated backend closes
|
|
254
|
+
// cleanly at the socket level and Node gives 'close' without an
|
|
255
|
+
// 'error' of its own. The Pool already reports the same object the
|
|
256
|
+
// same way. Emitted before 'close': the loss is the cause and the
|
|
257
|
+
// close is the consequence. Safe to do unconditionally, since
|
|
258
|
+
// SafeEventEmitter drops an 'error' nobody is listening for instead
|
|
259
|
+
// of throwing - and only for a connection that was up, so a failed
|
|
260
|
+
// connect stays the business of the promise connect() returns.
|
|
261
|
+
this.socket.on('close', (reason) => {
|
|
262
|
+
const live = this._live;
|
|
263
|
+
this._live = false;
|
|
264
|
+
if (reason && live)
|
|
265
|
+
this.emit('error', reason);
|
|
266
|
+
this.emit('close', reason);
|
|
267
|
+
});
|
|
126
268
|
this.socket.on('notification', payload => this.emit('notification', payload));
|
|
127
269
|
// Covers a notice that arrives while no statement is running. One
|
|
128
270
|
// raised *by* a statement is dispatched to that statement's own
|
|
@@ -201,7 +343,7 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
201
343
|
}
|
|
202
344
|
async execute(sql, options = {}, cb) {
|
|
203
345
|
this.assertConnected();
|
|
204
|
-
const transactionCommand =
|
|
346
|
+
const transactionCommand = isTransactionCommand(sql);
|
|
205
347
|
let beginFirst = false;
|
|
206
348
|
let commitLast = false;
|
|
207
349
|
const { autoCommit } = options;
|
|
@@ -225,7 +367,10 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
225
367
|
// out from under it) - but ordered so the cheap local read still
|
|
226
368
|
// short-circuits the getter whenever rollbackOnError is already
|
|
227
369
|
// false, which it is outside a transaction.
|
|
228
|
-
const rollbackOnError =
|
|
370
|
+
const rollbackOnError =
|
|
371
|
+
// Not `transactionCommand`: `SET TRANSACTION` still wants the
|
|
372
|
+
// implicit BEGIN above, and only the savepoint is fatal to it.
|
|
373
|
+
!refusesSavepoint(sql) &&
|
|
229
374
|
this.inTransaction &&
|
|
230
375
|
(options.rollbackOnError ?? this.config.rollbackOnError ?? true);
|
|
231
376
|
if (rollbackOnError && this.inTransaction)
|
|
@@ -299,10 +444,27 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
299
444
|
* that point). A matching number of commit() calls is then needed to
|
|
300
445
|
* actually commit - see commit().
|
|
301
446
|
*/
|
|
302
|
-
async startTransaction() {
|
|
447
|
+
async startTransaction(options) {
|
|
448
|
+
// A nested call is a depth increment, not a second BEGIN - so modes
|
|
449
|
+
// it carries could only be applied to a transaction someone else
|
|
450
|
+
// opened. PostgreSQL would take them: a nested `BEGIN ISOLATION
|
|
451
|
+
// LEVEL SERIALIZABLE` before any query has run *silently changes the
|
|
452
|
+
// outer transaction's level* (measured, only a warning about the
|
|
453
|
+
// redundant BEGIN), and after one it raises 25001. Neither is what
|
|
454
|
+
// the caller of a nested scope means, so this says so instead.
|
|
455
|
+
// Built before anything is counted: an isolation level this client
|
|
456
|
+
// does not know throws here, and a start that never happened must
|
|
457
|
+
// not leave the depth one higher than the transactions there are.
|
|
458
|
+
const sql = beginStatement(options);
|
|
459
|
+
if (options && this._transactionDepth > 0)
|
|
460
|
+
throw new Error('startTransaction() cannot set transaction modes on a transaction ' +
|
|
461
|
+
'that is already open - they belong to the BEGIN that opens it');
|
|
462
|
+
if (this.inTransaction) {
|
|
463
|
+
this._transactionDepth++;
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
await this.execute(sql);
|
|
303
467
|
this._transactionDepth++;
|
|
304
|
-
if (!this.inTransaction)
|
|
305
|
-
await this.execute('BEGIN');
|
|
306
468
|
}
|
|
307
469
|
/**
|
|
308
470
|
* Starts a savepoint, or - if one under the same `name` is already open -
|
|
@@ -414,6 +576,95 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
414
576
|
cancel() {
|
|
415
577
|
return this.socket.cancel();
|
|
416
578
|
}
|
|
579
|
+
/**
|
|
580
|
+
* Claims the wire for one statement, and hands back the function that
|
|
581
|
+
* releases it.
|
|
582
|
+
*
|
|
583
|
+
* Several statements share it by default: PostgreSQL correlates
|
|
584
|
+
* responses to requests by order, so a connection can carry more than
|
|
585
|
+
* one at a time and a caller that does not wait for each reply is
|
|
586
|
+
* measurably faster. `exclusive` is `pipeline: false` - the statement
|
|
587
|
+
* waits until nothing else is running here, and nothing else starts
|
|
588
|
+
* until it releases.
|
|
589
|
+
*
|
|
590
|
+
* Returns the release function itself rather than a promise of one
|
|
591
|
+
* whenever it can be taken without waiting, which is every statement
|
|
592
|
+
* on a connection nobody has asked to serialise - the common case, and
|
|
593
|
+
* the one that must not pay for this.
|
|
594
|
+
*/
|
|
595
|
+
enterWire(exclusive) {
|
|
596
|
+
if (!exclusive && !this._wireLock) {
|
|
597
|
+
this._wireUsers++;
|
|
598
|
+
return this._releaseWire;
|
|
599
|
+
}
|
|
600
|
+
return this._enterWireQueued(!!exclusive);
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Asks the server how it renders money, once per connection.
|
|
604
|
+
*
|
|
605
|
+
* `money` travels as an int64 of the smallest currency unit, and how
|
|
606
|
+
* many of those make one unit is `lc_monetary` - which the server
|
|
607
|
+
* does not report among its startup parameters, so `1234` on the wire
|
|
608
|
+
* is $12.34, ¥1234 or 1.234 KWD depending on a setting only the server
|
|
609
|
+
* knows. Asking it to render a value it already knows is the whole
|
|
610
|
+
* answer, and needs no locale data on this side.
|
|
611
|
+
*
|
|
612
|
+
* `1` is the value asked for because it cannot carry a thousands
|
|
613
|
+
* separator: whatever separator comes back can only be the decimal
|
|
614
|
+
* one, and the digits after it can only be the fraction. `$1.00` is
|
|
615
|
+
* two, `¥1` is none.
|
|
616
|
+
*/
|
|
617
|
+
async ensureMoneyFormat() {
|
|
618
|
+
if (this._moneyFormat)
|
|
619
|
+
return;
|
|
620
|
+
if (!this._moneyFormatPromise) {
|
|
621
|
+
// Assigned before the query starts: the query goes through
|
|
622
|
+
// queryCached like any other, and would ask for the format again
|
|
623
|
+
// on its way out.
|
|
624
|
+
let done;
|
|
625
|
+
this._moneyFormatPromise = new Promise(r => (done = r));
|
|
626
|
+
this.queryCached("select '1'::money::text", undefined, undefined, {
|
|
627
|
+
columnFormat: DataFormat.text,
|
|
628
|
+
})
|
|
629
|
+
.then(r => {
|
|
630
|
+
const text = r.rows?.[0]?.[0];
|
|
631
|
+
if (typeof text === 'string')
|
|
632
|
+
this._moneyFormat = parseMoneyFormat(text);
|
|
633
|
+
})
|
|
634
|
+
.catch(() => undefined)
|
|
635
|
+
.then(done);
|
|
636
|
+
}
|
|
637
|
+
return this._moneyFormatPromise;
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Whether these columns need the server's money format and this
|
|
641
|
+
* connection has not asked for it yet.
|
|
642
|
+
*
|
|
643
|
+
* The scan costs a look at each column's OID, and only until the
|
|
644
|
+
* answer is in - from then on the first test ends it.
|
|
645
|
+
*/
|
|
646
|
+
needsMoneyFormat(fields, options) {
|
|
647
|
+
if (this._moneyFormat || !fields)
|
|
648
|
+
return false;
|
|
649
|
+
const asString = options?.fetchAsString;
|
|
650
|
+
const l = fields.length;
|
|
651
|
+
let i;
|
|
652
|
+
let oid;
|
|
653
|
+
for (i = 0; i < l; i++) {
|
|
654
|
+
oid = fields[i].dataTypeId;
|
|
655
|
+
if (oid !== DataTypeOIDs.money && oid !== DataTypeOIDs._money)
|
|
656
|
+
continue;
|
|
657
|
+
// Named by fetchAsString, so the value goes back exactly as the
|
|
658
|
+
// server rendered it and no decoder asks what a minor unit is -
|
|
659
|
+
// which naming `money` does for a `money[]` column too.
|
|
660
|
+
if (fetchAsStringNamesOid(asString, oid) ||
|
|
661
|
+
(oid === DataTypeOIDs._money &&
|
|
662
|
+
fetchAsStringNamesElement(asString, DataTypeOIDs.money)))
|
|
663
|
+
continue;
|
|
664
|
+
return true;
|
|
665
|
+
}
|
|
666
|
+
return false;
|
|
667
|
+
}
|
|
417
668
|
ref() {
|
|
418
669
|
this._refCount++;
|
|
419
670
|
}
|
|
@@ -532,6 +783,7 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
532
783
|
return stream;
|
|
533
784
|
}
|
|
534
785
|
async _execute(sql, options = {}, cb) {
|
|
786
|
+
options = this.withDefaults(options);
|
|
535
787
|
this.ref();
|
|
536
788
|
try {
|
|
537
789
|
const timingEnabled = options.timing ?? this.config.timing ?? false;
|
|
@@ -544,11 +796,13 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
544
796
|
let parsers;
|
|
545
797
|
let current = { command: undefined };
|
|
546
798
|
let fields;
|
|
799
|
+
const deferred = [];
|
|
800
|
+
let currentDeferred;
|
|
547
801
|
let error;
|
|
548
802
|
const typeMap = options.typeMap || GlobalTypeMap;
|
|
549
803
|
const rowDecoder = resolveRowDecoder(options);
|
|
550
804
|
this.runningQueryCount++;
|
|
551
|
-
|
|
805
|
+
const scriptResult = await this.socket.sendQueryMessage(sql, (code, msg, done) => {
|
|
552
806
|
switch (code) {
|
|
553
807
|
case Protocol.BackendMessageCode.ErrorResponse:
|
|
554
808
|
error = msg;
|
|
@@ -578,9 +832,25 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
578
832
|
parsers = getParsers(typeMap, fields, options);
|
|
579
833
|
current.fields = wrapRowDescription(typeMap, fields, DataFormat.text, options);
|
|
580
834
|
current.rows = [];
|
|
835
|
+
currentDeferred = this.needsMoneyFormat(fields, options)
|
|
836
|
+
? {
|
|
837
|
+
target: current,
|
|
838
|
+
raw: [],
|
|
839
|
+
parsers,
|
|
840
|
+
fields: current.fields,
|
|
841
|
+
}
|
|
842
|
+
: undefined;
|
|
843
|
+
if (currentDeferred)
|
|
844
|
+
deferred.push(currentDeferred);
|
|
581
845
|
break;
|
|
582
846
|
case Protocol.BackendMessageCode.DataRow:
|
|
583
847
|
{
|
|
848
|
+
// Set aside rather than read against a scale nobody has
|
|
849
|
+
// confirmed - see _decodeDeferredRows().
|
|
850
|
+
if (currentDeferred) {
|
|
851
|
+
currentDeferred.raw.push(msg);
|
|
852
|
+
break;
|
|
853
|
+
}
|
|
584
854
|
const row = rowDecoder.decode(parsers, msg.data, msg.columnCount, options, current.fields);
|
|
585
855
|
if (cb)
|
|
586
856
|
cb('row', row);
|
|
@@ -589,6 +859,7 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
589
859
|
}
|
|
590
860
|
break;
|
|
591
861
|
case Protocol.BackendMessageCode.CommandComplete:
|
|
862
|
+
currentDeferred = undefined;
|
|
592
863
|
// Ignore BEGIN command that we added to sql
|
|
593
864
|
current.command = msg.command;
|
|
594
865
|
if (reportsRowsAffected(current.command))
|
|
@@ -620,6 +891,9 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
620
891
|
break;
|
|
621
892
|
}
|
|
622
893
|
});
|
|
894
|
+
if (deferred.length)
|
|
895
|
+
await this._decodeDeferredRows(deferred, rowDecoder, options);
|
|
896
|
+
return scriptResult;
|
|
623
897
|
}
|
|
624
898
|
finally {
|
|
625
899
|
this.runningQueryCount--;
|
|
@@ -655,9 +929,14 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
655
929
|
* of failing every call from then on.
|
|
656
930
|
*/
|
|
657
931
|
async queryCached(sql, paramTypes, params, options) {
|
|
932
|
+
// Read before the merge below: a connection-wide fetchAsString must
|
|
933
|
+
// not give every statement a name on first sight - see
|
|
934
|
+
// _queryCached().
|
|
935
|
+
const callWantsFields = !!options.fetchAsString?.length || !!options.unknownTypesAsString;
|
|
936
|
+
options = this.withDefaults(options);
|
|
658
937
|
const savepoint = this._inlineSavepointFor(sql, options);
|
|
659
938
|
try {
|
|
660
|
-
return await this._queryCached(sql, paramTypes, params, options, savepoint);
|
|
939
|
+
return await this._queryCached(sql, paramTypes, params, options, savepoint, callWantsFields);
|
|
661
940
|
}
|
|
662
941
|
catch (e) {
|
|
663
942
|
// PostgreSQL throws away everything between a failed statement and
|
|
@@ -673,16 +952,19 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
673
952
|
/**
|
|
674
953
|
* The savepoint rollbackOnError calls for, or undefined when this call
|
|
675
954
|
* needs none: outside a transaction there is nothing to roll back to,
|
|
676
|
-
*
|
|
677
|
-
* boundaries (wrapping COMMIT in a savepoint would be nonsense)
|
|
955
|
+
* a statement that is itself a transaction command manages its own
|
|
956
|
+
* boundaries (wrapping COMMIT in a savepoint would be nonsense), and
|
|
957
|
+
* PostgreSQL refuses `SET TRANSACTION` inside one outright.
|
|
678
958
|
*/
|
|
679
959
|
_inlineSavepointFor(sql, options) {
|
|
680
|
-
if (!this.inTransaction ||
|
|
960
|
+
if (!this.inTransaction || refusesSavepoint(sql))
|
|
681
961
|
return undefined;
|
|
682
962
|
const on = options.rollbackOnError ?? this.config.rollbackOnError ?? true;
|
|
683
963
|
return on ? this._onErrorSavePoint : undefined;
|
|
684
964
|
}
|
|
685
|
-
async _queryCached(sql, paramTypes, params, options, savepoint
|
|
965
|
+
async _queryCached(sql, paramTypes, params, options, savepoint,
|
|
966
|
+
/** Whether the CALL asked for text columns, not the connection. */
|
|
967
|
+
callWantsFields) {
|
|
686
968
|
const enabled = options.prepare ?? this.config.prepare ?? true;
|
|
687
969
|
if (!enabled)
|
|
688
970
|
return this.queryOnce(sql, paramTypes, params, options, savepoint);
|
|
@@ -718,18 +1000,56 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
718
1000
|
// text (what queryOnce() falls back to).
|
|
719
1001
|
// unknownTypesAsString needs the columns known before the Bind for
|
|
720
1002
|
// the same reason fetchAsString does, so it earns a name the same way.
|
|
721
|
-
|
|
1003
|
+
// ...unless the caller already asked for the whole row as text, in
|
|
1004
|
+
// which case there is nothing for either option to resolve and the
|
|
1005
|
+
// Parse+Describe would buy nothing. An array of formats is not the
|
|
1006
|
+
// same thing: it can be shorter than the row, and the columns it
|
|
1007
|
+
// does not cover are binary.
|
|
1008
|
+
//
|
|
1009
|
+
// And only for a list the CALL carried. The same list set once on
|
|
1010
|
+
// the connection applies to every statement, so naming each one on
|
|
1011
|
+
// first sight would double the round trips of a workload full of
|
|
1012
|
+
// one-shot SQL and leave a server-side statement behind for each -
|
|
1013
|
+
// measured at 5 distinct statements, 5 round trips against 10. A
|
|
1014
|
+
// connection-wide list takes queryOnce()'s whole-row-text fallback
|
|
1015
|
+
// on a statement's first run instead, and per-column codes from its
|
|
1016
|
+
// second, which is where the name arrives anyway.
|
|
1017
|
+
const needsFields = callWantsFields && options.columnFormat !== DataFormat.text;
|
|
722
1018
|
if (!needsFields && !this._earnsAName(key))
|
|
723
1019
|
return this.queryOnce(sql, paramTypes, params, options, savepoint);
|
|
1020
|
+
let preparing = this._preparing.get(key);
|
|
1021
|
+
if (!preparing) {
|
|
1022
|
+
// Registered before the first await inside, so everything that
|
|
1023
|
+
// arrives while this one is in flight finds it rather than
|
|
1024
|
+
// starting a second one.
|
|
1025
|
+
preparing = this._prepareForCache(key, sql, paramTypes);
|
|
1026
|
+
this._preparing.set(key, preparing);
|
|
1027
|
+
}
|
|
1028
|
+
else if (!needsFields) {
|
|
1029
|
+
// Someone else is already naming it. Waiting would cost a round
|
|
1030
|
+
// trip the one-shot path does not - the name is for the calls
|
|
1031
|
+
// after this burst, not for this one.
|
|
1032
|
+
return this.queryOnce(sql, paramTypes, params, options, savepoint);
|
|
1033
|
+
}
|
|
1034
|
+
const entry = await preparing;
|
|
1035
|
+
return this.executeReused(entry.name, entry.fields, paramTypes, params, options, savepoint);
|
|
1036
|
+
}
|
|
1037
|
+
/**
|
|
1038
|
+
* Gives this SQL a name and caches it, once, however many callers are
|
|
1039
|
+
* waiting. A Parse that fails caches nothing and reports itself; the
|
|
1040
|
+
* statement is simply never reused. It runs outside the savepoint, as
|
|
1041
|
+
* the prepare() the transaction path used to do ahead of its own
|
|
1042
|
+
* wrapper.
|
|
1043
|
+
*/
|
|
1044
|
+
_prepareForCache(key, sql, paramTypes) {
|
|
724
1045
|
const name = 'C_' + ++this._preparedCounter;
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
this.
|
|
732
|
-
return this.executeReused(name, entry.fields, paramTypes, params, options, savepoint);
|
|
1046
|
+
return (async () => {
|
|
1047
|
+
const { fields } = await this.prepareOnce(sql, paramTypes, name);
|
|
1048
|
+
const entry = { name, fields };
|
|
1049
|
+
await this._evictPreparedIfFull();
|
|
1050
|
+
this._preparedCache.set(key, entry);
|
|
1051
|
+
return entry;
|
|
1052
|
+
})().finally(() => this._preparing.delete(key));
|
|
733
1053
|
}
|
|
734
1054
|
/**
|
|
735
1055
|
* Counts one more use of this SQL and says whether it has now been seen
|
|
@@ -782,6 +1102,7 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
782
1102
|
await Promise.all([closed, synced]);
|
|
783
1103
|
}
|
|
784
1104
|
async queryOnce(sql, paramTypes, params, options, savepoint) {
|
|
1105
|
+
options = this.withDefaults(options);
|
|
785
1106
|
this.assertConnected();
|
|
786
1107
|
this.ref();
|
|
787
1108
|
try {
|
|
@@ -856,11 +1177,17 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
856
1177
|
case Protocol.BackendMessageCode.DataRow:
|
|
857
1178
|
rows.push(msg);
|
|
858
1179
|
break;
|
|
1180
|
+
// An empty statement - '' or nothing but a comment - answers
|
|
1181
|
+
// with this *instead of* a CommandComplete, so it has to take
|
|
1182
|
+
// the same slot: a RELEASE riding along behind it would
|
|
1183
|
+
// otherwise be read as the caller's own command. The sentinel
|
|
1184
|
+
// carries no command name, which is what `pg` reports too.
|
|
1185
|
+
case Protocol.BackendMessageCode.EmptyQueryResponse:
|
|
859
1186
|
case Protocol.BackendMessageCode.CommandComplete:
|
|
860
1187
|
if (leadingCommandTags)
|
|
861
1188
|
leadingCommandTags--;
|
|
862
1189
|
else if (!commandTag)
|
|
863
|
-
commandTag = msg;
|
|
1190
|
+
commandTag = msg || EMPTY_QUERY_TAG;
|
|
864
1191
|
break;
|
|
865
1192
|
case Protocol.BackendMessageCode.ErrorResponse:
|
|
866
1193
|
error = msg;
|
|
@@ -878,6 +1205,12 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
878
1205
|
});
|
|
879
1206
|
if (commandTag?.command)
|
|
880
1207
|
result.command = commandTag.command;
|
|
1208
|
+
// The rows are still raw here, so the one question money cannot
|
|
1209
|
+
// answer for itself can still be asked before they are read.
|
|
1210
|
+
if (this.needsMoneyFormat(resultFields, options)) {
|
|
1211
|
+
await this.ensureMoneyFormat();
|
|
1212
|
+
options = this.withDefaults(options);
|
|
1213
|
+
}
|
|
881
1214
|
if (resultFields && parsers) {
|
|
882
1215
|
if (!result.command)
|
|
883
1216
|
result.command = 'SELECT';
|
|
@@ -1015,6 +1348,7 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
1015
1348
|
* that arrive before one belong to the statement it closes.
|
|
1016
1349
|
*/
|
|
1017
1350
|
async executePipeline(requests, options) {
|
|
1351
|
+
options = this.withDefaults(options);
|
|
1018
1352
|
this.assertConnected();
|
|
1019
1353
|
this.ref();
|
|
1020
1354
|
try {
|
|
@@ -1025,6 +1359,7 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
1025
1359
|
const results = [];
|
|
1026
1360
|
let pendingFields;
|
|
1027
1361
|
let pendingRows;
|
|
1362
|
+
const deferred = [];
|
|
1028
1363
|
let error;
|
|
1029
1364
|
// Plan every statement against the prepared statement cache before
|
|
1030
1365
|
// writing anything: one that is already prepared binds to its name
|
|
@@ -1138,12 +1473,24 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
1138
1473
|
if (!result.command)
|
|
1139
1474
|
result.command = 'SELECT';
|
|
1140
1475
|
const rows = pendingRows || [];
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1476
|
+
if (this.needsMoneyFormat(fields, options)) {
|
|
1477
|
+
// Set aside: see _decodeDeferredRows().
|
|
1478
|
+
result.rows = [];
|
|
1479
|
+
deferred.push({
|
|
1480
|
+
target: result,
|
|
1481
|
+
raw: rows,
|
|
1482
|
+
parsers,
|
|
1483
|
+
fields: resultFields,
|
|
1484
|
+
});
|
|
1485
|
+
}
|
|
1486
|
+
else {
|
|
1487
|
+
const l = rows.length;
|
|
1488
|
+
let i;
|
|
1489
|
+
for (i = 0; i < l; i++) {
|
|
1490
|
+
rows[i] = rowDecoder.decode(parsers, rows[i].data, rows[i].columnCount, options, resultFields);
|
|
1491
|
+
}
|
|
1492
|
+
result.rows = rows;
|
|
1145
1493
|
}
|
|
1146
|
-
result.rows = rows;
|
|
1147
1494
|
}
|
|
1148
1495
|
if (reportsRowsAffected(result.command))
|
|
1149
1496
|
result.rowsAffected = msg.rowCount;
|
|
@@ -1201,6 +1548,8 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
1201
1548
|
fields: promo.plan.fields,
|
|
1202
1549
|
});
|
|
1203
1550
|
}
|
|
1551
|
+
if (deferred.length)
|
|
1552
|
+
await this._decodeDeferredRows(deferred, rowDecoder, options);
|
|
1204
1553
|
if (timingEnabled) {
|
|
1205
1554
|
const elapsed = performance.now() - startTime;
|
|
1206
1555
|
const l = results.length;
|
|
@@ -1215,6 +1564,7 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
1215
1564
|
}
|
|
1216
1565
|
}
|
|
1217
1566
|
async executeBatchReused(statementName, cachedFields, paramTypes, paramSets, options) {
|
|
1567
|
+
options = this.withDefaults(options);
|
|
1218
1568
|
this.assertConnected();
|
|
1219
1569
|
this.ref();
|
|
1220
1570
|
try {
|
|
@@ -1320,6 +1670,7 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
1320
1670
|
}
|
|
1321
1671
|
}
|
|
1322
1672
|
async executeReused(statementName, cachedFields, paramTypes, params, options, savepoint) {
|
|
1673
|
+
options = this.withDefaults(options);
|
|
1323
1674
|
this.assertConnected();
|
|
1324
1675
|
this.ref();
|
|
1325
1676
|
try {
|
|
@@ -1381,6 +1732,8 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
1381
1732
|
case Protocol.BackendMessageCode.DataRow:
|
|
1382
1733
|
rows.push(msg);
|
|
1383
1734
|
break;
|
|
1735
|
+
// See queryOnce() for why an empty statement lands here.
|
|
1736
|
+
case Protocol.BackendMessageCode.EmptyQueryResponse:
|
|
1384
1737
|
case Protocol.BackendMessageCode.CommandComplete:
|
|
1385
1738
|
// Three statements can answer here when a savepoint rides
|
|
1386
1739
|
// along: SAVEPOINT's tag, then the caller's own, then
|
|
@@ -1389,7 +1742,7 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
1389
1742
|
if (leadingCommandTags)
|
|
1390
1743
|
leadingCommandTags--;
|
|
1391
1744
|
else if (!commandTag)
|
|
1392
|
-
commandTag = msg;
|
|
1745
|
+
commandTag = msg || EMPTY_QUERY_TAG;
|
|
1393
1746
|
break;
|
|
1394
1747
|
case Protocol.BackendMessageCode.ErrorResponse:
|
|
1395
1748
|
error = msg;
|
|
@@ -1407,6 +1760,12 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
1407
1760
|
});
|
|
1408
1761
|
if (commandTag?.command)
|
|
1409
1762
|
result.command = commandTag.command;
|
|
1763
|
+
// The rows are still raw here, so the one question money cannot
|
|
1764
|
+
// answer for itself can still be asked before they are read.
|
|
1765
|
+
if (this.needsMoneyFormat(resultFields, options)) {
|
|
1766
|
+
await this.ensureMoneyFormat();
|
|
1767
|
+
options = this.withDefaults(options);
|
|
1768
|
+
}
|
|
1410
1769
|
if (resultFields && parsers) {
|
|
1411
1770
|
if (!result.command)
|
|
1412
1771
|
result.command = 'SELECT';
|
|
@@ -1433,10 +1792,172 @@ export class IntlConnection extends SafeEventEmitter {
|
|
|
1433
1792
|
const handled = super.emit(event, ...args);
|
|
1434
1793
|
return this.owner ? this.owner.emit(event, ...args) || handled : handled;
|
|
1435
1794
|
}
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1795
|
+
/**
|
|
1796
|
+
* The slow half of enterWire(): either an exclusive statement, or an
|
|
1797
|
+
* ordinary one arriving while an exclusive statement holds the wire.
|
|
1798
|
+
*
|
|
1799
|
+
* The claim itself is synchronous on purpose. Each waiter wakes in its
|
|
1800
|
+
* own microtask and runs to its next `await` without interruption, so
|
|
1801
|
+
* re-reading `_wireLock` and assigning it in the same stretch is what
|
|
1802
|
+
* makes two waiters unable to both believe they took it.
|
|
1803
|
+
*/
|
|
1804
|
+
async _enterWireQueued(exclusive) {
|
|
1805
|
+
while (this._wireLock)
|
|
1806
|
+
await this._wireLock;
|
|
1807
|
+
if (!exclusive) {
|
|
1808
|
+
this._wireUsers++;
|
|
1809
|
+
return this._releaseWire;
|
|
1810
|
+
}
|
|
1811
|
+
let unlock;
|
|
1812
|
+
this._wireLock = new Promise(resolve => (unlock = resolve));
|
|
1813
|
+
// Whatever was already in flight when the lock went up still has to
|
|
1814
|
+
// finish - the point of asking is not to be interleaved with it.
|
|
1815
|
+
if (this._wireUsers)
|
|
1816
|
+
await new Promise(resolve => (this._wireIdleWaiters || (this._wireIdleWaiters = [])).push(resolve));
|
|
1817
|
+
this._wireUsers++;
|
|
1818
|
+
return () => {
|
|
1819
|
+
this._wireLock = undefined;
|
|
1820
|
+
this._releaseWire();
|
|
1821
|
+
unlock();
|
|
1822
|
+
};
|
|
1823
|
+
}
|
|
1824
|
+
/**
|
|
1825
|
+
* `options` with whatever this connection can answer for it: the
|
|
1826
|
+
* data-mapping defaults it was configured with, and the money format
|
|
1827
|
+
* it has learned.
|
|
1828
|
+
*
|
|
1829
|
+
* At most one shallow copy per statement, and only when there is
|
|
1830
|
+
* something to fill in - a connection configured with nothing and no
|
|
1831
|
+
* money in sight hands the caller's own object straight back.
|
|
1832
|
+
*/
|
|
1833
|
+
/**
|
|
1834
|
+
* Rows left raw because a money column's scale was not in yet.
|
|
1835
|
+
*
|
|
1836
|
+
* Two paths decode inside the message loop, where there is no longer
|
|
1837
|
+
* anywhere to ask the server anything: the Simple Query protocol and
|
|
1838
|
+
* a pipeline. Rather than read those against a scale nobody
|
|
1839
|
+
* confirmed, they set the rows aside and this reads them once the
|
|
1840
|
+
* loop is over and the question can be asked.
|
|
1841
|
+
*/
|
|
1842
|
+
async _decodeDeferredRows(deferred, rowDecoder, options) {
|
|
1843
|
+
await this.ensureMoneyFormat();
|
|
1844
|
+
const opts = this.withDefaults(options);
|
|
1845
|
+
let d;
|
|
1846
|
+
let i;
|
|
1847
|
+
let k;
|
|
1848
|
+
for (i = 0; i < deferred.length; i++) {
|
|
1849
|
+
d = deferred[i];
|
|
1850
|
+
const l = d.raw.length;
|
|
1851
|
+
const out = new Array(l);
|
|
1852
|
+
for (k = 0; k < l; k++)
|
|
1853
|
+
out[k] = rowDecoder.decode(d.parsers, d.raw[k].data, d.raw[k].columnCount, opts, d.fields);
|
|
1854
|
+
d.target.rows = out;
|
|
1855
|
+
}
|
|
1856
|
+
return opts;
|
|
1857
|
+
}
|
|
1858
|
+
/**
|
|
1859
|
+
* The type map a statement is parsed and its parameters typed with:
|
|
1860
|
+
* the call's, else the connection's, with whatever `temporalTypes`
|
|
1861
|
+
* asks for registered on top of it.
|
|
1862
|
+
*/
|
|
1863
|
+
resolveTypeMap(options) {
|
|
1864
|
+
const defaults = this._mappingDefaults;
|
|
1865
|
+
const base = options?.typeMap || defaults?.typeMap || GlobalTypeMap;
|
|
1866
|
+
const temporal = options?.temporalTypes ?? defaults?.temporalTypes;
|
|
1867
|
+
return temporal ? temporalTypeMap(temporal, base) : base;
|
|
1868
|
+
}
|
|
1869
|
+
withDefaults(options) {
|
|
1870
|
+
const defaults = this._mappingDefaults;
|
|
1871
|
+
const money = this._moneyFormat;
|
|
1872
|
+
const dateStyle = this._currentDateStyle();
|
|
1873
|
+
if (!defaults &&
|
|
1874
|
+
!money &&
|
|
1875
|
+
!dateStyle &&
|
|
1876
|
+
!options.temporalTypes &&
|
|
1877
|
+
!options.fetchAsString &&
|
|
1878
|
+
options.decimalAsString === undefined)
|
|
1879
|
+
return options;
|
|
1880
|
+
let out;
|
|
1881
|
+
if (defaults) {
|
|
1882
|
+
let k;
|
|
1883
|
+
for (k in defaults) {
|
|
1884
|
+
// The call always wins; the connection only fills a gap.
|
|
1885
|
+
if (options[k] !== undefined)
|
|
1886
|
+
continue;
|
|
1887
|
+
out = out || { ...options };
|
|
1888
|
+
out[k] = defaults[k];
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
if (money && !(out || options).moneyFormat) {
|
|
1892
|
+
out = out || { ...options };
|
|
1893
|
+
out.moneyFormat = money;
|
|
1894
|
+
}
|
|
1895
|
+
if (dateStyle && !(out || options).dateStyle) {
|
|
1896
|
+
out = out || { ...options };
|
|
1897
|
+
out.dateStyle = dateStyle;
|
|
1898
|
+
}
|
|
1899
|
+
// Every path into a statement passes through here, so a selector
|
|
1900
|
+
// that asks for something this does not do is refused once, at the
|
|
1901
|
+
// call that carries it, rather than in whichever of the three
|
|
1902
|
+
// readers of the list happens to reach it first.
|
|
1903
|
+
if ((out || options).fetchAsString)
|
|
1904
|
+
validateFetchAsString((out || options).fetchAsString, this.resolveTypeMap(out || options));
|
|
1905
|
+
if ((out || options).decimalAsString !== undefined)
|
|
1906
|
+
validateDecimalAsString((out || options).decimalAsString);
|
|
1907
|
+
if ((out || options).temporalTypes) {
|
|
1908
|
+
// On top of whatever type map is in effect, rather than instead of
|
|
1909
|
+
// it. The map is memoized per (base, selection), so this is two
|
|
1910
|
+
// lookups and not a copy.
|
|
1911
|
+
const base = (out || options).typeMap || GlobalTypeMap;
|
|
1912
|
+
const map = this.resolveTypeMap(out || options);
|
|
1913
|
+
if (map !== base) {
|
|
1914
|
+
out = out || { ...options };
|
|
1915
|
+
out.typeMap = map;
|
|
1916
|
+
}
|
|
1917
|
+
if (!(out || options).timeZone) {
|
|
1918
|
+
const tz = this._currentTimeZone();
|
|
1919
|
+
if (tz) {
|
|
1920
|
+
out = out || { ...options };
|
|
1921
|
+
out.timeZone = tz;
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
return out || options;
|
|
1926
|
+
}
|
|
1927
|
+
/**
|
|
1928
|
+
* The session's DateStyle, when it is not ISO.
|
|
1929
|
+
*
|
|
1930
|
+
* Read off the reported parameters rather than cached once: `SET
|
|
1931
|
+
* DateStyle` is answered with a fresh ParameterStatus, so the string
|
|
1932
|
+
* changes under us and a connection that changes it mid-session must
|
|
1933
|
+
* not go on decoding by the old one. The comparison is against the
|
|
1934
|
+
* same string the socket holds, so it costs a read and a pointer
|
|
1935
|
+
* compare while nothing changes - which is always, for the ISO
|
|
1936
|
+
* default that needs no parsing at all.
|
|
1937
|
+
*/
|
|
1938
|
+
/**
|
|
1939
|
+
* The zone `timestamptz` values are given when they decode to a
|
|
1940
|
+
* Temporal.ZonedDateTime. Checked when the server's answer changes, so
|
|
1941
|
+
* that a zone nothing can resolve is reported against the setting it
|
|
1942
|
+
* came from instead of from inside a decode.
|
|
1943
|
+
*/
|
|
1944
|
+
_currentTimeZone() {
|
|
1945
|
+
const raw = this.socket.sessionParameters.TimeZone;
|
|
1946
|
+
if (raw !== this._timeZoneRaw) {
|
|
1947
|
+
this._timeZoneRaw = raw;
|
|
1948
|
+
this._timeZone = raw
|
|
1949
|
+
? requireTimeZone(raw, 'the session TimeZone')
|
|
1950
|
+
: undefined;
|
|
1951
|
+
}
|
|
1952
|
+
return this._timeZone;
|
|
1953
|
+
}
|
|
1954
|
+
_currentDateStyle() {
|
|
1955
|
+
const raw = this.socket.sessionParameters.DateStyle;
|
|
1956
|
+
if (raw !== this._dateStyleRaw) {
|
|
1957
|
+
this._dateStyleRaw = raw;
|
|
1958
|
+
this._dateStyle = parseDateStyleSetting(raw);
|
|
1959
|
+
}
|
|
1960
|
+
return this._dateStyle;
|
|
1440
1961
|
}
|
|
1441
1962
|
}
|
|
1442
1963
|
export function getIntlConnection(connection) {
|