neonctl 2.22.2 → 2.23.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/README.md +84 -0
  2. package/analytics.js +5 -2
  3. package/commands/branches.js +9 -1
  4. package/commands/connection_string.js +9 -1
  5. package/commands/functions.js +268 -0
  6. package/commands/index.js +4 -0
  7. package/commands/neon_auth.js +1013 -0
  8. package/commands/projects.js +9 -1
  9. package/commands/psql.js +6 -1
  10. package/functions_api.js +43 -0
  11. package/package.json +15 -5
  12. package/psql/cli.js +51 -0
  13. package/psql/command/cmd_cond.js +437 -0
  14. package/psql/command/cmd_connect.js +815 -0
  15. package/psql/command/cmd_copy.js +1025 -0
  16. package/psql/command/cmd_describe.js +1810 -0
  17. package/psql/command/cmd_format.js +909 -0
  18. package/psql/command/cmd_io.js +2187 -0
  19. package/psql/command/cmd_lo.js +385 -0
  20. package/psql/command/cmd_meta.js +970 -0
  21. package/psql/command/cmd_misc.js +187 -0
  22. package/psql/command/cmd_pipeline.js +1141 -0
  23. package/psql/command/cmd_restrict.js +171 -0
  24. package/psql/command/cmd_show.js +751 -0
  25. package/psql/command/dispatch.js +343 -0
  26. package/psql/command/inputQueue.js +42 -0
  27. package/psql/command/shared.js +71 -0
  28. package/psql/complete/filenames.js +139 -0
  29. package/psql/complete/index.js +104 -0
  30. package/psql/complete/matcher.js +314 -0
  31. package/psql/complete/psqlVars.js +247 -0
  32. package/psql/complete/queries.js +491 -0
  33. package/psql/complete/rules.js +2387 -0
  34. package/psql/core/common.js +1250 -0
  35. package/psql/core/help.js +576 -0
  36. package/psql/core/mainloop.js +1353 -0
  37. package/psql/core/prompt.js +437 -0
  38. package/psql/core/settings.js +684 -0
  39. package/psql/core/sqlHelp.js +1066 -0
  40. package/psql/core/startup.js +840 -0
  41. package/psql/core/syncVars.js +116 -0
  42. package/psql/core/variables.js +287 -0
  43. package/psql/describe/formatters.js +1277 -0
  44. package/psql/describe/processNamePattern.js +270 -0
  45. package/psql/describe/queries.js +2373 -0
  46. package/psql/describe/versionGate.js +43 -0
  47. package/psql/index.js +2005 -0
  48. package/psql/io/history.js +299 -0
  49. package/psql/io/input.js +120 -0
  50. package/psql/io/lineEditor/buffer.js +323 -0
  51. package/psql/io/lineEditor/complete.js +227 -0
  52. package/psql/io/lineEditor/filename.js +159 -0
  53. package/psql/io/lineEditor/index.js +891 -0
  54. package/psql/io/lineEditor/keymap.js +738 -0
  55. package/psql/io/lineEditor/vt100.js +363 -0
  56. package/psql/io/pgpass.js +202 -0
  57. package/psql/io/pgservice.js +194 -0
  58. package/psql/io/psqlrc.js +422 -0
  59. package/psql/print/aligned.js +1756 -0
  60. package/psql/print/asciidoc.js +248 -0
  61. package/psql/print/crosstab.js +460 -0
  62. package/psql/print/csv.js +92 -0
  63. package/psql/print/html.js +258 -0
  64. package/psql/print/json.js +96 -0
  65. package/psql/print/latex.js +396 -0
  66. package/psql/print/pager.js +265 -0
  67. package/psql/print/troff.js +258 -0
  68. package/psql/print/unaligned.js +118 -0
  69. package/psql/print/units.js +135 -0
  70. package/psql/scanner/slash.js +513 -0
  71. package/psql/scanner/sql.js +910 -0
  72. package/psql/scanner/stringutils.js +390 -0
  73. package/psql/types/backslash.js +1 -0
  74. package/psql/types/connection.js +1 -0
  75. package/psql/types/index.js +7 -0
  76. package/psql/types/printer.js +1 -0
  77. package/psql/types/repl.js +1 -0
  78. package/psql/types/scanner.js +24 -0
  79. package/psql/types/settings.js +1 -0
  80. package/psql/types/variables.js +1 -0
  81. package/psql/wire/connection.js +2844 -0
  82. package/psql/wire/copy.js +108 -0
  83. package/psql/wire/notify.js +59 -0
  84. package/psql/wire/pipeline.js +519 -0
  85. package/psql/wire/protocol.js +466 -0
  86. package/psql/wire/sasl.js +296 -0
  87. package/psql/wire/tls.js +596 -0
  88. package/test_utils/fixtures.js +1 -0
  89. package/utils/esbuild.js +147 -0
  90. package/utils/psql.js +107 -11
  91. package/utils/zip.js +4 -0
  92. package/writer.js +1 -1
  93. package/commands/auth.test.js +0 -211
  94. package/commands/branches.test.js +0 -460
  95. package/commands/checkout.test.js +0 -170
  96. package/commands/connection_string.test.js +0 -196
  97. package/commands/data_api.test.js +0 -169
  98. package/commands/databases.test.js +0 -39
  99. package/commands/help.test.js +0 -9
  100. package/commands/init.test.js +0 -56
  101. package/commands/ip_allow.test.js +0 -59
  102. package/commands/link.test.js +0 -381
  103. package/commands/operations.test.js +0 -7
  104. package/commands/orgs.test.js +0 -7
  105. package/commands/projects.test.js +0 -144
  106. package/commands/psql.test.js +0 -49
  107. package/commands/roles.test.js +0 -37
  108. package/commands/set_context.test.js +0 -159
  109. package/commands/vpc_endpoints.test.js +0 -69
  110. package/context.test.js +0 -119
  111. package/env.test.js +0 -55
  112. package/utils/formats.test.js +0 -32
  113. package/writer.test.js +0 -104
