postgrejs 3.8.0 → 3.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +59 -1
- package/README.md +50 -0
- package/connection/connection.d.ts +14 -2
- package/connection/connection.js +63 -11
- package/connection/intl-connection.d.ts +164 -6
- package/connection/intl-connection.js +558 -37
- package/connection/pool.d.ts +30 -6
- package/connection/pool.js +22 -3
- package/connection/prepared-statement.js +22 -2
- package/data-type-map.d.ts +16 -0
- package/data-type-map.js +56 -12
- package/data-types/circle-type.js +5 -2
- package/data-types/classes/geometric.d.ts +49 -7
- package/data-types/classes/geometric.js +59 -11
- package/data-types/classes/interval.d.ts +40 -16
- package/data-types/classes/interval.js +68 -40
- package/data-types/classes/numeric.d.ts +6 -0
- package/data-types/classes/numeric.js +8 -0
- package/data-types/classes/range.d.ts +6 -1
- package/data-types/classes/range.js +6 -1
- package/data-types/date-type.js +2 -2
- package/data-types/interval-type.js +1 -1
- package/data-types/money-type.d.ts +38 -0
- package/data-types/money-type.js +212 -0
- package/data-types/numeric-type.d.ts +2 -0
- package/data-types/numeric-type.js +22 -11
- package/data-types/temporal-types.d.ts +19 -0
- package/data-types/temporal-types.js +520 -0
- package/data-types/timestamp-type.js +2 -2
- package/data-types/timestamptz-type.js +2 -2
- package/index.d.ts +3 -0
- package/index.js +3 -0
- package/interfaces/data-mapping-options.d.ts +126 -4
- package/interfaces/database-connection-params.d.ts +32 -5
- package/interfaces/query-options.d.ts +32 -0
- package/interfaces/script-execute-options.d.ts +32 -0
- package/interfaces/transaction-options.d.ts +27 -0
- package/interfaces/transaction-options.js +1 -0
- package/package.json +1 -1
- package/protocol/database-error.d.ts +15 -0
- package/protocol/database-error.js +19 -0
- package/protocol/frontend.js +1 -1
- package/protocol/pg-socket.js +7 -10
- package/util/array-leaf.d.ts +10 -0
- package/util/array-leaf.js +22 -0
- package/util/connection-config.js +11 -1
- package/util/date-style.d.ts +47 -0
- package/util/date-style.js +123 -0
- package/util/decimal-as-string.d.ts +11 -0
- package/util/decimal-as-string.js +30 -0
- package/util/fetch-as-string.d.ts +33 -0
- package/util/fetch-as-string.js +122 -0
- package/util/get-parsers.js +18 -1
- package/util/parse-datetime.d.ts +4 -3
- package/util/parse-datetime.js +34 -9
- package/util/resolve-column-formats.d.ts +5 -11
- package/util/resolve-column-formats.js +28 -24
- package/util/row-decoder.d.ts +24 -4
- package/util/row-decoder.js +25 -10
- package/util/sql-tag.js +2 -1
- package/util/stringify-for-sql.js +6 -5
- package/util/temporal.d.ts +47 -0
- package/util/temporal.js +61 -0
- package/util/transaction-command.d.ts +7 -0
- package/util/transaction-command.js +32 -0
- package/util/unspecified-param.d.ts +18 -8
- package/util/unspecified-param.js +22 -14
|
@@ -14,20 +14,29 @@ function pad2(v) {
|
|
|
14
14
|
* zone, can add one to a timestamp. Anything here that answered "how many
|
|
15
15
|
* milliseconds is this" would have to invent those lengths.
|
|
16
16
|
*
|
|
17
|
-
*
|
|
17
|
+
* It carries the fields that have a value and no others, which is what
|
|
18
|
+
* the wire format carries too - three quantities, and a zero among them
|
|
19
|
+
* is not a field:
|
|
18
20
|
*
|
|
19
21
|
* ```ts
|
|
20
22
|
* const iv = new Interval({ days: 1, hours: 2 });
|
|
21
|
-
* iv
|
|
23
|
+
* Object.keys(iv); // ['days', 'hours']
|
|
24
|
+
* iv.minutes; // undefined
|
|
22
25
|
* String(iv); // '1 day 02:00:00'
|
|
23
26
|
* iv.toISOString(); // 'P0Y0M1DT2H0M0S'
|
|
24
27
|
* ```
|
|
25
28
|
*
|
|
26
|
-
* The field names are `pg`'s (`postgres-interval`), so code
|
|
27
|
-
* reads the same values from the same places
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
29
|
+
* The field names and the shape are `pg`'s (`postgres-interval`), so code
|
|
30
|
+
* ported from it reads the same values from the same places and a
|
|
31
|
+
* `JSON.stringify` of a row says the same thing. The difference is that
|
|
32
|
+
* this has a `toString()` - `pg`'s object gives `[object Object]` in a log
|
|
33
|
+
* line - and a `toPostgres()`, so the value can be sent back.
|
|
34
|
+
*
|
|
35
|
+
* The fields are optional, so `iv.hours + 1` does not compile without
|
|
36
|
+
* saying what an absent one means (`(iv.hours ?? 0) + 1`). That is the
|
|
37
|
+
* point: which fields a value has depends on the value, not on the type,
|
|
38
|
+
* and the older shape - every field present and 0 - hid that behind
|
|
39
|
+
* numbers nobody wrote.
|
|
31
40
|
*
|
|
32
41
|
* `milliseconds` carries the sub-second part and can be fractional, since
|
|
33
42
|
* PostgreSQL stores microseconds: `interval '0.000001 seconds'` is
|
|
@@ -36,25 +45,35 @@ function pad2(v) {
|
|
|
36
45
|
* A negative interval carries the sign on each field that has one, the way
|
|
37
46
|
* the server prints it: `interval '-1 day -2 hours'` is
|
|
38
47
|
* `{ days: -1, hours: -2 }`.
|
|
48
|
+
*
|
|
49
|
+
* `toPostgres()` is `pg`'s convention for "write yourself back", so a
|
|
50
|
+
* value read here can be passed to any encoder that follows it - this
|
|
51
|
+
* client's own parameter path never needed it, since it knows the class.
|
|
39
52
|
*/
|
|
40
53
|
export class Interval {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
seconds;
|
|
48
|
-
/** The sub-second part, fractional down to a microsecond. */
|
|
49
|
-
milliseconds;
|
|
54
|
+
/**
|
|
55
|
+
* Keeps the fields that have a value. A zero is not one: it is what
|
|
56
|
+
* every field of every interval would otherwise be filled with, and
|
|
57
|
+
* `{ days: 1 }` says what `interval '1 day'` is far better than the
|
|
58
|
+
* same thing plus six zeroes nobody wrote.
|
|
59
|
+
*/
|
|
50
60
|
constructor(fields) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
61
|
+
if (!fields)
|
|
62
|
+
return;
|
|
63
|
+
if (fields.years)
|
|
64
|
+
this.years = fields.years;
|
|
65
|
+
if (fields.months)
|
|
66
|
+
this.months = fields.months;
|
|
67
|
+
if (fields.days)
|
|
68
|
+
this.days = fields.days;
|
|
69
|
+
if (fields.hours)
|
|
70
|
+
this.hours = fields.hours;
|
|
71
|
+
if (fields.minutes)
|
|
72
|
+
this.minutes = fields.minutes;
|
|
73
|
+
if (fields.seconds)
|
|
74
|
+
this.seconds = fields.seconds;
|
|
75
|
+
if (fields.milliseconds)
|
|
76
|
+
this.milliseconds = fields.milliseconds;
|
|
58
77
|
}
|
|
59
78
|
/**
|
|
60
79
|
* Builds one from the three quantities the wire format carries: a signed
|
|
@@ -85,21 +104,22 @@ export class Interval {
|
|
|
85
104
|
}
|
|
86
105
|
/** The whole month count, as the wire format carries it. */
|
|
87
106
|
get totalMonths() {
|
|
88
|
-
return this.years * 12 + this.months;
|
|
107
|
+
return (this.years ?? 0) * 12 + (this.months ?? 0);
|
|
89
108
|
}
|
|
90
109
|
/** The time part as a signed microsecond count, as the wire format carries it. */
|
|
91
110
|
get totalMicroseconds() {
|
|
92
111
|
// Split so that a fractional hours/minutes/seconds is folded in rather
|
|
93
112
|
// than silently truncated, while whole values stay exact past what a
|
|
94
113
|
// double can hold.
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
(
|
|
114
|
+
const h = this.hours ?? 0;
|
|
115
|
+
const mi = this.minutes ?? 0;
|
|
116
|
+
const sec = this.seconds ?? 0;
|
|
117
|
+
const whole = Math.trunc(h) * 3600 + Math.trunc(mi) * 60 + Math.trunc(sec);
|
|
118
|
+
const fraction = (h - Math.trunc(h)) * 3600 +
|
|
119
|
+
(mi - Math.trunc(mi)) * 60 +
|
|
120
|
+
(sec - Math.trunc(sec));
|
|
101
121
|
return (BigInt(whole) * US_PER_SECOND +
|
|
102
|
-
BigInt(Math.round(fraction * 1e6 + this.milliseconds * 1000)));
|
|
122
|
+
BigInt(Math.round(fraction * 1e6 + (this.milliseconds ?? 0) * 1000)));
|
|
103
123
|
}
|
|
104
124
|
/**
|
|
105
125
|
* The interval as PostgreSQL itself prints it under the default
|
|
@@ -155,27 +175,35 @@ export class Interval {
|
|
|
155
175
|
left -= minutes * US_PER_MINUTE;
|
|
156
176
|
const seconds = left / US_PER_SECOND;
|
|
157
177
|
left -= seconds * US_PER_SECOND;
|
|
178
|
+
// The sign goes on the components that have a value, not on all of
|
|
179
|
+
// them: `interval '-1 day -2 hours'` is `P0Y0M-1DT-2H0M0S`, and
|
|
180
|
+
// signing the empty ones printed `-0M-0S` - which is what `pg` does
|
|
181
|
+
// not write and no reader expects.
|
|
158
182
|
const sign = negative ? '-' : '';
|
|
159
|
-
|
|
183
|
+
const signed = (v) => (v ? sign : '') + v;
|
|
184
|
+
let secondsPart = (seconds || left ? sign : '') + seconds;
|
|
160
185
|
if (left)
|
|
161
186
|
secondsPart += '.' + String(left).padStart(6, '0').replace(/0+$/, '');
|
|
162
187
|
return ('P' +
|
|
163
|
-
this.years +
|
|
188
|
+
(this.years ?? 0) +
|
|
164
189
|
'Y' +
|
|
165
|
-
this.months +
|
|
190
|
+
(this.months ?? 0) +
|
|
166
191
|
'M' +
|
|
167
|
-
this.days +
|
|
192
|
+
(this.days ?? 0) +
|
|
168
193
|
'DT' +
|
|
169
|
-
|
|
170
|
-
hours +
|
|
194
|
+
signed(hours) +
|
|
171
195
|
'H' +
|
|
172
|
-
|
|
173
|
-
minutes +
|
|
196
|
+
signed(minutes) +
|
|
174
197
|
'M' +
|
|
175
198
|
secondsPart +
|
|
176
199
|
'S');
|
|
177
200
|
}
|
|
178
|
-
|
|
201
|
+
/**
|
|
202
|
+
* The literal PostgreSQL reads back, for an encoder that asks the value
|
|
203
|
+
* how to write itself - `pg`'s convention, and the reason a value this
|
|
204
|
+
* client decoded can be handed straight to one.
|
|
205
|
+
*/
|
|
206
|
+
toPostgres() {
|
|
179
207
|
return this.toString();
|
|
180
208
|
}
|
|
181
209
|
}
|
|
@@ -29,6 +29,12 @@ export declare class Numeric {
|
|
|
29
29
|
/** The decimal exactly as PostgreSQL wrote it; casts back through `::numeric`. */
|
|
30
30
|
toString(): string;
|
|
31
31
|
toJSON(): string;
|
|
32
|
+
/**
|
|
33
|
+
* The literal PostgreSQL reads back, for an encoder that asks the value
|
|
34
|
+
* how to write itself - `pg`'s convention, and the reason a value this
|
|
35
|
+
* client decoded can be handed straight to one.
|
|
36
|
+
*/
|
|
37
|
+
toPostgres(): string;
|
|
32
38
|
/** The value as a double, which is lossy - that is the whole point of this class. */
|
|
33
39
|
toNumber(): number;
|
|
34
40
|
/**
|
|
@@ -35,6 +35,14 @@ export class Numeric {
|
|
|
35
35
|
toJSON() {
|
|
36
36
|
return this.value;
|
|
37
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* The literal PostgreSQL reads back, for an encoder that asks the value
|
|
40
|
+
* how to write itself - `pg`'s convention, and the reason a value this
|
|
41
|
+
* client decoded can be handed straight to one.
|
|
42
|
+
*/
|
|
43
|
+
toPostgres() {
|
|
44
|
+
return this.toString();
|
|
45
|
+
}
|
|
38
46
|
/** The value as a double, which is lossy - that is the whole point of this class. */
|
|
39
47
|
toNumber() {
|
|
40
48
|
return parseFloat(this.value);
|
|
@@ -70,5 +70,10 @@ export declare class Range<T = any> {
|
|
|
70
70
|
* `::int4range` and reads the way the same value does in psql.
|
|
71
71
|
*/
|
|
72
72
|
toString(): string;
|
|
73
|
-
|
|
73
|
+
/**
|
|
74
|
+
* The literal PostgreSQL reads back, for an encoder that asks the value
|
|
75
|
+
* how to write itself - `pg`'s convention, and the reason a value this
|
|
76
|
+
* client decoded can be handed straight to one.
|
|
77
|
+
*/
|
|
78
|
+
toPostgres(): string;
|
|
74
79
|
}
|
|
@@ -105,7 +105,12 @@ export class Range {
|
|
|
105
105
|
formatBound(this.upper) +
|
|
106
106
|
(this.upperInclusive && this.upper !== null ? ']' : ')'));
|
|
107
107
|
}
|
|
108
|
-
|
|
108
|
+
/**
|
|
109
|
+
* The literal PostgreSQL reads back, for an encoder that asks the value
|
|
110
|
+
* how to write itself - `pg`'s convention, and the reason a value this
|
|
111
|
+
* client decoded can be handed straight to one.
|
|
112
|
+
*/
|
|
113
|
+
toPostgres() {
|
|
109
114
|
return this.toString();
|
|
110
115
|
}
|
|
111
116
|
}
|
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);
|
|
@@ -60,7 +60,7 @@ export const IntervalType = {
|
|
|
60
60
|
encodeBinary(buf, v) {
|
|
61
61
|
const iv = toInterval(v);
|
|
62
62
|
buf.writeBigInt64BE(iv.totalMicroseconds);
|
|
63
|
-
buf.writeInt32BE(iv.days);
|
|
63
|
+
buf.writeInt32BE(iv.days ?? 0);
|
|
64
64
|
buf.writeInt32BE(iv.totalMonths);
|
|
65
65
|
},
|
|
66
66
|
decodeText: parseIntervalText,
|
|
@@ -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,212 @@
|
|
|
1
|
+
import { DataTypeOIDs } from '../constants.js';
|
|
2
|
+
import { readBigInt64BE } from '../util/bigint-methods.js';
|
|
3
|
+
import { wantsDecimalString } from '../util/decimal-as-string.js';
|
|
4
|
+
import { Numeric } from './classes/numeric.js';
|
|
5
|
+
import { trimTrailingZeros } from './numeric-type.js';
|
|
6
|
+
/**
|
|
7
|
+
* What the server's own `lc_monetary` does to a money value, when
|
|
8
|
+
* nothing has asked it. Two fraction digits and a dot is what the great
|
|
9
|
+
* majority of locales use, and it is only ever reached when the probe
|
|
10
|
+
* itself failed - see IntlConnection.ensureMoneyFormat().
|
|
11
|
+
*/
|
|
12
|
+
const DEFAULT_SCALE = 2;
|
|
13
|
+
const DEFAULT_SEPARATOR = '.';
|
|
14
|
+
/**
|
|
15
|
+
* Reads the server's own rendering of `1::money` - see
|
|
16
|
+
* `IntlConnection.ensureMoneyFormat()` for why it is that value: with no
|
|
17
|
+
* thousands separator in it, any separator present can only be the
|
|
18
|
+
* decimal one, and the digits after it can only be the fraction.
|
|
19
|
+
*/
|
|
20
|
+
export function parseMoneyFormat(text) {
|
|
21
|
+
let lastSep = -1;
|
|
22
|
+
let i;
|
|
23
|
+
const l = text.length;
|
|
24
|
+
let c;
|
|
25
|
+
for (i = 0; i < l; i++) {
|
|
26
|
+
c = text.charCodeAt(i);
|
|
27
|
+
if (c >= 48 && c <= 57)
|
|
28
|
+
continue;
|
|
29
|
+
if (c === 46 /* . */ || c === 44 /* , */)
|
|
30
|
+
lastSep = i;
|
|
31
|
+
}
|
|
32
|
+
if (lastSep < 0)
|
|
33
|
+
return { scale: 0, decimalSeparator: DEFAULT_SEPARATOR };
|
|
34
|
+
let scale = 0;
|
|
35
|
+
for (i = lastSep + 1; i < l; i++) {
|
|
36
|
+
c = text.charCodeAt(i);
|
|
37
|
+
if (c >= 48 && c <= 57)
|
|
38
|
+
scale++;
|
|
39
|
+
}
|
|
40
|
+
return { scale, decimalSeparator: text[lastSep] };
|
|
41
|
+
}
|
|
42
|
+
const FALLBACK_FORMAT = {
|
|
43
|
+
scale: DEFAULT_SCALE,
|
|
44
|
+
decimalSeparator: DEFAULT_SEPARATOR,
|
|
45
|
+
};
|
|
46
|
+
function formatOf(options) {
|
|
47
|
+
return options?.moneyFormat || FALLBACK_FORMAT;
|
|
48
|
+
}
|
|
49
|
+
/** The exact decimal `minorUnits` stands for at `scale` fraction digits. */
|
|
50
|
+
export function moneyToString(minorUnits, scale) {
|
|
51
|
+
if (!scale)
|
|
52
|
+
return minorUnits.toString();
|
|
53
|
+
const neg = minorUnits < 0n;
|
|
54
|
+
let digits = (neg ? -minorUnits : minorUnits).toString();
|
|
55
|
+
if (digits.length <= scale)
|
|
56
|
+
digits = digits.padStart(scale + 1, '0');
|
|
57
|
+
return ((neg ? '-' : '') +
|
|
58
|
+
digits.slice(0, digits.length - scale) +
|
|
59
|
+
'.' +
|
|
60
|
+
digits.slice(digits.length - scale));
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* A number while a double carries the value exactly, a Numeric after
|
|
64
|
+
* that - the same rule `numeric` decodes by, and for the same reason.
|
|
65
|
+
* money is an int64 of minor units, so it reaches past 2^53 long before
|
|
66
|
+
* it runs out of range.
|
|
67
|
+
*/
|
|
68
|
+
function toNumberOrNumeric(s) {
|
|
69
|
+
const n = parseFloat(s);
|
|
70
|
+
// The fraction is always written out to the full scale - `1` at scale
|
|
71
|
+
// 2 is `1.00` - and those zeroes are padding, not a difference.
|
|
72
|
+
return String(n) === trimTrailingZeros(s) ? n : new Numeric(s);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Every digit in `s`, read as minor units at `scale` - which is how a
|
|
76
|
+
* rendered money value is read back without knowing the locale that
|
|
77
|
+
* rendered it. `-$1,234.50` is `-123450` at scale 2, whatever the symbol
|
|
78
|
+
* and the separators were.
|
|
79
|
+
*/
|
|
80
|
+
function parseMoneyText(s) {
|
|
81
|
+
let digits = '';
|
|
82
|
+
let neg = false;
|
|
83
|
+
let i;
|
|
84
|
+
const l = s.length;
|
|
85
|
+
let c;
|
|
86
|
+
for (i = 0; i < l; i++) {
|
|
87
|
+
c = s.charCodeAt(i);
|
|
88
|
+
if (c >= 48 && c <= 57)
|
|
89
|
+
digits += s[i];
|
|
90
|
+
else if (c === 45 /* - */ || c === 40 /* ( */)
|
|
91
|
+
neg = true;
|
|
92
|
+
}
|
|
93
|
+
if (!digits)
|
|
94
|
+
digits = '0';
|
|
95
|
+
const v = BigInt(digits);
|
|
96
|
+
return neg ? -v : v;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* PostgreSQL's fixed-point currency type.
|
|
100
|
+
*
|
|
101
|
+
* On the wire it is an int64 of the smallest currency unit - `$12.34`
|
|
102
|
+
* arrives as `1234` - and how many of those make a unit is `lc_monetary`,
|
|
103
|
+
* which the server does not report in its startup parameters. So the
|
|
104
|
+
* connection asks it once, on its way up, and every money value is read
|
|
105
|
+
* against that answer. See `IntlConnection.ensureMoneyFormat()`.
|
|
106
|
+
*
|
|
107
|
+
* The value decodes to a plain number, or a `Numeric` when a double
|
|
108
|
+
* cannot carry it. The currency symbol is *not* part of it: it is the
|
|
109
|
+
* server's rendering of the value, not the value, and
|
|
110
|
+
* `fetchAsString: [DataTypeOIDs.money]` asks for that rendering when it
|
|
111
|
+
* is what the caller wants - which is also what `pg` always returns.
|
|
112
|
+
*
|
|
113
|
+
* The question is asked the first time a money column is on its way
|
|
114
|
+
* back, while the rows are still raw bytes - so no value is ever read
|
|
115
|
+
* against a scale nobody confirmed, and a caller who never touches
|
|
116
|
+
* money never asks. `execute()` and `pipeline()` decode inside the
|
|
117
|
+
* message loop, where there is nowhere left to ask from, so those two
|
|
118
|
+
* set the rows aside and read them once the loop is over.
|
|
119
|
+
*
|
|
120
|
+
* `moneyFormat` in the options answers it in advance, for a caller who
|
|
121
|
+
* knows the server or is reading a value some other server rendered.
|
|
122
|
+
*/
|
|
123
|
+
export const MoneyType = {
|
|
124
|
+
name: 'money',
|
|
125
|
+
oid: DataTypeOIDs.money,
|
|
126
|
+
jsType: 'number',
|
|
127
|
+
/**
|
|
128
|
+
* Written in the server's own decimal separator, so that the value the
|
|
129
|
+
* server parses is the value that was meant - money input is read in
|
|
130
|
+
* `lc_monetary` too, and `12.34` is not `12,34` to a locale that
|
|
131
|
+
* separates with a comma.
|
|
132
|
+
*/
|
|
133
|
+
encodeText(v, options) {
|
|
134
|
+
const { scale, decimalSeparator } = formatOf(options);
|
|
135
|
+
const s = moneyToString(toExactMinor(v, scale), scale);
|
|
136
|
+
return decimalSeparator === '.' ? s : s.replace('.', decimalSeparator);
|
|
137
|
+
},
|
|
138
|
+
encodeBinary(buf, v, options) {
|
|
139
|
+
const { scale } = formatOf(options);
|
|
140
|
+
buf.writeBigInt64BE(toExactMinor(v, scale));
|
|
141
|
+
},
|
|
142
|
+
decodeBinary(buf, offset = 0, _len, options) {
|
|
143
|
+
const v = typeof buf.readBigInt64BE === 'function'
|
|
144
|
+
? buf.readBigInt64BE(offset)
|
|
145
|
+
: readBigInt64BE(buf, offset);
|
|
146
|
+
const s = moneyToString(v, formatOf(options).scale);
|
|
147
|
+
// The exact decimal is what the decode already built; handing it
|
|
148
|
+
// back is the whole of `decimalAsString`.
|
|
149
|
+
return wantsDecimalString(options, DataTypeOIDs.money)
|
|
150
|
+
? s
|
|
151
|
+
: toNumberOrNumeric(s);
|
|
152
|
+
},
|
|
153
|
+
decodeText(s, options) {
|
|
154
|
+
const { scale } = formatOf(options);
|
|
155
|
+
const exact = moneyToString(parseMoneyText(s), scale);
|
|
156
|
+
return wantsDecimalString(options, DataTypeOIDs.money)
|
|
157
|
+
? exact
|
|
158
|
+
: toNumberOrNumeric(exact);
|
|
159
|
+
},
|
|
160
|
+
/**
|
|
161
|
+
* Never inferred: a JavaScript number is `int4`, `float8` or `numeric`
|
|
162
|
+
* long before anyone means money by it, and nothing about the value
|
|
163
|
+
* says which. `new BindParam(DataTypeOIDs.money, 12.34)` asks for it.
|
|
164
|
+
*/
|
|
165
|
+
inferrable: false,
|
|
166
|
+
isType(v) {
|
|
167
|
+
return typeof v === 'number' || v instanceof Numeric;
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
/** The int64 of minor units `v` stands for, without going through a double. */
|
|
171
|
+
function toExactMinor(v, scale) {
|
|
172
|
+
if (typeof v === 'bigint')
|
|
173
|
+
return v;
|
|
174
|
+
const s = v instanceof Numeric ? v.value : String(v ?? 0);
|
|
175
|
+
const neg = s.trimStart().startsWith('-');
|
|
176
|
+
let digits = '';
|
|
177
|
+
let frac = -1;
|
|
178
|
+
let i;
|
|
179
|
+
const l = s.length;
|
|
180
|
+
let c;
|
|
181
|
+
for (i = 0; i < l; i++) {
|
|
182
|
+
c = s.charCodeAt(i);
|
|
183
|
+
if (c >= 48 && c <= 57) {
|
|
184
|
+
digits += s[i];
|
|
185
|
+
if (frac >= 0)
|
|
186
|
+
frac++;
|
|
187
|
+
}
|
|
188
|
+
else if (c === 46 /* . */ || c === 44 /* , */)
|
|
189
|
+
frac = 0;
|
|
190
|
+
}
|
|
191
|
+
if (!digits)
|
|
192
|
+
return 0n;
|
|
193
|
+
if (frac < 0)
|
|
194
|
+
frac = 0;
|
|
195
|
+
// Pad or round to exactly `scale` fraction digits.
|
|
196
|
+
if (frac < scale)
|
|
197
|
+
digits = digits.padEnd(digits.length + scale - frac, '0');
|
|
198
|
+
else if (frac > scale) {
|
|
199
|
+
const cut = frac - scale;
|
|
200
|
+
const keep = digits.slice(0, digits.length - cut);
|
|
201
|
+
const next = digits.charCodeAt(digits.length - cut) - 48;
|
|
202
|
+
digits = (BigInt(keep || '0') + (next >= 5 ? 1n : 0n)).toString();
|
|
203
|
+
}
|
|
204
|
+
const out = BigInt(digits || '0');
|
|
205
|
+
return neg ? -out : out;
|
|
206
|
+
}
|
|
207
|
+
export const ArrayMoneyType = {
|
|
208
|
+
...MoneyType,
|
|
209
|
+
name: '_money',
|
|
210
|
+
oid: DataTypeOIDs._money,
|
|
211
|
+
elementsOID: DataTypeOIDs.money,
|
|
212
|
+
};
|
|
@@ -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
|
/**
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DataTypeOIDs } from '../constants.js';
|
|
2
2
|
import { assertCoercedNumber } from '../util/assert-integer.js';
|
|
3
|
+
import { wantsDecimalString } from '../util/decimal-as-string.js';
|
|
3
4
|
import { fastParseFloatBuffer } from '../util/fast-parsefloat.js';
|
|
4
5
|
import { Numeric } from './classes/numeric.js';
|
|
5
6
|
const NUMERIC_NEG = 0x4000;
|
|
@@ -16,7 +17,7 @@ const PAIRS = new Array(100);
|
|
|
16
17
|
for (let i = 0; i < 100; i++)
|
|
17
18
|
PAIRS[i] = i < 10 ? '0' + i : '' + i;
|
|
18
19
|
/** The decimal without the zeroes a declared scale pads it out to. */
|
|
19
|
-
function trimTrailingZeros(s) {
|
|
20
|
+
export function trimTrailingZeros(s) {
|
|
20
21
|
const dot = s.indexOf('.');
|
|
21
22
|
if (dot < 0)
|
|
22
23
|
return s;
|
|
@@ -130,7 +131,7 @@ export const NumericType = {
|
|
|
130
131
|
buf.writeInt16BE(digits[i]);
|
|
131
132
|
}
|
|
132
133
|
},
|
|
133
|
-
decodeBinary(v, offset = 0) {
|
|
134
|
+
decodeBinary(v, offset = 0, _len, options) {
|
|
134
135
|
const len = v.readInt16BE(offset);
|
|
135
136
|
const weight = v.readInt16BE(offset + 2);
|
|
136
137
|
// sign is a bitmask (0x0000/0x4000/0xC000/0xD000/0xF000), not a two's
|
|
@@ -138,12 +139,16 @@ export const NumericType = {
|
|
|
138
139
|
// values (which set the top bit) never compare equal to the constants.
|
|
139
140
|
const sign = v.readUInt16BE(offset + 4);
|
|
140
141
|
const scale = v.readInt16BE(offset + 6);
|
|
142
|
+
// The words PostgreSQL itself writes for these, when the caller
|
|
143
|
+
// asked for strings: a decimal string type that sometimes answers a
|
|
144
|
+
// number would have to be checked for on every value.
|
|
145
|
+
const asString = wantsDecimalString(options, DataTypeOIDs.numeric);
|
|
141
146
|
if (sign === NUMERIC_NAN)
|
|
142
|
-
return NaN;
|
|
147
|
+
return asString ? 'NaN' : NaN;
|
|
143
148
|
if (sign === NUMERIC_PINF)
|
|
144
|
-
return Infinity;
|
|
149
|
+
return asString ? 'Infinity' : Infinity;
|
|
145
150
|
if (sign === NUMERIC_NINF)
|
|
146
|
-
return -Infinity;
|
|
151
|
+
return asString ? '-Infinity' : -Infinity;
|
|
147
152
|
const digits = [];
|
|
148
153
|
for (let i = 0; i < len; i++) {
|
|
149
154
|
digits[i] = v.readInt16BE(offset + 8 + i * 2);
|
|
@@ -157,10 +162,13 @@ export const NumericType = {
|
|
|
157
162
|
// and `scale` the digits after the point, which is enough to rule
|
|
158
163
|
// both out. `weight === -1` is a value in [0.0001, 1), far from the
|
|
159
164
|
// exponent that would make JavaScript switch notation.
|
|
160
|
-
if (
|
|
161
|
-
(weight
|
|
165
|
+
if (!asString &&
|
|
166
|
+
((weight >= 0 &&
|
|
167
|
+
weight <= 4 &&
|
|
168
|
+
(weight + 1) * DEC_DIGITS + scale <= 15) ||
|
|
169
|
+
(weight === -1 && scale <= 15)))
|
|
162
170
|
return parseFloat(numString);
|
|
163
|
-
return toNumberOrNumeric(numString);
|
|
171
|
+
return asString ? numString : toNumberOrNumeric(numString);
|
|
164
172
|
},
|
|
165
173
|
encodeText(v) {
|
|
166
174
|
// Handed over as written. Going through a double here would undo
|
|
@@ -169,7 +177,9 @@ export const NumericType = {
|
|
|
169
177
|
// the exponent form a JavaScript number stringifies to.
|
|
170
178
|
return typeof v === 'string' ? v.trim() : String(v);
|
|
171
179
|
},
|
|
172
|
-
decodeText(v) {
|
|
180
|
+
decodeText(v, options) {
|
|
181
|
+
if (wantsDecimalString(options, DataTypeOIDs.numeric))
|
|
182
|
+
return v;
|
|
173
183
|
// NaN and the infinities arrive as those words and stay numbers.
|
|
174
184
|
const n = parseFloat(v);
|
|
175
185
|
if (!Number.isFinite(n))
|
|
@@ -180,8 +190,9 @@ export const NumericType = {
|
|
|
180
190
|
// Eight characters hold at most eight digits, cannot reach 1e21 -
|
|
181
191
|
// which needs twenty-two - and cannot reach 1e-7, which needs `0.`
|
|
182
192
|
// and seven more. So a value this short is carried faithfully by
|
|
183
|
-
// definition and keeps the parser that never builds a string
|
|
184
|
-
|
|
193
|
+
// definition and keeps the parser that never builds a string - but
|
|
194
|
+
// only while a number is what the caller wants back.
|
|
195
|
+
if (len <= 8 && !wantsDecimalString(options, DataTypeOIDs.numeric))
|
|
185
196
|
return fastParseFloatBuffer(buf, offset, len);
|
|
186
197
|
return NumericType.decodeText(buf.toString('latin1', offset, offset + len), options);
|
|
187
198
|
},
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { DataTypeMap } from '../data-type-map.js';
|
|
2
|
+
import type { DataType } from '../interfaces/data-type.js';
|
|
3
|
+
import type { OID } from '../types.js';
|
|
4
|
+
import { type TemporalApi } from '../util/temporal.js';
|
|
5
|
+
/** The five types a caller may ask for by OID. */
|
|
6
|
+
export declare const TemporalCapableOIDs: readonly OID[];
|
|
7
|
+
/** Every registration one selected OID stands for. */
|
|
8
|
+
export declare function temporalTypesFor(oid: OID, T: TemporalApi): DataType[];
|
|
9
|
+
/**
|
|
10
|
+
* A type map that decodes the selected date/time types into `Temporal`
|
|
11
|
+
* values, built on top of `base` - which keeps whatever else the caller
|
|
12
|
+
* registered there.
|
|
13
|
+
*
|
|
14
|
+
* The same selection on the same base always gives back the same map.
|
|
15
|
+
* That is not only to save the copy: a prepared statement is reused only
|
|
16
|
+
* while the map it was parsed with is the same object, so handing out a
|
|
17
|
+
* fresh map per statement would quietly empty the statement cache.
|
|
18
|
+
*/
|
|
19
|
+
export declare function temporalTypeMap(selection?: boolean | OID[], base?: DataTypeMap): DataTypeMap;
|