PostgreSQL Source Code  git master
describe.c
Go to the documentation of this file.
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-2020, 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_am.h"
18 #include "catalog/pg_attribute_d.h"
19 #include "catalog/pg_cast_d.h"
20 #include "catalog/pg_class_d.h"
21 #include "catalog/pg_default_acl_d.h"
22 #include "common.h"
23 #include "common/logging.h"
24 #include "describe.h"
25 #include "fe_utils/mbprint.h"
26 #include "fe_utils/print.h"
27 #include "fe_utils/string_utils.h"
28 #include "settings.h"
29 #include "variables.h"
30 
31 static bool describeOneTableDetails(const char *schemaname,
32  const char *relationname,
33  const char *oid,
34  bool verbose);
35 static void add_tablespace_footer(printTableContent *const cont, char relkind,
36  Oid tablespace, const bool newline);
37 static void add_role_attribute(PQExpBuffer buf, const char *const str);
38 static bool listTSParsersVerbose(const char *pattern);
39 static bool describeOneTSParser(const char *oid, const char *nspname,
40  const char *prsname);
41 static bool listTSConfigsVerbose(const char *pattern);
42 static bool describeOneTSConfig(const char *oid, const char *nspname,
43  const char *cfgname,
44  const char *pnspname, const char *prsname);
45 static void printACLColumn(PQExpBuffer buf, const char *colname);
46 static bool listOneExtensionContents(const char *extname, const char *oid);
47 
48 
49 /*----------------
50  * Handlers for various slash commands displaying some sort of list
51  * of things in the database.
52  *
53  * Note: try to format the queries to look nice in -E output.
54  *----------------
55  */
56 
57 
58 /*
59  * \da
60  * Takes an optional regexp to select particular aggregates
61  */
62 bool
63 describeAggregates(const char *pattern, bool verbose, bool showSystem)
64 {
66  PGresult *res;
67  printQueryOpt myopt = pset.popt;
68 
69  initPQExpBuffer(&buf);
70 
71  printfPQExpBuffer(&buf,
72  "SELECT n.nspname as \"%s\",\n"
73  " p.proname AS \"%s\",\n"
74  " pg_catalog.format_type(p.prorettype, NULL) AS \"%s\",\n",
75  gettext_noop("Schema"),
76  gettext_noop("Name"),
77  gettext_noop("Result data type"));
78 
79  if (pset.sversion >= 80400)
80  appendPQExpBuffer(&buf,
81  " CASE WHEN p.pronargs = 0\n"
82  " THEN CAST('*' AS pg_catalog.text)\n"
83  " ELSE pg_catalog.pg_get_function_arguments(p.oid)\n"
84  " END AS \"%s\",\n",
85  gettext_noop("Argument data types"));
86  else if (pset.sversion >= 80200)
87  appendPQExpBuffer(&buf,
88  " CASE WHEN p.pronargs = 0\n"
89  " THEN CAST('*' AS pg_catalog.text)\n"
90  " ELSE\n"
91  " pg_catalog.array_to_string(ARRAY(\n"
92  " SELECT\n"
93  " pg_catalog.format_type(p.proargtypes[s.i], NULL)\n"
94  " FROM\n"
95  " pg_catalog.generate_series(0, pg_catalog.array_upper(p.proargtypes, 1)) AS s(i)\n"
96  " ), ', ')\n"
97  " END AS \"%s\",\n",
98  gettext_noop("Argument data types"));
99  else
100  appendPQExpBuffer(&buf,
101  " pg_catalog.format_type(p.proargtypes[0], NULL) AS \"%s\",\n",
102  gettext_noop("Argument data types"));
103 
104  if (pset.sversion >= 110000)
105  appendPQExpBuffer(&buf,
106  " pg_catalog.obj_description(p.oid, 'pg_proc') as \"%s\"\n"
107  "FROM pg_catalog.pg_proc p\n"
108  " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace\n"
109  "WHERE p.prokind = 'a'\n",
110  gettext_noop("Description"));
111  else
112  appendPQExpBuffer(&buf,
113  " pg_catalog.obj_description(p.oid, 'pg_proc') as \"%s\"\n"
114  "FROM pg_catalog.pg_proc p\n"
115  " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace\n"
116  "WHERE p.proisagg\n",
117  gettext_noop("Description"));
118 
119  if (!showSystem && !pattern)
120  appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
121  " AND n.nspname <> 'information_schema'\n");
122 
123  processSQLNamePattern(pset.db, &buf, pattern, true, false,
124  "n.nspname", "p.proname", NULL,
125  "pg_catalog.pg_function_is_visible(p.oid)");
126 
127  appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 4;");
128 
129  res = PSQLexec(buf.data);
130  termPQExpBuffer(&buf);
131  if (!res)
132  return false;
133 
134  myopt.nullPrint = NULL;
135  myopt.title = _("List of aggregate functions");
136  myopt.translate_header = true;
137 
138  printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
139 
140  PQclear(res);
141  return true;
142 }
143 
144 /*
145  * \dA
146  * Takes an optional regexp to select particular access methods
147  */
148 bool
149 describeAccessMethods(const char *pattern, bool verbose)
150 {
152  PGresult *res;
153  printQueryOpt myopt = pset.popt;
154  static const bool translate_columns[] = {false, true, false, false};
155 
156  if (pset.sversion < 90600)
157  {
158  char sverbuf[32];
159 
160  pg_log_error("The server (version %s) does not support access methods.",
162  sverbuf, sizeof(sverbuf)));
163  return true;
164  }
165 
166  initPQExpBuffer(&buf);
167 
168  printfPQExpBuffer(&buf,
169  "SELECT amname AS \"%s\",\n"
170  " CASE amtype"
171  " WHEN 'i' THEN '%s'"
172  " WHEN 't' THEN '%s'"
173  " END AS \"%s\"",
174  gettext_noop("Name"),
175  gettext_noop("Index"),
176  gettext_noop("Table"),
177  gettext_noop("Type"));
178 
179  if (verbose)
180  {
181  appendPQExpBuffer(&buf,
182  ",\n amhandler AS \"%s\",\n"
183  " pg_catalog.obj_description(oid, 'pg_am') AS \"%s\"",
184  gettext_noop("Handler"),
185  gettext_noop("Description"));
186  }
187 
189  "\nFROM pg_catalog.pg_am\n");
190 
191  processSQLNamePattern(pset.db, &buf, pattern, false, false,
192  NULL, "amname", NULL,
193  NULL);
194 
195  appendPQExpBufferStr(&buf, "ORDER BY 1;");
196 
197  res = PSQLexec(buf.data);
198  termPQExpBuffer(&buf);
199  if (!res)
200  return false;
201 
202  myopt.nullPrint = NULL;
203  myopt.title = _("List of access methods");
204  myopt.translate_header = true;
205  myopt.translate_columns = translate_columns;
206  myopt.n_translate_columns = lengthof(translate_columns);
207 
208  printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
209 
210  PQclear(res);
211  return true;
212 }
213 
214 /*
215  * \db
216  * Takes an optional regexp to select particular tablespaces
217  */
218 bool
219 describeTablespaces(const char *pattern, bool verbose)
220 {
222  PGresult *res;
223  printQueryOpt myopt = pset.popt;
224 
225  if (pset.sversion < 80000)
226  {
227  char sverbuf[32];
228 
229  pg_log_info("The server (version %s) does not support tablespaces.",
231  sverbuf, sizeof(sverbuf)));
232  return true;
233  }
234 
235  initPQExpBuffer(&buf);
236 
237  if (pset.sversion >= 90200)
238  printfPQExpBuffer(&buf,
239  "SELECT spcname AS \"%s\",\n"
240  " pg_catalog.pg_get_userbyid(spcowner) AS \"%s\",\n"
241  " pg_catalog.pg_tablespace_location(oid) AS \"%s\"",
242  gettext_noop("Name"),
243  gettext_noop("Owner"),
244  gettext_noop("Location"));
245  else
246  printfPQExpBuffer(&buf,
247  "SELECT spcname AS \"%s\",\n"
248  " pg_catalog.pg_get_userbyid(spcowner) AS \"%s\",\n"
249  " spclocation AS \"%s\"",
250  gettext_noop("Name"),
251  gettext_noop("Owner"),
252  gettext_noop("Location"));
253 
254  if (verbose)
255  {
256  appendPQExpBufferStr(&buf, ",\n ");
257  printACLColumn(&buf, "spcacl");
258  }
259 
260  if (verbose && pset.sversion >= 90000)
261  appendPQExpBuffer(&buf,
262  ",\n spcoptions AS \"%s\"",
263  gettext_noop("Options"));
264 
265  if (verbose && pset.sversion >= 90200)
266  appendPQExpBuffer(&buf,
267  ",\n pg_catalog.pg_size_pretty(pg_catalog.pg_tablespace_size(oid)) AS \"%s\"",
268  gettext_noop("Size"));
269 
270  if (verbose && pset.sversion >= 80200)
271  appendPQExpBuffer(&buf,
272  ",\n pg_catalog.shobj_description(oid, 'pg_tablespace') AS \"%s\"",
273  gettext_noop("Description"));
274 
276  "\nFROM pg_catalog.pg_tablespace\n");
277 
278  processSQLNamePattern(pset.db, &buf, pattern, false, false,
279  NULL, "spcname", NULL,
280  NULL);
281 
282  appendPQExpBufferStr(&buf, "ORDER BY 1;");
283 
284  res = PSQLexec(buf.data);
285  termPQExpBuffer(&buf);
286  if (!res)
287  return false;
288 
289  myopt.nullPrint = NULL;
290  myopt.title = _("List of tablespaces");
291  myopt.translate_header = true;
292 
293  printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
294 
295  PQclear(res);
296  return true;
297 }
298 
299 
300 /*
301  * \df
302  * Takes an optional regexp to select particular functions.
303  *
304  * As with \d, you can specify the kinds of functions you want:
305  *
306  * a for aggregates
307  * n for normal
308  * p for procedure
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;
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',
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',
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 
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)
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 ");
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 {
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  {
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)
702  " ORDER BY e.enumsortorder\n");
703  else
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 {
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;
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"));
899  "\nFROM pg_catalog.pg_database d\n");
900  if (verbose && pset.sversion >= 80000)
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 {
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 {
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.",
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 {
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 {
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;
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;
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 
2105  if (tableinfo.ispartition)
2106  {
2107  /* Footer information for a partition child table */
2108  PGresult *result;
2109 
2110  printfPQExpBuffer(&buf,
2111  "SELECT inhparent::pg_catalog.regclass,\n"
2112  " pg_catalog.pg_get_expr(c.relpartbound, c.oid)");
2113  /* If verbose, also request the partition constraint definition */
2114  if (verbose)
2115  appendPQExpBufferStr(&buf,
2116  ",\n pg_catalog.pg_get_partition_constraintdef(c.oid)");
2117  appendPQExpBuffer(&buf,
2118  "\nFROM pg_catalog.pg_class c"
2119  " JOIN pg_catalog.pg_inherits i"
2120  " ON c.oid = inhrelid"
2121  "\nWHERE c.oid = '%s';", oid);
2122  result = PSQLexec(buf.data);
2123  if (!result)
2124  goto error_return;
2125 
2126  if (PQntuples(result) > 0)
2127  {
2128  char *parent_name = PQgetvalue(result, 0, 0);
2129  char *partdef = PQgetvalue(result, 0, 1);
2130 
2131  printfPQExpBuffer(&tmpbuf, _("Partition of: %s %s"), parent_name,
2132  partdef);
2133  printTableAddFooter(&cont, tmpbuf.data);
2134 
2135  if (verbose)
2136  {
2137  char *partconstraintdef = NULL;
2138 
2139  if (!PQgetisnull(result, 0, 2))
2140  partconstraintdef = PQgetvalue(result, 0, 2);
2141  /* If there isn't any constraint, show that explicitly */
2142  if (partconstraintdef == NULL || partconstraintdef[0] == '\0')
2143  printfPQExpBuffer(&tmpbuf, _("No partition constraint"));
2144  else
2145  printfPQExpBuffer(&tmpbuf, _("Partition constraint: %s"),
2146  partconstraintdef);
2147  printTableAddFooter(&cont, tmpbuf.data);
2148  }
2149  }
2150  PQclear(result);
2151  }
2152 
2153  if (tableinfo.relkind == RELKIND_PARTITIONED_TABLE)
2154  {
2155  /* Footer information for a partitioned table (partitioning parent) */
2156  PGresult *result;
2157 
2158  printfPQExpBuffer(&buf,
2159  "SELECT pg_catalog.pg_get_partkeydef('%s'::pg_catalog.oid);",
2160  oid);
2161  result = PSQLexec(buf.data);
2162  if (!result)
2163  goto error_return;
2164 
2165  if (PQntuples(result) == 1)
2166  {
2167  char *partkeydef = PQgetvalue(result, 0, 0);
2168 
2169  printfPQExpBuffer(&tmpbuf, _("Partition key: %s"), partkeydef);
2170  printTableAddFooter(&cont, tmpbuf.data);
2171  }
2172  PQclear(result);
2173  }
2174 
2175  if (tableinfo.relkind == RELKIND_TOASTVALUE)
2176  {
2177  /* For a TOAST table, print name of owning table */
2178  PGresult *result;
2179 
2180  printfPQExpBuffer(&buf,
2181  "SELECT n.nspname, c.relname\n"
2182  "FROM pg_catalog.pg_class c"
2183  " JOIN pg_catalog.pg_namespace n"
2184  " ON n.oid = c.relnamespace\n"
2185  "WHERE reltoastrelid = '%s';", oid);
2186  result = PSQLexec(buf.data);
2187  if (!result)
2188  goto error_return;
2189 
2190  if (PQntuples(result) == 1)
2191  {
2192  char *schemaname = PQgetvalue(result, 0, 0);
2193  char *relname = PQgetvalue(result, 0, 1);
2194 
2195  printfPQExpBuffer(&tmpbuf, _("Owning table: \"%s.%s\""),
2196  schemaname, relname);
2197  printTableAddFooter(&cont, tmpbuf.data);
2198  }
2199  PQclear(result);
2200  }
2201 
2202  if (tableinfo.relkind == RELKIND_INDEX ||
2203  tableinfo.relkind == RELKIND_PARTITIONED_INDEX)
2204  {
2205  /* Footer information about an index */
2206  PGresult *result;
2207 
2208  printfPQExpBuffer(&buf,
2209  "SELECT i.indisunique, i.indisprimary, i.indisclustered, ");
2210  if (pset.sversion >= 80200)
2211  appendPQExpBufferStr(&buf, "i.indisvalid,\n");
2212  else
2213  appendPQExpBufferStr(&buf, "true AS indisvalid,\n");
2214  if (pset.sversion >= 90000)
2215  appendPQExpBufferStr(&buf,
2216  " (NOT i.indimmediate) AND "
2217  "EXISTS (SELECT 1 FROM pg_catalog.pg_constraint "
2218  "WHERE conrelid = i.indrelid AND "
2219  "conindid = i.indexrelid AND "
2220  "contype IN ('p','u','x') AND "
2221  "condeferrable) AS condeferrable,\n"
2222  " (NOT i.indimmediate) AND "
2223  "EXISTS (SELECT 1 FROM pg_catalog.pg_constraint "
2224  "WHERE conrelid = i.indrelid AND "
2225  "conindid = i.indexrelid AND "
2226  "contype IN ('p','u','x') AND "
2227  "condeferred) AS condeferred,\n");
2228  else
2229  appendPQExpBufferStr(&buf,
2230  " false AS condeferrable, false AS condeferred,\n");
2231 
2232  if (pset.sversion >= 90400)
2233  appendPQExpBufferStr(&buf, "i.indisreplident,\n");
2234  else
2235  appendPQExpBufferStr(&buf, "false AS indisreplident,\n");
2236 
2237  appendPQExpBuffer(&buf, " a.amname, c2.relname, "
2238  "pg_catalog.pg_get_expr(i.indpred, i.indrelid, true)\n"
2239  "FROM pg_catalog.pg_index i, pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_am a\n"
2240  "WHERE i.indexrelid = c.oid AND c.oid = '%s' AND c.relam = a.oid\n"
2241  "AND i.indrelid = c2.oid;",
2242  oid);
2243 
2244  result = PSQLexec(buf.data);
2245  if (!result)
2246  goto error_return;
2247  else if (PQntuples(result) != 1)
2248  {
2249  PQclear(result);
2250  goto error_return;
2251  }
2252  else
2253  {
2254  char *indisunique = PQgetvalue(result, 0, 0);
2255  char *indisprimary = PQgetvalue(result, 0, 1);
2256  char *indisclustered = PQgetvalue(result, 0, 2);
2257  char *indisvalid = PQgetvalue(result, 0, 3);
2258  char *deferrable = PQgetvalue(result, 0, 4);
2259  char *deferred = PQgetvalue(result, 0, 5);
2260  char *indisreplident = PQgetvalue(result, 0, 6);
2261  char *indamname = PQgetvalue(result, 0, 7);
2262  char *indtable = PQgetvalue(result, 0, 8);
2263  char *indpred = PQgetvalue(result, 0, 9);
2264 
2265  if (strcmp(indisprimary, "t") == 0)
2266  printfPQExpBuffer(&tmpbuf, _("primary key, "));
2267  else if (strcmp(indisunique, "t") == 0)
2268  printfPQExpBuffer(&tmpbuf, _("unique, "));
2269  else
2270  resetPQExpBuffer(&tmpbuf);
2271  appendPQExpBuffer(&tmpbuf, "%s, ", indamname);
2272 
2273  /* we assume here that index and table are in same schema */
2274  appendPQExpBuffer(&tmpbuf, _("for table \"%s.%s\""),
2275  schemaname, indtable);
2276 
2277  if (strlen(indpred))
2278  appendPQExpBuffer(&tmpbuf, _(", predicate (%s)"), indpred);
2279 
2280  if (strcmp(indisclustered, "t") == 0)
2281  appendPQExpBufferStr(&tmpbuf, _(", clustered"));
2282 
2283  if (strcmp(indisvalid, "t") != 0)
2284  appendPQExpBufferStr(&tmpbuf, _(", invalid"));
2285 
2286  if (strcmp(deferrable, "t") == 0)
2287  appendPQExpBufferStr(&tmpbuf, _(", deferrable"));
2288 
2289  if (strcmp(deferred, "t") == 0)
2290  appendPQExpBufferStr(&tmpbuf, _(", initially deferred"));
2291 
2292  if (strcmp(indisreplident, "t") == 0)
2293  appendPQExpBufferStr(&tmpbuf, _(", replica identity"));
2294 
2295  printTableAddFooter(&cont, tmpbuf.data);
2296 
2297  /*
2298  * If it's a partitioned index, we'll print the tablespace below
2299  */
2300  if (tableinfo.relkind == RELKIND_INDEX)
2301  add_tablespace_footer(&cont, tableinfo.relkind,
2302  tableinfo.tablespace, true);
2303  }
2304 
2305  PQclear(result);
2306  }
2307  /* If you add relkinds here, see also "Finish printing..." stanza below */
2308  else if (tableinfo.relkind == RELKIND_RELATION ||
2309  tableinfo.relkind == RELKIND_MATVIEW ||
2310  tableinfo.relkind == RELKIND_FOREIGN_TABLE ||
2311  tableinfo.relkind == RELKIND_PARTITIONED_TABLE ||
2312  tableinfo.relkind == RELKIND_PARTITIONED_INDEX ||
2313  tableinfo.relkind == RELKIND_TOASTVALUE)
2314  {
2315  /* Footer information about a table */
2316  PGresult *result = NULL;
2317  int tuples = 0;
2318 
2319  /* print indexes */
2320  if (tableinfo.hasindex)
2321  {
2322  printfPQExpBuffer(&buf,
2323  "SELECT c2.relname, i.indisprimary, i.indisunique, i.indisclustered, ");
2324  if (pset.sversion >= 80200)
2325  appendPQExpBufferStr(&buf, "i.indisvalid, ");
2326  else
2327  appendPQExpBufferStr(&buf, "true as indisvalid, ");
2328  appendPQExpBufferStr(&buf, "pg_catalog.pg_get_indexdef(i.indexrelid, 0, true),\n ");
2329  if (pset.sversion >= 90000)
2330  appendPQExpBufferStr(&buf,
2331  "pg_catalog.pg_get_constraintdef(con.oid, true), "
2332  "contype, condeferrable, condeferred");
2333  else
2334  appendPQExpBufferStr(&buf,
2335  "null AS constraintdef, null AS contype, "
2336  "false AS condeferrable, false AS condeferred");
2337  if (pset.sversion >= 90400)
2338  appendPQExpBufferStr(&buf, ", i.indisreplident");
2339  else
2340  appendPQExpBufferStr(&buf, ", false AS indisreplident");
2341  if (pset.sversion >= 80000)
2342  appendPQExpBufferStr(&buf, ", c2.reltablespace");
2343  appendPQExpBufferStr(&buf,
2344  "\nFROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i\n");
2345  if (pset.sversion >= 90000)
2346  appendPQExpBufferStr(&buf,
2347  " LEFT JOIN pg_catalog.pg_constraint con ON (conrelid = i.indrelid AND conindid = i.indexrelid AND contype IN ('p','u','x'))\n");
2348  appendPQExpBuffer(&buf,
2349  "WHERE c.oid = '%s' AND c.oid = i.indrelid AND i.indexrelid = c2.oid\n"
2350  "ORDER BY i.indisprimary DESC, c2.relname;",
2351  oid);
2352  result = PSQLexec(buf.data);
2353  if (!result)
2354  goto error_return;
2355  else
2356  tuples = PQntuples(result);
2357 
2358  if (tuples > 0)
2359  {
2360  printTableAddFooter(&cont, _("Indexes:"));
2361  for (i = 0; i < tuples; i++)
2362  {
2363  /* untranslated index name */
2364  printfPQExpBuffer(&buf, " \"%s\"",
2365  PQgetvalue(result, i, 0));
2366 
2367  /* If exclusion constraint, print the constraintdef */
2368  if (strcmp(PQgetvalue(result, i, 7), "x") == 0)
2369  {
2370  appendPQExpBuffer(&buf, " %s",
2371  PQgetvalue(result, i, 6));
2372  }
2373  else
2374  {
2375  const char *indexdef;
2376  const char *usingpos;
2377 
2378  /* Label as primary key or unique (but not both) */
2379  if (strcmp(PQgetvalue(result, i, 1), "t") == 0)
2380  appendPQExpBufferStr(&buf, " PRIMARY KEY,");
2381  else if (strcmp(PQgetvalue(result, i, 2), "t") == 0)
2382  {
2383  if (strcmp(PQgetvalue(result, i, 7), "u") == 0)
2384  appendPQExpBufferStr(&buf, " UNIQUE CONSTRAINT,");
2385  else
2386  appendPQExpBufferStr(&buf, " UNIQUE,");
2387  }
2388 
2389  /* Everything after "USING" is echoed verbatim */
2390  indexdef = PQgetvalue(result, i, 5);
2391  usingpos = strstr(indexdef, " USING ");
2392  if (usingpos)
2393  indexdef = usingpos + 7;
2394  appendPQExpBuffer(&buf, " %s", indexdef);
2395 
2396  /* Need these for deferrable PK/UNIQUE indexes */
2397  if (strcmp(PQgetvalue(result, i, 8), "t") == 0)
2398  appendPQExpBufferStr(&buf, " DEFERRABLE");
2399 
2400  if (strcmp(PQgetvalue(result, i, 9), "t") == 0)
2401  appendPQExpBufferStr(&buf, " INITIALLY DEFERRED");
2402  }
2403 
2404  /* Add these for all cases */
2405  if (strcmp(PQgetvalue(result, i, 3), "t") == 0)
2406  appendPQExpBufferStr(&buf, " CLUSTER");
2407 
2408  if (strcmp(PQgetvalue(result, i, 4), "t") != 0)
2409  appendPQExpBufferStr(&buf, " INVALID");
2410 
2411  if (strcmp(PQgetvalue(result, i, 10), "t") == 0)
2412  appendPQExpBufferStr(&buf, " REPLICA IDENTITY");
2413 
2414  printTableAddFooter(&cont, buf.data);
2415 
2416  /* Print tablespace of the index on the same line */
2417  if (pset.sversion >= 80000)
2418  add_tablespace_footer(&cont, RELKIND_INDEX,
2419  atooid(PQgetvalue(result, i, 11)),
2420  false);
2421  }
2422  }
2423  PQclear(result);
2424  }
2425 
2426  /* print table (and column) check constraints */
2427  if (tableinfo.checks)
2428  {
2429  printfPQExpBuffer(&buf,
2430  "SELECT r.conname, "
2431  "pg_catalog.pg_get_constraintdef(r.oid, true)\n"
2432  "FROM pg_catalog.pg_constraint r\n"
2433  "WHERE r.conrelid = '%s' AND r.contype = 'c'\n"
2434  "ORDER BY 1;",
2435  oid);
2436  result = PSQLexec(buf.data);
2437  if (!result)
2438  goto error_return;
2439  else
2440  tuples = PQntuples(result);
2441 
2442  if (tuples > 0)
2443  {
2444  printTableAddFooter(&cont, _("Check constraints:"));
2445  for (i = 0; i < tuples; i++)
2446  {
2447  /* untranslated constraint name and def */
2448  printfPQExpBuffer(&buf, " \"%s\" %s",
2449  PQgetvalue(result, i, 0),
2450  PQgetvalue(result, i, 1));
2451 
2452  printTableAddFooter(&cont, buf.data);
2453  }
2454  }
2455  PQclear(result);
2456  }
2457 
2458  /*
2459  * Print foreign-key constraints (there are none if no triggers,
2460  * except if the table is partitioned, in which case the triggers
2461  * appear in the partitions)
2462  */
2463  if (tableinfo.hastriggers ||
2464  tableinfo.relkind == RELKIND_PARTITIONED_TABLE)
2465  {
2466  if (pset.sversion >= 120000 &&
2467  (tableinfo.ispartition || tableinfo.relkind == RELKIND_PARTITIONED_TABLE))
2468  {
2469  /*
2470  * Put the constraints defined in this table first, followed
2471  * by the constraints defined in ancestor partitioned tables.
2472  */
2473  printfPQExpBuffer(&buf,
2474  "SELECT conrelid = '%s'::pg_catalog.regclass AS sametable,\n"
2475  " conname,\n"
2476  " pg_catalog.pg_get_constraintdef(oid, true) AS condef,\n"
2477  " conrelid::pg_catalog.regclass AS ontable\n"
2478  " FROM pg_catalog.pg_constraint,\n"
2479  " pg_catalog.pg_partition_ancestors('%s')\n"
2480  " WHERE conrelid = relid AND contype = 'f' AND conparentid = 0\n"
2481  "ORDER BY sametable DESC, conname;",
2482  oid, oid);
2483  }
2484  else
2485  {
2486  printfPQExpBuffer(&buf,
2487  "SELECT true as sametable, conname,\n"
2488  " pg_catalog.pg_get_constraintdef(r.oid, true) as condef,\n"
2489  " conrelid::pg_catalog.regclass AS ontable\n"
2490  "FROM pg_catalog.pg_constraint r\n"
2491  "WHERE r.conrelid = '%s' AND r.contype = 'f'\n",
2492  oid);
2493 
2494  if (pset.sversion >= 120000)
2495  appendPQExpBufferStr(&buf, " AND conparentid = 0\n");
2496  appendPQExpBufferStr(&buf, "ORDER BY conname");
2497  }
2498 
2499  result = PSQLexec(buf.data);
2500  if (!result)
2501  goto error_return;
2502  else
2503  tuples = PQntuples(result);
2504 
2505  if (tuples > 0)
2506  {
2507  int i_sametable = PQfnumber(result, "sametable"),
2508  i_conname = PQfnumber(result, "conname"),
2509  i_condef = PQfnumber(result, "condef"),
2510  i_ontable = PQfnumber(result, "ontable");
2511 
2512  printTableAddFooter(&cont, _("Foreign-key constraints:"));
2513  for (i = 0; i < tuples; i++)
2514  {
2515  /*
2516  * Print untranslated constraint name and definition. Use
2517  * a "TABLE tab" prefix when the constraint is defined in
2518  * a parent partitioned table.
2519  */
2520  if (strcmp(PQgetvalue(result, i, i_sametable), "f") == 0)
2521  printfPQExpBuffer(&buf, " TABLE \"%s\" CONSTRAINT \"%s\" %s",
2522  PQgetvalue(result, i, i_ontable),
2523  PQgetvalue(result, i, i_conname),
2524  PQgetvalue(result, i, i_condef));
2525  else
2526  printfPQExpBuffer(&buf, " \"%s\" %s",
2527  PQgetvalue(result, i, i_conname),
2528  PQgetvalue(result, i, i_condef));
2529 
2530  printTableAddFooter(&cont, buf.data);
2531  }
2532  }
2533  PQclear(result);
2534  }
2535 
2536  /* print incoming foreign-key references */
2537  if (tableinfo.hastriggers ||
2538  tableinfo.relkind == RELKIND_PARTITIONED_TABLE)
2539  {
2540  if (pset.sversion >= 120000)
2541  {
2542  printfPQExpBuffer(&buf,
2543  "SELECT conname, conrelid::pg_catalog.regclass AS ontable,\n"
2544  " pg_catalog.pg_get_constraintdef(oid, true) AS condef\n"
2545  " FROM pg_catalog.pg_constraint c\n"
2546  " WHERE confrelid IN (SELECT pg_catalog.pg_partition_ancestors('%s')\n"
2547  " UNION ALL VALUES ('%s'::pg_catalog.regclass))\n"
2548  " AND contype = 'f' AND conparentid = 0\n"
2549  "ORDER BY conname;",
2550  oid, oid);
2551  }
2552  else
2553  {
2554  printfPQExpBuffer(&buf,
2555  "SELECT conname, conrelid::pg_catalog.regclass AS ontable,\n"
2556  " pg_catalog.pg_get_constraintdef(oid, true) AS condef\n"
2557  " FROM pg_catalog.pg_constraint\n"
2558  " WHERE confrelid = %s AND contype = 'f'\n"
2559  "ORDER BY conname;",
2560  oid);
2561  }
2562 
2563  result = PSQLexec(buf.data);
2564  if (!result)
2565  goto error_return;
2566  else
2567  tuples = PQntuples(result);
2568 
2569  if (tuples > 0)
2570  {
2571  int i_conname = PQfnumber(result, "conname"),
2572  i_ontable = PQfnumber(result, "ontable"),
2573  i_condef = PQfnumber(result, "condef");
2574 
2575  printTableAddFooter(&cont, _("Referenced by:"));
2576  for (i = 0; i < tuples; i++)
2577  {
2578  printfPQExpBuffer(&buf, " TABLE \"%s\" CONSTRAINT \"%s\" %s",
2579  PQgetvalue(result, i, i_ontable),
2580  PQgetvalue(result, i, i_conname),
2581  PQgetvalue(result, i, i_condef));
2582 
2583  printTableAddFooter(&cont, buf.data);
2584  }
2585  }
2586  PQclear(result);
2587  }
2588 
2589  /* print any row-level policies */
2590  if (pset.sversion >= 90500)
2591  {
2592  printfPQExpBuffer(&buf, "SELECT pol.polname,");
2593  if (pset.sversion >= 100000)
2594  appendPQExpBufferStr(&buf,
2595  " pol.polpermissive,\n");
2596  else
2597  appendPQExpBufferStr(&buf,
2598  " 't' as polpermissive,\n");
2599  appendPQExpBuffer(&buf,
2600  " 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"
2601  " pg_catalog.pg_get_expr(pol.polqual, pol.polrelid),\n"
2602  " pg_catalog.pg_get_expr(pol.polwithcheck, pol.polrelid),\n"
2603  " CASE pol.polcmd\n"
2604  " WHEN 'r' THEN 'SELECT'\n"
2605  " WHEN 'a' THEN 'INSERT'\n"
2606  " WHEN 'w' THEN 'UPDATE'\n"
2607  " WHEN 'd' THEN 'DELETE'\n"
2608  " END AS cmd\n"
2609  "FROM pg_catalog.pg_policy pol\n"
2610  "WHERE pol.polrelid = '%s' ORDER BY 1;",
2611  oid);
2612 
2613  result = PSQLexec(buf.data);
2614  if (!result)
2615  goto error_return;
2616  else
2617  tuples = PQntuples(result);
2618 
2619  /*
2620  * Handle cases where RLS is enabled and there are policies, or
2621  * there aren't policies, or RLS isn't enabled but there are
2622  * policies
2623  */
2624  if (tableinfo.rowsecurity && !tableinfo.forcerowsecurity && tuples > 0)
2625  printTableAddFooter(&cont, _("Policies:"));
2626 
2627  if (tableinfo.rowsecurity && tableinfo.forcerowsecurity && tuples > 0)
2628  printTableAddFooter(&cont, _("Policies (forced row security enabled):"));
2629 
2630  if (tableinfo.rowsecurity && !tableinfo.forcerowsecurity && tuples == 0)
2631  printTableAddFooter(&cont, _("Policies (row security enabled): (none)"));
2632 
2633  if (tableinfo.rowsecurity && tableinfo.forcerowsecurity && tuples == 0)
2634  printTableAddFooter(&cont, _("Policies (forced row security enabled): (none)"));
2635 
2636  if (!tableinfo.rowsecurity && tuples > 0)
2637  printTableAddFooter(&cont, _("Policies (row security disabled):"));
2638 
2639  /* Might be an empty set - that's ok */
2640  for (i = 0; i < tuples; i++)
2641  {
2642  printfPQExpBuffer(&buf, " POLICY \"%s\"",
2643  PQgetvalue(result, i, 0));
2644 
2645  if (*(PQgetvalue(result, i, 1)) == 'f')
2646  appendPQExpBufferStr(&buf, " AS RESTRICTIVE");
2647 
2648  if (!PQgetisnull(result, i, 5))
2649  appendPQExpBuffer(&buf, " FOR %s",
2650  PQgetvalue(result, i, 5));
2651 
2652  if (!PQgetisnull(result, i, 2))
2653  {
2654  appendPQExpBuffer(&buf, "\n TO %s",
2655  PQgetvalue(result, i, 2));
2656  }
2657 
2658  if (!PQgetisnull(result, i, 3))
2659  appendPQExpBuffer(&buf, "\n USING (%s)",
2660  PQgetvalue(result, i, 3));
2661 
2662  if (!PQgetisnull(result, i, 4))
2663  appendPQExpBuffer(&buf, "\n WITH CHECK (%s)",
2664  PQgetvalue(result, i, 4));
2665 
2666  printTableAddFooter(&cont, buf.data);
2667 
2668  }
2669  PQclear(result);
2670  }
2671 
2672  /* print any extended statistics */
2673  if (pset.sversion >= 100000)
2674  {
2675  printfPQExpBuffer(&buf,
2676  "SELECT oid, "
2677  "stxrelid::pg_catalog.regclass, "
2678  "stxnamespace::pg_catalog.regnamespace AS nsp, "
2679  "stxname,\n"
2680  " (SELECT pg_catalog.string_agg(pg_catalog.quote_ident(attname),', ')\n"
2681  " FROM pg_catalog.unnest(stxkeys) s(attnum)\n"
2682  " JOIN pg_catalog.pg_attribute a ON (stxrelid = a.attrelid AND\n"
2683  " a.attnum = s.attnum AND NOT attisdropped)) AS columns,\n"
2684  " 'd' = any(stxkind) AS ndist_enabled,\n"
2685  " 'f' = any(stxkind) AS deps_enabled,\n"
2686  " 'm' = any(stxkind) AS mcv_enabled\n"
2687  "FROM pg_catalog.pg_statistic_ext stat "
2688  "WHERE stxrelid = '%s'\n"
2689  "ORDER BY 1;",
2690  oid);
2691 
2692  result = PSQLexec(buf.data);
2693  if (!result)
2694  goto error_return;
2695  else
2696  tuples = PQntuples(result);
2697 
2698  if (tuples > 0)
2699  {
2700  printTableAddFooter(&cont, _("Statistics objects:"));
2701 
2702  for (i = 0; i < tuples; i++)
2703  {
2704  bool gotone = false;
2705 
2706  printfPQExpBuffer(&buf, " ");
2707 
2708  /* statistics object name (qualified with namespace) */
2709  appendPQExpBuffer(&buf, "\"%s\".\"%s\" (",
2710  PQgetvalue(result, i, 2),
2711  PQgetvalue(result, i, 3));
2712 
2713  /* options */
2714  if (strcmp(PQgetvalue(result, i, 5), "t") == 0)
2715  {
2716  appendPQExpBufferStr(&buf, "ndistinct");
2717  gotone = true;
2718  }
2719 
2720  if (strcmp(PQgetvalue(result, i, 6), "t") == 0)
2721  {
2722  appendPQExpBuffer(&buf, "%sdependencies", gotone ? ", " : "");
2723  gotone = true;
2724  }
2725 
2726  if (strcmp(PQgetvalue(result, i, 7), "t") == 0)
2727  {
2728  appendPQExpBuffer(&buf, "%smcv", gotone ? ", " : "");
2729  }
2730 
2731  appendPQExpBuffer(&buf, ") ON %s FROM %s",
2732  PQgetvalue(result, i, 4),
2733  PQgetvalue(result, i, 1));
2734 
2735  printTableAddFooter(&cont, buf.data);
2736  }
2737  }
2738  PQclear(result);
2739  }
2740 
2741  /* print rules */
2742  if (tableinfo.hasrules && tableinfo.relkind != RELKIND_MATVIEW)
2743  {
2744  if (pset.sversion >= 80300)
2745  {
2746  printfPQExpBuffer(&buf,
2747  "SELECT r.rulename, trim(trailing ';' from pg_catalog.pg_get_ruledef(r.oid, true)), "
2748  "ev_enabled\n"
2749  "FROM pg_catalog.pg_rewrite r\n"
2750  "WHERE r.ev_class = '%s' ORDER BY 1;",
2751  oid);
2752  }
2753  else
2754  {
2755  printfPQExpBuffer(&buf,
2756  "SELECT r.rulename, trim(trailing ';' from pg_catalog.pg_get_ruledef(r.oid, true)), "
2757  "'O' AS ev_enabled\n"
2758  "FROM pg_catalog.pg_rewrite r\n"
2759  "WHERE r.ev_class = '%s' ORDER BY 1;",
2760  oid);
2761  }
2762  result = PSQLexec(buf.data);
2763  if (!result)
2764  goto error_return;
2765  else
2766  tuples = PQntuples(result);
2767 
2768  if (tuples > 0)
2769  {
2770  bool have_heading;
2771  int category;
2772 
2773  for (category = 0; category < 4; category++)
2774  {
2775  have_heading = false;
2776 
2777  for (i = 0; i < tuples; i++)
2778  {
2779  const char *ruledef;
2780  bool list_rule = false;
2781 
2782  switch (category)
2783  {
2784  case 0:
2785  if (*PQgetvalue(result, i, 2) == 'O')
2786  list_rule = true;
2787  break;
2788  case 1:
2789  if (*PQgetvalue(result, i, 2) == 'D')
2790  list_rule = true;
2791  break;
2792  case 2:
2793  if (*PQgetvalue(result, i, 2) == 'A')
2794  list_rule = true;
2795  break;
2796  case 3:
2797  if (*PQgetvalue(result, i, 2) == 'R')
2798  list_rule = true;
2799  break;
2800  }
2801  if (!list_rule)
2802  continue;
2803 
2804  if (!have_heading)
2805  {
2806  switch (category)
2807  {
2808  case 0:
2809  printfPQExpBuffer(&buf, _("Rules:"));
2810  break;
2811  case 1:
2812  printfPQExpBuffer(&buf, _("Disabled rules:"));
2813  break;
2814  case 2:
2815  printfPQExpBuffer(&buf, _("Rules firing always:"));
2816  break;
2817  case 3:
2818  printfPQExpBuffer(&buf, _("Rules firing on replica only:"));
2819  break;
2820  }
2821  printTableAddFooter(&cont, buf.data);
2822  have_heading = true;
2823  }
2824 
2825  /* Everything after "CREATE RULE" is echoed verbatim */
2826  ruledef = PQgetvalue(result, i, 1);
2827  ruledef += 12;
2828  printfPQExpBuffer(&buf, " %s", ruledef);
2829  printTableAddFooter(&cont, buf.data);
2830  }
2831  }
2832  }
2833  PQclear(result);
2834  }
2835 
2836  /* print any publications */
2837  if (pset.sversion >= 100000)
2838  {
2839  printfPQExpBuffer(&buf,
2840  "SELECT pubname\n"
2841  "FROM pg_catalog.pg_publication p\n"
2842  "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
2843  "WHERE pr.prrelid = '%s'\n"
2844  "UNION ALL\n"
2845  "SELECT pubname\n"
2846  "FROM pg_catalog.pg_publication p\n"
2847  "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
2848  "ORDER BY 1;",
2849  oid, oid);
2850 
2851  result = PSQLexec(buf.data);
2852  if (!result)
2853  goto error_return;
2854  else
2855  tuples = PQntuples(result);
2856 
2857  if (tuples > 0)
2858  printTableAddFooter(&cont, _("Publications:"));
2859 
2860  /* Might be an empty set - that's ok */
2861  for (i = 0; i < tuples; i++)
2862  {
2863  printfPQExpBuffer(&buf, " \"%s\"",
2864  PQgetvalue(result, i, 0));
2865 
2866  printTableAddFooter(&cont, buf.data);
2867  }
2868  PQclear(result);
2869  }
2870  }
2871 
2872  /* Get view_def if table is a view or materialized view */
2873  if ((tableinfo.relkind == RELKIND_VIEW ||
2874  tableinfo.relkind == RELKIND_MATVIEW) && verbose)
2875  {
2876  PGresult *result;
2877 
2878  printfPQExpBuffer(&buf,
2879  "SELECT pg_catalog.pg_get_viewdef('%s'::pg_catalog.oid, true);",
2880  oid);
2881  result = PSQLexec(buf.data);
2882  if (!result)
2883  goto error_return;
2884 
2885  if (PQntuples(result) > 0)
2886  view_def = pg_strdup(PQgetvalue(result, 0, 0));
2887 
2888  PQclear(result);
2889  }
2890 
2891  if (view_def)
2892  {
2893  PGresult *result = NULL;
2894 
2895  /* Footer information about a view */
2896  printTableAddFooter(&cont, _("View definition:"));
2897  printTableAddFooter(&cont, view_def);
2898 
2899  /* print rules */
2900  if (tableinfo.hasrules)
2901  {
2902  printfPQExpBuffer(&buf,
2903  "SELECT r.rulename, trim(trailing ';' from pg_catalog.pg_get_ruledef(r.oid, true))\n"
2904  "FROM pg_catalog.pg_rewrite r\n"
2905  "WHERE r.ev_class = '%s' AND r.rulename != '_RETURN' ORDER BY 1;",
2906  oid);
2907  result = PSQLexec(buf.data);
2908  if (!result)
2909  goto error_return;
2910 
2911  if (PQntuples(result) > 0)
2912  {
2913  printTableAddFooter(&cont, _("Rules:"));
2914  for (i = 0; i < PQntuples(result); i++)
2915  {
2916  const char *ruledef;
2917 
2918  /* Everything after "CREATE RULE" is echoed verbatim */
2919  ruledef = PQgetvalue(result, i, 1);
2920  ruledef += 12;
2921 
2922  printfPQExpBuffer(&buf, " %s", ruledef);
2923  printTableAddFooter(&cont, buf.data);
2924  }
2925  }
2926  PQclear(result);
2927  }
2928  }
2929 
2930  /*
2931  * Print triggers next, if any (but only user-defined triggers). This
2932  * could apply to either a table or a view.
2933  */
2934  if (tableinfo.hastriggers)
2935  {
2936  PGresult *result;
2937  int tuples;
2938 
2939  printfPQExpBuffer(&buf,
2940  "SELECT t.tgname, "
2941  "pg_catalog.pg_get_triggerdef(t.oid%s), "
2942  "t.tgenabled, %s, %s\n"
2943  "FROM pg_catalog.pg_trigger t\n"
2944  "WHERE t.tgrelid = '%s' AND ",
2945  (pset.sversion >= 90000 ? ", true" : ""),
2946  (pset.sversion >= 90000 ? "t.tgisinternal" :
2947  pset.sversion >= 80300 ?
2948  "t.tgconstraint <> 0 AS tgisinternal" :
2949  "false AS tgisinternal"),
2950  (pset.sversion >= 130000 ?
2951  "(SELECT (NULLIF(a.relid, t.tgrelid))::pg_catalog.regclass"
2952  " FROM pg_catalog.pg_trigger AS u, "
2953  " pg_catalog.pg_partition_ancestors(t.tgrelid) AS a"
2954  " WHERE u.tgname = t.tgname AND u.tgrelid = a.relid"
2955  " AND u.tgparentid = 0) AS parent" :
2956  "NULL AS parent"),
2957  oid);
2958  if (pset.sversion >= 110000)
2959  appendPQExpBufferStr(&buf, "(NOT t.tgisinternal OR (t.tgisinternal AND t.tgenabled = 'D') \n"
2960  " OR EXISTS (SELECT 1 FROM pg_catalog.pg_depend WHERE objid = t.oid \n"
2961  " AND refclassid = 'pg_catalog.pg_trigger'::pg_catalog.regclass))");
2962  else if (pset.sversion >= 90000)
2963  /* display/warn about disabled internal triggers */
2964  appendPQExpBufferStr(&buf, "(NOT t.tgisinternal OR (t.tgisinternal AND t.tgenabled = 'D'))");
2965  else if (pset.sversion >= 80300)
2966  appendPQExpBufferStr(&buf, "(t.tgconstraint = 0 OR (t.tgconstraint <> 0 AND t.tgenabled = 'D'))");
2967  else
2968  appendPQExpBufferStr(&buf,
2969  "(NOT tgisconstraint "
2970  " OR NOT EXISTS"
2971  " (SELECT 1 FROM pg_catalog.pg_depend d "
2972  " JOIN pg_catalog.pg_constraint c ON (d.refclassid = c.tableoid AND d.refobjid = c.oid) "
2973  " WHERE d.classid = t.tableoid AND d.objid = t.oid AND d.deptype = 'i' AND c.contype = 'f'))");
2974  appendPQExpBufferStr(&buf, "\nORDER BY 1;");
2975 
2976  result = PSQLexec(buf.data);
2977  if (!result)
2978  goto error_return;
2979  else
2980  tuples = PQntuples(result);
2981 
2982  if (tuples > 0)
2983  {
2984  bool have_heading;
2985  int category;
2986 
2987  /*
2988  * split the output into 4 different categories. Enabled triggers,
2989  * disabled triggers and the two special ALWAYS and REPLICA
2990  * configurations.
2991  */
2992  for (category = 0; category <= 4; category++)
2993  {
2994  have_heading = false;
2995  for (i = 0; i < tuples; i++)
2996  {
2997  bool list_trigger;
2998  const char *tgdef;
2999  const char *usingpos;
3000  const char *tgenabled;
3001  const char *tgisinternal;
3002 
3003  /*
3004  * Check if this trigger falls into the current category
3005  */
3006  tgenabled = PQgetvalue(result, i, 2);
3007  tgisinternal = PQgetvalue(result, i, 3);
3008  list_trigger = false;
3009  switch (category)
3010  {
3011  case 0:
3012  if (*tgenabled == 'O' || *tgenabled == 't')
3013  list_trigger = true;
3014  break;
3015  case 1:
3016  if ((*tgenabled == 'D' || *tgenabled == 'f') &&
3017  *tgisinternal == 'f')
3018  list_trigger = true;
3019  break;
3020  case 2:
3021  if ((*tgenabled == 'D' || *tgenabled == 'f') &&
3022  *tgisinternal == 't')
3023  list_trigger = true;
3024  break;
3025  case 3:
3026  if (*tgenabled == 'A')
3027  list_trigger = true;
3028  break;
3029  case 4:
3030  if (*tgenabled == 'R')
3031  list_trigger = true;
3032  break;
3033  }
3034  if (list_trigger == false)
3035  continue;
3036 
3037  /* Print the category heading once */
3038  if (have_heading == false)
3039  {
3040  switch (category)
3041  {
3042  case 0:
3043  printfPQExpBuffer(&buf, _("Triggers:"));
3044  break;
3045  case 1:
3046  if (pset.sversion >= 80300)
3047  printfPQExpBuffer(&buf, _("Disabled user triggers:"));
3048  else
3049  printfPQExpBuffer(&buf, _("Disabled triggers:"));
3050  break;
3051  case 2:
3052  printfPQExpBuffer(&buf, _("Disabled internal triggers:"));
3053  break;
3054  case 3:
3055  printfPQExpBuffer(&buf, _("Triggers firing always:"));
3056  break;
3057  case 4:
3058  printfPQExpBuffer(&buf, _("Triggers firing on replica only:"));
3059  break;
3060 
3061  }
3062  printTableAddFooter(&cont, buf.data);
3063  have_heading = true;
3064  }
3065 
3066  /* Everything after "TRIGGER" is echoed verbatim */
3067  tgdef = PQgetvalue(result, i, 1);
3068  usingpos = strstr(tgdef, " TRIGGER ");
3069  if (usingpos)
3070  tgdef = usingpos + 9;
3071 
3072  printfPQExpBuffer(&buf, " %s", tgdef);
3073 
3074  /* Visually distinguish inherited triggers */
3075  if (!PQgetisnull(result, i, 4))
3076  appendPQExpBuffer(&buf, ", ON TABLE %s",
3077  PQgetvalue(result, i, 4));
3078 
3079  printTableAddFooter(&cont, buf.data);
3080  }
3081  }
3082  }
3083  PQclear(result);
3084  }
3085 
3086  /*
3087  * Finish printing the footer information about a table.
3088  */
3089  if (tableinfo.relkind == RELKIND_RELATION ||
3090  tableinfo.relkind == RELKIND_MATVIEW ||
3091  tableinfo.relkind == RELKIND_FOREIGN_TABLE ||
3092  tableinfo.relkind == RELKIND_PARTITIONED_TABLE ||
3093  tableinfo.relkind == RELKIND_PARTITIONED_INDEX ||
3094  tableinfo.relkind == RELKIND_TOASTVALUE)
3095  {
3096  bool is_partitioned;
3097  PGresult *result;
3098  int tuples;
3099 
3100  /* simplify some repeated tests below */
3101  is_partitioned = (tableinfo.relkind == RELKIND_PARTITIONED_TABLE ||
3102  tableinfo.relkind == RELKIND_PARTITIONED_INDEX);
3103 
3104  /* print foreign server name */
3105  if (tableinfo.relkind == RELKIND_FOREIGN_TABLE)
3106  {
3107  char *ftoptions;
3108 
3109  /* Footer information about foreign table */
3110  printfPQExpBuffer(&buf,
3111  "SELECT s.srvname,\n"
3112  " pg_catalog.array_to_string(ARRAY(\n"
3113  " SELECT pg_catalog.quote_ident(option_name)"
3114  " || ' ' || pg_catalog.quote_literal(option_value)\n"
3115  " FROM pg_catalog.pg_options_to_table(ftoptions)), ', ')\n"
3116  "FROM pg_catalog.pg_foreign_table f,\n"
3117  " pg_catalog.pg_foreign_server s\n"
3118  "WHERE f.ftrelid = '%s' AND s.oid = f.ftserver;",
3119  oid);
3120  result = PSQLexec(buf.data);
3121  if (!result)
3122  goto error_return;
3123  else if (PQntuples(result) != 1)
3124  {
3125  PQclear(result);
3126  goto error_return;
3127  }
3128 
3129  /* Print server name */
3130  printfPQExpBuffer(&buf, _("Server: %s"),
3131  PQgetvalue(result, 0, 0));
3132  printTableAddFooter(&cont, buf.data);
3133 
3134  /* Print per-table FDW options, if any */
3135  ftoptions = PQgetvalue(result, 0, 1);
3136  if (ftoptions && ftoptions[0] != '\0')
3137  {
3138  printfPQExpBuffer(&buf, _("FDW options: (%s)"), ftoptions);
3139  printTableAddFooter(&cont, buf.data);
3140  }
3141  PQclear(result);
3142  }
3143 
3144  /* print tables inherited from (exclude partitioned parents) */
3145  printfPQExpBuffer(&buf,
3146  "SELECT c.oid::pg_catalog.regclass\n"
3147  "FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i\n"
3148  "WHERE c.oid = i.inhparent AND i.inhrelid = '%s'\n"
3149  " AND c.relkind != " CppAsString2(RELKIND_PARTITIONED_TABLE)
3150  " AND c.relkind != " CppAsString2(RELKIND_PARTITIONED_INDEX)
3151  "\nORDER BY inhseqno;",
3152  oid);
3153 
3154  result = PSQLexec(buf.data);
3155  if (!result)
3156  goto error_return;
3157  else
3158  {
3159  const char *s = _("Inherits");
3160  int sw = pg_wcswidth(s, strlen(s), pset.encoding);
3161 
3162  tuples = PQntuples(result);
3163 
3164  for (i = 0; i < tuples; i++)
3165  {
3166  if (i == 0)
3167  printfPQExpBuffer(&buf, "%s: %s",
3168  s, PQgetvalue(result, i, 0));
3169  else
3170  printfPQExpBuffer(&buf, "%*s %s",
3171  sw, "", PQgetvalue(result, i, 0));
3172  if (i < tuples - 1)
3173  appendPQExpBufferChar(&buf, ',');
3174 
3175  printTableAddFooter(&cont, buf.data);
3176  }
3177 
3178  PQclear(result);
3179  }
3180 
3181  /* print child tables (with additional info if partitions) */
3182  if (pset.sversion >= 100000)
3183  printfPQExpBuffer(&buf,
3184  "SELECT c.oid::pg_catalog.regclass, c.relkind,"
3185  " pg_catalog.pg_get_expr(c.relpartbound, c.oid)\n"
3186  "FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i\n"
3187  "WHERE c.oid = i.inhrelid AND i.inhparent = '%s'\n"
3188  "ORDER BY pg_catalog.pg_get_expr(c.relpartbound, c.oid) = 'DEFAULT',"
3189  " c.oid::pg_catalog.regclass::pg_catalog.text;",
3190  oid);
3191  else if (pset.sversion >= 80300)
3192  printfPQExpBuffer(&buf,
3193  "SELECT c.oid::pg_catalog.regclass, c.relkind, NULL\n"
3194  "FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i\n"
3195  "WHERE c.oid = i.inhrelid AND i.inhparent = '%s'\n"
3196  "ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;",
3197  oid);
3198  else
3199  printfPQExpBuffer(&buf,
3200  "SELECT c.oid::pg_catalog.regclass, c.relkind, NULL\n"
3201  "FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i\n"
3202  "WHERE c.oid = i.inhrelid AND i.inhparent = '%s'\n"
3203  "ORDER BY c.relname;",
3204  oid);
3205 
3206  result = PSQLexec(buf.data);
3207  if (!result)
3208  goto error_return;
3209  tuples = PQntuples(result);
3210 
3211  /*
3212  * For a partitioned table with no partitions, always print the number
3213  * of partitions as zero, even when verbose output is expected.
3214  * Otherwise, we will not print "Partitions" section for a partitioned
3215  * table without any partitions.
3216  */
3217  if (is_partitioned && tuples == 0)
3218  {
3219  printfPQExpBuffer(&buf, _("Number of partitions: %d"), tuples);
3220  printTableAddFooter(&cont, buf.data);
3221  }
3222  else if (!verbose)
3223  {
3224  /* print the number of child tables, if any */
3225  if (tuples > 0)
3226  {
3227  if (is_partitioned)
3228  printfPQExpBuffer(&buf, _("Number of partitions: %d (Use \\d+ to list them.)"), tuples);
3229  else
3230  printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
3231  printTableAddFooter(&cont, buf.data);
3232  }
3233  }
3234  else
3235  {
3236  /* display the list of child tables */
3237  const char *ct = is_partitioned ? _("Partitions") : _("Child tables");
3238  int ctw = pg_wcswidth(ct, strlen(ct), pset.encoding);
3239 
3240  for (i = 0; i < tuples; i++)
3241  {
3242  char child_relkind = *PQgetvalue(result, i, 1);
3243 
3244  if (i == 0)
3245  printfPQExpBuffer(&buf, "%s: %s",
3246  ct, PQgetvalue(result, i, 0));
3247  else
3248  printfPQExpBuffer(&buf, "%*s %s",
3249  ctw, "", PQgetvalue(result, i, 0));
3250  if (!PQgetisnull(result, i, 2))
3251  appendPQExpBuffer(&buf, " %s", PQgetvalue(result, i, 2));
3252  if (child_relkind == RELKIND_PARTITIONED_TABLE ||
3253  child_relkind == RELKIND_PARTITIONED_INDEX)
3254  appendPQExpBufferStr(&buf, ", PARTITIONED");
3255  if (i < tuples - 1)
3256  appendPQExpBufferChar(&buf, ',');
3257 
3258  printTableAddFooter(&cont, buf.data);
3259  }
3260  }
3261  PQclear(result);
3262 
3263  /* Table type */
3264  if (tableinfo.reloftype)
3265  {
3266  printfPQExpBuffer(&buf, _("Typed table of type: %s"), tableinfo.reloftype);
3267  printTableAddFooter(&cont, buf.data);
3268  }
3269 
3270  if (verbose &&
3271  (tableinfo.relkind == RELKIND_RELATION ||
3272  tableinfo.relkind == RELKIND_MATVIEW) &&
3273 
3274  /*
3275  * No need to display default values; we already display a REPLICA
3276  * IDENTITY marker on indexes.
3277  */
3278  tableinfo.relreplident != 'i' &&
3279  ((strcmp(schemaname, "pg_catalog") != 0 && tableinfo.relreplident != 'd') ||
3280  (strcmp(schemaname, "pg_catalog") == 0 && tableinfo.relreplident != 'n')))
3281  {
3282  const char *s = _("Replica Identity");
3283 
3284  printfPQExpBuffer(&buf, "%s: %s",
3285  s,
3286  tableinfo.relreplident == 'f' ? "FULL" :
3287  tableinfo.relreplident == 'n' ? "NOTHING" :
3288  "???");
3289 
3290  printTableAddFooter(&cont, buf.data);
3291  }
3292 
3293  /* OIDs, if verbose and not a materialized view */
3294  if (verbose && tableinfo.relkind != RELKIND_MATVIEW && tableinfo.hasoids)
3295  printTableAddFooter(&cont, _("Has OIDs: yes"));
3296 
3297  /* Tablespace info */
3298  add_tablespace_footer(&cont, tableinfo.relkind, tableinfo.tablespace,
3299  true);
3300 
3301  /* Access method info */
3302  if (verbose && tableinfo.relam != NULL && !pset.hide_tableam)
3303  {
3304  printfPQExpBuffer(&buf, _("Access method: %s"), tableinfo.relam);
3305  printTableAddFooter(&cont, buf.data);
3306  }
3307  }
3308 
3309  /* reloptions, if verbose */
3310  if (verbose &&
3311  tableinfo.reloptions && tableinfo.reloptions[0] != '\0')
3312  {
3313  const char *t = _("Options");
3314 
3315  printfPQExpBuffer(&buf, "%s: %s", t, tableinfo.reloptions);
3316  printTableAddFooter(&cont, buf.data);
3317  }
3318 
3319  printTable(&cont, pset.queryFout, false, pset.logfile);
3320 
3321  retval = true;
3322 
3323 error_return:
3324 
3325  /* clean up */
3326  if (printTableInitialized)
3327  printTableCleanup(&cont);
3328  termPQExpBuffer(&buf);
3329  termPQExpBuffer(&title);
3330  termPQExpBuffer(&tmpbuf);
3331 
3332  if (view_def)
3333  free(view_def);
3334 
3335  if (res)
3336  PQclear(res);
3337 
3338  return retval;
3339 }
3340 
3341 /*
3342  * Add a tablespace description to a footer. If 'newline' is true, it is added
3343  * in a new line; otherwise it's appended to the current value of the last
3344  * footer.
3345  */
3346 static void
3347 add_tablespace_footer(printTableContent *const cont, char relkind,
3348  Oid tablespace, const bool newline)
3349 {
3350  /* relkinds for which we support tablespaces */
3351  if (relkind == RELKIND_RELATION ||
3352  relkind == RELKIND_MATVIEW ||
3353  relkind == RELKIND_INDEX ||
3354  relkind == RELKIND_PARTITIONED_TABLE ||
3355  relkind == RELKIND_PARTITIONED_INDEX ||
3356  relkind == RELKIND_TOASTVALUE)
3357  {
3358  /*
3359  * We ignore the database default tablespace so that users not using
3360  * tablespaces don't need to know about them. This case also covers
3361  * pre-8.0 servers, for which tablespace will always be 0.
3362  */
3363  if (tablespace != 0)
3364  {
3365  PGresult *result = NULL;
3367 
3368  initPQExpBuffer(&buf);
3369  printfPQExpBuffer(&buf,
3370  "SELECT spcname FROM pg_catalog.pg_tablespace\n"
3371  "WHERE oid = '%u';", tablespace);
3372  result = PSQLexec(buf.data);
3373  if (!result)
3374  {
3375  termPQExpBuffer(&buf);
3376  return;
3377  }
3378  /* Should always be the case, but.... */
3379  if (PQntuples(result) > 0)
3380  {
3381  if (newline)
3382  {
3383  /* Add the tablespace as a new footer */
3384  printfPQExpBuffer(&buf, _("Tablespace: \"%s\""),
3385  PQgetvalue(result, 0, 0));
3386  printTableAddFooter(cont, buf.data);
3387  }
3388  else
3389  {
3390  /* Append the tablespace to the latest footer */
3391  printfPQExpBuffer(&buf, "%s", cont->footer->data);
3392 
3393  /*-------
3394  translator: before this string there's an index description like
3395  '"foo_pkey" PRIMARY KEY, btree (a)' */
3396  appendPQExpBuffer(&buf, _(", tablespace \"%s\""),
3397  PQgetvalue(result, 0, 0));
3398  printTableSetFooter(cont, buf.data);
3399  }
3400  }
3401  PQclear(result);
3402  termPQExpBuffer(&buf);
3403  }
3404  }
3405 }
3406 
3407 /*
3408  * \du or \dg
3409  *
3410  * Describes roles. Any schema portion of the pattern is ignored.
3411  */
3412 bool
3413 describeRoles(const char *pattern, bool verbose, bool showSystem)
3414 {
3416  PGresult *res;
3417  printTableContent cont;
3418  printTableOpt myopt = pset.popt.topt;
3419  int ncols = 3;
3420  int nrows = 0;
3421  int i;
3422  int conns;
3423  const char align = 'l';
3424  char **attr;
3425 
3426  myopt.default_footer = false;
3427 
3428  initPQExpBuffer(&buf);
3429 
3430  if (pset.sversion >= 80100)
3431  {
3432  printfPQExpBuffer(&buf,
3433  "SELECT r.rolname, r.rolsuper, r.rolinherit,\n"
3434  " r.rolcreaterole, r.rolcreatedb, r.rolcanlogin,\n"
3435  " r.rolconnlimit, r.rolvaliduntil,\n"
3436  " ARRAY(SELECT b.rolname\n"
3437  " FROM pg_catalog.pg_auth_members m\n"
3438  " JOIN pg_catalog.pg_roles b ON (m.roleid = b.oid)\n"
3439  " WHERE m.member = r.oid) as memberof");
3440 
3441  if (verbose && pset.sversion >= 80200)
3442  {
3443  appendPQExpBufferStr(&buf, "\n, pg_catalog.shobj_description(r.oid, 'pg_authid') AS description");
3444  ncols++;
3445  }
3446  if (pset.sversion >= 90100)
3447  {
3448  appendPQExpBufferStr(&buf, "\n, r.rolreplication");
3449  }
3450 
3451  if (pset.sversion >= 90500)
3452  {
3453  appendPQExpBufferStr(&buf, "\n, r.rolbypassrls");
3454  }
3455 
3456  appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_roles r\n");
3457 
3458  if (!showSystem && !pattern)
3459  appendPQExpBufferStr(&buf, "WHERE r.rolname !~ '^pg_'\n");
3460 
3461  processSQLNamePattern(pset.db, &buf, pattern, false, false,
3462  NULL, "r.rolname", NULL, NULL);
3463  }
3464  else
3465  {
3466  printfPQExpBuffer(&buf,
3467  "SELECT u.usename AS rolname,\n"
3468  " u.usesuper AS rolsuper,\n"
3469  " true AS rolinherit, false AS rolcreaterole,\n"
3470  " u.usecreatedb AS rolcreatedb, true AS rolcanlogin,\n"
3471  " -1 AS rolconnlimit,"
3472  " u.valuntil as rolvaliduntil,\n"
3473  " ARRAY(SELECT g.groname FROM pg_catalog.pg_group g WHERE u.usesysid = ANY(g.grolist)) as memberof"
3474  "\nFROM pg_catalog.pg_user u\n");
3475 
3476  processSQLNamePattern(pset.db, &buf, pattern, false, false,
3477  NULL, "u.usename", NULL, NULL);
3478  }
3479 
3480  appendPQExpBufferStr(&buf, "ORDER BY 1;");
3481 
3482  res = PSQLexec(buf.data);
3483  if (!res)
3484  return false;
3485 
3486  nrows = PQntuples(res);
3487  attr = pg_malloc0((nrows + 1) * sizeof(*attr));
3488 
3489  printTableInit(&cont, &myopt, _("List of roles"), ncols, nrows);
3490 
3491  printTableAddHeader(&cont, gettext_noop("Role name"), true, align);
3492  printTableAddHeader(&cont, gettext_noop("Attributes"), true, align);
3493  printTableAddHeader(&cont, gettext_noop("Member of"), true, align);
3494 
3495  if (verbose && pset.sversion >= 80200)
3496  printTableAddHeader(&cont, gettext_noop("Description"), true, align);
3497 
3498  for (i = 0; i < nrows; i++)
3499  {
3500  printTableAddCell(&cont, PQgetvalue(res, i, 0), false, false);
3501 
3502  resetPQExpBuffer(&buf);
3503  if (strcmp(PQgetvalue(res, i, 1), "t") == 0)
3504  add_role_attribute(&buf, _("Superuser"));
3505 
3506  if (strcmp(PQgetvalue(res, i, 2), "t") != 0)
3507  add_role_attribute(&buf, _("No inheritance"));
3508 
3509  if (strcmp(PQgetvalue(res, i, 3), "t") == 0)
3510  add_role_attribute(&buf, _("Create role"));
3511 
3512  if (strcmp(PQgetvalue(res, i, 4), "t") == 0)
3513  add_role_attribute(&buf, _("Create DB"));
3514 
3515  if (strcmp(PQgetvalue(res, i, 5), "t") != 0)
3516  add_role_attribute(&buf, _("Cannot login"));
3517 
3518  if (pset.sversion >= 90100)
3519  if (strcmp(PQgetvalue(res, i, (verbose ? 10 : 9)), "t") == 0)
3520  add_role_attribute(&buf, _("Replication"));
3521 
3522  if (pset.sversion >= 90500)
3523  if (strcmp(PQgetvalue(res, i, (verbose ? 11 : 10)), "t") == 0)
3524  add_role_attribute(&buf, _("Bypass RLS"));
3525 
3526  conns = atoi(PQgetvalue(res, i, 6));
3527  if (conns >= 0)
3528  {
3529  if (buf.len > 0)
3530  appendPQExpBufferChar(&buf, '\n');
3531 
3532  if (conns == 0)
3533  appendPQExpBufferStr(&buf, _("No connections"));
3534  else
3535  appendPQExpBuffer(&buf, ngettext("%d connection",
3536  "%d connections",
3537  conns),
3538  conns);
3539  }
3540 
3541  if (strcmp(PQgetvalue(res, i, 7), "") != 0)
3542  {
3543  if (buf.len > 0)
3544  appendPQExpBufferChar(&buf, '\n');
3545  appendPQExpBufferStr(&buf, _("Password valid until "));
3546  appendPQExpBufferStr(&buf, PQgetvalue(res, i, 7));
3547  }
3548 
3549  attr[i] = pg_strdup(buf.data);
3550 
3551  printTableAddCell(&cont, attr[i], false, false);
3552 
3553  printTableAddCell(&cont, PQgetvalue(res, i, 8), false, false);
3554 
3555  if (verbose && pset.sversion >= 80200)
3556  printTableAddCell(&cont, PQgetvalue(res, i, 9), false, false);
3557  }
3558  termPQExpBuffer(&buf);
3559 
3560  printTable(&cont, pset.queryFout, false, pset.logfile);
3561  printTableCleanup(&cont);
3562 
3563  for (i = 0; i < nrows; i++)
3564  free(attr[i]);
3565  free(attr);
3566 
3567  PQclear(res);
3568  return true;
3569 }
3570 
3571 static void
3573 {
3574  if (buf->len > 0)
3575  appendPQExpBufferStr(buf, ", ");
3576 
3577  appendPQExpBufferStr(buf, str);
3578 }
3579 
3580 /*
3581  * \drds
3582  */
3583 bool
3584 listDbRoleSettings(const char *pattern, const char *pattern2)
3585 {
3587  PGresult *res;
3588  printQueryOpt myopt = pset.popt;
3589  bool havewhere;
3590 
3591  if (pset.sversion < 90000)
3592  {
3593  char sverbuf[32];
3594 
3595  pg_log_error("The server (version %s) does not support per-database role settings.",
3597  sverbuf, sizeof(sverbuf)));
3598  return true;
3599  }
3600 
3601  initPQExpBuffer(&buf);
3602 
3603  printfPQExpBuffer(&buf, "SELECT rolname AS \"%s\", datname AS \"%s\",\n"
3604  "pg_catalog.array_to_string(setconfig, E'\\n') AS \"%s\"\n"
3605  "FROM pg_catalog.pg_db_role_setting s\n"
3606  "LEFT JOIN pg_catalog.pg_database d ON d.oid = setdatabase\n"
3607  "LEFT JOIN pg_catalog.pg_roles r ON r.oid = setrole\n",
3608  gettext_noop("Role"),
3609  gettext_noop("Database"),
3610  gettext_noop("Settings"));
3611  havewhere = processSQLNamePattern(pset.db, &buf, pattern, false, false,
3612  NULL, "r.rolname", NULL, NULL);
3613  processSQLNamePattern(pset.db, &buf, pattern2, havewhere, false,
3614  NULL, "d.datname", NULL, NULL);
3615  appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
3616 
3617  res = PSQLexec(buf.data);
3618  termPQExpBuffer(&buf);
3619  if (!res)
3620  return false;
3621 
3622  /*
3623  * Most functions in this file are content to print an empty table when
3624  * there are no matching objects. We intentionally deviate from that
3625  * here, but only in !quiet mode, because of the possibility that the user
3626  * is confused about what the two pattern arguments mean.
3627  */
3628  if (PQntuples(res) == 0 && !pset.quiet)
3629  {
3630  if (pattern && pattern2)
3631  pg_log_error("Did not find any settings for role \"%s\" and database \"%s\".",
3632  pattern, pattern2);
3633  else if (pattern)
3634  pg_log_error("Did not find any settings for role \"%s\".",
3635  pattern);
3636  else
3637  pg_log_error("Did not find any settings.");
3638  }
3639  else
3640  {
3641  myopt.nullPrint = NULL;
3642  myopt.title = _("List of settings");
3643  myopt.translate_header = true;
3644 
3645  printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
3646  }
3647 
3648  PQclear(res);
3649  return true;
3650 }
3651 
3652 
3653 /*
3654  * listTables()
3655  *
3656  * handler for \dt, \di, etc.
3657  *
3658  * tabtypes is an array of characters, specifying what info is desired:
3659  * t - tables
3660  * i - indexes
3661  * v - views
3662  * m - materialized views
3663  * s - sequences
3664  * E - foreign table (Note: different from 'f', the relkind value)
3665  * (any order of the above is fine)
3666  */
3667 bool
3668 listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSystem)
3669 {
3670  bool showTables = strchr(tabtypes, 't') != NULL;
3671  bool showIndexes = strchr(tabtypes, 'i') != NULL;
3672  bool showViews = strchr(tabtypes, 'v') != NULL;
3673  bool showMatViews = strchr(tabtypes, 'm') != NULL;
3674  bool showSeq = strchr(tabtypes, 's') != NULL;
3675  bool showForeign = strchr(tabtypes, 'E') != NULL;
3676 
3678  PGresult *res;
3679  printQueryOpt myopt = pset.popt;
3680  int cols_so_far;
3681  bool translate_columns[] = {false, false, true, false, false, false, false, false};
3682 
3683  /* If tabtypes is empty, we default to \dtvmsE (but see also command.c) */
3684  if (!(showTables || showIndexes || showViews || showMatViews || showSeq || showForeign))
3685  showTables = showViews = showMatViews = showSeq = showForeign = true;
3686 
3687  initPQExpBuffer(&buf);
3688 
3689  /*
3690  * Note: as of Pg 8.2, we no longer use relkind 's' (special), but we keep
3691  * it here for backwards compatibility.
3692  */
3693  printfPQExpBuffer(&buf,
3694  "SELECT n.nspname as \"%s\",\n"
3695  " c.relname as \"%s\",\n"
3696  " CASE c.relkind"
3697  " WHEN " CppAsString2(RELKIND_RELATION) " THEN '%s'"
3698  " WHEN " CppAsString2(RELKIND_VIEW) " THEN '%s'"
3699  " WHEN " CppAsString2(RELKIND_MATVIEW) " THEN '%s'"
3700  " WHEN " CppAsString2(RELKIND_INDEX) " THEN '%s'"
3701  " WHEN " CppAsString2(RELKIND_SEQUENCE) " THEN '%s'"
3702  " WHEN 's' THEN '%s'"
3703  " WHEN " CppAsString2(RELKIND_FOREIGN_TABLE) " THEN '%s'"
3704  " WHEN " CppAsString2(RELKIND_PARTITIONED_TABLE) " THEN '%s'"
3705  " WHEN " CppAsString2(RELKIND_PARTITIONED_INDEX) " THEN '%s'"
3706  " END as \"%s\",\n"
3707  " pg_catalog.pg_get_userbyid(c.relowner) as \"%s\"",
3708  gettext_noop("Schema"),
3709  gettext_noop("Name"),
3710  gettext_noop("table"),
3711  gettext_noop("view"),
3712  gettext_noop("materialized view"),
3713  gettext_noop("index"),
3714  gettext_noop("sequence"),
3715  gettext_noop("special"),
3716  gettext_noop("foreign table"),
3717  gettext_noop("partitioned table"),
3718  gettext_noop("partitioned index"),
3719  gettext_noop("Type"),
3720  gettext_noop("Owner"));
3721  cols_so_far = 4;
3722 
3723  if (showIndexes)
3724  {
3725  appendPQExpBuffer(&buf,
3726  ",\n c2.relname as \"%s\"",
3727  gettext_noop("Table"));
3728  cols_so_far++;
3729  }
3730 
3731  if (verbose)
3732  {
3733  /*
3734  * Show whether a relation is permanent, temporary, or unlogged. Like
3735  * describeOneTableDetails(), we consider that persistence emerged in
3736  * v9.1, even though related concepts existed before.
3737  */
3738  if (pset.sversion >= 90100)
3739  {
3740  appendPQExpBuffer(&buf,
3741  ",\n CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
3742  gettext_noop("permanent"),
3743  gettext_noop("temporary"),
3744  gettext_noop("unlogged"),
3745  gettext_noop("Persistence"));
3746  translate_columns[cols_so_far] = true;
3747  }
3748 
3749  /*
3750  * We don't bother to count cols_so_far below here, as there's no need
3751  * to; this might change with future additions to the output columns.
3752  */
3753 
3754  /*
3755  * As of PostgreSQL 9.0, use pg_table_size() to show a more accurate
3756  * size of a table, including FSM, VM and TOAST tables.
3757  */
3758  if (pset.sversion >= 90000)
3759  appendPQExpBuffer(&buf,
3760  ",\n pg_catalog.pg_size_pretty(pg_catalog.pg_table_size(c.oid)) as \"%s\"",
3761  gettext_noop("Size"));
3762  else if (pset.sversion >= 80100)
3763  appendPQExpBuffer(&buf,
3764  ",\n pg_catalog.pg_size_pretty(pg_catalog.pg_relation_size(c.oid)) as \"%s\"",
3765  gettext_noop("Size"));
3766 
3767  appendPQExpBuffer(&buf,
3768  ",\n pg_catalog.obj_description(c.oid, 'pg_class') as \"%s\"",
3769  gettext_noop("Description"));
3770  }
3771 
3772  appendPQExpBufferStr(&buf,
3773  "\nFROM pg_catalog.pg_class c"
3774  "\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace");
3775  if (showIndexes)
3776  appendPQExpBufferStr(&buf,
3777  "\n LEFT JOIN pg_catalog.pg_index i ON i.indexrelid = c.oid"
3778  "\n LEFT JOIN pg_catalog.pg_class c2 ON i.indrelid = c2.oid");
3779 
3780  appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
3781  if (showTables)
3782  appendPQExpBufferStr(&buf, CppAsString2(RELKIND_RELATION) ","
3783  CppAsString2(RELKIND_PARTITIONED_TABLE) ",");
3784  if (showViews)
3785  appendPQExpBufferStr(&buf, CppAsString2(RELKIND_VIEW) ",");
3786  if (showMatViews)
3787  appendPQExpBufferStr(&buf, CppAsString2(RELKIND_MATVIEW) ",");
3788  if (showIndexes)
3789  appendPQExpBufferStr(&buf, CppAsString2(RELKIND_INDEX) ","
3790  CppAsString2(RELKIND_PARTITIONED_INDEX) ",");
3791  if (showSeq)
3792  appendPQExpBufferStr(&buf, CppAsString2(RELKIND_SEQUENCE) ",");
3793  if (showSystem || pattern)
3794  appendPQExpBufferStr(&buf, "'s',"); /* was RELKIND_SPECIAL */
3795  if (showForeign)
3796  appendPQExpBufferStr(&buf, CppAsString2(RELKIND_FOREIGN_TABLE) ",");
3797 
3798  appendPQExpBufferStr(&buf, "''"); /* dummy */
3799  appendPQExpBufferStr(&buf, ")\n");
3800 
3801  if (!showSystem && !pattern)
3802  appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
3803  " AND n.nspname <> 'information_schema'\n");
3804 
3805  /*
3806  * TOAST objects are suppressed unconditionally. Since we don't provide
3807  * any way to select RELKIND_TOASTVALUE above, we would never show toast
3808  * tables in any case; it seems a bit confusing to allow their indexes to
3809  * be shown. Use plain \d if you really need to look at a TOAST
3810  * table/index.
3811  */
3812  appendPQExpBufferStr(&buf, " AND n.nspname !~ '^pg_toast'\n");
3813 
3814  processSQLNamePattern(pset.db, &buf, pattern, true, false,
3815  "n.nspname", "c.relname", NULL,
3816  "pg_catalog.pg_table_is_visible(c.oid)");
3817 
3818  appendPQExpBufferStr(&buf, "ORDER BY 1,2;");
3819 
3820  res = PSQLexec(buf.data);
3821  termPQExpBuffer(&buf);
3822  if (!res)
3823  return false;
3824 
3825  /*
3826  * Most functions in this file are content to print an empty table when
3827  * there are no matching objects. We intentionally deviate from that
3828  * here, but only in !quiet mode, for historical reasons.
3829  */
3830  if (PQntuples(res) == 0 && !pset.quiet)
3831  {
3832  if (pattern)
3833  pg_log_error("Did not find any relation named \"%s\".",
3834  pattern);
3835  else
3836  pg_log_error("Did not find any relations.");
3837  }
3838  else
3839  {
3840  myopt.nullPrint = NULL;
3841  myopt.title = _("List of relations");
3842  myopt.translate_header = true;
3843  myopt.translate_columns = translate_columns;
3844  myopt.n_translate_columns = lengthof(translate_columns);
3845 
3846  printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
3847  }
3848 
3849  PQclear(res);
3850  return true;
3851 }
3852 
3853 /*
3854  * \dP
3855  * Takes an optional regexp to select particular relations
3856  *
3857  * As with \d, you can specify the kinds of relations you want:
3858  *
3859  * t for tables
3860  * i for indexes
3861  *
3862  * And there's additional flags:
3863  *
3864  * n to list non-leaf partitioned tables
3865  *
3866  * and you can mix and match these in any order.
3867  */
3868 bool
3869 listPartitionedTables(const char *reltypes, const char *pattern, bool verbose)
3870 {
3871  bool showTables = strchr(reltypes, 't') != NULL;
3872  bool showIndexes = strchr(reltypes, 'i') != NULL;
3873  bool showNested = strchr(reltypes, 'n') != NULL;
3875  PQExpBufferData title;
3876  PGresult *res;
3877  printQueryOpt myopt = pset.popt;
3878  bool translate_columns[] = {false, false, false, false, false, false, false, false, false};
3879  const char *tabletitle;
3880  bool mixed_output = false;
3881 
3882  /*
3883  * Note: Declarative table partitioning is only supported as of Pg 10.0.
3884  */
3885  if (pset.sversion < 100000)
3886  {
3887  char sverbuf[32];
3888 
3889  pg_log_error("The server (version %s) does not support declarative table partitioning.",
3891  sverbuf, sizeof(sverbuf)));
3892  return true;
3893  }
3894 
3895  /* If no relation kind was selected, show them all */
3896  if (!showTables && !showIndexes)
3897  showTables = showIndexes = true;
3898 
3899  if (showIndexes && !showTables)
3900  tabletitle = _("List of partitioned indexes"); /* \dPi */
3901  else if (showTables && !showIndexes)
3902  tabletitle = _("List of partitioned tables"); /* \dPt */
3903  else
3904  {
3905  /* show all kinds */
3906  tabletitle = _("List of partitioned relations");
3907  mixed_output = true;
3908  }
3909 
3910  initPQExpBuffer(&buf);
3911 
3912  printfPQExpBuffer(&buf,
3913  "SELECT n.nspname as \"%s\",\n"
3914  " c.relname as \"%s\",\n"
3915  " pg_catalog.pg_get_userbyid(c.relowner) as \"%s\"",
3916  gettext_noop("Schema"),
3917  gettext_noop("Name"),
3918  gettext_noop("Owner"));
3919 
3920  if (mixed_output)
3921  {
3922  appendPQExpBuffer(&buf,
3923  ",\n CASE c.relkind"
3924  " WHEN " CppAsString2(RELKIND_PARTITIONED_TABLE) " THEN '%s'"
3925  " WHEN " CppAsString2(RELKIND_PARTITIONED_INDEX) " THEN '%s'"
3926  " END as \"%s\"",
3927  gettext_noop("partitioned table"),
3928  gettext_noop("partitioned index"),
3929  gettext_noop("Type"));
3930 
3931  translate_columns[3] = true;
3932  }
3933 
3934  if (showNested || pattern)
3935  appendPQExpBuffer(&buf,
3936  ",\n inh.inhparent::regclass as \"%s\"",
3937  gettext_noop("Parent name"));
3938 
3939  if (showIndexes)
3940  appendPQExpBuffer(&buf,
3941  ",\n c2.oid::regclass as \"%s\"",
3942  gettext_noop("Table"));
3943 
3944  if (verbose)
3945  {
3946  if (showNested)
3947  {
3948  appendPQExpBuffer(&buf,
3949  ",\n s.dps as \"%s\"",
3950  gettext_noop("Leaf partition size"));
3951  appendPQExpBuffer(&buf,
3952  ",\n s.tps as \"%s\"",
3953  gettext_noop("Total size"));
3954  }
3955  else
3956  /* Sizes of all partitions are considered in this case. */
3957  appendPQExpBuffer(&buf,
3958  ",\n s.tps as \"%s\"",
3959  gettext_noop("Total size"));
3960 
3961  appendPQExpBuffer(&buf,
3962  ",\n pg_catalog.obj_description(c.oid, 'pg_class') as \"%s\"",
3963  gettext_noop("Description"));
3964  }
3965 
3966  appendPQExpBufferStr(&buf,
3967  "\nFROM pg_catalog.pg_class c"
3968  "\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace");
3969 
3970  if (showIndexes)
3971  appendPQExpBufferStr(&buf,
3972  "\n LEFT JOIN pg_catalog.pg_index i ON i.indexrelid = c.oid"
3973  "\n LEFT JOIN pg_catalog.pg_class c2 ON i.indrelid = c2.oid");
3974 
3975  if (showNested || pattern)
3976  appendPQExpBufferStr(&buf,
3977  "\n LEFT JOIN pg_catalog.pg_inherits inh ON c.oid = inh.inhrelid");
3978 
3979  if (verbose)
3980  {
3981  if (pset.sversion < 120000)
3982  {
3983  appendPQExpBufferStr(&buf,
3984  ",\n LATERAL (WITH RECURSIVE d\n"
3985  " AS (SELECT inhrelid AS oid, 1 AS level\n"
3986  " FROM pg_catalog.pg_inherits\n"
3987  " WHERE inhparent = c.oid\n"
3988  " UNION ALL\n"
3989  " SELECT inhrelid, level + 1\n"
3990  " FROM pg_catalog.pg_inherits i\n"
3991  " JOIN d ON i.inhparent = d.oid)\n"
3992  " SELECT pg_catalog.pg_size_pretty(sum(pg_catalog.pg_table_size("
3993  "d.oid))) AS tps,\n"
3994  " pg_catalog.pg_size_pretty(sum("
3995  "\n CASE WHEN d.level = 1"
3996  " THEN pg_catalog.pg_table_size(d.oid) ELSE 0 END)) AS dps\n"
3997  " FROM d) s");
3998  }
3999  else
4000  {
4001  /* PostgreSQL 12 has pg_partition_tree function */
4002  appendPQExpBufferStr(&buf,
4003  ",\n LATERAL (SELECT pg_catalog.pg_size_pretty(sum("
4004  "\n CASE WHEN ppt.isleaf AND ppt.level = 1"
4005  "\n THEN pg_catalog.pg_table_size(ppt.relid)"
4006  " ELSE 0 END)) AS dps"
4007  ",\n pg_catalog.pg_size_pretty(sum("
4008  "pg_catalog.pg_table_size(ppt.relid))) AS tps"
4009  "\n FROM pg_catalog.pg_partition_tree(c.oid) ppt) s");
4010  }
4011  }
4012 
4013  appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
4014  if (showTables)
4015  appendPQExpBufferStr(&buf, CppAsString2(RELKIND_PARTITIONED_TABLE) ",");
4016  if (showIndexes)
4017  appendPQExpBufferStr(&buf, CppAsString2(RELKIND_PARTITIONED_INDEX) ",");
4018  appendPQExpBufferStr(&buf, "''"); /* dummy */
4019  appendPQExpBufferStr(&buf, ")\n");
4020 
4021  appendPQExpBufferStr(&buf, !showNested && !pattern ?
4022  " AND NOT c.relispartition\n" : "");
4023 
4024  if (!pattern)
4025  appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
4026  " AND n.nspname <> 'information_schema'\n");
4027 
4028  /*
4029  * TOAST objects are suppressed unconditionally. Since we don't provide
4030  * any way to select RELKIND_TOASTVALUE above, we would never show toast
4031  * tables in any case; it seems a bit confusing to allow their indexes to
4032  * be shown. Use plain \d if you really need to look at a TOAST
4033  * table/index.
4034  */
4035  appendPQExpBufferStr(&buf, " AND n.nspname !~ '^pg_toast'\n");
4036 
4037  processSQLNamePattern(pset.db, &buf, pattern, true, false,
4038  "n.nspname", "c.relname", NULL,
4039  "pg_catalog.pg_table_is_visible(c.oid)");
4040 
4041  appendPQExpBuffer(&buf, "ORDER BY \"Schema\", %s%s\"Name\";",
4042  mixed_output ? "\"Type\" DESC, " : "",
4043  showNested || pattern ? "\"Parent name\" NULLS FIRST, " : "");
4044 
4045  res = PSQLexec(buf.data);
4046  termPQExpBuffer(&buf);
4047  if (!res)
4048  return false;
4049 
4050  initPQExpBuffer(&title);
4051  appendPQExpBufferStr(&title, tabletitle);
4052 
4053  myopt.nullPrint = NULL;
4054  myopt.title = title.data;
4055  myopt.translate_header = true;
4056  myopt.translate_columns = translate_columns;
4057  myopt.n_translate_columns = lengthof(translate_columns);
4058 
4059  printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
4060 
4061  termPQExpBuffer(&title);
4062 
4063  PQclear(res);
4064  return true;
4065 }
4066 
4067 /*
4068  * \dL
4069  *
4070  * Describes languages.
4071  */
4072 bool
4073 listLanguages(const char *pattern, bool verbose, bool showSystem)
4074 {
4076  PGresult *res;
4077  printQueryOpt myopt = pset.popt;
4078 
4079  initPQExpBuffer(&buf);
4080 
4081  printfPQExpBuffer(&buf,
4082  "SELECT l.lanname AS \"%s\",\n",
4083  gettext_noop("Name"));
4084  if (pset.sversion >= 80300)
4085  appendPQExpBuffer(&buf,
4086  " pg_catalog.pg_get_userbyid(l.lanowner) as \"%s\",\n",
4087  gettext_noop("Owner"));
4088 
4089  appendPQExpBuffer(&buf,
4090  " l.lanpltrusted AS \"%s\"",
4091  gettext_noop("Trusted"));
4092 
4093  if (verbose)
4094  {
4095  appendPQExpBuffer(&buf,
4096  ",\n NOT l.lanispl AS \"%s\",\n"
4097  " l.lanplcallfoid::pg_catalog.regprocedure AS \"%s\",\n"
4098  " l.lanvalidator::pg_catalog.regprocedure AS \"%s\",\n ",
4099  gettext_noop("Internal language"),
4100  gettext_noop("Call handler"),
4101  gettext_noop("Validator"));
4102  if (pset.sversion >= 90000)
4103  appendPQExpBuffer(&buf, "l.laninline::pg_catalog.regprocedure AS \"%s\",\n ",
4104  gettext_noop("Inline handler"));
4105  printACLColumn(&buf, "l.lanacl");
4106  }
4107 
4108  appendPQExpBuffer(&buf,
4109  ",\n d.description AS \"%s\""
4110  "\nFROM pg_catalog.pg_language l\n"
4111  "LEFT JOIN pg_catalog.pg_description d\n"
4112  " ON d.classoid = l.tableoid AND d.objoid = l.oid\n"
4113  " AND d.objsubid = 0\n",
4114  gettext_noop("Description"));
4115 
4116  if (pattern)
4117  processSQLNamePattern(pset.db, &buf, pattern, false, false,
4118  NULL, "l.lanname", NULL, NULL);
4119 
4120  if (!showSystem && !pattern)
4121  appendPQExpBufferStr(&buf, "WHERE l.lanplcallfoid != 0\n");
4122 
4123 
4124  appendPQExpBufferStr(&buf, "ORDER BY 1;");
4125 
4126  res = PSQLexec(buf.data);
4127  termPQExpBuffer(&buf);
4128  if (!res)
4129  return false;
4130 
4131  myopt.nullPrint = NULL;
4132  myopt.title = _("List of languages");
4133  myopt.translate_header = true;
4134 
4135  printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
4136 
4137  PQclear(res);
4138  return true;
4139 }
4140 
4141 
4142 /*
4143  * \dD
4144  *
4145  * Describes domains.
4146  */
4147 bool
4148 listDomains(const char *pattern, bool verbose, bool showSystem)
4149 {
4151  PGresult *res;
4152  printQueryOpt myopt = pset.popt;
4153 
4154  initPQExpBuffer(&buf);
4155 
4156  printfPQExpBuffer(&buf,
4157  "SELECT n.nspname as \"%s\",\n"
4158  " t.typname as \"%s\",\n"
4159  " pg_catalog.format_type(t.typbasetype, t.typtypmod) as \"%s\",\n",
4160  gettext_noop("Schema"),
4161  gettext_noop("Name"),
4162  gettext_noop("Type"));
4163 
4164  if (pset.sversion >= 90100)
4165  appendPQExpBuffer(&buf,
4166  " (SELECT c.collname FROM pg_catalog.pg_collation c, pg_catalog.pg_type bt\n"
4167  " WHERE c.oid = t.typcollation AND bt.oid = t.typbasetype AND t.typcollation <> bt.typcollation) as \"%s\",\n",
4168  gettext_noop("Collation"));
4169  appendPQExpBuffer(&buf,
4170  " CASE WHEN t.typnotnull THEN 'not null' END as \"%s\",\n"
4171  " t.typdefault as \"%s\",\n"
4172  " pg_catalog.array_to_string(ARRAY(\n"
4173  " SELECT pg_catalog.pg_get_constraintdef(r.oid, true) FROM pg_catalog.pg_constraint r WHERE t.oid = r.contypid\n"
4174  " ), ' ') as \"%s\"",
4175  gettext_noop("Nullable"),
4176  gettext_noop("Default"),
4177  gettext_noop("Check"));
4178 
4179  if (verbose)
4180  {
4181  if (pset.sversion >= 90200)
4182  {
4183  appendPQExpBufferStr(&buf, ",\n ");
4184  printACLColumn(&buf, "t.typacl");
4185  }
4186  appendPQExpBuffer(&buf,
4187  ",\n d.description as \"%s\"",
4188  gettext_noop("Description"));
4189  }
4190 
4191  appendPQExpBufferStr(&buf,
4192  "\nFROM pg_catalog.pg_type t\n"
4193  " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace\n");
4194 
4195  if (verbose)
4196  appendPQExpBufferStr(&buf,
4197  " LEFT JOIN pg_catalog.pg_description d "
4198  "ON d.classoid = t.tableoid AND d.objoid = t.oid "
4199  "AND d.objsubid = 0\n");
4200 
4201  appendPQExpBufferStr(&buf, "WHERE t.typtype = 'd'\n");
4202 
4203  if (!showSystem && !pattern)
4204  appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
4205  " AND n.nspname <> 'information_schema'\n");
4206 
4207  processSQLNamePattern(pset.db, &buf, pattern, true, false,
4208  "n.nspname", "t.typname", NULL,
4209  "pg_catalog.pg_type_is_visible(t.oid)");
4210 
4211  appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
4212 
4213  res = PSQLexec(buf.data);
4214  termPQExpBuffer(&buf);
4215  if (!res)
4216  return false;
4217 
4218  myopt.nullPrint = NULL;
4219  myopt.title = _("List of domains");
4220  myopt.translate_header = true;
4221 
4222  printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
4223 
4224  PQclear(res);
4225  return true;
4226 }
4227 
4228 /*
4229  * \dc
4230  *
4231  * Describes conversions.
4232  */
4233 bool
4234 listConversions(const char *pattern, bool verbose, bool showSystem)
4235 {
4237  PGresult *res;
4238  printQueryOpt myopt = pset.popt;
4239  static const bool translate_columns[] =
4240  {false, false, false, false, true, false};
4241 
4242  initPQExpBuffer(&buf);
4243 
4244  printfPQExpBuffer(&buf,
4245  "SELECT n.nspname AS \"%s\",\n"
4246  " c.conname AS \"%s\",\n"
4247  " pg_catalog.pg_encoding_to_char(c.conforencoding) AS \"%s\",\n"
4248  " pg_catalog.pg_encoding_to_char(c.contoencoding) AS \"%s\",\n"
4249  " CASE WHEN c.condefault THEN '%s'\n"
4250  " ELSE '%s' END AS \"%s\"",
4251  gettext_noop("Schema"),
4252  gettext_noop("Name"),
4253  gettext_noop("Source"),
4254  gettext_noop("Destination"),
4255  gettext_noop("yes"), gettext_noop("no"),
4256  gettext_noop("Default?"));
4257 
4258  if (verbose)
4259  appendPQExpBuffer(&buf,
4260  ",\n d.description AS \"%s\"",
4261  gettext_noop("Description"));
4262 
4263  appendPQExpBufferStr(&buf,
4264  "\nFROM pg_catalog.pg_conversion c\n"
4265  " JOIN pg_catalog.pg_namespace n "
4266  "ON n.oid = c.connamespace\n");
4267 
4268  if (verbose)
4269  appendPQExpBufferStr(&buf,
4270  "LEFT JOIN pg_catalog.pg_description d "
4271  "ON d.classoid = c.tableoid\n"
4272  " AND d.objoid = c.oid "
4273  "AND d.objsubid = 0\n");
4274 
4275  appendPQExpBufferStr(&buf, "WHERE true\n");
4276 
4277  if (!showSystem && !pattern)
4278  appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
4279  " AND n.nspname <> 'information_schema'\n");
4280 
4281  processSQLNamePattern(pset.db, &buf, pattern, true, false,
4282  "n.nspname", "c.conname", NULL,
4283  "pg_catalog.pg_conversion_is_visible(c.oid)");
4284 
4285  appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
4286 
4287  res = PSQLexec(buf.data);
4288  termPQExpBuffer(&buf);
4289  if (!res)
4290  return false;
4291 
4292  myopt.nullPrint = NULL;
4293  myopt.title = _("List of conversions");
4294  myopt.translate_header = true;
4295  myopt.translate_columns = translate_columns;
4296  myopt.n_translate_columns = lengthof(translate_columns);
4297 
4298  printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
4299 
4300  PQclear(res);
4301  return true;
4302 }
4303 
4304 /*
4305  * \dy
4306  *
4307  * Describes Event Triggers.
4308  */
4309 bool
4310 listEventTriggers(const char *pattern, bool verbose)
4311 {
4313  PGresult *res;
4314  printQueryOpt myopt = pset.popt;
4315  static const bool translate_columns[] =
4316  {false, false, false, true, false, false, false};
4317 
4318  initPQExpBuffer(&buf);
4319 
4320  printfPQExpBuffer(&buf,
4321  "SELECT evtname as \"%s\", "
4322  "evtevent as \"%s\", "
4323  "pg_catalog.pg_get_userbyid(e.evtowner) as \"%s\",\n"
4324  " case evtenabled when 'O' then '%s'"
4325  " when 'R' then '%s'"
4326  " when 'A' then '%s'"
4327  " when 'D' then '%s' end as \"%s\",\n"
4328  " e.evtfoid::pg_catalog.regproc as \"%s\", "
4329  "pg_catalog.array_to_string(array(select x"
4330  " from pg_catalog.unnest(evttags) as t(x)), ', ') as \"%s\"",
4331  gettext_noop("Name"),
4332  gettext_noop("Event"),
4333  gettext_noop("Owner"),
4334  gettext_noop("enabled"),
4335  gettext_noop("replica"),
4336  gettext_noop("always"),
4337  gettext_noop("disabled"),
4338  gettext_noop("Enabled"),
4339  gettext_noop("Function"),
4340  gettext_noop("Tags"));
4341  if (verbose)
4342  appendPQExpBuffer(&buf,
4343  ",\npg_catalog.obj_description(e.oid, 'pg_event_trigger') as \"%s\"",
4344  gettext_noop("Description"));
4345  appendPQExpBufferStr(&buf,
4346  "\nFROM pg_catalog.pg_event_trigger e ");
4347 
4348  processSQLNamePattern(pset.db, &buf, pattern, false, false,
4349  NULL, "evtname", NULL, NULL);
4350 
4351  appendPQExpBufferStr(&buf, "ORDER BY 1");
4352 
4353  res = PSQLexec(buf.data);
4354  termPQExpBuffer(&buf);
4355  if (!res)
4356  return false;
4357 
4358  myopt.nullPrint = NULL;
4359  myopt.title = _("List of event triggers");
4360  myopt.translate_header = true;
4361  myopt.translate_columns = translate_columns;
4362  myopt.n_translate_columns = lengthof(translate_columns);
4363 
4364  printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
4365 
4366  PQclear(res);
4367  return true;
4368 }
4369 
4370 /*
4371  * \dC
4372  *
4373  * Describes casts.
4374  */
4375 bool
4376 listCasts(const char *pattern, bool verbose)
4377 {
4379  PGresult *res;
4380  printQueryOpt myopt = pset.popt;
4381  static const bool translate_columns[] = {false, false, false, true, false};
4382 
4383  initPQExpBuffer(&buf);
4384 
4385  printfPQExpBuffer(&buf,
4386  "SELECT pg_catalog.format_type(castsource, NULL) AS \"%s\",\n"
4387  " pg_catalog.format_type(casttarget, NULL) AS \"%s\",\n",
4388  gettext_noop("Source type"),
4389  gettext_noop("Target type"));
4390 
4391  /*
4392  * We don't attempt to localize '(binary coercible)' or '(with inout)',
4393  * because there's too much risk of gettext translating a function name
4394  * that happens to match some string in the PO database.
4395  */
4396  if (pset.sversion >= 80400)
4397  appendPQExpBuffer(&buf,
4398  " CASE WHEN c.castmethod = '%c' THEN '(binary coercible)'\n"
4399  " WHEN c.castmethod = '%c' THEN '(with inout)'\n"
4400  " ELSE p.proname\n"
4401  " END AS \"%s\",\n",
4402  COERCION_METHOD_BINARY,
4403  COERCION_METHOD_INOUT,
4404  gettext_noop("Function"));
4405  else
4406  appendPQExpBuffer(&buf,
4407  " CASE WHEN c.castfunc = 0 THEN '(binary coercible)'\n"
4408  " ELSE p.proname\n"
4409  " END AS \"%s\",\n",
4410  gettext_noop("Function"));
4411 
4412  appendPQExpBuffer(&buf,
4413  " CASE WHEN c.castcontext = '%c' THEN '%s'\n"
4414  " WHEN c.castcontext = '%c' THEN '%s'\n"
4415  " ELSE '%s'\n"
4416  " END AS \"%s\"",
4417  COERCION_CODE_EXPLICIT,
4418  gettext_noop("no"),
4419  COERCION_CODE_ASSIGNMENT,
4420  gettext_noop("in assignment"),
4421  gettext_noop("yes"),
4422  gettext_noop("Implicit?"));
4423 
4424  if (verbose)
4425  appendPQExpBuffer(&buf,
4426  ",\n d.description AS \"%s\"",
4427  gettext_noop("Description"));
4428 
4429  /*
4430  * We need a left join to pg_proc for binary casts; the others are just
4431  * paranoia.
4432  */
4433  appendPQExpBufferStr(&buf,
4434  "\nFROM pg_catalog.pg_cast c LEFT JOIN pg_catalog.pg_proc p\n"
4435  " ON c.castfunc = p.oid\n"
4436  " LEFT JOIN pg_catalog.pg_type ts\n"
4437  " ON c.castsource = ts.oid\n"
4438  " LEFT JOIN pg_catalog.pg_namespace ns\n"
4439  " ON ns.oid = ts.typnamespace\n"
4440  " LEFT JOIN pg_catalog.pg_type tt\n"
4441  " ON c.casttarget = tt.oid\n"
4442  " LEFT JOIN pg_catalog.pg_namespace nt\n"
4443  " ON nt.oid = tt.typnamespace\n");
4444 
4445  if (verbose)
4446  appendPQExpBufferStr(&buf,
4447  " LEFT JOIN pg_catalog.pg_description d\n"
4448  " ON d.classoid = c.tableoid AND d.objoid = "
4449  "c.oid AND d.objsubid = 0\n");
4450 
4451  appendPQExpBufferStr(&buf, "WHERE ( (true");
4452 
4453  /*
4454  * Match name pattern against either internal or external name of either
4455  * castsource or casttarget
4456  */
4457  processSQLNamePattern(pset.db, &buf, pattern, true, false,
4458  "ns.nspname", "ts.typname",
4459  "pg_catalog.format_type(ts.oid, NULL)",
4460  "pg_catalog.pg_type_is_visible(ts.oid)");
4461 
4462  appendPQExpBufferStr(&buf, ") OR (true");
4463 
4464  processSQLNamePattern(pset.db, &buf, pattern, true, false,
4465  "nt.nspname", "tt.typname",
4466  "pg_catalog.format_type(tt.oid, NULL)",
4467  "pg_catalog.pg_type_is_visible(tt.oid)");
4468 
4469  appendPQExpBufferStr(&buf, ") )\nORDER BY 1, 2;");
4470 
4471  res = PSQLexec(buf.data);
4472  termPQExpBuffer(&buf);
4473  if (!res)
4474  return false;
4475 
4476  myopt.nullPrint = NULL;
4477  myopt.title = _("List of casts");
4478  myopt.translate_header = true;
4479  myopt.translate_columns = translate_columns;
4480  myopt.n_translate_columns = lengthof(translate_columns);
4481 
4482  printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
4483 
4484  PQclear(res);
4485  return true;
4486 }
4487 
4488 /*
4489  * \dO
4490  *
4491  * Describes collations.
4492  */
4493 bool
4494 listCollations(const char *pattern, bool verbose, bool showSystem)
4495 {
4497  PGresult *res;
4498  printQueryOpt myopt = pset.popt;
4499  static const bool translate_columns[] = {false, false, false, false, false, true, false};
4500 
4501  if (pset.sversion < 90100)
4502  {
4503  char sverbuf[32];
4504 
4505  pg_log_error("The server (version %s) does not support collations.",
4507  sverbuf, sizeof(sverbuf)));
4508  return true;
4509  }
4510 
4511  initPQExpBuffer(&buf);
4512 
4513  printfPQExpBuffer(&buf,
4514  "SELECT n.nspname AS \"%s\",\n"
4515  " c.collname AS \"%s\",\n"
4516  " c.collcollate AS \"%s\",\n"
4517  " c.collctype AS \"%s\"",
4518  gettext_noop("Schema"),
4519  gettext_noop("Name"),
4520  gettext_noop("Collate"),
4521  gettext_noop("Ctype"));
4522 
4523  if (pset.sversion >= 100000)
4524  appendPQExpBuffer(&buf,
4525  ",\n CASE c.collprovider WHEN 'd' THEN 'default' WHEN 'c' THEN 'libc' WHEN 'i' THEN 'icu' END AS \"%s\"",
4526  gettext_noop("Provider"));
4527  else
4528  appendPQExpBuffer(&buf,
4529  ",\n 'libc' AS \"%s\"",
4530  gettext_noop("Provider"));
4531 
4532  if (pset.sversion >= 120000)
4533  appendPQExpBuffer(&buf,
4534  ",\n CASE WHEN c.collisdeterministic THEN '%s' ELSE '%s' END AS \"%s\"",
4535  gettext_noop("yes"), gettext_noop("no"),
4536  gettext_noop("Deterministic?"));
4537  else
4538  appendPQExpBuffer(&buf,
4539  ",\n '%s' AS \"%s\"",
4540  gettext_noop("yes"),
4541  gettext_noop("Deterministic?"));
4542 
4543  if (verbose)
4544  appendPQExpBuffer(&buf,
4545  ",\n pg_catalog.obj_description(c.oid, 'pg_collation') AS \"%s\"",
4546  gettext_noop("Description"));
4547 
4548  appendPQExpBufferStr(&buf,
4549  "\nFROM pg_catalog.pg_collation c, pg_catalog.pg_namespace n\n"
4550  "WHERE n.oid = c.collnamespace\n");
4551 
4552  if (!showSystem && !pattern)
4553  appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
4554  " AND n.nspname <> 'information_schema'\n");
4555 
4556  /*
4557  * Hide collations that aren't usable in the current database's encoding.
4558  * If you think to change this, note that pg_collation_is_visible rejects
4559  * unusable collations, so you will need to hack name pattern processing
4560  * somehow to avoid inconsistent behavior.
4561  */
4562  appendPQExpBufferStr(&buf, " AND c.collencoding IN (-1, pg_catalog.pg_char_to_encoding(pg_catalog.getdatabaseencoding()))\n");
4563 
4564  processSQLNamePattern(pset.db, &buf, pattern, true, false,
4565  "n.nspname", "c.collname", NULL,
4566  "pg_catalog.pg_collation_is_visible(c.oid)");
4567 
4568  appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
4569 
4570  res = PSQLexec(buf.data);
4571  termPQExpBuffer(&buf);
4572  if (!res)
4573  return false;
4574 
4575  myopt.nullPrint = NULL;
4576  myopt.title = _("List of collations");
4577  myopt.translate_header = true;
4578  myopt.translate_columns = translate_columns;
4579  myopt.n_translate_columns = lengthof(translate_columns);
4580 
4581  printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
4582 
4583  PQclear(res);
4584  return true;
4585 }
4586 
4587 /*
4588  * \dn
4589  *
4590  * Describes schemas (namespaces)
4591  */
4592 bool
4593 listSchemas(const char *pattern, bool verbose, bool showSystem)
4594 {
4596  PGresult *res;
4597  printQueryOpt myopt = pset.popt;
4598 
4599  initPQExpBuffer(&buf);
4600  printfPQExpBuffer(&buf,
4601  "SELECT n.nspname AS \"%s\",\n"
4602  " pg_catalog.pg_get_userbyid(n.nspowner) AS \"%s\"",
4603  gettext_noop("Name"),
4604  gettext_noop("Owner"));
4605 
4606  if (verbose)
4607  {
4608  appendPQExpBufferStr(&buf, ",\n ");
4609  printACLColumn(&buf, "n.nspacl");
4610  appendPQExpBuffer(&buf,
4611  ",\n pg_catalog.obj_description(n.oid, 'pg_namespace') AS \"%s\"",
4612  gettext_noop("Description"));
4613  }
4614 
4615  appendPQExpBufferStr(&buf,
4616  "\nFROM pg_catalog.pg_namespace n\n");
4617 
4618  if (!showSystem && !pattern)
4619  appendPQExpBufferStr(&buf,
4620  "WHERE n.nspname !~ '^pg_' AND n.nspname <> 'information_schema'\n");
4621 
4622  processSQLNamePattern(pset.db, &buf, pattern,
4623  !showSystem && !pattern, false,
4624  NULL, "n.nspname", NULL,
4625  NULL);
4626 
4627  appendPQExpBufferStr(&buf, "ORDER BY 1;");
4628 
4629  res = PSQLexec(buf.data);
4630  termPQExpBuffer(&buf);
4631  if (!res)
4632  return false;
4633 
4634  myopt.nullPrint = NULL;
4635  myopt.title = _("List of schemas");
4636  myopt.translate_header = true;
4637 
4638  printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
4639 
4640  PQclear(res);
4641  return true;
4642 }
4643 
4644 
4645 /*
4646  * \dFp
4647  * list text search parsers
4648  */
4649 bool
4650 listTSParsers(const char *pattern, bool verbose)
4651 {
4653  PGresult *res;
4654  printQueryOpt myopt = pset.popt;
4655 
4656  if (pset.sversion < 80300)
4657  {
4658  char sverbuf[32];
4659 
4660  pg_log_error("The server (version %s) does not support full text search.",
4662  sverbuf, sizeof(sverbuf)));
4663  return true;
4664  }
4665 
4666  if (verbose)
4667  return listTSParsersVerbose(pattern);
4668 
4669  initPQExpBuffer(&buf);
4670 
4671  printfPQExpBuffer(&buf,
4672  "SELECT\n"
4673  " n.nspname as \"%s\",\n"
4674  " p.prsname as \"%s\",\n"
4675  " pg_catalog.obj_description(p.oid, 'pg_ts_parser') as \"%s\"\n"
4676  "FROM pg_catalog.pg_ts_parser p\n"
4677  "LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.prsnamespace\n",
4678  gettext_noop("Schema"),
4679  gettext_noop("Name"),
4680  gettext_noop("Description")
4681  );
4682 
4683  processSQLNamePattern(pset.db, &buf, pattern, false, false,
4684  "n.nspname", "p.prsname", NULL,
4685  "pg_catalog.pg_ts_parser_is_visible(p.oid)");
4686 
4687  appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
4688 
4689  res = PSQLexec(buf.data);
4690  termPQExpBuffer(&buf);
4691  if (!res)
4692  return false;
4693 
4694  myopt.nullPrint = NULL;
4695  myopt.title = _("List of text search parsers");
4696  myopt.translate_header = true;
4697 
4698  printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
4699 
4700  PQclear(res);
4701  return true;
4702 }
4703 
4704 /*
4705  * full description of parsers
4706  */
4707 static bool
4708 listTSParsersVerbose(const char *pattern)
4709 {
4711  PGresult *res;
4712  int i;
4713 
4714  initPQExpBuffer(&buf);
4715 
4716  printfPQExpBuffer(&buf,
4717  "SELECT p.oid,\n"
4718  " n.nspname,\n"
4719  " p.prsname\n"
4720  "FROM pg_catalog.pg_ts_parser p\n"
4721  "LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.prsnamespace\n"
4722  );
4723 
4724  processSQLNamePattern(pset.db, &buf, pattern, false, false,
4725  "n.nspname", "p.prsname", NULL,
4726  "pg_catalog.pg_ts_parser_is_visible(p.oid)");
4727 
4728  appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
4729 
4730  res = PSQLexec(buf.data);
4731  termPQExpBuffer(&buf);
4732  if (!res)
4733  return false;
4734 
4735  if (PQntuples(res) == 0)
4736  {
4737  if (!pset.quiet)
4738  {
4739  if (pattern)
4740  pg_log_error("Did not find any text search parser named \"%s\".",
4741  pattern);
4742  else
4743  pg_log_error("Did not find any text search parsers.");
4744  }
4745  PQclear(res);
4746  return false;
4747  }
4748 
4749  for (i = 0; i < PQntuples(res); i++)
4750  {
4751  const char *oid;
4752  const char *nspname = NULL;
4753  const char *prsname;
4754 
4755  oid = PQgetvalue(res, i, 0);
4756  if (!PQgetisnull(res, i, 1))
4757  nspname = PQgetvalue(res, i, 1);
4758  prsname = PQgetvalue(res, i, 2);
4759 
4760  if (!describeOneTSParser(oid, nspname, prsname))
4761  {
4762  PQclear(res);
4763  return false;
4764  }
4765 
4766  if (cancel_pressed)
4767  {
4768  PQclear(res);
4769  return false;
4770  }
4771  }
4772 
4773  PQclear(res);
4774  return true;
4775 }
4776 
4777 static bool
4778 describeOneTSParser(const char *oid, const char *nspname, const char *prsname)
4779 {
4781  PGresult *res;
4782  PQExpBufferData title;
4783  printQueryOpt myopt = pset.popt;
4784  static const bool translate_columns[] = {true, false, false};
4785 
4786  initPQExpBuffer(&buf);
4787 
4788  printfPQExpBuffer(&buf,
4789  "SELECT '%s' AS \"%s\",\n"
4790  " p.prsstart::pg_catalog.regproc AS \"%s\",\n"
4791  " pg_catalog.obj_description(p.prsstart, 'pg_proc') as \"%s\"\n"
4792  " FROM pg_catalog.pg_ts_parser p\n"
4793  " WHERE p.oid = '%s'\n"
4794  "UNION ALL\n"
4795  "SELECT '%s',\n"
4796  " p.prstoken::pg_catalog.regproc,\n"
4797  " pg_catalog.obj_description(p.prstoken, 'pg_proc')\n"
4798  " FROM pg_catalog.pg_ts_parser p\n"
4799  " WHERE p.oid = '%s'\n"
4800  "UNION ALL\n"
4801  "SELECT '%s',\n"
4802  " p.prsend::pg_catalog.regproc,\n"
4803  " pg_catalog.obj_description(p.prsend, 'pg_proc')\n"
4804  " FROM pg_catalog.pg_ts_parser p\n"
4805  " WHERE p.oid = '%s'\n"
4806  "UNION ALL\n"
4807  "SELECT '%s',\n"
4808  " p.prsheadline::pg_catalog.regproc,\n"
4809  " pg_catalog.obj_description(p.prsheadline, 'pg_proc')\n"
4810  " FROM pg_catalog.pg_ts_parser p\n"
4811  " WHERE p.oid = '%s'\n"
4812  "UNION ALL\n"
4813  "SELECT '%s',\n"
4814  " p.prslextype::pg_catalog.regproc,\n"
4815  " pg_catalog.obj_description(p.prslextype, 'pg_proc')\n"
4816  " FROM pg_catalog.pg_ts_parser p\n"
4817  " WHERE p.oid = '%s';",
4818  gettext_noop("Start parse"),
4819  gettext_noop("Method"),
4820  gettext_noop("Function"),
4821  gettext_noop("Description"),
4822  oid,
4823  gettext_noop("Get next token"),
4824  oid,
4825  gettext_noop("End parse"),
4826  oid,
4827  gettext_noop("Get headline"),
4828  oid,
4829  gettext_noop("Get token types"),
4830  oid);
4831 
4832  res = PSQLexec(buf.data);
4833  termPQExpBuffer(&buf);
4834  if (!res)
4835  return false;
4836 
4837  myopt.nullPrint = NULL;
4838  initPQExpBuffer(&title);
4839  if (nspname)
4840  printfPQExpBuffer(&title, _("Text search parser \"%s.%s\""),
4841  nspname, prsname);
4842  else
4843  printfPQExpBuffer(&title, _("Text search parser \"%s\""), prsname);
4844  myopt.title = title.data;
4845  myopt.footers = NULL;
4846  myopt.topt.default_footer = false;
4847  myopt.translate_header = true;
4848  myopt.translate_columns = translate_columns;
4849  myopt.n_translate_columns = lengthof(translate_columns);
4850 
4851  printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
4852 
4853  PQclear(res);
4854 
4855  initPQExpBuffer(&buf);
4856 
4857  printfPQExpBuffer(&buf,
4858  "SELECT t.alias as \"%s\",\n"
4859  " t.description as \"%s\"\n"
4860  "FROM pg_catalog.ts_token_type( '%s'::pg_catalog.oid ) as t\n"
4861  "ORDER BY 1;",
4862  gettext_noop("Token name"),
4863  gettext_noop("Description"),
4864  oid);
4865 
4866  res = PSQLexec(buf.data);
4867  termPQExpBuffer(&buf);
4868  if (!res)
4869  return false;
4870 
4871  myopt.nullPrint = NULL;
4872  if (nspname)
4873  printfPQExpBuffer(&title, _("Token types for parser \"%s.%s\""),
4874  nspname, prsname);
4875  else
4876  printfPQExpBuffer(&title, _("Token types for parser \"%s\""), prsname);
4877  myopt.title = title.data;
4878  myopt.footers = NULL;
4879  myopt.topt.default_footer = true;
4880  myopt.translate_header = true;
4881  myopt.translate_columns = NULL;
4882  myopt.n_translate_columns = 0;
4883 
4884  printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
4885 
4886  termPQExpBuffer(&title);
4887  PQclear(res);
4888  return true;
4889 }
4890 
4891 
4892 /*
4893  * \dFd
4894  * list text search dictionaries
4895  */
4896 bool
4897 listTSDictionaries(const char *pattern, bool verbose)
4898 {
4900  PGresult *res;
4901  printQueryOpt myopt = pset.popt;
4902 
4903  if (pset.sversion < 80300)
4904  {
4905  char sverbuf[32];
4906 
4907  pg_log_error("The server (version %s) does not support full text search.",
4909  sverbuf, sizeof(sverbuf)));
4910  return true;
4911  }
4912 
4913  initPQExpBuffer(&buf);
4914 
4915  printfPQExpBuffer(&buf,
4916  "SELECT\n"
4917  " n.nspname as \"%s\",\n"
4918  " d.dictname as \"%s\",\n",
4919  gettext_noop("Schema"),
4920  gettext_noop("Name"));
4921 
4922  if (verbose)
4923  {
4924  appendPQExpBuffer(&buf,
4925  " ( SELECT COALESCE(nt.nspname, '(null)')::pg_catalog.text || '.' || t.tmplname FROM\n"
4926  " pg_catalog.pg_ts_template t\n"
4927  " LEFT JOIN pg_catalog.pg_namespace nt ON nt.oid = t.tmplnamespace\n"
4928  " WHERE d.dicttemplate = t.oid ) AS \"%s\",\n"
4929  " d.dictinitoption as \"%s\",\n",
4930  gettext_noop("Template"),
4931  gettext_noop("Init options"));
4932  }
4933 
4934  appendPQExpBuffer(&buf,
4935  " pg_catalog.obj_description(d.oid, 'pg_ts_dict') as \"%s\"\n",
4936  gettext_noop("Description"));
4937 
4938  appendPQExpBufferStr(&buf, "FROM pg_catalog.pg_ts_dict d\n"
4939  "LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.dictnamespace\n");
4940 
4941  processSQLNamePattern(pset.db, &buf, pattern, false, false,
4942  "n.nspname", "d.dictname", NULL,
4943  "pg_catalog.pg_ts_dict_is_visible(d.oid)");
4944 
4945  appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
4946 
4947  res = PSQLexec(buf.data);
4948  termPQExpBuffer(&buf);
4949  if (!res)
4950  return false;
4951 
4952  myopt.nullPrint = NULL;
4953  myopt.title = _("List of text search dictionaries");
4954  myopt.translate_header = true;
4955 
4956  printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
4957 
4958  PQclear(res);
4959  return true;
4960 }
4961 
4962 
4963 /*
4964  * \dFt
4965  * list text search templates
4966  */
4967 bool
4968 listTSTemplates(const char *pattern, bool verbose)
4969 {
4971  PGresult *res;
4972  printQueryOpt myopt = pset.popt;
4973 
4974  if (pset.sversion < 80300)
4975  {
4976  char sverbuf[32];
4977 
4978  pg_log_error("The server (version %s) does not support full text search.",
4980  sverbuf, sizeof(sverbuf)));
4981  return true;
4982  }
4983 
4984  initPQExpBuffer(&buf);
4985 
4986  if (verbose)
4987  printfPQExpBuffer(&buf,
4988  "SELECT\n"
4989  " n.nspname AS \"%s\",\n"
4990  " t.tmplname AS \"%s\",\n"
4991  " t.tmplinit::pg_catalog.regproc AS \"%s\",\n"
4992  " t.tmpllexize::pg_catalog.regproc AS \"%s\",\n"
4993  " pg_catalog.obj_description(t.oid, 'pg_ts_template') AS \"%s\"\n",
4994  gettext_noop("Schema"),
4995  gettext_noop("Name"),
4996  gettext_noop("Init"),
4997  gettext_noop("Lexize"),
4998  gettext_noop("Description"));
4999  else
5000  printfPQExpBuffer(&buf,
5001  "SELECT\n"
5002  " n.nspname AS \"%s\",\n"
5003  " t.tmplname AS \"%s\",\n"
5004  " pg_catalog.obj_description(t.oid, 'pg_ts_template') AS \"%s\"\n",
5005  gettext_noop("Schema"),
5006  gettext_noop("Name"),
5007  gettext_noop("Description"));
5008 
5009  appendPQExpBufferStr(&buf, "FROM pg_catalog.pg_ts_template t\n"
5010  "LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.tmplnamespace\n");
5011 
5012  processSQLNamePattern(pset.db, &buf, pattern, false, false,
5013  "n.nspname", "t.tmplname", NULL,
5014  "pg_catalog.pg_ts_template_is_visible(t.oid)");
5015 
5016  appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
5017 
5018  res = PSQLexec(buf.data);
5019  termPQExpBuffer(&buf);
5020  if (!res)
5021  return false;
5022 
5023  myopt.nullPrint = NULL;
5024  myopt.title = _("List of text search templates");
5025  myopt.translate_header = true;
5026 
5027  printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
5028 
5029  PQclear(res);
5030  return true;
5031 }
5032 
5033 
5034 /*
5035  * \dF
5036  * list text search configurations
5037  */
5038 bool
5039 listTSConfigs(const char *pattern, bool verbose)
5040 {
5042  PGresult *res;
5043  printQueryOpt myopt = pset.popt;
5044 
5045  if (pset.sversion < 80300)
5046  {
5047  char sverbuf[32];
5048 
5049  pg_log_error("The server (version %s) does not support full text search.",
5051  sverbuf, sizeof(sverbuf)));
5052  return true;
5053  }
5054 
5055  if (verbose)
5056  return listTSConfigsVerbose(pattern);
5057 
5058  initPQExpBuffer(&buf);
5059 
5060  printfPQExpBuffer(&buf,
5061  "SELECT\n"
5062  " n.nspname as \"%s\",\n"
5063  " c.cfgname as \"%s\",\n"
5064  " pg_catalog.obj_description(c.oid, 'pg_ts_config') as \"%s\"\n"
5065  "FROM pg_catalog.pg_ts_config c\n"
5066  "LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.cfgnamespace\n",
5067  gettext_noop("Schema"),
5068  gettext_noop("Name"),
5069  gettext_noop("Description")
5070  );
5071 
5072  processSQLNamePattern(pset.db, &buf, pattern, false, false,
5073  "n.nspname", "c.cfgname", NULL,
5074  "pg_catalog.pg_ts_config_is_visible(c.oid)");
5075 
5076  appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
5077 
5078  res = PSQLexec(buf.data);
5079  termPQExpBuffer(&buf);
5080  if (!res)
5081  return false;
5082 
5083  myopt.nullPrint = NULL;
5084  myopt.title = _("List of text search configurations");
5085  myopt.translate_header = true;
5086 
5087  printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
5088 
5089  PQclear(res);
5090  return true;
5091 }
5092 
5093 static bool
5094 listTSConfigsVerbose(const char *pattern)
5095 {
5097  PGresult *res;
5098  int i;
5099 
5100  initPQExpBuffer(&buf);
5101 
5102  printfPQExpBuffer(&buf,
5103  "SELECT c.oid, c.cfgname,\n"
5104  " n.nspname,\n"
5105  " p.prsname,\n"
5106  " np.nspname as pnspname\n"
5107  "FROM pg_catalog.pg_ts_config c\n"
5108  " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.cfgnamespace,\n"
5109  " pg_catalog.pg_ts_parser p\n"
5110  " LEFT JOIN pg_catalog.pg_namespace np ON np.oid = p.prsnamespace\n"
5111  "WHERE p.oid = c.cfgparser\n"
5112  );
5113 
5114  processSQLNamePattern(pset.db, &buf, pattern, true, false,
5115  "n.nspname", "c.cfgname", NULL,
5116  "pg_catalog.pg_ts_config_is_visible(c.oid)");
5117 
5118  appendPQExpBufferStr(&buf, "ORDER BY 3, 2;");
5119 
5120  res = PSQLexec(buf.data);
5121  termPQExpBuffer(&buf);
5122  if (!res)
5123  return false;
5124 
5125  if (PQntuples(res) == 0)
5126  {
5127  if (!pset.quiet)
5128  {
5129  if (pattern)
5130  pg_log_error("Did not find any text search configuration named \"%s\".",
5131  pattern);
5132  else
5133  pg_log_error("Did not find any text search configurations.");
5134  }
5135  PQclear(res);
5136  return false;
5137  }
5138 
5139  for (i = 0; i < PQntuples(res); i++)
5140  {
5141  const char *oid;
5142  const char *cfgname;
5143  const char *nspname = NULL;
5144  const char *prsname;
5145  const char *pnspname = NULL;
5146 
5147  oid = PQgetvalue(res, i, 0);
5148  cfgname = PQgetvalue(res, i, 1);
5149  if (!PQgetisnull(res, i, 2))
5150  nspname = PQgetvalue(res, i, 2);
5151  prsname = PQgetvalue(res, i, 3);
5152  if (!PQgetisnull(res, i, 4))
5153  pnspname = PQgetvalue(res, i, 4);
5154 
5155  if (!describeOneTSConfig(oid, nspname, cfgname, pnspname, prsname))
5156  {
5157  PQclear(res);
5158  return false;
5159  }
5160 
5161  if (cancel_pressed)
5162  {
5163  PQclear(res);
5164  return false;
5165  }
5166  }
5167 
5168  PQclear(res);
5169  return true;
5170 }
5171 
5172 static bool
5173 describeOneTSConfig(const char *oid, const char *nspname, const char *cfgname,
5174  const char *pnspname, const char *prsname)
5175 {
5177  title;
5178  PGresult *res;
5179  printQueryOpt myopt = pset.popt;
5180 
5181  initPQExpBuffer(&buf);
5182 
5183  printfPQExpBuffer(&buf,
5184  "SELECT\n"
5185  " ( SELECT t.alias FROM\n"
5186  " pg_catalog.ts_token_type(c.cfgparser) AS t\n"
5187  " WHERE t.tokid = m.maptokentype ) AS \"%s\",\n"
5188  " pg_catalog.btrim(\n"
5189  " ARRAY( SELECT mm.mapdict::pg_catalog.regdictionary\n"
5190  " FROM pg_catalog.pg_ts_config_map AS mm\n"
5191  " WHERE mm.mapcfg = m.mapcfg AND mm.maptokentype = m.maptokentype\n"
5192  " ORDER BY mapcfg, maptokentype, mapseqno\n"
5193  " ) :: pg_catalog.text,\n"
5194  " '{}') AS \"%s\"\n"
5195  "FROM pg_catalog.pg_ts_config AS c, pg_catalog.pg_ts_config_map AS m\n"
5196  "WHERE c.oid = '%s' AND m.mapcfg = c.oid\n"
5197  "GROUP BY m.mapcfg, m.maptokentype, c.cfgparser\n"
5198  "ORDER BY 1;",
5199  gettext_noop("Token"),
5200  gettext_noop("Dictionaries"),
5201  oid);
5202 
5203  res = PSQLexec(buf.data);
5204  termPQExpBuffer(&buf);
5205  if (!res)
5206  return false;
5207 
5208  initPQExpBuffer(&title);
5209 
5210  if (nspname)
5211  appendPQExpBuffer(&title, _("Text search configuration \"%s.%s\""),
5212  nspname, cfgname);
5213  else
5214  appendPQExpBuffer(&title, _("Text search configuration \"%s\""),
5215  cfgname);
5216 
5217  if (pnspname)
5218  appendPQExpBuffer(&title, _("\nParser: \"%s.%s\""),
5219  pnspname, prsname);
5220  else
5221  appendPQExpBuffer(&title, _("\nParser: \"%s\""),
5222  prsname);
5223 
5224  myopt.nullPrint = NULL;
5225  myopt.title = title.data;
5226  myopt.footers = NULL;
5227  myopt.topt.default_footer = false;
5228  myopt.translate_header = true;
5229 
5230  printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
5231 
5232  termPQExpBuffer(&title);
5233 
5234  PQclear(res);
5235  return true;
5236 }
5237 
5238 
5239 /*
5240  * \dew
5241  *
5242  * Describes foreign-data wrappers
5243  */
5244 bool
5245 listForeignDataWrappers(const char *pattern, bool verbose)
5246 {
5248  PGresult *res;
5249  printQueryOpt myopt = pset.popt;
5250 
5251  if (pset.sversion < 80400)
5252  {
5253  char sverbuf[32];
5254 
5255  pg_log_error("The server (version %s) does not support foreign-data wrappers.",
5257  sverbuf, sizeof(sverbuf)));
5258  return true;
5259  }
5260 
5261  initPQExpBuffer(&buf);
5262  printfPQExpBuffer(&buf,
5263  "SELECT fdw.fdwname AS \"%s\",\n"
5264  " pg_catalog.pg_get_userbyid(fdw.fdwowner) AS \"%s\",\n",
5265  gettext_noop("Name"),
5266  gettext_noop("Owner"));
5267  if (pset.sversion >= 90100)
5268  appendPQExpBuffer(&buf,
5269  " fdw.fdwhandler::pg_catalog.regproc AS \"%s\",\n",
5270  gettext_noop("Handler"));
5271  appendPQExpBuffer(&buf,
5272  " fdw.fdwvalidator::pg_catalog.regproc AS \"%s\"",
5273  gettext_noop("Validator"));
5274 
5275  if (verbose)
5276  {
5277  appendPQExpBufferStr(&buf, ",\n ");
5278  printACLColumn(&buf, "fdwacl");
5279  appendPQExpBuffer(&buf,
5280  ",\n CASE WHEN fdwoptions IS NULL THEN '' ELSE "
5281  " '(' || pg_catalog.array_to_string(ARRAY(SELECT "
5282  " pg_catalog.quote_ident(option_name) || ' ' || "
5283  " pg_catalog.quote_literal(option_value) FROM "
5284  " pg_catalog.pg_options_to_table(fdwoptions)), ', ') || ')' "
5285  " END AS \"%s\"",
5286  gettext_noop("FDW options"));
5287 
5288  if (pset.sversion >= 90100)
5289  appendPQExpBuffer(&buf,
5290  ",\n d.description AS \"%s\" ",
5291  gettext_noop("Description"));
5292  }
5293 
5294  appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_foreign_data_wrapper fdw\n");
5295 
5296  if (verbose && pset.sversion >= 90100)
5297  appendPQExpBufferStr(&buf,
5298  "LEFT JOIN pg_catalog.pg_description d\n"
5299  " ON d.classoid = fdw.tableoid "
5300  "AND d.objoid = fdw.oid AND d.objsubid = 0\n");
5301 
5302  processSQLNamePattern(pset.db, &buf, pattern, false, false,
5303  NULL, "fdwname", NULL, NULL);
5304 
5305  appendPQExpBufferStr(&buf, "ORDER BY 1;");
5306 
5307  res = PSQLexec(buf.data);
5308  termPQExpBuffer(&buf);
5309  if (!res)
5310  return false;
5311 
5312  myopt.nullPrint = NULL;
5313  myopt.title = _("List of foreign-data wrappers");
5314  myopt.translate_header = true;
5315 
5316  printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
5317 
5318  PQclear(res);
5319  return true;
5320 }
5321 
5322 /*
5323  * \des
5324  *
5325  * Describes foreign servers.
5326  */
5327 bool
5328 listForeignServers(const char *pattern, bool verbose)
5329 {
5331  PGresult *res;
5332  printQueryOpt myopt = pset.popt;
5333 
5334  if (pset.sversion < 80400)
5335  {
5336  char sverbuf[32];
5337 
5338  pg_log_error("The server (version %s) does not support foreign servers.",
5340  sverbuf, sizeof(sverbuf)));
5341  return true;
5342  }
5343 
5344  initPQExpBuffer(&buf);
5345  printfPQExpBuffer(&buf,
5346  "SELECT s.srvname AS \"%s\",\n"
5347  " pg_catalog.pg_get_userbyid(s.srvowner) AS \"%s\",\n"
5348  " f.fdwname AS \"%s\"",
5349  gettext_noop("Name"),
5350  gettext_noop("Owner"),
5351  gettext_noop("Foreign-data wrapper"));
5352 
5353  if (verbose)
5354  {
5355  appendPQExpBufferStr(&buf, ",\n ");
5356  printACLColumn(&buf, "s.srvacl");
5357  appendPQExpBuffer(&buf,
5358  ",\n"
5359  " s.srvtype AS \"%s\",\n"
5360  " s.srvversion AS \"%s\",\n"
5361  " CASE WHEN srvoptions IS NULL THEN '' ELSE "
5362  " '(' || pg_catalog.array_to_string(ARRAY(SELECT "
5363  " pg_catalog.quote_ident(option_name) || ' ' || "
5364  " pg_catalog.quote_literal(option_value) FROM "
5365  " pg_catalog.pg_options_to_table(srvoptions)), ', ') || ')' "
5366  " END AS \"%s\",\n"
5367  " d.description AS \"%s\"",
5368  gettext_noop("Type"),
5369  gettext_noop("Version"),
5370  gettext_noop("FDW options"),
5371  gettext_noop("Description"));
5372  }
5373 
5374  appendPQExpBufferStr(&buf,
5375  "\nFROM pg_catalog.pg_foreign_server s\n"
5376  " JOIN pg_catalog.pg_foreign_data_wrapper f ON f.oid=s.srvfdw\n");
5377 
5378  if (verbose)
5379  appendPQExpBufferStr(&buf,
5380  "LEFT JOIN pg_catalog.pg_description d\n "
5381  "ON d.classoid = s.tableoid AND d.objoid = s.oid "
5382  "AND d.objsubid = 0\n");
5383 
5384  processSQLNamePattern(pset.db, &buf, pattern, false, false,
5385  NULL, "s.srvname", NULL, NULL);
5386 
5387  appendPQExpBufferStr(&buf, "ORDER BY 1;");
5388 
5389  res = PSQLexec(buf.data);
5390  termPQExpBuffer(&buf);
5391  if (!res)
5392  return false;
5393 
5394  myopt.nullPrint = NULL;
5395  myopt.title = _("List of foreign servers");
5396  myopt.translate_header = true;
5397 
5398  printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
5399 
5400  PQclear(res);
5401  return true;
5402 }
5403 
5404 /*
5405  * \deu
5406  *
5407  * Describes user mappings.
5408  */
5409 bool
5410 listUserMappings(const char *pattern, bool verbose)
5411 {
5413  PGresult *res;
5414  printQueryOpt myopt = pset.popt;
5415 
5416  if (pset.sversion < 80400)
5417  {
5418  char sverbuf[32];
5419 
5420  pg_log_error("The server (version %s) does not support user mappings.",
5422  sverbuf, sizeof(sverbuf)));
5423  return true;
5424  }
5425 
5426  initPQExpBuffer(&buf);
5427  printfPQExpBuffer(&buf,
5428  "SELECT um.srvname AS \"%s\",\n"
5429  " um.usename AS \"%s\"",
5430  gettext_noop("Server"),
5431  gettext_noop("User name"));
5432 
5433  if (verbose)
5434  appendPQExpBuffer(&buf,
5435  ",\n CASE WHEN umoptions IS NULL THEN '' ELSE "
5436  " '(' || pg_catalog.array_to_string(ARRAY(SELECT "
5437  " pg_catalog.quote_ident(option_name) || ' ' || "
5438  " pg_catalog.quote_literal(option_value) FROM "
5439  " pg_catalog.pg_options_to_table(umoptions)), ', ') || ')' "
5440  " END AS \"%s\"",
5441  gettext_noop("FDW options"));
5442 
5443  appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_user_mappings um\n");
5444 
5445  processSQLNamePattern(pset.db, &buf, pattern, false, false,
5446  NULL, "um.srvname", "um.usename", NULL);
5447 
5448  appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
5449 
5450  res = PSQLexec(buf.data);
5451  termPQExpBuffer(&buf);
5452  if (!res)
5453  return false;
5454 
5455  myopt.nullPrint = NULL;
5456  myopt.title = _("List of user mappings");
5457  myopt.translate_header = true;
5458 
5459  printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
5460 
5461  PQclear(res);
5462  return true;
5463 }
5464 
5465 /*
5466  * \det
5467  *
5468  * Describes foreign tables.
5469  */
5470 bool
5471 listForeignTables(const char *pattern, bool verbose)
5472 {
5474  PGresult *res;
5475  printQueryOpt myopt = pset.popt;
5476 
5477  if (pset.sversion < 90100)
5478  {
5479  char sverbuf[32];
5480 
5481  pg_log_error("The server (version %s) does not support foreign tables.",
5483  sverbuf, sizeof(sverbuf)));
5484  return true;
5485  }
5486 
5487  initPQExpBuffer(&buf);
5488  printfPQExpBuffer(&buf,
5489  "SELECT n.nspname AS \"%s\",\n"
5490  " c.relname AS \"%s\",\n"
5491  " s.srvname AS \"%s\"",
5492  gettext_noop("Schema"),
5493  gettext_noop("Table"),
5494  gettext_noop("Server"));
5495 
5496  if (verbose)
5497  appendPQExpBuffer(&buf,
5498  ",\n CASE WHEN ftoptions IS NULL THEN '' ELSE "
5499  " '(' || pg_catalog.array_to_string(ARRAY(SELECT "
5500  " pg_catalog.quote_ident(option_name) || ' ' || "
5501  " pg_catalog.quote_literal(option_value) FROM "
5502  " pg_catalog.pg_options_to_table(ftoptions)), ', ') || ')' "
5503  " END AS \"%s\",\n"
5504  " d.description AS \"%s\"",
5505  gettext_noop("FDW options"),
5506  gettext_noop("Description"));
5507 
5508  appendPQExpBufferStr(&buf,
5509  "\nFROM pg_catalog.pg_foreign_table ft\n"
5510  " INNER JOIN pg_catalog.pg_class c"
5511  " ON c.oid = ft.ftrelid\n"
5512  " INNER JOIN pg_catalog.pg_namespace n"
5513  " ON n.oid = c.relnamespace\n"
5514  " INNER JOIN pg_catalog.pg_foreign_server s"
5515  " ON s.oid = ft.ftserver\n");
5516  if (verbose)
5517  appendPQExpBufferStr(&buf,
5518  " LEFT JOIN pg_catalog.pg_description d\n"
5519  " ON d.classoid = c.tableoid AND "
5520  "d.objoid = c.oid AND d.objsubid = 0\n");
5521 
5522  processSQLNamePattern(pset.db, &buf, pattern, false, false,
5523  "n.nspname", "c.relname", NULL,
5524  "pg_catalog.pg_table_is_visible(c.oid)");
5525 
5526  appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
5527 
5528  res = PSQLexec(buf.data);
5529  termPQ