postgrejs 3.6.1 → 3.8.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 +66 -6
- package/README.md +9 -83
- package/connection/connection.js +13 -1
- package/connection/intl-connection.d.ts +5 -3
- package/connection/intl-connection.js +70 -20
- package/connection/pool.js +6 -0
- package/connection/prepared-statement.d.ts +11 -0
- package/connection/prepared-statement.js +19 -5
- 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 +41 -0
- package/data-types/classes/numeric.js +51 -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/int2-vector-type.js +4 -1
- 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/oid-vector-type.js +4 -1
- 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/timestamptz-type.js +9 -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/frontend.js +17 -0
- package/protocol/pg-socket.js +16 -2
- package/util/decode-binaryarray.js +8 -1
- package/util/encode-binaryarray.d.ts +12 -1
- package/util/encode-binaryarray.js +13 -2
- package/util/format-datetime.d.ts +18 -0
- package/util/format-datetime.js +33 -0
- 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/sql-tag.js +15 -0
- package/util/stringify-for-sql.js +44 -1
- package/util/unspecified-param.d.ts +32 -0
- package/util/unspecified-param.js +40 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/** A `point`: one position. */
|
|
2
|
+
export declare class Point {
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
constructor(x?: number, y?: number);
|
|
6
|
+
/** As PostgreSQL prints it, so it casts back through `::point`. */
|
|
7
|
+
toString(): string;
|
|
8
|
+
toJSON(): string;
|
|
9
|
+
static isPointLike(v: any): boolean;
|
|
10
|
+
}
|
|
11
|
+
/** A `circle`: a centre and a radius. */
|
|
12
|
+
export declare class Circle {
|
|
13
|
+
x: number;
|
|
14
|
+
y: number;
|
|
15
|
+
r: number;
|
|
16
|
+
constructor(x?: number, y?: number, r?: number);
|
|
17
|
+
/** As PostgreSQL prints it, so it casts back through `::circle`. */
|
|
18
|
+
toString(): string;
|
|
19
|
+
toJSON(): string;
|
|
20
|
+
static isCircleLike(v: any): boolean;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Two corners, which is how both `box` and `lseg` are carried - and why
|
|
24
|
+
* they are two classes rather than one shared shape. They were
|
|
25
|
+
* indistinguishable as plain objects: both types' isType() accepted
|
|
26
|
+
* `{x1, y1, x2, y2}`, so `determine()` answered `box` for every one of
|
|
27
|
+
* them and an lseg parameter could not be expressed at all.
|
|
28
|
+
*/
|
|
29
|
+
declare abstract class TwoPoints {
|
|
30
|
+
x1: number;
|
|
31
|
+
y1: number;
|
|
32
|
+
x2: number;
|
|
33
|
+
y2: number;
|
|
34
|
+
constructor(x1?: number, y1?: number, x2?: number, y2?: number);
|
|
35
|
+
toJSON(): string;
|
|
36
|
+
static isTwoPointsLike(v: any): boolean;
|
|
37
|
+
}
|
|
38
|
+
/** A `box`: a rectangle given by two opposite corners. */
|
|
39
|
+
export declare class Box extends TwoPoints {
|
|
40
|
+
/** As PostgreSQL prints it, so it casts back through `::box`. */
|
|
41
|
+
toString(): string;
|
|
42
|
+
}
|
|
43
|
+
/** An `lseg`: the straight line between two points. */
|
|
44
|
+
export declare class LineSegment extends TwoPoints {
|
|
45
|
+
/** As PostgreSQL prints it, so it casts back through `::lseg`. */
|
|
46
|
+
toString(): string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* A `line`: the infinite line Ax + By + C = 0.
|
|
50
|
+
*
|
|
51
|
+
* PostgreSQL stores and prints the three coefficients whatever form the
|
|
52
|
+
* literal took - `line '((0,0),(1,1))'` comes back as `{1,-1,0}` - so
|
|
53
|
+
* that is what this carries. A pair of points would be a second, lossy
|
|
54
|
+
* spelling of the same thing, since a line through them is not the
|
|
55
|
+
* points.
|
|
56
|
+
*/
|
|
57
|
+
export declare class Line {
|
|
58
|
+
a: number;
|
|
59
|
+
b: number;
|
|
60
|
+
c: number;
|
|
61
|
+
constructor(a?: number, b?: number, c?: number);
|
|
62
|
+
/** As PostgreSQL prints it, so it casts back through `::line`. */
|
|
63
|
+
toString(): string;
|
|
64
|
+
toJSON(): string;
|
|
65
|
+
static isLineLike(v: any): boolean;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* The list of points `path` and `polygon` share. They are two classes
|
|
69
|
+
* rather than one for the same reason `Box` and `LineSegment` are: the
|
|
70
|
+
* values are indistinguishable by shape, so only the class can say which
|
|
71
|
+
* type a parameter is meant for.
|
|
72
|
+
*/
|
|
73
|
+
declare abstract class PointList {
|
|
74
|
+
points: Point[];
|
|
75
|
+
/** Plain `{x, y}` objects are accepted and kept as Points. */
|
|
76
|
+
constructor(points?: (Point | {
|
|
77
|
+
x: number;
|
|
78
|
+
y: number;
|
|
79
|
+
})[]);
|
|
80
|
+
toJSON(): string;
|
|
81
|
+
protected join(): string;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* A `path`: a series of points, open or closed.
|
|
85
|
+
*
|
|
86
|
+
* Closed is the default because it is PostgreSQL's - a literal written
|
|
87
|
+
* without brackets, `path '1,2,3,4'`, comes back closed. The flag is
|
|
88
|
+
* part of the value and not a formatting choice: the server stores it
|
|
89
|
+
* and prints `[...]` for an open path against `(...)` for a closed one.
|
|
90
|
+
*/
|
|
91
|
+
export declare class Path extends PointList {
|
|
92
|
+
isClosed: boolean;
|
|
93
|
+
constructor(points?: (Point | {
|
|
94
|
+
x: number;
|
|
95
|
+
y: number;
|
|
96
|
+
})[], isClosed?: boolean);
|
|
97
|
+
/** As PostgreSQL prints it, so it casts back through `::path`. */
|
|
98
|
+
toString(): string;
|
|
99
|
+
}
|
|
100
|
+
/** A `polygon`: a closed series of points. */
|
|
101
|
+
export declare class Polygon extends PointList {
|
|
102
|
+
/** As PostgreSQL prints it, so it casts back through `::polygon`. */
|
|
103
|
+
toString(): string;
|
|
104
|
+
}
|
|
105
|
+
export { PointList, TwoPoints };
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a value is a bare object literal rather than an instance of
|
|
3
|
+
* some class.
|
|
4
|
+
*
|
|
5
|
+
* The geometric types accept both - a Point, and the `{x, y}` that was
|
|
6
|
+
* the only thing they returned before these classes existed - but a
|
|
7
|
+
* BoxType must not claim a LineSegment just because it carries the same
|
|
8
|
+
* four numbers, so the shape check is limited to plain objects.
|
|
9
|
+
*/
|
|
10
|
+
function isPlainObject(v) {
|
|
11
|
+
return (v !== null &&
|
|
12
|
+
typeof v === 'object' &&
|
|
13
|
+
Object.getPrototypeOf(v) === Object.prototype);
|
|
14
|
+
}
|
|
15
|
+
function isNum(v) {
|
|
16
|
+
return typeof v === 'number';
|
|
17
|
+
}
|
|
18
|
+
/** A `point`: one position. */
|
|
19
|
+
export class Point {
|
|
20
|
+
x;
|
|
21
|
+
y;
|
|
22
|
+
constructor(x = 0, y = 0) {
|
|
23
|
+
this.x = x;
|
|
24
|
+
this.y = y;
|
|
25
|
+
}
|
|
26
|
+
/** As PostgreSQL prints it, so it casts back through `::point`. */
|
|
27
|
+
toString() {
|
|
28
|
+
return `(${this.x},${this.y})`;
|
|
29
|
+
}
|
|
30
|
+
toJSON() {
|
|
31
|
+
return this.toString();
|
|
32
|
+
}
|
|
33
|
+
/* c8 ignore next 3 */
|
|
34
|
+
static isPointLike(v) {
|
|
35
|
+
return (isPlainObject(v) &&
|
|
36
|
+
Object.keys(v).length === 2 &&
|
|
37
|
+
isNum(v.x) &&
|
|
38
|
+
isNum(v.y));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** A `circle`: a centre and a radius. */
|
|
42
|
+
export class Circle {
|
|
43
|
+
x;
|
|
44
|
+
y;
|
|
45
|
+
r;
|
|
46
|
+
constructor(x = 0, y = 0, r = 0) {
|
|
47
|
+
this.x = x;
|
|
48
|
+
this.y = y;
|
|
49
|
+
this.r = r;
|
|
50
|
+
}
|
|
51
|
+
/** As PostgreSQL prints it, so it casts back through `::circle`. */
|
|
52
|
+
toString() {
|
|
53
|
+
return `<(${this.x},${this.y}),${this.r}>`;
|
|
54
|
+
}
|
|
55
|
+
toJSON() {
|
|
56
|
+
return this.toString();
|
|
57
|
+
}
|
|
58
|
+
static isCircleLike(v) {
|
|
59
|
+
return (isPlainObject(v) &&
|
|
60
|
+
Object.keys(v).length === 3 &&
|
|
61
|
+
isNum(v.x) &&
|
|
62
|
+
isNum(v.y) &&
|
|
63
|
+
isNum(v.r));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Two corners, which is how both `box` and `lseg` are carried - and why
|
|
68
|
+
* they are two classes rather than one shared shape. They were
|
|
69
|
+
* indistinguishable as plain objects: both types' isType() accepted
|
|
70
|
+
* `{x1, y1, x2, y2}`, so `determine()` answered `box` for every one of
|
|
71
|
+
* them and an lseg parameter could not be expressed at all.
|
|
72
|
+
*/
|
|
73
|
+
class TwoPoints {
|
|
74
|
+
x1;
|
|
75
|
+
y1;
|
|
76
|
+
x2;
|
|
77
|
+
y2;
|
|
78
|
+
constructor(x1 = 0, y1 = 0, x2 = 0, y2 = 0) {
|
|
79
|
+
this.x1 = x1;
|
|
80
|
+
this.y1 = y1;
|
|
81
|
+
this.x2 = x2;
|
|
82
|
+
this.y2 = y2;
|
|
83
|
+
}
|
|
84
|
+
toJSON() {
|
|
85
|
+
return this.toString();
|
|
86
|
+
}
|
|
87
|
+
static isTwoPointsLike(v) {
|
|
88
|
+
return (isPlainObject(v) &&
|
|
89
|
+
Object.keys(v).length === 4 &&
|
|
90
|
+
isNum(v.x1) &&
|
|
91
|
+
isNum(v.y1) &&
|
|
92
|
+
isNum(v.x2) &&
|
|
93
|
+
isNum(v.y2));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/** A `box`: a rectangle given by two opposite corners. */
|
|
97
|
+
export class Box extends TwoPoints {
|
|
98
|
+
/** As PostgreSQL prints it, so it casts back through `::box`. */
|
|
99
|
+
toString() {
|
|
100
|
+
return `(${this.x1},${this.y1}),(${this.x2},${this.y2})`;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/** An `lseg`: the straight line between two points. */
|
|
104
|
+
export class LineSegment extends TwoPoints {
|
|
105
|
+
/** As PostgreSQL prints it, so it casts back through `::lseg`. */
|
|
106
|
+
toString() {
|
|
107
|
+
return `[(${this.x1},${this.y1}),(${this.x2},${this.y2})]`;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* A `line`: the infinite line Ax + By + C = 0.
|
|
112
|
+
*
|
|
113
|
+
* PostgreSQL stores and prints the three coefficients whatever form the
|
|
114
|
+
* literal took - `line '((0,0),(1,1))'` comes back as `{1,-1,0}` - so
|
|
115
|
+
* that is what this carries. A pair of points would be a second, lossy
|
|
116
|
+
* spelling of the same thing, since a line through them is not the
|
|
117
|
+
* points.
|
|
118
|
+
*/
|
|
119
|
+
export class Line {
|
|
120
|
+
a;
|
|
121
|
+
b;
|
|
122
|
+
c;
|
|
123
|
+
constructor(a = 0, b = 0, c = 0) {
|
|
124
|
+
this.a = a;
|
|
125
|
+
this.b = b;
|
|
126
|
+
this.c = c;
|
|
127
|
+
}
|
|
128
|
+
/** As PostgreSQL prints it, so it casts back through `::line`. */
|
|
129
|
+
toString() {
|
|
130
|
+
return `{${this.a},${this.b},${this.c}}`;
|
|
131
|
+
}
|
|
132
|
+
toJSON() {
|
|
133
|
+
return this.toString();
|
|
134
|
+
}
|
|
135
|
+
static isLineLike(v) {
|
|
136
|
+
return (isPlainObject(v) &&
|
|
137
|
+
Object.keys(v).length === 3 &&
|
|
138
|
+
isNum(v.a) &&
|
|
139
|
+
isNum(v.b) &&
|
|
140
|
+
isNum(v.c));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* The list of points `path` and `polygon` share. They are two classes
|
|
145
|
+
* rather than one for the same reason `Box` and `LineSegment` are: the
|
|
146
|
+
* values are indistinguishable by shape, so only the class can say which
|
|
147
|
+
* type a parameter is meant for.
|
|
148
|
+
*/
|
|
149
|
+
class PointList {
|
|
150
|
+
points;
|
|
151
|
+
/** Plain `{x, y}` objects are accepted and kept as Points. */
|
|
152
|
+
constructor(points = []) {
|
|
153
|
+
this.points = points.map(p => p instanceof Point ? p : new Point(p.x, p.y));
|
|
154
|
+
}
|
|
155
|
+
toJSON() {
|
|
156
|
+
return this.toString();
|
|
157
|
+
}
|
|
158
|
+
join() {
|
|
159
|
+
let out = '';
|
|
160
|
+
const l = this.points.length;
|
|
161
|
+
let i;
|
|
162
|
+
for (i = 0; i < l; i++) {
|
|
163
|
+
if (i)
|
|
164
|
+
out += ',';
|
|
165
|
+
out += this.points[i].toString();
|
|
166
|
+
}
|
|
167
|
+
return out;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* A `path`: a series of points, open or closed.
|
|
172
|
+
*
|
|
173
|
+
* Closed is the default because it is PostgreSQL's - a literal written
|
|
174
|
+
* without brackets, `path '1,2,3,4'`, comes back closed. The flag is
|
|
175
|
+
* part of the value and not a formatting choice: the server stores it
|
|
176
|
+
* and prints `[...]` for an open path against `(...)` for a closed one.
|
|
177
|
+
*/
|
|
178
|
+
export class Path extends PointList {
|
|
179
|
+
isClosed;
|
|
180
|
+
constructor(points = [], isClosed = true) {
|
|
181
|
+
super(points);
|
|
182
|
+
this.isClosed = isClosed;
|
|
183
|
+
}
|
|
184
|
+
/** As PostgreSQL prints it, so it casts back through `::path`. */
|
|
185
|
+
toString() {
|
|
186
|
+
const inner = this.join();
|
|
187
|
+
return this.isClosed ? `(${inner})` : `[${inner}]`;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/** A `polygon`: a closed series of points. */
|
|
191
|
+
export class Polygon extends PointList {
|
|
192
|
+
/** As PostgreSQL prints it, so it casts back through `::polygon`. */
|
|
193
|
+
toString() {
|
|
194
|
+
return `(${this.join()})`;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
export { PointList, TwoPoints };
|
|
@@ -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,41 @@
|
|
|
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 the value keeps
|
|
13
|
+
* saying what it is. A bare string parameter does reach a numeric
|
|
14
|
+
* column - it goes out with no declared type and the server resolves it
|
|
15
|
+
* there (see `util/unspecified-param.ts`) - but only where there is a
|
|
16
|
+
* column to resolve it from: `determine()` answers `varchar` for it, an
|
|
17
|
+
* inlined literal is written with no cast, and a decoded one is
|
|
18
|
+
* indistinguishable from text. A `Numeric` carries the type with the
|
|
19
|
+
* digits.
|
|
20
|
+
*
|
|
21
|
+
* There is deliberately no `valueOf()`. Arithmetic on one of these has
|
|
22
|
+
* to be an explicit decision - `Symbol.toPrimitive` hands back the exact
|
|
23
|
+
* text for every hint, so `String(n)` and `${n}` are lossless, and
|
|
24
|
+
* `toNumber()` is the way to ask for the double and say so.
|
|
25
|
+
*/
|
|
26
|
+
export declare class Numeric {
|
|
27
|
+
readonly value: string;
|
|
28
|
+
constructor(value: string | number | bigint);
|
|
29
|
+
/** The decimal exactly as PostgreSQL wrote it; casts back through `::numeric`. */
|
|
30
|
+
toString(): string;
|
|
31
|
+
toJSON(): string;
|
|
32
|
+
/** The value as a double, which is lossy - that is the whole point of this class. */
|
|
33
|
+
toNumber(): number;
|
|
34
|
+
/**
|
|
35
|
+
* The text for every hint, the numeric one included: coercing to a
|
|
36
|
+
* double behind the caller's back is the loss this class exists to
|
|
37
|
+
* prevent, so `n * 2` has to go through the string and `n + 1`
|
|
38
|
+
* concatenates rather than quietly rounding.
|
|
39
|
+
*/
|
|
40
|
+
[Symbol.toPrimitive](): string;
|
|
41
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
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 the value keeps
|
|
13
|
+
* saying what it is. A bare string parameter does reach a numeric
|
|
14
|
+
* column - it goes out with no declared type and the server resolves it
|
|
15
|
+
* there (see `util/unspecified-param.ts`) - but only where there is a
|
|
16
|
+
* column to resolve it from: `determine()` answers `varchar` for it, an
|
|
17
|
+
* inlined literal is written with no cast, and a decoded one is
|
|
18
|
+
* indistinguishable from text. A `Numeric` carries the type with the
|
|
19
|
+
* digits.
|
|
20
|
+
*
|
|
21
|
+
* There is deliberately no `valueOf()`. Arithmetic on one of these has
|
|
22
|
+
* to be an explicit decision - `Symbol.toPrimitive` hands back the exact
|
|
23
|
+
* text for every hint, so `String(n)` and `${n}` are lossless, and
|
|
24
|
+
* `toNumber()` is the way to ask for the double and say so.
|
|
25
|
+
*/
|
|
26
|
+
export class Numeric {
|
|
27
|
+
value;
|
|
28
|
+
constructor(value) {
|
|
29
|
+
this.value = typeof value === 'string' ? value : String(value);
|
|
30
|
+
}
|
|
31
|
+
/** The decimal exactly as PostgreSQL wrote it; casts back through `::numeric`. */
|
|
32
|
+
toString() {
|
|
33
|
+
return this.value;
|
|
34
|
+
}
|
|
35
|
+
toJSON() {
|
|
36
|
+
return this.value;
|
|
37
|
+
}
|
|
38
|
+
/** The value as a double, which is lossy - that is the whole point of this class. */
|
|
39
|
+
toNumber() {
|
|
40
|
+
return parseFloat(this.value);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The text for every hint, the numeric one included: coercing to a
|
|
44
|
+
* double behind the caller's back is the loss this class exists to
|
|
45
|
+
* prevent, so `n * 2` has to go through the string and `n + 1`
|
|
46
|
+
* concatenates rather than quietly rounding.
|
|
47
|
+
*/
|
|
48
|
+
[Symbol.toPrimitive]() {
|
|
49
|
+
return this.value;
|
|
50
|
+
}
|
|
51
|
+
}
|