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.
Files changed (56) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/README.md +50 -0
  3. package/connection/connection.d.ts +14 -2
  4. package/connection/connection.js +70 -10
  5. package/connection/intl-connection.d.ts +146 -5
  6. package/connection/intl-connection.js +485 -35
  7. package/connection/pool.d.ts +30 -6
  8. package/connection/pool.js +28 -3
  9. package/connection/prepared-statement.d.ts +11 -0
  10. package/connection/prepared-statement.js +39 -5
  11. package/data-type-map.d.ts +16 -0
  12. package/data-type-map.js +56 -12
  13. package/data-types/classes/numeric.d.ts +8 -4
  14. package/data-types/classes/numeric.js +8 -4
  15. package/data-types/date-type.js +2 -2
  16. package/data-types/int2-vector-type.js +4 -1
  17. package/data-types/money-type.d.ts +38 -0
  18. package/data-types/money-type.js +203 -0
  19. package/data-types/numeric-type.d.ts +2 -0
  20. package/data-types/numeric-type.js +1 -1
  21. package/data-types/oid-vector-type.js +4 -1
  22. package/data-types/timestamp-type.js +2 -2
  23. package/data-types/timestamptz-type.js +11 -2
  24. package/index.d.ts +1 -0
  25. package/index.js +1 -0
  26. package/interfaces/data-mapping-options.d.ts +27 -0
  27. package/interfaces/database-connection-params.d.ts +32 -5
  28. package/interfaces/query-options.d.ts +32 -0
  29. package/interfaces/script-execute-options.d.ts +32 -0
  30. package/interfaces/transaction-options.d.ts +27 -0
  31. package/interfaces/transaction-options.js +1 -0
  32. package/package.json +1 -1
  33. package/protocol/database-error.d.ts +15 -0
  34. package/protocol/database-error.js +19 -0
  35. package/protocol/frontend.js +17 -0
  36. package/protocol/pg-socket.js +7 -10
  37. package/util/array-leaf.d.ts +10 -0
  38. package/util/array-leaf.js +22 -0
  39. package/util/date-style.d.ts +47 -0
  40. package/util/date-style.js +123 -0
  41. package/util/decode-binaryarray.js +8 -1
  42. package/util/encode-binaryarray.d.ts +12 -1
  43. package/util/encode-binaryarray.js +13 -2
  44. package/util/format-datetime.d.ts +18 -0
  45. package/util/format-datetime.js +33 -0
  46. package/util/parse-datetime.d.ts +4 -3
  47. package/util/parse-datetime.js +34 -9
  48. package/util/resolve-column-formats.js +4 -0
  49. package/util/row-decoder.d.ts +24 -4
  50. package/util/row-decoder.js +25 -10
  51. package/util/sql-tag.js +16 -0
  52. package/util/stringify-for-sql.js +7 -0
  53. package/util/transaction-command.d.ts +7 -0
  54. package/util/transaction-command.js +32 -0
  55. package/util/unspecified-param.d.ts +27 -0
  56. package/util/unspecified-param.js +31 -0
@@ -5,16 +5,21 @@ import type { QueryResult } from '../interfaces/query-result.js';
5
5
  import type { ScriptExecuteOptions } from '../interfaces/script-execute-options.js';
6
6
  import type { ScriptResult } from '../interfaces/script-result.js';
7
7
  import type { StatementPrepareOptions } from '../interfaces/statement-prepare-options.js';
8
+ import type { TransactionOptions } from '../interfaces/transaction-options.js';
8
9
  import { SafeEventEmitter } from '../safe-event-emitter.js';
9
10
  import { QueryRequest } from '../util/sql-tag.js';
10
11
  import { Connection, type NotificationCallback } from './connection.js';
11
12
  import { IntlConnection } from './intl-connection.js';
12
13
  import type { PreparedStatement } from './prepared-statement.js';
