neon 2.30.1 → 2.31.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/dist/auth.js +2 -4
- package/dist/commands/auth.js +1 -1
- package/dist/commands/bucket.js +7 -1
- package/dist/commands/data_api.js +0 -1
- package/dist/commands/env.js +5 -6
- package/dist/functions_api.js +4 -0
- package/dist/psql/command/cmd_format.js +27 -1
- package/dist/psql/command/cmd_meta.js +0 -1
- package/dist/psql/command/shared.js +2 -2
- package/dist/psql/complete/psqlVars.js +2 -0
- package/dist/psql/core/prompt.js +0 -1
- package/dist/psql/core/settings.js +2 -0
- package/dist/psql/describe/formatters.js +41 -1
- package/dist/psql/describe/queries.js +78 -4
- package/dist/psql/describe/versionGate.js +1 -0
- package/dist/psql/io/lineEditor/index.js +0 -1
- package/dist/psql/print/aligned.js +8 -25
- package/dist/psql/print/asciidoc.js +6 -24
- package/dist/psql/print/csv.js +4 -23
- package/dist/psql/print/html.js +6 -24
- package/dist/psql/print/latex.js +8 -25
- package/dist/psql/print/troff.js +6 -24
- package/dist/psql/print/unaligned.js +4 -24
- package/dist/psql/print/units.js +53 -0
- package/dist/psql/wire/connection.js +0 -1
- package/dist/psql/wire/sasl.js +0 -2
- package/dist/test_utils/fixtures.js +0 -1
- package/package.json +12 -16
package/dist/auth.js
CHANGED
|
@@ -33,8 +33,7 @@ export const refreshToken = async ({ oauthHost, clientId, allowUnsafeTls }, toke
|
|
|
33
33
|
const configuration = await client.discovery(new URL(oauthHost), clientId, { token_endpoint_auth_method: "none" }, client.None(), {
|
|
34
34
|
timeout: SERVER_TIMEOUT,
|
|
35
35
|
execute: allowUnsafeTls
|
|
36
|
-
?
|
|
37
|
-
[client.allowInsecureRequests]
|
|
36
|
+
? [client.allowInsecureRequests]
|
|
38
37
|
: undefined,
|
|
39
38
|
});
|
|
40
39
|
return await client.refreshTokenGrant(configuration, tokenSet.refresh_token);
|
|
@@ -44,8 +43,7 @@ export const auth = async ({ oauthHost, clientId, allowUnsafeTls, }) => {
|
|
|
44
43
|
const configuration = await client.discovery(new URL(oauthHost), clientId, { token_endpoint_auth_method: "none" }, client.None(), {
|
|
45
44
|
timeout: SERVER_TIMEOUT,
|
|
46
45
|
execute: allowUnsafeTls
|
|
47
|
-
?
|
|
48
|
-
[client.allowInsecureRequests]
|
|
46
|
+
? [client.allowInsecureRequests]
|
|
49
47
|
: undefined,
|
|
50
48
|
});
|
|
51
49
|
//
|
package/dist/commands/auth.js
CHANGED
|
@@ -44,7 +44,7 @@ export const authFlow = async ({ configDir, oauthHost, clientId, apiHost, forceA
|
|
|
44
44
|
const preserveCredentials = async (path, credentials, apiClient) => {
|
|
45
45
|
const { data: { id }, } = await apiClient.getCurrentUserInfo();
|
|
46
46
|
const contents = JSON.stringify({
|
|
47
|
-
//
|
|
47
|
+
// Cast to a plain record: we intentionally spread the credentials object.
|
|
48
48
|
...credentials,
|
|
49
49
|
user_id: id,
|
|
50
50
|
});
|
package/dist/commands/bucket.js
CHANGED
|
@@ -354,7 +354,13 @@ const fileToWebStream = (path) => {
|
|
|
354
354
|
return new ReadableStream({
|
|
355
355
|
start(controller) {
|
|
356
356
|
source.on("data", (chunk) => {
|
|
357
|
-
|
|
357
|
+
// `createReadStream` with no encoding always emits Buffers at
|
|
358
|
+
// runtime; @types/node types the `data` chunk as `string | Buffer`,
|
|
359
|
+
// so normalize (a string would only appear if an encoding were set).
|
|
360
|
+
const bytes = typeof chunk === "string"
|
|
361
|
+
? new TextEncoder().encode(chunk)
|
|
362
|
+
: new Uint8Array(chunk);
|
|
363
|
+
controller.enqueue(bytes);
|
|
358
364
|
if ((controller.desiredSize ?? 0) <= 0)
|
|
359
365
|
source.pause();
|
|
360
366
|
});
|
|
@@ -182,7 +182,6 @@ const get = async (props) => {
|
|
|
182
182
|
});
|
|
183
183
|
const { data } = await props.apiClient.getProjectBranchDataApi(props.projectId, branchId, database);
|
|
184
184
|
// Drop available_schemas from json/yaml output (not part of the public surface).
|
|
185
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
186
185
|
const { available_schemas: _ignored, ...publicData } = data;
|
|
187
186
|
// For table output, flatten db_schemas onto the top-level for column rendering.
|
|
188
187
|
const tableRow = {
|
package/dist/commands/env.js
CHANGED
|
@@ -52,17 +52,16 @@ const NEON_VAR_NAMES = Object.values(NEON_ENV_VAR_KEYS).flatMap((group) => Objec
|
|
|
52
52
|
* `NEON_AUTH_*` / `NEON_DATA_API_*` lines instead of leaving credentials for features that
|
|
53
53
|
* aren't enabled.
|
|
54
54
|
*
|
|
55
|
-
* Deliberately **excludes** the storage
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
55
|
+
* Deliberately **excludes** the storage vars Neon projects onto third-party SDK names
|
|
56
|
+
* (`AWS_*`): those collide with credentials a user may set by hand, so `env pull` only ever
|
|
57
|
+
* writes them, never prunes them. The AI Gateway is emitted solely under its Neon-branded
|
|
58
|
+
* vars (`NEON_AI_GATEWAY_*`), which are owned and pruned.
|
|
59
59
|
*/
|
|
60
60
|
const NEON_OWNED_ENV_KEYS = [
|
|
61
61
|
...Object.values(NEON_ENV_VAR_KEYS.postgres),
|
|
62
62
|
...Object.values(NEON_ENV_VAR_KEYS.auth),
|
|
63
63
|
...Object.values(NEON_ENV_VAR_KEYS.dataApi),
|
|
64
|
-
NEON_ENV_VAR_KEYS.aiGateway
|
|
65
|
-
NEON_ENV_VAR_KEYS.aiGateway.neonBaseUrl,
|
|
64
|
+
...Object.values(NEON_ENV_VAR_KEYS.aiGateway),
|
|
66
65
|
];
|
|
67
66
|
export const pull = async (props, opts = {}) => {
|
|
68
67
|
const cwd = props.cwd ?? process.cwd();
|
package/dist/functions_api.js
CHANGED
|
@@ -28,6 +28,10 @@ export const deleteFunction = async (apiClient, projectId, branchId, slug) => {
|
|
|
28
28
|
};
|
|
29
29
|
export const createDeployment = async (apiClient, projectId, branchId, slug, params) => {
|
|
30
30
|
const form = new FormData();
|
|
31
|
+
// TS 5.9 types `Uint8Array` as `Uint8Array<ArrayBufferLike>`, which is not
|
|
32
|
+
// assignable to `BlobPart` (it wants an `ArrayBuffer`-backed view, not a
|
|
33
|
+
// possibly-`SharedArrayBuffer` one). The bundle is always a plain
|
|
34
|
+
// `Uint8Array` over a regular `ArrayBuffer`, so the cast is safe.
|
|
31
35
|
form.append("zip", new Blob([params.zip]), "bundle.zip");
|
|
32
36
|
form.append("runtime", params.runtime);
|
|
33
37
|
if (params.environment)
|
|
@@ -546,10 +546,34 @@ silent = false) => {
|
|
|
546
546
|
return { status: "ok" };
|
|
547
547
|
}
|
|
548
548
|
case "null": {
|
|
549
|
-
|
|
549
|
+
// Upstream guards the assignment with `if (value)`: the bare form
|
|
550
|
+
// (`\pset null`, value === null) prints the current setting WITHOUT
|
|
551
|
+
// resetting it. Only an explicit value (including the empty string,
|
|
552
|
+
// `\pset null ''`) changes nullPrint.
|
|
553
|
+
if (value !== null) {
|
|
554
|
+
topt.nullPrint = value;
|
|
555
|
+
}
|
|
550
556
|
writeOutMaybe(`Null display is "${topt.nullPrint}".\n`);
|
|
551
557
|
return { status: "ok" };
|
|
552
558
|
}
|
|
559
|
+
// `\pset display_true` / `\pset display_false` (PG 19+). Upstream guards
|
|
560
|
+
// the assignment with `if (value)` — the bare form (no value) prints the
|
|
561
|
+
// current setting WITHOUT resetting it, unlike `null`. The default when
|
|
562
|
+
// unset is the literal 't' / 'f'.
|
|
563
|
+
case "display_true": {
|
|
564
|
+
if (value !== null) {
|
|
565
|
+
topt.truePrint = value;
|
|
566
|
+
}
|
|
567
|
+
writeOutMaybe(`Boolean true display is "${topt.truePrint ?? "t"}".\n`);
|
|
568
|
+
return { status: "ok" };
|
|
569
|
+
}
|
|
570
|
+
case "display_false": {
|
|
571
|
+
if (value !== null) {
|
|
572
|
+
topt.falsePrint = value;
|
|
573
|
+
}
|
|
574
|
+
writeOutMaybe(`Boolean false display is "${topt.falsePrint ?? "f"}".\n`);
|
|
575
|
+
return { status: "ok" };
|
|
576
|
+
}
|
|
553
577
|
case "csv_fieldsep": {
|
|
554
578
|
if (value !== null) {
|
|
555
579
|
// Upstream `do_pset` splits the validation in two: length-based
|
|
@@ -751,6 +775,8 @@ const printAllPset = (topt) => {
|
|
|
751
775
|
writeOut(`border ${topt.border}\n`);
|
|
752
776
|
writeOut(`columns ${topt.columns}\n`);
|
|
753
777
|
writeOut(`csv_fieldsep ${psetQuotedString(topt.csvFieldSep)}\n`);
|
|
778
|
+
writeOut(`display_false ${psetQuotedString(topt.falsePrint ?? "f")}\n`);
|
|
779
|
+
writeOut(`display_true ${psetQuotedString(topt.truePrint ?? "t")}\n`);
|
|
754
780
|
writeOut(`expanded ${topt.expanded}\n`);
|
|
755
781
|
writeOut(`fieldsep ${psetQuotedString(topt.fieldSep)}\n`);
|
|
756
782
|
// fieldsep_zero / recordsep_zero are derived: upstream emits "on" iff
|
|
@@ -382,7 +382,6 @@ export const cmdSetenv = {
|
|
|
382
382
|
// each cursor advances exactly once per call.
|
|
383
383
|
const value = ctx.nextArg("normal");
|
|
384
384
|
if (value === null) {
|
|
385
|
-
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
386
385
|
delete process.env[envname];
|
|
387
386
|
}
|
|
388
387
|
else {
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Why a shared file: the WP spec asks for cmd-isolated test factories, not
|
|
10
10
|
* for the command implementations themselves to duplicate one-line
|
|
11
|
-
* primitives. Going through these helpers also keeps the
|
|
12
|
-
*
|
|
11
|
+
* primitives. Going through these helpers also keeps the `noConsole`
|
|
12
|
+
* lint rule satisfied — every write touches `process.stdout` /
|
|
13
13
|
* `process.stderr` directly rather than `console.log` / `console.error`.
|
|
14
14
|
*/
|
|
15
15
|
/** Write to stdout. */
|
package/dist/psql/core/prompt.js
CHANGED
|
@@ -356,7 +356,6 @@ const isConditionalActive = (cond) => {
|
|
|
356
356
|
const visibleWidth = (s) => {
|
|
357
357
|
// Strip CSI escapes (ESC `[` … letter). Conservative — only trims the
|
|
358
358
|
// common ANSI color form psql emits. Newlines reset the count.
|
|
359
|
-
// eslint-disable-next-line no-control-regex
|
|
360
359
|
const stripped = s.replace(/\x1b\[[0-9;]*[A-Za-z]/g, "");
|
|
361
360
|
const lastNl = stripped.lastIndexOf("\n");
|
|
362
361
|
const tail = lastNl === -1 ? stripped : stripped.slice(lastNl + 1);
|
|
@@ -91,6 +91,8 @@ const buildDefaultPrintOpts = () => ({
|
|
|
91
91
|
translateHeader: false,
|
|
92
92
|
translateColumns: null,
|
|
93
93
|
nullPrint: "",
|
|
94
|
+
truePrint: "t",
|
|
95
|
+
falsePrint: "f",
|
|
94
96
|
csvFieldSep: DEFAULT_CSV_FIELD_SEP,
|
|
95
97
|
// Upstream `pset.popt.topt.expanded_header_width_type =
|
|
96
98
|
// PRINT_XHEADER_FULL` — default rendered as the literal "full" in the
|
|
@@ -45,7 +45,7 @@ import { latexLongtablePrinter, latexPrinter } from "../print/latex.js";
|
|
|
45
45
|
import { troffMsPrinter } from "../print/troff.js";
|
|
46
46
|
import { unalignedPrinter } from "../print/unaligned.js";
|
|
47
47
|
import { applyPattern, processSQLNamePattern, } from "./processNamePattern.js";
|
|
48
|
-
import { fetchForeignTableInfo, fetchInheritedBy, fetchInherits, fetchNotNullConstraints, fetchPartitionKey, fetchPartitionOf, fetchPerColumnFdwOptions, fetchPolicies, fetchStatisticsObjects, fetchTableInfo, fetchTablePublications, fetchTableSubscriptions, } from "./queries.js";
|
|
48
|
+
import { fetchExcludedFromPublications, fetchForeignTableInfo, fetchInheritedBy, fetchInherits, fetchNotNullConstraints, fetchPartitionKey, fetchPartitionOf, fetchPerColumnFdwOptions, fetchPolicies, fetchSequencePublications, fetchStatisticsObjects, fetchTableInfo, fetchTablePublications, fetchTableSubscriptions, } from "./queries.js";
|
|
49
49
|
import { PG_14, serverAtLeast } from "./versionGate.js";
|
|
50
50
|
/**
|
|
51
51
|
* Pick the printer for the active output format. Mirrors `pickPrinter`
|
|
@@ -530,6 +530,13 @@ export const describeOneTableDetails = async (conn, oid, schema, name, relkind,
|
|
|
530
530
|
relkind === "f") {
|
|
531
531
|
push(await captureSection((b) => renderPublicationsSection(conn, oid, b)));
|
|
532
532
|
}
|
|
533
|
+
// ----- Excluded from publications (PG 19+ FOR ALL TABLES ... EXCEPT) -----
|
|
534
|
+
if (relkind === "r" ||
|
|
535
|
+
relkind === "p" ||
|
|
536
|
+
relkind === "m" ||
|
|
537
|
+
relkind === "f") {
|
|
538
|
+
push(await captureSection((b) => renderExcludedFromPublicationsSection(conn, oid, b)));
|
|
539
|
+
}
|
|
533
540
|
// ----- Subscriptions (any publishable relkind; permission-denied silent) -----
|
|
534
541
|
if (relkind === "r" ||
|
|
535
542
|
relkind === "p" ||
|
|
@@ -1088,6 +1095,25 @@ const renderPublicationsSection = async (conn, oid, out) => {
|
|
|
1088
1095
|
out.write(` "${cellToString(r[0] ?? "")}"\n`);
|
|
1089
1096
|
}
|
|
1090
1097
|
};
|
|
1098
|
+
/**
|
|
1099
|
+
* Render `Excluded from publications:\n "name"` (one per row) for any
|
|
1100
|
+
* FOR ALL TABLES publication that excludes this relation via an EXCEPT
|
|
1101
|
+
* clause (PG 19+). No-op when the result set is empty, which includes all
|
|
1102
|
+
* pre-PG-19 servers (the query builder returns an empty set there).
|
|
1103
|
+
*/
|
|
1104
|
+
const renderExcludedFromPublicationsSection = async (conn, oid, out) => {
|
|
1105
|
+
const q = fetchExcludedFromPublications({
|
|
1106
|
+
oid,
|
|
1107
|
+
serverVersion: conn.serverVersion,
|
|
1108
|
+
});
|
|
1109
|
+
const rs = await conn.query(q.sql, q.params);
|
|
1110
|
+
if (rs.rows.length === 0)
|
|
1111
|
+
return;
|
|
1112
|
+
out.write("Excluded from publications:\n");
|
|
1113
|
+
for (const r of rs.rows) {
|
|
1114
|
+
out.write(` "${cellToString(r[0] ?? "")}"\n`);
|
|
1115
|
+
}
|
|
1116
|
+
};
|
|
1091
1117
|
/**
|
|
1092
1118
|
* Render `Subscriptions:\n "name"` (one per row). Requires superuser
|
|
1093
1119
|
* access to `pg_subscription` — when the query fails with a permission
|
|
@@ -1207,6 +1233,20 @@ export const describeOneSequence = async (conn, oid, schema, name, out, popt) =>
|
|
|
1207
1233
|
if (ownRs.rows.length > 0) {
|
|
1208
1234
|
footers.push(`Owned by: ${cellToString(ownRs.rows[0][0])}`);
|
|
1209
1235
|
}
|
|
1236
|
+
// PG 19 replicates sequences via FOR ALL SEQUENCES publications; upstream
|
|
1237
|
+
// appends an "Included in publications:" footer listing them (one pub per
|
|
1238
|
+
// indented line). The query builder returns an empty set pre-PG-19.
|
|
1239
|
+
const pubQ = fetchSequencePublications({
|
|
1240
|
+
oid,
|
|
1241
|
+
serverVersion: conn.serverVersion,
|
|
1242
|
+
});
|
|
1243
|
+
const pubRs = await conn.query(pubQ.sql, pubQ.params);
|
|
1244
|
+
if (pubRs.rows.length > 0) {
|
|
1245
|
+
const lines = pubRs.rows
|
|
1246
|
+
.map((r) => ` "${cellToString(r[0] ?? "")}"`)
|
|
1247
|
+
.join("\n");
|
|
1248
|
+
footers.push(`Included in publications:\n${lines}`);
|
|
1249
|
+
}
|
|
1210
1250
|
// Suppress the row-count footer — upstream's sequence detail output is
|
|
1211
1251
|
// a single row with no `(1 row)` line. Pass the Owned-by line as a
|
|
1212
1252
|
// user footer so the printer places it before the trailing blank.
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* branches in the C source collapse into TS conditional concatenation gated
|
|
18
18
|
* on `serverVersion`.
|
|
19
19
|
*/
|
|
20
|
-
import { PG_9_5, PG_9_6, PG_10, PG_11, PG_12, PG_13, PG_14, PG_15, PG_16, PG_17, PG_18, serverAtLeast, serverLess, } from "./versionGate.js";
|
|
20
|
+
import { PG_9_5, PG_9_6, PG_10, PG_11, PG_12, PG_13, PG_14, PG_15, PG_16, PG_17, PG_18, PG_19, serverAtLeast, serverLess, } from "./versionGate.js";
|
|
21
21
|
/* ------------------------------------------------------------------ */
|
|
22
22
|
/* Shared helpers */
|
|
23
23
|
/* ------------------------------------------------------------------ */
|
|
@@ -1665,7 +1665,7 @@ export const listPublications = (opts) => {
|
|
|
1665
1665
|
let sql = 'SELECT pubname AS "Name",\n' +
|
|
1666
1666
|
' pg_catalog.pg_get_userbyid(pubowner) AS "Owner",\n' +
|
|
1667
1667
|
' puballtables AS "All tables"';
|
|
1668
|
-
if (serverAtLeast(serverVersion,
|
|
1668
|
+
if (serverAtLeast(serverVersion, PG_19)) {
|
|
1669
1669
|
sql += ',\n puballsequences AS "All sequences"';
|
|
1670
1670
|
}
|
|
1671
1671
|
sql +=
|
|
@@ -1702,7 +1702,7 @@ export const describePublications = (opts) => {
|
|
|
1702
1702
|
description: "Details of publications",
|
|
1703
1703
|
};
|
|
1704
1704
|
}
|
|
1705
|
-
const hasSeq = serverAtLeast(serverVersion,
|
|
1705
|
+
const hasSeq = serverAtLeast(serverVersion, PG_19);
|
|
1706
1706
|
const hasTrunc = serverAtLeast(serverVersion, PG_11);
|
|
1707
1707
|
const hasGen = serverAtLeast(serverVersion, PG_18);
|
|
1708
1708
|
const hasViaRoot = serverAtLeast(serverVersion, PG_13);
|
|
@@ -1769,9 +1769,24 @@ export const describeSubscriptions = (opts) => {
|
|
|
1769
1769
|
if (serverAtLeast(serverVersion, PG_17)) {
|
|
1770
1770
|
sql += ', subfailover AS "Failover"\n';
|
|
1771
1771
|
}
|
|
1772
|
+
// PG 19 added foreign-server-backed subscriptions and dead-tuple
|
|
1773
|
+
// retention. Upstream surfaces these five columns in `\dRs+`:
|
|
1774
|
+
// the source server name (resolved from subserver), the retention
|
|
1775
|
+
// toggles/limits, and the WAL-receiver timeout (placed after
|
|
1776
|
+
// Conninfo, matching describe.c).
|
|
1777
|
+
if (serverAtLeast(serverVersion, PG_19)) {
|
|
1778
|
+
sql +=
|
|
1779
|
+
', (select srvname from pg_foreign_server where oid=subserver) AS "Server"\n' +
|
|
1780
|
+
', subretaindeadtuples AS "Retain dead tuples"\n' +
|
|
1781
|
+
', submaxretention AS "Max retention duration"\n' +
|
|
1782
|
+
', subretentionactive AS "Retention active"\n';
|
|
1783
|
+
}
|
|
1772
1784
|
sql +=
|
|
1773
1785
|
', subsynccommit AS "Synchronous commit"\n' +
|
|
1774
1786
|
', subconninfo AS "Conninfo"\n';
|
|
1787
|
+
if (serverAtLeast(serverVersion, PG_19)) {
|
|
1788
|
+
sql += ', subwalrcvtimeout AS "Receiver timeout"\n';
|
|
1789
|
+
}
|
|
1775
1790
|
if (serverAtLeast(serverVersion, PG_15)) {
|
|
1776
1791
|
sql += ', subskiplsn AS "Skip LSN"\n';
|
|
1777
1792
|
}
|
|
@@ -2303,12 +2318,19 @@ export const fetchTablePublications = (opts) => {
|
|
|
2303
2318
|
};
|
|
2304
2319
|
}
|
|
2305
2320
|
const hasPubNs = serverAtLeast(serverVersion, PG_15);
|
|
2321
|
+
// PG 19 added publication EXCEPT lists (`pg_publication_rel.prexcept`).
|
|
2322
|
+
// An excepted relation still has a `pg_publication_rel` row, so the
|
|
2323
|
+
// explicit-membership check must skip those rows or the table would be
|
|
2324
|
+
// wrongly reported as "Included in publications". The excepted set is
|
|
2325
|
+
// surfaced separately via `fetchExcludedFromPublications`.
|
|
2326
|
+
const hasExcept = serverAtLeast(serverVersion, PG_19);
|
|
2327
|
+
const exceptGuard = hasExcept ? " AND NOT pr.prexcept" : "";
|
|
2306
2328
|
let sql = "SELECT pub.pubname\n" +
|
|
2307
2329
|
"FROM pg_catalog.pg_publication pub\n" +
|
|
2308
2330
|
"WHERE pub.puballtables\n" +
|
|
2309
2331
|
" OR EXISTS (\n" +
|
|
2310
2332
|
" SELECT 1 FROM pg_catalog.pg_publication_rel pr\n" +
|
|
2311
|
-
` WHERE pr.prpubid = pub.oid AND pr.prrelid = '${oid}'\n` +
|
|
2333
|
+
` WHERE pr.prpubid = pub.oid AND pr.prrelid = '${oid}'${exceptGuard}\n` +
|
|
2312
2334
|
" )";
|
|
2313
2335
|
if (hasPubNs) {
|
|
2314
2336
|
sql +=
|
|
@@ -2345,6 +2367,58 @@ export const fetchTableSubscriptions = (opts) => {
|
|
|
2345
2367
|
"ORDER BY 1;";
|
|
2346
2368
|
return { sql, params: [], description: "Table subscriptions" };
|
|
2347
2369
|
};
|
|
2370
|
+
/**
|
|
2371
|
+
* Publications that exclude this relation via a FOR ALL TABLES ... EXCEPT
|
|
2372
|
+
* clause (PG 19+, `pg_publication_rel.prexcept`). Mirrors the upstream
|
|
2373
|
+
* "Excluded from publications:" footer in `describeOneTableDetails`.
|
|
2374
|
+
* Considers the partition root as well, since an EXCEPT can be declared on
|
|
2375
|
+
* the root of a partitioned table.
|
|
2376
|
+
*
|
|
2377
|
+
* Returns an empty set on servers older than PG 19 (the column and the
|
|
2378
|
+
* feature don't exist), so the caller suppresses the footer without a
|
|
2379
|
+
* separate version branch at the call site.
|
|
2380
|
+
*/
|
|
2381
|
+
export const fetchExcludedFromPublications = (opts) => {
|
|
2382
|
+
const { oid, serverVersion } = opts;
|
|
2383
|
+
if (serverLess(serverVersion, PG_19)) {
|
|
2384
|
+
return {
|
|
2385
|
+
sql: "/* server < 19 does not support publication EXCEPT lists */ SELECT 1 WHERE false;",
|
|
2386
|
+
params: [],
|
|
2387
|
+
description: "Excluded from publications",
|
|
2388
|
+
};
|
|
2389
|
+
}
|
|
2390
|
+
const sql = "SELECT pubname\n" +
|
|
2391
|
+
"FROM pg_catalog.pg_publication p\n" +
|
|
2392
|
+
"JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n" +
|
|
2393
|
+
`WHERE (pr.prrelid = '${oid}' OR pr.prrelid = pg_catalog.pg_partition_root('${oid}'))\n` +
|
|
2394
|
+
"AND pr.prexcept\n" +
|
|
2395
|
+
"ORDER BY 1;";
|
|
2396
|
+
return { sql, params: [], description: "Excluded from publications" };
|
|
2397
|
+
};
|
|
2398
|
+
/**
|
|
2399
|
+
* Publications a sequence belongs to. PG 19 added sequence replication
|
|
2400
|
+
* (`pg_publication.puballsequences`); a sequence is included when a
|
|
2401
|
+
* publication is declared FOR ALL SEQUENCES and the sequence itself is
|
|
2402
|
+
* publishable. Mirrors the "Included in publications:" footer emitted for
|
|
2403
|
+
* sequences in upstream `describeOneTableDetails`.
|
|
2404
|
+
*
|
|
2405
|
+
* Returns an empty set on servers older than PG 19.
|
|
2406
|
+
*/
|
|
2407
|
+
export const fetchSequencePublications = (opts) => {
|
|
2408
|
+
const { oid, serverVersion } = opts;
|
|
2409
|
+
if (serverLess(serverVersion, PG_19)) {
|
|
2410
|
+
return {
|
|
2411
|
+
sql: "/* server < 19 does not support sequence publications */ SELECT 1 WHERE false;",
|
|
2412
|
+
params: [],
|
|
2413
|
+
description: "Sequence publications",
|
|
2414
|
+
};
|
|
2415
|
+
}
|
|
2416
|
+
const sql = "SELECT pubname FROM pg_catalog.pg_publication p\n" +
|
|
2417
|
+
"WHERE p.puballsequences\n" +
|
|
2418
|
+
` AND pg_catalog.pg_relation_is_publishable('${oid}')\n` +
|
|
2419
|
+
"ORDER BY 1;";
|
|
2420
|
+
return { sql, params: [], description: "Sequence publications" };
|
|
2421
|
+
};
|
|
2348
2422
|
/**
|
|
2349
2423
|
* Per-column FDW options for a foreign table. Returns one row per column
|
|
2350
2424
|
* that has at least one `attfdwoptions` entry. Output columns:
|
|
@@ -773,7 +773,6 @@ export const renderSearchLine = (pattern, entry) => {
|
|
|
773
773
|
const stripAnsi = (text) =>
|
|
774
774
|
// Targets the SGR forms we emit (e.g. \x1b[7m, \x1b[27m). Kept narrow on
|
|
775
775
|
// purpose so it doesn't accidentally eat legitimate `[` characters.
|
|
776
|
-
// eslint-disable-next-line no-control-regex
|
|
777
776
|
text.replace(/\x1b\[[0-9;]*m/g, "");
|
|
778
777
|
// ---------------------------------------------------------------------------
|
|
779
778
|
// Display-width helpers (inlined copy of WP-09's tables; kept minimal).
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { boolDisplayOf, renderCellValue } from "./units.js";
|
|
2
2
|
/**
|
|
3
3
|
* Aligned tabular printer (psql's default output mode).
|
|
4
4
|
*
|
|
@@ -354,27 +354,7 @@ const RIGHT_ALIGNED_OIDS = new Set([
|
|
|
354
354
|
1700, // numeric
|
|
355
355
|
]);
|
|
356
356
|
const isRightAlignedField = (oid) => RIGHT_ALIGNED_OIDS.has(oid);
|
|
357
|
-
const renderCell = (cell, nullPrint, numericLocale) =>
|
|
358
|
-
if (cell === null || cell === undefined)
|
|
359
|
-
return nullPrint;
|
|
360
|
-
if (typeof cell === "string") {
|
|
361
|
-
return formatNumericLocale(cell, numericLocale);
|
|
362
|
-
}
|
|
363
|
-
if (typeof cell === "number" || typeof cell === "bigint") {
|
|
364
|
-
return formatNumericLocale(cell.toString(), numericLocale);
|
|
365
|
-
}
|
|
366
|
-
if (typeof cell === "boolean")
|
|
367
|
-
return cell ? "t" : "f";
|
|
368
|
-
if (cell instanceof Date)
|
|
369
|
-
return cell.toISOString();
|
|
370
|
-
if (cell instanceof Uint8Array) {
|
|
371
|
-
let hex = "\\x";
|
|
372
|
-
for (const b of cell)
|
|
373
|
-
hex += b.toString(16).padStart(2, "0");
|
|
374
|
-
return hex;
|
|
375
|
-
}
|
|
376
|
-
return JSON.stringify(cell);
|
|
377
|
-
};
|
|
357
|
+
const renderCell = (cell, nullPrint, numericLocale, dataTypeID, boolDisplay) => renderCellValue(cell, nullPrint, numericLocale, dataTypeID, boolDisplay);
|
|
378
358
|
const ASCII_GLYPHS = {
|
|
379
359
|
hrule: "-",
|
|
380
360
|
vrule: "|",
|
|
@@ -498,10 +478,11 @@ const glyphsFor = (style) => {
|
|
|
498
478
|
export const computeColumnWidths = (rs, topt) => {
|
|
499
479
|
const nullPrint = topt.nullPrint;
|
|
500
480
|
const numericLocale = topt.numericLocale;
|
|
481
|
+
const boolDisplay = boolDisplayOf(topt);
|
|
501
482
|
const widths = rs.fields.map((f) => displayWidth(f.name));
|
|
502
483
|
for (const row of rs.rows) {
|
|
503
484
|
for (let i = 0; i < rs.fields.length; i++) {
|
|
504
|
-
const cellText = renderCell(row[i], nullPrint, numericLocale);
|
|
485
|
+
const cellText = renderCell(row[i], nullPrint, numericLocale, rs.fields[i]?.dataTypeID, boolDisplay);
|
|
505
486
|
for (const line of cellText.split("\n")) {
|
|
506
487
|
const w = displayWidth(line);
|
|
507
488
|
if (w > widths[i])
|
|
@@ -585,7 +566,8 @@ const renderHorizontal = (rs, opts, wrapped) => {
|
|
|
585
566
|
const headers = rs.fields.map((f) => f.name);
|
|
586
567
|
const aligns = rs.fields.map((f) => isRightAlignedField(f.dataTypeID) ? "right" : "left");
|
|
587
568
|
// Pre-render & measure every cell.
|
|
588
|
-
const
|
|
569
|
+
const boolDisplay = boolDisplayOf(topt);
|
|
570
|
+
const cellGrid = rs.rows.map((row) => row.map((cell, i) => formatCell(renderCell(cell, nullPrint, topt.numericLocale, rs.fields[i]?.dataTypeID, boolDisplay))));
|
|
589
571
|
const headerCells = headers.map((h) => formatCell(h));
|
|
590
572
|
const colCount = rs.fields.length;
|
|
591
573
|
const widths = headerCells.map((c) => c.width);
|
|
@@ -1141,7 +1123,8 @@ const renderVertical = (rs, opts) => {
|
|
|
1141
1123
|
// Width of the value column = max value width across all rows.
|
|
1142
1124
|
let valueWidth = 0;
|
|
1143
1125
|
let dmultiline = false;
|
|
1144
|
-
const
|
|
1126
|
+
const boolDisplay = boolDisplayOf(topt);
|
|
1127
|
+
const cellGrid = rs.rows.map((row) => row.map((cell, i) => renderCell(cell, nullPrint, topt.numericLocale, rs.fields[i]?.dataTypeID, boolDisplay)));
|
|
1145
1128
|
for (const row of cellGrid) {
|
|
1146
1129
|
for (const v of row) {
|
|
1147
1130
|
if (v.includes("\n"))
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { boolDisplayOf, renderCellValue } from "./units.js";
|
|
2
2
|
/**
|
|
3
3
|
* AsciiDoc printer.
|
|
4
4
|
*
|
|
@@ -69,7 +69,8 @@ const printFlat = (rs, opts, out) => {
|
|
|
69
69
|
const headers = rs.fields.map((f) => f.name);
|
|
70
70
|
const aligns = rs.fields.map((f) => NUMERIC_OIDS.has(f.dataTypeID) ? "r" : "l");
|
|
71
71
|
const ncols = rs.fields.length;
|
|
72
|
-
const
|
|
72
|
+
const boolDisplay = boolDisplayOf(topt);
|
|
73
|
+
const cells = rs.rows.map((row) => row.map((cell, i) => renderCell(cell, nullPrint, topt.numericLocale, rs.fields[i]?.dataTypeID, boolDisplay)));
|
|
73
74
|
let buf = "";
|
|
74
75
|
if (startTable) {
|
|
75
76
|
// Force a paragraph break (upstream always emits a leading "\n").
|
|
@@ -135,7 +136,8 @@ const printExpanded = (rs, opts, out) => {
|
|
|
135
136
|
const footers = opts.footers ?? topt.footers;
|
|
136
137
|
const headers = rs.fields.map((f) => f.name);
|
|
137
138
|
const aligns = rs.fields.map((f) => NUMERIC_OIDS.has(f.dataTypeID) ? "r" : "l");
|
|
138
|
-
const
|
|
139
|
+
const boolDisplay = boolDisplayOf(topt);
|
|
140
|
+
const cells = rs.rows.map((row) => row.map((cell, i) => renderCell(cell, nullPrint, topt.numericLocale, rs.fields[i]?.dataTypeID, boolDisplay)));
|
|
139
141
|
let buf = "";
|
|
140
142
|
if (startTable) {
|
|
141
143
|
buf += "\n";
|
|
@@ -225,24 +227,4 @@ const escapeAsciidoc = (input) => {
|
|
|
225
227
|
}
|
|
226
228
|
return out;
|
|
227
229
|
};
|
|
228
|
-
const renderCell = (cell, nullPrint, numericLocale) =>
|
|
229
|
-
if (cell === null || cell === undefined)
|
|
230
|
-
return nullPrint;
|
|
231
|
-
if (typeof cell === "string") {
|
|
232
|
-
return formatNumericLocale(cell, numericLocale);
|
|
233
|
-
}
|
|
234
|
-
if (typeof cell === "number" || typeof cell === "bigint") {
|
|
235
|
-
return formatNumericLocale(cell.toString(), numericLocale);
|
|
236
|
-
}
|
|
237
|
-
if (typeof cell === "boolean")
|
|
238
|
-
return cell ? "t" : "f";
|
|
239
|
-
if (cell instanceof Date)
|
|
240
|
-
return cell.toISOString();
|
|
241
|
-
if (cell instanceof Uint8Array) {
|
|
242
|
-
let hex = "\\x";
|
|
243
|
-
for (const b of cell)
|
|
244
|
-
hex += b.toString(16).padStart(2, "0");
|
|
245
|
-
return hex;
|
|
246
|
-
}
|
|
247
|
-
return JSON.stringify(cell);
|
|
248
|
-
};
|
|
230
|
+
const renderCell = (cell, nullPrint, numericLocale, dataTypeID, boolDisplay) => renderCellValue(cell, nullPrint, numericLocale, dataTypeID, boolDisplay);
|
package/dist/psql/print/csv.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { boolDisplayOf, renderCellValue } from "./units.js";
|
|
2
2
|
/**
|
|
3
3
|
* RFC 4180 CSV printer.
|
|
4
4
|
*
|
|
@@ -34,7 +34,8 @@ export const csvPrinter = {
|
|
|
34
34
|
const nullPrint = opts.nullPrint !== "" ? opts.nullPrint : topt.nullPrint;
|
|
35
35
|
const expanded = topt.expanded === "on";
|
|
36
36
|
const headers = rs.fields.map((f) => f.name);
|
|
37
|
-
const
|
|
37
|
+
const boolDisplay = boolDisplayOf(topt);
|
|
38
|
+
const cells = rs.rows.map((row) => row.map((cell, i) => renderCell(cell, nullPrint, topt.numericLocale, rs.fields[i]?.dataTypeID, boolDisplay)));
|
|
38
39
|
let outBuf = "";
|
|
39
40
|
if (expanded) {
|
|
40
41
|
for (const row of cells) {
|
|
@@ -60,27 +61,7 @@ export const csvPrinter = {
|
|
|
60
61
|
return Promise.resolve();
|
|
61
62
|
},
|
|
62
63
|
};
|
|
63
|
-
const renderCell = (cell, nullPrint, numericLocale) =>
|
|
64
|
-
if (cell === null || cell === undefined)
|
|
65
|
-
return nullPrint;
|
|
66
|
-
if (typeof cell === "string") {
|
|
67
|
-
return formatNumericLocale(cell, numericLocale);
|
|
68
|
-
}
|
|
69
|
-
if (typeof cell === "number" || typeof cell === "bigint") {
|
|
70
|
-
return formatNumericLocale(cell.toString(), numericLocale);
|
|
71
|
-
}
|
|
72
|
-
if (typeof cell === "boolean")
|
|
73
|
-
return cell ? "t" : "f";
|
|
74
|
-
if (cell instanceof Date)
|
|
75
|
-
return cell.toISOString();
|
|
76
|
-
if (cell instanceof Uint8Array) {
|
|
77
|
-
let hex = "\\x";
|
|
78
|
-
for (const b of cell)
|
|
79
|
-
hex += b.toString(16).padStart(2, "0");
|
|
80
|
-
return hex;
|
|
81
|
-
}
|
|
82
|
-
return JSON.stringify(cell);
|
|
83
|
-
};
|
|
64
|
+
const renderCell = (cell, nullPrint, numericLocale, dataTypeID, boolDisplay) => renderCellValue(cell, nullPrint, numericLocale, dataTypeID, boolDisplay);
|
|
84
65
|
const csvField = (value, sep) => {
|
|
85
66
|
const needsQuote = value.includes(sep) ||
|
|
86
67
|
value.includes('"') ||
|
package/dist/psql/print/html.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { boolDisplayOf, renderCellValue } from "./units.js";
|
|
2
2
|
/**
|
|
3
3
|
* HTML printer.
|
|
4
4
|
*
|
|
@@ -71,7 +71,8 @@ const printFlat = (rs, opts, out) => {
|
|
|
71
71
|
const footers = opts.footers ?? topt.footers;
|
|
72
72
|
const headers = rs.fields.map((f) => f.name);
|
|
73
73
|
const aligns = rs.fields.map((f) => NUMERIC_OIDS.has(f.dataTypeID) ? "right" : "left");
|
|
74
|
-
const
|
|
74
|
+
const boolDisplay = boolDisplayOf(topt);
|
|
75
|
+
const cells = rs.rows.map((row) => row.map((cell, i) => renderCell(cell, nullPrint, topt.numericLocale, rs.fields[i]?.dataTypeID, boolDisplay)));
|
|
75
76
|
let buf = "";
|
|
76
77
|
if (startTable) {
|
|
77
78
|
buf += `<table border="${String(topt.border)}"`;
|
|
@@ -133,7 +134,8 @@ const printExpanded = (rs, opts, out) => {
|
|
|
133
134
|
const footers = opts.footers ?? topt.footers;
|
|
134
135
|
const headers = rs.fields.map((f) => f.name);
|
|
135
136
|
const aligns = rs.fields.map((f) => NUMERIC_OIDS.has(f.dataTypeID) ? "right" : "left");
|
|
136
|
-
const
|
|
137
|
+
const boolDisplay = boolDisplayOf(topt);
|
|
138
|
+
const cells = rs.rows.map((row) => row.map((cell, i) => renderCell(cell, nullPrint, topt.numericLocale, rs.fields[i]?.dataTypeID, boolDisplay)));
|
|
137
139
|
let buf = "";
|
|
138
140
|
if (startTable) {
|
|
139
141
|
buf += `<table border="${String(topt.border)}"`;
|
|
@@ -235,24 +237,4 @@ const escapeHtml = (input) => {
|
|
|
235
237
|
}
|
|
236
238
|
return out;
|
|
237
239
|
};
|
|
238
|
-
const renderCell = (cell, nullPrint, numericLocale) =>
|
|
239
|
-
if (cell === null || cell === undefined)
|
|
240
|
-
return nullPrint;
|
|
241
|
-
if (typeof cell === "string") {
|
|
242
|
-
return formatNumericLocale(cell, numericLocale);
|
|
243
|
-
}
|
|
244
|
-
if (typeof cell === "number" || typeof cell === "bigint") {
|
|
245
|
-
return formatNumericLocale(cell.toString(), numericLocale);
|
|
246
|
-
}
|
|
247
|
-
if (typeof cell === "boolean")
|
|
248
|
-
return cell ? "t" : "f";
|
|
249
|
-
if (cell instanceof Date)
|
|
250
|
-
return cell.toISOString();
|
|
251
|
-
if (cell instanceof Uint8Array) {
|
|
252
|
-
let hex = "\\x";
|
|
253
|
-
for (const b of cell)
|
|
254
|
-
hex += b.toString(16).padStart(2, "0");
|
|
255
|
-
return hex;
|
|
256
|
-
}
|
|
257
|
-
return JSON.stringify(cell);
|
|
258
|
-
};
|
|
240
|
+
const renderCell = (cell, nullPrint, numericLocale, dataTypeID, boolDisplay) => renderCellValue(cell, nullPrint, numericLocale, dataTypeID, boolDisplay);
|
package/dist/psql/print/latex.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { boolDisplayOf, renderCellValue } from "./units.js";
|
|
2
2
|
/**
|
|
3
3
|
* LaTeX printers — `latex` (tabular) and `latex-longtable` (longtable).
|
|
4
4
|
*
|
|
@@ -53,7 +53,8 @@ const printLatexFlat = (rs, opts, out) => {
|
|
|
53
53
|
const headers = rs.fields.map((f) => f.name);
|
|
54
54
|
const ncols = rs.fields.length;
|
|
55
55
|
const aligns = rs.fields.map((f) => NUMERIC_OIDS.has(f.dataTypeID) ? "r" : "l");
|
|
56
|
-
const
|
|
56
|
+
const boolDisplay = boolDisplayOf(topt);
|
|
57
|
+
const cells = rs.rows.map((row) => row.map((cell, i) => renderCell(cell, nullPrint, topt.numericLocale, rs.fields[i]?.dataTypeID, boolDisplay)));
|
|
57
58
|
let buf = "";
|
|
58
59
|
if (startTable) {
|
|
59
60
|
if (!tuplesOnly && title) {
|
|
@@ -122,7 +123,8 @@ const printLatexVertical = (rs, opts, out) => {
|
|
|
122
123
|
const title = opts.title ?? topt.title;
|
|
123
124
|
const footers = opts.footers ?? topt.footers;
|
|
124
125
|
const headers = rs.fields.map((f) => f.name);
|
|
125
|
-
const
|
|
126
|
+
const boolDisplay = boolDisplayOf(topt);
|
|
127
|
+
const cells = rs.rows.map((row) => row.map((cell, i) => renderCell(cell, nullPrint, topt.numericLocale, rs.fields[i]?.dataTypeID, boolDisplay)));
|
|
126
128
|
let buf = "";
|
|
127
129
|
if (startTable) {
|
|
128
130
|
if (!tuplesOnly && title) {
|
|
@@ -194,7 +196,8 @@ export const latexLongtablePrinter = {
|
|
|
194
196
|
const headers = rs.fields.map((f) => f.name);
|
|
195
197
|
const ncols = rs.fields.length;
|
|
196
198
|
const aligns = rs.fields.map((f) => NUMERIC_OIDS.has(f.dataTypeID) ? "r" : "l");
|
|
197
|
-
const
|
|
199
|
+
const boolDisplay = boolDisplayOf(topt);
|
|
200
|
+
const cells = rs.rows.map((row) => row.map((cell, i) => renderCell(cell, nullPrint, topt.numericLocale, rs.fields[i]?.dataTypeID, boolDisplay)));
|
|
198
201
|
// `topt.tableAttr` for longtable encodes per-column widths in a
|
|
199
202
|
// whitespace-separated list, consumed left-to-right with a fall
|
|
200
203
|
// back to the previous value once exhausted.
|
|
@@ -373,24 +376,4 @@ const escapeLatex = (input) => {
|
|
|
373
376
|
}
|
|
374
377
|
return out;
|
|
375
378
|
};
|
|
376
|
-
const renderCell = (cell, nullPrint, numericLocale) =>
|
|
377
|
-
if (cell === null || cell === undefined)
|
|
378
|
-
return nullPrint;
|
|
379
|
-
if (typeof cell === "string") {
|
|
380
|
-
return formatNumericLocale(cell, numericLocale);
|
|
381
|
-
}
|
|
382
|
-
if (typeof cell === "number" || typeof cell === "bigint") {
|
|
383
|
-
return formatNumericLocale(cell.toString(), numericLocale);
|
|
384
|
-
}
|
|
385
|
-
if (typeof cell === "boolean")
|
|
386
|
-
return cell ? "t" : "f";
|
|
387
|
-
if (cell instanceof Date)
|
|
388
|
-
return cell.toISOString();
|
|
389
|
-
if (cell instanceof Uint8Array) {
|
|
390
|
-
let hex = "\\x";
|
|
391
|
-
for (const b of cell)
|
|
392
|
-
hex += b.toString(16).padStart(2, "0");
|
|
393
|
-
return hex;
|
|
394
|
-
}
|
|
395
|
-
return JSON.stringify(cell);
|
|
396
|
-
};
|
|
379
|
+
const renderCell = (cell, nullPrint, numericLocale, dataTypeID, boolDisplay) => renderCellValue(cell, nullPrint, numericLocale, dataTypeID, boolDisplay);
|
package/dist/psql/print/troff.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { boolDisplayOf, renderCellValue } from "./units.js";
|
|
2
2
|
/**
|
|
3
3
|
* Troff MS printer.
|
|
4
4
|
*
|
|
@@ -78,7 +78,8 @@ const printFlat = (rs, opts, out) => {
|
|
|
78
78
|
const headers = rs.fields.map((f) => f.name);
|
|
79
79
|
const ncols = rs.fields.length;
|
|
80
80
|
const aligns = rs.fields.map((f) => NUMERIC_OIDS.has(f.dataTypeID) ? "r" : "l");
|
|
81
|
-
const
|
|
81
|
+
const boolDisplay = boolDisplayOf(topt);
|
|
82
|
+
const cells = rs.rows.map((row) => row.map((cell, i) => renderCell(cell, nullPrint, topt.numericLocale, rs.fields[i]?.dataTypeID, boolDisplay)));
|
|
82
83
|
let buf = "";
|
|
83
84
|
if (startTable) {
|
|
84
85
|
if (!tuplesOnly && title) {
|
|
@@ -135,7 +136,8 @@ const printExpanded = (rs, opts, out) => {
|
|
|
135
136
|
const title = opts.title ?? topt.title;
|
|
136
137
|
const footers = opts.footers ?? topt.footers;
|
|
137
138
|
const headers = rs.fields.map((f) => f.name);
|
|
138
|
-
const
|
|
139
|
+
const boolDisplay = boolDisplayOf(topt);
|
|
140
|
+
const cells = rs.rows.map((row) => row.map((cell, i) => renderCell(cell, nullPrint, topt.numericLocale, rs.fields[i]?.dataTypeID, boolDisplay)));
|
|
139
141
|
let buf = "";
|
|
140
142
|
// currentFormat: 0 = none yet, 1 = "Record N" header (c s),
|
|
141
143
|
// 2 = body (c l or c | l). Upstream uses the same tri-state to
|
|
@@ -235,24 +237,4 @@ const escapeTroff = (input) => {
|
|
|
235
237
|
}
|
|
236
238
|
return out;
|
|
237
239
|
};
|
|
238
|
-
const renderCell = (cell, nullPrint, numericLocale) =>
|
|
239
|
-
if (cell === null || cell === undefined)
|
|
240
|
-
return nullPrint;
|
|
241
|
-
if (typeof cell === "string") {
|
|
242
|
-
return formatNumericLocale(cell, numericLocale);
|
|
243
|
-
}
|
|
244
|
-
if (typeof cell === "number" || typeof cell === "bigint") {
|
|
245
|
-
return formatNumericLocale(cell.toString(), numericLocale);
|
|
246
|
-
}
|
|
247
|
-
if (typeof cell === "boolean")
|
|
248
|
-
return cell ? "t" : "f";
|
|
249
|
-
if (cell instanceof Date)
|
|
250
|
-
return cell.toISOString();
|
|
251
|
-
if (cell instanceof Uint8Array) {
|
|
252
|
-
let hex = "\\x";
|
|
253
|
-
for (const b of cell)
|
|
254
|
-
hex += b.toString(16).padStart(2, "0");
|
|
255
|
-
return hex;
|
|
256
|
-
}
|
|
257
|
-
return JSON.stringify(cell);
|
|
258
|
-
};
|
|
240
|
+
const renderCell = (cell, nullPrint, numericLocale, dataTypeID, boolDisplay) => renderCellValue(cell, nullPrint, numericLocale, dataTypeID, boolDisplay);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { boolDisplayOf, renderCellValue } from "./units.js";
|
|
2
2
|
/**
|
|
3
3
|
* Unaligned tabular printer.
|
|
4
4
|
*
|
|
@@ -24,7 +24,8 @@ export const unalignedPrinter = {
|
|
|
24
24
|
const expanded = topt.expanded === "on";
|
|
25
25
|
const tuplesOnly = topt.tuplesOnly;
|
|
26
26
|
const headers = rs.fields.map((f) => f.name);
|
|
27
|
-
const
|
|
27
|
+
const boolDisplay = boolDisplayOf(topt);
|
|
28
|
+
const cells = rs.rows.map((row) => row.map((cell, i) => renderCell(cell, nullPrint, topt.numericLocale, rs.fields[i]?.dataTypeID, boolDisplay)));
|
|
28
29
|
let outBuf = "";
|
|
29
30
|
if (expanded) {
|
|
30
31
|
// Vertical mode mirrors print.c `print_unaligned_vertical`: each
|
|
@@ -95,25 +96,4 @@ export const unalignedPrinter = {
|
|
|
95
96
|
return Promise.resolve();
|
|
96
97
|
},
|
|
97
98
|
};
|
|
98
|
-
const renderCell = (cell, nullPrint, numericLocale) =>
|
|
99
|
-
if (cell === null || cell === undefined)
|
|
100
|
-
return nullPrint;
|
|
101
|
-
if (typeof cell === "string") {
|
|
102
|
-
return formatNumericLocale(cell, numericLocale);
|
|
103
|
-
}
|
|
104
|
-
if (typeof cell === "number" || typeof cell === "bigint") {
|
|
105
|
-
return formatNumericLocale(cell.toString(), numericLocale);
|
|
106
|
-
}
|
|
107
|
-
if (typeof cell === "boolean")
|
|
108
|
-
return cell ? "t" : "f";
|
|
109
|
-
if (cell instanceof Date)
|
|
110
|
-
return cell.toISOString();
|
|
111
|
-
if (cell instanceof Uint8Array) {
|
|
112
|
-
// Bytea -> hex escape, matching libpq's `\x` form.
|
|
113
|
-
let hex = "\\x";
|
|
114
|
-
for (const b of cell)
|
|
115
|
-
hex += b.toString(16).padStart(2, "0");
|
|
116
|
-
return hex;
|
|
117
|
-
}
|
|
118
|
-
return JSON.stringify(cell);
|
|
119
|
-
};
|
|
99
|
+
const renderCell = (cell, nullPrint, numericLocale, dataTypeID, boolDisplay) => renderCellValue(cell, nullPrint, numericLocale, dataTypeID, boolDisplay);
|
package/dist/psql/print/units.js
CHANGED
|
@@ -5,6 +5,59 @@
|
|
|
5
5
|
* `pg_size_pretty()` output, and the `\timing` line format.
|
|
6
6
|
*/
|
|
7
7
|
const NUMERIC_RE = /^-?\d+(\.\d+)?$/;
|
|
8
|
+
/** PostgreSQL `bool` type OID. Cells of this column type honour the
|
|
9
|
+
* `\pset display_true` / `\pset display_false` strings (PG 19+). */
|
|
10
|
+
export const BOOLOID = 16;
|
|
11
|
+
/**
|
|
12
|
+
* Extract the `\pset display_true`/`display_false` pair from a print-table
|
|
13
|
+
* options object, defaulting to psql's `'t'`/`'f'`. Kept here so every
|
|
14
|
+
* printer derives `boolDisplay` the same way.
|
|
15
|
+
*/
|
|
16
|
+
export const boolDisplayOf = (topt) => ({
|
|
17
|
+
true: topt.truePrint ?? "t",
|
|
18
|
+
false: topt.falsePrint ?? "f",
|
|
19
|
+
});
|
|
20
|
+
/**
|
|
21
|
+
* Render a single result cell to its display string, shared by every
|
|
22
|
+
* table-format printer (aligned/unaligned/csv/html/latex/troff/asciidoc).
|
|
23
|
+
*
|
|
24
|
+
* Boolean handling mirrors upstream print.c: a cell in a BOOLOID column
|
|
25
|
+
* arriving over the wire as the text `'t'`/`'f'` (or a JS boolean, for
|
|
26
|
+
* synthetic result sets) renders through `boolDisplay`, which carries the
|
|
27
|
+
* `\pset display_true`/`display_false` values. `dataTypeID`/`boolDisplay`
|
|
28
|
+
* are optional so existing callers that don't thread column type through
|
|
29
|
+
* keep the prior behaviour (`'t'`/`'f'`).
|
|
30
|
+
*/
|
|
31
|
+
export const renderCellValue = (cell, nullPrint, numericLocale, dataTypeID, boolDisplay, locale) => {
|
|
32
|
+
if (cell === null || cell === undefined)
|
|
33
|
+
return nullPrint;
|
|
34
|
+
const trueStr = boolDisplay?.true ?? "t";
|
|
35
|
+
const falseStr = boolDisplay?.false ?? "f";
|
|
36
|
+
// BOOLOID column: the text-format wire value is 't'/'f'. Map through the
|
|
37
|
+
// configured display strings (upstream keys on the 't' first byte).
|
|
38
|
+
if (dataTypeID === BOOLOID && typeof cell === "string") {
|
|
39
|
+
if (cell === "t" || cell === "f")
|
|
40
|
+
return cell === "t" ? trueStr : falseStr;
|
|
41
|
+
}
|
|
42
|
+
if (typeof cell === "string") {
|
|
43
|
+
return formatNumericLocale(cell, numericLocale, locale);
|
|
44
|
+
}
|
|
45
|
+
if (typeof cell === "number" || typeof cell === "bigint") {
|
|
46
|
+
return formatNumericLocale(cell.toString(), numericLocale, locale);
|
|
47
|
+
}
|
|
48
|
+
if (typeof cell === "boolean")
|
|
49
|
+
return cell ? trueStr : falseStr;
|
|
50
|
+
if (cell instanceof Date)
|
|
51
|
+
return cell.toISOString();
|
|
52
|
+
if (cell instanceof Uint8Array) {
|
|
53
|
+
// Bytea -> hex escape, matching libpq's `\x` form.
|
|
54
|
+
let hex = "\\x";
|
|
55
|
+
for (const b of cell)
|
|
56
|
+
hex += b.toString(16).padStart(2, "0");
|
|
57
|
+
return hex;
|
|
58
|
+
}
|
|
59
|
+
return JSON.stringify(cell);
|
|
60
|
+
};
|
|
8
61
|
/**
|
|
9
62
|
* Format a numeric string using locale-aware thousand separators and
|
|
10
63
|
* decimal point. Equivalent to print.c `format_numeric_locale` but
|
|
@@ -741,7 +741,6 @@ export class PgConnection {
|
|
|
741
741
|
throw new Error("PgConnection.prepare: server returned no parameter description");
|
|
742
742
|
}
|
|
743
743
|
const { paramOids, fields } = descResult;
|
|
744
|
-
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
745
744
|
const conn = this;
|
|
746
745
|
return {
|
|
747
746
|
name,
|
package/dist/psql/wire/sasl.js
CHANGED
|
@@ -279,7 +279,6 @@ function xor(a, b) {
|
|
|
279
279
|
const NON_ASCII_SPACE_RE = new RegExp("[" + "\u00A0\u1680" + "\u2000-\u200B" + "\u202F\u205F\u3000" + "]", "g");
|
|
280
280
|
// Combining marks and ZWJ are intentionally in this class; RFC 3454 Table B.1
|
|
281
281
|
// strips them precisely because they combine with neighbouring code points.
|
|
282
|
-
/* eslint-disable no-misleading-character-class */
|
|
283
282
|
const MAPPED_TO_NOTHING_RE = new RegExp("[" +
|
|
284
283
|
"\u00AD\u034F\u1806" +
|
|
285
284
|
"\u180B-\u180D" +
|
|
@@ -287,7 +286,6 @@ const MAPPED_TO_NOTHING_RE = new RegExp("[" +
|
|
|
287
286
|
"\uFE00-\uFE0F" +
|
|
288
287
|
"\uFEFF" +
|
|
289
288
|
"]", "g");
|
|
290
|
-
/* eslint-enable no-misleading-character-class */
|
|
291
289
|
function saslprep(password) {
|
|
292
290
|
return password
|
|
293
291
|
.replace(NON_ASCII_SPACE_RE, " ")
|
|
@@ -27,7 +27,6 @@ const reserveClosedPort = () => new Promise((resolve, reject) => {
|
|
|
27
27
|
});
|
|
28
28
|
});
|
|
29
29
|
export const test = originalTest.extend({
|
|
30
|
-
// eslint-disable-next-line no-empty-pattern
|
|
31
30
|
runMockServer: async ({}, use) => {
|
|
32
31
|
let startedServer;
|
|
33
32
|
await use(async (mockDir) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neon",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.31.0",
|
|
4
4
|
"description": "CLI tool for Neon Serverless Postgres",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"neon",
|
|
@@ -43,41 +43,38 @@
|
|
|
43
43
|
"diff": "5.2.0",
|
|
44
44
|
"fflate": "^0.8.3",
|
|
45
45
|
"neon-init": "0.20.0",
|
|
46
|
-
"open": "10.
|
|
46
|
+
"open": "^10.2.0",
|
|
47
47
|
"openid-client": "6.8.1",
|
|
48
48
|
"pg-protocol": "^1.14.0",
|
|
49
49
|
"prompts": "2.4.2",
|
|
50
50
|
"undici": "^7.28.0",
|
|
51
51
|
"which": "3.0.1",
|
|
52
|
-
"yaml": "2.
|
|
52
|
+
"yaml": "^2.9.0",
|
|
53
53
|
"yargs": "17.7.2",
|
|
54
|
-
"@neon/config": "0.9.1",
|
|
55
54
|
"@neon/sdk": "1.0.0",
|
|
55
|
+
"@neon/config": "0.9.1",
|
|
56
56
|
"@neon/config-runtime": "0.9.1",
|
|
57
|
-
"@neon/env": "0.
|
|
57
|
+
"@neon/env": "0.11.0"
|
|
58
58
|
},
|
|
59
59
|
"optionalDependencies": {
|
|
60
60
|
"esbuild": "0.28.1"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@apidevtools/swagger-parser": "12.1.0",
|
|
64
|
-
"@eslint/js": "9.29.0",
|
|
65
64
|
"@rollup/plugin-commonjs": "25.0.8",
|
|
66
65
|
"@rollup/plugin-json": "6.1.0",
|
|
67
66
|
"@rollup/plugin-node-resolve": "15.2.3",
|
|
68
67
|
"@testcontainers/postgresql": "^11.14.0",
|
|
69
68
|
"@types/cli-table": "0.3.4",
|
|
70
69
|
"@types/diff": "5.2.1",
|
|
71
|
-
"@types/eslint__js": "8.42.3",
|
|
72
70
|
"@types/express": "4.17.21",
|
|
73
|
-
"@types/node": "
|
|
71
|
+
"@types/node": "^20.19.0",
|
|
74
72
|
"@types/prompts": "2.4.9",
|
|
75
73
|
"@types/which": "3.0.4",
|
|
76
|
-
"@types/yargs": "17.0.
|
|
77
|
-
"@vitest/coverage-v8": "
|
|
74
|
+
"@types/yargs": "^17.0.35",
|
|
75
|
+
"@vitest/coverage-v8": "^3.0.9",
|
|
78
76
|
"@yao-pkg/pkg": "6.10.0",
|
|
79
77
|
"emocks": "3.0.4",
|
|
80
|
-
"eslint": "9.29.0",
|
|
81
78
|
"express": "4.19.2",
|
|
82
79
|
"node-pty": "1.1.0",
|
|
83
80
|
"oauth2-mock-server": "8.1.0",
|
|
@@ -85,9 +82,8 @@
|
|
|
85
82
|
"rollup": "3.29.4",
|
|
86
83
|
"strip-ansi": "7.1.0",
|
|
87
84
|
"tsx": "4.22.3",
|
|
88
|
-
"typescript": "5.
|
|
89
|
-
"
|
|
90
|
-
"vitest": "1.6.1"
|
|
85
|
+
"typescript": "^5.9.0",
|
|
86
|
+
"vitest": "^3.0.9"
|
|
91
87
|
},
|
|
92
88
|
"publishConfig": {
|
|
93
89
|
"access": "public",
|
|
@@ -113,8 +109,8 @@
|
|
|
113
109
|
"build": "pnpm generateParams && pnpm clean && tsc -p tsconfig.build.json && cp src/*.html ./dist",
|
|
114
110
|
"bundle": "node pkg.js",
|
|
115
111
|
"typecheck": "tsc --noEmit",
|
|
116
|
-
"lint": "pnpm typecheck &&
|
|
117
|
-
"lint:fix": "pnpm typecheck &&
|
|
112
|
+
"lint": "pnpm typecheck && biome check src",
|
|
113
|
+
"lint:fix": "pnpm typecheck && biome check src --write",
|
|
118
114
|
"test": "pnpm build && vitest run",
|
|
119
115
|
"test:ci": "pnpm build && vitest run",
|
|
120
116
|
"test:conformance": "vitest run --config tests/psql-conformance/vitest.config.ts"
|