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