PostgreSQL Source Code git master
vacuuming.c
Go to the documentation of this file.
1/*-------------------------------------------------------------------------
2 * vacuuming.c
3 * Helper routines for vacuumdb
4 *
5 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
6 * Portions Copyright (c) 1994, Regents of the University of California
7 *
8 * src/bin/scripts/vacuuming.c
9 *
10 *-------------------------------------------------------------------------
11 */
12
13#include "postgres_fe.h"
14
15#include "catalog/pg_attribute_d.h"
16#include "catalog/pg_class_d.h"
17#include "common/connect.h"
18#include "common/logging.h"
19#include "fe_utils/cancel.h"
24#include "vacuuming.h"
25
26
27static int vacuum_one_database(ConnParams *cparams,
28 vacuumingOptions *vacopts,
29 int stage,
30 SimpleStringList *objects,
31 SimpleStringList **found_objs,
32 int concurrentCons,
33 const char *progname);
34static int vacuum_all_databases(ConnParams *cparams,
35 vacuumingOptions *vacopts,
36 SimpleStringList *objects,
37 int concurrentCons,
38 const char *progname);
40 vacuumingOptions *vacopts,
41 SimpleStringList *objects);
44 vacuumingOptions *vacopts, const char *table);
45static void run_vacuum_command(ParallelSlot *free_slot,
46 vacuumingOptions *vacopts, const char *sql,
47 const char *table);
48
49/*
50 * Executes vacuum/analyze as indicated. Returns 0 if the plan is carried
51 * to completion, or -1 in case of certain errors (which should hopefully
52 * been already reported.) Other errors are reported via pg_fatal().
53 */
54int
55vacuuming_main(ConnParams *cparams, const char *dbname,
56 const char *maintenance_db, vacuumingOptions *vacopts,
57 SimpleStringList *objects,
58 unsigned int tbl_count, int concurrentCons,
59 const char *progname)
60{
62
63 /* Avoid opening extra connections. */
64 if (tbl_count > 0 && (concurrentCons > tbl_count))
65 concurrentCons = tbl_count;
66
67 if (vacopts->objfilter & OBJFILTER_ALL_DBS)
68 {
69 cparams->dbname = maintenance_db;
70
71 return vacuum_all_databases(cparams, vacopts,
72 objects,
73 concurrentCons,
74 progname);
75 }
76 else
77 {
78 if (dbname == NULL)
79 {
80 if (getenv("PGDATABASE"))
81 dbname = getenv("PGDATABASE");
82 else if (getenv("PGUSER"))
83 dbname = getenv("PGUSER");
84 else
86 }
87
88 cparams->dbname = dbname;
89
90 if (vacopts->mode == MODE_ANALYZE_IN_STAGES)
91 {
92 SimpleStringList *found_objs = NULL;
93
94 for (int stage = 0; stage < ANALYZE_NUM_STAGES; stage++)
95 {
96 int ret;
97
98 ret = vacuum_one_database(cparams, vacopts,
99 stage,
100 objects,
101 vacopts->missing_stats_only ? &found_objs : NULL,
102 concurrentCons,
103 progname);
104 if (ret != 0)
105 {
106 free_retrieved_objects(found_objs);
107 return ret;
108 }
109 }
110
111 free_retrieved_objects(found_objs);
112 return EXIT_SUCCESS;
113 }
114 else
115 return vacuum_one_database(cparams, vacopts,
117 objects, NULL,
118 concurrentCons,
119 progname);
120 }
121}
122
123/*
124 * vacuum_one_database
125 *
126 * Process tables in the given database.
127 *
128 * There are two ways to specify the list of objects to process:
129 *
130 * 1) The "found_objs" parameter is a double pointer to a fully qualified list
131 * of objects to process, as returned by a previous call to
132 * vacuum_one_database().
133 *
134 * a) If both "found_objs" (the double pointer) and "*found_objs" (the
135 * once-dereferenced double pointer) are not NULL, this list takes
136 * priority, and anything specified in "objects" is ignored.
137 *
138 * b) If "found_objs" (the double pointer) is not NULL but "*found_objs"
139 * (the once-dereferenced double pointer) _is_ NULL, the "objects"
140 * parameter takes priority, and the results of the catalog query
141 * described in (2) are stored in "found_objs".
142 *
143 * c) If "found_objs" (the double pointer) is NULL, the "objects"
144 * parameter again takes priority, and the results of the catalog query
145 * are not saved.
146 *
147 * 2) The "objects" parameter is a user-specified list of objects to process.
148 * When (1b) or (1c) applies, this function performs a catalog query to
149 * retrieve a fully qualified list of objects to process, as described
150 * below.
151 *
152 * a) If "objects" is not NULL, the catalog query gathers only the objects
153 * listed in "objects".
154 *
155 * b) If "objects" is NULL, all tables in the database are gathered.
156 *
157 * Note that this function is only concerned with running exactly one stage
158 * when in analyze-in-stages mode; caller must iterate on us if necessary.
159 *
160 * If concurrentCons is > 1, multiple connections are used to vacuum tables
161 * in parallel.
162 */
163static int
165 vacuumingOptions *vacopts,
166 int stage,
167 SimpleStringList *objects,
168 SimpleStringList **found_objs,
169 int concurrentCons,
170 const char *progname)
171{
172 PQExpBufferData sql;
173 PGconn *conn;
176 int ntups = 0;
177 const char *initcmd;
178 SimpleStringList *retobjs = NULL;
179 bool free_retobjs = false;
180 int ret = EXIT_SUCCESS;
181 const char *stage_commands[] = {
182 "SET default_statistics_target=1; SET vacuum_cost_delay=0;",
183 "SET default_statistics_target=10; RESET vacuum_cost_delay;",
184 "RESET default_statistics_target;"
185 };
186 const char *stage_messages[] = {
187 gettext_noop("Generating minimal optimizer statistics (1 target)"),
188 gettext_noop("Generating medium optimizer statistics (10 targets)"),
189 gettext_noop("Generating default (full) optimizer statistics")
190 };
191
192 Assert(stage == ANALYZE_NO_STAGE ||
193 (stage >= 0 && stage < ANALYZE_NUM_STAGES));
194
195 conn = connectDatabase(cparams, progname, vacopts->echo, false, true);
196
197 if (vacopts->disable_page_skipping && PQserverVersion(conn) < 90600)
198 {
199 PQfinish(conn);
200 pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
201 "disable-page-skipping", "9.6");
202 }
203
204 if (vacopts->no_index_cleanup && PQserverVersion(conn) < 120000)
205 {
206 PQfinish(conn);
207 pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
208 "no-index-cleanup", "12");
209 }
210
211 if (vacopts->force_index_cleanup && PQserverVersion(conn) < 120000)
212 {
213 PQfinish(conn);
214 pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
215 "force-index-cleanup", "12");
216 }
217
218 if (!vacopts->do_truncate && PQserverVersion(conn) < 120000)
219 {
220 PQfinish(conn);
221 pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
222 "no-truncate", "12");
223 }
224
225 if (!vacopts->process_main && PQserverVersion(conn) < 160000)
226 {
227 PQfinish(conn);
228 pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
229 "no-process-main", "16");
230 }
231
232 if (!vacopts->process_toast && PQserverVersion(conn) < 140000)
233 {
234 PQfinish(conn);
235 pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
236 "no-process-toast", "14");
237 }
238
239 if (vacopts->skip_locked && PQserverVersion(conn) < 120000)
240 {
241 PQfinish(conn);
242 pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
243 "skip-locked", "12");
244 }
245
246 if (vacopts->min_xid_age != 0 && PQserverVersion(conn) < 90600)
247 {
248 PQfinish(conn);
249 pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
250 "--min-xid-age", "9.6");
251 }
252
253 if (vacopts->min_mxid_age != 0 && PQserverVersion(conn) < 90600)
254 {
255 PQfinish(conn);
256 pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
257 "--min-mxid-age", "9.6");
258 }
259
260 if (vacopts->parallel_workers >= 0 && PQserverVersion(conn) < 130000)
261 {
262 PQfinish(conn);
263 pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
264 "--parallel", "13");
265 }
266
267 if (vacopts->buffer_usage_limit && PQserverVersion(conn) < 160000)
268 {
269 PQfinish(conn);
270 pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
271 "--buffer-usage-limit", "16");
272 }
273
274 if (vacopts->missing_stats_only && PQserverVersion(conn) < 150000)
275 {
276 PQfinish(conn);
277 pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
278 "--missing-stats-only", "15");
279 }
280
281 /* skip_database_stats is used automatically if server supports it */
282 vacopts->skip_database_stats = (PQserverVersion(conn) >= 160000);
283
284 if (!vacopts->quiet)
285 {
286 if (vacopts->mode == MODE_ANALYZE_IN_STAGES)
287 printf(_("%s: processing database \"%s\": %s\n"),
288 progname, PQdb(conn), _(stage_messages[stage]));
289 else
290 printf(_("%s: vacuuming database \"%s\"\n"),
291 progname, PQdb(conn));
292 fflush(stdout);
293 }
294
295 /*
296 * If the caller provided the results of a previous catalog query, just
297 * use that. Otherwise, run the catalog query ourselves and set the
298 * return variable if provided. (If it is, then freeing the string list
299 * becomes the caller's responsibility.)
300 */
301 if (found_objs && *found_objs)
302 retobjs = *found_objs;
303 else
304 {
305 retobjs = retrieve_objects(conn, vacopts, objects);
306 if (found_objs)
307 *found_objs = retobjs;
308 else
309 free_retobjs = true;
310 }
311
312 /*
313 * Count the number of objects in the catalog query result. If there are
314 * none, we are done.
315 */
316 for (cell = retobjs->head; cell; cell = cell->next)
317 ntups++;
318
319 if (ntups == 0)
320 {
321 PQfinish(conn);
322 if (free_retobjs)
323 free_retrieved_objects(retobjs);
324 return EXIT_SUCCESS;
325 }
326
327 /*
328 * Ensure concurrentCons is sane. If there are more connections than
329 * vacuumable relations, we don't need to use them all.
330 */
331 if (concurrentCons > ntups)
332 concurrentCons = ntups;
333 if (concurrentCons <= 0)
334 concurrentCons = 1;
335
336 /*
337 * All slots need to be prepared to run the appropriate analyze stage, if
338 * caller requested that mode. We have to prepare the initial connection
339 * ourselves before setting up the slots.
340 */
341 if (vacopts->mode == MODE_ANALYZE_IN_STAGES)
342 {
343 initcmd = stage_commands[stage];
344
345 if (vacopts->dry_run)
346 printf("%s\n", initcmd);
347 else
348 executeCommand(conn, initcmd, vacopts->echo);
349 }
350 else
351 initcmd = NULL;
352
353 /*
354 * Setup the database connections. We reuse the connection we already have
355 * for the first slot. If not in parallel mode, the first slot in the
356 * array contains the connection.
357 */
358 sa = ParallelSlotsSetup(concurrentCons, cparams, progname,
359 vacopts->echo, initcmd);
361
362 initPQExpBuffer(&sql);
363
364 cell = retobjs->head;
365 do
366 {
367 const char *tabname = cell->val;
368 ParallelSlot *free_slot;
369
370 if (CancelRequested)
371 {
372 ret = EXIT_FAILURE;
373 goto finish;
374 }
375
376 free_slot = ParallelSlotsGetIdle(sa, NULL);
377 if (!free_slot)
378 {
379 ret = EXIT_FAILURE;
380 goto finish;
381 }
382
383 prepare_vacuum_command(free_slot->connection, &sql,
384 vacopts, tabname);
385
386 /*
387 * Execute the vacuum. All errors are handled in processQueryResult
388 * through ParallelSlotsGetIdle.
389 */
391 run_vacuum_command(free_slot, vacopts, sql.data, tabname);
392
393 cell = cell->next;
394 } while (cell != NULL);
395
397 {
398 ret = EXIT_FAILURE;
399 goto finish;
400 }
401
402 /* If we used SKIP_DATABASE_STATS, mop up with ONLY_DATABASE_STATS */
403 if (vacopts->mode == MODE_VACUUM && vacopts->skip_database_stats)
404 {
405 const char *cmd = "VACUUM (ONLY_DATABASE_STATS);";
406 ParallelSlot *free_slot = ParallelSlotsGetIdle(sa, NULL);
407
408 if (!free_slot)
409 {
410 ret = EXIT_FAILURE;
411 goto finish;
412 }
413
415 run_vacuum_command(free_slot, vacopts, cmd, NULL);
416
418 ret = EXIT_FAILURE; /* error already reported by handler */
419 }
420
421finish:
423 pg_free(sa);
424 termPQExpBuffer(&sql);
425 if (free_retobjs)
426 free_retrieved_objects(retobjs);
427
428 return ret;
429}
430
431/*
432 * Vacuum/analyze all connectable databases.
433 *
434 * In analyze-in-stages mode, we process all databases in one stage before
435 * moving on to the next stage. That ensure minimal stats are available
436 * quickly everywhere before generating more detailed ones.
437 */
438static int
440 vacuumingOptions *vacopts,
441 SimpleStringList *objects,
442 int concurrentCons,
443 const char *progname)
444{
445 int ret = EXIT_SUCCESS;
446 PGconn *conn;
447 PGresult *result;
448 int numdbs;
449
450 conn = connectMaintenanceDatabase(cparams, progname, vacopts->echo);
451 result = executeQuery(conn,
452 "SELECT datname FROM pg_database WHERE datallowconn AND datconnlimit <> -2 ORDER BY 1;",
453 vacopts->echo);
454 numdbs = PQntuples(result);
455 PQfinish(conn);
456
457 if (vacopts->mode == MODE_ANALYZE_IN_STAGES)
458 {
459 SimpleStringList **found_objs = NULL;
460
461 if (vacopts->missing_stats_only)
462 found_objs = palloc0(numdbs * sizeof(SimpleStringList *));
463
464 /*
465 * When analyzing all databases in stages, we analyze them all in the
466 * fastest stage first, so that initial statistics become available
467 * for all of them as soon as possible.
468 *
469 * This means we establish several times as many connections, but
470 * that's a secondary consideration.
471 */
472 for (int stage = 0; stage < ANALYZE_NUM_STAGES; stage++)
473 {
474 for (int i = 0; i < numdbs; i++)
475 {
476 cparams->override_dbname = PQgetvalue(result, i, 0);
477 ret = vacuum_one_database(cparams, vacopts, stage,
478 objects,
479 vacopts->missing_stats_only ? &found_objs[i] : NULL,
480 concurrentCons,
481 progname);
482 if (ret != EXIT_SUCCESS)
483 break;
484 }
485 if (ret != EXIT_SUCCESS)
486 break;
487 }
488
489 if (vacopts->missing_stats_only)
490 {
491 for (int i = 0; i < numdbs; i++)
492 free_retrieved_objects(found_objs[i]);
493 pg_free(found_objs);
494 }
495 }
496 else
497 {
498 for (int i = 0; i < numdbs; i++)
499 {
500 cparams->override_dbname = PQgetvalue(result, i, 0);
501 ret = vacuum_one_database(cparams, vacopts,
503 objects,
504 NULL,
505 concurrentCons,
506 progname);
507 if (ret != EXIT_SUCCESS)
508 break;
509 }
510 }
511
512 PQclear(result);
513
514 return ret;
515}
516
517/*
518 * Prepare the list of tables to process by querying the catalogs.
519 *
520 * Since we execute the constructed query with the default search_path (which
521 * could be unsafe), everything in this query MUST be fully qualified.
522 *
523 * First, build a WITH clause for the catalog query if any tables were
524 * specified, with a set of values made of relation names and their optional
525 * set of columns. This is used to match any provided column lists with the
526 * generated qualified identifiers and to filter for the tables provided via
527 * --table. If a listed table does not exist, the catalog query will fail.
528 */
529static SimpleStringList *
531 SimpleStringList *objects)
532{
534 PQExpBufferData catalog_query;
535 PGresult *res;
538 bool objects_listed = false;
539
540 initPQExpBuffer(&catalog_query);
541 for (cell = objects ? objects->head : NULL; cell; cell = cell->next)
542 {
543 char *just_table = NULL;
544 const char *just_columns = NULL;
545
546 if (!objects_listed)
547 {
548 appendPQExpBufferStr(&catalog_query,
549 "WITH listed_objects (object_oid, column_list) AS (\n"
550 " VALUES (");
551 objects_listed = true;
552 }
553 else
554 appendPQExpBufferStr(&catalog_query, ",\n (");
555
557 {
558 appendStringLiteralConn(&catalog_query, cell->val, conn);
559 appendPQExpBufferStr(&catalog_query, "::pg_catalog.regnamespace, ");
560 }
561
562 if (vacopts->objfilter & OBJFILTER_TABLE)
563 {
564 /*
565 * Split relation and column names given by the user, this is used
566 * to feed the CTE with values on which are performed pre-run
567 * validity checks as well. For now these happen only on the
568 * relation name.
569 */
571 &just_table, &just_columns);
572
573 appendStringLiteralConn(&catalog_query, just_table, conn);
574 appendPQExpBufferStr(&catalog_query, "::pg_catalog.regclass, ");
575 }
576
577 if (just_columns && just_columns[0] != '\0')
578 appendStringLiteralConn(&catalog_query, just_columns, conn);
579 else
580 appendPQExpBufferStr(&catalog_query, "NULL");
581
582 appendPQExpBufferStr(&catalog_query, "::pg_catalog.text)");
583
584 pg_free(just_table);
585 }
586
587 /* Finish formatting the CTE */
588 if (objects_listed)
589 appendPQExpBufferStr(&catalog_query, "\n)\n");
590
591 appendPQExpBufferStr(&catalog_query, "SELECT c.relname, ns.nspname");
592
593 if (objects_listed)
594 appendPQExpBufferStr(&catalog_query, ", listed_objects.column_list");
595
596 appendPQExpBufferStr(&catalog_query,
597 " FROM pg_catalog.pg_class c\n"
598 " JOIN pg_catalog.pg_namespace ns"
599 " ON c.relnamespace OPERATOR(pg_catalog.=) ns.oid\n"
600 " CROSS JOIN LATERAL (SELECT c.relkind IN ("
601 CppAsString2(RELKIND_PARTITIONED_TABLE) ", "
602 CppAsString2(RELKIND_PARTITIONED_INDEX) ")) as p (inherited)\n"
603 " LEFT JOIN pg_catalog.pg_class t"
604 " ON c.reltoastrelid OPERATOR(pg_catalog.=) t.oid\n");
605
606 /*
607 * Used to match the tables or schemas listed by the user, completing the
608 * JOIN clause.
609 */
610 if (objects_listed)
611 {
612 appendPQExpBufferStr(&catalog_query, " LEFT JOIN listed_objects"
613 " ON listed_objects.object_oid"
614 " OPERATOR(pg_catalog.=) ");
615
616 if (vacopts->objfilter & OBJFILTER_TABLE)
617 appendPQExpBufferStr(&catalog_query, "c.oid\n");
618 else
619 appendPQExpBufferStr(&catalog_query, "ns.oid\n");
620 }
621
622 /*
623 * Exclude temporary tables, beginning the WHERE clause.
624 */
625 appendPQExpBufferStr(&catalog_query,
626 " WHERE c.relpersistence OPERATOR(pg_catalog.!=) "
627 CppAsString2(RELPERSISTENCE_TEMP) "\n");
628
629 /*
630 * Used to match the tables or schemas listed by the user, for the WHERE
631 * clause.
632 */
633 if (objects_listed)
634 {
635 if (vacopts->objfilter & OBJFILTER_SCHEMA_EXCLUDE)
636 appendPQExpBufferStr(&catalog_query,
637 " AND listed_objects.object_oid IS NULL\n");
638 else
639 appendPQExpBufferStr(&catalog_query,
640 " AND listed_objects.object_oid IS NOT NULL\n");
641 }
642
643 /*
644 * If no tables were listed, filter for the relevant relation types. If
645 * tables were given via --table, don't bother filtering by relation type.
646 * Instead, let the server decide whether a given relation can be
647 * processed in which case the user will know about it.
648 */
649 if ((vacopts->objfilter & OBJFILTER_TABLE) == 0)
650 {
651 /*
652 * vacuumdb should generally follow the behavior of the underlying
653 * VACUUM and ANALYZE commands. In MODE_ANALYZE mode, process regular
654 * tables, materialized views, and partitioned tables, just like
655 * ANALYZE (with no specific target tables) does. Otherwise, process
656 * only regular tables and materialized views, since VACUUM skips
657 * partitioned tables when no target tables are specified.
658 */
659 if (vacopts->mode == MODE_ANALYZE)
660 appendPQExpBufferStr(&catalog_query,
661 " AND c.relkind OPERATOR(pg_catalog.=) ANY (array["
662 CppAsString2(RELKIND_RELATION) ", "
663 CppAsString2(RELKIND_MATVIEW) ", "
664 CppAsString2(RELKIND_PARTITIONED_TABLE) "])\n");
665 else
666 appendPQExpBufferStr(&catalog_query,
667 " AND c.relkind OPERATOR(pg_catalog.=) ANY (array["
668 CppAsString2(RELKIND_RELATION) ", "
669 CppAsString2(RELKIND_MATVIEW) "])\n");
670 }
671
672 /*
673 * For --min-xid-age and --min-mxid-age, the age of the relation is the
674 * greatest of the ages of the main relation and its associated TOAST
675 * table. The commands generated by vacuumdb will also process the TOAST
676 * table for the relation if necessary, so it does not need to be
677 * considered separately.
678 */
679 if (vacopts->min_xid_age != 0)
680 {
681 appendPQExpBuffer(&catalog_query,
682 " AND GREATEST(pg_catalog.age(c.relfrozenxid),"
683 " pg_catalog.age(t.relfrozenxid)) "
684 " OPERATOR(pg_catalog.>=) '%d'::pg_catalog.int4\n"
685 " AND c.relfrozenxid OPERATOR(pg_catalog.!=)"
686 " '0'::pg_catalog.xid\n",
687 vacopts->min_xid_age);
688 }
689
690 if (vacopts->min_mxid_age != 0)
691 {
692 appendPQExpBuffer(&catalog_query,
693 " AND GREATEST(pg_catalog.mxid_age(c.relminmxid),"
694 " pg_catalog.mxid_age(t.relminmxid)) OPERATOR(pg_catalog.>=)"
695 " '%d'::pg_catalog.int4\n"
696 " AND c.relminmxid OPERATOR(pg_catalog.!=)"
697 " '0'::pg_catalog.xid\n",
698 vacopts->min_mxid_age);
699 }
700
701 if (vacopts->missing_stats_only)
702 {
703 appendPQExpBufferStr(&catalog_query, " AND (\n");
704
705 /* regular stats */
706 appendPQExpBufferStr(&catalog_query,
707 " EXISTS (SELECT NULL FROM pg_catalog.pg_attribute a\n"
708 " WHERE a.attrelid OPERATOR(pg_catalog.=) c.oid\n"
709 " AND a.attnum OPERATOR(pg_catalog.>) 0::pg_catalog.int2\n"
710 " AND NOT a.attisdropped\n"
711 " AND a.attstattarget IS DISTINCT FROM 0::pg_catalog.int2\n"
712 " AND a.attgenerated OPERATOR(pg_catalog.<>) "
713 CppAsString2(ATTRIBUTE_GENERATED_VIRTUAL) "\n"
714 " AND NOT EXISTS (SELECT NULL FROM pg_catalog.pg_statistic s\n"
715 " WHERE s.starelid OPERATOR(pg_catalog.=) a.attrelid\n"
716 " AND s.staattnum OPERATOR(pg_catalog.=) a.attnum\n"
717 " AND s.stainherit OPERATOR(pg_catalog.=) p.inherited))\n");
718
719 /* extended stats */
720 appendPQExpBufferStr(&catalog_query,
721 " OR EXISTS (SELECT NULL FROM pg_catalog.pg_statistic_ext e\n"
722 " WHERE e.stxrelid OPERATOR(pg_catalog.=) c.oid\n"
723 " AND e.stxstattarget IS DISTINCT FROM 0::pg_catalog.int2\n"
724 " AND NOT EXISTS (SELECT NULL FROM pg_catalog.pg_statistic_ext_data d\n"
725 " WHERE d.stxoid OPERATOR(pg_catalog.=) e.oid\n"
726 " AND d.stxdinherit OPERATOR(pg_catalog.=) p.inherited))\n");
727
728 /* expression indexes */
729 appendPQExpBufferStr(&catalog_query,
730 " OR EXISTS (SELECT NULL FROM pg_catalog.pg_attribute a\n"
731 " JOIN pg_catalog.pg_index i"
732 " ON i.indexrelid OPERATOR(pg_catalog.=) a.attrelid\n"
733 " WHERE i.indrelid OPERATOR(pg_catalog.=) c.oid\n"
734 " AND i.indkey[a.attnum OPERATOR(pg_catalog.-) 1::pg_catalog.int2]"
735 " OPERATOR(pg_catalog.=) 0::pg_catalog.int2\n"
736 " AND a.attnum OPERATOR(pg_catalog.>) 0::pg_catalog.int2\n"
737 " AND NOT a.attisdropped\n"
738 " AND a.attstattarget IS DISTINCT FROM 0::pg_catalog.int2\n"
739 " AND NOT EXISTS (SELECT NULL FROM pg_catalog.pg_statistic s\n"
740 " WHERE s.starelid OPERATOR(pg_catalog.=) a.attrelid\n"
741 " AND s.staattnum OPERATOR(pg_catalog.=) a.attnum\n"
742 " AND s.stainherit OPERATOR(pg_catalog.=) p.inherited))\n");
743
744 /* inheritance and regular stats */
745 appendPQExpBufferStr(&catalog_query,
746 " OR EXISTS (SELECT NULL FROM pg_catalog.pg_attribute a\n"
747 " WHERE a.attrelid OPERATOR(pg_catalog.=) c.oid\n"
748 " AND a.attnum OPERATOR(pg_catalog.>) 0::pg_catalog.int2\n"
749 " AND NOT a.attisdropped\n"
750 " AND a.attstattarget IS DISTINCT FROM 0::pg_catalog.int2\n"
751 " AND a.attgenerated OPERATOR(pg_catalog.<>) "
752 CppAsString2(ATTRIBUTE_GENERATED_VIRTUAL) "\n"
753 " AND c.relhassubclass\n"
754 " AND NOT p.inherited\n"
755 " AND EXISTS (SELECT NULL FROM pg_catalog.pg_inherits h\n"
756 " WHERE h.inhparent OPERATOR(pg_catalog.=) c.oid)\n"
757 " AND NOT EXISTS (SELECT NULL FROM pg_catalog.pg_statistic s\n"
758 " WHERE s.starelid OPERATOR(pg_catalog.=) a.attrelid\n"
759 " AND s.staattnum OPERATOR(pg_catalog.=) a.attnum\n"
760 " AND s.stainherit))\n");
761
762 /* inheritance and extended stats */
763 appendPQExpBufferStr(&catalog_query,
764 " OR EXISTS (SELECT NULL FROM pg_catalog.pg_statistic_ext e\n"
765 " WHERE e.stxrelid OPERATOR(pg_catalog.=) c.oid\n"
766 " AND e.stxstattarget IS DISTINCT FROM 0::pg_catalog.int2\n"
767 " AND c.relhassubclass\n"
768 " AND NOT p.inherited\n"
769 " AND EXISTS (SELECT NULL FROM pg_catalog.pg_inherits h\n"
770 " WHERE h.inhparent OPERATOR(pg_catalog.=) c.oid)\n"
771 " AND NOT EXISTS (SELECT NULL FROM pg_catalog.pg_statistic_ext_data d\n"
772 " WHERE d.stxoid OPERATOR(pg_catalog.=) e.oid\n"
773 " AND d.stxdinherit))\n");
774
775 appendPQExpBufferStr(&catalog_query, " )\n");
776 }
777
778 /*
779 * Execute the catalog query. We use the default search_path for this
780 * query for consistency with table lookups done elsewhere by the user.
781 */
782 appendPQExpBufferStr(&catalog_query, " ORDER BY c.relpages DESC;");
783 executeCommand(conn, "RESET search_path;", vacopts->echo);
784 res = executeQuery(conn, catalog_query.data, vacopts->echo);
785 termPQExpBuffer(&catalog_query);
787
788 /*
789 * Build qualified identifiers for each table, including the column list
790 * if given.
791 */
793 for (int i = 0; i < PQntuples(res); i++)
794 {
797 PQgetvalue(res, i, 0),
799
800 if (objects_listed && !PQgetisnull(res, i, 2))
802
803 simple_string_list_append(found_objs, buf.data);
805 }
807 PQclear(res);
808
809 return found_objs;
810}
811
812/*
813 * Free the results of retrieve_objects().
814 *
815 * For caller convenience, we allow the argument to be NULL,
816 * although retrieve_objects() will never return that.
817 */
818static void
820{
821 if (list)
822 {
824 pg_free(list);
825 }
826}
827
828/*
829 * Construct a vacuum/analyze command to run based on the given
830 * options, in the given string buffer, which may contain previous garbage.
831 *
832 * The table name used must be already properly quoted. The command generated
833 * depends on the server version involved and it is semicolon-terminated.
834 */
835static void
837 vacuumingOptions *vacopts, const char *table)
838{
839 int serverVersion = PQserverVersion(conn);
840 const char *paren = " (";
841 const char *comma = ", ";
842 const char *sep = paren;
843
844 resetPQExpBuffer(sql);
845
846 if (vacopts->mode == MODE_ANALYZE ||
847 vacopts->mode == MODE_ANALYZE_IN_STAGES)
848 {
849 appendPQExpBufferStr(sql, "ANALYZE");
850
851 /* parenthesized grammar of ANALYZE is supported since v11 */
852 if (serverVersion >= 110000)
853 {
854 if (vacopts->skip_locked)
855 {
856 /* SKIP_LOCKED is supported since v12 */
857 Assert(serverVersion >= 120000);
858 appendPQExpBuffer(sql, "%sSKIP_LOCKED", sep);
859 sep = comma;
860 }
861 if (vacopts->verbose)
862 {
863 appendPQExpBuffer(sql, "%sVERBOSE", sep);
864 sep = comma;
865 }
866 if (vacopts->buffer_usage_limit)
867 {
868 Assert(serverVersion >= 160000);
869 appendPQExpBuffer(sql, "%sBUFFER_USAGE_LIMIT '%s'", sep,
870 vacopts->buffer_usage_limit);
871 sep = comma;
872 }
873 if (sep != paren)
874 appendPQExpBufferChar(sql, ')');
875 }
876 else
877 {
878 if (vacopts->verbose)
879 appendPQExpBufferStr(sql, " VERBOSE");
880 }
881 }
882 else
883 {
884 appendPQExpBufferStr(sql, "VACUUM");
885
886 /* parenthesized grammar of VACUUM is supported since v9.0 */
887 if (serverVersion >= 90000)
888 {
889 if (vacopts->disable_page_skipping)
890 {
891 /* DISABLE_PAGE_SKIPPING is supported since v9.6 */
892 Assert(serverVersion >= 90600);
893 appendPQExpBuffer(sql, "%sDISABLE_PAGE_SKIPPING", sep);
894 sep = comma;
895 }
896 if (vacopts->no_index_cleanup)
897 {
898 /* "INDEX_CLEANUP FALSE" has been supported since v12 */
899 Assert(serverVersion >= 120000);
900 Assert(!vacopts->force_index_cleanup);
901 appendPQExpBuffer(sql, "%sINDEX_CLEANUP FALSE", sep);
902 sep = comma;
903 }
904 if (vacopts->force_index_cleanup)
905 {
906 /* "INDEX_CLEANUP TRUE" has been supported since v12 */
907 Assert(serverVersion >= 120000);
908 Assert(!vacopts->no_index_cleanup);
909 appendPQExpBuffer(sql, "%sINDEX_CLEANUP TRUE", sep);
910 sep = comma;
911 }
912 if (!vacopts->do_truncate)
913 {
914 /* TRUNCATE is supported since v12 */
915 Assert(serverVersion >= 120000);
916 appendPQExpBuffer(sql, "%sTRUNCATE FALSE", sep);
917 sep = comma;
918 }
919 if (!vacopts->process_main)
920 {
921 /* PROCESS_MAIN is supported since v16 */
922 Assert(serverVersion >= 160000);
923 appendPQExpBuffer(sql, "%sPROCESS_MAIN FALSE", sep);
924 sep = comma;
925 }
926 if (!vacopts->process_toast)
927 {
928 /* PROCESS_TOAST is supported since v14 */
929 Assert(serverVersion >= 140000);
930 appendPQExpBuffer(sql, "%sPROCESS_TOAST FALSE", sep);
931 sep = comma;
932 }
933 if (vacopts->skip_database_stats)
934 {
935 /* SKIP_DATABASE_STATS is supported since v16 */
936 Assert(serverVersion >= 160000);
937 appendPQExpBuffer(sql, "%sSKIP_DATABASE_STATS", sep);
938 sep = comma;
939 }
940 if (vacopts->skip_locked)
941 {
942 /* SKIP_LOCKED is supported since v12 */
943 Assert(serverVersion >= 120000);
944 appendPQExpBuffer(sql, "%sSKIP_LOCKED", sep);
945 sep = comma;
946 }
947 if (vacopts->full)
948 {
949 appendPQExpBuffer(sql, "%sFULL", sep);
950 sep = comma;
951 }
952 if (vacopts->freeze)
953 {
954 appendPQExpBuffer(sql, "%sFREEZE", sep);
955 sep = comma;
956 }
957 if (vacopts->verbose)
958 {
959 appendPQExpBuffer(sql, "%sVERBOSE", sep);
960 sep = comma;
961 }
962 if (vacopts->and_analyze)
963 {
964 appendPQExpBuffer(sql, "%sANALYZE", sep);
965 sep = comma;
966 }
967 if (vacopts->parallel_workers >= 0)
968 {
969 /* PARALLEL is supported since v13 */
970 Assert(serverVersion >= 130000);
971 appendPQExpBuffer(sql, "%sPARALLEL %d", sep,
972 vacopts->parallel_workers);
973 sep = comma;
974 }
975 if (vacopts->buffer_usage_limit)
976 {
977 Assert(serverVersion >= 160000);
978 appendPQExpBuffer(sql, "%sBUFFER_USAGE_LIMIT '%s'", sep,
979 vacopts->buffer_usage_limit);
980 sep = comma;
981 }
982 if (sep != paren)
983 appendPQExpBufferChar(sql, ')');
984 }
985 else
986 {
987 if (vacopts->full)
988 appendPQExpBufferStr(sql, " FULL");
989 if (vacopts->freeze)
990 appendPQExpBufferStr(sql, " FREEZE");
991 if (vacopts->verbose)
992 appendPQExpBufferStr(sql, " VERBOSE");
993 if (vacopts->and_analyze)
994 appendPQExpBufferStr(sql, " ANALYZE");
995 }
996 }
997
998 appendPQExpBuffer(sql, " %s;", table);
999}
1000
1001/*
1002 * Send a vacuum/analyze command to the server, returning after sending the
1003 * command. If dry_run is true, the command is printed but not sent to the
1004 * server.
1005 *
1006 * Any errors during command execution are reported to stderr.
1007 */
1008static void
1010 const char *sql, const char *table)
1011{
1012 bool status = true;
1013 PGconn *conn = free_slot->connection;
1014
1015 if (vacopts->echo || vacopts->dry_run)
1016 printf("%s\n", sql);
1017
1018 if (vacopts->dry_run)
1019 ParallelSlotSetIdle(free_slot);
1020 else
1021 status = PQsendQuery(conn, sql) == 1;
1022
1023 if (!status)
1024 {
1025 if (table)
1026 {
1027 pg_log_error("vacuuming of table \"%s\" in database \"%s\" failed: %s",
1029 }
1030 else
1031 {
1032 pg_log_error("vacuuming of database \"%s\" failed: %s",
1034 }
1035 }
1036}
1037
1038/*
1039 * Returns a newly malloc'd version of 'src' with escaped single quotes and
1040 * backslashes.
1041 */
1042char *
1043escape_quotes(const char *src)
1044{
1045 char *result = escape_single_quotes_ascii(src);
1046
1047 if (!result)
1048 pg_fatal("out of memory");
1049 return result;
1050}
void splitTableColumnsSpec(const char *spec, int encoding, char **table, const char **columns)
Definition: common.c:33
#define gettext_noop(x)
Definition: c.h:1194
#define CppAsString2(x)
Definition: c.h:434
volatile sig_atomic_t CancelRequested
Definition: cancel.c:59
void setup_cancel_handler(void(*query_cancel_callback)(void))
Definition: cancel.c:183
#define ALWAYS_SECURE_SEARCH_PATH_SQL
Definition: connect.h:25
PGconn * connectMaintenanceDatabase(ConnParams *cparams, const char *progname, bool echo)
PGconn * connectDatabase(const ConnParams *cparams, const char *progname, bool echo, bool fail_ok, bool allow_password_reuse)
Definition: connect_utils.c:32
PGresult * executeQuery(PGconn *conn, const char *query)
Definition: connectdb.c:278
#define _(x)
Definition: elog.c:91
int PQserverVersion(const PGconn *conn)
Definition: fe-connect.c:7694
char * PQdb(const PGconn *conn)
Definition: fe-connect.c:7538
int PQclientEncoding(const PGconn *conn)
Definition: fe-connect.c:7794
void PQfinish(PGconn *conn)
Definition: fe-connect.c:5316
char * PQerrorMessage(const PGconn *conn)
Definition: fe-connect.c:7704
int PQsendQuery(PGconn *conn, const char *query)
Definition: fe-exec.c:1433
void pg_free(void *ptr)
Definition: fe_memutils.c:105
#define palloc0_object(type)
Definition: fe_memutils.h:75
Assert(PointerIsAligned(start, uint64))
#define comma
Definition: indent_codes.h:48
int i
Definition: isn.c:77
#define PQgetvalue
Definition: libpq-be-fe.h:253
#define PQclear
Definition: libpq-be-fe.h:245
#define PQgetisnull
Definition: libpq-be-fe.h:255
#define PQntuples
Definition: libpq-be-fe.h:251
#define pg_log_error(...)
Definition: logging.h:106
const char * progname
Definition: main.c:44
void * palloc0(Size size)
Definition: mcxt.c:1395
ParallelSlotArray * ParallelSlotsSetup(int numslots, ConnParams *cparams, const char *progname, bool echo, const char *initcmd)
bool ParallelSlotsWaitCompletion(ParallelSlotArray *sa)
bool TableCommandResultHandler(PGresult *res, PGconn *conn, void *context)
ParallelSlot * ParallelSlotsGetIdle(ParallelSlotArray *sa, const char *dbname)
void ParallelSlotsTerminate(ParallelSlotArray *sa)
void ParallelSlotsAdoptConn(ParallelSlotArray *sa, PGconn *conn)
static void ParallelSlotSetHandler(ParallelSlot *slot, ParallelSlotResultHandler handler, void *context)
Definition: parallel_slot.h:47
static void ParallelSlotSetIdle(ParallelSlot *slot)
Definition: parallel_slot.h:62
#define pg_fatal(...)
static void executeCommand(PGconn *conn, const char *query)
Definition: pg_dumpall.c:1786
static const struct lconv_member_info table[]
static char buf[DEFAULT_XLOG_SEG_SIZE]
Definition: pg_test_fsync.c:71
char * escape_single_quotes_ascii(const char *src)
Definition: quotes.c:33
#define printf(...)
Definition: port.h:266
void initPQExpBuffer(PQExpBuffer str)
Definition: pqexpbuffer.c:90
void resetPQExpBuffer(PQExpBuffer str)
Definition: pqexpbuffer.c:146
void appendPQExpBuffer(PQExpBuffer str, const char *fmt,...)
Definition: pqexpbuffer.c:265
void appendPQExpBufferChar(PQExpBuffer str, char ch)
Definition: pqexpbuffer.c:378
void appendPQExpBufferStr(PQExpBuffer str, const char *data)
Definition: pqexpbuffer.c:367
void termPQExpBuffer(PQExpBuffer str)
Definition: pqexpbuffer.c:129
#define EXIT_SUCCESS
Definition: settings.h:193
#define EXIT_FAILURE
Definition: settings.h:197
void simple_string_list_append(SimpleStringList *list, const char *val)
Definition: simple_list.c:63
void simple_string_list_destroy(SimpleStringList *list)
Definition: simple_list.c:125
char * dbname
Definition: streamutil.c:49
PGconn * conn
Definition: streamutil.c:52
const char * fmtQualifiedIdEnc(const char *schema, const char *id, int encoding)
Definition: string_utils.c:263
void appendStringLiteralConn(PQExpBuffer buf, const char *str, PGconn *conn)
Definition: string_utils.c:446
PGconn * connection
Definition: parallel_slot.h:23
char val[FLEXIBLE_ARRAY_MEMBER]
Definition: simple_list.h:37
struct SimpleStringListCell * next
Definition: simple_list.h:34
SimpleStringListCell * head
Definition: simple_list.h:42
char * override_dbname
Definition: pg_backup.h:93
char * dbname
Definition: pg_backup.h:86
bool force_index_cleanup
Definition: vacuuming.h:47
RunMode mode
Definition: vacuuming.h:34
bool no_index_cleanup
Definition: vacuuming.h:46
bits32 objfilter
Definition: vacuuming.h:35
int parallel_workers
Definition: vacuuming.h:44
bool disable_page_skipping
Definition: vacuuming.h:40
bool process_toast
Definition: vacuuming.h:50
char * buffer_usage_limit
Definition: vacuuming.h:52
bool missing_stats_only
Definition: vacuuming.h:53
bool skip_database_stats
Definition: vacuuming.h:51
const char * get_user_name_or_exit(const char *progname)
Definition: username.c:74
static void prepare_vacuum_command(PGconn *conn, PQExpBuffer sql, vacuumingOptions *vacopts, const char *table)
Definition: vacuuming.c:836
static int vacuum_all_databases(ConnParams *cparams, vacuumingOptions *vacopts, SimpleStringList *objects, int concurrentCons, const char *progname)
Definition: vacuuming.c:439
static int vacuum_one_database(ConnParams *cparams, vacuumingOptions *vacopts, int stage, SimpleStringList *objects, SimpleStringList **found_objs, int concurrentCons, const char *progname)
Definition: vacuuming.c:164
static void free_retrieved_objects(SimpleStringList *list)
Definition: vacuuming.c:819
static SimpleStringList * retrieve_objects(PGconn *conn, vacuumingOptions *vacopts, SimpleStringList *objects)
Definition: vacuuming.c:530
int vacuuming_main(ConnParams *cparams, const char *dbname, const char *maintenance_db, vacuumingOptions *vacopts, SimpleStringList *objects, unsigned int tbl_count, int concurrentCons, const char *progname)
Definition: vacuuming.c:55
char * escape_quotes(const char *src)
Definition: vacuuming.c:1043
static void run_vacuum_command(ParallelSlot *free_slot, vacuumingOptions *vacopts, const char *sql, const char *table)
Definition: vacuuming.c:1009
#define OBJFILTER_ALL_DBS
Definition: vacuuming.h:60
@ MODE_ANALYZE
Definition: vacuuming.h:23
@ MODE_VACUUM
Definition: vacuuming.h:22
@ MODE_ANALYZE_IN_STAGES
Definition: vacuuming.h:24
#define ANALYZE_NUM_STAGES
Definition: vacuuming.h:29
#define OBJFILTER_SCHEMA_EXCLUDE
Definition: vacuuming.h:64
#define OBJFILTER_TABLE
Definition: vacuuming.h:62
#define ANALYZE_NO_STAGE
Definition: vacuuming.h:28
#define OBJFILTER_SCHEMA
Definition: vacuuming.h:63