neonctl 2.22.0 → 2.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/README.md +242 -16
  2. package/analytics.js +5 -2
  3. package/commands/branches.js +9 -1
  4. package/commands/checkout.js +249 -0
  5. package/commands/connection_string.js +15 -2
  6. package/commands/data_api.js +286 -0
  7. package/commands/functions.js +277 -0
  8. package/commands/index.js +12 -0
  9. package/commands/link.js +667 -0
  10. package/commands/neon_auth.js +1013 -0
  11. package/commands/projects.js +9 -1
  12. package/commands/psql.js +62 -0
  13. package/commands/set_context.js +7 -2
  14. package/context.js +86 -14
  15. package/functions_api.js +44 -0
  16. package/index.js +3 -0
  17. package/package.json +60 -51
  18. package/psql/cli.js +51 -0
  19. package/psql/command/cmd_cond.js +437 -0
  20. package/psql/command/cmd_connect.js +815 -0
  21. package/psql/command/cmd_copy.js +1025 -0
  22. package/psql/command/cmd_describe.js +1810 -0
  23. package/psql/command/cmd_format.js +909 -0
  24. package/psql/command/cmd_io.js +2187 -0
  25. package/psql/command/cmd_lo.js +385 -0
  26. package/psql/command/cmd_meta.js +970 -0
  27. package/psql/command/cmd_misc.js +187 -0
  28. package/psql/command/cmd_pipeline.js +1141 -0
  29. package/psql/command/cmd_restrict.js +171 -0
  30. package/psql/command/cmd_show.js +751 -0
  31. package/psql/command/dispatch.js +343 -0
  32. package/psql/command/inputQueue.js +42 -0
  33. package/psql/command/shared.js +71 -0
  34. package/psql/complete/filenames.js +139 -0
  35. package/psql/complete/index.js +104 -0
  36. package/psql/complete/matcher.js +314 -0
  37. package/psql/complete/psqlVars.js +247 -0
  38. package/psql/complete/queries.js +491 -0
  39. package/psql/complete/rules.js +2387 -0
  40. package/psql/core/common.js +1250 -0
  41. package/psql/core/help.js +576 -0
  42. package/psql/core/mainloop.js +1353 -0
  43. package/psql/core/prompt.js +437 -0
  44. package/psql/core/settings.js +684 -0
  45. package/psql/core/sqlHelp.js +1066 -0
  46. package/psql/core/startup.js +840 -0
  47. package/psql/core/syncVars.js +116 -0
  48. package/psql/core/variables.js +287 -0
  49. package/psql/describe/formatters.js +1277 -0
  50. package/psql/describe/processNamePattern.js +270 -0
  51. package/psql/describe/queries.js +2373 -0
  52. package/psql/describe/versionGate.js +43 -0
  53. package/psql/index.js +2005 -0
  54. package/psql/io/history.js +299 -0
  55. package/psql/io/input.js +120 -0
  56. package/psql/io/lineEditor/buffer.js +323 -0
  57. package/psql/io/lineEditor/complete.js +227 -0
  58. package/psql/io/lineEditor/filename.js +159 -0
  59. package/psql/io/lineEditor/index.js +891 -0
  60. package/psql/io/lineEditor/keymap.js +738 -0
  61. package/psql/io/lineEditor/vt100.js +363 -0
  62. package/psql/io/pgpass.js +202 -0
  63. package/psql/io/pgservice.js +194 -0
  64. package/psql/io/psqlrc.js +422 -0
  65. package/psql/print/aligned.js +1756 -0
  66. package/psql/print/asciidoc.js +248 -0
  67. package/psql/print/crosstab.js +460 -0
  68. package/psql/print/csv.js +92 -0
  69. package/psql/print/html.js +258 -0
  70. package/psql/print/json.js +96 -0
  71. package/psql/print/latex.js +396 -0
  72. package/psql/print/pager.js +265 -0
  73. package/psql/print/troff.js +258 -0
  74. package/psql/print/unaligned.js +118 -0
  75. package/psql/print/units.js +135 -0
  76. package/psql/scanner/slash.js +513 -0
  77. package/psql/scanner/sql.js +910 -0
  78. package/psql/scanner/stringutils.js +390 -0
  79. package/psql/types/backslash.js +1 -0
  80. package/psql/types/connection.js +1 -0
  81. package/psql/types/index.js +7 -0
  82. package/psql/types/printer.js +1 -0
  83. package/psql/types/repl.js +1 -0
  84. package/psql/types/scanner.js +24 -0
  85. package/psql/types/settings.js +1 -0
  86. package/psql/types/variables.js +1 -0
  87. package/psql/wire/connection.js +2844 -0
  88. package/psql/wire/copy.js +108 -0
  89. package/psql/wire/notify.js +59 -0
  90. package/psql/wire/pipeline.js +519 -0
  91. package/psql/wire/protocol.js +466 -0
  92. package/psql/wire/sasl.js +296 -0
  93. package/psql/wire/tls.js +596 -0
  94. package/test_utils/fixtures.js +1 -0
  95. package/utils/enrichers.js +18 -1
  96. package/utils/esbuild.js +147 -0
  97. package/utils/middlewares.js +1 -1
  98. package/utils/psql.js +107 -11
  99. package/utils/zip.js +4 -0
  100. package/writer.js +1 -1
  101. package/commands/auth.test.js +0 -211
  102. package/commands/branches.test.js +0 -460
  103. package/commands/connection_string.test.js +0 -196
  104. package/commands/databases.test.js +0 -39
  105. package/commands/help.test.js +0 -9
  106. package/commands/init.test.js +0 -56
  107. package/commands/ip_allow.test.js +0 -59
  108. package/commands/operations.test.js +0 -7
  109. package/commands/orgs.test.js +0 -7
  110. package/commands/projects.test.js +0 -144
  111. package/commands/roles.test.js +0 -37
  112. package/commands/set_context.test.js +0 -159
  113. package/commands/vpc_endpoints.test.js +0 -69
  114. package/env.test.js +0 -55
  115. package/utils/formats.test.js +0 -32
  116. package/writer.test.js +0 -104
