1 /*-------------------------------------------------------------------------
5 * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
7 * src/bin/scripts/reindexdb.c
9 *-------------------------------------------------------------------------
12 #include "postgres_fe.h"
16 #include "catalog/pg_class_d.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
37 static SimpleStringList
*get_parallel_object_list(PGconn
*conn
,
39 SimpleStringList
*user_list
,
41 static void reindex_one_database(ConnParams
*cparams
, ReindexType type
,
42 SimpleStringList
*user_list
,
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
);
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},
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
;
94 bool syscatalog
= 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)
117 host
= pg_strdup(optarg
);
120 port
= pg_strdup(optarg
);
123 username
= pg_strdup(optarg
);
126 prompt_password
= TRI_NO
;
129 prompt_password
= TRI_YES
;
138 simple_string_list_append(&schemas
, optarg
);
141 dbname
= pg_strdup(optarg
);
150 simple_string_list_append(&tables
, optarg
);
153 simple_string_list_append(&indexes
, optarg
);
156 if (!option_parse_int(optarg
, "-j/--jobs", 1, INT_MAX
,
167 maintenance_db
= pg_strdup(optarg
);
170 tablespace
= pg_strdup(optarg
);
173 fprintf(stderr
, _("Try \"%s --help\" for more information.\n"), progname
);
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
];
190 pg_log_error("too many command-line arguments (first is \"%s\")",
192 fprintf(stderr
, _("Try \"%s --help\" for more information.\n"), progname
);
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
);
209 pg_log_error("cannot reindex all databases and a specific one at the same time");
214 pg_log_error("cannot reindex all databases and system catalogs at the same time");
217 if (schemas
.head
!= NULL
)
219 pg_log_error("cannot reindex specific schema(s) in all databases");
222 if (tables
.head
!= NULL
)
224 pg_log_error("cannot reindex specific table(s) in all databases");
227 if (indexes
.head
!= NULL
)
229 pg_log_error("cannot reindex specific index(es) in all databases");
233 cparams
.dbname
= maintenance_db
;
235 reindex_all_databases(&cparams
, progname
, echo
, quiet
, verbose
,
236 concurrently
, concurrentCons
, tablespace
);
240 if (schemas
.head
!= NULL
)
242 pg_log_error("cannot reindex specific schema(s) and system catalogs at the same time");
245 if (tables
.head
!= NULL
)
247 pg_log_error("cannot reindex specific table(s) and system catalogs at the same time");
250 if (indexes
.head
!= NULL
)
252 pg_log_error("cannot reindex specific index(es) and system catalogs at the same time");
256 if (concurrentCons
> 1)
258 pg_log_error("cannot use multiple jobs to reindex system catalogs");
264 if (getenv("PGDATABASE"))
265 dbname
= getenv("PGDATABASE");
266 else if (getenv("PGUSER"))
267 dbname
= getenv("PGUSER");
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
);
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");
293 if (getenv("PGDATABASE"))
294 dbname
= getenv("PGDATABASE");
295 else if (getenv("PGUSER"))
296 dbname
= getenv("PGUSER");
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
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
);
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
)
339 SimpleStringListCell
*cell
;
340 bool parallel
= concurrentCons
> 1;
341 SimpleStringList
*process_list
= user_list
;
342 ReindexType process_type
= type
;
343 ParallelSlotArray
*sa
;
347 conn
= connectDatabase(cparams
, progname
, echo
, false, false);
349 if (concurrently
&& PQserverVersion(conn
) < 120000)
352 pg_log_error("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
353 "concurrently", "12");
357 if (tablespace
&& PQserverVersion(conn
) < 140000)
360 pg_log_error("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
367 switch (process_type
)
369 case REINDEX_DATABASE
:
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
));
384 Assert(user_list
!= NULL
);
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.
400 pg_log_warning("cannot reindex system catalogs concurrently, skipping all");
402 run_reindex_command(conn
, REINDEX_SYSTEM
, PQdb(conn
), echo
,
403 verbose
, concurrently
, false,
406 /* Build a list of relations from the database */
407 process_list
= get_parallel_object_list(conn
, process_type
,
409 process_type
= REINDEX_TABLE
;
411 /* Bail out if nothing to process */
412 if (process_list
== NULL
)
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
,
422 process_type
= REINDEX_TABLE
;
424 /* Bail out if nothing to process */
425 if (process_list
== NULL
)
438 * Fall through. The list of items for tables is already
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
)
454 /* no need to continue if there are more elements than jobs */
455 if (items_count
>= concurrentCons
)
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
;
478 free_slot
= ParallelSlotsGetIdle(sa
, NULL
);
485 ParallelSlotSetHandler(free_slot
, TableCommandResultHandler
, NULL
);
486 run_reindex_command(free_slot
->connection
, process_type
, objname
,
487 echo
, verbose
, concurrently
, true, tablespace
);
490 } while (cell
!= NULL
);
492 if (!ParallelSlotsWaitCompletion(sa
))
496 if (process_list
!= user_list
)
498 simple_string_list_destroy(process_list
);
499 pg_free(process_list
);
502 ParallelSlotsTerminate(sa
);
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
;
522 /* build the REINDEX query */
523 initPQExpBuffer(&sql
);
525 appendPQExpBufferStr(&sql
, "REINDEX ");
529 appendPQExpBuffer(&sql
, "%sVERBOSE", sep
);
535 appendPQExpBuffer(&sql
, "%sTABLESPACE %s", sep
, fmtId(tablespace
));
540 appendPQExpBufferStr(&sql
, ") ");
545 case REINDEX_DATABASE
:
546 appendPQExpBufferStr(&sql
, "DATABASE ");
549 appendPQExpBufferStr(&sql
, "INDEX ");
552 appendPQExpBufferStr(&sql
, "SCHEMA ");
555 appendPQExpBufferStr(&sql
, "SYSTEM ");
558 appendPQExpBufferStr(&sql
, "TABLE ");
563 * Parenthesized grammar is only supported for CONCURRENTLY since
564 * PostgreSQL 14. Since 12, CONCURRENTLY can be specified after the
568 appendPQExpBufferStr(&sql
, "CONCURRENTLY ");
573 case REINDEX_DATABASE
:
575 appendPQExpBufferStr(&sql
, fmtId(name
));
579 appendQualifiedRelation(&sql
, name
, conn
, echo
);
582 appendPQExpBufferStr(&sql
, name
);
586 /* finish the query */
587 appendPQExpBufferChar(&sql
, ';');
592 printf("%s\n", sql
.data
);
594 status
= PQsendQuery(conn
, sql
.data
) == 1;
597 status
= executeMaintenanceCommand(conn
, sql
.data
, echo
);
603 case REINDEX_DATABASE
:
604 pg_log_error("reindexing of database \"%s\" failed: %s",
605 PQdb(conn
), PQerrorMessage(conn
));
608 pg_log_error("reindexing of index \"%s\" in database \"%s\" failed: %s",
609 name
, PQdb(conn
), PQerrorMessage(conn
));
612 pg_log_error("reindexing of schema \"%s\" in database \"%s\" failed: %s",
613 name
, PQdb(conn
), PQerrorMessage(conn
));
616 pg_log_error("reindexing of system catalogs in database \"%s\" failed: %s",
617 PQdb(conn
), PQerrorMessage(conn
));
620 pg_log_error("reindexing of table \"%s\" in database \"%s\" failed: %s",
621 name
, PQdb(conn
), PQerrorMessage(conn
));
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
642 static SimpleStringList
*
643 get_parallel_object_list(PGconn
*conn
, ReindexType type
,
644 SimpleStringList
*user_list
, bool echo
)
646 PQExpBufferData catalog_query
;
649 SimpleStringList
*tables
;
653 initPQExpBuffer(&catalog_query
);
656 * The queries here are using a safe search_path, so there's no need to
657 * fully qualify everything.
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;");
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
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
;
701 appendPQExpBufferStr(&catalog_query
, ", ");
705 appendStringLiteralConn(&catalog_query
, nspname
, conn
);
708 appendPQExpBufferStr(&catalog_query
, ")\n"
709 " ORDER BY c.relpages DESC;");
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
);
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
);
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
)
763 conn
= connectMaintenanceDatabase(cparams
, progname
, echo
);
764 result
= executeQuery(conn
, "SELECT datname FROM pg_database WHERE datallowconn ORDER BY 1;", echo
);
767 for (i
= 0; i
< PQntuples(result
); i
++)
769 char *dbname
= PQgetvalue(result
, i
, 0);
773 printf(_("%s: reindexing database \"%s\"\n"), progname
, dbname
);
777 cparams
->override_dbname
= dbname
;
779 reindex_one_database(cparams
, REINDEX_DATABASE
, NULL
,
780 progname
, echo
, verbose
, concurrently
,
781 concurrentCons
, tablespace
);
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
);