neonctl 2.22.0 → 2.23.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/README.md +242 -16
- package/analytics.js +5 -2
- package/commands/branches.js +9 -1
- package/commands/checkout.js +249 -0
- package/commands/connection_string.js +15 -2
- package/commands/data_api.js +286 -0
- package/commands/functions.js +277 -0
- package/commands/index.js +12 -0
- package/commands/link.js +667 -0
- package/commands/neon_auth.js +1013 -0
- package/commands/projects.js +9 -1
- package/commands/psql.js +62 -0
- package/commands/set_context.js +7 -2
- package/context.js +86 -14
- package/functions_api.js +44 -0
- package/index.js +3 -0
- package/package.json +60 -51
- package/psql/cli.js +51 -0
- package/psql/command/cmd_cond.js +437 -0
- package/psql/command/cmd_connect.js +815 -0
- package/psql/command/cmd_copy.js +1025 -0
- package/psql/command/cmd_describe.js +1810 -0
- package/psql/command/cmd_format.js +909 -0
- package/psql/command/cmd_io.js +2187 -0
- package/psql/command/cmd_lo.js +385 -0
- package/psql/command/cmd_meta.js +970 -0
- package/psql/command/cmd_misc.js +187 -0
- package/psql/command/cmd_pipeline.js +1141 -0
- package/psql/command/cmd_restrict.js +171 -0
- package/psql/command/cmd_show.js +751 -0
- package/psql/command/dispatch.js +343 -0
- package/psql/command/inputQueue.js +42 -0
- package/psql/command/shared.js +71 -0
- package/psql/complete/filenames.js +139 -0
- package/psql/complete/index.js +104 -0
- package/psql/complete/matcher.js +314 -0
- package/psql/complete/psqlVars.js +247 -0
- package/psql/complete/queries.js +491 -0
- package/psql/complete/rules.js +2387 -0
- package/psql/core/common.js +1250 -0
- package/psql/core/help.js +576 -0
- package/psql/core/mainloop.js +1353 -0
- package/psql/core/prompt.js +437 -0
- package/psql/core/settings.js +684 -0
- package/psql/core/sqlHelp.js +1066 -0
- package/psql/core/startup.js +840 -0
- package/psql/core/syncVars.js +116 -0
- package/psql/core/variables.js +287 -0
- package/psql/describe/formatters.js +1277 -0
- package/psql/describe/processNamePattern.js +270 -0
- package/psql/describe/queries.js +2373 -0
- package/psql/describe/versionGate.js +43 -0
- package/psql/index.js +2005 -0
- package/psql/io/history.js +299 -0
- package/psql/io/input.js +120 -0
- package/psql/io/lineEditor/buffer.js +323 -0
- package/psql/io/lineEditor/complete.js +227 -0
- package/psql/io/lineEditor/filename.js +159 -0
- package/psql/io/lineEditor/index.js +891 -0
- package/psql/io/lineEditor/keymap.js +738 -0
- package/psql/io/lineEditor/vt100.js +363 -0
- package/psql/io/pgpass.js +202 -0
- package/psql/io/pgservice.js +194 -0
- package/psql/io/psqlrc.js +422 -0
- package/psql/print/aligned.js +1756 -0
- package/psql/print/asciidoc.js +248 -0
- package/psql/print/crosstab.js +460 -0
- package/psql/print/csv.js +92 -0
- package/psql/print/html.js +258 -0
- package/psql/print/json.js +96 -0
- package/psql/print/latex.js +396 -0
- package/psql/print/pager.js +265 -0
- package/psql/print/troff.js +258 -0
- package/psql/print/unaligned.js +118 -0
- package/psql/print/units.js +135 -0
- package/psql/scanner/slash.js +513 -0
- package/psql/scanner/sql.js +910 -0
- package/psql/scanner/stringutils.js +390 -0
- package/psql/types/backslash.js +1 -0
- package/psql/types/connection.js +1 -0
- package/psql/types/index.js +7 -0
- package/psql/types/printer.js +1 -0
- package/psql/types/repl.js +1 -0
- package/psql/types/scanner.js +24 -0
- package/psql/types/settings.js +1 -0
- package/psql/types/variables.js +1 -0
- package/psql/wire/connection.js +2844 -0
- package/psql/wire/copy.js +108 -0
- package/psql/wire/notify.js +59 -0
- package/psql/wire/pipeline.js +519 -0
- package/psql/wire/protocol.js +466 -0
- package/psql/wire/sasl.js +296 -0
- package/psql/wire/tls.js +596 -0
- package/test_utils/fixtures.js +1 -0
- package/utils/enrichers.js +18 -1
- package/utils/esbuild.js +147 -0
- package/utils/middlewares.js +1 -1
- package/utils/psql.js +107 -11
- package/utils/zip.js +4 -0
- package/writer.js +1 -1
- package/commands/auth.test.js +0 -211
- package/commands/branches.test.js +0 -460
- package/commands/connection_string.test.js +0 -196
- package/commands/databases.test.js +0 -39
- package/commands/help.test.js +0 -9
- package/commands/init.test.js +0 -56
- package/commands/ip_allow.test.js +0 -59
- package/commands/operations.test.js +0 -7
- package/commands/orgs.test.js +0 -7
- package/commands/projects.test.js +0 -144
- package/commands/roles.test.js +0 -37
- package/commands/set_context.test.js +0 -159
- package/commands/vpc_endpoints.test.js +0 -69
- package/env.test.js +0 -55
- package/utils/formats.test.js +0 -32
- package/writer.test.js +0 -104
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgreSQL wire protocol message codec — thin adapter over `pg-protocol`.
|
|
3
|
+
*
|
|
4
|
+
* Background (per the plan): the original module was a hand-rolled wire codec
|
|
5
|
+
* for protocol v3.0. We've now swapped the bytes for `pg-protocol@1.14`
|
|
6
|
+
* (node-postgres' upstream codec) and this file is the seam:
|
|
7
|
+
*
|
|
8
|
+
* - Frontend builders (`Query`, `StartupMessage`, `Parse`, `Bind`, …) are
|
|
9
|
+
* re-exports of `serialize.*` adapted to our existing call signatures, so
|
|
10
|
+
* `connection.ts` and `pipeline.ts` keep working unchanged.
|
|
11
|
+
* - `MessageParser` wraps pg-protocol's synchronous `Parser` and reshapes
|
|
12
|
+
* each parsed message into our existing `BackendMessage` union — so the
|
|
13
|
+
* connection-state-machine switches on `.type` ('RowDescription', 'DataRow',
|
|
14
|
+
* …) and reads the same field names ('values', 'tag', 'fields', …) as
|
|
15
|
+
* before.
|
|
16
|
+
*
|
|
17
|
+
* Exceptions (kept hand-rolled):
|
|
18
|
+
*
|
|
19
|
+
* - `CancelRequest`: pg-protocol exposes one as `serialize.cancel`, and it
|
|
20
|
+
* is layout-compatible with our previous output. We delegate to it.
|
|
21
|
+
* - `SSLRequest`: pg-protocol *does* expose this as `serialize.requestSsl`;
|
|
22
|
+
* we re-export it for symmetry. No hand-roll needed.
|
|
23
|
+
* - `StartupMessage`: pg-protocol's `serialize.startup` unconditionally
|
|
24
|
+
* appends `client_encoding=UTF8` to whatever the caller supplies. We
|
|
25
|
+
* defensively filter `client_encoding` from the caller-provided map
|
|
26
|
+
* before passing through, since our connect layer always sets it to UTF8
|
|
27
|
+
* anyway. If it's set to something else, that's a caller bug — assert.
|
|
28
|
+
*
|
|
29
|
+
* Why this shim layer (instead of using pg-protocol directly):
|
|
30
|
+
*
|
|
31
|
+
* - Field-name mapping. pg-protocol's parser emits camelCase names
|
|
32
|
+
* (`'rowDescription'`, `'dataRow'`, …) and uses different field names
|
|
33
|
+
* (`fields` vs `values`, `text` vs `tag`, `processID` vs `processId`).
|
|
34
|
+
* Translating in one place keeps `connection.ts` minimal.
|
|
35
|
+
* - DataRow values. pg-protocol parses every value as a UTF-8 string;
|
|
36
|
+
* our connection layer expects `(Buffer | null)[]` (so binary-format
|
|
37
|
+
* columns can keep their bytes intact later). We re-buffer here.
|
|
38
|
+
* - ErrorResponse / NoticeResponse fields. pg-protocol attaches the parsed
|
|
39
|
+
* fields as named properties on the Error/Notice instance. We re-pack
|
|
40
|
+
* them into the Map<tag,value> shape our `fieldsToNotice` helper consumes.
|
|
41
|
+
*/
|
|
42
|
+
import { Buffer } from 'node:buffer';
|
|
43
|
+
// pg-protocol@1.14 ships two entry points via its `exports` map:
|
|
44
|
+
// - `./esm/index.js` (the `"import"` key) — a thin ESM wrapper that does
|
|
45
|
+
// `import * as protocol from '../dist/index.js'`.
|
|
46
|
+
// - `./dist/index.js` (the `"require"` / `"default"` keys) — the actual
|
|
47
|
+
// CJS implementation.
|
|
48
|
+
// On Node 20, resolving `'pg-protocol'` from an ESM context picks the
|
|
49
|
+
// `esm/index.js` wrapper, BUT the surrounding `package.json` has no
|
|
50
|
+
// `"type": "module"` field. Node 20 still parses the wrapper as CJS, hits
|
|
51
|
+
// the `import` syntax, and dies with `SyntaxError: Cannot use import
|
|
52
|
+
// statement outside a module`. Node 22+ honours the `"import"` exports
|
|
53
|
+
// key as a forced-ESM hint and avoids this. To stay portable across both,
|
|
54
|
+
// we import the CJS implementation directly — same pattern node-postgres
|
|
55
|
+
// uses for the `Parser` subpath, and what we already do on the next line.
|
|
56
|
+
import pgProtocol from 'pg-protocol/dist/index.js';
|
|
57
|
+
const { serialize } = pgProtocol;
|
|
58
|
+
import { Parser } from 'pg-protocol/dist/parser.js';
|
|
59
|
+
export class ProtocolError extends Error {
|
|
60
|
+
constructor(message) {
|
|
61
|
+
super(message);
|
|
62
|
+
this.name = 'ProtocolError';
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Frontend message encoders
|
|
67
|
+
//
|
|
68
|
+
// These are thin wrappers over `pg-protocol`'s `serialize.*`. The signatures
|
|
69
|
+
// mirror our previous hand-rolled API so callers in `connection.ts` and
|
|
70
|
+
// `pipeline.ts` don't need to change.
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
/**
|
|
73
|
+
* StartupMessage. pg-protocol always appends `client_encoding=UTF8`; we
|
|
74
|
+
* strip any incoming `client_encoding` to avoid duplicating it on the wire.
|
|
75
|
+
* Our connect layer always sets UTF8 anyway, so this is a no-op in practice.
|
|
76
|
+
*/
|
|
77
|
+
export function StartupMessage(params) {
|
|
78
|
+
const filtered = {};
|
|
79
|
+
for (const [key, value] of Object.entries(params)) {
|
|
80
|
+
if (value === undefined)
|
|
81
|
+
continue;
|
|
82
|
+
if (key === 'client_encoding')
|
|
83
|
+
continue;
|
|
84
|
+
filtered[key] = value;
|
|
85
|
+
}
|
|
86
|
+
return serialize.startup(filtered);
|
|
87
|
+
}
|
|
88
|
+
/** SSLRequest — pg-protocol exposes this as `requestSsl`. */
|
|
89
|
+
export function SSLRequest() {
|
|
90
|
+
return serialize.requestSsl();
|
|
91
|
+
}
|
|
92
|
+
/** CancelRequest — pg-protocol's `cancel` is layout-compatible. */
|
|
93
|
+
export function CancelRequest(processId, secretKey) {
|
|
94
|
+
return serialize.cancel(processId, secretKey);
|
|
95
|
+
}
|
|
96
|
+
/** Query: 'Q' + cstring(sql). */
|
|
97
|
+
export function Query(sql) {
|
|
98
|
+
return serialize.query(sql);
|
|
99
|
+
}
|
|
100
|
+
/** Terminate: 'X' + 4-byte length. */
|
|
101
|
+
export function Terminate() {
|
|
102
|
+
return serialize.end();
|
|
103
|
+
}
|
|
104
|
+
/** Sync: 'S' + 4-byte length. */
|
|
105
|
+
export function Sync() {
|
|
106
|
+
return serialize.sync();
|
|
107
|
+
}
|
|
108
|
+
/** Flush: 'H' + 4-byte length. */
|
|
109
|
+
export function Flush() {
|
|
110
|
+
return serialize.flush();
|
|
111
|
+
}
|
|
112
|
+
/** Parse: 'P' + cstring(name) + cstring(sql) + Int16 nparam + {Int32 oid}*. */
|
|
113
|
+
export function Parse(name, sql, paramTypes) {
|
|
114
|
+
return serialize.parse({ name, text: sql, types: paramTypes });
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Bind: 'B' + cstring(portal) + cstring(stmt) + per-value param formats +
|
|
118
|
+
* value array + result formats.
|
|
119
|
+
*
|
|
120
|
+
* Note on parameter formats: pg-protocol's `serialize.bind` auto-detects the
|
|
121
|
+
* format byte per value (`Buffer → 1`, `string/null → 0`); the caller-supplied
|
|
122
|
+
* `paramFormats` arg is therefore ignored. This matches the hand-rolled
|
|
123
|
+
* codec's effective behaviour because every caller passes either an empty
|
|
124
|
+
* array (= "all text", which is what pg-protocol picks for strings) or a
|
|
125
|
+
* format list that already agrees with the value's type.
|
|
126
|
+
*
|
|
127
|
+
* Note on result formats: we want every column in *text* format (`[0]`) for
|
|
128
|
+
* `psql`-style printing. pg-protocol forces a single shared result-format
|
|
129
|
+
* byte (`binary: boolean`); we pass `binary: false` to get text columns
|
|
130
|
+
* regardless of the caller-supplied `resultFormats` array.
|
|
131
|
+
*/
|
|
132
|
+
export function Bind(portal, stmt, paramFormats, params, resultFormats) {
|
|
133
|
+
// `paramFormats` and `resultFormats` are read for the rare future caller
|
|
134
|
+
// that might want explicit control; pg-protocol's `serialize.bind` doesn't
|
|
135
|
+
// expose either, so we intentionally drop them on the floor for now.
|
|
136
|
+
void paramFormats;
|
|
137
|
+
void resultFormats;
|
|
138
|
+
return serialize.bind({
|
|
139
|
+
portal,
|
|
140
|
+
statement: stmt,
|
|
141
|
+
values: params,
|
|
142
|
+
binary: false,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
/** Describe: 'D' + byte('S'|'P') + cstring(name). */
|
|
146
|
+
export function Describe(target, name) {
|
|
147
|
+
return serialize.describe({ type: target, name });
|
|
148
|
+
}
|
|
149
|
+
/** Execute: 'E' + cstring(portal) + Int32 maxRows. */
|
|
150
|
+
export function Execute(portal, maxRows) {
|
|
151
|
+
return serialize.execute({ portal, rows: maxRows });
|
|
152
|
+
}
|
|
153
|
+
/** Close: 'C' + byte('S'|'P') + cstring(name). */
|
|
154
|
+
export function Close(target, name) {
|
|
155
|
+
return serialize.close({ type: target, name });
|
|
156
|
+
}
|
|
157
|
+
/** PasswordMessage: 'p' + cstring(password). */
|
|
158
|
+
export function PasswordMessage(password) {
|
|
159
|
+
return serialize.password(password);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* SASLInitialResponse: 'p' + cstring(mechanism) + Int32(bodyLen) + body.
|
|
163
|
+
*
|
|
164
|
+
* pg-protocol takes the initial response as a *string*; our SCRAM client
|
|
165
|
+
* hands back a Buffer. SCRAM-SHA-256 messages are ASCII so this round-trips
|
|
166
|
+
* losslessly via UTF-8.
|
|
167
|
+
*/
|
|
168
|
+
export function SASLInitialResponse(mechanism, response) {
|
|
169
|
+
return serialize.sendSASLInitialResponseMessage(mechanism, response.toString('utf8'));
|
|
170
|
+
}
|
|
171
|
+
/** SASLResponse: 'p' + opaque body (no NUL terminator). */
|
|
172
|
+
export function SASLResponse(response) {
|
|
173
|
+
return serialize.sendSCRAMClientFinalMessage(response.toString('utf8'));
|
|
174
|
+
}
|
|
175
|
+
/** CopyData: 'd' + opaque bytes. */
|
|
176
|
+
export function CopyData(data) {
|
|
177
|
+
return serialize.copyData(data);
|
|
178
|
+
}
|
|
179
|
+
/** CopyDone: 'c' + 4-byte length. */
|
|
180
|
+
export function CopyDone() {
|
|
181
|
+
return serialize.copyDone();
|
|
182
|
+
}
|
|
183
|
+
/** CopyFail: 'f' + cstring(reason). */
|
|
184
|
+
export function CopyFail(message) {
|
|
185
|
+
return serialize.copyFail(message);
|
|
186
|
+
}
|
|
187
|
+
// ---------------------------------------------------------------------------
|
|
188
|
+
// Backend message parser
|
|
189
|
+
// ---------------------------------------------------------------------------
|
|
190
|
+
/**
|
|
191
|
+
* Streaming framer with a synchronous `feed()` API.
|
|
192
|
+
*
|
|
193
|
+
* Internally delegates to `pg-protocol`'s `Parser` (which exposes a
|
|
194
|
+
* `parse(buffer, callback)` method that emits each completed backend message
|
|
195
|
+
* via callback). On each `feed()` call we drain the parser and collect the
|
|
196
|
+
* emitted messages into an array, reshaping each pg-protocol message into our
|
|
197
|
+
* `BackendMessage` union (so `connection.ts`'s switch arms keep working).
|
|
198
|
+
*
|
|
199
|
+
* pg-protocol's `Parser.parse` calls the callback synchronously for every
|
|
200
|
+
* complete message and keeps incomplete trailing bytes buffered internally.
|
|
201
|
+
* That's a near-perfect match for our previous hand-rolled `MessageParser`.
|
|
202
|
+
*/
|
|
203
|
+
export class MessageParser {
|
|
204
|
+
constructor() {
|
|
205
|
+
this.inner = new Parser();
|
|
206
|
+
/** Bytes buffered inside `inner` but not yet emitted. Diagnostic only. */
|
|
207
|
+
this.buffered = 0;
|
|
208
|
+
}
|
|
209
|
+
feed(chunk) {
|
|
210
|
+
const out = [];
|
|
211
|
+
try {
|
|
212
|
+
this.inner.parse(chunk, (msg) => {
|
|
213
|
+
out.push(adaptBackendMessage(msg));
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
catch (err) {
|
|
217
|
+
// pg-protocol throws plain `Error` on bad input (unknown auth
|
|
218
|
+
// subtype, truncated frames, …). Normalize to our type.
|
|
219
|
+
throw err instanceof ProtocolError
|
|
220
|
+
? err
|
|
221
|
+
: new ProtocolError(err instanceof Error ? err.message : String(err));
|
|
222
|
+
}
|
|
223
|
+
// Probe the parser's leftover length via its internal field. Used by a
|
|
224
|
+
// handful of tests for diagnostics; not relied on in production code.
|
|
225
|
+
const probe = this.inner;
|
|
226
|
+
this.buffered = probe.bufferLength ?? 0;
|
|
227
|
+
return out;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Number of bytes buffered but not yet emitted (e.g. partial trailing
|
|
231
|
+
* message). Exposed for tests / diagnostics.
|
|
232
|
+
*/
|
|
233
|
+
get bufferedBytes() {
|
|
234
|
+
return this.buffered;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
function adaptBackendMessage(raw) {
|
|
238
|
+
const msg = raw;
|
|
239
|
+
switch (msg.name) {
|
|
240
|
+
case 'authenticationOk':
|
|
241
|
+
return { type: 'AuthenticationOk' };
|
|
242
|
+
case 'authenticationCleartextPassword':
|
|
243
|
+
return { type: 'AuthenticationCleartextPassword' };
|
|
244
|
+
case 'authenticationMD5Password':
|
|
245
|
+
return {
|
|
246
|
+
type: 'AuthenticationMD5Password',
|
|
247
|
+
salt: Buffer.from(msg.salt),
|
|
248
|
+
};
|
|
249
|
+
case 'authenticationSASL':
|
|
250
|
+
return {
|
|
251
|
+
type: 'AuthenticationSASL',
|
|
252
|
+
mechanisms: msg.mechanisms,
|
|
253
|
+
};
|
|
254
|
+
case 'authenticationSASLContinue':
|
|
255
|
+
return {
|
|
256
|
+
type: 'AuthenticationSASLContinue',
|
|
257
|
+
data: Buffer.from(msg.data, 'utf8'),
|
|
258
|
+
};
|
|
259
|
+
case 'authenticationSASLFinal':
|
|
260
|
+
return {
|
|
261
|
+
type: 'AuthenticationSASLFinal',
|
|
262
|
+
data: Buffer.from(msg.data, 'utf8'),
|
|
263
|
+
};
|
|
264
|
+
case 'parameterStatus':
|
|
265
|
+
return {
|
|
266
|
+
type: 'ParameterStatus',
|
|
267
|
+
name: msg.parameterName,
|
|
268
|
+
value: msg.parameterValue,
|
|
269
|
+
};
|
|
270
|
+
case 'backendKeyData':
|
|
271
|
+
return {
|
|
272
|
+
type: 'BackendKeyData',
|
|
273
|
+
processId: msg.processID,
|
|
274
|
+
secretKey: msg.secretKey,
|
|
275
|
+
};
|
|
276
|
+
case 'readyForQuery': {
|
|
277
|
+
const status = msg.status;
|
|
278
|
+
if (status !== 'I' && status !== 'T' && status !== 'E') {
|
|
279
|
+
throw new ProtocolError(`ReadyForQuery: unexpected status ${JSON.stringify(status)}`);
|
|
280
|
+
}
|
|
281
|
+
return { type: 'ReadyForQuery', status };
|
|
282
|
+
}
|
|
283
|
+
case 'rowDescription': {
|
|
284
|
+
const fields = msg.fields.map((f) => ({
|
|
285
|
+
name: f.name,
|
|
286
|
+
tableID: f.tableID,
|
|
287
|
+
columnID: f.columnID,
|
|
288
|
+
dataTypeID: f.dataTypeID,
|
|
289
|
+
dataTypeSize: f.dataTypeSize,
|
|
290
|
+
dataTypeModifier: f.dataTypeModifier,
|
|
291
|
+
format: f.format === 'binary' ? 1 : 0,
|
|
292
|
+
}));
|
|
293
|
+
return { type: 'RowDescription', fields };
|
|
294
|
+
}
|
|
295
|
+
case 'dataRow': {
|
|
296
|
+
// pg-protocol always parses values as UTF-8 strings (or null for SQL
|
|
297
|
+
// NULL). Our connection layer expects (Buffer | null)[] so it can hand
|
|
298
|
+
// text-format columns through `.toString('utf8')` and pass binary
|
|
299
|
+
// columns through unchanged. Re-encode here.
|
|
300
|
+
const fields = msg.fields;
|
|
301
|
+
const values = new Array(fields.length);
|
|
302
|
+
for (let i = 0; i < fields.length; i++) {
|
|
303
|
+
const v = fields[i];
|
|
304
|
+
values[i] = v === null ? null : Buffer.from(v, 'utf8');
|
|
305
|
+
}
|
|
306
|
+
return { type: 'DataRow', values };
|
|
307
|
+
}
|
|
308
|
+
case 'commandComplete':
|
|
309
|
+
return { type: 'CommandComplete', tag: msg.text };
|
|
310
|
+
case 'emptyQuery':
|
|
311
|
+
return { type: 'EmptyQueryResponse' };
|
|
312
|
+
case 'error':
|
|
313
|
+
return { type: 'ErrorResponse', fields: errorOrNoticeFields(msg) };
|
|
314
|
+
case 'notice':
|
|
315
|
+
return { type: 'NoticeResponse', fields: errorOrNoticeFields(msg) };
|
|
316
|
+
case 'notification':
|
|
317
|
+
return {
|
|
318
|
+
type: 'NotificationResponse',
|
|
319
|
+
processId: msg.processId,
|
|
320
|
+
channel: msg.channel,
|
|
321
|
+
payload: msg.payload,
|
|
322
|
+
};
|
|
323
|
+
case 'copyInResponse':
|
|
324
|
+
return adaptCopyResponse('CopyInResponse', msg);
|
|
325
|
+
case 'copyOutResponse':
|
|
326
|
+
return adaptCopyResponse('CopyOutResponse', msg);
|
|
327
|
+
case 'replicationStart':
|
|
328
|
+
// pg-protocol emits a bare marker for the 'W' (CopyBothResponse) byte
|
|
329
|
+
// — its `Parser` recognises the message code but does not decode the
|
|
330
|
+
// payload (overall format + per-column format bytes). The body shape
|
|
331
|
+
// is identical to CopyInResponse / CopyOutResponse but we don't need
|
|
332
|
+
// those fields: the connection layer reacts to CopyBothResponse by
|
|
333
|
+
// raising a clean diagnostic because this client does not implement
|
|
334
|
+
// CopyBoth streaming.
|
|
335
|
+
return { type: 'CopyBothResponse' };
|
|
336
|
+
case 'copyData':
|
|
337
|
+
return { type: 'CopyData', data: Buffer.from(msg.chunk) };
|
|
338
|
+
case 'copyDone':
|
|
339
|
+
return { type: 'CopyDone' };
|
|
340
|
+
case 'noData':
|
|
341
|
+
return { type: 'NoData' };
|
|
342
|
+
case 'parseComplete':
|
|
343
|
+
return { type: 'ParseComplete' };
|
|
344
|
+
case 'bindComplete':
|
|
345
|
+
return { type: 'BindComplete' };
|
|
346
|
+
case 'closeComplete':
|
|
347
|
+
return { type: 'CloseComplete' };
|
|
348
|
+
case 'portalSuspended':
|
|
349
|
+
return { type: 'PortalSuspended' };
|
|
350
|
+
case 'parameterDescription':
|
|
351
|
+
return {
|
|
352
|
+
type: 'ParameterDescription',
|
|
353
|
+
oids: msg.dataTypeIDs,
|
|
354
|
+
};
|
|
355
|
+
default:
|
|
356
|
+
throw new ProtocolError(`Unknown backend message: ${String(msg.name)}`);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
function adaptCopyResponse(type, msg) {
|
|
360
|
+
const binary = msg.binary;
|
|
361
|
+
const columnTypes = msg.columnTypes;
|
|
362
|
+
const columnFormats = columnTypes.map((f) => (f === 1 ? 1 : 0));
|
|
363
|
+
return {
|
|
364
|
+
type,
|
|
365
|
+
overallFormat: binary ? 1 : 0,
|
|
366
|
+
columnFormats,
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Re-pack pg-protocol's flattened Error/Notice fields (named properties like
|
|
371
|
+
* `severity`, `code`, `detail`, …) into our previous `Map<tag, value>` shape
|
|
372
|
+
* keyed by the on-wire single-letter tag. The map is consumed by
|
|
373
|
+
* `fieldsToNotice` below.
|
|
374
|
+
*/
|
|
375
|
+
function errorOrNoticeFields(msg) {
|
|
376
|
+
const out = new Map();
|
|
377
|
+
// The on-wire tag → field-name mapping is per PG docs §53.8. pg-protocol
|
|
378
|
+
// stores `severity` from BOTH `S` and `V` (it overwrites with `V` if present)
|
|
379
|
+
// — to preserve our previous behaviour (where the map carried both raw
|
|
380
|
+
// tags), we copy S = V = severity when severity is defined. Consumers like
|
|
381
|
+
// `fieldsToNotice` look at V then S so this matches.
|
|
382
|
+
const set = (tag, value) => {
|
|
383
|
+
if (typeof value === 'string')
|
|
384
|
+
out.set(tag, value);
|
|
385
|
+
};
|
|
386
|
+
set('S', msg.severity);
|
|
387
|
+
set('V', msg.severity);
|
|
388
|
+
set('C', msg.code);
|
|
389
|
+
set('M', msg.message);
|
|
390
|
+
set('D', msg.detail);
|
|
391
|
+
set('H', msg.hint);
|
|
392
|
+
set('P', msg.position);
|
|
393
|
+
set('p', msg.internalPosition);
|
|
394
|
+
set('q', msg.internalQuery);
|
|
395
|
+
set('W', msg.where);
|
|
396
|
+
set('s', msg.schema);
|
|
397
|
+
set('t', msg.table);
|
|
398
|
+
set('c', msg.column);
|
|
399
|
+
set('d', msg.dataType);
|
|
400
|
+
set('n', msg.constraint);
|
|
401
|
+
set('F', msg.file);
|
|
402
|
+
set('L', msg.line);
|
|
403
|
+
set('R', msg.routine);
|
|
404
|
+
return out;
|
|
405
|
+
}
|
|
406
|
+
// ---------------------------------------------------------------------------
|
|
407
|
+
// Notice helper (unchanged; consumed by connection.ts)
|
|
408
|
+
// ---------------------------------------------------------------------------
|
|
409
|
+
/**
|
|
410
|
+
* Convert a notice/error field map into a Notice-shaped object. The wire
|
|
411
|
+
* tags are PG's single-letter codes; we promote the ones the Connection
|
|
412
|
+
* interface exposes.
|
|
413
|
+
*/
|
|
414
|
+
export function fieldsToNotice(fields) {
|
|
415
|
+
// Per PG docs: V is the non-localized severity (preferred when present),
|
|
416
|
+
// S is the localized severity (always present). Message M is mandatory.
|
|
417
|
+
const severity = fields.get('V') ?? fields.get('S') ?? '';
|
|
418
|
+
const message = fields.get('M') ?? '';
|
|
419
|
+
const out = { severity, message };
|
|
420
|
+
const code = fields.get('C');
|
|
421
|
+
if (code !== undefined)
|
|
422
|
+
out.code = code;
|
|
423
|
+
const detail = fields.get('D');
|
|
424
|
+
if (detail !== undefined)
|
|
425
|
+
out.detail = detail;
|
|
426
|
+
const hint = fields.get('H');
|
|
427
|
+
if (hint !== undefined)
|
|
428
|
+
out.hint = hint;
|
|
429
|
+
const position = fields.get('P');
|
|
430
|
+
if (position !== undefined)
|
|
431
|
+
out.position = position;
|
|
432
|
+
const internalPosition = fields.get('p');
|
|
433
|
+
if (internalPosition !== undefined)
|
|
434
|
+
out.internalPosition = internalPosition;
|
|
435
|
+
const internalQuery = fields.get('q');
|
|
436
|
+
if (internalQuery !== undefined)
|
|
437
|
+
out.internalQuery = internalQuery;
|
|
438
|
+
const where = fields.get('W');
|
|
439
|
+
if (where !== undefined)
|
|
440
|
+
out.where = where;
|
|
441
|
+
const schema = fields.get('s');
|
|
442
|
+
if (schema !== undefined)
|
|
443
|
+
out.schema = schema;
|
|
444
|
+
const table = fields.get('t');
|
|
445
|
+
if (table !== undefined)
|
|
446
|
+
out.table = table;
|
|
447
|
+
const column = fields.get('c');
|
|
448
|
+
if (column !== undefined)
|
|
449
|
+
out.column = column;
|
|
450
|
+
const dataType = fields.get('d');
|
|
451
|
+
if (dataType !== undefined)
|
|
452
|
+
out.dataType = dataType;
|
|
453
|
+
const constraint = fields.get('n');
|
|
454
|
+
if (constraint !== undefined)
|
|
455
|
+
out.constraint = constraint;
|
|
456
|
+
const file = fields.get('F');
|
|
457
|
+
if (file !== undefined)
|
|
458
|
+
out.file = file;
|
|
459
|
+
const line = fields.get('L');
|
|
460
|
+
if (line !== undefined)
|
|
461
|
+
out.line = line;
|
|
462
|
+
const routine = fields.get('R');
|
|
463
|
+
if (routine !== undefined)
|
|
464
|
+
out.routine = routine;
|
|
465
|
+
return out;
|
|
466
|
+
}
|