neon 3.0.0 → 3.1.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 +70 -5
- package/dist/_chunks/auth_selection-DGgq6ifc.js +83 -0
- package/dist/_chunks/cmd_pipeline-CUbBO9U_.js +2818 -0
- package/dist/_chunks/credentials-MYdHdKah.js +188 -0
- package/dist/_chunks/env-NbA61JR3.js +585 -0
- package/dist/_chunks/env_services-Tz9G4JeT.js +531 -0
- package/dist/_chunks/paths-DMq0Lt7a.js +151 -0
- package/dist/_chunks/profiles-Ir29rqns.js +217 -0
- package/dist/_chunks/psql-DWH-kc69.js +2169 -0
- package/dist/_chunks/rolldown-runtime-D7D4PA-g.js +13 -0
- package/dist/_chunks/secure_file-BucZj4yQ.js +39 -0
- package/dist/analytics.js +163 -207
- package/dist/api.js +815 -758
- package/dist/auth.js +121 -141
- package/dist/auth_context.js +39 -53
- package/dist/cli.js +4 -7
- package/dist/commands/api.js +220 -250
- package/dist/commands/api_keys.js +251 -314
- package/dist/commands/auth.js +283 -328
- package/dist/commands/bootstrap.js +372 -437
- package/dist/commands/branches.js +304 -455
- package/dist/commands/bucket.js +374 -514
- package/dist/commands/checkout.js +213 -298
- package/dist/commands/config.js +573 -690
- package/dist/commands/connection_string.js +137 -165
- package/dist/commands/data_api.js +238 -260
- package/dist/commands/databases.js +67 -76
- package/dist/commands/deploy.js +31 -25
- package/dist/commands/dev.js +639 -719
- package/dist/commands/diff.js +156 -200
- package/dist/commands/env.js +255 -305
- package/dist/commands/functions.js +275 -355
- package/dist/commands/index.js +70 -65
- package/dist/commands/init.js +84 -119
- package/dist/commands/inspect.js +55 -55
- package/dist/commands/ip_allow.js +88 -120
- package/dist/commands/link.js +874 -1019
- package/dist/commands/logs.js +291 -0
- package/dist/commands/neon_auth.js +725 -933
- package/dist/commands/operations.js +34 -25
- package/dist/commands/orgs.js +28 -18
- package/dist/commands/profile.js +615 -846
- package/dist/commands/projects.js +313 -373
- package/dist/commands/psql.js +60 -58
- package/dist/commands/roles.js +55 -58
- package/dist/commands/schema_diff.js +87 -131
- package/dist/commands/set_context.js +34 -26
- package/dist/commands/snapshots.js +288 -413
- package/dist/commands/status.js +41 -37
- package/dist/commands/user.js +21 -10
- package/dist/commands/vpc_endpoints.js +85 -113
- package/dist/config.js +7 -6
- package/dist/config_format.js +50 -66
- package/dist/config_template.js +128 -157
- package/dist/context.js +183 -235
- package/dist/current_branch_fast_path.js +40 -49
- package/dist/dev/env.js +2 -446
- package/dist/dev/functions.js +54 -68
- package/dist/dev/inputs.js +46 -58
- package/dist/dev/runtime.js +135 -164
- package/dist/dev/websocket.js +766 -959
- package/dist/env.js +27 -33
- package/dist/env_file.js +118 -132
- package/dist/env_services.js +2 -51
- package/dist/errors.js +57 -68
- package/dist/functions_api.js +45 -43
- package/dist/help.js +189 -140
- package/dist/index.js +182 -257
- package/dist/init/agents.js +137 -118
- package/dist/init/auth.js +58 -68
- package/dist/init/bootstrap.js +325 -396
- package/dist/init/build_config.js +4 -2
- package/dist/init/detect_agent.js +56 -101
- package/dist/init/editors.js +35 -52
- package/dist/init/enrich_output.js +51 -66
- package/dist/init/extension.js +134 -171
- package/dist/init/inspect.js +179 -266
- package/dist/init/interactive.js +510 -622
- package/dist/init/neonctl.js +117 -168
- package/dist/init/orchestrate.js +157 -173
- package/dist/init/phases/auth.js +188 -202
- package/dist/init/phases/cleanup.js +23 -23
- package/dist/init/phases/db.js +251 -277
- package/dist/init/phases/getting_started.js +213 -223
- package/dist/init/phases/mcp.js +174 -224
- package/dist/init/phases/migrations.js +247 -248
- package/dist/init/phases/neon_auth.js +114 -133
- package/dist/init/phases/setup.js +546 -703
- package/dist/init/phases/skills.js +75 -86
- package/dist/init/phases/status.js +72 -67
- package/dist/init/resolve_context.js +102 -99
- package/dist/init/route_command.js +91 -98
- package/dist/init/skills.js +174 -218
- package/dist/init/vsix.js +77 -99
- package/dist/log.js +17 -16
- package/dist/neon_services.js +104 -129
- package/dist/parameters.gen.js +481 -471
- package/dist/pkg.js +17 -19
- package/dist/profile_keys.js +44 -47
- package/dist/psql/cli.js +44 -47
- package/dist/psql/command/cmd_cond.js +231 -406
- package/dist/psql/command/cmd_connect.js +557 -764
- package/dist/psql/command/cmd_copy.js +728 -984
- package/dist/psql/command/cmd_describe.js +1499 -1688
- package/dist/psql/command/cmd_format.js +733 -905
- package/dist/psql/command/cmd_io.js +2 -2193
- package/dist/psql/command/cmd_lo.js +297 -359
- package/dist/psql/command/cmd_meta.js +727 -878
- package/dist/psql/command/cmd_misc.js +138 -172
- package/dist/psql/command/cmd_pipeline.js +2 -1148
- package/dist/psql/command/cmd_restrict.js +119 -155
- package/dist/psql/command/cmd_show.js +529 -688
- package/dist/psql/command/dispatch.js +260 -325
- package/dist/psql/command/inputQueue.js +35 -33
- package/dist/psql/command/shared.js +49 -63
- package/dist/psql/complete/filenames.js +90 -133
- package/dist/psql/complete/index.js +59 -97
- package/dist/psql/complete/matcher.js +236 -300
- package/dist/psql/complete/psqlVars.js +218 -223
- package/dist/psql/complete/queries.js +159 -177
- package/dist/psql/complete/rules.js +1493 -2299
- package/dist/psql/core/common.js +2 -1253
- package/dist/psql/core/help.js +456 -546
- package/dist/psql/core/mainloop.js +692 -1303
- package/dist/psql/core/prompt.js +391 -408
- package/dist/psql/core/settings.js +429 -644
- package/dist/psql/core/sqlHelp.js +480 -554
- package/dist/psql/core/startup.js +2 -846
- package/dist/psql/core/syncVars.js +67 -110
- package/dist/psql/core/variables.js +156 -278
- package/dist/psql/describe/formatters.js +884 -1285
- package/dist/psql/describe/processNamePattern.js +173 -260
- package/dist/psql/describe/queries.js +1368 -2403
- package/dist/psql/describe/versionGate.js +32 -41
- package/dist/psql/index.js +2 -2030
- package/dist/psql/io/history.js +232 -271
- package/dist/psql/io/input.js +103 -108
- package/dist/psql/io/lineEditor/buffer.js +238 -319
- package/dist/psql/io/lineEditor/complete.js +135 -213
- package/dist/psql/io/lineEditor/filename.js +139 -148
- package/dist/psql/io/lineEditor/index.js +653 -870
- package/dist/psql/io/lineEditor/keymap.js +544 -702
- package/dist/psql/io/lineEditor/vt100.js +294 -341
- package/dist/psql/io/pgpass.js +158 -187
- package/dist/psql/io/pgservice.js +146 -183
- package/dist/psql/io/psqlrc.js +328 -403
- package/dist/psql/print/aligned.js +1020 -1683
- package/dist/psql/print/asciidoc.js +180 -214
- package/dist/psql/print/crosstab.js +281 -442
- package/dist/psql/print/csv.js +48 -70
- package/dist/psql/print/html.js +195 -226
- package/dist/psql/print/json.js +75 -88
- package/dist/psql/print/latex.js +291 -364
- package/dist/psql/print/pager.js +171 -242
- package/dist/psql/print/troff.js +194 -226
- package/dist/psql/print/unaligned.js +69 -95
- package/dist/psql/print/units.js +167 -169
- package/dist/psql/scanner/slash.js +428 -483
- package/dist/psql/scanner/sql.js +445 -889
- package/dist/psql/scanner/stringutils.js +309 -379
- package/dist/psql/types/index.js +8 -7
- package/dist/psql/types/scanner.js +25 -22
- package/dist/psql/wire/connection.js +2042 -2803
- package/dist/psql/wire/copy.js +84 -100
- package/dist/psql/wire/notify.js +39 -59
- package/dist/psql/wire/pipeline.js +305 -518
- package/dist/psql/wire/protocol.js +349 -417
- package/dist/psql/wire/sasl.js +180 -265
- package/dist/psql/wire/tls.js +400 -561
- package/dist/storage_api.js +115 -129
- package/dist/test_utils/fixtures.js +94 -113
- package/dist/test_utils/oauth_server.js +10 -7
- package/dist/test_utils/project_dir.js +33 -0
- package/dist/utils/ai_gateway_notice.js +131 -162
- package/dist/utils/api_enums.js +21 -28
- package/dist/utils/auth.js +10 -4
- package/dist/utils/branch_notice.js +20 -19
- package/dist/utils/branch_picker.js +83 -89
- package/dist/utils/cli_name.js +15 -12
- package/dist/utils/compute_units.js +20 -27
- package/dist/utils/config_diff.js +127 -158
- package/dist/utils/enrichers.js +95 -148
- package/dist/utils/esbuild.js +130 -189
- package/dist/utils/flags.js +35 -47
- package/dist/utils/formats.js +8 -15
- package/dist/utils/git_diff.js +69 -80
- package/dist/utils/inspect_db.js +101 -143
- package/dist/utils/inspect_queries.js +179 -142
- package/dist/utils/middlewares.js +39 -45
- package/dist/utils/openapi.js +87 -99
- package/dist/utils/package_manager.js +312 -110
- package/dist/utils/point_in_time.js +49 -53
- package/dist/utils/psql.js +89 -106
- package/dist/utils/service_picker.js +55 -58
- package/dist/utils/string.js +5 -5
- package/dist/utils/ui.js +38 -55
- package/dist/utils/write_sync.js +26 -35
- package/dist/utils/zip.js +4 -3
- package/dist/writer.js +67 -87
- package/package.json +11 -6
- package/dist/_shared/auth_selection.js +0 -86
- package/dist/_shared/credentials.js +0 -209
- package/dist/_shared/env-core/env.js +0 -558
- package/dist/_shared/env-core/reuse-secrets.js +0 -223
- package/dist/_shared/paths.js +0 -148
- package/dist/_shared/profiles.js +0 -276
- package/dist/_shared/secure_file.js +0 -43
|
@@ -1,2857 +1,2096 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PgConnection — the wire-layer Connection implementation (WP-02 + WP-16).
|
|
3
|
-
*
|
|
4
|
-
* Implements `Connection` (frozen WP-00 interface in
|
|
5
|
-
* `src/psql/types/connection.ts`) on top of a single TCP / TLS socket. The
|
|
6
|
-
* class is essentially a state machine:
|
|
7
|
-
*
|
|
8
|
-
* auth → drive Authentication* messages
|
|
9
|
-
* await-ready → collect ParameterStatus + BackendKeyData
|
|
10
|
-
* idle → ready for a Query / extended-protocol cycle
|
|
11
|
-
* in-query → simple Query in flight, accumulating ResultSets
|
|
12
|
-
* in-copy-in → COPY FROM STDIN data transfer in progress (WP-16)
|
|
13
|
-
* in-copy-out → COPY TO STDOUT data transfer in progress (WP-16)
|
|
14
|
-
* closed → socket gone
|
|
15
|
-
*
|
|
16
|
-
* What this module owns:
|
|
17
|
-
* - Socket lifecycle (open, optional TLS, Terminate, close).
|
|
18
|
-
* - Auth: Cleartext, MD5, SASL/SCRAM (delegating to ./sasl.ts).
|
|
19
|
-
* - Tracking server `serverVersion` from ParameterStatus.
|
|
20
|
-
* - Simple-query path (`execSimple`) — collect RowDescription/DataRow/
|
|
21
|
-
* CommandComplete tuples into ResultSet[].
|
|
22
|
-
* - Async messages (Notice, Notification) routed through ./notify.ts.
|
|
23
|
-
* - ErrorResponse → rejected promise mapped to ConnectError shape.
|
|
24
|
-
* - Async cancel via a side connection (CancelRequest).
|
|
25
|
-
* - COPY streaming (WP-16): `startCopyIn` returns a `CopyInStream` that
|
|
26
|
-
* wires CopyData / CopyDone / CopyFail; `startCopyOut` returns an
|
|
27
|
-
* `AsyncIterable<Buffer>` that drains CopyData messages from the wire.
|
|
28
|
-
* The state machine threads through `in-copy-in` / `in-copy-out`.
|
|
29
|
-
*
|
|
30
|
-
* What is stubbed / deferred:
|
|
31
|
-
* - Extended-query protocol (Parse/Bind/Execute/Sync). The framing is in
|
|
32
|
-
* protocol.ts but the high-level path (parameterised `query()`, prepared
|
|
33
|
-
* statements, pipeline) is WP-21. We throw clearly when called.
|
|
34
|
-
*/
|
|
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
1
|
import { NoticeMultiplexer } from "./notify.js";
|
|
2
|
+
import { Bind, CancelRequest, Close, CopyData, CopyDone, CopyFail, Describe, Execute, MessageParser, Parse, PasswordMessage, Query, SASLInitialResponse, SASLResponse, StartupMessage, Sync, Terminate, fieldsToNotice } from "./protocol.js";
|
|
41
3
|
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
4
|
import { createScramClient } from "./sasl.js";
|
|
44
5
|
import { negotiateTls } from "./tls.js";
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
import { Buffer } from "node:buffer";
|
|
8
|
+
import * as dns from "node:dns/promises";
|
|
9
|
+
import * as net from "node:net";
|
|
10
|
+
import * as tls from "node:tls";
|
|
11
|
+
//#region src/psql/wire/connection.ts
|
|
12
|
+
/**
|
|
13
|
+
* PgConnection — the wire-layer Connection implementation (WP-02 + WP-16).
|
|
14
|
+
*
|
|
15
|
+
* Implements `Connection` (frozen WP-00 interface in
|
|
16
|
+
* `src/psql/types/connection.ts`) on top of a single TCP / TLS socket. The
|
|
17
|
+
* class is essentially a state machine:
|
|
18
|
+
*
|
|
19
|
+
* auth → drive Authentication* messages
|
|
20
|
+
* await-ready → collect ParameterStatus + BackendKeyData
|
|
21
|
+
* idle → ready for a Query / extended-protocol cycle
|
|
22
|
+
* in-query → simple Query in flight, accumulating ResultSets
|
|
23
|
+
* in-copy-in → COPY FROM STDIN data transfer in progress (WP-16)
|
|
24
|
+
* in-copy-out → COPY TO STDOUT data transfer in progress (WP-16)
|
|
25
|
+
* closed → socket gone
|
|
26
|
+
*
|
|
27
|
+
* What this module owns:
|
|
28
|
+
* - Socket lifecycle (open, optional TLS, Terminate, close).
|
|
29
|
+
* - Auth: Cleartext, MD5, SASL/SCRAM (delegating to ./sasl.ts).
|
|
30
|
+
* - Tracking server `serverVersion` from ParameterStatus.
|
|
31
|
+
* - Simple-query path (`execSimple`) — collect RowDescription/DataRow/
|
|
32
|
+
* CommandComplete tuples into ResultSet[].
|
|
33
|
+
* - Async messages (Notice, Notification) routed through ./notify.ts.
|
|
34
|
+
* - ErrorResponse → rejected promise mapped to ConnectError shape.
|
|
35
|
+
* - Async cancel via a side connection (CancelRequest).
|
|
36
|
+
* - COPY streaming (WP-16): `startCopyIn` returns a `CopyInStream` that
|
|
37
|
+
* wires CopyData / CopyDone / CopyFail; `startCopyOut` returns an
|
|
38
|
+
* `AsyncIterable<Buffer>` that drains CopyData messages from the wire.
|
|
39
|
+
* The state machine threads through `in-copy-in` / `in-copy-out`.
|
|
40
|
+
*
|
|
41
|
+
* What is stubbed / deferred:
|
|
42
|
+
* - Extended-query protocol (Parse/Bind/Execute/Sync). The framing is in
|
|
43
|
+
* protocol.ts but the high-level path (parameterised `query()`, prepared
|
|
44
|
+
* statements, pipeline) is WP-21. We throw clearly when called.
|
|
45
|
+
*/
|
|
45
46
|
/**
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
* Map a Notice-flavoured fields map into a `ConnectError` that includes a
|
|
48
|
+
* recognizable `message` plus a `cause` slot for the raw record.
|
|
49
|
+
*/
|
|
49
50
|
function fieldsToConnectError(fields) {
|
|
50
|
-
|
|
51
|
+
return {
|
|
52
|
+
...fieldsToNotice(fields),
|
|
53
|
+
cause: fields
|
|
54
|
+
};
|
|
51
55
|
}
|
|
52
56
|
/**
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
* Synthetic ConnectError used as the rejection reason for queued
|
|
58
|
+
* pipeline ops that the server skipped after a preceding ErrorResponse.
|
|
59
|
+
* Mirrors libpq's `PGRES_PIPELINE_ABORTED` result — the message text
|
|
60
|
+
* matches the string libpq stamps onto skipped ops so the cmd layer
|
|
61
|
+
* can render it byte-identically with vanilla psql's `\getresults` /
|
|
62
|
+
* `\endpipeline` output.
|
|
63
|
+
*/
|
|
60
64
|
function pipelineAbortedError() {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
return {
|
|
66
|
+
severity: "ERROR",
|
|
67
|
+
code: "",
|
|
68
|
+
message: "Pipeline aborted, command did not run",
|
|
69
|
+
pipelineAborted: true
|
|
70
|
+
};
|
|
67
71
|
}
|
|
68
72
|
/** Parse "PostgreSQL 16.2 …" into a numeric `major * 10000 + minor * 100`. */
|
|
69
73
|
function parseServerVersion(value) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const major = parseInt(m[1], 10);
|
|
78
|
-
const minor = m[2] !== undefined ? parseInt(m[2], 10) : 0;
|
|
79
|
-
if (major >= 10) {
|
|
80
|
-
return major * 10000 + minor;
|
|
81
|
-
}
|
|
82
|
-
const patch = m[3] !== undefined ? parseInt(m[3], 10) : 0;
|
|
83
|
-
return major * 10000 + minor * 100 + patch;
|
|
74
|
+
const m = /^([0-9]+)(?:\.([0-9]+))?(?:\.([0-9]+))?/.exec(value);
|
|
75
|
+
if (!m) return 0;
|
|
76
|
+
const major = parseInt(m[1], 10);
|
|
77
|
+
const minor = m[2] !== void 0 ? parseInt(m[2], 10) : 0;
|
|
78
|
+
if (major >= 10) return major * 1e4 + minor;
|
|
79
|
+
const patch = m[3] !== void 0 ? parseInt(m[3], 10) : 0;
|
|
80
|
+
return major * 1e4 + minor * 100 + patch;
|
|
84
81
|
}
|
|
85
82
|
/**
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
83
|
+
* MD5 auth: `'md5' + md5( md5(password + user) || salt )`. Inner hash uses
|
|
84
|
+
* the username; outer is salted. PG uses lowercase hex everywhere.
|
|
85
|
+
*/
|
|
89
86
|
function md5AuthPayload(user, password, salt) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
.digest("hex");
|
|
93
|
-
const outer = createHash("md5")
|
|
94
|
-
.update(inner, "utf8")
|
|
95
|
-
.update(salt)
|
|
96
|
-
.digest("hex");
|
|
97
|
-
return "md5" + outer;
|
|
87
|
+
const inner = createHash("md5").update(password + user, "utf8").digest("hex");
|
|
88
|
+
return "md5" + createHash("md5").update(inner, "utf8").update(salt).digest("hex");
|
|
98
89
|
}
|
|
99
90
|
/**
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
91
|
+
* Fisher-Yates in-place shuffle of the candidate hosts list. Used by
|
|
92
|
+
* `load_balance_hosts=random`. Hook the random source so tests can inject a
|
|
93
|
+
* deterministic permutation.
|
|
94
|
+
*/
|
|
104
95
|
function shuffleInPlace(arr, rng = Math.random) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
96
|
+
for (let i = arr.length - 1; i > 0; i -= 1) {
|
|
97
|
+
const j = Math.floor(rng() * (i + 1));
|
|
98
|
+
const tmp = arr[i];
|
|
99
|
+
arr[i] = arr[j];
|
|
100
|
+
arr[j] = tmp;
|
|
101
|
+
}
|
|
111
102
|
}
|
|
112
103
|
/**
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
104
|
+
* After a successful handshake, decide whether this connection matches the
|
|
105
|
+
* caller's `target_session_attrs` constraint by running
|
|
106
|
+
* `SELECT pg_is_in_recovery()`. Returns `true` if the connection is
|
|
107
|
+
* acceptable, `false` if it should be torn down and the next host tried.
|
|
108
|
+
*
|
|
109
|
+
* - 'any' (or undefined) — always accepts.
|
|
110
|
+
* - 'read-write' / 'primary' — accepts when NOT in recovery.
|
|
111
|
+
* - 'read-only' / 'standby' — accepts when IN recovery.
|
|
112
|
+
*
|
|
113
|
+
* If the probe query itself fails we treat the connection as unacceptable
|
|
114
|
+
* (returns false) so the orchestrator falls through to the next candidate
|
|
115
|
+
* rather than handing the caller a half-broken connection.
|
|
116
|
+
*/
|
|
126
117
|
async function checkSessionAttrs(conn, tsa) {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
else {
|
|
148
|
-
return false;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
catch {
|
|
152
|
-
return false;
|
|
153
|
-
}
|
|
154
|
-
switch (tsa) {
|
|
155
|
-
case "read-write":
|
|
156
|
-
case "primary":
|
|
157
|
-
return !inRecovery;
|
|
158
|
-
case "read-only":
|
|
159
|
-
case "standby":
|
|
160
|
-
return inRecovery;
|
|
161
|
-
// 'prefer-standby' is unwrapped by the orchestrator into two passes
|
|
162
|
-
// ('standby' then 'any'), so we never see it here. Fall through to
|
|
163
|
-
// accept-any for safety.
|
|
164
|
-
default:
|
|
165
|
-
return true;
|
|
166
|
-
}
|
|
118
|
+
if (tsa === void 0 || tsa === "any") return true;
|
|
119
|
+
let inRecovery;
|
|
120
|
+
try {
|
|
121
|
+
const sets = await conn.execSimple("SELECT pg_is_in_recovery()");
|
|
122
|
+
if (sets.length === 0 || sets[0].rows.length === 0) return false;
|
|
123
|
+
const raw = sets[0].rows[0][0];
|
|
124
|
+
if (raw === true) inRecovery = true;
|
|
125
|
+
else if (raw === false) inRecovery = false;
|
|
126
|
+
else if (typeof raw === "string") inRecovery = raw === "t" || raw.toLowerCase() === "true";
|
|
127
|
+
else return false;
|
|
128
|
+
} catch {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
switch (tsa) {
|
|
132
|
+
case "read-write":
|
|
133
|
+
case "primary": return !inRecovery;
|
|
134
|
+
case "read-only":
|
|
135
|
+
case "standby": return inRecovery;
|
|
136
|
+
default: return true;
|
|
137
|
+
}
|
|
167
138
|
}
|
|
168
139
|
/**
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const COPY_OUT_HWM =
|
|
175
|
-
const COPY_OUT_LWM =
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
// current ResultSet's `copyOutBytes` so the renderer emits them at
|
|
1849
|
-
// the result's position in the chain — instead of streaming them
|
|
1850
|
-
// straight to a sink at receive time, which would hoist the COPY
|
|
1851
|
-
// bytes above any tuples-producing results that haven't been
|
|
1852
|
-
// rendered yet (see hunk 5722-5730 in regress/psql).
|
|
1853
|
-
this.copyOutMidBatchActive = true;
|
|
1854
|
-
q.current = q.current ?? {
|
|
1855
|
-
command: "",
|
|
1856
|
-
rowCount: null,
|
|
1857
|
-
oid: null,
|
|
1858
|
-
fields: [],
|
|
1859
|
-
rows: [],
|
|
1860
|
-
notices: [],
|
|
1861
|
-
};
|
|
1862
|
-
q.current.copyOutBytes = q.current.copyOutBytes ?? [];
|
|
1863
|
-
return;
|
|
1864
|
-
}
|
|
1865
|
-
case "CopyData": {
|
|
1866
|
-
// CopyData arrives during execSimple only when we're in the mid-batch
|
|
1867
|
-
// COPY-OUT phase (CopyOutResponse flipped the flag above). Stash the
|
|
1868
|
-
// payload on the current result's `copyOutBytes` so the caller can
|
|
1869
|
-
// render in order. Anything else is a protocol error.
|
|
1870
|
-
if (this.copyOutMidBatchActive) {
|
|
1871
|
-
const cur = q.current;
|
|
1872
|
-
if (cur) {
|
|
1873
|
-
cur.copyOutBytes = cur.copyOutBytes ?? [];
|
|
1874
|
-
cur.copyOutBytes.push(msg.data);
|
|
1875
|
-
}
|
|
1876
|
-
return;
|
|
1877
|
-
}
|
|
1878
|
-
q.error = {
|
|
1879
|
-
severity: "ERROR",
|
|
1880
|
-
message: "Unexpected backend message during query: CopyData",
|
|
1881
|
-
};
|
|
1882
|
-
return;
|
|
1883
|
-
}
|
|
1884
|
-
case "CopyDone": {
|
|
1885
|
-
// Server signals end of COPY-OUT data — next message will be
|
|
1886
|
-
// CommandComplete for the COPY statement, then the batch resumes.
|
|
1887
|
-
if (this.copyOutMidBatchActive) {
|
|
1888
|
-
this.copyOutMidBatchActive = false;
|
|
1889
|
-
return;
|
|
1890
|
-
}
|
|
1891
|
-
q.error = {
|
|
1892
|
-
severity: "ERROR",
|
|
1893
|
-
message: "Unexpected backend message during query: CopyDone",
|
|
1894
|
-
};
|
|
1895
|
-
return;
|
|
1896
|
-
}
|
|
1897
|
-
case "CopyBothResponse": {
|
|
1898
|
-
// Walsender (`replication=database` / `replication=true`) commands
|
|
1899
|
-
// such as `START_REPLICATION` transition the connection into a
|
|
1900
|
-
// CopyBoth streaming phase (WAL records flowing from server +
|
|
1901
|
-
// keepalive replies flowing from client). This client does not
|
|
1902
|
-
// implement WAL streaming — upstream libpq's `PQexec` similarly
|
|
1903
|
-
// refuses to handle PGRES_COPY_BOTH and surfaces a diagnostic. We
|
|
1904
|
-
// mirror that: reject the pending query with a "syntax error" style
|
|
1905
|
-
// message (matching the conformance assertion) and tear the socket
|
|
1906
|
-
// down so the next query / process exit is clean.
|
|
1907
|
-
const cbErr = {
|
|
1908
|
-
severity: "ERROR",
|
|
1909
|
-
code: "0A000",
|
|
1910
|
-
message: "syntax error: unexpected CopyBothResponse from server (replication streaming is not supported by this client)",
|
|
1911
|
-
};
|
|
1912
|
-
q.error = cbErr;
|
|
1913
|
-
q.reject(asThrowable(cbErr));
|
|
1914
|
-
this.pendingQuery = null;
|
|
1915
|
-
this.socketError = new Error(cbErr.message);
|
|
1916
|
-
try {
|
|
1917
|
-
this.socket.destroy();
|
|
1918
|
-
}
|
|
1919
|
-
catch {
|
|
1920
|
-
// ignore
|
|
1921
|
-
}
|
|
1922
|
-
this.state = "closed";
|
|
1923
|
-
return;
|
|
1924
|
-
}
|
|
1925
|
-
default:
|
|
1926
|
-
// Unknown messages during a query are protocol errors but not fatal
|
|
1927
|
-
// for the connection — record them.
|
|
1928
|
-
q.error = {
|
|
1929
|
-
severity: "ERROR",
|
|
1930
|
-
message: `Unexpected backend message during query: ${msg.type}`,
|
|
1931
|
-
};
|
|
1932
|
-
return;
|
|
1933
|
-
}
|
|
1934
|
-
}
|
|
1935
|
-
// -------------------------------------------------------------------------
|
|
1936
|
-
// Socket → parser → state dispatch
|
|
1937
|
-
// -------------------------------------------------------------------------
|
|
1938
|
-
onData(chunk) {
|
|
1939
|
-
let messages;
|
|
1940
|
-
try {
|
|
1941
|
-
messages = this.parser.feed(chunk);
|
|
1942
|
-
}
|
|
1943
|
-
catch (err) {
|
|
1944
|
-
this.socketError =
|
|
1945
|
-
err instanceof Error ? err : new Error(String(err));
|
|
1946
|
-
this.failPending(this.socketError);
|
|
1947
|
-
try {
|
|
1948
|
-
this.socket.destroy();
|
|
1949
|
-
}
|
|
1950
|
-
catch {
|
|
1951
|
-
// ignore
|
|
1952
|
-
}
|
|
1953
|
-
this.state = "closed";
|
|
1954
|
-
return;
|
|
1955
|
-
}
|
|
1956
|
-
for (const msg of messages) {
|
|
1957
|
-
this.dispatch(msg);
|
|
1958
|
-
if (this.state === "closed")
|
|
1959
|
-
break;
|
|
1960
|
-
}
|
|
1961
|
-
}
|
|
1962
|
-
dispatch(msg) {
|
|
1963
|
-
// Async backend messages always allowed. NotificationResponse can arrive
|
|
1964
|
-
// in *any* state since LISTEN payloads come in whenever a NOTIFY fires.
|
|
1965
|
-
if (msg.type === "NotificationResponse") {
|
|
1966
|
-
this.notify.emitNotification(msg.channel, msg.payload, msg.processId);
|
|
1967
|
-
return;
|
|
1968
|
-
}
|
|
1969
|
-
switch (this.state) {
|
|
1970
|
-
case "auth":
|
|
1971
|
-
this.handleAuthMessage(msg);
|
|
1972
|
-
return;
|
|
1973
|
-
case "await-ready":
|
|
1974
|
-
this.handleAwaitReady(msg);
|
|
1975
|
-
return;
|
|
1976
|
-
case "idle":
|
|
1977
|
-
// ParameterStatus changes can arrive asynchronously (SET).
|
|
1978
|
-
if (msg.type === "ParameterStatus") {
|
|
1979
|
-
this.params.set(msg.name, msg.value);
|
|
1980
|
-
if (msg.name === "server_version") {
|
|
1981
|
-
this.serverVersion = parseServerVersion(msg.value);
|
|
1982
|
-
}
|
|
1983
|
-
return;
|
|
1984
|
-
}
|
|
1985
|
-
if (msg.type === "NoticeResponse") {
|
|
1986
|
-
this.notify.emit(fieldsToNotice(msg.fields));
|
|
1987
|
-
return;
|
|
1988
|
-
}
|
|
1989
|
-
// (NotificationResponse is handled by the early-out above.)
|
|
1990
|
-
// Anything else in idle is unexpected.
|
|
1991
|
-
this.socketError = new Error(`Unexpected ${msg.type} in idle state`);
|
|
1992
|
-
try {
|
|
1993
|
-
this.socket.destroy();
|
|
1994
|
-
}
|
|
1995
|
-
catch {
|
|
1996
|
-
// ignore
|
|
1997
|
-
}
|
|
1998
|
-
this.state = "closed";
|
|
1999
|
-
return;
|
|
2000
|
-
case "in-query":
|
|
2001
|
-
this.handleQueryMessage(msg);
|
|
2002
|
-
return;
|
|
2003
|
-
case "in-extended":
|
|
2004
|
-
this.handleExtendedMessage(msg);
|
|
2005
|
-
return;
|
|
2006
|
-
case "in-copy-in":
|
|
2007
|
-
this.handleCopyInMessage(msg);
|
|
2008
|
-
return;
|
|
2009
|
-
case "in-copy-out":
|
|
2010
|
-
this.handleCopyOutMessage(msg);
|
|
2011
|
-
return;
|
|
2012
|
-
case "closed":
|
|
2013
|
-
return;
|
|
2014
|
-
}
|
|
2015
|
-
}
|
|
2016
|
-
failPending(err) {
|
|
2017
|
-
if (this.pendingQuery) {
|
|
2018
|
-
const q = this.pendingQuery;
|
|
2019
|
-
this.pendingQuery = null;
|
|
2020
|
-
// If the server delivered an ErrorResponse just before the socket
|
|
2021
|
-
// closed (e.g. a FATAL "terminating connection due to administrator
|
|
2022
|
-
// command" when the backend is killed mid-query), prefer that
|
|
2023
|
-
// structured error over the generic "Socket closed" fallback so the
|
|
2024
|
-
// diagnostic carries the server's wording. Mirrors libpq's behaviour
|
|
2025
|
-
// where `PQexec` surfaces the FATAL message and `PQerrorMessage`
|
|
2026
|
-
// returns the server-supplied text.
|
|
2027
|
-
//
|
|
2028
|
-
// `q.error` is initialised to `null` by `execSimple`; only a non-null
|
|
2029
|
-
// value indicates a server-side ErrorResponse was actually captured.
|
|
2030
|
-
q.reject(q.error != null ? asThrowable(q.error) : err);
|
|
2031
|
-
}
|
|
2032
|
-
if (this.extDriver) {
|
|
2033
|
-
const d = this.extDriver;
|
|
2034
|
-
this.extDriver = null;
|
|
2035
|
-
for (const op of d.queue)
|
|
2036
|
-
op.reject(err);
|
|
2037
|
-
}
|
|
2038
|
-
if (this.startupReject) {
|
|
2039
|
-
const r = this.startupReject;
|
|
2040
|
-
this.startupResolve = null;
|
|
2041
|
-
this.startupReject = null;
|
|
2042
|
-
r(err);
|
|
2043
|
-
}
|
|
2044
|
-
if (this.copyStartReject) {
|
|
2045
|
-
const r = this.copyStartReject;
|
|
2046
|
-
this.copyStartResolve = null;
|
|
2047
|
-
this.copyStartReject = null;
|
|
2048
|
-
r(err);
|
|
2049
|
-
}
|
|
2050
|
-
if (this.copyIn) {
|
|
2051
|
-
const d = this.copyIn;
|
|
2052
|
-
this.copyIn = null;
|
|
2053
|
-
if (d.rejectDone)
|
|
2054
|
-
d.rejectDone(err);
|
|
2055
|
-
}
|
|
2056
|
-
if (this.copyOut) {
|
|
2057
|
-
const d = this.copyOut;
|
|
2058
|
-
this.copyOut = null;
|
|
2059
|
-
d.error =
|
|
2060
|
-
err instanceof Error
|
|
2061
|
-
? { severity: "ERROR", message: err.message }
|
|
2062
|
-
: { severity: "ERROR", message: String(err) };
|
|
2063
|
-
if (d.waker) {
|
|
2064
|
-
const w = d.waker;
|
|
2065
|
-
d.waker = null;
|
|
2066
|
-
w();
|
|
2067
|
-
}
|
|
2068
|
-
}
|
|
2069
|
-
}
|
|
2070
|
-
ensureIdle() {
|
|
2071
|
-
if (this.state === "closed") {
|
|
2072
|
-
throw new Error("PgConnection: connection is closed");
|
|
2073
|
-
}
|
|
2074
|
-
if (this.state !== "idle" && this.state !== "in-extended") {
|
|
2075
|
-
throw new Error(`PgConnection: cannot start query in state ${this.state}`);
|
|
2076
|
-
}
|
|
2077
|
-
}
|
|
2078
|
-
// -------------------------------------------------------------------------
|
|
2079
|
-
// Extended-protocol driver (WP-21).
|
|
2080
|
-
//
|
|
2081
|
-
// Each public enqueueX method appends one op to `extDriver.queue` and
|
|
2082
|
-
// returns a Promise that resolves when the op's terminator backend message
|
|
2083
|
-
// arrives. The caller is responsible for writing the matching wire frame
|
|
2084
|
-
// (Parse/Bind/Describe/Execute/Close/Sync) to the socket.
|
|
2085
|
-
// -------------------------------------------------------------------------
|
|
2086
|
-
startExtendedBatch() {
|
|
2087
|
-
if (this.state === "idle") {
|
|
2088
|
-
this.state = "in-extended";
|
|
2089
|
-
this.extDriver = { queue: [], error: null };
|
|
2090
|
-
}
|
|
2091
|
-
else if (this.state !== "in-extended") {
|
|
2092
|
-
throw new Error(`PgConnection: cannot start extended batch in state ${this.state}`);
|
|
2093
|
-
}
|
|
2094
|
-
else if (!this.extDriver) {
|
|
2095
|
-
this.extDriver = { queue: [], error: null };
|
|
2096
|
-
}
|
|
2097
|
-
}
|
|
2098
|
-
writeRaw(buf) {
|
|
2099
|
-
this.socket.write(buf);
|
|
2100
|
-
}
|
|
2101
|
-
enqueueParse() {
|
|
2102
|
-
return this.enqueueOp({
|
|
2103
|
-
kind: "parse",
|
|
2104
|
-
resolve: () => undefined,
|
|
2105
|
-
reject: () => undefined,
|
|
2106
|
-
});
|
|
2107
|
-
}
|
|
2108
|
-
enqueueBind() {
|
|
2109
|
-
return this.enqueueOp({
|
|
2110
|
-
kind: "bind",
|
|
2111
|
-
resolve: () => undefined,
|
|
2112
|
-
reject: () => undefined,
|
|
2113
|
-
});
|
|
2114
|
-
}
|
|
2115
|
-
enqueueDescribeStatement() {
|
|
2116
|
-
return this.enqueueOp({
|
|
2117
|
-
kind: "describeS",
|
|
2118
|
-
resolve: () => undefined,
|
|
2119
|
-
reject: () => undefined,
|
|
2120
|
-
paramOids: null,
|
|
2121
|
-
});
|
|
2122
|
-
}
|
|
2123
|
-
enqueueDescribePortal() {
|
|
2124
|
-
return this.enqueueOp({
|
|
2125
|
-
kind: "describeP",
|
|
2126
|
-
resolve: () => undefined,
|
|
2127
|
-
reject: () => undefined,
|
|
2128
|
-
});
|
|
2129
|
-
}
|
|
2130
|
-
/**
|
|
2131
|
-
* Variant of {@link enqueueDescribePortal} that pipes the resolved fields
|
|
2132
|
-
* onto the very next `execute` op already (or yet to be) on the queue.
|
|
2133
|
-
*/
|
|
2134
|
-
enqueueDescribePortalIntoNextExecute() {
|
|
2135
|
-
const driver = this.extDriver;
|
|
2136
|
-
if (!driver) {
|
|
2137
|
-
return Promise.reject(new Error("enqueueDescribePortalIntoNextExecute: not in extended state"));
|
|
2138
|
-
}
|
|
2139
|
-
return new Promise((resolve, reject) => {
|
|
2140
|
-
driver.queue.push({
|
|
2141
|
-
kind: "describeP",
|
|
2142
|
-
resolve: (v) => {
|
|
2143
|
-
const fields = v;
|
|
2144
|
-
for (const op of driver.queue) {
|
|
2145
|
-
if (op.kind === "execute" && op.fields === null) {
|
|
2146
|
-
op.fields = fields;
|
|
2147
|
-
break;
|
|
2148
|
-
}
|
|
2149
|
-
}
|
|
2150
|
-
resolve();
|
|
2151
|
-
},
|
|
2152
|
-
reject,
|
|
2153
|
-
});
|
|
2154
|
-
});
|
|
2155
|
-
}
|
|
2156
|
-
enqueueExecute() {
|
|
2157
|
-
return this.enqueueOp({
|
|
2158
|
-
kind: "execute",
|
|
2159
|
-
resolve: () => undefined,
|
|
2160
|
-
reject: () => undefined,
|
|
2161
|
-
current: null,
|
|
2162
|
-
notices: [],
|
|
2163
|
-
fields: null,
|
|
2164
|
-
});
|
|
2165
|
-
}
|
|
2166
|
-
enqueueExecuteWithFields(fields) {
|
|
2167
|
-
return this.enqueueOp({
|
|
2168
|
-
kind: "execute",
|
|
2169
|
-
resolve: () => undefined,
|
|
2170
|
-
reject: () => undefined,
|
|
2171
|
-
current: null,
|
|
2172
|
-
notices: [],
|
|
2173
|
-
fields,
|
|
2174
|
-
});
|
|
2175
|
-
}
|
|
2176
|
-
enqueueClose() {
|
|
2177
|
-
return this.enqueueOp({
|
|
2178
|
-
kind: "close",
|
|
2179
|
-
resolve: () => undefined,
|
|
2180
|
-
reject: () => undefined,
|
|
2181
|
-
});
|
|
2182
|
-
}
|
|
2183
|
-
enqueueSync() {
|
|
2184
|
-
return this.enqueueOp({
|
|
2185
|
-
kind: "sync",
|
|
2186
|
-
resolve: () => undefined,
|
|
2187
|
-
reject: () => undefined,
|
|
2188
|
-
});
|
|
2189
|
-
}
|
|
2190
|
-
enqueueOp(opSkeleton) {
|
|
2191
|
-
if (!this.extDriver) {
|
|
2192
|
-
return Promise.reject(new Error("enqueueOp: not in extended state"));
|
|
2193
|
-
}
|
|
2194
|
-
const driver = this.extDriver;
|
|
2195
|
-
return new Promise((resolve, reject) => {
|
|
2196
|
-
const op = opSkeleton;
|
|
2197
|
-
op.resolve = resolve;
|
|
2198
|
-
op.reject = reject;
|
|
2199
|
-
driver.queue.push(op);
|
|
2200
|
-
});
|
|
2201
|
-
}
|
|
2202
|
-
handleExtendedMessage(msg) {
|
|
2203
|
-
const driver = this.extDriver;
|
|
2204
|
-
if (!driver)
|
|
2205
|
-
return;
|
|
2206
|
-
if (msg.type === "ParameterStatus") {
|
|
2207
|
-
this.params.set(msg.name, msg.value);
|
|
2208
|
-
if (msg.name === "server_version") {
|
|
2209
|
-
this.serverVersion = parseServerVersion(msg.value);
|
|
2210
|
-
}
|
|
2211
|
-
return;
|
|
2212
|
-
}
|
|
2213
|
-
if (msg.type === "NoticeResponse") {
|
|
2214
|
-
const notice = fieldsToNotice(msg.fields);
|
|
2215
|
-
this.notify.emit(notice);
|
|
2216
|
-
const head = driver.queue[0];
|
|
2217
|
-
if (head && head.kind === "execute")
|
|
2218
|
-
head.notices.push(notice);
|
|
2219
|
-
return;
|
|
2220
|
-
}
|
|
2221
|
-
if (msg.type === "NotificationResponse") {
|
|
2222
|
-
this.notify.emitNotification(msg.channel, msg.payload, msg.processId);
|
|
2223
|
-
return;
|
|
2224
|
-
}
|
|
2225
|
-
if (msg.type === "ErrorResponse") {
|
|
2226
|
-
driver.error = fieldsToConnectError(msg.fields);
|
|
2227
|
-
// Reject ALL queued non-sync ops eagerly. Upstream server semantics:
|
|
2228
|
-
// once a P/B/D/E op errors, the server skips every subsequent message
|
|
2229
|
-
// until the next Sync. If the client (e.g. `\flushrequest` + `\getresults`
|
|
2230
|
-
// after an aborted bind) doesn't issue Sync next, no further wire
|
|
2231
|
-
// messages will arrive — so we must cascade-reject the rest of the
|
|
2232
|
-
// queue NOW or those promises hang forever.
|
|
2233
|
-
//
|
|
2234
|
-
// Mirror libpq's `PGRES_PIPELINE_ABORTED` marker for follow-on ops:
|
|
2235
|
-
// the FIRST failing op carries the real `ErrorResponse` payload,
|
|
2236
|
-
// every subsequent op gets a synthetic "Pipeline aborted, command
|
|
2237
|
-
// did not run" error so `\getresults` / `\endpipeline` can
|
|
2238
|
-
// distinguish the originating ERROR from the cascaded skips. See
|
|
2239
|
-
// upstream `pqPipelineProcessQueue` in `fe-exec.c`.
|
|
2240
|
-
let first = true;
|
|
2241
|
-
while (driver.queue.length > 0) {
|
|
2242
|
-
const head = driver.queue[0];
|
|
2243
|
-
if (head.kind === "sync")
|
|
2244
|
-
break;
|
|
2245
|
-
driver.queue.shift();
|
|
2246
|
-
if (first) {
|
|
2247
|
-
head.reject(driver.error);
|
|
2248
|
-
first = false;
|
|
2249
|
-
}
|
|
2250
|
-
else {
|
|
2251
|
-
head.reject(pipelineAbortedError());
|
|
2252
|
-
}
|
|
2253
|
-
}
|
|
2254
|
-
return;
|
|
2255
|
-
}
|
|
2256
|
-
// COPY-in-pipeline: when an `Execute` in pipeline mode hits a
|
|
2257
|
-
// `COPY ... FROM STDIN` / `COPY ... TO STDOUT`, the server replies
|
|
2258
|
-
// with `CopyInResponse` / `CopyOutResponse` instead of the usual
|
|
2259
|
-
// result-stream messages. Upstream libpq refuses the combination
|
|
2260
|
-
// with "COPY in a pipeline is not supported, aborting connection"
|
|
2261
|
-
// and tears the connection down. Mirror that so `\startpipeline +
|
|
2262
|
-
// COPY ...` surfaces the expected fatal error rather than hanging
|
|
2263
|
-
// on a response the extended driver doesn't know how to consume.
|
|
2264
|
-
if (msg.type === "CopyInResponse" || msg.type === "CopyOutResponse") {
|
|
2265
|
-
this.abortForCopyInPipeline();
|
|
2266
|
-
return;
|
|
2267
|
-
}
|
|
2268
|
-
// Drain any ops added to the queue AFTER the initial cascade-reject
|
|
2269
|
-
// (e.g. a `\sendpipeline` issued by the user once `\getresults`
|
|
2270
|
-
// returned the first ErrorResponse) — those ops were never visible
|
|
2271
|
-
// to the original cascade loop, but the server skipped them too
|
|
2272
|
-
// because it stays in PIPELINE_ABORTED until the next Sync. Mark
|
|
2273
|
-
// them as cascaded (`pipelineAborted`) rather than the real error:
|
|
2274
|
-
// libpq surfaces the real error only on the OP that actually
|
|
2275
|
-
// failed, and stamps every subsequent skipped op with the
|
|
2276
|
-
// PGRES_PIPELINE_ABORTED marker. The cmd layer's `\getresults` /
|
|
2277
|
-
// `\endpipeline` paths render the marker as
|
|
2278
|
-
// `Pipeline aborted, command did not run` (no `ERROR:` prefix).
|
|
2279
|
-
while (driver.error !== null) {
|
|
2280
|
-
const head = driver.queue[0];
|
|
2281
|
-
if (!head || head.kind === "sync")
|
|
2282
|
-
break;
|
|
2283
|
-
driver.queue.shift();
|
|
2284
|
-
head.reject(pipelineAbortedError());
|
|
2285
|
-
}
|
|
2286
|
-
const head = driver.queue[0];
|
|
2287
|
-
if (!head) {
|
|
2288
|
-
this.protocolFail(new Error(`Unexpected backend message ${msg.type} in in-extended`));
|
|
2289
|
-
return;
|
|
2290
|
-
}
|
|
2291
|
-
switch (msg.type) {
|
|
2292
|
-
case "ParseComplete":
|
|
2293
|
-
if (head.kind !== "parse") {
|
|
2294
|
-
this.protocolFail(new Error("ParseComplete arrived but head op is " + head.kind));
|
|
2295
|
-
return;
|
|
2296
|
-
}
|
|
2297
|
-
driver.queue.shift();
|
|
2298
|
-
head.resolve(undefined);
|
|
2299
|
-
return;
|
|
2300
|
-
case "BindComplete":
|
|
2301
|
-
if (head.kind !== "bind") {
|
|
2302
|
-
this.protocolFail(new Error("BindComplete arrived but head op is " + head.kind));
|
|
2303
|
-
return;
|
|
2304
|
-
}
|
|
2305
|
-
driver.queue.shift();
|
|
2306
|
-
head.resolve(undefined);
|
|
2307
|
-
return;
|
|
2308
|
-
case "CloseComplete":
|
|
2309
|
-
if (head.kind !== "close") {
|
|
2310
|
-
this.protocolFail(new Error("CloseComplete arrived but head op is " + head.kind));
|
|
2311
|
-
return;
|
|
2312
|
-
}
|
|
2313
|
-
driver.queue.shift();
|
|
2314
|
-
head.resolve(undefined);
|
|
2315
|
-
return;
|
|
2316
|
-
case "ParameterDescription":
|
|
2317
|
-
if (head.kind !== "describeS") {
|
|
2318
|
-
this.protocolFail(new Error("ParameterDescription arrived but head op is " +
|
|
2319
|
-
head.kind));
|
|
2320
|
-
return;
|
|
2321
|
-
}
|
|
2322
|
-
head.paramOids = msg.oids;
|
|
2323
|
-
return;
|
|
2324
|
-
case "RowDescription":
|
|
2325
|
-
if (head.kind === "describeS") {
|
|
2326
|
-
driver.queue.shift();
|
|
2327
|
-
head.resolve({
|
|
2328
|
-
paramOids: head.paramOids ?? [],
|
|
2329
|
-
fields: msg.fields,
|
|
2330
|
-
});
|
|
2331
|
-
return;
|
|
2332
|
-
}
|
|
2333
|
-
if (head.kind === "describeP") {
|
|
2334
|
-
driver.queue.shift();
|
|
2335
|
-
head.resolve(msg.fields);
|
|
2336
|
-
return;
|
|
2337
|
-
}
|
|
2338
|
-
if (head.kind === "execute") {
|
|
2339
|
-
head.current = {
|
|
2340
|
-
command: "",
|
|
2341
|
-
rowCount: null,
|
|
2342
|
-
oid: null,
|
|
2343
|
-
fields: msg.fields,
|
|
2344
|
-
rows: [],
|
|
2345
|
-
notices: [],
|
|
2346
|
-
};
|
|
2347
|
-
head.fields = msg.fields;
|
|
2348
|
-
return;
|
|
2349
|
-
}
|
|
2350
|
-
this.protocolFail(new Error("Unexpected RowDescription at head op " + head.kind));
|
|
2351
|
-
return;
|
|
2352
|
-
case "NoData":
|
|
2353
|
-
if (head.kind === "describeS") {
|
|
2354
|
-
driver.queue.shift();
|
|
2355
|
-
head.resolve({
|
|
2356
|
-
paramOids: head.paramOids ?? [],
|
|
2357
|
-
fields: [],
|
|
2358
|
-
});
|
|
2359
|
-
return;
|
|
2360
|
-
}
|
|
2361
|
-
if (head.kind === "describeP") {
|
|
2362
|
-
driver.queue.shift();
|
|
2363
|
-
head.resolve([]);
|
|
2364
|
-
return;
|
|
2365
|
-
}
|
|
2366
|
-
this.protocolFail(new Error("Unexpected NoData at head op " + head.kind));
|
|
2367
|
-
return;
|
|
2368
|
-
case "DataRow": {
|
|
2369
|
-
if (head.kind !== "execute") {
|
|
2370
|
-
this.protocolFail(new Error("DataRow at head op " + head.kind));
|
|
2371
|
-
return;
|
|
2372
|
-
}
|
|
2373
|
-
const fields = head.fields ?? head.current?.fields ?? [];
|
|
2374
|
-
if (!head.current) {
|
|
2375
|
-
head.current = {
|
|
2376
|
-
command: "",
|
|
2377
|
-
rowCount: null,
|
|
2378
|
-
oid: null,
|
|
2379
|
-
fields,
|
|
2380
|
-
rows: [],
|
|
2381
|
-
notices: [],
|
|
2382
|
-
};
|
|
2383
|
-
}
|
|
2384
|
-
head.current.rows.push(decodeDataRow(msg.values, fields));
|
|
2385
|
-
return;
|
|
2386
|
-
}
|
|
2387
|
-
case "CommandComplete": {
|
|
2388
|
-
if (head.kind !== "execute") {
|
|
2389
|
-
this.protocolFail(new Error("CommandComplete at head op " + head.kind));
|
|
2390
|
-
return;
|
|
2391
|
-
}
|
|
2392
|
-
const { command, rowCount, oid } = parseCommandTag(msg.tag);
|
|
2393
|
-
const set = head.current ?? {
|
|
2394
|
-
command,
|
|
2395
|
-
rowCount,
|
|
2396
|
-
oid,
|
|
2397
|
-
fields: head.fields ?? [],
|
|
2398
|
-
rows: [],
|
|
2399
|
-
notices: [],
|
|
2400
|
-
};
|
|
2401
|
-
set.command = command;
|
|
2402
|
-
set.rowCount = rowCount;
|
|
2403
|
-
set.oid = oid;
|
|
2404
|
-
set.notices = head.notices.splice(0);
|
|
2405
|
-
driver.queue.shift();
|
|
2406
|
-
head.resolve(set);
|
|
2407
|
-
return;
|
|
2408
|
-
}
|
|
2409
|
-
case "EmptyQueryResponse": {
|
|
2410
|
-
if (head.kind !== "execute") {
|
|
2411
|
-
this.protocolFail(new Error("EmptyQueryResponse at head op " + head.kind));
|
|
2412
|
-
return;
|
|
2413
|
-
}
|
|
2414
|
-
const set = {
|
|
2415
|
-
command: "",
|
|
2416
|
-
rowCount: null,
|
|
2417
|
-
oid: null,
|
|
2418
|
-
fields: [],
|
|
2419
|
-
rows: [],
|
|
2420
|
-
notices: head.notices.splice(0),
|
|
2421
|
-
};
|
|
2422
|
-
driver.queue.shift();
|
|
2423
|
-
head.resolve(set);
|
|
2424
|
-
return;
|
|
2425
|
-
}
|
|
2426
|
-
case "PortalSuspended": {
|
|
2427
|
-
if (head.kind !== "execute") {
|
|
2428
|
-
this.protocolFail(new Error("PortalSuspended at head op " + head.kind));
|
|
2429
|
-
return;
|
|
2430
|
-
}
|
|
2431
|
-
const set = head.current ?? {
|
|
2432
|
-
command: "",
|
|
2433
|
-
rowCount: null,
|
|
2434
|
-
oid: null,
|
|
2435
|
-
fields: head.fields ?? [],
|
|
2436
|
-
rows: [],
|
|
2437
|
-
notices: head.notices.splice(0),
|
|
2438
|
-
};
|
|
2439
|
-
set.notices = head.notices.splice(0);
|
|
2440
|
-
driver.queue.shift();
|
|
2441
|
-
head.resolve(set);
|
|
2442
|
-
return;
|
|
2443
|
-
}
|
|
2444
|
-
case "ReadyForQuery": {
|
|
2445
|
-
this.txStatus = msg.status;
|
|
2446
|
-
if (head.kind !== "sync") {
|
|
2447
|
-
this.protocolFail(new Error("ReadyForQuery but head op is " + head.kind));
|
|
2448
|
-
return;
|
|
2449
|
-
}
|
|
2450
|
-
driver.queue.shift();
|
|
2451
|
-
const stickyErr = driver.error;
|
|
2452
|
-
driver.error = null;
|
|
2453
|
-
if (stickyErr) {
|
|
2454
|
-
head.reject(stickyErr);
|
|
2455
|
-
}
|
|
2456
|
-
else {
|
|
2457
|
-
head.resolve(undefined);
|
|
2458
|
-
}
|
|
2459
|
-
if (driver.queue.length === 0 && !this._extPipelineActive) {
|
|
2460
|
-
this.state = "idle";
|
|
2461
|
-
this.extDriver = null;
|
|
2462
|
-
}
|
|
2463
|
-
return;
|
|
2464
|
-
}
|
|
2465
|
-
default:
|
|
2466
|
-
this.protocolFail(new Error(`Unexpected ${msg.type} in in-extended state`));
|
|
2467
|
-
return;
|
|
2468
|
-
}
|
|
2469
|
-
}
|
|
2470
|
-
protocolFail(err) {
|
|
2471
|
-
this.socketError = err;
|
|
2472
|
-
this.failPending(err);
|
|
2473
|
-
try {
|
|
2474
|
-
this.socket.destroy();
|
|
2475
|
-
}
|
|
2476
|
-
catch {
|
|
2477
|
-
// ignore
|
|
2478
|
-
}
|
|
2479
|
-
this.state = "closed";
|
|
2480
|
-
}
|
|
2481
|
-
/**
|
|
2482
|
-
* Abort the connection because the server replied with CopyInResponse /
|
|
2483
|
-
* CopyOutResponse while a pipeline (`_extPipelineActive`) was active.
|
|
2484
|
-
* Upstream libpq emits the exact diagnostic
|
|
2485
|
-
* `"COPY in a pipeline is not supported, aborting connection"` and tears
|
|
2486
|
-
* the socket down — we mirror that. Pending operations are rejected; the
|
|
2487
|
-
* connection is left in `closed` so subsequent commands fail cleanly
|
|
2488
|
-
* (matching the "aborting connection" promise).
|
|
2489
|
-
*/
|
|
2490
|
-
abortForCopyInPipeline() {
|
|
2491
|
-
const err = {
|
|
2492
|
-
severity: "FATAL",
|
|
2493
|
-
message: "COPY in a pipeline is not supported, aborting connection",
|
|
2494
|
-
};
|
|
2495
|
-
this.socketError = new Error(err.message);
|
|
2496
|
-
this.failPending(err);
|
|
2497
|
-
try {
|
|
2498
|
-
this.socket.destroy();
|
|
2499
|
-
}
|
|
2500
|
-
catch {
|
|
2501
|
-
// ignore
|
|
2502
|
-
}
|
|
2503
|
-
this.state = "closed";
|
|
2504
|
-
}
|
|
2505
|
-
}
|
|
2506
|
-
// -------------------------------------------------------------------------
|
|
2507
|
-
// Public factory
|
|
2508
|
-
// -------------------------------------------------------------------------
|
|
2509
|
-
/**
|
|
2510
|
-
* Pluggable random source for `load_balance_hosts=random`. Public so tests
|
|
2511
|
-
* can inject a deterministic permutation; production code leaves it `null`
|
|
2512
|
-
* and falls back to `Math.random`. NOT part of the connection's external
|
|
2513
|
-
* contract — internal-only escape hatch.
|
|
2514
|
-
*/
|
|
2515
|
-
PgConnection._loadBalanceRng = null;
|
|
140
|
+
* COPY-OUT backpressure thresholds (bytes). When buffered data exceeds the
|
|
141
|
+
* high-water mark we pause the socket; the consumer's `next()` resumes it once
|
|
142
|
+
* it drains below the low-water mark. Bounds RSS for a server that produces
|
|
143
|
+
* rows faster than the sink consumes them (review item #11).
|
|
144
|
+
*/
|
|
145
|
+
const COPY_OUT_HWM = 8388608;
|
|
146
|
+
const COPY_OUT_LWM = 1048576;
|
|
147
|
+
var PgConnection = class PgConnection {
|
|
148
|
+
/**
|
|
149
|
+
* Queue one COPY-FROM-STDIN data block. Call once per expected
|
|
150
|
+
* CopyInResponse, in the order they will arrive during the upcoming
|
|
151
|
+
* `execSimple`. The mainloop owns the parsing of `\.`-terminated stdin
|
|
152
|
+
* blocks; we just buffer the raw bytes and ship them when the server is
|
|
153
|
+
* ready.
|
|
154
|
+
*/
|
|
155
|
+
queueCopyInData(data) {
|
|
156
|
+
this.copyInMidBatchQueue.push(data);
|
|
157
|
+
}
|
|
158
|
+
/** Drop any queued COPY-FROM-STDIN data blocks. */
|
|
159
|
+
clearCopyInDataQueue() {
|
|
160
|
+
this.copyInMidBatchQueue.length = 0;
|
|
161
|
+
}
|
|
162
|
+
constructor(socket, opts, channelBindingData) {
|
|
163
|
+
this.parser = new MessageParser();
|
|
164
|
+
this.serverVersion = 0;
|
|
165
|
+
this.params = /* @__PURE__ */ new Map();
|
|
166
|
+
this.processId = 0;
|
|
167
|
+
this.secretKey = 0;
|
|
168
|
+
this.txStatus = "I";
|
|
169
|
+
this.state = "auth";
|
|
170
|
+
this.pendingQuery = null;
|
|
171
|
+
this.extDriver = null;
|
|
172
|
+
this._extPipelineActive = false;
|
|
173
|
+
this.copyIn = null;
|
|
174
|
+
this.copyOut = null;
|
|
175
|
+
this.copyStartResolve = null;
|
|
176
|
+
this.copyStartReject = null;
|
|
177
|
+
this.lastCopyTag = null;
|
|
178
|
+
this.copyInMidBatchQueue = [];
|
|
179
|
+
this.copyOutMidBatchSink = null;
|
|
180
|
+
this.copyOutMidBatchActive = false;
|
|
181
|
+
this.passwordUsed = false;
|
|
182
|
+
this.notify = new NoticeMultiplexer();
|
|
183
|
+
this.scram = null;
|
|
184
|
+
this.authChallengeSeen = false;
|
|
185
|
+
this.saslFinalSeen = false;
|
|
186
|
+
this.socketError = null;
|
|
187
|
+
this.startupResolve = null;
|
|
188
|
+
this.startupReject = null;
|
|
189
|
+
this.socket = socket;
|
|
190
|
+
this.opts = opts;
|
|
191
|
+
this.channelBindingData = channelBindingData;
|
|
192
|
+
this._password = opts.password ?? null;
|
|
193
|
+
socket.on("data", (chunk) => {
|
|
194
|
+
this.onData(chunk);
|
|
195
|
+
});
|
|
196
|
+
socket.on("error", (err) => {
|
|
197
|
+
this.socketError = err;
|
|
198
|
+
this.failPending(err);
|
|
199
|
+
});
|
|
200
|
+
socket.on("close", () => {
|
|
201
|
+
if (this.state !== "closed") {
|
|
202
|
+
this.state = "closed";
|
|
203
|
+
this.failPending(this.socketError ?? /* @__PURE__ */ new Error("Socket closed"));
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
static {
|
|
208
|
+
this._loadBalanceRng = null;
|
|
209
|
+
}
|
|
210
|
+
static {
|
|
211
|
+
this._dnsLookupAll = null;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Open a Postgres connection. Supports multi-host (`opts.hosts`) with
|
|
215
|
+
* sequential or random iteration, a `target_session_attrs` filter, and the
|
|
216
|
+
* libpq-style `prefer-standby` two-pass fallback.
|
|
217
|
+
*
|
|
218
|
+
* Iteration semantics:
|
|
219
|
+
* 1. Build the candidate list from `opts.hosts` (preferred) or
|
|
220
|
+
* `[{host: opts.host, port: opts.port}]`.
|
|
221
|
+
* 2. If `loadBalanceHosts === 'random'`, Fisher-Yates shuffle in place.
|
|
222
|
+
* 3. For each candidate (in order), attempt: openSocket → TLS → auth →
|
|
223
|
+
* startup. On failure, record the error and try the next.
|
|
224
|
+
* 4. On successful handshake, if `target_session_attrs` is restrictive,
|
|
225
|
+
* run `SELECT pg_is_in_recovery()`. If the role doesn't match, close
|
|
226
|
+
* this connection and try the next.
|
|
227
|
+
* 5. `prefer-standby` runs TWO passes: first accepting only standbys,
|
|
228
|
+
* second falling back to any host.
|
|
229
|
+
* 6. If no candidate succeeds, throw the LAST encountered error
|
|
230
|
+
* (preserves the most-recent failure mode for diagnostics).
|
|
231
|
+
*/
|
|
232
|
+
static async connect(opts) {
|
|
233
|
+
const seed = opts.hosts !== void 0 && opts.hosts.length > 0 ? [...opts.hosts] : [{
|
|
234
|
+
host: opts.host,
|
|
235
|
+
port: opts.port
|
|
236
|
+
}];
|
|
237
|
+
const candidates = opts.hostaddr !== void 0 && opts.hostaddr !== "" ? seed.map((c) => ({
|
|
238
|
+
host: c.host,
|
|
239
|
+
address: opts.hostaddr,
|
|
240
|
+
port: c.port
|
|
241
|
+
})) : await expandHostsViaDns(seed);
|
|
242
|
+
if (opts.loadBalanceHosts === "random") shuffleInPlace(candidates, PgConnection._loadBalanceRng ?? Math.random);
|
|
243
|
+
const tsa = opts.targetSessionAttrs ?? "any";
|
|
244
|
+
const passes = tsa === "prefer-standby" ? ["standby", "any"] : [tsa];
|
|
245
|
+
let lastErr = null;
|
|
246
|
+
for (const passTsa of passes) for (const candidate of candidates) {
|
|
247
|
+
const candidateOpts = {
|
|
248
|
+
...opts,
|
|
249
|
+
host: candidate.host,
|
|
250
|
+
port: candidate.port
|
|
251
|
+
};
|
|
252
|
+
let conn;
|
|
253
|
+
try {
|
|
254
|
+
conn = await PgConnection.connectSingle(candidateOpts, candidate.address);
|
|
255
|
+
} catch (err) {
|
|
256
|
+
lastErr = err;
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
if (await checkSessionAttrs(conn, passTsa)) return conn;
|
|
260
|
+
try {
|
|
261
|
+
await conn.close();
|
|
262
|
+
} catch {}
|
|
263
|
+
lastErr = /* @__PURE__ */ new Error(`target_session_attrs=${String(passTsa)} did not match host ${candidate.host}:${String(candidate.port)}`);
|
|
264
|
+
}
|
|
265
|
+
if (lastErr !== null) {
|
|
266
|
+
if (lastErr instanceof Error) throw lastErr;
|
|
267
|
+
let message;
|
|
268
|
+
if (typeof lastErr === "object" && lastErr !== null && "message" in lastErr && typeof lastErr.message === "string") message = lastErr.message;
|
|
269
|
+
else if (typeof lastErr === "string" || typeof lastErr === "number" || typeof lastErr === "boolean") message = String(lastErr);
|
|
270
|
+
else message = "PgConnection.connect: unknown error";
|
|
271
|
+
const wrapped = new Error(message);
|
|
272
|
+
wrapped.cause = lastErr;
|
|
273
|
+
throw wrapped;
|
|
274
|
+
}
|
|
275
|
+
throw new Error("PgConnection.connect: no candidate hosts configured");
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Per-host connect attempt: open the socket, negotiate TLS, run the auth
|
|
279
|
+
* dance, complete startup. Same shape as the pre-multihost `connect()`;
|
|
280
|
+
* the multi-host orchestrator above wraps this for each candidate.
|
|
281
|
+
*/
|
|
282
|
+
static async connectSingle(opts, addressOverride) {
|
|
283
|
+
if (isUnixSocketHost(opts.host) && (opts.ssl === "require" || opts.ssl === "verify-ca" || opts.ssl === "verify-full")) throw new Error(`sslmode=${opts.ssl} is not supported over Unix-domain sockets (host=${opts.host})`);
|
|
284
|
+
if (isUnixSocketHost(opts.host) && opts.requirepeer !== void 0 && opts.requirepeer !== "") throw new Error(`requirepeer="${opts.requirepeer}" cannot be enforced: Node provides no Unix-domain socket peer-credential API; refusing to connect rather than skip the check`);
|
|
285
|
+
const rawSocket = await openSocket(opts, addressOverride);
|
|
286
|
+
let socket = rawSocket;
|
|
287
|
+
let channelBindingData = null;
|
|
288
|
+
try {
|
|
289
|
+
const servername = tlsServername(opts);
|
|
290
|
+
const tlsConnectionOptions = {
|
|
291
|
+
...servername !== void 0 ? { servername } : {},
|
|
292
|
+
rejectUnauthorized: opts.ssl === "verify-ca" || opts.ssl === "verify-full",
|
|
293
|
+
ALPNProtocols: ["postgresql"]
|
|
294
|
+
};
|
|
295
|
+
if (opts.ssl !== "verify-full") tlsConnectionOptions.checkServerIdentity = () => void 0;
|
|
296
|
+
else if (opts.sslsni === false) tlsConnectionOptions.checkServerIdentity = (_host, cert) => tls.checkServerIdentity(opts.host, cert);
|
|
297
|
+
applyTlsProtocolVersionRange(tlsConnectionOptions, opts);
|
|
298
|
+
const tlsResult = await negotiateTls(rawSocket, isUnixSocketHost(opts.host) ? "disable" : opts.ssl, tlsConnectionOptions, {
|
|
299
|
+
sslcert: opts.sslcert,
|
|
300
|
+
sslkey: opts.sslkey,
|
|
301
|
+
sslcertmode: opts.sslcertmode,
|
|
302
|
+
sslpassword: opts.sslpassword,
|
|
303
|
+
sslrootcert: opts.sslrootcert,
|
|
304
|
+
sslcrl: opts.sslcrl,
|
|
305
|
+
sslcrldir: opts.sslcrldir,
|
|
306
|
+
sslkeylogfile: opts.sslkeylogfile
|
|
307
|
+
}, isUnixSocketHost(opts.host) ? "postgres" : opts.sslnegotiation ?? "postgres");
|
|
308
|
+
if (tlsResult.kind === "tls") {
|
|
309
|
+
socket = tlsResult.socket;
|
|
310
|
+
channelBindingData = tlsResult.channelBindingData;
|
|
311
|
+
} else socket = tlsResult.socket;
|
|
312
|
+
} catch (err) {
|
|
313
|
+
try {
|
|
314
|
+
rawSocket.destroy();
|
|
315
|
+
} catch {}
|
|
316
|
+
throw err;
|
|
317
|
+
}
|
|
318
|
+
const conn = new PgConnection(socket, opts, channelBindingData);
|
|
319
|
+
await conn.startup();
|
|
320
|
+
return conn;
|
|
321
|
+
}
|
|
322
|
+
parameterStatus(name) {
|
|
323
|
+
return this.params.get(name);
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Expose the connection target as `meta.database` / `meta.user` / `meta.host`
|
|
327
|
+
* / `meta.port` / `meta.pid` so the prompt renderer (which duck-types these
|
|
328
|
+
* via `MaybeWithMeta`) can render `%/`, `%n`, `%m`, `%>`, `%p` without
|
|
329
|
+
* additional plumbing. Postgres doesn't emit a `database` ParameterStatus,
|
|
330
|
+
* so these come from the connect opts / BackendKeyData.
|
|
331
|
+
*/
|
|
332
|
+
get database() {
|
|
333
|
+
return this.opts.database;
|
|
334
|
+
}
|
|
335
|
+
get user() {
|
|
336
|
+
return this.opts.user;
|
|
337
|
+
}
|
|
338
|
+
get host() {
|
|
339
|
+
return this.opts.host;
|
|
340
|
+
}
|
|
341
|
+
get port() {
|
|
342
|
+
return this.opts.port;
|
|
343
|
+
}
|
|
344
|
+
get pid() {
|
|
345
|
+
return this.processId;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* The password supplied at connect time (or `null`). Mirrors libpq's
|
|
349
|
+
* retention of the password on the live `PGconn` so `\c <newdb>` can
|
|
350
|
+
* reconnect transparently. Read-only by design — the field is set once in
|
|
351
|
+
* the constructor and never mutated.
|
|
352
|
+
*/
|
|
353
|
+
get password() {
|
|
354
|
+
return this._password;
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* If the connection was upgraded to TLS during negotiation, return the
|
|
358
|
+
* cipher info for the active session. Returns `null` for plain-text
|
|
359
|
+
* connections. Used by the startup banner to render an `SSL connection
|
|
360
|
+
* (protocol: …, cipher: …)` line that mirrors upstream psql, and by
|
|
361
|
+
* `\conninfo` (PG18 connection-information table) to fill the SSL rows.
|
|
362
|
+
*/
|
|
363
|
+
getTlsInfo() {
|
|
364
|
+
const s = this.socket;
|
|
365
|
+
if (typeof s.getCipher !== "function") return null;
|
|
366
|
+
try {
|
|
367
|
+
const cipher = s.getCipher();
|
|
368
|
+
const protocol = s.getProtocol?.() ?? cipher.version ?? "unknown";
|
|
369
|
+
if (!cipher.name) return null;
|
|
370
|
+
const compression = "off";
|
|
371
|
+
const alpnRaw = s.alpnProtocol;
|
|
372
|
+
const alpn = typeof alpnRaw === "string" && alpnRaw.length > 0 ? alpnRaw : null;
|
|
373
|
+
return {
|
|
374
|
+
protocol: String(protocol),
|
|
375
|
+
cipher: cipher.standardName ?? cipher.name,
|
|
376
|
+
standardName: cipher.standardName,
|
|
377
|
+
compression,
|
|
378
|
+
alpn,
|
|
379
|
+
library: "OpenSSL",
|
|
380
|
+
keyBits: sslKeyBitsFromCipher(cipher.standardName ?? cipher.name)
|
|
381
|
+
};
|
|
382
|
+
} catch {
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Static connection facts for the PG18 `\conninfo` table, sourced from the
|
|
388
|
+
* connect opts and the live socket (no SQL is issued):
|
|
389
|
+
*
|
|
390
|
+
* - `host` / `port` / `options`: from the connect opts.
|
|
391
|
+
* - `hostaddr`: the resolved peer IP — `opts.hostaddr` if the caller
|
|
392
|
+
* fixed one, else the TCP socket's `remoteAddress` (undefined for a
|
|
393
|
+
* Unix-domain socket → `null`).
|
|
394
|
+
* - `backendPid`: the backend process id from BackendKeyData.
|
|
395
|
+
* - `passwordUsed`: whether a password was actually sent during auth.
|
|
396
|
+
* - `gssapiUsed`: always `false` — we have no GSSAPI support.
|
|
397
|
+
*/
|
|
398
|
+
getConnectionInfo() {
|
|
399
|
+
const remote = this.opts.hostaddr !== void 0 && this.opts.hostaddr !== "" ? this.opts.hostaddr : this.socket.remoteAddress ?? null;
|
|
400
|
+
return {
|
|
401
|
+
host: this.opts.host,
|
|
402
|
+
hostaddr: remote,
|
|
403
|
+
port: this.opts.port,
|
|
404
|
+
options: this.opts.options ?? null,
|
|
405
|
+
backendPid: this.processId,
|
|
406
|
+
passwordUsed: this.passwordUsed,
|
|
407
|
+
gssapiUsed: false
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
async query(sql, params) {
|
|
411
|
+
if (params === void 0) {
|
|
412
|
+
const sets = await this.execSimple(sql);
|
|
413
|
+
if (sets.length === 0) throw new Error("PgConnection.query: server returned no result sets");
|
|
414
|
+
return sets[sets.length - 1];
|
|
415
|
+
}
|
|
416
|
+
this.ensureIdle();
|
|
417
|
+
const encoded = encodeParams(params);
|
|
418
|
+
this.startExtendedBatch();
|
|
419
|
+
const parseP = this.enqueueParse();
|
|
420
|
+
const bindP = this.enqueueBind();
|
|
421
|
+
const descP = this.enqueueDescribePortalIntoNextExecute();
|
|
422
|
+
const execP = this.enqueueExecute();
|
|
423
|
+
const syncP = this.enqueueSync();
|
|
424
|
+
this.socket.write(Parse("", sql, []));
|
|
425
|
+
this.socket.write(Bind("", "", [], encoded, [0]));
|
|
426
|
+
this.socket.write(Describe("P", ""));
|
|
427
|
+
this.socket.write(Execute("", 0));
|
|
428
|
+
this.socket.write(Sync());
|
|
429
|
+
let firstErr = null;
|
|
430
|
+
const cap = (e) => {
|
|
431
|
+
if (firstErr === null) firstErr = e;
|
|
432
|
+
};
|
|
433
|
+
parseP.catch(cap);
|
|
434
|
+
bindP.catch(cap);
|
|
435
|
+
descP.catch(cap);
|
|
436
|
+
let result = null;
|
|
437
|
+
execP.then((rs) => {
|
|
438
|
+
result = rs;
|
|
439
|
+
}, cap);
|
|
440
|
+
await syncP.catch(cap);
|
|
441
|
+
if (firstErr !== null) throw asThrowable(firstErr);
|
|
442
|
+
if (result === null) throw new Error("PgConnection.query: server returned no result");
|
|
443
|
+
return result;
|
|
444
|
+
}
|
|
445
|
+
async execSimple(sql) {
|
|
446
|
+
this.ensureIdle();
|
|
447
|
+
return new Promise((resolve, reject) => {
|
|
448
|
+
this.pendingQuery = {
|
|
449
|
+
resolve,
|
|
450
|
+
reject,
|
|
451
|
+
current: null,
|
|
452
|
+
finished: [],
|
|
453
|
+
notices: [],
|
|
454
|
+
error: null
|
|
455
|
+
};
|
|
456
|
+
this.state = "in-query";
|
|
457
|
+
this.socket.write(Query(sql));
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
async prepare(name, sql, paramTypes) {
|
|
461
|
+
this.ensureIdle();
|
|
462
|
+
const oids = paramTypes ?? [];
|
|
463
|
+
this.startExtendedBatch();
|
|
464
|
+
const parseP = this.enqueueParse();
|
|
465
|
+
const descP = this.enqueueDescribeStatement();
|
|
466
|
+
const syncP = this.enqueueSync();
|
|
467
|
+
this.socket.write(Parse(name, sql, oids));
|
|
468
|
+
this.socket.write(Describe("S", name));
|
|
469
|
+
this.socket.write(Sync());
|
|
470
|
+
let firstErr = null;
|
|
471
|
+
const cap = (e) => {
|
|
472
|
+
if (firstErr === null) firstErr = e;
|
|
473
|
+
};
|
|
474
|
+
parseP.catch(cap);
|
|
475
|
+
let descResult = null;
|
|
476
|
+
descP.then((r) => {
|
|
477
|
+
descResult = r;
|
|
478
|
+
}, cap);
|
|
479
|
+
await syncP.catch(cap);
|
|
480
|
+
if (firstErr !== null) throw asThrowable(firstErr);
|
|
481
|
+
if (descResult === null) throw new Error("PgConnection.prepare: server returned no parameter description");
|
|
482
|
+
const { paramOids, fields } = descResult;
|
|
483
|
+
const conn = this;
|
|
484
|
+
return {
|
|
485
|
+
name,
|
|
486
|
+
paramTypes: paramOids,
|
|
487
|
+
async bind(values, paramFormats) {
|
|
488
|
+
conn.ensureIdle();
|
|
489
|
+
const encoded = encodeParams(values);
|
|
490
|
+
conn.startExtendedBatch();
|
|
491
|
+
const bP = conn.enqueueBind();
|
|
492
|
+
const sP = conn.enqueueSync();
|
|
493
|
+
conn.socket.write(Bind("", name, paramFormats ?? [], encoded, [0]));
|
|
494
|
+
conn.socket.write(Sync());
|
|
495
|
+
let err = null;
|
|
496
|
+
bP.catch((e) => {
|
|
497
|
+
if (err === null) err = e;
|
|
498
|
+
});
|
|
499
|
+
await sP.catch((e) => {
|
|
500
|
+
if (err === null) err = e;
|
|
501
|
+
});
|
|
502
|
+
if (err !== null) throw asThrowable(err);
|
|
503
|
+
},
|
|
504
|
+
describe() {
|
|
505
|
+
return Promise.resolve(fields);
|
|
506
|
+
},
|
|
507
|
+
async execute(maxRows) {
|
|
508
|
+
conn.ensureIdle();
|
|
509
|
+
conn.startExtendedBatch();
|
|
510
|
+
const eP = conn.enqueueExecuteWithFields(fields);
|
|
511
|
+
const sP = conn.enqueueSync();
|
|
512
|
+
conn.socket.write(Execute("", maxRows ?? 0));
|
|
513
|
+
conn.socket.write(Sync());
|
|
514
|
+
let err = null;
|
|
515
|
+
let rs = null;
|
|
516
|
+
eP.then((r) => {
|
|
517
|
+
rs = r;
|
|
518
|
+
}, (e) => {
|
|
519
|
+
if (err === null) err = e;
|
|
520
|
+
});
|
|
521
|
+
await sP.catch((e) => {
|
|
522
|
+
if (err === null) err = e;
|
|
523
|
+
});
|
|
524
|
+
if (err !== null) throw asThrowable(err);
|
|
525
|
+
if (rs === null) throw new Error("PgConnection.prepare.execute: server returned no result");
|
|
526
|
+
return rs;
|
|
527
|
+
},
|
|
528
|
+
async bindAndExecute(values, maxRows, paramFormats) {
|
|
529
|
+
conn.ensureIdle();
|
|
530
|
+
const encoded = encodeParams(values);
|
|
531
|
+
conn.startExtendedBatch();
|
|
532
|
+
const bP = conn.enqueueBind();
|
|
533
|
+
const eP = conn.enqueueExecuteWithFields(fields);
|
|
534
|
+
const sP = conn.enqueueSync();
|
|
535
|
+
conn.socket.write(Bind("", name, paramFormats ?? [], encoded, [0]));
|
|
536
|
+
conn.socket.write(Execute("", maxRows ?? 0));
|
|
537
|
+
conn.socket.write(Sync());
|
|
538
|
+
let err = null;
|
|
539
|
+
let rs = null;
|
|
540
|
+
bP.catch((e) => {
|
|
541
|
+
if (err === null) err = e;
|
|
542
|
+
});
|
|
543
|
+
eP.then((r) => {
|
|
544
|
+
rs = r;
|
|
545
|
+
}, (e) => {
|
|
546
|
+
if (err === null) err = e;
|
|
547
|
+
});
|
|
548
|
+
await sP.catch((e) => {
|
|
549
|
+
if (err === null) err = e;
|
|
550
|
+
});
|
|
551
|
+
if (err !== null) throw asThrowable(err);
|
|
552
|
+
if (rs === null) throw new Error("PgConnection.prepare.bindAndExecute: server returned no result");
|
|
553
|
+
return rs;
|
|
554
|
+
},
|
|
555
|
+
async close() {
|
|
556
|
+
conn.ensureIdle();
|
|
557
|
+
conn.startExtendedBatch();
|
|
558
|
+
const cP = conn.enqueueClose();
|
|
559
|
+
const sP = conn.enqueueSync();
|
|
560
|
+
conn.socket.write(Close("S", name));
|
|
561
|
+
conn.socket.write(Sync());
|
|
562
|
+
let err = null;
|
|
563
|
+
cP.catch((e) => {
|
|
564
|
+
if (err === null) err = e;
|
|
565
|
+
});
|
|
566
|
+
await sP.catch((e) => {
|
|
567
|
+
if (err === null) err = e;
|
|
568
|
+
});
|
|
569
|
+
if (err !== null) throw asThrowable(err);
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Issue `Close('S', name) + Sync` directly, without a preceding Parse.
|
|
575
|
+
* The server responds with CloseComplete + ReadyForQuery (even when
|
|
576
|
+
* the named statement doesn't exist — PG treats unknown-name Close as
|
|
577
|
+
* a no-op). Used by `\close_prepared NAME` so we don't have to fake a
|
|
578
|
+
* Parse just to reach the Close step.
|
|
579
|
+
*/
|
|
580
|
+
async closePreparedStatement(name) {
|
|
581
|
+
this.ensureIdle();
|
|
582
|
+
this.startExtendedBatch();
|
|
583
|
+
const cP = this.enqueueClose();
|
|
584
|
+
const sP = this.enqueueSync();
|
|
585
|
+
this.socket.write(Close("S", name));
|
|
586
|
+
this.socket.write(Sync());
|
|
587
|
+
let err = null;
|
|
588
|
+
cP.catch((e) => {
|
|
589
|
+
if (err === null) err = e;
|
|
590
|
+
});
|
|
591
|
+
await sP.catch((e) => {
|
|
592
|
+
if (err === null) err = e;
|
|
593
|
+
});
|
|
594
|
+
if (err !== null) throw asThrowable(err);
|
|
595
|
+
}
|
|
596
|
+
startCopyIn(sql) {
|
|
597
|
+
this.ensureIdle();
|
|
598
|
+
if (this._extPipelineActive) {
|
|
599
|
+
this.abortForCopyInPipeline();
|
|
600
|
+
return Promise.reject(Object.assign(/* @__PURE__ */ new Error("COPY in a pipeline is not supported, aborting connection"), { severity: "FATAL" }));
|
|
601
|
+
}
|
|
602
|
+
return new Promise((resolve, reject) => {
|
|
603
|
+
this.copyIn = {
|
|
604
|
+
resolveDone: null,
|
|
605
|
+
rejectDone: null,
|
|
606
|
+
error: null,
|
|
607
|
+
commandTag: null,
|
|
608
|
+
closed: false
|
|
609
|
+
};
|
|
610
|
+
this.copyStartResolve = () => {
|
|
611
|
+
resolve(this.makeCopyInStream());
|
|
612
|
+
};
|
|
613
|
+
this.copyStartReject = reject;
|
|
614
|
+
this.state = "in-query";
|
|
615
|
+
this.socket.write(Query(sql));
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
startCopyOut(sql) {
|
|
619
|
+
this.ensureIdle();
|
|
620
|
+
if (this._extPipelineActive) {
|
|
621
|
+
this.abortForCopyInPipeline();
|
|
622
|
+
return Promise.reject(Object.assign(/* @__PURE__ */ new Error("COPY in a pipeline is not supported, aborting connection"), { severity: "FATAL" }));
|
|
623
|
+
}
|
|
624
|
+
return new Promise((resolve, reject) => {
|
|
625
|
+
this.copyOut = {
|
|
626
|
+
queue: [],
|
|
627
|
+
queuedBytes: 0,
|
|
628
|
+
waker: null,
|
|
629
|
+
done: false,
|
|
630
|
+
abandoned: false,
|
|
631
|
+
error: null,
|
|
632
|
+
commandTag: null
|
|
633
|
+
};
|
|
634
|
+
this.copyStartResolve = () => {
|
|
635
|
+
resolve(this.makeCopyOutStream());
|
|
636
|
+
};
|
|
637
|
+
this.copyStartReject = reject;
|
|
638
|
+
this.state = "in-query";
|
|
639
|
+
this.socket.write(Query(sql));
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
pipeline() {
|
|
643
|
+
this.ensureIdle();
|
|
644
|
+
return new PipelineSession(this);
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* Cancel whatever the connection is currently doing.
|
|
648
|
+
*
|
|
649
|
+
* The routing is state-aware so the mainloop SIGINT handler can call this
|
|
650
|
+
* blindly without knowing the protocol phase:
|
|
651
|
+
*
|
|
652
|
+
* - `in-copy-in`: we hold the writing end of the data stream, so the
|
|
653
|
+
* correct action is a client-initiated CopyFail on the *same* socket.
|
|
654
|
+
* Sending a side CancelRequest would race with our own pending writes;
|
|
655
|
+
* CopyFail is the spec-blessed abort path. The server replies with
|
|
656
|
+
* ErrorResponse + ReadyForQuery and we transition back to idle.
|
|
657
|
+
* - `in-copy-out`: the server is pushing data at us. CopyFail is not a
|
|
658
|
+
* valid client message here, so we fall back to the side CancelRequest
|
|
659
|
+
* path that normal queries use. PG will surface an ErrorResponse and
|
|
660
|
+
* tear the COPY down.
|
|
661
|
+
* - everything else: side CancelRequest, the historical behaviour.
|
|
662
|
+
*
|
|
663
|
+
* Best-effort. We don't reject if BackendKeyData hasn't arrived yet
|
|
664
|
+
* during the auth dance — there's nothing to cancel; we just return.
|
|
665
|
+
*/
|
|
666
|
+
async cancel() {
|
|
667
|
+
if (this.state === "in-copy-in" && this.copyIn && !this.copyIn.closed) {
|
|
668
|
+
this.copyIn.closed = true;
|
|
669
|
+
try {
|
|
670
|
+
this.socket.write(CopyFail("canceled by user"));
|
|
671
|
+
} catch {}
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
if (this.processId === 0) return;
|
|
675
|
+
const cancelSocket = await openSocket(this.opts, this.opts.hostaddr !== void 0 && this.opts.hostaddr !== "" ? this.opts.hostaddr : void 0);
|
|
676
|
+
let writeSocket = cancelSocket;
|
|
677
|
+
try {
|
|
678
|
+
const cancelServername = tlsServername(this.opts);
|
|
679
|
+
const cancelTlsOpts = {
|
|
680
|
+
...cancelServername !== void 0 ? { servername: cancelServername } : {},
|
|
681
|
+
rejectUnauthorized: this.opts.ssl === "verify-ca" || this.opts.ssl === "verify-full",
|
|
682
|
+
ALPNProtocols: ["postgresql"]
|
|
683
|
+
};
|
|
684
|
+
if (this.opts.ssl !== "verify-full") cancelTlsOpts.checkServerIdentity = () => void 0;
|
|
685
|
+
else if (this.opts.sslsni === false) {
|
|
686
|
+
const host = this.opts.host;
|
|
687
|
+
cancelTlsOpts.checkServerIdentity = (_h, cert) => tls.checkServerIdentity(host, cert);
|
|
688
|
+
}
|
|
689
|
+
applyTlsProtocolVersionRange(cancelTlsOpts, this.opts);
|
|
690
|
+
const t = await negotiateTls(cancelSocket, isUnixSocketHost(this.opts.host) ? "disable" : this.opts.ssl, cancelTlsOpts, {
|
|
691
|
+
sslcert: this.opts.sslcert,
|
|
692
|
+
sslkey: this.opts.sslkey,
|
|
693
|
+
sslcertmode: this.opts.sslcertmode,
|
|
694
|
+
sslpassword: this.opts.sslpassword,
|
|
695
|
+
sslrootcert: this.opts.sslrootcert,
|
|
696
|
+
sslcrl: this.opts.sslcrl,
|
|
697
|
+
sslcrldir: this.opts.sslcrldir,
|
|
698
|
+
sslkeylogfile: this.opts.sslkeylogfile
|
|
699
|
+
}, isUnixSocketHost(this.opts.host) ? "postgres" : this.opts.sslnegotiation ?? "postgres");
|
|
700
|
+
writeSocket = t.kind === "tls" ? t.socket : t.socket;
|
|
701
|
+
await new Promise((resolve, reject) => {
|
|
702
|
+
writeSocket.write(CancelRequest(this.processId, this.secretKey), (err) => {
|
|
703
|
+
if (err) reject(err);
|
|
704
|
+
else resolve();
|
|
705
|
+
});
|
|
706
|
+
});
|
|
707
|
+
} finally {
|
|
708
|
+
try {
|
|
709
|
+
writeSocket.end();
|
|
710
|
+
} catch {}
|
|
711
|
+
try {
|
|
712
|
+
cancelSocket.destroy();
|
|
713
|
+
} catch {}
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
escapeIdentifier(value) {
|
|
717
|
+
return "\"" + value.replace(/"/g, "\"\"") + "\"";
|
|
718
|
+
}
|
|
719
|
+
escapeLiteral(value) {
|
|
720
|
+
const doubled = value.replace(/'/g, "''");
|
|
721
|
+
if (value.includes("\\")) return "E'" + doubled.replace(/\\/g, "\\\\") + "'";
|
|
722
|
+
return "'" + doubled + "'";
|
|
723
|
+
}
|
|
724
|
+
async setClientEncoding(name) {
|
|
725
|
+
await this.execSimple(`SET client_encoding TO ${this.escapeLiteral(name)}`);
|
|
726
|
+
}
|
|
727
|
+
onNotice(handler) {
|
|
728
|
+
return this.notify.onNotice(handler);
|
|
729
|
+
}
|
|
730
|
+
onNotification(handler) {
|
|
731
|
+
return this.notify.onNotification(handler);
|
|
732
|
+
}
|
|
733
|
+
async close() {
|
|
734
|
+
if (this.state === "closed") return;
|
|
735
|
+
try {
|
|
736
|
+
this.socket.write(Terminate());
|
|
737
|
+
} catch {}
|
|
738
|
+
this.state = "closed";
|
|
739
|
+
this.notify.clear();
|
|
740
|
+
await new Promise((resolve) => {
|
|
741
|
+
this.socket.once("close", () => {
|
|
742
|
+
resolve();
|
|
743
|
+
});
|
|
744
|
+
try {
|
|
745
|
+
this.socket.end();
|
|
746
|
+
} catch {
|
|
747
|
+
resolve();
|
|
748
|
+
}
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
isClosed() {
|
|
752
|
+
return this.state === "closed";
|
|
753
|
+
}
|
|
754
|
+
startup() {
|
|
755
|
+
return new Promise((resolve, reject) => {
|
|
756
|
+
const params = {
|
|
757
|
+
user: this.opts.user,
|
|
758
|
+
database: this.opts.database,
|
|
759
|
+
client_encoding: this.opts.clientEncoding ?? "UTF8"
|
|
760
|
+
};
|
|
761
|
+
if (this.opts.applicationName !== void 0) params.application_name = this.opts.applicationName;
|
|
762
|
+
if (this.opts.options !== void 0) params.options = this.opts.options;
|
|
763
|
+
if (this.opts.replication !== void 0) params.replication = this.opts.replication;
|
|
764
|
+
this.startupResolve = resolve;
|
|
765
|
+
this.startupReject = reject;
|
|
766
|
+
this.socket.write(StartupMessage(params));
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* Enforce `require_auth` against an observed server-requested method.
|
|
771
|
+
* Returns true when the connection should proceed; false (with
|
|
772
|
+
* {@link failStartup} already called) when the policy was violated.
|
|
773
|
+
*/
|
|
774
|
+
checkRequireAuth(observed) {
|
|
775
|
+
const policy = this.opts.requireAuth;
|
|
776
|
+
if (policy === void 0) return true;
|
|
777
|
+
const hit = policy.methods.has(observed);
|
|
778
|
+
if (!(policy.negated ? !hit : hit)) {
|
|
779
|
+
this.failStartup(/* @__PURE__ */ new Error(`auth method "${observed}" requirement failed`));
|
|
780
|
+
return false;
|
|
781
|
+
}
|
|
782
|
+
return true;
|
|
783
|
+
}
|
|
784
|
+
handleAuthMessage(msg) {
|
|
785
|
+
switch (msg.type) {
|
|
786
|
+
case "AuthenticationOk":
|
|
787
|
+
if (this.opts.channelBinding === "require" && (this.scram === null || this.scram.mechanism !== "SCRAM-SHA-256-PLUS")) {
|
|
788
|
+
this.failStartup(/* @__PURE__ */ new Error("channel binding required, but server authenticated client without channel binding"));
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
if (this.scram !== null && !this.saslFinalSeen) {
|
|
792
|
+
this.failStartup(/* @__PURE__ */ new Error("server sent AuthenticationOk without completing SCRAM authentication (server signature not verified)"));
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
if (!this.authChallengeSeen && !this.checkRequireAuth("none")) return;
|
|
796
|
+
this.state = "await-ready";
|
|
797
|
+
return;
|
|
798
|
+
case "AuthenticationCleartextPassword":
|
|
799
|
+
this.authChallengeSeen = true;
|
|
800
|
+
if (this.opts.channelBinding === "require") {
|
|
801
|
+
this.failStartup(/* @__PURE__ */ new Error("channel binding required but not supported by server's authentication request"));
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
if (!this.checkRequireAuth("password")) return;
|
|
805
|
+
if (this.opts.password === void 0) {
|
|
806
|
+
this.failStartup(/* @__PURE__ */ new Error("Server requested cleartext password but no password was provided"));
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
this.passwordUsed = true;
|
|
810
|
+
this.socket.write(PasswordMessage(this.opts.password));
|
|
811
|
+
return;
|
|
812
|
+
case "AuthenticationMD5Password": {
|
|
813
|
+
this.authChallengeSeen = true;
|
|
814
|
+
if (this.opts.channelBinding === "require") {
|
|
815
|
+
this.failStartup(/* @__PURE__ */ new Error("channel binding required but not supported by server's authentication request"));
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
if (!this.checkRequireAuth("md5")) return;
|
|
819
|
+
if (this.opts.password === void 0) {
|
|
820
|
+
this.failStartup(/* @__PURE__ */ new Error("Server requested MD5 password but no password was provided"));
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
const payload = md5AuthPayload(this.opts.user, this.opts.password, msg.salt);
|
|
824
|
+
this.passwordUsed = true;
|
|
825
|
+
this.socket.write(PasswordMessage(payload));
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
case "AuthenticationSASL":
|
|
829
|
+
this.authChallengeSeen = true;
|
|
830
|
+
if (this.opts.password === void 0) {
|
|
831
|
+
this.failStartup(/* @__PURE__ */ new Error("Server requested SASL auth but no password was provided"));
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
if (!this.checkRequireAuth("scram-sha-256")) return;
|
|
835
|
+
if (this.opts.channelBinding === "require" && !msg.mechanisms.includes("SCRAM-SHA-256-PLUS")) {
|
|
836
|
+
this.failStartup(/* @__PURE__ */ new Error("channel binding required but not supported by server's authentication request"));
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
if (this.opts.channelBinding === "require" && this.channelBindingData === null) {
|
|
840
|
+
this.failStartup(/* @__PURE__ */ new Error("channel binding required but not supported by server's authentication request"));
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
try {
|
|
844
|
+
this.scram = createScramClient({
|
|
845
|
+
user: this.opts.user,
|
|
846
|
+
password: this.opts.password,
|
|
847
|
+
mechanisms: msg.mechanisms,
|
|
848
|
+
channelBinding: this.channelBindingData !== null && this.opts.channelBinding !== "disable" ? {
|
|
849
|
+
type: "tls-server-end-point",
|
|
850
|
+
data: this.channelBindingData
|
|
851
|
+
} : void 0
|
|
852
|
+
});
|
|
853
|
+
const { mechanism, clientFirstMessage } = this.scram.start();
|
|
854
|
+
this.passwordUsed = true;
|
|
855
|
+
this.socket.write(SASLInitialResponse(mechanism, clientFirstMessage));
|
|
856
|
+
} catch (err) {
|
|
857
|
+
this.failStartup(err);
|
|
858
|
+
}
|
|
859
|
+
return;
|
|
860
|
+
case "AuthenticationSASLContinue":
|
|
861
|
+
if (!this.scram) {
|
|
862
|
+
this.failStartup(/* @__PURE__ */ new Error("Received AuthenticationSASLContinue without an active SCRAM client"));
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
try {
|
|
866
|
+
const reply = this.scram.continue(msg.data);
|
|
867
|
+
this.socket.write(SASLResponse(reply));
|
|
868
|
+
} catch (err) {
|
|
869
|
+
this.failStartup(err);
|
|
870
|
+
}
|
|
871
|
+
return;
|
|
872
|
+
case "AuthenticationSASLFinal":
|
|
873
|
+
if (!this.scram) {
|
|
874
|
+
this.failStartup(/* @__PURE__ */ new Error("Received AuthenticationSASLFinal without an active SCRAM client"));
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
try {
|
|
878
|
+
this.scram.finish(msg.data);
|
|
879
|
+
this.saslFinalSeen = true;
|
|
880
|
+
} catch (err) {
|
|
881
|
+
this.failStartup(err);
|
|
882
|
+
}
|
|
883
|
+
return;
|
|
884
|
+
case "ErrorResponse":
|
|
885
|
+
this.failStartup(fieldsToConnectError(msg.fields));
|
|
886
|
+
return;
|
|
887
|
+
case "NoticeResponse":
|
|
888
|
+
this.notify.emit(fieldsToNotice(msg.fields));
|
|
889
|
+
return;
|
|
890
|
+
default:
|
|
891
|
+
this.handleAwaitReady(msg);
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
handleAwaitReady(msg) {
|
|
896
|
+
switch (msg.type) {
|
|
897
|
+
case "ParameterStatus":
|
|
898
|
+
this.params.set(msg.name, msg.value);
|
|
899
|
+
if (msg.name === "server_version") this.serverVersion = parseServerVersion(msg.value);
|
|
900
|
+
return;
|
|
901
|
+
case "BackendKeyData":
|
|
902
|
+
this.processId = msg.processId;
|
|
903
|
+
this.secretKey = msg.secretKey;
|
|
904
|
+
return;
|
|
905
|
+
case "ReadyForQuery":
|
|
906
|
+
this.txStatus = msg.status;
|
|
907
|
+
this.state = "idle";
|
|
908
|
+
if (this.startupResolve) {
|
|
909
|
+
const r = this.startupResolve;
|
|
910
|
+
this.startupResolve = null;
|
|
911
|
+
this.startupReject = null;
|
|
912
|
+
r();
|
|
913
|
+
}
|
|
914
|
+
return;
|
|
915
|
+
case "ErrorResponse":
|
|
916
|
+
this.failStartup(fieldsToConnectError(msg.fields));
|
|
917
|
+
return;
|
|
918
|
+
case "NoticeResponse":
|
|
919
|
+
this.notify.emit(fieldsToNotice(msg.fields));
|
|
920
|
+
return;
|
|
921
|
+
default:
|
|
922
|
+
this.failStartup(/* @__PURE__ */ new Error(`Unexpected message ${msg.type} during connection startup`));
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
failStartup(err) {
|
|
927
|
+
if (this.startupReject) {
|
|
928
|
+
const r = this.startupReject;
|
|
929
|
+
this.startupResolve = null;
|
|
930
|
+
this.startupReject = null;
|
|
931
|
+
r(err);
|
|
932
|
+
}
|
|
933
|
+
try {
|
|
934
|
+
this.socket.destroy();
|
|
935
|
+
} catch {}
|
|
936
|
+
this.state = "closed";
|
|
937
|
+
}
|
|
938
|
+
makeCopyInStream() {
|
|
939
|
+
const driver = this.copyIn;
|
|
940
|
+
if (!driver) throw new Error("PgConnection: makeCopyInStream called without driver");
|
|
941
|
+
return {
|
|
942
|
+
write: (chunk) => {
|
|
943
|
+
if (this.state === "closed") return Promise.reject(/* @__PURE__ */ new Error("Connection closed"));
|
|
944
|
+
if (driver.closed) return Promise.reject(/* @__PURE__ */ new Error("CopyInStream already closed"));
|
|
945
|
+
const data = typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
|
|
946
|
+
return new Promise((resolve, reject) => {
|
|
947
|
+
this.socket.write(CopyData(data), (err) => {
|
|
948
|
+
if (err) reject(err);
|
|
949
|
+
else resolve();
|
|
950
|
+
});
|
|
951
|
+
});
|
|
952
|
+
},
|
|
953
|
+
end: () => {
|
|
954
|
+
if (driver.closed) return Promise.reject(/* @__PURE__ */ new Error("CopyInStream already closed"));
|
|
955
|
+
driver.closed = true;
|
|
956
|
+
return new Promise((resolve, reject) => {
|
|
957
|
+
driver.resolveDone = resolve;
|
|
958
|
+
driver.rejectDone = reject;
|
|
959
|
+
this.socket.write(CopyDone());
|
|
960
|
+
});
|
|
961
|
+
},
|
|
962
|
+
fail: (reason) => {
|
|
963
|
+
if (driver.closed) return Promise.reject(/* @__PURE__ */ new Error("CopyInStream already closed"));
|
|
964
|
+
driver.closed = true;
|
|
965
|
+
return new Promise((resolve, reject) => {
|
|
966
|
+
driver.resolveDone = () => {
|
|
967
|
+
resolve();
|
|
968
|
+
};
|
|
969
|
+
driver.rejectDone = reject;
|
|
970
|
+
this.socket.write(CopyFail(reason));
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
makeCopyOutStream() {
|
|
976
|
+
const driver = this.copyOut;
|
|
977
|
+
if (!driver) throw new Error("PgConnection: makeCopyOutStream called without driver");
|
|
978
|
+
const isClosed = () => this.state === "closed";
|
|
979
|
+
const resumeSocket = () => {
|
|
980
|
+
this.socket.resume?.();
|
|
981
|
+
};
|
|
982
|
+
return { [Symbol.asyncIterator]() {
|
|
983
|
+
return {
|
|
984
|
+
async next() {
|
|
985
|
+
for (;;) {
|
|
986
|
+
if (driver.queue.length > 0) {
|
|
987
|
+
const next = driver.queue.shift();
|
|
988
|
+
if (next === void 0) continue;
|
|
989
|
+
driver.queuedBytes -= next.length;
|
|
990
|
+
if (driver.queuedBytes <= COPY_OUT_LWM) resumeSocket();
|
|
991
|
+
return {
|
|
992
|
+
value: next,
|
|
993
|
+
done: false
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
if (driver.error) {
|
|
997
|
+
const ce = driver.error;
|
|
998
|
+
const wrapped = new Error(ce.message);
|
|
999
|
+
wrapped.cause = ce;
|
|
1000
|
+
throw wrapped;
|
|
1001
|
+
}
|
|
1002
|
+
if (driver.done) return {
|
|
1003
|
+
value: void 0,
|
|
1004
|
+
done: true
|
|
1005
|
+
};
|
|
1006
|
+
if (isClosed()) throw new Error("Connection closed mid-COPY-OUT");
|
|
1007
|
+
await new Promise((resolve) => {
|
|
1008
|
+
driver.waker = resolve;
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
1011
|
+
},
|
|
1012
|
+
return() {
|
|
1013
|
+
driver.abandoned = true;
|
|
1014
|
+
driver.queue.length = 0;
|
|
1015
|
+
driver.queuedBytes = 0;
|
|
1016
|
+
resumeSocket();
|
|
1017
|
+
return Promise.resolve({
|
|
1018
|
+
value: void 0,
|
|
1019
|
+
done: true
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
};
|
|
1023
|
+
} };
|
|
1024
|
+
}
|
|
1025
|
+
/**
|
|
1026
|
+
* Handle messages arriving in `in-query` state when there is no
|
|
1027
|
+
* `pendingQuery` — i.e. the caller invoked `startCopyIn` / `startCopyOut`
|
|
1028
|
+
* and is waiting for the server to switch into copy mode.
|
|
1029
|
+
*/
|
|
1030
|
+
handleCopyStartMessage(msg) {
|
|
1031
|
+
switch (msg.type) {
|
|
1032
|
+
case "CopyInResponse":
|
|
1033
|
+
if (this._extPipelineActive) {
|
|
1034
|
+
this.abortForCopyInPipeline();
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
if (this.copyIn) {
|
|
1038
|
+
this.state = "in-copy-in";
|
|
1039
|
+
const r = this.copyStartResolve;
|
|
1040
|
+
this.copyStartResolve = null;
|
|
1041
|
+
this.copyStartReject = null;
|
|
1042
|
+
if (r) r();
|
|
1043
|
+
}
|
|
1044
|
+
return;
|
|
1045
|
+
case "CopyOutResponse":
|
|
1046
|
+
if (this._extPipelineActive) {
|
|
1047
|
+
this.abortForCopyInPipeline();
|
|
1048
|
+
return;
|
|
1049
|
+
}
|
|
1050
|
+
if (this.copyOut) {
|
|
1051
|
+
this.state = "in-copy-out";
|
|
1052
|
+
const r = this.copyStartResolve;
|
|
1053
|
+
this.copyStartResolve = null;
|
|
1054
|
+
this.copyStartReject = null;
|
|
1055
|
+
if (r) r();
|
|
1056
|
+
}
|
|
1057
|
+
return;
|
|
1058
|
+
case "ErrorResponse": {
|
|
1059
|
+
const err = fieldsToConnectError(msg.fields);
|
|
1060
|
+
if (this.copyIn) {
|
|
1061
|
+
if (this.copyStartReject) this.copyStartReject(err);
|
|
1062
|
+
this.copyStartResolve = null;
|
|
1063
|
+
this.copyStartReject = null;
|
|
1064
|
+
this.copyIn = null;
|
|
1065
|
+
}
|
|
1066
|
+
if (this.copyOut) {
|
|
1067
|
+
if (this.copyStartReject) this.copyStartReject(err);
|
|
1068
|
+
this.copyStartResolve = null;
|
|
1069
|
+
this.copyStartReject = null;
|
|
1070
|
+
this.copyOut = null;
|
|
1071
|
+
}
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
case "ReadyForQuery":
|
|
1075
|
+
this.txStatus = msg.status;
|
|
1076
|
+
this.state = "idle";
|
|
1077
|
+
return;
|
|
1078
|
+
case "NoticeResponse":
|
|
1079
|
+
this.notify.emit(fieldsToNotice(msg.fields));
|
|
1080
|
+
return;
|
|
1081
|
+
case "ParameterStatus":
|
|
1082
|
+
this.params.set(msg.name, msg.value);
|
|
1083
|
+
return;
|
|
1084
|
+
default:
|
|
1085
|
+
if (this.copyStartReject) this.copyStartReject(/* @__PURE__ */ new Error(`Unexpected backend message before COPY response: ${msg.type}`));
|
|
1086
|
+
this.copyStartResolve = null;
|
|
1087
|
+
this.copyStartReject = null;
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
handleCopyInMessage(msg) {
|
|
1092
|
+
const driver = this.copyIn;
|
|
1093
|
+
if (!driver) return;
|
|
1094
|
+
switch (msg.type) {
|
|
1095
|
+
case "CommandComplete":
|
|
1096
|
+
driver.commandTag = msg.tag;
|
|
1097
|
+
this.lastCopyTag = msg.tag;
|
|
1098
|
+
return;
|
|
1099
|
+
case "ErrorResponse":
|
|
1100
|
+
driver.error = fieldsToConnectError(msg.fields);
|
|
1101
|
+
return;
|
|
1102
|
+
case "NoticeResponse":
|
|
1103
|
+
this.notify.emit(fieldsToNotice(msg.fields));
|
|
1104
|
+
return;
|
|
1105
|
+
case "ParameterStatus":
|
|
1106
|
+
this.params.set(msg.name, msg.value);
|
|
1107
|
+
return;
|
|
1108
|
+
case "ReadyForQuery":
|
|
1109
|
+
this.txStatus = msg.status;
|
|
1110
|
+
this.state = "idle";
|
|
1111
|
+
this.copyIn = null;
|
|
1112
|
+
if (driver.error) {
|
|
1113
|
+
if (driver.rejectDone) driver.rejectDone(driver.error);
|
|
1114
|
+
} else if (driver.resolveDone) driver.resolveDone();
|
|
1115
|
+
return;
|
|
1116
|
+
default:
|
|
1117
|
+
driver.error = {
|
|
1118
|
+
severity: "ERROR",
|
|
1119
|
+
message: `Unexpected backend message during COPY IN: ${msg.type}`
|
|
1120
|
+
};
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
handleCopyOutMessage(msg) {
|
|
1125
|
+
const driver = this.copyOut;
|
|
1126
|
+
if (!driver) return;
|
|
1127
|
+
switch (msg.type) {
|
|
1128
|
+
case "CopyData":
|
|
1129
|
+
if (driver.abandoned) return;
|
|
1130
|
+
driver.queue.push(msg.data);
|
|
1131
|
+
driver.queuedBytes += msg.data.length;
|
|
1132
|
+
if (driver.queuedBytes >= COPY_OUT_HWM) this.socket.pause?.();
|
|
1133
|
+
if (driver.waker) {
|
|
1134
|
+
const w = driver.waker;
|
|
1135
|
+
driver.waker = null;
|
|
1136
|
+
w();
|
|
1137
|
+
}
|
|
1138
|
+
return;
|
|
1139
|
+
case "CopyDone": return;
|
|
1140
|
+
case "CommandComplete":
|
|
1141
|
+
driver.commandTag = msg.tag;
|
|
1142
|
+
this.lastCopyTag = msg.tag;
|
|
1143
|
+
return;
|
|
1144
|
+
case "ErrorResponse":
|
|
1145
|
+
driver.error = fieldsToConnectError(msg.fields);
|
|
1146
|
+
return;
|
|
1147
|
+
case "NoticeResponse":
|
|
1148
|
+
this.notify.emit(fieldsToNotice(msg.fields));
|
|
1149
|
+
return;
|
|
1150
|
+
case "ParameterStatus":
|
|
1151
|
+
this.params.set(msg.name, msg.value);
|
|
1152
|
+
return;
|
|
1153
|
+
case "ReadyForQuery":
|
|
1154
|
+
this.txStatus = msg.status;
|
|
1155
|
+
this.state = "idle";
|
|
1156
|
+
driver.done = true;
|
|
1157
|
+
this.copyOut = null;
|
|
1158
|
+
if (driver.waker) {
|
|
1159
|
+
const w = driver.waker;
|
|
1160
|
+
driver.waker = null;
|
|
1161
|
+
w();
|
|
1162
|
+
}
|
|
1163
|
+
return;
|
|
1164
|
+
default:
|
|
1165
|
+
driver.error = {
|
|
1166
|
+
severity: "ERROR",
|
|
1167
|
+
message: `Unexpected backend message during COPY OUT: ${msg.type}`
|
|
1168
|
+
};
|
|
1169
|
+
if (driver.waker) {
|
|
1170
|
+
const w = driver.waker;
|
|
1171
|
+
driver.waker = null;
|
|
1172
|
+
w();
|
|
1173
|
+
}
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
handleQueryMessage(msg) {
|
|
1178
|
+
const q = this.pendingQuery;
|
|
1179
|
+
if (!q) {
|
|
1180
|
+
this.handleCopyStartMessage(msg);
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
1183
|
+
switch (msg.type) {
|
|
1184
|
+
case "RowDescription":
|
|
1185
|
+
q.current = {
|
|
1186
|
+
command: "",
|
|
1187
|
+
rowCount: null,
|
|
1188
|
+
oid: null,
|
|
1189
|
+
fields: msg.fields,
|
|
1190
|
+
rows: [],
|
|
1191
|
+
notices: []
|
|
1192
|
+
};
|
|
1193
|
+
return;
|
|
1194
|
+
case "DataRow":
|
|
1195
|
+
if (!q.current) q.current = {
|
|
1196
|
+
command: "",
|
|
1197
|
+
rowCount: null,
|
|
1198
|
+
oid: null,
|
|
1199
|
+
fields: [],
|
|
1200
|
+
rows: [],
|
|
1201
|
+
notices: []
|
|
1202
|
+
};
|
|
1203
|
+
q.current.rows.push(decodeDataRow(msg.values, q.current.fields));
|
|
1204
|
+
return;
|
|
1205
|
+
case "CommandComplete": {
|
|
1206
|
+
const { command, rowCount, oid } = parseCommandTag(msg.tag);
|
|
1207
|
+
const set = q.current ?? {
|
|
1208
|
+
command,
|
|
1209
|
+
rowCount,
|
|
1210
|
+
oid,
|
|
1211
|
+
fields: [],
|
|
1212
|
+
rows: [],
|
|
1213
|
+
notices: []
|
|
1214
|
+
};
|
|
1215
|
+
set.command = command;
|
|
1216
|
+
set.rowCount = rowCount;
|
|
1217
|
+
set.oid = oid;
|
|
1218
|
+
set.notices = q.notices.splice(0);
|
|
1219
|
+
q.finished.push(set);
|
|
1220
|
+
q.current = null;
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
1223
|
+
case "EmptyQueryResponse": {
|
|
1224
|
+
const set = {
|
|
1225
|
+
command: "",
|
|
1226
|
+
rowCount: null,
|
|
1227
|
+
oid: null,
|
|
1228
|
+
fields: [],
|
|
1229
|
+
rows: [],
|
|
1230
|
+
notices: q.notices.splice(0)
|
|
1231
|
+
};
|
|
1232
|
+
q.finished.push(set);
|
|
1233
|
+
q.current = null;
|
|
1234
|
+
return;
|
|
1235
|
+
}
|
|
1236
|
+
case "ParameterStatus":
|
|
1237
|
+
this.params.set(msg.name, msg.value);
|
|
1238
|
+
if (msg.name === "server_version") this.serverVersion = parseServerVersion(msg.value);
|
|
1239
|
+
return;
|
|
1240
|
+
case "NoticeResponse": {
|
|
1241
|
+
const notice = fieldsToNotice(msg.fields);
|
|
1242
|
+
q.notices.push(notice);
|
|
1243
|
+
this.notify.emit(notice);
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1246
|
+
case "NotificationResponse":
|
|
1247
|
+
this.notify.emitNotification(msg.channel, msg.payload, msg.processId);
|
|
1248
|
+
return;
|
|
1249
|
+
case "ErrorResponse":
|
|
1250
|
+
q.error = fieldsToConnectError(msg.fields);
|
|
1251
|
+
return;
|
|
1252
|
+
case "ReadyForQuery":
|
|
1253
|
+
this.txStatus = msg.status;
|
|
1254
|
+
this.state = "idle";
|
|
1255
|
+
this.pendingQuery = null;
|
|
1256
|
+
if (q.error) {
|
|
1257
|
+
const err = asThrowable(q.error);
|
|
1258
|
+
err.partialResults = q.finished;
|
|
1259
|
+
q.reject(err);
|
|
1260
|
+
} else q.resolve(q.finished);
|
|
1261
|
+
return;
|
|
1262
|
+
case "CopyInResponse": {
|
|
1263
|
+
if (this._extPipelineActive) {
|
|
1264
|
+
this.abortForCopyInPipeline();
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
const data = this.copyInMidBatchQueue.shift();
|
|
1268
|
+
if (data !== void 0) {
|
|
1269
|
+
try {
|
|
1270
|
+
if (data.length > 0) this.socket.write(CopyData(data));
|
|
1271
|
+
this.socket.write(CopyDone());
|
|
1272
|
+
} catch {}
|
|
1273
|
+
return;
|
|
1274
|
+
}
|
|
1275
|
+
q.error = {
|
|
1276
|
+
severity: "ERROR",
|
|
1277
|
+
message: "COPY FROM STDIN not supported via execSimple — use \\copy or startCopyIn"
|
|
1278
|
+
};
|
|
1279
|
+
try {
|
|
1280
|
+
this.socket.write(CopyFail("COPY FROM STDIN not driven by client"));
|
|
1281
|
+
} catch {}
|
|
1282
|
+
return;
|
|
1283
|
+
}
|
|
1284
|
+
case "CopyOutResponse":
|
|
1285
|
+
if (this._extPipelineActive) {
|
|
1286
|
+
this.abortForCopyInPipeline();
|
|
1287
|
+
return;
|
|
1288
|
+
}
|
|
1289
|
+
this.copyOutMidBatchActive = true;
|
|
1290
|
+
q.current = q.current ?? {
|
|
1291
|
+
command: "",
|
|
1292
|
+
rowCount: null,
|
|
1293
|
+
oid: null,
|
|
1294
|
+
fields: [],
|
|
1295
|
+
rows: [],
|
|
1296
|
+
notices: []
|
|
1297
|
+
};
|
|
1298
|
+
q.current.copyOutBytes = q.current.copyOutBytes ?? [];
|
|
1299
|
+
return;
|
|
1300
|
+
case "CopyData":
|
|
1301
|
+
if (this.copyOutMidBatchActive) {
|
|
1302
|
+
const cur = q.current;
|
|
1303
|
+
if (cur) {
|
|
1304
|
+
cur.copyOutBytes = cur.copyOutBytes ?? [];
|
|
1305
|
+
cur.copyOutBytes.push(msg.data);
|
|
1306
|
+
}
|
|
1307
|
+
return;
|
|
1308
|
+
}
|
|
1309
|
+
q.error = {
|
|
1310
|
+
severity: "ERROR",
|
|
1311
|
+
message: "Unexpected backend message during query: CopyData"
|
|
1312
|
+
};
|
|
1313
|
+
return;
|
|
1314
|
+
case "CopyDone":
|
|
1315
|
+
if (this.copyOutMidBatchActive) {
|
|
1316
|
+
this.copyOutMidBatchActive = false;
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1319
|
+
q.error = {
|
|
1320
|
+
severity: "ERROR",
|
|
1321
|
+
message: "Unexpected backend message during query: CopyDone"
|
|
1322
|
+
};
|
|
1323
|
+
return;
|
|
1324
|
+
case "CopyBothResponse": {
|
|
1325
|
+
const cbErr = {
|
|
1326
|
+
severity: "ERROR",
|
|
1327
|
+
code: "0A000",
|
|
1328
|
+
message: "syntax error: unexpected CopyBothResponse from server (replication streaming is not supported by this client)"
|
|
1329
|
+
};
|
|
1330
|
+
q.error = cbErr;
|
|
1331
|
+
q.reject(asThrowable(cbErr));
|
|
1332
|
+
this.pendingQuery = null;
|
|
1333
|
+
this.socketError = new Error(cbErr.message);
|
|
1334
|
+
try {
|
|
1335
|
+
this.socket.destroy();
|
|
1336
|
+
} catch {}
|
|
1337
|
+
this.state = "closed";
|
|
1338
|
+
return;
|
|
1339
|
+
}
|
|
1340
|
+
default:
|
|
1341
|
+
q.error = {
|
|
1342
|
+
severity: "ERROR",
|
|
1343
|
+
message: `Unexpected backend message during query: ${msg.type}`
|
|
1344
|
+
};
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
onData(chunk) {
|
|
1349
|
+
let messages;
|
|
1350
|
+
try {
|
|
1351
|
+
messages = this.parser.feed(chunk);
|
|
1352
|
+
} catch (err) {
|
|
1353
|
+
this.socketError = err instanceof Error ? err : new Error(String(err));
|
|
1354
|
+
this.failPending(this.socketError);
|
|
1355
|
+
try {
|
|
1356
|
+
this.socket.destroy();
|
|
1357
|
+
} catch {}
|
|
1358
|
+
this.state = "closed";
|
|
1359
|
+
return;
|
|
1360
|
+
}
|
|
1361
|
+
for (const msg of messages) {
|
|
1362
|
+
this.dispatch(msg);
|
|
1363
|
+
if (this.state === "closed") break;
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
dispatch(msg) {
|
|
1367
|
+
if (msg.type === "NotificationResponse") {
|
|
1368
|
+
this.notify.emitNotification(msg.channel, msg.payload, msg.processId);
|
|
1369
|
+
return;
|
|
1370
|
+
}
|
|
1371
|
+
switch (this.state) {
|
|
1372
|
+
case "auth":
|
|
1373
|
+
this.handleAuthMessage(msg);
|
|
1374
|
+
return;
|
|
1375
|
+
case "await-ready":
|
|
1376
|
+
this.handleAwaitReady(msg);
|
|
1377
|
+
return;
|
|
1378
|
+
case "idle":
|
|
1379
|
+
if (msg.type === "ParameterStatus") {
|
|
1380
|
+
this.params.set(msg.name, msg.value);
|
|
1381
|
+
if (msg.name === "server_version") this.serverVersion = parseServerVersion(msg.value);
|
|
1382
|
+
return;
|
|
1383
|
+
}
|
|
1384
|
+
if (msg.type === "NoticeResponse") {
|
|
1385
|
+
this.notify.emit(fieldsToNotice(msg.fields));
|
|
1386
|
+
return;
|
|
1387
|
+
}
|
|
1388
|
+
this.socketError = /* @__PURE__ */ new Error(`Unexpected ${msg.type} in idle state`);
|
|
1389
|
+
try {
|
|
1390
|
+
this.socket.destroy();
|
|
1391
|
+
} catch {}
|
|
1392
|
+
this.state = "closed";
|
|
1393
|
+
return;
|
|
1394
|
+
case "in-query":
|
|
1395
|
+
this.handleQueryMessage(msg);
|
|
1396
|
+
return;
|
|
1397
|
+
case "in-extended":
|
|
1398
|
+
this.handleExtendedMessage(msg);
|
|
1399
|
+
return;
|
|
1400
|
+
case "in-copy-in":
|
|
1401
|
+
this.handleCopyInMessage(msg);
|
|
1402
|
+
return;
|
|
1403
|
+
case "in-copy-out":
|
|
1404
|
+
this.handleCopyOutMessage(msg);
|
|
1405
|
+
return;
|
|
1406
|
+
case "closed": return;
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
failPending(err) {
|
|
1410
|
+
if (this.pendingQuery) {
|
|
1411
|
+
const q = this.pendingQuery;
|
|
1412
|
+
this.pendingQuery = null;
|
|
1413
|
+
q.reject(q.error != null ? asThrowable(q.error) : err);
|
|
1414
|
+
}
|
|
1415
|
+
if (this.extDriver) {
|
|
1416
|
+
const d = this.extDriver;
|
|
1417
|
+
this.extDriver = null;
|
|
1418
|
+
for (const op of d.queue) op.reject(err);
|
|
1419
|
+
}
|
|
1420
|
+
if (this.startupReject) {
|
|
1421
|
+
const r = this.startupReject;
|
|
1422
|
+
this.startupResolve = null;
|
|
1423
|
+
this.startupReject = null;
|
|
1424
|
+
r(err);
|
|
1425
|
+
}
|
|
1426
|
+
if (this.copyStartReject) {
|
|
1427
|
+
const r = this.copyStartReject;
|
|
1428
|
+
this.copyStartResolve = null;
|
|
1429
|
+
this.copyStartReject = null;
|
|
1430
|
+
r(err);
|
|
1431
|
+
}
|
|
1432
|
+
if (this.copyIn) {
|
|
1433
|
+
const d = this.copyIn;
|
|
1434
|
+
this.copyIn = null;
|
|
1435
|
+
if (d.rejectDone) d.rejectDone(err);
|
|
1436
|
+
}
|
|
1437
|
+
if (this.copyOut) {
|
|
1438
|
+
const d = this.copyOut;
|
|
1439
|
+
this.copyOut = null;
|
|
1440
|
+
d.error = err instanceof Error ? {
|
|
1441
|
+
severity: "ERROR",
|
|
1442
|
+
message: err.message
|
|
1443
|
+
} : {
|
|
1444
|
+
severity: "ERROR",
|
|
1445
|
+
message: String(err)
|
|
1446
|
+
};
|
|
1447
|
+
if (d.waker) {
|
|
1448
|
+
const w = d.waker;
|
|
1449
|
+
d.waker = null;
|
|
1450
|
+
w();
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
ensureIdle() {
|
|
1455
|
+
if (this.state === "closed") throw new Error("PgConnection: connection is closed");
|
|
1456
|
+
if (this.state !== "idle" && this.state !== "in-extended") throw new Error(`PgConnection: cannot start query in state ${this.state}`);
|
|
1457
|
+
}
|
|
1458
|
+
startExtendedBatch() {
|
|
1459
|
+
if (this.state === "idle") {
|
|
1460
|
+
this.state = "in-extended";
|
|
1461
|
+
this.extDriver = {
|
|
1462
|
+
queue: [],
|
|
1463
|
+
error: null
|
|
1464
|
+
};
|
|
1465
|
+
} else if (this.state !== "in-extended") throw new Error(`PgConnection: cannot start extended batch in state ${this.state}`);
|
|
1466
|
+
else if (!this.extDriver) this.extDriver = {
|
|
1467
|
+
queue: [],
|
|
1468
|
+
error: null
|
|
1469
|
+
};
|
|
1470
|
+
}
|
|
1471
|
+
writeRaw(buf) {
|
|
1472
|
+
this.socket.write(buf);
|
|
1473
|
+
}
|
|
1474
|
+
enqueueParse() {
|
|
1475
|
+
return this.enqueueOp({
|
|
1476
|
+
kind: "parse",
|
|
1477
|
+
resolve: () => void 0,
|
|
1478
|
+
reject: () => void 0
|
|
1479
|
+
});
|
|
1480
|
+
}
|
|
1481
|
+
enqueueBind() {
|
|
1482
|
+
return this.enqueueOp({
|
|
1483
|
+
kind: "bind",
|
|
1484
|
+
resolve: () => void 0,
|
|
1485
|
+
reject: () => void 0
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1488
|
+
enqueueDescribeStatement() {
|
|
1489
|
+
return this.enqueueOp({
|
|
1490
|
+
kind: "describeS",
|
|
1491
|
+
resolve: () => void 0,
|
|
1492
|
+
reject: () => void 0,
|
|
1493
|
+
paramOids: null
|
|
1494
|
+
});
|
|
1495
|
+
}
|
|
1496
|
+
enqueueDescribePortal() {
|
|
1497
|
+
return this.enqueueOp({
|
|
1498
|
+
kind: "describeP",
|
|
1499
|
+
resolve: () => void 0,
|
|
1500
|
+
reject: () => void 0
|
|
1501
|
+
});
|
|
1502
|
+
}
|
|
1503
|
+
/**
|
|
1504
|
+
* Variant of {@link enqueueDescribePortal} that pipes the resolved fields
|
|
1505
|
+
* onto the very next `execute` op already (or yet to be) on the queue.
|
|
1506
|
+
*/
|
|
1507
|
+
enqueueDescribePortalIntoNextExecute() {
|
|
1508
|
+
const driver = this.extDriver;
|
|
1509
|
+
if (!driver) return Promise.reject(/* @__PURE__ */ new Error("enqueueDescribePortalIntoNextExecute: not in extended state"));
|
|
1510
|
+
return new Promise((resolve, reject) => {
|
|
1511
|
+
driver.queue.push({
|
|
1512
|
+
kind: "describeP",
|
|
1513
|
+
resolve: (v) => {
|
|
1514
|
+
const fields = v;
|
|
1515
|
+
for (const op of driver.queue) if (op.kind === "execute" && op.fields === null) {
|
|
1516
|
+
op.fields = fields;
|
|
1517
|
+
break;
|
|
1518
|
+
}
|
|
1519
|
+
resolve();
|
|
1520
|
+
},
|
|
1521
|
+
reject
|
|
1522
|
+
});
|
|
1523
|
+
});
|
|
1524
|
+
}
|
|
1525
|
+
enqueueExecute() {
|
|
1526
|
+
return this.enqueueOp({
|
|
1527
|
+
kind: "execute",
|
|
1528
|
+
resolve: () => void 0,
|
|
1529
|
+
reject: () => void 0,
|
|
1530
|
+
current: null,
|
|
1531
|
+
notices: [],
|
|
1532
|
+
fields: null
|
|
1533
|
+
});
|
|
1534
|
+
}
|
|
1535
|
+
enqueueExecuteWithFields(fields) {
|
|
1536
|
+
return this.enqueueOp({
|
|
1537
|
+
kind: "execute",
|
|
1538
|
+
resolve: () => void 0,
|
|
1539
|
+
reject: () => void 0,
|
|
1540
|
+
current: null,
|
|
1541
|
+
notices: [],
|
|
1542
|
+
fields
|
|
1543
|
+
});
|
|
1544
|
+
}
|
|
1545
|
+
enqueueClose() {
|
|
1546
|
+
return this.enqueueOp({
|
|
1547
|
+
kind: "close",
|
|
1548
|
+
resolve: () => void 0,
|
|
1549
|
+
reject: () => void 0
|
|
1550
|
+
});
|
|
1551
|
+
}
|
|
1552
|
+
enqueueSync() {
|
|
1553
|
+
return this.enqueueOp({
|
|
1554
|
+
kind: "sync",
|
|
1555
|
+
resolve: () => void 0,
|
|
1556
|
+
reject: () => void 0
|
|
1557
|
+
});
|
|
1558
|
+
}
|
|
1559
|
+
enqueueOp(opSkeleton) {
|
|
1560
|
+
if (!this.extDriver) return Promise.reject(/* @__PURE__ */ new Error("enqueueOp: not in extended state"));
|
|
1561
|
+
const driver = this.extDriver;
|
|
1562
|
+
return new Promise((resolve, reject) => {
|
|
1563
|
+
const op = opSkeleton;
|
|
1564
|
+
op.resolve = resolve;
|
|
1565
|
+
op.reject = reject;
|
|
1566
|
+
driver.queue.push(op);
|
|
1567
|
+
});
|
|
1568
|
+
}
|
|
1569
|
+
handleExtendedMessage(msg) {
|
|
1570
|
+
const driver = this.extDriver;
|
|
1571
|
+
if (!driver) return;
|
|
1572
|
+
if (msg.type === "ParameterStatus") {
|
|
1573
|
+
this.params.set(msg.name, msg.value);
|
|
1574
|
+
if (msg.name === "server_version") this.serverVersion = parseServerVersion(msg.value);
|
|
1575
|
+
return;
|
|
1576
|
+
}
|
|
1577
|
+
if (msg.type === "NoticeResponse") {
|
|
1578
|
+
const notice = fieldsToNotice(msg.fields);
|
|
1579
|
+
this.notify.emit(notice);
|
|
1580
|
+
const head = driver.queue[0];
|
|
1581
|
+
if (head && head.kind === "execute") head.notices.push(notice);
|
|
1582
|
+
return;
|
|
1583
|
+
}
|
|
1584
|
+
if (msg.type === "NotificationResponse") {
|
|
1585
|
+
this.notify.emitNotification(msg.channel, msg.payload, msg.processId);
|
|
1586
|
+
return;
|
|
1587
|
+
}
|
|
1588
|
+
if (msg.type === "ErrorResponse") {
|
|
1589
|
+
driver.error = fieldsToConnectError(msg.fields);
|
|
1590
|
+
let first = true;
|
|
1591
|
+
while (driver.queue.length > 0) {
|
|
1592
|
+
const head = driver.queue[0];
|
|
1593
|
+
if (head.kind === "sync") break;
|
|
1594
|
+
driver.queue.shift();
|
|
1595
|
+
if (first) {
|
|
1596
|
+
head.reject(driver.error);
|
|
1597
|
+
first = false;
|
|
1598
|
+
} else head.reject(pipelineAbortedError());
|
|
1599
|
+
}
|
|
1600
|
+
return;
|
|
1601
|
+
}
|
|
1602
|
+
if (msg.type === "CopyInResponse" || msg.type === "CopyOutResponse") {
|
|
1603
|
+
this.abortForCopyInPipeline();
|
|
1604
|
+
return;
|
|
1605
|
+
}
|
|
1606
|
+
while (driver.error !== null) {
|
|
1607
|
+
const head = driver.queue[0];
|
|
1608
|
+
if (!head || head.kind === "sync") break;
|
|
1609
|
+
driver.queue.shift();
|
|
1610
|
+
head.reject(pipelineAbortedError());
|
|
1611
|
+
}
|
|
1612
|
+
const head = driver.queue[0];
|
|
1613
|
+
if (!head) {
|
|
1614
|
+
this.protocolFail(/* @__PURE__ */ new Error(`Unexpected backend message ${msg.type} in in-extended`));
|
|
1615
|
+
return;
|
|
1616
|
+
}
|
|
1617
|
+
switch (msg.type) {
|
|
1618
|
+
case "ParseComplete":
|
|
1619
|
+
if (head.kind !== "parse") {
|
|
1620
|
+
this.protocolFail(/* @__PURE__ */ new Error("ParseComplete arrived but head op is " + head.kind));
|
|
1621
|
+
return;
|
|
1622
|
+
}
|
|
1623
|
+
driver.queue.shift();
|
|
1624
|
+
head.resolve(void 0);
|
|
1625
|
+
return;
|
|
1626
|
+
case "BindComplete":
|
|
1627
|
+
if (head.kind !== "bind") {
|
|
1628
|
+
this.protocolFail(/* @__PURE__ */ new Error("BindComplete arrived but head op is " + head.kind));
|
|
1629
|
+
return;
|
|
1630
|
+
}
|
|
1631
|
+
driver.queue.shift();
|
|
1632
|
+
head.resolve(void 0);
|
|
1633
|
+
return;
|
|
1634
|
+
case "CloseComplete":
|
|
1635
|
+
if (head.kind !== "close") {
|
|
1636
|
+
this.protocolFail(/* @__PURE__ */ new Error("CloseComplete arrived but head op is " + head.kind));
|
|
1637
|
+
return;
|
|
1638
|
+
}
|
|
1639
|
+
driver.queue.shift();
|
|
1640
|
+
head.resolve(void 0);
|
|
1641
|
+
return;
|
|
1642
|
+
case "ParameterDescription":
|
|
1643
|
+
if (head.kind !== "describeS") {
|
|
1644
|
+
this.protocolFail(/* @__PURE__ */ new Error("ParameterDescription arrived but head op is " + head.kind));
|
|
1645
|
+
return;
|
|
1646
|
+
}
|
|
1647
|
+
head.paramOids = msg.oids;
|
|
1648
|
+
return;
|
|
1649
|
+
case "RowDescription":
|
|
1650
|
+
if (head.kind === "describeS") {
|
|
1651
|
+
driver.queue.shift();
|
|
1652
|
+
head.resolve({
|
|
1653
|
+
paramOids: head.paramOids ?? [],
|
|
1654
|
+
fields: msg.fields
|
|
1655
|
+
});
|
|
1656
|
+
return;
|
|
1657
|
+
}
|
|
1658
|
+
if (head.kind === "describeP") {
|
|
1659
|
+
driver.queue.shift();
|
|
1660
|
+
head.resolve(msg.fields);
|
|
1661
|
+
return;
|
|
1662
|
+
}
|
|
1663
|
+
if (head.kind === "execute") {
|
|
1664
|
+
head.current = {
|
|
1665
|
+
command: "",
|
|
1666
|
+
rowCount: null,
|
|
1667
|
+
oid: null,
|
|
1668
|
+
fields: msg.fields,
|
|
1669
|
+
rows: [],
|
|
1670
|
+
notices: []
|
|
1671
|
+
};
|
|
1672
|
+
head.fields = msg.fields;
|
|
1673
|
+
return;
|
|
1674
|
+
}
|
|
1675
|
+
this.protocolFail(/* @__PURE__ */ new Error("Unexpected RowDescription at head op " + head.kind));
|
|
1676
|
+
return;
|
|
1677
|
+
case "NoData":
|
|
1678
|
+
if (head.kind === "describeS") {
|
|
1679
|
+
driver.queue.shift();
|
|
1680
|
+
head.resolve({
|
|
1681
|
+
paramOids: head.paramOids ?? [],
|
|
1682
|
+
fields: []
|
|
1683
|
+
});
|
|
1684
|
+
return;
|
|
1685
|
+
}
|
|
1686
|
+
if (head.kind === "describeP") {
|
|
1687
|
+
driver.queue.shift();
|
|
1688
|
+
head.resolve([]);
|
|
1689
|
+
return;
|
|
1690
|
+
}
|
|
1691
|
+
this.protocolFail(/* @__PURE__ */ new Error("Unexpected NoData at head op " + head.kind));
|
|
1692
|
+
return;
|
|
1693
|
+
case "DataRow": {
|
|
1694
|
+
if (head.kind !== "execute") {
|
|
1695
|
+
this.protocolFail(/* @__PURE__ */ new Error("DataRow at head op " + head.kind));
|
|
1696
|
+
return;
|
|
1697
|
+
}
|
|
1698
|
+
const fields = head.fields ?? head.current?.fields ?? [];
|
|
1699
|
+
if (!head.current) head.current = {
|
|
1700
|
+
command: "",
|
|
1701
|
+
rowCount: null,
|
|
1702
|
+
oid: null,
|
|
1703
|
+
fields,
|
|
1704
|
+
rows: [],
|
|
1705
|
+
notices: []
|
|
1706
|
+
};
|
|
1707
|
+
head.current.rows.push(decodeDataRow(msg.values, fields));
|
|
1708
|
+
return;
|
|
1709
|
+
}
|
|
1710
|
+
case "CommandComplete": {
|
|
1711
|
+
if (head.kind !== "execute") {
|
|
1712
|
+
this.protocolFail(/* @__PURE__ */ new Error("CommandComplete at head op " + head.kind));
|
|
1713
|
+
return;
|
|
1714
|
+
}
|
|
1715
|
+
const { command, rowCount, oid } = parseCommandTag(msg.tag);
|
|
1716
|
+
const set = head.current ?? {
|
|
1717
|
+
command,
|
|
1718
|
+
rowCount,
|
|
1719
|
+
oid,
|
|
1720
|
+
fields: head.fields ?? [],
|
|
1721
|
+
rows: [],
|
|
1722
|
+
notices: []
|
|
1723
|
+
};
|
|
1724
|
+
set.command = command;
|
|
1725
|
+
set.rowCount = rowCount;
|
|
1726
|
+
set.oid = oid;
|
|
1727
|
+
set.notices = head.notices.splice(0);
|
|
1728
|
+
driver.queue.shift();
|
|
1729
|
+
head.resolve(set);
|
|
1730
|
+
return;
|
|
1731
|
+
}
|
|
1732
|
+
case "EmptyQueryResponse": {
|
|
1733
|
+
if (head.kind !== "execute") {
|
|
1734
|
+
this.protocolFail(/* @__PURE__ */ new Error("EmptyQueryResponse at head op " + head.kind));
|
|
1735
|
+
return;
|
|
1736
|
+
}
|
|
1737
|
+
const set = {
|
|
1738
|
+
command: "",
|
|
1739
|
+
rowCount: null,
|
|
1740
|
+
oid: null,
|
|
1741
|
+
fields: [],
|
|
1742
|
+
rows: [],
|
|
1743
|
+
notices: head.notices.splice(0)
|
|
1744
|
+
};
|
|
1745
|
+
driver.queue.shift();
|
|
1746
|
+
head.resolve(set);
|
|
1747
|
+
return;
|
|
1748
|
+
}
|
|
1749
|
+
case "PortalSuspended": {
|
|
1750
|
+
if (head.kind !== "execute") {
|
|
1751
|
+
this.protocolFail(/* @__PURE__ */ new Error("PortalSuspended at head op " + head.kind));
|
|
1752
|
+
return;
|
|
1753
|
+
}
|
|
1754
|
+
const set = head.current ?? {
|
|
1755
|
+
command: "",
|
|
1756
|
+
rowCount: null,
|
|
1757
|
+
oid: null,
|
|
1758
|
+
fields: head.fields ?? [],
|
|
1759
|
+
rows: [],
|
|
1760
|
+
notices: head.notices.splice(0)
|
|
1761
|
+
};
|
|
1762
|
+
set.notices = head.notices.splice(0);
|
|
1763
|
+
driver.queue.shift();
|
|
1764
|
+
head.resolve(set);
|
|
1765
|
+
return;
|
|
1766
|
+
}
|
|
1767
|
+
case "ReadyForQuery": {
|
|
1768
|
+
this.txStatus = msg.status;
|
|
1769
|
+
if (head.kind !== "sync") {
|
|
1770
|
+
this.protocolFail(/* @__PURE__ */ new Error("ReadyForQuery but head op is " + head.kind));
|
|
1771
|
+
return;
|
|
1772
|
+
}
|
|
1773
|
+
driver.queue.shift();
|
|
1774
|
+
const stickyErr = driver.error;
|
|
1775
|
+
driver.error = null;
|
|
1776
|
+
if (stickyErr) head.reject(stickyErr);
|
|
1777
|
+
else head.resolve(void 0);
|
|
1778
|
+
if (driver.queue.length === 0 && !this._extPipelineActive) {
|
|
1779
|
+
this.state = "idle";
|
|
1780
|
+
this.extDriver = null;
|
|
1781
|
+
}
|
|
1782
|
+
return;
|
|
1783
|
+
}
|
|
1784
|
+
default:
|
|
1785
|
+
this.protocolFail(/* @__PURE__ */ new Error(`Unexpected ${msg.type} in in-extended state`));
|
|
1786
|
+
return;
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
protocolFail(err) {
|
|
1790
|
+
this.socketError = err;
|
|
1791
|
+
this.failPending(err);
|
|
1792
|
+
try {
|
|
1793
|
+
this.socket.destroy();
|
|
1794
|
+
} catch {}
|
|
1795
|
+
this.state = "closed";
|
|
1796
|
+
}
|
|
1797
|
+
/**
|
|
1798
|
+
* Abort the connection because the server replied with CopyInResponse /
|
|
1799
|
+
* CopyOutResponse while a pipeline (`_extPipelineActive`) was active.
|
|
1800
|
+
* Upstream libpq emits the exact diagnostic
|
|
1801
|
+
* `"COPY in a pipeline is not supported, aborting connection"` and tears
|
|
1802
|
+
* the socket down — we mirror that. Pending operations are rejected; the
|
|
1803
|
+
* connection is left in `closed` so subsequent commands fail cleanly
|
|
1804
|
+
* (matching the "aborting connection" promise).
|
|
1805
|
+
*/
|
|
1806
|
+
abortForCopyInPipeline() {
|
|
1807
|
+
const err = {
|
|
1808
|
+
severity: "FATAL",
|
|
1809
|
+
message: "COPY in a pipeline is not supported, aborting connection"
|
|
1810
|
+
};
|
|
1811
|
+
this.socketError = new Error(err.message);
|
|
1812
|
+
this.failPending(err);
|
|
1813
|
+
try {
|
|
1814
|
+
this.socket.destroy();
|
|
1815
|
+
} catch {}
|
|
1816
|
+
this.state = "closed";
|
|
1817
|
+
}
|
|
1818
|
+
};
|
|
2516
1819
|
/**
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
* Returning an empty array signals "treat as unresolvable" and the
|
|
2523
|
-
* candidate is dropped from the iteration set (matching libpq's "no
|
|
2524
|
-
* results from getaddrinfo" path).
|
|
2525
|
-
*/
|
|
2526
|
-
PgConnection._dnsLookupAll = null;
|
|
2527
|
-
// ---------------------------------------------------------------------------
|
|
2528
|
-
// Socket open helper. Supports TCP (default) and Unix-domain sockets when
|
|
2529
|
-
// `opts.host` starts with `/` — matching libpq's `pqUnixSocketPath()` which
|
|
2530
|
-
// reads the directory from PGHOST and builds `<dir>/.s.PGSQL.<port>` as the
|
|
2531
|
-
// actual filesystem socket path.
|
|
2532
|
-
// ---------------------------------------------------------------------------
|
|
2533
|
-
/**
|
|
2534
|
-
* `true` if the host value should be interpreted as a Unix-domain socket
|
|
2535
|
-
* directory. libpq's rule: any value starting with `/` is a path.
|
|
2536
|
-
*/
|
|
2537
|
-
export function isUnixSocketHost(host) {
|
|
2538
|
-
return host.startsWith("/");
|
|
1820
|
+
* `true` if the host value should be interpreted as a Unix-domain socket
|
|
1821
|
+
* directory. libpq's rule: any value starting with `/` is a path.
|
|
1822
|
+
*/
|
|
1823
|
+
function isUnixSocketHost(host) {
|
|
1824
|
+
return host.startsWith("/");
|
|
2539
1825
|
}
|
|
2540
1826
|
/**
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
1827
|
+
* Build the actual filesystem path Postgres listens on under a socket
|
|
1828
|
+
* directory: `<dir>/.s.PGSQL.<port>`. Mirrors the libpq layout so any
|
|
1829
|
+
* server started with `unix_socket_directories=<dir>` is reachable.
|
|
1830
|
+
*/
|
|
1831
|
+
function unixSocketPath(dir, port) {
|
|
1832
|
+
return `${dir}/.s.PGSQL.${String(port)}`;
|
|
2547
1833
|
}
|
|
2548
1834
|
/**
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
1835
|
+
* Expand the configured (host, port) list by resolving each hostname to
|
|
1836
|
+
* its full set of A/AAAA records. Mirrors libpq's `getaddrinfo`-then-
|
|
1837
|
+
* iterate-all behaviour exercised by upstream's
|
|
1838
|
+
* `src/interfaces/libpq/t/004_load_balance_dns.pl`. Without this step a
|
|
1839
|
+
* single hostname that resolves to N IPs would only ever produce one
|
|
1840
|
+
* candidate (Node's `net.connect({host})` picks one address from the
|
|
1841
|
+
* lookup result), so `load_balance_hosts=random` couldn't shuffle across
|
|
1842
|
+
* the DNS-returned set.
|
|
1843
|
+
*
|
|
1844
|
+
* - Unix-domain socket paths (`/var/run/postgres`) are passed through
|
|
1845
|
+
* unchanged — they don't participate in DNS at all.
|
|
1846
|
+
* - IPv4/IPv6 literals are passed through unchanged — DNS resolution
|
|
1847
|
+
* would just round-trip them.
|
|
1848
|
+
* - Hostnames are resolved via `dns.lookup(host, {all: true})`. The
|
|
1849
|
+
* test seam `PgConnection._dnsLookupAll` overrides the resolver so
|
|
1850
|
+
* unit tests can drive a hostname through a fixed IP set without
|
|
1851
|
+
* touching the real DNS.
|
|
1852
|
+
* - A hostname that fails to resolve (or returns zero records) is
|
|
1853
|
+
* dropped from the iteration set. The connect loop's `lastErr`
|
|
1854
|
+
* surfaces the original error if every host fails.
|
|
1855
|
+
*/
|
|
2570
1856
|
async function expandHostsViaDns(seed) {
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
}
|
|
2597
|
-
}
|
|
2598
|
-
return out;
|
|
1857
|
+
const out = [];
|
|
1858
|
+
for (const c of seed) {
|
|
1859
|
+
if (isUnixSocketHost(c.host) || net.isIP(c.host) !== 0) {
|
|
1860
|
+
out.push({
|
|
1861
|
+
host: c.host,
|
|
1862
|
+
port: c.port
|
|
1863
|
+
});
|
|
1864
|
+
continue;
|
|
1865
|
+
}
|
|
1866
|
+
let addrs;
|
|
1867
|
+
try {
|
|
1868
|
+
addrs = PgConnection._dnsLookupAll ? await PgConnection._dnsLookupAll(c.host) : await dns.lookup(c.host, {
|
|
1869
|
+
all: true,
|
|
1870
|
+
family: 0
|
|
1871
|
+
});
|
|
1872
|
+
} catch {
|
|
1873
|
+
continue;
|
|
1874
|
+
}
|
|
1875
|
+
for (const a of addrs) out.push({
|
|
1876
|
+
host: c.host,
|
|
1877
|
+
address: a.address,
|
|
1878
|
+
port: c.port
|
|
1879
|
+
});
|
|
1880
|
+
}
|
|
1881
|
+
return out;
|
|
2599
1882
|
}
|
|
2600
1883
|
/**
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
1884
|
+
* Map a libpq protocol-version string (`TLSv1` / `TLSv1.1` / `TLSv1.2` /
|
|
1885
|
+
* `TLSv1.3`) to Node's `SecureVersion` literal. Returns `undefined` for
|
|
1886
|
+
* unset / empty input. Unknown values shouldn't reach here (the parsing
|
|
1887
|
+
* layer in `index.ts` validates them), but we return `undefined` rather than
|
|
1888
|
+
* casting so a stray value can't smuggle a bogus literal into Node's TLS
|
|
1889
|
+
* options.
|
|
1890
|
+
*/
|
|
2608
1891
|
function toSecureVersion(value) {
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
return undefined;
|
|
2617
|
-
}
|
|
1892
|
+
switch (value) {
|
|
1893
|
+
case "TLSv1":
|
|
1894
|
+
case "TLSv1.1":
|
|
1895
|
+
case "TLSv1.2":
|
|
1896
|
+
case "TLSv1.3": return value;
|
|
1897
|
+
default: return;
|
|
1898
|
+
}
|
|
2618
1899
|
}
|
|
2619
1900
|
/**
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
1901
|
+
* Map libpq's `ssl_min_protocol_version` / `ssl_max_protocol_version` onto
|
|
1902
|
+
* Node's `tls.connect` `minVersion` / `maxVersion`. Unset values leave Node's
|
|
1903
|
+
* compiled-in defaults in place.
|
|
1904
|
+
*/
|
|
2624
1905
|
function applyTlsProtocolVersionRange(tlsOpts, opts) {
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
if (max !== undefined)
|
|
2630
|
-
tlsOpts.maxVersion = max;
|
|
1906
|
+
const min = toSecureVersion(opts.sslMinProtocolVersion);
|
|
1907
|
+
if (min !== void 0) tlsOpts.minVersion = min;
|
|
1908
|
+
const max = toSecureVersion(opts.sslMaxProtocolVersion);
|
|
1909
|
+
if (max !== void 0) tlsOpts.maxVersion = max;
|
|
2631
1910
|
}
|
|
2632
1911
|
/**
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
1912
|
+
* libpq `sslsni`: the TLS SNI servername to send, or `undefined` to suppress
|
|
1913
|
+
* the SNI extension entirely. `sslsni=0` (`opts.sslsni === false`) suppresses
|
|
1914
|
+
* it; unset / `sslsni=1` sends `opts.host` (libpq's default). Returned value
|
|
1915
|
+
* is spread into the `tls.connect` options as `servername`.
|
|
1916
|
+
*
|
|
1917
|
+
* Exported for unit tests.
|
|
1918
|
+
*/
|
|
1919
|
+
function tlsServername(opts) {
|
|
1920
|
+
return opts.sslsni === false ? void 0 : opts.host;
|
|
2642
1921
|
}
|
|
2643
1922
|
/**
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
: undefined,
|
|
2663
|
-
};
|
|
1923
|
+
* Translate libpq's `keepalives` / `keepalives_idle` into the arguments for
|
|
1924
|
+
* Node's `socket.setKeepAlive(enable, initialDelay)`:
|
|
1925
|
+
* - `enable`: `false` only when `keepalives === false` (libpq `keepalives=0`);
|
|
1926
|
+
* unset / `true` keeps keepalives on (libpq default).
|
|
1927
|
+
* - `initialDelayMs`: `keepalives_idle` seconds → milliseconds, or
|
|
1928
|
+
* `undefined` to leave the OS default.
|
|
1929
|
+
*
|
|
1930
|
+
* libpq's `keepalives_interval` and `keepalives_count` have NO Node net API
|
|
1931
|
+
* equivalent (`setKeepAlive` exposes only enable + initial delay), so they are
|
|
1932
|
+
* intentionally not represented here.
|
|
1933
|
+
*
|
|
1934
|
+
* Exported for unit tests.
|
|
1935
|
+
*/
|
|
1936
|
+
function keepAliveArgs(opts) {
|
|
1937
|
+
return {
|
|
1938
|
+
enable: opts.keepalives !== false,
|
|
1939
|
+
initialDelayMs: opts.keepalivesIdle !== void 0 ? opts.keepalivesIdle * 1e3 : void 0
|
|
1940
|
+
};
|
|
2664
1941
|
}
|
|
2665
1942
|
/**
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
const n = parseInt(digits[1], 10);
|
|
2693
|
-
if (Number.isFinite(n) && n > 0)
|
|
2694
|
-
return n;
|
|
2695
|
-
}
|
|
2696
|
-
return null;
|
|
1943
|
+
* Derive the symmetric key length (in bits) from a negotiated TLS cipher
|
|
1944
|
+
* name, for the PG18 `\conninfo` "SSL Key Bits" row. Node's TLS API doesn't
|
|
1945
|
+
* expose the key length directly, so we parse it out of the cipher name the
|
|
1946
|
+
* way upstream's `SSL_CIPHER_get_bits` effectively reports it:
|
|
1947
|
+
*
|
|
1948
|
+
* - `AES_256` / `CHACHA20` → 256
|
|
1949
|
+
* - `AES_128` → 128
|
|
1950
|
+
* - otherwise, the first run of digits in the standard name (`…128…` etc.)
|
|
1951
|
+
* - `null` when no length can be determined.
|
|
1952
|
+
*
|
|
1953
|
+
* Both the OpenSSL standard name (`TLS_AES_256_GCM_SHA384`,
|
|
1954
|
+
* `ECDHE-RSA-AES128-GCM-SHA256`) and Node's IANA-style name are handled by
|
|
1955
|
+
* uppercasing and matching the keyword forms first. Exported for unit tests.
|
|
1956
|
+
*/
|
|
1957
|
+
function sslKeyBitsFromCipher(name) {
|
|
1958
|
+
const upper = name.toUpperCase();
|
|
1959
|
+
if (upper.includes("CHACHA20")) return 256;
|
|
1960
|
+
if (upper.includes("AES_256") || upper.includes("AES256")) return 256;
|
|
1961
|
+
if (upper.includes("AES_128") || upper.includes("AES128")) return 128;
|
|
1962
|
+
if (upper.includes("AES_192") || upper.includes("AES192")) return 192;
|
|
1963
|
+
const digits = /(\d{2,4})/.exec(upper);
|
|
1964
|
+
if (digits) {
|
|
1965
|
+
const n = parseInt(digits[1], 10);
|
|
1966
|
+
if (Number.isFinite(n) && n > 0) return n;
|
|
1967
|
+
}
|
|
1968
|
+
return null;
|
|
2697
1969
|
}
|
|
2698
|
-
function openSocket(opts,
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
}, timeout);
|
|
2732
|
-
}
|
|
2733
|
-
const cleanup = () => {
|
|
2734
|
-
if (timer)
|
|
2735
|
-
clearTimeout(timer);
|
|
2736
|
-
socket.removeListener("error", onError);
|
|
2737
|
-
socket.removeListener("connect", onConnect);
|
|
2738
|
-
};
|
|
2739
|
-
const onError = (err) => {
|
|
2740
|
-
cleanup();
|
|
2741
|
-
reject(err);
|
|
2742
|
-
};
|
|
2743
|
-
const onConnect = () => {
|
|
2744
|
-
cleanup();
|
|
2745
|
-
resolve(socket);
|
|
2746
|
-
};
|
|
2747
|
-
socket.once("error", onError);
|
|
2748
|
-
socket.once("connect", onConnect);
|
|
2749
|
-
});
|
|
1970
|
+
function openSocket(opts, addressOverride) {
|
|
1971
|
+
return new Promise((resolve, reject) => {
|
|
1972
|
+
const isUnix = isUnixSocketHost(opts.host);
|
|
1973
|
+
const socket = isUnix ? net.connect({ path: unixSocketPath(opts.host, opts.port) }) : net.connect({
|
|
1974
|
+
host: addressOverride ?? opts.host,
|
|
1975
|
+
port: opts.port
|
|
1976
|
+
});
|
|
1977
|
+
if (!isUnix) {
|
|
1978
|
+
const { enable, initialDelayMs } = keepAliveArgs(opts);
|
|
1979
|
+
if (initialDelayMs !== void 0) socket.setKeepAlive(enable, initialDelayMs);
|
|
1980
|
+
else socket.setKeepAlive(enable);
|
|
1981
|
+
}
|
|
1982
|
+
const timeout = opts.connectTimeoutMs;
|
|
1983
|
+
let timer = null;
|
|
1984
|
+
if (timeout !== void 0 && timeout > 0) timer = setTimeout(() => {
|
|
1985
|
+
socket.destroy(/* @__PURE__ */ new Error(`Connect timed out after ${String(timeout)} ms`));
|
|
1986
|
+
}, timeout);
|
|
1987
|
+
const cleanup = () => {
|
|
1988
|
+
if (timer) clearTimeout(timer);
|
|
1989
|
+
socket.removeListener("error", onError);
|
|
1990
|
+
socket.removeListener("connect", onConnect);
|
|
1991
|
+
};
|
|
1992
|
+
const onError = (err) => {
|
|
1993
|
+
cleanup();
|
|
1994
|
+
reject(err);
|
|
1995
|
+
};
|
|
1996
|
+
const onConnect = () => {
|
|
1997
|
+
cleanup();
|
|
1998
|
+
resolve(socket);
|
|
1999
|
+
};
|
|
2000
|
+
socket.once("error", onError);
|
|
2001
|
+
socket.once("connect", onConnect);
|
|
2002
|
+
});
|
|
2750
2003
|
}
|
|
2751
|
-
// ---------------------------------------------------------------------------
|
|
2752
|
-
// Result decoding helpers
|
|
2753
|
-
// ---------------------------------------------------------------------------
|
|
2754
2004
|
/**
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2005
|
+
* Decode a wire-protocol DataRow into JS values. We follow the simple psql
|
|
2006
|
+
* policy: text format → utf-8 string, binary format → Buffer. Type-aware
|
|
2007
|
+
* decoding (timestamps, arrays, etc.) is the caller's responsibility — that
|
|
2008
|
+
* matches `psql` which prints raw server text.
|
|
2009
|
+
*/
|
|
2760
2010
|
function decodeDataRow(values, fields) {
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2011
|
+
const out = new Array(values.length);
|
|
2012
|
+
for (let i = 0; i < values.length; i++) {
|
|
2013
|
+
const v = values[i];
|
|
2014
|
+
if (v === null) {
|
|
2015
|
+
out[i] = null;
|
|
2016
|
+
continue;
|
|
2017
|
+
}
|
|
2018
|
+
const fmt = fields[i]?.format ?? 0;
|
|
2019
|
+
out[i] = fmt === 1 ? v : v.toString("utf8");
|
|
2020
|
+
}
|
|
2021
|
+
return out;
|
|
2772
2022
|
}
|
|
2773
2023
|
/**
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2024
|
+
* Parse the CommandComplete tag — examples:
|
|
2025
|
+
* "SELECT 17"
|
|
2026
|
+
* "INSERT 0 1" (oid is 0 in modern PG; second number is rowCount)
|
|
2027
|
+
* "UPDATE 3"
|
|
2028
|
+
* "CREATE TABLE" (no rowCount)
|
|
2029
|
+
*
|
|
2030
|
+
* Anything that doesn't match → command = the whole tag, rowCount = null.
|
|
2031
|
+
*/
|
|
2782
2032
|
function parseCommandTag(tag) {
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2033
|
+
const trimmed = tag.trim();
|
|
2034
|
+
const insertMatch = /^INSERT (\d+) (\d+)$/.exec(trimmed);
|
|
2035
|
+
if (insertMatch) return {
|
|
2036
|
+
command: "INSERT",
|
|
2037
|
+
oid: parseInt(insertMatch[1], 10),
|
|
2038
|
+
rowCount: parseInt(insertMatch[2], 10)
|
|
2039
|
+
};
|
|
2040
|
+
const m = /^([A-Z][A-Z ]*?)(?: (\d+))?$/.exec(trimmed);
|
|
2041
|
+
if (!m) return {
|
|
2042
|
+
command: trimmed,
|
|
2043
|
+
rowCount: null,
|
|
2044
|
+
oid: null
|
|
2045
|
+
};
|
|
2046
|
+
return {
|
|
2047
|
+
command: m[1],
|
|
2048
|
+
rowCount: m[2] !== void 0 ? parseInt(m[2], 10) : null,
|
|
2049
|
+
oid: null
|
|
2050
|
+
};
|
|
2801
2051
|
}
|
|
2802
2052
|
/**
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
return JSON.stringify(v);
|
|
2821
|
-
}
|
|
2822
|
-
catch {
|
|
2823
|
-
return "";
|
|
2824
|
-
}
|
|
2825
|
-
});
|
|
2053
|
+
* Encode JS values into the (Buffer | string | null)[] format that
|
|
2054
|
+
* {@link Bind} accepts. Text-format only — server coerces. Matches psql's
|
|
2055
|
+
* default `\bind` behaviour.
|
|
2056
|
+
*/
|
|
2057
|
+
function encodeParams(values) {
|
|
2058
|
+
return values.map((v) => {
|
|
2059
|
+
if (v === null || v === void 0) return null;
|
|
2060
|
+
if (Buffer.isBuffer(v)) return v;
|
|
2061
|
+
if (typeof v === "string") return v;
|
|
2062
|
+
if (typeof v === "boolean") return v ? "t" : "f";
|
|
2063
|
+
if (typeof v === "number" || typeof v === "bigint") return v.toString();
|
|
2064
|
+
try {
|
|
2065
|
+
return JSON.stringify(v);
|
|
2066
|
+
} catch {
|
|
2067
|
+
return "";
|
|
2068
|
+
}
|
|
2069
|
+
});
|
|
2826
2070
|
}
|
|
2827
2071
|
/**
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2072
|
+
* Coerce arbitrary rejection values to a thrown `Error`.
|
|
2073
|
+
*
|
|
2074
|
+
* For our `ConnectError` shape (`{ severity, code, message, … }`), the
|
|
2075
|
+
* resulting Error preserves every enumerable field of the source object as
|
|
2076
|
+
* own properties — so callers can still read `.code`, `.severity`, `.hint`,
|
|
2077
|
+
* `.position`, etc. directly off the thrown value while also getting a proper
|
|
2078
|
+
* `Error` instance (so `instanceof Error` works and `.message` / `.stack` are
|
|
2079
|
+
* populated for generic loggers).
|
|
2080
|
+
*/
|
|
2837
2081
|
function asThrowable(v) {
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
continue;
|
|
2851
|
-
err[key] = source[key];
|
|
2852
|
-
}
|
|
2853
|
-
err.cause = v;
|
|
2854
|
-
return err;
|
|
2855
|
-
}
|
|
2856
|
-
return new Error(String(v));
|
|
2082
|
+
if (v instanceof Error) return v;
|
|
2083
|
+
if (typeof v === "object" && v !== null && "message" in v && typeof v.message === "string") {
|
|
2084
|
+
const source = v;
|
|
2085
|
+
const err = new Error(source.message);
|
|
2086
|
+
for (const key of Object.keys(source)) {
|
|
2087
|
+
if (key === "message") continue;
|
|
2088
|
+
err[key] = source[key];
|
|
2089
|
+
}
|
|
2090
|
+
err.cause = v;
|
|
2091
|
+
return err;
|
|
2092
|
+
}
|
|
2093
|
+
return new Error(String(v));
|
|
2857
2094
|
}
|
|
2095
|
+
//#endregion
|
|
2096
|
+
export { PgConnection, encodeParams, isUnixSocketHost, keepAliveArgs, sslKeyBitsFromCipher, tlsServername, unixSocketPath };
|