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.
Files changed (67) hide show
  1. package/CHANGELOG.md +59 -1
  2. package/README.md +50 -0
  3. package/connection/connection.d.ts +14 -2
  4. package/connection/connection.js +63 -11
  5. package/connection/intl-connection.d.ts +164 -6
  6. package/connection/intl-connection.js +558 -37
  7. package/connection/pool.d.ts +30 -6
  8. package/connection/pool.js +22 -3
  9. package/connection/prepared-statement.js +22 -2
  10. package/data-type-map.d.ts +16 -0
  11. package/data-type-map.js +56 -12
  12. package/data-types/circle-type.js +5 -2
  13. package/data-types/classes/geometric.d.ts +49 -7
  14. package/data-types/classes/geometric.js +59 -11
  15. package/data-types/classes/interval.d.ts +40 -16
  16. package/data-types/classes/interval.js +68 -40
  17. package/data-types/classes/numeric.d.ts +6 -0
  18. package/data-types/classes/numeric.js +8 -0
  19. package/data-types/classes/range.d.ts +6 -1
  20. package/data-types/classes/range.js +6 -1
  21. package/data-types/date-type.js +2 -2
  22. package/data-types/interval-type.js +1 -1
  23. package/data-types/money-type.d.ts +38 -0
  24. package/data-types/money-type.js +212 -0
  25. package/data-types/numeric-type.d.ts +2 -0
  26. package/data-types/numeric-type.js +22 -11
  27. package/data-types/temporal-types.d.ts +19 -0
  28. package/data-types/temporal-types.js +520 -0
  29. package/data-types/timestamp-type.js +2 -2
  30. package/data-types/timestamptz-type.js +2 -2
  31. package/index.d.ts +3 -0
  32. package/index.js +3 -0
  33. package/interfaces/data-mapping-options.d.ts +126 -4
  34. package/interfaces/database-connection-params.d.ts +32 -5
  35. package/interfaces/query-options.d.ts +32 -0
  36. package/interfaces/script-execute-options.d.ts +32 -0
  37. package/interfaces/transaction-options.d.ts +27 -0
  38. package/interfaces/transaction-options.js +1 -0
  39. package/package.json +1 -1
  40. package/protocol/database-error.d.ts +15 -0
  41. package/protocol/database-error.js +19 -0
  42. package/protocol/frontend.js +1 -1
  43. package/protocol/pg-socket.js +7 -10
  44. package/util/array-leaf.d.ts +10 -0
  45. package/util/array-leaf.js +22 -0
  46. package/util/connection-config.js +11 -1
  47. package/util/date-style.d.ts +47 -0
  48. package/util/date-style.js +123 -0
  49. package/util/decimal-as-string.d.ts +11 -0
  50. package/util/decimal-as-string.js +30 -0
  51. package/util/fetch-as-string.d.ts +33 -0
  52. package/util/fetch-as-string.js +122 -0
  53. package/util/get-parsers.js +18 -1
  54. package/util/parse-datetime.d.ts +4 -3
  55. package/util/parse-datetime.js +34 -9
  56. package/util/resolve-column-formats.d.ts +5 -11
  57. package/util/resolve-column-formats.js +28 -24
  58. package/util/row-decoder.d.ts +24 -4
  59. package/util/row-decoder.js +25 -10
  60. package/util/sql-tag.js +2 -1
  61. package/util/stringify-for-sql.js +6 -5
  62. package/util/temporal.d.ts +47 -0
  63. package/util/temporal.js +61 -0
  64. package/util/transaction-command.d.ts +7 -0
  65. package/util/transaction-command.js +32 -0
  66. package/util/unspecified-param.d.ts +18 -8
  67. 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 { fetchAsStringEqual, resolveColumnFormats, } from '../util/resolve-column-formats.js';
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('error', err => this._onError(err));
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
- this.socket.on('close', (reason) => this.emit('close', reason));
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 = TRANSACTION_COMMAND_PATTERN.test(sql);
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 = !transactionCommand &&
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
- return await this.socket.sendQueryMessage(sql, (code, msg, done) => {
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
- * and a statement that is itself a transaction command manages its own
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 || TRANSACTION_COMMAND_PATTERN.test(sql))
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
- const needsFields = !!options.fetchAsString?.length || !!options.unknownTypesAsString;
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
- // A Parse that fails caches nothing and reports itself; the statement
726
- // is simply never reused. It runs outside the savepoint, as the
727
- // prepare() the transaction path used to do ahead of its own wrapper.
728
- const { fields } = await this.prepareOnce(sql, paramTypes, name);
729
- const entry = { name, fields };
730
- await this._evictPreparedIfFull();
731
- this._preparedCache.set(key, entry);
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
- const l = rows.length;
1142
- let i;
1143
- for (i = 0; i < l; i++) {
1144
- rows[i] = rowDecoder.decode(parsers, rows[i].data, rows[i].columnCount, options, resultFields);
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
- _onError(err) {
1437
- if (this.socket.state !== ConnectionState.READY)
1438
- return;
1439
- this.emit('error', err);
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) {