@@ -0,0 +1,2373 @@
1
+ /**
2
+ * SQL builders for psql's `\d*` describe commands.
3
+ *
4
+ * Each exported function returns a {@link DescribeQuery} carrying the SQL the
5
+ * upstream C `describe.c` would have produced for the same `\d*` invocation,
6
+ * adapted for the active `serverVersion`. The header text (column aliases)
7
+ * matches upstream literally so that WP-20's formatter can rely on stable
8
+ * output column names.
9
+ *
10
+ * Pattern matching (the `processSQLNamePattern` flex routine in upstream) is
11
+ * NOT yet implemented; we accept a `pattern` option and emit a placeholder
12
+ * `(<col> ~ $1 OR $1 IS NULL)` clause where needed. WP-20 wires the real
13
+ * pattern parser. See TODO comments throughout.
14
+ *
15
+ * Translation policy: SQL whitespace is normalized but column aliases and
16
+ * catalog table/column names are byte-identical to upstream. Conditional
17
+ * branches in the C source collapse into TS conditional concatenation gated
18
+ * on `serverVersion`.
19
+ */
20
+ import { serverAtLeast, serverLess, PG_9_5, PG_9_6, PG_10, PG_11, PG_12, PG_13, PG_14, PG_15, PG_16, PG_17, PG_18, } from './versionGate.js';
21
+ /* ------------------------------------------------------------------ */
22
+ /* Shared helpers */
23
+ /* ------------------------------------------------------------------ */
24
+ /**
25
+ * Reproduces upstream `printACLColumn(buf, colname)`: emits the standard
26
+ * ACL pretty-printing expression with column alias "Access privileges".
27
+ */
28
+ const aclColumn = (colname) => `CASE WHEN pg_catalog.array_length(${colname}, 1) = 0 THEN '(none)'` +
29
+ ` ELSE pg_catalog.array_to_string(${colname}, E'\\n') END AS "Access privileges"`;
30
+ /**
31
+ * Placeholder pattern clause. WP-20 will replace these stubs with the real
32
+ * `processSQLNamePattern` port. For now the clause is a tautology so the
33
+ * query runs unfiltered; the pattern parameter is still threaded through
34
+ * `params` so callers do not need to change call sites later.
35
+ *
36
+ * @param hasWhere whether a WHERE clause already exists in the SQL
37
+ * @param schemaCol column expression for the schema name, or undefined
38
+ * @param nameCol column expression for the object name
39
+ */
40
+ const patternStub = (hasWhere, schemaCol, nameCol) => {
41
+ // TODO(WP-20): real pattern matching via processSQLNamePattern port.
42
+ void schemaCol;
43
+ void nameCol;
44
+ const join = hasWhere ? ' AND ' : 'WHERE ';
45
+ return `${join}true /* TODO(WP-20): pattern matching */\n`;
46
+ };
47
+ /**
48
+ * Per-argument pattern slot for `\df name argtype1 argtype2 …` and
49
+ * `\do name argtype1 argtype2`. Each slot emits a uniquely tagged
50
+ * placeholder so the dispatcher can substitute the i-th processed
51
+ * type-name pattern (matched against `t<i>.typname`, `nt<i>.nspname`,
52
+ * and `format_type(t<i>.oid, NULL)` like upstream).
53
+ */
54
+ const argPatternStub = (slot) => `true /* ARG_PATTERN_${slot} */`;
55
+ const orderBy = (cols) => `ORDER BY ${cols};`;
56
+ // Returns the base params for a describe query. Always empty: the pattern
57
+ // itself is threaded through `processSQLNamePattern` + `applyPattern` in
58
+ // cmd_describe.ts, which renders the conditions with their own `$N` slots
59
+ // and pushes the right values into params at runtime. Returning [pattern]
60
+ // here would leave $1 unreferenced in the final SQL (applyPattern shifts
61
+ // the conditions' $N by baseParams.length), causing the server to reject
62
+ // the Parse with "could not determine data type of parameter $1".
63
+ const params = () => [];
64
+ /* ------------------------------------------------------------------ */
65
+ /* \da — describeAggregates */
66
+ /* ------------------------------------------------------------------ */
67
+ export const describeAggregates = (opts) => {
68
+ const { showSystem, pattern, serverVersion } = opts;
69
+ let sql = '';
70
+ sql +=
71
+ 'SELECT n.nspname as "Schema",\n' +
72
+ ' p.proname AS "Name",\n' +
73
+ ' pg_catalog.format_type(p.prorettype, NULL) AS "Result data type",\n' +
74
+ ' CASE WHEN p.pronargs = 0\n' +
75
+ " THEN CAST('*' AS pg_catalog.text)\n" +
76
+ ' ELSE pg_catalog.pg_get_function_arguments(p.oid)\n' +
77
+ ' END AS "Argument data types",\n' +
78
+ ' pg_catalog.obj_description(p.oid, \'pg_proc\') as "Description"\n' +
79
+ 'FROM pg_catalog.pg_proc p\n' +
80
+ ' LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace\n';
81
+ sql += serverAtLeast(serverVersion, PG_11)
82
+ ? "WHERE p.prokind = 'a'\n"
83
+ : 'WHERE p.proisagg\n';
84
+ if (!showSystem && pattern === undefined) {
85
+ sql +=
86
+ " AND n.nspname <> 'pg_catalog'\n" +
87
+ " AND n.nspname <> 'information_schema'\n";
88
+ }
89
+ sql += patternStub(true, 'n.nspname', 'p.proname');
90
+ sql += orderBy('1, 2, 4');
91
+ return {
92
+ sql,
93
+ params: params(),
94
+ description: 'List of aggregate functions',
95
+ };
96
+ };
97
+ /* ------------------------------------------------------------------ */
98
+ /* \dA — describeAccessMethods */
99
+ /* ------------------------------------------------------------------ */
100
+ export const describeAccessMethods = (opts) => {
101
+ const { verbose, serverVersion } = opts;
102
+ if (serverLess(serverVersion, 9, 6)) {
103
+ return {
104
+ sql: '/* server < 9.6 does not support access methods */ SELECT 1 WHERE false;',
105
+ params: [],
106
+ description: 'List of access methods',
107
+ };
108
+ }
109
+ let sql = 'SELECT amname AS "Name",\n' +
110
+ " CASE amtype WHEN 'i' THEN 'Index' WHEN 't' THEN 'Table' END AS \"Type\"";
111
+ if (verbose) {
112
+ sql +=
113
+ ',\n amhandler AS "Handler",\n' +
114
+ ' pg_catalog.obj_description(oid, \'pg_am\') AS "Description"';
115
+ }
116
+ sql += '\nFROM pg_catalog.pg_am\n';
117
+ sql += patternStub(false, undefined, 'amname');
118
+ sql += orderBy('1');
119
+ return { sql, params: params(), description: 'List of access methods' };
120
+ };
121
+ /* ------------------------------------------------------------------ */
122
+ /* \db — describeTablespaces */
123
+ /* ------------------------------------------------------------------ */
124
+ export const describeTablespaces = (opts) => {
125
+ const { verbose } = opts;
126
+ let sql = 'SELECT spcname AS "Name",\n' +
127
+ ' pg_catalog.pg_get_userbyid(spcowner) AS "Owner",\n' +
128
+ ' pg_catalog.pg_tablespace_location(oid) AS "Location"';
129
+ if (verbose) {
130
+ sql += ',\n ' + aclColumn('spcacl');
131
+ sql +=
132
+ ',\n spcoptions AS "Options",\n' +
133
+ ' pg_catalog.pg_size_pretty(pg_catalog.pg_tablespace_size(oid)) AS "Size",\n' +
134
+ ' pg_catalog.shobj_description(oid, \'pg_tablespace\') AS "Description"';
135
+ }
136
+ sql += '\nFROM pg_catalog.pg_tablespace\n';
137
+ sql += patternStub(false, undefined, 'spcname');
138
+ sql += orderBy('1');
139
+ return { sql, params: params(), description: 'List of tablespaces' };
140
+ };
141
+ export const describeFunctions = (opts) => {
142
+ const { verbose, showSystem, pattern, serverVersion } = opts;
143
+ const functypes = opts.functypes ?? '';
144
+ const argPatterns = opts.argPatterns ?? [];
145
+ let showAgg = functypes.includes('a');
146
+ let showNorm = functypes.includes('n');
147
+ let showProc = functypes.includes('p');
148
+ let showTrig = functypes.includes('t');
149
+ let showWin = functypes.includes('w');
150
+ if (!showAgg && !showNorm && !showProc && !showTrig && !showWin) {
151
+ showAgg = showNorm = showTrig = showWin = true;
152
+ if (serverAtLeast(serverVersion, PG_11))
153
+ showProc = true;
154
+ }
155
+ let sql = 'SELECT n.nspname as "Schema",\n p.proname as "Name",\n';
156
+ if (serverAtLeast(serverVersion, PG_11)) {
157
+ sql +=
158
+ ' pg_catalog.pg_get_function_result(p.oid) as "Result data type",\n' +
159
+ ' pg_catalog.pg_get_function_arguments(p.oid) as "Argument data types",\n' +
160
+ ' CASE p.prokind\n' +
161
+ " WHEN 'a' THEN 'agg'\n" +
162
+ " WHEN 'w' THEN 'window'\n" +
163
+ " WHEN 'p' THEN 'proc'\n" +
164
+ " ELSE 'func'\n" +
165
+ ' END as "Type"';
166
+ }
167
+ else {
168
+ sql +=
169
+ ' pg_catalog.pg_get_function_result(p.oid) as "Result data type",\n' +
170
+ ' pg_catalog.pg_get_function_arguments(p.oid) as "Argument data types",\n' +
171
+ ' CASE\n' +
172
+ " WHEN p.proisagg THEN 'agg'\n" +
173
+ " WHEN p.proiswindow THEN 'window'\n" +
174
+ " WHEN p.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype THEN 'trigger'\n" +
175
+ " ELSE 'func'\n" +
176
+ ' END as "Type"';
177
+ }
178
+ if (verbose) {
179
+ sql +=
180
+ ',\n CASE\n' +
181
+ " WHEN p.provolatile = 'i' THEN 'immutable'\n" +
182
+ " WHEN p.provolatile = 's' THEN 'stable'\n" +
183
+ " WHEN p.provolatile = 'v' THEN 'volatile'\n" +
184
+ ' END as "Volatility"';
185
+ if (serverAtLeast(serverVersion, PG_9_6)) {
186
+ sql +=
187
+ ',\n CASE\n' +
188
+ " WHEN p.proparallel = 'r' THEN 'restricted'\n" +
189
+ " WHEN p.proparallel = 's' THEN 'safe'\n" +
190
+ " WHEN p.proparallel = 'u' THEN 'unsafe'\n" +
191
+ ' END as "Parallel"';
192
+ }
193
+ sql +=
194
+ ',\n pg_catalog.pg_get_userbyid(p.proowner) as "Owner"' +
195
+ ",\n CASE WHEN prosecdef THEN 'definer' ELSE 'invoker' END AS \"Security\"";
196
+ // PG 18 added a "Leakproof?" column to verbose `\df+`. The pg_proc
197
+ // catalog has carried `proleakproof` since 9.2, but upstream's psql
198
+ // only began surfacing it in 18.
199
+ if (serverAtLeast(serverVersion, PG_18)) {
200
+ sql +=
201
+ ",\n CASE WHEN p.proleakproof THEN 'yes' ELSE 'no' END as \"Leakproof?\"";
202
+ }
203
+ sql += ',\n ' + aclColumn('p.proacl');
204
+ sql +=
205
+ ',\n l.lanname as "Language"' +
206
+ ",\n CASE WHEN l.lanname IN ('internal', 'c') THEN p.prosrc END as \"Internal name\"" +
207
+ ',\n pg_catalog.obj_description(p.oid, \'pg_proc\') as "Description"';
208
+ }
209
+ sql +=
210
+ '\nFROM pg_catalog.pg_proc p' +
211
+ '\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace\n';
212
+ // Upstream emits the per-argument joins *before* the verbose-language
213
+ // join so that all `t<i>`/`nt<i>` tables exist by the time the WHERE
214
+ // pulls in per-arg conditions. See describe.c::describeFunctions.
215
+ for (let i = 0; i < argPatterns.length; i++) {
216
+ sql +=
217
+ ` LEFT JOIN pg_catalog.pg_type t${i} ON t${i}.oid = p.proargtypes[${i}]\n` +
218
+ ` LEFT JOIN pg_catalog.pg_namespace nt${i} ON nt${i}.oid = t${i}.typnamespace\n`;
219
+ }
220
+ if (verbose) {
221
+ sql += ' LEFT JOIN pg_catalog.pg_language l ON l.oid = p.prolang\n';
222
+ }
223
+ let hasWhere = false;
224
+ const allTypes = showAgg && showNorm && showProc && showTrig && showWin;
225
+ if (!allTypes && showNorm) {
226
+ if (!showAgg) {
227
+ sql += hasWhere ? ' AND ' : 'WHERE ';
228
+ hasWhere = true;
229
+ sql += serverAtLeast(serverVersion, PG_11)
230
+ ? "p.prokind <> 'a'\n"
231
+ : 'NOT p.proisagg\n';
232
+ }
233
+ if (!showProc && serverAtLeast(serverVersion, PG_11)) {
234
+ sql += hasWhere ? ' AND ' : 'WHERE ';
235
+ hasWhere = true;
236
+ sql += "p.prokind <> 'p'\n";
237
+ }
238
+ if (!showTrig) {
239
+ sql += hasWhere ? ' AND ' : 'WHERE ';
240
+ hasWhere = true;
241
+ sql += "p.prorettype <> 'pg_catalog.trigger'::pg_catalog.regtype\n";
242
+ }
243
+ if (!showWin) {
244
+ sql += hasWhere ? ' AND ' : 'WHERE ';
245
+ hasWhere = true;
246
+ sql += serverAtLeast(serverVersion, PG_11)
247
+ ? "p.prokind <> 'w'\n"
248
+ : 'NOT p.proiswindow\n';
249
+ }
250
+ }
251
+ else if (!allTypes) {
252
+ sql += 'WHERE (\n ';
253
+ hasWhere = true;
254
+ const parts = [];
255
+ if (showAgg) {
256
+ parts.push(serverAtLeast(serverVersion, PG_11) ? "p.prokind = 'a'" : 'p.proisagg');
257
+ }
258
+ if (showTrig) {
259
+ parts.push("p.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype");
260
+ }
261
+ if (showProc)
262
+ parts.push("p.prokind = 'p'");
263
+ if (showWin) {
264
+ parts.push(serverAtLeast(serverVersion, PG_11)
265
+ ? "p.prokind = 'w'"
266
+ : 'p.proiswindow');
267
+ }
268
+ sql += parts.join('\n OR ') + '\n )\n';
269
+ }
270
+ sql += patternStub(hasWhere, 'n.nspname', 'p.proname');
271
+ // Upstream's per-argument filter loop. `-` means "no type in this
272
+ // slot" (function has fewer args); anything else is a type-name
273
+ // pattern matched against typname/format_type — same shape as `\dT`.
274
+ // Each slot emits a uniquely tagged placeholder so cmd_describe.ts
275
+ // can splice in the appropriate per-arg conditions.
276
+ for (let i = 0; i < argPatterns.length; i++) {
277
+ if (argPatterns[i] === '-') {
278
+ sql += ` AND t${i}.typname IS NULL\n`;
279
+ }
280
+ else {
281
+ sql += ` AND ${argPatternStub(i)}\n`;
282
+ }
283
+ }
284
+ if (!showSystem && pattern === undefined) {
285
+ sql +=
286
+ " AND n.nspname <> 'pg_catalog'\n" +
287
+ " AND n.nspname <> 'information_schema'\n";
288
+ }
289
+ sql += orderBy('1, 2, 4');
290
+ return { sql, params: params(), description: 'List of functions' };
291
+ };
292
+ /* ------------------------------------------------------------------ */
293
+ /* \dT — describeTypes */
294
+ /* ------------------------------------------------------------------ */
295
+ export const describeTypes = (opts) => {
296
+ const { verbose, showSystem, pattern } = opts;
297
+ let sql = 'SELECT n.nspname as "Schema",\n pg_catalog.format_type(t.oid, NULL) AS "Name",\n';
298
+ if (verbose) {
299
+ sql +=
300
+ ' t.typname AS "Internal name",\n' +
301
+ ' CASE WHEN t.typrelid != 0\n' +
302
+ " THEN CAST('tuple' AS pg_catalog.text)\n" +
303
+ ' WHEN t.typlen < 0\n' +
304
+ " THEN CAST('var' AS pg_catalog.text)\n" +
305
+ ' ELSE CAST(t.typlen AS pg_catalog.text)\n' +
306
+ ' END AS "Size",\n' +
307
+ ' pg_catalog.array_to_string(\n' +
308
+ ' ARRAY(\n' +
309
+ ' SELECT e.enumlabel\n' +
310
+ ' FROM pg_catalog.pg_enum e\n' +
311
+ ' WHERE e.enumtypid = t.oid\n' +
312
+ ' ORDER BY e.enumsortorder\n' +
313
+ ' ),\n' +
314
+ " E'\\n'\n" +
315
+ ' ) AS "Elements",\n' +
316
+ ' pg_catalog.pg_get_userbyid(t.typowner) AS "Owner",\n ' +
317
+ aclColumn('t.typacl') +
318
+ ',\n ';
319
+ }
320
+ sql += ' pg_catalog.obj_description(t.oid, \'pg_type\') as "Description"\n';
321
+ sql +=
322
+ 'FROM pg_catalog.pg_type t\n' +
323
+ ' LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace\n';
324
+ sql += 'WHERE (t.typrelid = 0 ';
325
+ sql +=
326
+ "OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid))\n";
327
+ if (!pattern?.includes('[]')) {
328
+ sql +=
329
+ ' AND NOT EXISTS(SELECT 1 FROM pg_catalog.pg_type el WHERE el.oid = t.typelem AND el.typarray = t.oid)\n';
330
+ }
331
+ if (!showSystem && pattern === undefined) {
332
+ sql +=
333
+ " AND n.nspname <> 'pg_catalog'\n" +
334
+ " AND n.nspname <> 'information_schema'\n";
335
+ }
336
+ sql += patternStub(true, 'n.nspname', 't.typname');
337
+ sql += orderBy('1, 2');
338
+ return { sql, params: params(), description: 'List of data types' };
339
+ };
340
+ export const describeOperators = (opts) => {
341
+ const { verbose, showSystem, pattern, serverVersion } = opts;
342
+ let argPatterns = opts.argPatterns ?? [];
343
+ // Upstream caps at 2 — extra args past two are silently dropped.
344
+ if (argPatterns.length > 2)
345
+ argPatterns = argPatterns.slice(0, 2);
346
+ let sql = 'SELECT n.nspname as "Schema",\n' +
347
+ ' o.oprname AS "Name",\n' +
348
+ ' CASE WHEN o.oprkind=\'l\' THEN NULL ELSE pg_catalog.format_type(o.oprleft, NULL) END AS "Left arg type",\n' +
349
+ ' CASE WHEN o.oprkind=\'r\' THEN NULL ELSE pg_catalog.format_type(o.oprright, NULL) END AS "Right arg type",\n' +
350
+ ' pg_catalog.format_type(o.oprresult, NULL) AS "Result type",\n';
351
+ if (verbose) {
352
+ sql += ' o.oprcode AS "Function",\n';
353
+ // PG 18 added "Leakproof?" to verbose `\do+`. Earlier servers only
354
+ // emitted Function and Description.
355
+ if (serverAtLeast(serverVersion, PG_18)) {
356
+ sql +=
357
+ " CASE WHEN p.proleakproof THEN 'yes' ELSE 'no' END AS \"Leakproof?\",\n";
358
+ }
359
+ }
360
+ sql +=
361
+ " coalesce(pg_catalog.obj_description(o.oid, 'pg_operator'),\n" +
362
+ ' pg_catalog.obj_description(o.oprcode, \'pg_proc\')) AS "Description"\n' +
363
+ 'FROM pg_catalog.pg_operator o\n' +
364
+ ' LEFT JOIN pg_catalog.pg_namespace n ON n.oid = o.oprnamespace\n';
365
+ // Upstream emits arg-type joins before the verbose `pg_proc` join.
366
+ // With one arg pattern: only join the right-hand side (oprright);
367
+ // with two: both oprleft/oprright. Tag indices match the join name
368
+ // so `t0/nt0` correspond to the first arg pattern slot.
369
+ if (argPatterns.length === 1) {
370
+ sql +=
371
+ ' LEFT JOIN pg_catalog.pg_type t0 ON t0.oid = o.oprright\n' +
372
+ ' LEFT JOIN pg_catalog.pg_namespace nt0 ON nt0.oid = t0.typnamespace\n';
373
+ }
374
+ else if (argPatterns.length >= 2) {
375
+ sql +=
376
+ ' LEFT JOIN pg_catalog.pg_type t0 ON t0.oid = o.oprleft\n' +
377
+ ' LEFT JOIN pg_catalog.pg_namespace nt0 ON nt0.oid = t0.typnamespace\n' +
378
+ ' LEFT JOIN pg_catalog.pg_type t1 ON t1.oid = o.oprright\n' +
379
+ ' LEFT JOIN pg_catalog.pg_namespace nt1 ON nt1.oid = t1.typnamespace\n';
380
+ }
381
+ if (verbose && serverAtLeast(serverVersion, PG_18)) {
382
+ // Only joined when we need proleakproof from pg_proc (PG 18+).
383
+ sql += ' LEFT JOIN pg_catalog.pg_proc p ON p.oid = o.oprcode\n';
384
+ }
385
+ let hasWhere = false;
386
+ if (!showSystem && pattern === undefined) {
387
+ sql +=
388
+ "WHERE n.nspname <> 'pg_catalog'\n AND n.nspname <> 'information_schema'\n";
389
+ hasWhere = true;
390
+ }
391
+ sql += patternStub(hasWhere, 'n.nspname', 'o.oprname');
392
+ // With a single arg pattern, upstream additionally constrains the
393
+ // operator to be unary-right (oprleft = 0) so unrelated binary
394
+ // operators don't leak through the `t0` join.
395
+ if (argPatterns.length === 1) {
396
+ sql += ' AND o.oprleft = 0\n';
397
+ }
398
+ for (let i = 0; i < argPatterns.length; i++) {
399
+ if (argPatterns[i] === '-') {
400
+ sql += ` AND t${i}.typname IS NULL\n`;
401
+ }
402
+ else {
403
+ sql += ` AND ${argPatternStub(i)}\n`;
404
+ }
405
+ }
406
+ sql += orderBy('1, 2, 3, 4');
407
+ return { sql, params: params(), description: 'List of operators' };
408
+ };
409
+ /* ------------------------------------------------------------------ */
410
+ /* \l / \list — listAllDbs */
411
+ /* ------------------------------------------------------------------ */
412
+ export const listAllDbs = (opts) => {
413
+ const { verbose, serverVersion } = opts;
414
+ let sql = 'SELECT\n' +
415
+ ' d.datname as "Name",\n' +
416
+ ' pg_catalog.pg_get_userbyid(d.datdba) as "Owner",\n' +
417
+ ' pg_catalog.pg_encoding_to_char(d.encoding) as "Encoding",\n';
418
+ if (serverAtLeast(serverVersion, PG_15)) {
419
+ sql +=
420
+ ' CASE d.datlocprovider' +
421
+ " WHEN 'b' THEN 'builtin'" +
422
+ " WHEN 'c' THEN 'libc'" +
423
+ " WHEN 'i' THEN 'icu'" +
424
+ ' END AS "Locale Provider",\n';
425
+ }
426
+ else {
427
+ sql += ' \'libc\' AS "Locale Provider",\n';
428
+ }
429
+ sql += ' d.datcollate as "Collate",\n d.datctype as "Ctype",\n';
430
+ if (serverAtLeast(serverVersion, PG_17)) {
431
+ sql += ' d.datlocale as "Locale",\n';
432
+ }
433
+ else if (serverAtLeast(serverVersion, PG_15)) {
434
+ sql += ' d.daticulocale as "Locale",\n';
435
+ }
436
+ else {
437
+ sql += ' NULL as "Locale",\n';
438
+ }
439
+ if (serverAtLeast(serverVersion, PG_16)) {
440
+ sql += ' d.daticurules as "ICU Rules",\n';
441
+ }
442
+ else {
443
+ sql += ' NULL as "ICU Rules",\n';
444
+ }
445
+ sql += ' ' + aclColumn('d.datacl');
446
+ if (verbose) {
447
+ sql +=
448
+ ",\n CASE WHEN pg_catalog.has_database_privilege(d.datname, 'CONNECT')\n" +
449
+ ' THEN pg_catalog.pg_size_pretty(pg_catalog.pg_database_size(d.datname))\n' +
450
+ " ELSE 'No Access'\n" +
451
+ ' END as "Size"' +
452
+ ',\n t.spcname as "Tablespace"' +
453
+ ',\n pg_catalog.shobj_description(d.oid, \'pg_database\') as "Description"';
454
+ }
455
+ sql += '\nFROM pg_catalog.pg_database d\n';
456
+ if (verbose) {
457
+ sql += ' JOIN pg_catalog.pg_tablespace t on d.dattablespace = t.oid\n';
458
+ }
459
+ if (opts.pattern !== undefined) {
460
+ sql += patternStub(false, undefined, 'd.datname');
461
+ }
462
+ sql += orderBy('1');
463
+ return { sql, params: params(), description: 'List of databases' };
464
+ };
465
+ /* ------------------------------------------------------------------ */
466
+ /* \dp / \z — permissionsList */
467
+ /* ------------------------------------------------------------------ */
468
+ export const permissionsList = (opts) => {
469
+ const { showSystem, pattern, serverVersion } = opts;
470
+ let sql = 'SELECT n.nspname as "Schema",\n' +
471
+ ' c.relname as "Name",\n' +
472
+ ' CASE c.relkind' +
473
+ " WHEN 'r' THEN 'table'" +
474
+ " WHEN 'v' THEN 'view'" +
475
+ " WHEN 'm' THEN 'materialized view'" +
476
+ " WHEN 'S' THEN 'sequence'" +
477
+ " WHEN 'f' THEN 'foreign table'" +
478
+ " WHEN 'p' THEN 'partitioned table'" +
479
+ ' END as "Type",\n ';
480
+ sql += aclColumn('c.relacl');
481
+ sql +=
482
+ ',\n pg_catalog.array_to_string(ARRAY(\n' +
483
+ " SELECT attname || E':\\n ' || pg_catalog.array_to_string(attacl, E'\\n ')\n" +
484
+ ' FROM pg_catalog.pg_attribute a\n' +
485
+ ' WHERE attrelid = c.oid AND NOT attisdropped AND attacl IS NOT NULL\n' +
486
+ ' ), E\'\\n\') AS "Column privileges"';
487
+ if (serverAtLeast(serverVersion, PG_10)) {
488
+ sql +=
489
+ ',\n pg_catalog.array_to_string(ARRAY(\n' +
490
+ ' SELECT polname\n' +
491
+ " || CASE WHEN NOT polpermissive THEN E' (RESTRICTIVE)' ELSE '' END\n" +
492
+ " || CASE WHEN polcmd != '*' THEN E' (' || polcmd::pg_catalog.text || E'):' ELSE E':' END\n" +
493
+ " || CASE WHEN polqual IS NOT NULL THEN E'\\n (u): ' || pg_catalog.pg_get_expr(polqual, polrelid) ELSE E'' END\n" +
494
+ " || CASE WHEN polwithcheck IS NOT NULL THEN E'\\n (c): ' || pg_catalog.pg_get_expr(polwithcheck, polrelid) ELSE E'' END\n" +
495
+ " || CASE WHEN polroles <> '{0}' THEN E'\\n to: ' || pg_catalog.array_to_string(\n" +
496
+ ' ARRAY(SELECT rolname FROM pg_catalog.pg_roles WHERE oid = ANY (polroles) ORDER BY 1)\n' +
497
+ " , E', ') ELSE E'' END\n" +
498
+ ' FROM pg_catalog.pg_policy pol\n' +
499
+ ' WHERE polrelid = c.oid), E\'\\n\') AS "Policies"';
500
+ }
501
+ else if (serverAtLeast(serverVersion, PG_9_5)) {
502
+ sql +=
503
+ ',\n pg_catalog.array_to_string(ARRAY(\n' +
504
+ ' SELECT polname\n' +
505
+ " || CASE WHEN polcmd != '*' THEN E' (' || polcmd::pg_catalog.text || E'):' ELSE E':' END\n" +
506
+ " || CASE WHEN polqual IS NOT NULL THEN E'\\n (u): ' || pg_catalog.pg_get_expr(polqual, polrelid) ELSE E'' END\n" +
507
+ " || CASE WHEN polwithcheck IS NOT NULL THEN E'\\n (c): ' || pg_catalog.pg_get_expr(polwithcheck, polrelid) ELSE E'' END\n" +
508
+ " || CASE WHEN polroles <> '{0}' THEN E'\\n to: ' || pg_catalog.array_to_string(\n" +
509
+ ' ARRAY(SELECT rolname FROM pg_catalog.pg_roles WHERE oid = ANY (polroles) ORDER BY 1)\n' +
510
+ " , E', ') ELSE E'' END\n" +
511
+ ' FROM pg_catalog.pg_policy pol\n' +
512
+ ' WHERE polrelid = c.oid), E\'\\n\') AS "Policies"';
513
+ }
514
+ sql +=
515
+ '\nFROM pg_catalog.pg_class c\n' +
516
+ ' LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n' +
517
+ "WHERE c.relkind IN ('r','v','m','S','f','p')\n";
518
+ if (!showSystem && pattern === undefined) {
519
+ sql +=
520
+ " AND n.nspname <> 'pg_catalog'\n AND n.nspname <> 'information_schema'\n";
521
+ }
522
+ sql += patternStub(true, 'n.nspname', 'c.relname');
523
+ sql += orderBy('1, 2');
524
+ return { sql, params: params(), description: 'Access privileges' };
525
+ };
526
+ /* ------------------------------------------------------------------ */
527
+ /* \ddp — listDefaultACLs */
528
+ /* ------------------------------------------------------------------ */
529
+ export const listDefaultACLs = (opts) => {
530
+ const { serverVersion } = opts;
531
+ // PG 18 added DEFACLOBJ_LARGEOBJECT ('L'); pre-18 servers can never
532
+ // produce that defaclobjtype value, so emitting the WHEN is harmless
533
+ // there — but to mirror upstream `\ddp` output, we gate it.
534
+ let sql = 'SELECT pg_catalog.pg_get_userbyid(d.defaclrole) AS "Owner",\n' +
535
+ ' n.nspname AS "Schema",\n' +
536
+ ' CASE d.defaclobjtype' +
537
+ " WHEN 'r' THEN 'table'" +
538
+ " WHEN 'S' THEN 'sequence'" +
539
+ " WHEN 'f' THEN 'function'" +
540
+ " WHEN 'T' THEN 'type'" +
541
+ " WHEN 'n' THEN 'schema'";
542
+ if (serverAtLeast(serverVersion, PG_18)) {
543
+ sql += " WHEN 'L' THEN 'large object'";
544
+ }
545
+ sql += ' END AS "Type",\n ';
546
+ sql += aclColumn('d.defaclacl');
547
+ sql +=
548
+ '\nFROM pg_catalog.pg_default_acl d\n' +
549
+ ' LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.defaclnamespace\n';
550
+ sql += patternStub(false, 'n.nspname', 'pg_catalog.pg_get_userbyid(d.defaclrole)');
551
+ sql += orderBy('1, 2, 3');
552
+ return {
553
+ sql,
554
+ params: params(),
555
+ description: 'Default access privileges',
556
+ };
557
+ };
558
+ /* ------------------------------------------------------------------ */
559
+ /* \dd — objectDescription */
560
+ /* ------------------------------------------------------------------ */
561
+ export const objectDescription = (opts) => {
562
+ const sysPart = (objLabel) => !opts.showSystem && opts.pattern === undefined
563
+ ? `WHERE n.nspname <> 'pg_catalog' AND n.nspname <> 'information_schema'` +
564
+ ` /* ${objLabel} */ `
565
+ : `WHERE true /* ${objLabel} */ `;
566
+ let sql = 'SELECT DISTINCT tt.nspname AS "Schema", tt.name AS "Name", tt.object AS "Object", d.description AS "Description"\n' +
567
+ 'FROM (\n';
568
+ sql +=
569
+ ' SELECT pgc.oid as oid, pgc.tableoid AS tableoid, n.nspname as nspname,\n' +
570
+ ' CAST(pgc.conname AS pg_catalog.text) as name,' +
571
+ " CAST('table constraint' AS pg_catalog.text) as object\n" +
572
+ ' FROM pg_catalog.pg_constraint pgc\n' +
573
+ ' JOIN pg_catalog.pg_class c ON c.oid = pgc.conrelid\n' +
574
+ ' LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n' +
575
+ sysPart('table constraint') +
576
+ '\n';
577
+ sql +=
578
+ 'UNION ALL\n' +
579
+ ' SELECT pgc.oid, pgc.tableoid, n.nspname,\n' +
580
+ ' CAST(pgc.conname AS pg_catalog.text),' +
581
+ " CAST('domain constraint' AS pg_catalog.text)\n" +
582
+ ' FROM pg_catalog.pg_constraint pgc\n' +
583
+ ' JOIN pg_catalog.pg_type t ON t.oid = pgc.contypid\n' +
584
+ ' LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace\n' +
585
+ sysPart('domain constraint') +
586
+ '\n';
587
+ sql +=
588
+ 'UNION ALL\n' +
589
+ ' SELECT o.oid, o.tableoid, n.nspname,\n' +
590
+ ' CAST(o.opcname AS pg_catalog.text),' +
591
+ " CAST('operator class' AS pg_catalog.text)\n" +
592
+ ' FROM pg_catalog.pg_opclass o\n' +
593
+ ' JOIN pg_catalog.pg_am am ON o.opcmethod = am.oid\n' +
594
+ ' JOIN pg_catalog.pg_namespace n ON n.oid = o.opcnamespace\n' +
595
+ sysPart('operator class') +
596
+ '\n';
597
+ sql +=
598
+ 'UNION ALL\n' +
599
+ ' SELECT opf.oid, opf.tableoid, n.nspname,\n' +
600
+ ' CAST(opf.opfname AS pg_catalog.text),' +
601
+ " CAST('operator family' AS pg_catalog.text)\n" +
602
+ ' FROM pg_catalog.pg_opfamily opf\n' +
603
+ ' JOIN pg_catalog.pg_am am ON opf.opfmethod = am.oid\n' +
604
+ ' JOIN pg_catalog.pg_namespace n ON opf.opfnamespace = n.oid\n' +
605
+ sysPart('operator family') +
606
+ '\n';
607
+ sql +=
608
+ 'UNION ALL\n' +
609
+ ' SELECT r.oid, r.tableoid, n.nspname,\n' +
610
+ ' CAST(r.rulename AS pg_catalog.text),' +
611
+ " CAST('rule' AS pg_catalog.text)\n" +
612
+ ' FROM pg_catalog.pg_rewrite r\n' +
613
+ ' JOIN pg_catalog.pg_class c ON c.oid = r.ev_class\n' +
614
+ ' LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n' +
615
+ " WHERE r.rulename != '_RETURN'\n";
616
+ sql +=
617
+ 'UNION ALL\n' +
618
+ ' SELECT t.oid, t.tableoid, n.nspname,\n' +
619
+ ' CAST(t.tgname AS pg_catalog.text),' +
620
+ " CAST('trigger' AS pg_catalog.text)\n" +
621
+ ' FROM pg_catalog.pg_trigger t\n' +
622
+ ' JOIN pg_catalog.pg_class c ON c.oid = t.tgrelid\n' +
623
+ ' LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n' +
624
+ sysPart('trigger') +
625
+ '\n';
626
+ sql +=
627
+ ') AS tt\n' +
628
+ ' JOIN pg_catalog.pg_description d ON (tt.oid = d.objoid AND tt.tableoid = d.classoid AND d.objsubid = 0)\n';
629
+ sql += orderBy('1, 2, 3');
630
+ return { sql, params: params(), description: 'Object descriptions' };
631
+ };
632
+ /* ------------------------------------------------------------------ */
633
+ /* \d (no args / \d <name>) — describeTableDetails */
634
+ /* This WP delivers ONLY the lookup query; the per-relation detail */
635
+ /* render is WP-20. */
636
+ /* ------------------------------------------------------------------ */
637
+ export const describeTableDetails = (opts) => {
638
+ const { verbose, showSystem, pattern, serverVersion } = opts;
639
+ const hideTableam = opts.hideTableam ?? false;
640
+ // For bare `\d [pattern]` upstream renders the same relations table as
641
+ // `\dtvmsiE` does, with the per-relkind type column populated. Include
642
+ // the verbose-mode columns when caller asks for them so `\d+ [pat]`
643
+ // matches `\dt+ [pat]` shape.
644
+ let sql = 'SELECT n.nspname as "Schema",\n' +
645
+ ' c.relname as "Name",\n' +
646
+ ' CASE c.relkind' +
647
+ " WHEN 'r' THEN 'table'" +
648
+ " WHEN 'v' THEN 'view'" +
649
+ " WHEN 'm' THEN 'materialized view'" +
650
+ " WHEN 'i' THEN 'index'" +
651
+ " WHEN 'S' THEN 'sequence'" +
652
+ " WHEN 't' THEN 'TOAST table'" +
653
+ " WHEN 'f' THEN 'foreign table'" +
654
+ " WHEN 'p' THEN 'partitioned table'" +
655
+ " WHEN 'I' THEN 'partitioned index'" +
656
+ ' END as "Type",\n' +
657
+ ' pg_catalog.pg_get_userbyid(c.relowner) as "Owner"';
658
+ if (verbose) {
659
+ sql +=
660
+ ',\n CASE c.relpersistence' +
661
+ " WHEN 'p' THEN 'permanent'" +
662
+ " WHEN 't' THEN 'temporary'" +
663
+ " WHEN 'u' THEN 'unlogged'" +
664
+ ' END as "Persistence"';
665
+ if (!hideTableam && serverAtLeast(serverVersion, PG_12)) {
666
+ sql += ',\n am.amname as "Access method"';
667
+ }
668
+ sql +=
669
+ ',\n pg_catalog.pg_size_pretty(pg_catalog.pg_total_relation_size(c.oid)) as "Size"' +
670
+ ',\n pg_catalog.obj_description(c.oid, \'pg_class\') as "Description"';
671
+ }
672
+ sql +=
673
+ '\nFROM pg_catalog.pg_class c' +
674
+ '\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace';
675
+ if (verbose && !hideTableam && serverAtLeast(serverVersion, PG_12)) {
676
+ sql += '\n LEFT JOIN pg_catalog.pg_am am ON am.oid = c.relam';
677
+ }
678
+ sql += "\nWHERE c.relkind IN ('r','p','v','m','S','f','')\n";
679
+ if (!showSystem && pattern === undefined) {
680
+ sql +=
681
+ " AND n.nspname <> 'pg_catalog'\n" +
682
+ " AND n.nspname !~ '^pg_toast'\n" +
683
+ " AND n.nspname <> 'information_schema'\n";
684
+ }
685
+ sql += patternStub(true, 'n.nspname', 'c.relname');
686
+ sql += orderBy('1,2');
687
+ return {
688
+ sql,
689
+ params: params(),
690
+ description: 'List of relations',
691
+ };
692
+ };
693
+ /* ------------------------------------------------------------------ */
694
+ /* \du / \dg — describeRoles */
695
+ /* ------------------------------------------------------------------ */
696
+ /**
697
+ * Upstream `describeRoles` (psql/describe.c) fetches the raw boolean
698
+ * columns then builds the cooked "Attributes" cell client-side from
699
+ * fixed per-flag strings. We do that aggregation in SQL via a
700
+ * NULL-skipping array_to_string so the printer sees only the final
701
+ * two visible columns ("Role name", "Attributes") plus an optional
702
+ * "Description" column when `+` is used. This matches the column
703
+ * layout, header banner ("List of roles"), and content ordering of
704
+ * vanilla psql 18, including the `Cannot login`-only rows produced by
705
+ * `\du regress_du_role*` in `src/test/regress/expected/psql.out`.
706
+ */
707
+ export const describeRoles = (opts) => {
708
+ const { verbose, showSystem, pattern, serverVersion } = opts;
709
+ // Per-flag attribute strings, in upstream order. Each VALUES row
710
+ // contributes one string (or NULL when the flag is off) so
711
+ // array_to_string can join them with ", ".
712
+ const attrParts = [
713
+ "CASE WHEN r.rolsuper THEN 'Superuser' END",
714
+ "CASE WHEN NOT r.rolinherit THEN 'No inheritance' END",
715
+ "CASE WHEN r.rolcreaterole THEN 'Create role' END",
716
+ "CASE WHEN r.rolcreatedb THEN 'Create DB' END",
717
+ "CASE WHEN NOT r.rolcanlogin THEN 'Cannot login' END",
718
+ "CASE WHEN r.rolreplication THEN 'Replication' END",
719
+ ];
720
+ if (serverAtLeast(serverVersion, PG_9_5)) {
721
+ attrParts.push("CASE WHEN r.rolbypassrls THEN 'Bypass RLS' END");
722
+ }
723
+ // `rolconnlimit = -1` means unlimited; only render when >= 0.
724
+ attrParts.push('CASE WHEN r.rolconnlimit >= 0 THEN' +
725
+ " pg_catalog.format('%s connection%s', r.rolconnlimit," +
726
+ " CASE WHEN r.rolconnlimit = 1 THEN '' ELSE 's' END) END");
727
+ // `rolvaliduntil` rendered with upstream's `Password valid until <ts>`.
728
+ attrParts.push('CASE WHEN r.rolvaliduntil IS NOT NULL THEN' +
729
+ " pg_catalog.format('Password valid until %s', r.rolvaliduntil) END");
730
+ const attrValues = attrParts.map((p) => `(${p})`).join(',\n ');
731
+ let sql = 'SELECT r.rolname AS "Role name",\n' +
732
+ ' pg_catalog.array_to_string(ARRAY(\n' +
733
+ ' SELECT a FROM (VALUES\n' +
734
+ ` ${attrValues}\n` +
735
+ ' ) AS x(a)\n' +
736
+ ' WHERE a IS NOT NULL\n' +
737
+ ' ), \', \') AS "Attributes"';
738
+ if (verbose) {
739
+ sql +=
740
+ ',\n pg_catalog.shobj_description(r.oid, \'pg_authid\') AS "Description"';
741
+ }
742
+ sql += '\nFROM pg_catalog.pg_roles r\n';
743
+ let hasWhere = false;
744
+ if (!showSystem && pattern === undefined) {
745
+ sql += "WHERE r.rolname !~ '^pg_'\n";
746
+ hasWhere = true;
747
+ }
748
+ sql += patternStub(hasWhere, undefined, 'r.rolname');
749
+ sql += orderBy('1');
750
+ return { sql, params: params(), description: 'List of roles' };
751
+ };
752
+ export const listDbRoleSettings = (opts) => {
753
+ let sql = 'SELECT rolname AS "Role", datname AS "Database",\n' +
754
+ 'pg_catalog.array_to_string(setconfig, E\'\\n\') AS "Settings"\n' +
755
+ 'FROM pg_catalog.pg_db_role_setting s\n' +
756
+ 'LEFT JOIN pg_catalog.pg_database d ON d.oid = setdatabase\n' +
757
+ 'LEFT JOIN pg_catalog.pg_roles r ON r.oid = setrole\n';
758
+ let hasWhere = false;
759
+ if (opts.pattern !== undefined) {
760
+ sql += patternStub(hasWhere, undefined, 'r.rolname');
761
+ hasWhere = true;
762
+ }
763
+ if (opts.pattern2 !== undefined) {
764
+ sql += patternStub(hasWhere, undefined, 'd.datname');
765
+ }
766
+ sql += orderBy('1, 2');
767
+ const ps = [];
768
+ if (opts.pattern !== undefined)
769
+ ps.push(opts.pattern);
770
+ if (opts.pattern2 !== undefined)
771
+ ps.push(opts.pattern2);
772
+ return { sql, params: ps, description: 'List of settings' };
773
+ };
774
+ /* ------------------------------------------------------------------ */
775
+ /* \drg — describeRoleGrants */
776
+ /* ------------------------------------------------------------------ */
777
+ export const describeRoleGrants = (opts) => {
778
+ const { showSystem, pattern, serverVersion } = opts;
779
+ let sql = 'SELECT m.rolname AS "Role name", r.rolname AS "Member of",\n' +
780
+ " pg_catalog.concat_ws(', ',\n";
781
+ if (serverAtLeast(serverVersion, PG_16)) {
782
+ sql +=
783
+ " CASE WHEN pam.admin_option THEN 'ADMIN' END,\n" +
784
+ " CASE WHEN pam.inherit_option THEN 'INHERIT' END,\n" +
785
+ " CASE WHEN pam.set_option THEN 'SET' END\n";
786
+ }
787
+ else {
788
+ sql +=
789
+ " CASE WHEN pam.admin_option THEN 'ADMIN' END,\n" +
790
+ " CASE WHEN m.rolinherit THEN 'INHERIT' END,\n" +
791
+ " 'SET'\n";
792
+ }
793
+ sql += ' ) AS "Options",\n g.rolname AS "Grantor"\n';
794
+ sql +=
795
+ 'FROM pg_catalog.pg_roles m\n' +
796
+ ' JOIN pg_catalog.pg_auth_members pam ON (pam.member = m.oid)\n' +
797
+ ' LEFT JOIN pg_catalog.pg_roles r ON (pam.roleid = r.oid)\n' +
798
+ ' LEFT JOIN pg_catalog.pg_roles g ON (pam.grantor = g.oid)\n';
799
+ let hasWhere = false;
800
+ if (!showSystem && pattern === undefined) {
801
+ sql += "WHERE m.rolname !~ '^pg_'\n";
802
+ hasWhere = true;
803
+ }
804
+ sql += patternStub(hasWhere, undefined, 'm.rolname');
805
+ sql += orderBy('1, 2, 4');
806
+ return { sql, params: params(), description: 'List of role grants' };
807
+ };
808
+ export const listTables = (opts) => {
809
+ const { verbose, showSystem, pattern, serverVersion } = opts;
810
+ const hideTableam = opts.hideTableam ?? false;
811
+ const tt = opts.tabtypes ?? '';
812
+ let showTables = tt.includes('t');
813
+ const showIndexes = tt.includes('i');
814
+ let showViews = tt.includes('v');
815
+ let showMatViews = tt.includes('m');
816
+ let showSeq = tt.includes('s');
817
+ let showForeign = tt.includes('E');
818
+ const ntypes = +showTables +
819
+ +showIndexes +
820
+ +showViews +
821
+ +showMatViews +
822
+ +showSeq +
823
+ +showForeign;
824
+ if (ntypes === 0) {
825
+ showTables = showViews = showMatViews = showSeq = showForeign = true;
826
+ }
827
+ let sql = 'SELECT n.nspname as "Schema",\n' +
828
+ ' c.relname as "Name",\n' +
829
+ ' CASE c.relkind' +
830
+ " WHEN 'r' THEN 'table'" +
831
+ " WHEN 'v' THEN 'view'" +
832
+ " WHEN 'm' THEN 'materialized view'" +
833
+ " WHEN 'i' THEN 'index'" +
834
+ " WHEN 'S' THEN 'sequence'" +
835
+ " WHEN 't' THEN 'TOAST table'" +
836
+ " WHEN 'f' THEN 'foreign table'" +
837
+ " WHEN 'p' THEN 'partitioned table'" +
838
+ " WHEN 'I' THEN 'partitioned index'" +
839
+ ' END as "Type",\n' +
840
+ ' pg_catalog.pg_get_userbyid(c.relowner) as "Owner"';
841
+ if (showIndexes)
842
+ sql += ',\n c2.relname as "Table"';
843
+ if (verbose) {
844
+ sql +=
845
+ ',\n CASE c.relpersistence' +
846
+ " WHEN 'p' THEN 'permanent'" +
847
+ " WHEN 't' THEN 'temporary'" +
848
+ " WHEN 'u' THEN 'unlogged'" +
849
+ ' END as "Persistence"';
850
+ const wantsAm = !hideTableam &&
851
+ serverAtLeast(serverVersion, PG_12) &&
852
+ (showTables || showMatViews || showIndexes);
853
+ if (wantsAm)
854
+ sql += ',\n am.amname as "Access method"';
855
+ // Upstream uses `pg_total_relation_size` (table + indexes + toast)
856
+ // in verbose mode so the formatting matches `pg_size_pretty` exactly
857
+ // — including the "8192 bytes" vs "8 kB" threshold the upstream
858
+ // tests pin to.
859
+ sql +=
860
+ ',\n pg_catalog.pg_size_pretty(pg_catalog.pg_total_relation_size(c.oid)) as "Size"' +
861
+ ',\n pg_catalog.obj_description(c.oid, \'pg_class\') as "Description"';
862
+ }
863
+ sql +=
864
+ '\nFROM pg_catalog.pg_class c' +
865
+ '\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace';
866
+ if (verbose &&
867
+ !hideTableam &&
868
+ serverAtLeast(serverVersion, PG_12) &&
869
+ (showTables || showMatViews || showIndexes)) {
870
+ sql += '\n LEFT JOIN pg_catalog.pg_am am ON am.oid = c.relam';
871
+ }
872
+ if (showIndexes) {
873
+ sql +=
874
+ '\n LEFT JOIN pg_catalog.pg_index i ON i.indexrelid = c.oid' +
875
+ '\n LEFT JOIN pg_catalog.pg_class c2 ON i.indrelid = c2.oid';
876
+ }
877
+ sql += '\nWHERE c.relkind IN (';
878
+ const kinds = [];
879
+ if (showTables) {
880
+ kinds.push("'r'", "'p'");
881
+ if (showSystem || pattern !== undefined)
882
+ kinds.push("'t'");
883
+ }
884
+ if (showViews)
885
+ kinds.push("'v'");
886
+ if (showMatViews)
887
+ kinds.push("'m'");
888
+ if (showIndexes)
889
+ kinds.push("'i'", "'I'");
890
+ if (showSeq)
891
+ kinds.push("'S'");
892
+ if (showSystem || pattern !== undefined)
893
+ kinds.push("'s'");
894
+ if (showForeign)
895
+ kinds.push("'f'");
896
+ kinds.push("''");
897
+ sql += kinds.join(',') + ')\n';
898
+ if (!showSystem && pattern === undefined) {
899
+ sql +=
900
+ " AND n.nspname <> 'pg_catalog'\n" +
901
+ " AND n.nspname !~ '^pg_toast'\n" +
902
+ " AND n.nspname <> 'information_schema'\n";
903
+ }
904
+ sql += patternStub(true, 'n.nspname', 'c.relname');
905
+ sql += orderBy('1,2');
906
+ // Upstream `describe.c::listTables` uses a type-specific title when the
907
+ // caller filters to a single relkind family, falling back to "relations"
908
+ // for mixed (or default) selections. Mirror that so `\dt` / `\dv` /
909
+ // `\dm` / `\di` / `\ds` / `\dE` titles match.
910
+ let description = 'List of relations';
911
+ if (ntypes === 1) {
912
+ if (showTables)
913
+ description = 'List of tables';
914
+ else if (showIndexes)
915
+ description = 'List of indexes';
916
+ else if (showViews)
917
+ description = 'List of views';
918
+ else if (showMatViews)
919
+ description = 'List of materialized views';
920
+ else if (showSeq)
921
+ description = 'List of sequences';
922
+ else if (showForeign)
923
+ description = 'List of foreign tables';
924
+ }
925
+ return { sql, params: params(), description };
926
+ };
927
+ export const listPartitionedTables = (opts) => {
928
+ const { verbose, pattern, serverVersion } = opts;
929
+ if (serverLess(serverVersion, PG_10)) {
930
+ return {
931
+ sql: '/* server < 10 does not support declarative partitioning */ SELECT 1 WHERE false;',
932
+ params: [],
933
+ description: 'List of partitioned relations',
934
+ };
935
+ }
936
+ const rt = opts.reltypes ?? '';
937
+ let showTables = rt.includes('t');
938
+ let showIndexes = rt.includes('i');
939
+ const showNested = rt.includes('n');
940
+ if (!showTables && !showIndexes)
941
+ showTables = showIndexes = true;
942
+ const mixed = showTables && showIndexes;
943
+ let sql = 'SELECT n.nspname as "Schema",\n' +
944
+ ' c.relname as "Name",\n' +
945
+ ' pg_catalog.pg_get_userbyid(c.relowner) as "Owner"';
946
+ if (mixed) {
947
+ sql +=
948
+ ',\n CASE c.relkind' +
949
+ " WHEN 'p' THEN 'partitioned table'" +
950
+ " WHEN 'I' THEN 'partitioned index'" +
951
+ ' END as "Type"';
952
+ }
953
+ if (showNested || pattern !== undefined) {
954
+ sql += ',\n inh.inhparent::pg_catalog.regclass as "Parent name"';
955
+ }
956
+ if (showIndexes) {
957
+ sql += ',\n c2.oid::pg_catalog.regclass as "Table"';
958
+ }
959
+ if (verbose) {
960
+ sql += ',\n am.amname as "Access method"';
961
+ if (showNested) {
962
+ sql += ',\n s.dps as "Leaf partition size"';
963
+ sql += ',\n s.tps as "Total size"';
964
+ }
965
+ else {
966
+ sql += ',\n s.tps as "Total size"';
967
+ }
968
+ sql +=
969
+ ',\n pg_catalog.obj_description(c.oid, \'pg_class\') as "Description"';
970
+ }
971
+ sql +=
972
+ '\nFROM pg_catalog.pg_class c' +
973
+ '\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace';
974
+ if (showIndexes) {
975
+ sql +=
976
+ '\n LEFT JOIN pg_catalog.pg_index i ON i.indexrelid = c.oid' +
977
+ '\n LEFT JOIN pg_catalog.pg_class c2 ON i.indrelid = c2.oid';
978
+ }
979
+ if (showNested || pattern !== undefined) {
980
+ sql +=
981
+ '\n LEFT JOIN pg_catalog.pg_inherits inh ON c.oid = inh.inhrelid';
982
+ }
983
+ if (verbose) {
984
+ sql += '\n LEFT JOIN pg_catalog.pg_am am ON c.relam = am.oid';
985
+ if (serverLess(serverVersion, PG_12)) {
986
+ sql +=
987
+ ',\n LATERAL (WITH RECURSIVE d AS (\n' +
988
+ ' SELECT inhrelid AS oid, 1 AS level FROM pg_catalog.pg_inherits WHERE inhparent = c.oid\n' +
989
+ ' UNION ALL\n' +
990
+ ' SELECT inhrelid, level + 1 FROM pg_catalog.pg_inherits i JOIN d ON i.inhparent = d.oid)\n' +
991
+ ' SELECT pg_catalog.pg_size_pretty(sum(pg_catalog.pg_table_size(d.oid))) AS tps,\n' +
992
+ ' pg_catalog.pg_size_pretty(sum(CASE WHEN d.level = 1 THEN pg_catalog.pg_table_size(d.oid) ELSE 0 END)) AS dps\n' +
993
+ ' FROM d) s';
994
+ }
995
+ else {
996
+ sql +=
997
+ ',\n LATERAL (SELECT pg_catalog.pg_size_pretty(sum(\n' +
998
+ ' CASE WHEN ppt.isleaf AND ppt.level = 1\n' +
999
+ ' THEN pg_catalog.pg_table_size(ppt.relid) ELSE 0 END)) AS dps,\n' +
1000
+ ' pg_catalog.pg_size_pretty(sum(pg_catalog.pg_table_size(ppt.relid))) AS tps\n' +
1001
+ ' FROM pg_catalog.pg_partition_tree(c.oid) ppt) s';
1002
+ }
1003
+ }
1004
+ sql += '\nWHERE c.relkind IN (';
1005
+ const kinds = [];
1006
+ if (showTables)
1007
+ kinds.push("'p'");
1008
+ if (showIndexes)
1009
+ kinds.push("'I'");
1010
+ kinds.push("''");
1011
+ sql += kinds.join(',') + ')\n';
1012
+ if (!showNested && pattern === undefined) {
1013
+ sql += ' AND NOT c.relispartition\n';
1014
+ }
1015
+ if (pattern === undefined) {
1016
+ sql +=
1017
+ " AND n.nspname <> 'pg_catalog'\n" +
1018
+ " AND n.nspname !~ '^pg_toast'\n" +
1019
+ " AND n.nspname <> 'information_schema'\n";
1020
+ }
1021
+ sql += patternStub(true, 'n.nspname', 'c.relname');
1022
+ sql += `ORDER BY "Schema", ${mixed ? '"Type" DESC, ' : ''}${showNested || pattern !== undefined ? '"Parent name" NULLS FIRST, ' : ''}"Name";`;
1023
+ // Mirror upstream's `tabletitle` switch: `\dPi*` lists indexes,
1024
+ // `\dPt*` lists tables, and any mixed/empty form lists relations.
1025
+ const description = showIndexes && !showTables
1026
+ ? 'List of partitioned indexes'
1027
+ : showTables && !showIndexes
1028
+ ? 'List of partitioned tables'
1029
+ : 'List of partitioned relations';
1030
+ return {
1031
+ sql,
1032
+ params: params(),
1033
+ description,
1034
+ };
1035
+ };
1036
+ /* ------------------------------------------------------------------ */
1037
+ /* \dL — listLanguages */
1038
+ /* ------------------------------------------------------------------ */
1039
+ export const listLanguages = (opts) => {
1040
+ const { verbose, showSystem, pattern } = opts;
1041
+ let sql = 'SELECT l.lanname AS "Name",\n' +
1042
+ ' pg_catalog.pg_get_userbyid(l.lanowner) as "Owner",\n' +
1043
+ ' l.lanpltrusted AS "Trusted"';
1044
+ if (verbose) {
1045
+ sql +=
1046
+ ',\n NOT l.lanispl AS "Internal language",\n' +
1047
+ ' l.lanplcallfoid::pg_catalog.regprocedure AS "Call handler",\n' +
1048
+ ' l.lanvalidator::pg_catalog.regprocedure AS "Validator",\n ' +
1049
+ 'l.laninline::pg_catalog.regprocedure AS "Inline handler",\n ' +
1050
+ aclColumn('l.lanacl');
1051
+ }
1052
+ sql +=
1053
+ ',\n d.description AS "Description"\n' +
1054
+ 'FROM pg_catalog.pg_language l\n' +
1055
+ 'LEFT JOIN pg_catalog.pg_description d\n' +
1056
+ ' ON d.classoid = l.tableoid AND d.objoid = l.oid AND d.objsubid = 0\n';
1057
+ let hasWhere = false;
1058
+ if (pattern !== undefined) {
1059
+ sql += patternStub(hasWhere, undefined, 'l.lanname');
1060
+ hasWhere = true;
1061
+ }
1062
+ if (!showSystem && pattern === undefined) {
1063
+ sql += 'WHERE l.lanplcallfoid != 0\n';
1064
+ hasWhere = true;
1065
+ }
1066
+ sql += orderBy('1');
1067
+ return { sql, params: params(), description: 'List of languages' };
1068
+ };
1069
+ /* ------------------------------------------------------------------ */
1070
+ /* \dD — listDomains */
1071
+ /* ------------------------------------------------------------------ */
1072
+ export const listDomains = (opts) => {
1073
+ const { verbose, showSystem, pattern } = opts;
1074
+ let sql = 'SELECT n.nspname as "Schema",\n' +
1075
+ ' t.typname as "Name",\n' +
1076
+ ' pg_catalog.format_type(t.typbasetype, t.typtypmod) as "Type",\n' +
1077
+ ' (SELECT c.collname FROM pg_catalog.pg_collation c, pg_catalog.pg_type bt\n' +
1078
+ ' WHERE c.oid = t.typcollation AND bt.oid = t.typbasetype AND t.typcollation <> bt.typcollation) as "Collation",\n' +
1079
+ ' CASE WHEN t.typnotnull THEN \'not null\' END as "Nullable",\n' +
1080
+ ' t.typdefault as "Default",\n' +
1081
+ " pg_catalog.array_to_string(ARRAY(\n SELECT pg_catalog.pg_get_constraintdef(r.oid, true) FROM pg_catalog.pg_constraint r WHERE t.oid = r.contypid AND r.contype = 'c' ORDER BY r.conname\n ), ' ') as \"Check\"";
1082
+ if (verbose) {
1083
+ sql += ',\n ' + aclColumn('t.typacl');
1084
+ sql += ',\n d.description as "Description"';
1085
+ }
1086
+ sql +=
1087
+ '\nFROM pg_catalog.pg_type t\n' +
1088
+ ' LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace\n';
1089
+ if (verbose) {
1090
+ sql +=
1091
+ ' LEFT JOIN pg_catalog.pg_description d ON d.classoid = t.tableoid AND d.objoid = t.oid AND d.objsubid = 0\n';
1092
+ }
1093
+ sql += "WHERE t.typtype = 'd'\n";
1094
+ if (!showSystem && pattern === undefined) {
1095
+ sql +=
1096
+ " AND n.nspname <> 'pg_catalog'\n AND n.nspname <> 'information_schema'\n";
1097
+ }
1098
+ sql += patternStub(true, 'n.nspname', 't.typname');
1099
+ sql += orderBy('1, 2');
1100
+ return { sql, params: params(), description: 'List of domains' };
1101
+ };
1102
+ /* ------------------------------------------------------------------ */
1103
+ /* \dc — listConversions */
1104
+ /* ------------------------------------------------------------------ */
1105
+ export const listConversions = (opts) => {
1106
+ const { verbose, showSystem, pattern } = opts;
1107
+ let sql = 'SELECT n.nspname AS "Schema",\n' +
1108
+ ' c.conname AS "Name",\n' +
1109
+ ' pg_catalog.pg_encoding_to_char(c.conforencoding) AS "Source",\n' +
1110
+ ' pg_catalog.pg_encoding_to_char(c.contoencoding) AS "Destination",\n' +
1111
+ " CASE WHEN c.condefault THEN 'yes' ELSE 'no' END AS \"Default?\"";
1112
+ if (verbose)
1113
+ sql += ',\n d.description AS "Description"';
1114
+ sql +=
1115
+ '\nFROM pg_catalog.pg_conversion c\n' +
1116
+ ' JOIN pg_catalog.pg_namespace n ON n.oid = c.connamespace\n';
1117
+ if (verbose) {
1118
+ sql +=
1119
+ 'LEFT JOIN pg_catalog.pg_description d ON d.classoid = c.tableoid AND d.objoid = c.oid AND d.objsubid = 0\n';
1120
+ }
1121
+ sql += 'WHERE true\n';
1122
+ if (!showSystem && pattern === undefined) {
1123
+ sql +=
1124
+ " AND n.nspname <> 'pg_catalog'\n AND n.nspname <> 'information_schema'\n";
1125
+ }
1126
+ sql += patternStub(true, 'n.nspname', 'c.conname');
1127
+ sql += orderBy('1, 2');
1128
+ return { sql, params: params(), description: 'List of conversions' };
1129
+ };
1130
+ /* ------------------------------------------------------------------ */
1131
+ /* \dconfig — describeConfigurationParameters */
1132
+ /* ------------------------------------------------------------------ */
1133
+ export const describeConfigurationParameters = (opts) => {
1134
+ const { verbose, pattern, serverVersion } = opts;
1135
+ let sql = 'SELECT s.name AS "Parameter", pg_catalog.current_setting(s.name) AS "Value"';
1136
+ if (verbose) {
1137
+ sql += ', s.vartype AS "Type", s.context AS "Context", ';
1138
+ if (serverAtLeast(serverVersion, PG_15)) {
1139
+ sql += aclColumn('p.paracl');
1140
+ }
1141
+ else {
1142
+ sql += 'NULL AS "Access privileges"';
1143
+ }
1144
+ }
1145
+ sql += '\nFROM pg_catalog.pg_settings s\n';
1146
+ if (verbose && serverAtLeast(serverVersion, PG_15)) {
1147
+ sql +=
1148
+ ' LEFT JOIN pg_catalog.pg_parameter_acl p ON pg_catalog.lower(s.name) = p.parname\n';
1149
+ }
1150
+ if (pattern !== undefined) {
1151
+ sql += patternStub(false, undefined, 'pg_catalog.lower(s.name)');
1152
+ }
1153
+ else {
1154
+ sql +=
1155
+ "WHERE s.source <> 'default' AND s.setting IS DISTINCT FROM s.boot_val\n";
1156
+ }
1157
+ sql += orderBy('1');
1158
+ return {
1159
+ sql,
1160
+ params: params(),
1161
+ description: pattern !== undefined
1162
+ ? 'List of configuration parameters'
1163
+ : 'List of non-default configuration parameters',
1164
+ };
1165
+ };
1166
+ /* ------------------------------------------------------------------ */
1167
+ /* \dy — listEventTriggers */
1168
+ /* ------------------------------------------------------------------ */
1169
+ export const listEventTriggers = (opts) => {
1170
+ const { verbose } = opts;
1171
+ let sql = 'SELECT evtname as "Name", evtevent as "Event", pg_catalog.pg_get_userbyid(e.evtowner) as "Owner",\n' +
1172
+ " case evtenabled when 'O' then 'enabled' when 'R' then 'replica' when 'A' then 'always' when 'D' then 'disabled' end as \"Enabled\",\n" +
1173
+ ' e.evtfoid::pg_catalog.regproc as "Function", ' +
1174
+ 'pg_catalog.array_to_string(array(select x from pg_catalog.unnest(evttags) as t(x)), \', \') as "Tags"';
1175
+ if (verbose) {
1176
+ sql +=
1177
+ ',\npg_catalog.obj_description(e.oid, \'pg_event_trigger\') as "Description"';
1178
+ }
1179
+ sql += '\nFROM pg_catalog.pg_event_trigger e\n';
1180
+ sql += patternStub(false, undefined, 'evtname');
1181
+ sql += orderBy('1');
1182
+ return { sql, params: params(), description: 'List of event triggers' };
1183
+ };
1184
+ /* ------------------------------------------------------------------ */
1185
+ /* \dX — listExtendedStats */
1186
+ /* ------------------------------------------------------------------ */
1187
+ export const listExtendedStats = (opts) => {
1188
+ const { verbose, serverVersion } = opts;
1189
+ if (serverLess(serverVersion, PG_10)) {
1190
+ return {
1191
+ sql: '/* server < 10 does not support extended statistics */ SELECT 1 WHERE false;',
1192
+ params: [],
1193
+ description: 'List of extended statistics',
1194
+ };
1195
+ }
1196
+ let sql = 'SELECT\n' +
1197
+ 'es.stxnamespace::pg_catalog.regnamespace::pg_catalog.text AS "Schema",\n' +
1198
+ 'es.stxname AS "Name",\n';
1199
+ if (serverAtLeast(serverVersion, PG_14)) {
1200
+ sql +=
1201
+ 'pg_catalog.format(\'%s FROM %s\', pg_catalog.pg_get_statisticsobjdef_columns(es.oid), es.stxrelid::pg_catalog.regclass) AS "Definition"';
1202
+ }
1203
+ else {
1204
+ sql +=
1205
+ "pg_catalog.format('%s FROM %s',\n" +
1206
+ " (SELECT pg_catalog.string_agg(pg_catalog.quote_ident(a.attname),', ')\n" +
1207
+ ' FROM pg_catalog.unnest(es.stxkeys) s(attnum)\n' +
1208
+ ' JOIN pg_catalog.pg_attribute a\n' +
1209
+ ' ON (es.stxrelid = a.attrelid AND a.attnum = s.attnum AND NOT a.attisdropped)),\n' +
1210
+ 'es.stxrelid::pg_catalog.regclass) AS "Definition"';
1211
+ }
1212
+ sql +=
1213
+ ",\nCASE WHEN 'd' = any(es.stxkind) THEN 'defined' END AS \"Ndistinct\",\n" +
1214
+ "CASE WHEN 'f' = any(es.stxkind) THEN 'defined' END AS \"Dependencies\"";
1215
+ if (serverAtLeast(serverVersion, PG_12)) {
1216
+ sql += ",\nCASE WHEN 'm' = any(es.stxkind) THEN 'defined' END AS \"MCV\"";
1217
+ }
1218
+ if (verbose) {
1219
+ sql +=
1220
+ ', \npg_catalog.obj_description(oid, \'pg_statistic_ext\') AS "Description"';
1221
+ }
1222
+ sql += '\nFROM pg_catalog.pg_statistic_ext es\n';
1223
+ sql += patternStub(false, 'es.stxnamespace::pg_catalog.regnamespace::pg_catalog.text', 'es.stxname');
1224
+ sql += orderBy('1, 2');
1225
+ return {
1226
+ sql,
1227
+ params: params(),
1228
+ description: 'List of extended statistics',
1229
+ };
1230
+ };
1231
+ /* ------------------------------------------------------------------ */
1232
+ /* \dC — listCasts */
1233
+ /* ------------------------------------------------------------------ */
1234
+ export const listCasts = (opts) => {
1235
+ const { verbose, serverVersion } = opts;
1236
+ let sql = 'SELECT pg_catalog.format_type(castsource, NULL) AS "Source type",\n' +
1237
+ ' pg_catalog.format_type(casttarget, NULL) AS "Target type",\n' +
1238
+ " CASE WHEN c.castmethod = 'b' THEN '(binary coercible)'\n" +
1239
+ " WHEN c.castmethod = 'i' THEN '(with inout)'\n" +
1240
+ ' ELSE p.proname\n' +
1241
+ ' END AS "Function",\n' +
1242
+ " CASE WHEN c.castcontext = 'e' THEN 'no'\n" +
1243
+ " WHEN c.castcontext = 'a' THEN 'in assignment'\n" +
1244
+ " ELSE 'yes'\n" +
1245
+ ' END AS "Implicit?"';
1246
+ if (verbose) {
1247
+ // PG 18 added "Leakproof?" to verbose `\dC+`. The pg_proc join
1248
+ // already exists for the Function column, so emitting the cast's
1249
+ // function's `proleakproof` is incremental.
1250
+ if (serverAtLeast(serverVersion, PG_18)) {
1251
+ sql +=
1252
+ ",\n CASE WHEN p.proleakproof THEN 'yes' ELSE 'no' END AS \"Leakproof?\"";
1253
+ }
1254
+ sql += ',\n d.description AS "Description"';
1255
+ }
1256
+ sql +=
1257
+ '\nFROM pg_catalog.pg_cast c LEFT JOIN pg_catalog.pg_proc p\n' +
1258
+ ' ON c.castfunc = p.oid\n' +
1259
+ ' LEFT JOIN pg_catalog.pg_type ts ON c.castsource = ts.oid\n' +
1260
+ ' LEFT JOIN pg_catalog.pg_namespace ns ON ns.oid = ts.typnamespace\n' +
1261
+ ' LEFT JOIN pg_catalog.pg_type tt ON c.casttarget = tt.oid\n' +
1262
+ ' LEFT JOIN pg_catalog.pg_namespace nt ON nt.oid = tt.typnamespace\n';
1263
+ if (verbose) {
1264
+ sql +=
1265
+ ' LEFT JOIN pg_catalog.pg_description d ON d.classoid = c.tableoid AND d.objoid = c.oid AND d.objsubid = 0\n';
1266
+ }
1267
+ sql += 'WHERE ( (true';
1268
+ sql += patternStub(true, 'ns.nspname', 'ts.typname');
1269
+ sql += ') OR (true';
1270
+ sql += patternStub(true, 'nt.nspname', 'tt.typname');
1271
+ sql += ') )\n';
1272
+ sql += orderBy('1, 2');
1273
+ return { sql, params: params(), description: 'List of casts' };
1274
+ };
1275
+ /* ------------------------------------------------------------------ */
1276
+ /* \dO — listCollations */
1277
+ /* ------------------------------------------------------------------ */
1278
+ export const listCollations = (opts) => {
1279
+ const { verbose, showSystem, pattern, serverVersion } = opts;
1280
+ let sql = 'SELECT\n n.nspname AS "Schema",\n c.collname AS "Name",\n';
1281
+ if (serverAtLeast(serverVersion, PG_10)) {
1282
+ sql +=
1283
+ ' CASE c.collprovider' +
1284
+ " WHEN 'd' THEN 'default'" +
1285
+ " WHEN 'b' THEN 'builtin'" +
1286
+ " WHEN 'c' THEN 'libc'" +
1287
+ " WHEN 'i' THEN 'icu'" +
1288
+ ' END AS "Provider",\n';
1289
+ }
1290
+ else {
1291
+ sql += ' \'libc\' AS "Provider",\n';
1292
+ }
1293
+ sql += ' c.collcollate AS "Collate",\n c.collctype AS "Ctype",\n';
1294
+ if (serverAtLeast(serverVersion, PG_17)) {
1295
+ sql += ' c.colllocale AS "Locale",\n';
1296
+ }
1297
+ else if (serverAtLeast(serverVersion, PG_15)) {
1298
+ sql += ' c.colliculocale AS "Locale",\n';
1299
+ }
1300
+ else {
1301
+ sql += ' c.collcollate AS "Locale",\n';
1302
+ }
1303
+ if (serverAtLeast(serverVersion, PG_16)) {
1304
+ sql += ' c.collicurules AS "ICU Rules",\n';
1305
+ }
1306
+ else {
1307
+ sql += ' NULL AS "ICU Rules",\n';
1308
+ }
1309
+ if (serverAtLeast(serverVersion, PG_12)) {
1310
+ sql +=
1311
+ " CASE WHEN c.collisdeterministic THEN 'yes' ELSE 'no' END AS \"Deterministic?\"";
1312
+ }
1313
+ else {
1314
+ sql += ' \'yes\' AS "Deterministic?"';
1315
+ }
1316
+ if (verbose) {
1317
+ sql +=
1318
+ ',\n pg_catalog.obj_description(c.oid, \'pg_collation\') AS "Description"';
1319
+ }
1320
+ sql +=
1321
+ '\nFROM pg_catalog.pg_collation c, pg_catalog.pg_namespace n\n' +
1322
+ 'WHERE n.oid = c.collnamespace\n';
1323
+ if (!showSystem && pattern === undefined) {
1324
+ sql +=
1325
+ " AND n.nspname <> 'pg_catalog'\n AND n.nspname <> 'information_schema'\n";
1326
+ }
1327
+ sql +=
1328
+ ' AND c.collencoding IN (-1, pg_catalog.pg_char_to_encoding(pg_catalog.getdatabaseencoding()))\n';
1329
+ sql += patternStub(true, 'n.nspname', 'c.collname');
1330
+ sql += orderBy('1, 2');
1331
+ return { sql, params: params(), description: 'List of collations' };
1332
+ };
1333
+ /* ------------------------------------------------------------------ */
1334
+ /* \dn — listSchemas */
1335
+ /* ------------------------------------------------------------------ */
1336
+ export const listSchemas = (opts) => {
1337
+ const { verbose, showSystem, pattern } = opts;
1338
+ let sql = 'SELECT n.nspname AS "Name",\n' +
1339
+ ' pg_catalog.pg_get_userbyid(n.nspowner) AS "Owner"';
1340
+ if (verbose) {
1341
+ sql += ',\n ' + aclColumn('n.nspacl');
1342
+ sql +=
1343
+ ',\n pg_catalog.obj_description(n.oid, \'pg_namespace\') AS "Description"';
1344
+ }
1345
+ sql += '\nFROM pg_catalog.pg_namespace n\n';
1346
+ if (!showSystem && pattern === undefined) {
1347
+ sql += "WHERE n.nspname !~ '^pg_' AND n.nspname <> 'information_schema'\n";
1348
+ }
1349
+ sql += patternStub(!showSystem && pattern === undefined, undefined, 'n.nspname');
1350
+ sql += orderBy('1');
1351
+ return { sql, params: params(), description: 'List of schemas' };
1352
+ };
1353
+ /* ------------------------------------------------------------------ */
1354
+ /* \dFp — listTSParsers */
1355
+ /* ------------------------------------------------------------------ */
1356
+ export const listTSParsers = (opts) => {
1357
+ const { verbose } = opts;
1358
+ if (verbose) {
1359
+ // Verbose form: fetch (oid, schema, name) so the renderer can issue
1360
+ // per-parser detail queries (see describeOneTSParser below). WP-20 will
1361
+ // wire the iteration.
1362
+ let sql = 'SELECT p.oid,\n n.nspname,\n p.prsname\n' +
1363
+ 'FROM pg_catalog.pg_ts_parser p\n' +
1364
+ 'LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.prsnamespace\n';
1365
+ sql += patternStub(false, 'n.nspname', 'p.prsname');
1366
+ sql += orderBy('1, 2');
1367
+ return {
1368
+ sql,
1369
+ params: params(),
1370
+ description: 'List of text search parsers (verbose)',
1371
+ };
1372
+ }
1373
+ let sql = 'SELECT\n n.nspname as "Schema",\n p.prsname as "Name",\n' +
1374
+ ' pg_catalog.obj_description(p.oid, \'pg_ts_parser\') as "Description"\n' +
1375
+ 'FROM pg_catalog.pg_ts_parser p\n' +
1376
+ 'LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.prsnamespace\n';
1377
+ sql += patternStub(false, 'n.nspname', 'p.prsname');
1378
+ sql += orderBy('1, 2');
1379
+ return {
1380
+ sql,
1381
+ params: params(),
1382
+ description: 'List of text search parsers',
1383
+ };
1384
+ };
1385
+ /**
1386
+ * Per-parser detail (one row per phase). Caller binds the parser oid.
1387
+ * Two queries upstream; we emit both as a UNION via a single string.
1388
+ */
1389
+ export const describeOneTSParser = (opts) => {
1390
+ const { oid } = opts;
1391
+ const sql = `SELECT 'Start parse' AS "Method",\n` +
1392
+ ` p.prsstart::pg_catalog.regproc AS "Function",\n` +
1393
+ ` pg_catalog.obj_description(p.prsstart, 'pg_proc') as "Description"\n` +
1394
+ ` FROM pg_catalog.pg_ts_parser p WHERE p.oid = '${oid}'\n` +
1395
+ `UNION ALL\n` +
1396
+ `SELECT 'Get next token', p.prstoken::pg_catalog.regproc, pg_catalog.obj_description(p.prstoken, 'pg_proc')\n` +
1397
+ ` FROM pg_catalog.pg_ts_parser p WHERE p.oid = '${oid}'\n` +
1398
+ `UNION ALL\n` +
1399
+ `SELECT 'End parse', p.prsend::pg_catalog.regproc, pg_catalog.obj_description(p.prsend, 'pg_proc')\n` +
1400
+ ` FROM pg_catalog.pg_ts_parser p WHERE p.oid = '${oid}'\n` +
1401
+ `UNION ALL\n` +
1402
+ `SELECT 'Get headline', p.prsheadline::pg_catalog.regproc, pg_catalog.obj_description(p.prsheadline, 'pg_proc')\n` +
1403
+ ` FROM pg_catalog.pg_ts_parser p WHERE p.oid = '${oid}'\n` +
1404
+ `UNION ALL\n` +
1405
+ `SELECT 'Get token types', p.prslextype::pg_catalog.regproc, pg_catalog.obj_description(p.prslextype, 'pg_proc')\n` +
1406
+ ` FROM pg_catalog.pg_ts_parser p WHERE p.oid = '${oid}';`;
1407
+ return { sql, params: [], description: 'Text search parser details' };
1408
+ };
1409
+ /* ------------------------------------------------------------------ */
1410
+ /* \dFd — listTSDictionaries */
1411
+ /* ------------------------------------------------------------------ */
1412
+ export const listTSDictionaries = (opts) => {
1413
+ const { verbose } = opts;
1414
+ let sql = 'SELECT\n n.nspname as "Schema",\n d.dictname as "Name",\n';
1415
+ if (verbose) {
1416
+ sql +=
1417
+ " ( SELECT COALESCE(nt.nspname, '(null)')::pg_catalog.text || '.' || t.tmplname FROM\n" +
1418
+ ' pg_catalog.pg_ts_template t\n' +
1419
+ ' LEFT JOIN pg_catalog.pg_namespace nt ON nt.oid = t.tmplnamespace\n' +
1420
+ ' WHERE d.dicttemplate = t.oid ) AS "Template",\n' +
1421
+ ' d.dictinitoption as "Init options",\n';
1422
+ }
1423
+ sql +=
1424
+ ' pg_catalog.obj_description(d.oid, \'pg_ts_dict\') as "Description"\n' +
1425
+ 'FROM pg_catalog.pg_ts_dict d\n' +
1426
+ 'LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.dictnamespace\n';
1427
+ sql += patternStub(false, 'n.nspname', 'd.dictname');
1428
+ sql += orderBy('1, 2');
1429
+ return {
1430
+ sql,
1431
+ params: params(),
1432
+ description: 'List of text search dictionaries',
1433
+ };
1434
+ };
1435
+ /* ------------------------------------------------------------------ */
1436
+ /* \dFt — listTSTemplates */
1437
+ /* ------------------------------------------------------------------ */
1438
+ export const listTSTemplates = (opts) => {
1439
+ const { verbose } = opts;
1440
+ let sql;
1441
+ if (verbose) {
1442
+ sql =
1443
+ 'SELECT\n n.nspname AS "Schema",\n t.tmplname AS "Name",\n' +
1444
+ ' t.tmplinit::pg_catalog.regproc AS "Init",\n' +
1445
+ ' t.tmpllexize::pg_catalog.regproc AS "Lexize",\n' +
1446
+ ' pg_catalog.obj_description(t.oid, \'pg_ts_template\') AS "Description"\n';
1447
+ }
1448
+ else {
1449
+ sql =
1450
+ 'SELECT\n n.nspname AS "Schema",\n t.tmplname AS "Name",\n' +
1451
+ ' pg_catalog.obj_description(t.oid, \'pg_ts_template\') AS "Description"\n';
1452
+ }
1453
+ sql +=
1454
+ 'FROM pg_catalog.pg_ts_template t\n' +
1455
+ 'LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.tmplnamespace\n';
1456
+ sql += patternStub(false, 'n.nspname', 't.tmplname');
1457
+ sql += orderBy('1, 2');
1458
+ return {
1459
+ sql,
1460
+ params: params(),
1461
+ description: 'List of text search templates',
1462
+ };
1463
+ };
1464
+ /* ------------------------------------------------------------------ */
1465
+ /* \dF — listTSConfigs */
1466
+ /* ------------------------------------------------------------------ */
1467
+ export const listTSConfigs = (opts) => {
1468
+ const { verbose } = opts;
1469
+ if (verbose) {
1470
+ let sql = 'SELECT c.oid, c.cfgname, n.nspname, p.prsname, np.nspname as pnspname\n' +
1471
+ 'FROM pg_catalog.pg_ts_config c\n' +
1472
+ ' LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.cfgnamespace,\n' +
1473
+ ' pg_catalog.pg_ts_parser p\n' +
1474
+ ' LEFT JOIN pg_catalog.pg_namespace np ON np.oid = p.prsnamespace\n' +
1475
+ 'WHERE p.oid = c.cfgparser\n';
1476
+ sql += patternStub(true, 'n.nspname', 'c.cfgname');
1477
+ sql += orderBy('3, 2');
1478
+ return {
1479
+ sql,
1480
+ params: params(),
1481
+ description: 'List of text search configurations (verbose)',
1482
+ };
1483
+ }
1484
+ let sql = 'SELECT\n n.nspname as "Schema",\n c.cfgname as "Name",\n' +
1485
+ ' pg_catalog.obj_description(c.oid, \'pg_ts_config\') as "Description"\n' +
1486
+ 'FROM pg_catalog.pg_ts_config c\n' +
1487
+ 'LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.cfgnamespace\n';
1488
+ sql += patternStub(false, 'n.nspname', 'c.cfgname');
1489
+ sql += orderBy('1, 2');
1490
+ return {
1491
+ sql,
1492
+ params: params(),
1493
+ description: 'List of text search configurations',
1494
+ };
1495
+ };
1496
+ /* ------------------------------------------------------------------ */
1497
+ /* \dew — listForeignDataWrappers */
1498
+ /* ------------------------------------------------------------------ */
1499
+ export const listForeignDataWrappers = (opts) => {
1500
+ const { verbose } = opts;
1501
+ let sql = 'SELECT fdw.fdwname AS "Name",\n' +
1502
+ ' pg_catalog.pg_get_userbyid(fdw.fdwowner) AS "Owner",\n' +
1503
+ ' fdw.fdwhandler::pg_catalog.regproc AS "Handler",\n' +
1504
+ ' fdw.fdwvalidator::pg_catalog.regproc AS "Validator"';
1505
+ if (verbose) {
1506
+ sql += ',\n ' + aclColumn('fdwacl');
1507
+ sql +=
1508
+ ",\n CASE WHEN fdwoptions IS NULL THEN '' ELSE\n" +
1509
+ " '(' || pg_catalog.array_to_string(ARRAY(SELECT pg_catalog.quote_ident(option_name) || ' ' || pg_catalog.quote_literal(option_value) FROM pg_catalog.pg_options_to_table(fdwoptions)), ', ') || ')'\n" +
1510
+ ' END AS "FDW options",\n' +
1511
+ ' d.description AS "Description" ';
1512
+ }
1513
+ sql += '\nFROM pg_catalog.pg_foreign_data_wrapper fdw\n';
1514
+ if (verbose) {
1515
+ sql +=
1516
+ 'LEFT JOIN pg_catalog.pg_description d ON d.classoid = fdw.tableoid AND d.objoid = fdw.oid AND d.objsubid = 0\n';
1517
+ }
1518
+ sql += patternStub(false, undefined, 'fdwname');
1519
+ sql += orderBy('1');
1520
+ return {
1521
+ sql,
1522
+ params: params(),
1523
+ description: 'List of foreign-data wrappers',
1524
+ };
1525
+ };
1526
+ /* ------------------------------------------------------------------ */
1527
+ /* \des — listForeignServers */
1528
+ /* ------------------------------------------------------------------ */
1529
+ export const listForeignServers = (opts) => {
1530
+ const { verbose } = opts;
1531
+ let sql = 'SELECT s.srvname AS "Name",\n' +
1532
+ ' pg_catalog.pg_get_userbyid(s.srvowner) AS "Owner",\n' +
1533
+ ' f.fdwname AS "Foreign-data wrapper"';
1534
+ if (verbose) {
1535
+ sql += ',\n ' + aclColumn('s.srvacl');
1536
+ sql +=
1537
+ ',\n s.srvtype AS "Type",\n' +
1538
+ ' s.srvversion AS "Version",\n' +
1539
+ " CASE WHEN srvoptions IS NULL THEN '' ELSE\n" +
1540
+ " '(' || pg_catalog.array_to_string(ARRAY(SELECT pg_catalog.quote_ident(option_name) || ' ' || pg_catalog.quote_literal(option_value) FROM pg_catalog.pg_options_to_table(srvoptions)), ', ') || ')'\n" +
1541
+ ' END AS "FDW options",\n' +
1542
+ ' d.description AS "Description"';
1543
+ }
1544
+ sql +=
1545
+ '\nFROM pg_catalog.pg_foreign_server s\n' +
1546
+ ' JOIN pg_catalog.pg_foreign_data_wrapper f ON f.oid=s.srvfdw\n';
1547
+ if (verbose) {
1548
+ sql +=
1549
+ 'LEFT JOIN pg_catalog.pg_description d ON d.classoid = s.tableoid AND d.objoid = s.oid AND d.objsubid = 0\n';
1550
+ }
1551
+ sql += patternStub(false, undefined, 's.srvname');
1552
+ sql += orderBy('1');
1553
+ return { sql, params: params(), description: 'List of foreign servers' };
1554
+ };
1555
+ /* ------------------------------------------------------------------ */
1556
+ /* \deu — listUserMappings */
1557
+ /* ------------------------------------------------------------------ */
1558
+ export const listUserMappings = (opts) => {
1559
+ const { verbose } = opts;
1560
+ let sql = 'SELECT um.srvname AS "Server",\n um.usename AS "User name"';
1561
+ if (verbose) {
1562
+ sql +=
1563
+ ",\n CASE WHEN umoptions IS NULL THEN '' ELSE\n" +
1564
+ " '(' || pg_catalog.array_to_string(ARRAY(SELECT pg_catalog.quote_ident(option_name) || ' ' || pg_catalog.quote_literal(option_value) FROM pg_catalog.pg_options_to_table(umoptions)), ', ') || ')'\n" +
1565
+ ' END AS "FDW options"';
1566
+ }
1567
+ sql += '\nFROM pg_catalog.pg_user_mappings um\n';
1568
+ sql += patternStub(false, undefined, 'um.srvname');
1569
+ sql += orderBy('1, 2');
1570
+ return { sql, params: params(), description: 'List of user mappings' };
1571
+ };
1572
+ /* ------------------------------------------------------------------ */
1573
+ /* \det — listForeignTables */
1574
+ /* ------------------------------------------------------------------ */
1575
+ export const listForeignTables = (opts) => {
1576
+ const { verbose } = opts;
1577
+ let sql = 'SELECT n.nspname AS "Schema",\n c.relname AS "Table",\n s.srvname AS "Server"';
1578
+ if (verbose) {
1579
+ sql +=
1580
+ ",\n CASE WHEN ftoptions IS NULL THEN '' ELSE\n" +
1581
+ " '(' || pg_catalog.array_to_string(ARRAY(SELECT pg_catalog.quote_ident(option_name) || ' ' || pg_catalog.quote_literal(option_value) FROM pg_catalog.pg_options_to_table(ftoptions)), ', ') || ')'\n" +
1582
+ ' END AS "FDW options",\n' +
1583
+ ' d.description AS "Description"';
1584
+ }
1585
+ sql +=
1586
+ '\nFROM pg_catalog.pg_foreign_table ft\n' +
1587
+ ' INNER JOIN pg_catalog.pg_class c ON c.oid = ft.ftrelid\n' +
1588
+ ' INNER JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n' +
1589
+ ' INNER JOIN pg_catalog.pg_foreign_server s ON s.oid = ft.ftserver\n';
1590
+ if (verbose) {
1591
+ sql +=
1592
+ ' LEFT JOIN pg_catalog.pg_description d ON d.classoid = c.tableoid AND d.objoid = c.oid AND d.objsubid = 0\n';
1593
+ }
1594
+ sql += patternStub(false, 'n.nspname', 'c.relname');
1595
+ sql += orderBy('1, 2');
1596
+ return { sql, params: params(), description: 'List of foreign tables' };
1597
+ };
1598
+ /* ------------------------------------------------------------------ */
1599
+ /* \dx — listExtensions / \dx+ — listExtensionContents */
1600
+ /* ------------------------------------------------------------------ */
1601
+ export const listExtensions = (opts) => {
1602
+ const { serverVersion } = opts;
1603
+ // PG 18 added the "Default version" column to `\dx` to make it
1604
+ // easy to spot extensions that are behind the available default.
1605
+ const hasDefaultVersion = serverAtLeast(serverVersion, PG_18);
1606
+ let sql = 'SELECT e.extname AS "Name", e.extversion AS "Version", ';
1607
+ if (hasDefaultVersion) {
1608
+ sql += 'ae.default_version AS "Default version",';
1609
+ }
1610
+ sql +=
1611
+ 'n.nspname AS "Schema", d.description AS "Description"\n' +
1612
+ 'FROM pg_catalog.pg_extension e ' +
1613
+ 'LEFT JOIN pg_catalog.pg_namespace n ON n.oid = e.extnamespace ' +
1614
+ "LEFT JOIN pg_catalog.pg_description d ON d.objoid = e.oid AND d.classoid = 'pg_catalog.pg_extension'::pg_catalog.regclass";
1615
+ if (hasDefaultVersion) {
1616
+ sql +=
1617
+ ' LEFT JOIN pg_catalog.pg_available_extensions() ae(name, default_version, comment) ON ae.name = e.extname';
1618
+ }
1619
+ sql += '\n';
1620
+ sql += patternStub(false, undefined, 'e.extname');
1621
+ sql += orderBy('1');
1622
+ return {
1623
+ sql,
1624
+ params: params(),
1625
+ description: 'List of installed extensions',
1626
+ };
1627
+ };
1628
+ export const listExtensionContents = () => {
1629
+ let sql = 'SELECT e.extname, e.oid\nFROM pg_catalog.pg_extension e\n';
1630
+ sql += patternStub(false, undefined, 'e.extname');
1631
+ sql += orderBy('1');
1632
+ return {
1633
+ sql,
1634
+ params: params(),
1635
+ description: 'Get matching extensions to list contents for',
1636
+ };
1637
+ };
1638
+ /**
1639
+ * Per-extension contents (`\dx+ foo`). Caller binds the extension oid.
1640
+ */
1641
+ export const listOneExtensionContents = (opts) => {
1642
+ const sql = 'SELECT pg_catalog.pg_describe_object(classid, objid, 0) AS "Object description"\n' +
1643
+ 'FROM pg_catalog.pg_depend\n' +
1644
+ `WHERE refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass AND refobjid = '${opts.oid}' AND deptype = 'e'\n` +
1645
+ 'ORDER BY 1;';
1646
+ return { sql, params: [], description: 'Objects in extension' };
1647
+ };
1648
+ /* ------------------------------------------------------------------ */
1649
+ /* \dRp — listPublications / describePublications (\dRp+) */
1650
+ /* ------------------------------------------------------------------ */
1651
+ export const listPublications = (opts) => {
1652
+ const { serverVersion } = opts;
1653
+ if (serverLess(serverVersion, PG_10)) {
1654
+ return {
1655
+ sql: '/* server < 10 does not support publications */ SELECT 1 WHERE false;',
1656
+ params: [],
1657
+ description: 'List of publications',
1658
+ };
1659
+ }
1660
+ let sql = 'SELECT pubname AS "Name",\n' +
1661
+ ' pg_catalog.pg_get_userbyid(pubowner) AS "Owner",\n' +
1662
+ ' puballtables AS "All tables"';
1663
+ if (serverAtLeast(serverVersion, 19)) {
1664
+ sql += ',\n puballsequences AS "All sequences"';
1665
+ }
1666
+ sql +=
1667
+ ',\n pubinsert AS "Inserts",\n pubupdate AS "Updates",\n pubdelete AS "Deletes"';
1668
+ if (serverAtLeast(serverVersion, PG_11)) {
1669
+ sql += ',\n pubtruncate AS "Truncates"';
1670
+ }
1671
+ // PG 18 added `pg_publication.pubgencols`, surfaced as "Generated
1672
+ // columns" in `\dRp`. Values: 'n' = none, 's' = stored.
1673
+ if (serverAtLeast(serverVersion, PG_18)) {
1674
+ sql +=
1675
+ ',\n (CASE pubgencols ' +
1676
+ "WHEN 'n' THEN 'none' WHEN 's' THEN 'stored' END) AS \"Generated columns\"";
1677
+ }
1678
+ if (serverAtLeast(serverVersion, PG_13)) {
1679
+ sql += ',\n pubviaroot AS "Via root"';
1680
+ }
1681
+ sql += '\nFROM pg_catalog.pg_publication\n';
1682
+ sql += patternStub(false, undefined, 'pubname');
1683
+ sql += orderBy('1');
1684
+ return { sql, params: params(), description: 'List of publications' };
1685
+ };
1686
+ /**
1687
+ * Detail-lookup query for `\dRp+`. Output columns match the unmodified
1688
+ * `describePublications` upstream lookup (positional columns are then
1689
+ * fanned-out into per-publication detail blocks by WP-20).
1690
+ */
1691
+ export const describePublications = (opts) => {
1692
+ const { serverVersion } = opts;
1693
+ if (serverLess(serverVersion, PG_10)) {
1694
+ return {
1695
+ sql: '/* server < 10 does not support publications */ SELECT 1 WHERE false;',
1696
+ params: [],
1697
+ description: 'Details of publications',
1698
+ };
1699
+ }
1700
+ const hasSeq = serverAtLeast(serverVersion, 19);
1701
+ const hasTrunc = serverAtLeast(serverVersion, PG_11);
1702
+ const hasGen = serverAtLeast(serverVersion, PG_18);
1703
+ const hasViaRoot = serverAtLeast(serverVersion, PG_13);
1704
+ let sql = 'SELECT oid, pubname,\n pg_catalog.pg_get_userbyid(pubowner) AS owner,\n puballtables';
1705
+ sql += hasSeq ? ', puballsequences' : ', false AS puballsequences';
1706
+ sql += ', pubinsert, pubupdate, pubdelete';
1707
+ sql += hasTrunc ? ', pubtruncate' : ', false AS pubtruncate';
1708
+ if (hasGen) {
1709
+ sql +=
1710
+ ", (CASE pubgencols WHEN 'n' THEN 'none' WHEN 's' THEN 'stored' END) AS pubgencols";
1711
+ }
1712
+ else {
1713
+ sql += ", 'none' AS pubgencols";
1714
+ }
1715
+ sql += hasViaRoot ? ', pubviaroot' : ', false AS pubviaroot';
1716
+ sql += ", pg_catalog.obj_description(oid, 'pg_publication')";
1717
+ sql += '\nFROM pg_catalog.pg_publication\n';
1718
+ sql += patternStub(false, undefined, 'pubname');
1719
+ sql += orderBy('2');
1720
+ return { sql, params: params(), description: 'Details of publications' };
1721
+ };
1722
+ /* ------------------------------------------------------------------ */
1723
+ /* \dRs — describeSubscriptions */
1724
+ /* ------------------------------------------------------------------ */
1725
+ export const describeSubscriptions = (opts) => {
1726
+ const { verbose, serverVersion } = opts;
1727
+ if (serverLess(serverVersion, PG_10)) {
1728
+ return {
1729
+ sql: '/* server < 10 does not support subscriptions */ SELECT 1 WHERE false;',
1730
+ params: [],
1731
+ description: 'List of subscriptions',
1732
+ };
1733
+ }
1734
+ let sql = 'SELECT subname AS "Name"\n' +
1735
+ ', pg_catalog.pg_get_userbyid(subowner) AS "Owner"\n' +
1736
+ ', subenabled AS "Enabled"\n' +
1737
+ ', subpublications AS "Publication"\n';
1738
+ if (verbose) {
1739
+ if (serverAtLeast(serverVersion, PG_14)) {
1740
+ sql += ', subbinary AS "Binary"\n';
1741
+ if (serverAtLeast(serverVersion, PG_16)) {
1742
+ sql +=
1743
+ ', (CASE substream\n' +
1744
+ " WHEN 'f' THEN 'off'\n" +
1745
+ " WHEN 't' THEN 'on'\n" +
1746
+ " WHEN 'p' THEN 'parallel'\n" +
1747
+ ' END) AS "Streaming"\n';
1748
+ }
1749
+ else {
1750
+ sql += ', substream AS "Streaming"\n';
1751
+ }
1752
+ }
1753
+ if (serverAtLeast(serverVersion, PG_15)) {
1754
+ sql +=
1755
+ ', subtwophasestate AS "Two-phase commit"\n' +
1756
+ ', subdisableonerr AS "Disable on error"\n';
1757
+ }
1758
+ if (serverAtLeast(serverVersion, PG_16)) {
1759
+ sql +=
1760
+ ', suborigin AS "Origin"\n' +
1761
+ ', subpasswordrequired AS "Password required"\n' +
1762
+ ', subrunasowner AS "Run as owner?"\n';
1763
+ }
1764
+ if (serverAtLeast(serverVersion, PG_17)) {
1765
+ sql += ', subfailover AS "Failover"\n';
1766
+ }
1767
+ sql +=
1768
+ ', subsynccommit AS "Synchronous commit"\n' +
1769
+ ', subconninfo AS "Conninfo"\n';
1770
+ if (serverAtLeast(serverVersion, PG_15)) {
1771
+ sql += ', subskiplsn AS "Skip LSN"\n';
1772
+ }
1773
+ sql +=
1774
+ ', pg_catalog.obj_description(oid, \'pg_subscription\') AS "Description"\n';
1775
+ }
1776
+ sql +=
1777
+ 'FROM pg_catalog.pg_subscription\n' +
1778
+ 'WHERE subdbid = (SELECT oid FROM pg_catalog.pg_database WHERE datname = pg_catalog.current_database())';
1779
+ sql += patternStub(true, undefined, 'subname');
1780
+ sql += orderBy('1');
1781
+ return { sql, params: params(), description: 'List of subscriptions' };
1782
+ };
1783
+ export const listOperatorClasses = (opts) => {
1784
+ const { verbose } = opts;
1785
+ let sql = 'SELECT\n am.amname AS "AM",\n' +
1786
+ ' pg_catalog.format_type(c.opcintype, NULL) AS "Input type",\n' +
1787
+ ' CASE\n' +
1788
+ ' WHEN c.opckeytype <> 0 AND c.opckeytype <> c.opcintype\n' +
1789
+ ' THEN pg_catalog.format_type(c.opckeytype, NULL)\n' +
1790
+ ' ELSE NULL\n' +
1791
+ ' END AS "Storage type",\n' +
1792
+ ' CASE\n' +
1793
+ ' WHEN pg_catalog.pg_opclass_is_visible(c.oid)\n' +
1794
+ " THEN pg_catalog.format('%I', c.opcname)\n" +
1795
+ " ELSE pg_catalog.format('%I.%I', n.nspname, c.opcname)\n" +
1796
+ ' END AS "Operator class",\n' +
1797
+ " (CASE WHEN c.opcdefault THEN 'yes' ELSE 'no' END) AS \"Default?\"";
1798
+ if (verbose) {
1799
+ sql +=
1800
+ ',\n CASE\n' +
1801
+ ' WHEN pg_catalog.pg_opfamily_is_visible(of.oid)\n' +
1802
+ " THEN pg_catalog.format('%I', of.opfname)\n" +
1803
+ " ELSE pg_catalog.format('%I.%I', ofn.nspname, of.opfname)\n" +
1804
+ ' END AS "Operator family",\n' +
1805
+ ' pg_catalog.pg_get_userbyid(c.opcowner) AS "Owner"\n';
1806
+ }
1807
+ sql +=
1808
+ '\nFROM pg_catalog.pg_opclass c\n' +
1809
+ ' LEFT JOIN pg_catalog.pg_am am on am.oid = c.opcmethod\n' +
1810
+ ' LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.opcnamespace\n' +
1811
+ ' LEFT JOIN pg_catalog.pg_type t ON t.oid = c.opcintype\n' +
1812
+ ' LEFT JOIN pg_catalog.pg_namespace tn ON tn.oid = t.typnamespace\n';
1813
+ if (verbose) {
1814
+ sql +=
1815
+ ' LEFT JOIN pg_catalog.pg_opfamily of ON of.oid = c.opcfamily\n' +
1816
+ ' LEFT JOIN pg_catalog.pg_namespace ofn ON ofn.oid = of.opfnamespace\n';
1817
+ }
1818
+ let hasWhere = false;
1819
+ if (opts.amPattern !== undefined) {
1820
+ sql += patternStub(hasWhere, undefined, 'am.amname');
1821
+ hasWhere = true;
1822
+ }
1823
+ if (opts.typePattern !== undefined) {
1824
+ sql += patternStub(hasWhere, 'tn.nspname', 't.typname');
1825
+ }
1826
+ sql += orderBy('1, 2, 4');
1827
+ const ps = [];
1828
+ if (opts.amPattern !== undefined)
1829
+ ps.push(opts.amPattern);
1830
+ if (opts.typePattern !== undefined)
1831
+ ps.push(opts.typePattern);
1832
+ return { sql, params: ps, description: 'List of operator classes' };
1833
+ };
1834
+ /* ------------------------------------------------------------------ */
1835
+ /* \dAf — listOperatorFamilies */
1836
+ /* ------------------------------------------------------------------ */
1837
+ export const listOperatorFamilies = (opts) => {
1838
+ const { verbose } = opts;
1839
+ let sql = 'SELECT\n am.amname AS "AM",\n' +
1840
+ ' CASE\n' +
1841
+ ' WHEN pg_catalog.pg_opfamily_is_visible(f.oid)\n' +
1842
+ " THEN pg_catalog.format('%I', f.opfname)\n" +
1843
+ " ELSE pg_catalog.format('%I.%I', n.nspname, f.opfname)\n" +
1844
+ ' END AS "Operator family",\n' +
1845
+ ' (SELECT\n' +
1846
+ " pg_catalog.string_agg(pg_catalog.format_type(oc.opcintype, NULL), ', ')\n" +
1847
+ ' FROM pg_catalog.pg_opclass oc\n' +
1848
+ ' WHERE oc.opcfamily = f.oid) "Applicable types"';
1849
+ if (verbose) {
1850
+ sql += ',\n pg_catalog.pg_get_userbyid(f.opfowner) AS "Owner"\n';
1851
+ }
1852
+ sql +=
1853
+ '\nFROM pg_catalog.pg_opfamily f\n' +
1854
+ ' LEFT JOIN pg_catalog.pg_am am on am.oid = f.opfmethod\n' +
1855
+ ' LEFT JOIN pg_catalog.pg_namespace n ON n.oid = f.opfnamespace\n';
1856
+ let hasWhere = false;
1857
+ if (opts.amPattern !== undefined) {
1858
+ sql += patternStub(hasWhere, undefined, 'am.amname');
1859
+ hasWhere = true;
1860
+ }
1861
+ if (opts.typePattern !== undefined) {
1862
+ sql +=
1863
+ ` ${hasWhere ? 'AND' : 'WHERE'} EXISTS (\n` +
1864
+ ' SELECT 1 FROM pg_catalog.pg_type t\n' +
1865
+ ' JOIN pg_catalog.pg_opclass oc ON oc.opcintype = t.oid\n' +
1866
+ ' LEFT JOIN pg_catalog.pg_namespace tn ON tn.oid = t.typnamespace\n' +
1867
+ ' WHERE oc.opcfamily = f.oid\n' +
1868
+ patternStub(true, 'tn.nspname', 't.typname') +
1869
+ ' )\n';
1870
+ }
1871
+ sql += orderBy('1, 2');
1872
+ const ps = [];
1873
+ if (opts.amPattern !== undefined)
1874
+ ps.push(opts.amPattern);
1875
+ if (opts.typePattern !== undefined)
1876
+ ps.push(opts.typePattern);
1877
+ return { sql, params: ps, description: 'List of operator families' };
1878
+ };
1879
+ export const listOpFamilyOperators = (opts) => {
1880
+ const { verbose, serverVersion } = opts;
1881
+ let sql = 'SELECT\n am.amname AS "AM",\n' +
1882
+ ' CASE\n' +
1883
+ ' WHEN pg_catalog.pg_opfamily_is_visible(of.oid)\n' +
1884
+ " THEN pg_catalog.format('%I', of.opfname)\n" +
1885
+ " ELSE pg_catalog.format('%I.%I', nsf.nspname, of.opfname)\n" +
1886
+ ' END AS "Operator family",\n' +
1887
+ ' o.amopopr::pg_catalog.regoperator AS "Operator"\n,' +
1888
+ ' o.amopstrategy AS "Strategy",\n' +
1889
+ ' CASE o.amoppurpose\n' +
1890
+ " WHEN 'o' THEN 'ordering'\n" +
1891
+ " WHEN 's' THEN 'search'\n" +
1892
+ ' END AS "Purpose"\n';
1893
+ if (verbose) {
1894
+ sql += ', ofs.opfname AS "Sort opfamily"';
1895
+ // PG 18 added "Leakproof?" to verbose `\dAo+`. Same pattern as
1896
+ // `\df+` — the operator's underlying function's proleakproof.
1897
+ if (serverAtLeast(serverVersion, PG_18)) {
1898
+ sql +=
1899
+ ",\n CASE WHEN p.proleakproof THEN 'yes' ELSE 'no' END AS \"Leakproof?\"";
1900
+ }
1901
+ sql += '\n';
1902
+ }
1903
+ sql +=
1904
+ 'FROM pg_catalog.pg_amop o\n' +
1905
+ ' LEFT JOIN pg_catalog.pg_opfamily of ON of.oid = o.amopfamily\n' +
1906
+ ' LEFT JOIN pg_catalog.pg_am am ON am.oid = of.opfmethod AND am.oid = o.amopmethod\n' +
1907
+ ' LEFT JOIN pg_catalog.pg_namespace nsf ON of.opfnamespace = nsf.oid\n';
1908
+ if (verbose) {
1909
+ sql +=
1910
+ ' LEFT JOIN pg_catalog.pg_opfamily ofs ON ofs.oid = o.amopsortfamily\n';
1911
+ if (serverAtLeast(serverVersion, PG_18)) {
1912
+ sql +=
1913
+ ' LEFT JOIN pg_catalog.pg_operator op ON op.oid = o.amopopr\n' +
1914
+ ' LEFT JOIN pg_catalog.pg_proc p ON p.oid = op.oprcode\n';
1915
+ }
1916
+ }
1917
+ let hasWhere = false;
1918
+ if (opts.amPattern !== undefined) {
1919
+ sql += patternStub(hasWhere, undefined, 'am.amname');
1920
+ hasWhere = true;
1921
+ }
1922
+ if (opts.familyPattern !== undefined) {
1923
+ sql += patternStub(hasWhere, 'nsf.nspname', 'of.opfname');
1924
+ }
1925
+ sql +=
1926
+ 'ORDER BY 1, 2,\n' +
1927
+ ' o.amoplefttype = o.amoprighttype DESC,\n' +
1928
+ ' pg_catalog.format_type(o.amoplefttype, NULL),\n' +
1929
+ ' pg_catalog.format_type(o.amoprighttype, NULL),\n' +
1930
+ ' o.amopstrategy;';
1931
+ const ps = [];
1932
+ if (opts.amPattern !== undefined)
1933
+ ps.push(opts.amPattern);
1934
+ if (opts.familyPattern !== undefined)
1935
+ ps.push(opts.familyPattern);
1936
+ return {
1937
+ sql,
1938
+ params: ps,
1939
+ description: 'List of operators of operator families',
1940
+ };
1941
+ };
1942
+ /* ------------------------------------------------------------------ */
1943
+ /* \dAp — listOpFamilyFunctions */
1944
+ /* ------------------------------------------------------------------ */
1945
+ export const listOpFamilyFunctions = (opts) => {
1946
+ const { verbose } = opts;
1947
+ let sql = 'SELECT\n am.amname AS "AM",\n' +
1948
+ ' CASE\n' +
1949
+ ' WHEN pg_catalog.pg_opfamily_is_visible(of.oid)\n' +
1950
+ " THEN pg_catalog.format('%I', of.opfname)\n" +
1951
+ " ELSE pg_catalog.format('%I.%I', ns.nspname, of.opfname)\n" +
1952
+ ' END AS "Operator family",\n' +
1953
+ ' pg_catalog.format_type(ap.amproclefttype, NULL) AS "Registered left type",\n' +
1954
+ ' pg_catalog.format_type(ap.amprocrighttype, NULL) AS "Registered right type",\n' +
1955
+ ' ap.amprocnum AS "Number"\n';
1956
+ sql += verbose
1957
+ ? ', ap.amproc::pg_catalog.regprocedure AS "Function"\n'
1958
+ : ', p.proname AS "Function"\n';
1959
+ sql +=
1960
+ 'FROM pg_catalog.pg_amproc ap\n' +
1961
+ ' LEFT JOIN pg_catalog.pg_opfamily of ON of.oid = ap.amprocfamily\n' +
1962
+ ' LEFT JOIN pg_catalog.pg_am am ON am.oid = of.opfmethod\n' +
1963
+ ' LEFT JOIN pg_catalog.pg_namespace ns ON of.opfnamespace = ns.oid\n' +
1964
+ ' LEFT JOIN pg_catalog.pg_proc p ON ap.amproc = p.oid\n';
1965
+ let hasWhere = false;
1966
+ if (opts.amPattern !== undefined) {
1967
+ sql += patternStub(hasWhere, undefined, 'am.amname');
1968
+ hasWhere = true;
1969
+ }
1970
+ if (opts.familyPattern !== undefined) {
1971
+ sql += patternStub(hasWhere, 'ns.nspname', 'of.opfname');
1972
+ }
1973
+ sql +=
1974
+ 'ORDER BY 1, 2,\n ap.amproclefttype = ap.amprocrighttype DESC,\n 3, 4, 5;';
1975
+ const ps = [];
1976
+ if (opts.amPattern !== undefined)
1977
+ ps.push(opts.amPattern);
1978
+ if (opts.familyPattern !== undefined)
1979
+ ps.push(opts.familyPattern);
1980
+ return {
1981
+ sql,
1982
+ params: ps,
1983
+ description: 'List of support functions of operator families',
1984
+ };
1985
+ };
1986
+ /* ------------------------------------------------------------------ */
1987
+ /* \dl / \lo_list — listLargeObjects */
1988
+ /* ------------------------------------------------------------------ */
1989
+ export const listLargeObjects = (opts) => {
1990
+ const { verbose } = opts;
1991
+ let sql = 'SELECT oid as "ID",\n pg_catalog.pg_get_userbyid(lomowner) as "Owner",\n ';
1992
+ if (verbose) {
1993
+ sql += aclColumn('lomacl') + ',\n ';
1994
+ }
1995
+ sql +=
1996
+ 'pg_catalog.obj_description(oid, \'pg_largeobject\') as "Description"\n' +
1997
+ 'FROM pg_catalog.pg_largeobject_metadata\n' +
1998
+ 'ORDER BY oid';
1999
+ return { sql, params: [], description: 'Large objects' };
2000
+ };
2001
+ /* ------------------------------------------------------------------ */
2002
+ /* \sf — show function definition */
2003
+ /* \sv — show view definition */
2004
+ /* These are command-level (in command.c) but the SQL is trivial and */
2005
+ /* belongs with the rest of the describe SQL. */
2006
+ /* ------------------------------------------------------------------ */
2007
+ /**
2008
+ * Look up function OID by name for `\sf` / `\sf+`. Caller renders
2009
+ * `pg_catalog.pg_get_functiondef(oid)` afterwards. The lookup itself
2010
+ * uses regprocedure casting via the placeholder; WP-20 will replace.
2011
+ */
2012
+ export const showFunction = (opts) => {
2013
+ // We emit a select returning the function definition by name; upstream
2014
+ // psql resolves to oid then calls pg_get_functiondef(oid). Combine.
2015
+ const sql = `SELECT pg_catalog.pg_get_functiondef('${opts.name.replace(/'/g, "''")}'::pg_catalog.regprocedure) AS def;`;
2016
+ return { sql, params: [], description: 'Function definition' };
2017
+ };
2018
+ /**
2019
+ * Look up view definition for `\sv` / `\sv+`.
2020
+ */
2021
+ export const showView = (opts) => {
2022
+ const sql = `SELECT pg_catalog.pg_get_viewdef('${opts.name.replace(/'/g, "''")}'::pg_catalog.regclass, true) AS def;`;
2023
+ return { sql, params: [], description: 'View definition' };
2024
+ };
2025
+ /* ------------------------------------------------------------------ */
2026
+ /* Per-relation follow-up queries used by describeOneTableDetails. */
2027
+ /* Each builder takes a relation oid and returns a DescribeQuery the */
2028
+ /* formatter executes after the columns table. These mirror the */
2029
+ /* PSQLexec calls in upstream `describe.c` for the same sections. */
2030
+ /* ------------------------------------------------------------------ */
2031
+ /**
2032
+ * Per-relation table-info query. Returns one row carrying the flags
2033
+ * the formatter needs for the optional footer sections: RLS toggles
2034
+ * (relrowsecurity, relforcerowsecurity), replica-identity char,
2035
+ * partition flag, tablespace oid, access-method oid and pre-joined
2036
+ * pg_tablespace.spcname / pg_am.amname so the formatter doesn't have
2037
+ * to fan out for those.
2038
+ *
2039
+ * Columns: relrowsecurity, relforcerowsecurity, relreplident,
2040
+ * relispartition, reltablespace, relam, spcname, amname.
2041
+ *
2042
+ * Pre-PG 9.5 servers don't expose `relrowsecurity` /
2043
+ * `relforcerowsecurity` / `relispartition`; we synthesize false there.
2044
+ * Pre-PG 12 doesn't carry table AMs in pg_class.relam, so we leave the
2045
+ * amname slot null on those servers.
2046
+ */
2047
+ export const fetchTableInfo = (opts) => {
2048
+ const { oid, serverVersion } = opts;
2049
+ const hasRowSec = serverAtLeast(serverVersion, PG_9_5);
2050
+ const hasIsPart = serverAtLeast(serverVersion, PG_10);
2051
+ const hasRelAm = serverAtLeast(serverVersion, PG_12);
2052
+ const rowSec = hasRowSec
2053
+ ? 'c.relrowsecurity, c.relforcerowsecurity'
2054
+ : 'false AS relrowsecurity, false AS relforcerowsecurity';
2055
+ const isPart = hasIsPart ? 'c.relispartition' : 'false AS relispartition';
2056
+ const amCol = hasRelAm ? 'c.relam' : '0::oid AS relam';
2057
+ const amName = hasRelAm ? 'am.amname' : 'NULL::name AS amname';
2058
+ const amJoin = hasRelAm
2059
+ ? ' LEFT JOIN pg_catalog.pg_am am ON am.oid = c.relam\n'
2060
+ : '';
2061
+ const sql = 'SELECT ' +
2062
+ rowSec +
2063
+ ',\n c.relreplident,\n ' +
2064
+ isPart +
2065
+ ',\n c.reltablespace,\n ' +
2066
+ amCol +
2067
+ ',\n ts.spcname,\n ' +
2068
+ amName +
2069
+ '\nFROM pg_catalog.pg_class c\n' +
2070
+ ' LEFT JOIN pg_catalog.pg_tablespace ts ON ts.oid = c.reltablespace\n' +
2071
+ amJoin +
2072
+ `WHERE c.oid = '${oid}';`;
2073
+ return { sql, params: [], description: 'Relation footer info' };
2074
+ };
2075
+ /**
2076
+ * Named NOT NULL constraints (`pg_constraint.contype = 'n'`), introduced
2077
+ * in PG 18. Output columns mirror upstream `describeOneTableDetails`
2078
+ * (describe.c ~3104):
2079
+ * conname, attname, connoinherit, conislocal.
2080
+ *
2081
+ * The constraint references a single column via `conkey[1]`; we join
2082
+ * `pg_attribute` on that attnum and order by it so the footer lists the
2083
+ * constraints in column order. On pre-PG-18 servers the catalog never
2084
+ * carries `contype = 'n'` rows, so the query returns empty and the
2085
+ * caller suppresses the footer — no version branch is needed in the SQL.
2086
+ */
2087
+ export const fetchNotNullConstraints = (opts) => {
2088
+ const { oid, serverVersion } = opts;
2089
+ if (serverLess(serverVersion, PG_18)) {
2090
+ return {
2091
+ sql: 'SELECT NULL::pg_catalog.text, NULL::pg_catalog.text, false, false WHERE false;',
2092
+ params: [],
2093
+ description: 'Not-null constraints',
2094
+ };
2095
+ }
2096
+ const sql = 'SELECT co.conname, at.attname, co.connoinherit, co.conislocal\n' +
2097
+ 'FROM pg_catalog.pg_constraint co\n' +
2098
+ ' JOIN pg_catalog.pg_attribute at\n' +
2099
+ ' ON (at.attrelid = co.conrelid AND at.attnum = co.conkey[1])\n' +
2100
+ `WHERE co.contype = 'n' AND co.conrelid = '${oid}'\n` +
2101
+ 'ORDER BY at.attnum;';
2102
+ return { sql, params: [], description: 'Not-null constraints' };
2103
+ };
2104
+ /**
2105
+ * RLS policies for a relation. Output columns mirror upstream:
2106
+ * polname, polpermissive, roles (CSV or NULL), polqual, polwithcheck,
2107
+ * polcmd (mapped to text).
2108
+ *
2109
+ * The polcmd→keyword mapping matches upstream: 'r'→SELECT, 'a'→INSERT,
2110
+ * 'w'→UPDATE, 'd'→DELETE, '*'→NULL (i.e. ALL — emitted as no FOR clause).
2111
+ */
2112
+ export const fetchPolicies = (opts) => {
2113
+ const { oid, serverVersion } = opts;
2114
+ const permissive = serverAtLeast(serverVersion, PG_10)
2115
+ ? 'pol.polpermissive'
2116
+ : "'t' AS polpermissive";
2117
+ const sql = 'SELECT pol.polname,\n ' +
2118
+ permissive +
2119
+ ",\n CASE WHEN pol.polroles = '{0}' THEN NULL ELSE pg_catalog.array_to_string(array(SELECT rolname FROM pg_catalog.pg_roles WHERE oid = ANY (pol.polroles) ORDER BY 1), ', ') END AS roles,\n" +
2120
+ ' pg_catalog.pg_get_expr(pol.polqual, pol.polrelid) AS qual,\n' +
2121
+ ' pg_catalog.pg_get_expr(pol.polwithcheck, pol.polrelid) AS withcheck,\n' +
2122
+ ' CASE pol.polcmd\n' +
2123
+ " WHEN 'r' THEN 'SELECT'\n" +
2124
+ " WHEN 'a' THEN 'INSERT'\n" +
2125
+ " WHEN 'w' THEN 'UPDATE'\n" +
2126
+ " WHEN 'd' THEN 'DELETE'\n" +
2127
+ ' END AS cmd\n' +
2128
+ 'FROM pg_catalog.pg_policy pol\n' +
2129
+ `WHERE pol.polrelid = '${oid}'\n` +
2130
+ 'ORDER BY pol.polname;';
2131
+ return { sql, params: [], description: 'Relation policies' };
2132
+ };
2133
+ /**
2134
+ * Parents from `pg_inherits`. We exclude partitioned parents so
2135
+ * "Inherits:" only lists genuine inheritance (partition parents go in
2136
+ * "Partition of:" instead). Output: one column with a regclass-formatted
2137
+ * parent name per row.
2138
+ */
2139
+ export const fetchInherits = (opts) => {
2140
+ const { oid } = opts;
2141
+ const sql = 'SELECT c.oid::pg_catalog.regclass\n' +
2142
+ 'FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i\n' +
2143
+ `WHERE c.oid = i.inhparent AND i.inhrelid = '${oid}'\n` +
2144
+ " AND c.relkind NOT IN ('p', 'I')\n" +
2145
+ 'ORDER BY inhseqno;';
2146
+ return { sql, params: [], description: 'Inherited-from relations' };
2147
+ };
2148
+ /**
2149
+ * Child tables / partitions from `pg_inherits`. Output columns:
2150
+ * relname (regclass), relkind, inhdetachpending, partition-bound (or NULL).
2151
+ *
2152
+ * Pre-PG 10 servers don't expose `relpartbound`; pre-PG 14 don't have
2153
+ * `inhdetachpending`. We synthesize NULL / false there.
2154
+ */
2155
+ export const fetchInheritedBy = (opts) => {
2156
+ const { oid, serverVersion } = opts;
2157
+ const hasDetach = serverAtLeast(serverVersion, PG_14);
2158
+ const hasPartBound = serverAtLeast(serverVersion, PG_10);
2159
+ const detachCol = hasDetach
2160
+ ? 'i.inhdetachpending'
2161
+ : 'false AS inhdetachpending';
2162
+ const boundCol = hasPartBound
2163
+ ? 'pg_catalog.pg_get_expr(c.relpartbound, c.oid)'
2164
+ : 'NULL::text';
2165
+ const orderBy = hasPartBound
2166
+ ? "pg_catalog.pg_get_expr(c.relpartbound, c.oid) = 'DEFAULT'," +
2167
+ ' c.oid::pg_catalog.regclass::pg_catalog.text'
2168
+ : 'c.oid::pg_catalog.regclass::pg_catalog.text';
2169
+ const sql = 'SELECT c.oid::pg_catalog.regclass, c.relkind,\n ' +
2170
+ detachCol +
2171
+ ',\n ' +
2172
+ boundCol +
2173
+ '\nFROM pg_catalog.pg_class c, pg_catalog.pg_inherits i\n' +
2174
+ `WHERE c.oid = i.inhrelid AND i.inhparent = '${oid}'\n` +
2175
+ `ORDER BY ${orderBy};`;
2176
+ return { sql, params: [], description: 'Inheriting / child relations' };
2177
+ };
2178
+ /**
2179
+ * Partition-of details for a child partition (`relispartition = true`).
2180
+ * Returns parent regclass + partition bound (+ partition constraint when
2181
+ * verbose is requested by the caller). Per upstream, this is invoked
2182
+ * unconditionally for child partitions regardless of verbose, with the
2183
+ * partition-constraint column conditionally selected.
2184
+ */
2185
+ export const fetchPartitionOf = (opts) => {
2186
+ const { oid, serverVersion, withConstraint } = opts;
2187
+ const detachCol = serverAtLeast(serverVersion, PG_14)
2188
+ ? 'i.inhdetachpending'
2189
+ : 'false AS inhdetachpending';
2190
+ let sql = 'SELECT inhparent::pg_catalog.regclass,\n' +
2191
+ ' pg_catalog.pg_get_expr(c.relpartbound, c.oid),\n ' +
2192
+ detachCol;
2193
+ if (withConstraint) {
2194
+ sql += ',\n pg_catalog.pg_get_partition_constraintdef(c.oid)';
2195
+ }
2196
+ sql +=
2197
+ '\nFROM pg_catalog.pg_class c\n' +
2198
+ ' JOIN pg_catalog.pg_inherits i ON c.oid = i.inhrelid\n' +
2199
+ `WHERE c.oid = '${oid}';`;
2200
+ return { sql, params: [], description: 'Partition-of details' };
2201
+ };
2202
+ /**
2203
+ * Partition-key definition for a partitioned-table parent
2204
+ * (`relkind = 'p'`). One row, one text column.
2205
+ */
2206
+ export const fetchPartitionKey = (opts) => {
2207
+ const sql = `SELECT pg_catalog.pg_get_partkeydef('${opts.oid}'::pg_catalog.oid);`;
2208
+ return { sql, params: [], description: 'Partition key' };
2209
+ };
2210
+ /**
2211
+ * Foreign-table footer info: server name + ftoptions as a pre-formatted
2212
+ * "key 'val', key 'val'" string. Two-column single-row result; an empty
2213
+ * options string means no FDW options.
2214
+ */
2215
+ export const fetchForeignTableInfo = (opts) => {
2216
+ const { oid } = opts;
2217
+ const sql = 'SELECT s.srvname,\n' +
2218
+ ' pg_catalog.array_to_string(ARRAY(\n' +
2219
+ " SELECT pg_catalog.quote_ident(option_name) || ' ' || pg_catalog.quote_literal(option_value)\n" +
2220
+ " FROM pg_catalog.pg_options_to_table(ftoptions)), ', ') AS ftoptions\n" +
2221
+ 'FROM pg_catalog.pg_foreign_table f,\n' +
2222
+ ' pg_catalog.pg_foreign_server s\n' +
2223
+ `WHERE f.ftrelid = '${oid}' AND s.oid = f.ftserver;`;
2224
+ return { sql, params: [], description: 'Foreign-table footer info' };
2225
+ };
2226
+ /**
2227
+ * Pull `indexrelid::regclass` for the index marked as REPLICA IDENTITY
2228
+ * on this relation. Returns at most one row when relreplident = 'i'.
2229
+ */
2230
+ export const fetchReplicaIdentityIndex = (opts) => {
2231
+ const { oid } = opts;
2232
+ const sql = 'SELECT c2.relname\n' +
2233
+ 'FROM pg_catalog.pg_index i\n' +
2234
+ ' JOIN pg_catalog.pg_class c2 ON c2.oid = i.indexrelid\n' +
2235
+ `WHERE i.indrelid = '${oid}' AND i.indisreplident\n` +
2236
+ 'LIMIT 1;';
2237
+ return { sql, params: [], description: 'Replica-identity index' };
2238
+ };
2239
+ /**
2240
+ * Extended-statistics objects defined on a relation. Mirrors the upstream
2241
+ * verbose-only `\d+` section. Output columns:
2242
+ * stxnsp, stxname, ndist_enabled, deps_enabled, mcv_enabled, columns,
2243
+ * stxrelname, stxstattarget.
2244
+ *
2245
+ * Only meaningful on PG 10+ (the catalog table `pg_statistic_ext` doesn't
2246
+ * exist before then). Caller gates by `relkind in ('r','m','p','f')` and
2247
+ * verbose mode.
2248
+ */
2249
+ export const fetchStatisticsObjects = (opts) => {
2250
+ const { oid, serverVersion } = opts;
2251
+ if (serverLess(serverVersion, PG_10)) {
2252
+ return {
2253
+ sql: '/* server < 10 does not support extended statistics */ SELECT 1 WHERE false;',
2254
+ params: [],
2255
+ description: 'Statistics objects',
2256
+ };
2257
+ }
2258
+ // `pg_get_statisticsobjdef_columns` was added in PG 14. Older servers
2259
+ // reconstruct the column list from `pg_statistic_ext.stxkeys`.
2260
+ const columnsExpr = serverAtLeast(serverVersion, PG_14)
2261
+ ? 'pg_catalog.pg_get_statisticsobjdef_columns(es.oid)'
2262
+ : "(SELECT pg_catalog.string_agg(pg_catalog.quote_ident(a.attname), ', ')\n" +
2263
+ ' FROM pg_catalog.unnest(es.stxkeys) k(attnum)\n' +
2264
+ ' JOIN pg_catalog.pg_attribute a ON a.attrelid = es.stxrelid AND a.attnum = k.attnum AND NOT a.attisdropped)';
2265
+ // `pg_statistic_ext.stxstattarget` exists on every supported PG; PG 17
2266
+ // changed its type from int4 to int2 but we just CAST to text.
2267
+ const sql = 'SELECT es.stxnamespace::pg_catalog.regnamespace::pg_catalog.text AS stxnsp,\n' +
2268
+ ' es.stxname,\n' +
2269
+ " 'd' = any(es.stxkind) AS ndist_enabled,\n" +
2270
+ " 'f' = any(es.stxkind) AS deps_enabled,\n" +
2271
+ " 'm' = any(es.stxkind) AS mcv_enabled,\n" +
2272
+ ' ' +
2273
+ columnsExpr +
2274
+ ' AS columns,\n' +
2275
+ ' es.stxrelid::pg_catalog.regclass::pg_catalog.text AS stxrelname,\n' +
2276
+ ' es.stxstattarget\n' +
2277
+ 'FROM pg_catalog.pg_statistic_ext es\n' +
2278
+ `WHERE es.stxrelid = '${oid}'\n` +
2279
+ 'ORDER BY stxnsp, es.stxname;';
2280
+ return { sql, params: [], description: 'Statistics objects' };
2281
+ };
2282
+ /**
2283
+ * Publications a relation belongs to. Returns one column `pubname` per
2284
+ * publication. Covers the three sources upstream considers:
2285
+ * - explicit `pg_publication_rel` membership
2286
+ * - `puballtables` (FOR ALL TABLES)
2287
+ * - `pg_publication_namespace` (FOR ALL TABLES IN SCHEMA, PG 15+)
2288
+ *
2289
+ * Pre-PG 10 servers return an empty set (publications don't exist).
2290
+ */
2291
+ export const fetchTablePublications = (opts) => {
2292
+ const { oid, serverVersion } = opts;
2293
+ if (serverLess(serverVersion, PG_10)) {
2294
+ return {
2295
+ sql: '/* server < 10 does not support publications */ SELECT 1 WHERE false;',
2296
+ params: [],
2297
+ description: 'Table publications',
2298
+ };
2299
+ }
2300
+ const hasPubNs = serverAtLeast(serverVersion, PG_15);
2301
+ let sql = 'SELECT pub.pubname\n' +
2302
+ 'FROM pg_catalog.pg_publication pub\n' +
2303
+ 'WHERE pub.puballtables\n' +
2304
+ ' OR EXISTS (\n' +
2305
+ ' SELECT 1 FROM pg_catalog.pg_publication_rel pr\n' +
2306
+ ` WHERE pr.prpubid = pub.oid AND pr.prrelid = '${oid}'\n` +
2307
+ ' )';
2308
+ if (hasPubNs) {
2309
+ sql +=
2310
+ '\n OR EXISTS (\n' +
2311
+ ' SELECT 1 FROM pg_catalog.pg_publication_namespace pn\n' +
2312
+ ` JOIN pg_catalog.pg_class c ON c.oid = '${oid}'\n` +
2313
+ ' WHERE pn.pnpubid = pub.oid AND pn.pnnspid = c.relnamespace\n' +
2314
+ ' )';
2315
+ }
2316
+ sql += '\nORDER BY 1;';
2317
+ return { sql, params: [], description: 'Table publications' };
2318
+ };
2319
+ /**
2320
+ * Subscriptions that include this relation. Mirrors the upstream
2321
+ * `\d+` section. Requires read access to `pg_subscription`, which is
2322
+ * only granted to superusers — the formatter must swallow
2323
+ * permission-denied errors silently.
2324
+ *
2325
+ * Pre-PG 10 servers return an empty set.
2326
+ */
2327
+ export const fetchTableSubscriptions = (opts) => {
2328
+ const { oid, serverVersion } = opts;
2329
+ if (serverLess(serverVersion, PG_10)) {
2330
+ return {
2331
+ sql: '/* server < 10 does not support subscriptions */ SELECT 1 WHERE false;',
2332
+ params: [],
2333
+ description: 'Table subscriptions',
2334
+ };
2335
+ }
2336
+ const sql = 'SELECT sub.subname\n' +
2337
+ 'FROM pg_catalog.pg_subscription sub\n' +
2338
+ 'JOIN pg_catalog.pg_subscription_rel sr ON sr.srsubid = sub.oid\n' +
2339
+ `WHERE sr.srrelid = '${oid}'\n` +
2340
+ 'ORDER BY 1;';
2341
+ return { sql, params: [], description: 'Table subscriptions' };
2342
+ };
2343
+ /**
2344
+ * Per-column FDW options for a foreign table. Returns one row per column
2345
+ * that has at least one `attfdwoptions` entry. Output columns:
2346
+ * attname, opts (pre-formatted "k 'v', k 'v'").
2347
+ *
2348
+ * Only meaningful for `relkind = 'f'`.
2349
+ */
2350
+ export const fetchPerColumnFdwOptions = (opts) => {
2351
+ const { oid } = opts;
2352
+ const sql = 'SELECT a.attname,\n' +
2353
+ ' pg_catalog.array_to_string(ARRAY(\n' +
2354
+ " SELECT pg_catalog.quote_ident(option_name) || ' ' || pg_catalog.quote_literal(option_value)\n" +
2355
+ " FROM pg_catalog.pg_options_to_table(a.attfdwoptions)), ', ') AS opts\n" +
2356
+ 'FROM pg_catalog.pg_attribute a\n' +
2357
+ `WHERE a.attrelid = '${oid}' AND a.attnum > 0 AND NOT a.attisdropped\n` +
2358
+ ' AND a.attfdwoptions IS NOT NULL\n' +
2359
+ 'ORDER BY a.attnum;';
2360
+ return { sql, params: [], description: 'Per-column FDW options' };
2361
+ };
2362
+ /**
2363
+ * For a TOAST table (`relkind = 't'`), look up the owning user table.
2364
+ * Returns at most one row with the parent's regclass-formatted name.
2365
+ */
2366
+ export const fetchToastOwningTable = (opts) => {
2367
+ const { oid } = opts;
2368
+ const sql = 'SELECT oid::pg_catalog.regclass::pg_catalog.text AS relname\n' +
2369
+ 'FROM pg_catalog.pg_class\n' +
2370
+ `WHERE reltoastrelid = '${oid}'\n` +
2371
+ 'LIMIT 1;';
2372
+ return { sql, params: [], description: 'TOAST owning table' };
2373
+ };