Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * reindexdb
4 : *
5 : * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
6 : *
7 : * src/bin/scripts/reindexdb.c
8 : *
9 : *-------------------------------------------------------------------------
10 : */
11 :
12 : #include "postgres_fe.h"
13 :
14 : #include <limits.h>
15 :
16 : #include "catalog/pg_class_d.h"
17 : #include "common.h"
18 : #include "common/logging.h"
19 : #include "fe_utils/cancel.h"
20 : #include "fe_utils/option_utils.h"
21 : #include "fe_utils/parallel_slot.h"
22 : #include "fe_utils/query_utils.h"
23 : #include "fe_utils/simple_list.h"
24 : #include "fe_utils/string_utils.h"
25 :
26 : typedef enum ReindexType
27 : {
28 : REINDEX_DATABASE,
29 : REINDEX_INDEX,
30 : REINDEX_SCHEMA,
31 : REINDEX_SYSTEM,
32 : REINDEX_TABLE,
33 : } ReindexType;
34 :
35 :
36 : static SimpleStringList *get_parallel_object_list(PGconn *conn,
37 : ReindexType type,
38 : SimpleStringList *user_list,
39 : bool echo);
40 : static void reindex_one_database(ConnParams *cparams, ReindexType type,
41 : SimpleStringList *user_list,
42 : const char *progname,
43 : bool echo, bool verbose, bool concurrently,
44 : int concurrentCons, const char *tablespace);
45 : static void reindex_all_databases(ConnParams *cparams,
46 : const char *progname, bool echo,
47 : bool quiet, bool verbose, bool concurrently,
48 : int concurrentCons, const char *tablespace,
49 : bool syscatalog, SimpleStringList *schemas,
50 : SimpleStringList *tables,
51 : SimpleStringList *indexes);
52 : static void run_reindex_command(PGconn *conn, ReindexType type,
53 : const char *name, bool echo, bool verbose,
54 : bool concurrently, bool async,
55 : const char *tablespace);
56 :
57 : static void help(const char *progname);
58 :
59 : int
60 82 : main(int argc, char *argv[])
61 : {
62 : static struct option long_options[] = {
63 : {"host", required_argument, NULL, 'h'},
64 : {"port", required_argument, NULL, 'p'},
65 : {"username", required_argument, NULL, 'U'},
66 : {"no-password", no_argument, NULL, 'w'},
67 : {"password", no_argument, NULL, 'W'},
68 : {"echo", no_argument, NULL, 'e'},
69 : {"quiet", no_argument, NULL, 'q'},
70 : {"schema", required_argument, NULL, 'S'},
71 : {"dbname", required_argument, NULL, 'd'},
72 : {"all", no_argument, NULL, 'a'},
73 : {"system", no_argument, NULL, 's'},
74 : {"table", required_argument, NULL, 't'},
75 : {"index", required_argument, NULL, 'i'},
76 : {"jobs", required_argument, NULL, 'j'},
77 : {"verbose", no_argument, NULL, 'v'},
78 : {"concurrently", no_argument, NULL, 1},
79 : {"maintenance-db", required_argument, NULL, 2},
80 : {"tablespace", required_argument, NULL, 3},
81 : {NULL, 0, NULL, 0}
82 : };
83 :
84 : const char *progname;
85 : int optindex;
86 : int c;
87 :
88 82 : const char *dbname = NULL;
89 82 : const char *maintenance_db = NULL;
90 82 : const char *host = NULL;
91 82 : const char *port = NULL;
92 82 : const char *username = NULL;
93 82 : const char *tablespace = NULL;
94 82 : enum trivalue prompt_password = TRI_DEFAULT;
95 : ConnParams cparams;
96 82 : bool syscatalog = false;
97 82 : bool alldb = false;
98 82 : bool echo = false;
99 82 : bool quiet = false;
100 82 : bool verbose = false;
101 82 : bool concurrently = false;
102 82 : SimpleStringList indexes = {NULL, NULL};
103 82 : SimpleStringList tables = {NULL, NULL};
104 82 : SimpleStringList schemas = {NULL, NULL};
105 82 : int concurrentCons = 1;
106 :
107 82 : pg_logging_init(argv[0]);
108 82 : progname = get_progname(argv[0]);
109 82 : set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pgscripts"));
110 :
111 82 : handle_help_version_opts(argc, argv, "reindexdb", help);
112 :
113 : /* process command-line options */
114 226 : while ((c = getopt_long(argc, argv, "ad:eh:i:j:qp:sS:t:U:vwW", long_options, &optindex)) != -1)
115 : {
116 150 : switch (c)
117 : {
118 14 : case 'a':
119 14 : alldb = true;
120 14 : break;
121 4 : case 'd':
122 4 : dbname = pg_strdup(optarg);
123 4 : break;
124 8 : case 'e':
125 8 : echo = true;
126 8 : break;
127 0 : case 'h':
128 0 : host = pg_strdup(optarg);
129 0 : break;
130 16 : case 'i':
131 16 : simple_string_list_append(&indexes, optarg);
132 16 : break;
133 10 : case 'j':
134 10 : if (!option_parse_int(optarg, "-j/--jobs", 1, INT_MAX,
135 : &concurrentCons))
136 0 : exit(1);
137 10 : break;
138 0 : case 'q':
139 0 : quiet = true;
140 0 : break;
141 0 : case 'p':
142 0 : port = pg_strdup(optarg);
143 0 : break;
144 16 : case 's':
145 16 : syscatalog = true;
146 16 : break;
147 14 : case 'S':
148 14 : simple_string_list_append(&schemas, optarg);
149 14 : break;
150 24 : case 't':
151 24 : simple_string_list_append(&tables, optarg);
152 24 : break;
153 0 : case 'U':
154 0 : username = pg_strdup(optarg);
155 0 : break;
156 8 : case 'v':
157 8 : verbose = true;
158 8 : break;
159 0 : case 'w':
160 0 : prompt_password = TRI_NO;
161 0 : break;
162 0 : case 'W':
163 0 : prompt_password = TRI_YES;
164 0 : break;
165 20 : case 1:
166 20 : concurrently = true;
167 20 : break;
168 0 : case 2:
169 0 : maintenance_db = pg_strdup(optarg);
170 0 : break;
171 14 : case 3:
172 14 : tablespace = pg_strdup(optarg);
173 14 : break;
174 2 : default:
175 : /* getopt_long already emitted a complaint */
176 2 : pg_log_error_hint("Try \"%s --help\" for more information.", progname);
177 2 : exit(1);
178 : }
179 : }
180 :
181 : /*
182 : * Non-option argument specifies database name as long as it wasn't
183 : * already specified with -d / --dbname
184 : */
185 76 : if (optind < argc && dbname == NULL)
186 : {
187 56 : dbname = argv[optind];
188 56 : optind++;
189 : }
190 :
191 76 : if (optind < argc)
192 : {
193 0 : pg_log_error("too many command-line arguments (first is \"%s\")",
194 : argv[optind]);
195 0 : pg_log_error_hint("Try \"%s --help\" for more information.", progname);
196 0 : exit(1);
197 : }
198 :
199 : /* fill cparams except for dbname, which is set below */
200 76 : cparams.pghost = host;
201 76 : cparams.pgport = port;
202 76 : cparams.pguser = username;
203 76 : cparams.prompt_password = prompt_password;
204 76 : cparams.override_dbname = NULL;
205 :
206 76 : setup_cancel_handler(NULL);
207 :
208 76 : if (concurrentCons > 1 && syscatalog)
209 2 : pg_fatal("cannot use multiple jobs to reindex system catalogs");
210 :
211 74 : if (alldb)
212 : {
213 14 : if (dbname)
214 0 : pg_fatal("cannot reindex all databases and a specific one at the same time");
215 :
216 14 : cparams.dbname = maintenance_db;
217 :
218 14 : reindex_all_databases(&cparams, progname, echo, quiet, verbose,
219 : concurrently, concurrentCons, tablespace,
220 : syscatalog, &schemas, &tables, &indexes);
221 : }
222 : else
223 : {
224 60 : if (dbname == NULL)
225 : {
226 2 : if (getenv("PGDATABASE"))
227 2 : dbname = getenv("PGDATABASE");
228 0 : else if (getenv("PGUSER"))
229 0 : dbname = getenv("PGUSER");
230 : else
231 0 : dbname = get_user_name_or_exit(progname);
232 : }
233 :
234 60 : cparams.dbname = dbname;
235 :
236 60 : if (syscatalog)
237 12 : reindex_one_database(&cparams, REINDEX_SYSTEM, NULL,
238 : progname, echo, verbose,
239 : concurrently, 1, tablespace);
240 :
241 58 : if (schemas.head != NULL)
242 10 : reindex_one_database(&cparams, REINDEX_SCHEMA, &schemas,
243 : progname, echo, verbose,
244 : concurrently, concurrentCons, tablespace);
245 :
246 58 : if (indexes.head != NULL)
247 12 : reindex_one_database(&cparams, REINDEX_INDEX, &indexes,
248 : progname, echo, verbose,
249 : concurrently, concurrentCons, tablespace);
250 :
251 54 : if (tables.head != NULL)
252 22 : reindex_one_database(&cparams, REINDEX_TABLE, &tables,
253 : progname, echo, verbose,
254 : concurrently, concurrentCons, tablespace);
255 :
256 : /*
257 : * reindex database only if neither index nor table nor schema nor
258 : * system catalogs is specified
259 : */
260 50 : if (!syscatalog && indexes.head == NULL &&
261 34 : tables.head == NULL && schemas.head == NULL)
262 10 : reindex_one_database(&cparams, REINDEX_DATABASE, NULL,
263 : progname, echo, verbose,
264 : concurrently, concurrentCons, tablespace);
265 : }
266 :
267 62 : exit(0);
268 : }
269 :
270 : static void
271 104 : reindex_one_database(ConnParams *cparams, ReindexType type,
272 : SimpleStringList *user_list,
273 : const char *progname, bool echo,
274 : bool verbose, bool concurrently, int concurrentCons,
275 : const char *tablespace)
276 : {
277 : PGconn *conn;
278 : SimpleStringListCell *cell;
279 104 : SimpleStringListCell *indices_tables_cell = NULL;
280 104 : bool parallel = concurrentCons > 1;
281 104 : SimpleStringList *process_list = user_list;
282 104 : SimpleStringList *indices_tables_list = NULL;
283 104 : ReindexType process_type = type;
284 : ParallelSlotArray *sa;
285 104 : bool failed = false;
286 104 : int items_count = 0;
287 104 : char *prev_index_table_name = NULL;
288 104 : ParallelSlot *free_slot = NULL;
289 :
290 104 : conn = connectDatabase(cparams, progname, echo, false, true);
291 :
292 102 : if (concurrently && PQserverVersion(conn) < 120000)
293 : {
294 0 : PQfinish(conn);
295 0 : pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
296 : "concurrently", "12");
297 : }
298 :
299 102 : if (tablespace && PQserverVersion(conn) < 140000)
300 : {
301 0 : PQfinish(conn);
302 0 : pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
303 : "tablespace", "14");
304 : }
305 :
306 102 : if (!parallel)
307 : {
308 94 : switch (process_type)
309 : {
310 44 : case REINDEX_DATABASE:
311 : case REINDEX_SYSTEM:
312 :
313 : /*
314 : * Database and system reindexes only need to work on the
315 : * database itself, so build a list with a single entry.
316 : */
317 : Assert(user_list == NULL);
318 44 : process_list = pg_malloc0(sizeof(SimpleStringList));
319 44 : simple_string_list_append(process_list, PQdb(conn));
320 44 : break;
321 :
322 50 : case REINDEX_INDEX:
323 : case REINDEX_SCHEMA:
324 : case REINDEX_TABLE:
325 : Assert(user_list != NULL);
326 50 : break;
327 : }
328 94 : }
329 : else
330 : {
331 8 : switch (process_type)
332 : {
333 2 : case REINDEX_DATABASE:
334 :
335 : /* Build a list of relations from the database */
336 2 : process_list = get_parallel_object_list(conn, process_type,
337 : user_list, echo);
338 2 : process_type = REINDEX_TABLE;
339 :
340 : /* Bail out if nothing to process */
341 2 : if (process_list == NULL)
342 0 : return;
343 2 : break;
344 :
345 4 : case REINDEX_SCHEMA:
346 : Assert(user_list != NULL);
347 :
348 : /* Build a list of relations from all the schemas */
349 4 : process_list = get_parallel_object_list(conn, process_type,
350 : user_list, echo);
351 4 : process_type = REINDEX_TABLE;
352 :
353 : /* Bail out if nothing to process */
354 4 : if (process_list == NULL)
355 2 : return;
356 2 : break;
357 :
358 2 : case REINDEX_INDEX:
359 : Assert(user_list != NULL);
360 :
361 : /*
362 : * Build a list of relations from the indices. This will
363 : * accordingly reorder the list of indices too.
364 : */
365 2 : indices_tables_list = get_parallel_object_list(conn, process_type,
366 : user_list, echo);
367 :
368 : /*
369 : * Bail out if nothing to process. 'user_list' was modified
370 : * in-place, so check if it has at least one cell.
371 : */
372 2 : if (user_list->head == NULL)
373 0 : return;
374 :
375 : /*
376 : * Assuming 'user_list' is not empty, 'indices_tables_list'
377 : * shouldn't be empty as well.
378 : */
379 : Assert(indices_tables_list != NULL);
380 2 : indices_tables_cell = indices_tables_list->head;
381 :
382 2 : break;
383 :
384 0 : case REINDEX_SYSTEM:
385 : /* not supported */
386 : Assert(false);
387 0 : break;
388 :
389 0 : case REINDEX_TABLE:
390 :
391 : /*
392 : * Fall through. The list of items for tables is already
393 : * created.
394 : */
395 0 : break;
396 : }
397 100 : }
398 :
399 : /*
400 : * Adjust the number of concurrent connections depending on the items in
401 : * the list. We choose the minimum between the number of concurrent
402 : * connections and the number of items in the list.
403 : */
404 106 : for (cell = process_list->head; cell; cell = cell->next)
405 : {
406 106 : items_count++;
407 :
408 : /* no need to continue if there are more elements than jobs */
409 106 : if (items_count >= concurrentCons)
410 100 : break;
411 : }
412 100 : concurrentCons = Min(concurrentCons, items_count);
413 : Assert(concurrentCons > 0);
414 :
415 : Assert(process_list != NULL);
416 :
417 100 : sa = ParallelSlotsSetup(concurrentCons, cparams, progname, echo, NULL);
418 100 : ParallelSlotsAdoptConn(sa, conn);
419 :
420 100 : cell = process_list->head;
421 : do
422 : {
423 118 : const char *objname = cell->val;
424 118 : bool need_new_slot = true;
425 :
426 118 : if (CancelRequested)
427 : {
428 0 : failed = true;
429 0 : goto finish;
430 : }
431 :
432 : /*
433 : * For parallel index-level REINDEX, the indices of the same table are
434 : * ordered together and they are to be processed by the same job. So,
435 : * we don't switch the job as soon as the index belongs to the same
436 : * table as the previous one.
437 : */
438 118 : if (parallel && process_type == REINDEX_INDEX)
439 : {
440 4 : if (prev_index_table_name != NULL &&
441 2 : strcmp(prev_index_table_name, indices_tables_cell->val) == 0)
442 0 : need_new_slot = false;
443 4 : prev_index_table_name = indices_tables_cell->val;
444 4 : indices_tables_cell = indices_tables_cell->next;
445 : }
446 :
447 118 : if (need_new_slot)
448 : {
449 118 : free_slot = ParallelSlotsGetIdle(sa, NULL);
450 118 : if (!free_slot)
451 : {
452 0 : failed = true;
453 0 : goto finish;
454 : }
455 :
456 118 : ParallelSlotSetHandler(free_slot, TableCommandResultHandler, NULL);
457 : }
458 :
459 118 : run_reindex_command(free_slot->connection, process_type, objname,
460 : echo, verbose, concurrently, true, tablespace);
461 :
462 118 : cell = cell->next;
463 118 : } while (cell != NULL);
464 :
465 100 : if (!ParallelSlotsWaitCompletion(sa))
466 10 : failed = true;
467 :
468 90 : finish:
469 100 : if (process_list != user_list)
470 : {
471 48 : simple_string_list_destroy(process_list);
472 48 : pg_free(process_list);
473 : }
474 :
475 100 : if (indices_tables_list)
476 : {
477 2 : simple_string_list_destroy(indices_tables_list);
478 2 : pg_free(indices_tables_list);
479 : }
480 :
481 100 : ParallelSlotsTerminate(sa);
482 100 : pfree(sa);
483 :
484 100 : if (failed)
485 10 : exit(1);
486 : }
487 :
488 : static void
489 118 : run_reindex_command(PGconn *conn, ReindexType type, const char *name,
490 : bool echo, bool verbose, bool concurrently, bool async,
491 : const char *tablespace)
492 : {
493 118 : const char *paren = "(";
494 118 : const char *comma = ", ";
495 118 : const char *sep = paren;
496 : PQExpBufferData sql;
497 : bool status;
498 :
499 : Assert(name);
500 :
501 : /* build the REINDEX query */
502 118 : initPQExpBuffer(&sql);
503 :
504 118 : appendPQExpBufferStr(&sql, "REINDEX ");
505 :
506 118 : if (verbose)
507 : {
508 8 : appendPQExpBuffer(&sql, "%sVERBOSE", sep);
509 8 : sep = comma;
510 : }
511 :
512 118 : if (tablespace)
513 : {
514 14 : appendPQExpBuffer(&sql, "%sTABLESPACE %s", sep, fmtId(tablespace));
515 14 : sep = comma;
516 : }
517 :
518 118 : if (sep != paren)
519 18 : appendPQExpBufferStr(&sql, ") ");
520 :
521 : /* object type */
522 118 : switch (type)
523 : {
524 28 : case REINDEX_DATABASE:
525 28 : appendPQExpBufferStr(&sql, "DATABASE ");
526 28 : break;
527 18 : case REINDEX_INDEX:
528 18 : appendPQExpBufferStr(&sql, "INDEX ");
529 18 : break;
530 10 : case REINDEX_SCHEMA:
531 10 : appendPQExpBufferStr(&sql, "SCHEMA ");
532 10 : break;
533 16 : case REINDEX_SYSTEM:
534 16 : appendPQExpBufferStr(&sql, "SYSTEM ");
535 16 : break;
536 46 : case REINDEX_TABLE:
537 46 : appendPQExpBufferStr(&sql, "TABLE ");
538 46 : break;
539 : }
540 :
541 : /*
542 : * Parenthesized grammar is only supported for CONCURRENTLY since
543 : * PostgreSQL 14. Since 12, CONCURRENTLY can be specified after the
544 : * object type.
545 : */
546 118 : if (concurrently)
547 34 : appendPQExpBufferStr(&sql, "CONCURRENTLY ");
548 :
549 : /* object name */
550 118 : switch (type)
551 : {
552 44 : case REINDEX_DATABASE:
553 : case REINDEX_SYSTEM:
554 44 : appendPQExpBufferStr(&sql, fmtId(name));
555 44 : break;
556 64 : case REINDEX_INDEX:
557 : case REINDEX_TABLE:
558 64 : appendQualifiedRelation(&sql, name, conn, echo);
559 64 : break;
560 10 : case REINDEX_SCHEMA:
561 10 : appendPQExpBufferStr(&sql, name);
562 10 : break;
563 : }
564 :
565 : /* finish the query */
566 118 : appendPQExpBufferChar(&sql, ';');
567 :
568 118 : if (async)
569 : {
570 118 : if (echo)
571 20 : printf("%s\n", sql.data);
572 :
573 118 : status = PQsendQuery(conn, sql.data) == 1;
574 : }
575 : else
576 0 : status = executeMaintenanceCommand(conn, sql.data, echo);
577 :
578 118 : if (!status)
579 : {
580 0 : switch (type)
581 : {
582 0 : case REINDEX_DATABASE:
583 0 : pg_log_error("reindexing of database \"%s\" failed: %s",
584 : PQdb(conn), PQerrorMessage(conn));
585 0 : break;
586 0 : case REINDEX_INDEX:
587 0 : pg_log_error("reindexing of index \"%s\" in database \"%s\" failed: %s",
588 : name, PQdb(conn), PQerrorMessage(conn));
589 0 : break;
590 0 : case REINDEX_SCHEMA:
591 0 : pg_log_error("reindexing of schema \"%s\" in database \"%s\" failed: %s",
592 : name, PQdb(conn), PQerrorMessage(conn));
593 0 : break;
594 0 : case REINDEX_SYSTEM:
595 0 : pg_log_error("reindexing of system catalogs in database \"%s\" failed: %s",
596 : PQdb(conn), PQerrorMessage(conn));
597 0 : break;
598 0 : case REINDEX_TABLE:
599 0 : pg_log_error("reindexing of table \"%s\" in database \"%s\" failed: %s",
600 : name, PQdb(conn), PQerrorMessage(conn));
601 0 : break;
602 : }
603 0 : if (!async)
604 : {
605 0 : PQfinish(conn);
606 0 : exit(1);
607 : }
608 : }
609 :
610 118 : termPQExpBuffer(&sql);
611 118 : }
612 :
613 : /*
614 : * Prepare the list of objects to process by querying the catalogs.
615 : *
616 : * This function will return a SimpleStringList object containing the entire
617 : * list of tables in the given database that should be processed by a parallel
618 : * database-wide reindex (excluding system tables), or NULL if there's no such
619 : * table.
620 : */
621 : static SimpleStringList *
622 8 : get_parallel_object_list(PGconn *conn, ReindexType type,
623 : SimpleStringList *user_list, bool echo)
624 : {
625 : PQExpBufferData catalog_query;
626 : PQExpBufferData buf;
627 : PGresult *res;
628 : SimpleStringList *tables;
629 : int ntups,
630 : i;
631 :
632 8 : initPQExpBuffer(&catalog_query);
633 :
634 : /*
635 : * The queries here are using a safe search_path, so there's no need to
636 : * fully qualify everything.
637 : */
638 8 : switch (type)
639 : {
640 2 : case REINDEX_DATABASE:
641 : Assert(user_list == NULL);
642 2 : appendPQExpBufferStr(&catalog_query,
643 : "SELECT c.relname, ns.nspname\n"
644 : " FROM pg_catalog.pg_class c\n"
645 : " JOIN pg_catalog.pg_namespace ns"
646 : " ON c.relnamespace = ns.oid\n"
647 : " WHERE ns.nspname != 'pg_catalog'\n"
648 : " AND c.relkind IN ("
649 : CppAsString2(RELKIND_RELATION) ", "
650 : CppAsString2(RELKIND_MATVIEW) ")\n"
651 : " AND c.relpersistence != "
652 : CppAsString2(RELPERSISTENCE_TEMP) "\n"
653 : " ORDER BY c.relpages DESC;");
654 2 : break;
655 :
656 4 : case REINDEX_SCHEMA:
657 : {
658 : SimpleStringListCell *cell;
659 4 : bool nsp_listed = false;
660 :
661 : Assert(user_list != NULL);
662 :
663 : /*
664 : * All the tables from all the listed schemas are grabbed at
665 : * once.
666 : */
667 4 : appendPQExpBufferStr(&catalog_query,
668 : "SELECT c.relname, ns.nspname\n"
669 : " FROM pg_catalog.pg_class c\n"
670 : " JOIN pg_catalog.pg_namespace ns"
671 : " ON c.relnamespace = ns.oid\n"
672 : " WHERE c.relkind IN ("
673 : CppAsString2(RELKIND_RELATION) ", "
674 : CppAsString2(RELKIND_MATVIEW) ")\n"
675 : " AND c.relpersistence != "
676 : CppAsString2(RELPERSISTENCE_TEMP) "\n"
677 : " AND ns.nspname IN (");
678 :
679 10 : for (cell = user_list->head; cell; cell = cell->next)
680 : {
681 6 : const char *nspname = cell->val;
682 :
683 6 : if (nsp_listed)
684 2 : appendPQExpBufferStr(&catalog_query, ", ");
685 : else
686 4 : nsp_listed = true;
687 :
688 6 : appendStringLiteralConn(&catalog_query, nspname, conn);
689 : }
690 :
691 4 : appendPQExpBufferStr(&catalog_query, ")\n"
692 : " ORDER BY c.relpages DESC;");
693 : }
694 4 : break;
695 :
696 2 : case REINDEX_INDEX:
697 : {
698 : SimpleStringListCell *cell;
699 :
700 : Assert(user_list != NULL);
701 :
702 : /*
703 : * Straight-forward index-level REINDEX is not supported with
704 : * multiple jobs as we cannot control the concurrent
705 : * processing of multiple indexes depending on the same
706 : * relation. But we can extract the appropriate table name
707 : * for the index and put REINDEX INDEX commands into different
708 : * jobs, according to the parent tables.
709 : *
710 : * We will order the results to group the same tables
711 : * together. We fetch index names as well to build a new list
712 : * of them with matching order.
713 : */
714 2 : appendPQExpBufferStr(&catalog_query,
715 : "SELECT t.relname, n.nspname, i.relname\n"
716 : "FROM pg_catalog.pg_index x\n"
717 : "JOIN pg_catalog.pg_class t ON t.oid = x.indrelid\n"
718 : "JOIN pg_catalog.pg_class i ON i.oid = x.indexrelid\n"
719 : "LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.relnamespace\n"
720 : "WHERE x.indexrelid OPERATOR(pg_catalog.=) ANY(ARRAY['");
721 :
722 6 : for (cell = user_list->head; cell; cell = cell->next)
723 : {
724 4 : if (cell != user_list->head)
725 2 : appendPQExpBufferStr(&catalog_query, "', '");
726 :
727 4 : appendQualifiedRelation(&catalog_query, cell->val, conn, echo);
728 : }
729 :
730 : /*
731 : * Order tables by the size of its greatest index. Within the
732 : * table, order indexes by their sizes.
733 : */
734 2 : appendPQExpBufferStr(&catalog_query,
735 : "']::pg_catalog.regclass[])\n"
736 : "ORDER BY max(i.relpages) OVER \n"
737 : " (PARTITION BY n.nspname, t.relname),\n"
738 : " n.nspname, t.relname, i.relpages;\n");
739 :
740 : /*
741 : * We're going to re-order the user_list to match the order of
742 : * tables. So, empty the user_list to fill it from the query
743 : * result.
744 : */
745 2 : simple_string_list_destroy(user_list);
746 2 : user_list->head = user_list->tail = NULL;
747 : }
748 2 : break;
749 :
750 0 : case REINDEX_SYSTEM:
751 : case REINDEX_TABLE:
752 : Assert(false);
753 0 : break;
754 : }
755 :
756 8 : res = executeQuery(conn, catalog_query.data, echo);
757 8 : termPQExpBuffer(&catalog_query);
758 :
759 : /*
760 : * If no rows are returned, there are no matching tables, so we are done.
761 : */
762 8 : ntups = PQntuples(res);
763 8 : if (ntups == 0)
764 : {
765 2 : PQclear(res);
766 2 : PQfinish(conn);
767 2 : return NULL;
768 : }
769 :
770 6 : tables = pg_malloc0(sizeof(SimpleStringList));
771 :
772 : /* Build qualified identifiers for each table */
773 6 : initPQExpBuffer(&buf);
774 30 : for (i = 0; i < ntups; i++)
775 : {
776 24 : appendPQExpBufferStr(&buf,
777 24 : fmtQualifiedId(PQgetvalue(res, i, 1),
778 24 : PQgetvalue(res, i, 0)));
779 :
780 24 : simple_string_list_append(tables, buf.data);
781 24 : resetPQExpBuffer(&buf);
782 :
783 24 : if (type == REINDEX_INDEX)
784 : {
785 : /*
786 : * For index-level REINDEX, rebuild the list of indexes to match
787 : * the order of tables list.
788 : */
789 4 : appendPQExpBufferStr(&buf,
790 4 : fmtQualifiedId(PQgetvalue(res, i, 1),
791 4 : PQgetvalue(res, i, 2)));
792 :
793 4 : simple_string_list_append(user_list, buf.data);
794 4 : resetPQExpBuffer(&buf);
795 : }
796 : }
797 6 : termPQExpBuffer(&buf);
798 6 : PQclear(res);
799 :
800 6 : return tables;
801 : }
802 :
803 : static void
804 14 : reindex_all_databases(ConnParams *cparams,
805 : const char *progname, bool echo, bool quiet, bool verbose,
806 : bool concurrently, int concurrentCons,
807 : const char *tablespace, bool syscatalog,
808 : SimpleStringList *schemas, SimpleStringList *tables,
809 : SimpleStringList *indexes)
810 : {
811 : PGconn *conn;
812 : PGresult *result;
813 : int i;
814 :
815 14 : conn = connectMaintenanceDatabase(cparams, progname, echo);
816 14 : result = executeQuery(conn,
817 : "SELECT datname FROM pg_database WHERE datallowconn AND datconnlimit <> -2 ORDER BY 1;",
818 : echo);
819 14 : PQfinish(conn);
820 :
821 52 : for (i = 0; i < PQntuples(result); i++)
822 : {
823 38 : char *dbname = PQgetvalue(result, i, 0);
824 :
825 38 : if (!quiet)
826 : {
827 38 : printf(_("%s: reindexing database \"%s\"\n"), progname, dbname);
828 38 : fflush(stdout);
829 : }
830 :
831 38 : cparams->override_dbname = dbname;
832 :
833 38 : if (syscatalog)
834 4 : reindex_one_database(cparams, REINDEX_SYSTEM, NULL,
835 : progname, echo, verbose,
836 : concurrently, 1, tablespace);
837 :
838 38 : if (schemas->head != NULL)
839 4 : reindex_one_database(cparams, REINDEX_SCHEMA, schemas,
840 : progname, echo, verbose,
841 : concurrently, concurrentCons, tablespace);
842 :
843 38 : if (indexes->head != NULL)
844 4 : reindex_one_database(cparams, REINDEX_INDEX, indexes,
845 : progname, echo, verbose,
846 : concurrently, 1, tablespace);
847 :
848 38 : if (tables->head != NULL)
849 4 : reindex_one_database(cparams, REINDEX_TABLE, tables,
850 : progname, echo, verbose,
851 : concurrently, concurrentCons, tablespace);
852 :
853 : /*
854 : * reindex database only if neither index nor table nor schema nor
855 : * system catalogs is specified
856 : */
857 38 : if (!syscatalog && indexes->head == NULL &&
858 30 : tables->head == NULL && schemas->head == NULL)
859 22 : reindex_one_database(cparams, REINDEX_DATABASE, NULL,
860 : progname, echo, verbose,
861 : concurrently, concurrentCons, tablespace);
862 : }
863 :
864 14 : PQclear(result);
865 14 : }
866 :
867 : static void
868 2 : help(const char *progname)
869 : {
870 2 : printf(_("%s reindexes a PostgreSQL database.\n\n"), progname);
871 2 : printf(_("Usage:\n"));
872 2 : printf(_(" %s [OPTION]... [DBNAME]\n"), progname);
873 2 : printf(_("\nOptions:\n"));
874 2 : printf(_(" -a, --all reindex all databases\n"));
875 2 : printf(_(" --concurrently reindex concurrently\n"));
876 2 : printf(_(" -d, --dbname=DBNAME database to reindex\n"));
877 2 : printf(_(" -e, --echo show the commands being sent to the server\n"));
878 2 : printf(_(" -i, --index=INDEX recreate specific index(es) only\n"));
879 2 : printf(_(" -j, --jobs=NUM use this many concurrent connections to reindex\n"));
880 2 : printf(_(" -q, --quiet don't write any messages\n"));
881 2 : printf(_(" -s, --system reindex system catalogs only\n"));
882 2 : printf(_(" -S, --schema=SCHEMA reindex specific schema(s) only\n"));
883 2 : printf(_(" -t, --table=TABLE reindex specific table(s) only\n"));
884 2 : printf(_(" --tablespace=TABLESPACE tablespace where indexes are rebuilt\n"));
885 2 : printf(_(" -v, --verbose write a lot of output\n"));
886 2 : printf(_(" -V, --version output version information, then exit\n"));
887 2 : printf(_(" -?, --help show this help, then exit\n"));
888 2 : printf(_("\nConnection options:\n"));
889 2 : printf(_(" -h, --host=HOSTNAME database server host or socket directory\n"));
890 2 : printf(_(" -p, --port=PORT database server port\n"));
891 2 : printf(_(" -U, --username=USERNAME user name to connect as\n"));
892 2 : printf(_(" -w, --no-password never prompt for password\n"));
893 2 : printf(_(" -W, --password force password prompt\n"));
894 2 : printf(_(" --maintenance-db=DBNAME alternate maintenance database\n"));
895 2 : printf(_("\nRead the description of the SQL command REINDEX for details.\n"));
896 2 : printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
897 2 : printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
898 2 : }
|