postgrejs 3.8.0 → 3.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -1
- package/README.md +50 -0
- package/connection/connection.d.ts +14 -2
- package/connection/connection.js +57 -9
- package/connection/intl-connection.d.ts +146 -5
- package/connection/intl-connection.js +458 -30
- 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/date-type.js +2 -2
- package/data-types/money-type.d.ts +38 -0
- package/data-types/money-type.js +203 -0
- package/data-types/numeric-type.d.ts +2 -0
- package/data-types/numeric-type.js +1 -1
- package/data-types/timestamp-type.js +2 -2
- package/data-types/timestamptz-type.js +2 -2
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/interfaces/data-mapping-options.d.ts +27 -0
- package/interfaces/database-connection-params.d.ts +32 -5
- package/interfaces/query-options.d.ts +32 -0
- package/interfaces/script-execute-options.d.ts +32 -0
- package/interfaces/transaction-options.d.ts +27 -0
- package/interfaces/transaction-options.js +1 -0
- package/package.json +1 -1
- package/protocol/database-error.d.ts +15 -0
- package/protocol/database-error.js +19 -0
- package/protocol/frontend.js +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/date-style.d.ts +47 -0
- package/util/date-style.js +123 -0
- package/util/parse-datetime.d.ts +4 -3
- package/util/parse-datetime.js +34 -9
- package/util/resolve-column-formats.js +4 -0
- package/util/row-decoder.d.ts +24 -4
- package/util/row-decoder.js +25 -10
- package/util/sql-tag.js +2 -1
- package/util/transaction-command.d.ts +7 -0
- package/util/transaction-command.js +32 -0
- package/util/unspecified-param.d.ts +0 -5
- package/util/unspecified-param.js +1 -10
package/connection/pool.d.ts
CHANGED
|
@@ -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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
|
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
|
package/connection/pool.js
CHANGED
|
@@ -272,10 +272,10 @@ export class Pool extends SafeEventEmitter {
|
|
|
272
272
|
* is free to pick a different connection, and a transaction lives on
|
|
273
273
|
* one. This is the way to get several statements onto the same one.
|
|
274
274
|
*/
|
|
275
|
-
async transaction(fn) {
|
|
275
|
+
async transaction(fn, options) {
|
|
276
276
|
const connection = await this.acquire();
|
|
277
277
|
try {
|
|
278
|
-
return await connection.transaction(fn);
|
|
278
|
+
return await connection.transaction(fn, options);
|
|
279
279
|
}
|
|
280
280
|
finally {
|
|
281
281
|
await this.release(connection);
|
|
@@ -361,8 +361,27 @@ export class Pool extends SafeEventEmitter {
|
|
|
361
361
|
// connection that could not be created stays unambiguous.
|
|
362
362
|
this.emit('error', reason);
|
|
363
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
|
+
*/
|
|
364
383
|
_canPipeline(pipeline, sql, autoCommit, signal) {
|
|
365
|
-
return (pipeline === true &&
|
|
384
|
+
return ((pipeline != null ? pipeline : this.config.pipeline === true) &&
|
|
366
385
|
!signal &&
|
|
367
386
|
this._pipelineMaxQueries > 1 &&
|
|
368
387
|
this._pipelineMaxConnections > 0 &&
|
|
@@ -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,6 +113,7 @@ 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)
|
|
@@ -153,7 +159,17 @@ export class PreparedStatement extends SafeEventEmitter {
|
|
|
153
159
|
*/
|
|
154
160
|
async _withTransaction(options = {}, fn, canInlineSavepoint = false) {
|
|
155
161
|
const intlCon = getIntlConnection(this.connection);
|
|
156
|
-
|
|
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);
|
|
157
173
|
let beginFirst = false;
|
|
158
174
|
let commitLast = false;
|
|
159
175
|
const autoCommit = options?.autoCommit;
|
|
@@ -170,7 +186,11 @@ export class PreparedStatement extends SafeEventEmitter {
|
|
|
170
186
|
await intlCon.execute('BEGIN');
|
|
171
187
|
// See IntlConnection.execute()'s own rollbackOnError for why
|
|
172
188
|
// intlCon.inTransaction goes first here but last in the checks below.
|
|
173
|
-
const rollbackOnError =
|
|
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) &&
|
|
174
194
|
intlCon.inTransaction &&
|
|
175
195
|
(options?.rollbackOnError ?? intlCon.config.rollbackOnError ?? true);
|
|
176
196
|
// When `fn` can put the savepoint on the wire itself, SAVEPOINT and
|
package/data-type-map.d.ts
CHANGED
|
@@ -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 =
|
|
101
|
-
t =
|
|
102
|
-
|
|
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]);
|
package/data-types/date-type.js
CHANGED
|
@@ -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);
|
|
@@ -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;
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { DataTypeOIDs } from '../constants.js';
|
|
2
|
+
import { readBigInt64BE } from '../util/bigint-methods.js';
|
|
3
|
+
import { Numeric } from './classes/numeric.js';
|
|
4
|
+
import { trimTrailingZeros } from './numeric-type.js';
|
|
5
|
+
/**
|
|
6
|
+
* What the server's own `lc_monetary` does to a money value, when
|
|
7
|
+
* nothing has asked it. Two fraction digits and a dot is what the great
|
|
8
|
+
* majority of locales use, and it is only ever reached when the probe
|
|
9
|
+
* itself failed - see IntlConnection.ensureMoneyFormat().
|
|
10
|
+
*/
|
|
11
|
+
const DEFAULT_SCALE = 2;
|
|
12
|
+
const DEFAULT_SEPARATOR = '.';
|
|
13
|
+
/**
|
|
14
|
+
* Reads the server's own rendering of `1::money` - see
|
|
15
|
+
* `IntlConnection.ensureMoneyFormat()` for why it is that value: with no
|
|
16
|
+
* thousands separator in it, any separator present can only be the
|
|
17
|
+
* decimal one, and the digits after it can only be the fraction.
|
|
18
|
+
*/
|
|
19
|
+
export function parseMoneyFormat(text) {
|
|
20
|
+
let lastSep = -1;
|
|
21
|
+
let i;
|
|
22
|
+
const l = text.length;
|
|
23
|
+
let c;
|
|
24
|
+
for (i = 0; i < l; i++) {
|
|
25
|
+
c = text.charCodeAt(i);
|
|
26
|
+
if (c >= 48 && c <= 57)
|
|
27
|
+
continue;
|
|
28
|
+
if (c === 46 /* . */ || c === 44 /* , */)
|
|
29
|
+
lastSep = i;
|
|
30
|
+
}
|
|
31
|
+
if (lastSep < 0)
|
|
32
|
+
return { scale: 0, decimalSeparator: DEFAULT_SEPARATOR };
|
|
33
|
+
let scale = 0;
|
|
34
|
+
for (i = lastSep + 1; i < l; i++) {
|
|
35
|
+
c = text.charCodeAt(i);
|
|
36
|
+
if (c >= 48 && c <= 57)
|
|
37
|
+
scale++;
|
|
38
|
+
}
|
|
39
|
+
return { scale, decimalSeparator: text[lastSep] };
|
|
40
|
+
}
|
|
41
|
+
const FALLBACK_FORMAT = {
|
|
42
|
+
scale: DEFAULT_SCALE,
|
|
43
|
+
decimalSeparator: DEFAULT_SEPARATOR,
|
|
44
|
+
};
|
|
45
|
+
function formatOf(options) {
|
|
46
|
+
return options?.moneyFormat || FALLBACK_FORMAT;
|
|
47
|
+
}
|
|
48
|
+
/** The exact decimal `minorUnits` stands for at `scale` fraction digits. */
|
|
49
|
+
export function moneyToString(minorUnits, scale) {
|
|
50
|
+
if (!scale)
|
|
51
|
+
return minorUnits.toString();
|
|
52
|
+
const neg = minorUnits < 0n;
|
|
53
|
+
let digits = (neg ? -minorUnits : minorUnits).toString();
|
|
54
|
+
if (digits.length <= scale)
|
|
55
|
+
digits = digits.padStart(scale + 1, '0');
|
|
56
|
+
return ((neg ? '-' : '') +
|
|
57
|
+
digits.slice(0, digits.length - scale) +
|
|
58
|
+
'.' +
|
|
59
|
+
digits.slice(digits.length - scale));
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* A number while a double carries the value exactly, a Numeric after
|
|
63
|
+
* that - the same rule `numeric` decodes by, and for the same reason.
|
|
64
|
+
* money is an int64 of minor units, so it reaches past 2^53 long before
|
|
65
|
+
* it runs out of range.
|
|
66
|
+
*/
|
|
67
|
+
function toNumberOrNumeric(s) {
|
|
68
|
+
const n = parseFloat(s);
|
|
69
|
+
// The fraction is always written out to the full scale - `1` at scale
|
|
70
|
+
// 2 is `1.00` - and those zeroes are padding, not a difference.
|
|
71
|
+
return String(n) === trimTrailingZeros(s) ? n : new Numeric(s);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Every digit in `s`, read as minor units at `scale` - which is how a
|
|
75
|
+
* rendered money value is read back without knowing the locale that
|
|
76
|
+
* rendered it. `-$1,234.50` is `-123450` at scale 2, whatever the symbol
|
|
77
|
+
* and the separators were.
|
|
78
|
+
*/
|
|
79
|
+
function parseMoneyText(s) {
|
|
80
|
+
let digits = '';
|
|
81
|
+
let neg = false;
|
|
82
|
+
let i;
|
|
83
|
+
const l = s.length;
|
|
84
|
+
let c;
|
|
85
|
+
for (i = 0; i < l; i++) {
|
|
86
|
+
c = s.charCodeAt(i);
|
|
87
|
+
if (c >= 48 && c <= 57)
|
|
88
|
+
digits += s[i];
|
|
89
|
+
else if (c === 45 /* - */ || c === 40 /* ( */)
|
|
90
|
+
neg = true;
|
|
91
|
+
}
|
|
92
|
+
if (!digits)
|
|
93
|
+
digits = '0';
|
|
94
|
+
const v = BigInt(digits);
|
|
95
|
+
return neg ? -v : v;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* PostgreSQL's fixed-point currency type.
|
|
99
|
+
*
|
|
100
|
+
* On the wire it is an int64 of the smallest currency unit - `$12.34`
|
|
101
|
+
* arrives as `1234` - and how many of those make a unit is `lc_monetary`,
|
|
102
|
+
* which the server does not report in its startup parameters. So the
|
|
103
|
+
* connection asks it once, on its way up, and every money value is read
|
|
104
|
+
* against that answer. See `IntlConnection.ensureMoneyFormat()`.
|
|
105
|
+
*
|
|
106
|
+
* The value decodes to a plain number, or a `Numeric` when a double
|
|
107
|
+
* cannot carry it. The currency symbol is *not* part of it: it is the
|
|
108
|
+
* server's rendering of the value, not the value, and
|
|
109
|
+
* `fetchAsString: [DataTypeOIDs.money]` asks for that rendering when it
|
|
110
|
+
* is what the caller wants - which is also what `pg` always returns.
|
|
111
|
+
*
|
|
112
|
+
* The question is asked the first time a money column is on its way
|
|
113
|
+
* back, while the rows are still raw bytes - so no value is ever read
|
|
114
|
+
* against a scale nobody confirmed, and a caller who never touches
|
|
115
|
+
* money never asks. `execute()` and `pipeline()` decode inside the
|
|
116
|
+
* message loop, where there is nowhere left to ask from, so those two
|
|
117
|
+
* set the rows aside and read them once the loop is over.
|
|
118
|
+
*
|
|
119
|
+
* `moneyFormat` in the options answers it in advance, for a caller who
|
|
120
|
+
* knows the server or is reading a value some other server rendered.
|
|
121
|
+
*/
|
|
122
|
+
export const MoneyType = {
|
|
123
|
+
name: 'money',
|
|
124
|
+
oid: DataTypeOIDs.money,
|
|
125
|
+
jsType: 'number',
|
|
126
|
+
/**
|
|
127
|
+
* Written in the server's own decimal separator, so that the value the
|
|
128
|
+
* server parses is the value that was meant - money input is read in
|
|
129
|
+
* `lc_monetary` too, and `12.34` is not `12,34` to a locale that
|
|
130
|
+
* separates with a comma.
|
|
131
|
+
*/
|
|
132
|
+
encodeText(v, options) {
|
|
133
|
+
const { scale, decimalSeparator } = formatOf(options);
|
|
134
|
+
const s = moneyToString(toExactMinor(v, scale), scale);
|
|
135
|
+
return decimalSeparator === '.' ? s : s.replace('.', decimalSeparator);
|
|
136
|
+
},
|
|
137
|
+
encodeBinary(buf, v, options) {
|
|
138
|
+
const { scale } = formatOf(options);
|
|
139
|
+
buf.writeBigInt64BE(toExactMinor(v, scale));
|
|
140
|
+
},
|
|
141
|
+
decodeBinary(buf, offset = 0, _len, options) {
|
|
142
|
+
const v = typeof buf.readBigInt64BE === 'function'
|
|
143
|
+
? buf.readBigInt64BE(offset)
|
|
144
|
+
: readBigInt64BE(buf, offset);
|
|
145
|
+
return toNumberOrNumeric(moneyToString(v, formatOf(options).scale));
|
|
146
|
+
},
|
|
147
|
+
decodeText(s, options) {
|
|
148
|
+
const { scale } = formatOf(options);
|
|
149
|
+
return toNumberOrNumeric(moneyToString(parseMoneyText(s), scale));
|
|
150
|
+
},
|
|
151
|
+
/**
|
|
152
|
+
* Never inferred: a JavaScript number is `int4`, `float8` or `numeric`
|
|
153
|
+
* long before anyone means money by it, and nothing about the value
|
|
154
|
+
* says which. `new BindParam(DataTypeOIDs.money, 12.34)` asks for it.
|
|
155
|
+
*/
|
|
156
|
+
inferrable: false,
|
|
157
|
+
isType(v) {
|
|
158
|
+
return typeof v === 'number' || v instanceof Numeric;
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
/** The int64 of minor units `v` stands for, without going through a double. */
|
|
162
|
+
function toExactMinor(v, scale) {
|
|
163
|
+
if (typeof v === 'bigint')
|
|
164
|
+
return v;
|
|
165
|
+
const s = v instanceof Numeric ? v.value : String(v ?? 0);
|
|
166
|
+
const neg = s.trimStart().startsWith('-');
|
|
167
|
+
let digits = '';
|
|
168
|
+
let frac = -1;
|
|
169
|
+
let i;
|
|
170
|
+
const l = s.length;
|
|
171
|
+
let c;
|
|
172
|
+
for (i = 0; i < l; i++) {
|
|
173
|
+
c = s.charCodeAt(i);
|
|
174
|
+
if (c >= 48 && c <= 57) {
|
|
175
|
+
digits += s[i];
|
|
176
|
+
if (frac >= 0)
|
|
177
|
+
frac++;
|
|
178
|
+
}
|
|
179
|
+
else if (c === 46 /* . */ || c === 44 /* , */)
|
|
180
|
+
frac = 0;
|
|
181
|
+
}
|
|
182
|
+
if (!digits)
|
|
183
|
+
return 0n;
|
|
184
|
+
if (frac < 0)
|
|
185
|
+
frac = 0;
|
|
186
|
+
// Pad or round to exactly `scale` fraction digits.
|
|
187
|
+
if (frac < scale)
|
|
188
|
+
digits = digits.padEnd(digits.length + scale - frac, '0');
|
|
189
|
+
else if (frac > scale) {
|
|
190
|
+
const cut = frac - scale;
|
|
191
|
+
const keep = digits.slice(0, digits.length - cut);
|
|
192
|
+
const next = digits.charCodeAt(digits.length - cut) - 48;
|
|
193
|
+
digits = (BigInt(keep || '0') + (next >= 5 ? 1n : 0n)).toString();
|
|
194
|
+
}
|
|
195
|
+
const out = BigInt(digits || '0');
|
|
196
|
+
return neg ? -out : out;
|
|
197
|
+
}
|
|
198
|
+
export const ArrayMoneyType = {
|
|
199
|
+
...MoneyType,
|
|
200
|
+
name: '_money',
|
|
201
|
+
oid: DataTypeOIDs._money,
|
|
202
|
+
elementsOID: DataTypeOIDs.money,
|
|
203
|
+
};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { DataType } from '../interfaces/data-type.js';
|
|
2
|
+
/** The decimal without the zeroes a declared scale pads it out to. */
|
|
3
|
+
export declare function trimTrailingZeros(s: string): string;
|
|
2
4
|
export declare const NumericType: DataType;
|
|
3
5
|
export declare const ArrayNumericType: DataType;
|
|
4
6
|
/**
|
|
@@ -16,7 +16,7 @@ const PAIRS = new Array(100);
|
|
|
16
16
|
for (let i = 0; i < 100; i++)
|
|
17
17
|
PAIRS[i] = i < 10 ? '0' + i : '' + i;
|
|
18
18
|
/** The decimal without the zeroes a declared scale pads it out to. */
|
|
19
|
-
function trimTrailingZeros(s) {
|
|
19
|
+
export function trimTrailingZeros(s) {
|
|
20
20
|
const dot = s.indexOf('.');
|
|
21
21
|
if (dot < 0)
|
|
22
22
|
return s;
|
|
@@ -13,7 +13,7 @@ export const TimestampType = {
|
|
|
13
13
|
},
|
|
14
14
|
encodeBinary(buf, v, options) {
|
|
15
15
|
if (typeof v === 'string')
|
|
16
|
-
v = parseDateTime(v, options.utcDates);
|
|
16
|
+
v = parseDateTime(v, options.utcDates, options.dateStyle);
|
|
17
17
|
if (v === Infinity) {
|
|
18
18
|
buf.writeInt32BE(0x7fffffff); // hi
|
|
19
19
|
buf.writeUInt32BE(0xffffffff); // lo
|
|
@@ -51,7 +51,7 @@ export const TimestampType = {
|
|
|
51
51
|
return d;
|
|
52
52
|
},
|
|
53
53
|
decodeText(v, options) {
|
|
54
|
-
return parseDateTime(v, options.utcDates);
|
|
54
|
+
return parseDateTime(v, options.utcDates, options.dateStyle);
|
|
55
55
|
},
|
|
56
56
|
// Reads PostgreSQL's own timestamp shape straight from the wire bytes -
|
|
57
57
|
// see timestamptz-type.ts's decodeTextBuffer for why. The guard is what
|
|
@@ -14,7 +14,7 @@ export const TimestamptzType = {
|
|
|
14
14
|
},
|
|
15
15
|
encodeBinary(buf, v, options) {
|
|
16
16
|
if (typeof v === 'string')
|
|
17
|
-
v = parseDateTimeTz(v, options.utcDates);
|
|
17
|
+
v = parseDateTimeTz(v, options.utcDates, options.dateStyle);
|
|
18
18
|
if (v === Infinity) {
|
|
19
19
|
buf.writeInt32BE(0x7fffffff); // hi
|
|
20
20
|
buf.writeUInt32BE(0xffffffff); // lo
|
|
@@ -53,7 +53,7 @@ export const TimestamptzType = {
|
|
|
53
53
|
return new Date((lo + hi * timeMul) / 1000 + timeShift);
|
|
54
54
|
},
|
|
55
55
|
decodeText(v, options) {
|
|
56
|
-
return parseDateTimeTz(v, options.utcDates);
|
|
56
|
+
return parseDateTimeTz(v, options.utcDates, options.dateStyle);
|
|
57
57
|
},
|
|
58
58
|
// Reads PostgreSQL's own timestamp shape straight from the wire bytes.
|
|
59
59
|
// Building the string first and handing it to decodeText() costs about
|
package/index.d.ts
CHANGED
|
@@ -27,6 +27,7 @@ export * from './interfaces/query-result.js';
|
|
|
27
27
|
export * from './interfaces/script-execute-options.js';
|
|
28
28
|
export * from './interfaces/script-result.js';
|
|
29
29
|
export * from './interfaces/statement-prepare-options.js';
|
|
30
|
+
export * from './interfaces/transaction-options.js';
|
|
30
31
|
export * from './protocol/connection-lost-error.js';
|
|
31
32
|
export * from './protocol/database-error.js';
|
|
32
33
|
export * from './protocol/smart-buffer.js';
|
package/index.js
CHANGED
|
@@ -27,6 +27,7 @@ export * from './interfaces/query-result.js';
|
|
|
27
27
|
export * from './interfaces/script-execute-options.js';
|
|
28
28
|
export * from './interfaces/script-result.js';
|
|
29
29
|
export * from './interfaces/statement-prepare-options.js';
|
|
30
|
+
export * from './interfaces/transaction-options.js';
|
|
30
31
|
export * from './protocol/connection-lost-error.js';
|
|
31
32
|
export * from './protocol/database-error.js';
|
|
32
33
|
export * from './protocol/smart-buffer.js';
|