postgrejs 3.6.1 → 3.7.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 (63) hide show
  1. package/CHANGELOG.md +50 -6
  2. package/README.md +9 -83
  3. package/connection/intl-connection.d.ts +5 -3
  4. package/connection/intl-connection.js +43 -15
  5. package/connection/prepared-statement.js +2 -2
  6. package/constants.d.ts +60 -0
  7. package/constants.js +119 -1
  8. package/data-type-map.js +82 -0
  9. package/data-types/bit-type.d.ts +5 -0
  10. package/data-types/bit-type.js +91 -0
  11. package/data-types/box-type.js +9 -19
  12. package/data-types/circle-type.js +6 -16
  13. package/data-types/classes/geometric.d.ts +105 -0
  14. package/data-types/classes/geometric.js +197 -0
  15. package/data-types/classes/interval.d.ts +73 -0
  16. package/data-types/classes/interval.js +181 -0
  17. package/data-types/classes/numeric.d.ts +37 -0
  18. package/data-types/classes/numeric.js +47 -0
  19. package/data-types/classes/range.d.ts +74 -0
  20. package/data-types/classes/range.js +111 -0
  21. package/data-types/classes/type-oid.d.ts +34 -0
  22. package/data-types/classes/type-oid.js +45 -0
  23. package/data-types/inet-type.d.ts +5 -0
  24. package/data-types/inet-type.js +286 -0
  25. package/data-types/interval-type.d.ts +5 -0
  26. package/data-types/interval-type.js +79 -0
  27. package/data-types/jsonpath-type.d.ts +3 -0
  28. package/data-types/jsonpath-type.js +53 -0
  29. package/data-types/line-type.d.ts +3 -0
  30. package/data-types/line-type.js +41 -0
  31. package/data-types/lseg-type.js +9 -19
  32. package/data-types/macaddr-type.d.ts +5 -0
  33. package/data-types/macaddr-type.js +86 -0
  34. package/data-types/numeric-type.js +98 -69
  35. package/data-types/path-type.d.ts +5 -0
  36. package/data-types/path-type.js +139 -0
  37. package/data-types/pg-lsn-type.d.ts +3 -0
  38. package/data-types/pg-lsn-type.js +58 -0
  39. package/data-types/point-type.js +8 -13
  40. package/data-types/range-type.d.ts +23 -0
  41. package/data-types/range-type.js +342 -0
  42. package/data-types/snapshot-type.d.ts +5 -0
  43. package/data-types/snapshot-type.js +96 -0
  44. package/data-types/tid-type.d.ts +3 -0
  45. package/data-types/tid-type.js +50 -0
  46. package/data-types/timetz-type.d.ts +3 -0
  47. package/data-types/timetz-type.js +184 -0
  48. package/data-types/tsvector-type.d.ts +5 -0
  49. package/data-types/tsvector-type.js +216 -0
  50. package/data-types/xid-type.d.ts +7 -0
  51. package/data-types/xid-type.js +110 -0
  52. package/index.d.ts +4 -0
  53. package/index.js +4 -0
  54. package/interfaces/data-mapping-options.d.ts +26 -0
  55. package/interfaces/data-type.d.ts +8 -9
  56. package/package.json +1 -1
  57. package/protocol/connection-lost-error.d.ts +5 -3
  58. package/protocol/connection-lost-error.js +5 -3
  59. package/protocol/pg-socket.js +16 -2
  60. package/util/parse-array.js +17 -2
  61. package/util/resolve-column-formats.d.ts +2 -1
  62. package/util/resolve-column-formats.js +35 -8
  63. package/util/stringify-for-sql.js +37 -1
