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