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