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
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { REQUIRES_TYPE_OID, setTypeOid } from './type-oid.js';
|
|
2
|
+
function formatBound(v) {
|
|
3
|
+
if (v === null || v === undefined)
|
|
4
|
+
return '';
|
|
5
|
+
// A Date renders as an ISO 8601 timestamp rather than the JavaScript
|
|
6
|
+
// form: PostgreSQL accepts it back, `Mon Oct 22 2020 ...` it does not,
|
|
7
|
+
// and the point of this method is that the string casts to a range
|
|
8
|
+
// again.
|
|
9
|
+
const s = v instanceof Date ? v.toISOString() : String(v);
|
|
10
|
+
// The server quotes a bound that is empty or carries anything that
|
|
11
|
+
// would be read as punctuation of the literal itself - whitespace
|
|
12
|
+
// included, which is why a timestamp is always quoted. Inside the
|
|
13
|
+
// quotes a quote and a backslash are each doubled.
|
|
14
|
+
if (s === '' || /["\\()[\],]|\s/.test(s))
|
|
15
|
+
return '"' + s.replace(/\\/g, '\\\\').replace(/"/g, '""') + '"';
|
|
16
|
+
return s;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* PostgreSQL's range types - `int4range`, `tstzrange` and the rest - as
|
|
20
|
+
* a pair of bounds, each of which may be absent (the range runs to
|
|
21
|
+
* infinity that way) and each of which may or may not be included.
|
|
22
|
+
*
|
|
23
|
+
* ```ts
|
|
24
|
+
* const r = new Range(1, 10); // the default bounds, [1,10)
|
|
25
|
+
* r.lowerInclusive; // true
|
|
26
|
+
* r.upperInclusive; // false
|
|
27
|
+
* String(r); // '[1,10)'
|
|
28
|
+
*
|
|
29
|
+
* new Range(null, 10); // '(,10)' - unbounded below
|
|
30
|
+
* new Range(1, 10, '[]'); // '[1,10]'
|
|
31
|
+
* Range.empty(); // 'empty' - no values at all
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* `empty` is a value of its own rather than a range whose bounds happen
|
|
35
|
+
* to meet, and `(,)` - unbounded at both ends - is its opposite, not the
|
|
36
|
+
* same thing. Only `isEmpty` tells them apart.
|
|
37
|
+
*
|
|
38
|
+
* The bounds are the element type's own JavaScript values, so a
|
|
39
|
+
* `tstzrange` has `Date`s in it and an `int4range` has numbers. An absent
|
|
40
|
+
* bound is `null`, which is also how PostgreSQL's own constructors take
|
|
41
|
+
* it: `int4range(null, 10)`.
|
|
42
|
+
*
|
|
43
|
+
* Note what the server does to a discrete type: `int4range(1,10,'[]')` is
|
|
44
|
+
* stored and returned as `[1,11)`, because for integers those are the
|
|
45
|
+
* same set. So `upperInclusive` comes back false for every `int4range`,
|
|
46
|
+
* `int8range` and `daterange` - that is PostgreSQL normalizing, not this
|
|
47
|
+
* class losing something.
|
|
48
|
+
*
|
|
49
|
+
* `toString()` prints what the server prints for a range over numbers.
|
|
50
|
+
* Over dates it cannot: a `Range` holds `Date`s and has no way to know
|
|
51
|
+
* whether it came from a `daterange`, a `tsrange` or a `tstzrange`, which
|
|
52
|
+
* the server renders three different ways. It prints the instant in ISO
|
|
53
|
+
* 8601 instead - the truth about the `Date` it holds, and something
|
|
54
|
+
* PostgreSQL accepts back, but not byte-for-byte what a `daterange`
|
|
55
|
+
* prints. The exact wire form is what goes out when the Range is passed
|
|
56
|
+
* as a parameter, where the element type's own renderer is in reach.
|
|
57
|
+
*/
|
|
58
|
+
export class Range {
|
|
59
|
+
/**
|
|
60
|
+
* All six range types answer `instanceof Range`, so one has to be named
|
|
61
|
+
* rather than guessed - see REQUIRES_TYPE_OID.
|
|
62
|
+
*/
|
|
63
|
+
static [REQUIRES_TYPE_OID] = true;
|
|
64
|
+
/** The lower bound, or null when the range runs to negative infinity. */
|
|
65
|
+
lower;
|
|
66
|
+
/** The upper bound, or null when the range runs to infinity. */
|
|
67
|
+
upper;
|
|
68
|
+
lowerInclusive;
|
|
69
|
+
upperInclusive;
|
|
70
|
+
/** True for `empty`, which contains no values - not the same as `(,)`. */
|
|
71
|
+
isEmpty;
|
|
72
|
+
/**
|
|
73
|
+
* @param oid Which range type this is - `DataTypeOIDs.int4range` and so
|
|
74
|
+
* on. A Range that came from a query already carries the one it was
|
|
75
|
+
* decoded from; give it here when building one to send, or name it at
|
|
76
|
+
* the call site with `new BindParam(oid, range)`.
|
|
77
|
+
*/
|
|
78
|
+
constructor(lower = null, upper = null, bounds = '[)', oid) {
|
|
79
|
+
this.lower = lower;
|
|
80
|
+
this.upper = upper;
|
|
81
|
+
// An absent bound is never inclusive; the server prints `(` for it
|
|
82
|
+
// whatever was asked for.
|
|
83
|
+
this.lowerInclusive = bounds.charAt(0) === '[' && lower !== null;
|
|
84
|
+
this.upperInclusive = bounds.charAt(1) === ']' && upper !== null;
|
|
85
|
+
this.isEmpty = false;
|
|
86
|
+
if (oid !== undefined)
|
|
87
|
+
setTypeOid(this, oid);
|
|
88
|
+
}
|
|
89
|
+
/** The empty range, which contains no values. */
|
|
90
|
+
static empty(oid) {
|
|
91
|
+
const r = new Range(null, null, '[)', oid);
|
|
92
|
+
r.isEmpty = true;
|
|
93
|
+
return r;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* The range as PostgreSQL prints it, so it casts straight back through
|
|
97
|
+
* `::int4range` and reads the way the same value does in psql.
|
|
98
|
+
*/
|
|
99
|
+
toString() {
|
|
100
|
+
if (this.isEmpty)
|
|
101
|
+
return 'empty';
|
|
102
|
+
return ((this.lowerInclusive && this.lower !== null ? '[' : '(') +
|
|
103
|
+
formatBound(this.lower) +
|
|
104
|
+
',' +
|
|
105
|
+
formatBound(this.upper) +
|
|
106
|
+
(this.upperInclusive && this.upper !== null ? ']' : ')'));
|
|
107
|
+
}
|
|
108
|
+
toJSON() {
|
|
109
|
+
return this.toString();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Maybe, OID } from '../../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* The OID a decoded value came from, carried on the value itself.
|
|
4
|
+
*
|
|
5
|
+
* A symbol rather than a property named `oid`, and non-enumerable on top
|
|
6
|
+
* of that: `{ oid: 3904 }` is an ordinary object somebody might store in
|
|
7
|
+
* a json column, and reading a plain key would mistake it for a typed
|
|
8
|
+
* value. A symbol cannot collide, and being non-enumerable keeps it out
|
|
9
|
+
* of `JSON.stringify`, object spread, `Object.keys` and expect's
|
|
10
|
+
* `toStrictEqual` - so a stamped value compares equal to an unstamped one
|
|
11
|
+
* with the same fields.
|
|
12
|
+
*/
|
|
13
|
+
export declare const TYPE_OID: unique symbol;
|
|
14
|
+
/**
|
|
15
|
+
* Marks a class whose instances cannot be typed by inspection, so passing
|
|
16
|
+
* one as an untyped parameter is an error rather than a guess.
|
|
17
|
+
*
|
|
18
|
+
* Range is the case: all six range types answer `instanceof Range` and
|
|
19
|
+
* nothing about a Range says which it is. Without this, such a value
|
|
20
|
+
* falls through to JsonType - whose isType() takes any object - and goes
|
|
21
|
+
* out declared `json`, which comes back looking right.
|
|
22
|
+
*/
|
|
23
|
+
export declare const REQUIRES_TYPE_OID: unique symbol;
|
|
24
|
+
/**
|
|
25
|
+
* Records which type a value was decoded from, so it can go back as that
|
|
26
|
+
* type without the caller naming it again.
|
|
27
|
+
*
|
|
28
|
+
* Only worth doing for a value whose class cannot say what it is on its
|
|
29
|
+
* own - it costs a defineProperty per value (~160ns), which is not free
|
|
30
|
+
* on a column of them.
|
|
31
|
+
*/
|
|
32
|
+
export declare function setTypeOid<T extends object>(value: T, oid: OID): T;
|
|
33
|
+
/** The OID a value was decoded from, if it carries one. */
|
|
34
|
+
export declare function getTypeOid(value: unknown): Maybe<OID>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The OID a decoded value came from, carried on the value itself.
|
|
3
|
+
*
|
|
4
|
+
* A symbol rather than a property named `oid`, and non-enumerable on top
|
|
5
|
+
* of that: `{ oid: 3904 }` is an ordinary object somebody might store in
|
|
6
|
+
* a json column, and reading a plain key would mistake it for a typed
|
|
7
|
+
* value. A symbol cannot collide, and being non-enumerable keeps it out
|
|
8
|
+
* of `JSON.stringify`, object spread, `Object.keys` and expect's
|
|
9
|
+
* `toStrictEqual` - so a stamped value compares equal to an unstamped one
|
|
10
|
+
* with the same fields.
|
|
11
|
+
*/
|
|
12
|
+
export const TYPE_OID = Symbol('postgrejs.typeOid');
|
|
13
|
+
/**
|
|
14
|
+
* Marks a class whose instances cannot be typed by inspection, so passing
|
|
15
|
+
* one as an untyped parameter is an error rather than a guess.
|
|
16
|
+
*
|
|
17
|
+
* Range is the case: all six range types answer `instanceof Range` and
|
|
18
|
+
* nothing about a Range says which it is. Without this, such a value
|
|
19
|
+
* falls through to JsonType - whose isType() takes any object - and goes
|
|
20
|
+
* out declared `json`, which comes back looking right.
|
|
21
|
+
*/
|
|
22
|
+
export const REQUIRES_TYPE_OID = Symbol('postgrejs.requiresTypeOid');
|
|
23
|
+
/**
|
|
24
|
+
* Records which type a value was decoded from, so it can go back as that
|
|
25
|
+
* type without the caller naming it again.
|
|
26
|
+
*
|
|
27
|
+
* Only worth doing for a value whose class cannot say what it is on its
|
|
28
|
+
* own - it costs a defineProperty per value (~160ns), which is not free
|
|
29
|
+
* on a column of them.
|
|
30
|
+
*/
|
|
31
|
+
export function setTypeOid(value, oid) {
|
|
32
|
+
Object.defineProperty(value, TYPE_OID, {
|
|
33
|
+
value: oid,
|
|
34
|
+
enumerable: false,
|
|
35
|
+
writable: true,
|
|
36
|
+
configurable: true,
|
|
37
|
+
});
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
/** The OID a value was decoded from, if it carries one. */
|
|
41
|
+
export function getTypeOid(value) {
|
|
42
|
+
return value != null
|
|
43
|
+
? value[TYPE_OID]
|
|
44
|
+
: undefined;
|
|
45
|
+
}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { DataTypeOIDs } from '../constants.js';
|
|
2
|
+
/**
|
|
3
|
+
* `inet` and `cidr` share a file because they share everything that
|
|
4
|
+
* matters: one wire format, one JavaScript shape, and one pair of
|
|
5
|
+
* conversions. A single flag byte tells them apart on the wire, and on
|
|
6
|
+
* output the only difference is whether the netmask is always printed.
|
|
7
|
+
*
|
|
8
|
+
* Both decode to a string. JavaScript has no address type, and every Node
|
|
9
|
+
* API that takes an address - `net`, `dns`, `http` - takes a string, so an
|
|
10
|
+
* object of parts would be something every caller has to join back
|
|
11
|
+
* together before using. `pg` answers with a string too.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* PostgreSQL's own address-family numbers, deliberately not the
|
|
15
|
+
* platform's: they are written into the stored value, so they cannot
|
|
16
|
+
* depend on how a particular `<sys/socket.h>` happens to number `AF_INET6`.
|
|
17
|
+
*/
|
|
18
|
+
const PGSQL_AF_INET = 2;
|
|
19
|
+
const PGSQL_AF_INET6 = 3;
|
|
20
|
+
const IPV4_PATTERN = /^\d{1,3}(?:\.\d{1,3}){0,3}$/;
|
|
21
|
+
const IPV6_GROUP_PATTERN = /^[0-9a-fA-F]{1,4}$/;
|
|
22
|
+
/**
|
|
23
|
+
* Writes the four octets of an IPv4 address.
|
|
24
|
+
*
|
|
25
|
+
* Fewer than four is allowed, the rest reading as zero - `10/8` for
|
|
26
|
+
* `10.0.0.0/8`. That is PostgreSQL's own rule for both types, confirmed
|
|
27
|
+
* against the server rather than assumed: `inet '10.0/16'` is accepted
|
|
28
|
+
* and stored as `10.0.0.0/16`.
|
|
29
|
+
*/
|
|
30
|
+
function writeIPv4(out, offset, v) {
|
|
31
|
+
if (!IPV4_PATTERN.test(v))
|
|
32
|
+
return false;
|
|
33
|
+
const parts = v.split('.');
|
|
34
|
+
const l = parts.length;
|
|
35
|
+
let i;
|
|
36
|
+
let n;
|
|
37
|
+
for (i = 0; i < l; i++) {
|
|
38
|
+
n = +parts[i];
|
|
39
|
+
if (n > 255)
|
|
40
|
+
return false;
|
|
41
|
+
out[offset + i] = n;
|
|
42
|
+
}
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Writes the sixteen bytes of an IPv6 address, accepting the two
|
|
47
|
+
* shorthands the server does: a single `::` standing for at least one
|
|
48
|
+
* all-zero group, and a trailing dotted-quad for the last two groups.
|
|
49
|
+
*/
|
|
50
|
+
function writeIPv6(out, offset, v) {
|
|
51
|
+
const dot = v.indexOf('.');
|
|
52
|
+
if (dot >= 0) {
|
|
53
|
+
// An embedded IPv4 tail occupies the last two groups. Rewritten into
|
|
54
|
+
// hex here so the group walk below does not have to know about it.
|
|
55
|
+
const i = v.lastIndexOf(':', dot);
|
|
56
|
+
if (i < 0)
|
|
57
|
+
return false;
|
|
58
|
+
const quad = Buffer.alloc(4);
|
|
59
|
+
const tail = v.slice(i + 1);
|
|
60
|
+
// Unlike a bare IPv4 address, the embedded form has no abbreviated
|
|
61
|
+
// spelling - `::1.2` is not an address.
|
|
62
|
+
if (tail.split('.').length !== 4 || !writeIPv4(quad, 0, tail))
|
|
63
|
+
return false;
|
|
64
|
+
v =
|
|
65
|
+
v.slice(0, i + 1) +
|
|
66
|
+
quad.readUInt16BE(0).toString(16) +
|
|
67
|
+
':' +
|
|
68
|
+
quad.readUInt16BE(2).toString(16);
|
|
69
|
+
}
|
|
70
|
+
const halves = v.split('::');
|
|
71
|
+
if (halves.length > 2)
|
|
72
|
+
return false;
|
|
73
|
+
const head = halves[0] ? halves[0].split(':') : [];
|
|
74
|
+
const tail = halves.length === 2 && halves[1] ? halves[1].split(':') : [];
|
|
75
|
+
const given = head.length + tail.length;
|
|
76
|
+
// Without `::` every group must be written out; with it, at least one
|
|
77
|
+
// must be left for the `::` itself to stand for.
|
|
78
|
+
if (halves.length === 2 ? given > 7 : given !== 8)
|
|
79
|
+
return false;
|
|
80
|
+
out.fill(0, offset, offset + 16);
|
|
81
|
+
let p = offset;
|
|
82
|
+
let g;
|
|
83
|
+
for (g of head) {
|
|
84
|
+
if (!IPV6_GROUP_PATTERN.test(g))
|
|
85
|
+
return false;
|
|
86
|
+
out.writeUInt16BE(parseInt(g, 16), p);
|
|
87
|
+
p += 2;
|
|
88
|
+
}
|
|
89
|
+
p = offset + 16 - tail.length * 2;
|
|
90
|
+
for (g of tail) {
|
|
91
|
+
if (!IPV6_GROUP_PATTERN.test(g))
|
|
92
|
+
return false;
|
|
93
|
+
out.writeUInt16BE(parseInt(g, 16), p);
|
|
94
|
+
p += 2;
|
|
95
|
+
}
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Renders sixteen bytes the way `inet_net_ntop()` does, which is what
|
|
100
|
+
* this has to match - not any general IPv6 convention - because the
|
|
101
|
+
* point is to hand back the string the server itself would have printed.
|
|
102
|
+
*
|
|
103
|
+
* The longest run of zero groups collapses to `::`, but only when it
|
|
104
|
+
* covers at least two of them (`::2:3:4:5:6:7:8` comes back from the
|
|
105
|
+
* server as `0:2:3:4:5:6:7:8`), and the earliest run wins a tie. An
|
|
106
|
+
* address whose last four bytes are really an IPv4 one is printed in
|
|
107
|
+
* dotted form.
|
|
108
|
+
*/
|
|
109
|
+
function formatIPv6(buf, offset) {
|
|
110
|
+
const words = new Array(8);
|
|
111
|
+
let i;
|
|
112
|
+
for (i = 0; i < 8; i++)
|
|
113
|
+
words[i] = buf.readUInt16BE(offset + i * 2);
|
|
114
|
+
let bestBase = -1;
|
|
115
|
+
let bestLen = 0;
|
|
116
|
+
let curBase = -1;
|
|
117
|
+
let curLen = 0;
|
|
118
|
+
for (i = 0; i < 8; i++) {
|
|
119
|
+
if (words[i] !== 0) {
|
|
120
|
+
curBase = -1;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (curBase === -1) {
|
|
124
|
+
curBase = i;
|
|
125
|
+
curLen = 1;
|
|
126
|
+
}
|
|
127
|
+
else
|
|
128
|
+
curLen++;
|
|
129
|
+
if (curLen > bestLen) {
|
|
130
|
+
bestBase = curBase;
|
|
131
|
+
bestLen = curLen;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (bestLen < 2)
|
|
135
|
+
bestBase = -1;
|
|
136
|
+
let out = '';
|
|
137
|
+
for (i = 0; i < 8; i++) {
|
|
138
|
+
if (bestBase !== -1 && i >= bestBase && i < bestBase + bestLen) {
|
|
139
|
+
if (i === bestBase)
|
|
140
|
+
out += ':';
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (i !== 0)
|
|
144
|
+
out += ':';
|
|
145
|
+
// `::a.b.c.d` and `::ffff:a.b.c.d` - the two prefixes that mean the
|
|
146
|
+
// tail is an IPv4 address. Nothing else qualifies, so `::1:1.2.3.4`
|
|
147
|
+
// prints as `::1:102:304`.
|
|
148
|
+
if (i === 6 &&
|
|
149
|
+
bestBase === 0 &&
|
|
150
|
+
(bestLen === 6 || (bestLen === 5 && words[5] === 0xffff))) {
|
|
151
|
+
out +=
|
|
152
|
+
buf[offset + 12] +
|
|
153
|
+
'.' +
|
|
154
|
+
buf[offset + 13] +
|
|
155
|
+
'.' +
|
|
156
|
+
buf[offset + 14] +
|
|
157
|
+
'.' +
|
|
158
|
+
buf[offset + 15];
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
out += words[i].toString(16);
|
|
162
|
+
}
|
|
163
|
+
// A run that reaches the end leaves only one colon behind.
|
|
164
|
+
if (bestBase !== -1 && bestBase + bestLen === 8)
|
|
165
|
+
out += ':';
|
|
166
|
+
return out;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* The address and its netmask, or undefined if the string is not one.
|
|
170
|
+
* Splitting is done here rather than by the caller because the two types
|
|
171
|
+
* differ only in what they do with the parts.
|
|
172
|
+
*/
|
|
173
|
+
function parseAddress(v) {
|
|
174
|
+
if (typeof v !== 'string')
|
|
175
|
+
return undefined;
|
|
176
|
+
const slash = v.indexOf('/');
|
|
177
|
+
const addr = slash < 0 ? v : v.slice(0, slash);
|
|
178
|
+
const family = addr.includes(':') ? PGSQL_AF_INET6 : PGSQL_AF_INET;
|
|
179
|
+
const size = family === PGSQL_AF_INET6 ? 16 : 4;
|
|
180
|
+
const bytes = Buffer.alloc(size);
|
|
181
|
+
const ok = family === PGSQL_AF_INET6
|
|
182
|
+
? writeIPv6(bytes, 0, addr)
|
|
183
|
+
: writeIPv4(bytes, 0, addr);
|
|
184
|
+
if (!ok)
|
|
185
|
+
return undefined;
|
|
186
|
+
let bits = size * 8;
|
|
187
|
+
if (slash >= 0) {
|
|
188
|
+
const s = v.slice(slash + 1);
|
|
189
|
+
if (!/^\d{1,3}$/.test(s))
|
|
190
|
+
return undefined;
|
|
191
|
+
bits = +s;
|
|
192
|
+
if (bits > size * 8)
|
|
193
|
+
return undefined;
|
|
194
|
+
}
|
|
195
|
+
return { family, bits, bytes };
|
|
196
|
+
}
|
|
197
|
+
function encode(buf, v, isCidr) {
|
|
198
|
+
const a = parseAddress(v);
|
|
199
|
+
if (!a)
|
|
200
|
+
throw new Error(`"${v}" is not a valid ${isCidr ? 'cidr' : 'inet'} value`);
|
|
201
|
+
buf.writeUInt8(a.family);
|
|
202
|
+
// Unsigned: a full IPv6 mask is 128, which does not fit a signed byte.
|
|
203
|
+
buf.writeUInt8(a.bits);
|
|
204
|
+
buf.writeUInt8(isCidr ? 1 : 0);
|
|
205
|
+
buf.writeUInt8(a.bytes.length);
|
|
206
|
+
buf.writeBytes(a.bytes);
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Four header bytes - family, netmask bits, the cidr flag and the
|
|
210
|
+
* address length - then the address itself. The flag is what decides
|
|
211
|
+
* whether the netmask is printed, so one decoder serves both types: a
|
|
212
|
+
* `cidr` always carries its mask, an `inet` only when it is not the whole
|
|
213
|
+
* address.
|
|
214
|
+
*/
|
|
215
|
+
function decode(v, offset) {
|
|
216
|
+
const family = v[offset];
|
|
217
|
+
const bits = v[offset + 1];
|
|
218
|
+
const isCidr = v[offset + 2];
|
|
219
|
+
const size = v[offset + 3];
|
|
220
|
+
if (family !== PGSQL_AF_INET && family !== PGSQL_AF_INET6)
|
|
221
|
+
throw new Error(`Unknown address family ${family} in inet value`);
|
|
222
|
+
const text = family === PGSQL_AF_INET6
|
|
223
|
+
? formatIPv6(v, offset + 4)
|
|
224
|
+
: v[offset + 4] +
|
|
225
|
+
'.' +
|
|
226
|
+
v[offset + 5] +
|
|
227
|
+
'.' +
|
|
228
|
+
v[offset + 6] +
|
|
229
|
+
'.' +
|
|
230
|
+
v[offset + 7];
|
|
231
|
+
return isCidr || bits !== size * 8 ? text + '/' + bits : text;
|
|
232
|
+
}
|
|
233
|
+
export const InetType = {
|
|
234
|
+
name: 'inet',
|
|
235
|
+
oid: DataTypeOIDs.inet,
|
|
236
|
+
jsType: 'string',
|
|
237
|
+
// An address is not a shape - it is a string like any other, and an
|
|
238
|
+
// application storing one in a `text` column passes exactly the same
|
|
239
|
+
// value. Inferring this type from a string would send `inet` where
|
|
240
|
+
// `text` was meant and break that query, so the type is reached only by
|
|
241
|
+
// naming it: `new BindParam(DataTypeOIDs.inet, '192.168.0.1')`, or a
|
|
242
|
+
// column whose declared type says so.
|
|
243
|
+
inferrable: false,
|
|
244
|
+
encodeText(v) {
|
|
245
|
+
// Handed to the server as written; it does its own parsing here, and
|
|
246
|
+
// accepts spellings this file's own parser is not asked to know.
|
|
247
|
+
return '' + v;
|
|
248
|
+
},
|
|
249
|
+
encodeBinary(buf, v) {
|
|
250
|
+
encode(buf, v, false);
|
|
251
|
+
},
|
|
252
|
+
decodeBinary(v, offset = 0) {
|
|
253
|
+
return decode(v, offset);
|
|
254
|
+
},
|
|
255
|
+
decodeText(v) {
|
|
256
|
+
return v;
|
|
257
|
+
},
|
|
258
|
+
// An address is pure ASCII, so 'latin1' decodes identically to 'utf8'
|
|
259
|
+
// while skipping V8's multi-byte-sequence detection.
|
|
260
|
+
decodeTextBuffer(buf, offset, len) {
|
|
261
|
+
return buf.toString('latin1', offset, offset + len);
|
|
262
|
+
},
|
|
263
|
+
isType(v) {
|
|
264
|
+
return !!parseAddress(v);
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
export const ArrayInetType = {
|
|
268
|
+
...InetType,
|
|
269
|
+
name: '_inet',
|
|
270
|
+
oid: DataTypeOIDs._inet,
|
|
271
|
+
elementsOID: DataTypeOIDs.inet,
|
|
272
|
+
};
|
|
273
|
+
export const CidrType = {
|
|
274
|
+
...InetType,
|
|
275
|
+
name: 'cidr',
|
|
276
|
+
oid: DataTypeOIDs.cidr,
|
|
277
|
+
encodeBinary(buf, v) {
|
|
278
|
+
encode(buf, v, true);
|
|
279
|
+
},
|
|
280
|
+
};
|
|
281
|
+
export const ArrayCidrType = {
|
|
282
|
+
...CidrType,
|
|
283
|
+
name: '_cidr',
|
|
284
|
+
oid: DataTypeOIDs._cidr,
|
|
285
|
+
elementsOID: DataTypeOIDs.cidr,
|
|
286
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { DataType } from '../interfaces/data-type.js';
|
|
2
|
+
import { Interval } from './classes/interval.js';
|
|
3
|
+
export declare function parseIntervalText(v: string): Interval;
|
|
4
|
+
export declare const IntervalType: DataType;
|
|
5
|
+
export declare const ArrayIntervalType: DataType;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { DataTypeOIDs } from '../constants.js';
|
|
2
|
+
import { readBigInt64BE } from '../util/bigint-methods.js';
|
|
3
|
+
import { Interval } from './classes/interval.js';
|
|
4
|
+
/**
|
|
5
|
+
* What PostgreSQL prints under the default `IntervalStyle` - years, months
|
|
6
|
+
* and days as counted words, then a signed clock time. Every part is
|
|
7
|
+
* optional and the server omits the ones that are zero, except that a
|
|
8
|
+
* wholly zero interval still prints `00:00:00`.
|
|
9
|
+
*/
|
|
10
|
+
const INTERVAL_PATTERN = /^\s*(?:([+-]?\d+)\s+years?\s*)?(?:([+-]?\d+)\s+mons?\s*)?(?:([+-]?\d+)\s+days?\s*)?(?:([+-])?(\d+):(\d\d):(\d\d)(?:\.(\d{1,6}))?)?\s*$/;
|
|
11
|
+
function toInterval(v) {
|
|
12
|
+
if (v instanceof Interval)
|
|
13
|
+
return v;
|
|
14
|
+
if (typeof v === 'string')
|
|
15
|
+
return parseIntervalText(v);
|
|
16
|
+
if (v && typeof v === 'object')
|
|
17
|
+
return new Interval(v);
|
|
18
|
+
throw new TypeError(`"${typeof v}" cannot be encoded as an interval - pass an Interval, ` +
|
|
19
|
+
'a plain object of its fields, or a string PostgreSQL would accept');
|
|
20
|
+
}
|
|
21
|
+
export function parseIntervalText(v) {
|
|
22
|
+
const m = INTERVAL_PATTERN.exec(v);
|
|
23
|
+
if (!m) {
|
|
24
|
+
// Rather than answering with a zero interval, which is what reading
|
|
25
|
+
// an unrecognized shape leniently would amount to. The other styles
|
|
26
|
+
// (`sql_standard`, `postgres_verbose`, `iso_8601`) are not parsed
|
|
27
|
+
// here, and the binary path - which is the default - never sees any
|
|
28
|
+
// of this.
|
|
29
|
+
throw new Error(`"${v}" is not an interval in PostgreSQL's default IntervalStyle`);
|
|
30
|
+
}
|
|
31
|
+
const sign = m[4] === '-' ? -1 : 1;
|
|
32
|
+
return new Interval({
|
|
33
|
+
years: m[1] ? parseInt(m[1], 10) : 0,
|
|
34
|
+
months: m[2] ? parseInt(m[2], 10) : 0,
|
|
35
|
+
days: m[3] ? parseInt(m[3], 10) : 0,
|
|
36
|
+
hours: m[5] ? sign * parseInt(m[5], 10) : 0,
|
|
37
|
+
minutes: m[6] ? sign * parseInt(m[6], 10) : 0,
|
|
38
|
+
seconds: m[7] ? sign * parseInt(m[7], 10) : 0,
|
|
39
|
+
// The fraction is microseconds with trailing zeroes dropped, so `.5`
|
|
40
|
+
// is half a second rather than five microseconds.
|
|
41
|
+
milliseconds: m[8] ? (sign * parseInt(m[8].padEnd(6, '0'), 10)) / 1000 : 0,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
export const IntervalType = {
|
|
45
|
+
name: 'interval',
|
|
46
|
+
oid: DataTypeOIDs.interval,
|
|
47
|
+
jsType: 'Interval',
|
|
48
|
+
/**
|
|
49
|
+
* 16 bytes, and three separate quantities rather than one total: a
|
|
50
|
+
* signed microsecond count for the time, then a day count and a month
|
|
51
|
+
* count. The server keeps them apart because they are not convertible -
|
|
52
|
+
* see the Interval class.
|
|
53
|
+
*/
|
|
54
|
+
decodeBinary(v, offset = 0) {
|
|
55
|
+
const micros = typeof v.readBigInt64BE === 'function'
|
|
56
|
+
? v.readBigInt64BE(offset)
|
|
57
|
+
: readBigInt64BE(v, offset);
|
|
58
|
+
return Interval.fromParts(micros, v.readInt32BE(offset + 8), v.readInt32BE(offset + 12));
|
|
59
|
+
},
|
|
60
|
+
encodeBinary(buf, v) {
|
|
61
|
+
const iv = toInterval(v);
|
|
62
|
+
buf.writeBigInt64BE(iv.totalMicroseconds);
|
|
63
|
+
buf.writeInt32BE(iv.days);
|
|
64
|
+
buf.writeInt32BE(iv.totalMonths);
|
|
65
|
+
},
|
|
66
|
+
decodeText: parseIntervalText,
|
|
67
|
+
encodeText(v) {
|
|
68
|
+
return toInterval(v).toString();
|
|
69
|
+
},
|
|
70
|
+
isType(v) {
|
|
71
|
+
return v instanceof Interval;
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
export const ArrayIntervalType = {
|
|
75
|
+
...IntervalType,
|
|
76
|
+
name: '_interval',
|
|
77
|
+
oid: DataTypeOIDs._interval,
|
|
78
|
+
elementsOID: DataTypeOIDs.interval,
|
|
79
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { DataTypeOIDs } from '../constants.js';
|
|
2
|
+
/**
|
|
3
|
+
* A jsonpath decodes to a string, and there is nothing else it could
|
|
4
|
+
* sensibly be: the binary form is the text form with one byte in front
|
|
5
|
+
* of it. Parsing the expression into a tree would be inventing an API
|
|
6
|
+
* for something the server already understands and this client never
|
|
7
|
+
* evaluates. `pg` leaves it a string too.
|
|
8
|
+
*
|
|
9
|
+
* The string that comes back is PostgreSQL's normalized spelling, not
|
|
10
|
+
* what was written - `$.a[*].b` is stored and printed as `$."a"[*]."b"`.
|
|
11
|
+
*/
|
|
12
|
+
/** The only format the server has ever written, and the one it reads. */
|
|
13
|
+
const JSONPATH_VERSION = 1;
|
|
14
|
+
export const JsonPathType = {
|
|
15
|
+
name: 'jsonpath',
|
|
16
|
+
oid: DataTypeOIDs.jsonpath,
|
|
17
|
+
jsType: 'string',
|
|
18
|
+
// A jsonpath is a string, and inferring this type from one would send
|
|
19
|
+
// `jsonpath` where `text` was meant. See inet-type.ts.
|
|
20
|
+
inferrable: false,
|
|
21
|
+
encodeText(v) {
|
|
22
|
+
return '' + v;
|
|
23
|
+
},
|
|
24
|
+
/** A version byte, then the expression as UTF-8 - no length prefix. */
|
|
25
|
+
encodeBinary(buf, v) {
|
|
26
|
+
if (typeof v !== 'string')
|
|
27
|
+
throw new Error(`"${v}" is not a valid jsonpath value`);
|
|
28
|
+
buf.writeUInt8(JSONPATH_VERSION);
|
|
29
|
+
buf.writeString(v, 'utf8');
|
|
30
|
+
},
|
|
31
|
+
decodeBinary(v, offset = 0, len) {
|
|
32
|
+
const version = v[offset];
|
|
33
|
+
if (version !== JSONPATH_VERSION)
|
|
34
|
+
throw new Error(`Unsupported jsonpath version ${version}`);
|
|
35
|
+
return v.toString('utf8', offset + 1, offset + len);
|
|
36
|
+
},
|
|
37
|
+
decodeText(v) {
|
|
38
|
+
return v;
|
|
39
|
+
},
|
|
40
|
+
isType(v) {
|
|
41
|
+
// Every jsonpath begins with a variable or a mode word, and `$` is
|
|
42
|
+
// the only variable an expression can start with once `strict` or
|
|
43
|
+
// `lax` is allowed for - enough to tell a path from ordinary text
|
|
44
|
+
// without parsing one.
|
|
45
|
+
return typeof v === 'string' && /^\s*(?:(?:strict|lax)\s+)?[$@]/.test(v);
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
export const ArrayJsonPathType = {
|
|
49
|
+
...JsonPathType,
|
|
50
|
+
name: '_jsonpath',
|
|
51
|
+
oid: DataTypeOIDs._jsonpath,
|
|
52
|
+
elementsOID: DataTypeOIDs.jsonpath,
|
|
53
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { DataTypeOIDs } from '../constants.js';
|
|
2
|
+
import { Line } from './classes/geometric.js';
|
|
3
|
+
const LINE_PATTERN = /^\{ *(-?[\d.]+(?:e[-+]?\d+)?) *, *(-?[\d.]+(?:e[-+]?\d+)?) *, *(-?[\d.]+(?:e[-+]?\d+)?) *\}$/i;
|
|
4
|
+
export const LineType = {
|
|
5
|
+
name: 'line',
|
|
6
|
+
oid: DataTypeOIDs.line,
|
|
7
|
+
jsType: 'Line',
|
|
8
|
+
encodeText(v) {
|
|
9
|
+
return `{${v.a},${v.b},${v.c}}`;
|
|
10
|
+
},
|
|
11
|
+
/** Three float8s: the A, B and C of Ax + By + C = 0. */
|
|
12
|
+
encodeBinary(buf, v) {
|
|
13
|
+
buf.writeDoubleBE(v.a);
|
|
14
|
+
buf.writeDoubleBE(v.b);
|
|
15
|
+
buf.writeDoubleBE(v.c);
|
|
16
|
+
},
|
|
17
|
+
decodeBinary(v, offset = 0) {
|
|
18
|
+
return new Line(v.readDoubleBE(offset), v.readDoubleBE(offset + 8), v.readDoubleBE(offset + 16));
|
|
19
|
+
},
|
|
20
|
+
decodeText(v) {
|
|
21
|
+
const m = v.match(LINE_PATTERN);
|
|
22
|
+
if (!m)
|
|
23
|
+
return undefined;
|
|
24
|
+
return new Line(parseFloat(m[1]), parseFloat(m[2]), parseFloat(m[3]));
|
|
25
|
+
},
|
|
26
|
+
// See box-type.ts's decodeTextBuffer comment - same rationale.
|
|
27
|
+
decodeTextBuffer(buf, offset, len, options) {
|
|
28
|
+
return LineType.decodeText(buf.toString('latin1', offset, offset + len), options);
|
|
29
|
+
},
|
|
30
|
+
// A plain `{a, b, c}` is accepted for the same reason `{x, y}` is by
|
|
31
|
+
// point - see point-type.ts.
|
|
32
|
+
isType(v) {
|
|
33
|
+
return v instanceof Line || Line.isLineLike(v);
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
export const ArrayLineType = {
|
|
37
|
+
...LineType,
|
|
38
|
+
name: '_line',
|
|
39
|
+
oid: DataTypeOIDs._line,
|
|
40
|
+
elementsOID: DataTypeOIDs.line,
|
|
41
|
+
};
|