postgrejs 3.9.0 → 3.10.1

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 (36) hide show
  1. package/CHANGELOG.md +32 -1
  2. package/connection/connection.js +6 -2
  3. package/connection/intl-connection.d.ts +18 -1
  4. package/connection/intl-connection.js +104 -11
  5. package/data-types/circle-type.js +5 -2
  6. package/data-types/classes/geometric.d.ts +49 -7
  7. package/data-types/classes/geometric.js +59 -11
  8. package/data-types/classes/interval.d.ts +40 -16
  9. package/data-types/classes/interval.js +68 -40
  10. package/data-types/classes/numeric.d.ts +6 -0
  11. package/data-types/classes/numeric.js +8 -0
  12. package/data-types/classes/range.d.ts +6 -1
  13. package/data-types/classes/range.js +6 -1
  14. package/data-types/interval-type.js +1 -1
  15. package/data-types/money-type.js +11 -2
  16. package/data-types/numeric-type.js +21 -10
  17. package/data-types/temporal-types.d.ts +19 -0
  18. package/data-types/temporal-types.js +520 -0
  19. package/index.d.ts +2 -0
  20. package/index.js +2 -0
  21. package/interfaces/data-mapping-options.d.ts +99 -4
  22. package/interfaces/database-connection-params.d.ts +10 -0
  23. package/package.json +1 -1
  24. package/util/connection-config.js +65 -1
  25. package/util/decimal-as-string.d.ts +11 -0
  26. package/util/decimal-as-string.js +30 -0
  27. package/util/fetch-as-string.d.ts +33 -0
  28. package/util/fetch-as-string.js +122 -0
  29. package/util/get-parsers.js +18 -1
  30. package/util/resolve-column-formats.d.ts +5 -11
  31. package/util/resolve-column-formats.js +24 -24
  32. package/util/stringify-for-sql.js +6 -5
  33. package/util/temporal.d.ts +47 -0
  34. package/util/temporal.js +61 -0
  35. package/util/unspecified-param.d.ts +18 -3
  36. package/util/unspecified-param.js +21 -4
@@ -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
  }
@@ -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,
@@ -1,5 +1,6 @@
1
1
  import { DataTypeOIDs } from '../constants.js';
2
2
  import { readBigInt64BE } from '../util/bigint-methods.js';
3
+ import { wantsDecimalString } from '../util/decimal-as-string.js';
3
4
  import { Numeric } from './classes/numeric.js';
4
5
  import { trimTrailingZeros } from './numeric-type.js';
5
6
  /**
@@ -142,11 +143,19 @@ export const MoneyType = {
142
143
  const v = typeof buf.readBigInt64BE === 'function'
143
144
  ? buf.readBigInt64BE(offset)
144
145
  : readBigInt64BE(buf, offset);
145
- return toNumberOrNumeric(moneyToString(v, formatOf(options).scale));
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);
146
152
  },
147
153
  decodeText(s, options) {
148
154
  const { scale } = formatOf(options);
149
- return toNumberOrNumeric(moneyToString(parseMoneyText(s), scale));
155
+ const exact = moneyToString(parseMoneyText(s), scale);
156
+ return wantsDecimalString(options, DataTypeOIDs.money)
157
+ ? exact
158
+ : toNumberOrNumeric(exact);
150
159
  },
151
160
  /**
152
161
  * Never inferred: a JavaScript number is `int4`, `float8` or `numeric`
@@ -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;
@@ -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;