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