postgrejs 3.6.0 → 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 +64 -6
- package/README.md +9 -79
- package/connection/intl-connection.d.ts +5 -3
- package/connection/intl-connection.js +47 -17
- package/connection/pool.d.ts +22 -0
- package/connection/pool.js +45 -3
- 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 +5 -0
- package/index.js +5 -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 +42 -0
- package/protocol/connection-lost-error.js +56 -0
- package/protocol/pg-socket.d.ts +7 -0
- package/protocol/pg-socket.js +41 -3
- 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
package/data-types/lseg-type.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { DataTypeOIDs } from '../constants.js';
|
|
2
|
+
import { LineSegment, TwoPoints } from './classes/geometric.js';
|
|
2
3
|
const LSEG_PATTERN1 = /^\[ *\( *(-?\d+\.?\d*) *, *(-?\d+\.?\d*) *\) *, *\( *(-?\d+\.?\d*) *, *(-?\d+\.?\d*) *\) *]$/;
|
|
3
4
|
const LSEG_PATTERN2 = /^\( *\( *(-?\d+\.?\d*) *, *(-?\d+\.?\d*) *\) *, *\( *(-?\d+\.?\d*) *, *(-?\d+\.?\d*) *\) *\)$/;
|
|
4
5
|
const LSEG_PATTERN3 = /^\( *(-?\d+\.?\d*) *, *(-?\d+\.?\d*) *\) *, *\( *(-?\d+\.?\d*) *, *(-?\d+\.?\d*) *\)$/;
|
|
@@ -6,7 +7,7 @@ const LSEG_PATTERN4 = /^(-?\d+\.?\d*) *, *(-?\d+\.?\d*) *, *(-?\d+\.?\d*) *, *(-
|
|
|
6
7
|
export const LsegType = {
|
|
7
8
|
name: 'lseg',
|
|
8
9
|
oid: DataTypeOIDs.lseg,
|
|
9
|
-
jsType: '
|
|
10
|
+
jsType: 'LineSegment',
|
|
10
11
|
encodeText(v) {
|
|
11
12
|
return `[(${v.x1},${v.y1}),(${v.x2},${v.y2})]`;
|
|
12
13
|
},
|
|
@@ -17,12 +18,7 @@ export const LsegType = {
|
|
|
17
18
|
buf.writeDoubleBE(v.y2);
|
|
18
19
|
},
|
|
19
20
|
decodeBinary(v, offset = 0) {
|
|
20
|
-
return
|
|
21
|
-
x1: v.readDoubleBE(offset),
|
|
22
|
-
y1: v.readDoubleBE(offset + 8),
|
|
23
|
-
x2: v.readDoubleBE(offset + 16),
|
|
24
|
-
y2: v.readDoubleBE(offset + 24),
|
|
25
|
-
};
|
|
21
|
+
return new LineSegment(v.readDoubleBE(offset), v.readDoubleBE(offset + 8), v.readDoubleBE(offset + 16), v.readDoubleBE(offset + 24));
|
|
26
22
|
},
|
|
27
23
|
decodeText(v) {
|
|
28
24
|
const m = v.match(LSEG_PATTERN1) ||
|
|
@@ -31,24 +27,18 @@ export const LsegType = {
|
|
|
31
27
|
v.match(LSEG_PATTERN4);
|
|
32
28
|
if (!m)
|
|
33
29
|
return undefined;
|
|
34
|
-
return
|
|
35
|
-
x1: parseFloat(m[1]),
|
|
36
|
-
y1: parseFloat(m[2]),
|
|
37
|
-
x2: parseFloat(m[3]),
|
|
38
|
-
y2: parseFloat(m[4]),
|
|
39
|
-
};
|
|
30
|
+
return new LineSegment(parseFloat(m[1]), parseFloat(m[2]), parseFloat(m[3]), parseFloat(m[4]));
|
|
40
31
|
},
|
|
41
32
|
// See box-type.ts's decodeTextBuffer comment - same rationale.
|
|
42
33
|
decodeTextBuffer(buf, offset, len, options) {
|
|
43
34
|
return LsegType.decodeText(buf.toString('latin1', offset, offset + len), options);
|
|
44
35
|
},
|
|
36
|
+
// A plain object of the four numbers is still accepted - it is what
|
|
37
|
+
// this type returned before these classes existed - but only a plain
|
|
38
|
+
// one: box and lseg carry the same four, so an instance of the other
|
|
39
|
+
// must not be claimed here.
|
|
45
40
|
isType(v) {
|
|
46
|
-
return
|
|
47
|
-
Object.keys(v).length === 4 &&
|
|
48
|
-
typeof v.x1 === 'number' &&
|
|
49
|
-
typeof v.y1 === 'number' &&
|
|
50
|
-
typeof v.x2 === 'number' &&
|
|
51
|
-
typeof v.y2 === 'number');
|
|
41
|
+
return v instanceof LineSegment || TwoPoints.isTwoPointsLike(v);
|
|
52
42
|
},
|
|
53
43
|
};
|
|
54
44
|
export const ArrayLsegType = {
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { DataTypeOIDs } from '../constants.js';
|
|
2
|
+
/**
|
|
3
|
+
* `macaddr` and `macaddr8` share a file for the same reason `inet` and
|
|
4
|
+
* `cidr` do: one wire format - the address bytes and nothing else - and
|
|
5
|
+
* one JavaScript shape, differing only in length.
|
|
6
|
+
*
|
|
7
|
+
* Both decode to a string, in the `08:00:2b:01:02:03` form the server
|
|
8
|
+
* prints. `pg` answers with a string too.
|
|
9
|
+
*/
|
|
10
|
+
const SEPARATORS = /[.:-]/g;
|
|
11
|
+
const HEX_PATTERN = /^[0-9a-fA-F]+$/;
|
|
12
|
+
/**
|
|
13
|
+
* PostgreSQL accepts a MAC address written half a dozen ways -
|
|
14
|
+
* `08:00:2b:01:02:03`, `08-00-2b-01-02-03`, `0800.2b01.0203`,
|
|
15
|
+
* `08002b-010203`, `08002b010203` - which all come to the same thing
|
|
16
|
+
* once the separators are dropped: the right number of hex digits. It is
|
|
17
|
+
* not checked that the separators were placed sensibly, since nothing is
|
|
18
|
+
* gained by rejecting `0:800:2b0:102:03` on the client instead of just
|
|
19
|
+
* reading it.
|
|
20
|
+
*/
|
|
21
|
+
function parseMacaddr(v, size) {
|
|
22
|
+
if (typeof v !== 'string')
|
|
23
|
+
return undefined;
|
|
24
|
+
const hex = v.replace(SEPARATORS, '');
|
|
25
|
+
if (hex.length !== size * 2 || !HEX_PATTERN.test(hex)) {
|
|
26
|
+
// A six-byte address in a macaddr8 is not an error: the server widens
|
|
27
|
+
// it by inserting ff:fe in the middle, the modified EUI-64 rule, and
|
|
28
|
+
// `macaddr8 '08:00:2b:01:02:03'` is stored as `08:00:2b:ff:fe:01:02:03`.
|
|
29
|
+
if (size !== 8 || hex.length !== 12 || !HEX_PATTERN.test(hex))
|
|
30
|
+
return undefined;
|
|
31
|
+
return Buffer.from(hex.slice(0, 6) + 'fffe' + hex.slice(6), 'hex');
|
|
32
|
+
}
|
|
33
|
+
return Buffer.from(hex, 'hex');
|
|
34
|
+
}
|
|
35
|
+
function createType(name, oid, size) {
|
|
36
|
+
return {
|
|
37
|
+
name,
|
|
38
|
+
oid,
|
|
39
|
+
jsType: 'string',
|
|
40
|
+
// See inet-type.ts: a MAC address is an ordinary string as far as
|
|
41
|
+
// JavaScript is concerned, and inferring this type from one would
|
|
42
|
+
// send `macaddr` where `text` was meant. Name it to reach it.
|
|
43
|
+
inferrable: false,
|
|
44
|
+
encodeText(v) {
|
|
45
|
+
// Handed over as written - the server parses every spelling.
|
|
46
|
+
return '' + v;
|
|
47
|
+
},
|
|
48
|
+
encodeBinary(buf, v) {
|
|
49
|
+
const b = parseMacaddr(v, size);
|
|
50
|
+
if (!b)
|
|
51
|
+
throw new Error(`"${v}" is not a valid ${name} value`);
|
|
52
|
+
buf.writeBytes(b);
|
|
53
|
+
},
|
|
54
|
+
decodeBinary(v, offset = 0) {
|
|
55
|
+
let out = v.toString('hex', offset, offset + 1);
|
|
56
|
+
let i;
|
|
57
|
+
for (i = 1; i < size; i++)
|
|
58
|
+
out += ':' + v.toString('hex', offset + i, offset + i + 1);
|
|
59
|
+
return out;
|
|
60
|
+
},
|
|
61
|
+
decodeText(v) {
|
|
62
|
+
return v;
|
|
63
|
+
},
|
|
64
|
+
// Hex and colons - see inet-type.ts for why 'latin1'.
|
|
65
|
+
decodeTextBuffer(buf, offset, len) {
|
|
66
|
+
return buf.toString('latin1', offset, offset + len);
|
|
67
|
+
},
|
|
68
|
+
isType(v) {
|
|
69
|
+
return !!parseMacaddr(v, size);
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
export const MacaddrType = createType('macaddr', DataTypeOIDs.macaddr, 6);
|
|
74
|
+
export const ArrayMacaddrType = {
|
|
75
|
+
...MacaddrType,
|
|
76
|
+
name: '_macaddr',
|
|
77
|
+
oid: DataTypeOIDs._macaddr,
|
|
78
|
+
elementsOID: DataTypeOIDs.macaddr,
|
|
79
|
+
};
|
|
80
|
+
export const Macaddr8Type = createType('macaddr8', DataTypeOIDs.macaddr8, 8);
|
|
81
|
+
export const ArrayMacaddr8Type = {
|
|
82
|
+
...Macaddr8Type,
|
|
83
|
+
name: '_macaddr8',
|
|
84
|
+
oid: DataTypeOIDs._macaddr8,
|
|
85
|
+
elementsOID: DataTypeOIDs.macaddr8,
|
|
86
|
+
};
|
|
@@ -1,16 +1,55 @@
|
|
|
1
1
|
import { DataTypeOIDs } from '../constants.js';
|
|
2
2
|
import { assertCoercedNumber } from '../util/assert-integer.js';
|
|
3
3
|
import { fastParseFloatBuffer } from '../util/fast-parsefloat.js';
|
|
4
|
+
import { Numeric } from './classes/numeric.js';
|
|
4
5
|
const NUMERIC_NEG = 0x4000;
|
|
5
6
|
const NUMERIC_NAN = 0xc000;
|
|
6
7
|
const NUMERIC_PINF = 0xd000;
|
|
7
8
|
const NUMERIC_NINF = 0xf000;
|
|
8
9
|
const DEC_DIGITS = 4;
|
|
9
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Every two-digit string, so a base-10000 group is written with two
|
|
12
|
+
* lookups and one join instead of four divisions and four number-to-
|
|
13
|
+
* string conversions. Built once; 100 short strings.
|
|
14
|
+
*/
|
|
15
|
+
const PAIRS = new Array(100);
|
|
16
|
+
for (let i = 0; i < 100; i++)
|
|
17
|
+
PAIRS[i] = i < 10 ? '0' + i : '' + i;
|
|
18
|
+
/** The decimal without the zeroes a declared scale pads it out to. */
|
|
19
|
+
function trimTrailingZeros(s) {
|
|
20
|
+
const dot = s.indexOf('.');
|
|
21
|
+
if (dot < 0)
|
|
22
|
+
return s;
|
|
23
|
+
let end = s.length;
|
|
24
|
+
while (end > dot + 1 && s.charCodeAt(end - 1) === 48 /* 0 */)
|
|
25
|
+
end--;
|
|
26
|
+
return s.substring(0, end === dot + 1 ? dot : end);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* A number when a double carries the decimal faithfully, a Numeric when
|
|
30
|
+
* it does not.
|
|
31
|
+
*
|
|
32
|
+
* Faithfully means printing back the same digits, which is the only
|
|
33
|
+
* thing a caller can observe - so the test is the round trip itself
|
|
34
|
+
* rather than a rule about magnitude. It catches both ways of losing a
|
|
35
|
+
* value: digits a double cannot hold (`12345678901234567.89` comes back
|
|
36
|
+
* as `...68`) and a magnitude JavaScript prints in exponential notation
|
|
37
|
+
* (`-0.00000000000000001` as `-1e-17`), which PostgreSQL never writes.
|
|
38
|
+
*
|
|
39
|
+
* The padding a declared scale adds is not a difference - a
|
|
40
|
+
* `numeric(40,6)` holding 19.99 arrives as `19.990000` - so it comes off
|
|
41
|
+
* before the comparison.
|
|
42
|
+
*/
|
|
43
|
+
function toNumberOrNumeric(s) {
|
|
44
|
+
const n = parseFloat(s);
|
|
45
|
+
return String(n) === trimTrailingZeros(s) ? n : new Numeric(s);
|
|
46
|
+
}
|
|
10
47
|
export const NumericType = {
|
|
11
48
|
name: 'numeric',
|
|
12
49
|
oid: DataTypeOIDs.numeric,
|
|
13
|
-
|
|
50
|
+
// A number while one carries the value, a Numeric after that - the
|
|
51
|
+
// widened type is named here the way int8 names BigInt.
|
|
52
|
+
jsType: 'Numeric',
|
|
14
53
|
/**
|
|
15
54
|
* numeric on the wire is a base-10000 number: a digit count, the weight
|
|
16
55
|
* of the first group (in groups of four decimal digits, not digits), a
|
|
@@ -110,16 +149,44 @@ export const NumericType = {
|
|
|
110
149
|
digits[i] = v.readInt16BE(offset + 8 + i * 2);
|
|
111
150
|
}
|
|
112
151
|
const numString = numberBytesToString(digits, scale, weight, sign);
|
|
113
|
-
|
|
152
|
+
// The header bounds the value before the digits are even looked at,
|
|
153
|
+
// so the ordinary column never pays for the round-trip check below.
|
|
154
|
+
// A double carries about fifteen significant decimal digits and
|
|
155
|
+
// prints in plain notation while its exponent stays inside
|
|
156
|
+
// (-7, 21); `weight` is the first group's position counted in fours
|
|
157
|
+
// and `scale` the digits after the point, which is enough to rule
|
|
158
|
+
// both out. `weight === -1` is a value in [0.0001, 1), far from the
|
|
159
|
+
// exponent that would make JavaScript switch notation.
|
|
160
|
+
if ((weight >= 0 && weight <= 4 && (weight + 1) * DEC_DIGITS + scale <= 15) ||
|
|
161
|
+
(weight === -1 && scale <= 15))
|
|
162
|
+
return parseFloat(numString);
|
|
163
|
+
return toNumberOrNumeric(numString);
|
|
114
164
|
},
|
|
115
165
|
encodeText(v) {
|
|
116
|
-
|
|
117
|
-
|
|
166
|
+
// Handed over as written. Going through a double here would undo
|
|
167
|
+
// exactly what encodeBinary is careful to preserve, and the server
|
|
168
|
+
// accepts every spelling this could normalize to anyway - including
|
|
169
|
+
// the exponent form a JavaScript number stringifies to.
|
|
170
|
+
return typeof v === 'string' ? v.trim() : String(v);
|
|
171
|
+
},
|
|
172
|
+
decodeText(v) {
|
|
173
|
+
// NaN and the infinities arrive as those words and stay numbers.
|
|
174
|
+
const n = parseFloat(v);
|
|
175
|
+
if (!Number.isFinite(n))
|
|
176
|
+
return n;
|
|
177
|
+
return String(n) === trimTrailingZeros(v) ? n : new Numeric(v);
|
|
178
|
+
},
|
|
179
|
+
decodeTextBuffer(buf, offset, len, options) {
|
|
180
|
+
// Eight characters hold at most eight digits, cannot reach 1e21 -
|
|
181
|
+
// which needs twenty-two - and cannot reach 1e-7, which needs `0.`
|
|
182
|
+
// 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)
|
|
185
|
+
return fastParseFloatBuffer(buf, offset, len);
|
|
186
|
+
return NumericType.decodeText(buf.toString('latin1', offset, offset + len), options);
|
|
118
187
|
},
|
|
119
|
-
decodeText: parseFloat,
|
|
120
|
-
decodeTextBuffer: fastParseFloatBuffer,
|
|
121
188
|
isType(v) {
|
|
122
|
-
return typeof v === 'number';
|
|
189
|
+
return typeof v === 'number' || v instanceof Numeric;
|
|
123
190
|
},
|
|
124
191
|
};
|
|
125
192
|
export const ArrayNumericType = {
|
|
@@ -158,78 +225,40 @@ export function expandExponential(str) {
|
|
|
158
225
|
* @return String the number as String
|
|
159
226
|
*/
|
|
160
227
|
export function numberBytesToString(digits, scale, weight, sign) {
|
|
161
|
-
|
|
162
|
-
let d;
|
|
163
|
-
/*
|
|
164
|
-
* Allocate space for the result.
|
|
165
|
-
*
|
|
166
|
-
* i is set to the # of decimal digits before decimal point.
|
|
167
|
-
* dscale is the # of decimal digits we will print after decimal point.
|
|
168
|
-
* We may generate as many as DEC_DIGITS-1 excess digits at the end, and in addition we
|
|
169
|
-
* need room for sign, decimal point, null terminator.
|
|
170
|
-
*/
|
|
171
|
-
i = (weight + 1) * DEC_DIGITS;
|
|
172
|
-
if (i <= 0)
|
|
173
|
-
i = 1;
|
|
174
|
-
/*
|
|
175
|
-
* Output a dash for negative values
|
|
176
|
-
*/
|
|
228
|
+
const l = digits.length;
|
|
177
229
|
let out = sign === NUMERIC_NEG ? '-' : '';
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
*/
|
|
230
|
+
let d;
|
|
231
|
+
let g;
|
|
181
232
|
if (weight < 0) {
|
|
233
|
+
// The value is below 1, so the integer part is a bare zero and the
|
|
234
|
+
// first digit group belongs after the point.
|
|
182
235
|
d = weight + 1;
|
|
183
236
|
out += '0';
|
|
184
237
|
}
|
|
185
238
|
else {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
239
|
+
// Leading zeroes are suppressed in the first group only - it is the
|
|
240
|
+
// most significant one - and every group after it is four digits.
|
|
241
|
+
out += l > 0 ? '' + digits[0] : '0';
|
|
242
|
+
for (d = 1; d <= weight; d++) {
|
|
243
|
+
g = d < l ? digits[d] : 0;
|
|
244
|
+
out += PAIRS[(g / 100) | 0] + PAIRS[g % 100];
|
|
189
245
|
}
|
|
190
246
|
}
|
|
191
|
-
/*
|
|
192
|
-
* If requested, output a decimal point and all the digits that follow it.
|
|
193
|
-
* We initially put out a multiple of DEC_DIGITS digits, then truncate if
|
|
194
|
-
* needed.
|
|
195
|
-
*/
|
|
196
247
|
if (scale > 0) {
|
|
197
|
-
|
|
248
|
+
let frac = '';
|
|
249
|
+
let i;
|
|
198
250
|
for (i = 0; i < scale; d++, i += DEC_DIGITS) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
const extra = (i - scale) % DEC_DIGITS;
|
|
203
|
-
return out.substring(0, out.length - extra);
|
|
204
|
-
}
|
|
205
|
-
/* https://github.com/pgjdbc/pgjdbc/blob/3eca3a76aa4a04cb28cb960ed674cb67db30b5e3/pgjdbc/src/main/java/org/postgresql/util/ByteConverter.java */
|
|
206
|
-
/**
|
|
207
|
-
* Convert a number from binary representation to text representation.
|
|
208
|
-
* @param idx index of the digit to be converted in the digits array
|
|
209
|
-
* @param digits array of shorts that can be decoded as the number String
|
|
210
|
-
* @param alwaysPutIt a flag that indicate whether or not to put the digit char even if it is zero
|
|
211
|
-
* @return String the number as String
|
|
212
|
-
*/
|
|
213
|
-
function digitToString(idx, digits, alwaysPutIt) {
|
|
214
|
-
let out = '';
|
|
215
|
-
let dig = idx >= 0 && idx < digits.length ? digits[idx] : 0;
|
|
216
|
-
// Each dig represents 4 decimal digits (e.g. 9999)
|
|
217
|
-
// If we continue the number, then we need to print 0 as 0000 (alwaysPutIt parameter is true)
|
|
218
|
-
const l = ROUND_POWERS.length;
|
|
219
|
-
let p;
|
|
220
|
-
for (p = 1; p < l; p++) {
|
|
221
|
-
const pow = ROUND_POWERS[p];
|
|
222
|
-
const d1 = Math.trunc(dig / pow);
|
|
223
|
-
dig -= d1 * pow;
|
|
224
|
-
const putit = d1 > 0;
|
|
225
|
-
if (putit || alwaysPutIt) {
|
|
226
|
-
out += d1;
|
|
227
|
-
// We printed a character, so we need to print the rest of the current digits in dig
|
|
228
|
-
// For instance, we need to keep printing 000 from 1000 even if idx==0 (== it is the very
|
|
229
|
-
// beginning)
|
|
230
|
-
alwaysPutIt = true;
|
|
251
|
+
g = d >= 0 && d < l ? digits[d] : 0;
|
|
252
|
+
frac += PAIRS[(g / 100) | 0] + PAIRS[g % 100];
|
|
231
253
|
}
|
|
254
|
+
// Whole groups are written, so the last one can overshoot the
|
|
255
|
+
// display scale by up to three digits. Trimmed from the fraction
|
|
256
|
+
// rather than from the whole string: the two are the same thing
|
|
257
|
+
// whenever there is a fraction, and when there is not - a value
|
|
258
|
+
// below 1 with a scale of 0 - trimming the whole string took the
|
|
259
|
+
// bare `0` with it.
|
|
260
|
+
out +=
|
|
261
|
+
'.' + (i > scale ? frac.substring(0, frac.length - (i - scale)) : frac);
|
|
232
262
|
}
|
|
233
|
-
out += dig;
|
|
234
263
|
return out;
|
|
235
264
|
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { DataTypeOIDs } from '../constants.js';
|
|
2
|
+
import { Path, Point, Polygon } from './classes/geometric.js';
|
|
3
|
+
/**
|
|
4
|
+
* `path` and `polygon` are a series of points each, and differ on the
|
|
5
|
+
* wire only in that a path carries a flag saying whether it is closed.
|
|
6
|
+
* They share this file the way `box` and `lseg` share their shape, and
|
|
7
|
+
* for the same reason they are two classes: nothing about the points
|
|
8
|
+
* themselves says which type they were meant for.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Every coordinate in the literal, in order. The server writes
|
|
12
|
+
* `((1,2),(3,4))` for a closed path or a polygon and `[(1,2),(3,4)]` for
|
|
13
|
+
* an open path, and a float8 may reach exponent form - so the numbers
|
|
14
|
+
* are read out rather than matched against a fixed shape.
|
|
15
|
+
*/
|
|
16
|
+
const NUMBER_PATTERN = /-?\d+(?:\.\d+)?(?:e[-+]?\d+)?/gi;
|
|
17
|
+
const POINTS_PATTERN = /^\s*([[(])\s*\(.*\)\s*[\])]\s*$/;
|
|
18
|
+
function parsePoints(v) {
|
|
19
|
+
if (!POINTS_PATTERN.test(v))
|
|
20
|
+
return undefined;
|
|
21
|
+
const nums = v.match(NUMBER_PATTERN);
|
|
22
|
+
// A path or polygon holds at least one point, and coordinates come in
|
|
23
|
+
// pairs - an odd count is a literal this does not understand.
|
|
24
|
+
if (!nums || !nums.length || nums.length % 2)
|
|
25
|
+
return undefined;
|
|
26
|
+
const points = new Array(nums.length / 2);
|
|
27
|
+
let i;
|
|
28
|
+
for (i = 0; i < points.length; i++)
|
|
29
|
+
points[i] = new Point(parseFloat(nums[i * 2]), parseFloat(nums[i * 2 + 1]));
|
|
30
|
+
return points;
|
|
31
|
+
}
|
|
32
|
+
/** An int32 count, then each point as two float8s. */
|
|
33
|
+
function writePoints(buf, points) {
|
|
34
|
+
const l = points.length;
|
|
35
|
+
buf.writeInt32BE(l);
|
|
36
|
+
let i;
|
|
37
|
+
for (i = 0; i < l; i++) {
|
|
38
|
+
buf.writeDoubleBE(points[i].x);
|
|
39
|
+
buf.writeDoubleBE(points[i].y);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function readPoints(v, offset) {
|
|
43
|
+
const count = v.readInt32BE(offset);
|
|
44
|
+
const points = new Array(count);
|
|
45
|
+
let i;
|
|
46
|
+
let p = offset + 4;
|
|
47
|
+
for (i = 0; i < count; i++) {
|
|
48
|
+
points[i] = new Point(v.readDoubleBE(p), v.readDoubleBE(p + 8));
|
|
49
|
+
p += 16;
|
|
50
|
+
}
|
|
51
|
+
return points;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* A plain array of points is accepted as a parameter - it cannot be
|
|
55
|
+
* inferred, since `determine()` answers `point[]` for one, but it is
|
|
56
|
+
* unambiguous once the type has been named with a BindParam.
|
|
57
|
+
*/
|
|
58
|
+
function toPoints(v, name) {
|
|
59
|
+
if (Array.isArray(v))
|
|
60
|
+
return v.map(p => new Point(p.x, p.y));
|
|
61
|
+
if (v && Array.isArray(v.points))
|
|
62
|
+
return v.points;
|
|
63
|
+
throw new TypeError(`"${typeof v}" cannot be encoded as a ${name} - pass a ${name === 'path' ? 'Path' : 'Polygon'} or an array of points`);
|
|
64
|
+
}
|
|
65
|
+
export const PathType = {
|
|
66
|
+
name: 'path',
|
|
67
|
+
oid: DataTypeOIDs.path,
|
|
68
|
+
jsType: 'Path',
|
|
69
|
+
encodeText(v) {
|
|
70
|
+
return v instanceof Path
|
|
71
|
+
? v.toString()
|
|
72
|
+
: new Path(toPoints(v, 'path')).toString();
|
|
73
|
+
},
|
|
74
|
+
/** A closed flag, then the count and the points. */
|
|
75
|
+
encodeBinary(buf, v) {
|
|
76
|
+
buf.writeUInt8(v instanceof Path ? (v.isClosed ? 1 : 0) : 1);
|
|
77
|
+
writePoints(buf, toPoints(v, 'path'));
|
|
78
|
+
},
|
|
79
|
+
decodeBinary(v, offset = 0) {
|
|
80
|
+
return new Path(readPoints(v, offset + 1), !!v[offset]);
|
|
81
|
+
},
|
|
82
|
+
decodeText(v) {
|
|
83
|
+
const points = parsePoints(v);
|
|
84
|
+
if (!points)
|
|
85
|
+
return undefined;
|
|
86
|
+
// `[...]` is an open path, `(...)` a closed one.
|
|
87
|
+
return new Path(points, v.trimStart()[0] !== '[');
|
|
88
|
+
},
|
|
89
|
+
// See box-type.ts's decodeTextBuffer comment - same rationale.
|
|
90
|
+
decodeTextBuffer(buf, offset, len, options) {
|
|
91
|
+
return PathType.decodeText(buf.toString('latin1', offset, offset + len), options);
|
|
92
|
+
},
|
|
93
|
+
// Only a Path: a plain array of points is a `point[]` as far as
|
|
94
|
+
// inference is concerned, and claiming it here would take those arrays
|
|
95
|
+
// away from that type.
|
|
96
|
+
isType(v) {
|
|
97
|
+
return v instanceof Path;
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
export const ArrayPathType = {
|
|
101
|
+
...PathType,
|
|
102
|
+
name: '_path',
|
|
103
|
+
oid: DataTypeOIDs._path,
|
|
104
|
+
elementsOID: DataTypeOIDs.path,
|
|
105
|
+
};
|
|
106
|
+
export const PolygonType = {
|
|
107
|
+
name: 'polygon',
|
|
108
|
+
oid: DataTypeOIDs.polygon,
|
|
109
|
+
jsType: 'Polygon',
|
|
110
|
+
encodeText(v) {
|
|
111
|
+
return v instanceof Polygon
|
|
112
|
+
? v.toString()
|
|
113
|
+
: new Polygon(toPoints(v, 'polygon')).toString();
|
|
114
|
+
},
|
|
115
|
+
encodeBinary(buf, v) {
|
|
116
|
+
writePoints(buf, toPoints(v, 'polygon'));
|
|
117
|
+
},
|
|
118
|
+
decodeBinary(v, offset = 0) {
|
|
119
|
+
return new Polygon(readPoints(v, offset));
|
|
120
|
+
},
|
|
121
|
+
decodeText(v) {
|
|
122
|
+
const points = parsePoints(v);
|
|
123
|
+
return points && new Polygon(points);
|
|
124
|
+
},
|
|
125
|
+
// See box-type.ts's decodeTextBuffer comment - same rationale.
|
|
126
|
+
decodeTextBuffer(buf, offset, len, options) {
|
|
127
|
+
return PolygonType.decodeText(buf.toString('latin1', offset, offset + len), options);
|
|
128
|
+
},
|
|
129
|
+
// See PathType.isType().
|
|
130
|
+
isType(v) {
|
|
131
|
+
return v instanceof Polygon;
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
export const ArrayPolygonType = {
|
|
135
|
+
...PolygonType,
|
|
136
|
+
name: '_polygon',
|
|
137
|
+
oid: DataTypeOIDs._polygon,
|
|
138
|
+
elementsOID: DataTypeOIDs.polygon,
|
|
139
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { DataTypeOIDs } from '../constants.js';
|
|
2
|
+
/**
|
|
3
|
+
* A `pg_lsn` is a position in the write-ahead log - what
|
|
4
|
+
* `pg_current_wal_lsn()` and every column of `pg_stat_replication`
|
|
5
|
+
* answer with, so replication and monitoring tools read these routinely.
|
|
6
|
+
*
|
|
7
|
+
* It decodes to the `16/B374D848` string the server prints: the two
|
|
8
|
+
* 32-bit halves in upper-case hex, separated by a slash and neither of
|
|
9
|
+
* them padded - `0/0` and `A/B` are what the server writes for those,
|
|
10
|
+
* not `0/00000000`. That spelling is how an LSN is written everywhere -
|
|
11
|
+
* in logs, in
|
|
12
|
+
* `recovery_target_lsn`, in the documentation - and it casts straight
|
|
13
|
+
* back. A number would lose that and gain nothing, since the value is
|
|
14
|
+
* compared rather than arithmetic.
|
|
15
|
+
*/
|
|
16
|
+
const LSN_PATTERN = /^([0-9a-fA-F]{1,8})\/([0-9a-fA-F]{1,8})$/;
|
|
17
|
+
export const PgLsnType = {
|
|
18
|
+
name: 'pg_lsn',
|
|
19
|
+
oid: DataTypeOIDs.pg_lsn,
|
|
20
|
+
jsType: 'string',
|
|
21
|
+
// See inet-type.ts: a string-shaped type stays out of inference.
|
|
22
|
+
inferrable: false,
|
|
23
|
+
encodeText(v) {
|
|
24
|
+
return '' + v;
|
|
25
|
+
},
|
|
26
|
+
/** One unsigned 64-bit value, written as its two halves. */
|
|
27
|
+
encodeBinary(buf, v) {
|
|
28
|
+
const m = typeof v === 'string' ? LSN_PATTERN.exec(v) : undefined;
|
|
29
|
+
if (!m)
|
|
30
|
+
throw new Error(`"${v}" is not a valid pg_lsn value`);
|
|
31
|
+
buf.writeUInt32BE(parseInt(m[1], 16));
|
|
32
|
+
buf.writeUInt32BE(parseInt(m[2], 16));
|
|
33
|
+
},
|
|
34
|
+
decodeBinary(v, offset = 0) {
|
|
35
|
+
return (v.readUInt32BE(offset).toString(16).toUpperCase() +
|
|
36
|
+
'/' +
|
|
37
|
+
v
|
|
38
|
+
.readUInt32BE(offset + 4)
|
|
39
|
+
.toString(16)
|
|
40
|
+
.toUpperCase());
|
|
41
|
+
},
|
|
42
|
+
decodeText(v) {
|
|
43
|
+
return v;
|
|
44
|
+
},
|
|
45
|
+
// Hex and a slash - see inet-type.ts for why 'latin1'.
|
|
46
|
+
decodeTextBuffer(buf, offset, len) {
|
|
47
|
+
return buf.toString('latin1', offset, offset + len);
|
|
48
|
+
},
|
|
49
|
+
isType(v) {
|
|
50
|
+
return typeof v === 'string' && LSN_PATTERN.test(v);
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
export const ArrayPgLsnType = {
|
|
54
|
+
...PgLsnType,
|
|
55
|
+
name: '_pg_lsn',
|
|
56
|
+
oid: DataTypeOIDs._pg_lsn,
|
|
57
|
+
elementsOID: DataTypeOIDs.pg_lsn,
|
|
58
|
+
};
|
package/data-types/point-type.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { DataTypeOIDs } from '../constants.js';
|
|
2
|
+
import { Point } from './classes/geometric.js';
|
|
2
3
|
const POINT_PATTERN1 = /^\( *(-?\d+\.?\d*) *, *(-?\d+\.?\d*) *\)$/;
|
|
3
4
|
const POINT_PATTERN2 = /^(-?\d+\.?\d*) *, *(-?\d+\.?\d*)$/;
|
|
4
5
|
export const PointType = {
|
|
5
6
|
name: 'point',
|
|
6
7
|
oid: DataTypeOIDs.point,
|
|
7
|
-
jsType: '
|
|
8
|
+
jsType: 'Point',
|
|
8
9
|
encodeText(v) {
|
|
9
10
|
return `(${v.x},${v.y})`;
|
|
10
11
|
},
|
|
@@ -13,29 +14,23 @@ export const PointType = {
|
|
|
13
14
|
buf.writeDoubleBE(v.y);
|
|
14
15
|
},
|
|
15
16
|
decodeBinary(v, offset = 0) {
|
|
16
|
-
return
|
|
17
|
-
x: v.readDoubleBE(offset),
|
|
18
|
-
y: v.readDoubleBE(offset + 8),
|
|
19
|
-
};
|
|
17
|
+
return new Point(v.readDoubleBE(offset), v.readDoubleBE(offset + 8));
|
|
20
18
|
},
|
|
21
19
|
decodeText(v) {
|
|
22
20
|
const m = v.match(POINT_PATTERN1) || v.match(POINT_PATTERN2);
|
|
23
21
|
if (!m)
|
|
24
22
|
return undefined;
|
|
25
|
-
return
|
|
26
|
-
x: parseFloat(m[1]),
|
|
27
|
-
y: parseFloat(m[2]),
|
|
28
|
-
};
|
|
23
|
+
return new Point(parseFloat(m[1]), parseFloat(m[2]));
|
|
29
24
|
},
|
|
30
25
|
// See box-type.ts's decodeTextBuffer comment - same rationale.
|
|
31
26
|
decodeTextBuffer(buf, offset, len, options) {
|
|
32
27
|
return PointType.decodeText(buf.toString('latin1', offset, offset + len), options);
|
|
33
28
|
},
|
|
29
|
+
// A plain `{x, y}` is still accepted: it is what this type returned
|
|
30
|
+
// before Point existed, and code that builds one by hand should keep
|
|
31
|
+
// working.
|
|
34
32
|
isType(v) {
|
|
35
|
-
return
|
|
36
|
-
Object.keys(v).length === 2 &&
|
|
37
|
-
typeof v.x === 'number' &&
|
|
38
|
-
typeof v.y === 'number');
|
|
33
|
+
return v instanceof Point || Point.isPointLike(v);
|
|
39
34
|
},
|
|
40
35
|
};
|
|
41
36
|
export const ArrayPointType = {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { DataType } from '../interfaces/data-type.js';
|
|
2
|
+
import type { OID } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Builds the DataType pair for one of PostgreSQL's range types.
|
|
5
|
+
*
|
|
6
|
+
* Every range shares one wire format regardless of what it ranges over -
|
|
7
|
+
* a flags byte, then each bound that exists as a length-prefixed value of
|
|
8
|
+
* the element type - so the element's own codec does the work and this
|
|
9
|
+
* only has to say which bounds are there.
|
|
10
|
+
*
|
|
11
|
+
* Note what is deliberately not set: `elementsOID`. That marks an *array*
|
|
12
|
+
* type, and get-parsers.ts routes anything carrying it through
|
|
13
|
+
* decodeBinaryArray. A range is a scalar whose parts happen to be typed.
|
|
14
|
+
*/
|
|
15
|
+
export declare function createRangeType(name: string, oid: OID, element: DataType): DataType;
|
|
16
|
+
/**
|
|
17
|
+
* The multirange counterpart: a count, then each range length-prefixed,
|
|
18
|
+
* and in text a brace-wrapped comma-separated list. The ranges themselves
|
|
19
|
+
* are the same format, so this delegates to the range type rather than
|
|
20
|
+
* repeating it.
|
|
21
|
+
*/
|
|
22
|
+
export declare function createMultiRangeType(name: string, oid: OID, range: DataType): DataType;
|
|
23
|
+
export declare const RangeTypes: DataType[];
|