postgrejs 3.9.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.
@@ -0,0 +1,520 @@
1
+ import { DataTypeNames, DataTypeOIDs } from '../constants.js';
2
+ import { DataTypeMap, GlobalTypeMap } from '../data-type-map.js';
3
+ import { readBigInt64BE } from '../util/bigint-methods.js';
4
+ import { getTemporal, systemTimeZone, } from '../util/temporal.js';
5
+ import { DateType } from './date-type.js';
6
+ import { IntervalType, parseIntervalText } from './interval-type.js';
7
+ import { createMultiRangeType, createRangeType } from './range-type.js';
8
+ import { TimeType } from './time-type.js';
9
+ import { TimestampType } from './timestamp-type.js';
10
+ import { TimestamptzType } from './timestamptz-type.js';
11
+ /**
12
+ * PostgreSQL's date/time types decoded into `Temporal` values instead of
13
+ * `Date`.
14
+ *
15
+ * Three things `Date` cannot do and these can: carry the microseconds
16
+ * PostgreSQL actually stores (a `Date` is milliseconds, so the last three
17
+ * digits of every timestamp were being dropped), say that a `timestamp`
18
+ * has no zone at all rather than guessing one from `utcDates`, and keep
19
+ * a `date` a date instead of a midnight in some zone.
20
+ *
21
+ * Decoding only is opinionated: every type here still *accepts* as a
22
+ * parameter everything it accepted before, so turning this on does not
23
+ * invalidate the `Date`s and strings a caller already passes.
24
+ */
25
+ const EPOCH_2000_NS = 946684800000000000n;
26
+ const TS_INFINITY = 0x7fffffffffffffffn;
27
+ const TS_MINUS_INFINITY = -0x8000000000000000n;
28
+ const DATE_INFINITY = 0x7fffffff;
29
+ const DATE_MINUS_INFINITY = -0x80000000;
30
+ const MICROS_PER_DAY = 86400000000n;
31
+ const MAX_SAFE_MICROS = BigInt(Number.MAX_SAFE_INTEGER);
32
+ /** The five types a caller may ask for by OID. */
33
+ export const TemporalCapableOIDs = Object.freeze([
34
+ DataTypeOIDs.date,
35
+ DataTypeOIDs.time,
36
+ DataTypeOIDs.timestamp,
37
+ DataTypeOIDs.timestamptz,
38
+ DataTypeOIDs.interval,
39
+ ]);
40
+ function readMicros(v, offset) {
41
+ return typeof v.readBigInt64BE === 'function'
42
+ ? v.readBigInt64BE(offset)
43
+ : readBigInt64BE(v, offset);
44
+ }
45
+ /**
46
+ * PostgreSQL writes years before the common era as `0044-03-15 BC`;
47
+ * Temporal counts them astronomically, where that year is -43. Rewrites
48
+ * the one into the other - `-000043-03-15` - so the era is not simply
49
+ * refused, and the text path answers what the binary path already does.
50
+ */
51
+ function withoutEra(v) {
52
+ const body = v.slice(0, -3);
53
+ const dash = body.indexOf('-');
54
+ const year = 1 - +body.substring(0, dash);
55
+ return ((year < 0 ? '-' : '+') +
56
+ String(Math.abs(year)).padStart(6, '0') +
57
+ body.substring(dash));
58
+ }
59
+ function zoneOf(options) {
60
+ return options.timeZone || systemTimeZone();
61
+ }
62
+ /** Guards the one thing PostgreSQL can store and Temporal cannot hold. */
63
+ function requireMicrosecondPrecision(ns) {
64
+ if (ns % 1000n)
65
+ throw new TypeError('PostgreSQL stores microseconds, and this value carries ' +
66
+ 'nanoseconds that would be dropped. Round it first: ' +
67
+ "value.round({ smallestUnit: 'microsecond' }).");
68
+ return ns / 1000n;
69
+ }
70
+ /**
71
+ * A `Temporal.Duration` out of the three quantities PostgreSQL keeps
72
+ * apart, or an error when they cannot make one.
73
+ *
74
+ * `interval '1 mon -3 days'` is an ordinary PostgreSQL value and has no
75
+ * Duration: Temporal refuses a duration whose fields disagree in sign,
76
+ * and balancing months against days needs a date to count from, which a
77
+ * bare interval does not have.
78
+ */
79
+ function toDuration(T, months, days, micros) {
80
+ const signs = [
81
+ Math.sign(months),
82
+ Math.sign(days),
83
+ micros > 0n ? 1 : micros < 0n ? -1 : 0,
84
+ ];
85
+ let sign = 0;
86
+ let i;
87
+ for (i = 0; i < 3; i++) {
88
+ if (!signs[i])
89
+ continue;
90
+ if (!sign)
91
+ sign = signs[i];
92
+ else if (signs[i] !== sign)
93
+ throw new TypeError(`The interval ${months} mons ${days} days ${micros} us mixes ` +
94
+ 'signs, and Temporal.Duration allows only one sign for all of ' +
95
+ 'its fields. Leave `interval` out of `temporalTypes` for a ' +
96
+ 'column that holds values like this one.');
97
+ }
98
+ if (micros > MAX_SAFE_MICROS || micros < -MAX_SAFE_MICROS)
99
+ throw new RangeError(`An interval of ${micros} microseconds is past what a ` +
100
+ 'Temporal.Duration field can hold.');
101
+ // The time part is spread over hours/minutes/seconds rather than left
102
+ // as one microsecond count: a Duration never balances itself, so
103
+ // `interval '3 hours'` would answer 0 hours and 10800 seconds. Days
104
+ // and months are the two PostgreSQL keeps apart on purpose and are
105
+ // left exactly as they came.
106
+ const abs = micros < 0n ? -micros : micros;
107
+ const s = micros < 0n ? -1 : 1;
108
+ return T.Duration.from({
109
+ months,
110
+ days,
111
+ hours: Number(abs / 3600000000n) * s,
112
+ minutes: Number((abs / 60000000n) % 60n) * s,
113
+ seconds: Number((abs / 1000000n) % 60n) * s,
114
+ microseconds: Number(abs % 1000000n) * s,
115
+ });
116
+ }
117
+ /**
118
+ * The `Date` an existing decoder produced, as the Temporal value this
119
+ * type answers with. Only for the shapes the fast paths below hand back:
120
+ * `infinity` stays the number it already was, and an unreadable date -
121
+ * a zone abbreviation under a non-ISO DateStyle, which names no offset -
122
+ * is an error rather than an `Invalid Date` nobody can use.
123
+ */
124
+ function checkDate(v, str) {
125
+ if (typeof v === 'number')
126
+ return v;
127
+ if (Number.isNaN(v.getTime()))
128
+ throw new Error(`"${str}" could not be read as a date - see DataMappingOptions.dateStyle.`);
129
+ return v;
130
+ }
131
+ function temporalTimestamptzType(T) {
132
+ const decodeText = (v, options) => {
133
+ // ISO is the default DateStyle and is Instant's own shape once the
134
+ // space between date and time is allowed for, which it is.
135
+ if (!options.dateStyle) {
136
+ const c = v.charCodeAt(0);
137
+ if (c >= 0x30 && c <= 0x39)
138
+ return T.Instant.from(v.endsWith(' BC') ? withoutEra(v) : v).toZonedDateTimeISO(zoneOf(options));
139
+ }
140
+ const d = checkDate(TimestamptzType.decodeText(v, options), v);
141
+ // A styled rendering carries milliseconds at best, so nothing is
142
+ // lost by going through the Date the existing parser built.
143
+ return typeof d === 'number'
144
+ ? d
145
+ : T.Instant.fromEpochMilliseconds(d.getTime()).toZonedDateTimeISO(zoneOf(options));
146
+ };
147
+ return {
148
+ name: 'timestamptz',
149
+ oid: DataTypeOIDs.timestamptz,
150
+ jsType: 'Temporal.ZonedDateTime',
151
+ decodeBinary(v, offset = 0, _len, options) {
152
+ const micros = readMicros(v, offset);
153
+ if (micros === TS_INFINITY)
154
+ return Infinity;
155
+ if (micros === TS_MINUS_INFINITY)
156
+ return -Infinity;
157
+ return T.Instant.fromEpochNanoseconds(EPOCH_2000_NS + micros * 1000n).toZonedDateTimeISO(zoneOf(options));
158
+ },
159
+ decodeText,
160
+ decodeTextBuffer(buf, offset, len, options) {
161
+ return decodeText(buf.toString('latin1', offset, offset + len), options);
162
+ },
163
+ encodeBinary(buf, v, options) {
164
+ if (v instanceof T.ZonedDateTime || v instanceof T.Instant) {
165
+ buf.writeBigInt64BE(requireMicrosecondPrecision(v.epochNanoseconds - EPOCH_2000_NS));
166
+ return;
167
+ }
168
+ TimestamptzType.encodeBinary(buf, v, options);
169
+ },
170
+ encodeText(v, options) {
171
+ // Without the [zone] suffix, which PostgreSQL does not read - the
172
+ // offset in front of it is what carries the instant anyway.
173
+ if (v instanceof T.ZonedDateTime)
174
+ return v.toString({ timeZoneName: 'never' });
175
+ if (v instanceof T.Instant)
176
+ return v.toString();
177
+ return TimestamptzType.encodeText(v, options);
178
+ },
179
+ isType(v) {
180
+ return (v instanceof T.ZonedDateTime ||
181
+ v instanceof T.Instant ||
182
+ TimestamptzType.isType(v));
183
+ },
184
+ };
185
+ }
186
+ function temporalTimestampType(T) {
187
+ const fromDate = (d, options) => options.utcDates
188
+ ? T.PlainDateTime.from({
189
+ year: d.getUTCFullYear(),
190
+ month: d.getUTCMonth() + 1,
191
+ day: d.getUTCDate(),
192
+ hour: d.getUTCHours(),
193
+ minute: d.getUTCMinutes(),
194
+ second: d.getUTCSeconds(),
195
+ millisecond: d.getUTCMilliseconds(),
196
+ })
197
+ : T.PlainDateTime.from({
198
+ year: d.getFullYear(),
199
+ month: d.getMonth() + 1,
200
+ day: d.getDate(),
201
+ hour: d.getHours(),
202
+ minute: d.getMinutes(),
203
+ second: d.getSeconds(),
204
+ millisecond: d.getMilliseconds(),
205
+ });
206
+ const decodeText = (v, options) => {
207
+ if (!options.dateStyle) {
208
+ const c = v.charCodeAt(0);
209
+ if (c >= 0x30 && c <= 0x39)
210
+ return T.PlainDateTime.from(v.endsWith(' BC') ? withoutEra(v) : v);
211
+ }
212
+ const d = checkDate(TimestampType.decodeText(v, options), v);
213
+ return typeof d === 'number' ? d : fromDate(d, options);
214
+ };
215
+ return {
216
+ name: 'timestamp',
217
+ oid: DataTypeOIDs.timestamp,
218
+ jsType: 'Temporal.PlainDateTime',
219
+ decodeBinary(v, offset = 0) {
220
+ const micros = readMicros(v, offset);
221
+ if (micros === TS_INFINITY)
222
+ return Infinity;
223
+ if (micros === TS_MINUS_INFINITY)
224
+ return -Infinity;
225
+ // A timestamp carries no zone, so it is read in UTC and the zone
226
+ // dropped again - `utcDates` has nothing left to decide here.
227
+ return T.Instant.fromEpochNanoseconds(EPOCH_2000_NS + micros * 1000n)
228
+ .toZonedDateTimeISO('UTC')
229
+ .toPlainDateTime();
230
+ },
231
+ decodeText,
232
+ decodeTextBuffer(buf, offset, len, options) {
233
+ return decodeText(buf.toString('latin1', offset, offset + len), options);
234
+ },
235
+ encodeBinary(buf, v, options) {
236
+ if (v instanceof T.PlainDateTime) {
237
+ buf.writeBigInt64BE(requireMicrosecondPrecision(v.toZonedDateTime('UTC').epochNanoseconds - EPOCH_2000_NS));
238
+ return;
239
+ }
240
+ TimestampType.encodeBinary(buf, v, options);
241
+ },
242
+ encodeText(v, options) {
243
+ if (v instanceof T.PlainDateTime)
244
+ return v.toString();
245
+ return TimestampType.encodeText(v, options);
246
+ },
247
+ isType(v) {
248
+ return v instanceof T.PlainDateTime || TimestampType.isType(v);
249
+ },
250
+ };
251
+ }
252
+ function temporalDateType(T) {
253
+ const EPOCH_2000 = T.PlainDate.from('2000-01-01');
254
+ const decodeText = (v, options) => {
255
+ if (!options.dateStyle) {
256
+ const c = v.charCodeAt(0);
257
+ if (c >= 0x30 && c <= 0x39)
258
+ return T.PlainDate.from(v.endsWith(' BC') ? withoutEra(v) : v);
259
+ }
260
+ const d = checkDate(DateType.decodeText(v, options), v);
261
+ if (typeof d === 'number')
262
+ return d;
263
+ return options.utcDates
264
+ ? T.PlainDate.from({
265
+ year: d.getUTCFullYear(),
266
+ month: d.getUTCMonth() + 1,
267
+ day: d.getUTCDate(),
268
+ })
269
+ : T.PlainDate.from({
270
+ year: d.getFullYear(),
271
+ month: d.getMonth() + 1,
272
+ day: d.getDate(),
273
+ });
274
+ };
275
+ return {
276
+ name: 'date',
277
+ oid: DataTypeOIDs.date,
278
+ jsType: 'Temporal.PlainDate',
279
+ decodeBinary(v, offset = 0) {
280
+ const days = v.readInt32BE(offset);
281
+ if (days === DATE_INFINITY)
282
+ return Infinity;
283
+ if (days === DATE_MINUS_INFINITY)
284
+ return -Infinity;
285
+ return EPOCH_2000.add({ days });
286
+ },
287
+ decodeText,
288
+ decodeTextBuffer(buf, offset, len, options) {
289
+ return decodeText(buf.toString('latin1', offset, offset + len), options);
290
+ },
291
+ encodeBinary(buf, v, options) {
292
+ if (v instanceof T.PlainDate) {
293
+ buf.writeInt32BE(EPOCH_2000.until(v, { largestUnit: 'day' }).days);
294
+ return;
295
+ }
296
+ DateType.encodeBinary(buf, v, options);
297
+ },
298
+ encodeText(v, options) {
299
+ if (v instanceof T.PlainDate)
300
+ return v.toString();
301
+ return DateType.encodeText(v, options);
302
+ },
303
+ isType(v) {
304
+ return v instanceof T.PlainDate || DateType.isType(v);
305
+ },
306
+ };
307
+ }
308
+ function temporalTimeType(T) {
309
+ const MIDNIGHT = T.PlainTime.from('00:00:00');
310
+ // `time` is rendered the same way under every DateStyle, so there is
311
+ // no styled shape to fall back to here.
312
+ const decodeText = (v) => T.PlainTime.from(v);
313
+ return {
314
+ name: 'time',
315
+ oid: DataTypeOIDs.time,
316
+ jsType: 'Temporal.PlainTime',
317
+ decodeBinary(v, offset = 0) {
318
+ const micros = readMicros(v, offset);
319
+ // PostgreSQL's `time` runs to 24:00:00 inclusive; a PlainTime stops
320
+ // one nanosecond short of it, and adding a day's worth of
321
+ // microseconds to midnight would silently wrap back to 00:00:00.
322
+ if (micros >= MICROS_PER_DAY)
323
+ throw new RangeError("PostgreSQL's time '24:00:00' has no Temporal.PlainTime - it is " +
324
+ 'the end of the day, and a PlainTime only reaches 23:59:59.999999999.');
325
+ return MIDNIGHT.add({ microseconds: Number(micros) });
326
+ },
327
+ decodeText,
328
+ decodeTextBuffer(buf, offset, len) {
329
+ return decodeText(buf.toString('latin1', offset, offset + len));
330
+ },
331
+ encodeBinary(buf, v, options) {
332
+ if (v instanceof T.PlainTime) {
333
+ const d = MIDNIGHT.until(v, { largestUnit: 'microsecond' });
334
+ if (d.nanoseconds)
335
+ requireMicrosecondPrecision(BigInt(d.nanoseconds));
336
+ buf.writeBigInt64BE(BigInt(d.microseconds));
337
+ return;
338
+ }
339
+ TimeType.encodeBinary(buf, v, options);
340
+ },
341
+ encodeText(v, options) {
342
+ if (v instanceof T.PlainTime)
343
+ return v.toString();
344
+ return TimeType.encodeText(v, options);
345
+ },
346
+ isType(v) {
347
+ return v instanceof T.PlainTime || TimeType.isType(v);
348
+ },
349
+ };
350
+ }
351
+ function temporalIntervalType(T) {
352
+ return {
353
+ name: 'interval',
354
+ oid: DataTypeOIDs.interval,
355
+ jsType: 'Temporal.Duration',
356
+ decodeBinary(v, offset = 0) {
357
+ return toDuration(T, v.readInt32BE(offset + 12), v.readInt32BE(offset + 8), readMicros(v, offset));
358
+ },
359
+ decodeText(v) {
360
+ const iv = parseIntervalText(v);
361
+ return toDuration(T, iv.totalMonths, iv.days ?? 0, iv.totalMicroseconds);
362
+ },
363
+ encodeBinary(buf, v, options) {
364
+ if (!(v instanceof T.Duration)) {
365
+ IntervalType.encodeBinary(buf, v, options);
366
+ return;
367
+ }
368
+ buf.writeBigInt64BE(durationMicros(v));
369
+ buf.writeInt32BE(v.days + v.weeks * 7);
370
+ buf.writeInt32BE(v.years * 12 + v.months);
371
+ },
372
+ encodeText(v, options) {
373
+ // A Duration prints ISO 8601, which PostgreSQL reads whatever its
374
+ // IntervalStyle is set to.
375
+ if (v instanceof T.Duration)
376
+ return v.toString();
377
+ return IntervalType.encodeText(v, options);
378
+ },
379
+ isType(v) {
380
+ return v instanceof T.Duration || IntervalType.isType(v);
381
+ },
382
+ };
383
+ }
384
+ /** A Duration's time part, in the microseconds the wire carries. */
385
+ function durationMicros(d) {
386
+ if (d.nanoseconds % 1000)
387
+ requireMicrosecondPrecision(BigInt(d.nanoseconds));
388
+ return (BigInt(d.hours) * 3600000000n +
389
+ BigInt(d.minutes) * 60000000n +
390
+ BigInt(d.seconds) * 1000000n +
391
+ BigInt(d.milliseconds) * 1000n +
392
+ BigInt(d.microseconds) +
393
+ BigInt(d.nanoseconds / 1000));
394
+ }
395
+ const FAMILIES = {
396
+ [DataTypeOIDs.date]: {
397
+ build: temporalDateType,
398
+ arrayOid: DataTypeOIDs._date,
399
+ range: {
400
+ name: 'daterange',
401
+ oid: DataTypeOIDs.daterange,
402
+ arrayOid: DataTypeOIDs._daterange,
403
+ multiOid: DataTypeOIDs.datemultirange,
404
+ multiArrayOid: DataTypeOIDs._datemultirange,
405
+ },
406
+ },
407
+ [DataTypeOIDs.time]: {
408
+ build: temporalTimeType,
409
+ arrayOid: DataTypeOIDs._time,
410
+ },
411
+ [DataTypeOIDs.timestamp]: {
412
+ build: temporalTimestampType,
413
+ arrayOid: DataTypeOIDs._timestamp,
414
+ range: {
415
+ name: 'tsrange',
416
+ oid: DataTypeOIDs.tsrange,
417
+ arrayOid: DataTypeOIDs._tsrange,
418
+ multiOid: DataTypeOIDs.tsmultirange,
419
+ multiArrayOid: DataTypeOIDs._tsmultirange,
420
+ },
421
+ },
422
+ [DataTypeOIDs.timestamptz]: {
423
+ build: temporalTimestamptzType,
424
+ arrayOid: DataTypeOIDs._timestamptz,
425
+ range: {
426
+ name: 'tstzrange',
427
+ oid: DataTypeOIDs.tstzrange,
428
+ arrayOid: DataTypeOIDs._tstzrange,
429
+ multiOid: DataTypeOIDs.tstzmultirange,
430
+ multiArrayOid: DataTypeOIDs._tstzmultirange,
431
+ },
432
+ },
433
+ [DataTypeOIDs.interval]: {
434
+ build: temporalIntervalType,
435
+ arrayOid: DataTypeOIDs._interval,
436
+ },
437
+ };
438
+ /** Every registration one selected OID stands for. */
439
+ export function temporalTypesFor(oid, T) {
440
+ const spec = FAMILIES[oid];
441
+ const scalar = spec.build(T);
442
+ const out = [
443
+ scalar,
444
+ {
445
+ ...scalar,
446
+ name: '_' + scalar.name,
447
+ oid: spec.arrayOid,
448
+ elementsOID: scalar.oid,
449
+ },
450
+ ];
451
+ const r = spec.range;
452
+ if (r) {
453
+ const multiName = r.name.replace('range', 'multirange');
454
+ const range = createRangeType(r.name, r.oid, scalar);
455
+ const multi = createMultiRangeType(multiName, r.multiOid, range);
456
+ out.push(range, { ...range, name: '_' + r.name, oid: r.arrayOid, elementsOID: r.oid }, multi, {
457
+ ...multi,
458
+ name: '_' + multiName,
459
+ oid: r.multiArrayOid,
460
+ elementsOID: r.multiOid,
461
+ });
462
+ }
463
+ return out;
464
+ }
465
+ function normalizeSelection(selection) {
466
+ if (selection === true)
467
+ return TemporalCapableOIDs.slice();
468
+ if (selection === false)
469
+ return [];
470
+ if (!Array.isArray(selection))
471
+ throw new TypeError('`temporalTypes` is either a boolean or an array of OIDs, not ' +
472
+ typeof selection);
473
+ const out = [];
474
+ const l = selection.length;
475
+ let i;
476
+ let oid;
477
+ for (i = 0; i < l; i++) {
478
+ oid = selection[i];
479
+ if (!TemporalCapableOIDs.includes(oid))
480
+ throw new TypeError('`temporalTypes` accepts only date (1082), time (1083), timestamp ' +
481
+ '(1114), timestamptz (1184) and interval (1186). Received ' +
482
+ `${oid}${DataTypeNames[oid] ? ' (' + DataTypeNames[oid] + ')' : ''}.`);
483
+ if (!out.includes(oid))
484
+ out.push(oid);
485
+ }
486
+ return out.sort((a, b) => a - b);
487
+ }
488
+ const _derived = new WeakMap();
489
+ /**
490
+ * A type map that decodes the selected date/time types into `Temporal`
491
+ * values, built on top of `base` - which keeps whatever else the caller
492
+ * registered there.
493
+ *
494
+ * The same selection on the same base always gives back the same map.
495
+ * That is not only to save the copy: a prepared statement is reused only
496
+ * while the map it was parsed with is the same object, so handing out a
497
+ * fresh map per statement would quietly empty the statement cache.
498
+ */
499
+ export function temporalTypeMap(selection = true, base = GlobalTypeMap) {
500
+ const oids = normalizeSelection(selection);
501
+ if (!oids.length)
502
+ return base;
503
+ const key = oids.join(',');
504
+ let forBase = _derived.get(base);
505
+ if (!forBase) {
506
+ forBase = new Map();
507
+ _derived.set(base, forBase);
508
+ }
509
+ let map = forBase.get(key);
510
+ if (!map) {
511
+ const T = getTemporal();
512
+ map = new DataTypeMap(base);
513
+ const l = oids.length;
514
+ let i;
515
+ for (i = 0; i < l; i++)
516
+ map.register(temporalTypesFor(oids[i], T));
517
+ forBase.set(key, map);
518
+ }
519
+ return map;
520
+ }
package/index.d.ts CHANGED
@@ -12,6 +12,7 @@ export * from './data-types/classes/geometric.js';
12
12
  export * from './data-types/classes/interval.js';
