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