neonctl 2.28.0 → 2.29.1
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 +71 -71
- package/dist/analytics.js +35 -33
- package/dist/api.js +34 -34
- package/dist/auth.js +50 -44
- package/dist/cli.js +2 -2
- package/dist/commands/auth.js +58 -52
- package/dist/commands/bootstrap.js +115 -157
- package/dist/commands/branches.js +154 -147
- package/dist/commands/bucket.js +124 -118
- package/dist/commands/checkout.js +49 -49
- package/dist/commands/config.js +212 -88
- package/dist/commands/connection_string.js +62 -62
- package/dist/commands/data_api.js +96 -96
- package/dist/commands/databases.js +23 -23
- package/dist/commands/deploy.js +12 -12
- package/dist/commands/dev.js +114 -114
- package/dist/commands/env.js +43 -43
- package/dist/commands/functions.js +97 -98
- package/dist/commands/index.js +26 -26
- package/dist/commands/init.js +23 -22
- package/dist/commands/ip_allow.js +29 -29
- package/dist/commands/link.js +223 -166
- package/dist/commands/neon_auth.js +381 -363
- package/dist/commands/operations.js +11 -11
- package/dist/commands/orgs.js +8 -8
- package/dist/commands/projects.js +101 -99
- package/dist/commands/psql.js +31 -31
- package/dist/commands/roles.js +21 -21
- package/dist/commands/schema_diff.js +23 -23
- package/dist/commands/set_context.js +17 -17
- package/dist/commands/status.js +17 -17
- package/dist/commands/user.js +5 -5
- package/dist/commands/vpc_endpoints.js +50 -50
- package/dist/config.js +7 -7
- package/dist/config_format.js +5 -5
- package/dist/context.js +23 -16
- package/dist/current_branch_fast_path.js +6 -6
- package/dist/dev/env.js +34 -34
- package/dist/dev/functions.js +4 -4
- package/dist/dev/inputs.js +6 -6
- package/dist/dev/runtime.js +25 -25
- package/dist/env.js +14 -14
- package/dist/env_file.js +13 -13
- package/dist/errors.js +19 -19
- package/dist/functions_api.js +10 -10
- package/dist/help.js +15 -15
- package/dist/index.js +94 -92
- package/dist/log.js +2 -2
- package/dist/pkg.js +5 -5
- package/dist/psql/cli.js +4 -2
- package/dist/psql/command/cmd_cond.js +61 -61
- package/dist/psql/command/cmd_connect.js +159 -154
- package/dist/psql/command/cmd_copy.js +107 -97
- package/dist/psql/command/cmd_describe.js +368 -363
- package/dist/psql/command/cmd_format.js +276 -263
- package/dist/psql/command/cmd_io.js +269 -263
- package/dist/psql/command/cmd_lo.js +74 -66
- package/dist/psql/command/cmd_meta.js +148 -148
- package/dist/psql/command/cmd_misc.js +17 -17
- package/dist/psql/command/cmd_pipeline.js +142 -135
- package/dist/psql/command/cmd_restrict.js +25 -25
- package/dist/psql/command/cmd_show.js +183 -168
- package/dist/psql/command/dispatch.js +26 -26
- package/dist/psql/command/shared.js +14 -14
- package/dist/psql/complete/filenames.js +16 -16
- package/dist/psql/complete/index.js +4 -4
- package/dist/psql/complete/matcher.js +33 -32
- package/dist/psql/complete/psqlVars.js +173 -173
- package/dist/psql/complete/queries.js +5 -3
- package/dist/psql/complete/rules.js +900 -863
- package/dist/psql/core/common.js +136 -133
- package/dist/psql/core/help.js +343 -343
- package/dist/psql/core/mainloop.js +160 -153
- package/dist/psql/core/prompt.js +126 -123
- package/dist/psql/core/settings.js +111 -111
- package/dist/psql/core/sqlHelp.js +150 -150
- package/dist/psql/core/startup.js +211 -205
- package/dist/psql/core/syncVars.js +14 -14
- package/dist/psql/core/variables.js +24 -24
- package/dist/psql/describe/formatters.js +302 -289
- package/dist/psql/describe/processNamePattern.js +28 -28
- package/dist/psql/describe/queries.js +656 -651
- package/dist/psql/index.js +436 -411
- package/dist/psql/io/history.js +36 -36
- package/dist/psql/io/input.js +15 -15
- package/dist/psql/io/lineEditor/buffer.js +27 -25
- package/dist/psql/io/lineEditor/complete.js +15 -15
- package/dist/psql/io/lineEditor/filename.js +22 -22
- package/dist/psql/io/lineEditor/index.js +65 -62
- package/dist/psql/io/lineEditor/keymap.js +325 -318
- package/dist/psql/io/lineEditor/vt100.js +60 -60
- package/dist/psql/io/pgpass.js +18 -18
- package/dist/psql/io/pgservice.js +14 -14
- package/dist/psql/io/psqlrc.js +46 -46
- package/dist/psql/print/aligned.js +175 -166
- package/dist/psql/print/asciidoc.js +51 -51
- package/dist/psql/print/crosstab.js +34 -31
- package/dist/psql/print/csv.js +25 -22
- package/dist/psql/print/html.js +54 -54
- package/dist/psql/print/json.js +12 -12
- package/dist/psql/print/latex.js +118 -118
- package/dist/psql/print/pager.js +28 -26
- package/dist/psql/print/troff.js +48 -48
- package/dist/psql/print/unaligned.js +15 -14
- package/dist/psql/print/units.js +17 -17
- package/dist/psql/scanner/slash.js +48 -46
- package/dist/psql/scanner/sql.js +88 -84
- package/dist/psql/scanner/stringutils.js +21 -17
- package/dist/psql/types/index.js +7 -7
- package/dist/psql/types/scanner.js +8 -8
- package/dist/psql/wire/connection.js +341 -327
- package/dist/psql/wire/copy.js +7 -7
- package/dist/psql/wire/pipeline.js +26 -24
- package/dist/psql/wire/protocol.js +102 -102
- package/dist/psql/wire/sasl.js +62 -62
- package/dist/psql/wire/tls.js +79 -73
- package/dist/storage_api.js +15 -15
- package/dist/test_utils/fixtures.js +34 -31
- package/dist/test_utils/oauth_server.js +5 -5
- package/dist/utils/api_enums.js +13 -13
- package/dist/utils/branch_notice.js +5 -5
- package/dist/utils/branch_picker.js +26 -26
- package/dist/utils/compute_units.js +4 -4
- package/dist/utils/enrichers.js +20 -15
- package/dist/utils/esbuild.js +28 -28
- package/dist/utils/formats.js +1 -1
- package/dist/utils/middlewares.js +3 -3
- package/dist/utils/package_manager.js +68 -0
- package/dist/utils/point_in_time.js +12 -12
- package/dist/utils/psql.js +30 -30
- package/dist/utils/string.js +2 -2
- package/dist/utils/ui.js +9 -9
- package/dist/utils/zip.js +1 -1
- package/dist/writer.js +17 -17
- package/package.json +6 -7
|
@@ -32,16 +32,16 @@
|
|
|
32
32
|
* protocol.ts but the high-level path (parameterised `query()`, prepared
|
|
33
33
|
* statements, pipeline) is WP-21. We throw clearly when called.
|
|
34
34
|
*/
|
|
35
|
-
import
|
|
36
|
-
import
|
|
37
|
-
import * as
|
|
38
|
-
import
|
|
39
|
-
import
|
|
40
|
-
import {
|
|
41
|
-
import { PipelineSession } from
|
|
42
|
-
import {
|
|
43
|
-
import {
|
|
44
|
-
import {
|
|
35
|
+
import { Buffer } from "node:buffer";
|
|
36
|
+
import { createHash } from "node:crypto";
|
|
37
|
+
import * as dns from "node:dns/promises";
|
|
38
|
+
import * as net from "node:net";
|
|
39
|
+
import * as tls from "node:tls";
|
|
40
|
+
import { NoticeMultiplexer } from "./notify.js";
|
|
41
|
+
import { PipelineSession } from "./pipeline.js";
|
|
42
|
+
import { Bind, CancelRequest, Close, CopyData, CopyDone, CopyFail, Describe, Execute, fieldsToNotice, MessageParser, Parse, PasswordMessage, Query, SASLInitialResponse, SASLResponse, StartupMessage, Sync, Terminate, } from "./protocol.js";
|
|
43
|
+
import { createScramClient } from "./sasl.js";
|
|
44
|
+
import { negotiateTls } from "./tls.js";
|
|
45
45
|
/**
|
|
46
46
|
* Map a Notice-flavoured fields map into a `ConnectError` that includes a
|
|
47
47
|
* recognizable `message` plus a `cause` slot for the raw record.
|
|
@@ -59,9 +59,9 @@ function fieldsToConnectError(fields) {
|
|
|
59
59
|
*/
|
|
60
60
|
function pipelineAbortedError() {
|
|
61
61
|
return {
|
|
62
|
-
severity:
|
|
63
|
-
code:
|
|
64
|
-
message:
|
|
62
|
+
severity: "ERROR",
|
|
63
|
+
code: "",
|
|
64
|
+
message: "Pipeline aborted, command did not run",
|
|
65
65
|
pipelineAborted: true,
|
|
66
66
|
};
|
|
67
67
|
}
|
|
@@ -87,14 +87,14 @@ function parseServerVersion(value) {
|
|
|
87
87
|
* the username; outer is salted. PG uses lowercase hex everywhere.
|
|
88
88
|
*/
|
|
89
89
|
function md5AuthPayload(user, password, salt) {
|
|
90
|
-
const inner = createHash(
|
|
91
|
-
.update(password + user,
|
|
92
|
-
.digest(
|
|
93
|
-
const outer = createHash(
|
|
94
|
-
.update(inner,
|
|
90
|
+
const inner = createHash("md5")
|
|
91
|
+
.update(password + user, "utf8")
|
|
92
|
+
.digest("hex");
|
|
93
|
+
const outer = createHash("md5")
|
|
94
|
+
.update(inner, "utf8")
|
|
95
95
|
.update(salt)
|
|
96
|
-
.digest(
|
|
97
|
-
return
|
|
96
|
+
.digest("hex");
|
|
97
|
+
return "md5" + outer;
|
|
98
98
|
}
|
|
99
99
|
/**
|
|
100
100
|
* Fisher-Yates in-place shuffle of the candidate hosts list. Used by
|
|
@@ -124,11 +124,11 @@ function shuffleInPlace(arr, rng = Math.random) {
|
|
|
124
124
|
* rather than handing the caller a half-broken connection.
|
|
125
125
|
*/
|
|
126
126
|
async function checkSessionAttrs(conn, tsa) {
|
|
127
|
-
if (tsa === undefined || tsa ===
|
|
127
|
+
if (tsa === undefined || tsa === "any")
|
|
128
128
|
return true;
|
|
129
129
|
let inRecovery;
|
|
130
130
|
try {
|
|
131
|
-
const sets = await conn.execSimple(
|
|
131
|
+
const sets = await conn.execSimple("SELECT pg_is_in_recovery()");
|
|
132
132
|
if (sets.length === 0 || sets[0].rows.length === 0) {
|
|
133
133
|
return false;
|
|
134
134
|
}
|
|
@@ -141,8 +141,8 @@ async function checkSessionAttrs(conn, tsa) {
|
|
|
141
141
|
else if (raw === false) {
|
|
142
142
|
inRecovery = false;
|
|
143
143
|
}
|
|
144
|
-
else if (typeof raw ===
|
|
145
|
-
inRecovery = raw ===
|
|
144
|
+
else if (typeof raw === "string") {
|
|
145
|
+
inRecovery = raw === "t" || raw.toLowerCase() === "true";
|
|
146
146
|
}
|
|
147
147
|
else {
|
|
148
148
|
return false;
|
|
@@ -152,11 +152,11 @@ async function checkSessionAttrs(conn, tsa) {
|
|
|
152
152
|
return false;
|
|
153
153
|
}
|
|
154
154
|
switch (tsa) {
|
|
155
|
-
case
|
|
156
|
-
case
|
|
155
|
+
case "read-write":
|
|
156
|
+
case "primary":
|
|
157
157
|
return !inRecovery;
|
|
158
|
-
case
|
|
159
|
-
case
|
|
158
|
+
case "read-only":
|
|
159
|
+
case "standby":
|
|
160
160
|
return inRecovery;
|
|
161
161
|
// 'prefer-standby' is unwrapped by the orchestrator into two passes
|
|
162
162
|
// ('standby' then 'any'), so we never see it here. Fall through to
|
|
@@ -195,9 +195,9 @@ export class PgConnection {
|
|
|
195
195
|
this.params = new Map();
|
|
196
196
|
this.processId = 0;
|
|
197
197
|
this.secretKey = 0;
|
|
198
|
-
this.txStatus =
|
|
198
|
+
this.txStatus = "I";
|
|
199
199
|
// -- Connection state machine
|
|
200
|
-
this.state =
|
|
200
|
+
this.state = "auth";
|
|
201
201
|
this.pendingQuery = null;
|
|
202
202
|
this.extDriver = null;
|
|
203
203
|
/** True when `pipeline()` has handed out a PipelineSession (WP-21). */
|
|
@@ -285,17 +285,17 @@ export class PgConnection {
|
|
|
285
285
|
this.opts = opts;
|
|
286
286
|
this.channelBindingData = channelBindingData;
|
|
287
287
|
this._password = opts.password ?? null;
|
|
288
|
-
socket.on(
|
|
288
|
+
socket.on("data", (chunk) => {
|
|
289
289
|
this.onData(chunk);
|
|
290
290
|
});
|
|
291
|
-
socket.on(
|
|
291
|
+
socket.on("error", (err) => {
|
|
292
292
|
this.socketError = err;
|
|
293
293
|
this.failPending(err);
|
|
294
294
|
});
|
|
295
|
-
socket.on(
|
|
296
|
-
if (this.state !==
|
|
297
|
-
this.state =
|
|
298
|
-
this.failPending(this.socketError ?? new Error(
|
|
295
|
+
socket.on("close", () => {
|
|
296
|
+
if (this.state !== "closed") {
|
|
297
|
+
this.state = "closed";
|
|
298
|
+
this.failPending(this.socketError ?? new Error("Socket closed"));
|
|
299
299
|
}
|
|
300
300
|
});
|
|
301
301
|
}
|
|
@@ -342,20 +342,20 @@ export class PgConnection {
|
|
|
342
342
|
// the candidate's `address`, and `host` (the user-typed name) is
|
|
343
343
|
// preserved for SNI / `conn.host`. Only the single-host form carries a
|
|
344
344
|
// `hostaddr`; libpq does not support a hostaddr-per-host list here.
|
|
345
|
-
const candidates = opts.hostaddr !== undefined && opts.hostaddr !==
|
|
345
|
+
const candidates = opts.hostaddr !== undefined && opts.hostaddr !== ""
|
|
346
346
|
? seed.map((c) => ({
|
|
347
347
|
host: c.host,
|
|
348
348
|
address: opts.hostaddr,
|
|
349
349
|
port: c.port,
|
|
350
350
|
}))
|
|
351
351
|
: await expandHostsViaDns(seed);
|
|
352
|
-
if (opts.loadBalanceHosts ===
|
|
352
|
+
if (opts.loadBalanceHosts === "random") {
|
|
353
353
|
shuffleInPlace(candidates, PgConnection._loadBalanceRng ?? Math.random);
|
|
354
354
|
}
|
|
355
|
-
const tsa = opts.targetSessionAttrs ??
|
|
355
|
+
const tsa = opts.targetSessionAttrs ?? "any";
|
|
356
356
|
// `prefer-standby` runs two passes: first 'standby', then 'any'. Every
|
|
357
357
|
// other mode runs a single pass with the literal target.
|
|
358
|
-
const passes = tsa ===
|
|
358
|
+
const passes = tsa === "prefer-standby" ? ["standby", "any"] : [tsa];
|
|
359
359
|
let lastErr = null;
|
|
360
360
|
for (const passTsa of passes) {
|
|
361
361
|
for (const candidate of candidates) {
|
|
@@ -394,25 +394,25 @@ export class PgConnection {
|
|
|
394
394
|
if (lastErr instanceof Error)
|
|
395
395
|
throw lastErr;
|
|
396
396
|
let message;
|
|
397
|
-
if (typeof lastErr ===
|
|
397
|
+
if (typeof lastErr === "object" &&
|
|
398
398
|
lastErr !== null &&
|
|
399
|
-
|
|
400
|
-
typeof lastErr.message ===
|
|
399
|
+
"message" in lastErr &&
|
|
400
|
+
typeof lastErr.message === "string") {
|
|
401
401
|
message = lastErr.message;
|
|
402
402
|
}
|
|
403
|
-
else if (typeof lastErr ===
|
|
404
|
-
typeof lastErr ===
|
|
405
|
-
typeof lastErr ===
|
|
403
|
+
else if (typeof lastErr === "string" ||
|
|
404
|
+
typeof lastErr === "number" ||
|
|
405
|
+
typeof lastErr === "boolean") {
|
|
406
406
|
message = String(lastErr);
|
|
407
407
|
}
|
|
408
408
|
else {
|
|
409
|
-
message =
|
|
409
|
+
message = "PgConnection.connect: unknown error";
|
|
410
410
|
}
|
|
411
411
|
const wrapped = new Error(message);
|
|
412
412
|
wrapped.cause = lastErr;
|
|
413
413
|
throw wrapped;
|
|
414
414
|
}
|
|
415
|
-
throw new Error(
|
|
415
|
+
throw new Error("PgConnection.connect: no candidate hosts configured");
|
|
416
416
|
}
|
|
417
417
|
/**
|
|
418
418
|
* Per-host connect attempt: open the socket, negotiate TLS, run the auth
|
|
@@ -432,9 +432,9 @@ export class PgConnection {
|
|
|
432
432
|
// connections. We mirror the early rejection so a misconfigured caller
|
|
433
433
|
// gets a clear diagnostic instead of a confused TLS handshake.
|
|
434
434
|
if (isUnixSocketHost(opts.host) &&
|
|
435
|
-
(opts.ssl ===
|
|
436
|
-
opts.ssl ===
|
|
437
|
-
opts.ssl ===
|
|
435
|
+
(opts.ssl === "require" ||
|
|
436
|
+
opts.ssl === "verify-ca" ||
|
|
437
|
+
opts.ssl === "verify-full")) {
|
|
438
438
|
throw new Error(`sslmode=${opts.ssl} is not supported over Unix-domain sockets (host=${opts.host})`);
|
|
439
439
|
}
|
|
440
440
|
// libpq `requirepeer`: for Unix-domain sockets, libpq verifies the server
|
|
@@ -447,7 +447,7 @@ export class PgConnection {
|
|
|
447
447
|
// connections ignore `requirepeer`, matching libpq.)
|
|
448
448
|
if (isUnixSocketHost(opts.host) &&
|
|
449
449
|
opts.requirepeer !== undefined &&
|
|
450
|
-
opts.requirepeer !==
|
|
450
|
+
opts.requirepeer !== "") {
|
|
451
451
|
throw new Error(`requirepeer="${opts.requirepeer}" cannot be enforced: Node provides no ` +
|
|
452
452
|
`Unix-domain socket peer-credential API; refusing to connect rather ` +
|
|
453
453
|
`than skip the check`);
|
|
@@ -467,11 +467,11 @@ export class PgConnection {
|
|
|
467
467
|
const servername = tlsServername(opts);
|
|
468
468
|
const tlsConnectionOptions = {
|
|
469
469
|
...(servername !== undefined ? { servername } : {}),
|
|
470
|
-
rejectUnauthorized: opts.ssl ===
|
|
470
|
+
rejectUnauthorized: opts.ssl === "verify-ca" || opts.ssl === "verify-full",
|
|
471
471
|
// PG 17+ advertises ALPN for the 'postgresql' protocol; libpq sets
|
|
472
472
|
// this so a future-proof TLS proxy can route on ALPN instead of
|
|
473
473
|
// probing the wire. Always offer it — older servers ignore.
|
|
474
|
-
ALPNProtocols: [
|
|
474
|
+
ALPNProtocols: ["postgresql"],
|
|
475
475
|
// Cipher preference is left to the runtime's TLS library. Our
|
|
476
476
|
// ClientHello offers the byte-identical TLS-1.3 ciphersuite list and
|
|
477
477
|
// order as libpq (AES-256-GCM, ChaCha20, AES-128-GCM), so the suite
|
|
@@ -483,7 +483,7 @@ export class PgConnection {
|
|
|
483
483
|
// is TLS-1.2-only; `ciphersuites`/secureContext are ignored for the
|
|
484
484
|
// client offer), so this is left as-is.
|
|
485
485
|
};
|
|
486
|
-
if (opts.ssl !==
|
|
486
|
+
if (opts.ssl !== "verify-full") {
|
|
487
487
|
tlsConnectionOptions.checkServerIdentity = () => undefined;
|
|
488
488
|
}
|
|
489
489
|
else if (opts.sslsni === false) {
|
|
@@ -500,7 +500,7 @@ export class PgConnection {
|
|
|
500
500
|
// prefer — instead of negotiating it just stays plain. We short-
|
|
501
501
|
// circuit by passing 'disable' to negotiateTls; the caller's
|
|
502
502
|
// requested sslmode is preserved on opts for error reporting.
|
|
503
|
-
isUnixSocketHost(opts.host) ?
|
|
503
|
+
isUnixSocketHost(opts.host) ? "disable" : opts.ssl, tlsConnectionOptions, {
|
|
504
504
|
sslcert: opts.sslcert,
|
|
505
505
|
sslkey: opts.sslkey,
|
|
506
506
|
sslcertmode: opts.sslcertmode,
|
|
@@ -515,9 +515,9 @@ export class PgConnection {
|
|
|
515
515
|
// sslmode 'disable' above) — direct SSL is a TCP-only concept. The
|
|
516
516
|
// ALPN protocol is already on `tlsConnectionOptions` for both paths.
|
|
517
517
|
isUnixSocketHost(opts.host)
|
|
518
|
-
?
|
|
519
|
-
: (opts.sslnegotiation ??
|
|
520
|
-
if (tlsResult.kind ===
|
|
518
|
+
? "postgres"
|
|
519
|
+
: (opts.sslnegotiation ?? "postgres"));
|
|
520
|
+
if (tlsResult.kind === "tls") {
|
|
521
521
|
socket = tlsResult.socket;
|
|
522
522
|
channelBindingData = tlsResult.channelBindingData;
|
|
523
523
|
}
|
|
@@ -584,30 +584,32 @@ export class PgConnection {
|
|
|
584
584
|
*/
|
|
585
585
|
getTlsInfo() {
|
|
586
586
|
const s = this.socket;
|
|
587
|
-
if (typeof s.getCipher !==
|
|
587
|
+
if (typeof s.getCipher !== "function")
|
|
588
588
|
return null;
|
|
589
589
|
try {
|
|
590
590
|
const cipher = s.getCipher();
|
|
591
|
-
const protocol = s.getProtocol?.() ?? cipher.version ??
|
|
591
|
+
const protocol = s.getProtocol?.() ?? cipher.version ?? "unknown";
|
|
592
592
|
if (!cipher.name)
|
|
593
593
|
return null;
|
|
594
594
|
// TLS compression has been disabled by every modern stack since CRIME
|
|
595
595
|
// (2012); Node's TLS doesn't expose a compression accessor, so we
|
|
596
596
|
// always report "off". libpq does the same.
|
|
597
|
-
const compression =
|
|
597
|
+
const compression = "off";
|
|
598
598
|
// Node exposes the negotiated ALPN protocol on TLSSocket.alpnProtocol
|
|
599
599
|
// (string when negotiated, false when not). Postgres 17+ uses
|
|
600
600
|
// 'postgresql' here.
|
|
601
601
|
const alpnRaw = s
|
|
602
602
|
.alpnProtocol;
|
|
603
|
-
const alpn = typeof alpnRaw ===
|
|
603
|
+
const alpn = typeof alpnRaw === "string" && alpnRaw.length > 0
|
|
604
|
+
? alpnRaw
|
|
605
|
+
: null;
|
|
604
606
|
return {
|
|
605
607
|
protocol: String(protocol),
|
|
606
608
|
cipher: cipher.standardName ?? cipher.name,
|
|
607
609
|
standardName: cipher.standardName,
|
|
608
610
|
compression,
|
|
609
611
|
alpn,
|
|
610
|
-
library:
|
|
612
|
+
library: "OpenSSL",
|
|
611
613
|
keyBits: sslKeyBitsFromCipher(cipher.standardName ?? cipher.name),
|
|
612
614
|
};
|
|
613
615
|
}
|
|
@@ -628,7 +630,7 @@ export class PgConnection {
|
|
|
628
630
|
* - `gssapiUsed`: always `false` — we have no GSSAPI support.
|
|
629
631
|
*/
|
|
630
632
|
getConnectionInfo() {
|
|
631
|
-
const remote = this.opts.hostaddr !== undefined && this.opts.hostaddr !==
|
|
633
|
+
const remote = this.opts.hostaddr !== undefined && this.opts.hostaddr !== ""
|
|
632
634
|
? this.opts.hostaddr
|
|
633
635
|
: (this.socket.remoteAddress ?? null);
|
|
634
636
|
return {
|
|
@@ -657,7 +659,7 @@ export class PgConnection {
|
|
|
657
659
|
if (params === undefined) {
|
|
658
660
|
const sets = await this.execSimple(sql);
|
|
659
661
|
if (sets.length === 0) {
|
|
660
|
-
throw new Error(
|
|
662
|
+
throw new Error("PgConnection.query: server returned no result sets");
|
|
661
663
|
}
|
|
662
664
|
return sets[sets.length - 1];
|
|
663
665
|
}
|
|
@@ -672,10 +674,10 @@ export class PgConnection {
|
|
|
672
674
|
const descP = this.enqueueDescribePortalIntoNextExecute();
|
|
673
675
|
const execP = this.enqueueExecute();
|
|
674
676
|
const syncP = this.enqueueSync();
|
|
675
|
-
this.socket.write(Parse(
|
|
676
|
-
this.socket.write(Bind(
|
|
677
|
-
this.socket.write(Describe(
|
|
678
|
-
this.socket.write(Execute(
|
|
677
|
+
this.socket.write(Parse("", sql, []));
|
|
678
|
+
this.socket.write(Bind("", "", [], encoded, [0]));
|
|
679
|
+
this.socket.write(Describe("P", ""));
|
|
680
|
+
this.socket.write(Execute("", 0));
|
|
679
681
|
this.socket.write(Sync());
|
|
680
682
|
let firstErr = null;
|
|
681
683
|
const cap = (e) => {
|
|
@@ -693,7 +695,7 @@ export class PgConnection {
|
|
|
693
695
|
if (firstErr !== null)
|
|
694
696
|
throw asThrowable(firstErr);
|
|
695
697
|
if (result === null) {
|
|
696
|
-
throw new Error(
|
|
698
|
+
throw new Error("PgConnection.query: server returned no result");
|
|
697
699
|
}
|
|
698
700
|
return result;
|
|
699
701
|
}
|
|
@@ -708,7 +710,7 @@ export class PgConnection {
|
|
|
708
710
|
notices: [],
|
|
709
711
|
error: null,
|
|
710
712
|
};
|
|
711
|
-
this.state =
|
|
713
|
+
this.state = "in-query";
|
|
712
714
|
this.socket.write(Query(sql));
|
|
713
715
|
});
|
|
714
716
|
}
|
|
@@ -720,7 +722,7 @@ export class PgConnection {
|
|
|
720
722
|
const descP = this.enqueueDescribeStatement();
|
|
721
723
|
const syncP = this.enqueueSync();
|
|
722
724
|
this.socket.write(Parse(name, sql, oids));
|
|
723
|
-
this.socket.write(Describe(
|
|
725
|
+
this.socket.write(Describe("S", name));
|
|
724
726
|
this.socket.write(Sync());
|
|
725
727
|
let firstErr = null;
|
|
726
728
|
const cap = (e) => {
|
|
@@ -736,7 +738,7 @@ export class PgConnection {
|
|
|
736
738
|
if (firstErr !== null)
|
|
737
739
|
throw asThrowable(firstErr);
|
|
738
740
|
if (descResult === null) {
|
|
739
|
-
throw new Error(
|
|
741
|
+
throw new Error("PgConnection.prepare: server returned no parameter description");
|
|
740
742
|
}
|
|
741
743
|
const { paramOids, fields } = descResult;
|
|
742
744
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
@@ -750,7 +752,7 @@ export class PgConnection {
|
|
|
750
752
|
conn.startExtendedBatch();
|
|
751
753
|
const bP = conn.enqueueBind();
|
|
752
754
|
const sP = conn.enqueueSync();
|
|
753
|
-
conn.socket.write(Bind(
|
|
755
|
+
conn.socket.write(Bind("", name, paramFormats ?? [], encoded, [0]));
|
|
754
756
|
conn.socket.write(Sync());
|
|
755
757
|
let err = null;
|
|
756
758
|
bP.catch((e) => {
|
|
@@ -772,7 +774,7 @@ export class PgConnection {
|
|
|
772
774
|
conn.startExtendedBatch();
|
|
773
775
|
const eP = conn.enqueueExecuteWithFields(fields);
|
|
774
776
|
const sP = conn.enqueueSync();
|
|
775
|
-
conn.socket.write(Execute(
|
|
777
|
+
conn.socket.write(Execute("", maxRows ?? 0));
|
|
776
778
|
conn.socket.write(Sync());
|
|
777
779
|
let err = null;
|
|
778
780
|
let rs = null;
|
|
@@ -789,7 +791,7 @@ export class PgConnection {
|
|
|
789
791
|
if (err !== null)
|
|
790
792
|
throw asThrowable(err);
|
|
791
793
|
if (rs === null) {
|
|
792
|
-
throw new Error(
|
|
794
|
+
throw new Error("PgConnection.prepare.execute: server returned no result");
|
|
793
795
|
}
|
|
794
796
|
return rs;
|
|
795
797
|
},
|
|
@@ -800,8 +802,8 @@ export class PgConnection {
|
|
|
800
802
|
const bP = conn.enqueueBind();
|
|
801
803
|
const eP = conn.enqueueExecuteWithFields(fields);
|
|
802
804
|
const sP = conn.enqueueSync();
|
|
803
|
-
conn.socket.write(Bind(
|
|
804
|
-
conn.socket.write(Execute(
|
|
805
|
+
conn.socket.write(Bind("", name, paramFormats ?? [], encoded, [0]));
|
|
806
|
+
conn.socket.write(Execute("", maxRows ?? 0));
|
|
805
807
|
conn.socket.write(Sync());
|
|
806
808
|
let err = null;
|
|
807
809
|
let rs = null;
|
|
@@ -822,7 +824,7 @@ export class PgConnection {
|
|
|
822
824
|
if (err !== null)
|
|
823
825
|
throw asThrowable(err);
|
|
824
826
|
if (rs === null) {
|
|
825
|
-
throw new Error(
|
|
827
|
+
throw new Error("PgConnection.prepare.bindAndExecute: server returned no result");
|
|
826
828
|
}
|
|
827
829
|
return rs;
|
|
828
830
|
},
|
|
@@ -831,7 +833,7 @@ export class PgConnection {
|
|
|
831
833
|
conn.startExtendedBatch();
|
|
832
834
|
const cP = conn.enqueueClose();
|
|
833
835
|
const sP = conn.enqueueSync();
|
|
834
|
-
conn.socket.write(Close(
|
|
836
|
+
conn.socket.write(Close("S", name));
|
|
835
837
|
conn.socket.write(Sync());
|
|
836
838
|
let err = null;
|
|
837
839
|
cP.catch((e) => {
|
|
@@ -859,7 +861,7 @@ export class PgConnection {
|
|
|
859
861
|
this.startExtendedBatch();
|
|
860
862
|
const cP = this.enqueueClose();
|
|
861
863
|
const sP = this.enqueueSync();
|
|
862
|
-
this.socket.write(Close(
|
|
864
|
+
this.socket.write(Close("S", name));
|
|
863
865
|
this.socket.write(Sync());
|
|
864
866
|
let err = null;
|
|
865
867
|
cP.catch((e) => {
|
|
@@ -881,7 +883,7 @@ export class PgConnection {
|
|
|
881
883
|
// (see handleCopyStartMessage) for any path that bypasses this check.
|
|
882
884
|
if (this._extPipelineActive) {
|
|
883
885
|
this.abortForCopyInPipeline();
|
|
884
|
-
return Promise.reject(Object.assign(new Error(
|
|
886
|
+
return Promise.reject(Object.assign(new Error("COPY in a pipeline is not supported, aborting connection"), { severity: "FATAL" }));
|
|
885
887
|
}
|
|
886
888
|
// The driver waits in `in-query` state until CopyInResponse arrives — at
|
|
887
889
|
// which point the protocol switches and we move to `in-copy-in`. The
|
|
@@ -900,7 +902,7 @@ export class PgConnection {
|
|
|
900
902
|
resolve(this.makeCopyInStream());
|
|
901
903
|
};
|
|
902
904
|
this.copyStartReject = reject;
|
|
903
|
-
this.state =
|
|
905
|
+
this.state = "in-query";
|
|
904
906
|
this.socket.write(Query(sql));
|
|
905
907
|
});
|
|
906
908
|
}
|
|
@@ -908,7 +910,7 @@ export class PgConnection {
|
|
|
908
910
|
this.ensureIdle();
|
|
909
911
|
if (this._extPipelineActive) {
|
|
910
912
|
this.abortForCopyInPipeline();
|
|
911
|
-
return Promise.reject(Object.assign(new Error(
|
|
913
|
+
return Promise.reject(Object.assign(new Error("COPY in a pipeline is not supported, aborting connection"), { severity: "FATAL" }));
|
|
912
914
|
}
|
|
913
915
|
return new Promise((resolve, reject) => {
|
|
914
916
|
this.copyOut = {
|
|
@@ -924,7 +926,7 @@ export class PgConnection {
|
|
|
924
926
|
resolve(this.makeCopyOutStream());
|
|
925
927
|
};
|
|
926
928
|
this.copyStartReject = reject;
|
|
927
|
-
this.state =
|
|
929
|
+
this.state = "in-query";
|
|
928
930
|
this.socket.write(Query(sql));
|
|
929
931
|
});
|
|
930
932
|
}
|
|
@@ -956,10 +958,10 @@ export class PgConnection {
|
|
|
956
958
|
// In-copy-in: send CopyFail on the live socket so the server returns
|
|
957
959
|
// to ReadyForQuery cleanly. This is the same abort path the upstream
|
|
958
960
|
// SIGINT handler in `copy.c::handleCopyIn` triggers via longjmp.
|
|
959
|
-
if (this.state ===
|
|
961
|
+
if (this.state === "in-copy-in" && this.copyIn && !this.copyIn.closed) {
|
|
960
962
|
this.copyIn.closed = true;
|
|
961
963
|
try {
|
|
962
|
-
this.socket.write(CopyFail(
|
|
964
|
+
this.socket.write(CopyFail("canceled by user"));
|
|
963
965
|
}
|
|
964
966
|
catch {
|
|
965
967
|
// Socket may have died — failPending() will surface that.
|
|
@@ -978,7 +980,7 @@ export class PgConnection {
|
|
|
978
980
|
// Honour `hostaddr` on the cancel connection too: dial the fixed IP while
|
|
979
981
|
// keeping the user-typed host for SNI / cert verification, mirroring the
|
|
980
982
|
// primary connect path's `addressOverride`.
|
|
981
|
-
const cancelSocket = await openSocket(this.opts, this.opts.hostaddr !== undefined && this.opts.hostaddr !==
|
|
983
|
+
const cancelSocket = await openSocket(this.opts, this.opts.hostaddr !== undefined && this.opts.hostaddr !== ""
|
|
982
984
|
? this.opts.hostaddr
|
|
983
985
|
: undefined);
|
|
984
986
|
let writeSocket = cancelSocket;
|
|
@@ -990,10 +992,11 @@ export class PgConnection {
|
|
|
990
992
|
...(cancelServername !== undefined
|
|
991
993
|
? { servername: cancelServername }
|
|
992
994
|
: {}),
|
|
993
|
-
rejectUnauthorized: this.opts.ssl ===
|
|
994
|
-
|
|
995
|
+
rejectUnauthorized: this.opts.ssl === "verify-ca" ||
|
|
996
|
+
this.opts.ssl === "verify-full",
|
|
997
|
+
ALPNProtocols: ["postgresql"],
|
|
995
998
|
};
|
|
996
|
-
if (this.opts.ssl !==
|
|
999
|
+
if (this.opts.ssl !== "verify-full") {
|
|
997
1000
|
cancelTlsOpts.checkServerIdentity = () => undefined;
|
|
998
1001
|
}
|
|
999
1002
|
else if (this.opts.sslsni === false) {
|
|
@@ -1003,7 +1006,7 @@ export class PgConnection {
|
|
|
1003
1006
|
applyTlsProtocolVersionRange(cancelTlsOpts, this.opts);
|
|
1004
1007
|
const t = await negotiateTls(cancelSocket,
|
|
1005
1008
|
// Unix-domain socket: no TLS, regardless of caller's sslmode.
|
|
1006
|
-
isUnixSocketHost(this.opts.host) ?
|
|
1009
|
+
isUnixSocketHost(this.opts.host) ? "disable" : this.opts.ssl, cancelTlsOpts, {
|
|
1007
1010
|
sslcert: this.opts.sslcert,
|
|
1008
1011
|
sslkey: this.opts.sslkey,
|
|
1009
1012
|
sslcertmode: this.opts.sslcertmode,
|
|
@@ -1016,9 +1019,9 @@ export class PgConnection {
|
|
|
1016
1019
|
// Mirror the primary connect path's negotiation mode so a server
|
|
1017
1020
|
// configured for direct SSL also accepts the cancel connection.
|
|
1018
1021
|
isUnixSocketHost(this.opts.host)
|
|
1019
|
-
?
|
|
1020
|
-
: (this.opts.sslnegotiation ??
|
|
1021
|
-
writeSocket = t.kind ===
|
|
1022
|
+
? "postgres"
|
|
1023
|
+
: (this.opts.sslnegotiation ?? "postgres"));
|
|
1024
|
+
writeSocket = t.kind === "tls" ? t.socket : t.socket;
|
|
1022
1025
|
await new Promise((resolve, reject) => {
|
|
1023
1026
|
writeSocket.write(CancelRequest(this.processId, this.secretKey), (err) => {
|
|
1024
1027
|
if (err)
|
|
@@ -1051,8 +1054,8 @@ export class PgConnection {
|
|
|
1051
1054
|
// backslash, use the E'...' escape-string syntax so backslashes don't
|
|
1052
1055
|
// depend on `standard_conforming_strings`.
|
|
1053
1056
|
const doubled = value.replace(/'/g, "''");
|
|
1054
|
-
if (value.includes(
|
|
1055
|
-
return "E'" + doubled.replace(/\\/g,
|
|
1057
|
+
if (value.includes("\\")) {
|
|
1058
|
+
return "E'" + doubled.replace(/\\/g, "\\\\") + "'";
|
|
1056
1059
|
}
|
|
1057
1060
|
return "'" + doubled + "'";
|
|
1058
1061
|
}
|
|
@@ -1079,7 +1082,7 @@ export class PgConnection {
|
|
|
1079
1082
|
return this.notify.onNotification(handler);
|
|
1080
1083
|
}
|
|
1081
1084
|
async close() {
|
|
1082
|
-
if (this.state ===
|
|
1085
|
+
if (this.state === "closed")
|
|
1083
1086
|
return;
|
|
1084
1087
|
try {
|
|
1085
1088
|
this.socket.write(Terminate());
|
|
@@ -1087,10 +1090,10 @@ export class PgConnection {
|
|
|
1087
1090
|
catch {
|
|
1088
1091
|
// socket may already be dead; we still want to mark closed
|
|
1089
1092
|
}
|
|
1090
|
-
this.state =
|
|
1093
|
+
this.state = "closed";
|
|
1091
1094
|
this.notify.clear();
|
|
1092
1095
|
await new Promise((resolve) => {
|
|
1093
|
-
this.socket.once(
|
|
1096
|
+
this.socket.once("close", () => {
|
|
1094
1097
|
resolve();
|
|
1095
1098
|
});
|
|
1096
1099
|
try {
|
|
@@ -1102,7 +1105,7 @@ export class PgConnection {
|
|
|
1102
1105
|
});
|
|
1103
1106
|
}
|
|
1104
1107
|
isClosed() {
|
|
1105
|
-
return this.state ===
|
|
1108
|
+
return this.state === "closed";
|
|
1106
1109
|
}
|
|
1107
1110
|
// -------------------------------------------------------------------------
|
|
1108
1111
|
// Startup / auth state machine
|
|
@@ -1113,7 +1116,7 @@ export class PgConnection {
|
|
|
1113
1116
|
user: this.opts.user,
|
|
1114
1117
|
database: this.opts.database,
|
|
1115
1118
|
// psql sends client_encoding=UTF8 by default; we follow.
|
|
1116
|
-
client_encoding: this.opts.clientEncoding ??
|
|
1119
|
+
client_encoding: this.opts.clientEncoding ?? "UTF8",
|
|
1117
1120
|
};
|
|
1118
1121
|
if (this.opts.applicationName !== undefined) {
|
|
1119
1122
|
params.application_name = this.opts.applicationName;
|
|
@@ -1156,15 +1159,16 @@ export class PgConnection {
|
|
|
1156
1159
|
}
|
|
1157
1160
|
handleAuthMessage(msg) {
|
|
1158
1161
|
switch (msg.type) {
|
|
1159
|
-
case
|
|
1162
|
+
case "AuthenticationOk": {
|
|
1160
1163
|
// libpq parity: channel_binding=require demands that some prior
|
|
1161
1164
|
// auth step actually negotiated channel binding. A bare
|
|
1162
1165
|
// AuthenticationOk after no challenge ("trust") or after a cert
|
|
1163
1166
|
// exchange ("cert" HBA, clientcert=verify-full) means no SCRAM
|
|
1164
1167
|
// happened and we must refuse.
|
|
1165
|
-
if (this.opts.channelBinding ===
|
|
1166
|
-
(this.scram === null ||
|
|
1167
|
-
|
|
1168
|
+
if (this.opts.channelBinding === "require" &&
|
|
1169
|
+
(this.scram === null ||
|
|
1170
|
+
this.scram.mechanism !== "SCRAM-SHA-256-PLUS")) {
|
|
1171
|
+
this.failStartup(new Error("channel binding required, but server authenticated client without channel binding"));
|
|
1168
1172
|
return;
|
|
1169
1173
|
}
|
|
1170
1174
|
// Mutual-auth integrity: if a SCRAM exchange was started it MUST have
|
|
@@ -1172,45 +1176,45 @@ export class PgConnection {
|
|
|
1172
1176
|
// verified). A server that sends SASLContinue then jumps to
|
|
1173
1177
|
// AuthenticationOk never proves it knows the password (review #8).
|
|
1174
1178
|
if (this.scram !== null && !this.saslFinalSeen) {
|
|
1175
|
-
this.failStartup(new Error(
|
|
1176
|
-
|
|
1179
|
+
this.failStartup(new Error("server sent AuthenticationOk without completing SCRAM " +
|
|
1180
|
+
"authentication (server signature not verified)"));
|
|
1177
1181
|
return;
|
|
1178
1182
|
}
|
|
1179
1183
|
// require_auth=none allows trust auth; anything else is rejected
|
|
1180
1184
|
// here. If a prior challenge was sent and validated, skip — that
|
|
1181
1185
|
// method was already accepted by the check at its own branch.
|
|
1182
|
-
if (!this.authChallengeSeen && !this.checkRequireAuth(
|
|
1186
|
+
if (!this.authChallengeSeen && !this.checkRequireAuth("none")) {
|
|
1183
1187
|
return;
|
|
1184
1188
|
}
|
|
1185
|
-
this.state =
|
|
1189
|
+
this.state = "await-ready";
|
|
1186
1190
|
return;
|
|
1187
1191
|
}
|
|
1188
|
-
case
|
|
1192
|
+
case "AuthenticationCleartextPassword": {
|
|
1189
1193
|
this.authChallengeSeen = true;
|
|
1190
|
-
if (this.opts.channelBinding ===
|
|
1194
|
+
if (this.opts.channelBinding === "require") {
|
|
1191
1195
|
this.failStartup(new Error("channel binding required but not supported by server's authentication request"));
|
|
1192
1196
|
return;
|
|
1193
1197
|
}
|
|
1194
|
-
if (!this.checkRequireAuth(
|
|
1198
|
+
if (!this.checkRequireAuth("password"))
|
|
1195
1199
|
return;
|
|
1196
1200
|
if (this.opts.password === undefined) {
|
|
1197
|
-
this.failStartup(new Error(
|
|
1201
|
+
this.failStartup(new Error("Server requested cleartext password but no password was provided"));
|
|
1198
1202
|
return;
|
|
1199
1203
|
}
|
|
1200
1204
|
this.passwordUsed = true;
|
|
1201
1205
|
this.socket.write(PasswordMessage(this.opts.password));
|
|
1202
1206
|
return;
|
|
1203
1207
|
}
|
|
1204
|
-
case
|
|
1208
|
+
case "AuthenticationMD5Password": {
|
|
1205
1209
|
this.authChallengeSeen = true;
|
|
1206
|
-
if (this.opts.channelBinding ===
|
|
1210
|
+
if (this.opts.channelBinding === "require") {
|
|
1207
1211
|
this.failStartup(new Error("channel binding required but not supported by server's authentication request"));
|
|
1208
1212
|
return;
|
|
1209
1213
|
}
|
|
1210
|
-
if (!this.checkRequireAuth(
|
|
1214
|
+
if (!this.checkRequireAuth("md5"))
|
|
1211
1215
|
return;
|
|
1212
1216
|
if (this.opts.password === undefined) {
|
|
1213
|
-
this.failStartup(new Error(
|
|
1217
|
+
this.failStartup(new Error("Server requested MD5 password but no password was provided"));
|
|
1214
1218
|
return;
|
|
1215
1219
|
}
|
|
1216
1220
|
const payload = md5AuthPayload(this.opts.user, this.opts.password, msg.salt);
|
|
@@ -1218,24 +1222,24 @@ export class PgConnection {
|
|
|
1218
1222
|
this.socket.write(PasswordMessage(payload));
|
|
1219
1223
|
return;
|
|
1220
1224
|
}
|
|
1221
|
-
case
|
|
1225
|
+
case "AuthenticationSASL": {
|
|
1222
1226
|
this.authChallengeSeen = true;
|
|
1223
1227
|
if (this.opts.password === undefined) {
|
|
1224
|
-
this.failStartup(new Error(
|
|
1228
|
+
this.failStartup(new Error("Server requested SASL auth but no password was provided"));
|
|
1225
1229
|
return;
|
|
1226
1230
|
}
|
|
1227
|
-
if (!this.checkRequireAuth(
|
|
1231
|
+
if (!this.checkRequireAuth("scram-sha-256"))
|
|
1228
1232
|
return;
|
|
1229
1233
|
// channel_binding=require AND server didn't offer the PLUS
|
|
1230
1234
|
// variant — refuse before the SASL handshake starts. The check
|
|
1231
1235
|
// is split between here (no PLUS in the mechanism list) and
|
|
1232
1236
|
// chooseMechanism's fallback (PLUS present but no binding data).
|
|
1233
|
-
if (this.opts.channelBinding ===
|
|
1234
|
-
!msg.mechanisms.includes(
|
|
1237
|
+
if (this.opts.channelBinding === "require" &&
|
|
1238
|
+
!msg.mechanisms.includes("SCRAM-SHA-256-PLUS")) {
|
|
1235
1239
|
this.failStartup(new Error("channel binding required but not supported by server's authentication request"));
|
|
1236
1240
|
return;
|
|
1237
1241
|
}
|
|
1238
|
-
if (this.opts.channelBinding ===
|
|
1242
|
+
if (this.opts.channelBinding === "require" &&
|
|
1239
1243
|
this.channelBindingData === null) {
|
|
1240
1244
|
this.failStartup(new Error("channel binding required but not supported by server's authentication request"));
|
|
1241
1245
|
return;
|
|
@@ -1246,9 +1250,9 @@ export class PgConnection {
|
|
|
1246
1250
|
password: this.opts.password,
|
|
1247
1251
|
mechanisms: msg.mechanisms,
|
|
1248
1252
|
channelBinding: this.channelBindingData !== null &&
|
|
1249
|
-
this.opts.channelBinding !==
|
|
1253
|
+
this.opts.channelBinding !== "disable"
|
|
1250
1254
|
? {
|
|
1251
|
-
type:
|
|
1255
|
+
type: "tls-server-end-point",
|
|
1252
1256
|
data: this.channelBindingData,
|
|
1253
1257
|
}
|
|
1254
1258
|
: undefined,
|
|
@@ -1262,9 +1266,9 @@ export class PgConnection {
|
|
|
1262
1266
|
}
|
|
1263
1267
|
return;
|
|
1264
1268
|
}
|
|
1265
|
-
case
|
|
1269
|
+
case "AuthenticationSASLContinue": {
|
|
1266
1270
|
if (!this.scram) {
|
|
1267
|
-
this.failStartup(new Error(
|
|
1271
|
+
this.failStartup(new Error("Received AuthenticationSASLContinue without an active SCRAM client"));
|
|
1268
1272
|
return;
|
|
1269
1273
|
}
|
|
1270
1274
|
try {
|
|
@@ -1276,9 +1280,9 @@ export class PgConnection {
|
|
|
1276
1280
|
}
|
|
1277
1281
|
return;
|
|
1278
1282
|
}
|
|
1279
|
-
case
|
|
1283
|
+
case "AuthenticationSASLFinal": {
|
|
1280
1284
|
if (!this.scram) {
|
|
1281
|
-
this.failStartup(new Error(
|
|
1285
|
+
this.failStartup(new Error("Received AuthenticationSASLFinal without an active SCRAM client"));
|
|
1282
1286
|
return;
|
|
1283
1287
|
}
|
|
1284
1288
|
try {
|
|
@@ -1290,10 +1294,10 @@ export class PgConnection {
|
|
|
1290
1294
|
}
|
|
1291
1295
|
return;
|
|
1292
1296
|
}
|
|
1293
|
-
case
|
|
1297
|
+
case "ErrorResponse":
|
|
1294
1298
|
this.failStartup(fieldsToConnectError(msg.fields));
|
|
1295
1299
|
return;
|
|
1296
|
-
case
|
|
1300
|
+
case "NoticeResponse":
|
|
1297
1301
|
this.notify.emit(fieldsToNotice(msg.fields));
|
|
1298
1302
|
return;
|
|
1299
1303
|
default:
|
|
@@ -1306,19 +1310,19 @@ export class PgConnection {
|
|
|
1306
1310
|
}
|
|
1307
1311
|
handleAwaitReady(msg) {
|
|
1308
1312
|
switch (msg.type) {
|
|
1309
|
-
case
|
|
1313
|
+
case "ParameterStatus":
|
|
1310
1314
|
this.params.set(msg.name, msg.value);
|
|
1311
|
-
if (msg.name ===
|
|
1315
|
+
if (msg.name === "server_version") {
|
|
1312
1316
|
this.serverVersion = parseServerVersion(msg.value);
|
|
1313
1317
|
}
|
|
1314
1318
|
return;
|
|
1315
|
-
case
|
|
1319
|
+
case "BackendKeyData":
|
|
1316
1320
|
this.processId = msg.processId;
|
|
1317
1321
|
this.secretKey = msg.secretKey;
|
|
1318
1322
|
return;
|
|
1319
|
-
case
|
|
1323
|
+
case "ReadyForQuery":
|
|
1320
1324
|
this.txStatus = msg.status;
|
|
1321
|
-
this.state =
|
|
1325
|
+
this.state = "idle";
|
|
1322
1326
|
if (this.startupResolve) {
|
|
1323
1327
|
const r = this.startupResolve;
|
|
1324
1328
|
this.startupResolve = null;
|
|
@@ -1326,10 +1330,10 @@ export class PgConnection {
|
|
|
1326
1330
|
r();
|
|
1327
1331
|
}
|
|
1328
1332
|
return;
|
|
1329
|
-
case
|
|
1333
|
+
case "ErrorResponse":
|
|
1330
1334
|
this.failStartup(fieldsToConnectError(msg.fields));
|
|
1331
1335
|
return;
|
|
1332
|
-
case
|
|
1336
|
+
case "NoticeResponse":
|
|
1333
1337
|
this.notify.emit(fieldsToNotice(msg.fields));
|
|
1334
1338
|
return;
|
|
1335
1339
|
default:
|
|
@@ -1350,7 +1354,7 @@ export class PgConnection {
|
|
|
1350
1354
|
catch {
|
|
1351
1355
|
// ignore
|
|
1352
1356
|
}
|
|
1353
|
-
this.state =
|
|
1357
|
+
this.state = "closed";
|
|
1354
1358
|
}
|
|
1355
1359
|
// -------------------------------------------------------------------------
|
|
1356
1360
|
// COPY state machine (WP-16).
|
|
@@ -1370,17 +1374,19 @@ export class PgConnection {
|
|
|
1370
1374
|
makeCopyInStream() {
|
|
1371
1375
|
const driver = this.copyIn;
|
|
1372
1376
|
if (!driver) {
|
|
1373
|
-
throw new Error(
|
|
1377
|
+
throw new Error("PgConnection: makeCopyInStream called without driver");
|
|
1374
1378
|
}
|
|
1375
1379
|
return {
|
|
1376
1380
|
write: (chunk) => {
|
|
1377
|
-
if (this.state ===
|
|
1378
|
-
return Promise.reject(new Error(
|
|
1381
|
+
if (this.state === "closed") {
|
|
1382
|
+
return Promise.reject(new Error("Connection closed"));
|
|
1379
1383
|
}
|
|
1380
1384
|
if (driver.closed) {
|
|
1381
|
-
return Promise.reject(new Error(
|
|
1385
|
+
return Promise.reject(new Error("CopyInStream already closed"));
|
|
1382
1386
|
}
|
|
1383
|
-
const data = typeof chunk ===
|
|
1387
|
+
const data = typeof chunk === "string"
|
|
1388
|
+
? Buffer.from(chunk, "utf8")
|
|
1389
|
+
: chunk;
|
|
1384
1390
|
return new Promise((resolve, reject) => {
|
|
1385
1391
|
this.socket.write(CopyData(data), (err) => {
|
|
1386
1392
|
if (err)
|
|
@@ -1392,7 +1398,7 @@ export class PgConnection {
|
|
|
1392
1398
|
},
|
|
1393
1399
|
end: () => {
|
|
1394
1400
|
if (driver.closed) {
|
|
1395
|
-
return Promise.reject(new Error(
|
|
1401
|
+
return Promise.reject(new Error("CopyInStream already closed"));
|
|
1396
1402
|
}
|
|
1397
1403
|
driver.closed = true;
|
|
1398
1404
|
return new Promise((resolve, reject) => {
|
|
@@ -1403,7 +1409,7 @@ export class PgConnection {
|
|
|
1403
1409
|
},
|
|
1404
1410
|
fail: (reason) => {
|
|
1405
1411
|
if (driver.closed) {
|
|
1406
|
-
return Promise.reject(new Error(
|
|
1412
|
+
return Promise.reject(new Error("CopyInStream already closed"));
|
|
1407
1413
|
}
|
|
1408
1414
|
driver.closed = true;
|
|
1409
1415
|
return new Promise((resolve, reject) => {
|
|
@@ -1422,11 +1428,11 @@ export class PgConnection {
|
|
|
1422
1428
|
makeCopyOutStream() {
|
|
1423
1429
|
const driver = this.copyOut;
|
|
1424
1430
|
if (!driver) {
|
|
1425
|
-
throw new Error(
|
|
1431
|
+
throw new Error("PgConnection: makeCopyOutStream called without driver");
|
|
1426
1432
|
}
|
|
1427
1433
|
// Capture the state-getter as a closure so the iterator can observe
|
|
1428
1434
|
// connection close without holding a `this` alias (no-this-alias rule).
|
|
1429
|
-
const isClosed = () => this.state ===
|
|
1435
|
+
const isClosed = () => this.state === "closed";
|
|
1430
1436
|
// Resume reading once the buffered data drains — paired with the
|
|
1431
1437
|
// pause() the CopyData handler applies at the high-water mark (#11).
|
|
1432
1438
|
const resumeSocket = () => {
|
|
@@ -1451,14 +1457,15 @@ export class PgConnection {
|
|
|
1451
1457
|
// wants a real Error. Wrap once before throwing.
|
|
1452
1458
|
const ce = driver.error;
|
|
1453
1459
|
const wrapped = new Error(ce.message);
|
|
1454
|
-
wrapped.cause =
|
|
1460
|
+
wrapped.cause =
|
|
1461
|
+
ce;
|
|
1455
1462
|
throw wrapped;
|
|
1456
1463
|
}
|
|
1457
1464
|
if (driver.done) {
|
|
1458
1465
|
return { value: undefined, done: true };
|
|
1459
1466
|
}
|
|
1460
1467
|
if (isClosed()) {
|
|
1461
|
-
throw new Error(
|
|
1468
|
+
throw new Error("Connection closed mid-COPY-OUT");
|
|
1462
1469
|
}
|
|
1463
1470
|
await new Promise((resolve) => {
|
|
1464
1471
|
driver.waker = resolve;
|
|
@@ -1476,7 +1483,10 @@ export class PgConnection {
|
|
|
1476
1483
|
driver.queue.length = 0;
|
|
1477
1484
|
driver.queuedBytes = 0;
|
|
1478
1485
|
resumeSocket();
|
|
1479
|
-
return Promise.resolve({
|
|
1486
|
+
return Promise.resolve({
|
|
1487
|
+
value: undefined,
|
|
1488
|
+
done: true,
|
|
1489
|
+
});
|
|
1480
1490
|
},
|
|
1481
1491
|
};
|
|
1482
1492
|
},
|
|
@@ -1489,7 +1499,7 @@ export class PgConnection {
|
|
|
1489
1499
|
*/
|
|
1490
1500
|
handleCopyStartMessage(msg) {
|
|
1491
1501
|
switch (msg.type) {
|
|
1492
|
-
case
|
|
1502
|
+
case "CopyInResponse":
|
|
1493
1503
|
// COPY-in-pipeline: libpq aborts the connection with this exact
|
|
1494
1504
|
// diagnostic (matching upstream psql's behaviour). The `\copy`
|
|
1495
1505
|
// command layer detects pipeline-active and fails fast before
|
|
@@ -1500,7 +1510,7 @@ export class PgConnection {
|
|
|
1500
1510
|
return;
|
|
1501
1511
|
}
|
|
1502
1512
|
if (this.copyIn) {
|
|
1503
|
-
this.state =
|
|
1513
|
+
this.state = "in-copy-in";
|
|
1504
1514
|
const r = this.copyStartResolve;
|
|
1505
1515
|
this.copyStartResolve = null;
|
|
1506
1516
|
this.copyStartReject = null;
|
|
@@ -1508,13 +1518,13 @@ export class PgConnection {
|
|
|
1508
1518
|
r();
|
|
1509
1519
|
}
|
|
1510
1520
|
return;
|
|
1511
|
-
case
|
|
1521
|
+
case "CopyOutResponse":
|
|
1512
1522
|
if (this._extPipelineActive) {
|
|
1513
1523
|
this.abortForCopyInPipeline();
|
|
1514
1524
|
return;
|
|
1515
1525
|
}
|
|
1516
1526
|
if (this.copyOut) {
|
|
1517
|
-
this.state =
|
|
1527
|
+
this.state = "in-copy-out";
|
|
1518
1528
|
const r = this.copyStartResolve;
|
|
1519
1529
|
this.copyStartResolve = null;
|
|
1520
1530
|
this.copyStartReject = null;
|
|
@@ -1522,7 +1532,7 @@ export class PgConnection {
|
|
|
1522
1532
|
r();
|
|
1523
1533
|
}
|
|
1524
1534
|
return;
|
|
1525
|
-
case
|
|
1535
|
+
case "ErrorResponse": {
|
|
1526
1536
|
const err = fieldsToConnectError(msg.fields);
|
|
1527
1537
|
if (this.copyIn) {
|
|
1528
1538
|
if (this.copyStartReject)
|
|
@@ -1541,17 +1551,17 @@ export class PgConnection {
|
|
|
1541
1551
|
// Stay in `in-query` until ReadyForQuery, then return to idle below.
|
|
1542
1552
|
return;
|
|
1543
1553
|
}
|
|
1544
|
-
case
|
|
1554
|
+
case "ReadyForQuery":
|
|
1545
1555
|
// ReadyForQuery without a prior CopyXxxResponse means the server
|
|
1546
1556
|
// immediately rejected the COPY (we surfaced the ErrorResponse just
|
|
1547
1557
|
// above) — return to idle so the next command can fire.
|
|
1548
1558
|
this.txStatus = msg.status;
|
|
1549
|
-
this.state =
|
|
1559
|
+
this.state = "idle";
|
|
1550
1560
|
return;
|
|
1551
|
-
case
|
|
1561
|
+
case "NoticeResponse":
|
|
1552
1562
|
this.notify.emit(fieldsToNotice(msg.fields));
|
|
1553
1563
|
return;
|
|
1554
|
-
case
|
|
1564
|
+
case "ParameterStatus":
|
|
1555
1565
|
this.params.set(msg.name, msg.value);
|
|
1556
1566
|
return;
|
|
1557
1567
|
default:
|
|
@@ -1568,22 +1578,22 @@ export class PgConnection {
|
|
|
1568
1578
|
if (!driver)
|
|
1569
1579
|
return;
|
|
1570
1580
|
switch (msg.type) {
|
|
1571
|
-
case
|
|
1581
|
+
case "CommandComplete":
|
|
1572
1582
|
driver.commandTag = msg.tag;
|
|
1573
1583
|
this.lastCopyTag = msg.tag;
|
|
1574
1584
|
return;
|
|
1575
|
-
case
|
|
1585
|
+
case "ErrorResponse":
|
|
1576
1586
|
driver.error = fieldsToConnectError(msg.fields);
|
|
1577
1587
|
return;
|
|
1578
|
-
case
|
|
1588
|
+
case "NoticeResponse":
|
|
1579
1589
|
this.notify.emit(fieldsToNotice(msg.fields));
|
|
1580
1590
|
return;
|
|
1581
|
-
case
|
|
1591
|
+
case "ParameterStatus":
|
|
1582
1592
|
this.params.set(msg.name, msg.value);
|
|
1583
1593
|
return;
|
|
1584
|
-
case
|
|
1594
|
+
case "ReadyForQuery":
|
|
1585
1595
|
this.txStatus = msg.status;
|
|
1586
|
-
this.state =
|
|
1596
|
+
this.state = "idle";
|
|
1587
1597
|
this.copyIn = null;
|
|
1588
1598
|
if (driver.error) {
|
|
1589
1599
|
if (driver.rejectDone)
|
|
@@ -1597,7 +1607,7 @@ export class PgConnection {
|
|
|
1597
1607
|
// Unknown messages mid-COPY-IN are protocol errors; record and let
|
|
1598
1608
|
// the trailing ReadyForQuery flush the state.
|
|
1599
1609
|
driver.error = {
|
|
1600
|
-
severity:
|
|
1610
|
+
severity: "ERROR",
|
|
1601
1611
|
message: `Unexpected backend message during COPY IN: ${msg.type}`,
|
|
1602
1612
|
};
|
|
1603
1613
|
return;
|
|
@@ -1608,7 +1618,7 @@ export class PgConnection {
|
|
|
1608
1618
|
if (!driver)
|
|
1609
1619
|
return;
|
|
1610
1620
|
switch (msg.type) {
|
|
1611
|
-
case
|
|
1621
|
+
case "CopyData": {
|
|
1612
1622
|
// Consumer broke early: drop instead of buffering (review item #11).
|
|
1613
1623
|
if (driver.abandoned)
|
|
1614
1624
|
return;
|
|
@@ -1627,27 +1637,27 @@ export class PgConnection {
|
|
|
1627
1637
|
}
|
|
1628
1638
|
return;
|
|
1629
1639
|
}
|
|
1630
|
-
case
|
|
1640
|
+
case "CopyDone":
|
|
1631
1641
|
// Server signals it's done sending — we now expect CommandComplete +
|
|
1632
1642
|
// ReadyForQuery. Stay in in-copy-out until ReadyForQuery; the queue
|
|
1633
1643
|
// may still drain via the consumer.
|
|
1634
1644
|
return;
|
|
1635
|
-
case
|
|
1645
|
+
case "CommandComplete":
|
|
1636
1646
|
driver.commandTag = msg.tag;
|
|
1637
1647
|
this.lastCopyTag = msg.tag;
|
|
1638
1648
|
return;
|
|
1639
|
-
case
|
|
1649
|
+
case "ErrorResponse":
|
|
1640
1650
|
driver.error = fieldsToConnectError(msg.fields);
|
|
1641
1651
|
return;
|
|
1642
|
-
case
|
|
1652
|
+
case "NoticeResponse":
|
|
1643
1653
|
this.notify.emit(fieldsToNotice(msg.fields));
|
|
1644
1654
|
return;
|
|
1645
|
-
case
|
|
1655
|
+
case "ParameterStatus":
|
|
1646
1656
|
this.params.set(msg.name, msg.value);
|
|
1647
1657
|
return;
|
|
1648
|
-
case
|
|
1658
|
+
case "ReadyForQuery":
|
|
1649
1659
|
this.txStatus = msg.status;
|
|
1650
|
-
this.state =
|
|
1660
|
+
this.state = "idle";
|
|
1651
1661
|
driver.done = true;
|
|
1652
1662
|
this.copyOut = null;
|
|
1653
1663
|
if (driver.waker) {
|
|
@@ -1658,7 +1668,7 @@ export class PgConnection {
|
|
|
1658
1668
|
return;
|
|
1659
1669
|
default:
|
|
1660
1670
|
driver.error = {
|
|
1661
|
-
severity:
|
|
1671
|
+
severity: "ERROR",
|
|
1662
1672
|
message: `Unexpected backend message during COPY OUT: ${msg.type}`,
|
|
1663
1673
|
};
|
|
1664
1674
|
if (driver.waker) {
|
|
@@ -1681,9 +1691,9 @@ export class PgConnection {
|
|
|
1681
1691
|
return;
|
|
1682
1692
|
}
|
|
1683
1693
|
switch (msg.type) {
|
|
1684
|
-
case
|
|
1694
|
+
case "RowDescription":
|
|
1685
1695
|
q.current = {
|
|
1686
|
-
command:
|
|
1696
|
+
command: "",
|
|
1687
1697
|
rowCount: null,
|
|
1688
1698
|
oid: null,
|
|
1689
1699
|
fields: msg.fields,
|
|
@@ -1691,12 +1701,12 @@ export class PgConnection {
|
|
|
1691
1701
|
notices: [],
|
|
1692
1702
|
};
|
|
1693
1703
|
return;
|
|
1694
|
-
case
|
|
1704
|
+
case "DataRow": {
|
|
1695
1705
|
if (!q.current) {
|
|
1696
1706
|
// Server sent rows without a prior description — extremely rare
|
|
1697
1707
|
// (only for some legacy COPY error paths). Treat as empty desc.
|
|
1698
1708
|
q.current = {
|
|
1699
|
-
command:
|
|
1709
|
+
command: "",
|
|
1700
1710
|
rowCount: null,
|
|
1701
1711
|
oid: null,
|
|
1702
1712
|
fields: [],
|
|
@@ -1707,7 +1717,7 @@ export class PgConnection {
|
|
|
1707
1717
|
q.current.rows.push(decodeDataRow(msg.values, q.current.fields));
|
|
1708
1718
|
return;
|
|
1709
1719
|
}
|
|
1710
|
-
case
|
|
1720
|
+
case "CommandComplete": {
|
|
1711
1721
|
const { command, rowCount, oid } = parseCommandTag(msg.tag);
|
|
1712
1722
|
const set = q.current ?? {
|
|
1713
1723
|
command,
|
|
@@ -1725,9 +1735,9 @@ export class PgConnection {
|
|
|
1725
1735
|
q.current = null;
|
|
1726
1736
|
return;
|
|
1727
1737
|
}
|
|
1728
|
-
case
|
|
1738
|
+
case "EmptyQueryResponse": {
|
|
1729
1739
|
const set = {
|
|
1730
|
-
command:
|
|
1740
|
+
command: "",
|
|
1731
1741
|
rowCount: null,
|
|
1732
1742
|
oid: null,
|
|
1733
1743
|
fields: [],
|
|
@@ -1738,30 +1748,30 @@ export class PgConnection {
|
|
|
1738
1748
|
q.current = null;
|
|
1739
1749
|
return;
|
|
1740
1750
|
}
|
|
1741
|
-
case
|
|
1751
|
+
case "ParameterStatus":
|
|
1742
1752
|
this.params.set(msg.name, msg.value);
|
|
1743
|
-
if (msg.name ===
|
|
1753
|
+
if (msg.name === "server_version") {
|
|
1744
1754
|
this.serverVersion = parseServerVersion(msg.value);
|
|
1745
1755
|
}
|
|
1746
1756
|
return;
|
|
1747
|
-
case
|
|
1757
|
+
case "NoticeResponse": {
|
|
1748
1758
|
const notice = fieldsToNotice(msg.fields);
|
|
1749
1759
|
q.notices.push(notice);
|
|
1750
1760
|
this.notify.emit(notice);
|
|
1751
1761
|
return;
|
|
1752
1762
|
}
|
|
1753
|
-
case
|
|
1763
|
+
case "NotificationResponse":
|
|
1754
1764
|
this.notify.emitNotification(msg.channel, msg.payload, msg.processId);
|
|
1755
1765
|
return;
|
|
1756
|
-
case
|
|
1766
|
+
case "ErrorResponse": {
|
|
1757
1767
|
q.error = fieldsToConnectError(msg.fields);
|
|
1758
1768
|
// Don't reject yet — ReadyForQuery will arrive shortly and we want
|
|
1759
1769
|
// to drain queued NoticeResponse messages first.
|
|
1760
1770
|
return;
|
|
1761
1771
|
}
|
|
1762
|
-
case
|
|
1772
|
+
case "ReadyForQuery": {
|
|
1763
1773
|
this.txStatus = msg.status;
|
|
1764
|
-
this.state =
|
|
1774
|
+
this.state = "idle";
|
|
1765
1775
|
this.pendingQuery = null;
|
|
1766
1776
|
if (q.error) {
|
|
1767
1777
|
// Mirror libpq's behaviour: the result list contains every
|
|
@@ -1772,8 +1782,7 @@ export class PgConnection {
|
|
|
1772
1782
|
// (`executeAndPrint`) can render the pre-error rows in order
|
|
1773
1783
|
// before printing the error itself.
|
|
1774
1784
|
const err = asThrowable(q.error);
|
|
1775
|
-
err.partialResults =
|
|
1776
|
-
q.finished;
|
|
1785
|
+
err.partialResults = q.finished;
|
|
1777
1786
|
q.reject(err);
|
|
1778
1787
|
}
|
|
1779
1788
|
else {
|
|
@@ -1781,7 +1790,7 @@ export class PgConnection {
|
|
|
1781
1790
|
}
|
|
1782
1791
|
return;
|
|
1783
1792
|
}
|
|
1784
|
-
case
|
|
1793
|
+
case "CopyInResponse": {
|
|
1785
1794
|
// PG 17 added pipeline + COPY support but libpq still rejects the
|
|
1786
1795
|
// combination ("COPY in a pipeline is not supported, aborting
|
|
1787
1796
|
// connection"). Upstream psql surfaces that diagnostic and tears down
|
|
@@ -1817,11 +1826,11 @@ export class PgConnection {
|
|
|
1817
1826
|
return;
|
|
1818
1827
|
}
|
|
1819
1828
|
q.error = {
|
|
1820
|
-
severity:
|
|
1821
|
-
message:
|
|
1829
|
+
severity: "ERROR",
|
|
1830
|
+
message: "COPY FROM STDIN not supported via execSimple — use \\copy or startCopyIn",
|
|
1822
1831
|
};
|
|
1823
1832
|
try {
|
|
1824
|
-
this.socket.write(CopyFail(
|
|
1833
|
+
this.socket.write(CopyFail("COPY FROM STDIN not driven by client"));
|
|
1825
1834
|
}
|
|
1826
1835
|
catch {
|
|
1827
1836
|
// Write failures are surfaced via socket 'error' / 'close' handlers
|
|
@@ -1829,7 +1838,7 @@ export class PgConnection {
|
|
|
1829
1838
|
}
|
|
1830
1839
|
return;
|
|
1831
1840
|
}
|
|
1832
|
-
case
|
|
1841
|
+
case "CopyOutResponse": {
|
|
1833
1842
|
if (this._extPipelineActive) {
|
|
1834
1843
|
this.abortForCopyInPipeline();
|
|
1835
1844
|
return;
|
|
@@ -1844,7 +1853,7 @@ export class PgConnection {
|
|
|
1844
1853
|
// rendered yet (see hunk 5722-5730 in regress/psql).
|
|
1845
1854
|
this.copyOutMidBatchActive = true;
|
|
1846
1855
|
q.current = q.current ?? {
|
|
1847
|
-
command:
|
|
1856
|
+
command: "",
|
|
1848
1857
|
rowCount: null,
|
|
1849
1858
|
oid: null,
|
|
1850
1859
|
fields: [],
|
|
@@ -1854,7 +1863,7 @@ export class PgConnection {
|
|
|
1854
1863
|
q.current.copyOutBytes = q.current.copyOutBytes ?? [];
|
|
1855
1864
|
return;
|
|
1856
1865
|
}
|
|
1857
|
-
case
|
|
1866
|
+
case "CopyData": {
|
|
1858
1867
|
// CopyData arrives during execSimple only when we're in the mid-batch
|
|
1859
1868
|
// COPY-OUT phase (CopyOutResponse flipped the flag above). Stash the
|
|
1860
1869
|
// payload on the current result's `copyOutBytes` so the caller can
|
|
@@ -1868,12 +1877,12 @@ export class PgConnection {
|
|
|
1868
1877
|
return;
|
|
1869
1878
|
}
|
|
1870
1879
|
q.error = {
|
|
1871
|
-
severity:
|
|
1872
|
-
message:
|
|
1880
|
+
severity: "ERROR",
|
|
1881
|
+
message: "Unexpected backend message during query: CopyData",
|
|
1873
1882
|
};
|
|
1874
1883
|
return;
|
|
1875
1884
|
}
|
|
1876
|
-
case
|
|
1885
|
+
case "CopyDone": {
|
|
1877
1886
|
// Server signals end of COPY-OUT data — next message will be
|
|
1878
1887
|
// CommandComplete for the COPY statement, then the batch resumes.
|
|
1879
1888
|
if (this.copyOutMidBatchActive) {
|
|
@@ -1881,12 +1890,12 @@ export class PgConnection {
|
|
|
1881
1890
|
return;
|
|
1882
1891
|
}
|
|
1883
1892
|
q.error = {
|
|
1884
|
-
severity:
|
|
1885
|
-
message:
|
|
1893
|
+
severity: "ERROR",
|
|
1894
|
+
message: "Unexpected backend message during query: CopyDone",
|
|
1886
1895
|
};
|
|
1887
1896
|
return;
|
|
1888
1897
|
}
|
|
1889
|
-
case
|
|
1898
|
+
case "CopyBothResponse": {
|
|
1890
1899
|
// Walsender (`replication=database` / `replication=true`) commands
|
|
1891
1900
|
// such as `START_REPLICATION` transition the connection into a
|
|
1892
1901
|
// CopyBoth streaming phase (WAL records flowing from server +
|
|
@@ -1897,9 +1906,9 @@ export class PgConnection {
|
|
|
1897
1906
|
// message (matching the conformance assertion) and tear the socket
|
|
1898
1907
|
// down so the next query / process exit is clean.
|
|
1899
1908
|
const cbErr = {
|
|
1900
|
-
severity:
|
|
1901
|
-
code:
|
|
1902
|
-
message:
|
|
1909
|
+
severity: "ERROR",
|
|
1910
|
+
code: "0A000",
|
|
1911
|
+
message: "syntax error: unexpected CopyBothResponse from server (replication streaming is not supported by this client)",
|
|
1903
1912
|
};
|
|
1904
1913
|
q.error = cbErr;
|
|
1905
1914
|
q.reject(asThrowable(cbErr));
|
|
@@ -1911,14 +1920,14 @@ export class PgConnection {
|
|
|
1911
1920
|
catch {
|
|
1912
1921
|
// ignore
|
|
1913
1922
|
}
|
|
1914
|
-
this.state =
|
|
1923
|
+
this.state = "closed";
|
|
1915
1924
|
return;
|
|
1916
1925
|
}
|
|
1917
1926
|
default:
|
|
1918
1927
|
// Unknown messages during a query are protocol errors but not fatal
|
|
1919
1928
|
// for the connection — record them.
|
|
1920
1929
|
q.error = {
|
|
1921
|
-
severity:
|
|
1930
|
+
severity: "ERROR",
|
|
1922
1931
|
message: `Unexpected backend message during query: ${msg.type}`,
|
|
1923
1932
|
};
|
|
1924
1933
|
return;
|
|
@@ -1933,7 +1942,8 @@ export class PgConnection {
|
|
|
1933
1942
|
messages = this.parser.feed(chunk);
|
|
1934
1943
|
}
|
|
1935
1944
|
catch (err) {
|
|
1936
|
-
this.socketError =
|
|
1945
|
+
this.socketError =
|
|
1946
|
+
err instanceof Error ? err : new Error(String(err));
|
|
1937
1947
|
this.failPending(this.socketError);
|
|
1938
1948
|
try {
|
|
1939
1949
|
this.socket.destroy();
|
|
@@ -1941,39 +1951,39 @@ export class PgConnection {
|
|
|
1941
1951
|
catch {
|
|
1942
1952
|
// ignore
|
|
1943
1953
|
}
|
|
1944
|
-
this.state =
|
|
1954
|
+
this.state = "closed";
|
|
1945
1955
|
return;
|
|
1946
1956
|
}
|
|
1947
1957
|
for (const msg of messages) {
|
|
1948
1958
|
this.dispatch(msg);
|
|
1949
|
-
if (this.state ===
|
|
1959
|
+
if (this.state === "closed")
|
|
1950
1960
|
break;
|
|
1951
1961
|
}
|
|
1952
1962
|
}
|
|
1953
1963
|
dispatch(msg) {
|
|
1954
1964
|
// Async backend messages always allowed. NotificationResponse can arrive
|
|
1955
1965
|
// in *any* state since LISTEN payloads come in whenever a NOTIFY fires.
|
|
1956
|
-
if (msg.type ===
|
|
1966
|
+
if (msg.type === "NotificationResponse") {
|
|
1957
1967
|
this.notify.emitNotification(msg.channel, msg.payload, msg.processId);
|
|
1958
1968
|
return;
|
|
1959
1969
|
}
|
|
1960
1970
|
switch (this.state) {
|
|
1961
|
-
case
|
|
1971
|
+
case "auth":
|
|
1962
1972
|
this.handleAuthMessage(msg);
|
|
1963
1973
|
return;
|
|
1964
|
-
case
|
|
1974
|
+
case "await-ready":
|
|
1965
1975
|
this.handleAwaitReady(msg);
|
|
1966
1976
|
return;
|
|
1967
|
-
case
|
|
1977
|
+
case "idle":
|
|
1968
1978
|
// ParameterStatus changes can arrive asynchronously (SET).
|
|
1969
|
-
if (msg.type ===
|
|
1979
|
+
if (msg.type === "ParameterStatus") {
|
|
1970
1980
|
this.params.set(msg.name, msg.value);
|
|
1971
|
-
if (msg.name ===
|
|
1981
|
+
if (msg.name === "server_version") {
|
|
1972
1982
|
this.serverVersion = parseServerVersion(msg.value);
|
|
1973
1983
|
}
|
|
1974
1984
|
return;
|
|
1975
1985
|
}
|
|
1976
|
-
if (msg.type ===
|
|
1986
|
+
if (msg.type === "NoticeResponse") {
|
|
1977
1987
|
this.notify.emit(fieldsToNotice(msg.fields));
|
|
1978
1988
|
return;
|
|
1979
1989
|
}
|
|
@@ -1986,21 +1996,21 @@ export class PgConnection {
|
|
|
1986
1996
|
catch {
|
|
1987
1997
|
// ignore
|
|
1988
1998
|
}
|
|
1989
|
-
this.state =
|
|
1999
|
+
this.state = "closed";
|
|
1990
2000
|
return;
|
|
1991
|
-
case
|
|
2001
|
+
case "in-query":
|
|
1992
2002
|
this.handleQueryMessage(msg);
|
|
1993
2003
|
return;
|
|
1994
|
-
case
|
|
2004
|
+
case "in-extended":
|
|
1995
2005
|
this.handleExtendedMessage(msg);
|
|
1996
2006
|
return;
|
|
1997
|
-
case
|
|
2007
|
+
case "in-copy-in":
|
|
1998
2008
|
this.handleCopyInMessage(msg);
|
|
1999
2009
|
return;
|
|
2000
|
-
case
|
|
2010
|
+
case "in-copy-out":
|
|
2001
2011
|
this.handleCopyOutMessage(msg);
|
|
2002
2012
|
return;
|
|
2003
|
-
case
|
|
2013
|
+
case "closed":
|
|
2004
2014
|
return;
|
|
2005
2015
|
}
|
|
2006
2016
|
}
|
|
@@ -2049,8 +2059,8 @@ export class PgConnection {
|
|
|
2049
2059
|
this.copyOut = null;
|
|
2050
2060
|
d.error =
|
|
2051
2061
|
err instanceof Error
|
|
2052
|
-
? { severity:
|
|
2053
|
-
: { severity:
|
|
2062
|
+
? { severity: "ERROR", message: err.message }
|
|
2063
|
+
: { severity: "ERROR", message: String(err) };
|
|
2054
2064
|
if (d.waker) {
|
|
2055
2065
|
const w = d.waker;
|
|
2056
2066
|
d.waker = null;
|
|
@@ -2059,10 +2069,10 @@ export class PgConnection {
|
|
|
2059
2069
|
}
|
|
2060
2070
|
}
|
|
2061
2071
|
ensureIdle() {
|
|
2062
|
-
if (this.state ===
|
|
2063
|
-
throw new Error(
|
|
2072
|
+
if (this.state === "closed") {
|
|
2073
|
+
throw new Error("PgConnection: connection is closed");
|
|
2064
2074
|
}
|
|
2065
|
-
if (this.state !==
|
|
2075
|
+
if (this.state !== "idle" && this.state !== "in-extended") {
|
|
2066
2076
|
throw new Error(`PgConnection: cannot start query in state ${this.state}`);
|
|
2067
2077
|
}
|
|
2068
2078
|
}
|
|
@@ -2075,11 +2085,11 @@ export class PgConnection {
|
|
|
2075
2085
|
// (Parse/Bind/Describe/Execute/Close/Sync) to the socket.
|
|
2076
2086
|
// -------------------------------------------------------------------------
|
|
2077
2087
|
startExtendedBatch() {
|
|
2078
|
-
if (this.state ===
|
|
2079
|
-
this.state =
|
|
2088
|
+
if (this.state === "idle") {
|
|
2089
|
+
this.state = "in-extended";
|
|
2080
2090
|
this.extDriver = { queue: [], error: null };
|
|
2081
2091
|
}
|
|
2082
|
-
else if (this.state !==
|
|
2092
|
+
else if (this.state !== "in-extended") {
|
|
2083
2093
|
throw new Error(`PgConnection: cannot start extended batch in state ${this.state}`);
|
|
2084
2094
|
}
|
|
2085
2095
|
else if (!this.extDriver) {
|
|
@@ -2091,21 +2101,21 @@ export class PgConnection {
|
|
|
2091
2101
|
}
|
|
2092
2102
|
enqueueParse() {
|
|
2093
2103
|
return this.enqueueOp({
|
|
2094
|
-
kind:
|
|
2104
|
+
kind: "parse",
|
|
2095
2105
|
resolve: () => undefined,
|
|
2096
2106
|
reject: () => undefined,
|
|
2097
2107
|
});
|
|
2098
2108
|
}
|
|
2099
2109
|
enqueueBind() {
|
|
2100
2110
|
return this.enqueueOp({
|
|
2101
|
-
kind:
|
|
2111
|
+
kind: "bind",
|
|
2102
2112
|
resolve: () => undefined,
|
|
2103
2113
|
reject: () => undefined,
|
|
2104
2114
|
});
|
|
2105
2115
|
}
|
|
2106
2116
|
enqueueDescribeStatement() {
|
|
2107
2117
|
return this.enqueueOp({
|
|
2108
|
-
kind:
|
|
2118
|
+
kind: "describeS",
|
|
2109
2119
|
resolve: () => undefined,
|
|
2110
2120
|
reject: () => undefined,
|
|
2111
2121
|
paramOids: null,
|
|
@@ -2113,7 +2123,7 @@ export class PgConnection {
|
|
|
2113
2123
|
}
|
|
2114
2124
|
enqueueDescribePortal() {
|
|
2115
2125
|
return this.enqueueOp({
|
|
2116
|
-
kind:
|
|
2126
|
+
kind: "describeP",
|
|
2117
2127
|
resolve: () => undefined,
|
|
2118
2128
|
reject: () => undefined,
|
|
2119
2129
|
});
|
|
@@ -2125,15 +2135,15 @@ export class PgConnection {
|
|
|
2125
2135
|
enqueueDescribePortalIntoNextExecute() {
|
|
2126
2136
|
const driver = this.extDriver;
|
|
2127
2137
|
if (!driver) {
|
|
2128
|
-
return Promise.reject(new Error(
|
|
2138
|
+
return Promise.reject(new Error("enqueueDescribePortalIntoNextExecute: not in extended state"));
|
|
2129
2139
|
}
|
|
2130
2140
|
return new Promise((resolve, reject) => {
|
|
2131
2141
|
driver.queue.push({
|
|
2132
|
-
kind:
|
|
2142
|
+
kind: "describeP",
|
|
2133
2143
|
resolve: (v) => {
|
|
2134
2144
|
const fields = v;
|
|
2135
2145
|
for (const op of driver.queue) {
|
|
2136
|
-
if (op.kind ===
|
|
2146
|
+
if (op.kind === "execute" && op.fields === null) {
|
|
2137
2147
|
op.fields = fields;
|
|
2138
2148
|
break;
|
|
2139
2149
|
}
|
|
@@ -2146,7 +2156,7 @@ export class PgConnection {
|
|
|
2146
2156
|
}
|
|
2147
2157
|
enqueueExecute() {
|
|
2148
2158
|
return this.enqueueOp({
|
|
2149
|
-
kind:
|
|
2159
|
+
kind: "execute",
|
|
2150
2160
|
resolve: () => undefined,
|
|
2151
2161
|
reject: () => undefined,
|
|
2152
2162
|
current: null,
|
|
@@ -2156,7 +2166,7 @@ export class PgConnection {
|
|
|
2156
2166
|
}
|
|
2157
2167
|
enqueueExecuteWithFields(fields) {
|
|
2158
2168
|
return this.enqueueOp({
|
|
2159
|
-
kind:
|
|
2169
|
+
kind: "execute",
|
|
2160
2170
|
resolve: () => undefined,
|
|
2161
2171
|
reject: () => undefined,
|
|
2162
2172
|
current: null,
|
|
@@ -2166,21 +2176,21 @@ export class PgConnection {
|
|
|
2166
2176
|
}
|
|
2167
2177
|
enqueueClose() {
|
|
2168
2178
|
return this.enqueueOp({
|
|
2169
|
-
kind:
|
|
2179
|
+
kind: "close",
|
|
2170
2180
|
resolve: () => undefined,
|
|
2171
2181
|
reject: () => undefined,
|
|
2172
2182
|
});
|
|
2173
2183
|
}
|
|
2174
2184
|
enqueueSync() {
|
|
2175
2185
|
return this.enqueueOp({
|
|
2176
|
-
kind:
|
|
2186
|
+
kind: "sync",
|
|
2177
2187
|
resolve: () => undefined,
|
|
2178
2188
|
reject: () => undefined,
|
|
2179
2189
|
});
|
|
2180
2190
|
}
|
|
2181
2191
|
enqueueOp(opSkeleton) {
|
|
2182
2192
|
if (!this.extDriver) {
|
|
2183
|
-
return Promise.reject(new Error(
|
|
2193
|
+
return Promise.reject(new Error("enqueueOp: not in extended state"));
|
|
2184
2194
|
}
|
|
2185
2195
|
const driver = this.extDriver;
|
|
2186
2196
|
return new Promise((resolve, reject) => {
|
|
@@ -2194,26 +2204,26 @@ export class PgConnection {
|
|
|
2194
2204
|
const driver = this.extDriver;
|
|
2195
2205
|
if (!driver)
|
|
2196
2206
|
return;
|
|
2197
|
-
if (msg.type ===
|
|
2207
|
+
if (msg.type === "ParameterStatus") {
|
|
2198
2208
|
this.params.set(msg.name, msg.value);
|
|
2199
|
-
if (msg.name ===
|
|
2209
|
+
if (msg.name === "server_version") {
|
|
2200
2210
|
this.serverVersion = parseServerVersion(msg.value);
|
|
2201
2211
|
}
|
|
2202
2212
|
return;
|
|
2203
2213
|
}
|
|
2204
|
-
if (msg.type ===
|
|
2214
|
+
if (msg.type === "NoticeResponse") {
|
|
2205
2215
|
const notice = fieldsToNotice(msg.fields);
|
|
2206
2216
|
this.notify.emit(notice);
|
|
2207
2217
|
const head = driver.queue[0];
|
|
2208
|
-
if (head && head.kind ===
|
|
2218
|
+
if (head && head.kind === "execute")
|
|
2209
2219
|
head.notices.push(notice);
|
|
2210
2220
|
return;
|
|
2211
2221
|
}
|
|
2212
|
-
if (msg.type ===
|
|
2222
|
+
if (msg.type === "NotificationResponse") {
|
|
2213
2223
|
this.notify.emitNotification(msg.channel, msg.payload, msg.processId);
|
|
2214
2224
|
return;
|
|
2215
2225
|
}
|
|
2216
|
-
if (msg.type ===
|
|
2226
|
+
if (msg.type === "ErrorResponse") {
|
|
2217
2227
|
driver.error = fieldsToConnectError(msg.fields);
|
|
2218
2228
|
// Reject ALL queued non-sync ops eagerly. Upstream server semantics:
|
|
2219
2229
|
// once a P/B/D/E op errors, the server skips every subsequent message
|
|
@@ -2231,7 +2241,7 @@ export class PgConnection {
|
|
|
2231
2241
|
let first = true;
|
|
2232
2242
|
while (driver.queue.length > 0) {
|
|
2233
2243
|
const head = driver.queue[0];
|
|
2234
|
-
if (head.kind ===
|
|
2244
|
+
if (head.kind === "sync")
|
|
2235
2245
|
break;
|
|
2236
2246
|
driver.queue.shift();
|
|
2237
2247
|
if (first) {
|
|
@@ -2252,7 +2262,7 @@ export class PgConnection {
|
|
|
2252
2262
|
// and tears the connection down. Mirror that so `\startpipeline +
|
|
2253
2263
|
// COPY ...` surfaces the expected fatal error rather than hanging
|
|
2254
2264
|
// on a response the extended driver doesn't know how to consume.
|
|
2255
|
-
if (msg.type ===
|
|
2265
|
+
if (msg.type === "CopyInResponse" || msg.type === "CopyOutResponse") {
|
|
2256
2266
|
this.abortForCopyInPipeline();
|
|
2257
2267
|
return;
|
|
2258
2268
|
}
|
|
@@ -2269,7 +2279,7 @@ export class PgConnection {
|
|
|
2269
2279
|
// `Pipeline aborted, command did not run` (no `ERROR:` prefix).
|
|
2270
2280
|
while (driver.error !== null) {
|
|
2271
2281
|
const head = driver.queue[0];
|
|
2272
|
-
if (!head || head.kind ===
|
|
2282
|
+
if (!head || head.kind === "sync")
|
|
2273
2283
|
break;
|
|
2274
2284
|
driver.queue.shift();
|
|
2275
2285
|
head.reject(pipelineAbortedError());
|
|
@@ -2280,39 +2290,40 @@ export class PgConnection {
|
|
|
2280
2290
|
return;
|
|
2281
2291
|
}
|
|
2282
2292
|
switch (msg.type) {
|
|
2283
|
-
case
|
|
2284
|
-
if (head.kind !==
|
|
2285
|
-
this.protocolFail(new Error(
|
|
2293
|
+
case "ParseComplete":
|
|
2294
|
+
if (head.kind !== "parse") {
|
|
2295
|
+
this.protocolFail(new Error("ParseComplete arrived but head op is " + head.kind));
|
|
2286
2296
|
return;
|
|
2287
2297
|
}
|
|
2288
2298
|
driver.queue.shift();
|
|
2289
2299
|
head.resolve(undefined);
|
|
2290
2300
|
return;
|
|
2291
|
-
case
|
|
2292
|
-
if (head.kind !==
|
|
2293
|
-
this.protocolFail(new Error(
|
|
2301
|
+
case "BindComplete":
|
|
2302
|
+
if (head.kind !== "bind") {
|
|
2303
|
+
this.protocolFail(new Error("BindComplete arrived but head op is " + head.kind));
|
|
2294
2304
|
return;
|
|
2295
2305
|
}
|
|
2296
2306
|
driver.queue.shift();
|
|
2297
2307
|
head.resolve(undefined);
|
|
2298
2308
|
return;
|
|
2299
|
-
case
|
|
2300
|
-
if (head.kind !==
|
|
2301
|
-
this.protocolFail(new Error(
|
|
2309
|
+
case "CloseComplete":
|
|
2310
|
+
if (head.kind !== "close") {
|
|
2311
|
+
this.protocolFail(new Error("CloseComplete arrived but head op is " + head.kind));
|
|
2302
2312
|
return;
|
|
2303
2313
|
}
|
|
2304
2314
|
driver.queue.shift();
|
|
2305
2315
|
head.resolve(undefined);
|
|
2306
2316
|
return;
|
|
2307
|
-
case
|
|
2308
|
-
if (head.kind !==
|
|
2309
|
-
this.protocolFail(new Error(
|
|
2317
|
+
case "ParameterDescription":
|
|
2318
|
+
if (head.kind !== "describeS") {
|
|
2319
|
+
this.protocolFail(new Error("ParameterDescription arrived but head op is " +
|
|
2320
|
+
head.kind));
|
|
2310
2321
|
return;
|
|
2311
2322
|
}
|
|
2312
2323
|
head.paramOids = msg.oids;
|
|
2313
2324
|
return;
|
|
2314
|
-
case
|
|
2315
|
-
if (head.kind ===
|
|
2325
|
+
case "RowDescription":
|
|
2326
|
+
if (head.kind === "describeS") {
|
|
2316
2327
|
driver.queue.shift();
|
|
2317
2328
|
head.resolve({
|
|
2318
2329
|
paramOids: head.paramOids ?? [],
|
|
@@ -2320,14 +2331,14 @@ export class PgConnection {
|
|
|
2320
2331
|
});
|
|
2321
2332
|
return;
|
|
2322
2333
|
}
|
|
2323
|
-
if (head.kind ===
|
|
2334
|
+
if (head.kind === "describeP") {
|
|
2324
2335
|
driver.queue.shift();
|
|
2325
2336
|
head.resolve(msg.fields);
|
|
2326
2337
|
return;
|
|
2327
2338
|
}
|
|
2328
|
-
if (head.kind ===
|
|
2339
|
+
if (head.kind === "execute") {
|
|
2329
2340
|
head.current = {
|
|
2330
|
-
command:
|
|
2341
|
+
command: "",
|
|
2331
2342
|
rowCount: null,
|
|
2332
2343
|
oid: null,
|
|
2333
2344
|
fields: msg.fields,
|
|
@@ -2337,30 +2348,33 @@ export class PgConnection {
|
|
|
2337
2348
|
head.fields = msg.fields;
|
|
2338
2349
|
return;
|
|
2339
2350
|
}
|
|
2340
|
-
this.protocolFail(new Error(
|
|
2351
|
+
this.protocolFail(new Error("Unexpected RowDescription at head op " + head.kind));
|
|
2341
2352
|
return;
|
|
2342
|
-
case
|
|
2343
|
-
if (head.kind ===
|
|
2353
|
+
case "NoData":
|
|
2354
|
+
if (head.kind === "describeS") {
|
|
2344
2355
|
driver.queue.shift();
|
|
2345
|
-
head.resolve({
|
|
2356
|
+
head.resolve({
|
|
2357
|
+
paramOids: head.paramOids ?? [],
|
|
2358
|
+
fields: [],
|
|
2359
|
+
});
|
|
2346
2360
|
return;
|
|
2347
2361
|
}
|
|
2348
|
-
if (head.kind ===
|
|
2362
|
+
if (head.kind === "describeP") {
|
|
2349
2363
|
driver.queue.shift();
|
|
2350
2364
|
head.resolve([]);
|
|
2351
2365
|
return;
|
|
2352
2366
|
}
|
|
2353
|
-
this.protocolFail(new Error(
|
|
2367
|
+
this.protocolFail(new Error("Unexpected NoData at head op " + head.kind));
|
|
2354
2368
|
return;
|
|
2355
|
-
case
|
|
2356
|
-
if (head.kind !==
|
|
2357
|
-
this.protocolFail(new Error(
|
|
2369
|
+
case "DataRow": {
|
|
2370
|
+
if (head.kind !== "execute") {
|
|
2371
|
+
this.protocolFail(new Error("DataRow at head op " + head.kind));
|
|
2358
2372
|
return;
|
|
2359
2373
|
}
|
|
2360
2374
|
const fields = head.fields ?? head.current?.fields ?? [];
|
|
2361
2375
|
if (!head.current) {
|
|
2362
2376
|
head.current = {
|
|
2363
|
-
command:
|
|
2377
|
+
command: "",
|
|
2364
2378
|
rowCount: null,
|
|
2365
2379
|
oid: null,
|
|
2366
2380
|
fields,
|
|
@@ -2371,9 +2385,9 @@ export class PgConnection {
|
|
|
2371
2385
|
head.current.rows.push(decodeDataRow(msg.values, fields));
|
|
2372
2386
|
return;
|
|
2373
2387
|
}
|
|
2374
|
-
case
|
|
2375
|
-
if (head.kind !==
|
|
2376
|
-
this.protocolFail(new Error(
|
|
2388
|
+
case "CommandComplete": {
|
|
2389
|
+
if (head.kind !== "execute") {
|
|
2390
|
+
this.protocolFail(new Error("CommandComplete at head op " + head.kind));
|
|
2377
2391
|
return;
|
|
2378
2392
|
}
|
|
2379
2393
|
const { command, rowCount, oid } = parseCommandTag(msg.tag);
|
|
@@ -2393,13 +2407,13 @@ export class PgConnection {
|
|
|
2393
2407
|
head.resolve(set);
|
|
2394
2408
|
return;
|
|
2395
2409
|
}
|
|
2396
|
-
case
|
|
2397
|
-
if (head.kind !==
|
|
2398
|
-
this.protocolFail(new Error(
|
|
2410
|
+
case "EmptyQueryResponse": {
|
|
2411
|
+
if (head.kind !== "execute") {
|
|
2412
|
+
this.protocolFail(new Error("EmptyQueryResponse at head op " + head.kind));
|
|
2399
2413
|
return;
|
|
2400
2414
|
}
|
|
2401
2415
|
const set = {
|
|
2402
|
-
command:
|
|
2416
|
+
command: "",
|
|
2403
2417
|
rowCount: null,
|
|
2404
2418
|
oid: null,
|
|
2405
2419
|
fields: [],
|
|
@@ -2410,13 +2424,13 @@ export class PgConnection {
|
|
|
2410
2424
|
head.resolve(set);
|
|
2411
2425
|
return;
|
|
2412
2426
|
}
|
|
2413
|
-
case
|
|
2414
|
-
if (head.kind !==
|
|
2415
|
-
this.protocolFail(new Error(
|
|
2427
|
+
case "PortalSuspended": {
|
|
2428
|
+
if (head.kind !== "execute") {
|
|
2429
|
+
this.protocolFail(new Error("PortalSuspended at head op " + head.kind));
|
|
2416
2430
|
return;
|
|
2417
2431
|
}
|
|
2418
2432
|
const set = head.current ?? {
|
|
2419
|
-
command:
|
|
2433
|
+
command: "",
|
|
2420
2434
|
rowCount: null,
|
|
2421
2435
|
oid: null,
|
|
2422
2436
|
fields: head.fields ?? [],
|
|
@@ -2428,10 +2442,10 @@ export class PgConnection {
|
|
|
2428
2442
|
head.resolve(set);
|
|
2429
2443
|
return;
|
|
2430
2444
|
}
|
|
2431
|
-
case
|
|
2445
|
+
case "ReadyForQuery": {
|
|
2432
2446
|
this.txStatus = msg.status;
|
|
2433
|
-
if (head.kind !==
|
|
2434
|
-
this.protocolFail(new Error(
|
|
2447
|
+
if (head.kind !== "sync") {
|
|
2448
|
+
this.protocolFail(new Error("ReadyForQuery but head op is " + head.kind));
|
|
2435
2449
|
return;
|
|
2436
2450
|
}
|
|
2437
2451
|
driver.queue.shift();
|
|
@@ -2444,7 +2458,7 @@ export class PgConnection {
|
|
|
2444
2458
|
head.resolve(undefined);
|
|
2445
2459
|
}
|
|
2446
2460
|
if (driver.queue.length === 0 && !this._extPipelineActive) {
|
|
2447
|
-
this.state =
|
|
2461
|
+
this.state = "idle";
|
|
2448
2462
|
this.extDriver = null;
|
|
2449
2463
|
}
|
|
2450
2464
|
return;
|
|
@@ -2463,7 +2477,7 @@ export class PgConnection {
|
|
|
2463
2477
|
catch {
|
|
2464
2478
|
// ignore
|
|
2465
2479
|
}
|
|
2466
|
-
this.state =
|
|
2480
|
+
this.state = "closed";
|
|
2467
2481
|
}
|
|
2468
2482
|
/**
|
|
2469
2483
|
* Abort the connection because the server replied with CopyInResponse /
|
|
@@ -2476,8 +2490,8 @@ export class PgConnection {
|
|
|
2476
2490
|
*/
|
|
2477
2491
|
abortForCopyInPipeline() {
|
|
2478
2492
|
const err = {
|
|
2479
|
-
severity:
|
|
2480
|
-
message:
|
|
2493
|
+
severity: "FATAL",
|
|
2494
|
+
message: "COPY in a pipeline is not supported, aborting connection",
|
|
2481
2495
|
};
|
|
2482
2496
|
this.socketError = new Error(err.message);
|
|
2483
2497
|
this.failPending(err);
|
|
@@ -2487,7 +2501,7 @@ export class PgConnection {
|
|
|
2487
2501
|
catch {
|
|
2488
2502
|
// ignore
|
|
2489
2503
|
}
|
|
2490
|
-
this.state =
|
|
2504
|
+
this.state = "closed";
|
|
2491
2505
|
}
|
|
2492
2506
|
}
|
|
2493
2507
|
// -------------------------------------------------------------------------
|
|
@@ -2522,7 +2536,7 @@ PgConnection._dnsLookupAll = null;
|
|
|
2522
2536
|
* directory. libpq's rule: any value starting with `/` is a path.
|
|
2523
2537
|
*/
|
|
2524
2538
|
export function isUnixSocketHost(host) {
|
|
2525
|
-
return host.startsWith(
|
|
2539
|
+
return host.startsWith("/");
|
|
2526
2540
|
}
|
|
2527
2541
|
/**
|
|
2528
2542
|
* Build the actual filesystem path Postgres listens on under a socket
|
|
@@ -2594,10 +2608,10 @@ async function expandHostsViaDns(seed) {
|
|
|
2594
2608
|
*/
|
|
2595
2609
|
function toSecureVersion(value) {
|
|
2596
2610
|
switch (value) {
|
|
2597
|
-
case
|
|
2598
|
-
case
|
|
2599
|
-
case
|
|
2600
|
-
case
|
|
2611
|
+
case "TLSv1":
|
|
2612
|
+
case "TLSv1.1":
|
|
2613
|
+
case "TLSv1.2":
|
|
2614
|
+
case "TLSv1.3":
|
|
2601
2615
|
return value;
|
|
2602
2616
|
default:
|
|
2603
2617
|
return undefined;
|
|
@@ -2666,13 +2680,13 @@ export function keepAliveArgs(opts) {
|
|
|
2666
2680
|
*/
|
|
2667
2681
|
export function sslKeyBitsFromCipher(name) {
|
|
2668
2682
|
const upper = name.toUpperCase();
|
|
2669
|
-
if (upper.includes(
|
|
2683
|
+
if (upper.includes("CHACHA20"))
|
|
2670
2684
|
return 256;
|
|
2671
|
-
if (upper.includes(
|
|
2685
|
+
if (upper.includes("AES_256") || upper.includes("AES256"))
|
|
2672
2686
|
return 256;
|
|
2673
|
-
if (upper.includes(
|
|
2687
|
+
if (upper.includes("AES_128") || upper.includes("AES128"))
|
|
2674
2688
|
return 128;
|
|
2675
|
-
if (upper.includes(
|
|
2689
|
+
if (upper.includes("AES_192") || upper.includes("AES192"))
|
|
2676
2690
|
return 192;
|
|
2677
2691
|
const digits = /(\d{2,4})/.exec(upper);
|
|
2678
2692
|
if (digits) {
|
|
@@ -2720,8 +2734,8 @@ addressOverride) {
|
|
|
2720
2734
|
const cleanup = () => {
|
|
2721
2735
|
if (timer)
|
|
2722
2736
|
clearTimeout(timer);
|
|
2723
|
-
socket.removeListener(
|
|
2724
|
-
socket.removeListener(
|
|
2737
|
+
socket.removeListener("error", onError);
|
|
2738
|
+
socket.removeListener("connect", onConnect);
|
|
2725
2739
|
};
|
|
2726
2740
|
const onError = (err) => {
|
|
2727
2741
|
cleanup();
|
|
@@ -2731,8 +2745,8 @@ addressOverride) {
|
|
|
2731
2745
|
cleanup();
|
|
2732
2746
|
resolve(socket);
|
|
2733
2747
|
};
|
|
2734
|
-
socket.once(
|
|
2735
|
-
socket.once(
|
|
2748
|
+
socket.once("error", onError);
|
|
2749
|
+
socket.once("connect", onConnect);
|
|
2736
2750
|
});
|
|
2737
2751
|
}
|
|
2738
2752
|
// ---------------------------------------------------------------------------
|
|
@@ -2753,7 +2767,7 @@ function decodeDataRow(values, fields) {
|
|
|
2753
2767
|
continue;
|
|
2754
2768
|
}
|
|
2755
2769
|
const fmt = fields[i]?.format ?? 0;
|
|
2756
|
-
out[i] = fmt === 1 ? v : v.toString(
|
|
2770
|
+
out[i] = fmt === 1 ? v : v.toString("utf8");
|
|
2757
2771
|
}
|
|
2758
2772
|
return out;
|
|
2759
2773
|
}
|
|
@@ -2772,7 +2786,7 @@ function parseCommandTag(tag) {
|
|
|
2772
2786
|
const insertMatch = /^INSERT (\d+) (\d+)$/.exec(trimmed);
|
|
2773
2787
|
if (insertMatch) {
|
|
2774
2788
|
return {
|
|
2775
|
-
command:
|
|
2789
|
+
command: "INSERT",
|
|
2776
2790
|
oid: parseInt(insertMatch[1], 10),
|
|
2777
2791
|
rowCount: parseInt(insertMatch[2], 10),
|
|
2778
2792
|
};
|
|
@@ -2797,17 +2811,17 @@ export function encodeParams(values) {
|
|
|
2797
2811
|
return null;
|
|
2798
2812
|
if (Buffer.isBuffer(v))
|
|
2799
2813
|
return v;
|
|
2800
|
-
if (typeof v ===
|
|
2814
|
+
if (typeof v === "string")
|
|
2801
2815
|
return v;
|
|
2802
|
-
if (typeof v ===
|
|
2803
|
-
return v ?
|
|
2804
|
-
if (typeof v ===
|
|
2816
|
+
if (typeof v === "boolean")
|
|
2817
|
+
return v ? "t" : "f";
|
|
2818
|
+
if (typeof v === "number" || typeof v === "bigint")
|
|
2805
2819
|
return v.toString();
|
|
2806
2820
|
try {
|
|
2807
2821
|
return JSON.stringify(v);
|
|
2808
2822
|
}
|
|
2809
2823
|
catch {
|
|
2810
|
-
return
|
|
2824
|
+
return "";
|
|
2811
2825
|
}
|
|
2812
2826
|
});
|
|
2813
2827
|
}
|
|
@@ -2824,16 +2838,16 @@ export function encodeParams(values) {
|
|
|
2824
2838
|
function asThrowable(v) {
|
|
2825
2839
|
if (v instanceof Error)
|
|
2826
2840
|
return v;
|
|
2827
|
-
if (typeof v ===
|
|
2841
|
+
if (typeof v === "object" &&
|
|
2828
2842
|
v !== null &&
|
|
2829
|
-
|
|
2830
|
-
typeof v.message ===
|
|
2843
|
+
"message" in v &&
|
|
2844
|
+
typeof v.message === "string") {
|
|
2831
2845
|
const source = v;
|
|
2832
2846
|
const err = new Error(source.message);
|
|
2833
2847
|
// Copy every own enumerable field (severity, code, detail, hint, …) onto
|
|
2834
2848
|
// the Error so structural consumers keep working.
|
|
2835
2849
|
for (const key of Object.keys(source)) {
|
|
2836
|
-
if (key ===
|
|
2850
|
+
if (key === "message")
|
|
2837
2851
|
continue;
|
|
2838
2852
|
err[key] = source[key];
|
|
2839
2853
|
}
|