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