postgrejs 3.8.0 → 3.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/CHANGELOG.md +59 -1
  2. package/README.md +50 -0
  3. package/connection/connection.d.ts +14 -2
  4. package/connection/connection.js +63 -11
  5. package/connection/intl-connection.d.ts +164 -6
  6. package/connection/intl-connection.js +558 -37
  7. package/connection/pool.d.ts +30 -6
  8. package/connection/pool.js +22 -3
  9. package/connection/prepared-statement.js +22 -2
  10. package/data-type-map.d.ts +16 -0
  11. package/data-type-map.js +56 -12
  12. package/data-types/circle-type.js +5 -2
  13. package/data-types/classes/geometric.d.ts +49 -7
  14. package/data-types/classes/geometric.js +59 -11
  15. package/data-types/classes/interval.d.ts +40 -16
  16. package/data-types/classes/interval.js +68 -40
  17. package/data-types/classes/numeric.d.ts +6 -0
  18. package/data-types/classes/numeric.js +8 -0
  19. package/data-types/classes/range.d.ts +6 -1
  20. package/data-types/classes/range.js +6 -1
  21. package/data-types/date-type.js +2 -2
  22. package/data-types/interval-type.js +1 -1
  23. package/data-types/money-type.d.ts +38 -0
  24. package/data-types/money-type.js +212 -0
  25. package/data-types/numeric-type.d.ts +2 -0
  26. package/data-types/numeric-type.js +22 -11
  27. package/data-types/temporal-types.d.ts +19 -0
  28. package/data-types/temporal-types.js +520 -0
  29. package/data-types/timestamp-type.js +2 -2
  30. package/data-types/timestamptz-type.js +2 -2
  31. package/index.d.ts +3 -0
  32. package/index.js +3 -0
  33. package/interfaces/data-mapping-options.d.ts +126 -4
  34. package/interfaces/database-connection-params.d.ts +32 -5
  35. package/interfaces/query-options.d.ts +32 -0
  36. package/interfaces/script-execute-options.d.ts +32 -0
  37. package/interfaces/transaction-options.d.ts +27 -0
  38. package/interfaces/transaction-options.js +1 -0
  39. package/package.json +1 -1
  40. package/protocol/database-error.d.ts +15 -0
  41. package/protocol/database-error.js +19 -0
  42. package/protocol/frontend.js +1 -1
  43. package/protocol/pg-socket.js +7 -10
  44. package/util/array-leaf.d.ts +10 -0
  45. package/util/array-leaf.js +22 -0
  46. package/util/connection-config.js +11 -1
  47. package/util/date-style.d.ts +47 -0
  48. package/util/date-style.js +123 -0
  49. package/util/decimal-as-string.d.ts +11 -0
  50. package/util/decimal-as-string.js +30 -0
  51. package/util/fetch-as-string.d.ts +33 -0
  52. package/util/fetch-as-string.js +122 -0
  53. package/util/get-parsers.js +18 -1
  54. package/util/parse-datetime.d.ts +4 -3
  55. package/util/parse-datetime.js +34 -9
  56. package/util/resolve-column-formats.d.ts +5 -11
  57. package/util/resolve-column-formats.js +28 -24
  58. package/util/row-decoder.d.ts +24 -4
  59. package/util/row-decoder.js +25 -10
  60. package/util/sql-tag.js +2 -1
  61. package/util/stringify-for-sql.js +6 -5
  62. package/util/temporal.d.ts +47 -0
  63. package/util/temporal.js +61 -0
  64. package/util/transaction-command.d.ts +7 -0
  65. package/util/transaction-command.js +32 -0
  66. package/util/unspecified-param.d.ts +18 -8
  67. package/util/unspecified-param.js +22 -14
@@ -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
- * Every field is always present, 0 when the value has none:
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.minutes; // 0, not undefined
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 ported from it
27
- * reads the same values from the same places. The difference is that
28
- * package's objects are sparse - `interval '1 day'` gives it `{days: 1}`
29
- * and nothing else, so `iv.hours + 1` is NaN and a zero interval is `{}` -
30
- * and that it has no `toString()`, so a log line gets `[object Object]`.
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
- years;
42
- months;
43
- days;
44
- hours;
45
- minutes;
46
- /** Whole seconds; the sub-second part is in `milliseconds`. */
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
- this.years = fields?.years || 0;
52
- this.months = fields?.months || 0;
53
- this.days = fields?.days || 0;
54
- this.hours = fields?.hours || 0;
55
- this.minutes = fields?.minutes || 0;
56
- this.seconds = fields?.seconds || 0;
57
- this.milliseconds = fields?.milliseconds || 0;
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 whole = Math.trunc(this.hours) * 3600 +
96
- Math.trunc(this.minutes) * 60 +
97
- Math.trunc(this.seconds);
98
- const fraction = (this.hours - Math.trunc(this.hours)) * 3600 +
99
- (this.minutes - Math.trunc(this.minutes)) * 60 +
100
- (this.seconds - Math.trunc(this.seconds));
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
- let secondsPart = sign + seconds;
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
- sign +
170
- hours +
194
+ signed(hours) +
171
195
  'H' +
172
- sign +
173
- minutes +
196
+ signed(minutes) +
174
197
  'M' +
175
198
  secondsPart +
176
199
  'S');
177
200
  }
178
- toJSON() {
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
- toJSON(): string;
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
- toJSON() {
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
  }
@@ -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 ((weight >= 0 && weight <= 4 && (weight + 1) * DEC_DIGITS + scale <= 15) ||
161
- (weight === -1 && scale <= 15))
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
- if (len <= 8)
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;