Consistently use "superuser" instead of "super user"
[pgsql.git] / src / bin / scripts / reindexdb.c
bloba0b0250c499571305c7da82ef377d934ec32d038
1 /*-------------------------------------------------------------------------
3 * reindexdb
5 * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
7 * src/bin/scripts/reindexdb.c
9 *-------------------------------------------------------------------------
12 #include "postgres_fe.h"
14 #include <limits.h>
16 #include "catalog/pg_class_d.h"
17 #include "common.h"
18 #include "common/connect.h"
19 #include "common/logging.h"
20 #include "fe_utils/cancel.h"
21 #include "fe_utils/option_utils.h"
22 #include "fe_utils/parallel_slot.h"
23 #include "fe_utils/query_utils.h"
24 #include "fe_utils/simple_list.h"
25 #include "fe_utils/string_utils.h"
27 typedef enum ReindexType
29 REINDEX_DATABASE,
30 REINDEX_INDEX,
31 REINDEX_SCHEMA,
32 REINDEX_SYSTEM,
33 REINDEX_TABLE
34 } ReindexType;
37 static SimpleStringList *get_parallel_object_list(PGconn *conn,
38 ReindexType type,
39 SimpleStringList *user_list,
40 bool echo);
41 static void reindex_one_database(ConnParams *cparams, ReindexType type,
42 SimpleStringList *user_list,
43 const char *progname,
44 bool echo, bool verbose, bool concurrently,
45 int concurrentCons, const char *tablespace);
46 static void reindex_all_databases(ConnParams *cparams,
47 const char *progname, bool echo,
48 bool quiet, bool verbose, bool concurrently,
49 int concurrentCons, const char *tablespace);
50 static void run_reindex_command(PGconn *conn, ReindexType type,
51 const char *name, bool echo, bool verbose,
52 bool concurrently, bool async,
53 const char *tablespace);
55 static void help(const char *progname);
57 int
58 main(int argc, char *argv[])
60 static struct option long_options[] = {
61 {"host", required_argument, NULL, 'h'},
62 {"port", required_argument, NULL, 'p'},
63 {"username", required_argument, NULL, 'U'},
64 {"no-password", no_argument, NULL, 'w'},
65 {"password", no_argument, NULL, 'W'},
66 {"echo", no_argument, NULL, 'e'},
67 {"quiet", no_argument, NULL, 'q'},
68 {"schema", required_argument, NULL, 'S'},
69 {"dbname", required_argument, NULL, 'd'},
70 {"all", no_argument, NULL, 'a'},
71 {"system", no_argument, NULL, 's'},
72 {"table", required_argument, NULL, 't'},
73 {"index", required_argument, NULL, 'i'},
74 {"jobs", required_argument, NULL, 'j'},
75 {"verbose", no_argument, NULL, 'v'},
76 {"concurrently", no_argument, NULL, 1},
77 {"maintenance-db", required_argument, NULL, 2},
78 {"tablespace", required_argument, NULL, 3},
79 {NULL, 0, NULL, 0}
82 const char *progname;
83 int optindex;
84 int c;
86 const char *dbname = NULL;
87 const char *maintenance_db = NULL;
88 const char *host = NULL;
89 const char *port = NULL;
90 const char *username = NULL;
91 const char *tablespace = NULL;
92 enum trivalue prompt_password = TRI_DEFAULT;
93 ConnParams cparams;
94 bool syscatalog = false;
95 bool alldb = false;
96 bool echo = false;
97 bool quiet = false;
98 bool verbose = false;
99 bool concurrently = false;
100 SimpleStringList indexes = {NULL, NULL};
101 SimpleStringList tables = {NULL, NULL};
102 SimpleStringList schemas = {NULL, NULL};
103 int concurrentCons = 1;
105 pg_logging_init(argv[0]);
106 progname = get_progname(argv[0]);
107 set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pgscripts"));
109 handle_help_version_opts(argc, argv, "reindexdb", help);
111 /* process command-line options */
112 while ((c = getopt_long(argc, argv, "h:p:U:wWeqS:d:ast:i:j:v", long_options, &optindex)) != -1)
114 switch (c)
116 case 'h':
117 host = pg_strdup(optarg);
118 break;
119 case 'p':
120 port = pg_strdup(optarg);
121 break;
122 case 'U':
123 username = pg_strdup(optarg);
124 break;
125 case 'w':
126 prompt_password = TRI_NO;
127 break;
128 case 'W':
129 prompt_password = TRI_YES;
130 break;
131 case 'e':
132 echo = true;
133 break;
134 case 'q':
135 quiet = true;
136 break;
137 case 'S':
138 simple_string_list_append(&schemas, optarg);
139 break;
140 case 'd':
141 dbname = pg_strdup(optarg);
142 break;
143 case 'a':
144 alldb = true;
145 break;
146 case 's':
147 syscatalog = true;
148 break;
149 case 't':
150 simple_string_list_append(&tables, optarg);
151 break;
152 case 'i':
153 simple_string_list_append(&indexes, optarg);
154 break;
155 case 'j':
156 if (!option_parse_int(optarg, "-j/--jobs", 1, INT_MAX,
157 &concurrentCons))
158 exit(1);
159 break;
160 case 'v':
161 verbose = true;
162 break;
163 case 1:
164 concurrently = true;
165 break;
166 case 2:
167 maintenance_db = pg_strdup(optarg);
168 break;
169 case 3:
170 tablespace = pg_strdup(optarg);
171 break;
172 default:
173 fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
174 exit(1);
179 * Non-option argument specifies database name as long as it wasn't
180 * already specified with -d / --dbname
182 if (optind < argc && dbname == NULL)
184 dbname = argv[optind];
185 optind++;
188 if (optind < argc)
190 pg_log_error("too many command-line arguments (first is \"%s\")",
191 argv[optind]);
192 fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
193 exit(1);
196 /* fill cparams except for dbname, which is set below */
197 cparams.pghost = host;
198 cparams.pgport = port;
199 cparams.pguser = username;
200 cparams.prompt_password = prompt_password;
201 cparams.override_dbname = NULL;
203 setup_cancel_handler(NULL);
205 if (alldb)
207 if (dbname)
209 pg_log_error("cannot reindex all databases and a specific one at the same time");
210 exit(1);
212 if (syscatalog)
214 pg_log_error("cannot reindex all databases and system catalogs at the same time");
215 exit(1);
217 if (schemas.head != NULL)
219 pg_log_error("cannot reindex specific schema(s) in all databases");
220 exit(1);
222 if (tables.head != NULL)
224 pg_log_error("cannot reindex specific table(s) in all databases");
225 exit(1);
227 if (indexes.head != NULL)
229 pg_log_error("cannot reindex specific index(es) in all databases");
230 exit(1);
233 cparams.dbname = maintenance_db;
235 reindex_all_databases(&cparams, progname, echo, quiet, verbose,
236 concurrently, concurrentCons, tablespace);
238 else if (syscatalog)
240 if (schemas.head != NULL)
242 pg_log_error("cannot reindex specific schema(s) and system catalogs at the same time");
243 exit(1);
245 if (tables.head != NULL)
247 pg_log_error("cannot reindex specific table(s) and system catalogs at the same time");
248 exit(1);
250 if (indexes.head != NULL)
252 pg_log_error("cannot reindex specific index(es) and system catalogs at the same time");
253 exit(1);
256 if (concurrentCons > 1)
258 pg_log_error("cannot use multiple jobs to reindex system catalogs");
259 exit(1);
262 if (dbname == NULL)
264 if (getenv("PGDATABASE"))
265 dbname = getenv("PGDATABASE");
266 else if (getenv("PGUSER"))
267 dbname = getenv("PGUSER");
268 else
269 dbname = get_user_name_or_exit(progname);
272 cparams.dbname = dbname;
274 reindex_one_database(&cparams, REINDEX_SYSTEM, NULL,
275 progname, echo, verbose,
276 concurrently, 1, tablespace);
278 else
281 * Index-level REINDEX is not supported with multiple jobs as we
282 * cannot control the concurrent processing of multiple indexes
283 * depending on the same relation.
285 if (concurrentCons > 1 && indexes.head != NULL)
287 pg_log_error("cannot use multiple jobs to reindex indexes");
288 exit(1);
291 if (dbname == NULL)
293 if (getenv("PGDATABASE"))
294 dbname = getenv("PGDATABASE");
295 else if (getenv("PGUSER"))
296 dbname = getenv("PGUSER");
297 else
298 dbname = get_user_name_or_exit(progname);
301 cparams.dbname = dbname;
303 if (schemas.head != NULL)
304 reindex_one_database(&cparams, REINDEX_SCHEMA, &schemas,
305 progname, echo, verbose,
306 concurrently, concurrentCons, tablespace);
308 if (indexes.head != NULL)
309 reindex_one_database(&cparams, REINDEX_INDEX, &indexes,
310 progname, echo, verbose,
311 concurrently, 1, tablespace);
313 if (tables.head != NULL)
314 reindex_one_database(&cparams, REINDEX_TABLE, &tables,
315 progname, echo, verbose,
316 concurrently, concurrentCons, tablespace);
319 * reindex database only if neither index nor table nor schema is
320 * specified
322 if (indexes.head == NULL && tables.head == NULL && schemas.head == NULL)
323 reindex_one_database(&cparams, REINDEX_DATABASE, NULL,
324 progname, echo, verbose,
325 concurrently, concurrentCons, tablespace);
328 exit(0);
331 static void
332 reindex_one_database(ConnParams *cparams, ReindexType type,
333 SimpleStringList *user_list,
334 const char *progname, bool echo,
335 bool verbose, bool concurrently, int concurrentCons,
336 const char *tablespace)
338 PGconn *conn;
339 SimpleStringListCell *cell;
340 bool parallel = concurrentCons > 1;
341 SimpleStringList *process_list = user_list;
342 ReindexType process_type = type;
343 ParallelSlotArray *sa;
344 bool failed = false;
345 int items_count = 0;
347 conn = connectDatabase(cparams, progname, echo, false, false);
349 if (concurrently && PQserverVersion(conn) < 120000)
351 PQfinish(conn);
352 pg_log_error("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
353 "concurrently", "12");
354 exit(1);
357 if (tablespace && PQserverVersion(conn) < 140000)
359 PQfinish(conn);
360 pg_log_error("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
361 "tablespace", "14");
362 exit(1);
365 if (!parallel)
367 switch (process_type)
369 case REINDEX_DATABASE:
370 case REINDEX_SYSTEM:
373 * Database and system reindexes only need to work on the
374 * database itself, so build a list with a single entry.
376 Assert(user_list == NULL);
377 process_list = pg_malloc0(sizeof(SimpleStringList));
378 simple_string_list_append(process_list, PQdb(conn));
379 break;
381 case REINDEX_INDEX:
382 case REINDEX_SCHEMA:
383 case REINDEX_TABLE:
384 Assert(user_list != NULL);
385 break;
388 else
390 switch (process_type)
392 case REINDEX_DATABASE:
395 * Database-wide parallel reindex requires special processing.
396 * If multiple jobs were asked, we have to reindex system
397 * catalogs first as they cannot be processed in parallel.
399 if (concurrently)
400 pg_log_warning("cannot reindex system catalogs concurrently, skipping all");
401 else
402 run_reindex_command(conn, REINDEX_SYSTEM, PQdb(conn), echo,
403 verbose, concurrently, false,
404 tablespace);
406 /* Build a list of relations from the database */
407 process_list = get_parallel_object_list(conn, process_type,
408 user_list, echo);
409 process_type = REINDEX_TABLE;
411 /* Bail out if nothing to process */
412 if (process_list == NULL)
413 return;
414 break;
416 case REINDEX_SCHEMA:
417 Assert(user_list != NULL);
419 /* Build a list of relations from all the schemas */
420 process_list = get_parallel_object_list(conn, process_type,
421 user_list, echo);
422 process_type = REINDEX_TABLE;
424 /* Bail out if nothing to process */
425 if (process_list == NULL)
426 return;
427 break;
429 case REINDEX_SYSTEM:
430 case REINDEX_INDEX:
431 /* not supported */
432 Assert(false);
433 break;
435 case REINDEX_TABLE:
438 * Fall through. The list of items for tables is already
439 * created.
441 break;
446 * Adjust the number of concurrent connections depending on the items in
447 * the list. We choose the minimum between the number of concurrent
448 * connections and the number of items in the list.
450 for (cell = process_list->head; cell; cell = cell->next)
452 items_count++;
454 /* no need to continue if there are more elements than jobs */
455 if (items_count >= concurrentCons)
456 break;
458 concurrentCons = Min(concurrentCons, items_count);
459 Assert(concurrentCons > 0);
461 Assert(process_list != NULL);
463 sa = ParallelSlotsSetup(concurrentCons, cparams, progname, echo, NULL);
464 ParallelSlotsAdoptConn(sa, conn);
466 cell = process_list->head;
469 const char *objname = cell->val;
470 ParallelSlot *free_slot = NULL;
472 if (CancelRequested)
474 failed = true;
475 goto finish;
478 free_slot = ParallelSlotsGetIdle(sa, NULL);
479 if (!free_slot)
481 failed = true;
482 goto finish;
485 ParallelSlotSetHandler(free_slot, TableCommandResultHandler, NULL);
486 run_reindex_command(free_slot->connection, process_type, objname,
487 echo, verbose, concurrently, true, tablespace);
489 cell = cell->next;
490 } while (cell != NULL);
492 if (!ParallelSlotsWaitCompletion(sa))
493 failed = true;
495 finish:
496 if (process_list != user_list)
498 simple_string_list_destroy(process_list);
499 pg_free(process_list);
502 ParallelSlotsTerminate(sa);
503 pfree(sa);
505 if (failed)
506 exit(1);
509 static void
510 run_reindex_command(PGconn *conn, ReindexType type, const char *name,
511 bool echo, bool verbose, bool concurrently, bool async,
512 const char *tablespace)
514 const char *paren = "(";
515 const char *comma = ", ";
516 const char *sep = paren;
517 PQExpBufferData sql;
518 bool status;
520 Assert(name);
522 /* build the REINDEX query */
523 initPQExpBuffer(&sql);
525 appendPQExpBufferStr(&sql, "REINDEX ");
527 if (verbose)
529 appendPQExpBuffer(&sql, "%sVERBOSE", sep);
530 sep = comma;
533 if (tablespace)
535 appendPQExpBuffer(&sql, "%sTABLESPACE %s", sep, fmtId(tablespace));
536 sep = comma;
539 if (sep != paren)
540 appendPQExpBufferStr(&sql, ") ");
542 /* object type */
543 switch (type)
545 case REINDEX_DATABASE:
546 appendPQExpBufferStr(&sql, "DATABASE ");
547 break;
548 case REINDEX_INDEX:
549 appendPQExpBufferStr(&sql, "INDEX ");
550 break;
551 case REINDEX_SCHEMA:
552 appendPQExpBufferStr(&sql, "SCHEMA ");
553 break;
554 case REINDEX_SYSTEM:
555 appendPQExpBufferStr(&sql, "SYSTEM ");
556 break;
557 case REINDEX_TABLE:
558 appendPQExpBufferStr(&sql, "TABLE ");
559 break;
563 * Parenthesized grammar is only supported for CONCURRENTLY since
564 * PostgreSQL 14. Since 12, CONCURRENTLY can be specified after the
565 * object type.
567 if (concurrently)
568 appendPQExpBufferStr(&sql, "CONCURRENTLY ");
570 /* object name */
571 switch (type)
573 case REINDEX_DATABASE:
574 case REINDEX_SYSTEM:
575 appendPQExpBufferStr(&sql, fmtId(name));
576 break;
577 case REINDEX_INDEX:
578 case REINDEX_TABLE:
579 appendQualifiedRelation(&sql, name, conn, echo);
580 break;
581 case REINDEX_SCHEMA:
582 appendPQExpBufferStr(&sql, name);
583 break;
586 /* finish the query */
587 appendPQExpBufferChar(&sql, ';');
589 if (async)
591 if (echo)
592 printf("%s\n", sql.data);
594 status = PQsendQuery(conn, sql.data) == 1;
596 else
597 status = executeMaintenanceCommand(conn, sql.data, echo);
599 if (!status)
601 switch (type)
603 case REINDEX_DATABASE:
604 pg_log_error("reindexing of database \"%s\" failed: %s",
605 PQdb(conn), PQerrorMessage(conn));
606 break;
607 case REINDEX_INDEX:
608 pg_log_error("reindexing of index \"%s\" in database \"%s\" failed: %s",
609 name, PQdb(conn), PQerrorMessage(conn));
610 break;
611 case REINDEX_SCHEMA:
612 pg_log_error("reindexing of schema \"%s\" in database \"%s\" failed: %s",
613 name, PQdb(conn), PQerrorMessage(conn));
614 break;
615 case REINDEX_SYSTEM:
616 pg_log_error("reindexing of system catalogs in database \"%s\" failed: %s",
617 PQdb(conn), PQerrorMessage(conn));
618 break;
619 case REINDEX_TABLE:
620 pg_log_error("reindexing of table \"%s\" in database \"%s\" failed: %s",
621 name, PQdb(conn), PQerrorMessage(conn));
622 break;
624 if (!async)
626 PQfinish(conn);
627 exit(1);
631 termPQExpBuffer(&sql);
635 * Prepare the list of objects to process by querying the catalogs.
637 * This function will return a SimpleStringList object containing the entire
638 * list of tables in the given database that should be processed by a parallel
639 * database-wide reindex (excluding system tables), or NULL if there's no such
640 * table.
642 static SimpleStringList *
643 get_parallel_object_list(PGconn *conn, ReindexType type,
644 SimpleStringList *user_list, bool echo)
646 PQExpBufferData catalog_query;
647 PQExpBufferData buf;
648 PGresult *res;
649 SimpleStringList *tables;
650 int ntups,
653 initPQExpBuffer(&catalog_query);
656 * The queries here are using a safe search_path, so there's no need to
657 * fully qualify everything.
659 switch (type)
661 case REINDEX_DATABASE:
662 Assert(user_list == NULL);
663 appendPQExpBufferStr(&catalog_query,
664 "SELECT c.relname, ns.nspname\n"
665 " FROM pg_catalog.pg_class c\n"
666 " JOIN pg_catalog.pg_namespace ns"
667 " ON c.relnamespace = ns.oid\n"
668 " WHERE ns.nspname != 'pg_catalog'\n"
669 " AND c.relkind IN ("
670 CppAsString2(RELKIND_RELATION) ", "
671 CppAsString2(RELKIND_MATVIEW) ")\n"
672 " ORDER BY c.relpages DESC;");
673 break;
675 case REINDEX_SCHEMA:
677 SimpleStringListCell *cell;
678 bool nsp_listed = false;
680 Assert(user_list != NULL);
683 * All the tables from all the listed schemas are grabbed at
684 * once.
686 appendPQExpBufferStr(&catalog_query,
687 "SELECT c.relname, ns.nspname\n"
688 " FROM pg_catalog.pg_class c\n"
689 " JOIN pg_catalog.pg_namespace ns"
690 " ON c.relnamespace = ns.oid\n"
691 " WHERE c.relkind IN ("
692 CppAsString2(RELKIND_RELATION) ", "
693 CppAsString2(RELKIND_MATVIEW) ")\n"
694 " AND ns.nspname IN (");
696 for (cell = user_list->head; cell; cell = cell->next)
698 const char *nspname = cell->val;
700 if (nsp_listed)
701 appendPQExpBufferStr(&catalog_query, ", ");
702 else
703 nsp_listed = true;
705 appendStringLiteralConn(&catalog_query, nspname, conn);
708 appendPQExpBufferStr(&catalog_query, ")\n"
709 " ORDER BY c.relpages DESC;");
711 break;
713 case REINDEX_SYSTEM:
714 case REINDEX_INDEX:
715 case REINDEX_TABLE:
716 Assert(false);
717 break;
720 res = executeQuery(conn, catalog_query.data, echo);
721 termPQExpBuffer(&catalog_query);
724 * If no rows are returned, there are no matching tables, so we are done.
726 ntups = PQntuples(res);
727 if (ntups == 0)
729 PQclear(res);
730 PQfinish(conn);
731 return NULL;
734 tables = pg_malloc0(sizeof(SimpleStringList));
736 /* Build qualified identifiers for each table */
737 initPQExpBuffer(&buf);
738 for (i = 0; i < ntups; i++)
740 appendPQExpBufferStr(&buf,
741 fmtQualifiedId(PQgetvalue(res, i, 1),
742 PQgetvalue(res, i, 0)));
744 simple_string_list_append(tables, buf.data);
745 resetPQExpBuffer(&buf);
747 termPQExpBuffer(&buf);
748 PQclear(res);
750 return tables;
753 static void
754 reindex_all_databases(ConnParams *cparams,
755 const char *progname, bool echo, bool quiet, bool verbose,
756 bool concurrently, int concurrentCons,
757 const char *tablespace)
759 PGconn *conn;
760 PGresult *result;
761 int i;
763 conn = connectMaintenanceDatabase(cparams, progname, echo);
764 result = executeQuery(conn, "SELECT datname FROM pg_database WHERE datallowconn ORDER BY 1;", echo);
765 PQfinish(conn);
767 for (i = 0; i < PQntuples(result); i++)
769 char *dbname = PQgetvalue(result, i, 0);
771 if (!quiet)
773 printf(_("%s: reindexing database \"%s\"\n"), progname, dbname);
774 fflush(stdout);
777 cparams->override_dbname = dbname;
779 reindex_one_database(cparams, REINDEX_DATABASE, NULL,
780 progname, echo, verbose, concurrently,
781 concurrentCons, tablespace);
784 PQclear(result);
787 static void
788 help(const char *progname)
790 printf(_("%s reindexes a PostgreSQL database.\n\n"), progname);
791 printf(_("Usage:\n"));
792 printf(_(" %s [OPTION]... [DBNAME]\n"), progname);
793 printf(_("\nOptions:\n"));
794 printf(_(" -a, --all reindex all databases\n"));
795 printf(_(" --concurrently reindex concurrently\n"));
796 printf(_(" -d, --dbname=DBNAME database to reindex\n"));
797 printf(_(" -e, --echo show the commands being sent to the server\n"));
798 printf(_(" -i, --index=INDEX recreate specific index(es) only\n"));
799 printf(_(" -j, --jobs=NUM use this many concurrent connections to reindex\n"));
800 printf(_(" -q, --quiet don't write any messages\n"));
801 printf(_(" -s, --system reindex system catalogs\n"));
802 printf(_(" -S, --schema=SCHEMA reindex specific schema(s) only\n"));
803 printf(_(" -t, --table=TABLE reindex specific table(s) only\n"));
804 printf(_(" --tablespace=TABLESPACE tablespace where indexes are rebuilt\n"));
805 printf(_(" -v, --verbose write a lot of output\n"));
806 printf(_(" -V, --version output version information, then exit\n"));
807 printf(_(" -?, --help show this help, then exit\n"));
808 printf(_("\nConnection options:\n"));
809 printf(_(" -h, --host=HOSTNAME database server host or socket directory\n"));
810 printf(_(" -p, --port=PORT database server port\n"));
811 printf(_(" -U, --username=USERNAME user name to connect as\n"));
812 printf(_(" -w, --no-password never prompt for password\n"));
813 printf(_(" -W, --password force password prompt\n"));
814 printf(_(" --maintenance-db=DBNAME alternate maintenance database\n"));
815 printf(_("\nRead the description of the SQL command REINDEX for details.\n"));
816 printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
817 printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);