1 /*
2 * psql - the PostgreSQL interactive terminal
3 *
4 * Support for the various \d ("describe") commands. Note that the current
5 * expectation is that all functions in this file will succeed when working
6 * with servers of versions 7.4 and up. It's okay to omit irrelevant
7 * information for an old server, but not to fail outright.
8 *
9 * Copyright (c) 2000-2018, PostgreSQL Global Development Group
10 *
11 * src/bin/psql/describe.c
12 */
13 #include "postgres_fe.h"
14
15 #include <ctype.h>
16
17 #include "catalog/pg_attribute_d.h"
18 #include "catalog/pg_cast_d.h"
19 #include "catalog/pg_class_d.h"
20 #include "catalog/pg_default_acl_d.h"
21 #include "fe_utils/string_utils.h"
22
23 #include "common.h"
24 #include "describe.h"
25 #include "fe_utils/mbprint.h"
26 #include "fe_utils/print.h"
27 #include "settings.h"
28 #include "variables.h"
29
30
31 static bool describeOneTableDetails(const char *schemaname,
32 const char *relationname,
33 const char *oid,
34 bool verbose);
35 static void add_tablespace_footer(printTableContent *const cont, char relkind,
36 Oid tablespace, const bool newline);
37 static void add_role_attribute(PQExpBuffer buf, const char *const str);
38 static bool listTSParsersVerbose(const char *pattern);
39 static bool describeOneTSParser(const char *oid, const char *nspname,
40 const char *prsname);
41 static bool listTSConfigsVerbose(const char *pattern);
42 static bool describeOneTSConfig(const char *oid, const char *nspname,
43 const char *cfgname,
44 const char *pnspname, const char *prsname);
45 static void printACLColumn(PQExpBuffer buf, const char *colname);
46 static bool listOneExtensionContents(const char *extname, const char *oid);
47
48
49 /*----------------
50 * Handlers for various slash commands displaying some sort of list
51 * of things in the database.
52 *
53 * Note: try to format the queries to look nice in -E output.
54 *----------------
55 */
56
57
58 /*
59 * \da
60 * Takes an optional regexp to select particular aggregates
61 */
62 bool
describeAggregates(const char * pattern,bool verbose,bool showSystem)63 describeAggregates(const char *pattern, bool verbose, bool showSystem)
64 {
65 PQExpBufferData buf;
66 PGresult *res;
67 printQueryOpt myopt = pset.popt;
68
69 initPQExpBuffer(&buf);
70
71 printfPQExpBuffer(&buf,
72 "SELECT n.nspname as \"%s\",\n"
73 " p.proname AS \"%s\",\n"
74 " pg_catalog.format_type(p.prorettype, NULL) AS \"%s\",\n",
75 gettext_noop("Schema"),
76 gettext_noop("Name"),
77 gettext_noop("Result data type"));
78
79 if (pset.sversion >= 80400)
80 appendPQExpBuffer(&buf,
81 " CASE WHEN p.pronargs = 0\n"
82 " THEN CAST('*' AS pg_catalog.text)\n"
83 " ELSE pg_catalog.pg_get_function_arguments(p.oid)\n"
84 " END AS \"%s\",\n",
85 gettext_noop("Argument data types"));
86 else if (pset.sversion >= 80200)
87 appendPQExpBuffer(&buf,
88 " CASE WHEN p.pronargs = 0\n"
89 " THEN CAST('*' AS pg_catalog.text)\n"
90 " ELSE\n"
91 " pg_catalog.array_to_string(ARRAY(\n"
92 " SELECT\n"
93 " pg_catalog.format_type(p.proargtypes[s.i], NULL)\n"
94 " FROM\n"
95 " pg_catalog.generate_series(0, pg_catalog.array_upper(p.proargtypes, 1)) AS s(i)\n"
96 " ), ', ')\n"
97 " END AS \"%s\",\n",
98 gettext_noop("Argument data types"));
99 else
100 appendPQExpBuffer(&buf,
101 " pg_catalog.format_type(p.proargtypes[0], NULL) AS \"%s\",\n",
102 gettext_noop("Argument data types"));
103
104 if (pset.sversion >= 110000)
105 appendPQExpBuffer(&buf,
106 " pg_catalog.obj_description(p.oid, 'pg_proc') as \"%s\"\n"
107 "FROM pg_catalog.pg_proc p\n"
108 " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace\n"
109 "WHERE p.prokind = 'a'\n",
110 gettext_noop("Description"));
111 else
112 appendPQExpBuffer(&buf,
113 " pg_catalog.obj_description(p.oid, 'pg_proc') as \"%s\"\n"
114 "FROM pg_catalog.pg_proc p\n"
115 " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace\n"
116 "WHERE p.proisagg\n",
117 gettext_noop("Description"));
118
119 if (!showSystem && !pattern)
120 appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
121 " AND n.nspname <> 'information_schema'\n");
122
123 processSQLNamePattern(pset.db, &buf, pattern, true, false,
124 "n.nspname", "p.proname", NULL,
125 "pg_catalog.pg_function_is_visible(p.oid)");
126
127 appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 4;");
128
129 res = PSQLexec(buf.data);
130 termPQExpBuffer(&buf);
131 if (!res)
132 return false;
133
134 myopt.nullPrint = NULL;
135 myopt.title = _("List of aggregate functions");
136 myopt.translate_header = true;
137
138 printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
139
140 PQclear(res);
141 return true;
142 }
143
144 /*
145 * \dA
146 * Takes an optional regexp to select particular access methods
147 */
148 bool
describeAccessMethods(const char * pattern,bool verbose)149 describeAccessMethods(const char *pattern, bool verbose)
150 {
151 PQExpBufferData buf;
152 PGresult *res;
153 printQueryOpt myopt = pset.popt;
154 static const bool translate_columns[] = {false, true, false, false};
155
156 if (pset.sversion < 90600)
157 {
158 char sverbuf[32];
159
160 psql_error("The server (version %s) does not support access methods.\n",
161 formatPGVersionNumber(pset.sversion, false,
162 sverbuf, sizeof(sverbuf)));
163 return true;
164 }
165
166 initPQExpBuffer(&buf);
167
168 printfPQExpBuffer(&buf,
169 "SELECT amname AS \"%s\",\n"
170 " CASE amtype"
171 " WHEN 'i' THEN '%s'"
172 " END AS \"%s\"",
173 gettext_noop("Name"),
174 gettext_noop("Index"),
175 gettext_noop("Type"));
176
177 if (verbose)
178 {
179 appendPQExpBuffer(&buf,
180 ",\n amhandler AS \"%s\",\n"
181 " pg_catalog.obj_description(oid, 'pg_am') AS \"%s\"",
182 gettext_noop("Handler"),
183 gettext_noop("Description"));
184 }
185
186 appendPQExpBufferStr(&buf,
187 "\nFROM pg_catalog.pg_am\n");
188
189 processSQLNamePattern(pset.db, &buf, pattern, false, false,
190 NULL, "amname", NULL,
191 NULL);
192
193 appendPQExpBufferStr(&buf, "ORDER BY 1;");
194
195 res = PSQLexec(buf.data);
196 termPQExpBuffer(&buf);
197 if (!res)
198 return false;
199
200 myopt.nullPrint = NULL;
201 myopt.title = _("List of access methods");
202 myopt.translate_header = true;
203 myopt.translate_columns = translate_columns;
204 myopt.n_translate_columns = lengthof(translate_columns);
205
206 printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
207
208 PQclear(res);
209 return true;
210 }
211
212 /*
213 * \db
214 * Takes an optional regexp to select particular tablespaces
215 */
216 bool
describeTablespaces(const char * pattern,bool verbose)217 describeTablespaces(const char *pattern, bool verbose)
218 {
219 PQExpBufferData buf;
220 PGresult *res;
221 printQueryOpt myopt = pset.popt;
222
223 if (pset.sversion < 80000)
224 {
225 char sverbuf[32];
226
227 psql_error("The server (version %s) does not support tablespaces.\n",
228 formatPGVersionNumber(pset.sversion, false,
229 sverbuf, sizeof(sverbuf)));
230 return true;
231 }
232
233 initPQExpBuffer(&buf);
234
235 if (pset.sversion >= 90200)
236 printfPQExpBuffer(&buf,
237 "SELECT spcname AS \"%s\",\n"
238 " pg_catalog.pg_get_userbyid(spcowner) AS \"%s\",\n"
239 " pg_catalog.pg_tablespace_location(oid) AS \"%s\"",
240 gettext_noop("Name"),
241 gettext_noop("Owner"),
242 gettext_noop("Location"));
243 else
244 printfPQExpBuffer(&buf,
245 "SELECT spcname AS \"%s\",\n"
246 " pg_catalog.pg_get_userbyid(spcowner) AS \"%s\",\n"
247 " spclocation AS \"%s\"",
248 gettext_noop("Name"),
249 gettext_noop("Owner"),
250 gettext_noop("Location"));
251
252 if (verbose)
253 {
254 appendPQExpBufferStr(&buf, ",\n ");
255 printACLColumn(&buf, "spcacl");
256 }
257
258 if (verbose && pset.sversion >= 90000)
259 appendPQExpBuffer(&buf,
260 ",\n spcoptions AS \"%s\"",
261 gettext_noop("Options"));
262
263 if (verbose && pset.sversion >= 90200)
264 appendPQExpBuffer(&buf,
265 ",\n pg_catalog.pg_size_pretty(pg_catalog.pg_tablespace_size(oid)) AS \"%s\"",
266 gettext_noop("Size"));
267
268 if (verbose && pset.sversion >= 80200)
269 appendPQExpBuffer(&buf,
270 ",\n pg_catalog.shobj_description(oid, 'pg_tablespace') AS \"%s\"",
271 gettext_noop("Description"));
272
273 appendPQExpBufferStr(&buf,
274 "\nFROM pg_catalog.pg_tablespace\n");
275
276 processSQLNamePattern(pset.db, &buf, pattern, false, false,
277 NULL, "spcname", NULL,
278 NULL);
279
280 appendPQExpBufferStr(&buf, "ORDER BY 1;");
281
282 res = PSQLexec(buf.data);
283 termPQExpBuffer(&buf);
284 if (!res)
285 return false;
286
287 myopt.nullPrint = NULL;
288 myopt.title = _("List of tablespaces");
289 myopt.translate_header = true;
290
291 printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
292
293 PQclear(res);
294 return true;
295 }
296
297
298 /*
299 * \df
300 * Takes an optional regexp to select particular functions.
301 *
302 * As with \d, you can specify the kinds of functions you want:
303 *
304 * a for aggregates
305 * n for normal
306 * t for trigger
307 * w for window
308 *
309 * and you can mix and match these in any order.
310 */
311 bool
describeFunctions(const char * functypes,const char * pattern,bool verbose,bool showSystem)312 describeFunctions(const char *functypes, const char *pattern, bool verbose, bool showSystem)
313 {
314 bool showAggregate = strchr(functypes, 'a') != NULL;
315 bool showNormal = strchr(functypes, 'n') != NULL;
316 bool showProcedure = strchr(functypes, 'p') != NULL;
317 bool showTrigger = strchr(functypes, 't') != NULL;
318 bool showWindow = strchr(functypes, 'w') != NULL;
319 bool have_where;
320 PQExpBufferData buf;
321 PGresult *res;
322 printQueryOpt myopt = pset.popt;
323 static const bool translate_columns[] = {false, false, false, false, true, true, true, false, true, false, false, false, false};
324
325 /* No "Parallel" column before 9.6 */
326 static const bool translate_columns_pre_96[] = {false, false, false, false, true, true, false, true, false, false, false, false};
327
328 if (strlen(functypes) != strspn(functypes, "anptwS+"))
329 {
330 psql_error("\\df only takes [anptwS+] as options\n");
331 return true;
332 }
333
334 if (showProcedure && pset.sversion < 110000)
335 {
336 char sverbuf[32];
337
338 psql_error("\\df does not take a \"%c\" option with server version %s\n",
339 'p',
340 formatPGVersionNumber(pset.sversion, false,
341 sverbuf, sizeof(sverbuf)));
342 return true;
343 }
344
345 if (showWindow && pset.sversion < 80400)
346 {
347 char sverbuf[32];
348
349 psql_error("\\df does not take a \"%c\" option with server version %s\n",
350 'w',
351 formatPGVersionNumber(pset.sversion, false,
352 sverbuf, sizeof(sverbuf)));
353 return true;
354 }
355
356 if (!showAggregate && !showNormal && !showProcedure && !showTrigger && !showWindow)
357 {
358 showAggregate = showNormal = showTrigger = true;
359 if (pset.sversion >= 110000)
360 showProcedure = true;
361 if (pset.sversion >= 80400)
362 showWindow = true;
363 }
364
365 initPQExpBuffer(&buf);
366
367 printfPQExpBuffer(&buf,
368 "SELECT n.nspname as \"%s\",\n"
369 " p.proname as \"%s\",\n",
370 gettext_noop("Schema"),
371 gettext_noop("Name"));
372
373 if (pset.sversion >= 110000)
374 appendPQExpBuffer(&buf,
375 " pg_catalog.pg_get_function_result(p.oid) as \"%s\",\n"
376 " pg_catalog.pg_get_function_arguments(p.oid) as \"%s\",\n"
377 " CASE p.prokind\n"
378 " WHEN 'a' THEN '%s'\n"
379 " WHEN 'w' THEN '%s'\n"
380 " WHEN 'p' THEN '%s'\n"
381 " ELSE '%s'\n"
382 " END as \"%s\"",
383 gettext_noop("Result data type"),
384 gettext_noop("Argument data types"),
385 /* translator: "agg" is short for "aggregate" */
386 gettext_noop("agg"),
387 gettext_noop("window"),
388 gettext_noop("proc"),
389 gettext_noop("func"),
390 gettext_noop("Type"));
391 else if (pset.sversion >= 80400)
392 appendPQExpBuffer(&buf,
393 " pg_catalog.pg_get_function_result(p.oid) as \"%s\",\n"
394 " pg_catalog.pg_get_function_arguments(p.oid) as \"%s\",\n"
395 " CASE\n"
396 " WHEN p.proisagg THEN '%s'\n"
397 " WHEN p.proiswindow THEN '%s'\n"
398 " WHEN p.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype THEN '%s'\n"
399 " ELSE '%s'\n"
400 " END as \"%s\"",
401 gettext_noop("Result data type"),
402 gettext_noop("Argument data types"),
403 /* translator: "agg" is short for "aggregate" */
404 gettext_noop("agg"),
405 gettext_noop("window"),
406 gettext_noop("trigger"),
407 gettext_noop("func"),
408 gettext_noop("Type"));
409 else if (pset.sversion >= 80100)
410 appendPQExpBuffer(&buf,
411 " CASE WHEN p.proretset THEN 'SETOF ' ELSE '' END ||\n"
412 " pg_catalog.format_type(p.prorettype, NULL) as \"%s\",\n"
413 " CASE WHEN proallargtypes IS NOT NULL THEN\n"
414 " pg_catalog.array_to_string(ARRAY(\n"
415 " SELECT\n"
416 " CASE\n"
417 " WHEN p.proargmodes[s.i] = 'i' THEN ''\n"
418 " WHEN p.proargmodes[s.i] = 'o' THEN 'OUT '\n"
419 " WHEN p.proargmodes[s.i] = 'b' THEN 'INOUT '\n"
420 " WHEN p.proargmodes[s.i] = 'v' THEN 'VARIADIC '\n"
421 " END ||\n"
422 " CASE\n"
423 " WHEN COALESCE(p.proargnames[s.i], '') = '' THEN ''\n"
424 " ELSE p.proargnames[s.i] || ' '\n"
425 " END ||\n"
426 " pg_catalog.format_type(p.proallargtypes[s.i], NULL)\n"
427 " FROM\n"
428 " pg_catalog.generate_series(1, pg_catalog.array_upper(p.proallargtypes, 1)) AS s(i)\n"
429 " ), ', ')\n"
430 " ELSE\n"
431 " pg_catalog.array_to_string(ARRAY(\n"
432 " SELECT\n"
433 " CASE\n"
434 " WHEN COALESCE(p.proargnames[s.i+1], '') = '' THEN ''\n"
435 " ELSE p.proargnames[s.i+1] || ' '\n"
436 " END ||\n"
437 " pg_catalog.format_type(p.proargtypes[s.i], NULL)\n"
438 " FROM\n"
439 " pg_catalog.generate_series(0, pg_catalog.array_upper(p.proargtypes, 1)) AS s(i)\n"
440 " ), ', ')\n"
441 " END AS \"%s\",\n"
442 " CASE\n"
443 " WHEN p.proisagg THEN '%s'\n"
444 " WHEN p.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype THEN '%s'\n"
445 " ELSE '%s'\n"
446 " END AS \"%s\"",
447 gettext_noop("Result data type"),
448 gettext_noop("Argument data types"),
449 /* translator: "agg" is short for "aggregate" */
450 gettext_noop("agg"),
451 gettext_noop("trigger"),
452 gettext_noop("func"),
453 gettext_noop("Type"));
454 else
455 appendPQExpBuffer(&buf,
456 " CASE WHEN p.proretset THEN 'SETOF ' ELSE '' END ||\n"
457 " pg_catalog.format_type(p.prorettype, NULL) as \"%s\",\n"
458 " pg_catalog.oidvectortypes(p.proargtypes) as \"%s\",\n"
459 " CASE\n"
460 " WHEN p.proisagg THEN '%s'\n"
461 " WHEN p.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype THEN '%s'\n"
462 " ELSE '%s'\n"
463 " END AS \"%s\"",
464 gettext_noop("Result data type"),
465 gettext_noop("Argument data types"),
466 /* translator: "agg" is short for "aggregate" */
467 gettext_noop("agg"),
468 gettext_noop("trigger"),
469 gettext_noop("func"),
470 gettext_noop("Type"));
471
472 if (verbose)
473 {
474 appendPQExpBuffer(&buf,
475 ",\n CASE\n"
476 " WHEN p.provolatile = 'i' THEN '%s'\n"
477 " WHEN p.provolatile = 's' THEN '%s'\n"
478 " WHEN p.provolatile = 'v' THEN '%s'\n"
479 " END as \"%s\"",
480 gettext_noop("immutable"),
481 gettext_noop("stable"),
482 gettext_noop("volatile"),
483 gettext_noop("Volatility"));
484 if (pset.sversion >= 90600)
485 appendPQExpBuffer(&buf,
486 ",\n CASE\n"
487 " WHEN p.proparallel = 'r' THEN '%s'\n"
488 " WHEN p.proparallel = 's' THEN '%s'\n"
489 " WHEN p.proparallel = 'u' THEN '%s'\n"
490 " END as \"%s\"",
491 gettext_noop("restricted"),
492 gettext_noop("safe"),
493 gettext_noop("unsafe"),
494 gettext_noop("Parallel"));
495 appendPQExpBuffer(&buf,
496 ",\n pg_catalog.pg_get_userbyid(p.proowner) as \"%s\""
497 ",\n CASE WHEN prosecdef THEN '%s' ELSE '%s' END AS \"%s\"",
498 gettext_noop("Owner"),
499 gettext_noop("definer"),
500 gettext_noop("invoker"),
501 gettext_noop("Security"));
502 appendPQExpBufferStr(&buf, ",\n ");
503 printACLColumn(&buf, "p.proacl");
504 appendPQExpBuffer(&buf,
505 ",\n l.lanname as \"%s\""
506 ",\n p.prosrc as \"%s\""
507 ",\n pg_catalog.obj_description(p.oid, 'pg_proc') as \"%s\"",
508 gettext_noop("Language"),
509 gettext_noop("Source code"),
510 gettext_noop("Description"));
511 }
512
513 appendPQExpBufferStr(&buf,
514 "\nFROM pg_catalog.pg_proc p"
515 "\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace\n");
516
517 if (verbose)
518 appendPQExpBufferStr(&buf,
519 " LEFT JOIN pg_catalog.pg_language l ON l.oid = p.prolang\n");
520
521 have_where = false;
522
523 /* filter by function type, if requested */
524 if (showNormal && showAggregate && showProcedure && showTrigger && showWindow)
525 /* Do nothing */ ;
526 else if (showNormal)
527 {
528 if (!showAggregate)
529 {
530 if (have_where)
531 appendPQExpBufferStr(&buf, " AND ");
532 else
533 {
534 appendPQExpBufferStr(&buf, "WHERE ");
535 have_where = true;
536 }
537 if (pset.sversion >= 110000)
538 appendPQExpBufferStr(&buf, "p.prokind <> 'a'\n");
539 else
540 appendPQExpBufferStr(&buf, "NOT p.proisagg\n");
541 }
542 if (!showProcedure && pset.sversion >= 110000)
543 {
544 if (have_where)
545 appendPQExpBufferStr(&buf, " AND ");
546 else
547 {
548 appendPQExpBufferStr(&buf, "WHERE ");
549 have_where = true;
550 }
551 appendPQExpBufferStr(&buf, "p.prokind <> 'p'\n");
552 }
553 if (!showTrigger)
554 {
555 if (have_where)
556 appendPQExpBufferStr(&buf, " AND ");
557 else
558 {
559 appendPQExpBufferStr(&buf, "WHERE ");
560 have_where = true;
561 }
562 appendPQExpBufferStr(&buf, "p.prorettype <> 'pg_catalog.trigger'::pg_catalog.regtype\n");
563 }
564 if (!showWindow && pset.sversion >= 80400)
565 {
566 if (have_where)
567 appendPQExpBufferStr(&buf, " AND ");
568 else
569 {
570 appendPQExpBufferStr(&buf, "WHERE ");
571 have_where = true;
572 }
573 if (pset.sversion >= 110000)
574 appendPQExpBufferStr(&buf, "p.prokind <> 'w'\n");
575 else
576 appendPQExpBufferStr(&buf, "NOT p.proiswindow\n");
577 }
578 }
579 else
580 {
581 bool needs_or = false;
582
583 appendPQExpBufferStr(&buf, "WHERE (\n ");
584 have_where = true;
585 /* Note: at least one of these must be true ... */
586 if (showAggregate)
587 {
588 if (pset.sversion >= 110000)
589 appendPQExpBufferStr(&buf, "p.prokind = 'a'\n");
590 else
591 appendPQExpBufferStr(&buf, "p.proisagg\n");
592 needs_or = true;
593 }
594 if (showTrigger)
595 {
596 if (needs_or)
597 appendPQExpBufferStr(&buf, " OR ");
598 appendPQExpBufferStr(&buf,
599 "p.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype\n");
600 needs_or = true;
601 }
602 if (showProcedure)
603 {
604 if (needs_or)
605 appendPQExpBufferStr(&buf, " OR ");
606 appendPQExpBufferStr(&buf, "p.prokind = 'p'\n");
607 needs_or = true;
608 }
609 if (showWindow)
610 {
611 if (needs_or)
612 appendPQExpBufferStr(&buf, " OR ");
613 if (pset.sversion >= 110000)
614 appendPQExpBufferStr(&buf, "p.prokind = 'w'\n");
615 else
616 appendPQExpBufferStr(&buf, "p.proiswindow\n");
617 needs_or = true;
618 }
619 appendPQExpBufferStr(&buf, " )\n");
620 }
621
622 processSQLNamePattern(pset.db, &buf, pattern, have_where, false,
623 "n.nspname", "p.proname", NULL,
624 "pg_catalog.pg_function_is_visible(p.oid)");
625
626 if (!showSystem && !pattern)
627 appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
628 " AND n.nspname <> 'information_schema'\n");
629
630 appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 4;");
631
632 res = PSQLexec(buf.data);
633 termPQExpBuffer(&buf);
634 if (!res)
635 return false;
636
637 myopt.nullPrint = NULL;
638 myopt.title = _("List of functions");
639 myopt.translate_header = true;
640 if (pset.sversion >= 90600)
641 {
642 myopt.translate_columns = translate_columns;
643 myopt.n_translate_columns = lengthof(translate_columns);
644 }
645 else
646 {
647 myopt.translate_columns = translate_columns_pre_96;
648 myopt.n_translate_columns = lengthof(translate_columns_pre_96);
649 }
650
651 printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
652
653 PQclear(res);
654 return true;
655 }
656
657
658
659 /*
660 * \dT
661 * describe types
662 */
663 bool
describeTypes(const char * pattern,bool verbose,bool showSystem)664 describeTypes(const char *pattern, bool verbose, bool showSystem)
665 {
666 PQExpBufferData buf;
667 PGresult *res;
668 printQueryOpt myopt = pset.popt;
669
670 initPQExpBuffer(&buf);
671
672 printfPQExpBuffer(&buf,
673 "SELECT n.nspname as \"%s\",\n"
674 " pg_catalog.format_type(t.oid, NULL) AS \"%s\",\n",
675 gettext_noop("Schema"),
676 gettext_noop("Name"));
677 if (verbose)
678 appendPQExpBuffer(&buf,
679 " t.typname AS \"%s\",\n"
680 " CASE WHEN t.typrelid != 0\n"
681 " THEN CAST('tuple' AS pg_catalog.text)\n"
682 " WHEN t.typlen < 0\n"
683 " THEN CAST('var' AS pg_catalog.text)\n"
684 " ELSE CAST(t.typlen AS pg_catalog.text)\n"
685 " END AS \"%s\",\n",
686 gettext_noop("Internal name"),
687 gettext_noop("Size"));
688 if (verbose && pset.sversion >= 80300)
689 {
690 appendPQExpBufferStr(&buf,
691 " pg_catalog.array_to_string(\n"
692 " ARRAY(\n"
693 " SELECT e.enumlabel\n"
694 " FROM pg_catalog.pg_enum e\n"
695 " WHERE e.enumtypid = t.oid\n");
696
697 if (pset.sversion >= 90100)
698 appendPQExpBufferStr(&buf,
699 " ORDER BY e.enumsortorder\n");
700 else
701 appendPQExpBufferStr(&buf,
702 " ORDER BY e.oid\n");
703
704 appendPQExpBuffer(&buf,
705 " ),\n"
706 " E'\\n'\n"
707 " ) AS \"%s\",\n",
708 gettext_noop("Elements"));
709 }
710 if (verbose)
711 {
712 appendPQExpBuffer(&buf,
713 " pg_catalog.pg_get_userbyid(t.typowner) AS \"%s\",\n",
714 gettext_noop("Owner"));
715 }
716 if (verbose && pset.sversion >= 90200)
717 {
718 printACLColumn(&buf, "t.typacl");
719 appendPQExpBufferStr(&buf, ",\n ");
720 }
721
722 appendPQExpBuffer(&buf,
723 " pg_catalog.obj_description(t.oid, 'pg_type') as \"%s\"\n",
724 gettext_noop("Description"));
725
726 appendPQExpBufferStr(&buf, "FROM pg_catalog.pg_type t\n"
727 " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace\n");
728
729 /*
730 * do not include complex types (typrelid!=0) unless they are standalone
731 * composite types
732 */
733 appendPQExpBufferStr(&buf, "WHERE (t.typrelid = 0 ");
734 appendPQExpBufferStr(&buf, "OR (SELECT c.relkind = " CppAsString2(RELKIND_COMPOSITE_TYPE)
735 " FROM pg_catalog.pg_class c "
736 "WHERE c.oid = t.typrelid))\n");
737
738 /*
739 * do not include array types (before 8.3 we have to use the assumption
740 * that their names start with underscore)
741 */
742 if (pset.sversion >= 80300)
743 appendPQExpBufferStr(&buf, " AND NOT EXISTS(SELECT 1 FROM pg_catalog.pg_type el WHERE el.oid = t.typelem AND el.typarray = t.oid)\n");
744 else
745 appendPQExpBufferStr(&buf, " AND t.typname !~ '^_'\n");
746
747 if (!showSystem && !pattern)
748 appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
749 " AND n.nspname <> 'information_schema'\n");
750
751 /* Match name pattern against either internal or external name */
752 processSQLNamePattern(pset.db, &buf, pattern, true, false,
753 "n.nspname", "t.typname",
754 "pg_catalog.format_type(t.oid, NULL)",
755 "pg_catalog.pg_type_is_visible(t.oid)");
756
757 appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
758
759 res = PSQLexec(buf.data);
760 termPQExpBuffer(&buf);
761 if (!res)
762 return false;
763
764 myopt.nullPrint = NULL;
765 myopt.title = _("List of data types");
766 myopt.translate_header = true;
767
768 printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
769
770 PQclear(res);
771 return true;
772 }
773
774
775 /*
776 * \do
777 * Describe operators
778 */
779 bool
describeOperators(const char * pattern,bool verbose,bool showSystem)780 describeOperators(const char *pattern, bool verbose, bool showSystem)
781 {
782 PQExpBufferData buf;
783 PGresult *res;
784 printQueryOpt myopt = pset.popt;
785
786 initPQExpBuffer(&buf);
787
788 /*
789 * Note: before Postgres 9.1, we did not assign comments to any built-in
790 * operators, preferring to let the comment on the underlying function
791 * suffice. The coalesce() on the obj_description() calls below supports
792 * this convention by providing a fallback lookup of a comment on the
793 * operator's function. As of 9.1 there is a policy that every built-in
794 * operator should have a comment; so the coalesce() is no longer
795 * necessary so far as built-in operators are concerned. We keep it
796 * anyway, for now, because (1) third-party modules may still be following
797 * the old convention, and (2) we'd need to do it anyway when talking to a
798 * pre-9.1 server.
799 */
800
801 printfPQExpBuffer(&buf,
802 "SELECT n.nspname as \"%s\",\n"
803 " o.oprname AS \"%s\",\n"
804 " CASE WHEN o.oprkind='l' THEN NULL ELSE pg_catalog.format_type(o.oprleft, NULL) END AS \"%s\",\n"
805 " CASE WHEN o.oprkind='r' THEN NULL ELSE pg_catalog.format_type(o.oprright, NULL) END AS \"%s\",\n"
806 " pg_catalog.format_type(o.oprresult, NULL) AS \"%s\",\n",
807 gettext_noop("Schema"),
808 gettext_noop("Name"),
809 gettext_noop("Left arg type"),
810 gettext_noop("Right arg type"),
811 gettext_noop("Result type"));
812
813 if (verbose)
814 appendPQExpBuffer(&buf,
815 " o.oprcode AS \"%s\",\n",
816 gettext_noop("Function"));
817
818 appendPQExpBuffer(&buf,
819 " coalesce(pg_catalog.obj_description(o.oid, 'pg_operator'),\n"
820 " pg_catalog.obj_description(o.oprcode, 'pg_proc')) AS \"%s\"\n"
821 "FROM pg_catalog.pg_operator o\n"
822 " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = o.oprnamespace\n",
823 gettext_noop("Description"));
824
825 if (!showSystem && !pattern)
826 appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
827 " AND n.nspname <> 'information_schema'\n");
828
829 processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern, true,
830 "n.nspname", "o.oprname", NULL,
831 "pg_catalog.pg_operator_is_visible(o.oid)");
832
833 appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 3, 4;");
834
835 res = PSQLexec(buf.data);
836 termPQExpBuffer(&buf);
837 if (!res)
838 return false;
839
840 myopt.nullPrint = NULL;
841 myopt.title = _("List of operators");
842 myopt.translate_header = true;
843
844 printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
845
846 PQclear(res);
847 return true;
848 }
849
850
851 /*
852 * listAllDbs
853 *
854 * for \l, \list, and -l switch
855 */
856 bool
listAllDbs(const char * pattern,bool verbose)857 listAllDbs(const char *pattern, bool verbose)
858 {
859 PGresult *res;
860 PQExpBufferData buf;
861 printQueryOpt myopt = pset.popt;
862
863 initPQExpBuffer(&buf);
864
865 printfPQExpBuffer(&buf,
866 "SELECT d.datname as \"%s\",\n"
867 " pg_catalog.pg_get_userbyid(d.datdba) as \"%s\",\n"
868 " pg_catalog.pg_encoding_to_char(d.encoding) as \"%s\",\n",
869 gettext_noop("Name"),
870 gettext_noop("Owner"),
871 gettext_noop("Encoding"));
872 if (pset.sversion >= 80400)
873 appendPQExpBuffer(&buf,
874 " d.datcollate as \"%s\",\n"
875 " d.datctype as \"%s\",\n",
876 gettext_noop("Collate"),
877 gettext_noop("Ctype"));
878 appendPQExpBufferStr(&buf, " ");
879 printACLColumn(&buf, "d.datacl");
880 if (verbose && pset.sversion >= 80200)
881 appendPQExpBuffer(&buf,
882 ",\n CASE WHEN pg_catalog.has_database_privilege(d.datname, 'CONNECT')\n"
883 " THEN pg_catalog.pg_size_pretty(pg_catalog.pg_database_size(d.datname))\n"
884 " ELSE 'No Access'\n"
885 " END as \"%s\"",
886 gettext_noop("Size"));
887 if (verbose && pset.sversion >= 80000)
888 appendPQExpBuffer(&buf,
889 ",\n t.spcname as \"%s\"",
890 gettext_noop("Tablespace"));
891 if (verbose && pset.sversion >= 80200)
892 appendPQExpBuffer(&buf,
893 ",\n pg_catalog.shobj_description(d.oid, 'pg_database') as \"%s\"",
894 gettext_noop("Description"));
895 appendPQExpBufferStr(&buf,
896 "\nFROM pg_catalog.pg_database d\n");
897 if (verbose && pset.sversion >= 80000)
898 appendPQExpBufferStr(&buf,
899 " JOIN pg_catalog.pg_tablespace t on d.dattablespace = t.oid\n");
900
901 if (pattern)
902 processSQLNamePattern(pset.db, &buf, pattern, false, false,
903 NULL, "d.datname", NULL, NULL);
904
905 appendPQExpBufferStr(&buf, "ORDER BY 1;");
906 res = PSQLexec(buf.data);
907 termPQExpBuffer(&buf);
908 if (!res)
909 return false;
910
911 myopt.nullPrint = NULL;
912 myopt.title = _("List of databases");
913 myopt.translate_header = true;
914
915 printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
916
917 PQclear(res);
918 return true;
919 }
920
921
922 /*
923 * List Tables' Grant/Revoke Permissions
924 * \z (now also \dp -- perhaps more mnemonic)
925 */
926 bool
permissionsList(const char * pattern)927 permissionsList(const char *pattern)
928 {
929 PQExpBufferData buf;
930 PGresult *res;
931 printQueryOpt myopt = pset.popt;
932 static const bool translate_columns[] = {false, false, true, false, false, false};
933
934 initPQExpBuffer(&buf);
935
936 /*
937 * we ignore indexes and toast tables since they have no meaningful rights
938 */
939 printfPQExpBuffer(&buf,
940 "SELECT n.nspname as \"%s\",\n"
941 " c.relname as \"%s\",\n"
942 " CASE c.relkind"
943 " WHEN " CppAsString2(RELKIND_RELATION) " THEN '%s'"
944 " WHEN " CppAsString2(RELKIND_VIEW) " THEN '%s'"
945 " WHEN " CppAsString2(RELKIND_MATVIEW) " THEN '%s'"
946 " WHEN " CppAsString2(RELKIND_SEQUENCE) " THEN '%s'"
947 " WHEN " CppAsString2(RELKIND_FOREIGN_TABLE) " THEN '%s'"
948 " WHEN " CppAsString2(RELKIND_PARTITIONED_TABLE) " THEN '%s'"
949 " END as \"%s\",\n"
950 " ",
951 gettext_noop("Schema"),
952 gettext_noop("Name"),
953 gettext_noop("table"),
954 gettext_noop("view"),
955 gettext_noop("materialized view"),
956 gettext_noop("sequence"),
957 gettext_noop("foreign table"),
958 gettext_noop("table"), /* partitioned table */
959 gettext_noop("Type"));
960
961 printACLColumn(&buf, "c.relacl");
962
963 if (pset.sversion >= 80400)
964 appendPQExpBuffer(&buf,
965 ",\n pg_catalog.array_to_string(ARRAY(\n"
966 " SELECT attname || E':\\n ' || pg_catalog.array_to_string(attacl, E'\\n ')\n"
967 " FROM pg_catalog.pg_attribute a\n"
968 " WHERE attrelid = c.oid AND NOT attisdropped AND attacl IS NOT NULL\n"
969 " ), E'\\n') AS \"%s\"",
970 gettext_noop("Column privileges"));
971
972 if (pset.sversion >= 90500 && pset.sversion < 100000)
973 appendPQExpBuffer(&buf,
974 ",\n pg_catalog.array_to_string(ARRAY(\n"
975 " SELECT polname\n"
976 " || CASE WHEN polcmd != '*' THEN\n"
977 " E' (' || polcmd || E'):'\n"
978 " ELSE E':'\n"
979 " END\n"
980 " || CASE WHEN polqual IS NOT NULL THEN\n"
981 " E'\\n (u): ' || pg_catalog.pg_get_expr(polqual, polrelid)\n"
982 " ELSE E''\n"
983 " END\n"
984 " || CASE WHEN polwithcheck IS NOT NULL THEN\n"
985 " E'\\n (c): ' || pg_catalog.pg_get_expr(polwithcheck, polrelid)\n"
986 " ELSE E''\n"
987 " END"
988 " || CASE WHEN polroles <> '{0}' THEN\n"
989 " E'\\n to: ' || pg_catalog.array_to_string(\n"
990 " ARRAY(\n"
991 " SELECT rolname\n"
992 " FROM pg_catalog.pg_roles\n"
993 " WHERE oid = ANY (polroles)\n"
994 " ORDER BY 1\n"
995 " ), E', ')\n"
996 " ELSE E''\n"
997 " END\n"
998 " FROM pg_catalog.pg_policy pol\n"
999 " WHERE polrelid = c.oid), E'\\n')\n"
1000 " AS \"%s\"",
1001 gettext_noop("Policies"));
1002
1003 if (pset.sversion >= 100000)
1004 appendPQExpBuffer(&buf,
1005 ",\n pg_catalog.array_to_string(ARRAY(\n"
1006 " SELECT polname\n"
1007 " || CASE WHEN NOT polpermissive THEN\n"
1008 " E' (RESTRICTIVE)'\n"
1009 " ELSE '' END\n"
1010 " || CASE WHEN polcmd != '*' THEN\n"
1011 " E' (' || polcmd || E'):'\n"
1012 " ELSE E':'\n"
1013 " END\n"
1014 " || CASE WHEN polqual IS NOT NULL THEN\n"
1015 " E'\\n (u): ' || pg_catalog.pg_get_expr(polqual, polrelid)\n"
1016 " ELSE E''\n"
1017 " END\n"
1018 " || CASE WHEN polwithcheck IS NOT NULL THEN\n"
1019 " E'\\n (c): ' || pg_catalog.pg_get_expr(polwithcheck, polrelid)\n"
1020 " ELSE E''\n"
1021 " END"
1022 " || CASE WHEN polroles <> '{0}' THEN\n"
1023 " E'\\n to: ' || pg_catalog.array_to_string(\n"
1024 " ARRAY(\n"
1025 " SELECT rolname\n"
1026 " FROM pg_catalog.pg_roles\n"
1027 " WHERE oid = ANY (polroles)\n"
1028 " ORDER BY 1\n"
1029 " ), E', ')\n"
1030 " ELSE E''\n"
1031 " END\n"
1032 " FROM pg_catalog.pg_policy pol\n"
1033 " WHERE polrelid = c.oid), E'\\n')\n"
1034 " AS \"%s\"",
1035 gettext_noop("Policies"));
1036
1037 appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_class c\n"
1038 " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
1039 "WHERE c.relkind IN ("
1040 CppAsString2(RELKIND_RELATION) ","
1041 CppAsString2(RELKIND_VIEW) ","
1042 CppAsString2(RELKIND_MATVIEW) ","
1043 CppAsString2(RELKIND_SEQUENCE) ","
1044 CppAsString2(RELKIND_FOREIGN_TABLE) ","
1045 CppAsString2(RELKIND_PARTITIONED_TABLE) ")\n");
1046
1047 /*
1048 * Unless a schema pattern is specified, we suppress system and temp
1049 * tables, since they normally aren't very interesting from a permissions
1050 * point of view. You can see 'em by explicit request though, eg with \z
1051 * pg_catalog.*
1052 */
1053 processSQLNamePattern(pset.db, &buf, pattern, true, false,
1054 "n.nspname", "c.relname", NULL,
1055 "n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid)");
1056
1057 appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
1058
1059 res = PSQLexec(buf.data);
1060 if (!res)
1061 {
1062 termPQExpBuffer(&buf);
1063 return false;
1064 }
1065
1066 myopt.nullPrint = NULL;
1067 printfPQExpBuffer(&buf, _("Access privileges"));
1068 myopt.title = buf.data;
1069 myopt.translate_header = true;
1070 myopt.translate_columns = translate_columns;
1071 myopt.n_translate_columns = lengthof(translate_columns);
1072
1073 printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
1074
1075 termPQExpBuffer(&buf);
1076 PQclear(res);
1077 return true;
1078 }
1079
1080
1081 /*
1082 * \ddp
1083 *
1084 * List Default ACLs. The pattern can match either schema or role name.
1085 */
1086 bool
listDefaultACLs(const char * pattern)1087 listDefaultACLs(const char *pattern)
1088 {
1089 PQExpBufferData buf;
1090 PGresult *res;
1091 printQueryOpt myopt = pset.popt;
1092 static const bool translate_columns[] = {false, false, true, false};
1093
1094 if (pset.sversion < 90000)
1095 {
1096 char sverbuf[32];
1097
1098 psql_error("The server (version %s) does not support altering default privileges.\n",
1099 formatPGVersionNumber(pset.sversion, false,
1100 sverbuf, sizeof(sverbuf)));
1101 return true;
1102 }
1103
1104 initPQExpBuffer(&buf);
1105
1106 printfPQExpBuffer(&buf,
1107 "SELECT pg_catalog.pg_get_userbyid(d.defaclrole) AS \"%s\",\n"
1108 " n.nspname AS \"%s\",\n"
1109 " CASE d.defaclobjtype WHEN '%c' THEN '%s' WHEN '%c' THEN '%s' WHEN '%c' THEN '%s' WHEN '%c' THEN '%s' WHEN '%c' THEN '%s' END AS \"%s\",\n"
1110 " ",
1111 gettext_noop("Owner"),
1112 gettext_noop("Schema"),
1113 DEFACLOBJ_RELATION,
1114 gettext_noop("table"),
1115 DEFACLOBJ_SEQUENCE,
1116 gettext_noop("sequence"),
1117 DEFACLOBJ_FUNCTION,
1118 gettext_noop("function"),
1119 DEFACLOBJ_TYPE,
1120 gettext_noop("type"),
1121 DEFACLOBJ_NAMESPACE,
1122 gettext_noop("schema"),
1123 gettext_noop("Type"));
1124
1125 printACLColumn(&buf, "d.defaclacl");
1126
1127 appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_default_acl d\n"
1128 " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.defaclnamespace\n");
1129
1130 processSQLNamePattern(pset.db, &buf, pattern, false, false,
1131 NULL,
1132 "n.nspname",
1133 "pg_catalog.pg_get_userbyid(d.defaclrole)",
1134 NULL);
1135
1136 appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 3;");
1137
1138 res = PSQLexec(buf.data);
1139 if (!res)
1140 {
1141 termPQExpBuffer(&buf);
1142 return false;
1143 }
1144
1145 myopt.nullPrint = NULL;
1146 printfPQExpBuffer(&buf, _("Default access privileges"));
1147 myopt.title = buf.data;
1148 myopt.translate_header = true;
1149 myopt.translate_columns = translate_columns;
1150 myopt.n_translate_columns = lengthof(translate_columns);
1151
1152 printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
1153
1154 termPQExpBuffer(&buf);
1155 PQclear(res);
1156 return true;
1157 }
1158
1159
1160 /*
1161 * Get object comments
1162 *
1163 * \dd [foo]
1164 *
1165 * Note: This command only lists comments for object types which do not have
1166 * their comments displayed by their own backslash commands. The following
1167 * types of objects will be displayed: constraint, operator class,
1168 * operator family, rule, and trigger.
1169 *
1170 */
1171 bool
objectDescription(const char * pattern,bool showSystem)1172 objectDescription(const char *pattern, bool showSystem)
1173 {
1174 PQExpBufferData buf;
1175 PGresult *res;
1176 printQueryOpt myopt = pset.popt;
1177 static const bool translate_columns[] = {false, false, true, false};
1178
1179 initPQExpBuffer(&buf);
1180
1181 appendPQExpBuffer(&buf,
1182 "SELECT DISTINCT tt.nspname AS \"%s\", tt.name AS \"%s\", tt.object AS \"%s\", d.description AS \"%s\"\n"
1183 "FROM (\n",
1184 gettext_noop("Schema"),
1185 gettext_noop("Name"),
1186 gettext_noop("Object"),
1187 gettext_noop("Description"));
1188
1189 /* Table constraint descriptions */
1190 appendPQExpBuffer(&buf,
1191 " SELECT pgc.oid as oid, pgc.tableoid AS tableoid,\n"
1192 " n.nspname as nspname,\n"
1193 " CAST(pgc.conname AS pg_catalog.text) as name,"
1194 " CAST('%s' AS pg_catalog.text) as object\n"
1195 " FROM pg_catalog.pg_constraint pgc\n"
1196 " JOIN pg_catalog.pg_class c "
1197 "ON c.oid = pgc.conrelid\n"
1198 " LEFT JOIN pg_catalog.pg_namespace n "
1199 " ON n.oid = c.relnamespace\n",
1200 gettext_noop("table constraint"));
1201
1202 if (!showSystem && !pattern)
1203 appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
1204 " AND n.nspname <> 'information_schema'\n");
1205
1206 processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern,
1207 false, "n.nspname", "pgc.conname", NULL,
1208 "pg_catalog.pg_table_is_visible(c.oid)");
1209
1210 /* Domain constraint descriptions */
1211 appendPQExpBuffer(&buf,
1212 "UNION ALL\n"
1213 " SELECT pgc.oid as oid, pgc.tableoid AS tableoid,\n"
1214 " n.nspname as nspname,\n"
1215 " CAST(pgc.conname AS pg_catalog.text) as name,"
1216 " CAST('%s' AS pg_catalog.text) as object\n"
1217 " FROM pg_catalog.pg_constraint pgc\n"
1218 " JOIN pg_catalog.pg_type t "
1219 "ON t.oid = pgc.contypid\n"
1220 " LEFT JOIN pg_catalog.pg_namespace n "
1221 " ON n.oid = t.typnamespace\n",
1222 gettext_noop("domain constraint"));
1223
1224 if (!showSystem && !pattern)
1225 appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
1226 " AND n.nspname <> 'information_schema'\n");
1227
1228 processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern,
1229 false, "n.nspname", "pgc.conname", NULL,
1230 "pg_catalog.pg_type_is_visible(t.oid)");
1231
1232
1233 /*
1234 * pg_opclass.opcmethod only available in 8.3+
1235 */
1236 if (pset.sversion >= 80300)
1237 {
1238 /* Operator class descriptions */
1239 appendPQExpBuffer(&buf,
1240 "UNION ALL\n"
1241 " SELECT o.oid as oid, o.tableoid as tableoid,\n"
1242 " n.nspname as nspname,\n"
1243 " CAST(o.opcname AS pg_catalog.text) as name,\n"
1244 " CAST('%s' AS pg_catalog.text) as object\n"
1245 " FROM pg_catalog.pg_opclass o\n"
1246 " JOIN pg_catalog.pg_am am ON "
1247 "o.opcmethod = am.oid\n"
1248 " JOIN pg_catalog.pg_namespace n ON "
1249 "n.oid = o.opcnamespace\n",
1250 gettext_noop("operator class"));
1251
1252 if (!showSystem && !pattern)
1253 appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
1254 " AND n.nspname <> 'information_schema'\n");
1255
1256 processSQLNamePattern(pset.db, &buf, pattern, true, false,
1257 "n.nspname", "o.opcname", NULL,
1258 "pg_catalog.pg_opclass_is_visible(o.oid)");
1259 }
1260
1261 /*
1262 * although operator family comments have been around since 8.3,
1263 * pg_opfamily_is_visible is only available in 9.2+
1264 */
1265 if (pset.sversion >= 90200)
1266 {
1267 /* Operator family descriptions */
1268 appendPQExpBuffer(&buf,
1269 "UNION ALL\n"
1270 " SELECT opf.oid as oid, opf.tableoid as tableoid,\n"
1271 " n.nspname as nspname,\n"
1272 " CAST(opf.opfname AS pg_catalog.text) AS name,\n"
1273 " CAST('%s' AS pg_catalog.text) as object\n"
1274 " FROM pg_catalog.pg_opfamily opf\n"
1275 " JOIN pg_catalog.pg_am am "
1276 "ON opf.opfmethod = am.oid\n"
1277 " JOIN pg_catalog.pg_namespace n "
1278 "ON opf.opfnamespace = n.oid\n",
1279 gettext_noop("operator family"));
1280
1281 if (!showSystem && !pattern)
1282 appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
1283 " AND n.nspname <> 'information_schema'\n");
1284
1285 processSQLNamePattern(pset.db, &buf, pattern, true, false,
1286 "n.nspname", "opf.opfname", NULL,
1287 "pg_catalog.pg_opfamily_is_visible(opf.oid)");
1288 }
1289
1290 /* Rule descriptions (ignore rules for views) */
1291 appendPQExpBuffer(&buf,
1292 "UNION ALL\n"
1293 " SELECT r.oid as oid, r.tableoid as tableoid,\n"
1294 " n.nspname as nspname,\n"
1295 " CAST(r.rulename AS pg_catalog.text) as name,"
1296 " CAST('%s' AS pg_catalog.text) as object\n"
1297 " FROM pg_catalog.pg_rewrite r\n"
1298 " JOIN pg_catalog.pg_class c ON c.oid = r.ev_class\n"
1299 " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
1300 " WHERE r.rulename != '_RETURN'\n",
1301 gettext_noop("rule"));
1302
1303 if (!showSystem && !pattern)
1304 appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
1305 " AND n.nspname <> 'information_schema'\n");
1306
1307 processSQLNamePattern(pset.db, &buf, pattern, true, false,
1308 "n.nspname", "r.rulename", NULL,
1309 "pg_catalog.pg_table_is_visible(c.oid)");
1310
1311 /* Trigger descriptions */
1312 appendPQExpBuffer(&buf,
1313 "UNION ALL\n"
1314 " SELECT t.oid as oid, t.tableoid as tableoid,\n"
1315 " n.nspname as nspname,\n"
1316 " CAST(t.tgname AS pg_catalog.text) as name,"
1317 " CAST('%s' AS pg_catalog.text) as object\n"
1318 " FROM pg_catalog.pg_trigger t\n"
1319 " JOIN pg_catalog.pg_class c ON c.oid = t.tgrelid\n"
1320 " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n",
1321 gettext_noop("trigger"));
1322
1323 if (!showSystem && !pattern)
1324 appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
1325 " AND n.nspname <> 'information_schema'\n");
1326
1327 processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern, false,
1328 "n.nspname", "t.tgname", NULL,
1329 "pg_catalog.pg_table_is_visible(c.oid)");
1330
1331 appendPQExpBufferStr(&buf,
1332 ") AS tt\n"
1333 " JOIN pg_catalog.pg_description d ON (tt.oid = d.objoid AND tt.tableoid = d.classoid AND d.objsubid = 0)\n");
1334
1335 appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 3;");
1336
1337 res = PSQLexec(buf.data);
1338 termPQExpBuffer(&buf);
1339 if (!res)
1340 return false;
1341
1342 myopt.nullPrint = NULL;
1343 myopt.title = _("Object descriptions");
1344 myopt.translate_header = true;
1345 myopt.translate_columns = translate_columns;
1346 myopt.n_translate_columns = lengthof(translate_columns);
1347
1348 printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
1349
1350 PQclear(res);
1351 return true;
1352 }
1353
1354
1355 /*
1356 * describeTableDetails (for \d)
1357 *
1358 * This routine finds the tables to be displayed, and calls
1359 * describeOneTableDetails for each one.
1360 *
1361 * verbose: if true, this is \d+
1362 */
1363 bool
describeTableDetails(const char * pattern,bool verbose,bool showSystem)1364 describeTableDetails(const char *pattern, bool verbose, bool showSystem)
1365 {
1366 PQExpBufferData buf;
1367 PGresult *res;
1368 int i;
1369
1370 initPQExpBuffer(&buf);
1371
1372 printfPQExpBuffer(&buf,
1373 "SELECT c.oid,\n"
1374 " n.nspname,\n"
1375 " c.relname\n"
1376 "FROM pg_catalog.pg_class c\n"
1377 " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n");
1378
1379 if (!showSystem && !pattern)
1380 appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
1381 " AND n.nspname <> 'information_schema'\n");
1382
1383 processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern, false,
1384 "n.nspname", "c.relname", NULL,
1385 "pg_catalog.pg_table_is_visible(c.oid)");
1386
1387 appendPQExpBufferStr(&buf, "ORDER BY 2, 3;");
1388
1389 res = PSQLexec(buf.data);
1390 termPQExpBuffer(&buf);
1391 if (!res)
1392 return false;
1393
1394 if (PQntuples(res) == 0)
1395 {
1396 if (!pset.quiet)
1397 {
1398 if (pattern)
1399 psql_error("Did not find any relation named \"%s\".\n",
1400 pattern);
1401 else
1402 psql_error("Did not find any relations.\n");
1403 }
1404 PQclear(res);
1405 return false;
1406 }
1407
1408 for (i = 0; i < PQntuples(res); i++)
1409 {
1410 const char *oid;
1411 const char *nspname;
1412 const char *relname;
1413
1414 oid = PQgetvalue(res, i, 0);
1415 nspname = PQgetvalue(res, i, 1);
1416 relname = PQgetvalue(res, i, 2);
1417
1418 if (!describeOneTableDetails(nspname, relname, oid, verbose))
1419 {
1420 PQclear(res);
1421 return false;
1422 }
1423 if (cancel_pressed)
1424 {
1425 PQclear(res);
1426 return false;
1427 }
1428 }
1429
1430 PQclear(res);
1431 return true;
1432 }
1433
1434 /*
1435 * describeOneTableDetails (for \d)
1436 *
1437 * Unfortunately, the information presented here is so complicated that it
1438 * cannot be done in a single query. So we have to assemble the printed table
1439 * by hand and pass it to the underlying printTable() function.
1440 */
1441 static bool
describeOneTableDetails(const char * schemaname,const char * relationname,const char * oid,bool verbose)1442 describeOneTableDetails(const char *schemaname,
1443 const char *relationname,
1444 const char *oid,
1445 bool verbose)
1446 {
1447 bool retval = false;
1448 PQExpBufferData buf;
1449 PGresult *res = NULL;
1450 printTableOpt myopt = pset.popt.topt;
1451 printTableContent cont;
1452 bool printTableInitialized = false;
1453 int i;
1454 char *view_def = NULL;
1455 char *headers[11];
1456 PQExpBufferData title;
1457 PQExpBufferData tmpbuf;
1458 int cols;
1459 int attname_col = -1, /* column indexes in "res" */
1460 atttype_col = -1,
1461 attrdef_col = -1,
1462 attnotnull_col = -1,
1463 attcoll_col = -1,
1464 attidentity_col = -1,
1465 isindexkey_col = -1,
1466 indexdef_col = -1,
1467 fdwopts_col = -1,
1468 attstorage_col = -1,
1469 attstattarget_col = -1,
1470 attdescr_col = -1;
1471 int numrows;
1472 struct
1473 {
1474 int16 checks;
1475 char relkind;
1476 bool hasindex;
1477 bool hasrules;
1478 bool hastriggers;
1479 bool rowsecurity;
1480 bool forcerowsecurity;
1481 bool hasoids;
1482 Oid tablespace;
1483 char *reloptions;
1484 char *reloftype;
1485 char relpersistence;
1486 char relreplident;
1487 } tableinfo;
1488 bool show_column_details = false;
1489
1490 myopt.default_footer = false;
1491 /* This output looks confusing in expanded mode. */
1492 myopt.expanded = false;
1493
1494 initPQExpBuffer(&buf);
1495 initPQExpBuffer(&title);
1496 initPQExpBuffer(&tmpbuf);
1497
1498 /* Get general table info */
1499 if (pset.sversion >= 90500)
1500 {
1501 printfPQExpBuffer(&buf,
1502 "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
1503 "c.relhastriggers, c.relrowsecurity, c.relforcerowsecurity, "
1504 "c.relhasoids, %s, c.reltablespace, "
1505 "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, "
1506 "c.relpersistence, c.relreplident\n"
1507 "FROM pg_catalog.pg_class c\n "
1508 "LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)\n"
1509 "WHERE c.oid = '%s';",
1510 (verbose ?
1511 "pg_catalog.array_to_string(c.reloptions || "
1512 "array(select 'toast.' || x from pg_catalog.unnest(tc.reloptions) x), ', ')\n"
1513 : "''"),
1514 oid);
1515 }
1516 else if (pset.sversion >= 90400)
1517 {
1518 printfPQExpBuffer(&buf,
1519 "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
1520 "c.relhastriggers, false, false, c.relhasoids, "
1521 "%s, c.reltablespace, "
1522 "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, "
1523 "c.relpersistence, c.relreplident\n"
1524 "FROM pg_catalog.pg_class c\n "
1525 "LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)\n"
1526 "WHERE c.oid = '%s';",
1527 (verbose ?
1528 "pg_catalog.array_to_string(c.reloptions || "
1529 "array(select 'toast.' || x from pg_catalog.unnest(tc.reloptions) x), ', ')\n"
1530 : "''"),
1531 oid);
1532 }
1533 else if (pset.sversion >= 90100)
1534 {
1535 printfPQExpBuffer(&buf,
1536 "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
1537 "c.relhastriggers, false, false, c.relhasoids, "
1538 "%s, c.reltablespace, "
1539 "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, "
1540 "c.relpersistence\n"
1541 "FROM pg_catalog.pg_class c\n "
1542 "LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)\n"
1543 "WHERE c.oid = '%s';",
1544 (verbose ?
1545 "pg_catalog.array_to_string(c.reloptions || "
1546 "array(select 'toast.' || x from pg_catalog.unnest(tc.reloptions) x), ', ')\n"
1547 : "''"),
1548 oid);
1549 }
1550 else if (pset.sversion >= 90000)
1551 {
1552 printfPQExpBuffer(&buf,
1553 "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
1554 "c.relhastriggers, false, false, c.relhasoids, "
1555 "%s, c.reltablespace, "
1556 "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END\n"
1557 "FROM pg_catalog.pg_class c\n "
1558 "LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)\n"
1559 "WHERE c.oid = '%s';",
1560 (verbose ?
1561 "pg_catalog.array_to_string(c.reloptions || "
1562 "array(select 'toast.' || x from pg_catalog.unnest(tc.reloptions) x), ', ')\n"
1563 : "''"),
1564 oid);
1565 }
1566 else if (pset.sversion >= 80400)
1567 {
1568 printfPQExpBuffer(&buf,
1569 "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
1570 "c.relhastriggers, false, false, c.relhasoids, "
1571 "%s, c.reltablespace\n"
1572 "FROM pg_catalog.pg_class c\n "
1573 "LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)\n"
1574 "WHERE c.oid = '%s';",
1575 (verbose ?
1576 "pg_catalog.array_to_string(c.reloptions || "
1577 "array(select 'toast.' || x from pg_catalog.unnest(tc.reloptions) x), ', ')\n"
1578 : "''"),
1579 oid);
1580 }
1581 else if (pset.sversion >= 80200)
1582 {
1583 printfPQExpBuffer(&buf,
1584 "SELECT relchecks, relkind, relhasindex, relhasrules, "
1585 "reltriggers <> 0, false, false, relhasoids, "
1586 "%s, reltablespace\n"
1587 "FROM pg_catalog.pg_class WHERE oid = '%s';",
1588 (verbose ?
1589 "pg_catalog.array_to_string(reloptions, E', ')" : "''"),
1590 oid);
1591 }
1592 else if (pset.sversion >= 80000)
1593 {
1594 printfPQExpBuffer(&buf,
1595 "SELECT relchecks, relkind, relhasindex, relhasrules, "
1596 "reltriggers <> 0, false, false, relhasoids, "
1597 "'', reltablespace\n"
1598 "FROM pg_catalog.pg_class WHERE oid = '%s';",
1599 oid);
1600 }
1601 else
1602 {
1603 printfPQExpBuffer(&buf,
1604 "SELECT relchecks, relkind, relhasindex, relhasrules, "
1605 "reltriggers <> 0, false, false, relhasoids, "
1606 "'', ''\n"
1607 "FROM pg_catalog.pg_class WHERE oid = '%s';",
1608 oid);
1609 }
1610
1611 res = PSQLexec(buf.data);
1612 if (!res)
1613 goto error_return;
1614
1615 /* Did we get anything? */
1616 if (PQntuples(res) == 0)
1617 {
1618 if (!pset.quiet)
1619 psql_error("Did not find any relation with OID %s.\n", oid);
1620 goto error_return;
1621 }
1622
1623 tableinfo.checks = atoi(PQgetvalue(res, 0, 0));
1624 tableinfo.relkind = *(PQgetvalue(res, 0, 1));
1625 tableinfo.hasindex = strcmp(PQgetvalue(res, 0, 2), "t") == 0;
1626 tableinfo.hasrules = strcmp(PQgetvalue(res, 0, 3), "t") == 0;
1627 tableinfo.hastriggers = strcmp(PQgetvalue(res, 0, 4), "t") == 0;
1628 tableinfo.rowsecurity = strcmp(PQgetvalue(res, 0, 5), "t") == 0;
1629 tableinfo.forcerowsecurity = strcmp(PQgetvalue(res, 0, 6), "t") == 0;
1630 tableinfo.hasoids = strcmp(PQgetvalue(res, 0, 7), "t") == 0;
1631 tableinfo.reloptions = (pset.sversion >= 80200) ?
1632 pg_strdup(PQgetvalue(res, 0, 8)) : NULL;
1633 tableinfo.tablespace = (pset.sversion >= 80000) ?
1634 atooid(PQgetvalue(res, 0, 9)) : 0;
1635 tableinfo.reloftype = (pset.sversion >= 90000 &&
1636 strcmp(PQgetvalue(res, 0, 10), "") != 0) ?
1637 pg_strdup(PQgetvalue(res, 0, 10)) : NULL;
1638 tableinfo.relpersistence = (pset.sversion >= 90100) ?
1639 *(PQgetvalue(res, 0, 11)) : 0;
1640 tableinfo.relreplident = (pset.sversion >= 90400) ?
1641 *(PQgetvalue(res, 0, 12)) : 'd';
1642 PQclear(res);
1643 res = NULL;
1644
1645 /*
1646 * If it's a sequence, deal with it here separately.
1647 */
1648 if (tableinfo.relkind == RELKIND_SEQUENCE)
1649 {
1650 PGresult *result = NULL;
1651 printQueryOpt myopt = pset.popt;
1652 char *footers[2] = {NULL, NULL};
1653
1654 if (pset.sversion >= 100000)
1655 {
1656 printfPQExpBuffer(&buf,
1657 "SELECT pg_catalog.format_type(seqtypid, NULL) AS \"%s\",\n"
1658 " seqstart AS \"%s\",\n"
1659 " seqmin AS \"%s\",\n"
1660 " seqmax AS \"%s\",\n"
1661 " seqincrement AS \"%s\",\n"
1662 " CASE WHEN seqcycle THEN '%s' ELSE '%s' END AS \"%s\",\n"
1663 " seqcache AS \"%s\"\n",
1664 gettext_noop("Type"),
1665 gettext_noop("Start"),
1666 gettext_noop("Minimum"),
1667 gettext_noop("Maximum"),
1668 gettext_noop("Increment"),
1669 gettext_noop("yes"),
1670 gettext_noop("no"),
1671 gettext_noop("Cycles?"),
1672 gettext_noop("Cache"));
1673 appendPQExpBuffer(&buf,
1674 "FROM pg_catalog.pg_sequence\n"
1675 "WHERE seqrelid = '%s';",
1676 oid);
1677 }
1678 else
1679 {
1680 printfPQExpBuffer(&buf,
1681 "SELECT 'bigint' AS \"%s\",\n"
1682 " start_value AS \"%s\",\n"
1683 " min_value AS \"%s\",\n"
1684 " max_value AS \"%s\",\n"
1685 " increment_by AS \"%s\",\n"
1686 " CASE WHEN is_cycled THEN '%s' ELSE '%s' END AS \"%s\",\n"
1687 " cache_value AS \"%s\"\n",
1688 gettext_noop("Type"),
1689 gettext_noop("Start"),
1690 gettext_noop("Minimum"),
1691 gettext_noop("Maximum"),
1692 gettext_noop("Increment"),
1693 gettext_noop("yes"),
1694 gettext_noop("no"),
1695 gettext_noop("Cycles?"),
1696 gettext_noop("Cache"));
1697 appendPQExpBuffer(&buf, "FROM %s", fmtId(schemaname));
1698 /* must be separate because fmtId isn't reentrant */
1699 appendPQExpBuffer(&buf, ".%s;", fmtId(relationname));
1700 }
1701
1702 res = PSQLexec(buf.data);
1703 if (!res)
1704 goto error_return;
1705
1706 /* Footer information about a sequence */
1707
1708 /* Get the column that owns this sequence */
1709 printfPQExpBuffer(&buf, "SELECT pg_catalog.quote_ident(nspname) || '.' ||"
1710 "\n pg_catalog.quote_ident(relname) || '.' ||"
1711 "\n pg_catalog.quote_ident(attname),"
1712 "\n d.deptype"
1713 "\nFROM pg_catalog.pg_class c"
1714 "\nINNER JOIN pg_catalog.pg_depend d ON c.oid=d.refobjid"
1715 "\nINNER JOIN pg_catalog.pg_namespace n ON n.oid=c.relnamespace"
1716 "\nINNER JOIN pg_catalog.pg_attribute a ON ("
1717 "\n a.attrelid=c.oid AND"
1718 "\n a.attnum=d.refobjsubid)"
1719 "\nWHERE d.classid='pg_catalog.pg_class'::pg_catalog.regclass"
1720 "\n AND d.refclassid='pg_catalog.pg_class'::pg_catalog.regclass"
1721 "\n AND d.objid='%s'"
1722 "\n AND d.deptype IN ('a', 'i')",
1723 oid);
1724
1725 result = PSQLexec(buf.data);
1726
1727 /*
1728 * If we get no rows back, don't show anything (obviously). We should
1729 * never get more than one row back, but if we do, just ignore it and
1730 * don't print anything.
1731 */
1732 if (!result)
1733 goto error_return;
1734 else if (PQntuples(result) == 1)
1735 {
1736 switch (PQgetvalue(result, 0, 1)[0])
1737 {
1738 case 'a':
1739 footers[0] = psprintf(_("Owned by: %s"),
1740 PQgetvalue(result, 0, 0));
1741 break;
1742 case 'i':
1743 footers[0] = psprintf(_("Sequence for identity column: %s"),
1744 PQgetvalue(result, 0, 0));
1745 break;
1746 }
1747 }
1748 PQclear(result);
1749
1750 printfPQExpBuffer(&title, _("Sequence \"%s.%s\""),
1751 schemaname, relationname);
1752
1753 myopt.footers = footers;
1754 myopt.topt.default_footer = false;
1755 myopt.title = title.data;
1756 myopt.translate_header = true;
1757
1758 printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
1759
1760 if (footers[0])
1761 free(footers[0]);
1762
1763 retval = true;
1764 goto error_return; /* not an error, just return early */
1765 }
1766
1767 /* Identify whether we should print collation, nullable, default vals */
1768 if (tableinfo.relkind == RELKIND_RELATION ||
1769 tableinfo.relkind == RELKIND_VIEW ||
1770 tableinfo.relkind == RELKIND_MATVIEW ||
1771 tableinfo.relkind == RELKIND_FOREIGN_TABLE ||
1772 tableinfo.relkind == RELKIND_COMPOSITE_TYPE ||
1773 tableinfo.relkind == RELKIND_PARTITIONED_TABLE)
1774 show_column_details = true;
1775
1776 /*
1777 * Get per-column info
1778 *
1779 * Since the set of query columns we need varies depending on relkind and
1780 * server version, we compute all the column numbers on-the-fly. Column
1781 * number variables for columns not fetched are left as -1; this avoids
1782 * duplicative test logic below.
1783 */
1784 cols = 0;
1785 printfPQExpBuffer(&buf, "SELECT a.attname");
1786 attname_col = cols++;
1787 appendPQExpBufferStr(&buf, ",\n pg_catalog.format_type(a.atttypid, a.atttypmod)");
1788 atttype_col = cols++;
1789
1790 if (show_column_details)
1791 {
1792 appendPQExpBufferStr(&buf,
1793 ",\n (SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid) for 128)"
1794 "\n FROM pg_catalog.pg_attrdef d"
1795 "\n WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef)"
1796 ",\n a.attnotnull");
1797 attrdef_col = cols++;
1798 attnotnull_col = cols++;
1799 if (pset.sversion >= 90100)
1800 appendPQExpBufferStr(&buf, ",\n (SELECT c.collname FROM pg_catalog.pg_collation c, pg_catalog.pg_type t\n"
1801 " WHERE c.oid = a.attcollation AND t.oid = a.atttypid AND a.attcollation <> t.typcollation) AS attcollation");
1802 else
1803 appendPQExpBufferStr(&buf, ",\n NULL AS attcollation");
1804 attcoll_col = cols++;
1805 if (pset.sversion >= 100000)
1806 appendPQExpBufferStr(&buf, ",\n a.attidentity");
1807 else
1808 appendPQExpBufferStr(&buf, ",\n ''::pg_catalog.char AS attidentity");
1809 attidentity_col = cols++;
1810 }
1811 if (tableinfo.relkind == RELKIND_INDEX ||
1812 tableinfo.relkind == RELKIND_PARTITIONED_INDEX)
1813 {
1814 if (pset.sversion >= 110000)
1815 {
1816 appendPQExpBuffer(&buf, ",\n CASE WHEN a.attnum <= (SELECT i.indnkeyatts FROM pg_catalog.pg_index i WHERE i.indexrelid = '%s') THEN '%s' ELSE '%s' END AS is_key",
1817 oid,
1818 gettext_noop("yes"),
1819 gettext_noop("no"));
1820 isindexkey_col = cols++;
1821 }
1822 appendPQExpBufferStr(&buf, ",\n pg_catalog.pg_get_indexdef(a.attrelid, a.attnum, TRUE) AS indexdef");
1823 indexdef_col = cols++;
1824 }
1825 /* FDW options for foreign table column, only for 9.2 or later */
1826 if (tableinfo.relkind == RELKIND_FOREIGN_TABLE && pset.sversion >= 90200)
1827 {
1828 appendPQExpBufferStr(&buf, ",\n CASE WHEN attfdwoptions IS NULL THEN '' ELSE "
1829 " '(' || pg_catalog.array_to_string(ARRAY(SELECT pg_catalog.quote_ident(option_name) || ' ' || pg_catalog.quote_literal(option_value) FROM "
1830 " pg_catalog.pg_options_to_table(attfdwoptions)), ', ') || ')' END AS attfdwoptions");
1831 fdwopts_col = cols++;
1832 }
1833 if (verbose)
1834 {
1835 appendPQExpBufferStr(&buf, ",\n a.attstorage");
1836 attstorage_col = cols++;
1837
1838 /* stats target, if relevant to relkind */
1839 if (tableinfo.relkind == RELKIND_RELATION ||
1840 tableinfo.relkind == RELKIND_INDEX ||
1841 tableinfo.relkind == RELKIND_PARTITIONED_INDEX ||
1842 tableinfo.relkind == RELKIND_MATVIEW ||
1843 tableinfo.relkind == RELKIND_FOREIGN_TABLE ||
1844 tableinfo.relkind == RELKIND_PARTITIONED_TABLE)
1845 {
1846 appendPQExpBufferStr(&buf, ",\n CASE WHEN a.attstattarget=-1 THEN NULL ELSE a.attstattarget END AS attstattarget");
1847 attstattarget_col = cols++;
1848 }
1849
1850 /*
1851 * In 9.0+, we have column comments for: relations, views, composite
1852 * types, and foreign tables (cf. CommentObject() in comment.c).
1853 */
1854 if (tableinfo.relkind == RELKIND_RELATION ||
1855 tableinfo.relkind == RELKIND_VIEW ||
1856 tableinfo.relkind == RELKIND_MATVIEW ||
1857 tableinfo.relkind == RELKIND_FOREIGN_TABLE ||
1858 tableinfo.relkind == RELKIND_COMPOSITE_TYPE ||
1859 tableinfo.relkind == RELKIND_PARTITIONED_TABLE)
1860 {
1861 appendPQExpBufferStr(&buf, ",\n pg_catalog.col_description(a.attrelid, a.attnum)");
1862 attdescr_col = cols++;
1863 }
1864 }
1865
1866 appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_attribute a");
1867 appendPQExpBuffer(&buf, "\nWHERE a.attrelid = '%s' AND a.attnum > 0 AND NOT a.attisdropped", oid);
1868 appendPQExpBufferStr(&buf, "\nORDER BY a.attnum;");
1869
1870 res = PSQLexec(buf.data);
1871 if (!res)
1872 goto error_return;
1873 numrows = PQntuples(res);
1874
1875 /* Make title */
1876 switch (tableinfo.relkind)
1877 {
1878 case RELKIND_RELATION:
1879 if (tableinfo.relpersistence == 'u')
1880 printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
1881 schemaname, relationname);
1882 else
1883 printfPQExpBuffer(&title, _("Table \"%s.%s\""),
1884 schemaname, relationname);
1885 break;
1886 case RELKIND_VIEW:
1887 printfPQExpBuffer(&title, _("View \"%s.%s\""),
1888 schemaname, relationname);
1889 break;
1890 case RELKIND_MATVIEW:
1891 if (tableinfo.relpersistence == 'u')
1892 printfPQExpBuffer(&title, _("Unlogged materialized view \"%s.%s\""),
1893 schemaname, relationname);
1894 else
1895 printfPQExpBuffer(&title, _("Materialized view \"%s.%s\""),
1896 schemaname, relationname);
1897 break;
1898 case RELKIND_INDEX:
1899 case RELKIND_PARTITIONED_INDEX:
1900 if (tableinfo.relpersistence == 'u')
1901 printfPQExpBuffer(&title, _("Unlogged index \"%s.%s\""),
1902 schemaname, relationname);
1903 else
1904 printfPQExpBuffer(&title, _("Index \"%s.%s\""),
1905 schemaname, relationname);
1906 break;
1907 case 's':
1908 /* not used as of 8.2, but keep it for backwards compatibility */
1909 printfPQExpBuffer(&title, _("Special relation \"%s.%s\""),
1910 schemaname, relationname);
1911 break;
1912 case RELKIND_TOASTVALUE:
1913 printfPQExpBuffer(&title, _("TOAST table \"%s.%s\""),
1914 schemaname, relationname);
1915 break;
1916 case RELKIND_COMPOSITE_TYPE:
1917 printfPQExpBuffer(&title, _("Composite type \"%s.%s\""),
1918 schemaname, relationname);
1919 break;
1920 case RELKIND_FOREIGN_TABLE:
1921 printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
1922 schemaname, relationname);
1923 break;
1924 case RELKIND_PARTITIONED_TABLE:
1925 if (tableinfo.relpersistence == 'u')
1926 printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
1927 schemaname, relationname);
1928 else
1929 printfPQExpBuffer(&title, _("Table \"%s.%s\""),
1930 schemaname, relationname);
1931 break;
1932 default:
1933 /* untranslated unknown relkind */
1934 printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
1935 tableinfo.relkind, schemaname, relationname);
1936 break;
1937 }
1938
1939 /* Fill headers[] with the names of the columns we will output */
1940 cols = 0;
1941 headers[cols++] = gettext_noop("Column");
1942 headers[cols++] = gettext_noop("Type");
1943 if (show_column_details)
1944 {
1945 headers[cols++] = gettext_noop("Collation");
1946 headers[cols++] = gettext_noop("Nullable");
1947 headers[cols++] = gettext_noop("Default");
1948 }
1949 if (isindexkey_col >= 0)
1950 headers[cols++] = gettext_noop("Key?");
1951 if (indexdef_col >= 0)
1952 headers[cols++] = gettext_noop("Definition");
1953 if (fdwopts_col >= 0)
1954 headers[cols++] = gettext_noop("FDW options");
1955 if (attstorage_col >= 0)
1956 headers[cols++] = gettext_noop("Storage");
1957 if (attstattarget_col >= 0)
1958 headers[cols++] = gettext_noop("Stats target");
1959 if (attdescr_col >= 0)
1960 headers[cols++] = gettext_noop("Description");
1961
1962 Assert(cols <= lengthof(headers));
1963
1964 printTableInit(&cont, &myopt, title.data, cols, numrows);
1965 printTableInitialized = true;
1966
1967 for (i = 0; i < cols; i++)
1968 printTableAddHeader(&cont, headers[i], true, 'l');
1969
1970 /* Generate table cells to be printed */
1971 for (i = 0; i < numrows; i++)
1972 {
1973 /* Column */
1974 printTableAddCell(&cont, PQgetvalue(res, i, attname_col), false, false);
1975
1976 /* Type */
1977 printTableAddCell(&cont, PQgetvalue(res, i, atttype_col), false, false);
1978
1979 /* Collation, Nullable, Default */
1980 if (show_column_details)
1981 {
1982 char *identity;
1983 char *default_str = "";
1984
1985 printTableAddCell(&cont, PQgetvalue(res, i, attcoll_col), false, false);
1986
1987 printTableAddCell(&cont,
1988 strcmp(PQgetvalue(res, i, attnotnull_col), "t") == 0 ? "not null" : "",
1989 false, false);
1990
1991 identity = PQgetvalue(res, i, attidentity_col);
1992
1993 if (!identity[0])
1994 /* (note: above we cut off the 'default' string at 128) */
1995 default_str = PQgetvalue(res, i, attrdef_col);
1996 else if (identity[0] == ATTRIBUTE_IDENTITY_ALWAYS)
1997 default_str = "generated always as identity";
1998 else if (identity[0] == ATTRIBUTE_IDENTITY_BY_DEFAULT)
1999 default_str = "generated by default as identity";
2000
2001 printTableAddCell(&cont, default_str, false, false);
2002 }
2003
2004 /* Info for index columns */
2005 if (isindexkey_col >= 0)
2006 printTableAddCell(&cont, PQgetvalue(res, i, isindexkey_col), true, false);
2007 if (indexdef_col >= 0)
2008 printTableAddCell(&cont, PQgetvalue(res, i, indexdef_col), false, false);
2009
2010 /* FDW options for foreign table columns */
2011 if (fdwopts_col >= 0)
2012 printTableAddCell(&cont, PQgetvalue(res, i, fdwopts_col), false, false);
2013
2014 /* Storage and Description */
2015 if (attstorage_col >= 0)
2016 {
2017 char *storage = PQgetvalue(res, i, attstorage_col);
2018
2019 /* these strings are literal in our syntax, so not translated. */
2020 printTableAddCell(&cont, (storage[0] == 'p' ? "plain" :
2021 (storage[0] == 'm' ? "main" :
2022 (storage[0] == 'x' ? "extended" :
2023 (storage[0] == 'e' ? "external" :
2024 "???")))),
2025 false, false);
2026 }
2027
2028 /* Statistics target, if the relkind supports this feature */
2029 if (attstattarget_col >= 0)
2030 printTableAddCell(&cont, PQgetvalue(res, i, attstattarget_col),
2031 false, false);
2032
2033 /* Column comments, if the relkind supports this feature */
2034 if (attdescr_col >= 0)
2035 printTableAddCell(&cont, PQgetvalue(res, i, attdescr_col),
2036 false, false);
2037 }
2038
2039 /* Make footers */
2040 if (pset.sversion >= 100000)
2041 {
2042 /* Get the partition information */
2043 PGresult *result;
2044 char *parent_name;
2045 char *partdef;
2046 char *partconstraintdef = NULL;
2047
2048 printfPQExpBuffer(&buf,
2049 "SELECT inhparent::pg_catalog.regclass,\n"
2050 " pg_catalog.pg_get_expr(c.relpartbound, inhrelid)");
2051 /* If verbose, also request the partition constraint definition */
2052 if (verbose)
2053 appendPQExpBuffer(&buf,
2054 ",\n pg_catalog.pg_get_partition_constraintdef(inhrelid)");
2055 appendPQExpBuffer(&buf,
2056 "\nFROM pg_catalog.pg_class c"
2057 " JOIN pg_catalog.pg_inherits i"
2058 " ON c.oid = inhrelid"
2059 "\nWHERE c.oid = '%s' AND c.relispartition;", oid);
2060 result = PSQLexec(buf.data);
2061 if (!result)
2062 goto error_return;
2063
2064 if (PQntuples(result) > 0)
2065 {
2066 parent_name = PQgetvalue(result, 0, 0);
2067 partdef = PQgetvalue(result, 0, 1);
2068
2069 if (PQnfields(result) == 3 && !PQgetisnull(result, 0, 2))
2070 partconstraintdef = PQgetvalue(result, 0, 2);
2071
2072 printfPQExpBuffer(&tmpbuf, _("Partition of: %s %s"), parent_name,
2073 partdef);
2074 printTableAddFooter(&cont, tmpbuf.data);
2075
2076 if (verbose)
2077 {
2078 /* If there isn't any constraint, show that explicitly */
2079 if (partconstraintdef == NULL || partconstraintdef[0] == '\0')
2080 printfPQExpBuffer(&tmpbuf, _("No partition constraint"));
2081 else
2082 printfPQExpBuffer(&tmpbuf, _("Partition constraint: %s"),
2083 partconstraintdef);
2084 printTableAddFooter(&cont, tmpbuf.data);
2085 }
2086 }
2087 PQclear(result);
2088 }
2089
2090 if (tableinfo.relkind == RELKIND_PARTITIONED_TABLE)
2091 {
2092 /* Get the partition key information */
2093 PGresult *result;
2094 char *partkeydef;
2095
2096 printfPQExpBuffer(&buf,
2097 "SELECT pg_catalog.pg_get_partkeydef('%s'::pg_catalog.oid);",
2098 oid);
2099 result = PSQLexec(buf.data);
2100 if (!result)
2101 goto error_return;
2102
2103 if (PQntuples(result) == 1)
2104 {
2105 partkeydef = PQgetvalue(result, 0, 0);
2106 printfPQExpBuffer(&tmpbuf, _("Partition key: %s"), partkeydef);
2107 printTableAddFooter(&cont, tmpbuf.data);
2108 }
2109 PQclear(result);
2110 }
2111
2112 if (tableinfo.relkind == RELKIND_INDEX ||
2113 tableinfo.relkind == RELKIND_PARTITIONED_INDEX)
2114 {
2115 /* Footer information about an index */
2116 PGresult *result;
2117
2118 printfPQExpBuffer(&buf,
2119 "SELECT i.indisunique, i.indisprimary, i.indisclustered, ");
2120 if (pset.sversion >= 80200)
2121 appendPQExpBufferStr(&buf, "i.indisvalid,\n");
2122 else
2123 appendPQExpBufferStr(&buf, "true AS indisvalid,\n");
2124 if (pset.sversion >= 90000)
2125 appendPQExpBufferStr(&buf,
2126 " (NOT i.indimmediate) AND "
2127 "EXISTS (SELECT 1 FROM pg_catalog.pg_constraint "
2128 "WHERE conrelid = i.indrelid AND "
2129 "conindid = i.indexrelid AND "
2130 "contype IN ('p','u','x') AND "
2131 "condeferrable) AS condeferrable,\n"
2132 " (NOT i.indimmediate) AND "
2133 "EXISTS (SELECT 1 FROM pg_catalog.pg_constraint "
2134 "WHERE conrelid = i.indrelid AND "
2135 "conindid = i.indexrelid AND "
2136 "contype IN ('p','u','x') AND "
2137 "condeferred) AS condeferred,\n");
2138 else
2139 appendPQExpBufferStr(&buf,
2140 " false AS condeferrable, false AS condeferred,\n");
2141
2142 if (pset.sversion >= 90400)
2143 appendPQExpBuffer(&buf, "i.indisreplident,\n");
2144 else
2145 appendPQExpBuffer(&buf, "false AS indisreplident,\n");
2146
2147 appendPQExpBuffer(&buf, " a.amname, c2.relname, "
2148 "pg_catalog.pg_get_expr(i.indpred, i.indrelid, true)\n"
2149 "FROM pg_catalog.pg_index i, pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_am a\n"
2150 "WHERE i.indexrelid = c.oid AND c.oid = '%s' AND c.relam = a.oid\n"
2151 "AND i.indrelid = c2.oid;",
2152 oid);
2153
2154 result = PSQLexec(buf.data);
2155 if (!result)
2156 goto error_return;
2157 else if (PQntuples(result) != 1)
2158 {
2159 PQclear(result);
2160 goto error_return;
2161 }
2162 else
2163 {
2164 char *indisunique = PQgetvalue(result, 0, 0);
2165 char *indisprimary = PQgetvalue(result, 0, 1);
2166 char *indisclustered = PQgetvalue(result, 0, 2);
2167 char *indisvalid = PQgetvalue(result, 0, 3);
2168 char *deferrable = PQgetvalue(result, 0, 4);
2169 char *deferred = PQgetvalue(result, 0, 5);
2170 char *indisreplident = PQgetvalue(result, 0, 6);
2171 char *indamname = PQgetvalue(result, 0, 7);
2172 char *indtable = PQgetvalue(result, 0, 8);
2173 char *indpred = PQgetvalue(result, 0, 9);
2174
2175 if (strcmp(indisprimary, "t") == 0)
2176 printfPQExpBuffer(&tmpbuf, _("primary key, "));
2177 else if (strcmp(indisunique, "t") == 0)
2178 printfPQExpBuffer(&tmpbuf, _("unique, "));
2179 else
2180 resetPQExpBuffer(&tmpbuf);
2181 appendPQExpBuffer(&tmpbuf, "%s, ", indamname);
2182
2183 /* we assume here that index and table are in same schema */
2184 appendPQExpBuffer(&tmpbuf, _("for table \"%s.%s\""),
2185 schemaname, indtable);
2186
2187 if (strlen(indpred))
2188 appendPQExpBuffer(&tmpbuf, _(", predicate (%s)"), indpred);
2189
2190 if (strcmp(indisclustered, "t") == 0)
2191 appendPQExpBufferStr(&tmpbuf, _(", clustered"));
2192
2193 if (strcmp(indisvalid, "t") != 0)
2194 appendPQExpBufferStr(&tmpbuf, _(", invalid"));
2195
2196 if (strcmp(deferrable, "t") == 0)
2197 appendPQExpBufferStr(&tmpbuf, _(", deferrable"));
2198
2199 if (strcmp(deferred, "t") == 0)
2200 appendPQExpBufferStr(&tmpbuf, _(", initially deferred"));
2201
2202 if (strcmp(indisreplident, "t") == 0)
2203 appendPQExpBuffer(&tmpbuf, _(", replica identity"));
2204
2205 printTableAddFooter(&cont, tmpbuf.data);
2206 add_tablespace_footer(&cont, tableinfo.relkind,
2207 tableinfo.tablespace, true);
2208 }
2209
2210 PQclear(result);
2211 }
2212 else if (tableinfo.relkind == RELKIND_RELATION ||
2213 tableinfo.relkind == RELKIND_MATVIEW ||
2214 tableinfo.relkind == RELKIND_FOREIGN_TABLE ||
2215 tableinfo.relkind == RELKIND_PARTITIONED_TABLE)
2216 {
2217 /* Footer information about a table */
2218 PGresult *result = NULL;
2219 int tuples = 0;
2220
2221 /* print indexes */
2222 if (tableinfo.hasindex)
2223 {
2224 printfPQExpBuffer(&buf,
2225 "SELECT c2.relname, i.indisprimary, i.indisunique, i.indisclustered, ");
2226 if (pset.sversion >= 80200)
2227 appendPQExpBufferStr(&buf, "i.indisvalid, ");
2228 else
2229 appendPQExpBufferStr(&buf, "true as indisvalid, ");
2230 appendPQExpBufferStr(&buf, "pg_catalog.pg_get_indexdef(i.indexrelid, 0, true),\n ");
2231 if (pset.sversion >= 90000)
2232 appendPQExpBufferStr(&buf,
2233 "pg_catalog.pg_get_constraintdef(con.oid, true), "
2234 "contype, condeferrable, condeferred");
2235 else
2236 appendPQExpBufferStr(&buf,
2237 "null AS constraintdef, null AS contype, "
2238 "false AS condeferrable, false AS condeferred");
2239 if (pset.sversion >= 90400)
2240 appendPQExpBufferStr(&buf, ", i.indisreplident");
2241 else
2242 appendPQExpBufferStr(&buf, ", false AS indisreplident");
2243 if (pset.sversion >= 80000)
2244 appendPQExpBufferStr(&buf, ", c2.reltablespace");
2245 appendPQExpBufferStr(&buf,
2246 "\nFROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i\n");
2247 if (pset.sversion >= 90000)
2248 appendPQExpBufferStr(&buf,
2249 " LEFT JOIN pg_catalog.pg_constraint con ON (conrelid = i.indrelid AND conindid = i.indexrelid AND contype IN ('p','u','x'))\n");
2250 appendPQExpBuffer(&buf,
2251 "WHERE c.oid = '%s' AND c.oid = i.indrelid AND i.indexrelid = c2.oid\n"
2252 "ORDER BY i.indisprimary DESC, i.indisunique DESC, c2.relname;",
2253 oid);
2254 result = PSQLexec(buf.data);
2255 if (!result)
2256 goto error_return;
2257 else
2258 tuples = PQntuples(result);
2259
2260 if (tuples > 0)
2261 {
2262 printTableAddFooter(&cont, _("Indexes:"));
2263 for (i = 0; i < tuples; i++)
2264 {
2265 /* untranslated index name */
2266 printfPQExpBuffer(&buf, " \"%s\"",
2267 PQgetvalue(result, i, 0));
2268
2269 /* If exclusion constraint, print the constraintdef */
2270 if (strcmp(PQgetvalue(result, i, 7), "x") == 0)
2271 {
2272 appendPQExpBuffer(&buf, " %s",
2273 PQgetvalue(result, i, 6));
2274 }
2275 else
2276 {
2277 const char *indexdef;
2278 const char *usingpos;
2279
2280 /* Label as primary key or unique (but not both) */
2281 if (strcmp(PQgetvalue(result, i, 1), "t") == 0)
2282 appendPQExpBufferStr(&buf, " PRIMARY KEY,");
2283 else if (strcmp(PQgetvalue(result, i, 2), "t") == 0)
2284 {
2285 if (strcmp(PQgetvalue(result, i, 7), "u") == 0)
2286 appendPQExpBufferStr(&buf, " UNIQUE CONSTRAINT,");
2287 else
2288 appendPQExpBufferStr(&buf, " UNIQUE,");
2289 }
2290
2291 /* Everything after "USING" is echoed verbatim */
2292 indexdef = PQgetvalue(result, i, 5);
2293 usingpos = strstr(indexdef, " USING ");
2294 if (usingpos)
2295 indexdef = usingpos + 7;
2296 appendPQExpBuffer(&buf, " %s", indexdef);
2297
2298 /* Need these for deferrable PK/UNIQUE indexes */
2299 if (strcmp(PQgetvalue(result, i, 8), "t") == 0)
2300 appendPQExpBufferStr(&buf, " DEFERRABLE");
2301
2302 if (strcmp(PQgetvalue(result, i, 9), "t") == 0)
2303 appendPQExpBufferStr(&buf, " INITIALLY DEFERRED");
2304 }
2305
2306 /* Add these for all cases */
2307 if (strcmp(PQgetvalue(result, i, 3), "t") == 0)
2308 appendPQExpBufferStr(&buf, " CLUSTER");
2309
2310 if (strcmp(PQgetvalue(result, i, 4), "t") != 0)
2311 appendPQExpBufferStr(&buf, " INVALID");
2312
2313 if (strcmp(PQgetvalue(result, i, 10), "t") == 0)
2314 appendPQExpBuffer(&buf, " REPLICA IDENTITY");
2315
2316 printTableAddFooter(&cont, buf.data);
2317
2318 /* Print tablespace of the index on the same line */
2319 if (pset.sversion >= 80000)
2320 add_tablespace_footer(&cont, RELKIND_INDEX,
2321 atooid(PQgetvalue(result, i, 11)),
2322 false);
2323 }
2324 }
2325 PQclear(result);
2326 }
2327
2328 /* print table (and column) check constraints */
2329 if (tableinfo.checks)
2330 {
2331 printfPQExpBuffer(&buf,
2332 "SELECT r.conname, "
2333 "pg_catalog.pg_get_constraintdef(r.oid, true)\n"
2334 "FROM pg_catalog.pg_constraint r\n"
2335 "WHERE r.conrelid = '%s' AND r.contype = 'c'\n"
2336 "ORDER BY 1;",
2337 oid);
2338 result = PSQLexec(buf.data);
2339 if (!result)
2340 goto error_return;
2341 else
2342 tuples = PQntuples(result);
2343
2344 if (tuples > 0)
2345 {
2346 printTableAddFooter(&cont, _("Check constraints:"));
2347 for (i = 0; i < tuples; i++)
2348 {
2349 /* untranslated constraint name and def */
2350 printfPQExpBuffer(&buf, " \"%s\" %s",
2351 PQgetvalue(result, i, 0),
2352 PQgetvalue(result, i, 1));
2353
2354 printTableAddFooter(&cont, buf.data);
2355 }
2356 }
2357 PQclear(result);
2358 }
2359
2360 /*
2361 * Print foreign-key constraints (there are none if no triggers,
2362 * except if the table is partitioned, in which case the triggers
2363 * appear in the partitions)
2364 */
2365 if (tableinfo.hastriggers ||
2366 tableinfo.relkind == RELKIND_PARTITIONED_TABLE)
2367 {
2368 printfPQExpBuffer(&buf,
2369 "SELECT conname,\n"
2370 " pg_catalog.pg_get_constraintdef(r.oid, true) as condef\n"
2371 "FROM pg_catalog.pg_constraint r\n"
2372 "WHERE r.conrelid = '%s' AND r.contype = 'f' ORDER BY 1;",
2373 oid);
2374 result = PSQLexec(buf.data);
2375 if (!result)
2376 goto error_return;
2377 else
2378 tuples = PQntuples(result);
2379
2380 if (tuples > 0)
2381 {
2382 printTableAddFooter(&cont, _("Foreign-key constraints:"));
2383 for (i = 0; i < tuples; i++)
2384 {
2385 /* untranslated constraint name and def */
2386 printfPQExpBuffer(&buf, " \"%s\" %s",
2387 PQgetvalue(result, i, 0),
2388 PQgetvalue(result, i, 1));
2389
2390 printTableAddFooter(&cont, buf.data);
2391 }
2392 }
2393 PQclear(result);
2394 }
2395
2396 /* print incoming foreign-key references (none if no triggers) */
2397 if (tableinfo.hastriggers)
2398 {
2399 printfPQExpBuffer(&buf,
2400 "SELECT conname, conrelid::pg_catalog.regclass,\n"
2401 " pg_catalog.pg_get_constraintdef(c.oid, true) as condef\n"
2402 "FROM pg_catalog.pg_constraint c\n"
2403 "WHERE c.confrelid = '%s' AND c.contype = 'f' ORDER BY 1;",
2404 oid);
2405 result = PSQLexec(buf.data);
2406 if (!result)
2407 goto error_return;
2408 else
2409 tuples = PQntuples(result);
2410
2411 if (tuples > 0)
2412 {
2413 printTableAddFooter(&cont, _("Referenced by:"));
2414 for (i = 0; i < tuples; i++)
2415 {
2416 printfPQExpBuffer(&buf, " TABLE \"%s\" CONSTRAINT \"%s\" %s",
2417 PQgetvalue(result, i, 1),
2418 PQgetvalue(result, i, 0),
2419 PQgetvalue(result, i, 2));
2420
2421 printTableAddFooter(&cont, buf.data);
2422 }
2423 }
2424 PQclear(result);
2425 }
2426
2427 /* print any row-level policies */
2428 if (pset.sversion >= 90500)
2429 {
2430 printfPQExpBuffer(&buf, "SELECT pol.polname,");
2431 if (pset.sversion >= 100000)
2432 appendPQExpBuffer(&buf,
2433 " pol.polpermissive,\n");
2434 else
2435 appendPQExpBuffer(&buf,
2436 " 't' as polpermissive,\n");
2437 appendPQExpBuffer(&buf,
2438 " 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,\n"
2439 " pg_catalog.pg_get_expr(pol.polqual, pol.polrelid),\n"
2440 " pg_catalog.pg_get_expr(pol.polwithcheck, pol.polrelid),\n"
2441 " CASE pol.polcmd\n"
2442 " WHEN 'r' THEN 'SELECT'\n"
2443 " WHEN 'a' THEN 'INSERT'\n"
2444 " WHEN 'w' THEN 'UPDATE'\n"
2445 " WHEN 'd' THEN 'DELETE'\n"
2446 " END AS cmd\n"
2447 "FROM pg_catalog.pg_policy pol\n"
2448 "WHERE pol.polrelid = '%s' ORDER BY 1;",
2449 oid);
2450
2451 result = PSQLexec(buf.data);
2452 if (!result)
2453 goto error_return;
2454 else
2455 tuples = PQntuples(result);
2456
2457 /*
2458 * Handle cases where RLS is enabled and there are policies, or
2459 * there aren't policies, or RLS isn't enabled but there are
2460 * policies
2461 */
2462 if (tableinfo.rowsecurity && !tableinfo.forcerowsecurity && tuples > 0)
2463 printTableAddFooter(&cont, _("Policies:"));
2464
2465 if (tableinfo.rowsecurity && tableinfo.forcerowsecurity && tuples > 0)
2466 printTableAddFooter(&cont, _("Policies (forced row security enabled):"));
2467
2468 if (tableinfo.rowsecurity && !tableinfo.forcerowsecurity && tuples == 0)
2469 printTableAddFooter(&cont, _("Policies (row security enabled): (none)"));
2470
2471 if (tableinfo.rowsecurity && tableinfo.forcerowsecurity && tuples == 0)
2472 printTableAddFooter(&cont, _("Policies (forced row security enabled): (none)"));
2473
2474 if (!tableinfo.rowsecurity && tuples > 0)
2475 printTableAddFooter(&cont, _("Policies (row security disabled):"));
2476
2477 /* Might be an empty set - that's ok */
2478 for (i = 0; i < tuples; i++)
2479 {
2480 printfPQExpBuffer(&buf, " POLICY \"%s\"",
2481 PQgetvalue(result, i, 0));
2482
2483 if (*(PQgetvalue(result, i, 1)) == 'f')
2484 appendPQExpBuffer(&buf, " AS RESTRICTIVE");
2485
2486 if (!PQgetisnull(result, i, 5))
2487 appendPQExpBuffer(&buf, " FOR %s",
2488 PQgetvalue(result, i, 5));
2489
2490 if (!PQgetisnull(result, i, 2))
2491 {
2492 appendPQExpBuffer(&buf, "\n TO %s",
2493 PQgetvalue(result, i, 2));
2494 }
2495
2496 if (!PQgetisnull(result, i, 3))
2497 appendPQExpBuffer(&buf, "\n USING (%s)",
2498 PQgetvalue(result, i, 3));
2499
2500 if (!PQgetisnull(result, i, 4))
2501 appendPQExpBuffer(&buf, "\n WITH CHECK (%s)",
2502 PQgetvalue(result, i, 4));
2503
2504 printTableAddFooter(&cont, buf.data);
2505
2506 }
2507 PQclear(result);
2508 }
2509
2510 /* print any extended statistics */
2511 if (pset.sversion >= 100000)
2512 {
2513 printfPQExpBuffer(&buf,
2514 "SELECT oid, "
2515 "stxrelid::pg_catalog.regclass, "
2516 "stxnamespace::pg_catalog.regnamespace AS nsp, "
2517 "stxname,\n"
2518 " (SELECT pg_catalog.string_agg(pg_catalog.quote_ident(attname),', ')\n"
2519 " FROM pg_catalog.unnest(stxkeys) s(attnum)\n"
2520 " JOIN pg_catalog.pg_attribute a ON (stxrelid = a.attrelid AND\n"
2521 " a.attnum = s.attnum AND NOT attisdropped)) AS columns,\n"
2522 " 'd' = any(stxkind) AS ndist_enabled,\n"
2523 " 'f' = any(stxkind) AS deps_enabled\n"
2524 "FROM pg_catalog.pg_statistic_ext stat "
2525 "WHERE stxrelid = '%s'\n"
2526 "ORDER BY 1;",
2527 oid);
2528
2529 result = PSQLexec(buf.data);
2530 if (!result)
2531 goto error_return;
2532 else
2533 tuples = PQntuples(result);
2534
2535 if (tuples > 0)
2536 {
2537 printTableAddFooter(&cont, _("Statistics objects:"));
2538
2539 for (i = 0; i < tuples; i++)
2540 {
2541 bool gotone = false;
2542
2543 printfPQExpBuffer(&buf, " ");
2544
2545 /* statistics object name (qualified with namespace) */
2546 appendPQExpBuffer(&buf, "\"%s\".\"%s\" (",
2547 PQgetvalue(result, i, 2),
2548 PQgetvalue(result, i, 3));
2549
2550 /* options */
2551 if (strcmp(PQgetvalue(result, i, 5), "t") == 0)
2552 {
2553 appendPQExpBufferStr(&buf, "ndistinct");
2554 gotone = true;
2555 }
2556
2557 if (strcmp(PQgetvalue(result, i, 6), "t") == 0)
2558 {
2559 appendPQExpBuffer(&buf, "%sdependencies", gotone ? ", " : "");
2560 }
2561
2562 appendPQExpBuffer(&buf, ") ON %s FROM %s",
2563 PQgetvalue(result, i, 4),
2564 PQgetvalue(result, i, 1));
2565
2566 printTableAddFooter(&cont, buf.data);
2567 }
2568 }
2569 PQclear(result);
2570 }
2571
2572 /* print rules */
2573 if (tableinfo.hasrules && tableinfo.relkind != RELKIND_MATVIEW)
2574 {
2575 if (pset.sversion >= 80300)
2576 {
2577 printfPQExpBuffer(&buf,
2578 "SELECT r.rulename, trim(trailing ';' from pg_catalog.pg_get_ruledef(r.oid, true)), "
2579 "ev_enabled\n"
2580 "FROM pg_catalog.pg_rewrite r\n"
2581 "WHERE r.ev_class = '%s' ORDER BY 1;",
2582 oid);
2583 }
2584 else
2585 {
2586 printfPQExpBuffer(&buf,
2587 "SELECT r.rulename, trim(trailing ';' from pg_catalog.pg_get_ruledef(r.oid, true)), "
2588 "'O' AS ev_enabled\n"
2589 "FROM pg_catalog.pg_rewrite r\n"
2590 "WHERE r.ev_class = '%s' ORDER BY 1;",
2591 oid);
2592 }
2593 result = PSQLexec(buf.data);
2594 if (!result)
2595 goto error_return;
2596 else
2597 tuples = PQntuples(result);
2598
2599 if (tuples > 0)
2600 {
2601 bool have_heading;
2602 int category;
2603
2604 for (category = 0; category < 4; category++)
2605 {
2606 have_heading = false;
2607
2608 for (i = 0; i < tuples; i++)
2609 {
2610 const char *ruledef;
2611 bool list_rule = false;
2612
2613 switch (category)
2614 {
2615 case 0:
2616 if (*PQgetvalue(result, i, 2) == 'O')
2617 list_rule = true;
2618 break;
2619 case 1:
2620 if (*PQgetvalue(result, i, 2) == 'D')
2621 list_rule = true;
2622 break;
2623 case 2:
2624 if (*PQgetvalue(result, i, 2) == 'A')
2625 list_rule = true;
2626 break;
2627 case 3:
2628 if (*PQgetvalue(result, i, 2) == 'R')
2629 list_rule = true;
2630 break;
2631 }
2632 if (!list_rule)
2633 continue;
2634
2635 if (!have_heading)
2636 {
2637 switch (category)
2638 {
2639 case 0:
2640 printfPQExpBuffer(&buf, _("Rules:"));
2641 break;
2642 case 1:
2643 printfPQExpBuffer(&buf, _("Disabled rules:"));
2644 break;
2645 case 2:
2646 printfPQExpBuffer(&buf, _("Rules firing always:"));
2647 break;
2648 case 3:
2649 printfPQExpBuffer(&buf, _("Rules firing on replica only:"));
2650 break;
2651 }
2652 printTableAddFooter(&cont, buf.data);
2653 have_heading = true;
2654 }
2655
2656 /* Everything after "CREATE RULE" is echoed verbatim */
2657 ruledef = PQgetvalue(result, i, 1);
2658 ruledef += 12;
2659 printfPQExpBuffer(&buf, " %s", ruledef);
2660 printTableAddFooter(&cont, buf.data);
2661 }
2662 }
2663 }
2664 PQclear(result);
2665 }
2666
2667 /* print any publications */
2668 if (pset.sversion >= 100000)
2669 {
2670 printfPQExpBuffer(&buf,
2671 "SELECT pubname\n"
2672 "FROM pg_catalog.pg_publication p\n"
2673 "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
2674 "WHERE pr.prrelid = '%s'\n"
2675 "UNION ALL\n"
2676 "SELECT pubname\n"
2677 "FROM pg_catalog.pg_publication p\n"
2678 "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
2679 "ORDER BY 1;",
2680 oid, oid);
2681
2682 result = PSQLexec(buf.data);
2683 if (!result)
2684 goto error_return;
2685 else
2686 tuples = PQntuples(result);
2687
2688 if (tuples > 0)
2689 printTableAddFooter(&cont, _("Publications:"));
2690
2691 /* Might be an empty set - that's ok */
2692 for (i = 0; i < tuples; i++)
2693 {
2694 printfPQExpBuffer(&buf, " \"%s\"",
2695 PQgetvalue(result, i, 0));
2696
2697 printTableAddFooter(&cont, buf.data);
2698 }
2699 PQclear(result);
2700 }
2701 }
2702
2703 /* Get view_def if table is a view or materialized view */
2704 if ((tableinfo.relkind == RELKIND_VIEW ||
2705 tableinfo.relkind == RELKIND_MATVIEW) && verbose)
2706 {
2707 PGresult *result;
2708
2709 printfPQExpBuffer(&buf,
2710 "SELECT pg_catalog.pg_get_viewdef('%s'::pg_catalog.oid, true);",
2711 oid);
2712 result = PSQLexec(buf.data);
2713 if (!result)
2714 goto error_return;
2715
2716 if (PQntuples(result) > 0)
2717 view_def = pg_strdup(PQgetvalue(result, 0, 0));
2718
2719 PQclear(result);
2720 }
2721
2722 if (view_def)
2723 {
2724 PGresult *result = NULL;
2725
2726 /* Footer information about a view */
2727 printTableAddFooter(&cont, _("View definition:"));
2728 printTableAddFooter(&cont, view_def);
2729
2730 /* print rules */
2731 if (tableinfo.hasrules)
2732 {
2733 printfPQExpBuffer(&buf,
2734 "SELECT r.rulename, trim(trailing ';' from pg_catalog.pg_get_ruledef(r.oid, true))\n"
2735 "FROM pg_catalog.pg_rewrite r\n"
2736 "WHERE r.ev_class = '%s' AND r.rulename != '_RETURN' ORDER BY 1;",
2737 oid);
2738 result = PSQLexec(buf.data);
2739 if (!result)
2740 goto error_return;
2741
2742 if (PQntuples(result) > 0)
2743 {
2744 printTableAddFooter(&cont, _("Rules:"));
2745 for (i = 0; i < PQntuples(result); i++)
2746 {
2747 const char *ruledef;
2748
2749 /* Everything after "CREATE RULE" is echoed verbatim */
2750 ruledef = PQgetvalue(result, i, 1);
2751 ruledef += 12;
2752
2753 printfPQExpBuffer(&buf, " %s", ruledef);
2754 printTableAddFooter(&cont, buf.data);
2755 }
2756 }
2757 PQclear(result);
2758 }
2759 }
2760
2761 /*
2762 * Print triggers next, if any (but only user-defined triggers). This
2763 * could apply to either a table or a view.
2764 */
2765 if (tableinfo.hastriggers)
2766 {
2767 PGresult *result;
2768 int tuples;
2769
2770 printfPQExpBuffer(&buf,
2771 "SELECT t.tgname, "
2772 "pg_catalog.pg_get_triggerdef(t.oid%s), "
2773 "t.tgenabled, %s\n"
2774 "FROM pg_catalog.pg_trigger t\n"
2775 "WHERE t.tgrelid = '%s' AND ",
2776 (pset.sversion >= 90000 ? ", true" : ""),
2777 (pset.sversion >= 90000 ? "t.tgisinternal" :
2778 pset.sversion >= 80300 ?
2779 "t.tgconstraint <> 0 AS tgisinternal" :
2780 "false AS tgisinternal"), oid);
2781 if (pset.sversion >= 110000)
2782 appendPQExpBuffer(&buf, "(NOT t.tgisinternal OR (t.tgisinternal AND t.tgenabled = 'D') \n"
2783 " OR EXISTS (SELECT 1 FROM pg_catalog.pg_depend WHERE objid = t.oid \n"
2784 " AND refclassid = 'pg_catalog.pg_trigger'::pg_catalog.regclass))");
2785 else if (pset.sversion >= 90000)
2786 /* display/warn about disabled internal triggers */
2787 appendPQExpBuffer(&buf, "(NOT t.tgisinternal OR (t.tgisinternal AND t.tgenabled = 'D'))");
2788 else if (pset.sversion >= 80300)
2789 appendPQExpBufferStr(&buf, "(t.tgconstraint = 0 OR (t.tgconstraint <> 0 AND t.tgenabled = 'D'))");
2790 else
2791 appendPQExpBufferStr(&buf,
2792 "(NOT tgisconstraint "
2793 " OR NOT EXISTS"
2794 " (SELECT 1 FROM pg_catalog.pg_depend d "
2795 " JOIN pg_catalog.pg_constraint c ON (d.refclassid = c.tableoid AND d.refobjid = c.oid) "
2796 " WHERE d.classid = t.tableoid AND d.objid = t.oid AND d.deptype = 'i' AND c.contype = 'f'))");
2797 appendPQExpBufferStr(&buf, "\nORDER BY 1;");
2798
2799 result = PSQLexec(buf.data);
2800 if (!result)
2801 goto error_return;
2802 else
2803 tuples = PQntuples(result);
2804
2805 if (tuples > 0)
2806 {
2807 bool have_heading;
2808 int category;
2809
2810 /*
2811 * split the output into 4 different categories. Enabled triggers,
2812 * disabled triggers and the two special ALWAYS and REPLICA
2813 * configurations.
2814 */
2815 for (category = 0; category <= 4; category++)
2816 {
2817 have_heading = false;
2818 for (i = 0; i < tuples; i++)
2819 {
2820 bool list_trigger;
2821 const char *tgdef;
2822 const char *usingpos;
2823 const char *tgenabled;
2824 const char *tgisinternal;
2825
2826 /*
2827 * Check if this trigger falls into the current category
2828 */
2829 tgenabled = PQgetvalue(result, i, 2);
2830 tgisinternal = PQgetvalue(result, i, 3);
2831 list_trigger = false;
2832 switch (category)
2833 {
2834 case 0:
2835 if (*tgenabled == 'O' || *tgenabled == 't')
2836 list_trigger = true;
2837 break;
2838 case 1:
2839 if ((*tgenabled == 'D' || *tgenabled == 'f') &&
2840 *tgisinternal == 'f')
2841 list_trigger = true;
2842 break;
2843 case 2:
2844 if ((*tgenabled == 'D' || *tgenabled == 'f') &&
2845 *tgisinternal == 't')
2846 list_trigger = true;
2847 break;
2848 case 3:
2849 if (*tgenabled == 'A')
2850 list_trigger = true;
2851 break;
2852 case 4:
2853 if (*tgenabled == 'R')
2854 list_trigger = true;
2855 break;
2856 }
2857 if (list_trigger == false)
2858 continue;
2859
2860 /* Print the category heading once */
2861 if (have_heading == false)
2862 {
2863 switch (category)
2864 {
2865 case 0:
2866 printfPQExpBuffer(&buf, _("Triggers:"));
2867 break;
2868 case 1:
2869 if (pset.sversion >= 80300)
2870 printfPQExpBuffer(&buf, _("Disabled user triggers:"));
2871 else
2872 printfPQExpBuffer(&buf, _("Disabled triggers:"));
2873 break;
2874 case 2:
2875 printfPQExpBuffer(&buf, _("Disabled internal triggers:"));
2876 break;
2877 case 3:
2878 printfPQExpBuffer(&buf, _("Triggers firing always:"));
2879 break;
2880 case 4:
2881 printfPQExpBuffer(&buf, _("Triggers firing on replica only:"));
2882 break;
2883
2884 }
2885 printTableAddFooter(&cont, buf.data);
2886 have_heading = true;
2887 }
2888
2889 /* Everything after "TRIGGER" is echoed verbatim */
2890 tgdef = PQgetvalue(result, i, 1);
2891 usingpos = strstr(tgdef, " TRIGGER ");
2892 if (usingpos)
2893 tgdef = usingpos + 9;
2894
2895 printfPQExpBuffer(&buf, " %s", tgdef);
2896 printTableAddFooter(&cont, buf.data);
2897 }
2898 }
2899 }
2900 PQclear(result);
2901 }
2902
2903 /*
2904 * Finish printing the footer information about a table.
2905 */
2906 if (tableinfo.relkind == RELKIND_RELATION ||
2907 tableinfo.relkind == RELKIND_MATVIEW ||
2908 tableinfo.relkind == RELKIND_FOREIGN_TABLE ||
2909 tableinfo.relkind == RELKIND_PARTITIONED_TABLE)
2910 {
2911 PGresult *result;
2912 int tuples;
2913
2914 /* print foreign server name */
2915 if (tableinfo.relkind == RELKIND_FOREIGN_TABLE)
2916 {
2917 char *ftoptions;
2918
2919 /* Footer information about foreign table */
2920 printfPQExpBuffer(&buf,
2921 "SELECT s.srvname,\n"
2922 " pg_catalog.array_to_string(ARRAY(\n"
2923 " SELECT pg_catalog.quote_ident(option_name)"
2924 " || ' ' || pg_catalog.quote_literal(option_value)\n"
2925 " FROM pg_catalog.pg_options_to_table(ftoptions)), ', ')\n"
2926 "FROM pg_catalog.pg_foreign_table f,\n"
2927 " pg_catalog.pg_foreign_server s\n"
2928 "WHERE f.ftrelid = '%s' AND s.oid = f.ftserver;",
2929 oid);
2930 result = PSQLexec(buf.data);
2931 if (!result)
2932 goto error_return;
2933 else if (PQntuples(result) != 1)
2934 {
2935 PQclear(result);
2936 goto error_return;
2937 }
2938
2939 /* Print server name */
2940 printfPQExpBuffer(&buf, _("Server: %s"),
2941 PQgetvalue(result, 0, 0));
2942 printTableAddFooter(&cont, buf.data);
2943
2944 /* Print per-table FDW options, if any */
2945 ftoptions = PQgetvalue(result, 0, 1);
2946 if (ftoptions && ftoptions[0] != '\0')
2947 {
2948 printfPQExpBuffer(&buf, _("FDW options: (%s)"), ftoptions);
2949 printTableAddFooter(&cont, buf.data);
2950 }
2951 PQclear(result);
2952 }
2953
2954 /* print inherited tables (exclude, if parent is a partitioned table) */
2955 printfPQExpBuffer(&buf,
2956 "SELECT c.oid::pg_catalog.regclass"
2957 " FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
2958 " WHERE c.oid=i.inhparent AND i.inhrelid = '%s'"
2959 " AND c.relkind != " CppAsString2(RELKIND_PARTITIONED_TABLE)
2960 " ORDER BY inhseqno;", oid);
2961
2962 result = PSQLexec(buf.data);
2963 if (!result)
2964 goto error_return;
2965 else
2966 {
2967 const char *s = _("Inherits");
2968 int sw = pg_wcswidth(s, strlen(s), pset.encoding);
2969
2970 tuples = PQntuples(result);
2971
2972 for (i = 0; i < tuples; i++)
2973 {
2974 if (i == 0)
2975 printfPQExpBuffer(&buf, "%s: %s",
2976 s, PQgetvalue(result, i, 0));
2977 else
2978 printfPQExpBuffer(&buf, "%*s %s",
2979 sw, "", PQgetvalue(result, i, 0));
2980 if (i < tuples - 1)
2981 appendPQExpBufferChar(&buf, ',');
2982
2983 printTableAddFooter(&cont, buf.data);
2984 }
2985
2986 PQclear(result);
2987 }
2988
2989 /* print child tables (with additional info if partitions) */
2990 if (pset.sversion >= 100000)
2991 printfPQExpBuffer(&buf,
2992 "SELECT c.oid::pg_catalog.regclass,"
2993 " pg_catalog.pg_get_expr(c.relpartbound, c.oid),"
2994 " c.relkind"
2995 " FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
2996 " WHERE c.oid=i.inhrelid AND i.inhparent = '%s'"
2997 " ORDER BY pg_catalog.pg_get_expr(c.relpartbound, c.oid) = 'DEFAULT',"
2998 " c.oid::pg_catalog.regclass::pg_catalog.text;", oid);
2999 else if (pset.sversion >= 80300)
3000 printfPQExpBuffer(&buf,
3001 "SELECT c.oid::pg_catalog.regclass"
3002 " FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
3003 " WHERE c.oid=i.inhrelid AND i.inhparent = '%s'"
3004 " ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid);
3005 else
3006 printfPQExpBuffer(&buf,
3007 "SELECT c.oid::pg_catalog.regclass"
3008 " FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
3009 " WHERE c.oid=i.inhrelid AND i.inhparent = '%s'"
3010 " ORDER BY c.relname;", oid);
3011
3012 result = PSQLexec(buf.data);
3013 if (!result)
3014 goto error_return;
3015 else
3016 tuples = PQntuples(result);
3017
3018 /*
3019 * For a partitioned table with no partitions, always print the number
3020 * of partitions as zero, even when verbose output is expected.
3021 * Otherwise, we will not print "Partitions" section for a partitioned
3022 * table without any partitions.
3023 */
3024 if (tableinfo.relkind == RELKIND_PARTITIONED_TABLE && tuples == 0)
3025 {
3026 printfPQExpBuffer(&buf, _("Number of partitions: %d"), tuples);
3027 printTableAddFooter(&cont, buf.data);
3028 }
3029 else if (!verbose)
3030 {
3031 /* print the number of child tables, if any */
3032 if (tuples > 0)
3033 {
3034 if (tableinfo.relkind != RELKIND_PARTITIONED_TABLE)
3035 printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
3036 else
3037 printfPQExpBuffer(&buf, _("Number of partitions: %d (Use \\d+ to list them.)"), tuples);
3038 printTableAddFooter(&cont, buf.data);
3039 }
3040 }
3041 else
3042 {
3043 /* display the list of child tables */
3044 const char *ct = (tableinfo.relkind != RELKIND_PARTITIONED_TABLE) ?
3045 _("Child tables") : _("Partitions");
3046 int ctw = pg_wcswidth(ct, strlen(ct), pset.encoding);
3047
3048 for (i = 0; i < tuples; i++)
3049 {
3050 if (tableinfo.relkind != RELKIND_PARTITIONED_TABLE)
3051 {
3052 if (i == 0)
3053 printfPQExpBuffer(&buf, "%s: %s",
3054 ct, PQgetvalue(result, i, 0));
3055 else
3056 printfPQExpBuffer(&buf, "%*s %s",
3057 ctw, "", PQgetvalue(result, i, 0));
3058 }
3059 else
3060 {
3061 char *partitioned_note;
3062
3063 if (*PQgetvalue(result, i, 2) == RELKIND_PARTITIONED_TABLE)
3064 partitioned_note = ", PARTITIONED";
3065 else
3066 partitioned_note = "";
3067
3068 if (i == 0)
3069 printfPQExpBuffer(&buf, "%s: %s %s%s",
3070 ct, PQgetvalue(result, i, 0), PQgetvalue(result, i, 1),
3071 partitioned_note);
3072 else
3073 printfPQExpBuffer(&buf, "%*s %s %s%s",
3074 ctw, "", PQgetvalue(result, i, 0), PQgetvalue(result, i, 1),
3075 partitioned_note);
3076 }
3077 if (i < tuples - 1)
3078 appendPQExpBufferChar(&buf, ',');
3079
3080 printTableAddFooter(&cont, buf.data);
3081 }
3082 }
3083 PQclear(result);
3084
3085 /* Table type */
3086 if (tableinfo.reloftype)
3087 {
3088 printfPQExpBuffer(&buf, _("Typed table of type: %s"), tableinfo.reloftype);
3089 printTableAddFooter(&cont, buf.data);
3090 }
3091
3092 if (verbose &&
3093 (tableinfo.relkind == RELKIND_RELATION ||
3094 tableinfo.relkind == RELKIND_MATVIEW) &&
3095
3096 /*
3097 * No need to display default values; we already display a REPLICA
3098 * IDENTITY marker on indexes.
3099 */
3100 tableinfo.relreplident != 'i' &&
3101 ((strcmp(schemaname, "pg_catalog") != 0 && tableinfo.relreplident != 'd') ||
3102 (strcmp(schemaname, "pg_catalog") == 0 && tableinfo.relreplident != 'n')))
3103 {
3104 const char *s = _("Replica Identity");
3105
3106 printfPQExpBuffer(&buf, "%s: %s",
3107 s,
3108 tableinfo.relreplident == 'f' ? "FULL" :
3109 tableinfo.relreplident == 'n' ? "NOTHING" :
3110 "???");
3111
3112 printTableAddFooter(&cont, buf.data);
3113 }
3114
3115 /* OIDs, if verbose and not a materialized view */
3116 if (verbose && tableinfo.relkind != RELKIND_MATVIEW && tableinfo.hasoids)
3117 printTableAddFooter(&cont, _("Has OIDs: yes"));
3118
3119 /* Tablespace info */
3120 add_tablespace_footer(&cont, tableinfo.relkind, tableinfo.tablespace,
3121 true);
3122 }
3123
3124 /* reloptions, if verbose */
3125 if (verbose &&
3126 tableinfo.reloptions && tableinfo.reloptions[0] != '\0')
3127 {
3128 const char *t = _("Options");
3129
3130 printfPQExpBuffer(&buf, "%s: %s", t, tableinfo.reloptions);
3131 printTableAddFooter(&cont, buf.data);
3132 }
3133
3134 printTable(&cont, pset.queryFout, false, pset.logfile);
3135
3136 retval = true;
3137
3138 error_return:
3139
3140 /* clean up */
3141 if (printTableInitialized)
3142 printTableCleanup(&cont);
3143 termPQExpBuffer(&buf);
3144 termPQExpBuffer(&title);
3145 termPQExpBuffer(&tmpbuf);
3146
3147 if (view_def)
3148 free(view_def);
3149
3150 if (res)
3151 PQclear(res);
3152
3153 return retval;
3154 }
3155
3156 /*
3157 * Add a tablespace description to a footer. If 'newline' is true, it is added
3158 * in a new line; otherwise it's appended to the current value of the last
3159 * footer.
3160 */
3161 static void
add_tablespace_footer(printTableContent * const cont,char relkind,Oid tablespace,const bool newline)3162 add_tablespace_footer(printTableContent *const cont, char relkind,
3163 Oid tablespace, const bool newline)
3164 {
3165 /* relkinds for which we support tablespaces */
3166 if (relkind == RELKIND_RELATION ||
3167 relkind == RELKIND_MATVIEW ||
3168 relkind == RELKIND_INDEX ||
3169 relkind == RELKIND_PARTITIONED_TABLE)
3170 {
3171 /*
3172 * We ignore the database default tablespace so that users not using
3173 * tablespaces don't need to know about them. This case also covers
3174 * pre-8.0 servers, for which tablespace will always be 0.
3175 */
3176 if (tablespace != 0)
3177 {
3178 PGresult *result = NULL;
3179 PQExpBufferData buf;
3180
3181 initPQExpBuffer(&buf);
3182 printfPQExpBuffer(&buf,
3183 "SELECT spcname FROM pg_catalog.pg_tablespace\n"
3184 "WHERE oid = '%u';", tablespace);
3185 result = PSQLexec(buf.data);
3186 if (!result)
3187 {
3188 termPQExpBuffer(&buf);
3189 return;
3190 }
3191 /* Should always be the case, but.... */
3192 if (PQntuples(result) > 0)
3193 {
3194 if (newline)
3195 {
3196 /* Add the tablespace as a new footer */
3197 printfPQExpBuffer(&buf, _("Tablespace: \"%s\""),
3198 PQgetvalue(result, 0, 0));
3199 printTableAddFooter(cont, buf.data);
3200 }
3201 else
3202 {
3203 /* Append the tablespace to the latest footer */
3204 printfPQExpBuffer(&buf, "%s", cont->footer->data);
3205
3206 /*-------
3207 translator: before this string there's an index description like
3208 '"foo_pkey" PRIMARY KEY, btree (a)' */
3209 appendPQExpBuffer(&buf, _(", tablespace \"%s\""),
3210 PQgetvalue(result, 0, 0));
3211 printTableSetFooter(cont, buf.data);
3212 }
3213 }
3214 PQclear(result);
3215 termPQExpBuffer(&buf);
3216 }
3217 }
3218 }
3219
3220 /*
3221 * \du or \dg
3222 *
3223 * Describes roles. Any schema portion of the pattern is ignored.
3224 */
3225 bool
describeRoles(const char * pattern,bool verbose,bool showSystem)3226 describeRoles(const char *pattern, bool verbose, bool showSystem)
3227 {
3228 PQExpBufferData buf;
3229 PGresult *res;
3230 printTableContent cont;
3231 printTableOpt myopt = pset.popt.topt;
3232 int ncols = 3;
3233 int nrows = 0;
3234 int i;
3235 int conns;
3236 const char align = 'l';
3237 char **attr;
3238
3239 myopt.default_footer = false;
3240
3241 initPQExpBuffer(&buf);
3242
3243 if (pset.sversion >= 80100)
3244 {
3245 printfPQExpBuffer(&buf,
3246 "SELECT r.rolname, r.rolsuper, r.rolinherit,\n"
3247 " r.rolcreaterole, r.rolcreatedb, r.rolcanlogin,\n"
3248 " r.rolconnlimit, r.rolvaliduntil,\n"
3249 " ARRAY(SELECT b.rolname\n"
3250 " FROM pg_catalog.pg_auth_members m\n"
3251 " JOIN pg_catalog.pg_roles b ON (m.roleid = b.oid)\n"
3252 " WHERE m.member = r.oid) as memberof");
3253
3254 if (verbose && pset.sversion >= 80200)
3255 {
3256 appendPQExpBufferStr(&buf, "\n, pg_catalog.shobj_description(r.oid, 'pg_authid') AS description");
3257 ncols++;
3258 }
3259 if (pset.sversion >= 90100)
3260 {
3261 appendPQExpBufferStr(&buf, "\n, r.rolreplication");
3262 }
3263
3264 if (pset.sversion >= 90500)
3265 {
3266 appendPQExpBufferStr(&buf, "\n, r.rolbypassrls");
3267 }
3268
3269 appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_roles r\n");
3270
3271 if (!showSystem && !pattern)
3272 appendPQExpBufferStr(&buf, "WHERE r.rolname !~ '^pg_'\n");
3273
3274 processSQLNamePattern(pset.db, &buf, pattern, false, false,
3275 NULL, "r.rolname", NULL, NULL);
3276 }
3277 else
3278 {
3279 printfPQExpBuffer(&buf,
3280 "SELECT u.usename AS rolname,\n"
3281 " u.usesuper AS rolsuper,\n"
3282 " true AS rolinherit, false AS rolcreaterole,\n"
3283 " u.usecreatedb AS rolcreatedb, true AS rolcanlogin,\n"
3284 " -1 AS rolconnlimit,"
3285 " u.valuntil as rolvaliduntil,\n"
3286 " ARRAY(SELECT g.groname FROM pg_catalog.pg_group g WHERE u.usesysid = ANY(g.grolist)) as memberof"
3287 "\nFROM pg_catalog.pg_user u\n");
3288
3289 processSQLNamePattern(pset.db, &buf, pattern, false, false,
3290 NULL, "u.usename", NULL, NULL);
3291 }
3292
3293 appendPQExpBufferStr(&buf, "ORDER BY 1;");
3294
3295 res = PSQLexec(buf.data);
3296 if (!res)
3297 return false;
3298
3299 nrows = PQntuples(res);
3300 attr = pg_malloc0((nrows + 1) * sizeof(*attr));
3301
3302 printTableInit(&cont, &myopt, _("List of roles"), ncols, nrows);
3303
3304 printTableAddHeader(&cont, gettext_noop("Role name"), true, align);
3305 printTableAddHeader(&cont, gettext_noop("Attributes"), true, align);
3306 printTableAddHeader(&cont, gettext_noop("Member of"), true, align);
3307
3308 if (verbose && pset.sversion >= 80200)
3309 printTableAddHeader(&cont, gettext_noop("Description"), true, align);
3310
3311 for (i = 0; i < nrows; i++)
3312 {
3313 printTableAddCell(&cont, PQgetvalue(res, i, 0), false, false);
3314
3315 resetPQExpBuffer(&buf);
3316 if (strcmp(PQgetvalue(res, i, 1), "t") == 0)
3317 add_role_attribute(&buf, _("Superuser"));
3318
3319 if (strcmp(PQgetvalue(res, i, 2), "t") != 0)
3320 add_role_attribute(&buf, _("No inheritance"));
3321
3322 if (strcmp(PQgetvalue(res, i, 3), "t") == 0)
3323 add_role_attribute(&buf, _("Create role"));
3324
3325 if (strcmp(PQgetvalue(res, i, 4), "t") == 0)
3326 add_role_attribute(&buf, _("Create DB"));
3327
3328 if (strcmp(PQgetvalue(res, i, 5), "t") != 0)
3329 add_role_attribute(&buf, _("Cannot login"));
3330
3331 if (pset.sversion >= 90100)
3332 if (strcmp(PQgetvalue(res, i, (verbose ? 10 : 9)), "t") == 0)
3333 add_role_attribute(&buf, _("Replication"));
3334
3335 if (pset.sversion >= 90500)
3336 if (strcmp(PQgetvalue(res, i, (verbose ? 11 : 10)), "t") == 0)
3337 add_role_attribute(&buf, _("Bypass RLS"));
3338
3339 conns = atoi(PQgetvalue(res, i, 6));
3340 if (conns >= 0)
3341 {
3342 if (buf.len > 0)
3343 appendPQExpBufferChar(&buf, '\n');
3344
3345 if (conns == 0)
3346 appendPQExpBufferStr(&buf, _("No connections"));
3347 else
3348 appendPQExpBuffer(&buf, ngettext("%d connection",
3349 "%d connections",
3350 conns),
3351 conns);
3352 }
3353
3354 if (strcmp(PQgetvalue(res, i, 7), "") != 0)
3355 {
3356 if (buf.len > 0)
3357 appendPQExpBufferChar(&buf, '\n');
3358 appendPQExpBufferStr(&buf, _("Password valid until "));
3359 appendPQExpBufferStr(&buf, PQgetvalue(res, i, 7));
3360 }
3361
3362 attr[i] = pg_strdup(buf.data);
3363
3364 printTableAddCell(&cont, attr[i], false, false);
3365
3366 printTableAddCell(&cont, PQgetvalue(res, i, 8), false, false);
3367
3368 if (verbose && pset.sversion >= 80200)
3369 printTableAddCell(&cont, PQgetvalue(res, i, 9), false, false);
3370 }
3371 termPQExpBuffer(&buf);
3372
3373 printTable(&cont, pset.queryFout, false, pset.logfile);
3374 printTableCleanup(&cont);
3375
3376 for (i = 0; i < nrows; i++)
3377 free(attr[i]);
3378 free(attr);
3379
3380 PQclear(res);
3381 return true;
3382 }
3383
3384 static void
add_role_attribute(PQExpBuffer buf,const char * const str)3385 add_role_attribute(PQExpBuffer buf, const char *const str)
3386 {
3387 if (buf->len > 0)
3388 appendPQExpBufferStr(buf, ", ");
3389
3390 appendPQExpBufferStr(buf, str);
3391 }
3392
3393 /*
3394 * \drds
3395 */
3396 bool
listDbRoleSettings(const char * pattern,const char * pattern2)3397 listDbRoleSettings(const char *pattern, const char *pattern2)
3398 {
3399 PQExpBufferData buf;
3400 PGresult *res;
3401 printQueryOpt myopt = pset.popt;
3402 bool havewhere;
3403
3404 if (pset.sversion < 90000)
3405 {
3406 char sverbuf[32];
3407
3408 psql_error("The server (version %s) does not support per-database role settings.\n",
3409 formatPGVersionNumber(pset.sversion, false,
3410 sverbuf, sizeof(sverbuf)));
3411 return true;
3412 }
3413
3414 initPQExpBuffer(&buf);
3415
3416 printfPQExpBuffer(&buf, "SELECT rolname AS \"%s\", datname AS \"%s\",\n"
3417 "pg_catalog.array_to_string(setconfig, E'\\n') AS \"%s\"\n"
3418 "FROM pg_catalog.pg_db_role_setting s\n"
3419 "LEFT JOIN pg_catalog.pg_database d ON d.oid = setdatabase\n"
3420 "LEFT JOIN pg_catalog.pg_roles r ON r.oid = setrole\n",
3421 gettext_noop("Role"),
3422 gettext_noop("Database"),
3423 gettext_noop("Settings"));
3424 havewhere = processSQLNamePattern(pset.db, &buf, pattern, false, false,
3425 NULL, "r.rolname", NULL, NULL);
3426 processSQLNamePattern(pset.db, &buf, pattern2, havewhere, false,
3427 NULL, "d.datname", NULL, NULL);
3428 appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
3429
3430 res = PSQLexec(buf.data);
3431 termPQExpBuffer(&buf);
3432 if (!res)
3433 return false;
3434
3435 /*
3436 * Most functions in this file are content to print an empty table when
3437 * there are no matching objects. We intentionally deviate from that
3438 * here, but only in !quiet mode, because of the possibility that the user
3439 * is confused about what the two pattern arguments mean.
3440 */
3441 if (PQntuples(res) == 0 && !pset.quiet)
3442 {
3443 if (pattern && pattern2)
3444 psql_error("Did not find any settings for role \"%s\" and database \"%s\".\n",
3445 pattern, pattern2);
3446 else if (pattern)
3447 psql_error("Did not find any settings for role \"%s\".\n",
3448 pattern);
3449 else
3450 psql_error("Did not find any settings.\n");
3451 }
3452 else
3453 {
3454 myopt.nullPrint = NULL;
3455 myopt.title = _("List of settings");
3456 myopt.translate_header = true;
3457
3458 printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
3459 }
3460
3461 PQclear(res);
3462 return true;
3463 }
3464
3465
3466 /*
3467 * listTables()
3468 *
3469 * handler for \dt, \di, etc.
3470 *
3471 * tabtypes is an array of characters, specifying what info is desired:
3472 * t - tables
3473 * i - indexes
3474 * v - views
3475 * m - materialized views
3476 * s - sequences
3477 * E - foreign table (Note: different from 'f', the relkind value)
3478 * (any order of the above is fine)
3479 */
3480 bool
listTables(const char * tabtypes,const char * pattern,bool verbose,bool showSystem)3481 listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSystem)
3482 {
3483 bool showTables = strchr(tabtypes, 't') != NULL;
3484 bool showIndexes = strchr(tabtypes, 'i') != NULL;
3485 bool showViews = strchr(tabtypes, 'v') != NULL;
3486 bool showMatViews = strchr(tabtypes, 'm') != NULL;
3487 bool showSeq = strchr(tabtypes, 's') != NULL;
3488 bool showForeign = strchr(tabtypes, 'E') != NULL;
3489
3490 PQExpBufferData buf;
3491 PGresult *res;
3492 printQueryOpt myopt = pset.popt;
3493 static const bool translate_columns[] = {false, false, true, false, false, false, false};
3494
3495 /* If tabtypes is empty, we default to \dtvmsE (but see also command.c) */
3496 if (!(showTables || showIndexes || showViews || showMatViews || showSeq || showForeign))
3497 showTables = showViews = showMatViews = showSeq = showForeign = true;
3498
3499 initPQExpBuffer(&buf);
3500
3501 /*
3502 * Note: as of Pg 8.2, we no longer use relkind 's' (special), but we keep
3503 * it here for backwards compatibility.
3504 */
3505 printfPQExpBuffer(&buf,
3506 "SELECT n.nspname as \"%s\",\n"
3507 " c.relname as \"%s\",\n"
3508 " CASE c.relkind"
3509 " WHEN " CppAsString2(RELKIND_RELATION) " THEN '%s'"
3510 " WHEN " CppAsString2(RELKIND_VIEW) " THEN '%s'"
3511 " WHEN " CppAsString2(RELKIND_MATVIEW) " THEN '%s'"
3512 " WHEN " CppAsString2(RELKIND_INDEX) " THEN '%s'"
3513 " WHEN " CppAsString2(RELKIND_SEQUENCE) " THEN '%s'"
3514 " WHEN 's' THEN '%s'"
3515 " WHEN " CppAsString2(RELKIND_FOREIGN_TABLE) " THEN '%s'"
3516 " WHEN " CppAsString2(RELKIND_PARTITIONED_TABLE) " THEN '%s'"
3517 " WHEN " CppAsString2(RELKIND_PARTITIONED_INDEX) " THEN '%s'"
3518 " END as \"%s\",\n"
3519 " pg_catalog.pg_get_userbyid(c.relowner) as \"%s\"",
3520 gettext_noop("Schema"),
3521 gettext_noop("Name"),
3522 gettext_noop("table"),
3523 gettext_noop("view"),
3524 gettext_noop("materialized view"),
3525 gettext_noop("index"),
3526 gettext_noop("sequence"),
3527 gettext_noop("special"),
3528 gettext_noop("foreign table"),
3529 gettext_noop("table"), /* partitioned table */
3530 gettext_noop("index"), /* partitioned index */
3531 gettext_noop("Type"),
3532 gettext_noop("Owner"));
3533
3534 if (showIndexes)
3535 appendPQExpBuffer(&buf,
3536 ",\n c2.relname as \"%s\"",
3537 gettext_noop("Table"));
3538
3539 if (verbose)
3540 {
3541 /*
3542 * As of PostgreSQL 9.0, use pg_table_size() to show a more accurate
3543 * size of a table, including FSM, VM and TOAST tables.
3544 */
3545 if (pset.sversion >= 90000)
3546 appendPQExpBuffer(&buf,
3547 ",\n pg_catalog.pg_size_pretty(pg_catalog.pg_table_size(c.oid)) as \"%s\"",
3548 gettext_noop("Size"));
3549 else if (pset.sversion >= 80100)
3550 appendPQExpBuffer(&buf,
3551 ",\n pg_catalog.pg_size_pretty(pg_catalog.pg_relation_size(c.oid)) as \"%s\"",
3552 gettext_noop("Size"));
3553
3554 appendPQExpBuffer(&buf,
3555 ",\n pg_catalog.obj_description(c.oid, 'pg_class') as \"%s\"",
3556 gettext_noop("Description"));
3557 }
3558
3559 appendPQExpBufferStr(&buf,
3560 "\nFROM pg_catalog.pg_class c"
3561 "\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace");
3562 if (showIndexes)
3563 appendPQExpBufferStr(&buf,
3564 "\n LEFT JOIN pg_catalog.pg_index i ON i.indexrelid = c.oid"
3565 "\n LEFT JOIN pg_catalog.pg_class c2 ON i.indrelid = c2.oid");
3566
3567 appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
3568 if (showTables)
3569 appendPQExpBufferStr(&buf, CppAsString2(RELKIND_RELATION) ","
3570 CppAsString2(RELKIND_PARTITIONED_TABLE) ",");
3571 if (showViews)
3572 appendPQExpBufferStr(&buf, CppAsString2(RELKIND_VIEW) ",");
3573 if (showMatViews)
3574 appendPQExpBufferStr(&buf, CppAsString2(RELKIND_MATVIEW) ",");
3575 if (showIndexes)
3576 appendPQExpBufferStr(&buf, CppAsString2(RELKIND_INDEX) ","
3577 CppAsString2(RELKIND_PARTITIONED_INDEX) ",");
3578 if (showSeq)
3579 appendPQExpBufferStr(&buf, CppAsString2(RELKIND_SEQUENCE) ",");
3580 if (showSystem || pattern)
3581 appendPQExpBufferStr(&buf, "'s',"); /* was RELKIND_SPECIAL */
3582 if (showForeign)
3583 appendPQExpBufferStr(&buf, CppAsString2(RELKIND_FOREIGN_TABLE) ",");
3584
3585 appendPQExpBufferStr(&buf, "''"); /* dummy */
3586 appendPQExpBufferStr(&buf, ")\n");
3587
3588 if (!showSystem && !pattern)
3589 appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
3590 " AND n.nspname <> 'information_schema'\n");
3591
3592 /*
3593 * TOAST objects are suppressed unconditionally. Since we don't provide
3594 * any way to select RELKIND_TOASTVALUE above, we would never show toast
3595 * tables in any case; it seems a bit confusing to allow their indexes to
3596 * be shown. Use plain \d if you really need to look at a TOAST
3597 * table/index.
3598 */
3599 appendPQExpBufferStr(&buf, " AND n.nspname !~ '^pg_toast'\n");
3600
3601 processSQLNamePattern(pset.db, &buf, pattern, true, false,
3602 "n.nspname", "c.relname", NULL,
3603 "pg_catalog.pg_table_is_visible(c.oid)");
3604
3605 appendPQExpBufferStr(&buf, "ORDER BY 1,2;");
3606
3607 res = PSQLexec(buf.data);
3608 termPQExpBuffer(&buf);
3609 if (!res)
3610 return false;
3611
3612 /*
3613 * Most functions in this file are content to print an empty table when
3614 * there are no matching objects. We intentionally deviate from that
3615 * here, but only in !quiet mode, for historical reasons.
3616 */
3617 if (PQntuples(res) == 0 && !pset.quiet)
3618 {
3619 if (pattern)
3620 psql_error("Did not find any relation named \"%s\".\n",
3621 pattern);
3622 else
3623 psql_error("Did not find any relations.\n");
3624 }
3625 else
3626 {
3627 myopt.nullPrint = NULL;
3628 myopt.title = _("List of relations");
3629 myopt.translate_header = true;
3630 myopt.translate_columns = translate_columns;
3631 myopt.n_translate_columns = lengthof(translate_columns);
3632
3633 printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
3634 }
3635
3636 PQclear(res);
3637 return true;
3638 }
3639
3640
3641 /*
3642 * \dL
3643 *
3644 * Describes languages.
3645 */
3646 bool
listLanguages(const char * pattern,bool verbose,bool showSystem)3647 listLanguages(const char *pattern, bool verbose, bool showSystem)
3648 {
3649 PQExpBufferData buf;
3650 PGresult *res;
3651 printQueryOpt myopt = pset.popt;
3652
3653 initPQExpBuffer(&buf);
3654
3655 printfPQExpBuffer(&buf,
3656 "SELECT l.lanname AS \"%s\",\n",
3657 gettext_noop("Name"));
3658 if (pset.sversion >= 80300)
3659 appendPQExpBuffer(&buf,
3660 " pg_catalog.pg_get_userbyid(l.lanowner) as \"%s\",\n",
3661 gettext_noop("Owner"));
3662
3663 appendPQExpBuffer(&buf,
3664 " l.lanpltrusted AS \"%s\"",
3665 gettext_noop("Trusted"));
3666
3667 if (verbose)
3668 {
3669 appendPQExpBuffer(&buf,
3670 ",\n NOT l.lanispl AS \"%s\",\n"
3671 " l.lanplcallfoid::pg_catalog.regprocedure AS \"%s\",\n"
3672 " l.lanvalidator::pg_catalog.regprocedure AS \"%s\",\n ",
3673 gettext_noop("Internal language"),
3674 gettext_noop("Call handler"),
3675 gettext_noop("Validator"));
3676 if (pset.sversion >= 90000)
3677 appendPQExpBuffer(&buf, "l.laninline::pg_catalog.regprocedure AS \"%s\",\n ",
3678 gettext_noop("Inline handler"));
3679 printACLColumn(&buf, "l.lanacl");
3680 }
3681
3682 appendPQExpBuffer(&buf,
3683 ",\n d.description AS \"%s\""
3684 "\nFROM pg_catalog.pg_language l\n"
3685 "LEFT JOIN pg_catalog.pg_description d\n"
3686 " ON d.classoid = l.tableoid AND d.objoid = l.oid\n"
3687 " AND d.objsubid = 0\n",
3688 gettext_noop("Description"));
3689
3690 if (pattern)
3691 processSQLNamePattern(pset.db, &buf, pattern, false, false,
3692 NULL, "l.lanname", NULL, NULL);
3693
3694 if (!showSystem && !pattern)
3695 appendPQExpBufferStr(&buf, "WHERE l.lanplcallfoid != 0\n");
3696
3697
3698 appendPQExpBufferStr(&buf, "ORDER BY 1;");
3699
3700 res = PSQLexec(buf.data);
3701 termPQExpBuffer(&buf);
3702 if (!res)
3703 return false;
3704
3705 myopt.nullPrint = NULL;
3706 myopt.title = _("List of languages");
3707 myopt.translate_header = true;
3708
3709 printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
3710
3711 PQclear(res);
3712 return true;
3713 }
3714
3715
3716 /*
3717 * \dD
3718 *
3719 * Describes domains.
3720 */
3721 bool
listDomains(const char * pattern,bool verbose,bool showSystem)3722 listDomains(const char *pattern, bool verbose, bool showSystem)
3723 {
3724 PQExpBufferData buf;
3725 PGresult *res;
3726 printQueryOpt myopt = pset.popt;
3727
3728 initPQExpBuffer(&buf);
3729
3730 printfPQExpBuffer(&buf,
3731 "SELECT n.nspname as \"%s\",\n"
3732 " t.typname as \"%s\",\n"
3733 " pg_catalog.format_type(t.typbasetype, t.typtypmod) as \"%s\",\n",
3734 gettext_noop("Schema"),
3735 gettext_noop("Name"),
3736 gettext_noop("Type"));
3737
3738 if (pset.sversion >= 90100)
3739 appendPQExpBuffer(&buf,
3740 " (SELECT c.collname FROM pg_catalog.pg_collation c, pg_catalog.pg_type bt\n"
3741 " WHERE c.oid = t.typcollation AND bt.oid = t.typbasetype AND t.typcollation <> bt.typcollation) as \"%s\",\n",
3742 gettext_noop("Collation"));
3743 appendPQExpBuffer(&buf,
3744 " CASE WHEN t.typnotnull THEN 'not null' END as \"%s\",\n"
3745 " t.typdefault as \"%s\",\n"
3746 " pg_catalog.array_to_string(ARRAY(\n"
3747 " SELECT pg_catalog.pg_get_constraintdef(r.oid, true) FROM pg_catalog.pg_constraint r WHERE t.oid = r.contypid\n"
3748 " ), ' ') as \"%s\"",
3749 gettext_noop("Nullable"),
3750 gettext_noop("Default"),
3751 gettext_noop("Check"));
3752
3753 if (verbose)
3754 {
3755 if (pset.sversion >= 90200)
3756 {
3757 appendPQExpBufferStr(&buf, ",\n ");
3758 printACLColumn(&buf, "t.typacl");
3759 }
3760 appendPQExpBuffer(&buf,
3761 ",\n d.description as \"%s\"",
3762 gettext_noop("Description"));
3763 }
3764
3765 appendPQExpBufferStr(&buf,
3766 "\nFROM pg_catalog.pg_type t\n"
3767 " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace\n");
3768
3769 if (verbose)
3770 appendPQExpBufferStr(&buf,
3771 " LEFT JOIN pg_catalog.pg_description d "
3772 "ON d.classoid = t.tableoid AND d.objoid = t.oid "
3773 "AND d.objsubid = 0\n");
3774
3775 appendPQExpBufferStr(&buf, "WHERE t.typtype = 'd'\n");
3776
3777 if (!showSystem && !pattern)
3778 appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
3779 " AND n.nspname <> 'information_schema'\n");
3780
3781 processSQLNamePattern(pset.db, &buf, pattern, true, false,
3782 "n.nspname", "t.typname", NULL,
3783 "pg_catalog.pg_type_is_visible(t.oid)");
3784
3785 appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
3786
3787 res = PSQLexec(buf.data);
3788 termPQExpBuffer(&buf);
3789 if (!res)
3790 return false;
3791
3792 myopt.nullPrint = NULL;
3793 myopt.title = _("List of domains");
3794 myopt.translate_header = true;
3795
3796 printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
3797
3798 PQclear(res);
3799 return true;
3800 }
3801
3802 /*
3803 * \dc
3804 *
3805 * Describes conversions.
3806 */
3807 bool
listConversions(const char * pattern,bool verbose,bool showSystem)3808 listConversions(const char *pattern, bool verbose, bool showSystem)
3809 {
3810 PQExpBufferData buf;
3811 PGresult *res;
3812 printQueryOpt myopt = pset.popt;
3813 static const bool translate_columns[] =
3814 {false, false, false, false, true, false};
3815
3816 initPQExpBuffer(&buf);
3817
3818 printfPQExpBuffer(&buf,
3819 "SELECT n.nspname AS \"%s\",\n"
3820 " c.conname AS \"%s\",\n"
3821 " pg_catalog.pg_encoding_to_char(c.conforencoding) AS \"%s\",\n"
3822 " pg_catalog.pg_encoding_to_char(c.contoencoding) AS \"%s\",\n"
3823 " CASE WHEN c.condefault THEN '%s'\n"
3824 " ELSE '%s' END AS \"%s\"",
3825 gettext_noop("Schema"),
3826 gettext_noop("Name"),
3827 gettext_noop("Source"),
3828 gettext_noop("Destination"),
3829 gettext_noop("yes"), gettext_noop("no"),
3830 gettext_noop("Default?"));
3831
3832 if (verbose)
3833 appendPQExpBuffer(&buf,
3834 ",\n d.description AS \"%s\"",
3835 gettext_noop("Description"));
3836
3837 appendPQExpBufferStr(&buf,
3838 "\nFROM pg_catalog.pg_conversion c\n"
3839 " JOIN pg_catalog.pg_namespace n "
3840 "ON n.oid = c.connamespace\n");
3841
3842 if (verbose)
3843 appendPQExpBufferStr(&buf,
3844 "LEFT JOIN pg_catalog.pg_description d "
3845 "ON d.classoid = c.tableoid\n"
3846 " AND d.objoid = c.oid "
3847 "AND d.objsubid = 0\n");
3848
3849 appendPQExpBufferStr(&buf, "WHERE true\n");
3850
3851 if (!showSystem && !pattern)
3852 appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
3853 " AND n.nspname <> 'information_schema'\n");
3854
3855 processSQLNamePattern(pset.db, &buf, pattern, true, false,
3856 "n.nspname", "c.conname", NULL,
3857 "pg_catalog.pg_conversion_is_visible(c.oid)");
3858
3859 appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
3860
3861 res = PSQLexec(buf.data);
3862 termPQExpBuffer(&buf);
3863 if (!res)
3864 return false;
3865
3866 myopt.nullPrint = NULL;
3867 myopt.title = _("List of conversions");
3868 myopt.translate_header = true;
3869 myopt.translate_columns = translate_columns;
3870 myopt.n_translate_columns = lengthof(translate_columns);
3871
3872 printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
3873
3874 PQclear(res);
3875 return true;
3876 }
3877
3878 /*
3879 * \dy
3880 *
3881 * Describes Event Triggers.
3882 */
3883 bool
listEventTriggers(const char * pattern,bool verbose)3884 listEventTriggers(const char *pattern, bool verbose)
3885 {
3886 PQExpBufferData buf;
3887 PGresult *res;
3888 printQueryOpt myopt = pset.popt;
3889 static const bool translate_columns[] =
3890 {false, false, false, true, false, false, false};
3891
3892 initPQExpBuffer(&buf);
3893
3894 printfPQExpBuffer(&buf,
3895 "SELECT evtname as \"%s\", "
3896 "evtevent as \"%s\", "
3897 "pg_catalog.pg_get_userbyid(e.evtowner) as \"%s\",\n"
3898 " case evtenabled when 'O' then '%s'"
3899 " when 'R' then '%s'"
3900 " when 'A' then '%s'"
3901 " when 'D' then '%s' end as \"%s\",\n"
3902 " e.evtfoid::pg_catalog.regproc as \"%s\", "
3903 "pg_catalog.array_to_string(array(select x"
3904 " from pg_catalog.unnest(evttags) as t(x)), ', ') as \"%s\"",
3905 gettext_noop("Name"),
3906 gettext_noop("Event"),
3907 gettext_noop("Owner"),
3908 gettext_noop("enabled"),
3909 gettext_noop("replica"),
3910 gettext_noop("always"),
3911 gettext_noop("disabled"),
3912 gettext_noop("Enabled"),
3913 gettext_noop("Function"),
3914 gettext_noop("Tags"));
3915 if (verbose)
3916 appendPQExpBuffer(&buf,
3917 ",\npg_catalog.obj_description(e.oid, 'pg_event_trigger') as \"%s\"",
3918 gettext_noop("Description"));
3919 appendPQExpBufferStr(&buf,
3920 "\nFROM pg_catalog.pg_event_trigger e ");
3921
3922 processSQLNamePattern(pset.db, &buf, pattern, false, false,
3923 NULL, "evtname", NULL, NULL);
3924
3925 appendPQExpBufferStr(&buf, "ORDER BY 1");
3926
3927 res = PSQLexec(buf.data);
3928 termPQExpBuffer(&buf);
3929 if (!res)
3930 return false;
3931
3932 myopt.nullPrint = NULL;
3933 myopt.title = _("List of event triggers");
3934 myopt.translate_header = true;
3935 myopt.translate_columns = translate_columns;
3936 myopt.n_translate_columns = lengthof(translate_columns);
3937
3938 printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
3939
3940 PQclear(res);
3941 return true;
3942 }
3943
3944 /*
3945 * \dC
3946 *
3947 * Describes casts.
3948 */
3949 bool
listCasts(const char * pattern,bool verbose)3950 listCasts(const char *pattern, bool verbose)
3951 {
3952 PQExpBufferData buf;
3953 PGresult *res;
3954 printQueryOpt myopt = pset.popt;
3955 static const bool translate_columns[] = {false, false, false, true, false};
3956
3957 initPQExpBuffer(&buf);
3958
3959 printfPQExpBuffer(&buf,
3960 "SELECT pg_catalog.format_type(castsource, NULL) AS \"%s\",\n"
3961 " pg_catalog.format_type(casttarget, NULL) AS \"%s\",\n",
3962 gettext_noop("Source type"),
3963 gettext_noop("Target type"));
3964
3965 /*
3966 * We don't attempt to localize '(binary coercible)' or '(with inout)',
3967 * because there's too much risk of gettext translating a function name
3968 * that happens to match some string in the PO database.
3969 */
3970 if (pset.sversion >= 80400)
3971 appendPQExpBuffer(&buf,
3972 " CASE WHEN c.castmethod = '%c' THEN '(binary coercible)'\n"
3973 " WHEN c.castmethod = '%c' THEN '(with inout)'\n"
3974 " ELSE p.proname\n"
3975 " END AS \"%s\",\n",
3976 COERCION_METHOD_BINARY,
3977 COERCION_METHOD_INOUT,
3978 gettext_noop("Function"));
3979 else
3980 appendPQExpBuffer(&buf,
3981 " CASE WHEN c.castfunc = 0 THEN '(binary coercible)'\n"
3982 " ELSE p.proname\n"
3983 " END AS \"%s\",\n",
3984 gettext_noop("Function"));
3985
3986 appendPQExpBuffer(&buf,
3987 " CASE WHEN c.castcontext = '%c' THEN '%s'\n"
3988 " WHEN c.castcontext = '%c' THEN '%s'\n"
3989 " ELSE '%s'\n"
3990 " END AS \"%s\"",
3991 COERCION_CODE_EXPLICIT,
3992 gettext_noop("no"),
3993 COERCION_CODE_ASSIGNMENT,
3994 gettext_noop("in assignment"),
3995 gettext_noop("yes"),
3996 gettext_noop("Implicit?"));
3997
3998 if (verbose)
3999 appendPQExpBuffer(&buf,
4000 ",\n d.description AS \"%s\"",
4001 gettext_noop("Description"));
4002
4003 /*
4004 * We need a left join to pg_proc for binary casts; the others are just
4005 * paranoia.
4006 */
4007 appendPQExpBufferStr(&buf,
4008 "\nFROM pg_catalog.pg_cast c LEFT JOIN pg_catalog.pg_proc p\n"
4009 " ON c.castfunc = p.oid\n"
4010 " LEFT JOIN pg_catalog.pg_type ts\n"
4011 " ON c.castsource = ts.oid\n"
4012 " LEFT JOIN pg_catalog.pg_namespace ns\n"
4013 " ON ns.oid = ts.typnamespace\n"
4014 " LEFT JOIN pg_catalog.pg_type tt\n"
4015 " ON c.casttarget = tt.oid\n"
4016 " LEFT JOIN pg_catalog.pg_namespace nt\n"
4017 " ON nt.oid = tt.typnamespace\n");
4018
4019 if (verbose)
4020 appendPQExpBufferStr(&buf,
4021 " LEFT JOIN pg_catalog.pg_description d\n"
4022 " ON d.classoid = c.tableoid AND d.objoid = "
4023 "c.oid AND d.objsubid = 0\n");
4024
4025 appendPQExpBufferStr(&buf, "WHERE ( (true");
4026
4027 /*
4028 * Match name pattern against either internal or external name of either
4029 * castsource or casttarget
4030 */
4031 processSQLNamePattern(pset.db, &buf, pattern, true, false,
4032 "ns.nspname", "ts.typname",
4033 "pg_catalog.format_type(ts.oid, NULL)",
4034 "pg_catalog.pg_type_is_visible(ts.oid)");
4035
4036 appendPQExpBufferStr(&buf, ") OR (true");
4037
4038 processSQLNamePattern(pset.db, &buf, pattern, true, false,
4039 "nt.nspname", "tt.typname",
4040 "pg_catalog.format_type(tt.oid, NULL)",
4041 "pg_catalog.pg_type_is_visible(tt.oid)");
4042
4043 appendPQExpBufferStr(&buf, ") )\nORDER BY 1, 2;");
4044
4045 res = PSQLexec(buf.data);
4046 termPQExpBuffer(&buf);
4047 if (!res)
4048 return false;
4049
4050 myopt.nullPrint = NULL;
4051 myopt.title = _("List of casts");
4052 myopt.translate_header = true;
4053 myopt.translate_columns = translate_columns;
4054 myopt.n_translate_columns = lengthof(translate_columns);
4055
4056 printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
4057
4058 PQclear(res);
4059 return true;
4060 }
4061
4062 /*
4063 * \dO
4064 *
4065 * Describes collations.
4066 */
4067 bool
listCollations(const char * pattern,bool verbose,bool showSystem)4068 listCollations(const char *pattern, bool verbose, bool showSystem)
4069 {
4070 PQExpBufferData buf;
4071 PGresult *res;
4072 printQueryOpt myopt = pset.popt;
4073 static const bool translate_columns[] = {false, false, false, false, false, false};
4074
4075 if (pset.sversion < 90100)
4076 {
4077 char sverbuf[32];
4078
4079 psql_error("The server (version %s) does not support collations.\n",
4080 formatPGVersionNumber(pset.sversion, false,
4081 sverbuf, sizeof(sverbuf)));
4082 return true;
4083 }
4084
4085 initPQExpBuffer(&buf);
4086
4087 printfPQExpBuffer(&buf,
4088 "SELECT n.nspname AS \"%s\",\n"
4089 " c.collname AS \"%s\",\n"
4090 " c.collcollate AS \"%s\",\n"
4091 " c.collctype AS \"%s\"",
4092 gettext_noop("Schema"),
4093 gettext_noop("Name"),
4094 gettext_noop("Collate"),
4095 gettext_noop("Ctype"));
4096
4097 if (pset.sversion >= 100000)
4098 appendPQExpBuffer(&buf,
4099 ",\n CASE c.collprovider WHEN 'd' THEN 'default' WHEN 'c' THEN 'libc' WHEN 'i' THEN 'icu' END AS \"%s\"",
4100 gettext_noop("Provider"));
4101
4102 if (verbose)
4103 appendPQExpBuffer(&buf,
4104 ",\n pg_catalog.obj_description(c.oid, 'pg_collation') AS \"%s\"",
4105 gettext_noop("Description"));
4106
4107 appendPQExpBufferStr(&buf,
4108 "\nFROM pg_catalog.pg_collation c, pg_catalog.pg_namespace n\n"
4109 "WHERE n.oid = c.collnamespace\n");
4110
4111 if (!showSystem && !pattern)
4112 appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
4113 " AND n.nspname <> 'information_schema'\n");
4114
4115 /*
4116 * Hide collations that aren't usable in the current database's encoding.
4117 * If you think to change this, note that pg_collation_is_visible rejects
4118 * unusable collations, so you will need to hack name pattern processing
4119 * somehow to avoid inconsistent behavior.
4120 */
4121 appendPQExpBufferStr(&buf, " AND c.collencoding IN (-1, pg_catalog.pg_char_to_encoding(pg_catalog.getdatabaseencoding()))\n");
4122
4123 processSQLNamePattern(pset.db, &buf, pattern, true, false,
4124 "n.nspname", "c.collname", NULL,
4125 "pg_catalog.pg_collation_is_visible(c.oid)");
4126
4127 appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
4128
4129 res = PSQLexec(buf.data);
4130 termPQExpBuffer(&buf);
4131 if (!res)
4132 return false;
4133
4134 myopt.nullPrint = NULL;
4135 myopt.title = _("List of collations");
4136 myopt.translate_header = true;
4137 myopt.translate_columns = translate_columns;
4138 myopt.n_translate_columns = lengthof(translate_columns);
4139
4140 printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
4141
4142 PQclear(res);
4143 return true;
4144 }
4145
4146 /*
4147 * \dn
4148 *
4149 * Describes schemas (namespaces)
4150 */
4151 bool
listSchemas(const char * pattern,bool verbose,bool showSystem)4152 listSchemas(const char *pattern, bool verbose, bool showSystem)
4153 {
4154 PQExpBufferData buf;
4155 PGresult *res;
4156 printQueryOpt myopt = pset.popt;
4157
4158 initPQExpBuffer(&buf);
4159 printfPQExpBuffer(&buf,
4160 "SELECT n.nspname AS \"%s\",\n"
4161 " pg_catalog.pg_get_userbyid(n.nspowner) AS \"%s\"",
4162 gettext_noop("Name"),
4163 gettext_noop("Owner"));
4164
4165 if (verbose)
4166 {
4167 appendPQExpBufferStr(&buf, ",\n ");
4168 printACLColumn(&buf, "n.nspacl");
4169 appendPQExpBuffer(&buf,
4170 ",\n pg_catalog.obj_description(n.oid, 'pg_namespace') AS \"%s\"",
4171 gettext_noop("Description"));
4172 }
4173
4174 appendPQExpBuffer(&buf,
4175 "\nFROM pg_catalog.pg_namespace n\n");
4176
4177 if (!showSystem && !pattern)
4178 appendPQExpBufferStr(&buf,
4179 "WHERE n.nspname !~ '^pg_' AND n.nspname <> 'information_schema'\n");
4180
4181 processSQLNamePattern(pset.db, &buf, pattern,
4182 !showSystem && !pattern, false,
4183 NULL, "n.nspname", NULL,
4184 NULL);
4185
4186 appendPQExpBufferStr(&buf, "ORDER BY 1;");
4187
4188 res = PSQLexec(buf.data);
4189 termPQExpBuffer(&buf);
4190 if (!res)
4191 return false;
4192
4193 myopt.nullPrint = NULL;
4194 myopt.title = _("List of schemas");
4195 myopt.translate_header = true;
4196
4197 printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
4198
4199 PQclear(res);
4200 return true;
4201 }
4202
4203
4204 /*
4205 * \dFp
4206 * list text search parsers
4207 */
4208 bool
listTSParsers(const char * pattern,bool verbose)4209 listTSParsers(const char *pattern, bool verbose)
4210 {
4211 PQExpBufferData buf;
4212 PGresult *res;
4213 printQueryOpt myopt = pset.popt;
4214
4215 if (pset.sversion < 80300)
4216 {
4217 char sverbuf[32];
4218
4219 psql_error("The server (version %s) does not support full text search.\n",
4220 formatPGVersionNumber(pset.sversion, false,
4221 sverbuf, sizeof(sverbuf)));
4222 return true;
4223 }
4224
4225 if (verbose)
4226 return listTSParsersVerbose(pattern);
4227
4228 initPQExpBuffer(&buf);
4229
4230 printfPQExpBuffer(&buf,
4231 "SELECT\n"
4232 " n.nspname as \"%s\",\n"
4233 " p.prsname as \"%s\",\n"
4234 " pg_catalog.obj_description(p.oid, 'pg_ts_parser') as \"%s\"\n"
4235 "FROM pg_catalog.pg_ts_parser p\n"
4236 "LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.prsnamespace\n",
4237 gettext_noop("Schema"),
4238 gettext_noop("Name"),
4239 gettext_noop("Description")
4240 );
4241
4242 processSQLNamePattern(pset.db, &buf, pattern, false, false,
4243 "n.nspname", "p.prsname", NULL,
4244 "pg_catalog.pg_ts_parser_is_visible(p.oid)");
4245
4246 appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
4247
4248 res = PSQLexec(buf.data);
4249 termPQExpBuffer(&buf);
4250 if (!res)
4251 return false;
4252
4253 myopt.nullPrint = NULL;
4254 myopt.title = _("List of text search parsers");
4255 myopt.translate_header = true;
4256
4257 printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
4258
4259 PQclear(res);
4260 return true;
4261 }
4262
4263 /*
4264 * full description of parsers
4265 */
4266 static bool
listTSParsersVerbose(const char * pattern)4267 listTSParsersVerbose(const char *pattern)
4268 {
4269 PQExpBufferData buf;
4270 PGresult *res;
4271 int i;
4272
4273 initPQExpBuffer(&buf);
4274
4275 printfPQExpBuffer(&buf,
4276 "SELECT p.oid,\n"
4277 " n.nspname,\n"
4278 " p.prsname\n"
4279 "FROM pg_catalog.pg_ts_parser p\n"
4280 "LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.prsnamespace\n"
4281 );
4282
4283 processSQLNamePattern(pset.db, &buf, pattern, false, false,
4284 "n.nspname", "p.prsname", NULL,
4285 "pg_catalog.pg_ts_parser_is_visible(p.oid)");
4286
4287 appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
4288
4289 res = PSQLexec(buf.data);
4290 termPQExpBuffer(&buf);
4291 if (!res)
4292 return false;
4293
4294 if (PQntuples(res) == 0)
4295 {
4296 if (!pset.quiet)
4297 {
4298 if (pattern)
4299 psql_error("Did not find any text search parser named \"%s\".\n",
4300 pattern);
4301 else
4302 psql_error("Did not find any text search parsers.\n");
4303 }
4304 PQclear(res);
4305 return false;
4306 }
4307
4308 for (i = 0; i < PQntuples(res); i++)
4309 {
4310 const char *oid;
4311 const char *nspname = NULL;
4312 const char *prsname;
4313
4314 oid = PQgetvalue(res, i, 0);
4315 if (!PQgetisnull(res, i, 1))
4316 nspname = PQgetvalue(res, i, 1);
4317 prsname = PQgetvalue(res, i, 2);
4318
4319 if (!describeOneTSParser(oid, nspname, prsname))
4320 {
4321 PQclear(res);
4322 return false;
4323 }
4324
4325 if (cancel_pressed)
4326 {
4327 PQclear(res);
4328 return false;
4329 }
4330 }
4331
4332 PQclear(res);
4333 return true;
4334 }
4335
4336 static bool
describeOneTSParser(const char * oid,const char * nspname,const char * prsname)4337 describeOneTSParser(const char *oid, const char *nspname, const char *prsname)
4338 {
4339 PQExpBufferData buf;
4340 PGresult *res;
4341 PQExpBufferData title;
4342 printQueryOpt myopt = pset.popt;
4343 static const bool translate_columns[] = {true, false, false};
4344
4345 initPQExpBuffer(&buf);
4346
4347 printfPQExpBuffer(&buf,
4348 "SELECT '%s' AS \"%s\",\n"
4349 " p.prsstart::pg_catalog.regproc AS \"%s\",\n"
4350 " pg_catalog.obj_description(p.prsstart, 'pg_proc') as \"%s\"\n"
4351 " FROM pg_catalog.pg_ts_parser p\n"
4352 " WHERE p.oid = '%s'\n"
4353 "UNION ALL\n"
4354 "SELECT '%s',\n"
4355 " p.prstoken::pg_catalog.regproc,\n"
4356 " pg_catalog.obj_description(p.prstoken, 'pg_proc')\n"
4357 " FROM pg_catalog.pg_ts_parser p\n"
4358 " WHERE p.oid = '%s'\n"
4359 "UNION ALL\n"
4360 "SELECT '%s',\n"
4361 " p.prsend::pg_catalog.regproc,\n"
4362 " pg_catalog.obj_description(p.prsend, 'pg_proc')\n"
4363 " FROM pg_catalog.pg_ts_parser p\n"
4364 " WHERE p.oid = '%s'\n"
4365 "UNION ALL\n"
4366 "SELECT '%s',\n"
4367 " p.prsheadline::pg_catalog.regproc,\n"
4368 " pg_catalog.obj_description(p.prsheadline, 'pg_proc')\n"
4369 " FROM pg_catalog.pg_ts_parser p\n"
4370 " WHERE p.oid = '%s'\n"
4371 "UNION ALL\n"
4372 "SELECT '%s',\n"
4373 " p.prslextype::pg_catalog.regproc,\n"
4374 " pg_catalog.obj_description(p.prslextype, 'pg_proc')\n"
4375 " FROM pg_catalog.pg_ts_parser p\n"
4376 " WHERE p.oid = '%s';",
4377 gettext_noop("Start parse"),
4378 gettext_noop("Method"),
4379 gettext_noop("Function"),
4380 gettext_noop("Description"),
4381 oid,
4382 gettext_noop("Get next token"),
4383 oid,
4384 gettext_noop("End parse"),
4385 oid,
4386 gettext_noop("Get headline"),
4387 oid,
4388 gettext_noop("Get token types"),
4389 oid);
4390
4391 res = PSQLexec(buf.data);
4392 termPQExpBuffer(&buf);
4393 if (!res)
4394 return false;
4395
4396 myopt.nullPrint = NULL;
4397 initPQExpBuffer(&title);
4398 if (nspname)
4399 printfPQExpBuffer(&title, _("Text search parser \"%s.%s\""),
4400 nspname, prsname);
4401 else
4402 printfPQExpBuffer(&title, _("Text search parser \"%s\""), prsname);
4403 myopt.title = title.data;
4404 myopt.footers = NULL;
4405 myopt.topt.default_footer = false;
4406 myopt.translate_header = true;
4407 myopt.translate_columns = translate_columns;
4408 myopt.n_translate_columns = lengthof(translate_columns);
4409
4410 printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
4411
4412 PQclear(res);
4413
4414 initPQExpBuffer(&buf);
4415
4416 printfPQExpBuffer(&buf,
4417 "SELECT t.alias as \"%s\",\n"
4418 " t.description as \"%s\"\n"
4419 "FROM pg_catalog.ts_token_type( '%s'::pg_catalog.oid ) as t\n"
4420 "ORDER BY 1;",
4421 gettext_noop("Token name"),
4422 gettext_noop("Description"),
4423 oid);
4424
4425 res = PSQLexec(buf.data);
4426 termPQExpBuffer(&buf);
4427 if (!res)
4428 return false;
4429
4430 myopt.nullPrint = NULL;
4431 if (nspname)
4432 printfPQExpBuffer(&title, _("Token types for parser \"%s.%s\""),
4433 nspname, prsname);
4434 else
4435 printfPQExpBuffer(&title, _("Token types for parser \"%s\""), prsname);
4436 myopt.title = title.data;
4437 myopt.footers = NULL;
4438 myopt.topt.default_footer = true;
4439 myopt.translate_header = true;
4440 myopt.translate_columns = NULL;
4441 myopt.n_translate_columns = 0;
4442
4443 printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
4444
4445 termPQExpBuffer(&title);
4446 PQclear(res);
4447 return true;
4448 }
4449
4450
4451 /*
4452 * \dFd
4453 * list text search dictionaries
4454 */
4455 bool
listTSDictionaries(const char * pattern,bool verbose)4456 listTSDictionaries(const char *pattern, bool verbose)
4457 {
4458 PQExpBufferData buf;
4459 PGresult *res;
4460 printQueryOpt myopt = pset.popt;
4461
4462 if (pset.sversion < 80300)
4463 {
4464 char sverbuf[32];
4465
4466 psql_error("The server (version %s) does not support full text search.\n",
4467 formatPGVersionNumber(pset.sversion, false,
4468 sverbuf, sizeof(sverbuf)));
4469 return true;
4470 }
4471
4472 initPQExpBuffer(&buf);
4473
4474 printfPQExpBuffer(&buf,
4475 "SELECT\n"
4476 " n.nspname as \"%s\",\n"
4477 " d.dictname as \"%s\",\n",
4478 gettext_noop("Schema"),
4479 gettext_noop("Name"));
4480
4481 if (verbose)
4482 {
4483 appendPQExpBuffer(&buf,
4484 " ( SELECT COALESCE(nt.nspname, '(null)')::pg_catalog.text || '.' || t.tmplname FROM\n"
4485 " pg_catalog.pg_ts_template t\n"
4486 " LEFT JOIN pg_catalog.pg_namespace nt ON nt.oid = t.tmplnamespace\n"
4487 " WHERE d.dicttemplate = t.oid ) AS \"%s\",\n"
4488 " d.dictinitoption as \"%s\",\n",
4489 gettext_noop("Template"),
4490 gettext_noop("Init options"));
4491 }
4492
4493 appendPQExpBuffer(&buf,
4494 " pg_catalog.obj_description(d.oid, 'pg_ts_dict') as \"%s\"\n",
4495 gettext_noop("Description"));
4496
4497 appendPQExpBufferStr(&buf, "FROM pg_catalog.pg_ts_dict d\n"
4498 "LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.dictnamespace\n");
4499
4500 processSQLNamePattern(pset.db, &buf, pattern, false, false,
4501 "n.nspname", "d.dictname", NULL,
4502 "pg_catalog.pg_ts_dict_is_visible(d.oid)");
4503
4504 appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
4505
4506 res = PSQLexec(buf.data);
4507 termPQExpBuffer(&buf);
4508 if (!res)
4509 return false;
4510
4511 myopt.nullPrint = NULL;
4512 myopt.title = _("List of text search dictionaries");
4513 myopt.translate_header = true;
4514
4515 printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
4516
4517 PQclear(res);
4518 return true;
4519 }
4520
4521
4522 /*
4523 * \dFt
4524 * list text search templates
4525 */
4526 bool
listTSTemplates(const char * pattern,bool verbose)4527 listTSTemplates(const char *pattern, bool verbose)
4528 {
4529 PQExpBufferData buf;
4530 PGresult *res;
4531 printQueryOpt myopt = pset.popt;
4532
4533 if (pset.sversion < 80300)
4534 {
4535 char sverbuf[32];
4536
4537 psql_error("The server (version %s) does not support full text search.\n",
4538 formatPGVersionNumber(pset.sversion, false,
4539 sverbuf, sizeof(sverbuf)));
4540 return true;
4541 }
4542
4543 initPQExpBuffer(&buf);
4544
4545 if (verbose)
4546 printfPQExpBuffer(&buf,
4547 "SELECT\n"
4548 " n.nspname AS \"%s\",\n"
4549 " t.tmplname AS \"%s\",\n"
4550 " t.tmplinit::pg_catalog.regproc AS \"%s\",\n"
4551 " t.tmpllexize::pg_catalog.regproc AS \"%s\",\n"
4552 " pg_catalog.obj_description(t.oid, 'pg_ts_template') AS \"%s\"\n",
4553 gettext_noop("Schema"),
4554 gettext_noop("Name"),
4555 gettext_noop("Init"),
4556 gettext_noop("Lexize"),
4557 gettext_noop("Description"));
4558 else
4559 printfPQExpBuffer(&buf,
4560 "SELECT\n"
4561 " n.nspname AS \"%s\",\n"
4562 " t.tmplname AS \"%s\",\n"
4563 " pg_catalog.obj_description(t.oid, 'pg_ts_template') AS \"%s\"\n",
4564 gettext_noop("Schema"),
4565 gettext_noop("Name"),
4566 gettext_noop("Description"));
4567
4568 appendPQExpBufferStr(&buf, "FROM pg_catalog.pg_ts_template t\n"
4569 "LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.tmplnamespace\n");
4570
4571 processSQLNamePattern(pset.db, &buf, pattern, false, false,
4572 "n.nspname", "t.tmplname", NULL,
4573 "pg_catalog.pg_ts_template_is_visible(t.oid)");
4574
4575 appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
4576
4577 res = PSQLexec(buf.data);
4578 termPQExpBuffer(&buf);
4579 if (!res)
4580 return false;
4581
4582 myopt.nullPrint = NULL;
4583 myopt.title = _("List of text search templates");
4584 myopt.translate_header = true;
4585
4586 printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
4587
4588 PQclear(res);
4589 return true;
4590 }
4591
4592
4593 /*
4594 * \dF
4595 * list text search configurations
4596 */
4597 bool
listTSConfigs(const char * pattern,bool verbose)4598 listTSConfigs(const char *pattern, bool verbose)
4599 {
4600 PQExpBufferData buf;
4601 PGresult *res;
4602 printQueryOpt myopt = pset.popt;
4603
4604 if (pset.sversion < 80300)
4605 {
4606 char sverbuf[32];
4607
4608 psql_error("The server (version %s) does not support full text search.\n",
4609 formatPGVersionNumber(pset.sversion, false,
4610 sverbuf, sizeof(sverbuf)));
4611 return true;
4612 }
4613
4614 if (verbose)
4615 return listTSConfigsVerbose(pattern);
4616
4617 initPQExpBuffer(&buf);
4618
4619 printfPQExpBuffer(&buf,
4620 "SELECT\n"
4621 " n.nspname as \"%s\",\n"
4622 " c.cfgname as \"%s\",\n"
4623 " pg_catalog.obj_description(c.oid, 'pg_ts_config') as \"%s\"\n"
4624 "FROM pg_catalog.pg_ts_config c\n"
4625 "LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.cfgnamespace\n",
4626 gettext_noop("Schema"),
4627 gettext_noop("Name"),
4628 gettext_noop("Description")
4629 );
4630
4631 processSQLNamePattern(pset.db, &buf, pattern, false, false,
4632 "n.nspname", "c.cfgname", NULL,
4633 "pg_catalog.pg_ts_config_is_visible(c.oid)");
4634
4635 appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
4636
4637 res = PSQLexec(buf.data);
4638 termPQExpBuffer(&buf);
4639 if (!res)
4640 return false;
4641
4642 myopt.nullPrint = NULL;
4643 myopt.title = _("List of text search configurations");
4644 myopt.translate_header = true;
4645
4646 printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
4647
4648 PQclear(res);
4649 return true;
4650 }
4651
4652 static bool
listTSConfigsVerbose(const char * pattern)4653 listTSConfigsVerbose(const char *pattern)
4654 {
4655 PQExpBufferData buf;
4656 PGresult *res;
4657 int i;
4658
4659 initPQExpBuffer(&buf);
4660
4661 printfPQExpBuffer(&buf,
4662 "SELECT c.oid, c.cfgname,\n"
4663 " n.nspname,\n"
4664 " p.prsname,\n"
4665 " np.nspname as pnspname\n"
4666 "FROM pg_catalog.pg_ts_config c\n"
4667 " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.cfgnamespace,\n"
4668 " pg_catalog.pg_ts_parser p\n"
4669 " LEFT JOIN pg_catalog.pg_namespace np ON np.oid = p.prsnamespace\n"
4670 "WHERE p.oid = c.cfgparser\n"
4671 );
4672
4673 processSQLNamePattern(pset.db, &buf, pattern, true, false,
4674 "n.nspname", "c.cfgname", NULL,
4675 "pg_catalog.pg_ts_config_is_visible(c.oid)");
4676
4677 appendPQExpBufferStr(&buf, "ORDER BY 3, 2;");
4678
4679 res = PSQLexec(buf.data);
4680 termPQExpBuffer(&buf);
4681 if (!res)
4682 return false;
4683
4684 if (PQntuples(res) == 0)
4685 {
4686 if (!pset.quiet)
4687 {
4688 if (pattern)
4689 psql_error("Did not find any text search configuration named \"%s\".\n",
4690 pattern);
4691 else
4692 psql_error("Did not find any text search configurations.\n");
4693 }
4694 PQclear(res);
4695 return false;
4696 }
4697
4698 for (i = 0; i < PQntuples(res); i++)
4699 {
4700 const char *oid;
4701 const char *cfgname;
4702 const char *nspname = NULL;
4703 const char *prsname;
4704 const char *pnspname = NULL;
4705
4706 oid = PQgetvalue(res, i, 0);
4707 cfgname = PQgetvalue(res, i, 1);
4708 if (!PQgetisnull(res, i, 2))
4709 nspname = PQgetvalue(res, i, 2);
4710 prsname = PQgetvalue(res, i, 3);
4711 if (!PQgetisnull(res, i, 4))
4712 pnspname = PQgetvalue(res, i, 4);
4713
4714 if (!describeOneTSConfig(oid, nspname, cfgname, pnspname, prsname))
4715 {
4716 PQclear(res);
4717 return false;
4718 }
4719
4720 if (cancel_pressed)
4721 {
4722 PQclear(res);
4723 return false;
4724 }
4725 }
4726
4727 PQclear(res);
4728 return true;
4729 }
4730
4731 static bool
describeOneTSConfig(const char * oid,const char * nspname,const char * cfgname,const char * pnspname,const char * prsname)4732 describeOneTSConfig(const char *oid, const char *nspname, const char *cfgname,
4733 const char *pnspname, const char *prsname)
4734 {
4735 PQExpBufferData buf,
4736 title;
4737 PGresult *res;
4738 printQueryOpt myopt = pset.popt;
4739
4740 initPQExpBuffer(&buf);
4741
4742 printfPQExpBuffer(&buf,
4743 "SELECT\n"
4744 " ( SELECT t.alias FROM\n"
4745 " pg_catalog.ts_token_type(c.cfgparser) AS t\n"
4746 " WHERE t.tokid = m.maptokentype ) AS \"%s\",\n"
4747 " pg_catalog.btrim(\n"
4748 " ARRAY( SELECT mm.mapdict::pg_catalog.regdictionary\n"
4749 " FROM pg_catalog.pg_ts_config_map AS mm\n"
4750 " WHERE mm.mapcfg = m.mapcfg AND mm.maptokentype = m.maptokentype\n"
4751 " ORDER BY mapcfg, maptokentype, mapseqno\n"
4752 " ) :: pg_catalog.text,\n"
4753 " '{}') AS \"%s\"\n"
4754 "FROM pg_catalog.pg_ts_config AS c, pg_catalog.pg_ts_config_map AS m\n"
4755 "WHERE c.oid = '%s' AND m.mapcfg = c.oid\n"
4756 "GROUP BY m.mapcfg, m.maptokentype, c.cfgparser\n"
4757 "ORDER BY 1;",
4758 gettext_noop("Token"),
4759 gettext_noop("Dictionaries"),
4760 oid);
4761
4762 res = PSQLexec(buf.data);
4763 termPQExpBuffer(&buf);
4764 if (!res)
4765 return false;
4766
4767 initPQExpBuffer(&title);
4768
4769 if (nspname)
4770 appendPQExpBuffer(&title, _("Text search configuration \"%s.%s\""),
4771 nspname, cfgname);
4772 else
4773 appendPQExpBuffer(&title, _("Text search configuration \"%s\""),
4774 cfgname);
4775
4776 if (pnspname)
4777 appendPQExpBuffer(&title, _("\nParser: \"%s.%s\""),
4778 pnspname, prsname);
4779 else
4780 appendPQExpBuffer(&title, _("\nParser: \"%s\""),
4781 prsname);
4782
4783 myopt.nullPrint = NULL;
4784 myopt.title = title.data;
4785 myopt.footers = NULL;
4786 myopt.topt.default_footer = false;
4787 myopt.translate_header = true;
4788
4789 printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
4790
4791 termPQExpBuffer(&title);
4792
4793 PQclear(res);
4794 return true;
4795 }
4796
4797
4798 /*
4799 * \dew
4800 *
4801 * Describes foreign-data wrappers
4802 */
4803 bool
listForeignDataWrappers(const char * pattern,bool verbose)4804 listForeignDataWrappers(const char *pattern, bool verbose)
4805 {
4806 PQExpBufferData buf;
4807 PGresult *res;
4808 printQueryOpt myopt = pset.popt;
4809
4810 if (pset.sversion < 80400)
4811 {
4812 char sverbuf[32];
4813
4814 psql_error("The server (version %s) does not support foreign-data wrappers.\n",
4815 formatPGVersionNumber(pset.sversion, false,
4816 sverbuf, sizeof(sverbuf)));
4817 return true;
4818 }
4819
4820 initPQExpBuffer(&buf);
4821 printfPQExpBuffer(&buf,
4822 "SELECT fdw.fdwname AS \"%s\",\n"
4823 " pg_catalog.pg_get_userbyid(fdw.fdwowner) AS \"%s\",\n",
4824 gettext_noop("Name"),
4825 gettext_noop("Owner"));
4826 if (pset.sversion >= 90100)
4827 appendPQExpBuffer(&buf,
4828 " fdw.fdwhandler::pg_catalog.regproc AS \"%s\",\n",
4829 gettext_noop("Handler"));
4830 appendPQExpBuffer(&buf,
4831 " fdw.fdwvalidator::pg_catalog.regproc AS \"%s\"",
4832 gettext_noop("Validator"));
4833
4834 if (verbose)
4835 {
4836 appendPQExpBufferStr(&buf, ",\n ");
4837 printACLColumn(&buf, "fdwacl");
4838 appendPQExpBuffer(&buf,
4839 ",\n CASE WHEN fdwoptions IS NULL THEN '' ELSE "
4840 " '(' || pg_catalog.array_to_string(ARRAY(SELECT "
4841 " pg_catalog.quote_ident(option_name) || ' ' || "
4842 " pg_catalog.quote_literal(option_value) FROM "
4843 " pg_catalog.pg_options_to_table(fdwoptions)), ', ') || ')' "
4844 " END AS \"%s\"",
4845 gettext_noop("FDW options"));
4846
4847 if (pset.sversion >= 90100)
4848 appendPQExpBuffer(&buf,
4849 ",\n d.description AS \"%s\" ",
4850 gettext_noop("Description"));
4851 }
4852
4853 appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_foreign_data_wrapper fdw\n");
4854
4855 if (verbose && pset.sversion >= 90100)
4856 appendPQExpBufferStr(&buf,
4857 "LEFT JOIN pg_catalog.pg_description d\n"
4858 " ON d.classoid = fdw.tableoid "
4859 "AND d.objoid = fdw.oid AND d.objsubid = 0\n");
4860
4861 processSQLNamePattern(pset.db, &buf, pattern, false, false,
4862 NULL, "fdwname", NULL, NULL);
4863
4864 appendPQExpBufferStr(&buf, "ORDER BY 1;");
4865
4866 res = PSQLexec(buf.data);
4867 termPQExpBuffer(&buf);
4868 if (!res)
4869 return false;
4870
4871 myopt.nullPrint = NULL;
4872 myopt.title = _("List of foreign-data wrappers");
4873 myopt.translate_header = true;
4874
4875 printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
4876
4877 PQclear(res);
4878 return true;
4879 }
4880
4881 /*
4882 * \des
4883 *
4884 * Describes foreign servers.
4885 */
4886 bool
listForeignServers(const char * pattern,bool verbose)4887 listForeignServers(const char *pattern, bool verbose)
4888 {
4889 PQExpBufferData buf;
4890 PGresult *res;
4891 printQueryOpt myopt = pset.popt;
4892
4893 if (pset.sversion < 80400)
4894 {
4895 char sverbuf[32];
4896
4897 psql_error("The server (version %s) does not support foreign servers.\n",
4898 formatPGVersionNumber(pset.sversion, false,
4899 sverbuf, sizeof(sverbuf)));
4900 return true;
4901 }
4902
4903 initPQExpBuffer(&buf);
4904 printfPQExpBuffer(&buf,
4905 "SELECT s.srvname AS \"%s\",\n"
4906 " pg_catalog.pg_get_userbyid(s.srvowner) AS \"%s\",\n"
4907 " f.fdwname AS \"%s\"",
4908 gettext_noop("Name"),
4909 gettext_noop("Owner"),
4910 gettext_noop("Foreign-data wrapper"));
4911
4912 if (verbose)
4913 {
4914 appendPQExpBufferStr(&buf, ",\n ");
4915 printACLColumn(&buf, "s.srvacl");
4916 appendPQExpBuffer(&buf,
4917 ",\n"
4918 " s.srvtype AS \"%s\",\n"
4919 " s.srvversion AS \"%s\",\n"
4920 " CASE WHEN srvoptions IS NULL THEN '' ELSE "
4921 " '(' || pg_catalog.array_to_string(ARRAY(SELECT "
4922 " pg_catalog.quote_ident(option_name) || ' ' || "
4923 " pg_catalog.quote_literal(option_value) FROM "
4924 " pg_catalog.pg_options_to_table(srvoptions)), ', ') || ')' "
4925 " END AS \"%s\",\n"
4926 " d.description AS \"%s\"",
4927 gettext_noop("Type"),
4928 gettext_noop("Version"),
4929 gettext_noop("FDW options"),
4930 gettext_noop("Description"));
4931 }
4932
4933 appendPQExpBufferStr(&buf,
4934 "\nFROM pg_catalog.pg_foreign_server s\n"
4935 " JOIN pg_catalog.pg_foreign_data_wrapper f ON f.oid=s.srvfdw\n");
4936
4937 if (verbose)
4938 appendPQExpBufferStr(&buf,
4939 "LEFT JOIN pg_catalog.pg_description d\n "
4940 "ON d.classoid = s.tableoid AND d.objoid = s.oid "
4941 "AND d.objsubid = 0\n");
4942
4943 processSQLNamePattern(pset.db, &buf, pattern, false, false,
4944 NULL, "s.srvname", NULL, NULL);
4945
4946 appendPQExpBufferStr(&buf, "ORDER BY 1;");
4947
4948 res = PSQLexec(buf.data);
4949 termPQExpBuffer(&buf);
4950 if (!res)
4951 return false;
4952
4953 myopt.nullPrint = NULL;
4954 myopt.title = _("List of foreign servers");
4955 myopt.translate_header = true;
4956
4957 printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
4958
4959 PQclear(res);
4960 return true;
4961 }
4962
4963 /*
4964 * \deu
4965 *
4966 * Describes user mappings.
4967 */
4968 bool
listUserMappings(const char * pattern,bool verbose)4969 listUserMappings(const char *pattern, bool verbose)
4970 {
4971 PQExpBufferData buf;
4972 PGresult *res;
4973 printQueryOpt myopt = pset.popt;
4974
4975 if (pset.sversion < 80400)
4976 {
4977 char sverbuf[32];
4978
4979 psql_error("The server (version %s) does not support user mappings.\n",
4980 formatPGVersionNumber(pset.sversion, false,
4981 sverbuf, sizeof(sverbuf)));
4982 return true;
4983 }
4984
4985 initPQExpBuffer(&buf);
4986 printfPQExpBuffer(&buf,
4987 "SELECT um.srvname AS \"%s\",\n"
4988 " um.usename AS \"%s\"",
4989 gettext_noop("Server"),
4990 gettext_noop("User name"));
4991
4992 if (verbose)
4993 appendPQExpBuffer(&buf,
4994 ",\n CASE WHEN umoptions IS NULL THEN '' ELSE "
4995 " '(' || pg_catalog.array_to_string(ARRAY(SELECT "
4996 " pg_catalog.quote_ident(option_name) || ' ' || "
4997 " pg_catalog.quote_literal(option_value) FROM "
4998 " pg_catalog.pg_options_to_table(umoptions)), ', ') || ')' "
4999 " END AS \"%s\"",
5000 gettext_noop("FDW options"));
5001
5002 appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_user_mappings um\n");
5003
5004 processSQLNamePattern(pset.db, &buf, pattern, false, false,
5005 NULL, "um.srvname", "um.usename", NULL);
5006
5007 appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
5008
5009 res = PSQLexec(buf.data);
5010 termPQExpBuffer(&buf);
5011 if (!res)
5012 return false;
5013
5014 myopt.nullPrint = NULL;
5015 myopt.title = _("List of user mappings");
5016 myopt.translate_header = true;
5017
5018 printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
5019
5020 PQclear(res);
5021 return true;
5022 }
5023
5024 /*
5025 * \det
5026 *
5027 * Describes foreign tables.
5028 */
5029 bool
listForeignTables(const char * pattern,bool verbose)5030 listForeignTables(const char *pattern, bool verbose)
5031 {
5032 PQExpBufferData buf;
5033 PGresult *res;
5034 printQueryOpt myopt = pset.popt;
5035
5036 if (pset.sversion < 90100)
5037 {
5038 char sverbuf[32];
5039
5040 psql_error("The server (version %s) does not support foreign tables.\n",
5041 formatPGVersionNumber(pset.sversion, false,
5042 sverbuf, sizeof(sverbuf)));
5043 return true;
5044 }
5045
5046 initPQExpBuffer(&buf);
5047 printfPQExpBuffer(&buf,
5048 "SELECT n.nspname AS \"%s\",\n"
5049 " c.relname AS \"%s\",\n"
5050 " s.srvname AS \"%s\"",
5051 gettext_noop("Schema"),
5052 gettext_noop("Table"),
5053 gettext_noop("Server"));
5054
5055 if (verbose)
5056 appendPQExpBuffer(&buf,
5057 ",\n CASE WHEN ftoptions IS NULL THEN '' ELSE "
5058 " '(' || pg_catalog.array_to_string(ARRAY(SELECT "
5059 " pg_catalog.quote_ident(option_name) || ' ' || "
5060 " pg_catalog.quote_literal(option_value) FROM "
5061 " pg_catalog.pg_options_to_table(ftoptions)), ', ') || ')' "
5062 " END AS \"%s\",\n"
5063 " d.description AS \"%s\"",
5064 gettext_noop("FDW options"),
5065 gettext_noop("Description"));
5066
5067 appendPQExpBufferStr(&buf,
5068 "\nFROM pg_catalog.pg_foreign_table ft\n"
5069 " INNER JOIN pg_catalog.pg_class c"
5070 " ON c.oid = ft.ftrelid\n"
5071 " INNER JOIN pg_catalog.pg_namespace n"
5072 " ON n.oid = c.relnamespace\n"
5073 " INNER JOIN pg_catalog.pg_foreign_server s"
5074 " ON s.oid = ft.ftserver\n");
5075 if (verbose)
5076 appendPQExpBufferStr(&buf,
5077 " LEFT JOIN pg_catalog.pg_description d\n"
5078 " ON d.classoid = c.tableoid AND "
5079 "d.objoid = c.oid AND d.objsubid = 0\n");
5080
5081 processSQLNamePattern(pset.db, &buf, pattern, false, false,
5082 "n.nspname", "c.relname", NULL,
5083 "pg_catalog.pg_table_is_visible(c.oid)");
5084
5085 appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
5086
5087 res = PSQLexec(buf.data);
5088 termPQExpBuffer(&buf);
5089 if (!res)
5090 return false;
5091
5092 myopt.nullPrint = NULL;
5093 myopt.title = _("List of foreign tables");
5094 myopt.translate_header = true;
5095
5096 printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
5097
5098 PQclear(res);
5099 return true;
5100 }
5101
5102 /*
5103 * \dx
5104 *
5105 * Briefly describes installed extensions.
5106 */
5107 bool
listExtensions(const char * pattern)5108 listExtensions(const char *pattern)
5109 {
5110 PQExpBufferData buf;
5111 PGresult *res;
5112 printQueryOpt myopt = pset.popt;
5113
5114 if (pset.sversion < 90100)
5115 {
5116 char sverbuf[32];
5117
5118 psql_error("The server (version %s) does not support extensions.\n",
5119 formatPGVersionNumber(pset.sversion, false,
5120 sverbuf, sizeof(sverbuf)));
5121 return true;
5122 }
5123
5124 initPQExpBuffer(&buf);
5125 printfPQExpBuffer(&buf,
5126 "SELECT e.extname AS \"%s\", "
5127 "e.extversion AS \"%s\", n.nspname AS \"%s\", c.description AS \"%s\"\n"
5128 "FROM pg_catalog.pg_extension e "
5129 "LEFT JOIN pg_catalog.pg_namespace n ON n.oid = e.extnamespace "
5130 "LEFT JOIN pg_catalog.pg_description c ON c.objoid = e.oid "
5131 "AND c.classoid = 'pg_catalog.pg_extension'::pg_catalog.regclass\n",
5132 gettext_noop("Name"),
5133 gettext_noop("Version"),
5134 gettext_noop("Schema"),
5135 gettext_noop("Description"));
5136
5137 processSQLNamePattern(pset.db, &buf, pattern,
5138 false, false,
5139 NULL, "e.extname", NULL,
5140 NULL);
5141
5142 appendPQExpBufferStr(&buf, "ORDER BY 1;");
5143
5144 res = PSQLexec(buf.data);
5145 termPQExpBuffer(&buf);
5146 if (!res)
5147 return false;
5148
5149 myopt.nullPrint = NULL;
5150 myopt.title = _("List of installed extensions");
5151 myopt.translate_header = true;
5152
5153 printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
5154
5155 PQclear(res);
5156 return true;
5157 }
5158
5159 /*
5160 * \dx+
5161 *
5162 * List contents of installed extensions.
5163 */
5164 bool
listExtensionContents(const char * pattern)5165 listExtensionContents(const char *pattern)
5166 {
5167 PQExpBufferData buf;
5168 PGresult *res;
5169 int i;
5170
5171 if (pset.sversion < 90100)
5172 {
5173 char sverbuf[32];
5174
5175 psql_error("The server (version %s) does not support extensions.\n",
5176 formatPGVersionNumber(pset.sversion, false,
5177 sverbuf, sizeof(sverbuf)));
5178 return true;
5179 }
5180
5181 initPQExpBuffer(&buf);
5182 printfPQExpBuffer(&buf,
5183 "SELECT e.extname, e.oid\n"
5184 "FROM pg_catalog.pg_extension e\n");
5185
5186 processSQLNamePattern(pset.db, &buf, pattern,
5187 false, false,
5188 NULL, "e.extname", NULL,
5189 NULL);
5190
5191 appendPQExpBufferStr(&buf, "ORDER BY 1;");
5192
5193 res = PSQLexec(buf.data);
5194 termPQExpBuffer(&buf);
5195 if (!res)
5196 return false;
5197
5198 if (PQntuples(res) == 0)
5199 {
5200 if (!pset.quiet)
5201 {
5202 if (pattern)
5203 psql_error("Did not find any extension named \"%s\".\n",
5204 pattern);
5205 else
5206 psql_error("Did not find any extensions.\n");
5207 }
5208 PQclear(res);
5209 return false;
5210 }
5211
5212 for (i = 0; i < PQntuples(res); i++)
5213 {
5214 const char *extname;
5215 const char *oid;
5216
5217 extname = PQgetvalue(res, i, 0);
5218 oid = PQgetvalue(res, i, 1);
5219
5220 if (!listOneExtensionContents(extname, oid))
5221 {
5222 PQclear(res);
5223 return false;
5224 }
5225 if (cancel_pressed)
5226 {
5227 PQclear(res);
5228 return false;
5229 }
5230 }
5231
5232 PQclear(res);
5233 return true;
5234 }
5235
5236 static bool
listOneExtensionContents(const char * extname,const char * oid)5237 listOneExtensionContents(const char *extname, const char *oid)
5238 {
5239 PQExpBufferData buf;
5240 PGresult *res;
5241 PQExpBufferData title;
5242 printQueryOpt myopt = pset.popt;
5243
5244 initPQExpBuffer(&buf);
5245 printfPQExpBuffer(&buf,
5246 "SELECT pg_catalog.pg_describe_object(classid, objid, 0) AS \"%s\"\n"
5247 "FROM pg_catalog.pg_depend\n"
5248 "WHERE refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass AND refobjid = '%s' AND deptype = 'e'\n"
5249 "ORDER BY 1;",
5250 gettext_noop("Object description"),
5251 oid);
5252
5253 res = PSQLexec(buf.data);
5254 termPQExpBuffer(&buf);
5255 if (!res)
5256 return false;
5257
5258 myopt.nullPrint = NULL;
5259 initPQExpBuffer(&title);
5260 printfPQExpBuffer(&title, _("Objects in extension \"%s\""), extname);
5261 myopt.title = title.data;
5262 myopt.translate_header = true;
5263
5264 printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
5265
5266 termPQExpBuffer(&title);
5267 PQclear(res);
5268 return true;
5269 }
5270
5271 /*
5272 * \dRp
5273 * Lists publications.
5274 *
5275 * Takes an optional regexp to select particular publications
5276 */
5277 bool
listPublications(const char * pattern)5278 listPublications(const char *pattern)
5279 {
5280 PQExpBufferData buf;
5281 PGresult *res;
5282 printQueryOpt myopt = pset.popt;
5283 static const bool translate_columns[] = {false, false, false, false, false, false, false};
5284
5285 if (pset.sversion < 100000)
5286 {
5287 char sverbuf[32];
5288
5289 psql_error("The server (version %s) does not support publications.\n",
5290 formatPGVersionNumber(pset.sversion, false,
5291 sverbuf, sizeof(sverbuf)));
5292 return true;
5293 }
5294
5295 initPQExpBuffer(&buf);
5296
5297 printfPQExpBuffer(&buf,
5298 "SELECT pubname AS \"%s\",\n"
5299 " pg_catalog.pg_get_userbyid(pubowner) AS \"%s\",\n"
5300 " puballtables AS \"%s\",\n"
5301 " pubinsert AS \"%s\",\n"
5302 " pubupdate AS \"%s\",\n"
5303 " pubdelete AS \"%s\"",
5304 gettext_noop("Name"),
5305 gettext_noop("Owner"),
5306 gettext_noop("All tables"),
5307 gettext_noop("Inserts"),
5308 gettext_noop("Updates"),
5309 gettext_noop("Deletes"));
5310 if (pset.sversion >= 110000)
5311 appendPQExpBuffer(&buf,
5312 ",\n pubtruncate AS \"%s\"",
5313 gettext_noop("Truncates"));
5314
5315 appendPQExpBufferStr(&buf,
5316 "\nFROM pg_catalog.pg_publication\n");
5317
5318 processSQLNamePattern(pset.db, &buf, pattern, false, false,
5319 NULL, "pubname", NULL,
5320 NULL);
5321
5322 appendPQExpBufferStr(&buf, "ORDER BY 1;");
5323
5324 res = PSQLexec(buf.data);
5325 termPQExpBuffer(&buf);
5326 if (!res)
5327 return false;
5328
5329 myopt.nullPrint = NULL;
5330 myopt.title = _("List of publications");
5331 myopt.translate_header = true;
5332 myopt.translate_columns = translate_columns;
5333 myopt.n_translate_columns = lengthof(translate_columns);
5334
5335 printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
5336
5337 PQclear(res);
5338
5339 return true;
5340 }
5341
5342 /*
5343 * \dRp+
5344 * Describes publications including the contents.
5345 *
5346 * Takes an optional regexp to select particular publications
5347 */
5348 bool
describePublications(const char * pattern)5349 describePublications(const char *pattern)
5350 {
5351 PQExpBufferData buf;
5352 int i;
5353 PGresult *res;
5354 bool has_pubtruncate;
5355
5356 if (pset.sversion < 100000)
5357 {
5358 char sverbuf[32];
5359
5360 psql_error("The server (version %s) does not support publications.\n",
5361 formatPGVersionNumber(pset.sversion, false,
5362 sverbuf, sizeof(sverbuf)));
5363 return true;
5364 }
5365
5366 has_pubtruncate = (pset.sversion >= 110000);
5367
5368 initPQExpBuffer(&buf);
5369
5370 printfPQExpBuffer(&buf,
5371 "SELECT oid, pubname,\n"
5372 " pg_catalog.pg_get_userbyid(pubowner) AS owner,\n"
5373 " puballtables, pubinsert, pubupdate, pubdelete");
5374 if (has_pubtruncate)
5375 appendPQExpBuffer(&buf,
5376 ", pubtruncate");
5377 appendPQExpBuffer(&buf,
5378 "\nFROM pg_catalog.pg_publication\n");
5379
5380 processSQLNamePattern(pset.db, &buf, pattern, false, false,
5381 NULL, "pubname", NULL,
5382 NULL);
5383
5384 appendPQExpBufferStr(&buf, "ORDER BY 2;");
5385
5386 res = PSQLexec(buf.data);
5387 if (!res)
5388 {
5389 termPQExpBuffer(&buf);
5390 return false;
5391 }
5392
5393 if (PQntuples(res) == 0)
5394 {
5395 if (!pset.quiet)
5396 {
5397 if (pattern)
5398 psql_error("Did not find any publication named \"%s\".\n",
5399 pattern);
5400 else
5401 psql_error("Did not find any publications.\n");
5402 }
5403
5404 termPQExpBuffer(&buf);
5405 PQclear(res);
5406 return false;
5407 }
5408
5409 for (i = 0; i < PQntuples(res); i++)
5410 {
5411 const char align = 'l';
5412 int ncols = 5;
5413 int nrows = 1;
5414 int tables = 0;
5415 PGresult *tabres;
5416 char *pubid = PQgetvalue(res, i, 0);
5417 char *pubname = PQgetvalue(res, i, 1);
5418 bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
5419 int j;
5420 PQExpBufferData title;
5421 printTableOpt myopt = pset.popt.topt;
5422 printTableContent cont;
5423
5424 if (has_pubtruncate)
5425 ncols++;
5426
5427 initPQExpBuffer(&title);
5428 printfPQExpBuffer(&title, _("Publication %s"), pubname);
5429 printTableInit(&cont, &myopt, title.data, ncols, nrows);
5430
5431 printTableAddHeader(&cont, gettext_noop("Owner"), true, align);
5432 printTableAddHeader(&cont, gettext_noop("All tables"), true, align);
5433 printTableAddHeader(&cont, gettext_noop("Inserts"), true, align);
5434 printTableAddHeader(&cont, gettext_noop("Updates"), true, align);
5435 printTableAddHeader(&cont, gettext_noop("Deletes"), true, align);
5436 if (has_pubtruncate)
5437 printTableAddHeader(&cont, gettext_noop("Truncates"), true, align);
5438
5439 printTableAddCell(&cont, PQgetvalue(res, i, 2), false, false);
5440 printTableAddCell(&cont, PQgetvalue(res, i, 3), false, false);
5441 printTableAddCell(&cont, PQgetvalue(res, i, 4), false, false);
5442 printTableAddCell(&cont, PQgetvalue(res, i, 5), false, false);
5443 printTableAddCell(&cont, PQgetvalue(res, i, 6), false, false);
5444 if (has_pubtruncate)
5445 printTableAddCell(&cont, PQgetvalue(res, i, 7), false, false);
5446
5447 if (!puballtables)
5448 {
5449 printfPQExpBuffer(&buf,
5450 "SELECT n.nspname, c.relname\n"
5451 "FROM pg_catalog.pg_class c,\n"
5452 " pg_catalog.pg_namespace n,\n"
5453 " pg_catalog.pg_publication_rel pr\n"
5454 "WHERE c.relnamespace = n.oid\n"
5455 " AND c.oid = pr.prrelid\n"
5456 " AND pr.prpubid = '%s'\n"
5457 "ORDER BY 1,2", pubid);
5458
5459 tabres = PSQLexec(buf.data);
5460 if (!tabres)
5461 {
5462 printTableCleanup(&cont);
5463 PQclear(res);
5464 termPQExpBuffer(&buf);
5465 termPQExpBuffer(&title);
5466 return false;
5467 }
5468 else
5469 tables = PQntuples(tabres);
5470
5471 if (tables > 0)
5472 printTableAddFooter(&cont, _("Tables:"));
5473
5474 for (j = 0; j < tables; j++)
5475 {
5476 printfPQExpBuffer(&buf, " \"%s.%s\"",
5477 PQgetvalue(tabres, j, 0),
5478 PQgetvalue(tabres, j, 1));
5479
5480 printTableAddFooter(&cont, buf.data);
5481 }
5482 PQclear(tabres);
5483 }
5484
5485 printTable(&cont, pset.queryFout, false, pset.logfile);
5486 printTableCleanup(&cont);
5487
5488 termPQExpBuffer(&title);
5489 }
5490
5491 termPQExpBuffer(&buf);
5492 PQclear(res);
5493
5494 return true;
5495 }
5496
5497 /*
5498 * \dRs
5499 * Describes subscriptions.
5500 *
5501 * Takes an optional regexp to select particular subscriptions
5502 */
5503 bool
describeSubscriptions(const char * pattern,bool verbose)5504 describeSubscriptions(const char *pattern, bool verbose)
5505 {
5506 PQExpBufferData buf;
5507 PGresult *res;
5508 printQueryOpt myopt = pset.popt;
5509 static const bool translate_columns[] = {false, false, false, false,
5510 false, false};
5511
5512 if (pset.sversion < 100000)
5513 {
5514 char sverbuf[32];
5515
5516 psql_error("The server (version %s) does not support subscriptions.\n",
5517 formatPGVersionNumber(pset.sversion, false,
5518 sverbuf, sizeof(sverbuf)));
5519 return true;
5520 }
5521
5522 initPQExpBuffer(&buf);
5523
5524 printfPQExpBuffer(&buf,
5525 "SELECT subname AS \"%s\"\n"
5526 ", pg_catalog.pg_get_userbyid(subowner) AS \"%s\"\n"
5527 ", subenabled AS \"%s\"\n"
5528 ", subpublications AS \"%s\"\n",
5529 gettext_noop("Name"),
5530 gettext_noop("Owner"),
5531 gettext_noop("Enabled"),
5532 gettext_noop("Publication"));
5533
5534 if (verbose)
5535 {
5536 appendPQExpBuffer(&buf,
5537 ", subsynccommit AS \"%s\"\n"
5538 ", subconninfo AS \"%s\"\n",
5539 gettext_noop("Synchronous commit"),
5540 gettext_noop("Conninfo"));
5541 }
5542
5543 /* Only display subscriptions in current database. */
5544 appendPQExpBufferStr(&buf,
5545 "FROM pg_catalog.pg_subscription\n"
5546 "WHERE subdbid = (SELECT oid\n"
5547 " FROM pg_catalog.pg_database\n"
5548 " WHERE datname = pg_catalog.current_database())");
5549
5550 processSQLNamePattern(pset.db, &buf, pattern, true, false,
5551 NULL, "subname", NULL,
5552 NULL);
5553
5554 appendPQExpBufferStr(&buf, "ORDER BY 1;");
5555
5556 res = PSQLexec(buf.data);
5557 termPQExpBuffer(&buf);
5558 if (!res)
5559 return false;
5560
5561 myopt.nullPrint = NULL;
5562 myopt.title = _("List of subscriptions");
5563 myopt.translate_header = true;
5564 myopt.translate_columns = translate_columns;
5565 myopt.n_translate_columns = lengthof(translate_columns);
5566
5567 printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
5568
5569 PQclear(res);
5570 return true;
5571 }
5572
5573 /*
5574 * printACLColumn
5575 *
5576 * Helper function for consistently formatting ACL (privilege) columns.
5577 * The proper targetlist entry is appended to buf. Note lack of any
5578 * whitespace or comma decoration.
5579 */
5580 static void
printACLColumn(PQExpBuffer buf,const char * colname)5581 printACLColumn(PQExpBuffer buf, const char *colname)
5582 {
5583 if (pset.sversion >= 80100)
5584 appendPQExpBuffer(buf,
5585 "pg_catalog.array_to_string(%s, E'\\n') AS \"%s\"",
5586 colname, gettext_noop("Access privileges"));
5587 else
5588 appendPQExpBuffer(buf,
5589 "pg_catalog.array_to_string(%s, '\\n') AS \"%s\"",
5590 colname, gettext_noop("Access privileges"));
5591 }
5592