1 /*-------------------------------------------------------------------------
5 * Portions Copyright (c) 1996-2024, 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/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"
26 typedef enum ReindexType
36 static SimpleStringList
*get_parallel_object_list(PGconn
*conn
,
38 SimpleStringList
*user_list
,
40 static void reindex_one_database(ConnParams
*cparams
, ReindexType type
,
41 SimpleStringList
*user_list
,
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
);
57 static void help(const char *progname
);
60 main(int argc
, char *argv
[])
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},
88 const char *dbname
= NULL
;
89 const char *maintenance_db
= NULL
;
90 const char *host
= NULL
;
91 const char *port
= NULL
;
92 const char *username
= NULL
;
93 const char *tablespace
= NULL
;
94 enum trivalue prompt_password
= TRI_DEFAULT
;
96 bool syscatalog
= false;
100 bool verbose
= false;
101 bool concurrently
= false;
102 SimpleStringList indexes
= {NULL
, NULL
};
103 SimpleStringList tables
= {NULL
, NULL
};
104 SimpleStringList schemas
= {NULL
, NULL
};
105 int concurrentCons
= 1;
107 pg_logging_init(argv
[0]);
108 progname
= get_progname(argv
[0]);
109 set_pglocale_pgservice(argv
[0], PG_TEXTDOMAIN("pgscripts"));
111 handle_help_version_opts(argc
, argv
, "reindexdb", help
);
113 /* process command-line options */
114 while ((c
= getopt_long(argc
, argv
, "ad:eh:i:j:qp:sS:t:U:vwW", long_options
, &optindex
)) != -1)
122 dbname
= pg_strdup(optarg
);
128 host
= pg_strdup(optarg
);
131 simple_string_list_append(&indexes
, optarg
);
134 if (!option_parse_int(optarg
, "-j/--jobs", 1, INT_MAX
,
142 port
= pg_strdup(optarg
);
148 simple_string_list_append(&schemas
, optarg
);
151 simple_string_list_append(&tables
, optarg
);
154 username
= pg_strdup(optarg
);
160 prompt_password
= TRI_NO
;
163 prompt_password
= TRI_YES
;
169 maintenance_db
= pg_strdup(optarg
);
172 tablespace
= pg_strdup(optarg
);
175 /* getopt_long already emitted a complaint */
176 pg_log_error_hint("Try \"%s --help\" for more information.", progname
);
182 * Non-option argument specifies database name as long as it wasn't
183 * already specified with -d / --dbname
185 if (optind
< argc
&& dbname
== NULL
)
187 dbname
= argv
[optind
];
193 pg_log_error("too many command-line arguments (first is \"%s\")",
195 pg_log_error_hint("Try \"%s --help\" for more information.", progname
);
199 /* fill cparams except for dbname, which is set below */
200 cparams
.pghost
= host
;
201 cparams
.pgport
= port
;
202 cparams
.pguser
= username
;
203 cparams
.prompt_password
= prompt_password
;
204 cparams
.override_dbname
= NULL
;
206 setup_cancel_handler(NULL
);
208 if (concurrentCons
> 1 && syscatalog
)
209 pg_fatal("cannot use multiple jobs to reindex system catalogs");
214 pg_fatal("cannot reindex all databases and a specific one at the same time");
216 cparams
.dbname
= maintenance_db
;
218 reindex_all_databases(&cparams
, progname
, echo
, quiet
, verbose
,
219 concurrently
, concurrentCons
, tablespace
,
220 syscatalog
, &schemas
, &tables
, &indexes
);
226 if (getenv("PGDATABASE"))
227 dbname
= getenv("PGDATABASE");
228 else if (getenv("PGUSER"))
229 dbname
= getenv("PGUSER");
231 dbname
= get_user_name_or_exit(progname
);
234 cparams
.dbname
= dbname
;
237 reindex_one_database(&cparams
, REINDEX_SYSTEM
, NULL
,
238 progname
, echo
, verbose
,
239 concurrently
, 1, tablespace
);
241 if (schemas
.head
!= NULL
)
242 reindex_one_database(&cparams
, REINDEX_SCHEMA
, &schemas
,
243 progname
, echo
, verbose
,
244 concurrently
, concurrentCons
, tablespace
);
246 if (indexes
.head
!= NULL
)
247 reindex_one_database(&cparams
, REINDEX_INDEX
, &indexes
,
248 progname
, echo
, verbose
,
249 concurrently
, concurrentCons
, tablespace
);
251 if (tables
.head
!= NULL
)
252 reindex_one_database(&cparams
, REINDEX_TABLE
, &tables
,
253 progname
, echo
, verbose
,
254 concurrently
, concurrentCons
, tablespace
);
257 * reindex database only if neither index nor table nor schema nor
258 * system catalogs is specified
260 if (!syscatalog
&& indexes
.head
== NULL
&&
261 tables
.head
== NULL
&& schemas
.head
== NULL
)
262 reindex_one_database(&cparams
, REINDEX_DATABASE
, NULL
,
263 progname
, echo
, verbose
,
264 concurrently
, concurrentCons
, tablespace
);
271 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
)
278 SimpleStringListCell
*cell
;
279 SimpleStringListCell
*indices_tables_cell
= NULL
;
280 bool parallel
= concurrentCons
> 1;
281 SimpleStringList
*process_list
= user_list
;
282 SimpleStringList
*indices_tables_list
= NULL
;
283 ReindexType process_type
= type
;
284 ParallelSlotArray
*sa
;
287 char *prev_index_table_name
= NULL
;
288 ParallelSlot
*free_slot
= NULL
;
290 conn
= connectDatabase(cparams
, progname
, echo
, false, true);
292 if (concurrently
&& PQserverVersion(conn
) < 120000)
295 pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
296 "concurrently", "12");
299 if (tablespace
&& PQserverVersion(conn
) < 140000)
302 pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
308 switch (process_type
)
310 case REINDEX_DATABASE
:
314 * Database and system reindexes only need to work on the
315 * database itself, so build a list with a single entry.
317 Assert(user_list
== NULL
);
318 process_list
= pg_malloc0(sizeof(SimpleStringList
));
319 simple_string_list_append(process_list
, PQdb(conn
));
325 Assert(user_list
!= NULL
);
331 switch (process_type
)
333 case REINDEX_DATABASE
:
335 /* Build a list of relations from the database */
336 process_list
= get_parallel_object_list(conn
, process_type
,
338 process_type
= REINDEX_TABLE
;
340 /* Bail out if nothing to process */
341 if (process_list
== NULL
)
346 Assert(user_list
!= NULL
);
348 /* Build a list of relations from all the schemas */
349 process_list
= get_parallel_object_list(conn
, process_type
,
351 process_type
= REINDEX_TABLE
;
353 /* Bail out if nothing to process */
354 if (process_list
== NULL
)
359 Assert(user_list
!= NULL
);
362 * Build a list of relations from the indices. This will
363 * accordingly reorder the list of indices too.
365 indices_tables_list
= get_parallel_object_list(conn
, process_type
,
369 * Bail out if nothing to process. 'user_list' was modified
370 * in-place, so check if it has at least one cell.
372 if (user_list
->head
== NULL
)
376 * Assuming 'user_list' is not empty, 'indices_tables_list'
377 * shouldn't be empty as well.
379 Assert(indices_tables_list
!= NULL
);
380 indices_tables_cell
= indices_tables_list
->head
;
392 * Fall through. The list of items for tables is already
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.
404 for (cell
= process_list
->head
; cell
; cell
= cell
->next
)
408 /* no need to continue if there are more elements than jobs */
409 if (items_count
>= concurrentCons
)
412 concurrentCons
= Min(concurrentCons
, items_count
);
413 Assert(concurrentCons
> 0);
415 Assert(process_list
!= NULL
);
417 sa
= ParallelSlotsSetup(concurrentCons
, cparams
, progname
, echo
, NULL
);
418 ParallelSlotsAdoptConn(sa
, conn
);
420 cell
= process_list
->head
;
423 const char *objname
= cell
->val
;
424 bool need_new_slot
= true;
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.
438 if (parallel
&& process_type
== REINDEX_INDEX
)
440 if (prev_index_table_name
!= NULL
&&
441 strcmp(prev_index_table_name
, indices_tables_cell
->val
) == 0)
442 need_new_slot
= false;
443 prev_index_table_name
= indices_tables_cell
->val
;
444 indices_tables_cell
= indices_tables_cell
->next
;
449 free_slot
= ParallelSlotsGetIdle(sa
, NULL
);
456 ParallelSlotSetHandler(free_slot
, TableCommandResultHandler
, NULL
);
459 run_reindex_command(free_slot
->connection
, process_type
, objname
,
460 echo
, verbose
, concurrently
, true, tablespace
);
463 } while (cell
!= NULL
);
465 if (!ParallelSlotsWaitCompletion(sa
))
469 if (process_list
!= user_list
)
471 simple_string_list_destroy(process_list
);
472 pg_free(process_list
);
475 if (indices_tables_list
)
477 simple_string_list_destroy(indices_tables_list
);
478 pg_free(indices_tables_list
);
481 ParallelSlotsTerminate(sa
);
489 run_reindex_command(PGconn
*conn
, ReindexType type
, const char *name
,
490 bool echo
, bool verbose
, bool concurrently
, bool async
,
491 const char *tablespace
)
493 const char *paren
= "(";
494 const char *comma
= ", ";
495 const char *sep
= paren
;
501 /* build the REINDEX query */
502 initPQExpBuffer(&sql
);
504 appendPQExpBufferStr(&sql
, "REINDEX ");
508 appendPQExpBuffer(&sql
, "%sVERBOSE", sep
);
514 appendPQExpBuffer(&sql
, "%sTABLESPACE %s", sep
, fmtId(tablespace
));
519 appendPQExpBufferStr(&sql
, ") ");
524 case REINDEX_DATABASE
:
525 appendPQExpBufferStr(&sql
, "DATABASE ");
528 appendPQExpBufferStr(&sql
, "INDEX ");
531 appendPQExpBufferStr(&sql
, "SCHEMA ");
534 appendPQExpBufferStr(&sql
, "SYSTEM ");
537 appendPQExpBufferStr(&sql
, "TABLE ");
542 * Parenthesized grammar is only supported for CONCURRENTLY since
543 * PostgreSQL 14. Since 12, CONCURRENTLY can be specified after the
547 appendPQExpBufferStr(&sql
, "CONCURRENTLY ");
552 case REINDEX_DATABASE
:
554 appendPQExpBufferStr(&sql
, fmtId(name
));
558 appendQualifiedRelation(&sql
, name
, conn
, echo
);
561 appendPQExpBufferStr(&sql
, name
);
565 /* finish the query */
566 appendPQExpBufferChar(&sql
, ';');
571 printf("%s\n", sql
.data
);
573 status
= PQsendQuery(conn
, sql
.data
) == 1;
576 status
= executeMaintenanceCommand(conn
, sql
.data
, echo
);
582 case REINDEX_DATABASE
:
583 pg_log_error("reindexing of database \"%s\" failed: %s",
584 PQdb(conn
), PQerrorMessage(conn
));
587 pg_log_error("reindexing of index \"%s\" in database \"%s\" failed: %s",
588 name
, PQdb(conn
), PQerrorMessage(conn
));
591 pg_log_error("reindexing of schema \"%s\" in database \"%s\" failed: %s",
592 name
, PQdb(conn
), PQerrorMessage(conn
));
595 pg_log_error("reindexing of system catalogs in database \"%s\" failed: %s",
596 PQdb(conn
), PQerrorMessage(conn
));
599 pg_log_error("reindexing of table \"%s\" in database \"%s\" failed: %s",
600 name
, PQdb(conn
), PQerrorMessage(conn
));
610 termPQExpBuffer(&sql
);
614 * Prepare the list of objects to process by querying the catalogs.
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
621 static SimpleStringList
*
622 get_parallel_object_list(PGconn
*conn
, ReindexType type
,
623 SimpleStringList
*user_list
, bool echo
)
625 PQExpBufferData catalog_query
;
628 SimpleStringList
*tables
;
632 initPQExpBuffer(&catalog_query
);
635 * The queries here are using a safe search_path, so there's no need to
636 * fully qualify everything.
640 case REINDEX_DATABASE
:
641 Assert(user_list
== NULL
);
642 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;");
658 SimpleStringListCell
*cell
;
659 bool nsp_listed
= false;
661 Assert(user_list
!= NULL
);
664 * All the tables from all the listed schemas are grabbed at
667 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 (");
679 for (cell
= user_list
->head
; cell
; cell
= cell
->next
)
681 const char *nspname
= cell
->val
;
684 appendPQExpBufferStr(&catalog_query
, ", ");
688 appendStringLiteralConn(&catalog_query
, nspname
, conn
);
691 appendPQExpBufferStr(&catalog_query
, ")\n"
692 " ORDER BY c.relpages DESC;");
698 SimpleStringListCell
*cell
;
700 Assert(user_list
!= NULL
);
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.
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.
714 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['");
722 for (cell
= user_list
->head
; cell
; cell
= cell
->next
)
724 if (cell
!= user_list
->head
)
725 appendPQExpBufferStr(&catalog_query
, "', '");
727 appendQualifiedRelation(&catalog_query
, cell
->val
, conn
, echo
);
731 * Order tables by the size of its greatest index. Within the
732 * table, order indexes by their sizes.
734 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");
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
745 simple_string_list_destroy(user_list
);
746 user_list
->head
= user_list
->tail
= NULL
;
756 res
= executeQuery(conn
, catalog_query
.data
, echo
);
757 termPQExpBuffer(&catalog_query
);
760 * If no rows are returned, there are no matching tables, so we are done.
762 ntups
= PQntuples(res
);
770 tables
= pg_malloc0(sizeof(SimpleStringList
));
772 /* Build qualified identifiers for each table */
773 initPQExpBuffer(&buf
);
774 for (i
= 0; i
< ntups
; i
++)
776 appendPQExpBufferStr(&buf
,
777 fmtQualifiedId(PQgetvalue(res
, i
, 1),
778 PQgetvalue(res
, i
, 0)));
780 simple_string_list_append(tables
, buf
.data
);
781 resetPQExpBuffer(&buf
);
783 if (type
== REINDEX_INDEX
)
786 * For index-level REINDEX, rebuild the list of indexes to match
787 * the order of tables list.
789 appendPQExpBufferStr(&buf
,
790 fmtQualifiedId(PQgetvalue(res
, i
, 1),
791 PQgetvalue(res
, i
, 2)));
793 simple_string_list_append(user_list
, buf
.data
);
794 resetPQExpBuffer(&buf
);
797 termPQExpBuffer(&buf
);
804 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
)
815 conn
= connectMaintenanceDatabase(cparams
, progname
, echo
);
816 result
= executeQuery(conn
,
817 "SELECT datname FROM pg_database WHERE datallowconn AND datconnlimit <> -2 ORDER BY 1;",
821 for (i
= 0; i
< PQntuples(result
); i
++)
823 char *dbname
= PQgetvalue(result
, i
, 0);
827 printf(_("%s: reindexing database \"%s\"\n"), progname
, dbname
);
831 cparams
->override_dbname
= dbname
;
834 reindex_one_database(cparams
, REINDEX_SYSTEM
, NULL
,
835 progname
, echo
, verbose
,
836 concurrently
, 1, tablespace
);
838 if (schemas
->head
!= NULL
)
839 reindex_one_database(cparams
, REINDEX_SCHEMA
, schemas
,
840 progname
, echo
, verbose
,
841 concurrently
, concurrentCons
, tablespace
);
843 if (indexes
->head
!= NULL
)
844 reindex_one_database(cparams
, REINDEX_INDEX
, indexes
,
845 progname
, echo
, verbose
,
846 concurrently
, 1, tablespace
);
848 if (tables
->head
!= NULL
)
849 reindex_one_database(cparams
, REINDEX_TABLE
, tables
,
850 progname
, echo
, verbose
,
851 concurrently
, concurrentCons
, tablespace
);
854 * reindex database only if neither index nor table nor schema nor
855 * system catalogs is specified
857 if (!syscatalog
&& indexes
->head
== NULL
&&
858 tables
->head
== NULL
&& schemas
->head
== NULL
)
859 reindex_one_database(cparams
, REINDEX_DATABASE
, NULL
,
860 progname
, echo
, verbose
,
861 concurrently
, concurrentCons
, tablespace
);
868 help(const char *progname
)
870 printf(_("%s reindexes a PostgreSQL database.\n\n"), progname
);
871 printf(_("Usage:\n"));
872 printf(_(" %s [OPTION]... [DBNAME]\n"), progname
);
873 printf(_("\nOptions:\n"));
874 printf(_(" -a, --all reindex all databases\n"));
875 printf(_(" --concurrently reindex concurrently\n"));
876 printf(_(" -d, --dbname=DBNAME database to reindex\n"));
877 printf(_(" -e, --echo show the commands being sent to the server\n"));
878 printf(_(" -i, --index=INDEX recreate specific index(es) only\n"));
879 printf(_(" -j, --jobs=NUM use this many concurrent connections to reindex\n"));
880 printf(_(" -q, --quiet don't write any messages\n"));
881 printf(_(" -s, --system reindex system catalogs only\n"));
882 printf(_(" -S, --schema=SCHEMA reindex specific schema(s) only\n"));
883 printf(_(" -t, --table=TABLE reindex specific table(s) only\n"));
884 printf(_(" --tablespace=TABLESPACE tablespace where indexes are rebuilt\n"));
885 printf(_(" -v, --verbose write a lot of output\n"));
886 printf(_(" -V, --version output version information, then exit\n"));
887 printf(_(" -?, --help show this help, then exit\n"));
888 printf(_("\nConnection options:\n"));
889 printf(_(" -h, --host=HOSTNAME database server host or socket directory\n"));
890 printf(_(" -p, --port=PORT database server port\n"));
891 printf(_(" -U, --username=USERNAME user name to connect as\n"));
892 printf(_(" -w, --no-password never prompt for password\n"));
893 printf(_(" -W, --password force password prompt\n"));
894 printf(_(" --maintenance-db=DBNAME alternate maintenance database\n"));
895 printf(_("\nRead the description of the SQL command REINDEX for details.\n"));
896 printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT
);
897 printf(_("%s home page: <%s>\n"), PACKAGE_NAME
, PACKAGE_URL
);