@@ -0,0 +1,342 @@
1
+ import { DataTypeOIDs } from '../constants.js';
2
+ import { Range } from './classes/range.js';
3
+ import { setTypeOid } from './classes/type-oid.js';
4
+ import { DateType } from './date-type.js';
5
+ import { Int4Type } from './int4-type.js';
6
+ import { Int8Type } from './int8-type.js';
7
+ import { NumericType } from './numeric-type.js';
8
+ import { TimestampType } from './timestamp-type.js';
9
+ import { TimestamptzType } from './timestamptz-type.js';
10
+ // src/include/utils/rangetypes.h
11
+ const RANGE_EMPTY = 0x01;
12
+ const RANGE_LB_INC = 0x02;
13
+ const RANGE_UB_INC = 0x04;
14
+ const RANGE_LB_INF = 0x08;
15
+ const RANGE_UB_INF = 0x10;
16
+ /** `empty`, or two bounds with the brackets that say whether each is included. */
17
+ const RANGE_PATTERN = /^\s*([[(])(.*),(.*)([\])])\s*$/s;
18
+ function boundsOf(r) {
19
+ return ((r.lowerInclusive && r.lower !== null ? '[' : '(') +
20
+ (r.upperInclusive && r.upper !== null ? ']' : ')'));
21
+ }
22
+ /**
23
+ * Reads one bound out of a range literal. An empty string is an absent
24
+ * bound; a quoted one has its doubled quotes and backslashes put back.
25
+ */
26
+ function parseBound(s, decode, options) {
27
+ const t = s.trim();
28
+ if (t === '')
29
+ return null;
30
+ if (t.charAt(0) === '"') {
31
+ let out = '';
32
+ let i = 1;
33
+ while (i < t.length) {
34
+ const c = t.charAt(i);
35
+ if (c === '\\') {
36
+ out += t.charAt(i + 1);
37
+ i += 2;
38
+ continue;
39
+ }
40
+ if (c === '"') {
41
+ // A doubled quote is one quote; a single one ends the bound.
42
+ if (t.charAt(i + 1) === '"') {
43
+ out += '"';
44
+ i += 2;
45
+ continue;
46
+ }
47
+ break;
48
+ }
49
+ out += c;
50
+ i++;
51
+ }
52
+ return decode(out, options);
53
+ }
54
+ return decode(t, options);
55
+ }
56
+ function toRange(v) {
57
+ if (v instanceof Range)
58
+ return v;
59
+ if (Array.isArray(v))
60
+ return new Range(v[0] ?? null, v[1] ?? null);
61
+ throw new TypeError(`"${typeof v}" cannot be encoded as a range - pass a Range, or a ` +
62
+ '[lower, upper] pair');
63
+ }
64
+ /**
65
+ * Builds the DataType pair for one of PostgreSQL's range types.
66
+ *
67
+ * Every range shares one wire format regardless of what it ranges over -
68
+ * a flags byte, then each bound that exists as a length-prefixed value of
69
+ * the element type - so the element's own codec does the work and this
70
+ * only has to say which bounds are there.
71
+ *
72
+ * Note what is deliberately not set: `elementsOID`. That marks an *array*
73
+ * type, and get-parsers.ts routes anything carrying it through
74
+ * decodeBinaryArray. A range is a scalar whose parts happen to be typed.
75
+ */
76
+ export function createRangeType(name, oid, element) {
77
+ return {
78
+ name,
79
+ oid,
80
+ jsType: 'Range',
81
+ decodeBinary(v, offset = 0, _len, options) {
82
+ const flags = v[offset];
83
+ // Stamped with the type it came from, so it can go back as that
84
+ // type without being named again - which nothing about a Range
85
+ // could otherwise tell.
86
+ if (flags & RANGE_EMPTY)
87
+ return Range.empty(oid);
88
+ let p = offset + 1;
89
+ let lower = null;
90
+ let upper = null;
91
+ let l;
92
+ if (!(flags & RANGE_LB_INF)) {
93
+ l = v.readInt32BE(p);
94
+ p += 4;
95
+ lower = element.decodeBinary(v, p, l, options);
96
+ p += l;
97
+ }
98
+ if (!(flags & RANGE_UB_INF)) {
99
+ l = v.readInt32BE(p);
100
+ p += 4;
101
+ upper = element.decodeBinary(v, p, l, options);
102
+ }
103
+ return new Range(lower, upper, ((flags & RANGE_LB_INC ? '[' : '(') +
104
+ (flags & RANGE_UB_INC ? ']' : ')')), oid);
105
+ },
106
+ encodeBinary(buf, v, options) {
107
+ const r = toRange(v);
108
+ if (r.isEmpty) {
109
+ buf.writeUInt8(RANGE_EMPTY);
110
+ return;
111
+ }
112
+ let flags = 0;
113
+ if (r.lower === null)
114
+ flags |= RANGE_LB_INF;
115
+ else if (r.lowerInclusive)
116
+ flags |= RANGE_LB_INC;
117
+ if (r.upper === null)
118
+ flags |= RANGE_UB_INF;
119
+ else if (r.upperInclusive)
120
+ flags |= RANGE_UB_INC;
121
+ buf.writeUInt8(flags);
122
+ let pos;
123
+ if (r.lower !== null) {
124
+ buf.writeInt32BE(0); // reserved for the bound's length
125
+ pos = buf.position;
126
+ element.encodeBinary(buf, r.lower, options);
127
+ buf.buffer.writeInt32BE(buf.size - pos, pos - 4);
128
+ }
129
+ if (r.upper !== null) {
130
+ buf.writeInt32BE(0);
131
+ pos = buf.position;
132
+ element.encodeBinary(buf, r.upper, options);
133
+ buf.buffer.writeInt32BE(buf.size - pos, pos - 4);
134
+ }
135
+ },
136
+ decodeText(v, options) {
137
+ if (v.trim() === 'empty')
138
+ return Range.empty(oid);
139
+ const m = RANGE_PATTERN.exec(v);
140
+ if (!m)
141
+ throw new Error(`"${v}" is not a ${name} literal`);
142
+ return new Range(parseBound(m[2], element.decodeText, options), parseBound(m[3], element.decodeText, options), (m[1] + m[4]), oid);
143
+ },
144
+ encodeText(v, options) {
145
+ const r = toRange(v);
146
+ if (r.isEmpty)
147
+ return 'empty';
148
+ // Rendered through the element's own encodeText rather than
149
+ // Range.toString(), which has no element type to ask and falls back
150
+ // on String() - the difference shows on a Date bound.
151
+ const render = (x) => x === null
152
+ ? ''
153
+ : quote(element.encodeText ? element.encodeText(x, options) : String(x));
154
+ const bounds = boundsOf(r);
155
+ return (bounds.charAt(0) +
156
+ render(r.lower) +
157
+ ',' +
158
+ render(r.upper) +
159
+ bounds.charAt(1));
160
+ },
161
+ isType(v) {
162
+ return v instanceof Range;
163
+ },
164
+ // Every one of the six range types answers `instanceof Range` for the
165
+ // same value, and nothing about a Range says which it is - bounds of
166
+ // numbers fit int4range, int8range and numrange alike, Dates fit
167
+ // daterange, tsrange and tstzrange. So determine() would pick
168
+ // whichever was registered first and send an int4range as a
169
+ // tstzrange, which is what it did before this line: `new Range(1, 10)`
170
+ // arrived as `[1970-01-01T00:00:00.001Z,...)`. Ask for the one you
171
+ // mean with `new BindParam(DataTypeOIDs.int4range, value)`.
172
+ inferrable: false,
173
+ };
174
+ }
175
+ function quote(s) {
176
+ if (s === '' || /["\\()[\],]|\s/.test(s))
177
+ return '"' + s.replace(/\\/g, '\\\\').replace(/"/g, '""') + '"';
178
+ return s;
179
+ }
180
+ /**
181
+ * The multirange counterpart: a count, then each range length-prefixed,
182
+ * and in text a brace-wrapped comma-separated list. The ranges themselves
183
+ * are the same format, so this delegates to the range type rather than
184
+ * repeating it.
185
+ */
186
+ export function createMultiRangeType(name, oid, range) {
187
+ return {
188
+ name,
189
+ oid,
190
+ jsType: 'Range[]',
191
+ decodeBinary(v, offset = 0, _len, options) {
192
+ const count = v.readInt32BE(offset);
193
+ let p = offset + 4;
194
+ const out = new Array(count);
195
+ let l;
196
+ let i;
197
+ for (i = 0; i < count; i++) {
198
+ l = v.readInt32BE(p);
199
+ p += 4;
200
+ out[i] = range.decodeBinary(v, p, l, options);
201
+ p += l;
202
+ }
203
+ // The array carries the multirange's own OID; its elements already
204
+ // carry the range type's.
205
+ return setTypeOid(out, oid);
206
+ },
207
+ encodeBinary(buf, v, options) {
208
+ const items = Array.isArray(v) ? v : [v];
209
+ buf.writeInt32BE(items.length);
210
+ let pos;
211
+ let i;
212
+ for (i = 0; i < items.length; i++) {
213
+ buf.writeInt32BE(0); // reserved for this range's length
214
+ pos = buf.position;
215
+ range.encodeBinary(buf, items[i], options);
216
+ buf.buffer.writeInt32BE(buf.size - pos, pos - 4);
217
+ }
218
+ },
219
+ decodeText(v, options) {
220
+ const s = v.trim();
221
+ if (!s.startsWith('{') || !s.endsWith('}'))
222
+ throw new Error(`"${v}" is not a ${name} literal`);
223
+ const body = s.substring(1, s.length - 1).trim();
224
+ if (!body)
225
+ return setTypeOid([], oid);
226
+ // Ranges are split on the comma that follows a closing bracket, so
227
+ // the comma between a range's own two bounds is left alone.
228
+ const out = [];
229
+ let depth = 0;
230
+ let start = 0;
231
+ let quoted = false;
232
+ let i;
233
+ let c;
234
+ for (i = 0; i < body.length; i++) {
235
+ c = body.charAt(i);
236
+ if (quoted) {
237
+ if (c === '\\')
238
+ i++;
239
+ else if (c === '"')
240
+ quoted = false;
241
+ continue;
242
+ }
243
+ if (c === '"')
244
+ quoted = true;
245
+ else if (c === '[' || c === '(')
246
+ depth++;
247
+ else if (c === ']' || c === ')')
248
+ depth--;
249
+ else if (c === ',' && depth === 0) {
250
+ out.push(range.decodeText(body.substring(start, i), options));
251
+ start = i + 1;
252
+ }
253
+ }
254
+ out.push(range.decodeText(body.substring(start), options));
255
+ return setTypeOid(out, oid);
256
+ },
257
+ encodeText(v, options) {
258
+ const items = Array.isArray(v) ? v : [v];
259
+ return ('{' + items.map(x => range.encodeText(x, options)).join(',') + '}');
260
+ },
261
+ isType(v) {
262
+ return (Array.isArray(v) && v.length > 0 && v.every(x => x instanceof Range));
263
+ },
264
+ // An array of Ranges is also what a `Range[]` column decodes to, so
265
+ // leaving this to inference would make the two indistinguishable for
266
+ // an untyped parameter. Ask for it with BindParam when you mean it.
267
+ inferrable: false,
268
+ };
269
+ }
270
+ /**
271
+ * The six range types PostgreSQL ships, their multirange counterparts and
272
+ * the array form of each - twelve scalar types over one format, which is
273
+ * why they are built here rather than written out one file at a time.
274
+ */
275
+ const BUILT_IN = [
276
+ [
277
+ 'int4range',
278
+ DataTypeOIDs.int4range,
279
+ DataTypeOIDs._int4range,
280
+ DataTypeOIDs.int4multirange,
281
+ DataTypeOIDs._int4multirange,
282
+ DataTypeOIDs.int4,
283
+ Int4Type,
284
+ ],
285
+ [
286
+ 'int8range',
287
+ DataTypeOIDs.int8range,
288
+ DataTypeOIDs._int8range,
289
+ DataTypeOIDs.int8multirange,
290
+ DataTypeOIDs._int8multirange,
291
+ DataTypeOIDs.int8,
292
+ Int8Type,
293
+ ],
294
+ [
295
+ 'numrange',
296
+ DataTypeOIDs.numrange,
297
+ DataTypeOIDs._numrange,
298
+ DataTypeOIDs.nummultirange,
299
+ DataTypeOIDs._nummultirange,
300
+ DataTypeOIDs.numeric,
301
+ NumericType,
302
+ ],
303
+ [
304
+ 'daterange',
305
+ DataTypeOIDs.daterange,
306
+ DataTypeOIDs._daterange,
307
+ DataTypeOIDs.datemultirange,
308
+ DataTypeOIDs._datemultirange,
309
+ DataTypeOIDs.date,
310
+ DateType,
311
+ ],
312
+ [
313
+ 'tsrange',
314
+ DataTypeOIDs.tsrange,
315
+ DataTypeOIDs._tsrange,
316
+ DataTypeOIDs.tsmultirange,
317
+ DataTypeOIDs._tsmultirange,
318
+ DataTypeOIDs.timestamp,
319
+ TimestampType,
320
+ ],
321
+ [
322
+ 'tstzrange',
323
+ DataTypeOIDs.tstzrange,
324
+ DataTypeOIDs._tstzrange,
325
+ DataTypeOIDs.tstzmultirange,
326
+ DataTypeOIDs._tstzmultirange,
327
+ DataTypeOIDs.timestamptz,
328
+ TimestamptzType,
329
+ ],
330
+ ];
331
+ export const RangeTypes = [];
332
+ for (const [name, oid, arrayOid, multiOid, multiArrayOid, , element,] of BUILT_IN) {
333
+ const rangeType = createRangeType(name, oid, element);
334
+ const multiName = name.replace('range', 'multirange');
335
+ const multiType = createMultiRangeType(multiName, multiOid, rangeType);
336
+ RangeTypes.push(rangeType, { ...rangeType, name: '_' + name, oid: arrayOid, elementsOID: oid }, multiType, {
337
+ ...multiType,
338
+ name: '_' + multiName,
339
+ oid: multiArrayOid,
340
+ elementsOID: multiOid,
341
+ });
342
+ }
@@ -0,0 +1,5 @@
1
+ import type { DataType } from '../interfaces/data-type.js';
2
+ export declare const PgSnapshotType: DataType;
3
+ export declare const ArrayPgSnapshotType: DataType;
4
+ export declare const TxidSnapshotType: DataType;
5
+ export declare const ArrayTxidSnapshotType: DataType;
@@ -0,0 +1,96 @@
1
+ import { DataTypeOIDs } from '../constants.js';
2
+ /**
3
+ * `pg_snapshot` and its older name `txid_snapshot` - what
4
+ * `pg_current_snapshot()` answers with, and what tells you which
5
+ * transactions were in flight at a moment.
6
+ *
7
+ * Both decode to the `xmin:xmax:xip,xip` string the server prints. The
8
+ * parts are transaction ids, and there is nothing to do with them but
9
+ * compare them against another snapshot - which the server's own
10
+ * operators do far better than JavaScript could, since these are the
11
+ * 64-bit counters `xid8` carries. The string is the whole value and
12
+ * casts straight back.
13
+ */
14
+ const BIG32 = BigInt(32);
15
+ const MASK32 = BigInt(0xffffffff);
16
+ /** The largest high half a JavaScript number still holds exactly. */
17
+ const MAX_SAFE_HIGH = 0x1fffff;
18
+ const SNAPSHOT_PATTERN = /^(\d+):(\d+):(\d+(?:,\d+)*)?$/;
19
+ /** A 64-bit transaction id as its decimal text, whatever its magnitude. */
20
+ function readXid(v, offset) {
21
+ const hi = v.readUInt32BE(offset);
22
+ const lo = v.readUInt32BE(offset + 4);
23
+ if (hi <= MAX_SAFE_HIGH)
24
+ return '' + (hi * 4294967296 + lo);
25
+ return ((BigInt(hi) << BIG32) | BigInt(lo)).toString();
26
+ }
27
+ function writeXid(buf, s) {
28
+ const n = BigInt(s);
29
+ buf.writeUInt32BE(Number(n >> BIG32));
30
+ buf.writeUInt32BE(Number(n & MASK32));
31
+ }
32
+ function createType(name, oid) {
33
+ return {
34
+ name,
35
+ oid,
36
+ jsType: 'string',
37
+ // See inet-type.ts: a string-shaped type stays out of inference.
38
+ inferrable: false,
39
+ encodeText(v) {
40
+ return '' + v;
41
+ },
42
+ /**
43
+ * An int32 count of in-flight transactions, then xmin and xmax, then
44
+ * that many ids - each of them 64 bits.
45
+ */
46
+ encodeBinary(buf, v) {
47
+ const m = typeof v === 'string' ? SNAPSHOT_PATTERN.exec(v) : undefined;
48
+ if (!m)
49
+ throw new Error(`"${v}" is not a valid ${name} value`);
50
+ const xip = m[3] ? m[3].split(',') : [];
51
+ buf.writeInt32BE(xip.length);
52
+ writeXid(buf, m[1]);
53
+ writeXid(buf, m[2]);
54
+ let i;
55
+ const l = xip.length;
56
+ for (i = 0; i < l; i++)
57
+ writeXid(buf, xip[i]);
58
+ },
59
+ decodeBinary(v, offset = 0) {
60
+ const count = v.readInt32BE(offset);
61
+ let out = readXid(v, offset + 4) + ':' + readXid(v, offset + 12) + ':';
62
+ let i;
63
+ let p = offset + 20;
64
+ for (i = 0; i < count; i++, p += 8) {
65
+ if (i)
66
+ out += ',';
67
+ out += readXid(v, p);
68
+ }
69
+ return out;
70
+ },
71
+ decodeText(v) {
72
+ return v;
73
+ },
74
+ // Digits, colons and commas - see inet-type.ts for why 'latin1'.
75
+ decodeTextBuffer(buf, offset, len) {
76
+ return buf.toString('latin1', offset, offset + len);
77
+ },
78
+ isType(v) {
79
+ return typeof v === 'string' && SNAPSHOT_PATTERN.test(v);
80
+ },
81
+ };
82
+ }
83
+ export const PgSnapshotType = createType('pg_snapshot', DataTypeOIDs.pg_snapshot);
84
+ export const ArrayPgSnapshotType = {
85
+ ...PgSnapshotType,
86
+ name: '_pg_snapshot',
87
+ oid: DataTypeOIDs._pg_snapshot,
88
+ elementsOID: DataTypeOIDs.pg_snapshot,
89
+ };
90
+ export const TxidSnapshotType = createType('txid_snapshot', DataTypeOIDs.txid_snapshot);
91
+ export const ArrayTxidSnapshotType = {
92
+ ...TxidSnapshotType,
93
+ name: '_txid_snapshot',
94
+ oid: DataTypeOIDs._txid_snapshot,
95
+ elementsOID: DataTypeOIDs.txid_snapshot,
96
+ };
@@ -0,0 +1,3 @@
1
+ import type { DataType } from '../interfaces/data-type.js';
2
+ export declare const TidType: DataType;
3
+ export declare const ArrayTidType: DataType;
@@ -0,0 +1,50 @@
1
+ import { DataTypeOIDs } from '../constants.js';
2
+ /**
3
+ * A `tid` is where a row physically sits - the `ctid` system column,
4
+ * which applications select to identify a row without a key, to
5
+ * deduplicate, or to update exactly one of a set of identical rows.
6
+ *
7
+ * It decodes to the `(block,offset)` string the server prints. The two
8
+ * numbers are a physical address that VACUUM is free to change, so there
9
+ * is nothing to compute with them; what a caller does with a ctid is
10
+ * compare it or hand it straight back, and the string does both. `pg`
11
+ * leaves it a string too.
12
+ */
13
+ const TID_PATTERN = /^\(\s*(\d+)\s*,\s*(\d+)\s*\)$/;
14
+ export const TidType = {
15
+ name: 'tid',
16
+ oid: DataTypeOIDs.tid,
17
+ jsType: 'string',
18
+ // `(0,1)` is an ordinary-looking string - see inet-type.ts.
19
+ inferrable: false,
20
+ encodeText(v) {
21
+ return '' + v;
22
+ },
23
+ /** A uint32 block number and a uint16 offset within it. */
24
+ encodeBinary(buf, v) {
25
+ const m = typeof v === 'string' ? TID_PATTERN.exec(v) : undefined;
26
+ if (!m)
27
+ throw new Error(`"${v}" is not a valid tid value`);
28
+ buf.writeUInt32BE(+m[1]);
29
+ buf.writeUInt16BE(+m[2]);
30
+ },
31
+ decodeBinary(v, offset = 0) {
32
+ return ('(' + v.readUInt32BE(offset) + ',' + v.readUInt16BE(offset + 4) + ')');
33
+ },
34
+ decodeText(v) {
35
+ return v;
36
+ },
37
+ // Digits, a comma and two parentheses - see inet-type.ts for 'latin1'.
38
+ decodeTextBuffer(buf, offset, len) {
39
+ return buf.toString('latin1', offset, offset + len);
40
+ },
41
+ isType(v) {
42
+ return typeof v === 'string' && TID_PATTERN.test(v);
43
+ },
44
+ };
45
+ export const ArrayTidType = {
46
+ ...TidType,
47
+ name: '_tid',
48
+ oid: DataTypeOIDs._tid,
49
+ elementsOID: DataTypeOIDs.tid,
50
+ };
@@ -0,0 +1,3 @@
1
+ import type { DataType } from '../interfaces/data-type.js';
2
+ export declare const TimeTzType: DataType;
3
+ export declare const ArrayTimeTzType: DataType;
@@ -0,0 +1,184 @@
1
+ import { DataTypeOIDs } from '../constants.js';
2
+ import { readBigInt64BE } from '../util/bigint-methods.js';
3
+ /**
4
+ * `timetz` decodes to a string, alone among the date/time types, and the
5
+ * reason is the one thing that distinguishes it from `time`: the offset.
6
+ * A JavaScript Date has no field for one. Handing back a Date would mean
7
+ * either dropping the offset - throwing away the only part that makes
8
+ * this type different - or folding it into the instant, so that
9
+ * `12:00:00+03` and `09:00:00+00` became indistinguishable and writing
10
+ * the value back changed it. The string keeps both parts, is exactly what
11
+ * the server printed, and casts straight back. `pg` also leaves it a
12
+ * string.
13
+ *
14
+ * PostgreSQL's own documentation calls this type's definition of
15
+ * questionable usefulness - a time with no date cannot resolve a real
16
+ * zone, only a fixed offset - which is a further reason not to invent a
17
+ * richer representation for it here.
18
+ */
19
+ /**
20
+ * Hours may reach 24 (`24:00:00` is a legal `timetz`), the fraction is
21
+ * microseconds, and the offset may carry seconds - `12:34:56+03:00:30`
22
+ * is a value the server stores and prints.
23
+ */
24
+ const TIMETZ_PATTERN = /^\s*(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?(?:\.(\d{1,6}))?\s*(?:(Z|z)|([+-])(\d{1,2})(?::?(\d{2}))?(?::?(\d{2}))?)?\s*$/;
25
+ const MICROS_PER_DAY = 86400000000;
26
+ /** What the server accepts: ±15:59:59, and ±16 is out of range. */
27
+ const MAX_ZONE_SECONDS = 15 * 3600 + 59 * 60 + 59;
28
+ const pad = (n) => (n < 10 ? '0' + n : '' + n);
29
+ function parseTimeTzText(v) {
30
+ const m = TIMETZ_PATTERN.exec(v);
31
+ if (!m)
32
+ throw new Error(`"${v}" is not a valid timetz value`);
33
+ if (!m[5] && !m[6]) {
34
+ // The server would resolve this against the session's time zone,
35
+ // which is not knowable here - and guessing the Node process's zone
36
+ // instead would silently shift the value. The text path does not go
37
+ // through this function and still accepts the bare form, since there
38
+ // the server does the resolving itself.
39
+ throw new Error(`"${v}" has no time zone offset - give it one ("12:34:56+03"), or ` +
40
+ 'pass a Date, whose own offset is used');
41
+ }
42
+ const hours = +m[1];
43
+ const minutes = +m[2];
44
+ const seconds = m[3] ? +m[3] : 0;
45
+ const micros = ((hours * 60 + minutes) * 60 + seconds) * 1000000 +
46
+ (m[4] ? +m[4].padEnd(6, '0') : 0);
47
+ if (minutes > 59 || seconds > 59 || micros > MICROS_PER_DAY)
48
+ throw new Error(`"${v}" is out of range for a timetz value`);
49
+ let zone = 0;
50
+ if (m[6]) {
51
+ zone = +m[7] * 3600 + (m[8] ? +m[8] : 0) * 60 + (m[9] ? +m[9] : 0);
52
+ if (zone > MAX_ZONE_SECONDS)
53
+ throw new Error(`"${v}" has a time zone displacement out of range`);
54
+ // Stored west-positive, the opposite sign from the way it is written.
55
+ if (m[6] === '+')
56
+ zone = -zone;
57
+ }
58
+ return { micros, zone };
59
+ }
60
+ /**
61
+ * A Date carries no offset of its own, but its local components and the
62
+ * zone they are read in go together: `getHours()` means "in the local
63
+ * zone", so that zone's offset is the value's, not a guess. Under
64
+ * `utcDates` the encoders read UTC components instead, and then the
65
+ * offset that matches is zero - the same rule the rest of the date/time
66
+ * types follow.
67
+ */
68
+ function fromDate(v, options) {
69
+ const utc = !!options.utcDates;
70
+ const hours = utc ? v.getUTCHours() : v.getHours();
71
+ const minutes = utc ? v.getUTCMinutes() : v.getMinutes();
72
+ const seconds = utc ? v.getUTCSeconds() : v.getSeconds();
73
+ const ms = utc ? v.getUTCMilliseconds() : v.getMilliseconds();
74
+ return {
75
+ micros: (((hours * 60 + minutes) * 60 + seconds) * 1000 + ms) * 1000,
76
+ // getTimezoneOffset() is already minutes west of UTC, the same
77
+ // direction the wire uses.
78
+ zone: utc ? 0 : v.getTimezoneOffset() * 60,
79
+ };
80
+ }
81
+ function toParts(v, options) {
82
+ if (typeof v === 'string')
83
+ return parseTimeTzText(v);
84
+ if (v instanceof Date)
85
+ return fromDate(v, options);
86
+ throw new TypeError(`"${typeof v}" cannot be encoded as a timetz - pass the string form ` +
87
+ 'with its offset, or a Date');
88
+ }
89
+ /**
90
+ * Prints what the server prints: the clock time, then microseconds with
91
+ * trailing zeroes dropped and the whole fraction omitted when it is zero,
92
+ * then the offset - always with hours, with minutes when they are not
93
+ * zero and seconds only when they are not either.
94
+ */
95
+ function formatTimeTz(micros, zone) {
96
+ const us = micros % 1000000;
97
+ let rest = (micros - us) / 1000000;
98
+ const seconds = rest % 60;
99
+ rest = (rest - seconds) / 60;
100
+ const minutes = rest % 60;
101
+ const hours = (rest - minutes) / 60;
102
+ let out = pad(hours) + ':' + pad(minutes) + ':' + pad(seconds);
103
+ if (us)
104
+ out += '.' + String(us).padStart(6, '0').replace(/0+$/, '');
105
+ // Back to the way it is written, east-positive.
106
+ let z = -zone;
107
+ out += z < 0 ? '-' : '+';
108
+ if (z < 0)
109
+ z = -z;
110
+ const zs = z % 60;
111
+ z = (z - zs) / 60;
112
+ const zm = z % 60;
113
+ out += pad((z - zm) / 60);
114
+ if (zm || zs)
115
+ out += ':' + pad(zm);
116
+ if (zs)
117
+ out += ':' + pad(zs);
118
+ return out;
119
+ }
120
+ export const TimeTzType = {
121
+ name: 'timetz',
122
+ oid: DataTypeOIDs.timetz,
123
+ jsType: 'string',
124
+ // `time` already claims any string that looks like a clock time,
125
+ // offset and all, so inference here would only take those strings away
126
+ // from it - and a plain "12:34:56" is far more often a `time`. Name
127
+ // this type to reach it: `new BindParam(DataTypeOIDs.timetz, v)`.
128
+ inferrable: false,
129
+ /**
130
+ * Twelve bytes: microseconds since midnight as an int64, then the zone
131
+ * as an int32 of seconds west of UTC.
132
+ */
133
+ decodeBinary(v, offset = 0) {
134
+ const micros = typeof v.readBigInt64BE === 'function'
135
+ ? v.readBigInt64BE(offset)
136
+ : readBigInt64BE(v, offset);
137
+ // A day of microseconds is far inside the safe integer range, so
138
+ // this is only about the wire type, not the value.
139
+ return formatTimeTz(Number(micros), v.readInt32BE(offset + 8));
140
+ },
141
+ encodeBinary(buf, v, options) {
142
+ const p = toParts(v, options);
143
+ buf.writeBigInt64BE(BigInt(p.micros));
144
+ buf.writeInt32BE(p.zone);
145
+ },
146
+ decodeText(v) {
147
+ return v;
148
+ },
149
+ encodeText(v, options) {
150
+ // A string goes over as written - the server accepts spellings this
151
+ // file does not have to know, including a bare time, which it
152
+ // resolves against the session's own zone.
153
+ if (typeof v === 'string')
154
+ return v;
155
+ const p = toParts(v, options);
156
+ return formatTimeTz(p.micros, p.zone);
157
+ },
158
+ // Digits, colons and a sign - see inet-type.ts for why 'latin1'.
159
+ decodeTextBuffer(buf, offset, len) {
160
+ return buf.toString('latin1', offset, offset + len);
161
+ },
162
+ isType(v) {
163
+ // The type's own JavaScript shape is the string the server prints,
164
+ // which always carries an offset. A Date is accepted by the encoder
165
+ // as a convenience, but it is `time`'s shape, not this one's - the
166
+ // same line IntervalType draws between what it encodes and what it
167
+ // claims.
168
+ if (typeof v !== 'string')
169
+ return false;
170
+ try {
171
+ parseTimeTzText(v);
172
+ return true;
173
+ }
174
+ catch {
175
+ return false;
176
+ }
177
+ },
178
+ };
179
+ export const ArrayTimeTzType = {
180
+ ...TimeTzType,
181
+ name: '_timetz',
182
+ oid: DataTypeOIDs._timetz,
183
+ elementsOID: DataTypeOIDs.timetz,
184
+ };