@effect/sql-pg 4.0.0-rc.110 → 4.0.0-rc.112
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/ai-docs/package.json +2 -2
- package/dist/PgAuth.d.ts +69 -0
- package/dist/PgAuth.d.ts.map +1 -0
- package/dist/PgAuth.js +202 -0
- package/dist/PgAuth.js.map +1 -0
- package/dist/PgProtocol.d.ts +750 -0
- package/dist/PgProtocol.d.ts.map +1 -0
- package/dist/PgProtocol.js +1160 -0
- package/dist/PgProtocol.js.map +1 -0
- package/dist/PgTypes.d.ts +361 -0
- package/dist/PgTypes.d.ts.map +1 -0
- package/dist/PgTypes.js +1514 -0
- package/dist/PgTypes.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -1
- package/package.json +11 -7
- package/src/PgAuth.ts +287 -0
- package/src/PgProtocol.ts +1820 -0
- package/src/PgTypes.ts +1930 -0
- package/src/index.ts +15 -0
|
@@ -0,0 +1,1160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire codec for the PostgreSQL frontend/backend protocol, version 3.0.
|
|
3
|
+
*
|
|
4
|
+
* The module encodes frontend messages and decodes backend messages. Every
|
|
5
|
+
* function is pure: bytes in, bytes or plain data out. Nothing here opens a
|
|
6
|
+
* socket, negotiates TLS, or tracks session state, and nothing here decodes
|
|
7
|
+
* column values - `DataRow` fields stay raw bytes for `PgTypes` to interpret.
|
|
8
|
+
*
|
|
9
|
+
* Typed messages are a type byte, an `int32` length that counts itself but not
|
|
10
|
+
* the type byte, and a payload. Integers are big-endian and strings are
|
|
11
|
+
* NUL-terminated UTF-8 unless they are explicitly length-prefixed.
|
|
12
|
+
*
|
|
13
|
+
* Encoded frames and decoded byte fields are views into pooled buffers that
|
|
14
|
+
* are written once and never rewritten. They stay valid for as long as they
|
|
15
|
+
* are held, but holding one keeps its whole pool buffer alive, so copy
|
|
16
|
+
* anything that has to outlive the message it came from.
|
|
17
|
+
*
|
|
18
|
+
* @since 4.0.0
|
|
19
|
+
*/
|
|
20
|
+
import * as Data from "effect/Data";
|
|
21
|
+
import * as Result from "effect/Result";
|
|
22
|
+
/**
|
|
23
|
+
* Default `maxMessageSize` for `makeParser`: 16 MiB.
|
|
24
|
+
*
|
|
25
|
+
* @category constants
|
|
26
|
+
* @since 4.0.0
|
|
27
|
+
*/
|
|
28
|
+
export const defaultMaxMessageSize = 16 * 1024 * 1024;
|
|
29
|
+
/** Where a parser stops growing its buffer pool. */
|
|
30
|
+
const maxBufferSize = 64 * 1024;
|
|
31
|
+
/**
|
|
32
|
+
* Creates a `Parser`.
|
|
33
|
+
*
|
|
34
|
+
* Special pre-startup replies have no type byte and are not handled here; use
|
|
35
|
+
* `decodeSslResponse` for those.
|
|
36
|
+
*
|
|
37
|
+
* @category constructors
|
|
38
|
+
* @since 4.0.0
|
|
39
|
+
*/
|
|
40
|
+
export const makeParser = options => {
|
|
41
|
+
const maxMessageSize = options?.maxMessageSize ?? defaultMaxMessageSize;
|
|
42
|
+
const reader = new Reader();
|
|
43
|
+
let bufferSize = 8192;
|
|
44
|
+
let buffer = new Uint8Array(bufferSize);
|
|
45
|
+
let store = buffer.buffer;
|
|
46
|
+
let start = 0;
|
|
47
|
+
let end = 0;
|
|
48
|
+
let failed = false;
|
|
49
|
+
// Bytes already handed to the caller are never overwritten, so a full buffer
|
|
50
|
+
// is replaced rather than compacted in place. That lets `DataRow` fields be
|
|
51
|
+
// views instead of copies, which is the difference between one allocation per
|
|
52
|
+
// buffer and one per column.
|
|
53
|
+
//
|
|
54
|
+
// Every refill therefore allocates, so the pool doubles up to
|
|
55
|
+
// `maxBufferSize`: a busy connection spreads the allocation over more
|
|
56
|
+
// messages while a low-volume one stays small. A single oversized message
|
|
57
|
+
// grows its buffer beyond the pool without raising the pool itself.
|
|
58
|
+
const append = chunk => {
|
|
59
|
+
if (end + chunk.length > buffer.length) {
|
|
60
|
+
const pending = end - start;
|
|
61
|
+
if (bufferSize < maxBufferSize) bufferSize *= 2;
|
|
62
|
+
let capacity = bufferSize;
|
|
63
|
+
while (capacity < pending + chunk.length) capacity *= 2;
|
|
64
|
+
const next = new Uint8Array(capacity);
|
|
65
|
+
next.set(buffer.subarray(start, end));
|
|
66
|
+
buffer = next;
|
|
67
|
+
store = next.buffer;
|
|
68
|
+
start = 0;
|
|
69
|
+
end = pending;
|
|
70
|
+
}
|
|
71
|
+
buffer.set(chunk, end);
|
|
72
|
+
end += chunk.length;
|
|
73
|
+
};
|
|
74
|
+
return {
|
|
75
|
+
readField: options?.readField,
|
|
76
|
+
push(chunk) {
|
|
77
|
+
if (failed) {
|
|
78
|
+
throw new ParseError({
|
|
79
|
+
message: "Parser cannot be reused after a failure"
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
append(chunk);
|
|
84
|
+
const messages = [];
|
|
85
|
+
while (end - start >= 5) {
|
|
86
|
+
const length = buffer[start + 1] << 24 | buffer[start + 2] << 16 | buffer[start + 3] << 8 | buffer[start + 4];
|
|
87
|
+
if (length < 4) {
|
|
88
|
+
throw new ParseError({
|
|
89
|
+
message: `Invalid message length: ${length}`
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
if (length > maxMessageSize) {
|
|
93
|
+
throw new ParseError({
|
|
94
|
+
message: `Message length ${length} exceeds maxMessageSize ${maxMessageSize}`
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
if (end - start < length + 1) break;
|
|
98
|
+
const type = buffer[start];
|
|
99
|
+
const body = start + 5;
|
|
100
|
+
const limit = start + 1 + length;
|
|
101
|
+
start = limit;
|
|
102
|
+
if (type === BackendType.DataRow) {
|
|
103
|
+
// `buffer` always starts at byte 0 of `store`, so offsets index both.
|
|
104
|
+
messages.push(decodeDataRow(buffer, store, 0, body, limit, this.readField));
|
|
105
|
+
} else {
|
|
106
|
+
reader.reset(buffer, body, limit);
|
|
107
|
+
const message = decodeBackend(type, reader);
|
|
108
|
+
if (reader.offset !== limit) {
|
|
109
|
+
throw new ParseError({
|
|
110
|
+
message: `Message has ${limit - reader.offset} trailing byte(s)`
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
messages.push(message);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return messages;
|
|
117
|
+
} catch (error) {
|
|
118
|
+
failed = true;
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* Error produced when bytes cannot be interpreted as a protocol message.
|
|
126
|
+
*
|
|
127
|
+
* @category errors
|
|
128
|
+
* @since 4.0.0
|
|
129
|
+
*/
|
|
130
|
+
export class ParseError extends /*#__PURE__*/Data.TaggedError("PgProtocolParseError") {}
|
|
131
|
+
/**
|
|
132
|
+
* Error returned when a frontend message cannot be encoded.
|
|
133
|
+
*
|
|
134
|
+
* @category errors
|
|
135
|
+
* @since 4.0.0
|
|
136
|
+
*/
|
|
137
|
+
export class EncodeError extends /*#__PURE__*/Data.TaggedError("PgProtocolEncodeError") {}
|
|
138
|
+
const textEncoder = /*#__PURE__*/new TextEncoder();
|
|
139
|
+
const textDecoder = /*#__PURE__*/new TextDecoder("utf-8", {
|
|
140
|
+
fatal: true
|
|
141
|
+
});
|
|
142
|
+
/**
|
|
143
|
+
* Above this length `TextEncoder.encodeInto` beats a per-character loop, below
|
|
144
|
+
* it the call overhead dominates. Measured on V8: the loop runs at about a
|
|
145
|
+
* nanosecond per character and `encodeInto` costs about 50 ns whatever the
|
|
146
|
+
* length, so the crossover is around 50 characters.
|
|
147
|
+
*/
|
|
148
|
+
const asciiEncodeLimit = 48;
|
|
149
|
+
/**
|
|
150
|
+
* A view over part of a cached backing store. Slicing runs once per column of
|
|
151
|
+
* every row, and both obvious spellings are slower than this one: `subarray`
|
|
152
|
+
* consults the constructor's `Symbol.species` before it can allocate, and
|
|
153
|
+
* reading `.buffer` off a typed array is an accessor call rather than a field
|
|
154
|
+
* load. Callers hold the store and the array's byte offset instead.
|
|
155
|
+
*/
|
|
156
|
+
const view = (store, offset, length) => new Uint8Array(store, offset, length);
|
|
157
|
+
/** Up to this many bytes a copy loop beats `Uint8Array.prototype.set`. */
|
|
158
|
+
const smallCopyLimit = 8;
|
|
159
|
+
/**
|
|
160
|
+
* Writes messages back to back into a pooled buffer and hands out a view of
|
|
161
|
+
* each one, so encoding a message costs no allocation of its own. Bytes below
|
|
162
|
+
* `start` have already been handed out and are never rewritten; when the pool
|
|
163
|
+
* runs out it is replaced rather than reused.
|
|
164
|
+
*/
|
|
165
|
+
class Writer {
|
|
166
|
+
poolSize;
|
|
167
|
+
bytes;
|
|
168
|
+
view;
|
|
169
|
+
/** Where the message currently being written begins. */
|
|
170
|
+
start = 0;
|
|
171
|
+
offset = 0;
|
|
172
|
+
/** Set by `sqlNull`, read and cleared by the `makeBindEncoder` loop. */
|
|
173
|
+
isNull = false;
|
|
174
|
+
constructor(poolSize) {
|
|
175
|
+
this.poolSize = poolSize;
|
|
176
|
+
this.bytes = new Uint8Array(poolSize);
|
|
177
|
+
this.view = new DataView(this.bytes.buffer);
|
|
178
|
+
}
|
|
179
|
+
reserve(size) {
|
|
180
|
+
if (this.offset + size <= this.bytes.length) return;
|
|
181
|
+
const pending = this.offset - this.start;
|
|
182
|
+
let capacity = this.poolSize;
|
|
183
|
+
while (capacity < pending + size) capacity *= 2;
|
|
184
|
+
const next = new Uint8Array(capacity);
|
|
185
|
+
next.set(this.bytes.subarray(this.start, this.offset));
|
|
186
|
+
this.bytes = next;
|
|
187
|
+
this.view = new DataView(next.buffer);
|
|
188
|
+
this.start = 0;
|
|
189
|
+
this.offset = pending;
|
|
190
|
+
}
|
|
191
|
+
/** Starts a message, dropping anything a failed write left behind. */
|
|
192
|
+
begin() {
|
|
193
|
+
this.start = this.offset;
|
|
194
|
+
}
|
|
195
|
+
uint8(value) {
|
|
196
|
+
this.reserve(1);
|
|
197
|
+
this.bytes[this.offset++] = value;
|
|
198
|
+
}
|
|
199
|
+
int16(value) {
|
|
200
|
+
this.reserve(2);
|
|
201
|
+
const bytes = this.bytes;
|
|
202
|
+
const offset = this.offset;
|
|
203
|
+
bytes[offset] = value >>> 8;
|
|
204
|
+
bytes[offset + 1] = value;
|
|
205
|
+
this.offset = offset + 2;
|
|
206
|
+
}
|
|
207
|
+
int32(value) {
|
|
208
|
+
this.reserve(4);
|
|
209
|
+
this.setInt32(this.offset, value);
|
|
210
|
+
this.offset += 4;
|
|
211
|
+
}
|
|
212
|
+
setInt32(offset, value) {
|
|
213
|
+
const bytes = this.bytes;
|
|
214
|
+
bytes[offset] = value >>> 24;
|
|
215
|
+
bytes[offset + 1] = value >>> 16;
|
|
216
|
+
bytes[offset + 2] = value >>> 8;
|
|
217
|
+
bytes[offset + 3] = value;
|
|
218
|
+
}
|
|
219
|
+
float32(value) {
|
|
220
|
+
this.reserve(4);
|
|
221
|
+
this.view.setFloat32(this.offset, value);
|
|
222
|
+
this.offset += 4;
|
|
223
|
+
}
|
|
224
|
+
float64(value) {
|
|
225
|
+
this.reserve(8);
|
|
226
|
+
this.view.setFloat64(this.offset, value);
|
|
227
|
+
this.offset += 8;
|
|
228
|
+
}
|
|
229
|
+
bigInt64(value) {
|
|
230
|
+
this.reserve(8);
|
|
231
|
+
this.view.setBigInt64(this.offset, value);
|
|
232
|
+
this.offset += 8;
|
|
233
|
+
}
|
|
234
|
+
raw(value) {
|
|
235
|
+
const length = value.length;
|
|
236
|
+
this.reserve(length);
|
|
237
|
+
const bytes = this.bytes;
|
|
238
|
+
const offset = this.offset;
|
|
239
|
+
if (length <= smallCopyLimit) {
|
|
240
|
+
for (let index = 0; index < length; index++) bytes[offset + index] = value[index];
|
|
241
|
+
} else {
|
|
242
|
+
bytes.set(value, offset);
|
|
243
|
+
}
|
|
244
|
+
this.offset = offset + length;
|
|
245
|
+
}
|
|
246
|
+
sqlNull() {
|
|
247
|
+
this.isNull = true;
|
|
248
|
+
}
|
|
249
|
+
beginLength() {
|
|
250
|
+
// Relative to `start`, because a later write may move the message to a new
|
|
251
|
+
// pool buffer, which rebases `start` and `offset` but not the distance
|
|
252
|
+
// between them.
|
|
253
|
+
const token = this.offset - this.start;
|
|
254
|
+
this.int32(0);
|
|
255
|
+
return token;
|
|
256
|
+
}
|
|
257
|
+
endLength(token) {
|
|
258
|
+
this.setInt32(this.start + token, this.offset - this.start - token - 4);
|
|
259
|
+
}
|
|
260
|
+
utf8(value, nul = false) {
|
|
261
|
+
const length = value.length;
|
|
262
|
+
if (length <= asciiEncodeLimit) {
|
|
263
|
+
this.reserve(length + (nul ? 1 : 0));
|
|
264
|
+
const bytes = this.bytes;
|
|
265
|
+
const start = this.offset;
|
|
266
|
+
let i = 0;
|
|
267
|
+
for (; i < length; i++) {
|
|
268
|
+
const code = value.charCodeAt(i);
|
|
269
|
+
if (code > 0x7f) break;
|
|
270
|
+
bytes[start + i] = code;
|
|
271
|
+
}
|
|
272
|
+
if (i === length) {
|
|
273
|
+
const offset = start + length;
|
|
274
|
+
if (nul) bytes[offset] = 0;
|
|
275
|
+
this.offset = offset + (nul ? 1 : 0);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
// UTF-8 takes at most three bytes per UTF-16 code unit, and four for the
|
|
280
|
+
// two units of a surrogate pair, so this covers any string.
|
|
281
|
+
this.reserve(length * 3 + (nul ? 1 : 0));
|
|
282
|
+
this.offset += textEncoder.encodeInto(value, this.bytes.subarray(this.offset)).written;
|
|
283
|
+
if (nul) this.bytes[this.offset++] = 0;
|
|
284
|
+
}
|
|
285
|
+
cString(value) {
|
|
286
|
+
this.utf8(value, true);
|
|
287
|
+
}
|
|
288
|
+
finish() {
|
|
289
|
+
const value = view(this.bytes.buffer, this.bytes.byteOffset + this.start, this.offset - this.start);
|
|
290
|
+
if (this.bytes.length > this.poolSize) {
|
|
291
|
+
// An oversized message grew the pool; do not keep the rest of it around.
|
|
292
|
+
this.bytes = new Uint8Array(this.poolSize);
|
|
293
|
+
this.view = new DataView(this.bytes.buffer);
|
|
294
|
+
this.start = 0;
|
|
295
|
+
this.offset = 0;
|
|
296
|
+
} else {
|
|
297
|
+
this.start = this.offset;
|
|
298
|
+
}
|
|
299
|
+
return value;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
const emptyBytes = /*#__PURE__*/new Uint8Array(0);
|
|
303
|
+
/**
|
|
304
|
+
* A cursor over a message payload. The parser reuses one instance pointed at a
|
|
305
|
+
* window of its own buffer, so decoding a message allocates nothing beyond the
|
|
306
|
+
* message itself.
|
|
307
|
+
*/
|
|
308
|
+
class Reader {
|
|
309
|
+
bytes = emptyBytes;
|
|
310
|
+
/** The backing store of `bytes` and its offset into it, resolved per message. */
|
|
311
|
+
store = emptyBytes.buffer;
|
|
312
|
+
base = 0;
|
|
313
|
+
offset = 0;
|
|
314
|
+
limit = 0;
|
|
315
|
+
reset(bytes, offset, limit) {
|
|
316
|
+
this.bytes = bytes;
|
|
317
|
+
this.store = bytes.buffer;
|
|
318
|
+
this.base = bytes.byteOffset;
|
|
319
|
+
this.offset = offset;
|
|
320
|
+
this.limit = limit;
|
|
321
|
+
}
|
|
322
|
+
require(size) {
|
|
323
|
+
if (size < 0) {
|
|
324
|
+
throw new ParseError({
|
|
325
|
+
message: `Invalid read of ${size} byte(s)`
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
if (this.offset + size > this.limit) {
|
|
329
|
+
throw new ParseError({
|
|
330
|
+
message: `Truncated message: expected ${size} more byte(s)`
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
uint8() {
|
|
335
|
+
this.require(1);
|
|
336
|
+
return this.bytes[this.offset++];
|
|
337
|
+
}
|
|
338
|
+
int16() {
|
|
339
|
+
this.require(2);
|
|
340
|
+
const bytes = this.bytes;
|
|
341
|
+
const offset = this.offset;
|
|
342
|
+
this.offset = offset + 2;
|
|
343
|
+
return (bytes[offset] << 8 | bytes[offset + 1]) << 16 >> 16;
|
|
344
|
+
}
|
|
345
|
+
int32() {
|
|
346
|
+
this.require(4);
|
|
347
|
+
const bytes = this.bytes;
|
|
348
|
+
const offset = this.offset;
|
|
349
|
+
this.offset = offset + 4;
|
|
350
|
+
return bytes[offset] << 24 | bytes[offset + 1] << 16 | bytes[offset + 2] << 8 | bytes[offset + 3];
|
|
351
|
+
}
|
|
352
|
+
uint32() {
|
|
353
|
+
return this.int32() >>> 0;
|
|
354
|
+
}
|
|
355
|
+
raw(size) {
|
|
356
|
+
this.require(size);
|
|
357
|
+
const value = view(this.store, this.base + this.offset, size);
|
|
358
|
+
this.offset += size;
|
|
359
|
+
return value;
|
|
360
|
+
}
|
|
361
|
+
rest() {
|
|
362
|
+
return this.raw(this.limit - this.offset);
|
|
363
|
+
}
|
|
364
|
+
cString() {
|
|
365
|
+
const end = this.bytes.indexOf(0, this.offset);
|
|
366
|
+
if (end === -1 || end >= this.limit) {
|
|
367
|
+
throw new ParseError({
|
|
368
|
+
message: "Unterminated string"
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
const value = decodeUtf8(this.bytes, this.offset, end - this.offset);
|
|
372
|
+
this.offset = end + 1;
|
|
373
|
+
return value;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
/** Below this length a per-character loop beats `TextDecoder.decode`. */
|
|
377
|
+
const asciiDecodeLimit = 10;
|
|
378
|
+
/**
|
|
379
|
+
* Node's own UTF-8 decoder; see the note on its `PgTypes` counterpart. A
|
|
380
|
+
* result containing a replacement character goes to the strict decoder, so
|
|
381
|
+
* invalid bytes still fail exactly as they did.
|
|
382
|
+
*/
|
|
383
|
+
const utf8Slice = globalThis.Buffer?.prototype?.utf8Slice;
|
|
384
|
+
const decodeUtf8 = (bytes, offset, size) => {
|
|
385
|
+
if (size <= asciiDecodeLimit) {
|
|
386
|
+
let text = "";
|
|
387
|
+
let index = 0;
|
|
388
|
+
for (; index < size; index++) {
|
|
389
|
+
const code = bytes[offset + index];
|
|
390
|
+
if (code > 0x7f) break;
|
|
391
|
+
text += String.fromCharCode(code);
|
|
392
|
+
}
|
|
393
|
+
if (index === size) return text;
|
|
394
|
+
}
|
|
395
|
+
if (utf8Slice !== undefined) {
|
|
396
|
+
const text = utf8Slice.call(bytes, offset, offset + size);
|
|
397
|
+
if (text.indexOf("\ufffd") === -1) return text;
|
|
398
|
+
}
|
|
399
|
+
try {
|
|
400
|
+
return textDecoder.decode(view(bytes.buffer, bytes.byteOffset + offset, size));
|
|
401
|
+
} catch {
|
|
402
|
+
throw new ParseError({
|
|
403
|
+
message: "Invalid UTF-8 in message"
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
const sharedWriter = /*#__PURE__*/new Writer(8192);
|
|
408
|
+
/**
|
|
409
|
+
* Opens a typed message and leaves room for its length. Paired with `end`,
|
|
410
|
+
* which backfills it. A pair rather than a `write` callback, because a
|
|
411
|
+
* callback allocates a closure over the caller's options on every message.
|
|
412
|
+
*/
|
|
413
|
+
const begin = type => {
|
|
414
|
+
const writer = sharedWriter;
|
|
415
|
+
writer.begin();
|
|
416
|
+
writer.reserve(5);
|
|
417
|
+
const bytes = writer.bytes;
|
|
418
|
+
const offset = writer.offset;
|
|
419
|
+
bytes[offset] = type;
|
|
420
|
+
writer.offset = offset + 5;
|
|
421
|
+
return writer;
|
|
422
|
+
};
|
|
423
|
+
const end = () => {
|
|
424
|
+
// Relative to `start`, because writing may have moved the message to a new
|
|
425
|
+
// pool buffer. The length counts itself but not the type byte.
|
|
426
|
+
sharedWriter.setInt32(sharedWriter.start + 1, sharedWriter.offset - sharedWriter.start - 1);
|
|
427
|
+
return sharedWriter.finish();
|
|
428
|
+
};
|
|
429
|
+
const empty = type => {
|
|
430
|
+
const writer = begin(type);
|
|
431
|
+
writer.setInt32(writer.start + 1, 4);
|
|
432
|
+
return writer.finish();
|
|
433
|
+
};
|
|
434
|
+
const targetByte = target => target === "statement" ? 0x53 : 0x50;
|
|
435
|
+
const requireInt16Count = (count, name) => {
|
|
436
|
+
if (count > 0x7fff) throw new EncodeError({
|
|
437
|
+
message: `${name} count exceeds 32767: ${count}`
|
|
438
|
+
});
|
|
439
|
+
return count;
|
|
440
|
+
};
|
|
441
|
+
const encodeResult = evaluate => {
|
|
442
|
+
try {
|
|
443
|
+
return Result.succeed(evaluate());
|
|
444
|
+
} catch (error) {
|
|
445
|
+
if (error instanceof EncodeError) return Result.fail(error);
|
|
446
|
+
throw error;
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
/**
|
|
450
|
+
* Encodes a `Parse` message, returning `EncodeError` when its parameter count
|
|
451
|
+
* is outside the signed int16 wire range.
|
|
452
|
+
*
|
|
453
|
+
* @category encoding
|
|
454
|
+
* @since 4.0.0
|
|
455
|
+
*/
|
|
456
|
+
const encodeParseUnsafe = options => {
|
|
457
|
+
const writer = begin(0x50);
|
|
458
|
+
writer.cString(options.name);
|
|
459
|
+
writer.cString(options.query);
|
|
460
|
+
const parameterTypes = options.parameterTypes;
|
|
461
|
+
const count = requireInt16Count(parameterTypes.length, "Parse parameter type");
|
|
462
|
+
writer.reserve(2 + count * 4);
|
|
463
|
+
const bytes = writer.bytes;
|
|
464
|
+
let offset = writer.offset;
|
|
465
|
+
bytes[offset] = count >>> 8;
|
|
466
|
+
bytes[offset + 1] = count;
|
|
467
|
+
offset += 2;
|
|
468
|
+
for (let index = 0; index < count; index++) {
|
|
469
|
+
const oid = parameterTypes[index];
|
|
470
|
+
bytes[offset] = oid >>> 24;
|
|
471
|
+
bytes[offset + 1] = oid >>> 16;
|
|
472
|
+
bytes[offset + 2] = oid >>> 8;
|
|
473
|
+
bytes[offset + 3] = oid;
|
|
474
|
+
offset += 4;
|
|
475
|
+
}
|
|
476
|
+
writer.offset = offset;
|
|
477
|
+
return end();
|
|
478
|
+
};
|
|
479
|
+
export const encodeParse = options => encodeResult(() => encodeParseUnsafe(options));
|
|
480
|
+
/**
|
|
481
|
+
* Encodes a `Bind` message using the binary format code for parameters and
|
|
482
|
+
* results, returning `EncodeError` when its parameter count is outside the
|
|
483
|
+
* signed int16 wire range.
|
|
484
|
+
*
|
|
485
|
+
* @category encoding
|
|
486
|
+
* @since 4.0.0
|
|
487
|
+
*/
|
|
488
|
+
const encodeBindUnsafe = options => {
|
|
489
|
+
const writer = begin(0x42);
|
|
490
|
+
writer.cString(options.portal);
|
|
491
|
+
writer.cString(options.statement);
|
|
492
|
+
const parameters = options.parameters;
|
|
493
|
+
const count = requireInt16Count(parameters.length, "Bind parameter");
|
|
494
|
+
// Sizing the rest of the frame up front turns every remaining write into a
|
|
495
|
+
// plain store: one bounds check for the message instead of one per field.
|
|
496
|
+
let size = 10 + count * 4;
|
|
497
|
+
for (let index = 0; index < count; index++) {
|
|
498
|
+
const parameter = parameters[index];
|
|
499
|
+
if (parameter !== null) size += parameter.length;
|
|
500
|
+
}
|
|
501
|
+
writer.reserve(size);
|
|
502
|
+
const bytes = writer.bytes;
|
|
503
|
+
let offset = writer.offset;
|
|
504
|
+
// One parameter format code, binary, for every parameter.
|
|
505
|
+
bytes[offset] = 0;
|
|
506
|
+
bytes[offset + 1] = 1;
|
|
507
|
+
bytes[offset + 2] = 0;
|
|
508
|
+
bytes[offset + 3] = 1;
|
|
509
|
+
bytes[offset + 4] = count >>> 8;
|
|
510
|
+
bytes[offset + 5] = count;
|
|
511
|
+
offset += 6;
|
|
512
|
+
for (let index = 0; index < count; index++) {
|
|
513
|
+
const parameter = parameters[index];
|
|
514
|
+
if (parameter === null) {
|
|
515
|
+
bytes[offset] = 0xff;
|
|
516
|
+
bytes[offset + 1] = 0xff;
|
|
517
|
+
bytes[offset + 2] = 0xff;
|
|
518
|
+
bytes[offset + 3] = 0xff;
|
|
519
|
+
offset += 4;
|
|
520
|
+
} else {
|
|
521
|
+
const length = parameter.length;
|
|
522
|
+
bytes[offset] = length >>> 24;
|
|
523
|
+
bytes[offset + 1] = length >>> 16;
|
|
524
|
+
bytes[offset + 2] = length >>> 8;
|
|
525
|
+
bytes[offset + 3] = length;
|
|
526
|
+
offset += 4;
|
|
527
|
+
if (length <= smallCopyLimit) {
|
|
528
|
+
for (let byte = 0; byte < length; byte++) bytes[offset + byte] = parameter[byte];
|
|
529
|
+
} else {
|
|
530
|
+
bytes.set(parameter, offset);
|
|
531
|
+
}
|
|
532
|
+
offset += length;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
// One result format code, binary, for every column.
|
|
536
|
+
bytes[offset] = 0;
|
|
537
|
+
bytes[offset + 1] = 1;
|
|
538
|
+
bytes[offset + 2] = 0;
|
|
539
|
+
bytes[offset + 3] = 1;
|
|
540
|
+
writer.offset = offset + 4;
|
|
541
|
+
return end();
|
|
542
|
+
};
|
|
543
|
+
export const encodeBind = options => encodeResult(() => encodeBindUnsafe(options));
|
|
544
|
+
/**
|
|
545
|
+
* Builds a `Bind` encoder that writes parameters straight into the frame,
|
|
546
|
+
* skipping the array per parameter that `encodeBind` has to copy from.
|
|
547
|
+
*
|
|
548
|
+
* ```ts
|
|
549
|
+
* import { PgProtocol, PgTypes } from "@effect/sql-pg"
|
|
550
|
+
*
|
|
551
|
+
* const encodeBind = PgProtocol.makeBindEncoder(PgTypes.writeParameter)
|
|
552
|
+
* const frame = encodeBind({ portal: "", statement: "s1", parameters: [PgTypes.int4(1)] })
|
|
553
|
+
* ```
|
|
554
|
+
*
|
|
555
|
+
* @category encoding
|
|
556
|
+
* @since 4.0.0
|
|
557
|
+
*/
|
|
558
|
+
const valueWriterUnsafe = /*#__PURE__*/Symbol.for("@effect/sql-pg/PgProtocol/ValueWriter/unsafe");
|
|
559
|
+
export const makeBindEncoder = writeParameter => options => {
|
|
560
|
+
try {
|
|
561
|
+
const writer = begin(0x42);
|
|
562
|
+
writer.cString(options.portal);
|
|
563
|
+
writer.cString(options.statement);
|
|
564
|
+
const parameters = options.parameters;
|
|
565
|
+
const count = requireInt16Count(parameters.length, "Bind parameter");
|
|
566
|
+
writer.reserve(6);
|
|
567
|
+
const header = writer.bytes;
|
|
568
|
+
const headerOffset = writer.offset;
|
|
569
|
+
header[headerOffset] = 0;
|
|
570
|
+
header[headerOffset + 1] = 1;
|
|
571
|
+
header[headerOffset + 2] = 0;
|
|
572
|
+
header[headerOffset + 3] = 1;
|
|
573
|
+
header[headerOffset + 4] = count >>> 8;
|
|
574
|
+
header[headerOffset + 5] = count;
|
|
575
|
+
writer.offset = headerOffset + 6;
|
|
576
|
+
const writeUnsafe = writeParameter[valueWriterUnsafe];
|
|
577
|
+
for (let index = 0; index < count; index++) {
|
|
578
|
+
const token = writer.beginLength();
|
|
579
|
+
writer.isNull = false;
|
|
580
|
+
if (writeUnsafe === undefined) {
|
|
581
|
+
const written = writeParameter(writer, parameters[index]);
|
|
582
|
+
if (Result.isFailure(written)) return Result.fail(written.failure);
|
|
583
|
+
} else {
|
|
584
|
+
writeUnsafe(writer, parameters[index]);
|
|
585
|
+
}
|
|
586
|
+
if (writer.isNull) {
|
|
587
|
+
writer.isNull = false;
|
|
588
|
+
writer.offset = writer.start + token;
|
|
589
|
+
writer.int32(-1);
|
|
590
|
+
} else {
|
|
591
|
+
writer.endLength(token);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
writer.reserve(4);
|
|
595
|
+
const trailer = writer.bytes;
|
|
596
|
+
const trailerOffset = writer.offset;
|
|
597
|
+
trailer[trailerOffset] = 0;
|
|
598
|
+
trailer[trailerOffset + 1] = 1;
|
|
599
|
+
trailer[trailerOffset + 2] = 0;
|
|
600
|
+
trailer[trailerOffset + 3] = 1;
|
|
601
|
+
writer.offset = trailerOffset + 4;
|
|
602
|
+
return Result.succeed(end());
|
|
603
|
+
} catch (error) {
|
|
604
|
+
if (error instanceof EncodeError) return Result.fail(error);
|
|
605
|
+
throw error;
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
/**
|
|
609
|
+
* Encodes an `Execute` message.
|
|
610
|
+
*
|
|
611
|
+
* @category encoding
|
|
612
|
+
* @since 4.0.0
|
|
613
|
+
*/
|
|
614
|
+
export const encodeExecute = options => {
|
|
615
|
+
const writer = begin(0x45);
|
|
616
|
+
writer.cString(options.portal);
|
|
617
|
+
writer.int32(options.maxRows);
|
|
618
|
+
return end();
|
|
619
|
+
};
|
|
620
|
+
/**
|
|
621
|
+
* Encodes a `Describe` message.
|
|
622
|
+
*
|
|
623
|
+
* @category encoding
|
|
624
|
+
* @since 4.0.0
|
|
625
|
+
*/
|
|
626
|
+
export const encodeDescribe = options => {
|
|
627
|
+
const writer = begin(0x44);
|
|
628
|
+
writer.uint8(targetByte(options.target));
|
|
629
|
+
writer.cString(options.name);
|
|
630
|
+
return end();
|
|
631
|
+
};
|
|
632
|
+
/**
|
|
633
|
+
* Encodes a `Close` message.
|
|
634
|
+
*
|
|
635
|
+
* @category encoding
|
|
636
|
+
* @since 4.0.0
|
|
637
|
+
*/
|
|
638
|
+
export const encodeClose = options => {
|
|
639
|
+
const writer = begin(0x43);
|
|
640
|
+
writer.uint8(targetByte(options.target));
|
|
641
|
+
writer.cString(options.name);
|
|
642
|
+
return end();
|
|
643
|
+
};
|
|
644
|
+
/**
|
|
645
|
+
* Encodes a `Sync` message.
|
|
646
|
+
*
|
|
647
|
+
* @category encoding
|
|
648
|
+
* @since 4.0.0
|
|
649
|
+
*/
|
|
650
|
+
export const encodeSync = () => empty(0x53);
|
|
651
|
+
/**
|
|
652
|
+
* Encodes a `Flush` message.
|
|
653
|
+
*
|
|
654
|
+
* @category encoding
|
|
655
|
+
* @since 4.0.0
|
|
656
|
+
*/
|
|
657
|
+
export const encodeFlush = () => empty(0x48);
|
|
658
|
+
/**
|
|
659
|
+
* Encodes a `Terminate` message.
|
|
660
|
+
*
|
|
661
|
+
* @category encoding
|
|
662
|
+
* @since 4.0.0
|
|
663
|
+
*/
|
|
664
|
+
export const encodeTerminate = () => empty(0x58);
|
|
665
|
+
/**
|
|
666
|
+
* Encodes a `PasswordMessage`. The password is sent verbatim, so MD5 hashing
|
|
667
|
+
* belongs to the caller - see `PgAuth.md5Password`.
|
|
668
|
+
*
|
|
669
|
+
* @category encoding
|
|
670
|
+
* @since 4.0.0
|
|
671
|
+
*/
|
|
672
|
+
export const encodePasswordMessage = options => {
|
|
673
|
+
const writer = begin(0x70);
|
|
674
|
+
writer.cString(options.password);
|
|
675
|
+
return end();
|
|
676
|
+
};
|
|
677
|
+
/**
|
|
678
|
+
* Encodes a `SASLInitialResponse` message.
|
|
679
|
+
*
|
|
680
|
+
* @category encoding
|
|
681
|
+
* @since 4.0.0
|
|
682
|
+
*/
|
|
683
|
+
export const encodeSASLInitialResponse = options => {
|
|
684
|
+
const writer = begin(0x70);
|
|
685
|
+
writer.cString(options.mechanism);
|
|
686
|
+
if (options.initialResponse === null) {
|
|
687
|
+
writer.int32(-1);
|
|
688
|
+
} else {
|
|
689
|
+
writer.int32(options.initialResponse.length);
|
|
690
|
+
writer.raw(options.initialResponse);
|
|
691
|
+
}
|
|
692
|
+
return end();
|
|
693
|
+
};
|
|
694
|
+
/**
|
|
695
|
+
* Encodes a `SASLResponse` message.
|
|
696
|
+
*
|
|
697
|
+
* @category encoding
|
|
698
|
+
* @since 4.0.0
|
|
699
|
+
*/
|
|
700
|
+
export const encodeSASLResponse = options => {
|
|
701
|
+
const writer = begin(0x70);
|
|
702
|
+
writer.raw(options.data);
|
|
703
|
+
return end();
|
|
704
|
+
};
|
|
705
|
+
/**
|
|
706
|
+
* Encodes any frontend message.
|
|
707
|
+
*
|
|
708
|
+
* @category encoding
|
|
709
|
+
* @since 4.0.0
|
|
710
|
+
*/
|
|
711
|
+
export const encode = message => {
|
|
712
|
+
switch (message._tag) {
|
|
713
|
+
case "Parse":
|
|
714
|
+
return encodeParse(message);
|
|
715
|
+
case "Bind":
|
|
716
|
+
return encodeBind(message);
|
|
717
|
+
case "Execute":
|
|
718
|
+
return Result.succeed(encodeExecute(message));
|
|
719
|
+
case "Describe":
|
|
720
|
+
return Result.succeed(encodeDescribe(message));
|
|
721
|
+
case "Close":
|
|
722
|
+
return Result.succeed(encodeClose(message));
|
|
723
|
+
case "Sync":
|
|
724
|
+
return Result.succeed(encodeSync());
|
|
725
|
+
case "Flush":
|
|
726
|
+
return Result.succeed(encodeFlush());
|
|
727
|
+
case "Terminate":
|
|
728
|
+
return Result.succeed(encodeTerminate());
|
|
729
|
+
case "PasswordMessage":
|
|
730
|
+
return Result.succeed(encodePasswordMessage(message));
|
|
731
|
+
case "SASLInitialResponse":
|
|
732
|
+
return Result.succeed(encodeSASLInitialResponse(message));
|
|
733
|
+
case "SASLResponse":
|
|
734
|
+
return Result.succeed(encodeSASLResponse(message));
|
|
735
|
+
}
|
|
736
|
+
};
|
|
737
|
+
// -----------------------------------------------------------------------------
|
|
738
|
+
// special messages
|
|
739
|
+
// -----------------------------------------------------------------------------
|
|
740
|
+
const PROTOCOL_VERSION_3_0 = 196608;
|
|
741
|
+
const SSL_REQUEST_CODE = 80877103;
|
|
742
|
+
const CANCEL_REQUEST_CODE = 80877102;
|
|
743
|
+
/**
|
|
744
|
+
* Encodes an `SSLRequest`. It has no type byte and is only valid before
|
|
745
|
+
* startup.
|
|
746
|
+
*
|
|
747
|
+
* @category encoding
|
|
748
|
+
* @since 4.0.0
|
|
749
|
+
*/
|
|
750
|
+
export const encodeSslRequest = () => {
|
|
751
|
+
sharedWriter.begin();
|
|
752
|
+
sharedWriter.int32(8);
|
|
753
|
+
sharedWriter.int32(SSL_REQUEST_CODE);
|
|
754
|
+
return sharedWriter.finish();
|
|
755
|
+
};
|
|
756
|
+
/**
|
|
757
|
+
* Decodes the single byte the server sends in reply to an `SSLRequest`. `"S"`
|
|
758
|
+
* means the server will speak TLS, `"N"` means it will not.
|
|
759
|
+
*
|
|
760
|
+
* @category decoding
|
|
761
|
+
* @since 4.0.0
|
|
762
|
+
*/
|
|
763
|
+
export const decodeSslResponse = byte => byte === 0x53 ? Result.succeed("S") : byte === 0x4e ? Result.succeed("N") : Result.fail(new ParseError({
|
|
764
|
+
message: `Invalid SSLRequest response byte: ${byte}`
|
|
765
|
+
}));
|
|
766
|
+
/**
|
|
767
|
+
* Encodes a `StartupMessage` for protocol 3.0. It has no type byte.
|
|
768
|
+
* `client_encoding` defaults to `UTF8` because this codec always writes UTF-8.
|
|
769
|
+
*
|
|
770
|
+
* @category encoding
|
|
771
|
+
* @since 4.0.0
|
|
772
|
+
*/
|
|
773
|
+
export const encodeStartupMessage = parameters => {
|
|
774
|
+
const writer = sharedWriter;
|
|
775
|
+
writer.begin();
|
|
776
|
+
writer.int32(0);
|
|
777
|
+
writer.int32(PROTOCOL_VERSION_3_0);
|
|
778
|
+
for (const [key, value] of Object.entries(parameters)) {
|
|
779
|
+
if (value === undefined) continue;
|
|
780
|
+
writer.cString(key);
|
|
781
|
+
writer.cString(value);
|
|
782
|
+
}
|
|
783
|
+
if (parameters.client_encoding === undefined) {
|
|
784
|
+
writer.cString("client_encoding");
|
|
785
|
+
writer.cString("UTF8");
|
|
786
|
+
}
|
|
787
|
+
writer.uint8(0);
|
|
788
|
+
writer.setInt32(writer.start, writer.offset - writer.start);
|
|
789
|
+
return writer.finish();
|
|
790
|
+
};
|
|
791
|
+
/**
|
|
792
|
+
* Encodes a `CancelRequest`. It has no type byte and is sent on a separate
|
|
793
|
+
* connection, using the `pid` and `secret` from `BackendKeyData`.
|
|
794
|
+
*
|
|
795
|
+
* @category encoding
|
|
796
|
+
* @since 4.0.0
|
|
797
|
+
*/
|
|
798
|
+
export const encodeCancelRequest = options => {
|
|
799
|
+
const writer = sharedWriter;
|
|
800
|
+
writer.begin();
|
|
801
|
+
writer.int32(16);
|
|
802
|
+
writer.int32(CANCEL_REQUEST_CODE);
|
|
803
|
+
writer.int32(options.pid);
|
|
804
|
+
writer.int32(options.secret);
|
|
805
|
+
return writer.finish();
|
|
806
|
+
};
|
|
807
|
+
const BackendType = {
|
|
808
|
+
NotificationResponse: 0x41,
|
|
809
|
+
// A
|
|
810
|
+
CommandComplete: 0x43,
|
|
811
|
+
// C
|
|
812
|
+
DataRow: 0x44,
|
|
813
|
+
// D
|
|
814
|
+
ErrorResponse: 0x45,
|
|
815
|
+
// E
|
|
816
|
+
CopyInResponse: 0x47,
|
|
817
|
+
// G
|
|
818
|
+
CopyOutResponse: 0x48,
|
|
819
|
+
// H
|
|
820
|
+
EmptyQueryResponse: 0x49,
|
|
821
|
+
// I
|
|
822
|
+
BackendKeyData: 0x4b,
|
|
823
|
+
// K
|
|
824
|
+
NoticeResponse: 0x4e,
|
|
825
|
+
// N
|
|
826
|
+
Authentication: 0x52,
|
|
827
|
+
// R
|
|
828
|
+
ParameterStatus: 0x53,
|
|
829
|
+
// S
|
|
830
|
+
RowDescription: 0x54,
|
|
831
|
+
// T
|
|
832
|
+
CopyBothResponse: 0x57,
|
|
833
|
+
// W
|
|
834
|
+
ReadyForQuery: 0x5a,
|
|
835
|
+
// Z
|
|
836
|
+
CopyDone: 0x63,
|
|
837
|
+
// c
|
|
838
|
+
CopyData: 0x64,
|
|
839
|
+
// d
|
|
840
|
+
NoData: 0x6e,
|
|
841
|
+
// n
|
|
842
|
+
PortalSuspended: 0x73,
|
|
843
|
+
// s
|
|
844
|
+
ParameterDescription: 0x74,
|
|
845
|
+
// t
|
|
846
|
+
NegotiateProtocolVersion: 0x76,
|
|
847
|
+
// v
|
|
848
|
+
ParseComplete: 0x31,
|
|
849
|
+
// 1
|
|
850
|
+
BindComplete: 0x32,
|
|
851
|
+
// 2
|
|
852
|
+
CloseComplete: 0x33 // 3
|
|
853
|
+
};
|
|
854
|
+
const errorFieldNames = {
|
|
855
|
+
S: "severity",
|
|
856
|
+
V: "severityUnlocalized",
|
|
857
|
+
C: "code",
|
|
858
|
+
M: "message",
|
|
859
|
+
D: "detail",
|
|
860
|
+
H: "hint",
|
|
861
|
+
P: "position",
|
|
862
|
+
p: "internalPosition",
|
|
863
|
+
q: "internalQuery",
|
|
864
|
+
W: "where",
|
|
865
|
+
s: "schema",
|
|
866
|
+
t: "table",
|
|
867
|
+
c: "column",
|
|
868
|
+
d: "dataType",
|
|
869
|
+
n: "constraint",
|
|
870
|
+
F: "file",
|
|
871
|
+
L: "line",
|
|
872
|
+
R: "routine"
|
|
873
|
+
};
|
|
874
|
+
const decodeAuthentication = reader => {
|
|
875
|
+
const method = reader.int32();
|
|
876
|
+
switch (method) {
|
|
877
|
+
case 0:
|
|
878
|
+
return {
|
|
879
|
+
_tag: "AuthenticationOk"
|
|
880
|
+
};
|
|
881
|
+
case 3:
|
|
882
|
+
return {
|
|
883
|
+
_tag: "AuthenticationCleartextPassword"
|
|
884
|
+
};
|
|
885
|
+
case 5:
|
|
886
|
+
return {
|
|
887
|
+
_tag: "AuthenticationMD5Password",
|
|
888
|
+
salt: reader.raw(4)
|
|
889
|
+
};
|
|
890
|
+
case 10:
|
|
891
|
+
{
|
|
892
|
+
const mechanisms = [];
|
|
893
|
+
for (;;) {
|
|
894
|
+
const mechanism = reader.cString();
|
|
895
|
+
if (mechanism === "") break;
|
|
896
|
+
mechanisms.push(mechanism);
|
|
897
|
+
}
|
|
898
|
+
return {
|
|
899
|
+
_tag: "AuthenticationSASL",
|
|
900
|
+
mechanisms
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
case 11:
|
|
904
|
+
return {
|
|
905
|
+
_tag: "AuthenticationSASLContinue",
|
|
906
|
+
data: reader.rest()
|
|
907
|
+
};
|
|
908
|
+
case 12:
|
|
909
|
+
return {
|
|
910
|
+
_tag: "AuthenticationSASLFinal",
|
|
911
|
+
data: reader.rest()
|
|
912
|
+
};
|
|
913
|
+
default:
|
|
914
|
+
return {
|
|
915
|
+
_tag: "AuthenticationUnsupported",
|
|
916
|
+
method,
|
|
917
|
+
payload: reader.rest()
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
};
|
|
921
|
+
const decodeErrorFields = reader => {
|
|
922
|
+
const fields = {};
|
|
923
|
+
for (;;) {
|
|
924
|
+
const code = reader.uint8();
|
|
925
|
+
if (code === 0) break;
|
|
926
|
+
const key = String.fromCharCode(code);
|
|
927
|
+
fields[errorFieldNames[key] ?? key] = reader.cString();
|
|
928
|
+
}
|
|
929
|
+
return fields;
|
|
930
|
+
};
|
|
931
|
+
const requireNonNegativeCount = (count, name) => {
|
|
932
|
+
if (count < 0) {
|
|
933
|
+
throw new ParseError({
|
|
934
|
+
message: `Invalid ${name} count: ${count}`
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
return count;
|
|
938
|
+
};
|
|
939
|
+
const decodeCopyResponse = reader => {
|
|
940
|
+
const format = reader.uint8();
|
|
941
|
+
const count = requireNonNegativeCount(reader.int16(), "COPY column");
|
|
942
|
+
const columnFormats = new Array(count);
|
|
943
|
+
for (let i = 0; i < count; i++) {
|
|
944
|
+
columnFormats[i] = reader.int16();
|
|
945
|
+
}
|
|
946
|
+
return {
|
|
947
|
+
format,
|
|
948
|
+
columnFormats
|
|
949
|
+
};
|
|
950
|
+
};
|
|
951
|
+
// The one message that arrives per result row, so it reads the frame directly
|
|
952
|
+
// instead of going through `Reader`. Callers have already checked that the
|
|
953
|
+
// whole frame is buffered, which turns every field read into one bounds check
|
|
954
|
+
// against `limit`.
|
|
955
|
+
const decodeDataRow = (bytes, store, base, offset, limit, readField) => {
|
|
956
|
+
if (offset + 2 > limit) {
|
|
957
|
+
throw new ParseError({
|
|
958
|
+
message: "Truncated message: expected 2 more byte(s)"
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
const count = (bytes[offset] << 8 | bytes[offset + 1]) << 16 >> 16;
|
|
962
|
+
if (count < 0) {
|
|
963
|
+
throw new ParseError({
|
|
964
|
+
message: `Invalid DataRow field count: ${count}`
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
const values = new Array(count);
|
|
968
|
+
let position = offset + 2;
|
|
969
|
+
for (let i = 0; i < count; i++) {
|
|
970
|
+
if (position + 4 > limit) {
|
|
971
|
+
throw new ParseError({
|
|
972
|
+
message: "Truncated message: expected 4 more byte(s)"
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
const size = bytes[position] << 24 | bytes[position + 1] << 16 | bytes[position + 2] << 8 | bytes[position + 3];
|
|
976
|
+
position += 4;
|
|
977
|
+
if (size < 0) {
|
|
978
|
+
if (size < -1) {
|
|
979
|
+
throw new ParseError({
|
|
980
|
+
message: `Invalid DataRow field length: ${size}`
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
values[i] = readField === undefined ? null : readField(bytes, position, -1, i);
|
|
984
|
+
continue;
|
|
985
|
+
}
|
|
986
|
+
const next = position + size;
|
|
987
|
+
if (next > limit) {
|
|
988
|
+
throw new ParseError({
|
|
989
|
+
message: `Truncated message: expected ${size} more byte(s)`
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
values[i] = readField === undefined ? view(store, base + position, size) : readField(bytes, position, size, i);
|
|
993
|
+
position = next;
|
|
994
|
+
}
|
|
995
|
+
if (position !== limit) {
|
|
996
|
+
throw new ParseError({
|
|
997
|
+
message: `DataRow has ${limit - position} trailing byte(s)`
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
return {
|
|
1001
|
+
_tag: "DataRow",
|
|
1002
|
+
values
|
|
1003
|
+
};
|
|
1004
|
+
};
|
|
1005
|
+
const decodeBackend = (type, reader) => {
|
|
1006
|
+
switch (type) {
|
|
1007
|
+
case BackendType.Authentication:
|
|
1008
|
+
return decodeAuthentication(reader);
|
|
1009
|
+
case BackendType.ParameterStatus:
|
|
1010
|
+
return {
|
|
1011
|
+
_tag: "ParameterStatus",
|
|
1012
|
+
name: reader.cString(),
|
|
1013
|
+
value: reader.cString()
|
|
1014
|
+
};
|
|
1015
|
+
case BackendType.BackendKeyData:
|
|
1016
|
+
return {
|
|
1017
|
+
_tag: "BackendKeyData",
|
|
1018
|
+
pid: reader.int32(),
|
|
1019
|
+
secret: reader.int32()
|
|
1020
|
+
};
|
|
1021
|
+
case BackendType.ReadyForQuery:
|
|
1022
|
+
{
|
|
1023
|
+
const status = String.fromCharCode(reader.uint8());
|
|
1024
|
+
if (status !== "I" && status !== "T" && status !== "E") {
|
|
1025
|
+
throw new ParseError({
|
|
1026
|
+
message: `Invalid ReadyForQuery status: ${status}`
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1029
|
+
return {
|
|
1030
|
+
_tag: "ReadyForQuery",
|
|
1031
|
+
status
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
case BackendType.RowDescription:
|
|
1035
|
+
{
|
|
1036
|
+
const count = requireNonNegativeCount(reader.int16(), "RowDescription field");
|
|
1037
|
+
const fields = new Array(count);
|
|
1038
|
+
for (let i = 0; i < count; i++) {
|
|
1039
|
+
fields[i] = {
|
|
1040
|
+
name: reader.cString(),
|
|
1041
|
+
tableOid: reader.uint32(),
|
|
1042
|
+
columnAttributeNumber: reader.int16(),
|
|
1043
|
+
dataTypeOid: reader.uint32(),
|
|
1044
|
+
dataTypeSize: reader.int16(),
|
|
1045
|
+
typeModifier: reader.int32(),
|
|
1046
|
+
format: reader.int16()
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
return {
|
|
1050
|
+
_tag: "RowDescription",
|
|
1051
|
+
fields
|
|
1052
|
+
};
|
|
1053
|
+
}
|
|
1054
|
+
case BackendType.DataRow:
|
|
1055
|
+
return decodeDataRow(reader.bytes, reader.store, reader.base, reader.offset, reader.limit, undefined);
|
|
1056
|
+
case BackendType.CommandComplete:
|
|
1057
|
+
return {
|
|
1058
|
+
_tag: "CommandComplete",
|
|
1059
|
+
commandTag: reader.cString()
|
|
1060
|
+
};
|
|
1061
|
+
case BackendType.EmptyQueryResponse:
|
|
1062
|
+
return {
|
|
1063
|
+
_tag: "EmptyQueryResponse"
|
|
1064
|
+
};
|
|
1065
|
+
case BackendType.NoData:
|
|
1066
|
+
return {
|
|
1067
|
+
_tag: "NoData"
|
|
1068
|
+
};
|
|
1069
|
+
case BackendType.ParseComplete:
|
|
1070
|
+
return {
|
|
1071
|
+
_tag: "ParseComplete"
|
|
1072
|
+
};
|
|
1073
|
+
case BackendType.BindComplete:
|
|
1074
|
+
return {
|
|
1075
|
+
_tag: "BindComplete"
|
|
1076
|
+
};
|
|
1077
|
+
case BackendType.CloseComplete:
|
|
1078
|
+
return {
|
|
1079
|
+
_tag: "CloseComplete"
|
|
1080
|
+
};
|
|
1081
|
+
case BackendType.PortalSuspended:
|
|
1082
|
+
return {
|
|
1083
|
+
_tag: "PortalSuspended"
|
|
1084
|
+
};
|
|
1085
|
+
case BackendType.ParameterDescription:
|
|
1086
|
+
{
|
|
1087
|
+
const count = requireNonNegativeCount(reader.int16(), "ParameterDescription parameter");
|
|
1088
|
+
const parameterTypes = new Array(count);
|
|
1089
|
+
for (let i = 0; i < count; i++) {
|
|
1090
|
+
parameterTypes[i] = reader.uint32();
|
|
1091
|
+
}
|
|
1092
|
+
return {
|
|
1093
|
+
_tag: "ParameterDescription",
|
|
1094
|
+
parameterTypes
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
case BackendType.ErrorResponse:
|
|
1098
|
+
return {
|
|
1099
|
+
_tag: "ErrorResponse",
|
|
1100
|
+
fields: decodeErrorFields(reader)
|
|
1101
|
+
};
|
|
1102
|
+
case BackendType.NoticeResponse:
|
|
1103
|
+
return {
|
|
1104
|
+
_tag: "NoticeResponse",
|
|
1105
|
+
fields: decodeErrorFields(reader)
|
|
1106
|
+
};
|
|
1107
|
+
case BackendType.NotificationResponse:
|
|
1108
|
+
return {
|
|
1109
|
+
_tag: "NotificationResponse",
|
|
1110
|
+
pid: reader.int32(),
|
|
1111
|
+
channel: reader.cString(),
|
|
1112
|
+
payload: reader.cString()
|
|
1113
|
+
};
|
|
1114
|
+
case BackendType.NegotiateProtocolVersion:
|
|
1115
|
+
{
|
|
1116
|
+
const minorVersion = reader.int32();
|
|
1117
|
+
const count = requireNonNegativeCount(reader.int32(), "NegotiateProtocolVersion option");
|
|
1118
|
+
const unrecognizedOptions = new Array(count);
|
|
1119
|
+
for (let i = 0; i < count; i++) {
|
|
1120
|
+
unrecognizedOptions[i] = reader.cString();
|
|
1121
|
+
}
|
|
1122
|
+
return {
|
|
1123
|
+
_tag: "NegotiateProtocolVersion",
|
|
1124
|
+
minorVersion,
|
|
1125
|
+
unrecognizedOptions
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
case BackendType.CopyInResponse:
|
|
1129
|
+
return {
|
|
1130
|
+
_tag: "CopyInResponse",
|
|
1131
|
+
...decodeCopyResponse(reader)
|
|
1132
|
+
};
|
|
1133
|
+
case BackendType.CopyOutResponse:
|
|
1134
|
+
return {
|
|
1135
|
+
_tag: "CopyOutResponse",
|
|
1136
|
+
...decodeCopyResponse(reader)
|
|
1137
|
+
};
|
|
1138
|
+
case BackendType.CopyBothResponse:
|
|
1139
|
+
return {
|
|
1140
|
+
_tag: "CopyBothResponse",
|
|
1141
|
+
...decodeCopyResponse(reader)
|
|
1142
|
+
};
|
|
1143
|
+
case BackendType.CopyData:
|
|
1144
|
+
return {
|
|
1145
|
+
_tag: "CopyData",
|
|
1146
|
+
data: reader.rest()
|
|
1147
|
+
};
|
|
1148
|
+
case BackendType.CopyDone:
|
|
1149
|
+
return {
|
|
1150
|
+
_tag: "CopyDone"
|
|
1151
|
+
};
|
|
1152
|
+
default:
|
|
1153
|
+
return {
|
|
1154
|
+
_tag: "Unknown",
|
|
1155
|
+
type,
|
|
1156
|
+
payload: reader.rest()
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
};
|
|
1160
|
+
//# sourceMappingURL=PgProtocol.js.map
|