13
13
  export * from './data-types/classes/numeric.js';
14
14
  export * from './data-types/classes/range.js';
15
+ export * from './data-types/temporal-types.js';
15
16
  export * from './interfaces/batch-result.js';
16
17
  export * from './interfaces/command-result.js';
17
18
  export * from './interfaces/copy-from-rows-options.js';
@@ -33,6 +34,7 @@ export * from './protocol/database-error.js';
33
34
  export * from './protocol/smart-buffer.js';
34
35
  export * from './types.js';
35
36
  export * from './util/connection-config.js';
37
+ export * from './util/decimal-as-string.js';
36
38
  export * from './util/escape-identifier.js';
37
39
  export * from './util/escape-literal.js';
38
40
  export * from './util/parse-datetime.js';
package/index.js CHANGED
@@ -12,6 +12,7 @@ export * from './data-types/classes/geometric.js';
12
12
  export * from './data-types/classes/interval.js';
13
13
  export * from './data-types/classes/numeric.js';
14
14
  export * from './data-types/classes/range.js';
15
+ export * from './data-types/temporal-types.js';
15
16
  export * from './interfaces/batch-result.js';
16
17
  export * from './interfaces/command-result.js';
17
18
  export * from './interfaces/copy-from-rows-options.js';
@@ -33,6 +34,7 @@ export * from './protocol/database-error.js';
33
34
  export * from './protocol/smart-buffer.js';