@@ -0,0 +1,1810 @@
1
+ /**
2
+ * psql `\d*` describe-command dispatchers.
3
+ *
4
+ * Each command takes an optional pattern, parses verbose / show-system
5
+ * suffixes from the command name, runs the appropriate SQL template
6
+ * from {@link '../describe/queries.js'} through the pattern parser, and
7
+ * prints the result via the aligned printer.
8
+ *
9
+ * Command name suffix decoding:
10
+ *
11
+ * - Trailing `+` → `verbose=true` (extra columns)
12
+ * - Trailing `S` → `showSystem=true` (include `pg_*` schemas)
13
+ * - Both may combine: `\dtS+`, `\dt+S`, etc.
14
+ *
15
+ * Dispatch model: every `\d*` family registers itself as a separate
16
+ * primary name (e.g. `dt`, `dt+`, `dtS`, `dtS+`). That's not how psql
17
+ * itself does it — psql parses a single `\d` token and then peels off
18
+ * letter-by-letter — but our `BackslashRegistry` is keyed by full
19
+ * command name and is much easier to read this way. The semantic
20
+ * outcome is identical.
21
+ *
22
+ * For `\d <name>` (the bare describe), we look the relation up via
23
+ * {@link lookupOneRelation} and dispatch to the right `describeOne*`
24
+ * renderer based on relkind:
25
+ *
26
+ * - 'r' / 'p' / 'f' (regular, partitioned, foreign) →
27
+ * {@link describeOneTableDetails}
28
+ * - 'v' → {@link describeOneViewDetails}
29
+ * - 'S' → {@link describeOneSequence}
30
+ * - 'i' / 'I' (index, partitioned index) → table-detail with index header
31
+ * - 'm' → table-detail with materialized-view header
32
+ *
33
+ * Connection guard: every command checks `ctx.settings.db` is non-null
34
+ * and emits `\<cmd>: no current connection` to stderr on miss.
35
+ */
36
+ import { describeOneSequence, describeOneTableDetails, describeOneViewDetails, lookupOneRelation, runListQuery, } from '../describe/formatters.js';
37
+ import { describeAccessMethods, describeAggregates, describeConfigurationParameters, describeFunctions, describeOperators, describeRoleGrants, describeRoles, describeSubscriptions, describeTableDetails, describeTablespaces, describeTypes, listAllDbs, listCasts, listCollations, listConversions, listDbRoleSettings, listDefaultACLs, listDomains, listEventTriggers, listExtendedStats, listExtensions, listForeignDataWrappers, listForeignServers, listForeignTables, listLanguages, listLargeObjects, listOpFamilyFunctions, listOpFamilyOperators, listOperatorClasses, listOperatorFamilies, listPartitionedTables, listPublications, listSchemas, listTSConfigs, listTSDictionaries, listTSParsers, listTSTemplates, listTables, listUserMappings, objectDescription, permissionsList, } from '../describe/queries.js';
38
+ import { processSQLNamePattern, } from '../describe/processNamePattern.js';
39
+ import { writeErr } from './shared.js';
40
+ /**
41
+ * Helper: split a `\dXY+` command name into base, verbose, showSystem.
42
+ *
43
+ * "dtS+" → { base: "dt", verbose: true, showSystem: true }
44
+ * "df+" → { base: "df", verbose: true, showSystem: false }
45
+ * "dnS" → { base: "dn", verbose: false, showSystem: true }
46
+ */
47
+ const decodeSuffix = (cmdName, base) => {
48
+ const tail = cmdName.slice(base.length);
49
+ let verbose = false;
50
+ let showSystem = false;
51
+ // Suffix order is unrestricted in psql.
52
+ for (const ch of tail) {
53
+ if (ch === '+')
54
+ verbose = true;
55
+ else if (ch === 'S')
56
+ showSystem = true;
57
+ }
58
+ return { verbose, showSystem };
59
+ };
60
+ /** Resolve the current connection or null. */
61
+ const conn = (ctx) => ctx.settings.db;
62
+ /** Emit "no current connection" error. */
63
+ const noConn = (ctx) => {
64
+ writeErr(`\\${ctx.cmdName}: no current connection\n`);
65
+ return { status: 'error', errorWritten: true };
66
+ };
67
+ /**
68
+ * Read the connection's current database. Mirrors upstream's
69
+ * `PQdb(pset.db)` — used by the cross-database check. PgConnection
70
+ * exposes `database` as a getter; mocks typically pass it as a record
71
+ * property. Returns `''` on miss; callers compare case-sensitively
72
+ * (matching upstream's `strcmp`).
73
+ */
74
+ const currentDb = (c) => {
75
+ const meta = c;
76
+ return typeof meta.database === 'string' ? meta.database : '';
77
+ };
78
+ /**
79
+ * Validate a `processSQLNamePattern` result against the command's max
80
+ * dotted-name budget. Mirrors the dot-count check upstream's
81
+ * `processSQLNamePattern` performs after parsing:
82
+ *
83
+ * - `dotCount > maxDots` → "improper qualified name (too many dotted names)"
84
+ * - `dotCount == 2 && maxDots == 2 && dbLiteral != current_db` →
85
+ * "cross-database references are not implemented"
86
+ *
87
+ * Pass `maxDots = 0` for commands that don't accept schema-qualified
88
+ * patterns (`\dA`, `\dx`, `\dn`, `\db`, `\des`, …). Pass `maxDots = 2`
89
+ * for the schema-qualified-with-db family (`\dt`, `\df`, `\dD`, …) so
90
+ * the 2-dot case emits the dedicated cross-database error rather than
91
+ * a generic "too many" message.
92
+ *
93
+ * Returns `null` on success; the formatted error string otherwise.
94
+ */
95
+ const validatePattern = (pattern, result, maxDots, curDb) => {
96
+ if (pattern === null)
97
+ return null;
98
+ if (result.dotCount > maxDots) {
99
+ return `improper qualified name (too many dotted names): ${pattern}`;
100
+ }
101
+ if (maxDots >= 1 &&
102
+ result.dotCount === maxDots &&
103
+ result.dbLiteral !== null &&
104
+ result.dbLiteral !== curDb) {
105
+ return `cross-database references are not implemented: ${pattern}`;
106
+ }
107
+ return null;
108
+ };
109
+ const runWithPattern = async (ctx, pattern, query, patternOpts, maxDots = 2, overrides = {}) => {
110
+ const c = conn(ctx);
111
+ if (!c)
112
+ return noConn(ctx);
113
+ const result = processSQLNamePattern({ ...patternOpts, pattern });
114
+ const dotErr = validatePattern(pattern, result, maxDots, currentDb(c));
115
+ if (dotErr !== null) {
116
+ writeErr(`${dotErr}\n`);
117
+ return { status: 'error', errorWritten: true };
118
+ }
119
+ try {
120
+ const basePopt = ctx.settings.popt;
121
+ const popt = overrides.suppressDefaultFooter
122
+ ? { ...basePopt, topt: { ...basePopt.topt, defaultFooter: false } }
123
+ : basePopt;
124
+ await runListQuery(c, query, result, process.stdout, popt);
125
+ return { status: 'ok' };
126
+ }
127
+ catch (err) {
128
+ writeErr(`\\${ctx.cmdName}: ${errMsg(err)}\n`);
129
+ return { status: 'error', errorWritten: true };
130
+ }
131
+ };
132
+ /**
133
+ * Apply two distinct {@link NamePatternResult}s to a query that emits two
134
+ * placeholder occurrences (one per pattern). Mirrors `applyPattern` but
135
+ * threads each result into a single occurrence so they can carry
136
+ * different column targets (e.g. `\dAc <am> <type>` filters
137
+ * `am.amname` first and `t.typname` second). When a slot's result
138
+ * carries no conditions we substitute the `true` tautology so the
139
+ * surrounding `AND`/`WHERE` chain stays well-formed.
140
+ *
141
+ * Returns a query string with the placeholders replaced and a unified
142
+ * parameter list renumbered so `$N` references stay distinct.
143
+ */
144
+ const applyTwoPatterns = (sql, baseParams, results) => {
145
+ const placeholder = 'true /* TODO(WP-20): pattern matching */';
146
+ let rendered = sql;
147
+ const params = [...baseParams];
148
+ for (const result of results) {
149
+ const idx = rendered.indexOf(placeholder);
150
+ if (idx < 0)
151
+ break;
152
+ const conds = [
153
+ ...result.schemaConditions,
154
+ ...result.nameConditions,
155
+ ...result.visibilityConditions,
156
+ ];
157
+ const slotOffset = params.length;
158
+ const renumbered = conds.map((c) => c.replace(/\$(\d+)/g, (_, n) => `$${Number(n) + slotOffset}`));
159
+ params.push(...result.params);
160
+ const replacement = renumbered.length === 0 ? 'true' : `(${renumbered.join(' AND ')})`;
161
+ rendered =
162
+ rendered.slice(0, idx) +
163
+ replacement +
164
+ rendered.slice(idx + placeholder.length);
165
+ }
166
+ return { sql: rendered, params };
167
+ };
168
+ /**
169
+ * Run a list query with two distinct pattern slots. Builds the final
170
+ * SQL via {@link applyTwoPatterns}, hands it to {@link runListQuery}
171
+ * with an empty pattern result (so the second-pass `applyPattern` is a
172
+ * no-op — all placeholders are already resolved).
173
+ *
174
+ * Note: we discard the query builder's `params` because the two-pattern
175
+ * builders (`listOperatorClasses`, `listDbRoleSettings`) emit the raw
176
+ * user strings there as a courtesy for single-pattern callers. The
177
+ * regex-converted values come from `results[*].params` instead, so
178
+ * threading the originals would introduce unreferenced bind values.
179
+ */
180
+ const runDualPatternList = async (ctx, query, results) => {
181
+ const c = conn(ctx);
182
+ if (!c)
183
+ return noConn(ctx);
184
+ const { sql, params } = applyTwoPatterns(query.sql, [], results);
185
+ const finalQuery = {
186
+ ...query,
187
+ sql,
188
+ params,
189
+ };
190
+ const empty = {
191
+ schemaConditions: [],
192
+ nameConditions: [],
193
+ visibilityConditions: [],
194
+ params: [],
195
+ dotCount: 0,
196
+ dbLiteral: null,
197
+ };
198
+ try {
199
+ await runListQuery(c, finalQuery, empty, process.stdout, ctx.settings.popt);
200
+ return { status: 'ok' };
201
+ }
202
+ catch (err) {
203
+ writeErr(`\\${ctx.cmdName}: ${errMsg(err)}\n`);
204
+ return { status: 'error', errorWritten: true };
205
+ }
206
+ };
207
+ /**
208
+ * Apply per-argument type patterns into the `ARG_PATTERN_<i>`
209
+ * placeholders emitted by {@link describeFunctions} and
210
+ * {@link describeOperators}. Each non-`-` arg pattern produces a set of
211
+ * conditions against the `t<i>` / `nt<i>` join, matched against
212
+ * `typname`, `nt<i>.nspname`, and the formatted-type expression — same
213
+ * semantics as upstream's `validateSQLNamePattern(... ft, tiv, ...)`
214
+ * call in `describe.c`. `-` slots emit a literal `typname IS NULL`
215
+ * check at SQL build time and don't generate any conditions here.
216
+ *
217
+ * Returns the rewritten SQL plus the merged parameter list (renumbered
218
+ * so `$N` slots from each arg pattern don't collide with prior slots).
219
+ */
220
+ const applyArgPatterns = (sql, baseParams, argResults) => {
221
+ let rendered = sql;
222
+ const params = [...baseParams];
223
+ for (let i = 0; i < argResults.length; i++) {
224
+ const placeholder = `true /* ARG_PATTERN_${i} */`;
225
+ const idx = rendered.indexOf(placeholder);
226
+ if (idx < 0)
227
+ continue;
228
+ const r = argResults[i];
229
+ let replacement = 'true';
230
+ if (r !== null) {
231
+ const conds = [
232
+ ...r.schemaConditions,
233
+ ...r.nameConditions,
234
+ ...r.visibilityConditions,
235
+ ];
236
+ if (conds.length > 0) {
237
+ const slotOffset = params.length;
238
+ const renumbered = conds.map((c) => c.replace(/\$(\d+)/g, (_, n) => `$${Number(n) + slotOffset}`));
239
+ params.push(...r.params);
240
+ replacement = `(${renumbered.join(' AND ')})`;
241
+ }
242
+ }
243
+ rendered =
244
+ rendered.slice(0, idx) +
245
+ replacement +
246
+ rendered.slice(idx + placeholder.length);
247
+ }
248
+ return { sql: rendered, params };
249
+ };
250
+ /**
251
+ * Upstream `map_typename_pattern()`: a few aliases the user can type
252
+ * (`int`, `float`, `decimal`, …) get rewritten to the canonical type
253
+ * names that appear in `pg_type.typname` / `format_type()`. Mirrors
254
+ * `describe.c::map_typename_pattern`. Pattern matching is otherwise
255
+ * literal, so this is the only place the user's input gets rewritten.
256
+ */
257
+ const TYPENAME_ALIASES = {
258
+ decimal: 'numeric',
259
+ float: 'double precision',
260
+ int: 'integer',
261
+ 'bool[]': 'boolean[]',
262
+ 'decimal[]': 'numeric[]',
263
+ 'float[]': 'double precision[]',
264
+ 'float4[]': 'real[]',
265
+ 'float8[]': 'double precision[]',
266
+ 'int[]': 'integer[]',
267
+ 'int2[]': 'smallint[]',
268
+ 'int4[]': 'integer[]',
269
+ 'int8[]': 'bigint[]',
270
+ 'time[]': 'time without time zone[]',
271
+ 'timetz[]': 'time with time zone[]',
272
+ 'timestamp[]': 'timestamp without time zone[]',
273
+ 'timestamptz[]': 'timestamp with time zone[]',
274
+ 'varbit[]': 'bit varying[]',
275
+ 'varchar[]': 'character varying[]',
276
+ };
277
+ const mapTypenamePattern = (pattern) => TYPENAME_ALIASES[pattern.toLowerCase()] ?? pattern;
278
+ /**
279
+ * Process a per-argument type pattern slot for `\df` / `\do`. `-`
280
+ * returns null (the SQL builder handles the literal `IS NULL` check);
281
+ * any other value goes through {@link processSQLNamePattern} configured
282
+ * with the `t<i>` / `nt<i>` join columns and the formatted-type
283
+ * altnamevar so `\df foo int4` matches `oid -> integer` correctly.
284
+ */
285
+ const processArgPattern = (slot, raw) => {
286
+ if (raw === '-')
287
+ return null;
288
+ const mapped = mapTypenamePattern(raw);
289
+ return processSQLNamePattern({
290
+ pattern: mapped,
291
+ schemavar: `nt${slot}.nspname`,
292
+ namevar: `t${slot}.typname`,
293
+ altnamevar: `pg_catalog.format_type(t${slot}.oid, NULL)`,
294
+ visibilityrule: `pg_catalog.pg_type_is_visible(t${slot}.oid)`,
295
+ });
296
+ };
297
+ /**
298
+ * Drive a `\df` / `\do` style command: collect the main pattern, then
299
+ * any extra args as per-arg type filters, splice them all into the
300
+ * query, and print. Returns the collected arg patterns so the SQL
301
+ * builder upstream can mirror them as joins.
302
+ */
303
+ const collectArgPatterns = (ctx) => {
304
+ const args = [];
305
+ // Upstream caps `\do` at 2 args internally but reads all of them
306
+ // from the scanner; describeFunctions caps at FUNC_MAX_ARGS (100).
307
+ // We let the caller decide what to do with extras (mostly: ignore).
308
+ for (;;) {
309
+ const arg = ctx.nextArg('normal');
310
+ if (arg === null)
311
+ break;
312
+ args.push(arg);
313
+ }
314
+ return args;
315
+ };
316
+ /**
317
+ * Run a `\df` / `\do` query: handles the main pattern AND the per-arg
318
+ * type filters in one go. Mirrors {@link runWithPattern}, but also
319
+ * threads per-arg `NamePatternResult`s into the `ARG_PATTERN_<i>`
320
+ * placeholders before handing off to {@link runListQuery}.
321
+ */
322
+ const runFunctionOrOperatorQuery = async (ctx, pattern, argPatternResults, query, patternOpts) => {
323
+ const c = conn(ctx);
324
+ if (!c)
325
+ return noConn(ctx);
326
+ const result = processSQLNamePattern({ ...patternOpts, pattern });
327
+ const dotErr = validatePattern(pattern, result, 2, currentDb(c));
328
+ if (dotErr !== null) {
329
+ writeErr(`${dotErr}\n`);
330
+ return { status: 'error', errorWritten: true };
331
+ }
332
+ // Substitute the arg-pattern placeholders first so the params they
333
+ // contribute precede the main pattern's `$N` allocations.
334
+ const { sql, params } = applyArgPatterns(query.sql, query.params, argPatternResults);
335
+ const finalQuery = {
336
+ ...query,
337
+ sql,
338
+ params,
339
+ };
340
+ try {
341
+ await runListQuery(c, finalQuery, result, process.stdout, ctx.settings.popt);
342
+ return { status: 'ok' };
343
+ }
344
+ catch (err) {
345
+ writeErr(`\\${ctx.cmdName}: ${errMsg(err)}\n`);
346
+ return { status: 'error', errorWritten: true };
347
+ }
348
+ };
349
+ const errMsg = (err) => err instanceof Error ? err.message : String(err);
350
+ // ===========================================================================
351
+ // Individual command specs
352
+ // ===========================================================================
353
+ // ---- \d / \d <name> / \dt / \di / \dv / \dm / \ds / \dE -------------------
354
+ /** Bare `\d [pattern]` — list-or-detail. */
355
+ const makeDescribeCmd = (baseName) => ({
356
+ name: baseName,
357
+ run: async (ctx) => {
358
+ const pattern = ctx.nextArg('normal');
359
+ const c = conn(ctx);
360
+ if (!c)
361
+ return noConn(ctx);
362
+ const { verbose, showSystem } = decodeSuffix(ctx.cmdName, baseName);
363
+ // If pattern looks like a single concrete name (no wildcards),
364
+ // try a per-relation detail; otherwise list.
365
+ if (pattern && !/[*?]/.test(pattern)) {
366
+ // Pass the raw pattern straight through — lookupOneRelation folds /
367
+ // dequotes / splits schema.name via processSQLNamePattern.
368
+ try {
369
+ const rel = await lookupOneRelation(c, pattern);
370
+ if (rel) {
371
+ await dispatchDetail(ctx, c, rel, verbose);
372
+ return { status: 'ok' };
373
+ }
374
+ }
375
+ catch (err) {
376
+ writeErr(`\\${ctx.cmdName}: ${errMsg(err)}\n`);
377
+ return { status: 'error', errorWritten: true };
378
+ }
379
+ // Fall through to list (mirrors upstream behaviour: if no exact
380
+ // relation, treat the name as a list pattern).
381
+ }
382
+ // `\d`-family `x` suffix toggles expanded mode for the LIST view
383
+ // only — upstream `command.c` declines to apply it when a pattern is
384
+ // present and would dispatch to per-relation detail rendering.
385
+ const forceExpanded = ctx.cmdName.slice(baseName.length).includes('x') && pattern === null;
386
+ const savedPopt = ctx.settings.popt;
387
+ if (forceExpanded) {
388
+ ctx.settings.popt = {
389
+ ...savedPopt,
390
+ topt: { ...savedPopt.topt, expanded: 'on' },
391
+ };
392
+ }
393
+ try {
394
+ // List mode — use either describeTableDetails (for bare \d) or
395
+ // listTables(tabtypes=...) for the typed variants.
396
+ return await runTypedList(ctx, c, baseName, pattern, verbose, showSystem);
397
+ }
398
+ finally {
399
+ ctx.settings.popt = savedPopt;
400
+ }
401
+ },
402
+ });
403
+ const dispatchDetail = async (ctx, c, rel, verbose) => {
404
+ const popt = ctx.settings.popt;
405
+ switch (rel.relkind) {
406
+ case 'S':
407
+ await describeOneSequence(c, rel.oid, rel.nspname, rel.relname, process.stdout, popt);
408
+ return;
409
+ case 'v':
410
+ await describeOneViewDetails(c, rel.oid, rel.nspname, rel.relname, process.stdout, popt, verbose, ctx.settings.hideCompression);
411
+ return;
412
+ default:
413
+ await describeOneTableDetails(c, rel.oid, rel.nspname, rel.relname, rel.relkind, verbose, process.stdout, popt, ctx.settings.hideTableam, ctx.settings.hideCompression);
414
+ }
415
+ };
416
+ const runTypedList = async (ctx, c, baseName, pattern, verbose, showSystem) => {
417
+ const serverVersion = c.serverVersion;
418
+ const hideTableam = ctx.settings.hideTableam;
419
+ let query;
420
+ let tabtypes = '';
421
+ switch (baseName) {
422
+ case 'd':
423
+ query = describeTableDetails({
424
+ pattern: pattern ?? undefined,
425
+ verbose,
426
+ showSystem,
427
+ serverVersion,
428
+ hideTableam,
429
+ });
430
+ break;
431
+ case 'dt':
432
+ tabtypes = 't';
433
+ query = listTables({
434
+ pattern: pattern ?? undefined,
435
+ verbose,
436
+ showSystem,
437
+ serverVersion,
438
+ tabtypes,
439
+ hideTableam,
440
+ });
441
+ break;
442
+ case 'di':
443
+ tabtypes = 'i';
444
+ query = listTables({
445
+ pattern: pattern ?? undefined,
446
+ verbose,
447
+ showSystem,
448
+ serverVersion,
449
+ tabtypes,
450
+ hideTableam,
451
+ });
452
+ break;
453
+ case 'dv':
454
+ tabtypes = 'v';
455
+ query = listTables({
456
+ pattern: pattern ?? undefined,
457
+ verbose,
458
+ showSystem,
459
+ serverVersion,
460
+ tabtypes,
461
+ hideTableam,
462
+ });
463
+ break;
464
+ case 'dm':
465
+ tabtypes = 'm';
466
+ query = listTables({
467
+ pattern: pattern ?? undefined,
468
+ verbose,
469
+ showSystem,
470
+ serverVersion,
471
+ tabtypes,
472
+ hideTableam,
473
+ });
474
+ break;
475
+ case 'ds':
476
+ tabtypes = 's';
477
+ query = listTables({
478
+ pattern: pattern ?? undefined,
479
+ verbose,
480
+ showSystem,
481
+ serverVersion,
482
+ tabtypes,
483
+ hideTableam,
484
+ });
485
+ break;
486
+ case 'dE':
487
+ tabtypes = 'E';
488
+ query = listTables({
489
+ pattern: pattern ?? undefined,
490
+ verbose,
491
+ showSystem,
492
+ serverVersion,
493
+ tabtypes,
494
+ hideTableam,
495
+ });
496
+ break;
497
+ default:
498
+ return { status: 'error' };
499
+ }
500
+ const visibility = baseName === 'd'
501
+ ? 'pg_catalog.pg_table_is_visible(c.oid)'
502
+ : 'pg_catalog.pg_table_is_visible(c.oid)';
503
+ return runWithPattern(ctx, pattern, query, {
504
+ namevar: 'c.relname',
505
+ schemavar: 'n.nspname',
506
+ visibilityrule: visibility,
507
+ });
508
+ };
509
+ // Register all the relation-list-style commands with all suffix combos.
510
+ const RELATION_BASES = ['d', 'dt', 'di', 'dv', 'dm', 'ds', 'dE'];
511
+ // Standard suffix matrix. `x` is added separately for the bases that
512
+ // accept the expanded-mode toggle (currently `\d`/`\d+`).
513
+ const SUFFIX_COMBOS = ['', '+', 'S', 'S+', '+S'];
514
+ // Extended suffix matrix for `\d` only — adds the `x` (expanded) suffix
515
+ // in the same combinations regress exercises.
516
+ const DESCRIBE_X_SUFFIXES = ['x', '+x', 'x+', 'Sx', 'xS', '+Sx', '+xS'];
517
+ // ---- \df / \df+ / \dfS / \dfa / \dfn / \dfp / \dft / \dfw / \dfx -------
518
+ // Upstream's `command.c::exec_command_df` accepts a free-form suffix after
519
+ // `\df`: `+` for verbose, `S` to include system schemas, `x` to force
520
+ // expanded mode (when no pattern), and {a,n,p,t,w} to filter by function
521
+ // kind (aggregate / normal / procedure / trigger / window). Multiple may
522
+ // combine and order is unrestricted (`\dfax+`, `\df+xn`, etc.).
523
+ const cmdDescribeFunctions = (cmdName) => ({
524
+ name: cmdName,
525
+ run: async (ctx) => {
526
+ const pattern = ctx.nextArg('normal');
527
+ const c = conn(ctx);
528
+ if (!c)
529
+ return noConn(ctx);
530
+ const { verbose, showSystem } = decodeSuffix(cmdName, 'df');
531
+ let functypes = '';
532
+ const tail = cmdName.slice(2);
533
+ for (const ch of tail) {
534
+ if (ch === 'a' || ch === 'n' || ch === 'p' || ch === 't' || ch === 'w') {
535
+ functypes += ch;
536
+ }
537
+ }
538
+ // Per-argument type patterns: only collected when the main pattern
539
+ // is non-null (upstream: "Collect argument-type patterns too /
540
+ // otherwise it was just \df"). Each extra arg filters one slot of
541
+ // `proargtypes`; `-` means "no type in that slot".
542
+ const argPatterns = [];
543
+ if (pattern !== null) {
544
+ argPatterns.push(...collectArgPatterns(ctx));
545
+ }
546
+ const argPatternResults = argPatterns.map((a, i) => processArgPattern(i, a));
547
+ // `x` enables expanded mode for the printed result. For `\df` upstream
548
+ // applies the toggle whether or not a pattern is present (unlike `\d`,
549
+ // which only toggles when no pattern is given). Override popt locally
550
+ // so we don't leak the change to subsequent commands.
551
+ const forceExpanded = tail.includes('x');
552
+ const savedPopt = ctx.settings.popt;
553
+ if (forceExpanded) {
554
+ ctx.settings.popt = {
555
+ ...savedPopt,
556
+ topt: { ...savedPopt.topt, expanded: 'on' },
557
+ };
558
+ }
559
+ try {
560
+ const query = describeFunctions({
561
+ pattern: pattern ?? undefined,
562
+ verbose,
563
+ showSystem,
564
+ functypes,
565
+ argPatterns,
566
+ serverVersion: c.serverVersion,
567
+ });
568
+ return await runFunctionOrOperatorQuery(ctx, pattern, argPatternResults, query, {
569
+ namevar: 'p.proname',
570
+ schemavar: 'n.nspname',
571
+ visibilityrule: 'pg_catalog.pg_function_is_visible(p.oid)',
572
+ });
573
+ }
574
+ finally {
575
+ ctx.settings.popt = savedPopt;
576
+ }
577
+ },
578
+ });
579
+ // ---- \da ----------------------------------------------------------------
580
+ const cmdDescribeAggregates = (cmdName) => ({
581
+ name: cmdName,
582
+ run: async (ctx) => {
583
+ const pattern = ctx.nextArg('normal');
584
+ const c = conn(ctx);
585
+ if (!c)
586
+ return noConn(ctx);
587
+ const { showSystem } = decodeSuffix(cmdName, 'da');
588
+ const query = describeAggregates({
589
+ pattern: pattern ?? undefined,
590
+ showSystem,
591
+ serverVersion: c.serverVersion,
592
+ });
593
+ return runWithPattern(ctx, pattern, query, {
594
+ namevar: 'p.proname',
595
+ schemavar: 'n.nspname',
596
+ visibilityrule: 'pg_catalog.pg_function_is_visible(p.oid)',
597
+ });
598
+ },
599
+ });
600
+ // ---- \dA / \dA+ / \dAm / \dAm+ -----------------------------------------
601
+ // `\dAm[+]` is a Neon extension: it lists access methods with their
602
+ // handler and description columns always present, equivalent to upstream
603
+ // `\dA+`. Upstream psql doesn't accept this spelling; we register it as
604
+ // an alias so users who reach for "access methods" by full name get the
605
+ // verbose view (Name, Type, Handler, Description) by default.
606
+ const cmdDescribeAccessMethods = (cmdName) => ({
607
+ name: cmdName,
608
+ run: async (ctx) => {
609
+ const pattern = ctx.nextArg('normal');
610
+ const c = conn(ctx);
611
+ if (!c)
612
+ return noConn(ctx);
613
+ const isAlias = cmdName.startsWith('dAm');
614
+ const base = isAlias ? 'dAm' : 'dA';
615
+ const { verbose } = decodeSuffix(cmdName, base);
616
+ // `\dAm[+]` always displays the verbose columns; trailing `+` is
617
+ // accepted for syntactic parity but doesn't change output.
618
+ const query = describeAccessMethods({
619
+ pattern: pattern ?? undefined,
620
+ verbose: isAlias ? true : verbose,
621
+ serverVersion: c.serverVersion,
622
+ });
623
+ // Access methods are global, never schema-qualified; first dot is
624
+ // "too many dotted names".
625
+ const result = await runWithPattern(ctx, pattern, query, { namevar: 'amname' }, 0);
626
+ // Upstream `exec_command_d` drains any args past the first AFTER
627
+ // running the query and writing the result, emitting one warning
628
+ // per leftover token via `pg_log_warning`. Mirror that ordering so
629
+ // `\dA foo bar` prints the (empty) result first, then
630
+ // `\dA: extra argument "bar" ignored` to stderr.
631
+ for (let extra = ctx.nextArg('normal'); extra !== null;) {
632
+ writeErr(`\\${base}: extra argument "${extra}" ignored\n`);
633
+ extra = ctx.nextArg('normal');
634
+ }
635
+ return result;
636
+ },
637
+ });
638
+ // ---- \dAc / \dAc+ ------------------------------------------------------
639
+ // `\dAc [AM-pattern [TYPE-pattern]]` — list operator classes. Two-pattern
640
+ // command: the first arg filters by access-method name, the second by
641
+ // input-type schema/name. Mirrors upstream's `case 'A' / cmd[2] == 'c'`
642
+ // dispatch in `command.c::exec_command_d`.
643
+ const cmdListOperatorClasses = (cmdName) => ({
644
+ name: cmdName,
645
+ run: async (ctx) => {
646
+ const amPat = ctx.nextArg('normal');
647
+ const typePat = amPat ? ctx.nextArg('normal') : null;
648
+ const c = conn(ctx);
649
+ if (!c)
650
+ return noConn(ctx);
651
+ const { verbose } = decodeSuffix(cmdName, 'dAc');
652
+ const query = listOperatorClasses({
653
+ amPattern: amPat ?? undefined,
654
+ typePattern: typePat ?? undefined,
655
+ verbose,
656
+ serverVersion: c.serverVersion,
657
+ });
658
+ const results = [];
659
+ const curDb = currentDb(c);
660
+ if (amPat !== null) {
661
+ const r = processSQLNamePattern({
662
+ namevar: 'am.amname',
663
+ pattern: amPat,
664
+ });
665
+ // Access method names are flat — first dot is "too many".
666
+ const err = validatePattern(amPat, r, 0, curDb);
667
+ if (err !== null) {
668
+ writeErr(`${err}\n`);
669
+ return { status: 'error', errorWritten: true };
670
+ }
671
+ results.push(r);
672
+ }
673
+ if (typePat !== null) {
674
+ const r = processSQLNamePattern({
675
+ namevar: 't.typname',
676
+ schemavar: 'tn.nspname',
677
+ pattern: typePat,
678
+ });
679
+ // Type pattern accepts db.schema.name; cross-db check on 2-dot.
680
+ const err = validatePattern(typePat, r, 2, curDb);
681
+ if (err !== null) {
682
+ writeErr(`${err}\n`);
683
+ return { status: 'error', errorWritten: true };
684
+ }
685
+ results.push(r);
686
+ }
687
+ return runDualPatternList(ctx, query, results);
688
+ },
689
+ });
690
+ // ---- \dAf / \dAf+ ------------------------------------------------------
691
+ // `\dAf [AM-pattern [TYPE-pattern]]` — list operator families. Same
692
+ // two-pattern shape as `\dAc`. Routes through `listOperatorFamilies`
693
+ // from queries.ts.
694
+ const cmdListOperatorFamilies = (cmdName) => ({
695
+ name: cmdName,
696
+ run: async (ctx) => {
697
+ const amPat = ctx.nextArg('normal');
698
+ const typePat = amPat ? ctx.nextArg('normal') : null;
699
+ const c = conn(ctx);
700
+ if (!c)
701
+ return noConn(ctx);
702
+ const { verbose } = decodeSuffix(cmdName, 'dAf');
703
+ const query = listOperatorFamilies({
704
+ amPattern: amPat ?? undefined,
705
+ typePattern: typePat ?? undefined,
706
+ verbose,
707
+ serverVersion: c.serverVersion,
708
+ });
709
+ const results = [];
710
+ const curDb = currentDb(c);
711
+ if (amPat !== null) {
712
+ const r = processSQLNamePattern({
713
+ namevar: 'am.amname',
714
+ pattern: amPat,
715
+ });
716
+ const err = validatePattern(amPat, r, 0, curDb);
717
+ if (err !== null) {
718
+ writeErr(`${err}\n`);
719
+ return { status: 'error', errorWritten: true };
720
+ }
721
+ results.push(r);
722
+ }
723
+ if (typePat !== null) {
724
+ const r = processSQLNamePattern({
725
+ namevar: 't.typname',
726
+ schemavar: 'tn.nspname',
727
+ pattern: typePat,
728
+ });
729
+ const err = validatePattern(typePat, r, 2, curDb);
730
+ if (err !== null) {
731
+ writeErr(`${err}\n`);
732
+ return { status: 'error', errorWritten: true };
733
+ }
734
+ results.push(r);
735
+ }
736
+ return runDualPatternList(ctx, query, results);
737
+ },
738
+ });
739
+ // ---- \dAo / \dAo+ ------------------------------------------------------
740
+ // `\dAo [AM-pattern [OPFAMILY-pattern]]` — list operators in an opfamily.
741
+ // Routes through `listOpFamilyOperators` from queries.ts.
742
+ const cmdListOpFamilyOperators = (cmdName) => ({
743
+ name: cmdName,
744
+ run: async (ctx) => {
745
+ const amPat = ctx.nextArg('normal');
746
+ const familyPat = amPat ? ctx.nextArg('normal') : null;
747
+ const c = conn(ctx);
748
+ if (!c)
749
+ return noConn(ctx);
750
+ const { verbose } = decodeSuffix(cmdName, 'dAo');
751
+ const query = listOpFamilyOperators({
752
+ amPattern: amPat ?? undefined,
753
+ familyPattern: familyPat ?? undefined,
754
+ verbose,
755
+ serverVersion: c.serverVersion,
756
+ });
757
+ const results = [];
758
+ const curDb = currentDb(c);
759
+ if (amPat !== null) {
760
+ const r = processSQLNamePattern({
761
+ namevar: 'am.amname',
762
+ pattern: amPat,
763
+ });
764
+ const err = validatePattern(amPat, r, 0, curDb);
765
+ if (err !== null) {
766
+ writeErr(`${err}\n`);
767
+ return { status: 'error', errorWritten: true };
768
+ }
769
+ results.push(r);
770
+ }
771
+ if (familyPat !== null) {
772
+ const r = processSQLNamePattern({
773
+ namevar: 'of.opfname',
774
+ schemavar: 'nsf.nspname',
775
+ pattern: familyPat,
776
+ });
777
+ const err = validatePattern(familyPat, r, 2, curDb);
778
+ if (err !== null) {
779
+ writeErr(`${err}\n`);
780
+ return { status: 'error', errorWritten: true };
781
+ }
782
+ results.push(r);
783
+ }
784
+ return runDualPatternList(ctx, query, results);
785
+ },
786
+ });
787
+ // ---- \dAp / \dAp+ ------------------------------------------------------
788
+ // `\dAp [AM-pattern [OPFAMILY-pattern]]` — list support functions of an
789
+ // opfamily. Routes through `listOpFamilyFunctions` from queries.ts.
790
+ const cmdListOpFamilyFunctions = (cmdName) => ({
791
+ name: cmdName,
792
+ run: async (ctx) => {
793
+ const amPat = ctx.nextArg('normal');
794
+ const familyPat = amPat ? ctx.nextArg('normal') : null;
795
+ const c = conn(ctx);
796
+ if (!c)
797
+ return noConn(ctx);
798
+ const { verbose } = decodeSuffix(cmdName, 'dAp');
799
+ // `\dApx[+]` — expanded toggle, same convention as `\df`/`\z`.
800
+ const forceExpanded = cmdName.slice(3).includes('x');
801
+ const savedPopt = ctx.settings.popt;
802
+ if (forceExpanded) {
803
+ ctx.settings.popt = {
804
+ ...savedPopt,
805
+ topt: { ...savedPopt.topt, expanded: 'on' },
806
+ };
807
+ }
808
+ try {
809
+ const query = listOpFamilyFunctions({
810
+ amPattern: amPat ?? undefined,
811
+ familyPattern: familyPat ?? undefined,
812
+ verbose,
813
+ serverVersion: c.serverVersion,
814
+ });
815
+ const results = [];
816
+ const curDb = currentDb(c);
817
+ if (amPat !== null) {
818
+ const r = processSQLNamePattern({
819
+ namevar: 'am.amname',
820
+ pattern: amPat,
821
+ });
822
+ const err = validatePattern(amPat, r, 0, curDb);
823
+ if (err !== null) {
824
+ writeErr(`${err}\n`);
825
+ return { status: 'error', errorWritten: true };
826
+ }
827
+ results.push(r);
828
+ }
829
+ if (familyPat !== null) {
830
+ const r = processSQLNamePattern({
831
+ namevar: 'of.opfname',
832
+ schemavar: 'ns.nspname',
833
+ pattern: familyPat,
834
+ });
835
+ const err = validatePattern(familyPat, r, 2, curDb);
836
+ if (err !== null) {
837
+ writeErr(`${err}\n`);
838
+ return { status: 'error', errorWritten: true };
839
+ }
840
+ results.push(r);
841
+ }
842
+ return await runDualPatternList(ctx, query, results);
843
+ }
844
+ finally {
845
+ ctx.settings.popt = savedPopt;
846
+ }
847
+ },
848
+ });
849
+ // ---- \dT / \dT+ / \dTS --------------------------------------------------
850
+ const cmdDescribeTypes = (cmdName) => ({
851
+ name: cmdName,
852
+ run: async (ctx) => {
853
+ const pattern = ctx.nextArg('normal');
854
+ const c = conn(ctx);
855
+ if (!c)
856
+ return noConn(ctx);
857
+ const { verbose, showSystem } = decodeSuffix(cmdName, 'dT');
858
+ const query = describeTypes({
859
+ pattern: pattern ?? undefined,
860
+ verbose,
861
+ showSystem,
862
+ serverVersion: c.serverVersion,
863
+ });
864
+ return runWithPattern(ctx, pattern, query, {
865
+ namevar: 't.typname',
866
+ schemavar: 'n.nspname',
867
+ visibilityrule: 'pg_catalog.pg_type_is_visible(t.oid)',
868
+ });
869
+ },
870
+ });
871
+ // ---- \do / \do+ ---------------------------------------------------------
872
+ const cmdDescribeOperators = (cmdName) => ({
873
+ name: cmdName,
874
+ run: async (ctx) => {
875
+ const pattern = ctx.nextArg('normal');
876
+ const c = conn(ctx);
877
+ if (!c)
878
+ return noConn(ctx);
879
+ const { verbose, showSystem } = decodeSuffix(cmdName, 'do');
880
+ // Upstream: same arg-collection rule as `\df` (only when main
881
+ // pattern is non-null). describeOperators caps at 2 args; we still
882
+ // forward the user's input so the SQL builder can decide which
883
+ // joins to emit.
884
+ let argPatterns = [];
885
+ if (pattern !== null) {
886
+ argPatterns = collectArgPatterns(ctx);
887
+ }
888
+ if (argPatterns.length > 2)
889
+ argPatterns = argPatterns.slice(0, 2);
890
+ const argPatternResults = argPatterns.map((a, i) => processArgPattern(i, a));
891
+ const query = describeOperators({
892
+ pattern: pattern ?? undefined,
893
+ verbose,
894
+ showSystem,
895
+ argPatterns,
896
+ serverVersion: c.serverVersion,
897
+ });
898
+ return runFunctionOrOperatorQuery(ctx, pattern, argPatternResults, query, {
899
+ namevar: 'o.oprname',
900
+ schemavar: 'n.nspname',
901
+ visibilityrule: 'pg_catalog.pg_operator_is_visible(o.oid)',
902
+ });
903
+ },
904
+ });
905
+ // ---- \du / \dg / \dg+ / \du+ -------------------------------------------
906
+ const cmdDescribeRoles = (cmdName) => ({
907
+ name: cmdName,
908
+ run: async (ctx) => {
909
+ const pattern = ctx.nextArg('normal');
910
+ const c = conn(ctx);
911
+ if (!c)
912
+ return noConn(ctx);
913
+ const base = cmdName.startsWith('du') ? 'du' : 'dg';
914
+ const { verbose, showSystem } = decodeSuffix(cmdName, base);
915
+ const query = describeRoles({
916
+ pattern: pattern ?? undefined,
917
+ verbose,
918
+ showSystem,
919
+ serverVersion: c.serverVersion,
920
+ });
921
+ // Roles are global — first dot is "too many". Upstream calls
922
+ // `printQuery` with `default_footer = false` (describe.c
923
+ // `describeRoles`), so suppress the `(N rows)` counter for both
924
+ // populated and empty results to match `psql -E \du`.
925
+ return runWithPattern(ctx, pattern, query, { namevar: 'r.rolname' }, 0, {
926
+ suppressDefaultFooter: true,
927
+ });
928
+ },
929
+ });
930
+ // ---- \drds -------------------------------------------------------------
931
+ // `\drds [role-pattern [database-pattern]]` — list role-/database-level
932
+ // configuration settings (`pg_db_role_setting`). Two-pattern command:
933
+ // first arg filters by role, second by database. Empty results print a
934
+ // stderr notice ("Did not find any settings…") when not in quiet mode,
935
+ // mirroring upstream `describe.c::listDbRoleSettings`.
936
+ const cmdListDbRoleSettings = {
937
+ name: 'drds',
938
+ run: async (ctx) => {
939
+ const rolePat = ctx.nextArg('normal');
940
+ const dbPat = rolePat ? ctx.nextArg('normal') : null;
941
+ const c = conn(ctx);
942
+ if (!c)
943
+ return noConn(ctx);
944
+ const query = listDbRoleSettings({
945
+ pattern: rolePat ?? undefined,
946
+ pattern2: dbPat ?? undefined,
947
+ serverVersion: c.serverVersion,
948
+ });
949
+ const results = [];
950
+ const curDb = currentDb(c);
951
+ if (rolePat !== null) {
952
+ const r = processSQLNamePattern({
953
+ namevar: 'r.rolname',
954
+ pattern: rolePat,
955
+ });
956
+ // Role names are flat — first dot is "too many".
957
+ const err = validatePattern(rolePat, r, 0, curDb);
958
+ if (err !== null) {
959
+ writeErr(`${err}\n`);
960
+ return { status: 'error', errorWritten: true };
961
+ }
962
+ results.push(r);
963
+ }
964
+ if (dbPat !== null) {
965
+ const r = processSQLNamePattern({
966
+ namevar: 'd.datname',
967
+ pattern: dbPat,
968
+ });
969
+ // Database names are top-level — first dot is "too many".
970
+ const err = validatePattern(dbPat, r, 0, curDb);
971
+ if (err !== null) {
972
+ writeErr(`${err}\n`);
973
+ return { status: 'error', errorWritten: true };
974
+ }
975
+ results.push(r);
976
+ }
977
+ // Upstream deviates from the rest of describe.c here: when the
978
+ // result set is empty and we're not in --quiet, emit a stderr
979
+ // diagnostic instead of printing an empty table — the two-pattern
980
+ // shape makes confusion likely otherwise.
981
+ if (rolePat === null && dbPat === null) {
982
+ return runDualPatternList(ctx, query, results);
983
+ }
984
+ const { sql, params } = applyTwoPatterns(query.sql, [], results);
985
+ const finalQuery = {
986
+ ...query,
987
+ sql,
988
+ params,
989
+ };
990
+ try {
991
+ const rs = await c.query(sql, params);
992
+ if (rs.rows.length === 0 && !ctx.settings.quiet) {
993
+ if (rolePat !== null && dbPat !== null) {
994
+ writeErr(`Did not find any settings for role "${rolePat}" and database "${dbPat}".\n`);
995
+ }
996
+ else if (rolePat !== null) {
997
+ writeErr(`Did not find any settings for role "${rolePat}".\n`);
998
+ }
999
+ return { status: 'ok' };
1000
+ }
1001
+ // Re-print via the standard runner so the title and formatting
1002
+ // match peer list queries. We pass the already-substituted SQL
1003
+ // and an empty pattern result so the second-pass replace is a
1004
+ // no-op. (We accept the double-query cost on the small `\drds`
1005
+ // path; alternatives require duplicating the printer plumbing.)
1006
+ const empty = {
1007
+ schemaConditions: [],
1008
+ nameConditions: [],
1009
+ visibilityConditions: [],
1010
+ params: [],
1011
+ dotCount: 0,
1012
+ dbLiteral: null,
1013
+ };
1014
+ await runListQuery(c, finalQuery, empty, process.stdout, ctx.settings.popt);
1015
+ return { status: 'ok' };
1016
+ }
1017
+ catch (err) {
1018
+ writeErr(`\\${ctx.cmdName}: ${errMsg(err)}\n`);
1019
+ return { status: 'error', errorWritten: true };
1020
+ }
1021
+ },
1022
+ };
1023
+ // ---- \dn / \dn+ / \dnS --------------------------------------------------
1024
+ const cmdListSchemas = (cmdName) => ({
1025
+ name: cmdName,
1026
+ run: async (ctx) => {
1027
+ const pattern = ctx.nextArg('normal');
1028
+ const c = conn(ctx);
1029
+ if (!c)
1030
+ return noConn(ctx);
1031
+ const { verbose, showSystem } = decodeSuffix(cmdName, 'dn');
1032
+ const query = listSchemas({
1033
+ pattern: pattern ?? undefined,
1034
+ verbose,
1035
+ showSystem,
1036
+ serverVersion: c.serverVersion,
1037
+ });
1038
+ // Schemas live in a single namespace; the optional qualifier slot
1039
+ // is interpreted as a database name (cross-database check fires on
1040
+ // mismatch). `maxDots = 1`.
1041
+ return runWithPattern(ctx, pattern, query, { namevar: 'n.nspname' }, 1);
1042
+ },
1043
+ });
1044
+ // ---- \db / \db+ ---------------------------------------------------------
1045
+ // Tablespaces — not typically relevant on Neon. We still register so the
1046
+ // command exists; it returns an empty result against a managed deployment.
1047
+ const cmdDescribeTablespaces = (cmdName) => ({
1048
+ name: cmdName,
1049
+ run: async (ctx) => {
1050
+ const pattern = ctx.nextArg('normal');
1051
+ const c = conn(ctx);
1052
+ if (!c)
1053
+ return noConn(ctx);
1054
+ const { verbose } = decodeSuffix(cmdName, 'db');
1055
+ const query = describeTablespaces({
1056
+ pattern: pattern ?? undefined,
1057
+ verbose,
1058
+ serverVersion: c.serverVersion,
1059
+ });
1060
+ // Tablespaces are flat — first dot is "too many".
1061
+ return runWithPattern(ctx, pattern, query, { namevar: 'spcname' }, 0);
1062
+ },
1063
+ });
1064
+ // ---- \dD / \dDS / \dD+ -------------------------------------------------
1065
+ const cmdListDomains = (cmdName) => ({
1066
+ name: cmdName,
1067
+ run: async (ctx) => {
1068
+ const pattern = ctx.nextArg('normal');
1069
+ const c = conn(ctx);
1070
+ if (!c)
1071
+ return noConn(ctx);
1072
+ const { verbose, showSystem } = decodeSuffix(cmdName, 'dD');
1073
+ const query = listDomains({
1074
+ pattern: pattern ?? undefined,
1075
+ verbose,
1076
+ showSystem,
1077
+ serverVersion: c.serverVersion,
1078
+ });
1079
+ return runWithPattern(ctx, pattern, query, {
1080
+ namevar: 't.typname',
1081
+ schemavar: 'n.nspname',
1082
+ visibilityrule: 'pg_catalog.pg_type_is_visible(t.oid)',
1083
+ });
1084
+ },
1085
+ });
1086
+ // ---- \dc / \dcS / \dc+ -------------------------------------------------
1087
+ const cmdListConversions = (cmdName) => ({
1088
+ name: cmdName,
1089
+ run: async (ctx) => {
1090
+ const pattern = ctx.nextArg('normal');
1091
+ const c = conn(ctx);
1092
+ if (!c)
1093
+ return noConn(ctx);
1094
+ const { verbose, showSystem } = decodeSuffix(cmdName, 'dc');
1095
+ const query = listConversions({
1096
+ pattern: pattern ?? undefined,
1097
+ verbose,
1098
+ showSystem,
1099
+ serverVersion: c.serverVersion,
1100
+ });
1101
+ return runWithPattern(ctx, pattern, query, {
1102
+ namevar: 'c.conname',
1103
+ schemavar: 'n.nspname',
1104
+ visibilityrule: 'pg_catalog.pg_conversion_is_visible(c.oid)',
1105
+ });
1106
+ },
1107
+ });
1108
+ // ---- \dC / \dC+ --------------------------------------------------------
1109
+ const cmdListCasts = (cmdName) => ({
1110
+ name: cmdName,
1111
+ run: async (ctx) => {
1112
+ const pattern = ctx.nextArg('normal');
1113
+ const c = conn(ctx);
1114
+ if (!c)
1115
+ return noConn(ctx);
1116
+ const { verbose } = decodeSuffix(cmdName, 'dC');
1117
+ const query = listCasts({
1118
+ pattern: pattern ?? undefined,
1119
+ verbose,
1120
+ serverVersion: c.serverVersion,
1121
+ });
1122
+ return runWithPattern(ctx, pattern, query, {
1123
+ namevar: 'ts.typname',
1124
+ schemavar: 'ns.nspname',
1125
+ });
1126
+ },
1127
+ });
1128
+ // ---- \dL / \dLS / \dL+ -------------------------------------------------
1129
+ const cmdListLanguages = (cmdName) => ({
1130
+ name: cmdName,
1131
+ run: async (ctx) => {
1132
+ const pattern = ctx.nextArg('normal');
1133
+ const c = conn(ctx);
1134
+ if (!c)
1135
+ return noConn(ctx);
1136
+ const { verbose, showSystem } = decodeSuffix(cmdName, 'dL');
1137
+ const query = listLanguages({
1138
+ pattern: pattern ?? undefined,
1139
+ verbose,
1140
+ showSystem,
1141
+ serverVersion: c.serverVersion,
1142
+ });
1143
+ // Languages are global; first dot is a database qualifier (cross-db).
1144
+ return runWithPattern(ctx, pattern, query, { namevar: 'l.lanname' }, 1);
1145
+ },
1146
+ });
1147
+ // ---- \dO / \dO+ / \dOS -------------------------------------------------
1148
+ const cmdListCollations = (cmdName) => ({
1149
+ name: cmdName,
1150
+ run: async (ctx) => {
1151
+ const pattern = ctx.nextArg('normal');
1152
+ const c = conn(ctx);
1153
+ if (!c)
1154
+ return noConn(ctx);
1155
+ const { verbose, showSystem } = decodeSuffix(cmdName, 'dO');
1156
+ const query = listCollations({
1157
+ pattern: pattern ?? undefined,
1158
+ verbose,
1159
+ showSystem,
1160
+ serverVersion: c.serverVersion,
1161
+ });
1162
+ return runWithPattern(ctx, pattern, query, {
1163
+ namevar: 'c.collname',
1164
+ schemavar: 'n.nspname',
1165
+ visibilityrule: 'pg_catalog.pg_collation_is_visible(c.oid)',
1166
+ });
1167
+ },
1168
+ });
1169
+ // ---- \dp / \z ----------------------------------------------------------
1170
+ const cmdPermissionsList = (cmdName) => ({
1171
+ name: cmdName,
1172
+ run: async (ctx) => {
1173
+ const pattern = ctx.nextArg('normal');
1174
+ const c = conn(ctx);
1175
+ if (!c)
1176
+ return noConn(ctx);
1177
+ const base = cmdName.startsWith('z') ? 'z' : 'dp';
1178
+ const { showSystem } = decodeSuffix(cmdName, base);
1179
+ // `\z[x]`, `\dp[x]` — same expanded-mode toggle convention as `\df`,
1180
+ // applied whether or not a pattern is present.
1181
+ const forceExpanded = cmdName.slice(base.length).includes('x');
1182
+ const savedPopt = ctx.settings.popt;
1183
+ if (forceExpanded) {
1184
+ ctx.settings.popt = {
1185
+ ...savedPopt,
1186
+ topt: { ...savedPopt.topt, expanded: 'on' },
1187
+ };
1188
+ }
1189
+ try {
1190
+ const query = permissionsList({
1191
+ pattern: pattern ?? undefined,
1192
+ showSystem,
1193
+ serverVersion: c.serverVersion,
1194
+ });
1195
+ return await runWithPattern(ctx, pattern, query, {
1196
+ namevar: 'c.relname',
1197
+ schemavar: 'n.nspname',
1198
+ visibilityrule: 'pg_catalog.pg_table_is_visible(c.oid)',
1199
+ });
1200
+ }
1201
+ finally {
1202
+ ctx.settings.popt = savedPopt;
1203
+ }
1204
+ },
1205
+ });
1206
+ // ---- \ddp --------------------------------------------------------------
1207
+ const cmdListDefaultACLs = {
1208
+ name: 'ddp',
1209
+ run: async (ctx) => {
1210
+ const pattern = ctx.nextArg('normal');
1211
+ const c = conn(ctx);
1212
+ if (!c)
1213
+ return noConn(ctx);
1214
+ const query = listDefaultACLs({
1215
+ pattern: pattern ?? undefined,
1216
+ serverVersion: c.serverVersion,
1217
+ });
1218
+ return runWithPattern(ctx, pattern, query, {
1219
+ namevar: 'pg_catalog.pg_get_userbyid(d.defaclrole)',
1220
+ schemavar: 'n.nspname',
1221
+ });
1222
+ },
1223
+ };
1224
+ // ---- \dd ---------------------------------------------------------------
1225
+ const cmdObjectDescription = {
1226
+ name: 'dd',
1227
+ run: async (ctx) => {
1228
+ const pattern = ctx.nextArg('normal');
1229
+ const c = conn(ctx);
1230
+ if (!c)
1231
+ return noConn(ctx);
1232
+ const { showSystem } = decodeSuffix(ctx.cmdName, 'dd');
1233
+ const query = objectDescription({
1234
+ pattern: pattern ?? undefined,
1235
+ showSystem,
1236
+ serverVersion: c.serverVersion,
1237
+ });
1238
+ return runWithPattern(ctx, pattern, query, {
1239
+ namevar: 'tt.name',
1240
+ schemavar: 'tt.nspname',
1241
+ });
1242
+ },
1243
+ };
1244
+ // ---- \l / \list -------------------------------------------------------
1245
+ const cmdListAllDbs = {
1246
+ name: 'l',
1247
+ aliases: ['list'],
1248
+ run: async (ctx) => {
1249
+ const pattern = ctx.nextArg('normal');
1250
+ const c = conn(ctx);
1251
+ if (!c)
1252
+ return noConn(ctx);
1253
+ const { verbose } = decodeSuffix(ctx.cmdName, 'l');
1254
+ const query = listAllDbs({
1255
+ pattern: pattern ?? undefined,
1256
+ verbose,
1257
+ serverVersion: c.serverVersion,
1258
+ });
1259
+ return runWithPattern(ctx, pattern, query, { namevar: 'd.datname' });
1260
+ },
1261
+ };
1262
+ // ---- \dconfig ----------------------------------------------------------
1263
+ const cmdDescribeConfigParams = {
1264
+ name: 'dconfig',
1265
+ run: async (ctx) => {
1266
+ const pattern = ctx.nextArg('normal');
1267
+ const c = conn(ctx);
1268
+ if (!c)
1269
+ return noConn(ctx);
1270
+ const { verbose } = decodeSuffix(ctx.cmdName, 'dconfig');
1271
+ const query = describeConfigurationParameters({
1272
+ pattern: pattern ?? undefined,
1273
+ verbose,
1274
+ serverVersion: c.serverVersion,
1275
+ });
1276
+ // GUC names are flat — any dot is "too many".
1277
+ return runWithPattern(ctx, pattern, query, { namevar: 'pg_catalog.lower(s.name)' }, 0);
1278
+ },
1279
+ };
1280
+ // ---- \dy ---------------------------------------------------------------
1281
+ const cmdListEventTriggers = {
1282
+ name: 'dy',
1283
+ run: async (ctx) => {
1284
+ const pattern = ctx.nextArg('normal');
1285
+ const c = conn(ctx);
1286
+ if (!c)
1287
+ return noConn(ctx);
1288
+ const { verbose } = decodeSuffix(ctx.cmdName, 'dy');
1289
+ const query = listEventTriggers({
1290
+ pattern: pattern ?? undefined,
1291
+ verbose,
1292
+ serverVersion: c.serverVersion,
1293
+ });
1294
+ // Event triggers are global with no schema or database qualifier
1295
+ // (upstream `validateSQLNamePattern(..., NULL, 1)`): any dot in
1296
+ // the pattern is "too many".
1297
+ return runWithPattern(ctx, pattern, query, { namevar: 'evtname' }, 0);
1298
+ },
1299
+ };
1300
+ // ---- \dx / \dx+ -------------------------------------------------------
1301
+ const cmdListExtensions = {
1302
+ name: 'dx',
1303
+ run: async (ctx) => {
1304
+ const pattern = ctx.nextArg('normal');
1305
+ const c = conn(ctx);
1306
+ if (!c)
1307
+ return noConn(ctx);
1308
+ const query = listExtensions({
1309
+ pattern: pattern ?? undefined,
1310
+ serverVersion: c.serverVersion,
1311
+ });
1312
+ // Extensions are global with no schema or database qualifier
1313
+ // (upstream `validateSQLNamePattern(..., NULL, 1)`): any dot is
1314
+ // "too many".
1315
+ return runWithPattern(ctx, pattern, query, { namevar: 'e.extname' }, 0);
1316
+ },
1317
+ };
1318
+ // ---- \dl / \lo_list ---------------------------------------------------
1319
+ const cmdListLargeObjects = {
1320
+ name: 'dl',
1321
+ aliases: ['lo_list'],
1322
+ run: async (ctx) => {
1323
+ const c = conn(ctx);
1324
+ if (!c)
1325
+ return noConn(ctx);
1326
+ const { verbose } = decodeSuffix(ctx.cmdName, 'dl');
1327
+ const query = listLargeObjects({
1328
+ verbose,
1329
+ serverVersion: c.serverVersion,
1330
+ });
1331
+ return runWithPattern(ctx, null, query, { namevar: 'oid' });
1332
+ },
1333
+ };
1334
+ // ---- \dF / \dFp / \dFd / \dFt ----------------------------------------
1335
+ const cmdListTSConfigs = {
1336
+ name: 'dF',
1337
+ run: async (ctx) => {
1338
+ const pattern = ctx.nextArg('normal');
1339
+ const c = conn(ctx);
1340
+ if (!c)
1341
+ return noConn(ctx);
1342
+ const { verbose } = decodeSuffix(ctx.cmdName, 'dF');
1343
+ const query = listTSConfigs({
1344
+ pattern: pattern ?? undefined,
1345
+ verbose,
1346
+ serverVersion: c.serverVersion,
1347
+ });
1348
+ return runWithPattern(ctx, pattern, query, {
1349
+ namevar: 'c.cfgname',
1350
+ schemavar: 'n.nspname',
1351
+ });
1352
+ },
1353
+ };
1354
+ const cmdListTSParsers = {
1355
+ name: 'dFp',
1356
+ run: async (ctx) => {
1357
+ const pattern = ctx.nextArg('normal');
1358
+ const c = conn(ctx);
1359
+ if (!c)
1360
+ return noConn(ctx);
1361
+ const { verbose } = decodeSuffix(ctx.cmdName, 'dFp');
1362
+ const query = listTSParsers({
1363
+ pattern: pattern ?? undefined,
1364
+ verbose,
1365
+ serverVersion: c.serverVersion,
1366
+ });
1367
+ return runWithPattern(ctx, pattern, query, {
1368
+ namevar: 'p.prsname',
1369
+ schemavar: 'n.nspname',
1370
+ });
1371
+ },
1372
+ };
1373
+ const cmdListTSDictionaries = {
1374
+ name: 'dFd',
1375
+ run: async (ctx) => {
1376
+ const pattern = ctx.nextArg('normal');
1377
+ const c = conn(ctx);
1378
+ if (!c)
1379
+ return noConn(ctx);
1380
+ const { verbose } = decodeSuffix(ctx.cmdName, 'dFd');
1381
+ const query = listTSDictionaries({
1382
+ pattern: pattern ?? undefined,
1383
+ verbose,
1384
+ serverVersion: c.serverVersion,
1385
+ });
1386
+ return runWithPattern(ctx, pattern, query, {
1387
+ namevar: 'd.dictname',
1388
+ schemavar: 'n.nspname',
1389
+ });
1390
+ },
1391
+ };
1392
+ const cmdListTSTemplates = {
1393
+ name: 'dFt',
1394
+ run: async (ctx) => {
1395
+ const pattern = ctx.nextArg('normal');
1396
+ const c = conn(ctx);
1397
+ if (!c)
1398
+ return noConn(ctx);
1399
+ const { verbose } = decodeSuffix(ctx.cmdName, 'dFt');
1400
+ const query = listTSTemplates({
1401
+ pattern: pattern ?? undefined,
1402
+ verbose,
1403
+ serverVersion: c.serverVersion,
1404
+ });
1405
+ return runWithPattern(ctx, pattern, query, {
1406
+ namevar: 't.tmplname',
1407
+ schemavar: 'n.nspname',
1408
+ });
1409
+ },
1410
+ };
1411
+ // ---- \dew / \des / \deu / \det ---------------------------------------
1412
+ const cmdListForeignDataWrappers = {
1413
+ name: 'dew',
1414
+ run: async (ctx) => {
1415
+ const pattern = ctx.nextArg('normal');
1416
+ const c = conn(ctx);
1417
+ if (!c)
1418
+ return noConn(ctx);
1419
+ const { verbose } = decodeSuffix(ctx.cmdName, 'dew');
1420
+ const query = listForeignDataWrappers({
1421
+ pattern: pattern ?? undefined,
1422
+ verbose,
1423
+ serverVersion: c.serverVersion,
1424
+ });
1425
+ // FDWs are global with no schema or database qualifier (upstream
1426
+ // `validateSQLNamePattern(..., NULL, 1)`): any dot is "too many".
1427
+ return runWithPattern(ctx, pattern, query, { namevar: 'fdwname' }, 0);
1428
+ },
1429
+ };
1430
+ const cmdListForeignServers = {
1431
+ name: 'des',
1432
+ run: async (ctx) => {
1433
+ const pattern = ctx.nextArg('normal');
1434
+ const c = conn(ctx);
1435
+ if (!c)
1436
+ return noConn(ctx);
1437
+ const { verbose } = decodeSuffix(ctx.cmdName, 'des');
1438
+ const query = listForeignServers({
1439
+ pattern: pattern ?? undefined,
1440
+ verbose,
1441
+ serverVersion: c.serverVersion,
1442
+ });
1443
+ // Foreign servers are global with no schema or database qualifier
1444
+ // (upstream `validateSQLNamePattern(..., NULL, 1)`): any dot is
1445
+ // "too many".
1446
+ return runWithPattern(ctx, pattern, query, { namevar: 's.srvname' }, 0);
1447
+ },
1448
+ };
1449
+ const cmdListUserMappings = {
1450
+ name: 'deu',
1451
+ run: async (ctx) => {
1452
+ const pattern = ctx.nextArg('normal');
1453
+ const c = conn(ctx);
1454
+ if (!c)
1455
+ return noConn(ctx);
1456
+ const { verbose } = decodeSuffix(ctx.cmdName, 'deu');
1457
+ const query = listUserMappings({
1458
+ pattern: pattern ?? undefined,
1459
+ verbose,
1460
+ serverVersion: c.serverVersion,
1461
+ });
1462
+ // User mappings live alongside foreign servers (global, no schema
1463
+ // or db qualifier per upstream `validateSQLNamePattern(..., 1)`).
1464
+ return runWithPattern(ctx, pattern, query, { namevar: 'um.srvname' }, 0);
1465
+ },
1466
+ };
1467
+ const cmdListForeignTables = {
1468
+ name: 'det',
1469
+ run: async (ctx) => {
1470
+ const pattern = ctx.nextArg('normal');
1471
+ const c = conn(ctx);
1472
+ if (!c)
1473
+ return noConn(ctx);
1474
+ const { verbose } = decodeSuffix(ctx.cmdName, 'det');
1475
+ const query = listForeignTables({
1476
+ pattern: pattern ?? undefined,
1477
+ verbose,
1478
+ serverVersion: c.serverVersion,
1479
+ });
1480
+ return runWithPattern(ctx, pattern, query, {
1481
+ namevar: 'c.relname',
1482
+ schemavar: 'n.nspname',
1483
+ });
1484
+ },
1485
+ };
1486
+ // ---- \dP / \dPi / \dPt / \dPn -----------------------------------------
1487
+ const cmdListPartitionedTables = {
1488
+ name: 'dP',
1489
+ run: async (ctx) => {
1490
+ const pattern = ctx.nextArg('normal');
1491
+ const c = conn(ctx);
1492
+ if (!c)
1493
+ return noConn(ctx);
1494
+ const { verbose } = decodeSuffix(ctx.cmdName, 'dP');
1495
+ let reltypes = '';
1496
+ if (ctx.cmdName.includes('i'))
1497
+ reltypes += 'i';
1498
+ if (ctx.cmdName.includes('t'))
1499
+ reltypes += 't';
1500
+ if (ctx.cmdName.includes('n'))
1501
+ reltypes += 'n';
1502
+ const query = listPartitionedTables({
1503
+ pattern: pattern ?? undefined,
1504
+ verbose,
1505
+ reltypes,
1506
+ serverVersion: c.serverVersion,
1507
+ });
1508
+ return runWithPattern(ctx, pattern, query, {
1509
+ namevar: 'c.relname',
1510
+ schemavar: 'n.nspname',
1511
+ });
1512
+ },
1513
+ };
1514
+ // ---- \dX / \dX+ -------------------------------------------------------
1515
+ // Extended-statistics objects (`pg_statistic_ext`). Patterns accept the
1516
+ // usual schema-qualified form: `\dX` schema.name, with the cross-database
1517
+ // 2-dot check applied.
1518
+ const cmdListExtendedStats = (cmdName) => ({
1519
+ name: cmdName,
1520
+ run: async (ctx) => {
1521
+ const pattern = ctx.nextArg('normal');
1522
+ const c = conn(ctx);
1523
+ if (!c)
1524
+ return noConn(ctx);
1525
+ const { verbose } = decodeSuffix(cmdName, 'dX');
1526
+ const query = listExtendedStats({
1527
+ pattern: pattern ?? undefined,
1528
+ verbose,
1529
+ serverVersion: c.serverVersion,
1530
+ });
1531
+ return runWithPattern(ctx, pattern, query, {
1532
+ namevar: 'es.stxname',
1533
+ schemavar: 'es.stxnamespace::pg_catalog.regnamespace::pg_catalog.text',
1534
+ });
1535
+ },
1536
+ });
1537
+ // ---- \drg / \drg+ -----------------------------------------------------
1538
+ // Role grants — membership rows from `pg_auth_members`. Two-pattern shape
1539
+ // is upstream-specific to `\du`/`\dg`; `\drg` carries the same pattern as
1540
+ // `\du` (role name), with the cross-database check declined because roles
1541
+ // are global.
1542
+ const cmdDescribeRoleGrants = (cmdName) => ({
1543
+ name: cmdName,
1544
+ run: async (ctx) => {
1545
+ const pattern = ctx.nextArg('normal');
1546
+ const c = conn(ctx);
1547
+ if (!c)
1548
+ return noConn(ctx);
1549
+ const { showSystem } = decodeSuffix(cmdName, 'drg');
1550
+ const query = describeRoleGrants({
1551
+ pattern: pattern ?? undefined,
1552
+ showSystem,
1553
+ serverVersion: c.serverVersion,
1554
+ });
1555
+ return runWithPattern(ctx, pattern, query, { namevar: 'm.rolname' }, 0);
1556
+ },
1557
+ });
1558
+ // ---- \dRp / \dRs ------------------------------------------------------
1559
+ const cmdListPublications = {
1560
+ name: 'dRp',
1561
+ run: async (ctx) => {
1562
+ const pattern = ctx.nextArg('normal');
1563
+ const c = conn(ctx);
1564
+ if (!c)
1565
+ return noConn(ctx);
1566
+ const query = listPublications({
1567
+ pattern: pattern ?? undefined,
1568
+ serverVersion: c.serverVersion,
1569
+ });
1570
+ // Publications are global with no schema or database qualifier
1571
+ // (upstream `validateSQLNamePattern(..., NULL, 1)`): any dot is
1572
+ // "too many".
1573
+ return runWithPattern(ctx, pattern, query, { namevar: 'pubname' }, 0);
1574
+ },
1575
+ };
1576
+ const cmdDescribeSubscriptions = {
1577
+ name: 'dRs',
1578
+ run: async (ctx) => {
1579
+ const pattern = ctx.nextArg('normal');
1580
+ const c = conn(ctx);
1581
+ if (!c)
1582
+ return noConn(ctx);
1583
+ const { verbose } = decodeSuffix(ctx.cmdName, 'dRs');
1584
+ const query = describeSubscriptions({
1585
+ pattern: pattern ?? undefined,
1586
+ verbose,
1587
+ serverVersion: c.serverVersion,
1588
+ });
1589
+ // Subscriptions are global with no schema or database qualifier
1590
+ // (upstream `validateSQLNamePattern(..., NULL, 1)`): any dot is
1591
+ // "too many".
1592
+ return runWithPattern(ctx, pattern, query, { namevar: 'subname' }, 0);
1593
+ },
1594
+ };
1595
+ // ===========================================================================
1596
+ // Registration
1597
+ // ===========================================================================
1598
+ /**
1599
+ * Register every `\d*` command this WP implements on the given
1600
+ * registry. Called from `dispatch.ts::defaultRegistry()`.
1601
+ *
1602
+ * We register a separate spec per suffix combination so the dispatcher
1603
+ * can do a single name lookup without re-parsing. The implementation
1604
+ * functions are factory-style (`make…(cmdName)`) so each spec carries
1605
+ * the exact command name for suffix decoding.
1606
+ */
1607
+ export const registerDescribeCommands = (registry) => {
1608
+ // Relation list+detail family. Each suffix combination is its own
1609
+ // registered name; suffix decoding happens at runtime via the
1610
+ // `cmdName` field on the spec (set per-registration).
1611
+ for (const base of RELATION_BASES) {
1612
+ for (const suffix of SUFFIX_COMBOS) {
1613
+ const name = base + suffix;
1614
+ registry.register({ ...makeDescribeCmd(base), name });
1615
+ }
1616
+ }
1617
+ // `\d` accepts an additional `x` (expanded-mode toggle) suffix that we
1618
+ // register only for the bare `\d` base — upstream only honours it for
1619
+ // `\d` itself, not for typed variants like `\dt` or `\dv`.
1620
+ for (const suffix of DESCRIBE_X_SUFFIXES) {
1621
+ registry.register({ ...makeDescribeCmd('d'), name: 'd' + suffix });
1622
+ }
1623
+ // Functions. Beyond the standard `\df[+S]` matrix, upstream accepts
1624
+ // function-kind filter letters (a / n / p / t / w) and an expanded-mode
1625
+ // toggle (x) appended in any order, with multiple stacking. The fanout
1626
+ // covers the common one-letter additions used in regress; combined
1627
+ // letter sequences (`\dfax+`, `\dfxw`, …) are handled by the runtime
1628
+ // suffix walk in `cmdDescribeFunctions`.
1629
+ const dfTails = [
1630
+ '',
1631
+ '+',
1632
+ 'S',
1633
+ 'S+',
1634
+ '+S',
1635
+ 'a',
1636
+ 'n',
1637
+ 'p',
1638
+ 't',
1639
+ 'w',
1640
+ 'x',
1641
+ 'ax',
1642
+ 'nx',
1643
+ 'px',
1644
+ 'tx',
1645
+ 'wx',
1646
+ 'xa',
1647
+ 'xn',
1648
+ 'xp',
1649
+ 'xt',
1650
+ 'xw',
1651
+ 'a+',
1652
+ 'n+',
1653
+ 'p+',
1654
+ 't+',
1655
+ 'w+',
1656
+ 'x+',
1657
+ 'a+x',
1658
+ 'x+a',
1659
+ ];
1660
+ for (const tail of dfTails) {
1661
+ registry.register(cmdDescribeFunctions('df' + tail));
1662
+ }
1663
+ // Aggregates.
1664
+ for (const suffix of ['', 'S']) {
1665
+ registry.register(cmdDescribeAggregates('da' + suffix));
1666
+ }
1667
+ // Access methods. `\dA[+]` is upstream; `\dAm[+]` is a Neon-friendly
1668
+ // alias that some docs reach for. `\dAc[+]`, `\dAf[+]`, `\dAo[+]`,
1669
+ // `\dAp[+]` cover the operator-class / family / family-operator /
1670
+ // family-function families upstream's `command.c::exec_command_d`
1671
+ // dispatches.
1672
+ for (const suffix of ['', '+']) {
1673
+ registry.register(cmdDescribeAccessMethods('dA' + suffix));
1674
+ registry.register(cmdDescribeAccessMethods('dAm' + suffix));
1675
+ registry.register(cmdListOperatorClasses('dAc' + suffix));
1676
+ registry.register(cmdListOperatorFamilies('dAf' + suffix));
1677
+ registry.register(cmdListOpFamilyOperators('dAo' + suffix));
1678
+ registry.register(cmdListOpFamilyFunctions('dAp' + suffix));
1679
+ }
1680
+ // `x` (expanded) variants of the two-pattern access-method families.
1681
+ for (const tail of ['x', 'x+', '+x', 'xS', 'Sx']) {
1682
+ registry.register(cmdListOpFamilyFunctions('dAp' + tail));
1683
+ }
1684
+ // Types.
1685
+ for (const suffix of SUFFIX_COMBOS) {
1686
+ registry.register(cmdDescribeTypes('dT' + suffix));
1687
+ }
1688
+ // Operators.
1689
+ for (const suffix of SUFFIX_COMBOS) {
1690
+ registry.register(cmdDescribeOperators('do' + suffix));
1691
+ }
1692
+ // Roles.
1693
+ for (const suffix of ['', '+', 'S', 'S+', '+S']) {
1694
+ registry.register(cmdDescribeRoles('du' + suffix));
1695
+ registry.register(cmdDescribeRoles('dg' + suffix));
1696
+ }
1697
+ // Schemas.
1698
+ for (const suffix of SUFFIX_COMBOS) {
1699
+ registry.register(cmdListSchemas('dn' + suffix));
1700
+ }
1701
+ // Domains.
1702
+ for (const suffix of SUFFIX_COMBOS) {
1703
+ registry.register(cmdListDomains('dD' + suffix));
1704
+ }
1705
+ // Conversions.
1706
+ for (const suffix of SUFFIX_COMBOS) {
1707
+ registry.register(cmdListConversions('dc' + suffix));
1708
+ }
1709
+ // Casts.
1710
+ for (const suffix of ['', '+']) {
1711
+ registry.register(cmdListCasts('dC' + suffix));
1712
+ }
1713
+ // Tablespaces.
1714
+ for (const suffix of ['', '+']) {
1715
+ registry.register(cmdDescribeTablespaces('db' + suffix));
1716
+ }
1717
+ // Languages.
1718
+ for (const suffix of SUFFIX_COMBOS) {
1719
+ registry.register(cmdListLanguages('dL' + suffix));
1720
+ }
1721
+ // Collations.
1722
+ for (const suffix of SUFFIX_COMBOS) {
1723
+ registry.register(cmdListCollations('dO' + suffix));
1724
+ }
1725
+ // Permissions / default ACLs. `\dp` and `\z` are independent base names
1726
+ // (aliasing them via `aliases` couldn't carry separate `x`-suffix
1727
+ // variants), so register each with its own suffix matrix.
1728
+ for (const tail of ['', 'S', 'x', 'Sx', 'xS']) {
1729
+ registry.register(cmdPermissionsList('dp' + tail));
1730
+ registry.register(cmdPermissionsList('z' + tail));
1731
+ }
1732
+ registry.register(cmdListDefaultACLs);
1733
+ // Descriptions.
1734
+ registry.register(cmdObjectDescription);
1735
+ // Databases. `\l`/`\list` plus their `+` (verbose) variants — the scanner
1736
+ // folds the trailing `+` into the command name, so `\l+` needs its own
1737
+ // registration or it dispatches to nothing. `decodeSuffix`
1738
+ // reads the verbose flag back off `ctx.cmdName` at runtime.
1739
+ registry.register(cmdListAllDbs);
1740
+ registry.register({ ...cmdListAllDbs, name: 'l+', aliases: ['list+'] });
1741
+ // Config / event triggers / extensions / large objects.
1742
+ registry.register(cmdDescribeConfigParams);
1743
+ registry.register({ ...cmdDescribeConfigParams, name: 'dconfig+' });
1744
+ registry.register(cmdListEventTriggers);
1745
+ registry.register(cmdListExtensions);
1746
+ registry.register({ ...cmdListExtensions, name: 'dx+' });
1747
+ registry.register(cmdListLargeObjects);
1748
+ // Text search family.
1749
+ registry.register(cmdListTSConfigs);
1750
+ registry.register({ ...cmdListTSConfigs, name: 'dF+' });
1751
+ registry.register(cmdListTSParsers);
1752
+ registry.register({ ...cmdListTSParsers, name: 'dFp+' });
1753
+ registry.register(cmdListTSDictionaries);
1754
+ registry.register({ ...cmdListTSDictionaries, name: 'dFd+' });
1755
+ registry.register(cmdListTSTemplates);
1756
+ registry.register({ ...cmdListTSTemplates, name: 'dFt+' });
1757
+ // Foreign-data family.
1758
+ for (const variant of ['dew', 'dew+']) {
1759
+ registry.register({ ...cmdListForeignDataWrappers, name: variant });
1760
+ }
1761
+ for (const variant of ['des', 'des+']) {
1762
+ registry.register({ ...cmdListForeignServers, name: variant });
1763
+ }
1764
+ for (const variant of ['deu', 'deu+']) {
1765
+ registry.register({ ...cmdListUserMappings, name: variant });
1766
+ }
1767
+ for (const variant of ['det', 'det+']) {
1768
+ registry.register({ ...cmdListForeignTables, name: variant });
1769
+ }
1770
+ // Partitioned tables. Upstream's `\dP` accepts any concatenation of
1771
+ // {i,t,n}: `\dPtn` lists nested partitioned tables (table + nested
1772
+ // toggles), `\dPin` lists nested partitioned indexes, etc. We
1773
+ // register the most common combinations explicitly; the spec's
1774
+ // suffix-decoding loop (`includes('i'|'t'|'n')`) drives the actual
1775
+ // `reltypes` selection at runtime.
1776
+ for (const variant of [
1777
+ 'dP',
1778
+ 'dP+',
1779
+ 'dPi',
1780
+ 'dPi+',
1781
+ 'dPin',
1782
+ 'dPin+',
1783
+ 'dPt',
1784
+ 'dPt+',
1785
+ 'dPtn',
1786
+ 'dPtn+',
1787
+ 'dPn',
1788
+ 'dPn+',
1789
+ ]) {
1790
+ registry.register({ ...cmdListPartitionedTables, name: variant });
1791
+ }
1792
+ // Role-database settings.
1793
+ registry.register(cmdListDbRoleSettings);
1794
+ registry.register({ ...cmdListDbRoleSettings, name: 'drds+' });
1795
+ // Extended statistics (\dX / \dX+).
1796
+ for (const suffix of ['', '+']) {
1797
+ registry.register(cmdListExtendedStats('dX' + suffix));
1798
+ }
1799
+ // Role grants (\drg / \drg+ / \drgS).
1800
+ for (const suffix of ['', '+', 'S', 'S+', '+S']) {
1801
+ registry.register(cmdDescribeRoleGrants('drg' + suffix));
1802
+ }
1803
+ // Publication / subscription.
1804
+ for (const variant of ['dRp', 'dRp+']) {
1805
+ registry.register({ ...cmdListPublications, name: variant });
1806
+ }
1807
+ for (const variant of ['dRs', 'dRs+']) {
1808
+ registry.register({ ...cmdDescribeSubscriptions, name: variant });
1809
+ }
1810
+ };