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,184 @@
|
|
|
1
|
+
import { DataTypeOIDs } from '../constants.js';
|
|
2
|
+
import { readBigInt64BE } from '../util/bigint-methods.js';
|
|
3
|
+
/**
|
|
4
|
+
* `timetz` decodes to a string, alone among the date/time types, and the
|
|
5
|
+
* reason is the one thing that distinguishes it from `time`: the offset.
|
|
6
|
+
* A JavaScript Date has no field for one. Handing back a Date would mean
|
|
7
|
+
* either dropping the offset - throwing away the only part that makes
|
|
8
|
+
* this type different - or folding it into the instant, so that
|
|
9
|
+
* `12:00:00+03` and `09:00:00+00` became indistinguishable and writing
|
|
10
|
+
* the value back changed it. The string keeps both parts, is exactly what
|
|
11
|
+
* the server printed, and casts straight back. `pg` also leaves it a
|
|
12
|
+
* string.
|
|
13
|
+
*
|
|
14
|
+
* PostgreSQL's own documentation calls this type's definition of
|
|
15
|
+
* questionable usefulness - a time with no date cannot resolve a real
|
|
16
|
+
* zone, only a fixed offset - which is a further reason not to invent a
|
|
17
|
+
* richer representation for it here.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Hours may reach 24 (`24:00:00` is a legal `timetz`), the fraction is
|
|
21
|
+
* microseconds, and the offset may carry seconds - `12:34:56+03:00:30`
|
|
22
|
+
* is a value the server stores and prints.
|
|
23
|
+
*/
|
|
24
|
+
const TIMETZ_PATTERN = /^\s*(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?(?:\.(\d{1,6}))?\s*(?:(Z|z)|([+-])(\d{1,2})(?::?(\d{2}))?(?::?(\d{2}))?)?\s*$/;
|
|
25
|
+
const MICROS_PER_DAY = 86400000000;
|
|
26
|
+
/** What the server accepts: ±15:59:59, and ±16 is out of range. */
|
|
27
|
+
const MAX_ZONE_SECONDS = 15 * 3600 + 59 * 60 + 59;
|
|
28
|
+
const pad = (n) => (n < 10 ? '0' + n : '' + n);
|
|
29
|
+
function parseTimeTzText(v) {
|
|
30
|
+
const m = TIMETZ_PATTERN.exec(v);
|
|
31
|
+
if (!m)
|
|
32
|
+
throw new Error(`"${v}" is not a valid timetz value`);
|
|
33
|
+
if (!m[5] && !m[6]) {
|
|
34
|
+
// The server would resolve this against the session's time zone,
|
|
35
|
+
// which is not knowable here - and guessing the Node process's zone
|
|
36
|
+
// instead would silently shift the value. The text path does not go
|
|
37
|
+
// through this function and still accepts the bare form, since there
|
|
38
|
+
// the server does the resolving itself.
|
|
39
|
+
throw new Error(`"${v}" has no time zone offset - give it one ("12:34:56+03"), or ` +
|
|
40
|
+
'pass a Date, whose own offset is used');
|
|
41
|
+
}
|
|
42
|
+
const hours = +m[1];
|
|
43
|
+
const minutes = +m[2];
|
|
44
|
+
const seconds = m[3] ? +m[3] : 0;
|
|
45
|
+
const micros = ((hours * 60 + minutes) * 60 + seconds) * 1000000 +
|
|
46
|
+
(m[4] ? +m[4].padEnd(6, '0') : 0);
|
|
47
|
+
if (minutes > 59 || seconds > 59 || micros > MICROS_PER_DAY)
|
|
48
|
+
throw new Error(`"${v}" is out of range for a timetz value`);
|
|
49
|
+
let zone = 0;
|
|
50
|
+
if (m[6]) {
|
|
51
|
+
zone = +m[7] * 3600 + (m[8] ? +m[8] : 0) * 60 + (m[9] ? +m[9] : 0);
|
|
52
|
+
if (zone > MAX_ZONE_SECONDS)
|
|
53
|
+
throw new Error(`"${v}" has a time zone displacement out of range`);
|
|
54
|
+
// Stored west-positive, the opposite sign from the way it is written.
|
|
55
|
+
if (m[6] === '+')
|
|
56
|
+
zone = -zone;
|
|
57
|
+
}
|
|
58
|
+
return { micros, zone };
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* A Date carries no offset of its own, but its local components and the
|
|
62
|
+
* zone they are read in go together: `getHours()` means "in the local
|
|
63
|
+
* zone", so that zone's offset is the value's, not a guess. Under
|
|
64
|
+
* `utcDates` the encoders read UTC components instead, and then the
|
|
65
|
+
* offset that matches is zero - the same rule the rest of the date/time
|
|
66
|
+
* types follow.
|
|
67
|
+
*/
|
|
68
|
+
function fromDate(v, options) {
|
|
69
|
+
const utc = !!options.utcDates;
|
|
70
|
+
const hours = utc ? v.getUTCHours() : v.getHours();
|
|
71
|
+
const minutes = utc ? v.getUTCMinutes() : v.getMinutes();
|
|
72
|
+
const seconds = utc ? v.getUTCSeconds() : v.getSeconds();
|
|
73
|
+
const ms = utc ? v.getUTCMilliseconds() : v.getMilliseconds();
|
|
74
|
+
return {
|
|
75
|
+
micros: (((hours * 60 + minutes) * 60 + seconds) * 1000 + ms) * 1000,
|
|
76
|
+
// getTimezoneOffset() is already minutes west of UTC, the same
|
|
77
|
+
// direction the wire uses.
|
|
78
|
+
zone: utc ? 0 : v.getTimezoneOffset() * 60,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function toParts(v, options) {
|
|
82
|
+
if (typeof v === 'string')
|
|
83
|
+
return parseTimeTzText(v);
|
|
84
|
+
if (v instanceof Date)
|
|
85
|
+
return fromDate(v, options);
|
|
86
|
+
throw new TypeError(`"${typeof v}" cannot be encoded as a timetz - pass the string form ` +
|
|
87
|
+
'with its offset, or a Date');
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Prints what the server prints: the clock time, then microseconds with
|
|
91
|
+
* trailing zeroes dropped and the whole fraction omitted when it is zero,
|
|
92
|
+
* then the offset - always with hours, with minutes when they are not
|
|
93
|
+
* zero and seconds only when they are not either.
|
|
94
|
+
*/
|
|
95
|
+
function formatTimeTz(micros, zone) {
|
|
96
|
+
const us = micros % 1000000;
|
|
97
|
+
let rest = (micros - us) / 1000000;
|
|
98
|
+
const seconds = rest % 60;
|
|
99
|
+
rest = (rest - seconds) / 60;
|
|
100
|
+
const minutes = rest % 60;
|
|
101
|
+
const hours = (rest - minutes) / 60;
|
|
102
|
+
let out = pad(hours) + ':' + pad(minutes) + ':' + pad(seconds);
|
|
103
|
+
if (us)
|
|
104
|
+
out += '.' + String(us).padStart(6, '0').replace(/0+$/, '');
|
|
105
|
+
// Back to the way it is written, east-positive.
|
|
106
|
+
let z = -zone;
|
|
107
|
+
out += z < 0 ? '-' : '+';
|
|
108
|
+
if (z < 0)
|
|
109
|
+
z = -z;
|
|
110
|
+
const zs = z % 60;
|
|
111
|
+
z = (z - zs) / 60;
|
|
112
|
+
const zm = z % 60;
|
|
113
|
+
out += pad((z - zm) / 60);
|
|
114
|
+
if (zm || zs)
|
|
115
|
+
out += ':' + pad(zm);
|
|
116
|
+
if (zs)
|
|
117
|
+
out += ':' + pad(zs);
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
export const TimeTzType = {
|
|
121
|
+
name: 'timetz',
|
|
122
|
+
oid: DataTypeOIDs.timetz,
|
|
123
|
+
jsType: 'string',
|
|
124
|
+
// `time` already claims any string that looks like a clock time,
|
|
125
|
+
// offset and all, so inference here would only take those strings away
|
|
126
|
+
// from it - and a plain "12:34:56" is far more often a `time`. Name
|
|
127
|
+
// this type to reach it: `new BindParam(DataTypeOIDs.timetz, v)`.
|
|
128
|
+
inferrable: false,
|
|
129
|
+
/**
|
|
130
|
+
* Twelve bytes: microseconds since midnight as an int64, then the zone
|
|
131
|
+
* as an int32 of seconds west of UTC.
|
|
132
|
+
*/
|
|
133
|
+
decodeBinary(v, offset = 0) {
|
|
134
|
+
const micros = typeof v.readBigInt64BE === 'function'
|
|
135
|
+
? v.readBigInt64BE(offset)
|
|
136
|
+
: readBigInt64BE(v, offset);
|
|
137
|
+
// A day of microseconds is far inside the safe integer range, so
|
|
138
|
+
// this is only about the wire type, not the value.
|
|
139
|
+
return formatTimeTz(Number(micros), v.readInt32BE(offset + 8));
|
|
140
|
+
},
|
|
141
|
+
encodeBinary(buf, v, options) {
|
|
142
|
+
const p = toParts(v, options);
|
|
143
|
+
buf.writeBigInt64BE(BigInt(p.micros));
|
|
144
|
+
buf.writeInt32BE(p.zone);
|
|
145
|
+
},
|
|
146
|
+
decodeText(v) {
|
|
147
|
+
return v;
|
|
148
|
+
},
|
|
149
|
+
encodeText(v, options) {
|
|
150
|
+
// A string goes over as written - the server accepts spellings this
|
|
151
|
+
// file does not have to know, including a bare time, which it
|
|
152
|
+
// resolves against the session's own zone.
|
|
153
|
+
if (typeof v === 'string')
|
|
154
|
+
return v;
|
|
155
|
+
const p = toParts(v, options);
|
|
156
|
+
return formatTimeTz(p.micros, p.zone);
|
|
157
|
+
},
|
|
158
|
+
// Digits, colons and a sign - see inet-type.ts for why 'latin1'.
|
|
159
|
+
decodeTextBuffer(buf, offset, len) {
|
|
160
|
+
return buf.toString('latin1', offset, offset + len);
|
|
161
|
+
},
|
|
162
|
+
isType(v) {
|
|
163
|
+
// The type's own JavaScript shape is the string the server prints,
|
|
164
|
+
// which always carries an offset. A Date is accepted by the encoder
|
|
165
|
+
// as a convenience, but it is `time`'s shape, not this one's - the
|
|
166
|
+
// same line IntervalType draws between what it encodes and what it
|
|
167
|
+
// claims.
|
|
168
|
+
if (typeof v !== 'string')
|
|
169
|
+
return false;
|
|
170
|
+
try {
|
|
171
|
+
parseTimeTzText(v);
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
export const ArrayTimeTzType = {
|
|
180
|
+
...TimeTzType,
|
|
181
|
+
name: '_timetz',
|
|
182
|
+
oid: DataTypeOIDs._timetz,
|
|
183
|
+
elementsOID: DataTypeOIDs.timetz,
|
|
184
|
+
};
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { DataTypeOIDs } from '../constants.js';
|
|
2
|
+
/**
|
|
3
|
+
* `tsvector` and `tsquery` decode to the strings PostgreSQL prints -
|
|
4
|
+
* `'cat':1,3 'dog':2` and `'a' & !'b'` - and encode by handing a string
|
|
5
|
+
* straight back to the server. They share this file because they share
|
|
6
|
+
* that decision and half their quoting.
|
|
7
|
+
*
|
|
8
|
+
* **Why the text form.** It is the canonical spelling of both types: what
|
|
9
|
+
* the server accepts back, what the documentation is written in, and what
|
|
10
|
+
* someone inspecting an indexed document or a stored search wants to
|
|
11
|
+
* read. A structure of lexemes and positions, or a query tree, would be a
|
|
12
|
+
* second representation to convert to and from for values that are
|
|
13
|
+
* produced by `to_tsvector`/`to_tsquery` and consumed by a match
|
|
14
|
+
* operator, rather than walked. `pg` leaves both as strings.
|
|
15
|
+
*
|
|
16
|
+
* **Why no encodeBinary.** The server's own input parser is what defines
|
|
17
|
+
* these values. A `tsvector` literal is sorted and deduplicated on the
|
|
18
|
+
* way in, by a byte ordering and a position-merging rule that belong to
|
|
19
|
+
* the server; a `tsquery` literal is a small expression language, with
|
|
20
|
+
* operators that have been added across releases. Re-implementing either
|
|
21
|
+
* here would mean `'b a'::tsvector` or `'a<->b'::tsquery` quietly meaning
|
|
22
|
+
* one thing as a literal and another as a parameter, depending only on
|
|
23
|
+
* which column format the value travelled in. Handing the string over
|
|
24
|
+
* instead makes them the same by construction - the cost is that these
|
|
25
|
+
* two types cannot appear in a binary `COPY`, which `copyFrom` reports
|
|
26
|
+
* by name before it writes a row.
|
|
27
|
+
*
|
|
28
|
+
* Decoding is a different matter and is done here: the tree has already
|
|
29
|
+
* been built by the server, so reading it back is not parsing a grammar.
|
|
30
|
+
*/
|
|
31
|
+
/** The four weights, in the order the top two bits of a position hold. */
|
|
32
|
+
const VECTOR_WEIGHTS = ['', 'C', 'B', 'A'];
|
|
33
|
+
/** The weights a query value carries, as a bit each. */
|
|
34
|
+
const QUERY_WEIGHTS = [
|
|
35
|
+
[8, 'A'],
|
|
36
|
+
[4, 'B'],
|
|
37
|
+
[2, 'C'],
|
|
38
|
+
[1, 'D'],
|
|
39
|
+
];
|
|
40
|
+
const POSITION_MASK = 0x3fff;
|
|
41
|
+
const QI_VAL = 1;
|
|
42
|
+
const OP_NOT = 1;
|
|
43
|
+
const OP_AND = 2;
|
|
44
|
+
const OP_OR = 3;
|
|
45
|
+
const OP_PHRASE = 4;
|
|
46
|
+
/**
|
|
47
|
+
* How tightly each operator binds, which is what decides where the
|
|
48
|
+
* server puts parentheses - and so where this has to put them too.
|
|
49
|
+
*/
|
|
50
|
+
const PRIORITY = {
|
|
51
|
+
[OP_NOT]: 4,
|
|
52
|
+
[OP_PHRASE]: 3,
|
|
53
|
+
[OP_AND]: 2,
|
|
54
|
+
[OP_OR]: 1,
|
|
55
|
+
};
|
|
56
|
+
/** A lexeme is printed quoted, with `'` doubled and `\` escaped. */
|
|
57
|
+
function quote(lexeme) {
|
|
58
|
+
let out = "'";
|
|
59
|
+
const l = lexeme.length;
|
|
60
|
+
let i;
|
|
61
|
+
let c;
|
|
62
|
+
for (i = 0; i < l; i++) {
|
|
63
|
+
c = lexeme[i];
|
|
64
|
+
if (c === "'" || c === '\\')
|
|
65
|
+
out += c;
|
|
66
|
+
out += c;
|
|
67
|
+
}
|
|
68
|
+
return out + "'";
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* An int32 lexeme count, then for each: the lexeme as a NUL-terminated
|
|
72
|
+
* string, an int16 count of positions, and that many int16s holding the
|
|
73
|
+
* weight in the top two bits and the position in the low fourteen. A
|
|
74
|
+
* weight of D is the default and is not printed.
|
|
75
|
+
*/
|
|
76
|
+
function decodeVector(v, offset) {
|
|
77
|
+
const count = v.readInt32BE(offset);
|
|
78
|
+
let p = offset + 4;
|
|
79
|
+
let out = '';
|
|
80
|
+
let i;
|
|
81
|
+
let j;
|
|
82
|
+
let end;
|
|
83
|
+
let positions;
|
|
84
|
+
let entry;
|
|
85
|
+
for (i = 0; i < count; i++) {
|
|
86
|
+
end = v.indexOf(0, p);
|
|
87
|
+
if (i)
|
|
88
|
+
out += ' ';
|
|
89
|
+
out += quote(v.toString('utf8', p, end));
|
|
90
|
+
p = end + 1;
|
|
91
|
+
positions = v.readInt16BE(p);
|
|
92
|
+
p += 2;
|
|
93
|
+
for (j = 0; j < positions; j++) {
|
|
94
|
+
entry = v.readUInt16BE(p);
|
|
95
|
+
p += 2;
|
|
96
|
+
out +=
|
|
97
|
+
(j ? ',' : ':') + (entry & POSITION_MASK) + VECTOR_WEIGHTS[entry >> 14];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* An int32 node count, then the nodes themselves: an operator before its
|
|
104
|
+
* operands, and its right operand before its left. A value node carries
|
|
105
|
+
* a weight bitmask, a prefix flag and a NUL-terminated lexeme; a phrase
|
|
106
|
+
* operator carries an int16 distance.
|
|
107
|
+
*/
|
|
108
|
+
function readQueryNode(v, pos) {
|
|
109
|
+
const type = v[pos.p++];
|
|
110
|
+
if (type === QI_VAL) {
|
|
111
|
+
const weights = v[pos.p++];
|
|
112
|
+
const prefix = !!v[pos.p++];
|
|
113
|
+
const end = v.indexOf(0, pos.p);
|
|
114
|
+
const lexeme = v.toString('utf8', pos.p, end);
|
|
115
|
+
pos.p = end + 1;
|
|
116
|
+
return { lexeme, weights, prefix };
|
|
117
|
+
}
|
|
118
|
+
const oper = v[pos.p++];
|
|
119
|
+
if (oper === OP_NOT)
|
|
120
|
+
return { oper, right: readQueryNode(v, pos) };
|
|
121
|
+
let distance = 0;
|
|
122
|
+
if (oper === OP_PHRASE) {
|
|
123
|
+
distance = v.readInt16BE(pos.p);
|
|
124
|
+
pos.p += 2;
|
|
125
|
+
}
|
|
126
|
+
// Right before left, which is the order they were written in.
|
|
127
|
+
const right = readQueryNode(v, pos);
|
|
128
|
+
const left = readQueryNode(v, pos);
|
|
129
|
+
return { oper, distance, left, right };
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Prints the tree the way the server prints it, parentheses included.
|
|
133
|
+
* An operand is wrapped when it binds more loosely than what it sits
|
|
134
|
+
* under; the extra `rightPhraseOp` case is what keeps
|
|
135
|
+
* `'a' <-> ( 'b' <-> 'c' )` distinct from `'a' <-> 'b' <-> 'c'`, which
|
|
136
|
+
* are different queries.
|
|
137
|
+
*/
|
|
138
|
+
function printQuery(node, parentPriority, rightPhraseOp) {
|
|
139
|
+
if (node.oper === undefined) {
|
|
140
|
+
let out = quote(node.lexeme);
|
|
141
|
+
if (node.weights || node.prefix) {
|
|
142
|
+
// The star comes before the letters - `'d':*AB`, not `'d':AB*`.
|
|
143
|
+
out += ':';
|
|
144
|
+
if (node.prefix)
|
|
145
|
+
out += '*';
|
|
146
|
+
let w;
|
|
147
|
+
for (w of QUERY_WEIGHTS)
|
|
148
|
+
if (node.weights & w[0])
|
|
149
|
+
out += w[1];
|
|
150
|
+
}
|
|
151
|
+
return out;
|
|
152
|
+
}
|
|
153
|
+
const priority = PRIORITY[node.oper];
|
|
154
|
+
if (node.oper === OP_NOT)
|
|
155
|
+
return '!' + printQuery(node.right, priority, false);
|
|
156
|
+
const op = node.oper === OP_AND
|
|
157
|
+
? '&'
|
|
158
|
+
: node.oper === OP_OR
|
|
159
|
+
? '|'
|
|
160
|
+
: node.distance === 1
|
|
161
|
+
? '<->'
|
|
162
|
+
: '<' + node.distance + '>';
|
|
163
|
+
const out = printQuery(node.left, priority, false) +
|
|
164
|
+
' ' +
|
|
165
|
+
op +
|
|
166
|
+
' ' +
|
|
167
|
+
printQuery(node.right, priority, node.oper === OP_PHRASE);
|
|
168
|
+
return priority < parentPriority ||
|
|
169
|
+
(rightPhraseOp && priority === parentPriority)
|
|
170
|
+
? '( ' + out + ' )'
|
|
171
|
+
: out;
|
|
172
|
+
}
|
|
173
|
+
export const TsVectorType = {
|
|
174
|
+
name: 'tsvector',
|
|
175
|
+
oid: DataTypeOIDs.tsvector,
|
|
176
|
+
jsType: 'string',
|
|
177
|
+
// The text form is ordinary-looking text - see inet-type.ts for why
|
|
178
|
+
// none of these string-shaped types join inference.
|
|
179
|
+
inferrable: false,
|
|
180
|
+
encodeText(v) {
|
|
181
|
+
return '' + v;
|
|
182
|
+
},
|
|
183
|
+
decodeBinary(v, offset = 0) {
|
|
184
|
+
return decodeVector(v, offset);
|
|
185
|
+
},
|
|
186
|
+
decodeText(v) {
|
|
187
|
+
return v;
|
|
188
|
+
},
|
|
189
|
+
isType(v) {
|
|
190
|
+
return typeof v === 'string';
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
export const ArrayTsVectorType = {
|
|
194
|
+
...TsVectorType,
|
|
195
|
+
name: '_tsvector',
|
|
196
|
+
oid: DataTypeOIDs._tsvector,
|
|
197
|
+
elementsOID: DataTypeOIDs.tsvector,
|
|
198
|
+
};
|
|
199
|
+
export const TsQueryType = {
|
|
200
|
+
...TsVectorType,
|
|
201
|
+
name: 'tsquery',
|
|
202
|
+
oid: DataTypeOIDs.tsquery,
|
|
203
|
+
decodeBinary(v, offset = 0) {
|
|
204
|
+
const count = v.readInt32BE(offset);
|
|
205
|
+
// An empty query prints as nothing, and has no root to read.
|
|
206
|
+
if (!count)
|
|
207
|
+
return '';
|
|
208
|
+
return printQuery(readQueryNode(v, { p: offset + 4 }), 0, false);
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
export const ArrayTsQueryType = {
|
|
212
|
+
...TsQueryType,
|
|
213
|
+
name: '_tsquery',
|
|
214
|
+
oid: DataTypeOIDs._tsquery,
|
|
215
|
+
elementsOID: DataTypeOIDs.tsquery,
|
|
216
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { DataType } from '../interfaces/data-type.js';
|
|
2
|
+
export declare const XidType: DataType;
|
|
3
|
+
export declare const ArrayXidType: DataType;
|
|
4
|
+
export declare const CidType: DataType;
|
|
5
|
+
export declare const ArrayCidType: DataType;
|
|
6
|
+
export declare const Xid8Type: DataType;
|
|
7
|
+
export declare const ArrayXid8Type: DataType;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { DataTypeOIDs } from '../constants.js';
|
|
2
|
+
import { fastParseInt, fastParseIntBuffer } from '../util/fast-parseint.js';
|
|
3
|
+
/**
|
|
4
|
+
* `xid`, `xid8` and `cid` - the transaction and command counters behind
|
|
5
|
+
* the `xmin`, `xmax`, `cmin` and `cmax` system columns, which an
|
|
6
|
+
* application selects when it wants a row's identity for optimistic
|
|
7
|
+
* locking. They are counters, so they decode to numbers.
|
|
8
|
+
*
|
|
9
|
+
* None of the three joins inference. A JavaScript number says nothing
|
|
10
|
+
* about which of these it is, and claiming integers here would take them
|
|
11
|
+
* from `int4` and `int8`, where they almost always belong - name the
|
|
12
|
+
* type to use one.
|
|
13
|
+
*/
|
|
14
|
+
const BIG32 = BigInt(32);
|
|
15
|
+
/**
|
|
16
|
+
* A 64-bit value is only exactly a JavaScript number while its high half
|
|
17
|
+
* is at most this: 0x1fffff * 2^32 + 0xffffffff is exactly
|
|
18
|
+
* Number.MAX_SAFE_INTEGER.
|
|
19
|
+
*/
|
|
20
|
+
const MAX_SAFE_HIGH = 0x1fffff;
|
|
21
|
+
/** Both 64-bit types here are unsigned, so the halves are read apart. */
|
|
22
|
+
function readUInt64(v, offset) {
|
|
23
|
+
const hi = v.readUInt32BE(offset);
|
|
24
|
+
const lo = v.readUInt32BE(offset + 4);
|
|
25
|
+
if (hi <= MAX_SAFE_HIGH)
|
|
26
|
+
return hi * 4294967296 + lo;
|
|
27
|
+
return (BigInt(hi) << BIG32) | BigInt(lo);
|
|
28
|
+
}
|
|
29
|
+
function writeUInt64(buf, v) {
|
|
30
|
+
const n = typeof v === 'bigint' ? v : BigInt(fastParseInt(v));
|
|
31
|
+
buf.writeUInt32BE(Number(n >> BIG32));
|
|
32
|
+
buf.writeUInt32BE(Number(n & BigInt(0xffffffff)));
|
|
33
|
+
}
|
|
34
|
+
function createUInt32Type(name, oid) {
|
|
35
|
+
return {
|
|
36
|
+
name,
|
|
37
|
+
oid,
|
|
38
|
+
jsType: 'number',
|
|
39
|
+
inferrable: false,
|
|
40
|
+
encodeText(v) {
|
|
41
|
+
return '' + v;
|
|
42
|
+
},
|
|
43
|
+
encodeBinary(buf, v) {
|
|
44
|
+
buf.writeUInt32BE(fastParseInt(v));
|
|
45
|
+
},
|
|
46
|
+
decodeBinary(v, offset = 0) {
|
|
47
|
+
return v.readUInt32BE(offset);
|
|
48
|
+
},
|
|
49
|
+
decodeText: fastParseInt,
|
|
50
|
+
decodeTextBuffer: fastParseIntBuffer,
|
|
51
|
+
isType(v) {
|
|
52
|
+
return (typeof v === 'number' &&
|
|
53
|
+
Number.isInteger(v) &&
|
|
54
|
+
v >= 0 &&
|
|
55
|
+
v <= 4294967295);
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
export const XidType = createUInt32Type('xid', DataTypeOIDs.xid);
|
|
60
|
+
export const ArrayXidType = {
|
|
61
|
+
...XidType,
|
|
62
|
+
name: '_xid',
|
|
63
|
+
oid: DataTypeOIDs._xid,
|
|
64
|
+
elementsOID: DataTypeOIDs.xid,
|
|
65
|
+
};
|
|
66
|
+
export const CidType = createUInt32Type('cid', DataTypeOIDs.cid);
|
|
67
|
+
export const ArrayCidType = {
|
|
68
|
+
...CidType,
|
|
69
|
+
name: '_cid',
|
|
70
|
+
oid: DataTypeOIDs._cid,
|
|
71
|
+
elementsOID: DataTypeOIDs.cid,
|
|
72
|
+
};
|
|
73
|
+
export const Xid8Type = {
|
|
74
|
+
name: 'xid8',
|
|
75
|
+
oid: DataTypeOIDs.xid8,
|
|
76
|
+
// A number while the value fits one exactly, a BigInt after that -
|
|
77
|
+
// the same contract int8 has.
|
|
78
|
+
jsType: 'BigInt',
|
|
79
|
+
inferrable: false,
|
|
80
|
+
encodeText(v) {
|
|
81
|
+
return '' + v;
|
|
82
|
+
},
|
|
83
|
+
encodeBinary: writeUInt64,
|
|
84
|
+
decodeBinary(v, offset = 0) {
|
|
85
|
+
return readUInt64(v, offset);
|
|
86
|
+
},
|
|
87
|
+
decodeText(s) {
|
|
88
|
+
// A full transaction id never has a sign, so the digit count alone
|
|
89
|
+
// decides whether it can be a number.
|
|
90
|
+
if (s.length <= 15)
|
|
91
|
+
return Number(s);
|
|
92
|
+
const v = BigInt(s);
|
|
93
|
+
return v <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(v) : v;
|
|
94
|
+
},
|
|
95
|
+
decodeTextBuffer(buf, offset, len) {
|
|
96
|
+
if (len <= 15)
|
|
97
|
+
return fastParseIntBuffer(buf, offset, len);
|
|
98
|
+
return Xid8Type.decodeText(buf.toString('latin1', offset, offset + len), {});
|
|
99
|
+
},
|
|
100
|
+
isType(v) {
|
|
101
|
+
return (typeof v === 'bigint' ||
|
|
102
|
+
(typeof v === 'number' && Number.isInteger(v) && v >= 0));
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
export const ArrayXid8Type = {
|
|
106
|
+
...Xid8Type,
|
|
107
|
+
name: '_xid8',
|
|
108
|
+
oid: DataTypeOIDs._xid8,
|
|
109
|
+
elementsOID: DataTypeOIDs.xid8,
|
|
110
|
+
};
|
package/index.d.ts
CHANGED
|
@@ -8,6 +8,10 @@ export * from './connection/pool.js';
|
|
|
8
8
|
export * from './connection/prepared-statement.js';
|
|
9
9
|
export * from './constants.js';
|
|
10
10
|
export * from './data-type-map.js';
|
|
11
|
+
export * from './data-types/classes/geometric.js';
|
|
12
|
+
export * from './data-types/classes/interval.js';
|
|
13
|
+
export * from './data-types/classes/numeric.js';
|
|
14
|
+
export * from './data-types/classes/range.js';
|
|
11
15
|
export * from './interfaces/batch-result.js';
|
|
12
16
|
export * from './interfaces/command-result.js';
|
|
13
17
|
export * from './interfaces/copy-from-rows-options.js';
|
package/index.js
CHANGED
|
@@ -8,6 +8,10 @@ export * from './connection/pool.js';
|
|
|
8
8
|
export * from './connection/prepared-statement.js';
|
|
9
9
|
export * from './constants.js';
|
|
10
10
|
export * from './data-type-map.js';
|
|
11
|
+
export * from './data-types/classes/geometric.js';
|
|
12
|
+
export * from './data-types/classes/interval.js';
|
|
13
|
+
export * from './data-types/classes/numeric.js';
|
|
14
|
+
export * from './data-types/classes/range.js';
|
|
11
15
|
export * from './interfaces/batch-result.js';
|
|
12
16
|
export * from './interfaces/command-result.js';
|
|
13
17
|
export * from './interfaces/copy-from-rows-options.js';
|
|
@@ -22,4 +22,30 @@ export interface DataMappingOptions {
|
|
|
22
22
|
* that comes back.
|
|
23
23
|
*/
|
|
24
24
|
fetchAsString?: OID[];
|
|
25
|
+
/**
|
|
26
|
+
* Ask the server for text on any column this client has no way to
|
|
27
|
+
* decode, instead of taking bytes it cannot read.
|
|
28
|
+
*
|
|
29
|
+
* Result columns are requested in binary, and there is a decoder only
|
|
30
|
+
* for the types registered in the type map - so an enum, a composite,
|
|
31
|
+
* an extension type, and 54 of PostgreSQL's own built-ins (`interval`,
|
|
32
|
+
* `inet`, the range family, `tsvector`, `money`, ...) arrive as a raw
|
|
33
|
+
* `Buffer` that nothing can interpret. With this on they arrive as the
|
|
34
|
+
* string PostgreSQL would have printed, which is what `pg` gives for
|
|
35
|
+
* the same column.
|
|
36
|
+
*
|
|
37
|
+
* Off by default, and it is not free: the column types have to be known
|
|
38
|
+
* before the Bind that asks for them, so a query that has not been
|
|
39
|
+
* prepared yet is prepared on first sight (one extra round trip per
|
|
40
|
+
* distinct statement per connection, measured at 233µs -> 447µs on
|
|
41
|
+
* loopback), and one run with `prepare: false` asks for the whole row
|
|
42
|
+
* as text.
|
|
43
|
+
*
|
|
44
|
+
* A registered type is untouched, so nothing that decodes today starts
|
|
45
|
+
* arriving as a string. An array whose own OID is unregistered comes
|
|
46
|
+
* back as the array literal rather than a JS array - there is no way to
|
|
47
|
+
* know it is an array without reading the catalog, and `pg` answers the
|
|
48
|
+
* same way. `fields[i].dataTypeName` stays empty for the same reason.
|
|
49
|
+
*/
|
|
50
|
+
unknownTypesAsString?: boolean;
|
|
25
51
|
}
|
|
@@ -26,15 +26,14 @@ export interface DataType {
|
|
|
26
26
|
encodeText?: EncodeTextFunction;
|
|
27
27
|
encodeCalculateDim?: EncodeCalculateDimFunction;
|
|
28
28
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
29
|
+
/**
|
|
30
|
+
* The shape `box` and `lseg` share.
|
|
31
|
+
*
|
|
32
|
+
* @deprecated Both types now decode to a class of their own - `Box` and
|
|
33
|
+
* `LineSegment` - which is what tells them apart; they were
|
|
34
|
+
* indistinguishable while both were only this. Kept because a plain
|
|
35
|
+
* object of these four numbers is still accepted as a parameter.
|
|
36
|
+
*/
|
|
38
37
|
export interface Rectangle {
|
|
39
38
|
x1: number;
|
|
40
39
|
y1: number;
|
package/package.json
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
* A connection whose socket closed without anyone asking it to - the
|
|
3
3
|
* backend terminated by an administrator, a failover, a network fault.
|
|
4
4
|
*
|
|
5
|
-
* Reported on `Connection`'s `'close'` event as its reason,
|
|
6
|
-
*
|
|
7
|
-
* `'error'
|
|
5
|
+
* Reported on `Connection`'s `'close'` event as its reason, for a pooled
|
|
6
|
+
* connection on `Pool`'s `'destroy'` (as the second argument) and
|
|
7
|
+
* `'error'`, and as the rejection of whatever query was in flight when it
|
|
8
|
+
* happened - one object for all of them, so a caller that needs both can
|
|
9
|
+
* pair them by identity. `Pool` emits `'error'` for this and for a connection that
|
|
8
10
|
* could not be created in the first place, and those want different
|
|
9
11
|
* handling - this class is how a listener tells them apart:
|
|
10
12
|
*
|
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
* A connection whose socket closed without anyone asking it to - the
|
|
3
3
|
* backend terminated by an administrator, a failover, a network fault.
|
|
4
4
|
*
|
|
5
|
-
* Reported on `Connection`'s `'close'` event as its reason,
|
|
6
|
-
*
|
|
7
|
-
* `'error'
|
|
5
|
+
* Reported on `Connection`'s `'close'` event as its reason, for a pooled
|
|
6
|
+
* connection on `Pool`'s `'destroy'` (as the second argument) and
|
|
7
|
+
* `'error'`, and as the rejection of whatever query was in flight when it
|
|
8
|
+
* happened - one object for all of them, so a caller that needs both can
|
|
9
|
+
* pair them by identity. `Pool` emits `'error'` for this and for a connection that
|
|
8
10
|
* could not be created in the first place, and those want different
|
|
9
11
|
* handling - this class is how a listener tells them apart:
|
|
10
12
|
*
|
package/protocol/frontend.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { DEFAULT_COLUMN_FORMAT } from '../constants.js';
|
|
2
2
|
import { DataTypeMap } from '../data-type-map.js';
|
|
3
3
|
import { encodeBinaryArray } from '../util/encode-binaryarray.js';
|
|
4
|
+
import { formatDateParam } from '../util/format-datetime.js';
|
|
4
5
|
import { stringifyArrayLiteral } from '../util/stringify-arrayliteral.js';
|
|
6
|
+
import { arrayLeaf } from '../util/unspecified-param.js';
|
|
5
7
|
import { Protocol } from './protocol.js';
|
|
6
8
|
import { SmartBuffer } from './smart-buffer.js';
|
|
7
9
|
const DataFormat = Protocol.DataFormat;
|
|
@@ -186,6 +188,21 @@ export class Frontend {
|
|
|
186
188
|
io.writeLString(v, 'utf8');
|
|
187
189
|
}
|
|
188
190
|
}
|
|
191
|
+
else if (v instanceof Date) {
|
|
192
|
+
// No declared type, so the server resolves it from the column
|
|
193
|
+
// - which is the whole point, and why the text has to carry
|
|
194
|
+
// the zone. `String(v)` is a JavaScript date string the server
|
|
195
|
+
// cannot parse at all, which is what a prepared statement with
|
|
196
|
+
// no paramTypes used to send.
|
|
197
|
+
io.writeLString(formatDateParam(v, queryOptions), 'utf8');
|
|
198
|
+
}
|
|
199
|
+
else if (Array.isArray(v)) {
|
|
200
|
+
// Also undeclared, so the server reads an array literal and
|
|
201
|
+
// takes the element type from the column. `'' + v` would hand
|
|
202
|
+
// it `a,b`, which is not one - the elements have to be quoted
|
|
203
|
+
// and the braces written, and a nested array needs its own.
|
|
204
|
+
io.writeLString(stringifyArrayLiteral(v, queryOptions, arrayLeaf(v) instanceof Date ? formatDateParam : undefined), 'utf8');
|
|
205
|
+
}
|
|
189
206
|
else if (Buffer.isBuffer(v)) {
|
|
190
207
|
// Set param format to binary
|
|
191
208
|
io.buffer.writeInt16BE(Protocol.DataFormat.binary, formatOffset + i * 2);
|