1/*-------------------------------------------------------------------------
3 * Helper routines for vacuumdb
5 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
6 * Portions Copyright (c) 1994, Regents of the University of California
8 * src/bin/scripts/vacuuming.c
10 *-------------------------------------------------------------------------
15#include "catalog/pg_attribute_d.h"
16#include "catalog/pg_class_d.h"
33 const char *
progname,
bool echo,
bool quiet);
38 const char *
progname,
bool echo,
bool quiet);
49 * Executes vacuum/analyze as indicated. Returns 0 if the plan is carried
50 * to completion, or -1 in case of certain errors (which should hopefully
51 * been already reported.) Other errors are reported via pg_fatal().
57 unsigned int tbl_count,
int concurrentCons,
58 const char *
progname,
bool echo,
bool quiet)
62 /* Avoid opening extra connections. */
63 if (tbl_count > 0 && (concurrentCons > tbl_count))
64 concurrentCons = tbl_count;
68 cparams->
dbname = maintenance_db;
79 if (getenv(
"PGDATABASE"))
80 dbname = getenv(
"PGDATABASE");
81 else if (getenv(
"PGUSER"))
119 * vacuum_one_database
121 * Process tables in the given database.
123 * There are two ways to specify the list of objects to process:
125 * 1) The "found_objs" parameter is a double pointer to a fully qualified list
126 * of objects to process, as returned by a previous call to
127 * vacuum_one_database().
129 * a) If both "found_objs" (the double pointer) and "*found_objs" (the
130 * once-dereferenced double pointer) are not NULL, this list takes
131 * priority, and anything specified in "objects" is ignored.
133 * b) If "found_objs" (the double pointer) is not NULL but "*found_objs"
134 * (the once-dereferenced double pointer) _is_ NULL, the "objects"
135 * parameter takes priority, and the results of the catalog query
136 * described in (2) are stored in "found_objs".
138 * c) If "found_objs" (the double pointer) is NULL, the "objects"
139 * parameter again takes priority, and the results of the catalog query
142 * 2) The "objects" parameter is a user-specified list of objects to process.
143 * When (1b) or (1c) applies, this function performs a catalog query to
144 * retrieve a fully qualified list of objects to process, as described
147 * a) If "objects" is not NULL, the catalog query gathers only the objects
148 * listed in "objects".
150 * b) If "objects" is NULL, all tables in the database are gathered.
152 * Note that this function is only concerned with running exactly one stage
153 * when in analyze-in-stages mode; caller must iterate on us if necessary.
155 * If concurrentCons is > 1, multiple connections are used to vacuum tables
165 const char *
progname,
bool echo,
bool quiet)
175 const char *stage_commands[] = {
176 "SET default_statistics_target=1; SET vacuum_cost_delay=0;",
177 "SET default_statistics_target=10; RESET vacuum_cost_delay;",
178 "RESET default_statistics_target;"
180 const char *stage_messages[] = {
181 gettext_noop(
"Generating minimal optimizer statistics (1 target)"),
182 gettext_noop(
"Generating medium optimizer statistics (10 targets)"),
183 gettext_noop(
"Generating default (full) optimizer statistics")
194 pg_fatal(
"cannot use the \"%s\" option on server versions older than PostgreSQL %s",
195 "disable-page-skipping",
"9.6");
201 pg_fatal(
"cannot use the \"%s\" option on server versions older than PostgreSQL %s",
202 "no-index-cleanup",
"12");
208 pg_fatal(
"cannot use the \"%s\" option on server versions older than PostgreSQL %s",
209 "force-index-cleanup",
"12");
215 pg_fatal(
"cannot use the \"%s\" option on server versions older than PostgreSQL %s",
216 "no-truncate",
"12");
222 pg_fatal(
"cannot use the \"%s\" option on server versions older than PostgreSQL %s",
223 "no-process-main",
"16");
229 pg_fatal(
"cannot use the \"%s\" option on server versions older than PostgreSQL %s",
230 "no-process-toast",
"14");
236 pg_fatal(
"cannot use the \"%s\" option on server versions older than PostgreSQL %s",
237 "skip-locked",
"12");
243 pg_fatal(
"cannot use the \"%s\" option on server versions older than PostgreSQL %s",
244 "--min-xid-age",
"9.6");
250 pg_fatal(
"cannot use the \"%s\" option on server versions older than PostgreSQL %s",
251 "--min-mxid-age",
"9.6");
257 pg_fatal(
"cannot use the \"%s\" option on server versions older than PostgreSQL %s",
264 pg_fatal(
"cannot use the \"%s\" option on server versions older than PostgreSQL %s",
265 "--buffer-usage-limit",
"16");
271 pg_fatal(
"cannot use the \"%s\" option on server versions older than PostgreSQL %s",
272 "--missing-stats-only",
"15");
275 /* skip_database_stats is used automatically if server supports it */
281 printf(
_(
"%s: processing database \"%s\": %s\n"),
284 printf(
_(
"%s: vacuuming database \"%s\"\n"),
290 * If the caller provided the results of a previous catalog query, just
291 * use that. Otherwise, run the catalog query ourselves and set the
292 * return variable if provided.
294 if (found_objs && *found_objs)
295 retobjs = *found_objs;
300 *found_objs = retobjs;
304 * Count the number of objects in the catalog query result. If there are
307 for (cell = retobjs ? retobjs->
head : NULL; cell; cell = cell->
next)
317 * Ensure concurrentCons is sane. If there are more connections than
318 * vacuumable relations, we don't need to use them all.
320 if (concurrentCons > ntups)
321 concurrentCons = ntups;
322 if (concurrentCons <= 0)
326 * All slots need to be prepared to run the appropriate analyze stage, if
327 * caller requested that mode. We have to prepare the initial connection
328 * ourselves before setting up the slots.
332 initcmd = stage_commands[stage];
339 * Setup the database connections. We reuse the connection we already have
340 * for the first slot. If not in parallel mode, the first slot in the
341 * array contains the connection.
348 cell = retobjs->
head;
351 const char *tabname = cell->
val;
371 * Execute the vacuum. All errors are handled in processQueryResult
372 * through ParallelSlotsGetIdle.
379 }
while (cell != NULL);
387 /* If we used SKIP_DATABASE_STATS, mop up with ONLY_DATABASE_STATS */
390 const char *cmd =
"VACUUM (ONLY_DATABASE_STATS);";
403 ret =
EXIT_FAILURE;
/* error already reported by handler */
415 * Vacuum/analyze all connectable databases.
417 * In analyze-in-stages mode, we process all databases in one stage before
418 * moving on to the next stage. That ensure minimal stats are available
419 * quickly everywhere before generating more detailed ones.
426 const char *
progname,
bool echo,
bool quiet)
433 "SELECT datname FROM pg_database WHERE datallowconn AND datconnlimit <> -2 ORDER BY 1;",
445 * When analyzing all databases in stages, we analyze them all in the
446 * fastest stage first, so that initial statistics become available
447 * for all of them as soon as possible.
449 * This means we establish several times as many connections, but
450 * that's a secondary consideration.
493 * Prepare the list of tables to process by querying the catalogs.
495 * Since we execute the constructed query with the default search_path (which
496 * could be unsafe), everything in this query MUST be fully qualified.
498 * First, build a WITH clause for the catalog query if any tables were
499 * specified, with a set of values made of relation names and their optional
500 * set of columns. This is used to match any provided column lists with the
501 * generated qualified identifiers and to filter for the tables provided via
502 * --table. If a listed table does not exist, the catalog query will fail.
513 bool objects_listed =
false;
516 for (cell = objects ? objects->
head : NULL; cell; cell = cell->
next)
518 char *just_table = NULL;
519 const char *just_columns = NULL;
524 "WITH listed_objects (object_oid, column_list) AS (\n"
526 objects_listed =
true;
540 * Split relation and column names given by the user, this is used
541 * to feed the CTE with values on which are performed pre-run
542 * validity checks as well. For now these happen only on the
546 &just_table, &just_columns);
552 if (just_columns && just_columns[0] !=
'0円')
562 /* Finish formatting the CTE */
572 " FROM pg_catalog.pg_class c\n"
573 " JOIN pg_catalog.pg_namespace ns"
574 " ON c.relnamespace OPERATOR(pg_catalog.=) ns.oid\n"
575 " CROSS JOIN LATERAL (SELECT c.relkind IN ("
577 CppAsString2(RELKIND_PARTITIONED_INDEX)
")) as p (inherited)\n"
578 " LEFT JOIN pg_catalog.pg_class t"
579 " ON c.reltoastrelid OPERATOR(pg_catalog.=) t.oid\n");
582 * Used to match the tables or schemas listed by the user, completing the
588 " ON listed_objects.object_oid"
589 " OPERATOR(pg_catalog.=) ");
598 * Exclude temporary tables, beginning the WHERE clause.
601 " WHERE c.relpersistence OPERATOR(pg_catalog.!=) "
605 * Used to match the tables or schemas listed by the user, for the WHERE
612 " AND listed_objects.object_oid IS NULL\n");
615 " AND listed_objects.object_oid IS NOT NULL\n");
619 * If no tables were listed, filter for the relevant relation types. If
620 * tables were given via --table, don't bother filtering by relation type.
621 * Instead, let the server decide whether a given relation can be
622 * processed in which case the user will know about it.
627 * vacuumdb should generally follow the behavior of the underlying
628 * VACUUM and ANALYZE commands. In MODE_ANALYZE mode, process regular
629 * tables, materialized views, and partitioned tables, just like
630 * ANALYZE (with no specific target tables) does. Otherwise, process
631 * only regular tables and materialized views, since VACUUM skips
632 * partitioned tables when no target tables are specified.
636 " AND c.relkind OPERATOR(pg_catalog.=) ANY (array["
642 " AND c.relkind OPERATOR(pg_catalog.=) ANY (array["
648 * For --min-xid-age and --min-mxid-age, the age of the relation is the
649 * greatest of the ages of the main relation and its associated TOAST
650 * table. The commands generated by vacuumdb will also process the TOAST
651 * table for the relation if necessary, so it does not need to be
652 * considered separately.
657 " AND GREATEST(pg_catalog.age(c.relfrozenxid),"
658 " pg_catalog.age(t.relfrozenxid)) "
659 " OPERATOR(pg_catalog.>=) '%d'::pg_catalog.int4\n"
660 " AND c.relfrozenxid OPERATOR(pg_catalog.!=)"
661 " '0'::pg_catalog.xid\n",
668 " AND GREATEST(pg_catalog.mxid_age(c.relminmxid),"
669 " pg_catalog.mxid_age(t.relminmxid)) OPERATOR(pg_catalog.>=)"
670 " '%d'::pg_catalog.int4\n"
671 " AND c.relminmxid OPERATOR(pg_catalog.!=)"
672 " '0'::pg_catalog.xid\n",
682 " EXISTS (SELECT NULL FROM pg_catalog.pg_attribute a\n"
683 " WHERE a.attrelid OPERATOR(pg_catalog.=) c.oid\n"
684 " AND a.attnum OPERATOR(pg_catalog.>) 0::pg_catalog.int2\n"
685 " AND NOT a.attisdropped\n"
686 " AND a.attstattarget IS DISTINCT FROM 0::pg_catalog.int2\n"
687 " AND a.attgenerated OPERATOR(pg_catalog.<>) "
689 " AND NOT EXISTS (SELECT NULL FROM pg_catalog.pg_statistic s\n"
690 " WHERE s.starelid OPERATOR(pg_catalog.=) a.attrelid\n"
691 " AND s.staattnum OPERATOR(pg_catalog.=) a.attnum\n"
692 " AND s.stainherit OPERATOR(pg_catalog.=) p.inherited))\n");
696 " OR EXISTS (SELECT NULL FROM pg_catalog.pg_statistic_ext e\n"
697 " WHERE e.stxrelid OPERATOR(pg_catalog.=) c.oid\n"
698 " AND e.stxstattarget IS DISTINCT FROM 0::pg_catalog.int2\n"
699 " AND NOT EXISTS (SELECT NULL FROM pg_catalog.pg_statistic_ext_data d\n"
700 " WHERE d.stxoid OPERATOR(pg_catalog.=) e.oid\n"
701 " AND d.stxdinherit OPERATOR(pg_catalog.=) p.inherited))\n");
703 /* expression indexes */
705 " OR EXISTS (SELECT NULL FROM pg_catalog.pg_attribute a\n"
706 " JOIN pg_catalog.pg_index i"
707 " ON i.indexrelid OPERATOR(pg_catalog.=) a.attrelid\n"
708 " WHERE i.indrelid OPERATOR(pg_catalog.=) c.oid\n"
709 " AND i.indkey[a.attnum OPERATOR(pg_catalog.-) 1::pg_catalog.int2]"
710 " OPERATOR(pg_catalog.=) 0::pg_catalog.int2\n"
711 " AND a.attnum OPERATOR(pg_catalog.>) 0::pg_catalog.int2\n"
712 " AND NOT a.attisdropped\n"
713 " AND a.attstattarget IS DISTINCT FROM 0::pg_catalog.int2\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");
719 /* inheritance and regular stats */
721 " OR EXISTS (SELECT NULL FROM pg_catalog.pg_attribute a\n"
722 " WHERE a.attrelid OPERATOR(pg_catalog.=) c.oid\n"
723 " AND a.attnum OPERATOR(pg_catalog.>) 0::pg_catalog.int2\n"
724 " AND NOT a.attisdropped\n"
725 " AND a.attstattarget IS DISTINCT FROM 0::pg_catalog.int2\n"
726 " AND a.attgenerated OPERATOR(pg_catalog.<>) "
728 " AND c.relhassubclass\n"
729 " AND NOT p.inherited\n"
730 " AND EXISTS (SELECT NULL FROM pg_catalog.pg_inherits h\n"
731 " WHERE h.inhparent OPERATOR(pg_catalog.=) c.oid)\n"
732 " AND NOT EXISTS (SELECT NULL FROM pg_catalog.pg_statistic s\n"
733 " WHERE s.starelid OPERATOR(pg_catalog.=) a.attrelid\n"
734 " AND s.staattnum OPERATOR(pg_catalog.=) a.attnum\n"
735 " AND s.stainherit))\n");
737 /* inheritance and extended stats */
739 " OR EXISTS (SELECT NULL FROM pg_catalog.pg_statistic_ext e\n"
740 " WHERE e.stxrelid OPERATOR(pg_catalog.=) c.oid\n"
741 " AND e.stxstattarget IS DISTINCT FROM 0::pg_catalog.int2\n"
742 " AND c.relhassubclass\n"
743 " AND NOT p.inherited\n"
744 " AND EXISTS (SELECT NULL FROM pg_catalog.pg_inherits h\n"
745 " WHERE h.inhparent OPERATOR(pg_catalog.=) c.oid)\n"
746 " AND NOT EXISTS (SELECT NULL FROM pg_catalog.pg_statistic_ext_data d\n"
747 " WHERE d.stxoid OPERATOR(pg_catalog.=) e.oid\n"
748 " AND d.stxdinherit))\n");
754 * Execute the catalog query. We use the default search_path for this
755 * query for consistency with table lookups done elsewhere by the user.
764 * Build qualified identifiers for each table, including the column list
788 * Construct a vacuum/analyze command to run based on the given
789 * options, in the given string buffer, which may contain previous garbage.
791 * The table name used must be already properly quoted. The command generated
792 * depends on the server version involved and it is semicolon-terminated.
799 const char *paren =
" (";
800 const char *
comma =
", ";
801 const char *sep = paren;
810 /* parenthesized grammar of ANALYZE is supported since v11 */
811 if (serverVersion >= 110000)
815 /* SKIP_LOCKED is supported since v12 */
816 Assert(serverVersion >= 120000);
827 Assert(serverVersion >= 160000);
845 /* parenthesized grammar of VACUUM is supported since v9.0 */
846 if (serverVersion >= 90000)
850 /* DISABLE_PAGE_SKIPPING is supported since v9.6 */
851 Assert(serverVersion >= 90600);
857 /* "INDEX_CLEANUP FALSE" has been supported since v12 */
858 Assert(serverVersion >= 120000);
865 /* "INDEX_CLEANUP TRUE" has been supported since v12 */
866 Assert(serverVersion >= 120000);
873 /* TRUNCATE is supported since v12 */
874 Assert(serverVersion >= 120000);
880 /* PROCESS_MAIN is supported since v16 */
881 Assert(serverVersion >= 160000);
887 /* PROCESS_TOAST is supported since v14 */
888 Assert(serverVersion >= 140000);
894 /* SKIP_DATABASE_STATS is supported since v16 */
895 Assert(serverVersion >= 160000);
901 /* SKIP_LOCKED is supported since v12 */
902 Assert(serverVersion >= 120000);
928 /* PARALLEL is supported since v13 */
929 Assert(serverVersion >= 130000);
936 Assert(serverVersion >= 160000);
961 * Send a vacuum/analyze command to the server, returning after sending the
964 * Any errors during command execution are reported to stderr.
981 pg_log_error(
"vacuuming of table \"%s\" in database \"%s\" failed: %s",
993 * Returns a newly malloc'd version of 'src' with escaped single quotes and
void splitTableColumnsSpec(const char *spec, int encoding, char **table, const char **columns)
volatile sig_atomic_t CancelRequested
void setup_cancel_handler(void(*query_cancel_callback)(void))
#define ALWAYS_SECURE_SEARCH_PATH_SQL
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)
PGresult * executeQuery(PGconn *conn, const char *query)
int PQserverVersion(const PGconn *conn)
char * PQdb(const PGconn *conn)
int PQclientEncoding(const PGconn *conn)
void PQfinish(PGconn *conn)
char * PQerrorMessage(const PGconn *conn)
int PQsendQuery(PGconn *conn, const char *query)
Assert(PointerIsAligned(start, uint64))
#define pg_log_error(...)
void * palloc0(Size size)
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)
static void executeCommand(PGconn *conn, const char *query)
static const struct lconv_member_info table[]
char * escape_single_quotes_ascii(const char *src)
void initPQExpBuffer(PQExpBuffer str)
void resetPQExpBuffer(PQExpBuffer str)
void appendPQExpBuffer(PQExpBuffer str, const char *fmt,...)
void appendPQExpBufferChar(PQExpBuffer str, char ch)
void appendPQExpBufferStr(PQExpBuffer str, const char *data)
void termPQExpBuffer(PQExpBuffer str)
void simple_string_list_append(SimpleStringList *list, const char *val)
const char * fmtQualifiedIdEnc(const char *schema, const char *id, int encoding)
void appendStringLiteralConn(PQExpBuffer buf, const char *str, PGconn *conn)
char val[FLEXIBLE_ARRAY_MEMBER]
struct SimpleStringListCell * next
SimpleStringListCell * head
bool disable_page_skipping
char * buffer_usage_limit
const char * get_user_name_or_exit(const char *progname)
static SimpleStringList * retrieve_objects(PGconn *conn, vacuumingOptions *vacopts, SimpleStringList *objects, bool echo)
static void prepare_vacuum_command(PGconn *conn, PQExpBuffer sql, vacuumingOptions *vacopts, const char *table)
static void run_vacuum_command(PGconn *conn, const char *sql, bool echo, const char *table)
static int vacuum_all_databases(ConnParams *cparams, vacuumingOptions *vacopts, SimpleStringList *objects, int concurrentCons, const char *progname, bool echo, bool quiet)
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, bool echo, bool quiet)
char * escape_quotes(const char *src)
static int vacuum_one_database(ConnParams *cparams, vacuumingOptions *vacopts, int stage, SimpleStringList *objects, SimpleStringList **found_objs, int concurrentCons, const char *progname, bool echo, bool quiet)
#define OBJFILTER_ALL_DBS
#define ANALYZE_NUM_STAGES
#define OBJFILTER_SCHEMA_EXCLUDE