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