13
- export interface PoolPipelineOptions {
14
- pipeline?: boolean;
15
- }
16
- export type PoolQueryOptions = QueryOptions & PoolPipelineOptions;
17
- export type PoolScriptExecuteOptions = ScriptExecuteOptions & PoolPipelineOptions;
14
+ /**
15
+ * @deprecated `pipeline` is part of `QueryOptions` and
16
+ * `ScriptExecuteOptions` now, so every connection takes it too. Kept as
17
+ * an alias of that one field so the exported type still means what it
18
+ * did.
19
+ */
20
+ export type PoolPipelineOptions = Pick<QueryOptions, 'pipeline'>;
21
+ export type PoolQueryOptions = QueryOptions;
22
+ export type PoolScriptExecuteOptions = ScriptExecuteOptions;
18
23
  /**
19
24
  * One pooled connection the pipelined path is currently borrowing.
20
25
  *
@@ -93,7 +98,7 @@ export declare class Pool extends SafeEventEmitter {
93
98
  * is free to pick a different connection, and a transaction lives on
94
99
  * one. This is the way to get several statements onto the same one.
95
100
  */
96
- transaction<T>(fn: (connection: Connection) => Promise<T>): Promise<T>;
101
+ transaction<T>(fn: (connection: Connection) => Promise<T>, options?: TransactionOptions): Promise<T>;
97
102
  prepare(sql: string, options?: StatementPrepareOptions): Promise<PreparedStatement>;
98
103
  release(connection: Connection): Promise<void>;
99
104
  listen(channel: string, callback: NotificationCallback): Promise<void>;
