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,73 @@
|
|
|
1
|
+
/** The fields an Interval is built from. All optional, all defaulting to 0. */
|
|
2
|
+
export interface IntervalFields {
|
|
3
|
+
years?: number;
|
|
4
|
+
months?: number;
|
|
5
|
+
days?: number;
|
|
6
|
+
hours?: number;
|
|
7
|
+
minutes?: number;
|
|
8
|
+
seconds?: number;
|
|
9
|
+
milliseconds?: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* PostgreSQL's `interval`, as the three independent quantities it actually
|
|
13
|
+
* is: a number of months, a number of days, and a time.
|
|
14
|
+
*
|
|
15
|
+
* They are kept apart rather than reduced to one total because they are
|
|
16
|
+
* not convertible - a month is 28 to 31 days and a day is 23 to 25 hours
|
|
17
|
+
* across a DST boundary, so only the server, holding a calendar and a time
|
|
18
|
+
* zone, can add one to a timestamp. Anything here that answered "how many
|
|
19
|
+
* milliseconds is this" would have to invent those lengths.
|
|
20
|
+
*
|
|
21
|
+
* Every field is always present, 0 when the value has none:
|
|
22
|
+
*
|
|
23
|
+
* ```ts
|
|
24
|
+
* const iv = new Interval({ days: 1, hours: 2 });
|
|
25
|
+
* iv.minutes; // 0, not undefined
|
|
26
|
+
* String(iv); // '1 day 02:00:00'
|
|
27
|
+
* iv.toISOString(); // 'P0Y0M1DT2H0M0S'
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* The field names are `pg`'s (`postgres-interval`), so code ported from it
|
|
31
|
+
* reads the same values from the same places. The difference is that
|
|
32
|
+
* package's objects are sparse - `interval '1 day'` gives it `{days: 1}`
|
|
33
|
+
* and nothing else, so `iv.hours + 1` is NaN and a zero interval is `{}` -
|
|
34
|
+
* and that it has no `toString()`, so a log line gets `[object Object]`.
|
|
35
|
+
*
|
|
36
|
+
* `milliseconds` carries the sub-second part and can be fractional, since
|
|
37
|
+
* PostgreSQL stores microseconds: `interval '0.000001 seconds'` is
|
|
38
|
+
* `milliseconds: 0.001`. Also `pg`'s behaviour, kept for the same reason.
|
|
39
|
+
*
|
|
40
|
+
* A negative interval carries the sign on each field that has one, the way
|
|
41
|
+
* the server prints it: `interval '-1 day -2 hours'` is
|
|
42
|
+
* `{ days: -1, hours: -2 }`.
|
|
43
|
+
*/
|
|
44
|
+
export declare class Interval implements IntervalFields {
|
|
45
|
+
years: number;
|
|
46
|
+
months: number;
|
|
47
|
+
days: number;
|
|
48
|
+
hours: number;
|
|
49
|
+
minutes: number;
|
|
50
|
+
/** Whole seconds; the sub-second part is in `milliseconds`. */
|
|
51
|
+
seconds: number;
|
|
52
|
+
/** The sub-second part, fractional down to a microsecond. */
|
|
53
|
+
milliseconds: number;
|
|
54
|
+
constructor(fields?: IntervalFields);
|
|
55
|
+
/**
|
|
56
|
+
* Builds one from the three quantities the wire format carries: a signed
|
|
57
|
+
* microsecond count for the time, and day and month counts of their own.
|
|
58
|
+
*/
|
|
59
|
+
static fromParts(microseconds: bigint, days: number, months: number): Interval;
|
|
60
|
+
/** The whole month count, as the wire format carries it. */
|
|
61
|
+
get totalMonths(): number;
|
|
62
|
+
/** The time part as a signed microsecond count, as the wire format carries it. */
|
|
63
|
+
get totalMicroseconds(): bigint;
|
|
64
|
+
/**
|
|
65
|
+
* The interval as PostgreSQL itself prints it under the default
|
|
66
|
+
* `IntervalStyle`, so it round-trips through an `::interval` cast and
|
|
67
|
+
* reads the way the same value does in psql.
|
|
68
|
+
*/
|
|
69
|
+
toString(): string;
|
|
70
|
+
/** The interval as an ISO 8601 duration. */
|
|
71
|
+
toISOString(): string;
|
|
72
|
+
toJSON(): string;
|
|
73
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
const US_PER_SECOND = 1000000n;
|
|
2
|
+
const US_PER_MINUTE = 60n * US_PER_SECOND;
|
|
3
|
+
const US_PER_HOUR = 60n * US_PER_MINUTE;
|
|
4
|
+
function pad2(v) {
|
|
5
|
+
return v < 10 ? '0' + v : '' + v;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* PostgreSQL's `interval`, as the three independent quantities it actually
|
|
9
|
+
* is: a number of months, a number of days, and a time.
|
|
10
|
+
*
|
|
11
|
+
* They are kept apart rather than reduced to one total because they are
|
|
12
|
+
* not convertible - a month is 28 to 31 days and a day is 23 to 25 hours
|
|
13
|
+
* across a DST boundary, so only the server, holding a calendar and a time
|
|
14
|
+
* zone, can add one to a timestamp. Anything here that answered "how many
|
|
15
|
+
* milliseconds is this" would have to invent those lengths.
|
|
16
|
+
*
|
|
17
|
+
* Every field is always present, 0 when the value has none:
|
|
18
|
+
*
|
|
19
|
+
* ```ts
|
|
20
|
+
* const iv = new Interval({ days: 1, hours: 2 });
|
|
21
|
+
* iv.minutes; // 0, not undefined
|
|
22
|
+
* String(iv); // '1 day 02:00:00'
|
|
23
|
+
* iv.toISOString(); // 'P0Y0M1DT2H0M0S'
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* The field names are `pg`'s (`postgres-interval`), so code ported from it
|
|
27
|
+
* reads the same values from the same places. The difference is that
|
|
28
|
+
* package's objects are sparse - `interval '1 day'` gives it `{days: 1}`
|
|
29
|
+
* and nothing else, so `iv.hours + 1` is NaN and a zero interval is `{}` -
|
|
30
|
+
* and that it has no `toString()`, so a log line gets `[object Object]`.
|
|
31
|
+
*
|
|
32
|
+
* `milliseconds` carries the sub-second part and can be fractional, since
|
|
33
|
+
* PostgreSQL stores microseconds: `interval '0.000001 seconds'` is
|
|
34
|
+
* `milliseconds: 0.001`. Also `pg`'s behaviour, kept for the same reason.
|
|
35
|
+
*
|
|
36
|
+
* A negative interval carries the sign on each field that has one, the way
|
|
37
|
+
* the server prints it: `interval '-1 day -2 hours'` is
|
|
38
|
+
* `{ days: -1, hours: -2 }`.
|
|
39
|
+
*/
|
|
40
|
+
export class Interval {
|
|
41
|
+
years;
|
|
42
|
+
months;
|
|
43
|
+
days;
|
|
44
|
+
hours;
|
|
45
|
+
minutes;
|
|
46
|
+
/** Whole seconds; the sub-second part is in `milliseconds`. */
|
|
47
|
+
seconds;
|
|
48
|
+
/** The sub-second part, fractional down to a microsecond. */
|
|
49
|
+
milliseconds;
|
|
50
|
+
constructor(fields) {
|
|
51
|
+
this.years = fields?.years || 0;
|
|
52
|
+
this.months = fields?.months || 0;
|
|
53
|
+
this.days = fields?.days || 0;
|
|
54
|
+
this.hours = fields?.hours || 0;
|
|
55
|
+
this.minutes = fields?.minutes || 0;
|
|
56
|
+
this.seconds = fields?.seconds || 0;
|
|
57
|
+
this.milliseconds = fields?.milliseconds || 0;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Builds one from the three quantities the wire format carries: a signed
|
|
61
|
+
* microsecond count for the time, and day and month counts of their own.
|
|
62
|
+
*/
|
|
63
|
+
static fromParts(microseconds, days, months) {
|
|
64
|
+
const negative = microseconds < 0n;
|
|
65
|
+
let left = negative ? -microseconds : microseconds;
|
|
66
|
+
const hours = left / US_PER_HOUR;
|
|
67
|
+
left -= hours * US_PER_HOUR;
|
|
68
|
+
const minutes = left / US_PER_MINUTE;
|
|
69
|
+
left -= minutes * US_PER_MINUTE;
|
|
70
|
+
const seconds = left / US_PER_SECOND;
|
|
71
|
+
left -= seconds * US_PER_SECOND;
|
|
72
|
+
const sign = negative ? -1 : 1;
|
|
73
|
+
return new Interval({
|
|
74
|
+
// Months are stored as one count and printed as years plus months,
|
|
75
|
+
// so 13 comes back as a year and a month - which is what the server
|
|
76
|
+
// does with `interval '13 mons'`.
|
|
77
|
+
years: Math.trunc(months / 12),
|
|
78
|
+
months: months % 12,
|
|
79
|
+
days,
|
|
80
|
+
hours: sign * Number(hours),
|
|
81
|
+
minutes: sign * Number(minutes),
|
|
82
|
+
seconds: sign * Number(seconds),
|
|
83
|
+
milliseconds: (sign * Number(left)) / 1000,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
/** The whole month count, as the wire format carries it. */
|
|
87
|
+
get totalMonths() {
|
|
88
|
+
return this.years * 12 + this.months;
|
|
89
|
+
}
|
|
90
|
+
/** The time part as a signed microsecond count, as the wire format carries it. */
|
|
91
|
+
get totalMicroseconds() {
|
|
92
|
+
// Split so that a fractional hours/minutes/seconds is folded in rather
|
|
93
|
+
// than silently truncated, while whole values stay exact past what a
|
|
94
|
+
// double can hold.
|
|
95
|
+
const whole = Math.trunc(this.hours) * 3600 +
|
|
96
|
+
Math.trunc(this.minutes) * 60 +
|
|
97
|
+
Math.trunc(this.seconds);
|
|
98
|
+
const fraction = (this.hours - Math.trunc(this.hours)) * 3600 +
|
|
99
|
+
(this.minutes - Math.trunc(this.minutes)) * 60 +
|
|
100
|
+
(this.seconds - Math.trunc(this.seconds));
|
|
101
|
+
return (BigInt(whole) * US_PER_SECOND +
|
|
102
|
+
BigInt(Math.round(fraction * 1e6 + this.milliseconds * 1000)));
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* The interval as PostgreSQL itself prints it under the default
|
|
106
|
+
* `IntervalStyle`, so it round-trips through an `::interval` cast and
|
|
107
|
+
* reads the way the same value does in psql.
|
|
108
|
+
*/
|
|
109
|
+
toString() {
|
|
110
|
+
const out = [];
|
|
111
|
+
const { years, months, days } = this;
|
|
112
|
+
// Singular only for exactly 1 - the server prints `-1 days`, not
|
|
113
|
+
// `-1 day`.
|
|
114
|
+
if (years)
|
|
115
|
+
out.push(years + (years === 1 ? ' year' : ' years'));
|
|
116
|
+
if (months)
|
|
117
|
+
out.push(months + (months === 1 ? ' mon' : ' mons'));
|
|
118
|
+
if (days)
|
|
119
|
+
out.push(days + (days === 1 ? ' day' : ' days'));
|
|
120
|
+
const us = this.totalMicroseconds;
|
|
121
|
+
// The time is printed when it has a value, and also when nothing else
|
|
122
|
+
// does - a zero interval is `00:00:00` rather than an empty string.
|
|
123
|
+
if (us || !out.length) {
|
|
124
|
+
const negative = us < 0n;
|
|
125
|
+
let left = negative ? -us : us;
|
|
126
|
+
const hours = left / US_PER_HOUR;
|
|
127
|
+
left -= hours * US_PER_HOUR;
|
|
128
|
+
const minutes = left / US_PER_MINUTE;
|
|
129
|
+
left -= minutes * US_PER_MINUTE;
|
|
130
|
+
const seconds = left / US_PER_SECOND;
|
|
131
|
+
left -= seconds * US_PER_SECOND;
|
|
132
|
+
let time = (negative ? '-' : '') +
|
|
133
|
+
// Hours are not bounded at 24 and are padded only up to two
|
|
134
|
+
// digits: `100000:00:00` is what the server prints.
|
|
135
|
+
pad2(Number(hours)) +
|
|
136
|
+
':' +
|
|
137
|
+
pad2(Number(minutes)) +
|
|
138
|
+
':' +
|
|
139
|
+
pad2(Number(seconds));
|
|
140
|
+
if (left) {
|
|
141
|
+
time += '.' + String(left).padStart(6, '0').replace(/0+$/, '');
|
|
142
|
+
}
|
|
143
|
+
out.push(time);
|
|
144
|
+
}
|
|
145
|
+
return out.join(' ');
|
|
146
|
+
}
|
|
147
|
+
/** The interval as an ISO 8601 duration. */
|
|
148
|
+
toISOString() {
|
|
149
|
+
const us = this.totalMicroseconds;
|
|
150
|
+
const negative = us < 0n;
|
|
151
|
+
let left = negative ? -us : us;
|
|
152
|
+
const hours = left / US_PER_HOUR;
|
|
153
|
+
left -= hours * US_PER_HOUR;
|
|
154
|
+
const minutes = left / US_PER_MINUTE;
|
|
155
|
+
left -= minutes * US_PER_MINUTE;
|
|
156
|
+
const seconds = left / US_PER_SECOND;
|
|
157
|
+
left -= seconds * US_PER_SECOND;
|
|
158
|
+
const sign = negative ? '-' : '';
|
|
159
|
+
let secondsPart = sign + seconds;
|
|
160
|
+
if (left)
|
|
161
|
+
secondsPart += '.' + String(left).padStart(6, '0').replace(/0+$/, '');
|
|
162
|
+
return ('P' +
|
|
163
|
+
this.years +
|
|
164
|
+
'Y' +
|
|
165
|
+
this.months +
|
|
166
|
+
'M' +
|
|
167
|
+
this.days +
|
|
168
|
+
'DT' +
|
|
169
|
+
sign +
|
|
170
|
+
hours +
|
|
171
|
+
'H' +
|
|
172
|
+
sign +
|
|
173
|
+
minutes +
|
|
174
|
+
'M' +
|
|
175
|
+
secondsPart +
|
|
176
|
+
'S');
|
|
177
|
+
}
|
|
178
|
+
toJSON() {
|
|
179
|
+
return this.toString();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The exact decimal a `numeric` column holds, for the values a
|
|
3
|
+
* JavaScript number cannot carry.
|
|
4
|
+
*
|
|
5
|
+
* `numeric` exists precisely to hold numbers a double cannot - more than
|
|
6
|
+
* about fifteen significant digits, or a magnitude past what prints in
|
|
7
|
+
* plain notation - so decoding one into a double throws away the reason
|
|
8
|
+
* the column was declared that way. A value that does fit still decodes
|
|
9
|
+
* to a plain number; this class is what the rest become, and it carries
|
|
10
|
+
* the digits exactly as the server wrote them.
|
|
11
|
+
*
|
|
12
|
+
* It is a class rather than a bare string so that it can be told apart:
|
|
13
|
+
* `determine()` types a bare string as `varchar`, which the server then
|
|
14
|
+
* refuses to put in a numeric column, so a string could be read but not
|
|
15
|
+
* written back. A `Numeric` round-trips.
|
|
16
|
+
*
|
|
17
|
+
* There is deliberately no `valueOf()`. Arithmetic on one of these has
|
|
18
|
+
* to be an explicit decision - `Symbol.toPrimitive` hands back the exact
|
|
19
|
+
* text for every hint, so `String(n)` and `${n}` are lossless, and
|
|
20
|
+
* `toNumber()` is the way to ask for the double and say so.
|
|
21
|
+
*/
|
|
22
|
+
export declare class Numeric {
|
|
23
|
+
readonly value: string;
|
|
24
|
+
constructor(value: string | number | bigint);
|
|
25
|
+
/** The decimal exactly as PostgreSQL wrote it; casts back through `::numeric`. */
|
|
26
|
+
toString(): string;
|
|
27
|
+
toJSON(): string;
|
|
28
|
+
/** The value as a double, which is lossy - that is the whole point of this class. */
|
|
29
|
+
toNumber(): number;
|
|
30
|
+
/**
|
|
31
|
+
* The text for every hint, the numeric one included: coercing to a
|
|
32
|
+
* double behind the caller's back is the loss this class exists to
|
|
33
|
+
* prevent, so `n * 2` has to go through the string and `n + 1`
|
|
34
|
+
* concatenates rather than quietly rounding.
|
|
35
|
+
*/
|
|
36
|
+
[Symbol.toPrimitive](): string;
|
|
37
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The exact decimal a `numeric` column holds, for the values a
|
|
3
|
+
* JavaScript number cannot carry.
|
|
4
|
+
*
|
|
5
|
+
* `numeric` exists precisely to hold numbers a double cannot - more than
|
|
6
|
+
* about fifteen significant digits, or a magnitude past what prints in
|
|
7
|
+
* plain notation - so decoding one into a double throws away the reason
|
|
8
|
+
* the column was declared that way. A value that does fit still decodes
|
|
9
|
+
* to a plain number; this class is what the rest become, and it carries
|
|
10
|
+
* the digits exactly as the server wrote them.
|
|
11
|
+
*
|
|
12
|
+
* It is a class rather than a bare string so that it can be told apart:
|
|
13
|
+
* `determine()` types a bare string as `varchar`, which the server then
|
|
14
|
+
* refuses to put in a numeric column, so a string could be read but not
|
|
15
|
+
* written back. A `Numeric` round-trips.
|
|
16
|
+
*
|
|
17
|
+
* There is deliberately no `valueOf()`. Arithmetic on one of these has
|
|
18
|
+
* to be an explicit decision - `Symbol.toPrimitive` hands back the exact
|
|
19
|
+
* text for every hint, so `String(n)` and `${n}` are lossless, and
|
|
20
|
+
* `toNumber()` is the way to ask for the double and say so.
|
|
21
|
+
*/
|
|
22
|
+
export class Numeric {
|
|
23
|
+
value;
|
|
24
|
+
constructor(value) {
|
|
25
|
+
this.value = typeof value === 'string' ? value : String(value);
|
|
26
|
+
}
|
|
27
|
+
/** The decimal exactly as PostgreSQL wrote it; casts back through `::numeric`. */
|
|
28
|
+
toString() {
|
|
29
|
+
return this.value;
|
|
30
|
+
}
|
|
31
|
+
toJSON() {
|
|
32
|
+
return this.value;
|
|
33
|
+
}
|
|
34
|
+
/** The value as a double, which is lossy - that is the whole point of this class. */
|
|
35
|
+
toNumber() {
|
|
36
|
+
return parseFloat(this.value);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The text for every hint, the numeric one included: coercing to a
|
|
40
|
+
* double behind the caller's back is the loss this class exists to
|
|
41
|
+
* prevent, so `n * 2` has to go through the string and `n + 1`
|
|
42
|
+
* concatenates rather than quietly rounding.
|
|
43
|
+
*/
|
|
44
|
+
[Symbol.toPrimitive]() {
|
|
45
|
+
return this.value;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { OID } from '../../types.js';
|
|
2
|
+
import { REQUIRES_TYPE_OID } from './type-oid.js';
|
|
3
|
+
/** Which ends of a range include their bound, written as PostgreSQL writes it. */
|
|
4
|
+
export type RangeBounds = '[)' | '[]' | '()' | '(]';
|
|
5
|
+
/**
|
|
6
|
+
* PostgreSQL's range types - `int4range`, `tstzrange` and the rest - as
|
|
7
|
+
* a pair of bounds, each of which may be absent (the range runs to
|
|
8
|
+
* infinity that way) and each of which may or may not be included.
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* const r = new Range(1, 10); // the default bounds, [1,10)
|
|
12
|
+
* r.lowerInclusive; // true
|
|
13
|
+
* r.upperInclusive; // false
|
|
14
|
+
* String(r); // '[1,10)'
|
|
15
|
+
*
|
|
16
|
+
* new Range(null, 10); // '(,10)' - unbounded below
|
|
17
|
+
* new Range(1, 10, '[]'); // '[1,10]'
|
|
18
|
+
* Range.empty(); // 'empty' - no values at all
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* `empty` is a value of its own rather than a range whose bounds happen
|
|
22
|
+
* to meet, and `(,)` - unbounded at both ends - is its opposite, not the
|
|
23
|
+
* same thing. Only `isEmpty` tells them apart.
|
|
24
|
+
*
|
|
25
|
+
* The bounds are the element type's own JavaScript values, so a
|
|
26
|
+
* `tstzrange` has `Date`s in it and an `int4range` has numbers. An absent
|
|
27
|
+
* bound is `null`, which is also how PostgreSQL's own constructors take
|
|
28
|
+
* it: `int4range(null, 10)`.
|
|
29
|
+
*
|
|
30
|
+
* Note what the server does to a discrete type: `int4range(1,10,'[]')` is
|
|
31
|
+
* stored and returned as `[1,11)`, because for integers those are the
|
|
32
|
+
* same set. So `upperInclusive` comes back false for every `int4range`,
|
|
33
|
+
* `int8range` and `daterange` - that is PostgreSQL normalizing, not this
|
|
34
|
+
* class losing something.
|
|
35
|
+
*
|
|
36
|
+
* `toString()` prints what the server prints for a range over numbers.
|
|
37
|
+
* Over dates it cannot: a `Range` holds `Date`s and has no way to know
|
|
38
|
+
* whether it came from a `daterange`, a `tsrange` or a `tstzrange`, which
|
|
39
|
+
* the server renders three different ways. It prints the instant in ISO
|
|
40
|
+
* 8601 instead - the truth about the `Date` it holds, and something
|
|
41
|
+
* PostgreSQL accepts back, but not byte-for-byte what a `daterange`
|
|
42
|
+
* prints. The exact wire form is what goes out when the Range is passed
|
|
43
|
+
* as a parameter, where the element type's own renderer is in reach.
|
|
44
|
+
*/
|
|
45
|
+
export declare class Range<T = any> {
|
|
46
|
+
/**
|
|
47
|
+
* All six range types answer `instanceof Range`, so one has to be named
|
|
48
|
+
* rather than guessed - see REQUIRES_TYPE_OID.
|
|
49
|
+
*/
|
|
50
|
+
static readonly [REQUIRES_TYPE_OID] = true;
|
|
51
|
+
/** The lower bound, or null when the range runs to negative infinity. */
|
|
52
|
+
lower: T | null;
|
|
53
|
+
/** The upper bound, or null when the range runs to infinity. */
|
|
54
|
+
upper: T | null;
|
|
55
|
+
lowerInclusive: boolean;
|
|
56
|
+
upperInclusive: boolean;
|
|
57
|
+
/** True for `empty`, which contains no values - not the same as `(,)`. */
|
|
58
|
+
isEmpty: boolean;
|
|
59
|
+
/**
|
|
60
|
+
* @param oid Which range type this is - `DataTypeOIDs.int4range` and so
|
|
61
|
+
* on. A Range that came from a query already carries the one it was
|
|
62
|
+
* decoded from; give it here when building one to send, or name it at
|
|
63
|
+
* the call site with `new BindParam(oid, range)`.
|
|
64
|
+
*/
|
|
65
|
+
constructor(lower?: T | null, upper?: T | null, bounds?: RangeBounds, oid?: OID);
|
|
66
|
+
/** The empty range, which contains no values. */
|
|
67
|
+
static empty<T = any>(oid?: OID): Range<T>;
|
|
68
|
+
/**
|
|
69
|
+
* The range as PostgreSQL prints it, so it casts straight back through
|
|
70
|
+
* `::int4range` and reads the way the same value does in psql.
|
|
71
|
+
*/
|
|
72
|
+
toString(): string;
|
|
73
|
+
toJSON(): string;
|
|
74
|
+
}
|
|
@@ -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
|
+
}
|