neonctl 2.27.1 → 2.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -3
- package/dist/analytics.js +52 -34
- package/dist/api.js +643 -13
- package/dist/auth.js +50 -44
- package/dist/cli.js +8 -1
- package/dist/commands/auth.js +64 -51
- package/dist/commands/bootstrap.js +115 -157
- package/dist/commands/branches.js +160 -150
- package/dist/commands/bucket.js +183 -146
- package/dist/commands/checkout.js +51 -51
- package/dist/commands/config.js +228 -82
- package/dist/commands/connection_string.js +62 -62
- package/dist/commands/data_api.js +100 -101
- package/dist/commands/databases.js +29 -26
- package/dist/commands/deploy.js +12 -12
- package/dist/commands/dev.js +114 -114
- package/dist/commands/env.js +43 -43
- package/dist/commands/functions.js +101 -104
- package/dist/commands/index.js +27 -25
- package/dist/commands/init.js +23 -22
- package/dist/commands/ip_allow.js +29 -29
- package/dist/commands/link.js +232 -182
- package/dist/commands/neon_auth.js +385 -370
- package/dist/commands/operations.js +11 -11
- package/dist/commands/orgs.js +8 -8
- package/dist/commands/projects.js +103 -101
- package/dist/commands/psql.js +31 -31
- package/dist/commands/roles.js +27 -24
- package/dist/commands/schema_diff.js +25 -26
- package/dist/commands/set_context.js +17 -17
- package/dist/commands/status.js +40 -0
- package/dist/commands/user.js +5 -5
- package/dist/commands/vpc_endpoints.js +50 -50
- package/dist/config.js +7 -7
- package/dist/config_format.js +5 -5
- package/dist/context.js +37 -14
- package/dist/current_branch_fast_path.js +55 -0
- package/dist/dev/env.js +33 -33
- package/dist/dev/functions.js +4 -4
- package/dist/dev/inputs.js +6 -6
- package/dist/dev/runtime.js +25 -25
- package/dist/env.js +14 -14
- package/dist/env_file.js +13 -13
- package/dist/errors.js +68 -5
- package/dist/functions_api.js +10 -10
- package/dist/help.js +15 -15
- package/dist/index.js +110 -107
- package/dist/log.js +2 -2
- package/dist/parameters.gen.js +14 -14
- package/dist/pkg.js +5 -5
- package/dist/psql/cli.js +4 -2
- package/dist/psql/command/cmd_cond.js +61 -61
- package/dist/psql/command/cmd_connect.js +159 -154
- package/dist/psql/command/cmd_copy.js +107 -97
- package/dist/psql/command/cmd_describe.js +368 -363
- package/dist/psql/command/cmd_format.js +276 -263
- package/dist/psql/command/cmd_io.js +269 -263
- package/dist/psql/command/cmd_lo.js +74 -66
- package/dist/psql/command/cmd_meta.js +148 -148
- package/dist/psql/command/cmd_misc.js +17 -17
- package/dist/psql/command/cmd_pipeline.js +142 -135
- package/dist/psql/command/cmd_restrict.js +25 -25
- package/dist/psql/command/cmd_show.js +183 -168
- package/dist/psql/command/dispatch.js +26 -26
- package/dist/psql/command/shared.js +14 -14
- package/dist/psql/complete/filenames.js +16 -16
- package/dist/psql/complete/index.js +4 -4
- package/dist/psql/complete/matcher.js +33 -32
- package/dist/psql/complete/psqlVars.js +173 -173
- package/dist/psql/complete/queries.js +5 -3
- package/dist/psql/complete/rules.js +900 -863
- package/dist/psql/core/common.js +136 -133
- package/dist/psql/core/help.js +343 -343
- package/dist/psql/core/mainloop.js +160 -153
- package/dist/psql/core/prompt.js +126 -123
- package/dist/psql/core/settings.js +111 -111
- package/dist/psql/core/sqlHelp.js +150 -150
- package/dist/psql/core/startup.js +211 -205
- package/dist/psql/core/syncVars.js +14 -14
- package/dist/psql/core/variables.js +24 -24
- package/dist/psql/describe/formatters.js +302 -289
- package/dist/psql/describe/processNamePattern.js +28 -28
- package/dist/psql/describe/queries.js +656 -651
- package/dist/psql/index.js +436 -411
- package/dist/psql/io/history.js +36 -36
- package/dist/psql/io/input.js +15 -15
- package/dist/psql/io/lineEditor/buffer.js +27 -25
- package/dist/psql/io/lineEditor/complete.js +15 -15
- package/dist/psql/io/lineEditor/filename.js +22 -22
- package/dist/psql/io/lineEditor/index.js +65 -62
- package/dist/psql/io/lineEditor/keymap.js +325 -318
- package/dist/psql/io/lineEditor/vt100.js +60 -60
- package/dist/psql/io/pgpass.js +18 -18
- package/dist/psql/io/pgservice.js +14 -14
- package/dist/psql/io/psqlrc.js +46 -46
- package/dist/psql/print/aligned.js +175 -166
- package/dist/psql/print/asciidoc.js +51 -51
- package/dist/psql/print/crosstab.js +34 -31
- package/dist/psql/print/csv.js +25 -22
- package/dist/psql/print/html.js +54 -54
- package/dist/psql/print/json.js +12 -12
- package/dist/psql/print/latex.js +118 -118
- package/dist/psql/print/pager.js +28 -26
- package/dist/psql/print/troff.js +48 -48
- package/dist/psql/print/unaligned.js +15 -14
- package/dist/psql/print/units.js +17 -17
- package/dist/psql/scanner/slash.js +48 -46
- package/dist/psql/scanner/sql.js +88 -84
- package/dist/psql/scanner/stringutils.js +21 -17
- package/dist/psql/types/index.js +7 -7
- package/dist/psql/types/scanner.js +8 -8
- package/dist/psql/wire/connection.js +341 -327
- package/dist/psql/wire/copy.js +7 -7
- package/dist/psql/wire/pipeline.js +26 -24
- package/dist/psql/wire/protocol.js +102 -102
- package/dist/psql/wire/sasl.js +62 -62
- package/dist/psql/wire/tls.js +79 -73
- package/dist/storage_api.js +22 -23
- package/dist/test_utils/fixtures.js +74 -41
- package/dist/test_utils/oauth_server.js +5 -5
- package/dist/utils/api_enums.js +33 -0
- package/dist/utils/branch_notice.js +5 -5
- package/dist/utils/branch_picker.js +26 -26
- package/dist/utils/compute_units.js +4 -4
- package/dist/utils/enrichers.js +28 -16
- package/dist/utils/esbuild.js +28 -28
- package/dist/utils/formats.js +1 -1
- package/dist/utils/middlewares.js +3 -3
- package/dist/utils/package_manager.js +68 -0
- package/dist/utils/point_in_time.js +12 -12
- package/dist/utils/psql.js +30 -30
- package/dist/utils/string.js +2 -2
- package/dist/utils/ui.js +9 -9
- package/dist/utils/zip.js +1 -1
- package/dist/writer.js +17 -17
- package/package.json +10 -12
package/dist/psql/index.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import { defaultRegistry } from
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { applyStartupArgs, parseStartupArgs } from
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import
|
|
14
|
-
import
|
|
1
|
+
import { existsSync, promises as fs } from "node:fs";
|
|
2
|
+
import * as os from "node:os";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { createCondStack } from "./command/cmd_cond.js";
|
|
5
|
+
import { defaultRegistry } from "./command/dispatch.js";
|
|
6
|
+
import { EXIT_BADCONN, EXIT_FAILURE, EXIT_SUCCESS, EXIT_USER, runMainLoop, } from "./core/mainloop.js";
|
|
7
|
+
import { applyEnvOverrides, defaultSettings } from "./core/settings.js";
|
|
8
|
+
import { applyStartupArgs, parseStartupArgs } from "./core/startup.js";
|
|
9
|
+
import { syncConnectionVars } from "./core/syncVars.js";
|
|
10
|
+
import { createVarStore } from "./core/variables.js";
|
|
11
|
+
import { loadPgPass } from "./io/pgpass.js";
|
|
12
|
+
import { loadPgServices } from "./io/pgservice.js";
|
|
13
|
+
import { executeInputString, loadPsqlrc } from "./io/psqlrc.js";
|
|
14
|
+
import { PgConnection } from "./wire/connection.js";
|
|
15
15
|
/**
|
|
16
16
|
* Embedded TypeScript psql entrypoint.
|
|
17
17
|
*
|
|
@@ -27,7 +27,7 @@ export const runPsql = async (argv, stdio = {}) => {
|
|
|
27
27
|
const stdin = stdio.stdin ?? process.stdin;
|
|
28
28
|
const stdout = stdio.stdout ?? process.stdout;
|
|
29
29
|
const stderr = stdio.stderr ?? process.stderr;
|
|
30
|
-
const connectionUri = argv[0] ??
|
|
30
|
+
const connectionUri = argv[0] ?? "";
|
|
31
31
|
// Parse argv[0] in one of three shapes:
|
|
32
32
|
// - URI scheme (`postgres://…` / `postgresql://…`): the URI-partial
|
|
33
33
|
// parser handles authority, query, and `?service=…`.
|
|
@@ -45,10 +45,10 @@ export const runPsql = async (argv, stdio = {}) => {
|
|
|
45
45
|
// ConnectOptions layers.
|
|
46
46
|
let uriPartial = {};
|
|
47
47
|
let uriService;
|
|
48
|
-
if (connectionUri !==
|
|
48
|
+
if (connectionUri !== "" && looksLikeConnectionString(connectionUri)) {
|
|
49
49
|
try {
|
|
50
|
-
if (connectionUri.startsWith(
|
|
51
|
-
connectionUri.startsWith(
|
|
50
|
+
if (connectionUri.startsWith("postgres://") ||
|
|
51
|
+
connectionUri.startsWith("postgresql://")) {
|
|
52
52
|
uriPartial = parseConnectionUriPartial(connectionUri);
|
|
53
53
|
uriService = parseConnectionUriService(connectionUri);
|
|
54
54
|
}
|
|
@@ -58,7 +58,8 @@ export const runPsql = async (argv, stdio = {}) => {
|
|
|
58
58
|
// part of ConnectOptions); pull it out so the layered resolver
|
|
59
59
|
// can look it up.
|
|
60
60
|
const parsed = parseConninfo(connectionUri);
|
|
61
|
-
if (typeof parsed._service ===
|
|
61
|
+
if (typeof parsed._service === "string" &&
|
|
62
|
+
parsed._service.length > 0) {
|
|
62
63
|
uriService = parsed._service;
|
|
63
64
|
}
|
|
64
65
|
delete parsed._service;
|
|
@@ -72,11 +73,11 @@ export const runPsql = async (argv, stdio = {}) => {
|
|
|
72
73
|
}
|
|
73
74
|
// Parse psql args (argv[1..]). argv[0] is the connection URI consumed above.
|
|
74
75
|
const parsed = parseStartupArgs(argv.slice(1));
|
|
75
|
-
if (
|
|
76
|
-
if (parsed.kind ===
|
|
76
|
+
if ("kind" in parsed) {
|
|
77
|
+
if (parsed.kind === "help" || parsed.kind === "version") {
|
|
77
78
|
stdout.write(parsed.message);
|
|
78
|
-
if (!parsed.message.endsWith(
|
|
79
|
-
stdout.write(
|
|
79
|
+
if (!parsed.message.endsWith("\n"))
|
|
80
|
+
stdout.write("\n");
|
|
80
81
|
return EXIT_SUCCESS;
|
|
81
82
|
}
|
|
82
83
|
stderr.write(`psql: error: ${parsed.message}\n`);
|
|
@@ -212,7 +213,7 @@ const runPreActions = async (ctx, preActions, singleTransaction) => {
|
|
|
212
213
|
// count as a "failed switch" for exit-code purposes.
|
|
213
214
|
if (singleTransaction && settings.db) {
|
|
214
215
|
try {
|
|
215
|
-
await settings.db.execSimple(
|
|
216
|
+
await settings.db.execSimple("BEGIN");
|
|
216
217
|
beganTransaction = true;
|
|
217
218
|
}
|
|
218
219
|
catch (err) {
|
|
@@ -224,10 +225,12 @@ const runPreActions = async (ctx, preActions, singleTransaction) => {
|
|
|
224
225
|
for (const action of preActions) {
|
|
225
226
|
if (connectionLost)
|
|
226
227
|
break;
|
|
227
|
-
if (action.kind ===
|
|
228
|
+
if (action.kind === "command") {
|
|
228
229
|
let outcome;
|
|
229
230
|
try {
|
|
230
|
-
outcome = await executeInputString(action.sql, ctx, {
|
|
231
|
+
outcome = await executeInputString(action.sql, ctx, {
|
|
232
|
+
print: true,
|
|
233
|
+
});
|
|
231
234
|
}
|
|
232
235
|
catch (err) {
|
|
233
236
|
// Defensive: executeInputString shouldn't throw, but if a downstream
|
|
@@ -266,7 +269,7 @@ const runPreActions = async (ctx, preActions, singleTransaction) => {
|
|
|
266
269
|
// upstream, which only escalates to a stop when ON_ERROR_STOP fires).
|
|
267
270
|
let contents;
|
|
268
271
|
try {
|
|
269
|
-
contents = await fs.readFile(action.path,
|
|
272
|
+
contents = await fs.readFile(action.path, "utf8");
|
|
270
273
|
}
|
|
271
274
|
catch (err) {
|
|
272
275
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -280,7 +283,9 @@ const runPreActions = async (ctx, preActions, singleTransaction) => {
|
|
|
280
283
|
}
|
|
281
284
|
let outcome;
|
|
282
285
|
try {
|
|
283
|
-
outcome = await executeInputString(contents, ctx, {
|
|
286
|
+
outcome = await executeInputString(contents, ctx, {
|
|
287
|
+
print: true,
|
|
288
|
+
});
|
|
284
289
|
}
|
|
285
290
|
catch (err) {
|
|
286
291
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -310,7 +315,7 @@ const runPreActions = async (ctx, preActions, singleTransaction) => {
|
|
|
310
315
|
// and ON_ERROR_STOP fired, roll back. Otherwise commit — upstream commits
|
|
311
316
|
// even when individual statements failed without ON_ERROR_STOP.
|
|
312
317
|
if (beganTransaction && settings.db && !connectionLost) {
|
|
313
|
-
const closing = earlyStopOnError ?
|
|
318
|
+
const closing = earlyStopOnError ? "ROLLBACK" : "COMMIT";
|
|
314
319
|
try {
|
|
315
320
|
await settings.db.execSimple(closing);
|
|
316
321
|
}
|
|
@@ -328,7 +333,7 @@ const runPreActions = async (ctx, preActions, singleTransaction) => {
|
|
|
328
333
|
return status;
|
|
329
334
|
};
|
|
330
335
|
const writeStartupBanner = (connection, out) => {
|
|
331
|
-
const serverVersion = connection.parameterStatus(
|
|
336
|
+
const serverVersion = connection.parameterStatus("server_version") ?? "unknown";
|
|
332
337
|
// Client identifier. Matches upstream's `psql (18.4, server X.Y)` shape
|
|
333
338
|
// but signals that this is the embedded TS implementation so users can tell
|
|
334
339
|
// when they're on the fallback path.
|
|
@@ -342,7 +347,7 @@ const writeStartupBanner = (connection, out) => {
|
|
|
342
347
|
];
|
|
343
348
|
if (tls.alpn)
|
|
344
349
|
parts.push(`ALPN: ${tls.alpn}`);
|
|
345
|
-
out.write(`SSL connection (${parts.join(
|
|
350
|
+
out.write(`SSL connection (${parts.join(", ")})\n`);
|
|
346
351
|
}
|
|
347
352
|
out.write('Type "help" for help.\n\n');
|
|
348
353
|
};
|
|
@@ -352,45 +357,45 @@ const writeStartupBanner = (connection, out) => {
|
|
|
352
357
|
// recognize them as valid keys so callers don't get a spurious "unknown key"
|
|
353
358
|
// rejection for a libpq-spec key they expect to work.
|
|
354
359
|
const KNOWN_QUERY_KEYS = new Set([
|
|
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
|
-
|
|
360
|
+
"host",
|
|
361
|
+
"hostaddr",
|
|
362
|
+
"port",
|
|
363
|
+
"dbname",
|
|
364
|
+
"user",
|
|
365
|
+
"password",
|
|
366
|
+
"passfile",
|
|
367
|
+
"channel_binding",
|
|
368
|
+
"require_auth",
|
|
369
|
+
"connect_timeout",
|
|
370
|
+
"client_encoding",
|
|
371
|
+
"options",
|
|
372
|
+
"application_name",
|
|
373
|
+
"fallback_application_name",
|
|
374
|
+
"keepalives",
|
|
375
|
+
"keepalives_idle",
|
|
376
|
+
"keepalives_interval",
|
|
377
|
+
"keepalives_count",
|
|
378
|
+
"sslmode",
|
|
379
|
+
"sslnegotiation",
|
|
380
|
+
"sslcompression",
|
|
381
|
+
"sslcert",
|
|
382
|
+
"sslkey",
|
|
383
|
+
"sslcertmode",
|
|
384
|
+
"sslrootcert",
|
|
385
|
+
"sslcrl",
|
|
386
|
+
"sslcrldir",
|
|
387
|
+
"sslkeylogfile",
|
|
388
|
+
"sslsni",
|
|
389
|
+
"requirepeer",
|
|
390
|
+
"ssl_min_protocol_version",
|
|
391
|
+
"ssl_max_protocol_version",
|
|
392
|
+
"krbsrvname",
|
|
393
|
+
"gsslib",
|
|
394
|
+
"gssencmode",
|
|
395
|
+
"service",
|
|
396
|
+
"target_session_attrs",
|
|
397
|
+
"load_balance_hosts",
|
|
398
|
+
"replication",
|
|
394
399
|
]);
|
|
395
400
|
/**
|
|
396
401
|
* Tokenize a postgres connection URI into raw components.
|
|
@@ -405,33 +410,33 @@ const tokenizeConnectionUri = (uri) => {
|
|
|
405
410
|
// Strip scheme. Only postgres:// and postgresql:// are accepted; libpq
|
|
406
411
|
// rejects everything else with a "missing schema" error.
|
|
407
412
|
let rest;
|
|
408
|
-
if (uri.startsWith(
|
|
409
|
-
rest = uri.slice(
|
|
413
|
+
if (uri.startsWith("postgresql://")) {
|
|
414
|
+
rest = uri.slice("postgresql://".length);
|
|
410
415
|
}
|
|
411
|
-
else if (uri.startsWith(
|
|
412
|
-
rest = uri.slice(
|
|
416
|
+
else if (uri.startsWith("postgres://")) {
|
|
417
|
+
rest = uri.slice("postgres://".length);
|
|
413
418
|
}
|
|
414
419
|
else {
|
|
415
420
|
throw new Error(`unsupported scheme in URI: ${uri}`);
|
|
416
421
|
}
|
|
417
422
|
// Split off query string.
|
|
418
|
-
let query =
|
|
419
|
-
const qIdx = rest.indexOf(
|
|
423
|
+
let query = "";
|
|
424
|
+
const qIdx = rest.indexOf("?");
|
|
420
425
|
if (qIdx >= 0) {
|
|
421
426
|
query = rest.slice(qIdx + 1);
|
|
422
427
|
rest = rest.slice(0, qIdx);
|
|
423
428
|
}
|
|
424
429
|
// Split off path (database).
|
|
425
430
|
let database;
|
|
426
|
-
const pIdx = rest.indexOf(
|
|
431
|
+
const pIdx = rest.indexOf("/");
|
|
427
432
|
if (pIdx >= 0) {
|
|
428
433
|
const pathRaw = rest.slice(pIdx + 1);
|
|
429
|
-
database = pathRaw ===
|
|
434
|
+
database = pathRaw === "" ? undefined : decodePercent(pathRaw);
|
|
430
435
|
rest = rest.slice(0, pIdx);
|
|
431
436
|
}
|
|
432
437
|
// What's left is the authority: [userinfo@][host[:port]]
|
|
433
438
|
let userinfo;
|
|
434
|
-
const atIdx = rest.lastIndexOf(
|
|
439
|
+
const atIdx = rest.lastIndexOf("@");
|
|
435
440
|
if (atIdx >= 0) {
|
|
436
441
|
userinfo = rest.slice(0, atIdx);
|
|
437
442
|
rest = rest.slice(atIdx + 1);
|
|
@@ -439,7 +444,7 @@ const tokenizeConnectionUri = (uri) => {
|
|
|
439
444
|
let user;
|
|
440
445
|
let password;
|
|
441
446
|
if (userinfo !== undefined) {
|
|
442
|
-
const colon = userinfo.indexOf(
|
|
447
|
+
const colon = userinfo.indexOf(":");
|
|
443
448
|
if (colon >= 0) {
|
|
444
449
|
user = decodePercent(userinfo.slice(0, colon));
|
|
445
450
|
password = decodePercent(userinfo.slice(colon + 1));
|
|
@@ -490,22 +495,22 @@ const tokenizeConnectionUri = (uri) => {
|
|
|
490
495
|
* treat as no-host)
|
|
491
496
|
*/
|
|
492
497
|
const splitAuthorityTuples = (rest, uri) => {
|
|
493
|
-
if (rest ===
|
|
494
|
-
return [
|
|
498
|
+
if (rest === "")
|
|
499
|
+
return [""];
|
|
495
500
|
const tuples = [];
|
|
496
501
|
let start = 0;
|
|
497
502
|
let i = 0;
|
|
498
503
|
while (i < rest.length) {
|
|
499
504
|
const ch = rest[i];
|
|
500
|
-
if (ch ===
|
|
501
|
-
const closeIdx = rest.indexOf(
|
|
505
|
+
if (ch === "[") {
|
|
506
|
+
const closeIdx = rest.indexOf("]", i);
|
|
502
507
|
if (closeIdx < 0) {
|
|
503
508
|
throw new Error(`missing matching "]" in IPv6 host address: ${uri}`);
|
|
504
509
|
}
|
|
505
510
|
i = closeIdx + 1;
|
|
506
511
|
continue;
|
|
507
512
|
}
|
|
508
|
-
if (ch ===
|
|
513
|
+
if (ch === ",") {
|
|
509
514
|
tuples.push(rest.slice(start, i));
|
|
510
515
|
i += 1;
|
|
511
516
|
start = i;
|
|
@@ -517,26 +522,26 @@ const splitAuthorityTuples = (rest, uri) => {
|
|
|
517
522
|
return tuples;
|
|
518
523
|
};
|
|
519
524
|
const parseAuthorityTuple = (tuple, uri) => {
|
|
520
|
-
if (tuple ===
|
|
525
|
+
if (tuple === "")
|
|
521
526
|
return {};
|
|
522
|
-
if (tuple.startsWith(
|
|
523
|
-
const closeIdx = tuple.indexOf(
|
|
527
|
+
if (tuple.startsWith("[")) {
|
|
528
|
+
const closeIdx = tuple.indexOf("]");
|
|
524
529
|
if (closeIdx < 0) {
|
|
525
530
|
throw new Error(`missing matching "]" in IPv6 host address: ${uri}`);
|
|
526
531
|
}
|
|
527
532
|
const host = tuple.slice(1, closeIdx);
|
|
528
|
-
if (host ===
|
|
533
|
+
if (host === "") {
|
|
529
534
|
throw new Error(`IPv6 host address may not be empty: ${uri}`);
|
|
530
535
|
}
|
|
531
536
|
const after = tuple.slice(closeIdx + 1);
|
|
532
|
-
if (after ===
|
|
537
|
+
if (after === "")
|
|
533
538
|
return { host };
|
|
534
|
-
if (after.startsWith(
|
|
539
|
+
if (after.startsWith(":")) {
|
|
535
540
|
return { host, port: after.slice(1) };
|
|
536
541
|
}
|
|
537
542
|
throw new Error(`unexpected characters after IPv6 host address in URI: ${uri}`);
|
|
538
543
|
}
|
|
539
|
-
const colon = tuple.indexOf(
|
|
544
|
+
const colon = tuple.indexOf(":");
|
|
540
545
|
if (colon >= 0) {
|
|
541
546
|
return {
|
|
542
547
|
host: decodePercent(tuple.slice(0, colon)),
|
|
@@ -547,12 +552,12 @@ const parseAuthorityTuple = (tuple, uri) => {
|
|
|
547
552
|
};
|
|
548
553
|
const parseQuery = (raw) => {
|
|
549
554
|
const out = new Map();
|
|
550
|
-
if (raw ===
|
|
555
|
+
if (raw === "")
|
|
551
556
|
return out;
|
|
552
|
-
for (const segment of raw.split(
|
|
553
|
-
if (segment ===
|
|
557
|
+
for (const segment of raw.split("&")) {
|
|
558
|
+
if (segment === "")
|
|
554
559
|
continue;
|
|
555
|
-
const eq = segment.indexOf(
|
|
560
|
+
const eq = segment.indexOf("=");
|
|
556
561
|
if (eq < 0) {
|
|
557
562
|
// libpq: every query parameter must be `key=value`. Bare keys (no `=`)
|
|
558
563
|
// are rejected. Matches the upstream 001_uri.pl `?zzz` and
|
|
@@ -563,12 +568,12 @@ const parseQuery = (raw) => {
|
|
|
563
568
|
const valueRaw = segment.slice(eq + 1);
|
|
564
569
|
// libpq rejects an extra `=` in either key or value; matches the
|
|
565
570
|
// `?key=key=value` upstream case.
|
|
566
|
-
if (valueRaw.includes(
|
|
571
|
+
if (valueRaw.includes("=")) {
|
|
567
572
|
throw new Error(`extra "=" in query parameter "${decodePercent(keyRaw).trim()}"`);
|
|
568
573
|
}
|
|
569
574
|
const key = decodePercent(keyRaw).trim();
|
|
570
575
|
const value = decodePercent(valueRaw).trim();
|
|
571
|
-
if (key ===
|
|
576
|
+
if (key === "")
|
|
572
577
|
continue;
|
|
573
578
|
if (!KNOWN_QUERY_KEYS.has(key)) {
|
|
574
579
|
throw new Error(`invalid URI query parameter: "${key}"`);
|
|
@@ -594,7 +599,7 @@ const decodePercent = (s) => {
|
|
|
594
599
|
catch {
|
|
595
600
|
throw new Error(`invalid percent-encoded token in URI: ${s}`);
|
|
596
601
|
}
|
|
597
|
-
if (decoded.includes(
|
|
602
|
+
if (decoded.includes("\x00")) {
|
|
598
603
|
throw new Error(`forbidden NUL byte (%00) in URI: ${s}`);
|
|
599
604
|
}
|
|
600
605
|
return decoded;
|
|
@@ -602,11 +607,11 @@ const decodePercent = (s) => {
|
|
|
602
607
|
export const parseConnectionUri = (uri) => {
|
|
603
608
|
const raw = tokenizeConnectionUri(uri);
|
|
604
609
|
// libpq-style: query string can override authority components.
|
|
605
|
-
const queryUser = raw.query.get(
|
|
606
|
-
const queryPassword = raw.query.get(
|
|
607
|
-
const queryPort = raw.query.get(
|
|
608
|
-
const queryDbname = raw.query.get(
|
|
609
|
-
const queryHost = raw.query.get(
|
|
610
|
+
const queryUser = raw.query.get("user");
|
|
611
|
+
const queryPassword = raw.query.get("password");
|
|
612
|
+
const queryPort = raw.query.get("port");
|
|
613
|
+
const queryDbname = raw.query.get("dbname");
|
|
614
|
+
const queryHost = raw.query.get("host");
|
|
610
615
|
// Multi-host: either from the authority (`h1,h2,h3:5434`) or from
|
|
611
616
|
// `?host=h1,h2,h3&port=5432,5433,5434`. Query-string overrides authority
|
|
612
617
|
// (matching libpq: query params take precedence over URI structural
|
|
@@ -621,55 +626,55 @@ export const parseConnectionUri = (uri) => {
|
|
|
621
626
|
});
|
|
622
627
|
// Single-host fallbacks (preserve current behaviour for the `host` / `port`
|
|
623
628
|
// surface — the wire layer prefers `hosts` when set).
|
|
624
|
-
const host = hostsTuples.length > 0 && hostsTuples[0].host !==
|
|
629
|
+
const host = hostsTuples.length > 0 && hostsTuples[0].host !== ""
|
|
625
630
|
? hostsTuples[0].host
|
|
626
|
-
:
|
|
631
|
+
: "localhost";
|
|
627
632
|
const port = hostsTuples.length > 0 ? hostsTuples[0].port : 5432;
|
|
628
|
-
const user = queryUser !== undefined && queryUser !==
|
|
633
|
+
const user = queryUser !== undefined && queryUser !== ""
|
|
629
634
|
? queryUser
|
|
630
|
-
: raw.user !== undefined && raw.user !==
|
|
635
|
+
: raw.user !== undefined && raw.user !== ""
|
|
631
636
|
? raw.user
|
|
632
|
-
: (process.env.USER ??
|
|
637
|
+
: (process.env.USER ?? "");
|
|
633
638
|
const password = queryPassword ?? raw.password;
|
|
634
639
|
const database = queryDbname ?? raw.database ?? user;
|
|
635
|
-
let ssl = normalizeSslMode(raw.query.get(
|
|
636
|
-
const channelBinding = normalizeChannelBinding(raw.query.get(
|
|
640
|
+
let ssl = normalizeSslMode(raw.query.get("sslmode") ?? null);
|
|
641
|
+
const channelBinding = normalizeChannelBinding(raw.query.get("channel_binding") ?? null);
|
|
637
642
|
// GSSAPI is unsupported (no native Kerberos dep); validate+reject require.
|
|
638
|
-
validateGssEncMode(raw.query.get(
|
|
639
|
-
const options = raw.query.get(
|
|
643
|
+
validateGssEncMode(raw.query.get("gssencmode") ?? null);
|
|
644
|
+
const options = raw.query.get("options");
|
|
640
645
|
// Match upstream psql: default `application_name` to `'psql'` so users see
|
|
641
646
|
// the expected value in `pg_stat_activity`. The neonctl-specific identifier
|
|
642
647
|
// is still discoverable via the User-Agent the protocol layer sends.
|
|
643
|
-
const applicationName = raw.query.get(
|
|
644
|
-
const replication = normalizeReplication(raw.query.get(
|
|
645
|
-
const targetSessionAttrs = normalizeTargetSessionAttrs(raw.query.get(
|
|
646
|
-
const loadBalanceHosts = normalizeLoadBalanceHosts(raw.query.get(
|
|
648
|
+
const applicationName = raw.query.get("application_name") ?? "psql";
|
|
649
|
+
const replication = normalizeReplication(raw.query.get("replication") ?? null);
|
|
650
|
+
const targetSessionAttrs = normalizeTargetSessionAttrs(raw.query.get("target_session_attrs") ?? null);
|
|
651
|
+
const loadBalanceHosts = normalizeLoadBalanceHosts(raw.query.get("load_balance_hosts") ?? null);
|
|
647
652
|
// libpq PEM file paths. Empty string is treated as "not set" so a URI
|
|
648
653
|
// like `?sslcert=` doesn't surface as an attempt to load `""` from disk.
|
|
649
|
-
const sslcert = nonEmpty(raw.query.get(
|
|
650
|
-
const sslkey = nonEmpty(raw.query.get(
|
|
651
|
-
const sslcertmode = normalizeSslCertMode(raw.query.get(
|
|
652
|
-
const sslnegotiation = normalizeSslNegotiation(raw.query.get(
|
|
653
|
-
const sslrootcert = nonEmpty(raw.query.get(
|
|
654
|
-
const sslcrl = nonEmpty(raw.query.get(
|
|
655
|
-
const sslcrldir = nonEmpty(raw.query.get(
|
|
656
|
-
const sslkeylogfile = nonEmpty(raw.query.get(
|
|
654
|
+
const sslcert = nonEmpty(raw.query.get("sslcert"));
|
|
655
|
+
const sslkey = nonEmpty(raw.query.get("sslkey"));
|
|
656
|
+
const sslcertmode = normalizeSslCertMode(raw.query.get("sslcertmode") ?? null);
|
|
657
|
+
const sslnegotiation = normalizeSslNegotiation(raw.query.get("sslnegotiation") ?? null);
|
|
658
|
+
const sslrootcert = nonEmpty(raw.query.get("sslrootcert"));
|
|
659
|
+
const sslcrl = nonEmpty(raw.query.get("sslcrl"));
|
|
660
|
+
const sslcrldir = nonEmpty(raw.query.get("sslcrldir"));
|
|
661
|
+
const sslkeylogfile = nonEmpty(raw.query.get("sslkeylogfile"));
|
|
657
662
|
// libpq sslsni / keepalives toggles (0/1) + keepalives_idle (seconds) +
|
|
658
663
|
// requirepeer (OS user, validated but not enforceable in Node).
|
|
659
|
-
const sslsni = parseLibpqBool(nonEmpty(raw.query.get(
|
|
660
|
-
const keepalives = parseLibpqBool(nonEmpty(raw.query.get(
|
|
661
|
-
const keepalivesIdle = parseKeepalivesIdle(nonEmpty(raw.query.get(
|
|
662
|
-
const requirepeer = nonEmpty(raw.query.get(
|
|
664
|
+
const sslsni = parseLibpqBool(nonEmpty(raw.query.get("sslsni")));
|
|
665
|
+
const keepalives = parseLibpqBool(nonEmpty(raw.query.get("keepalives")));
|
|
666
|
+
const keepalivesIdle = parseKeepalivesIdle(nonEmpty(raw.query.get("keepalives_idle")));
|
|
667
|
+
const requirepeer = nonEmpty(raw.query.get("requirepeer"));
|
|
663
668
|
// libpq: `sslrootcert=system` raises the effective sslmode to verify-full.
|
|
664
669
|
// verify-full is the strongest mode, so this only ever raises it.
|
|
665
|
-
if (sslrootcert ===
|
|
666
|
-
ssl =
|
|
670
|
+
if (sslrootcert === "system" && ssl !== "verify-full") {
|
|
671
|
+
ssl = "verify-full";
|
|
667
672
|
}
|
|
668
673
|
// libpq `hostaddr`: a fixed IP that bypasses DNS while `host` still drives
|
|
669
674
|
// TLS SNI / cert verification. Empty string is "not set".
|
|
670
|
-
const hostaddr = nonEmpty(raw.query.get(
|
|
671
|
-
const sslMinProtocolVersion = normalizeTlsProtocolVersion(nonEmpty(raw.query.get(
|
|
672
|
-
const sslMaxProtocolVersion = normalizeTlsProtocolVersion(nonEmpty(raw.query.get(
|
|
675
|
+
const hostaddr = nonEmpty(raw.query.get("hostaddr"));
|
|
676
|
+
const sslMinProtocolVersion = normalizeTlsProtocolVersion(nonEmpty(raw.query.get("ssl_min_protocol_version")), "ssl_min_protocol_version");
|
|
677
|
+
const sslMaxProtocolVersion = normalizeTlsProtocolVersion(nonEmpty(raw.query.get("ssl_max_protocol_version")), "ssl_max_protocol_version");
|
|
673
678
|
assertTlsProtocolRange(sslMinProtocolVersion, sslMaxProtocolVersion);
|
|
674
679
|
assertTlsMaxProtocolSupported(sslMaxProtocolVersion);
|
|
675
680
|
// libpq rejects `sslnegotiation=direct` paired with a weak sslmode. The URI
|
|
@@ -690,7 +695,12 @@ export const parseConnectionUri = (uri) => {
|
|
|
690
695
|
...(hostaddr !== undefined ? { hostaddr } : {}),
|
|
691
696
|
...(replication !== undefined ? { replication } : {}),
|
|
692
697
|
...(hostsTuples.length > 1
|
|
693
|
-
? {
|
|
698
|
+
? {
|
|
699
|
+
hosts: hostsTuples.map((t) => ({
|
|
700
|
+
host: t.host,
|
|
701
|
+
port: t.port,
|
|
702
|
+
})),
|
|
703
|
+
}
|
|
694
704
|
: {}),
|
|
695
705
|
...(targetSessionAttrs !== undefined ? { targetSessionAttrs } : {}),
|
|
696
706
|
...(loadBalanceHosts !== undefined ? { loadBalanceHosts } : {}),
|
|
@@ -705,8 +715,12 @@ export const parseConnectionUri = (uri) => {
|
|
|
705
715
|
...(keepalives !== undefined ? { keepalives } : {}),
|
|
706
716
|
...(keepalivesIdle !== undefined ? { keepalivesIdle } : {}),
|
|
707
717
|
...(requirepeer !== undefined ? { requirepeer } : {}),
|
|
708
|
-
...(sslMinProtocolVersion !== undefined
|
|
709
|
-
|
|
718
|
+
...(sslMinProtocolVersion !== undefined
|
|
719
|
+
? { sslMinProtocolVersion }
|
|
720
|
+
: {}),
|
|
721
|
+
...(sslMaxProtocolVersion !== undefined
|
|
722
|
+
? { sslMaxProtocolVersion }
|
|
723
|
+
: {}),
|
|
710
724
|
};
|
|
711
725
|
};
|
|
712
726
|
/**
|
|
@@ -716,18 +730,18 @@ export const parseConnectionUri = (uri) => {
|
|
|
716
730
|
* unrecognised so the caller falls back to libpq's default (enabled).
|
|
717
731
|
*/
|
|
718
732
|
const parseLibpqBool = (raw) => {
|
|
719
|
-
if (raw === undefined || raw ===
|
|
733
|
+
if (raw === undefined || raw === "")
|
|
720
734
|
return undefined;
|
|
721
735
|
switch (raw.toLowerCase()) {
|
|
722
|
-
case
|
|
723
|
-
case
|
|
724
|
-
case
|
|
725
|
-
case
|
|
736
|
+
case "1":
|
|
737
|
+
case "true":
|
|
738
|
+
case "yes":
|
|
739
|
+
case "on":
|
|
726
740
|
return true;
|
|
727
|
-
case
|
|
728
|
-
case
|
|
729
|
-
case
|
|
730
|
-
case
|
|
741
|
+
case "0":
|
|
742
|
+
case "false":
|
|
743
|
+
case "no":
|
|
744
|
+
case "off":
|
|
731
745
|
return false;
|
|
732
746
|
default:
|
|
733
747
|
return undefined;
|
|
@@ -739,12 +753,12 @@ const parseLibpqBool = (raw) => {
|
|
|
739
753
|
* milliseconds for `socket.setKeepAlive`'s `initialDelay`).
|
|
740
754
|
*/
|
|
741
755
|
const parseKeepalivesIdle = (raw) => {
|
|
742
|
-
if (raw === undefined || raw ===
|
|
756
|
+
if (raw === undefined || raw === "")
|
|
743
757
|
return undefined;
|
|
744
758
|
const n = Number.parseInt(raw, 10);
|
|
745
759
|
return Number.isFinite(n) && n >= 0 ? n : undefined;
|
|
746
760
|
};
|
|
747
|
-
const nonEmpty = (v) => v === undefined || v ===
|
|
761
|
+
const nonEmpty = (v) => v === undefined || v === "" ? undefined : v;
|
|
748
762
|
/**
|
|
749
763
|
* Resolve the final {host, port}[] list for a URI:
|
|
750
764
|
*
|
|
@@ -764,10 +778,10 @@ const computeHostsTuples = (input) => {
|
|
|
764
778
|
// Case A: ?host=… overrides the authority host(s). Port resolution still
|
|
765
779
|
// prefers `?port=` (if supplied), but falls back to the authority port so
|
|
766
780
|
// e.g. `postgres://:12345?host=/path/to/socket` keeps `port=12345`.
|
|
767
|
-
if (queryHost !== undefined && queryHost !==
|
|
768
|
-
const hosts = queryHost.split(
|
|
769
|
-
const portList = queryPort !== undefined && queryPort !==
|
|
770
|
-
? queryPort.split(
|
|
781
|
+
if (queryHost !== undefined && queryHost !== "") {
|
|
782
|
+
const hosts = queryHost.split(",").map((h) => h.trim());
|
|
783
|
+
const portList = queryPort !== undefined && queryPort !== ""
|
|
784
|
+
? queryPort.split(",").map((p) => p.trim())
|
|
771
785
|
: null;
|
|
772
786
|
if (portList !== null &&
|
|
773
787
|
portList.length !== 1 &&
|
|
@@ -790,8 +804,8 @@ const computeHostsTuples = (input) => {
|
|
|
790
804
|
// Case B: authority carried a comma-list (`postgresql://h1,h2:5433/db`).
|
|
791
805
|
// Query-string `?port=` can still broadcast or pair with this list.
|
|
792
806
|
if (rawAuthorityHosts !== undefined && rawAuthorityHosts.length > 0) {
|
|
793
|
-
const portList = queryPort !== undefined && queryPort !==
|
|
794
|
-
? queryPort.split(
|
|
807
|
+
const portList = queryPort !== undefined && queryPort !== ""
|
|
808
|
+
? queryPort.split(",").map((p) => p.trim())
|
|
795
809
|
: null;
|
|
796
810
|
if (portList !== null &&
|
|
797
811
|
portList.length !== 1 &&
|
|
@@ -809,11 +823,11 @@ const computeHostsTuples = (input) => {
|
|
|
809
823
|
}
|
|
810
824
|
// Case C: single-host. Honour `?port=` (single value) if provided.
|
|
811
825
|
const portStr = queryPort ?? rawPort;
|
|
812
|
-
const host = rawHost !== undefined && rawHost !==
|
|
826
|
+
const host = rawHost !== undefined && rawHost !== "" ? rawHost : "";
|
|
813
827
|
return [{ host, port: parsePort(portStr) }];
|
|
814
828
|
};
|
|
815
829
|
const parsePort = (raw) => {
|
|
816
|
-
if (raw === undefined || raw ===
|
|
830
|
+
if (raw === undefined || raw === "")
|
|
817
831
|
return 5432;
|
|
818
832
|
// `Number.parseInt` silently tolerates trailing junk (`parseInt("12345 12")`
|
|
819
833
|
// === 12345), which would let an internal-whitespace value like the upstream
|
|
@@ -862,15 +876,15 @@ export const parseConninfo = (input) => {
|
|
|
862
876
|
break;
|
|
863
877
|
// Parse key: chars up to `=` or whitespace.
|
|
864
878
|
const keyStart = i;
|
|
865
|
-
while (i < n && input[i] !==
|
|
879
|
+
while (i < n && input[i] !== "=" && !/\s/.test(input[i]))
|
|
866
880
|
i += 1;
|
|
867
881
|
const key = input.slice(keyStart, i).toLowerCase();
|
|
868
|
-
if (key ===
|
|
882
|
+
if (key === "")
|
|
869
883
|
break;
|
|
870
884
|
// Skip whitespace before `=`.
|
|
871
885
|
while (i < n && /\s/.test(input[i]))
|
|
872
886
|
i += 1;
|
|
873
|
-
if (i >= n || input[i] !==
|
|
887
|
+
if (i >= n || input[i] !== "=") {
|
|
874
888
|
throw new Error(`missing "=" after "${key}" in conninfo string`);
|
|
875
889
|
}
|
|
876
890
|
i += 1; // consume `=`
|
|
@@ -884,7 +898,7 @@ export const parseConninfo = (input) => {
|
|
|
884
898
|
i += 1; // consume opening quote
|
|
885
899
|
const parts = [];
|
|
886
900
|
while (i < n && input[i] !== "'") {
|
|
887
|
-
if (input[i] ===
|
|
901
|
+
if (input[i] === "\\" && i + 1 < n) {
|
|
888
902
|
parts.push(input[i + 1]);
|
|
889
903
|
i += 2;
|
|
890
904
|
}
|
|
@@ -897,7 +911,7 @@ export const parseConninfo = (input) => {
|
|
|
897
911
|
throw new Error(`unterminated single quote in conninfo string for key "${key}"`);
|
|
898
912
|
}
|
|
899
913
|
i += 1; // consume closing quote
|
|
900
|
-
value = parts.join(
|
|
914
|
+
value = parts.join("");
|
|
901
915
|
}
|
|
902
916
|
else {
|
|
903
917
|
const valStart = i;
|
|
@@ -942,12 +956,12 @@ export const parseConninfo = (input) => {
|
|
|
942
956
|
};
|
|
943
957
|
const applyConninfoPair = (out, key, value) => {
|
|
944
958
|
switch (key) {
|
|
945
|
-
case
|
|
959
|
+
case "host": {
|
|
946
960
|
// Multi-host: `host=h1,h2,h3`. Store the list aside; the post-pass
|
|
947
961
|
// (finalizeConninfo) materialises it into `hosts` + matches up against
|
|
948
962
|
// any `port=p1,p2,p3` list.
|
|
949
|
-
if (value.includes(
|
|
950
|
-
out._hostList = value.split(
|
|
963
|
+
if (value.includes(",")) {
|
|
964
|
+
out._hostList = value.split(",").map((h) => h.trim());
|
|
951
965
|
out.host = out._hostList[0];
|
|
952
966
|
}
|
|
953
967
|
else {
|
|
@@ -956,9 +970,9 @@ const applyConninfoPair = (out, key, value) => {
|
|
|
956
970
|
}
|
|
957
971
|
return;
|
|
958
972
|
}
|
|
959
|
-
case
|
|
960
|
-
if (value.includes(
|
|
961
|
-
out._portList = value.split(
|
|
973
|
+
case "port": {
|
|
974
|
+
if (value.includes(",")) {
|
|
975
|
+
out._portList = value.split(",").map((p) => p.trim());
|
|
962
976
|
// First port still goes into the scalar slot for back-compat.
|
|
963
977
|
out.port = parsePort(out._portList[0]);
|
|
964
978
|
}
|
|
@@ -968,160 +982,161 @@ const applyConninfoPair = (out, key, value) => {
|
|
|
968
982
|
}
|
|
969
983
|
return;
|
|
970
984
|
}
|
|
971
|
-
case
|
|
985
|
+
case "user":
|
|
972
986
|
out.user = value;
|
|
973
987
|
return;
|
|
974
|
-
case
|
|
988
|
+
case "password":
|
|
975
989
|
out.password = value;
|
|
976
990
|
return;
|
|
977
|
-
case
|
|
991
|
+
case "dbname":
|
|
978
992
|
out.database = value;
|
|
979
993
|
return;
|
|
980
|
-
case
|
|
994
|
+
case "application_name":
|
|
981
995
|
out.applicationName = value;
|
|
982
996
|
return;
|
|
983
|
-
case
|
|
997
|
+
case "sslmode":
|
|
984
998
|
out.ssl = normalizeSslMode(value);
|
|
985
999
|
return;
|
|
986
|
-
case
|
|
1000
|
+
case "channel_binding": {
|
|
987
1001
|
const cb = normalizeChannelBinding(value);
|
|
988
1002
|
if (cb !== undefined)
|
|
989
1003
|
out.channelBinding = cb;
|
|
990
1004
|
return;
|
|
991
1005
|
}
|
|
992
|
-
case
|
|
1006
|
+
case "require_auth": {
|
|
993
1007
|
const ra = normalizeRequireAuth(value);
|
|
994
1008
|
if (ra !== undefined)
|
|
995
1009
|
out.requireAuth = ra;
|
|
996
1010
|
return;
|
|
997
1011
|
}
|
|
998
|
-
case
|
|
1012
|
+
case "connect_timeout": {
|
|
999
1013
|
const t = Number.parseInt(value, 10);
|
|
1000
1014
|
if (Number.isFinite(t) && t >= 0) {
|
|
1001
1015
|
out.connectTimeoutMs = t * 1000;
|
|
1002
1016
|
}
|
|
1003
1017
|
return;
|
|
1004
1018
|
}
|
|
1005
|
-
case
|
|
1019
|
+
case "client_encoding":
|
|
1006
1020
|
out.clientEncoding = value;
|
|
1007
1021
|
return;
|
|
1008
|
-
case
|
|
1022
|
+
case "options":
|
|
1009
1023
|
out.options = value;
|
|
1010
1024
|
return;
|
|
1011
|
-
case
|
|
1025
|
+
case "replication": {
|
|
1012
1026
|
const rep = normalizeReplication(value);
|
|
1013
1027
|
if (rep !== undefined)
|
|
1014
1028
|
out.replication = rep;
|
|
1015
1029
|
return;
|
|
1016
1030
|
}
|
|
1017
|
-
case
|
|
1031
|
+
case "target_session_attrs": {
|
|
1018
1032
|
const tsa = normalizeTargetSessionAttrs(value);
|
|
1019
1033
|
if (tsa !== undefined)
|
|
1020
1034
|
out.targetSessionAttrs = tsa;
|
|
1021
1035
|
return;
|
|
1022
1036
|
}
|
|
1023
|
-
case
|
|
1037
|
+
case "load_balance_hosts": {
|
|
1024
1038
|
const lbh = normalizeLoadBalanceHosts(value);
|
|
1025
1039
|
if (lbh !== undefined)
|
|
1026
1040
|
out.loadBalanceHosts = lbh;
|
|
1027
1041
|
return;
|
|
1028
1042
|
}
|
|
1029
|
-
case
|
|
1043
|
+
case "gssencmode":
|
|
1030
1044
|
// Unsupported (no GSSAPI); accept disable/prefer, reject require.
|
|
1031
1045
|
validateGssEncMode(value);
|
|
1032
1046
|
return;
|
|
1033
|
-
case
|
|
1034
|
-
if (value !==
|
|
1047
|
+
case "sslcert":
|
|
1048
|
+
if (value !== "")
|
|
1035
1049
|
out.sslcert = value;
|
|
1036
1050
|
return;
|
|
1037
|
-
case
|
|
1038
|
-
if (value !==
|
|
1051
|
+
case "sslkey":
|
|
1052
|
+
if (value !== "")
|
|
1039
1053
|
out.sslkey = value;
|
|
1040
1054
|
return;
|
|
1041
|
-
case
|
|
1055
|
+
case "sslcertmode": {
|
|
1042
1056
|
const cm = normalizeSslCertMode(value);
|
|
1043
1057
|
if (cm !== undefined)
|
|
1044
1058
|
out.sslcertmode = cm;
|
|
1045
1059
|
return;
|
|
1046
1060
|
}
|
|
1047
|
-
case
|
|
1061
|
+
case "sslnegotiation": {
|
|
1048
1062
|
const sn = normalizeSslNegotiation(value);
|
|
1049
1063
|
if (sn !== undefined)
|
|
1050
1064
|
out.sslnegotiation = sn;
|
|
1051
1065
|
return;
|
|
1052
1066
|
}
|
|
1053
|
-
case
|
|
1054
|
-
if (value !==
|
|
1067
|
+
case "sslrootcert":
|
|
1068
|
+
if (value !== "")
|
|
1055
1069
|
out.sslrootcert = value;
|
|
1056
1070
|
return;
|
|
1057
|
-
case
|
|
1058
|
-
if (value !==
|
|
1071
|
+
case "sslcrl":
|
|
1072
|
+
if (value !== "")
|
|
1059
1073
|
out.sslcrl = value;
|
|
1060
1074
|
return;
|
|
1061
|
-
case
|
|
1062
|
-
if (value !==
|
|
1075
|
+
case "sslcrldir":
|
|
1076
|
+
if (value !== "")
|
|
1063
1077
|
out.sslcrldir = value;
|
|
1064
1078
|
return;
|
|
1065
|
-
case
|
|
1066
|
-
if (value !==
|
|
1079
|
+
case "sslkeylogfile":
|
|
1080
|
+
if (value !== "")
|
|
1067
1081
|
out.sslkeylogfile = value;
|
|
1068
1082
|
return;
|
|
1069
|
-
case
|
|
1070
|
-
if (value !==
|
|
1083
|
+
case "hostaddr":
|
|
1084
|
+
if (value !== "")
|
|
1071
1085
|
out.hostaddr = value;
|
|
1072
1086
|
return;
|
|
1073
|
-
case
|
|
1074
|
-
const v = normalizeTlsProtocolVersion(value ===
|
|
1087
|
+
case "ssl_min_protocol_version": {
|
|
1088
|
+
const v = normalizeTlsProtocolVersion(value === "" ? undefined : value, "ssl_min_protocol_version");
|
|
1075
1089
|
if (v !== undefined)
|
|
1076
1090
|
out.sslMinProtocolVersion = v;
|
|
1077
1091
|
return;
|
|
1078
1092
|
}
|
|
1079
|
-
case
|
|
1080
|
-
const v = normalizeTlsProtocolVersion(value ===
|
|
1093
|
+
case "ssl_max_protocol_version": {
|
|
1094
|
+
const v = normalizeTlsProtocolVersion(value === "" ? undefined : value, "ssl_max_protocol_version");
|
|
1081
1095
|
if (v !== undefined)
|
|
1082
1096
|
out.sslMaxProtocolVersion = v;
|
|
1083
1097
|
return;
|
|
1084
1098
|
}
|
|
1085
|
-
case
|
|
1099
|
+
case "sslsni": {
|
|
1086
1100
|
const b = parseLibpqBool(value);
|
|
1087
1101
|
if (b !== undefined)
|
|
1088
1102
|
out.sslsni = b;
|
|
1089
1103
|
return;
|
|
1090
1104
|
}
|
|
1091
|
-
case
|
|
1105
|
+
case "keepalives": {
|
|
1092
1106
|
const b = parseLibpqBool(value);
|
|
1093
1107
|
if (b !== undefined)
|
|
1094
1108
|
out.keepalives = b;
|
|
1095
1109
|
return;
|
|
1096
1110
|
}
|
|
1097
|
-
case
|
|
1111
|
+
case "keepalives_idle": {
|
|
1098
1112
|
const n = parseKeepalivesIdle(value);
|
|
1099
1113
|
if (n !== undefined)
|
|
1100
1114
|
out.keepalivesIdle = n;
|
|
1101
1115
|
return;
|
|
1102
1116
|
}
|
|
1103
|
-
case
|
|
1104
|
-
if (value !==
|
|
1117
|
+
case "requirepeer":
|
|
1118
|
+
if (value !== "")
|
|
1105
1119
|
out.requirepeer = value;
|
|
1106
1120
|
return;
|
|
1107
1121
|
// Recognised libpq keys that we don't model — accept silently so we
|
|
1108
1122
|
// don't reject legitimate connection strings. keepalives_interval /
|
|
1109
1123
|
// keepalives_count have no Node net API equivalent (setKeepAlive only
|
|
1110
1124
|
// exposes enable + initial delay) — recognised but cannot be applied.
|
|
1111
|
-
case
|
|
1112
|
-
case
|
|
1113
|
-
case
|
|
1114
|
-
case
|
|
1115
|
-
case
|
|
1116
|
-
case
|
|
1117
|
-
case
|
|
1125
|
+
case "passfile":
|
|
1126
|
+
case "sslcompression":
|
|
1127
|
+
case "krbsrvname":
|
|
1128
|
+
case "gsslib":
|
|
1129
|
+
case "fallback_application_name":
|
|
1130
|
+
case "keepalives_interval":
|
|
1131
|
+
case "keepalives_count":
|
|
1118
1132
|
return;
|
|
1119
|
-
case
|
|
1133
|
+
case "service": {
|
|
1120
1134
|
// Service name is NOT a ConnectOptions field — it's resolved by
|
|
1121
1135
|
// the layered connect resolver in `core/startup.ts`. Stash it on
|
|
1122
1136
|
// a private staging slot so the caller (`runPsql`) can extract it
|
|
1123
1137
|
// alongside the URI-side `?service=…` parser.
|
|
1124
|
-
out._service =
|
|
1138
|
+
out._service =
|
|
1139
|
+
value;
|
|
1125
1140
|
return;
|
|
1126
1141
|
}
|
|
1127
1142
|
default:
|
|
@@ -1134,12 +1149,12 @@ const applyConninfoPair = (out, key, value) => {
|
|
|
1134
1149
|
* `recognized_connection_string()` test.
|
|
1135
1150
|
*/
|
|
1136
1151
|
export const looksLikeConnectionString = (s) => {
|
|
1137
|
-
if (s.startsWith(
|
|
1152
|
+
if (s.startsWith("postgresql://") || s.startsWith("postgres://"))
|
|
1138
1153
|
return true;
|
|
1139
1154
|
// A bare key=value pair (or several) — conninfo. We require the `=` to
|
|
1140
1155
|
// appear before any whitespace so values like "weird name" (a bareword
|
|
1141
1156
|
// database name with a space) don't get misclassified.
|
|
1142
|
-
const eq = s.indexOf(
|
|
1157
|
+
const eq = s.indexOf("=");
|
|
1143
1158
|
if (eq < 0)
|
|
1144
1159
|
return false;
|
|
1145
1160
|
const head = s.slice(0, eq);
|
|
@@ -1152,15 +1167,15 @@ const normalizeSslMode = (raw) => {
|
|
|
1152
1167
|
// downgrade. Mirrors libpq's `invalid sslmode value: "..."` and every
|
|
1153
1168
|
// sibling validator (normalizeChannelBinding, normalizeSslCertMode, …).
|
|
1154
1169
|
if (raw === null)
|
|
1155
|
-
return
|
|
1170
|
+
return "prefer";
|
|
1156
1171
|
const value = raw.toLowerCase();
|
|
1157
1172
|
switch (value) {
|
|
1158
|
-
case
|
|
1159
|
-
case
|
|
1160
|
-
case
|
|
1161
|
-
case
|
|
1162
|
-
case
|
|
1163
|
-
case
|
|
1173
|
+
case "disable":
|
|
1174
|
+
case "allow":
|
|
1175
|
+
case "prefer":
|
|
1176
|
+
case "require":
|
|
1177
|
+
case "verify-ca":
|
|
1178
|
+
case "verify-full":
|
|
1164
1179
|
return value;
|
|
1165
1180
|
default:
|
|
1166
1181
|
throw new Error(`invalid sslmode value: "${raw}"`);
|
|
@@ -1173,14 +1188,14 @@ const normalizeSslMode = (raw) => {
|
|
|
1173
1188
|
* keep the canonical mixed-case spelling Node's `tls` module expects for
|
|
1174
1189
|
* `minVersion` / `maxVersion`.
|
|
1175
1190
|
*/
|
|
1176
|
-
const TLS_PROTOCOL_VERSIONS = [
|
|
1191
|
+
const TLS_PROTOCOL_VERSIONS = ["TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3"];
|
|
1177
1192
|
/**
|
|
1178
1193
|
* Validate and canonicalise a `ssl_{min,max}_protocol_version` value. Returns
|
|
1179
1194
|
* the canonical spelling (`TLSv1.2` etc.) or `undefined` for empty / unset.
|
|
1180
1195
|
* Throws libpq's `invalid <key> value: "<raw>"` wording on a malformed value.
|
|
1181
1196
|
*/
|
|
1182
1197
|
const normalizeTlsProtocolVersion = (raw, key) => {
|
|
1183
|
-
if (raw === undefined || raw ===
|
|
1198
|
+
if (raw === undefined || raw === "")
|
|
1184
1199
|
return undefined;
|
|
1185
1200
|
const match = TLS_PROTOCOL_VERSIONS.find((v) => v.toLowerCase() === raw.toLowerCase());
|
|
1186
1201
|
if (match === undefined) {
|
|
@@ -1201,7 +1216,7 @@ const normalizeTlsProtocolVersion = (raw, key) => {
|
|
|
1201
1216
|
* libpq's ordering.
|
|
1202
1217
|
*/
|
|
1203
1218
|
const assertTlsMaxProtocolSupported = (max) => {
|
|
1204
|
-
if (max ===
|
|
1219
|
+
if (max === "TLSv1" || max === "TLSv1.1") {
|
|
1205
1220
|
throw new Error(`ssl_max_protocol_version "${max}" is not supported by this ` +
|
|
1206
1221
|
`runtime's TLS library — TLS 1.0/1.1 are disabled in Node's OpenSSL; ` +
|
|
1207
1222
|
`the minimum negotiable version is TLSv1.2`);
|
|
@@ -1222,13 +1237,13 @@ const assertTlsProtocolRange = (min, max) => {
|
|
|
1222
1237
|
}
|
|
1223
1238
|
};
|
|
1224
1239
|
const normalizeChannelBinding = (raw) => {
|
|
1225
|
-
if (raw === null || raw ===
|
|
1240
|
+
if (raw === null || raw === "")
|
|
1226
1241
|
return undefined;
|
|
1227
1242
|
const value = raw.toLowerCase();
|
|
1228
1243
|
switch (value) {
|
|
1229
|
-
case
|
|
1230
|
-
case
|
|
1231
|
-
case
|
|
1244
|
+
case "disable":
|
|
1245
|
+
case "prefer":
|
|
1246
|
+
case "require":
|
|
1232
1247
|
return value;
|
|
1233
1248
|
default:
|
|
1234
1249
|
// Mirror libpq's `invalid channel_binding value: "<raw>"`
|
|
@@ -1255,13 +1270,13 @@ const normalizeChannelBinding = (raw) => {
|
|
|
1255
1270
|
* working against Neon.
|
|
1256
1271
|
*/
|
|
1257
1272
|
const validateGssEncMode = (raw) => {
|
|
1258
|
-
if (raw === null || raw ===
|
|
1273
|
+
if (raw === null || raw === "")
|
|
1259
1274
|
return;
|
|
1260
1275
|
const value = raw.toLowerCase();
|
|
1261
|
-
if (value ===
|
|
1276
|
+
if (value === "disable" || value === "prefer")
|
|
1262
1277
|
return;
|
|
1263
|
-
if (value ===
|
|
1264
|
-
throw new Error(
|
|
1278
|
+
if (value === "require") {
|
|
1279
|
+
throw new Error("gssencmode=require is not supported: this client has no GSSAPI support");
|
|
1265
1280
|
}
|
|
1266
1281
|
throw new Error(`invalid gssencmode value: "${raw}"`);
|
|
1267
1282
|
};
|
|
@@ -1272,13 +1287,13 @@ const validateGssEncMode = (raw) => {
|
|
|
1272
1287
|
* `invalid sslcertmode value: "<raw>"` diagnostic.
|
|
1273
1288
|
*/
|
|
1274
1289
|
const normalizeSslCertMode = (raw) => {
|
|
1275
|
-
if (raw === null || raw ===
|
|
1290
|
+
if (raw === null || raw === "")
|
|
1276
1291
|
return undefined;
|
|
1277
1292
|
const value = raw.toLowerCase();
|
|
1278
1293
|
switch (value) {
|
|
1279
|
-
case
|
|
1280
|
-
case
|
|
1281
|
-
case
|
|
1294
|
+
case "disable":
|
|
1295
|
+
case "allow":
|
|
1296
|
+
case "require":
|
|
1282
1297
|
return value;
|
|
1283
1298
|
default:
|
|
1284
1299
|
throw new Error(`invalid sslcertmode value: "${raw}"`);
|
|
@@ -1291,12 +1306,12 @@ const normalizeSslCertMode = (raw) => {
|
|
|
1291
1306
|
* `invalid sslnegotiation value: "<raw>"` diagnostic.
|
|
1292
1307
|
*/
|
|
1293
1308
|
const normalizeSslNegotiation = (raw) => {
|
|
1294
|
-
if (raw === null || raw ===
|
|
1309
|
+
if (raw === null || raw === "")
|
|
1295
1310
|
return undefined;
|
|
1296
1311
|
const value = raw.toLowerCase();
|
|
1297
1312
|
switch (value) {
|
|
1298
|
-
case
|
|
1299
|
-
case
|
|
1313
|
+
case "postgres":
|
|
1314
|
+
case "direct":
|
|
1300
1315
|
return value;
|
|
1301
1316
|
default:
|
|
1302
1317
|
throw new Error(`invalid sslnegotiation value: "${raw}"`);
|
|
@@ -1310,21 +1325,21 @@ const normalizeSslNegotiation = (raw) => {
|
|
|
1310
1325
|
* exact `pqConnectOptions2` wording. No-op unless `sslnegotiation` is `direct`.
|
|
1311
1326
|
*/
|
|
1312
1327
|
const assertSslNegotiationModeCompatible = (ssl, sslnegotiation) => {
|
|
1313
|
-
if (sslnegotiation !==
|
|
1328
|
+
if (sslnegotiation !== "direct")
|
|
1314
1329
|
return;
|
|
1315
|
-
if (ssl ===
|
|
1330
|
+
if (ssl === "require" || ssl === "verify-ca" || ssl === "verify-full") {
|
|
1316
1331
|
return;
|
|
1317
1332
|
}
|
|
1318
1333
|
throw new Error(`weak sslmode "${ssl}" may not be used with sslnegotiation=direct`);
|
|
1319
1334
|
};
|
|
1320
1335
|
const VALID_REQUIRE_AUTH_METHODS = new Set([
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1336
|
+
"password",
|
|
1337
|
+
"md5",
|
|
1338
|
+
"gss",
|
|
1339
|
+
"sspi",
|
|
1340
|
+
"scram-sha-256",
|
|
1341
|
+
"creds",
|
|
1342
|
+
"none",
|
|
1328
1343
|
]);
|
|
1329
1344
|
/**
|
|
1330
1345
|
* Parse libpq's `require_auth` value: a comma-separated list of method
|
|
@@ -1336,10 +1351,10 @@ const VALID_REQUIRE_AUTH_METHODS = new Set([
|
|
|
1336
1351
|
* outer `psql: error: ...` channel.
|
|
1337
1352
|
*/
|
|
1338
1353
|
const normalizeRequireAuth = (raw) => {
|
|
1339
|
-
if (raw === null || raw ===
|
|
1354
|
+
if (raw === null || raw === "")
|
|
1340
1355
|
return undefined;
|
|
1341
1356
|
const tokens = raw
|
|
1342
|
-
.split(
|
|
1357
|
+
.split(",")
|
|
1343
1358
|
.map((s) => s.trim())
|
|
1344
1359
|
.filter((s) => s.length > 0);
|
|
1345
1360
|
if (tokens.length === 0)
|
|
@@ -1347,7 +1362,7 @@ const normalizeRequireAuth = (raw) => {
|
|
|
1347
1362
|
const methods = new Set();
|
|
1348
1363
|
let polarity = null;
|
|
1349
1364
|
for (const token of tokens) {
|
|
1350
|
-
const isNeg = token.startsWith(
|
|
1365
|
+
const isNeg = token.startsWith("!");
|
|
1351
1366
|
const name = (isNeg ? token.slice(1) : token).toLowerCase();
|
|
1352
1367
|
if (!VALID_REQUIRE_AUTH_METHODS.has(name)) {
|
|
1353
1368
|
throw new Error(`invalid require_auth method: "${token}"`);
|
|
@@ -1358,7 +1373,7 @@ const normalizeRequireAuth = (raw) => {
|
|
|
1358
1373
|
else if (polarity !== isNeg) {
|
|
1359
1374
|
// libpq wording: "negative require_auth method ... cannot be mixed
|
|
1360
1375
|
// with non-negative methods". We use a slightly shorter form here.
|
|
1361
|
-
throw new Error(
|
|
1376
|
+
throw new Error("require_auth methods cannot mix positive and negative entries");
|
|
1362
1377
|
}
|
|
1363
1378
|
methods.add(name);
|
|
1364
1379
|
}
|
|
@@ -1372,16 +1387,16 @@ const normalizeRequireAuth = (raw) => {
|
|
|
1372
1387
|
* applies. Throws on unrecognised values, matching libpq behaviour.
|
|
1373
1388
|
*/
|
|
1374
1389
|
const normalizeTargetSessionAttrs = (raw) => {
|
|
1375
|
-
if (raw === null || raw ===
|
|
1390
|
+
if (raw === null || raw === "")
|
|
1376
1391
|
return undefined;
|
|
1377
1392
|
const value = raw.toLowerCase();
|
|
1378
1393
|
switch (value) {
|
|
1379
|
-
case
|
|
1380
|
-
case
|
|
1381
|
-
case
|
|
1382
|
-
case
|
|
1383
|
-
case
|
|
1384
|
-
case
|
|
1394
|
+
case "any":
|
|
1395
|
+
case "read-write":
|
|
1396
|
+
case "read-only":
|
|
1397
|
+
case "primary":
|
|
1398
|
+
case "standby":
|
|
1399
|
+
case "prefer-standby":
|
|
1385
1400
|
return value;
|
|
1386
1401
|
default:
|
|
1387
1402
|
throw new Error(`invalid value for "target_session_attrs": "${raw}"`);
|
|
@@ -1396,10 +1411,10 @@ const normalizeTargetSessionAttrs = (raw) => {
|
|
|
1396
1411
|
* default ('disable') applies.
|
|
1397
1412
|
*/
|
|
1398
1413
|
const normalizeLoadBalanceHosts = (raw) => {
|
|
1399
|
-
if (raw === null || raw ===
|
|
1414
|
+
if (raw === null || raw === "")
|
|
1400
1415
|
return undefined;
|
|
1401
1416
|
const value = raw.toLowerCase();
|
|
1402
|
-
if (value ===
|
|
1417
|
+
if (value === "disable" || value === "random")
|
|
1403
1418
|
return value;
|
|
1404
1419
|
throw new Error(`invalid value for "load_balance_hosts": "${raw}"`);
|
|
1405
1420
|
};
|
|
@@ -1415,15 +1430,21 @@ const normalizeLoadBalanceHosts = (raw) => {
|
|
|
1415
1430
|
* silently sending an unexpected startup-message parameter).
|
|
1416
1431
|
*/
|
|
1417
1432
|
const normalizeReplication = (raw) => {
|
|
1418
|
-
if (raw === null || raw ===
|
|
1433
|
+
if (raw === null || raw === "")
|
|
1419
1434
|
return undefined;
|
|
1420
1435
|
const value = raw.toLowerCase();
|
|
1421
|
-
if (value ===
|
|
1422
|
-
return
|
|
1423
|
-
if (value ===
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1436
|
+
if (value === "database")
|
|
1437
|
+
return "database";
|
|
1438
|
+
if (value === "true" ||
|
|
1439
|
+
value === "on" ||
|
|
1440
|
+
value === "yes" ||
|
|
1441
|
+
value === "1") {
|
|
1442
|
+
return "true";
|
|
1443
|
+
}
|
|
1444
|
+
if (value === "false" ||
|
|
1445
|
+
value === "off" ||
|
|
1446
|
+
value === "no" ||
|
|
1447
|
+
value === "0") {
|
|
1427
1448
|
return undefined;
|
|
1428
1449
|
}
|
|
1429
1450
|
throw new Error(`invalid value for "replication": "${raw}"`);
|
|
@@ -1455,8 +1476,8 @@ const normalizeReplication = (raw) => {
|
|
|
1455
1476
|
*/
|
|
1456
1477
|
export const parseConnectionUriService = (uri) => {
|
|
1457
1478
|
const raw = tokenizeConnectionUri(uri);
|
|
1458
|
-
const value = raw.query.get(
|
|
1459
|
-
return value === undefined || value ===
|
|
1479
|
+
const value = raw.query.get("service");
|
|
1480
|
+
return value === undefined || value === "" ? undefined : value;
|
|
1460
1481
|
};
|
|
1461
1482
|
/**
|
|
1462
1483
|
* Parse a URI into a `Partial<ConnectOptions>` containing only the fields
|
|
@@ -1475,11 +1496,11 @@ export const parseConnectionUriService = (uri) => {
|
|
|
1475
1496
|
*/
|
|
1476
1497
|
export const parseConnectionUriPartial = (uri) => {
|
|
1477
1498
|
const raw = tokenizeConnectionUri(uri);
|
|
1478
|
-
const queryUser = raw.query.get(
|
|
1479
|
-
const queryPassword = raw.query.get(
|
|
1480
|
-
const queryPort = raw.query.get(
|
|
1481
|
-
const queryDbname = raw.query.get(
|
|
1482
|
-
const queryHost = raw.query.get(
|
|
1499
|
+
const queryUser = raw.query.get("user");
|
|
1500
|
+
const queryPassword = raw.query.get("password");
|
|
1501
|
+
const queryPort = raw.query.get("port");
|
|
1502
|
+
const queryDbname = raw.query.get("dbname");
|
|
1503
|
+
const queryHost = raw.query.get("host");
|
|
1483
1504
|
// Multi-host: same resolution as the full parser, but we treat its absence
|
|
1484
1505
|
// as "URI didn't say anything about hosts" rather than synthesising a
|
|
1485
1506
|
// localhost default.
|
|
@@ -1497,26 +1518,30 @@ export const parseConnectionUriPartial = (uri) => {
|
|
|
1497
1518
|
// about a port" so the service-file's port wins when we layer this
|
|
1498
1519
|
// partial above the service layer. Mirrors libpq's behaviour for
|
|
1499
1520
|
// `006_service.pl`'s `postgres:///?service=…` cases.
|
|
1500
|
-
const portInUri = (raw.port !== undefined && raw.port !==
|
|
1501
|
-
(queryPort !== undefined && queryPort !==
|
|
1502
|
-
(raw.hosts?.some((t) => t.port !== undefined && t.port !==
|
|
1521
|
+
const portInUri = (raw.port !== undefined && raw.port !== "") ||
|
|
1522
|
+
(queryPort !== undefined && queryPort !== "") ||
|
|
1523
|
+
(raw.hosts?.some((t) => t.port !== undefined && t.port !== "") ??
|
|
1524
|
+
false);
|
|
1503
1525
|
const out = {};
|
|
1504
1526
|
if (hostsTuples.length > 0) {
|
|
1505
1527
|
// First tuple drives the single-host surface; the full multi-host list
|
|
1506
1528
|
// is included only when the URI specified more than one. An empty-host
|
|
1507
1529
|
// tuple (e.g. `postgres://:12345/`) means "explicit port, no host" —
|
|
1508
1530
|
// we record the port but leave host to the next layer.
|
|
1509
|
-
if (hostsTuples[0].host !==
|
|
1531
|
+
if (hostsTuples[0].host !== "")
|
|
1510
1532
|
out.host = hostsTuples[0].host;
|
|
1511
1533
|
if (portInUri && hostsTuples[0].port !== 0)
|
|
1512
1534
|
out.port = hostsTuples[0].port;
|
|
1513
1535
|
if (hostsTuples.length > 1) {
|
|
1514
|
-
out.hosts = hostsTuples.map((t) => ({
|
|
1536
|
+
out.hosts = hostsTuples.map((t) => ({
|
|
1537
|
+
host: t.host,
|
|
1538
|
+
port: t.port,
|
|
1539
|
+
}));
|
|
1515
1540
|
}
|
|
1516
1541
|
}
|
|
1517
|
-
const userExplicit = queryUser !== undefined && queryUser !==
|
|
1542
|
+
const userExplicit = queryUser !== undefined && queryUser !== ""
|
|
1518
1543
|
? queryUser
|
|
1519
|
-
: raw.user !== undefined && raw.user !==
|
|
1544
|
+
: raw.user !== undefined && raw.user !== ""
|
|
1520
1545
|
? raw.user
|
|
1521
1546
|
: undefined;
|
|
1522
1547
|
if (userExplicit !== undefined)
|
|
@@ -1527,87 +1552,87 @@ export const parseConnectionUriPartial = (uri) => {
|
|
|
1527
1552
|
const database = queryDbname ?? raw.database;
|
|
1528
1553
|
if (database !== undefined)
|
|
1529
1554
|
out.database = database;
|
|
1530
|
-
const sslRaw = raw.query.get(
|
|
1531
|
-
if (sslRaw !== undefined && sslRaw !==
|
|
1555
|
+
const sslRaw = raw.query.get("sslmode");
|
|
1556
|
+
if (sslRaw !== undefined && sslRaw !== "") {
|
|
1532
1557
|
out.ssl = normalizeSslMode(sslRaw);
|
|
1533
1558
|
}
|
|
1534
|
-
const cb = normalizeChannelBinding(raw.query.get(
|
|
1559
|
+
const cb = normalizeChannelBinding(raw.query.get("channel_binding") ?? null);
|
|
1535
1560
|
if (cb !== undefined)
|
|
1536
1561
|
out.channelBinding = cb;
|
|
1537
|
-
const ra = normalizeRequireAuth(raw.query.get(
|
|
1562
|
+
const ra = normalizeRequireAuth(raw.query.get("require_auth") ?? null);
|
|
1538
1563
|
if (ra !== undefined)
|
|
1539
1564
|
out.requireAuth = ra;
|
|
1540
|
-
const options = raw.query.get(
|
|
1541
|
-
if (options !== undefined && options !==
|
|
1565
|
+
const options = raw.query.get("options");
|
|
1566
|
+
if (options !== undefined && options !== "")
|
|
1542
1567
|
out.options = options;
|
|
1543
|
-
const appName = raw.query.get(
|
|
1544
|
-
if (appName !== undefined && appName !==
|
|
1568
|
+
const appName = raw.query.get("application_name");
|
|
1569
|
+
if (appName !== undefined && appName !== "")
|
|
1545
1570
|
out.applicationName = appName;
|
|
1546
|
-
const replication = normalizeReplication(raw.query.get(
|
|
1571
|
+
const replication = normalizeReplication(raw.query.get("replication") ?? null);
|
|
1547
1572
|
if (replication !== undefined)
|
|
1548
1573
|
out.replication = replication;
|
|
1549
|
-
const targetSessionAttrs = normalizeTargetSessionAttrs(raw.query.get(
|
|
1574
|
+
const targetSessionAttrs = normalizeTargetSessionAttrs(raw.query.get("target_session_attrs") ?? null);
|
|
1550
1575
|
if (targetSessionAttrs !== undefined) {
|
|
1551
1576
|
out.targetSessionAttrs = targetSessionAttrs;
|
|
1552
1577
|
}
|
|
1553
|
-
const loadBalanceHosts = normalizeLoadBalanceHosts(raw.query.get(
|
|
1578
|
+
const loadBalanceHosts = normalizeLoadBalanceHosts(raw.query.get("load_balance_hosts") ?? null);
|
|
1554
1579
|
if (loadBalanceHosts !== undefined)
|
|
1555
1580
|
out.loadBalanceHosts = loadBalanceHosts;
|
|
1556
|
-
const sslcert = nonEmpty(raw.query.get(
|
|
1581
|
+
const sslcert = nonEmpty(raw.query.get("sslcert"));
|
|
1557
1582
|
if (sslcert !== undefined)
|
|
1558
1583
|
out.sslcert = sslcert;
|
|
1559
|
-
const sslkey = nonEmpty(raw.query.get(
|
|
1584
|
+
const sslkey = nonEmpty(raw.query.get("sslkey"));
|
|
1560
1585
|
if (sslkey !== undefined)
|
|
1561
1586
|
out.sslkey = sslkey;
|
|
1562
|
-
const sslcertmode = normalizeSslCertMode(raw.query.get(
|
|
1587
|
+
const sslcertmode = normalizeSslCertMode(raw.query.get("sslcertmode") ?? null);
|
|
1563
1588
|
if (sslcertmode !== undefined)
|
|
1564
1589
|
out.sslcertmode = sslcertmode;
|
|
1565
|
-
const sslnegotiation = normalizeSslNegotiation(raw.query.get(
|
|
1590
|
+
const sslnegotiation = normalizeSslNegotiation(raw.query.get("sslnegotiation") ?? null);
|
|
1566
1591
|
if (sslnegotiation !== undefined)
|
|
1567
1592
|
out.sslnegotiation = sslnegotiation;
|
|
1568
|
-
const sslrootcert = nonEmpty(raw.query.get(
|
|
1593
|
+
const sslrootcert = nonEmpty(raw.query.get("sslrootcert"));
|
|
1569
1594
|
if (sslrootcert !== undefined)
|
|
1570
1595
|
out.sslrootcert = sslrootcert;
|
|
1571
|
-
const sslcrl = nonEmpty(raw.query.get(
|
|
1596
|
+
const sslcrl = nonEmpty(raw.query.get("sslcrl"));
|
|
1572
1597
|
if (sslcrl !== undefined)
|
|
1573
1598
|
out.sslcrl = sslcrl;
|
|
1574
|
-
const sslcrldir = nonEmpty(raw.query.get(
|
|
1599
|
+
const sslcrldir = nonEmpty(raw.query.get("sslcrldir"));
|
|
1575
1600
|
if (sslcrldir !== undefined)
|
|
1576
1601
|
out.sslcrldir = sslcrldir;
|
|
1577
|
-
const sslkeylogfile = nonEmpty(raw.query.get(
|
|
1602
|
+
const sslkeylogfile = nonEmpty(raw.query.get("sslkeylogfile"));
|
|
1578
1603
|
if (sslkeylogfile !== undefined)
|
|
1579
1604
|
out.sslkeylogfile = sslkeylogfile;
|
|
1580
|
-
const sslsni = parseLibpqBool(nonEmpty(raw.query.get(
|
|
1605
|
+
const sslsni = parseLibpqBool(nonEmpty(raw.query.get("sslsni")));
|
|
1581
1606
|
if (sslsni !== undefined)
|
|
1582
1607
|
out.sslsni = sslsni;
|
|
1583
|
-
const keepalives = parseLibpqBool(nonEmpty(raw.query.get(
|
|
1608
|
+
const keepalives = parseLibpqBool(nonEmpty(raw.query.get("keepalives")));
|
|
1584
1609
|
if (keepalives !== undefined)
|
|
1585
1610
|
out.keepalives = keepalives;
|
|
1586
|
-
const keepalivesIdle = parseKeepalivesIdle(nonEmpty(raw.query.get(
|
|
1611
|
+
const keepalivesIdle = parseKeepalivesIdle(nonEmpty(raw.query.get("keepalives_idle")));
|
|
1587
1612
|
if (keepalivesIdle !== undefined)
|
|
1588
1613
|
out.keepalivesIdle = keepalivesIdle;
|
|
1589
|
-
const requirepeer = nonEmpty(raw.query.get(
|
|
1614
|
+
const requirepeer = nonEmpty(raw.query.get("requirepeer"));
|
|
1590
1615
|
if (requirepeer !== undefined)
|
|
1591
1616
|
out.requirepeer = requirepeer;
|
|
1592
|
-
const hostaddr = nonEmpty(raw.query.get(
|
|
1617
|
+
const hostaddr = nonEmpty(raw.query.get("hostaddr"));
|
|
1593
1618
|
if (hostaddr !== undefined)
|
|
1594
1619
|
out.hostaddr = hostaddr;
|
|
1595
|
-
const sslMin = normalizeTlsProtocolVersion(nonEmpty(raw.query.get(
|
|
1620
|
+
const sslMin = normalizeTlsProtocolVersion(nonEmpty(raw.query.get("ssl_min_protocol_version")), "ssl_min_protocol_version");
|
|
1596
1621
|
if (sslMin !== undefined)
|
|
1597
1622
|
out.sslMinProtocolVersion = sslMin;
|
|
1598
|
-
const sslMax = normalizeTlsProtocolVersion(nonEmpty(raw.query.get(
|
|
1623
|
+
const sslMax = normalizeTlsProtocolVersion(nonEmpty(raw.query.get("ssl_max_protocol_version")), "ssl_max_protocol_version");
|
|
1599
1624
|
if (sslMax !== undefined)
|
|
1600
1625
|
out.sslMaxProtocolVersion = sslMax;
|
|
1601
1626
|
assertTlsProtocolRange(out.sslMinProtocolVersion, out.sslMaxProtocolVersion);
|
|
1602
1627
|
assertTlsMaxProtocolSupported(out.sslMaxProtocolVersion);
|
|
1603
|
-
const connectTimeoutSec = raw.query.get(
|
|
1604
|
-
if (connectTimeoutSec !== undefined && connectTimeoutSec !==
|
|
1628
|
+
const connectTimeoutSec = raw.query.get("connect_timeout");
|
|
1629
|
+
if (connectTimeoutSec !== undefined && connectTimeoutSec !== "") {
|
|
1605
1630
|
const t = Number.parseInt(connectTimeoutSec, 10);
|
|
1606
1631
|
if (Number.isFinite(t) && t >= 0)
|
|
1607
1632
|
out.connectTimeoutMs = t * 1000;
|
|
1608
1633
|
}
|
|
1609
|
-
const clientEncoding = raw.query.get(
|
|
1610
|
-
if (clientEncoding !== undefined && clientEncoding !==
|
|
1634
|
+
const clientEncoding = raw.query.get("client_encoding");
|
|
1635
|
+
if (clientEncoding !== undefined && clientEncoding !== "") {
|
|
1611
1636
|
out.clientEncoding = clientEncoding;
|
|
1612
1637
|
}
|
|
1613
1638
|
return out;
|
|
@@ -1615,25 +1640,25 @@ export const parseConnectionUriPartial = (uri) => {
|
|
|
1615
1640
|
// Field map for PG* env vars. Order in the table is documentation; resolution
|
|
1616
1641
|
// only depends on whether the var is set.
|
|
1617
1642
|
const PG_ENV_FIELD_MAP = {
|
|
1618
|
-
PGHOST:
|
|
1619
|
-
PGHOSTADDR:
|
|
1620
|
-
PGPORT:
|
|
1621
|
-
PGUSER:
|
|
1622
|
-
PGDATABASE:
|
|
1623
|
-
PGPASSWORD:
|
|
1624
|
-
PGAPPNAME:
|
|
1625
|
-
PGOPTIONS:
|
|
1626
|
-
PGCLIENTENCODING:
|
|
1627
|
-
PGSSLMODE:
|
|
1628
|
-
PGSSLROOTCERT:
|
|
1629
|
-
PGSSLCERT:
|
|
1630
|
-
PGSSLKEY:
|
|
1631
|
-
PGSSLCERTMODE:
|
|
1632
|
-
PGSSLNEGOTIATION:
|
|
1633
|
-
PGSSLCRL:
|
|
1634
|
-
PGSSLCRLDIR:
|
|
1635
|
-
PGSSLKEYLOGFILE:
|
|
1636
|
-
PGCHANNELBINDING:
|
|
1643
|
+
PGHOST: "host",
|
|
1644
|
+
PGHOSTADDR: "hostaddr",
|
|
1645
|
+
PGPORT: "port",
|
|
1646
|
+
PGUSER: "user",
|
|
1647
|
+
PGDATABASE: "database",
|
|
1648
|
+
PGPASSWORD: "password",
|
|
1649
|
+
PGAPPNAME: "applicationName",
|
|
1650
|
+
PGOPTIONS: "options",
|
|
1651
|
+
PGCLIENTENCODING: "clientEncoding",
|
|
1652
|
+
PGSSLMODE: "ssl",
|
|
1653
|
+
PGSSLROOTCERT: "sslrootcert",
|
|
1654
|
+
PGSSLCERT: "sslcert",
|
|
1655
|
+
PGSSLKEY: "sslkey",
|
|
1656
|
+
PGSSLCERTMODE: "sslcertmode",
|
|
1657
|
+
PGSSLNEGOTIATION: "sslnegotiation",
|
|
1658
|
+
PGSSLCRL: "sslcrl",
|
|
1659
|
+
PGSSLCRLDIR: "sslcrldir",
|
|
1660
|
+
PGSSLKEYLOGFILE: "sslkeylogfile",
|
|
1661
|
+
PGCHANNELBINDING: "channelBinding",
|
|
1637
1662
|
};
|
|
1638
1663
|
/**
|
|
1639
1664
|
* Resolve the PG* env vars into a `Partial<ConnectOptions>`. Only set keys
|
|
@@ -1660,7 +1685,7 @@ export const envConnectionDefaults = (env) => {
|
|
|
1660
1685
|
const out = {};
|
|
1661
1686
|
const get = (k) => {
|
|
1662
1687
|
const v = env[k];
|
|
1663
|
-
return v !== undefined && v !==
|
|
1688
|
+
return v !== undefined && v !== "" ? v : undefined;
|
|
1664
1689
|
};
|
|
1665
1690
|
for (const [envName, field] of Object.entries(PG_ENV_FIELD_MAP)) {
|
|
1666
1691
|
const value = get(envName);
|
|
@@ -1668,7 +1693,7 @@ export const envConnectionDefaults = (env) => {
|
|
|
1668
1693
|
continue;
|
|
1669
1694
|
applyEnvValue(out, field, value);
|
|
1670
1695
|
}
|
|
1671
|
-
const timeoutRaw = get(
|
|
1696
|
+
const timeoutRaw = get("PGCONNECT_TIMEOUT");
|
|
1672
1697
|
if (timeoutRaw !== undefined) {
|
|
1673
1698
|
const t = Number.parseInt(timeoutRaw, 10);
|
|
1674
1699
|
if (Number.isFinite(t) && t >= 0)
|
|
@@ -1676,75 +1701,75 @@ export const envConnectionDefaults = (env) => {
|
|
|
1676
1701
|
}
|
|
1677
1702
|
// GSSAPI is unsupported; PGGSSENCMODE=require is rejected, disable/prefer
|
|
1678
1703
|
// accepted-and-ignored. Same contract as the URI/conninfo `gssencmode`.
|
|
1679
|
-
validateGssEncMode(get(
|
|
1704
|
+
validateGssEncMode(get("PGGSSENCMODE") ?? null);
|
|
1680
1705
|
return out;
|
|
1681
1706
|
};
|
|
1682
1707
|
const applyEnvValue = (out, field, value) => {
|
|
1683
1708
|
switch (field) {
|
|
1684
|
-
case
|
|
1709
|
+
case "host":
|
|
1685
1710
|
out.host = value;
|
|
1686
1711
|
return;
|
|
1687
|
-
case
|
|
1712
|
+
case "port": {
|
|
1688
1713
|
const p = Number.parseInt(value, 10);
|
|
1689
1714
|
if (Number.isFinite(p) && p > 0 && p <= 65535)
|
|
1690
1715
|
out.port = p;
|
|
1691
1716
|
return;
|
|
1692
1717
|
}
|
|
1693
|
-
case
|
|
1718
|
+
case "user":
|
|
1694
1719
|
out.user = value;
|
|
1695
1720
|
return;
|
|
1696
|
-
case
|
|
1721
|
+
case "database":
|
|
1697
1722
|
out.database = value;
|
|
1698
1723
|
return;
|
|
1699
|
-
case
|
|
1724
|
+
case "password":
|
|
1700
1725
|
out.password = value;
|
|
1701
1726
|
return;
|
|
1702
|
-
case
|
|
1727
|
+
case "applicationName":
|
|
1703
1728
|
out.applicationName = value;
|
|
1704
1729
|
return;
|
|
1705
|
-
case
|
|
1730
|
+
case "options":
|
|
1706
1731
|
out.options = value;
|
|
1707
1732
|
return;
|
|
1708
|
-
case
|
|
1733
|
+
case "clientEncoding":
|
|
1709
1734
|
out.clientEncoding = value;
|
|
1710
1735
|
return;
|
|
1711
|
-
case
|
|
1736
|
+
case "ssl":
|
|
1712
1737
|
out.ssl = normalizeSslMode(value);
|
|
1713
1738
|
return;
|
|
1714
|
-
case
|
|
1739
|
+
case "sslrootcert":
|
|
1715
1740
|
out.sslrootcert = value;
|
|
1716
1741
|
return;
|
|
1717
|
-
case
|
|
1742
|
+
case "sslcert":
|
|
1718
1743
|
out.sslcert = value;
|
|
1719
1744
|
return;
|
|
1720
|
-
case
|
|
1745
|
+
case "sslkey":
|
|
1721
1746
|
out.sslkey = value;
|
|
1722
1747
|
return;
|
|
1723
|
-
case
|
|
1748
|
+
case "sslcertmode": {
|
|
1724
1749
|
const cm = normalizeSslCertMode(value);
|
|
1725
1750
|
if (cm !== undefined)
|
|
1726
1751
|
out.sslcertmode = cm;
|
|
1727
1752
|
return;
|
|
1728
1753
|
}
|
|
1729
|
-
case
|
|
1754
|
+
case "sslnegotiation": {
|
|
1730
1755
|
const sn = normalizeSslNegotiation(value);
|
|
1731
1756
|
if (sn !== undefined)
|
|
1732
1757
|
out.sslnegotiation = sn;
|
|
1733
1758
|
return;
|
|
1734
1759
|
}
|
|
1735
|
-
case
|
|
1760
|
+
case "sslcrl":
|
|
1736
1761
|
out.sslcrl = value;
|
|
1737
1762
|
return;
|
|
1738
|
-
case
|
|
1763
|
+
case "sslcrldir":
|
|
1739
1764
|
out.sslcrldir = value;
|
|
1740
1765
|
return;
|
|
1741
|
-
case
|
|
1766
|
+
case "sslkeylogfile":
|
|
1742
1767
|
out.sslkeylogfile = value;
|
|
1743
1768
|
return;
|
|
1744
|
-
case
|
|
1769
|
+
case "hostaddr":
|
|
1745
1770
|
out.hostaddr = value;
|
|
1746
1771
|
return;
|
|
1747
|
-
case
|
|
1772
|
+
case "channelBinding": {
|
|
1748
1773
|
const cb = normalizeChannelBinding(value);
|
|
1749
1774
|
if (cb !== undefined)
|
|
1750
1775
|
out.channelBinding = cb;
|
|
@@ -1778,12 +1803,12 @@ const applyEnvValue = (out, field, value) => {
|
|
|
1778
1803
|
* surfaces an error (at TLS-load time, in the wire layer).
|
|
1779
1804
|
*/
|
|
1780
1805
|
export const libpqConnectionDefaults = (env) => ({
|
|
1781
|
-
host:
|
|
1806
|
+
host: "localhost",
|
|
1782
1807
|
port: 5432,
|
|
1783
|
-
user: env.USER ??
|
|
1784
|
-
database:
|
|
1785
|
-
ssl:
|
|
1786
|
-
applicationName:
|
|
1808
|
+
user: env.USER ?? "",
|
|
1809
|
+
database: "",
|
|
1810
|
+
ssl: "prefer",
|
|
1811
|
+
applicationName: "psql",
|
|
1787
1812
|
...defaultClientCertDefaults(env),
|
|
1788
1813
|
});
|
|
1789
1814
|
/**
|
|
@@ -1802,13 +1827,13 @@ export const libpqConnectionDefaults = (env) => ({
|
|
|
1802
1827
|
*/
|
|
1803
1828
|
export const defaultClientCertDefaults = (env) => {
|
|
1804
1829
|
const home = env.HOME ?? os.homedir();
|
|
1805
|
-
if (home === undefined || home ===
|
|
1830
|
+
if (home === undefined || home === "")
|
|
1806
1831
|
return {};
|
|
1807
1832
|
const out = {};
|
|
1808
|
-
const certPath = path.join(home,
|
|
1833
|
+
const certPath = path.join(home, ".postgresql", "postgresql.crt");
|
|
1809
1834
|
if (existsSync(certPath))
|
|
1810
1835
|
out.sslcert = certPath;
|
|
1811
|
-
const keyPath = path.join(home,
|
|
1836
|
+
const keyPath = path.join(home, ".postgresql", "postgresql.key");
|
|
1812
1837
|
if (existsSync(keyPath))
|
|
1813
1838
|
out.sslkey = keyPath;
|
|
1814
1839
|
return out;
|
|
@@ -1827,130 +1852,130 @@ export const serviceEntryToConnectOptions = (entry) => {
|
|
|
1827
1852
|
for (const [k, v] of Object.entries(entry)) {
|
|
1828
1853
|
const key = k.toLowerCase();
|
|
1829
1854
|
switch (key) {
|
|
1830
|
-
case
|
|
1831
|
-
if (v !==
|
|
1855
|
+
case "host":
|
|
1856
|
+
if (v !== "")
|
|
1832
1857
|
out.host = v;
|
|
1833
1858
|
break;
|
|
1834
|
-
case
|
|
1859
|
+
case "port": {
|
|
1835
1860
|
const p = Number.parseInt(v, 10);
|
|
1836
1861
|
if (Number.isFinite(p) && p > 0 && p <= 65535)
|
|
1837
1862
|
out.port = p;
|
|
1838
1863
|
break;
|
|
1839
1864
|
}
|
|
1840
|
-
case
|
|
1841
|
-
if (v !==
|
|
1865
|
+
case "user":
|
|
1866
|
+
if (v !== "")
|
|
1842
1867
|
out.user = v;
|
|
1843
1868
|
break;
|
|
1844
|
-
case
|
|
1845
|
-
if (v !==
|
|
1869
|
+
case "dbname":
|
|
1870
|
+
if (v !== "")
|
|
1846
1871
|
out.database = v;
|
|
1847
1872
|
break;
|
|
1848
|
-
case
|
|
1873
|
+
case "password":
|
|
1849
1874
|
out.password = v;
|
|
1850
1875
|
break;
|
|
1851
|
-
case
|
|
1852
|
-
if (v !==
|
|
1876
|
+
case "application_name":
|
|
1877
|
+
if (v !== "")
|
|
1853
1878
|
out.applicationName = v;
|
|
1854
1879
|
break;
|
|
1855
|
-
case
|
|
1856
|
-
if (v !==
|
|
1880
|
+
case "sslmode":
|
|
1881
|
+
if (v !== "")
|
|
1857
1882
|
out.ssl = normalizeSslMode(v);
|
|
1858
1883
|
break;
|
|
1859
|
-
case
|
|
1884
|
+
case "channel_binding": {
|
|
1860
1885
|
const cb = normalizeChannelBinding(v);
|
|
1861
1886
|
if (cb !== undefined)
|
|
1862
1887
|
out.channelBinding = cb;
|
|
1863
1888
|
break;
|
|
1864
1889
|
}
|
|
1865
|
-
case
|
|
1890
|
+
case "require_auth": {
|
|
1866
1891
|
const ra = normalizeRequireAuth(v);
|
|
1867
1892
|
if (ra !== undefined)
|
|
1868
1893
|
out.requireAuth = ra;
|
|
1869
1894
|
break;
|
|
1870
1895
|
}
|
|
1871
|
-
case
|
|
1872
|
-
if (v !==
|
|
1896
|
+
case "options":
|
|
1897
|
+
if (v !== "")
|
|
1873
1898
|
out.options = v;
|
|
1874
1899
|
break;
|
|
1875
|
-
case
|
|
1876
|
-
if (v !==
|
|
1900
|
+
case "client_encoding":
|
|
1901
|
+
if (v !== "")
|
|
1877
1902
|
out.clientEncoding = v;
|
|
1878
1903
|
break;
|
|
1879
|
-
case
|
|
1880
|
-
if (v !==
|
|
1904
|
+
case "sslcert":
|
|
1905
|
+
if (v !== "")
|
|
1881
1906
|
out.sslcert = v;
|
|
1882
1907
|
break;
|
|
1883
|
-
case
|
|
1884
|
-
if (v !==
|
|
1908
|
+
case "sslkey":
|
|
1909
|
+
if (v !== "")
|
|
1885
1910
|
out.sslkey = v;
|
|
1886
1911
|
break;
|
|
1887
|
-
case
|
|
1912
|
+
case "sslcertmode": {
|
|
1888
1913
|
const cm = normalizeSslCertMode(v);
|
|
1889
1914
|
if (cm !== undefined)
|
|
1890
1915
|
out.sslcertmode = cm;
|
|
1891
1916
|
break;
|
|
1892
1917
|
}
|
|
1893
|
-
case
|
|
1918
|
+
case "sslnegotiation": {
|
|
1894
1919
|
const sn = normalizeSslNegotiation(v);
|
|
1895
1920
|
if (sn !== undefined)
|
|
1896
1921
|
out.sslnegotiation = sn;
|
|
1897
1922
|
break;
|
|
1898
1923
|
}
|
|
1899
|
-
case
|
|
1900
|
-
if (v !==
|
|
1924
|
+
case "sslrootcert":
|
|
1925
|
+
if (v !== "")
|
|
1901
1926
|
out.sslrootcert = v;
|
|
1902
1927
|
break;
|
|
1903
|
-
case
|
|
1904
|
-
if (v !==
|
|
1928
|
+
case "sslcrl":
|
|
1929
|
+
if (v !== "")
|
|
1905
1930
|
out.sslcrl = v;
|
|
1906
1931
|
break;
|
|
1907
|
-
case
|
|
1908
|
-
if (v !==
|
|
1932
|
+
case "sslcrldir":
|
|
1933
|
+
if (v !== "")
|
|
1909
1934
|
out.sslcrldir = v;
|
|
1910
1935
|
break;
|
|
1911
|
-
case
|
|
1912
|
-
if (v !==
|
|
1936
|
+
case "sslkeylogfile":
|
|
1937
|
+
if (v !== "")
|
|
1913
1938
|
out.sslkeylogfile = v;
|
|
1914
1939
|
break;
|
|
1915
|
-
case
|
|
1940
|
+
case "sslsni": {
|
|
1916
1941
|
const b = parseLibpqBool(v);
|
|
1917
1942
|
if (b !== undefined)
|
|
1918
1943
|
out.sslsni = b;
|
|
1919
1944
|
break;
|
|
1920
1945
|
}
|
|
1921
|
-
case
|
|
1946
|
+
case "keepalives": {
|
|
1922
1947
|
const b = parseLibpqBool(v);
|
|
1923
1948
|
if (b !== undefined)
|
|
1924
1949
|
out.keepalives = b;
|
|
1925
1950
|
break;
|
|
1926
1951
|
}
|
|
1927
|
-
case
|
|
1952
|
+
case "keepalives_idle": {
|
|
1928
1953
|
const n = parseKeepalivesIdle(v);
|
|
1929
1954
|
if (n !== undefined)
|
|
1930
1955
|
out.keepalivesIdle = n;
|
|
1931
1956
|
break;
|
|
1932
1957
|
}
|
|
1933
|
-
case
|
|
1934
|
-
if (v !==
|
|
1958
|
+
case "requirepeer":
|
|
1959
|
+
if (v !== "")
|
|
1935
1960
|
out.requirepeer = v;
|
|
1936
1961
|
break;
|
|
1937
|
-
case
|
|
1938
|
-
if (v !==
|
|
1962
|
+
case "hostaddr":
|
|
1963
|
+
if (v !== "")
|
|
1939
1964
|
out.hostaddr = v;
|
|
1940
1965
|
break;
|
|
1941
|
-
case
|
|
1942
|
-
const pv = normalizeTlsProtocolVersion(v ===
|
|
1966
|
+
case "ssl_min_protocol_version": {
|
|
1967
|
+
const pv = normalizeTlsProtocolVersion(v === "" ? undefined : v, "ssl_min_protocol_version");
|
|
1943
1968
|
if (pv !== undefined)
|
|
1944
1969
|
out.sslMinProtocolVersion = pv;
|
|
1945
1970
|
break;
|
|
1946
1971
|
}
|
|
1947
|
-
case
|
|
1948
|
-
const pv = normalizeTlsProtocolVersion(v ===
|
|
1972
|
+
case "ssl_max_protocol_version": {
|
|
1973
|
+
const pv = normalizeTlsProtocolVersion(v === "" ? undefined : v, "ssl_max_protocol_version");
|
|
1949
1974
|
if (pv !== undefined)
|
|
1950
1975
|
out.sslMaxProtocolVersion = pv;
|
|
1951
1976
|
break;
|
|
1952
1977
|
}
|
|
1953
|
-
case
|
|
1978
|
+
case "connect_timeout": {
|
|
1954
1979
|
const t = Number.parseInt(v, 10);
|
|
1955
1980
|
if (Number.isFinite(t) && t >= 0)
|
|
1956
1981
|
out.connectTimeoutMs = t * 1000;
|
|
@@ -1987,15 +2012,15 @@ export const mergeConnectOptions = (layers, defaults) => {
|
|
|
1987
2012
|
// libpq: database defaults to the resolved user when no layer set it.
|
|
1988
2013
|
// The default `database: ''` from `libpqConnectionDefaults` is the
|
|
1989
2014
|
// sentinel for "nothing supplied".
|
|
1990
|
-
if (out.database ===
|
|
2015
|
+
if (out.database === "") {
|
|
1991
2016
|
out.database = out.user;
|
|
1992
2017
|
}
|
|
1993
2018
|
// libpq: `sslrootcert=system` raises the effective sslmode to verify-full
|
|
1994
2019
|
// (it makes no sense to trust the public CA store without verifying the
|
|
1995
2020
|
// chain AND the hostname). verify-full is the strongest mode, so this can
|
|
1996
2021
|
// only ever raise — never downgrade — an explicitly requested mode.
|
|
1997
|
-
if (out.sslrootcert ===
|
|
1998
|
-
out.ssl =
|
|
2022
|
+
if (out.sslrootcert === "system" && out.ssl !== "verify-full") {
|
|
2023
|
+
out.ssl = "verify-full";
|
|
1999
2024
|
}
|
|
2000
2025
|
// libpq validates `sslnegotiation=direct` against the FINAL sslmode (after
|
|
2001
2026
|
// any `sslrootcert=system` raise and cross-layer merge), rejecting a weak
|