@@ -113,6 +118,25 @@ export declare class Pool extends SafeEventEmitter {
113
118
  * the connection had no query in flight to reject.
114
119
  */
115
120
  protected _onConnectionDestroyed(intlCon: IntlConnection): void;
121
+ /**
122
+ * Whether a one-shot query may share a pooled connection with the
123
+ * queries already in flight on it.
124
+ *
125
+ * **Opt-in**, per call or with `pipeline: true` on the pool. It is
126
+ * worth asking for - `max` stops being a ceiling on concurrent
127
+ * queries and becomes one on connections, and a burst of 500 through
128
+ * a pool of 10 measured 12ms against 29ms - but it is not the default,
129
+ * because it moves the query onto a connection other callers are
130
+ * using and session state there stops being the caller's own.
131
+ *
132
+ * The exclusions below are not policy, they are statements that need a
133
+ * connection to themselves for longer than the call takes to resolve:
134
+ * a cursor reads from a portal afterwards, a transaction spans later
135
+ * statements, a COPY holds the connection mid-stream, `autoCommit:
136
+ * false` says a transaction is being managed by hand, and an
137
+ * `AbortSignal` cancels by tearing down whatever the connection is
138
+ * doing - which, shared, is not only this query.
139
+ */
116
140
  protected _canPipeline(pipeline: boolean | undefined, sql: string, autoCommit: boolean | undefined, signal: AbortSignal | undefined): boolean;
117
141
  /**
118
142
  * Picks the pooled connection a pipelined query should ride on, or
@@ -157,6 +157,12 @@ export class Pool extends SafeEventEmitter {
157
157
  connection.on('execute', (...args) => this.emit('execute', ...args));
158
158
  if (this.listenerCount('query'))
159
159
  connection.on('query', (...args) => this.emit('query', ...args));
160
+ // Forwarded with the connection that raised it, the way 'destroy'
161
+ // names its own subject: a caller using pool.query() never sees the
162
+ // Connection, so without this a notice raised by their statement has
163
+ // nowhere to go at all.
164
+ if (this.listenerCount('notice'))
165
+ connection.on('notice', msg => this.emit('notice', msg, connection));
160
166
  return connection;
161
167
  }
162
168
  /**
@@ -266,10 +272,10 @@ export class Pool extends SafeEventEmitter {
266
272
  * is free to pick a different connection, and a transaction lives on
267
273
  * one. This is the way to get several statements onto the same one.
268
274
  */
269
- async transaction(fn) {
275
+ async transaction(fn, options) {
270
276
  const connection = await this.acquire();
271
277
  try {
272
- return await connection.transaction(fn);
278
+ return await connection.transaction(fn, options);
273
279
  }
274
280
  finally {
275
281
  await this.release(connection);
@@ -355,8 +361,27 @@ export class Pool extends SafeEventEmitter {
355
361
  // connection that could not be created stays unambiguous.
356
362
  this.emit('error', reason);
357
363
  }
364
+ /**
365
+ * Whether a one-shot query may share a pooled connection with the
366
+ * queries already in flight on it.
367
+ *
368
+ * **Opt-in**, per call or with `pipeline: true` on the pool. It is
369
+ * worth asking for - `max` stops being a ceiling on concurrent
370
+ * queries and becomes one on connections, and a burst of 500 through
371
+ * a pool of 10 measured 12ms against 29ms - but it is not the default,
372
+ * because it moves the query onto a connection other callers are
373
+ * using and session state there stops being the caller's own.
374
+ *
375
+ * The exclusions below are not policy, they are statements that need a
376
+ * connection to themselves for longer than the call takes to resolve:
377
+ * a cursor reads from a portal afterwards, a transaction spans later
378
+ * statements, a COPY holds the connection mid-stream, `autoCommit:
379
+ * false` says a transaction is being managed by hand, and an
380
+ * `AbortSignal` cancels by tearing down whatever the connection is
381
+ * doing - which, shared, is not only this query.
382
+ */
358
383
  _canPipeline(pipeline, sql, autoCommit, signal) {
359
- return (pipeline === true &&
384
+ return ((pipeline != null ? pipeline : this.config.pipeline === true) &&
360
385
  !signal &&
361
386
  this._pipelineMaxQueries > 1 &&
362
387
  this._pipelineMaxConnections > 0 &&
@@ -68,6 +68,17 @@ export declare class PreparedStatement extends SafeEventEmitter implements Async
68
68
  * takes, minus `fetchCount` and `cursor`, which a batch cannot honour.
69
69
  */
70
70
  executeBatch(paramSets: any[][], options?: QueryOptions): Promise<BatchResult>;
71
+ /**
72
+ * A notice reaches both this statement and the connection it ran on.
73
+ *
74
+ * These are the three loops the statement keeps for its own close and
75
+ * cancel traffic; an `execute()` runs through the connection's loop
76
+ * instead and is forwarded there. Without this second hop a notice
77
+ * arriving on one of these three would be the only kind a caller
78
+ * holding a `Connection` never saw. Hard to provoke - `DEALLOCATE`
79
+ * does not normally raise one - which is why there is no test for it.
80
+ */
81
+ protected _emitNotice(msg: any): void;
71
82
  protected _executeBatchWithTransaction(paramSets: any[][], options: QueryOptions): Promise<BatchResult>;
72
83
  protected _executeWithTransaction(options?: QueryOptions): Promise<QueryResult>;
73
84
  /**
@@ -1,3 +1,4 @@
1
+ import { DataTypeOIDs } from '../constants.js';
1
2
  import { GlobalTypeMap } from '../data-type-map.js';
2
3
  import { Protocol } from '../protocol/protocol.js';
3
4
  import { SafeEventEmitter } from '../safe-event-emitter.js';
@@ -5,6 +6,7 @@ import { withAbortSignal } from '../util/abort-signal.js';
5
6
  import { getParsers } from '../util/get-parsers.js';
6
7
  import { resolveColumnFormats } from '../util/resolve-column-formats.js';
7
8
  import { resolveRowType } from '../util/row-decoder.js';
9
+ import { isTransactionCommand, refusesSavepoint, } from '../util/transaction-command.js';
8
10
  import { wrapRowDescription } from '../util/wrap-row-description.js';
9
11
  import { Cursor } from './cursor.js';
10
12
  import { getIntlConnection } from './intl-connection.js';
@@ -51,6 +53,9 @@ export class PreparedStatement extends SafeEventEmitter {
51
53
  }
52
54
  async execute(options = {}) {
53
55
  const intlCon = getIntlConnection(this.connection);
56
+ // Folded in here rather than deeper: a cursor keeps these options
57
+ // and decodes its own rows with them long after this call is over.
58
+ options = intlCon.withDefaults(options);
54
59
  if (options.signal)
55
60
  return withAbortSignal(options.signal, () => intlCon.cancel(), () => this._executeWithTransaction(options));
56
61
  return this._executeWithTransaction(options);
@@ -108,12 +113,27 @@ export class PreparedStatement extends SafeEventEmitter {
108
113
  throw new Error('executeBatch() cannot return a cursor - a batch runs every set to ' +
109
114
  'completion under one Sync, so there is no portal left to fetch from');
110
115
  const intlCon = getIntlConnection(this.connection);
116
+ options = intlCon.withDefaults(options);
111
117
  if (!paramSets.length)
112
118
  return { results: [], totalRowsAffected: 0 };
113
119
  if (options.signal)
114
120
  return withAbortSignal(options.signal, () => intlCon.cancel(), () => this._executeBatchWithTransaction(paramSets, options));
115
121
  return this._executeBatchWithTransaction(paramSets, options);
116
122
  }
123
+ /**
124
+ * A notice reaches both this statement and the connection it ran on.
125
+ *
126
+ * These are the three loops the statement keeps for its own close and
127
+ * cancel traffic; an `execute()` runs through the connection's loop
128
+ * instead and is forwarded there. Without this second hop a notice
129
+ * arriving on one of these three would be the only kind a caller
130
+ * holding a `Connection` never saw. Hard to provoke - `DEALLOCATE`
131
+ * does not normally raise one - which is why there is no test for it.
132
+ */
133
+ _emitNotice(msg) {
134
+ this.emit('notice', msg);
135
+ getIntlConnection(this.connection).emit('notice', msg);
136
+ }
117
137
  _executeBatchWithTransaction(paramSets, options) {
118
138
  const intlCon = getIntlConnection(this.connection);
119
139
  return this._withTransaction(options, () => intlCon.executeBatchReused(this.name, this._fields, this.paramTypes, paramSets, options));
@@ -139,7 +159,17 @@ export class PreparedStatement extends SafeEventEmitter {
139
159
  */
140
160
  async _withTransaction(options = {}, fn, canInlineSavepoint = false) {
141
161
  const intlCon = getIntlConnection(this.connection);
142
- const transactionCommand = this.sql.match(/^(\bBEGIN\b|\bCOMMIT\b|\bSTART\b|\bROLLBACK|SAVEPOINT|RELEASE\b)/i);
162
+ // See Connection._query(): a money parameter is written as an int64
163
+ // of minor units, so the server's scale has to be known before the
164
+ // Bind goes out.
165
+ if (this.paramTypes?.includes(DataTypeOIDs.money) ||
166
+ this.paramTypes?.includes(DataTypeOIDs._money) ||
167
+ // And before any of this statement's own money columns are read:
168
+ // a cursor and a batch both decode inside the message loop, where
169
+ // there is no longer anywhere to ask from.
170
+ intlCon.needsMoneyFormat(this._fields, options))
171
+ await intlCon.ensureMoneyFormat();
172
+ const transactionCommand = isTransactionCommand(this.sql);
143
173
  let beginFirst = false;
144
174
  let commitLast = false;
145
175
  const autoCommit = options?.autoCommit;
@@ -156,7 +186,11 @@ export class PreparedStatement extends SafeEventEmitter {
156
186
  await intlCon.execute('BEGIN');
157
187
  // See IntlConnection.execute()'s own rollbackOnError for why
158
188
  // intlCon.inTransaction goes first here but last in the checks below.
159
- const rollbackOnError = !transactionCommand &&
189
+ const rollbackOnError =
190
+ // See IntlConnection.execute(): `SET TRANSACTION` keeps the
191
+ // implicit BEGIN and loses only the savepoint, which PostgreSQL
192
+ // refuses to let it run inside.
193
+ !refusesSavepoint(this.sql) &&
160
194
  intlCon.inTransaction &&
161
195
  (options?.rollbackOnError ?? intlCon.config.rollbackOnError ?? true);
162
196
  // When `fn` can put the savepoint on the wire itself, SAVEPOINT and
@@ -268,7 +302,7 @@ export class PreparedStatement extends SafeEventEmitter {
268
302
  }, (code, msg, done) => {
269
303
  switch (code) {
270
304
  case Protocol.BackendMessageCode.NoticeResponse:
271
- this.emit('notice', msg);
305
+ this._emitNotice(msg);
272
306
  break;
273
307
  case Protocol.BackendMessageCode.CloseComplete:
274
308
  // Arrives twice (once per Close) - neither call is terminal.
@@ -299,7 +333,7 @@ export class PreparedStatement extends SafeEventEmitter {
299
333
  const closePromise = socket.sendCloseMessage({ type: 'S', name: this.name }, (code, msg, done) => {
300
334
  switch (code) {
301
335
  case Protocol.BackendMessageCode.NoticeResponse:
302
- this.emit('notice', msg);
336
+ this._emitNotice(msg);
303
337
  break;
304
338
  case Protocol.BackendMessageCode.CloseComplete:
305
339
  done();
@@ -314,7 +348,7 @@ export class PreparedStatement extends SafeEventEmitter {
314
348
  const syncPromise = socket.sendSyncMessage((code, msg, done) => {
315
349
  switch (code) {
316
350
  case Protocol.BackendMessageCode.NoticeResponse:
317
- this.emit('notice', msg);
351
+ this._emitNotice(msg);
318
352
  break;
319
353
  case Protocol.BackendMessageCode.ReadyForQuery:
320
354
  intoCon.transactionStatus = msg.status;
@@ -1,6 +1,8 @@
1
1
  import type { DataType } from './interfaces/data-type.js';
2
2
  import type { OID } from './types.js';
3
3
  export declare class DataTypeMap {
4
+ protected _inferScalars: DataType[];
5
+ protected _inferArrays: DataType[];
4
6
  private _itemsByOID;
5
7
  private _items;
6
8
  /**
@@ -17,6 +19,20 @@ export declare class DataTypeMap {
17
19
  constructor(other?: DataTypeMap);
18
20
  get(oid: OID): DataType;
19
21
  register(dataTypes: DataType | DataType[]): void;
22
+ /**
23
+ * The two lists determine() actually walks, newest-registered first.
24
+ *
25
+ * A value is either an array or it is not, so half of `_items` can
26
+ * never match it - and the half that can is the smaller one: of 127
27
+ * registered types, a plain number used to be tested against 22 and
28
+ * skipped past 105. Rebuilt on registration, which happens at startup
29
+ * and hardly ever again, so the walk itself reads a list that is
30
+ * already only what it needs.
31
+ *
32
+ * A type that is not inferrable is left out of both: determine() may
33
+ * never pick it, however well its isType matches.
34
+ */
35
+ protected _rebuildInferenceLists(): void;
20
36
  determine(value: any): OID;
21
37
  }
22
38
  export declare const GlobalTypeMap: DataTypeMap;
package/data-type-map.js CHANGED
@@ -21,6 +21,7 @@ import { ArrayJsonPathType, JsonPathType } from './data-types/jsonpath-type.js';
21
21
  import { ArrayLineType, LineType } from './data-types/line-type.js';
22
22
  import { ArrayLsegType, LsegType } from './data-types/lseg-type.js';
23
23
  import { ArrayMacaddr8Type, ArrayMacaddrType, Macaddr8Type, MacaddrType, } from './data-types/macaddr-type.js';
24
+ import { ArrayMoneyType, MoneyType } from './data-types/money-type.js';
24
25
  import { ArrayNumericType, NumericType } from './data-types/numeric-type.js';
25
26
  import { ArrayOidType, OidType } from './data-types/oid-type.js';
26
27
  import { ArrayOidVectorType, OidVectorType, } from './data-types/oid-vector-type.js';
@@ -38,7 +39,10 @@ import { ArrayTsQueryType, ArrayTsVectorType, TsQueryType, TsVectorType, } from
38
39
  import { ArrayUuidType, UuidType } from './data-types/uuid-type.js';
39
40
  import { ArrayVarcharType, VarcharType } from './data-types/varchar-type.js';
40
41
  import { ArrayCidType, ArrayXid8Type, ArrayXidType, CidType, Xid8Type, XidType, } from './data-types/xid-type.js';
42
+ import { arrayLeaf } from './util/array-leaf.js';
41
43
  export class DataTypeMap {
44
+ _inferScalars = [];
45
+ _inferArrays = [];
42
46
  _itemsByOID = {};
43
47
  _items = [];
44
48
  /**
@@ -56,6 +60,11 @@ export class DataTypeMap {
56
60
  if (other instanceof DataTypeMap) {
57
61
  this._items = [...other._items];
58
62
  this._itemsByOID = { ...other._itemsByOID };
63
+ // Shared, not copied: they are rebuilt wholesale by register(),
64
+ // so a copy that registers nothing never diverges and one that
65
+ // does gets its own pair then.
66
+ this._inferScalars = other._inferScalars;
67
+ this._inferArrays = other._inferArrays;
59
68
  }
60
69
  }
61
70
  get(oid) {
@@ -71,6 +80,37 @@ export class DataTypeMap {
71
80
  else
72
81
  this._items.push(t);
73
82
  }
83
+ this._rebuildInferenceLists();
84
+ }
85
+ /**
86
+ * The two lists determine() actually walks, newest-registered first.
87
+ *
88
+ * A value is either an array or it is not, so half of `_items` can
89
+ * never match it - and the half that can is the smaller one: of 127
90
+ * registered types, a plain number used to be tested against 22 and
91
+ * skipped past 105. Rebuilt on registration, which happens at startup
92
+ * and hardly ever again, so the walk itself reads a list that is
93
+ * already only what it needs.
94
+ *
95
+ * A type that is not inferrable is left out of both: determine() may
96
+ * never pick it, however well its isType matches.
97
+ */
98
+ _rebuildInferenceLists() {
99
+ const scalars = [];
100
+ const arrays = [];
101
+ let i;
102
+ let t;
103
+ // Built back to front, so index 0 is the newest registration and the
104
+ // walk runs forward - which is the direction a JS array is fastest
105
+ // to read and the one that needs no length arithmetic per step.
106
+ for (i = this._items.length - 1; i >= 0; i--) {
107
+ t = this._items[i];
108
+ if (t.inferrable === false)
109
+ continue;
110
+ (t.elementsOID ? arrays : scalars).push(t);
111
+ }
112
+ this._inferScalars = scalars;
113
+ this._inferArrays = arrays;
74
114
  }
75
115
  determine(value) {
76
116
  if (value == null)
@@ -94,21 +134,22 @@ export class DataTypeMap {
94
134
  'it at the call site (`new BindParam(oid, value)`)');
95
135
  }
96
136
  }
137
+ // An array is typed from the first value inside it that is actually
138
+ // a value: `value[0]` answers `_int2vector` for [[1, 2], [3, 4]],
139
+ // because the first element is itself an array and a vector is the
140
+ // one registered type an array of numbers matches, and answers
141
+ // nothing at all for [null, 2, 3].
97
142
  const valueIsArray = Array.isArray(value);
143
+ const candidates = valueIsArray ? this._inferArrays : this._inferScalars;
144
+ const v = valueIsArray ? arrayLeaf(value) : value;
145
+ // Newest-registered first, so a later registration of the same OID
146
+ // wins - see _rebuildInferenceLists() for how the order is kept.
147
+ const l = candidates.length;
98
148
  let i;
99
149
  let t;
100
- for (i = this._items.length - 1; i >= 0; i--) {
101
- t = this._items[i];
102
- // Walked newest-registered first, so a later registration of the
103
- // same OID wins - and a type marked `inferrable: false` is never
104
- // picked here at all, however well `isType` matches.
105
- if (t.inferrable === false)
106
- continue;
107
- if (valueIsArray) {
108
- if (t.elementsOID && t.isType(value[0]))
109
- return t.oid;
110
- }
111
- else if (!t.elementsOID && t.isType(value))
150
+ for (i = 0; i < l; i++) {
151
+ t = candidates[i];
152
+ if (t.isType(v))
112
153
  return t.oid;
113
154
  }
114
155
  return DataTypeOIDs.unknown;
@@ -121,6 +162,9 @@ GlobalTypeMap.register([JsonbType, ArrayJsonbType]);
121
162
  GlobalTypeMap.register([JsonType, ArrayJsonType]);
122
163
  GlobalTypeMap.register([BoolType, ArrayBoolType]);
123
164
  GlobalTypeMap.register([NumericType, ArrayNumericType]);
165
+ // Registered after numeric and not inferrable: a JavaScript number is
166
+ // never money by shape, and the type is reached by naming it.
167
+ GlobalTypeMap.register([MoneyType, ArrayMoneyType]);
124
168
  GlobalTypeMap.register([Float4Type, ArrayFloat4Type]);
125
169
  GlobalTypeMap.register([Float8Type, ArrayFloat8Type]);
126
170
  GlobalTypeMap.register([Int2Type, ArrayInt2Type]);
@@ -9,10 +9,14 @@
9
9
  * to a plain number; this class is what the rest become, and it carries
10
10
  * the digits exactly as the server wrote them.
11
11
  *
12
- * It is a class rather than a bare string so that it can be told apart:
13
- * `determine()` types a bare string as `varchar`, which the server then
14
- * refuses to put in a numeric column, so a string could be read but not
15
- * written back. A `Numeric` round-trips.
12
+ * It is a class rather than a bare string so that the value keeps
13
+ * saying what it is. A bare string parameter does reach a numeric
14
+ * column - it goes out with no declared type and the server resolves it
15
+ * there (see `util/unspecified-param.ts`) - but only where there is a
16
+ * column to resolve it from: `determine()` answers `varchar` for it, an
17
+ * inlined literal is written with no cast, and a decoded one is
18
+ * indistinguishable from text. A `Numeric` carries the type with the
19
+ * digits.
16
20
  *
17
21
  * There is deliberately no `valueOf()`. Arithmetic on one of these has
18
22
  * to be an explicit decision - `Symbol.toPrimitive` hands back the exact
@@ -9,10 +9,14 @@
9
9
  * to a plain number; this class is what the rest become, and it carries
10
10
  * the digits exactly as the server wrote them.
11
11
  *
12
- * It is a class rather than a bare string so that it can be told apart:
13
- * `determine()` types a bare string as `varchar`, which the server then
14
- * refuses to put in a numeric column, so a string could be read but not
15
- * written back. A `Numeric` round-trips.
12
+ * It is a class rather than a bare string so that the value keeps
13
+ * saying what it is. A bare string parameter does reach a numeric
14
+ * column - it goes out with no declared type and the server resolves it
15
+ * there (see `util/unspecified-param.ts`) - but only where there is a
16
+ * column to resolve it from: `determine()` answers `varchar` for it, an
17
+ * inlined literal is written with no cast, and a decoded one is
18
+ * indistinguishable from text. A `Numeric` carries the type with the
19
+ * digits.
16
20
  *
17
21
  * There is deliberately no `valueOf()`. Arithmetic on one of these has
18
22
  * to be an explicit decision - `Symbol.toPrimitive` hands back the exact
@@ -11,7 +11,7 @@ export const DateType = {
11
11
  },
12
12
  encodeBinary(buf, v, options) {
13
13
  if (typeof v === 'string')
14
- v = parseDate(v, options.utcDates);
14
+ v = parseDate(v, options.utcDates, options.dateStyle);
15
15
  if (v === Infinity) {
16
16
  buf.writeInt32BE(0x7fffffff);
17
17
  return;
@@ -42,7 +42,7 @@ export const DateType = {
42
42
  return d;
43
43
  },
44
44
  decodeText(v, options) {
45
- return parseDate(v, options.utcDates);
45
+ return parseDate(v, options.utcDates, options.dateStyle);
46
46
  },
47
47
  decodeTextBuffer(buf, offset, len, options) {
48
48
  return DateType.decodeText(buf.toString('latin1', offset, offset + len), options);
@@ -10,7 +10,10 @@ export const Int2VectorType = {
10
10
  encodeBinary(buf, v) {
11
11
  encodeBinaryArray(buf, v, DataTypeOIDs.int2, {}, (io, x) => {
12
12
  io.writeInt16BE(x);
13
- });
13
+ },
14
+ // A vector is genuinely 0-based, unlike every ordinary array -
15
+ // `array_lower(pg_index.indkey, 1)` is 0 on the server.
16
+ undefined, 0);
14
17
  },
15
18
  encodeCalculateDim(v) {
16
19
  return [v.length];
@@ -0,0 +1,38 @@
1
+ import type { MoneyFormat } from '../interfaces/data-mapping-options.js';
2
+ import type { DataType } from '../interfaces/data-type.js';
3
+ /**
4
+ * Reads the server's own rendering of `1::money` - see
5
+ * `IntlConnection.ensureMoneyFormat()` for why it is that value: with no
6
+ * thousands separator in it, any separator present can only be the
7
+ * decimal one, and the digits after it can only be the fraction.
8
+ */
9
+ export declare function parseMoneyFormat(text: string): MoneyFormat;
10
+ /** The exact decimal `minorUnits` stands for at `scale` fraction digits. */
11
+ export declare function moneyToString(minorUnits: bigint, scale: number): string;
12
+ /**
13
+ * PostgreSQL's fixed-point currency type.
14
+ *
15
+ * On the wire it is an int64 of the smallest currency unit - `$12.34`
16
+ * arrives as `1234` - and how many of those make a unit is `lc_monetary`,
17
+ * which the server does not report in its startup parameters. So the
18
+ * connection asks it once, on its way up, and every money value is read
19
+ * against that answer. See `IntlConnection.ensureMoneyFormat()`.
20
+ *
21
+ * The value decodes to a plain number, or a `Numeric` when a double
22
+ * cannot carry it. The currency symbol is *not* part of it: it is the
23
+ * server's rendering of the value, not the value, and
24
+ * `fetchAsString: [DataTypeOIDs.money]` asks for that rendering when it
25
+ * is what the caller wants - which is also what `pg` always returns.
26
+ *
27
+ * The question is asked the first time a money column is on its way
28
+ * back, while the rows are still raw bytes - so no value is ever read
29
+ * against a scale nobody confirmed, and a caller who never touches
30
+ * money never asks. `execute()` and `pipeline()` decode inside the
31
+ * message loop, where there is nowhere left to ask from, so those two
32
+ * set the rows aside and read them once the loop is over.
33
+ *
34
+ * `moneyFormat` in the options answers it in advance, for a caller who
35
+ * knows the server or is reading a value some other server rendered.
36
+ */
37
+ export declare const MoneyType: DataType;
38
+ export declare const ArrayMoneyType: DataType;