34
35
  export * from './types.js';
35
36
  export * from './util/connection-config.js';
37
+ export * from './util/decimal-as-string.js';
36
38
  export * from './util/escape-identifier.js';
37
39
  export * from './util/escape-literal.js';
38
40
  export * from './util/parse-datetime.js';
@@ -7,7 +7,99 @@ export interface MoneyFormat {
7
7
  /** The decimal separator the server prints, `.` or `,`. */
8
8
  decimalSeparator: string;
9
9
  }
10
+ /**
11
+ * One entry of `fetchAsString`: an OID on its own, or an OID with what
12
+ * it should reach.
13
+ */
14
+ export type FetchAsStringItem = OID | FetchAsStringSelector;
15
+ /** An OID named by `fetchAsString`, with what it reaches. */
16
+ export interface FetchAsStringSelector {
17
+ oid: OID;
18
+ /**
19
+ * Whether naming a scalar type also asks for columns of *arrays* of
20
+ * it - which is what naming it on its own does, so this defaults to
21
+ * `true`.
22
+ *
23
+ * `false` is for reproducing something that is inconsistent on the
24
+ * other side: `pg` hands back a scalar `numeric` as a string and a
25
+ * `numeric[]` as numbers, and a `numeric[]` column already decodes
26
+ * that way here, so only the scalar wants asking for as text.
27
+ */
28
+ arrays?: boolean;
29
+ /**
30
+ * Reserved. An element OID does not reach range columns today - a
31
+ * `numrange` column is selected by naming `numrange` - so `false` is
32
+ * the only value this can be given, and `true` is refused rather than
33
+ * ignored.
34
+ */
35
+ ranges?: boolean;
36
+ }
10
37
  export interface DataMappingOptions {
38
+ /**
39
+ * Decode `numeric` and `money` into the exact decimal string they
40
+ * carry, instead of a number.
41
+ *
42
+ * `true` takes both; an array takes only the ones it names, and may
43
+ * name only those two (`DecimalAsStringOIDs`). Their array, range and
44
+ * multirange types follow, since those decode through the element.
45
+ *
46
+ * The string is the value and nothing else: an optional leading `-`,
47
+ * the digits, and - for money - a decimal point with exactly the scale
48
+ * the server reported. No currency symbol, no thousands separator, no
49
+ * locale. That is what tells it apart from
50
+ * `fetchAsString: [DataTypeOIDs.money]`, which asks the *server* for
51
+ * its own rendering and gets `-$1,234.50` back, because `lc_monetary`
52
+ * is what writes a money value out.
53
+ *
54
+ * There is no conversion behind this: both types build the exact
55
+ * decimal while decoding and then decide whether a double can carry it
56
+ * (a `Numeric` when it cannot). This hands back that same string, so
57
+ * nothing is rounded, re-parsed or formatted a second time - which is
58
+ * the thing a caller doing it themselves cannot avoid, and cannot get
59
+ * right for money without knowing the scale.
60
+ *
61
+ * `numeric`'s non-finite values keep the spelling PostgreSQL writes:
62
+ * `'NaN'`, `'Infinity'`, `'-Infinity'`.
63
+ *
64
+ * Off by default.
65
+ */
66
+ decimalAsString?: boolean | OID[];
67
+ /**
68
+ * Decode PostgreSQL's date/time types into `Temporal` values instead
69
+ * of `Date`.
70
+ *
71
+ * `true` takes all five - `date`, `time`, `timestamp`, `timestamptz`
72
+ * and `interval`; an array takes only the ones it names, and may name
73
+ * only those five (`DataTypeOIDs.timestamptz`, and so on - see
74
+ * `TemporalCapableOIDs`). Each one selected brings its own array and
75
+ * range types with it, because those carry a copy of the element's
76
+ * decoders rather than following it.
77
+ *
78
+ * This is decoding only: every type still accepts as a *parameter*
79
+ * everything it accepted before, so the `Date`s and strings already
80
+ * being passed keep working.
81
+ *
82
+ * What it buys: the microseconds PostgreSQL stores and a `Date` cannot
83
+ * hold, a `timestamp` that says it has no zone instead of guessing one
84
+ * from `utcDates`, and a `date` that stays a date. What it costs: no
85
+ * runtime ships `Temporal` yet, so an application turning this on
86
+ * installs a polyfill (`temporal-polyfill`) and imports it before
87
+ * connecting; building these values is also dearer than `new Date`.
88
+ *
89
+ * Off by default.
90
+ */
91
+ temporalTypes?: boolean | OID[];
92
+ /**
93
+ * The time zone a `timestamptz` is given when it is decoded as a
94
+ * `Temporal.ZonedDateTime` - see `temporalTypes`.
95
+ *
96
+ * A connection fills this in from the `TimeZone` the server reports,
97
+ * so a caller normally never sets it; set it to print those values in
98
+ * a zone of your own choosing. It decides only what wall clock the
99
+ * value shows: a timestamptz is an absolute instant and is the same
100
+ * moment in every zone.
101
+ */
102
+ timeZone?: string;
11
103
  /**
12
104
  * How the server renders dates, when it is not rendering them in ISO.
13
105
  *
@@ -44,11 +136,14 @@ export interface DataMappingOptions {
44
136
  * a query whose statement is not prepared asks for the whole row as text
45
137
  * instead (see IntlConnection.queryOnce()).
46
138
  *
47
- * An array column is selected by its own array OID (`_timestamptz`),
48
- * never by its element's, and then the whole array literal is the string
49
- * that comes back.
139
+ * An array column can be named either way, and the two ask for
140
+ * different things. Its own array OID (`_timestamptz`) asks for the
141
+ * whole literal, one string. Its *element's* OID (`timestamptz`) asks
142
+ * for the elements: an array, of the strings the server wrote, with
143
+ * the nulls still null - the same thing naming that OID does for a
144
+ * column of one.
50
145
  */
51
- fetchAsString?: OID[];
146
+ fetchAsString?: FetchAsStringItem[];
52
147
  /**
53
148
  * Ask the server for text on any column this client has no way to
54
149
  * decode, instead of taking bytes it cannot read.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "postgrejs",
3
3
  "description": "Professional PostgreSQL client NodeJS",
4
- "version": "3.9.0",
4
+ "version": "3.10.0",
5
5
  "author": "Panates",
6
6
  "license": "BSD-3-Clause",
7
7
  "dependencies": {