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