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.
- package/CHANGELOG.md +50 -6
- package/README.md +9 -83
- package/connection/intl-connection.d.ts +5 -3
- package/connection/intl-connection.js +43 -15
- package/connection/prepared-statement.js +2 -2
- package/constants.d.ts +60 -0
- package/constants.js +119 -1
- package/data-type-map.js +82 -0
- package/data-types/bit-type.d.ts +5 -0
- package/data-types/bit-type.js +91 -0
- package/data-types/box-type.js +9 -19
- package/data-types/circle-type.js +6 -16
- package/data-types/classes/geometric.d.ts +105 -0
- package/data-types/classes/geometric.js +197 -0
- package/data-types/classes/interval.d.ts +73 -0
- package/data-types/classes/interval.js +181 -0
- package/data-types/classes/numeric.d.ts +37 -0
- package/data-types/classes/numeric.js +47 -0
- package/data-types/classes/range.d.ts +74 -0
- package/data-types/classes/range.js +111 -0
- package/data-types/classes/type-oid.d.ts +34 -0
- package/data-types/classes/type-oid.js +45 -0
- package/data-types/inet-type.d.ts +5 -0
- package/data-types/inet-type.js +286 -0
- package/data-types/interval-type.d.ts +5 -0
- package/data-types/interval-type.js +79 -0
- package/data-types/jsonpath-type.d.ts +3 -0
- package/data-types/jsonpath-type.js +53 -0
- package/data-types/line-type.d.ts +3 -0
- package/data-types/line-type.js +41 -0
- package/data-types/lseg-type.js +9 -19
- package/data-types/macaddr-type.d.ts +5 -0
- package/data-types/macaddr-type.js +86 -0
- package/data-types/numeric-type.js +98 -69
- package/data-types/path-type.d.ts +5 -0
- package/data-types/path-type.js +139 -0
- package/data-types/pg-lsn-type.d.ts +3 -0
- package/data-types/pg-lsn-type.js +58 -0
- package/data-types/point-type.js +8 -13
- package/data-types/range-type.d.ts +23 -0
- package/data-types/range-type.js +342 -0
- package/data-types/snapshot-type.d.ts +5 -0
- package/data-types/snapshot-type.js +96 -0
- package/data-types/tid-type.d.ts +3 -0
- package/data-types/tid-type.js +50 -0
- package/data-types/timetz-type.d.ts +3 -0
- package/data-types/timetz-type.js +184 -0
- package/data-types/tsvector-type.d.ts +5 -0
- package/data-types/tsvector-type.js +216 -0
- package/data-types/xid-type.d.ts +7 -0
- package/data-types/xid-type.js +110 -0
- package/index.d.ts +4 -0
- package/index.js +4 -0
- package/interfaces/data-mapping-options.d.ts +26 -0
- package/interfaces/data-type.d.ts +8 -9
- package/package.json +1 -1
- package/protocol/connection-lost-error.d.ts +5 -3
- package/protocol/connection-lost-error.js +5 -3
- package/protocol/pg-socket.js +16 -2
- package/util/parse-array.js +17 -2
- package/util/resolve-column-formats.d.ts +2 -1
- package/util/resolve-column-formats.js +35 -8
- 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,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,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,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
|
+
};
|