neon 3.0.0 → 3.1.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 +54 -0
- package/dist/_shared/auth_selection.js +76 -79
- package/dist/_shared/credentials.js +166 -187
- package/dist/_shared/env-core/env.js +354 -517
- package/dist/_shared/env-core/reuse-secrets.js +159 -203
- package/dist/_shared/paths.js +129 -126
- package/dist/_shared/profiles.js +192 -242
- package/dist/_shared/secure_file.js +36 -38
- package/dist/_virtual/_rolldown/runtime.js +13 -0
- package/dist/analytics.js +163 -207
- package/dist/api.js +815 -758
- package/dist/auth.js +121 -141
- package/dist/auth_context.js +39 -53
- package/dist/cli.js +4 -7
- package/dist/commands/api.js +220 -250
- package/dist/commands/api_keys.js +251 -314
- package/dist/commands/auth.js +283 -328
- package/dist/commands/bootstrap.js +372 -437
- package/dist/commands/branches.js +304 -455
- package/dist/commands/bucket.js +374 -514
- package/dist/commands/checkout.js +213 -298
- package/dist/commands/config.js +573 -690
- package/dist/commands/connection_string.js +137 -165
- package/dist/commands/data_api.js +238 -260
- package/dist/commands/databases.js +67 -76
- package/dist/commands/deploy.js +31 -25
- package/dist/commands/dev.js +639 -719
- package/dist/commands/diff.js +156 -200
- package/dist/commands/env.js +243 -303
- package/dist/commands/functions.js +275 -355
- package/dist/commands/index.js +70 -65
- package/dist/commands/init.js +84 -119
- package/dist/commands/inspect.js +55 -55
- package/dist/commands/ip_allow.js +88 -120
- package/dist/commands/link.js +874 -1019
- package/dist/commands/logs.js +291 -0
- package/dist/commands/neon_auth.js +725 -933
- package/dist/commands/operations.js +34 -25
- package/dist/commands/orgs.js +28 -18
- package/dist/commands/profile.js +614 -845
- package/dist/commands/projects.js +313 -373
- package/dist/commands/psql.js +60 -58
- package/dist/commands/roles.js +55 -58
- package/dist/commands/schema_diff.js +87 -131
- package/dist/commands/set_context.js +34 -26
- package/dist/commands/snapshots.js +288 -413
- package/dist/commands/status.js +41 -37
- package/dist/commands/user.js +21 -10
- package/dist/commands/vpc_endpoints.js +85 -113
- package/dist/config.js +7 -6
- package/dist/config_format.js +50 -66
- package/dist/config_template.js +128 -157
- package/dist/context.js +183 -235
- package/dist/current_branch_fast_path.js +40 -49
- package/dist/dev/env.js +313 -394
- package/dist/dev/functions.js +54 -68
- package/dist/dev/inputs.js +46 -58
- package/dist/dev/runtime.js +135 -164
- package/dist/dev/websocket.js +766 -959
- package/dist/env.js +27 -33
- package/dist/env_file.js +118 -132
- package/dist/env_services.js +36 -38
- package/dist/errors.js +57 -68
- package/dist/functions_api.js +45 -43
- package/dist/help.js +189 -140
- package/dist/index.js +182 -257
- package/dist/init/agents.js +137 -118
- package/dist/init/auth.js +58 -68
- package/dist/init/bootstrap.js +325 -396
- package/dist/init/build_config.js +4 -2
- package/dist/init/detect_agent.js +56 -101
- package/dist/init/editors.js +35 -52
- package/dist/init/enrich_output.js +51 -66
- package/dist/init/extension.js +134 -171
- package/dist/init/inspect.js +179 -266
- package/dist/init/interactive.js +510 -622
- package/dist/init/neonctl.js +117 -168
- package/dist/init/orchestrate.js +157 -173
- package/dist/init/phases/auth.js +188 -202
- package/dist/init/phases/cleanup.js +23 -23
- package/dist/init/phases/db.js +251 -277
- package/dist/init/phases/getting_started.js +213 -223
- package/dist/init/phases/mcp.js +174 -224
- package/dist/init/phases/migrations.js +247 -248
- package/dist/init/phases/neon_auth.js +114 -133
- package/dist/init/phases/setup.js +546 -703
- package/dist/init/phases/skills.js +75 -86
- package/dist/init/phases/status.js +72 -67
- package/dist/init/resolve_context.js +102 -99
- package/dist/init/route_command.js +91 -98
- package/dist/init/skills.js +174 -218
- package/dist/init/vsix.js +77 -99
- package/dist/log.js +17 -16
- package/dist/neon_services.js +104 -129
- package/dist/parameters.gen.js +481 -471
- package/dist/pkg.js +17 -19
- package/dist/profile_keys.js +44 -47
- package/dist/psql/cli.js +44 -47
- package/dist/psql/command/cmd_cond.js +231 -406
- package/dist/psql/command/cmd_connect.js +557 -764
- package/dist/psql/command/cmd_copy.js +727 -983
- package/dist/psql/command/cmd_describe.js +1499 -1688
- package/dist/psql/command/cmd_format.js +733 -905
- package/dist/psql/command/cmd_io.js +1293 -2082
- package/dist/psql/command/cmd_lo.js +297 -359
- package/dist/psql/command/cmd_meta.js +727 -878
- package/dist/psql/command/cmd_misc.js +138 -172
- package/dist/psql/command/cmd_pipeline.js +547 -1099
- package/dist/psql/command/cmd_restrict.js +119 -155
- package/dist/psql/command/cmd_show.js +529 -688
- package/dist/psql/command/dispatch.js +261 -325
- package/dist/psql/command/inputQueue.js +35 -33
- package/dist/psql/command/shared.js +49 -63
- package/dist/psql/complete/filenames.js +90 -133
- package/dist/psql/complete/index.js +59 -97
- package/dist/psql/complete/matcher.js +236 -300
- package/dist/psql/complete/psqlVars.js +218 -223
- package/dist/psql/complete/queries.js +159 -177
- package/dist/psql/complete/rules.js +1493 -2299
- package/dist/psql/core/common.js +762 -1180
- package/dist/psql/core/help.js +456 -546
- package/dist/psql/core/mainloop.js +692 -1302
- package/dist/psql/core/prompt.js +391 -408
- package/dist/psql/core/settings.js +429 -644
- package/dist/psql/core/sqlHelp.js +480 -554
- package/dist/psql/core/startup.js +626 -815
- package/dist/psql/core/syncVars.js +67 -110
- package/dist/psql/core/variables.js +156 -278
- package/dist/psql/describe/formatters.js +884 -1285
- package/dist/psql/describe/processNamePattern.js +173 -260
- package/dist/psql/describe/queries.js +1368 -2403
- package/dist/psql/describe/versionGate.js +32 -41
- package/dist/psql/index.js +1414 -1927
- package/dist/psql/io/history.js +232 -271
- package/dist/psql/io/input.js +103 -108
- package/dist/psql/io/lineEditor/buffer.js +238 -319
- package/dist/psql/io/lineEditor/complete.js +135 -213
- package/dist/psql/io/lineEditor/filename.js +139 -148
- package/dist/psql/io/lineEditor/index.js +653 -870
- package/dist/psql/io/lineEditor/keymap.js +544 -702
- package/dist/psql/io/lineEditor/vt100.js +294 -341
- package/dist/psql/io/pgpass.js +158 -187
- package/dist/psql/io/pgservice.js +146 -183
- package/dist/psql/io/psqlrc.js +328 -403
- package/dist/psql/print/aligned.js +1020 -1683
- package/dist/psql/print/asciidoc.js +180 -214
- package/dist/psql/print/crosstab.js +281 -442
- package/dist/psql/print/csv.js +48 -70
- package/dist/psql/print/html.js +195 -226
- package/dist/psql/print/json.js +75 -88
- package/dist/psql/print/latex.js +291 -364
- package/dist/psql/print/pager.js +171 -242
- package/dist/psql/print/troff.js +194 -226
- package/dist/psql/print/unaligned.js +69 -95
- package/dist/psql/print/units.js +167 -169
- package/dist/psql/scanner/slash.js +428 -483
- package/dist/psql/scanner/sql.js +445 -889
- package/dist/psql/scanner/stringutils.js +309 -379
- package/dist/psql/types/index.js +2 -7
- package/dist/psql/types/scanner.js +25 -22
- package/dist/psql/wire/connection.js +2042 -2803
- package/dist/psql/wire/copy.js +84 -100
- package/dist/psql/wire/notify.js +39 -59
- package/dist/psql/wire/pipeline.js +305 -518
- package/dist/psql/wire/protocol.js +349 -417
- package/dist/psql/wire/sasl.js +180 -265
- package/dist/psql/wire/tls.js +400 -561
- package/dist/storage_api.js +115 -129
- package/dist/test_utils/fixtures.js +94 -113
- package/dist/test_utils/oauth_server.js +10 -7
- package/dist/test_utils/project_dir.js +33 -0
- package/dist/utils/ai_gateway_notice.js +131 -162
- package/dist/utils/api_enums.js +21 -28
- package/dist/utils/auth.js +10 -4
- package/dist/utils/branch_notice.js +20 -19
- package/dist/utils/branch_picker.js +83 -89
- package/dist/utils/cli_name.js +15 -12
- package/dist/utils/compute_units.js +20 -27
- package/dist/utils/config_diff.js +127 -158
- package/dist/utils/enrichers.js +95 -148
- package/dist/utils/esbuild.js +130 -189
- package/dist/utils/flags.js +35 -47
- package/dist/utils/formats.js +8 -15
- package/dist/utils/git_diff.js +69 -80
- package/dist/utils/inspect_db.js +101 -143
- package/dist/utils/inspect_queries.js +179 -142
- package/dist/utils/middlewares.js +37 -44
- package/dist/utils/openapi.js +87 -99
- package/dist/utils/package_manager.js +312 -110
- package/dist/utils/point_in_time.js +49 -53
- package/dist/utils/psql.js +89 -106
- package/dist/utils/service_picker.js +55 -58
- package/dist/utils/string.js +5 -5
- package/dist/utils/ui.js +38 -55
- package/dist/utils/write_sync.js +26 -35
- package/dist/utils/zip.js +4 -3
- package/dist/writer.js +67 -87
- package/package.json +7 -5
|
@@ -1,41 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Rendering for psql's `\d*` describe commands.
|
|
3
|
-
*
|
|
4
|
-
* This module owns the runtime side of WP-20: take the SQL templates
|
|
5
|
-
* from {@link './queries.js'} (WP-19), run them against a real
|
|
6
|
-
* {@link Connection}, and render the result via the aligned printer
|
|
7
|
-
* for tabular sections plus free-form text for "footer" sections
|
|
8
|
-
* (`Indexes:`, `Foreign-key constraints:`, etc.) the way upstream
|
|
9
|
-
* `describe.c` does.
|
|
10
|
-
*
|
|
11
|
-
* Scope of the initial implementation:
|
|
12
|
-
*
|
|
13
|
-
* - {@link runListQuery} runs an arbitrary {@link DescribeQuery}
|
|
14
|
-
* (typically one of `listTables`, `describeFunctions`, etc.) and
|
|
15
|
-
* prints its result with the aligned printer. Title is taken from
|
|
16
|
-
* the query's `description`. This covers every `\d*` *list* command.
|
|
17
|
-
*
|
|
18
|
-
* - {@link describeOneTableDetails} fans out from the lookup query
|
|
19
|
-
* `describeTableDetails` into the per-relation detail render: a
|
|
20
|
-
* columns table at the top, followed by index / constraint / trigger
|
|
21
|
-
* sections as appropriate for the relkind. This is the bulk of
|
|
22
|
-
* upstream's `describeOneTableDetails()` from `describe.c`. We
|
|
23
|
-
* implement the *common* table layout (regular tables, views,
|
|
24
|
-
* materialized views, partitioned tables and indexes) — exotic
|
|
25
|
-
* sections (foreign-table options, replica identity, RLS policies,
|
|
26
|
-
* inheritance pretty-printing) are stubbed with the SQL queries in
|
|
27
|
-
* place but only minimal rendering. The output is sufficient for
|
|
28
|
-
* real-world `\d <name>` usage; gaps are flagged with TODO comments.
|
|
29
|
-
*
|
|
30
|
-
* - {@link describeOneSequence}, {@link describeOneFunctionDetails}
|
|
31
|
-
* and {@link describeOneViewDetails} are thinner: a single query +
|
|
32
|
-
* one section of output each.
|
|
33
|
-
*
|
|
34
|
-
* Pattern conditions: each list query has an `AND true /<!---->* TODO(WP-20)…`
|
|
35
|
-
* placeholder we replace via {@link applyPattern} before sending the
|
|
36
|
-
* query down the wire. See {@link processSQLNamePattern} for the
|
|
37
|
-
* pattern parser.
|
|
38
|
-
*/
|
|
39
1
|
import { alignedPrinter } from "../print/aligned.js";
|
|
40
2
|
import { asciidocPrinter } from "../print/asciidoc.js";
|
|
41
3
|
import { csvPrinter } from "../print/csv.js";
|
|
@@ -44,1287 +6,924 @@ import { jsonPrinter } from "../print/json.js";
|
|
|
44
6
|
import { latexLongtablePrinter, latexPrinter } from "../print/latex.js";
|
|
45
7
|
import { troffMsPrinter } from "../print/troff.js";
|
|
46
8
|
import { unalignedPrinter } from "../print/unaligned.js";
|
|
47
|
-
import { applyPattern, processSQLNamePattern
|
|
48
|
-
import {
|
|
49
|
-
import {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
9
|
+
import { applyPattern, processSQLNamePattern } from "./processNamePattern.js";
|
|
10
|
+
import { serverAtLeast } from "./versionGate.js";
|
|
11
|
+
import { fetchExcludedFromPublications, fetchForeignTableInfo, fetchInheritedBy, fetchInherits, fetchNotNullConstraints, fetchPartitionKey, fetchPartitionOf, fetchPerColumnFdwOptions, fetchPolicies, fetchSequencePublications, fetchStatisticsObjects, fetchTableInfo, fetchTablePublications, fetchTableSubscriptions } from "./queries.js";
|
|
12
|
+
//#region src/psql/describe/formatters.ts
|
|
13
|
+
/**
|
|
14
|
+
* Rendering for psql's `\d*` describe commands.
|
|
15
|
+
*
|
|
16
|
+
* This module owns the runtime side of WP-20: take the SQL templates
|
|
17
|
+
* from {@link './queries.js'} (WP-19), run them against a real
|
|
18
|
+
* {@link Connection}, and render the result via the aligned printer
|
|
19
|
+
* for tabular sections plus free-form text for "footer" sections
|
|
20
|
+
* (`Indexes:`, `Foreign-key constraints:`, etc.) the way upstream
|
|
21
|
+
* `describe.c` does.
|
|
22
|
+
*
|
|
23
|
+
* Scope of the initial implementation:
|
|
24
|
+
*
|
|
25
|
+
* - {@link runListQuery} runs an arbitrary {@link DescribeQuery}
|
|
26
|
+
* (typically one of `listTables`, `describeFunctions`, etc.) and
|
|
27
|
+
* prints its result with the aligned printer. Title is taken from
|
|
28
|
+
* the query's `description`. This covers every `\d*` *list* command.
|
|
29
|
+
*
|
|
30
|
+
* - {@link describeOneTableDetails} fans out from the lookup query
|
|
31
|
+
* `describeTableDetails` into the per-relation detail render: a
|
|
32
|
+
* columns table at the top, followed by index / constraint / trigger
|
|
33
|
+
* sections as appropriate for the relkind. This is the bulk of
|
|
34
|
+
* upstream's `describeOneTableDetails()` from `describe.c`. We
|
|
35
|
+
* implement the *common* table layout (regular tables, views,
|
|
36
|
+
* materialized views, partitioned tables and indexes) — exotic
|
|
37
|
+
* sections (foreign-table options, replica identity, RLS policies,
|
|
38
|
+
* inheritance pretty-printing) are stubbed with the SQL queries in
|
|
39
|
+
* place but only minimal rendering. The output is sufficient for
|
|
40
|
+
* real-world `\d <name>` usage; gaps are flagged with TODO comments.
|
|
41
|
+
*
|
|
42
|
+
* - {@link describeOneSequence}, {@link describeOneFunctionDetails}
|
|
43
|
+
* and {@link describeOneViewDetails} are thinner: a single query +
|
|
44
|
+
* one section of output each.
|
|
45
|
+
*
|
|
46
|
+
* Pattern conditions: each list query has an `AND true /<!---->* TODO(WP-20)…`
|
|
47
|
+
* placeholder we replace via {@link applyPattern} before sending the
|
|
48
|
+
* query down the wire. See {@link processSQLNamePattern} for the
|
|
49
|
+
* pattern parser.
|
|
50
|
+
*/
|
|
51
|
+
/**
|
|
52
|
+
* Pick the printer for the active output format. Mirrors `pickPrinter`
|
|
53
|
+
* in `core/common.ts`, but operates off `PrintQueryOpts.topt.format`
|
|
54
|
+
* since formatters don't have access to the full `PsqlSettings`. The
|
|
55
|
+
* aligned printer covers both `aligned` and `wrapped`; everything else
|
|
56
|
+
* routes to its dedicated module so `\d <obj>` honours the user's
|
|
57
|
+
* `\pset format` choice (asciidoc/csv/html/latex/etc.) the same way
|
|
58
|
+
* regular SELECTs do.
|
|
59
|
+
*/
|
|
59
60
|
const pickPrinterForFormat = (opts) => {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
return troffMsPrinter;
|
|
80
|
-
default:
|
|
81
|
-
return alignedPrinter;
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
/**
|
|
85
|
-
* Format a cell value coming back from the protocol layer. Connection
|
|
86
|
-
* decoded values arrive as strings (text mode) or null. We coerce
|
|
87
|
-
* everything to string for the printer.
|
|
88
|
-
*/
|
|
61
|
+
switch (opts.topt.format) {
|
|
62
|
+
case "aligned":
|
|
63
|
+
case "wrapped": return alignedPrinter;
|
|
64
|
+
case "unaligned": return unalignedPrinter;
|
|
65
|
+
case "csv": return csvPrinter;
|
|
66
|
+
case "json": return jsonPrinter;
|
|
67
|
+
case "html": return htmlPrinter;
|
|
68
|
+
case "asciidoc": return asciidocPrinter;
|
|
69
|
+
case "latex": return latexPrinter;
|
|
70
|
+
case "latex-longtable": return latexLongtablePrinter;
|
|
71
|
+
case "troff-ms": return troffMsPrinter;
|
|
72
|
+
default: return alignedPrinter;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Format a cell value coming back from the protocol layer. Connection
|
|
77
|
+
* decoded values arrive as strings (text mode) or null. We coerce
|
|
78
|
+
* everything to string for the printer.
|
|
79
|
+
*/
|
|
89
80
|
const cellToString = (v) => {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
return JSON.stringify(v);
|
|
106
|
-
}
|
|
107
|
-
catch {
|
|
108
|
-
return "";
|
|
109
|
-
}
|
|
110
|
-
};
|
|
111
|
-
/**
|
|
112
|
-
* Materialize a {@link ResultSet} into something the aligned printer
|
|
113
|
-
* can render. The printer expects `rows: unknown[][]`; we keep the
|
|
114
|
-
* shape but ensure cells are strings or null for the null-print logic.
|
|
115
|
-
*/
|
|
81
|
+
if (v === null || v === void 0) return "";
|
|
82
|
+
if (typeof v === "string") return v;
|
|
83
|
+
if (Buffer.isBuffer(v)) return v.toString("utf-8");
|
|
84
|
+
if (typeof v === "number" || typeof v === "boolean" || typeof v === "bigint") return String(v);
|
|
85
|
+
try {
|
|
86
|
+
return JSON.stringify(v);
|
|
87
|
+
} catch {
|
|
88
|
+
return "";
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Materialize a {@link ResultSet} into something the aligned printer
|
|
93
|
+
* can render. The printer expects `rows: unknown[][]`; we keep the
|
|
94
|
+
* shape but ensure cells are strings or null for the null-print logic.
|
|
95
|
+
*/
|
|
116
96
|
const coerceResultSet = (rs) => ({
|
|
117
|
-
|
|
118
|
-
|
|
97
|
+
...rs,
|
|
98
|
+
rows: rs.rows.map((row) => row.map((c) => c === null || c === void 0 ? null : cellToString(c)))
|
|
119
99
|
});
|
|
120
100
|
const makeSectionBuffer = () => {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
* suitable for `opts.footers`. Returns `null` when the section emitted
|
|
155
|
-
* nothing (so callers can skip pushing an empty entry). Trailing
|
|
156
|
-
* newlines are stripped: the printer re-appends a single `\n` per
|
|
157
|
-
* footer, and trailing blank lines (between sections / before next
|
|
158
|
-
* command) are emitted once by the printer's own footer-terminator.
|
|
159
|
-
*/
|
|
101
|
+
let buf = "";
|
|
102
|
+
const write = (chunk) => {
|
|
103
|
+
if (typeof chunk === "string") buf += chunk;
|
|
104
|
+
else buf += chunk.toString("utf-8");
|
|
105
|
+
return true;
|
|
106
|
+
};
|
|
107
|
+
const stub = {
|
|
108
|
+
write,
|
|
109
|
+
end: () => true,
|
|
110
|
+
on: () => stub,
|
|
111
|
+
once: () => stub,
|
|
112
|
+
emit: () => true,
|
|
113
|
+
removeListener: () => stub,
|
|
114
|
+
addListener: () => stub,
|
|
115
|
+
setDefaultEncoding: () => stub,
|
|
116
|
+
cork: () => void 0,
|
|
117
|
+
uncork: () => void 0,
|
|
118
|
+
destroy: () => stub,
|
|
119
|
+
writable: true,
|
|
120
|
+
writableEnded: false,
|
|
121
|
+
writableFinished: false,
|
|
122
|
+
toString: () => buf
|
|
123
|
+
};
|
|
124
|
+
return stub;
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* Capture the output of a single render-section helper into a string
|
|
128
|
+
* suitable for `opts.footers`. Returns `null` when the section emitted
|
|
129
|
+
* nothing (so callers can skip pushing an empty entry). Trailing
|
|
130
|
+
* newlines are stripped: the printer re-appends a single `\n` per
|
|
131
|
+
* footer, and trailing blank lines (between sections / before next
|
|
132
|
+
* command) are emitted once by the printer's own footer-terminator.
|
|
133
|
+
*/
|
|
160
134
|
const captureSection = async (fn) => {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
};
|
|
166
|
-
/**
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
fields.push(fakeField("Compression"));
|
|
374
|
-
if (includeStatsTarget)
|
|
375
|
-
fields.push(fakeField("Stats target"));
|
|
376
|
-
fields.push(fakeField("Description"));
|
|
377
|
-
}
|
|
378
|
-
if (hasAnyFdwOptions)
|
|
379
|
-
fields.push(fakeField("FDW options"));
|
|
380
|
-
const rows = colsRs.rows.map((r) => {
|
|
381
|
-
const colName = cellToString(r[0]);
|
|
382
|
-
const colType = cellToString(r[1]);
|
|
383
|
-
const colDefault = r[2] === null ? null : cellToString(r[2]);
|
|
384
|
-
const notnull = String(r[3]) === "t" || r[3] === true;
|
|
385
|
-
const collation = r[4] === null ? null : cellToString(r[4]);
|
|
386
|
-
const identity = cellToString(r[5] ?? "");
|
|
387
|
-
const generated = cellToString(r[6] ?? "");
|
|
388
|
-
const nullable = notnull ? "not null" : "";
|
|
389
|
-
let dflt = colDefault ?? "";
|
|
390
|
-
if (identity === "a") {
|
|
391
|
-
dflt = "generated always as identity";
|
|
392
|
-
}
|
|
393
|
-
else if (identity === "d") {
|
|
394
|
-
dflt = "generated by default as identity";
|
|
395
|
-
}
|
|
396
|
-
else if (generated === "s") {
|
|
397
|
-
// STORED generated column (PG 12+).
|
|
398
|
-
dflt = dflt ? `generated always as (${dflt}) stored` : "";
|
|
399
|
-
}
|
|
400
|
-
else if (generated === "v") {
|
|
401
|
-
// VIRTUAL generated column (PG 18+). Same expression rendering as
|
|
402
|
-
// STORED but without the trailing keyword.
|
|
403
|
-
dflt = dflt ? `generated always as (${dflt})` : "";
|
|
404
|
-
}
|
|
405
|
-
const row = isToast
|
|
406
|
-
? [colName, colType]
|
|
407
|
-
: [colName, colType, collation ?? "", nullable, dflt];
|
|
408
|
-
if (verboseCols) {
|
|
409
|
-
// Slot offsets: 7 = storage, [8 = compression if PG14+], stats, desc.
|
|
410
|
-
let idx = 7;
|
|
411
|
-
const storage = cellToString(r[idx++] ?? "");
|
|
412
|
-
row.push(storage);
|
|
413
|
-
if (includeCompression) {
|
|
414
|
-
const compression = cellToString(r[idx++] ?? "");
|
|
415
|
-
row.push(compression);
|
|
416
|
-
}
|
|
417
|
-
if (includeStatsTarget) {
|
|
418
|
-
const statsTarget = r[idx] === null ? "" : cellToString(r[idx] ?? "");
|
|
419
|
-
idx++;
|
|
420
|
-
row.push(statsTarget);
|
|
421
|
-
}
|
|
422
|
-
const description = r[idx] === null ? "" : cellToString(r[idx] ?? "");
|
|
423
|
-
row.push(description);
|
|
424
|
-
}
|
|
425
|
-
if (hasAnyFdwOptions) {
|
|
426
|
-
const opts = fdwOptionsByColumn.get(colName);
|
|
427
|
-
row.push(opts ? `(${opts})` : "");
|
|
428
|
-
}
|
|
429
|
-
return row;
|
|
430
|
-
});
|
|
431
|
-
const colsResult = {
|
|
432
|
-
command: "SELECT",
|
|
433
|
-
rowCount: rows.length,
|
|
434
|
-
oid: null,
|
|
435
|
-
fields,
|
|
436
|
-
rows,
|
|
437
|
-
notices: [],
|
|
438
|
-
};
|
|
439
|
-
// ----- Per-section footers, accumulated *before* the column table is
|
|
440
|
-
// printed. Upstream `describeOneTableDetails` attaches each
|
|
441
|
-
// relkind-specific footer to the columns table via
|
|
442
|
-
// `printTableAddFooter()`, then `printTable()` emits them flush
|
|
443
|
-
// against the data rows with a single trailing blank line at the
|
|
444
|
-
// end of the whole block. Routing every section through
|
|
445
|
-
// `opts.footers` mirrors that layout — single-line annotations
|
|
446
|
-
// (`Access method:`, `Tablespace:`, …) sit immediately under the
|
|
447
|
-
// last data row, multi-line group footers (`Indexes:`,
|
|
448
|
-
// `Foreign-key constraints:`, …) follow, and the trailing blank
|
|
449
|
-
// only fires after the last footer rather than between data and
|
|
450
|
-
// the first footer.
|
|
451
|
-
const footers = [];
|
|
452
|
-
const push = (s) => {
|
|
453
|
-
if (s !== null)
|
|
454
|
-
footers.push(s);
|
|
455
|
-
};
|
|
456
|
-
// ----- View definition (views / matviews, verbose only) -----
|
|
457
|
-
// Upstream attaches this as a table FOOTER (describe.c ~3175), so it
|
|
458
|
-
// renders flush against the column rows with the single trailing
|
|
459
|
-
// blank line the footer machinery adds — not as a separate block.
|
|
460
|
-
if ((relkind === "v" || relkind === "m") && verbose) {
|
|
461
|
-
const vrs = await conn.query(`SELECT pg_catalog.pg_get_viewdef('${oid}'::pg_catalog.oid, true);`, []);
|
|
462
|
-
if (vrs.rows.length > 0) {
|
|
463
|
-
push(`View definition:\n${cellToString(vrs.rows[0][0])}`);
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
// ----- Partition-key (partitioned-table parent only) -----
|
|
467
|
-
if (relkind === "p") {
|
|
468
|
-
push(await captureSection((b) => renderPartitionKeySection(conn, oid, b)));
|
|
469
|
-
}
|
|
470
|
-
// ----- Partition-of (child partition only) -----
|
|
471
|
-
if (relInfo.relispartition) {
|
|
472
|
-
push(await captureSection((b) => renderPartitionOfSection(conn, oid, verbose, b)));
|
|
473
|
-
}
|
|
474
|
-
// ----- Owning table (TOAST tables only — printed before Indexes).
|
|
475
|
-
// Upstream `describeOneTableDetails` adds the owning-table footer
|
|
476
|
-
// prior to attaching the indexes footer for `RELKIND_TOASTVALUE`.
|
|
477
|
-
if (relkind === "t") {
|
|
478
|
-
push(await captureSection((b) => renderToastOwningTableFooter(conn, oid, b)));
|
|
479
|
-
}
|
|
480
|
-
// ----- Indexes (tables / matviews / partitioned tables / TOAST) -----
|
|
481
|
-
if (relkind === "r" ||
|
|
482
|
-
relkind === "m" ||
|
|
483
|
-
relkind === "p" ||
|
|
484
|
-
relkind === "t") {
|
|
485
|
-
push(await captureSection((b) => renderIndexesSection(conn, oid, b)));
|
|
486
|
-
}
|
|
487
|
-
// ----- Check constraints -----
|
|
488
|
-
if (relkind === "r" || relkind === "p" || relkind === "f") {
|
|
489
|
-
push(await captureSection((b) => renderCheckConstraintsSection(conn, oid, b)));
|
|
490
|
-
}
|
|
491
|
-
// ----- Not-null constraints (PG 18+ named NOT NULL constraints) -----
|
|
492
|
-
// Upstream renders this footer in verbose mode between Check
|
|
493
|
-
// constraints and Foreign-key constraints (describe.c ~3104).
|
|
494
|
-
// The query returns empty on pre-PG-18 servers (no contype = 'n'
|
|
495
|
-
// rows), so the section is naturally absent there.
|
|
496
|
-
if (verbose && (relkind === "r" || relkind === "p" || relkind === "f")) {
|
|
497
|
-
push(await captureSection((b) => renderNotNullConstraintsSection(conn, oid, b)));
|
|
498
|
-
}
|
|
499
|
-
// ----- Foreign-key constraints -----
|
|
500
|
-
if (relkind === "r" || relkind === "p") {
|
|
501
|
-
push(await captureSection((b) => renderForeignKeyConstraintsSection(conn, oid, b)));
|
|
502
|
-
push(await captureSection((b) => renderReferencedBySection(conn, oid, b)));
|
|
503
|
-
}
|
|
504
|
-
// ----- Triggers -----
|
|
505
|
-
if (relkind === "r" || relkind === "p" || relkind === "v") {
|
|
506
|
-
push(await captureSection((b) => renderTriggersSection(conn, oid, b)));
|
|
507
|
-
}
|
|
508
|
-
// ----- RLS policies (regular + partitioned tables) -----
|
|
509
|
-
if (relkind === "r" || relkind === "p") {
|
|
510
|
-
push(await captureSection((b) => renderPoliciesSection(conn, oid, relInfo, b)));
|
|
511
|
-
}
|
|
512
|
-
// ----- Foreign-table footer: Server + FDW options -----
|
|
513
|
-
// Per-column FDW options are rendered inline within the columns
|
|
514
|
-
// table (see fdwOptionsByColumn above); no separate footer here.
|
|
515
|
-
if (relkind === "f") {
|
|
516
|
-
push(await captureSection((b) => renderForeignTableFooter(conn, oid, b)));
|
|
517
|
-
}
|
|
518
|
-
// ----- Inherits: (parents) — for tables, partitioned tables, foreign -----
|
|
519
|
-
if (relkind === "r" || relkind === "p" || relkind === "f") {
|
|
520
|
-
push(await captureSection((b) => renderInheritsSection(conn, oid, b)));
|
|
521
|
-
}
|
|
522
|
-
// ----- Inherited by / Partitions / Number of [child tables|partitions] -----
|
|
523
|
-
if (relkind === "r" || relkind === "p" || relkind === "f") {
|
|
524
|
-
push(await captureSection((b) => renderInheritedBySection(conn, oid, relkind, verbose, b)));
|
|
525
|
-
}
|
|
526
|
-
// ----- Publications (any publishable relkind) -----
|
|
527
|
-
if (relkind === "r" ||
|
|
528
|
-
relkind === "p" ||
|
|
529
|
-
relkind === "m" ||
|
|
530
|
-
relkind === "f") {
|
|
531
|
-
push(await captureSection((b) => renderPublicationsSection(conn, oid, b)));
|
|
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
|
-
}
|
|
540
|
-
// ----- Subscriptions (any publishable relkind; permission-denied silent) -----
|
|
541
|
-
if (relkind === "r" ||
|
|
542
|
-
relkind === "p" ||
|
|
543
|
-
relkind === "m" ||
|
|
544
|
-
relkind === "f") {
|
|
545
|
-
push(await captureSection((b) => renderSubscriptionsSection(conn, oid, b)));
|
|
546
|
-
}
|
|
547
|
-
// ----- Statistics objects (verbose; r/m/p/f) -----
|
|
548
|
-
if (verbose &&
|
|
549
|
-
(relkind === "r" ||
|
|
550
|
-
relkind === "m" ||
|
|
551
|
-
relkind === "p" ||
|
|
552
|
-
relkind === "f")) {
|
|
553
|
-
push(await captureSection((b) => renderStatisticsObjectsSection(conn, oid, b)));
|
|
554
|
-
}
|
|
555
|
-
// ----- Replica Identity (verbose, non-default, regular & matview).
|
|
556
|
-
// INDEX mode is rendered inline within Indexes:, so the footer
|
|
557
|
-
// is only emitted for FULL / NOTHING.
|
|
558
|
-
if (verbose && (relkind === "r" || relkind === "m")) {
|
|
559
|
-
push(await captureSection((b) => {
|
|
560
|
-
renderReplicaIdentitySection(schema, relInfo, b);
|
|
561
|
-
}));
|
|
562
|
-
}
|
|
563
|
-
// ----- Tablespace footer (verbose: explicit tablespace only) -----
|
|
564
|
-
if (verbose) {
|
|
565
|
-
push(await captureSection((b) => {
|
|
566
|
-
renderTablespaceFooter(relkind, relInfo, b);
|
|
567
|
-
}));
|
|
568
|
-
}
|
|
569
|
-
// ----- Access method footer (verbose: relkind r/p with relam set).
|
|
570
|
-
// Matviews ('m') show their access method inline in the header,
|
|
571
|
-
// so we don't double up here. Gated by `HIDE_TABLEAM` to mirror
|
|
572
|
-
// upstream — the per-test psql.sql toggles the variable to
|
|
573
|
-
// suppress access-method noise.
|
|
574
|
-
if (!hideTableam && verbose && (relkind === "r" || relkind === "p")) {
|
|
575
|
-
push(await captureSection((b) => {
|
|
576
|
-
renderAccessMethodFooter(relInfo, b);
|
|
577
|
-
}));
|
|
578
|
-
}
|
|
579
|
-
// Upstream's `printTable` is invoked with `default_footer = false`
|
|
580
|
-
// for the column listing: the row-count footer ("(N rows)") is
|
|
581
|
-
// suppressed so the relkind-specific footers we just collected drive
|
|
582
|
-
// the post-table layout. Pass them via `opts.footers` so the printer
|
|
583
|
-
// emits each one flush against the data rows and ends the block with
|
|
584
|
-
// a single trailing blank line.
|
|
585
|
-
const colOpts = {
|
|
586
|
-
...popt,
|
|
587
|
-
title,
|
|
588
|
-
topt: { ...popt.topt, title, defaultFooter: false },
|
|
589
|
-
footers: footers.length > 0 ? footers : null,
|
|
590
|
-
};
|
|
591
|
-
await pickPrinterForFormat(colOpts).printQuery(coerceResultSet(colsResult), colOpts, out);
|
|
592
|
-
};
|
|
593
|
-
/**
|
|
594
|
-
* Helper that runs {@link fetchTableInfo} and parses the resulting row
|
|
595
|
-
* into a {@link RelationInfo}. Returns sensible falsy defaults when the
|
|
596
|
-
* row is missing (shouldn't happen given the caller already looked up
|
|
597
|
-
* the relation, but we don't want to throw mid-render).
|
|
598
|
-
*/
|
|
135
|
+
const buf = makeSectionBuffer();
|
|
136
|
+
await fn(buf);
|
|
137
|
+
const text = buf.toString().replace(/\n+$/, "");
|
|
138
|
+
return text === "" ? null : text;
|
|
139
|
+
};
|
|
140
|
+
/**
|
|
141
|
+
* Run a list-style describe query and write its result via the aligned
|
|
142
|
+
* printer. Returns the {@link ResultSet} for callers that want to
|
|
143
|
+
* inspect or post-process. Used by `\dt`, `\df`, `\dn`, etc.
|
|
144
|
+
*/
|
|
145
|
+
const runListQuery = async (conn, query, patternResult, out, popt) => {
|
|
146
|
+
const { sql, params } = applyPattern(query.sql, patternResult, query.params);
|
|
147
|
+
const rs = await conn.query(sql, params);
|
|
148
|
+
const coerced = coerceResultSet(rs);
|
|
149
|
+
const titleOverride = query.description ?? popt.title;
|
|
150
|
+
const opts = {
|
|
151
|
+
...popt,
|
|
152
|
+
title: titleOverride,
|
|
153
|
+
topt: {
|
|
154
|
+
...popt.topt,
|
|
155
|
+
title: titleOverride ?? popt.topt.title
|
|
156
|
+
},
|
|
157
|
+
footers: rs.rows.length === 0 ? popt.footers : popt.footers !== null ? popt.footers : null
|
|
158
|
+
};
|
|
159
|
+
await pickPrinterForFormat(opts).printQuery(coerced, opts, out);
|
|
160
|
+
return rs;
|
|
161
|
+
};
|
|
162
|
+
const lookupRelations = async (conn, query, patternResult) => {
|
|
163
|
+
const { sql, params } = applyPattern(query.sql, patternResult, query.params);
|
|
164
|
+
return (await conn.query(sql, params)).rows.map((row) => ({
|
|
165
|
+
oid: Number(cellToString(row[0])),
|
|
166
|
+
nspname: cellToString(row[1]),
|
|
167
|
+
relname: cellToString(row[2]),
|
|
168
|
+
relkind: cellToString(row[3] ?? "")
|
|
169
|
+
}));
|
|
170
|
+
};
|
|
171
|
+
/**
|
|
172
|
+
* Lookup of one specific relation by `schema.name` for the `\d <name>`
|
|
173
|
+
* dispatch. Returns the row we need to choose the right `describeOne*`
|
|
174
|
+
* renderer — including `relkind` which the upstream code reads from
|
|
175
|
+
* a separate SELECT.
|
|
176
|
+
*/
|
|
177
|
+
const lookupOneRelation = async (conn, pattern) => {
|
|
178
|
+
const np = processSQLNamePattern({
|
|
179
|
+
pattern,
|
|
180
|
+
namevar: "c.relname",
|
|
181
|
+
schemavar: "n.nspname",
|
|
182
|
+
visibilityrule: "pg_catalog.pg_table_is_visible(c.oid)"
|
|
183
|
+
});
|
|
184
|
+
if (np.dotCount > 1) return null;
|
|
185
|
+
const conds = [
|
|
186
|
+
...np.schemaConditions,
|
|
187
|
+
...np.nameConditions,
|
|
188
|
+
...np.visibilityConditions
|
|
189
|
+
];
|
|
190
|
+
let sql = "SELECT c.oid, n.nspname, c.relname, c.relkind\nFROM pg_catalog.pg_class c\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n";
|
|
191
|
+
if (conds.length > 0) sql += `WHERE ${conds.join("\n AND ")}\n`;
|
|
192
|
+
sql += "ORDER BY 2, 3 LIMIT 1;";
|
|
193
|
+
const rs = await conn.query(sql, np.params);
|
|
194
|
+
if (rs.rows.length === 0) return null;
|
|
195
|
+
const row = rs.rows[0];
|
|
196
|
+
return {
|
|
197
|
+
oid: Number(cellToString(row[0])),
|
|
198
|
+
nspname: cellToString(row[1]),
|
|
199
|
+
relname: cellToString(row[2]),
|
|
200
|
+
relkind: cellToString(row[3])
|
|
201
|
+
};
|
|
202
|
+
};
|
|
203
|
+
/**
|
|
204
|
+
* Render `Table "schema.name"` (or the relkind-specific header) plus the
|
|
205
|
+
* column listing, followed by per-relkind sections (Indexes, Check
|
|
206
|
+
* constraints, Foreign-key constraints, Referenced-by, Triggers).
|
|
207
|
+
*
|
|
208
|
+
* Upstream `describeOneTableDetails()` is ~1500 LOC in `describe.c`;
|
|
209
|
+
* this implementation focuses on the headline experience and leaves
|
|
210
|
+
* exotic sections (RLS, replica identity, partition bounds rendering,
|
|
211
|
+
* pretty-printed inheritance) as TODOs. The query layer fetches the
|
|
212
|
+
* raw data so a follow-up WP can extend rendering without re-running
|
|
213
|
+
* queries.
|
|
214
|
+
*/
|
|
215
|
+
const describeOneTableDetails = async (conn, oid, schema, name, relkind, verbose, out, popt, hideTableam = false, hideCompression = false) => {
|
|
216
|
+
const relInfo = await fetchRelationInfo(conn, oid);
|
|
217
|
+
const baseTitle = headerForRelkind(relkind, schema, name);
|
|
218
|
+
const title = !hideTableam && relkind === "m" && relInfo.relam !== 0 && relInfo.amname ? `${baseTitle}\nAccess method: ${relInfo.amname}` : baseTitle;
|
|
219
|
+
const fdwOptionsByColumn = relkind === "f" ? await fetchPerColumnFdwOptionsMap(conn, oid) : /* @__PURE__ */ new Map();
|
|
220
|
+
const verboseCols = verbose && (relkind === "r" || relkind === "m" || relkind === "p" || relkind === "f" || relkind === "v" || relkind === "I" || relkind === "i");
|
|
221
|
+
const includeCompression = verboseCols && serverAtLeast(conn.serverVersion, 14e4) && !hideCompression && (relkind === "r" || relkind === "p" || relkind === "m");
|
|
222
|
+
const includeStatsTarget = verboseCols && (relkind === "r" || relkind === "i" || relkind === "I" || relkind === "m" || relkind === "f" || relkind === "p");
|
|
223
|
+
const colSql = "SELECT a.attname,\n pg_catalog.format_type(a.atttypid, a.atttypmod),\n (SELECT pg_catalog.pg_get_expr(d.adbin, d.adrelid, true)\n FROM pg_catalog.pg_attrdef d\n WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef),\n a.attnotnull,\n (SELECT c.collname FROM pg_catalog.pg_collation c, pg_catalog.pg_type t\n WHERE c.oid = a.attcollation AND t.oid = a.atttypid AND a.attcollation <> t.typcollation) AS attcollation,\n a.attidentity,\n a.attgenerated" + (verboseCols ? ",\n CASE a.attstorage WHEN 'p' THEN 'plain' WHEN 'e' THEN 'external' WHEN 'm' THEN 'main' WHEN 'x' THEN 'extended' ELSE '???' END AS attstorage" + (includeCompression ? ",\n CASE a.attcompression WHEN 'p' THEN 'pglz' WHEN 'l' THEN 'lz4' WHEN '' THEN '' ELSE '???' END AS attcompression" : "") + (includeStatsTarget ? ",\n CASE WHEN a.attstattarget = -1 THEN NULL ELSE a.attstattarget::text END AS attstattarget" : "") + ",\n pg_catalog.col_description(a.attrelid, a.attnum)" : "") + `
|
|
224
|
+
FROM pg_catalog.pg_attribute a
|
|
225
|
+
WHERE a.attrelid = '${oid}' AND a.attnum > 0 AND NOT a.attisdropped\nORDER BY a.attnum;`;
|
|
226
|
+
const colsRs = await conn.query(colSql, []);
|
|
227
|
+
const hasAnyFdwOptions = fdwOptionsByColumn.size > 0;
|
|
228
|
+
const isToast = relkind === "t";
|
|
229
|
+
const fields = [fakeField("Column"), fakeField("Type")];
|
|
230
|
+
if (!isToast) {
|
|
231
|
+
fields.push(fakeField("Collation"));
|
|
232
|
+
fields.push(fakeField("Nullable"));
|
|
233
|
+
fields.push(fakeField("Default"));
|
|
234
|
+
}
|
|
235
|
+
if (verboseCols) {
|
|
236
|
+
fields.push(fakeField("Storage"));
|
|
237
|
+
if (includeCompression) fields.push(fakeField("Compression"));
|
|
238
|
+
if (includeStatsTarget) fields.push(fakeField("Stats target"));
|
|
239
|
+
fields.push(fakeField("Description"));
|
|
240
|
+
}
|
|
241
|
+
if (hasAnyFdwOptions) fields.push(fakeField("FDW options"));
|
|
242
|
+
const rows = colsRs.rows.map((r) => {
|
|
243
|
+
const colName = cellToString(r[0]);
|
|
244
|
+
const colType = cellToString(r[1]);
|
|
245
|
+
const colDefault = r[2] === null ? null : cellToString(r[2]);
|
|
246
|
+
const notnull = String(r[3]) === "t" || r[3] === true;
|
|
247
|
+
const collation = r[4] === null ? null : cellToString(r[4]);
|
|
248
|
+
const identity = cellToString(r[5] ?? "");
|
|
249
|
+
const generated = cellToString(r[6] ?? "");
|
|
250
|
+
const nullable = notnull ? "not null" : "";
|
|
251
|
+
let dflt = colDefault ?? "";
|
|
252
|
+
if (identity === "a") dflt = "generated always as identity";
|
|
253
|
+
else if (identity === "d") dflt = "generated by default as identity";
|
|
254
|
+
else if (generated === "s") dflt = dflt ? `generated always as (${dflt}) stored` : "";
|
|
255
|
+
else if (generated === "v") dflt = dflt ? `generated always as (${dflt})` : "";
|
|
256
|
+
const row = isToast ? [colName, colType] : [
|
|
257
|
+
colName,
|
|
258
|
+
colType,
|
|
259
|
+
collation ?? "",
|
|
260
|
+
nullable,
|
|
261
|
+
dflt
|
|
262
|
+
];
|
|
263
|
+
if (verboseCols) {
|
|
264
|
+
let idx = 7;
|
|
265
|
+
const storage = cellToString(r[idx++] ?? "");
|
|
266
|
+
row.push(storage);
|
|
267
|
+
if (includeCompression) {
|
|
268
|
+
const compression = cellToString(r[idx++] ?? "");
|
|
269
|
+
row.push(compression);
|
|
270
|
+
}
|
|
271
|
+
if (includeStatsTarget) {
|
|
272
|
+
const statsTarget = r[idx] === null ? "" : cellToString(r[idx] ?? "");
|
|
273
|
+
idx++;
|
|
274
|
+
row.push(statsTarget);
|
|
275
|
+
}
|
|
276
|
+
const description = r[idx] === null ? "" : cellToString(r[idx] ?? "");
|
|
277
|
+
row.push(description);
|
|
278
|
+
}
|
|
279
|
+
if (hasAnyFdwOptions) {
|
|
280
|
+
const opts = fdwOptionsByColumn.get(colName);
|
|
281
|
+
row.push(opts ? `(${opts})` : "");
|
|
282
|
+
}
|
|
283
|
+
return row;
|
|
284
|
+
});
|
|
285
|
+
const colsResult = {
|
|
286
|
+
command: "SELECT",
|
|
287
|
+
rowCount: rows.length,
|
|
288
|
+
oid: null,
|
|
289
|
+
fields,
|
|
290
|
+
rows,
|
|
291
|
+
notices: []
|
|
292
|
+
};
|
|
293
|
+
const footers = [];
|
|
294
|
+
const push = (s) => {
|
|
295
|
+
if (s !== null) footers.push(s);
|
|
296
|
+
};
|
|
297
|
+
if ((relkind === "v" || relkind === "m") && verbose) {
|
|
298
|
+
const vrs = await conn.query(`SELECT pg_catalog.pg_get_viewdef('${oid}'::pg_catalog.oid, true);`, []);
|
|
299
|
+
if (vrs.rows.length > 0) push(`View definition:\n${cellToString(vrs.rows[0][0])}`);
|
|
300
|
+
}
|
|
301
|
+
if (relkind === "p") push(await captureSection((b) => renderPartitionKeySection(conn, oid, b)));
|
|
302
|
+
if (relInfo.relispartition) push(await captureSection((b) => renderPartitionOfSection(conn, oid, verbose, b)));
|
|
303
|
+
if (relkind === "t") push(await captureSection((b) => renderToastOwningTableFooter(conn, oid, b)));
|
|
304
|
+
if (relkind === "r" || relkind === "m" || relkind === "p" || relkind === "t") push(await captureSection((b) => renderIndexesSection(conn, oid, b)));
|
|
305
|
+
if (relkind === "r" || relkind === "p" || relkind === "f") push(await captureSection((b) => renderCheckConstraintsSection(conn, oid, b)));
|
|
306
|
+
if (verbose && (relkind === "r" || relkind === "p" || relkind === "f")) push(await captureSection((b) => renderNotNullConstraintsSection(conn, oid, b)));
|
|
307
|
+
if (relkind === "r" || relkind === "p") {
|
|
308
|
+
push(await captureSection((b) => renderForeignKeyConstraintsSection(conn, oid, b)));
|
|
309
|
+
push(await captureSection((b) => renderReferencedBySection(conn, oid, b)));
|
|
310
|
+
}
|
|
311
|
+
if (relkind === "r" || relkind === "p" || relkind === "v") push(await captureSection((b) => renderTriggersSection(conn, oid, b)));
|
|
312
|
+
if (relkind === "r" || relkind === "p") push(await captureSection((b) => renderPoliciesSection(conn, oid, relInfo, b)));
|
|
313
|
+
if (relkind === "f") push(await captureSection((b) => renderForeignTableFooter(conn, oid, b)));
|
|
314
|
+
if (relkind === "r" || relkind === "p" || relkind === "f") push(await captureSection((b) => renderInheritsSection(conn, oid, b)));
|
|
315
|
+
if (relkind === "r" || relkind === "p" || relkind === "f") push(await captureSection((b) => renderInheritedBySection(conn, oid, relkind, verbose, b)));
|
|
316
|
+
if (relkind === "r" || relkind === "p" || relkind === "m" || relkind === "f") push(await captureSection((b) => renderPublicationsSection(conn, oid, b)));
|
|
317
|
+
if (relkind === "r" || relkind === "p" || relkind === "m" || relkind === "f") push(await captureSection((b) => renderExcludedFromPublicationsSection(conn, oid, b)));
|
|
318
|
+
if (relkind === "r" || relkind === "p" || relkind === "m" || relkind === "f") push(await captureSection((b) => renderSubscriptionsSection(conn, oid, b)));
|
|
319
|
+
if (verbose && (relkind === "r" || relkind === "m" || relkind === "p" || relkind === "f")) push(await captureSection((b) => renderStatisticsObjectsSection(conn, oid, b)));
|
|
320
|
+
if (verbose && (relkind === "r" || relkind === "m")) push(await captureSection((b) => {
|
|
321
|
+
renderReplicaIdentitySection(schema, relInfo, b);
|
|
322
|
+
}));
|
|
323
|
+
if (verbose) push(await captureSection((b) => {
|
|
324
|
+
renderTablespaceFooter(relkind, relInfo, b);
|
|
325
|
+
}));
|
|
326
|
+
if (!hideTableam && verbose && (relkind === "r" || relkind === "p")) push(await captureSection((b) => {
|
|
327
|
+
renderAccessMethodFooter(relInfo, b);
|
|
328
|
+
}));
|
|
329
|
+
const colOpts = {
|
|
330
|
+
...popt,
|
|
331
|
+
title,
|
|
332
|
+
topt: {
|
|
333
|
+
...popt.topt,
|
|
334
|
+
title,
|
|
335
|
+
defaultFooter: false
|
|
336
|
+
},
|
|
337
|
+
footers: footers.length > 0 ? footers : null
|
|
338
|
+
};
|
|
339
|
+
await pickPrinterForFormat(colOpts).printQuery(coerceResultSet(colsResult), colOpts, out);
|
|
340
|
+
};
|
|
341
|
+
/**
|
|
342
|
+
* Helper that runs {@link fetchTableInfo} and parses the resulting row
|
|
343
|
+
* into a {@link RelationInfo}. Returns sensible falsy defaults when the
|
|
344
|
+
* row is missing (shouldn't happen given the caller already looked up
|
|
345
|
+
* the relation, but we don't want to throw mid-render).
|
|
346
|
+
*/
|
|
599
347
|
const fetchRelationInfo = async (conn, oid) => {
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
348
|
+
const q = fetchTableInfo({
|
|
349
|
+
oid,
|
|
350
|
+
serverVersion: conn.serverVersion
|
|
351
|
+
});
|
|
352
|
+
const rs = await conn.query(q.sql, q.params);
|
|
353
|
+
if (rs.rows.length === 0) return {
|
|
354
|
+
rowsecurity: false,
|
|
355
|
+
forcerowsecurity: false,
|
|
356
|
+
relreplident: "d",
|
|
357
|
+
relispartition: false,
|
|
358
|
+
reltablespace: 0,
|
|
359
|
+
relam: 0,
|
|
360
|
+
spcname: null,
|
|
361
|
+
amname: null
|
|
362
|
+
};
|
|
363
|
+
const r = rs.rows[0];
|
|
364
|
+
return {
|
|
365
|
+
rowsecurity: parseBool(r[0]),
|
|
366
|
+
forcerowsecurity: parseBool(r[1]),
|
|
367
|
+
relreplident: cellToString(r[2] ?? "d") || "d",
|
|
368
|
+
relispartition: parseBool(r[3]),
|
|
369
|
+
reltablespace: Number(cellToString(r[4] ?? "0")) || 0,
|
|
370
|
+
relam: Number(cellToString(r[5] ?? "0")) || 0,
|
|
371
|
+
spcname: r[6] === null || r[6] === void 0 ? null : cellToString(r[6]),
|
|
372
|
+
amname: r[7] === null || r[7] === void 0 ? null : cellToString(r[7])
|
|
373
|
+
};
|
|
625
374
|
};
|
|
626
375
|
/** Coerce a Postgres "t"/"f" text-mode boolean (or a real bool) to JS. */
|
|
627
|
-
const parseBool = (v) => v === true ||
|
|
376
|
+
const parseBool = (v) => v === true || typeof v === "string" && (v === "t" || v === "true");
|
|
628
377
|
/**
|
|
629
|
-
|
|
630
|
-
|
|
378
|
+
* Render `Partition key: <partkeydef>` for partitioned-table parents.
|
|
379
|
+
*/
|
|
631
380
|
const renderPartitionKeySection = async (conn, oid, out) => {
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
* partition (`relispartition = true`).
|
|
645
|
-
*/
|
|
381
|
+
const q = fetchPartitionKey({ oid });
|
|
382
|
+
const rs = await conn.query(q.sql, q.params);
|
|
383
|
+
if (rs.rows.length === 0) return;
|
|
384
|
+
const def = cellToString(rs.rows[0][0] ?? "");
|
|
385
|
+
if (def === "") return;
|
|
386
|
+
out.write(`Partition key: ${def}\n`);
|
|
387
|
+
};
|
|
388
|
+
/**
|
|
389
|
+
* Render the "Partition of: <parent> <bound>[ DETACH PENDING]" line and
|
|
390
|
+
* the verbose-only "Partition constraint:" follow-up for a child
|
|
391
|
+
* partition (`relispartition = true`).
|
|
392
|
+
*/
|
|
646
393
|
const renderPartitionOfSection = async (conn, oid, verbose, out) => {
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
/**
|
|
672
|
-
* Render the `Policies[...]:` header + one POLICY line per row. The
|
|
673
|
-
* exact header text encodes (rowsecurity, forcerowsecurity, has-policies)
|
|
674
|
-
* the same way upstream does, including the "(none)" tail for the
|
|
675
|
-
* enabled-but-no-policies cases.
|
|
676
|
-
*/
|
|
394
|
+
const q = fetchPartitionOf({
|
|
395
|
+
oid,
|
|
396
|
+
serverVersion: conn.serverVersion,
|
|
397
|
+
withConstraint: verbose
|
|
398
|
+
});
|
|
399
|
+
const rs = await conn.query(q.sql, q.params);
|
|
400
|
+
if (rs.rows.length === 0) return;
|
|
401
|
+
const row = rs.rows[0];
|
|
402
|
+
const parent = cellToString(row[0] ?? "");
|
|
403
|
+
const bound = cellToString(row[1] ?? "");
|
|
404
|
+
const tail = parseBool(row[2]) ? " DETACH PENDING" : "";
|
|
405
|
+
out.write(`Partition of: ${parent} ${bound}${tail}\n`);
|
|
406
|
+
if (verbose) {
|
|
407
|
+
const constraintdef = row[3] === null || row[3] === void 0 ? "" : cellToString(row[3]);
|
|
408
|
+
if (constraintdef === "") out.write("No partition constraint\n");
|
|
409
|
+
else out.write(`Partition constraint: ${constraintdef}\n`);
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
/**
|
|
413
|
+
* Render the `Policies[...]:` header + one POLICY line per row. The
|
|
414
|
+
* exact header text encodes (rowsecurity, forcerowsecurity, has-policies)
|
|
415
|
+
* the same way upstream does, including the "(none)" tail for the
|
|
416
|
+
* enabled-but-no-policies cases.
|
|
417
|
+
*/
|
|
677
418
|
const renderPoliciesSection = async (conn, oid, relInfo, out) => {
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
if (qual !== null)
|
|
716
|
-
line += `\n USING (${qual})`;
|
|
717
|
-
if (withcheck !== null)
|
|
718
|
-
line += `\n WITH CHECK (${withcheck})`;
|
|
719
|
-
out.write(`${line}\n`);
|
|
720
|
-
}
|
|
721
|
-
};
|
|
722
|
-
/**
|
|
723
|
-
* Render the foreign-table footer: `Server: <name>` + optional
|
|
724
|
-
* `FDW options: (key 'val', key 'val')`. Upstream pulls these in a
|
|
725
|
-
* single follow-up query; we mirror that shape via
|
|
726
|
-
* {@link fetchForeignTableInfo}.
|
|
727
|
-
*/
|
|
419
|
+
const q = fetchPolicies({
|
|
420
|
+
oid,
|
|
421
|
+
serverVersion: conn.serverVersion
|
|
422
|
+
});
|
|
423
|
+
const rs = await conn.query(q.sql, q.params);
|
|
424
|
+
const tuples = rs.rows.length;
|
|
425
|
+
const { rowsecurity, forcerowsecurity } = relInfo;
|
|
426
|
+
let header = null;
|
|
427
|
+
if (rowsecurity && !forcerowsecurity && tuples > 0) header = "Policies:";
|
|
428
|
+
else if (rowsecurity && forcerowsecurity && tuples > 0) header = "Policies (forced row security enabled):";
|
|
429
|
+
else if (rowsecurity && !forcerowsecurity && tuples === 0) header = "Policies (row security enabled): (none)";
|
|
430
|
+
else if (rowsecurity && forcerowsecurity && tuples === 0) header = "Policies (forced row security enabled): (none)";
|
|
431
|
+
else if (!rowsecurity && tuples > 0) header = "Policies (row security disabled):";
|
|
432
|
+
if (header === null) return;
|
|
433
|
+
out.write(`${header}\n`);
|
|
434
|
+
for (const r of rs.rows) {
|
|
435
|
+
const polname = cellToString(r[0]);
|
|
436
|
+
const permissive = parseBool(r[1]);
|
|
437
|
+
const roles = r[2] === null || r[2] === void 0 ? null : cellToString(r[2]);
|
|
438
|
+
const qual = r[3] === null || r[3] === void 0 ? null : cellToString(r[3]);
|
|
439
|
+
const withcheck = r[4] === null || r[4] === void 0 ? null : cellToString(r[4]);
|
|
440
|
+
const cmd = r[5] === null || r[5] === void 0 ? null : cellToString(r[5]);
|
|
441
|
+
let line = ` POLICY "${polname}"`;
|
|
442
|
+
if (!permissive) line += " AS RESTRICTIVE";
|
|
443
|
+
if (cmd !== null && cmd !== "") line += ` FOR ${cmd}`;
|
|
444
|
+
if (roles !== null) line += `\n TO ${roles}`;
|
|
445
|
+
if (qual !== null) line += `\n USING (${qual})`;
|
|
446
|
+
if (withcheck !== null) line += `\n WITH CHECK (${withcheck})`;
|
|
447
|
+
out.write(`${line}\n`);
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
/**
|
|
451
|
+
* Render the foreign-table footer: `Server: <name>` + optional
|
|
452
|
+
* `FDW options: (key 'val', key 'val')`. Upstream pulls these in a
|
|
453
|
+
* single follow-up query; we mirror that shape via
|
|
454
|
+
* {@link fetchForeignTableInfo}.
|
|
455
|
+
*/
|
|
728
456
|
const renderForeignTableFooter = async (conn, oid, out) => {
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
* `pg_inherits`. Partition parents are excluded (they're rendered via
|
|
744
|
-
* `Partition of:` instead) inside the query builder.
|
|
745
|
-
*/
|
|
457
|
+
const q = fetchForeignTableInfo({ oid });
|
|
458
|
+
const rs = await conn.query(q.sql, q.params);
|
|
459
|
+
if (rs.rows.length === 0) return;
|
|
460
|
+
const row = rs.rows[0];
|
|
461
|
+
const server = cellToString(row[0] ?? "");
|
|
462
|
+
const ftoptions = row[1] === null || row[1] === void 0 ? "" : cellToString(row[1]);
|
|
463
|
+
if (server !== "") out.write(`Server: ${server}\n`);
|
|
464
|
+
if (ftoptions !== "") out.write(`FDW options: (${ftoptions})\n`);
|
|
465
|
+
};
|
|
466
|
+
/**
|
|
467
|
+
* Render `Inherits: <parent>[, ...]` for relations with parents in
|
|
468
|
+
* `pg_inherits`. Partition parents are excluded (they're rendered via
|
|
469
|
+
* `Partition of:` instead) inside the query builder.
|
|
470
|
+
*/
|
|
746
471
|
const renderInheritsSection = async (conn, oid, out) => {
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
*/
|
|
472
|
+
const q = fetchInherits({ oid });
|
|
473
|
+
const rs = await conn.query(q.sql, q.params);
|
|
474
|
+
if (rs.rows.length === 0) return;
|
|
475
|
+
const label = "Inherits";
|
|
476
|
+
const indent = " ".repeat(8);
|
|
477
|
+
rs.rows.forEach((r, idx) => {
|
|
478
|
+
const parent = cellToString(r[0]);
|
|
479
|
+
const prefix = idx === 0 ? `${label}: ` : `${indent} `;
|
|
480
|
+
const trailing = idx < rs.rows.length - 1 ? "," : "";
|
|
481
|
+
out.write(`${prefix}${parent}${trailing}\n`);
|
|
482
|
+
});
|
|
483
|
+
};
|
|
484
|
+
/**
|
|
485
|
+
* Render the child-relation footer for inheritance / partition parents.
|
|
486
|
+
*
|
|
487
|
+
* - Partitioned parents always emit a `Number of partitions: N` footer
|
|
488
|
+
* (even when zero, even in verbose mode); when verbose=false and N>0
|
|
489
|
+
* the footer adds the `(Use \d+ to list them.)` hint. Verbose mode
|
|
490
|
+
* replaces the count with a full `Partitions:` list including bounds.
|
|
491
|
+
* - Non-partition parents (regular tables) emit `Number of child
|
|
492
|
+
* tables: N (Use \d+ to list them.)` (non-verbose) or `Child tables:`
|
|
493
|
+
* list (verbose).
|
|
494
|
+
*/
|
|
771
495
|
const renderInheritedBySection = async (conn, oid, relkind, verbose, out) => {
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
});
|
|
814
|
-
};
|
|
815
|
-
/**
|
|
816
|
-
* Render `Replica Identity: <value>` when the relation's `relreplident`
|
|
817
|
-
* is non-default and non-INDEX. Upstream skips this footer entirely for
|
|
818
|
-
* the default value ('d' in user schemas, 'n' for pg_catalog relations);
|
|
819
|
-
* INDEX-mode (relreplident = 'i') is surfaced inline on the matching
|
|
820
|
-
* index line in the Indexes: section, so no footer is emitted there
|
|
821
|
-
* either.
|
|
822
|
-
*/
|
|
496
|
+
const isPartitioned = relkind === "p" || relkind === "I";
|
|
497
|
+
const q = fetchInheritedBy({
|
|
498
|
+
oid,
|
|
499
|
+
serverVersion: conn.serverVersion
|
|
500
|
+
});
|
|
501
|
+
const rs = await conn.query(q.sql, q.params);
|
|
502
|
+
const tuples = rs.rows.length;
|
|
503
|
+
if (isPartitioned && tuples === 0) {
|
|
504
|
+
out.write("Number of partitions: 0\n");
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
if (!verbose) {
|
|
508
|
+
if (tuples === 0) return;
|
|
509
|
+
if (isPartitioned) out.write(`Number of partitions: ${tuples} (Use \\d+ to list them.)\n`);
|
|
510
|
+
else out.write(`Number of child tables: ${tuples} (Use \\d+ to list them.)\n`);
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
const label = isPartitioned ? "Partitions" : "Child tables";
|
|
514
|
+
const indent = " ".repeat(label.length);
|
|
515
|
+
rs.rows.forEach((r, idx) => {
|
|
516
|
+
const relname = cellToString(r[0]);
|
|
517
|
+
const childKind = cellToString(r[1] ?? "");
|
|
518
|
+
const detached = parseBool(r[2]);
|
|
519
|
+
const bound = r[3] === null || r[3] === void 0 ? "" : cellToString(r[3]);
|
|
520
|
+
let line = `${idx === 0 ? `${label}: ` : `${indent} `}${relname}`;
|
|
521
|
+
if (bound !== "") line += ` ${bound}`;
|
|
522
|
+
if (childKind === "p" || childKind === "I") line += ", PARTITIONED";
|
|
523
|
+
else if (childKind === "f") line += ", FOREIGN";
|
|
524
|
+
if (detached) line += " (DETACH PENDING)";
|
|
525
|
+
if (idx < rs.rows.length - 1) line += ",";
|
|
526
|
+
out.write(`${line}\n`);
|
|
527
|
+
});
|
|
528
|
+
};
|
|
529
|
+
/**
|
|
530
|
+
* Render `Replica Identity: <value>` when the relation's `relreplident`
|
|
531
|
+
* is non-default and non-INDEX. Upstream skips this footer entirely for
|
|
532
|
+
* the default value ('d' in user schemas, 'n' for pg_catalog relations);
|
|
533
|
+
* INDEX-mode (relreplident = 'i') is surfaced inline on the matching
|
|
534
|
+
* index line in the Indexes: section, so no footer is emitted there
|
|
535
|
+
* either.
|
|
536
|
+
*/
|
|
823
537
|
const renderReplicaIdentitySection = (schema, relInfo, out) => {
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
: ri === "d"
|
|
838
|
-
? "NOTHING"
|
|
839
|
-
: ri === "n"
|
|
840
|
-
? "NOTHING"
|
|
841
|
-
: "???";
|
|
842
|
-
out.write(`Replica Identity: ${label}\n`);
|
|
843
|
-
};
|
|
844
|
-
/**
|
|
845
|
-
* Emit `Tablespace: "<name>"` when the relation has an explicit
|
|
846
|
-
* (non-default) tablespace. Only meaningful for relkinds that support
|
|
847
|
-
* tablespaces — caller enforces the relkind filter.
|
|
848
|
-
*/
|
|
538
|
+
const ri = relInfo.relreplident;
|
|
539
|
+
if (ri === "i") return;
|
|
540
|
+
const isCatalog = schema === "pg_catalog";
|
|
541
|
+
if (!isCatalog && ri === "d") return;
|
|
542
|
+
if (isCatalog && ri === "n") return;
|
|
543
|
+
const label = ri === "f" ? "FULL" : ri === "d" ? "NOTHING" : ri === "n" ? "NOTHING" : "???";
|
|
544
|
+
out.write(`Replica Identity: ${label}\n`);
|
|
545
|
+
};
|
|
546
|
+
/**
|
|
547
|
+
* Emit `Tablespace: "<name>"` when the relation has an explicit
|
|
548
|
+
* (non-default) tablespace. Only meaningful for relkinds that support
|
|
549
|
+
* tablespaces — caller enforces the relkind filter.
|
|
550
|
+
*/
|
|
849
551
|
const renderTablespaceFooter = (relkind, relInfo, out) => {
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
relkind === "I" ||
|
|
854
|
-
relkind === "p" ||
|
|
855
|
-
relkind === "t";
|
|
856
|
-
if (!tsSupported)
|
|
857
|
-
return;
|
|
858
|
-
if (relInfo.reltablespace === 0 || !relInfo.spcname)
|
|
859
|
-
return;
|
|
860
|
-
out.write(`Tablespace: "${relInfo.spcname}"\n`);
|
|
552
|
+
if (!(relkind === "r" || relkind === "m" || relkind === "i" || relkind === "I" || relkind === "p" || relkind === "t")) return;
|
|
553
|
+
if (relInfo.reltablespace === 0 || !relInfo.spcname) return;
|
|
554
|
+
out.write(`Tablespace: "${relInfo.spcname}"\n`);
|
|
861
555
|
};
|
|
862
556
|
/**
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
557
|
+
* Emit `Access method: <name>` when the relation has an explicit table
|
|
558
|
+
* access method (PG 12+). Indexes have their AM rendered inline within
|
|
559
|
+
* the index definition string, so this footer covers only
|
|
560
|
+
* tables / materialized views / partitioned tables.
|
|
561
|
+
*/
|
|
868
562
|
const renderAccessMethodFooter = (relInfo, out) => {
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
*/
|
|
563
|
+
if (relInfo.relam === 0 || !relInfo.amname) return;
|
|
564
|
+
out.write(`Access method: ${relInfo.amname}\n`);
|
|
565
|
+
};
|
|
566
|
+
/**
|
|
567
|
+
* Render `Indexes:\n "name" PRIMARY KEY, btree (col)` for each index
|
|
568
|
+
* on `oid`. Free-form section — not a table.
|
|
569
|
+
*
|
|
570
|
+
* When the relation has INDEX-mode replica identity (relreplident = 'i'),
|
|
571
|
+
* the corresponding index gets a trailing " REPLICA IDENTITY" marker on
|
|
572
|
+
* its line, matching upstream `\d` output. The marker comes from each
|
|
573
|
+
* index's own `pg_index.indisreplident` flag — only one index can carry
|
|
574
|
+
* it, so no follow-up footer is needed for INDEX-mode RI.
|
|
575
|
+
*/
|
|
883
576
|
const renderIndexesSection = async (conn, oid, out) => {
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
else if (isUnique) {
|
|
930
|
-
line += contype === "u" ? " UNIQUE CONSTRAINT," : " UNIQUE,";
|
|
931
|
-
}
|
|
932
|
-
line += ` ${tail}`;
|
|
933
|
-
if (condeferrable)
|
|
934
|
-
line += " DEFERRABLE";
|
|
935
|
-
if (condeferred)
|
|
936
|
-
line += " INITIALLY DEFERRED";
|
|
937
|
-
}
|
|
938
|
-
if (isClustered)
|
|
939
|
-
line += " CLUSTER";
|
|
940
|
-
if (!isValid)
|
|
941
|
-
line += " INVALID";
|
|
942
|
-
if (isReplIdent)
|
|
943
|
-
line += " REPLICA IDENTITY";
|
|
944
|
-
out.write(`${line}\n`);
|
|
945
|
-
}
|
|
946
|
-
};
|
|
947
|
-
/**
|
|
948
|
-
* Render `Check constraints:\n "name" CHECK (expr)` list.
|
|
949
|
-
*/
|
|
577
|
+
const sql = `SELECT c2.relname, i.indisprimary, i.indisunique, i.indisclustered,
|
|
578
|
+
i.indisvalid,
|
|
579
|
+
pg_catalog.pg_get_indexdef(i.indexrelid, 0, true),
|
|
580
|
+
pg_catalog.pg_get_constraintdef(con.oid, true),
|
|
581
|
+
contype, condeferrable, condeferred,
|
|
582
|
+
i.indisreplident,
|
|
583
|
+
c2.reltablespace
|
|
584
|
+
FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i
|
|
585
|
+
LEFT JOIN pg_catalog.pg_constraint con ON (conrelid = i.indrelid AND conindid = i.indexrelid AND contype IN ('p','u','x'))
|
|
586
|
+
WHERE c.oid = '${oid}' AND c.oid = i.indrelid AND i.indexrelid = c2.oid\nORDER BY i.indisprimary DESC, c2.relname;`;
|
|
587
|
+
const rs = await conn.query(sql, []);
|
|
588
|
+
if (rs.rows.length === 0) return;
|
|
589
|
+
out.write("Indexes:\n");
|
|
590
|
+
for (const r of rs.rows) {
|
|
591
|
+
const idxName = cellToString(r[0]);
|
|
592
|
+
const isPrimary = String(r[1]) === "t" || r[1] === true;
|
|
593
|
+
const isUnique = String(r[2]) === "t" || r[2] === true;
|
|
594
|
+
const isClustered = String(r[3]) === "t" || r[3] === true;
|
|
595
|
+
const isValid = String(r[4]) === "t" || r[4] === true;
|
|
596
|
+
const indexdef = cellToString(r[5]);
|
|
597
|
+
const constrDef = r[6] !== null ? cellToString(r[6]) : "";
|
|
598
|
+
const contype = r[7] === null ? "" : cellToString(r[7]);
|
|
599
|
+
const condeferrable = String(r[8]) === "t" || r[8] === true;
|
|
600
|
+
const condeferred = String(r[9]) === "t" || r[9] === true;
|
|
601
|
+
const isReplIdent = String(r[10]) === "t" || r[10] === true;
|
|
602
|
+
let line = ` "${idxName}"`;
|
|
603
|
+
const usingPos = indexdef.indexOf(" USING ");
|
|
604
|
+
const tail = usingPos >= 0 ? indexdef.slice(usingPos + 7) : indexdef;
|
|
605
|
+
if (contype === "x") line += ` ${constrDef}`;
|
|
606
|
+
else {
|
|
607
|
+
if (isPrimary) line += " PRIMARY KEY,";
|
|
608
|
+
else if (isUnique) line += contype === "u" ? " UNIQUE CONSTRAINT," : " UNIQUE,";
|
|
609
|
+
line += ` ${tail}`;
|
|
610
|
+
if (condeferrable) line += " DEFERRABLE";
|
|
611
|
+
if (condeferred) line += " INITIALLY DEFERRED";
|
|
612
|
+
}
|
|
613
|
+
if (isClustered) line += " CLUSTER";
|
|
614
|
+
if (!isValid) line += " INVALID";
|
|
615
|
+
if (isReplIdent) line += " REPLICA IDENTITY";
|
|
616
|
+
out.write(`${line}\n`);
|
|
617
|
+
}
|
|
618
|
+
};
|
|
619
|
+
/**
|
|
620
|
+
* Render `Check constraints:\n "name" CHECK (expr)` list.
|
|
621
|
+
*/
|
|
950
622
|
const renderCheckConstraintsSection = async (conn, oid, out) => {
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
* ` NO INHERIT`; inherited-only constraints (`conislocal = false`) are
|
|
970
|
-
* tagged ` (inherited)` to match vanilla `\d+`. On pre-PG-18 servers the
|
|
971
|
-
* query returns no rows, so the whole section is suppressed.
|
|
972
|
-
*/
|
|
623
|
+
const sql = `SELECT r.conname, pg_catalog.pg_get_constraintdef(r.oid, true)
|
|
624
|
+
FROM pg_catalog.pg_constraint r
|
|
625
|
+
WHERE r.conrelid = '${oid}' AND r.contype = 'c'\nORDER BY 1;`;
|
|
626
|
+
const rs = await conn.query(sql, []);
|
|
627
|
+
if (rs.rows.length === 0) return;
|
|
628
|
+
out.write("Check constraints:\n");
|
|
629
|
+
for (const r of rs.rows) out.write(` "${cellToString(r[0])}" ${cellToString(r[1])}\n`);
|
|
630
|
+
};
|
|
631
|
+
/**
|
|
632
|
+
* Render `Not-null constraints:\n "name" NOT NULL "col"[ NO INHERIT]`
|
|
633
|
+
* list (PG 18+ named NOT NULL constraints, `pg_constraint.contype = 'n'`).
|
|
634
|
+
*
|
|
635
|
+
* Upstream `describeOneTableDetails` (describe.c ~3104) emits one line per
|
|
636
|
+
* constraint in `attnum` order. `connoinherit` adds a trailing
|
|
637
|
+
* ` NO INHERIT`; inherited-only constraints (`conislocal = false`) are
|
|
638
|
+
* tagged ` (inherited)` to match vanilla `\d+`. On pre-PG-18 servers the
|
|
639
|
+
* query returns no rows, so the whole section is suppressed.
|
|
640
|
+
*/
|
|
973
641
|
const renderNotNullConstraintsSection = async (conn, oid, out) => {
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
/**
|
|
996
|
-
* Render `Foreign-key constraints:\n "name" FOREIGN KEY ...` list.
|
|
997
|
-
*/
|
|
642
|
+
const q = fetchNotNullConstraints({
|
|
643
|
+
oid,
|
|
644
|
+
serverVersion: conn.serverVersion
|
|
645
|
+
});
|
|
646
|
+
const rs = await conn.query(q.sql, q.params);
|
|
647
|
+
if (rs.rows.length === 0) return;
|
|
648
|
+
out.write("Not-null constraints:\n");
|
|
649
|
+
for (const r of rs.rows) {
|
|
650
|
+
const conname = cellToString(r[0]);
|
|
651
|
+
const attname = cellToString(r[1]);
|
|
652
|
+
const noInherit = parseBool(r[2]);
|
|
653
|
+
const isLocal = parseBool(r[3]);
|
|
654
|
+
let line = ` "${conname}" NOT NULL "${attname}"`;
|
|
655
|
+
if (noInherit) line += " NO INHERIT";
|
|
656
|
+
else if (!isLocal) line += " (inherited)";
|
|
657
|
+
out.write(`${line}\n`);
|
|
658
|
+
}
|
|
659
|
+
};
|
|
660
|
+
/**
|
|
661
|
+
* Render `Foreign-key constraints:\n "name" FOREIGN KEY ...` list.
|
|
662
|
+
*/
|
|
998
663
|
const renderForeignKeyConstraintsSection = async (conn, oid, out) => {
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
/**
|
|
1012
|
-
* Render `Referenced by:\n TABLE "..." CONSTRAINT "..." FOREIGN KEY ...`
|
|
1013
|
-
* (incoming FKs from other tables).
|
|
1014
|
-
*/
|
|
664
|
+
const sql = `SELECT conname, pg_catalog.pg_get_constraintdef(oid, true) AS condef
|
|
665
|
+
FROM pg_catalog.pg_constraint
|
|
666
|
+
WHERE conrelid = '${oid}' AND contype = 'f'\nORDER BY conname;`;
|
|
667
|
+
const rs = await conn.query(sql, []);
|
|
668
|
+
if (rs.rows.length === 0) return;
|
|
669
|
+
out.write("Foreign-key constraints:\n");
|
|
670
|
+
for (const r of rs.rows) out.write(` "${cellToString(r[0])}" ${cellToString(r[1])}\n`);
|
|
671
|
+
};
|
|
672
|
+
/**
|
|
673
|
+
* Render `Referenced by:\n TABLE "..." CONSTRAINT "..." FOREIGN KEY ...`
|
|
674
|
+
* (incoming FKs from other tables).
|
|
675
|
+
*/
|
|
1015
676
|
const renderReferencedBySection = async (conn, oid, out) => {
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
};
|
|
1029
|
-
/**
|
|
1030
|
-
* Render `Triggers:\n name AFTER ... EXECUTE ...` list.
|
|
1031
|
-
*/
|
|
677
|
+
const sql = `SELECT conname, conrelid::pg_catalog.regclass,
|
|
678
|
+
pg_catalog.pg_get_constraintdef(oid, true) AS condef
|
|
679
|
+
FROM pg_catalog.pg_constraint
|
|
680
|
+
WHERE confrelid = '${oid}' AND contype = 'f'\nORDER BY conname;`;
|
|
681
|
+
const rs = await conn.query(sql, []);
|
|
682
|
+
if (rs.rows.length === 0) return;
|
|
683
|
+
out.write("Referenced by:\n");
|
|
684
|
+
for (const r of rs.rows) out.write(` TABLE "${cellToString(r[1])}" CONSTRAINT "${cellToString(r[0])}" ${cellToString(r[2])}\n`);
|
|
685
|
+
};
|
|
686
|
+
/**
|
|
687
|
+
* Render `Triggers:\n name AFTER ... EXECUTE ...` list.
|
|
688
|
+
*/
|
|
1032
689
|
const renderTriggersSection = async (conn, oid, out) => {
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
*
|
|
1049
|
-
* Upstream concatenates the active "kinds" (ndistinct / dependencies / mcv)
|
|
1050
|
-
* inside parentheses; we preserve insertion order matching upstream.
|
|
1051
|
-
*/
|
|
690
|
+
const sql = `SELECT t.tgname, pg_catalog.pg_get_triggerdef(t.oid, true) AS tgdef, t.tgenabled
|
|
691
|
+
FROM pg_catalog.pg_trigger t
|
|
692
|
+
WHERE t.tgrelid = '${oid}' AND NOT t.tgisinternal\nORDER BY 1;`;
|
|
693
|
+
const rs = await conn.query(sql, []);
|
|
694
|
+
if (rs.rows.length === 0) return;
|
|
695
|
+
out.write("Triggers:\n");
|
|
696
|
+
for (const r of rs.rows) out.write(` ${cellToString(r[1])}\n`);
|
|
697
|
+
};
|
|
698
|
+
/**
|
|
699
|
+
* Render `Statistics objects:\n "schema"."name" (kinds) ON cols FROM tbl`
|
|
700
|
+
* for each `pg_statistic_ext` row on the relation. Verbose-only.
|
|
701
|
+
*
|
|
702
|
+
* Upstream concatenates the active "kinds" (ndistinct / dependencies / mcv)
|
|
703
|
+
* inside parentheses; we preserve insertion order matching upstream.
|
|
704
|
+
*/
|
|
1052
705
|
const renderStatisticsObjectsSection = async (conn, oid, out) => {
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
* Render `Publications:\n "name"` (one per row) for any publication
|
|
1082
|
-
* the relation belongs to (explicit, FOR ALL TABLES, or FOR ALL TABLES
|
|
1083
|
-
* IN SCHEMA). No-op when the result set is empty.
|
|
1084
|
-
*/
|
|
706
|
+
const q = fetchStatisticsObjects({
|
|
707
|
+
oid,
|
|
708
|
+
serverVersion: conn.serverVersion
|
|
709
|
+
});
|
|
710
|
+
const rs = await conn.query(q.sql, q.params);
|
|
711
|
+
if (rs.rows.length === 0) return;
|
|
712
|
+
out.write("Statistics objects:\n");
|
|
713
|
+
for (const r of rs.rows) {
|
|
714
|
+
const nsp = cellToString(r[0] ?? "");
|
|
715
|
+
const name = cellToString(r[1] ?? "");
|
|
716
|
+
const ndist = parseBool(r[2]);
|
|
717
|
+
const deps = parseBool(r[3]);
|
|
718
|
+
const mcv = parseBool(r[4]);
|
|
719
|
+
const columns = cellToString(r[5] ?? "");
|
|
720
|
+
const relname = cellToString(r[6] ?? "");
|
|
721
|
+
const kinds = [];
|
|
722
|
+
if (ndist) kinds.push("ndistinct");
|
|
723
|
+
if (deps) kinds.push("dependencies");
|
|
724
|
+
if (mcv) kinds.push("mcv");
|
|
725
|
+
const kindStr = kinds.length > 0 ? ` (${kinds.join(", ")})` : "";
|
|
726
|
+
out.write(` "${nsp}"."${name}"${kindStr} ON ${columns} FROM ${relname}\n`);
|
|
727
|
+
}
|
|
728
|
+
};
|
|
729
|
+
/**
|
|
730
|
+
* Render `Publications:\n "name"` (one per row) for any publication
|
|
731
|
+
* the relation belongs to (explicit, FOR ALL TABLES, or FOR ALL TABLES
|
|
732
|
+
* IN SCHEMA). No-op when the result set is empty.
|
|
733
|
+
*/
|
|
1085
734
|
const renderPublicationsSection = async (conn, oid, out) => {
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
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
|
-
*/
|
|
735
|
+
const q = fetchTablePublications({
|
|
736
|
+
oid,
|
|
737
|
+
serverVersion: conn.serverVersion
|
|
738
|
+
});
|
|
739
|
+
const rs = await conn.query(q.sql, q.params);
|
|
740
|
+
if (rs.rows.length === 0) return;
|
|
741
|
+
out.write("Publications:\n");
|
|
742
|
+
for (const r of rs.rows) out.write(` "${cellToString(r[0] ?? "")}"\n`);
|
|
743
|
+
};
|
|
744
|
+
/**
|
|
745
|
+
* Render `Excluded from publications:\n "name"` (one per row) for any
|
|
746
|
+
* FOR ALL TABLES publication that excludes this relation via an EXCEPT
|
|
747
|
+
* clause (PG 19+). No-op when the result set is empty, which includes all
|
|
748
|
+
* pre-PG-19 servers (the query builder returns an empty set there).
|
|
749
|
+
*/
|
|
1104
750
|
const renderExcludedFromPublicationsSection = async (conn, oid, out) => {
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
* access to `pg_subscription` — when the query fails with a permission
|
|
1120
|
-
* error, the section is silently omitted (mirroring upstream behaviour).
|
|
1121
|
-
*/
|
|
751
|
+
const q = fetchExcludedFromPublications({
|
|
752
|
+
oid,
|
|
753
|
+
serverVersion: conn.serverVersion
|
|
754
|
+
});
|
|
755
|
+
const rs = await conn.query(q.sql, q.params);
|
|
756
|
+
if (rs.rows.length === 0) return;
|
|
757
|
+
out.write("Excluded from publications:\n");
|
|
758
|
+
for (const r of rs.rows) out.write(` "${cellToString(r[0] ?? "")}"\n`);
|
|
759
|
+
};
|
|
760
|
+
/**
|
|
761
|
+
* Render `Subscriptions:\n "name"` (one per row). Requires superuser
|
|
762
|
+
* access to `pg_subscription` — when the query fails with a permission
|
|
763
|
+
* error, the section is silently omitted (mirroring upstream behaviour).
|
|
764
|
+
*/
|
|
1122
765
|
const renderSubscriptionsSection = async (conn, oid, out) => {
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
* Pre-fetch per-column FDW options for a foreign table and index them by
|
|
1145
|
-
* column name so the column-table renderer can fold them in inline.
|
|
1146
|
-
* Upstream renders these as a trailing "FDW options: (k 'v', ...)" cell
|
|
1147
|
-
* on each affected column row, not as a separate footer.
|
|
1148
|
-
*/
|
|
766
|
+
const q = fetchTableSubscriptions({
|
|
767
|
+
oid,
|
|
768
|
+
serverVersion: conn.serverVersion
|
|
769
|
+
});
|
|
770
|
+
let rs;
|
|
771
|
+
try {
|
|
772
|
+
rs = await conn.query(q.sql, q.params);
|
|
773
|
+
} catch (err) {
|
|
774
|
+
if (isPermissionDeniedError(err)) return;
|
|
775
|
+
throw err;
|
|
776
|
+
}
|
|
777
|
+
if (rs.rows.length === 0) return;
|
|
778
|
+
out.write("Subscriptions:\n");
|
|
779
|
+
for (const r of rs.rows) out.write(` "${cellToString(r[0] ?? "")}"\n`);
|
|
780
|
+
};
|
|
781
|
+
/**
|
|
782
|
+
* Pre-fetch per-column FDW options for a foreign table and index them by
|
|
783
|
+
* column name so the column-table renderer can fold them in inline.
|
|
784
|
+
* Upstream renders these as a trailing "FDW options: (k 'v', ...)" cell
|
|
785
|
+
* on each affected column row, not as a separate footer.
|
|
786
|
+
*/
|
|
1149
787
|
const fetchPerColumnFdwOptionsMap = async (conn, oid) => {
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
*/
|
|
788
|
+
const q = fetchPerColumnFdwOptions({ oid });
|
|
789
|
+
const rs = await conn.query(q.sql, q.params);
|
|
790
|
+
const m = /* @__PURE__ */ new Map();
|
|
791
|
+
for (const r of rs.rows) {
|
|
792
|
+
const attname = cellToString(r[0] ?? "");
|
|
793
|
+
const opts = cellToString(r[1] ?? "");
|
|
794
|
+
if (attname !== "" && opts !== "") m.set(attname, opts);
|
|
795
|
+
}
|
|
796
|
+
return m;
|
|
797
|
+
};
|
|
798
|
+
/**
|
|
799
|
+
* Render `Owning table: "schema.name"` for a TOAST relation. Matches
|
|
800
|
+
* upstream's `\d <toast>` footer — upstream always emits the qualified
|
|
801
|
+
* `"schema.name"` form (even for `pg_catalog` parents that would
|
|
802
|
+
* otherwise be elided by search_path), so we look up the nsp+rel pair
|
|
803
|
+
* directly rather than relying on regclass-cast text.
|
|
804
|
+
*/
|
|
1168
805
|
const renderToastOwningTableFooter = async (conn, oid, out) => {
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
/**
|
|
1188
|
-
* Detect a "permission denied" PostgresError (SQLSTATE 42501) on a
|
|
1189
|
-
* thrown value. We look at both `code` (SQLSTATE) and the message text
|
|
1190
|
-
* because not every transport layer surfaces the code. The check is
|
|
1191
|
-
* intentionally conservative — we only swallow genuine privilege
|
|
1192
|
-
* errors, not arbitrary failures.
|
|
1193
|
-
*/
|
|
806
|
+
const sql = `SELECT n.nspname, c.relname
|
|
807
|
+
FROM pg_catalog.pg_class c
|
|
808
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
809
|
+
WHERE c.reltoastrelid = '${oid}'\nLIMIT 1;`;
|
|
810
|
+
const rs = await conn.query(sql, []);
|
|
811
|
+
if (rs.rows.length === 0) return;
|
|
812
|
+
const nspname = cellToString(rs.rows[0][0] ?? "");
|
|
813
|
+
const relname = cellToString(rs.rows[0][1] ?? "");
|
|
814
|
+
if (relname === "") return;
|
|
815
|
+
out.write(`Owning table: "${nspname}.${relname}"\n`);
|
|
816
|
+
};
|
|
817
|
+
/**
|
|
818
|
+
* Detect a "permission denied" PostgresError (SQLSTATE 42501) on a
|
|
819
|
+
* thrown value. We look at both `code` (SQLSTATE) and the message text
|
|
820
|
+
* because not every transport layer surfaces the code. The check is
|
|
821
|
+
* intentionally conservative — we only swallow genuine privilege
|
|
822
|
+
* errors, not arbitrary failures.
|
|
823
|
+
*/
|
|
1194
824
|
const isPermissionDeniedError = (err) => {
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
if (rs.rows.length > 0) {
|
|
1269
|
-
out.write(cellToString(rs.rows[0][0]));
|
|
1270
|
-
out.write("\n");
|
|
1271
|
-
}
|
|
1272
|
-
};
|
|
1273
|
-
/**
|
|
1274
|
-
* `\sv <name>` — show view definition.
|
|
1275
|
-
*/
|
|
1276
|
-
export const describeOneViewDetails = async (conn, oid, schema, name, out, popt, verbose = false, hideCompression = false) => {
|
|
1277
|
-
// Use the table renderer for columns first (views have columns). In
|
|
1278
|
-
// verbose mode this also adds the Storage / Stats target / Description
|
|
1279
|
-
// columns, matching upstream `\d+ <view>`.
|
|
1280
|
-
await describeOneTableDetails(conn, oid, schema, name, "v", verbose, out, popt, false, hideCompression);
|
|
1281
|
-
// The "View definition:" footer (verbose-only) is emitted by
|
|
1282
|
-
// describeOneTableDetails as a table footer so it renders flush with the
|
|
1283
|
-
// column rows and gets the single trailing blank — matching upstream
|
|
1284
|
-
// `describeOneTableDetails` (describe.c ~3151/3175). Nothing more to do.
|
|
1285
|
-
};
|
|
1286
|
-
/**
|
|
1287
|
-
* Translate a relkind char into the canonical header psql uses for
|
|
1288
|
-
* `\d <name>`. Examples: 'r' → `Table "schema.name"`; 'v' → `View "..."`.
|
|
1289
|
-
*/
|
|
825
|
+
if (err === null || typeof err !== "object") return false;
|
|
826
|
+
const code = err.code;
|
|
827
|
+
if (typeof code === "string" && code === "42501") return true;
|
|
828
|
+
const message = err.message;
|
|
829
|
+
if (typeof message === "string" && /permission denied/i.test(message)) return true;
|
|
830
|
+
return false;
|
|
831
|
+
};
|
|
832
|
+
/**
|
|
833
|
+
* `\ds <name>` — sequence details. Renders the columns of pg_sequence
|
|
834
|
+
* plus the `Owned by:` footer if applicable.
|
|
835
|
+
*/
|
|
836
|
+
const describeOneSequence = async (conn, oid, schema, name, out, popt) => {
|
|
837
|
+
const sql = `SELECT pg_catalog.format_type(seqtypid, NULL) AS "Type",
|
|
838
|
+
seqstart AS "Start", seqmin AS "Minimum", seqmax AS "Maximum",
|
|
839
|
+
seqincrement AS "Increment",
|
|
840
|
+
CASE WHEN seqcycle THEN 'yes' ELSE 'no' END AS "Cycles?",
|
|
841
|
+
seqcache AS "Cache"
|
|
842
|
+
FROM pg_catalog.pg_sequence WHERE seqrelid = '${oid}';`;
|
|
843
|
+
const rs = await conn.query(sql, []);
|
|
844
|
+
const title = `Sequence "${schema}.${name}"`;
|
|
845
|
+
const ownedSql = `SELECT pg_catalog.quote_ident(nspname) || '.' || pg_catalog.quote_ident(relname) || '.' || pg_catalog.quote_ident(attname)
|
|
846
|
+
FROM pg_catalog.pg_class c
|
|
847
|
+
JOIN pg_catalog.pg_depend d ON c.oid = d.refobjid
|
|
848
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
849
|
+
JOIN pg_catalog.pg_attribute a ON a.attrelid = c.oid AND a.attnum = d.refobjsubid
|
|
850
|
+
WHERE d.classid = 'pg_catalog.pg_class'::regclass AND d.refclassid = 'pg_catalog.pg_class'::regclass
|
|
851
|
+
AND d.objid = '${oid}' AND d.deptype IN ('a', 'i');`;
|
|
852
|
+
const ownRs = await conn.query(ownedSql, []);
|
|
853
|
+
const footers = [];
|
|
854
|
+
if (ownRs.rows.length > 0) footers.push(`Owned by: ${cellToString(ownRs.rows[0][0])}`);
|
|
855
|
+
const pubQ = fetchSequencePublications({
|
|
856
|
+
oid,
|
|
857
|
+
serverVersion: conn.serverVersion
|
|
858
|
+
});
|
|
859
|
+
const pubRs = await conn.query(pubQ.sql, pubQ.params);
|
|
860
|
+
if (pubRs.rows.length > 0) {
|
|
861
|
+
const lines = pubRs.rows.map((r) => ` "${cellToString(r[0] ?? "")}"`).join("\n");
|
|
862
|
+
footers.push(`Included in publications:\n${lines}`);
|
|
863
|
+
}
|
|
864
|
+
const seqOpts = {
|
|
865
|
+
...popt,
|
|
866
|
+
title,
|
|
867
|
+
topt: {
|
|
868
|
+
...popt.topt,
|
|
869
|
+
title,
|
|
870
|
+
defaultFooter: false
|
|
871
|
+
},
|
|
872
|
+
footers: footers.length > 0 ? footers : null
|
|
873
|
+
};
|
|
874
|
+
await pickPrinterForFormat(seqOpts).printQuery(coerceResultSet(rs), seqOpts, out);
|
|
875
|
+
};
|
|
876
|
+
/**
|
|
877
|
+
* `\sf <name>` — show function definition (full CREATE FUNCTION).
|
|
878
|
+
* Renders the single-column result as raw text.
|
|
879
|
+
*/
|
|
880
|
+
const describeOneFunctionDetails = async (conn, oid, out) => {
|
|
881
|
+
const sql = `SELECT pg_catalog.pg_get_functiondef('${oid}'::pg_catalog.oid) AS def;`;
|
|
882
|
+
const rs = await conn.query(sql, []);
|
|
883
|
+
if (rs.rows.length > 0) {
|
|
884
|
+
out.write(cellToString(rs.rows[0][0]));
|
|
885
|
+
out.write("\n");
|
|
886
|
+
}
|
|
887
|
+
};
|
|
888
|
+
/**
|
|
889
|
+
* `\sv <name>` — show view definition.
|
|
890
|
+
*/
|
|
891
|
+
const describeOneViewDetails = async (conn, oid, schema, name, out, popt, verbose = false, hideCompression = false) => {
|
|
892
|
+
await describeOneTableDetails(conn, oid, schema, name, "v", verbose, out, popt, false, hideCompression);
|
|
893
|
+
};
|
|
894
|
+
/**
|
|
895
|
+
* Translate a relkind char into the canonical header psql uses for
|
|
896
|
+
* `\d <name>`. Examples: 'r' → `Table "schema.name"`; 'v' → `View "..."`.
|
|
897
|
+
*/
|
|
1290
898
|
const headerForRelkind = (relkind, schema, name) => {
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
return `Composite type "${schema}.${name}"`;
|
|
1312
|
-
default:
|
|
1313
|
-
return `Relation "${schema}.${name}"`;
|
|
1314
|
-
}
|
|
1315
|
-
};
|
|
1316
|
-
/**
|
|
1317
|
-
* Build a minimal {@link FieldDescription} for synthesized rows where
|
|
1318
|
-
* we don't actually have a wire-level row description. Used by the
|
|
1319
|
-
* columns table in `describeOneTableDetails` because we synthesize
|
|
1320
|
-
* the layout from pg_attribute data.
|
|
1321
|
-
*/
|
|
899
|
+
switch (relkind) {
|
|
900
|
+
case "r": return `Table "${schema}.${name}"`;
|
|
901
|
+
case "v": return `View "${schema}.${name}"`;
|
|
902
|
+
case "m": return `Materialized view "${schema}.${name}"`;
|
|
903
|
+
case "S": return `Sequence "${schema}.${name}"`;
|
|
904
|
+
case "i": return `Index "${schema}.${name}"`;
|
|
905
|
+
case "I": return `Partitioned index "${schema}.${name}"`;
|
|
906
|
+
case "p": return `Partitioned table "${schema}.${name}"`;
|
|
907
|
+
case "f": return `Foreign table "${schema}.${name}"`;
|
|
908
|
+
case "t": return `TOAST table "${schema}.${name}"`;
|
|
909
|
+
case "c": return `Composite type "${schema}.${name}"`;
|
|
910
|
+
default: return `Relation "${schema}.${name}"`;
|
|
911
|
+
}
|
|
912
|
+
};
|
|
913
|
+
/**
|
|
914
|
+
* Build a minimal {@link FieldDescription} for synthesized rows where
|
|
915
|
+
* we don't actually have a wire-level row description. Used by the
|
|
916
|
+
* columns table in `describeOneTableDetails` because we synthesize
|
|
917
|
+
* the layout from pg_attribute data.
|
|
918
|
+
*/
|
|
1322
919
|
const fakeField = (name) => ({
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
920
|
+
name,
|
|
921
|
+
tableID: 0,
|
|
922
|
+
columnID: 0,
|
|
923
|
+
dataTypeID: 25,
|
|
924
|
+
dataTypeSize: -1,
|
|
925
|
+
dataTypeModifier: -1,
|
|
926
|
+
format: 0
|
|
1330
927
|
});
|
|
928
|
+
//#endregion
|
|
929
|
+
export { describeOneFunctionDetails, describeOneSequence, describeOneTableDetails, describeOneViewDetails, lookupOneRelation, lookupRelations, runListQuery };
|