diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml index 421c18d..48c77a9 100644 --- a/doc/src/sgml/ref/vacuum.sgml +++ b/doc/src/sgml/ref/vacuum.sgml @@ -21,9 +21,9 @@ PostgreSQL documentation -VACUUM [ ( { FULL | FREEZE | VERBOSE | ANALYZE | DISABLE_PAGE_SKIPPING } [, ...] ) ] [ table_name [ (column_name [, ...] ) ] ] -VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ table_name ] -VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ table_name [ (column_name [, ...] ) ] ] +VACUUM [ ( { FULL | FREEZE | VERBOSE | ANALYZE | DISABLE_PAGE_SKIPPING } [, ...] ) ] [ table_name [, ...] [ (column_name [, ...] ) ] ] +VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ table_name [, ...] ] +VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ table_name [, ...] [ (column_name [, ...] ) ] ] @@ -40,9 +40,10 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ - With no parameter, VACUUM processes every table in the + With no parameters, VACUUM processes every table in the current database that the current user has permission to vacuum. - With a parameter, VACUUM processes only that table. + With parameters, VACUUM processes only the tables + specified. @@ -152,9 +153,9 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ table_name - The name (optionally schema-qualified) of a specific table to + The names (optionally schema-qualified) of the specific tables to vacuum. If omitted, all regular tables and materialized views in the - current database are vacuumed. If the specified table is a partitioned + current database are vacuumed. If a specified table is a partitioned table, all of its leaf partitions are vacuumed. @@ -165,7 +166,8 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ The name of a specific column to analyze. Defaults to all columns. - If a column list is specified, ANALYZE is implied. + If a column list is specified, only one table may be specified, and + ANALYZE is implied. diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 9fbb0eb..ea9a203 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -67,7 +67,7 @@ static BufferAccessStrategy vac_strategy; /* non-export function prototypes */ -static List *get_rel_oids(Oid relid, const RangeVar *vacrel); +static List *get_rel_oids(Oid relid, const List *vacrels); static void vac_truncate_clog(TransactionId frozenXID, MultiXactId minMulti, TransactionId lastSaneFrozenXid, @@ -93,6 +93,12 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel) Assert((vacstmt->options & VACOPT_ANALYZE) || vacstmt->va_cols == NIL); Assert(!(vacstmt->options & VACOPT_SKIPTOAST)); + /* check that the tables and columns in the statement make sense */ + if (list_length(vacstmt->va_rels) != 1 && vacstmt->va_cols != NIL) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only one table may be specified when columns are specified"))); + /* * All freeze ages are zero if the FREEZE option is given; otherwise pass * them as -1 which means to use the default values. @@ -119,7 +125,7 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel) params.log_min_duration = -1; /* Now go through the common routine */ - vacuum(vacstmt->options, vacstmt->relation, InvalidOid, ¶ms, + vacuum(vacstmt->options, vacstmt->va_rels, InvalidOid, ¶ms, vacstmt->va_cols, NULL, isTopLevel); } @@ -146,7 +152,7 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel) * memory context that will not disappear at transaction commit. */ void -vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params, +vacuum(int options, List *va_rels, Oid relid, VacuumParams *params, List *va_cols, BufferAccessStrategy bstrategy, bool isTopLevel) { const char *stmttype; @@ -229,7 +235,7 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params, * Build list of relations to process, unless caller gave us one. (If we * build one, we put it in vac_context for safekeeping.) */ - relations = get_rel_oids(relid, relation); + relations = get_rel_oids(relid, va_rels); /* * Decide whether we need to start/commit our own transactions. @@ -284,6 +290,23 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params, PG_TRY(); { ListCell *cur; + RangeVar *relation = NULL; + + /* + * The "relation" argument in vacuum_rel(...) and analyze_rel(...) is + * only needed for autovacuum workers, which call vacuum(...) with + * only one relation specified. If multiple relations are specified + * (va_rels is empty or has multiple entries), then we know that we + * do not need the "relation" argument, as the current caller is not + * an autovacuum worker. + * + * This means we only need to specify a value for "relation" if we + * are vacuuming exactly one relation, which simplifies things a bit. + */ + if (list_length(va_rels) == 1) + relation = (RangeVar *) linitial(va_rels); + else + Assert(!IsAutoVacuumWorkerProcess()); in_vacuum = true; VacuumCostActive = (VacuumCostDelay > 0); @@ -379,7 +402,7 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params, * per-relation transactions. */ static List * -get_rel_oids(Oid relid, const RangeVar *vacrel) +get_rel_oids(Oid relid, const List *vacrels) { List *oid_list = NIL; MemoryContext oldcontext; @@ -391,49 +414,54 @@ get_rel_oids(Oid relid, const RangeVar *vacrel) oid_list = lappend_oid(oid_list, relid); MemoryContextSwitchTo(oldcontext); } - else if (vacrel) + else if (vacrels != NIL) { - /* Process a specific relation */ - Oid relid; - HeapTuple tuple; - Form_pg_class classForm; - bool include_parts; + /* Process specific relation(s) */ + ListCell *lc; - /* - * Since we don't take a lock here, the relation might be gone, or the - * RangeVar might no longer refer to the OID we look up here. In the - * former case, VACUUM will do nothing; in the latter case, it will - * process the OID we looked up here, rather than the new one. Neither - * is ideal, but there's little practical alternative, since we're - * going to commit this transaction and begin a new one between now - * and then. - */ - relid = RangeVarGetRelid(vacrel, NoLock, false); + foreach(lc, vacrels) + { + Oid relid; + HeapTuple tuple; + Form_pg_class classForm; + bool include_parts; - /* - * To check whether the relation is a partitioned table, fetch its - * syscache entry. - */ - tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); - if (!HeapTupleIsValid(tuple)) - elog(ERROR, "cache lookup failed for relation %u", relid); - classForm = (Form_pg_class) GETSTRUCT(tuple); - include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE); - ReleaseSysCache(tuple); + /* + * Since we don't take a lock here, the relation might be gone, or the + * RangeVar might no longer refer to the OID we look up here. In the + * former case, VACUUM will do nothing; in the latter case, it will + * process the OID we looked up here, rather than the new one. Neither + * is ideal, but there's little practical alternative, since we're + * going to commit this transaction and begin a new one between now + * and then. + */ + relid = RangeVarGetRelid((RangeVar *) lfirst(lc), NoLock, false); - /* - * Make relation list entries for this guy and its partitions, if any. - * Note that the list returned by find_all_inheritors() include the - * passed-in OID at its head. Also note that we did not request a - * lock to be taken to match what would be done otherwise. - */ - oldcontext = MemoryContextSwitchTo(vac_context); - if (include_parts) - oid_list = list_concat(oid_list, - find_all_inheritors(relid, NoLock, NULL)); - else - oid_list = lappend_oid(oid_list, relid); - MemoryContextSwitchTo(oldcontext); + /* + * To check whether the relation is a partitioned table, fetch its + * syscache entry. + */ + tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for relation %u", relid); + classForm = (Form_pg_class) GETSTRUCT(tuple); + include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE); + ReleaseSysCache(tuple); + + /* + * Make relation list entries for this guy and its partitions, if any. + * Note that the list returned by find_all_inheritors() include the + * passed-in OID at its head. Also note that we did not request a + * lock to be taken to match what would be done otherwise. + */ + oldcontext = MemoryContextSwitchTo(vac_context); + if (include_parts) + oid_list = list_concat(oid_list, + find_all_inheritors(relid, NoLock, NULL)); + else + oid_list = lappend_oid(oid_list, relid); + MemoryContextSwitchTo(oldcontext); + } } else { diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 2d2a9d0..788421a 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -3763,7 +3763,7 @@ _copyVacuumStmt(const VacuumStmt *from) VacuumStmt *newnode = makeNode(VacuumStmt); COPY_SCALAR_FIELD(options); - COPY_NODE_FIELD(relation); + COPY_NODE_FIELD(va_rels); COPY_NODE_FIELD(va_cols); return newnode; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index b5459cd..4fe2f09 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1663,7 +1663,7 @@ static bool _equalVacuumStmt(const VacuumStmt *a, const VacuumStmt *b) { COMPARE_SCALAR_FIELD(options); - COMPARE_NODE_FIELD(relation); + COMPARE_NODE_FIELD(va_rels); COMPARE_NODE_FIELD(va_cols); return true; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 65c004c..83b5bd7 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -10155,11 +10155,11 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose n->options |= VACOPT_FREEZE; if ($4) n->options |= VACOPT_VERBOSE; - n->relation = NULL; + n->va_rels = NIL; n->va_cols = NIL; - $$ = (Node *)n; + $$ = (Node *) n; } - | VACUUM opt_full opt_freeze opt_verbose qualified_name + | VACUUM opt_full opt_freeze opt_verbose qualified_name_list { VacuumStmt *n = makeNode(VacuumStmt); n->options = VACOPT_VACUUM; @@ -10169,9 +10169,9 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose n->options |= VACOPT_FREEZE; if ($4) n->options |= VACOPT_VERBOSE; - n->relation = $5; + n->va_rels = $5; n->va_cols = NIL; - $$ = (Node *)n; + $$ = (Node *) n; } | VACUUM opt_full opt_freeze opt_verbose AnalyzeStmt { @@ -10183,21 +10183,21 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose n->options |= VACOPT_FREEZE; if ($4) n->options |= VACOPT_VERBOSE; - $$ = (Node *)n; + $$ = (Node *) n; } | VACUUM '(' vacuum_option_list ')' { VacuumStmt *n = makeNode(VacuumStmt); n->options = VACOPT_VACUUM | $3; - n->relation = NULL; + n->va_rels = NIL; n->va_cols = NIL; $$ = (Node *) n; } - | VACUUM '(' vacuum_option_list ')' qualified_name opt_name_list + | VACUUM '(' vacuum_option_list ')' qualified_name_list opt_name_list { VacuumStmt *n = makeNode(VacuumStmt); n->options = VACOPT_VACUUM | $3; - n->relation = $5; + n->va_rels = $5; n->va_cols = $6; if (n->va_cols != NIL) /* implies analyze */ n->options |= VACOPT_ANALYZE; @@ -10234,19 +10234,19 @@ AnalyzeStmt: n->options = VACOPT_ANALYZE; if ($2) n->options |= VACOPT_VERBOSE; - n->relation = NULL; + n->va_rels = NIL; n->va_cols = NIL; - $$ = (Node *)n; + $$ = (Node *) n; } - | analyze_keyword opt_verbose qualified_name opt_name_list + | analyze_keyword opt_verbose qualified_name_list opt_name_list { VacuumStmt *n = makeNode(VacuumStmt); n->options = VACOPT_ANALYZE; if ($2) n->options |= VACOPT_VERBOSE; - n->relation = $3; + n->va_rels = $3; n->va_cols = $4; - $$ = (Node *)n; + $$ = (Node *) n; } ; diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index 89dd3b3..155d579 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -3125,8 +3125,8 @@ autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy) /* Let pgstat know what we're doing */ autovac_report_activity(tab); - vacuum(tab->at_vacoptions, &rangevar, tab->at_relid, &tab->at_params, NIL, - bstrategy, true); + vacuum(tab->at_vacoptions, list_make1(&rangevar), tab->at_relid, &tab->at_params, + NIL, bstrategy, true); } /* diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h index 541c2fa..52d6e44 100644 --- a/src/include/commands/vacuum.h +++ b/src/include/commands/vacuum.h @@ -158,7 +158,7 @@ extern int vacuum_multixact_freeze_table_age; /* in commands/vacuum.c */ extern void ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel); -extern void vacuum(int options, RangeVar *relation, Oid relid, +extern void vacuum(int options, List *va_rels, Oid relid, VacuumParams *params, List *va_cols, BufferAccessStrategy bstrategy, bool isTopLevel); extern void vac_open_indexes(Relation relation, LOCKMODE lockmode, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 46c23c2..f8411d2 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3066,7 +3066,7 @@ typedef struct VacuumStmt { NodeTag type; int options; /* OR of VacuumOption flags */ - RangeVar *relation; /* single table to process, or NULL */ + List *va_rels; /* list of tables to process, or NIL for all */ List *va_cols; /* list of column names, or NIL for all */ } VacuumStmt; diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out index 6f68663..855ac9f 100644 --- a/src/test/regress/expected/vacuum.out +++ b/src/test/regress/expected/vacuum.out @@ -80,8 +80,6 @@ CONTEXT: SQL function "do_analyze" statement 1 SQL function "wrap_do_analyze" statement 1 VACUUM FULL vactst; VACUUM (DISABLE_PAGE_SKIPPING) vaccluster; -DROP TABLE vaccluster; -DROP TABLE vactst; -- partitioned table CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a); CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1); @@ -90,4 +88,16 @@ UPDATE vacparted SET b = 'b'; VACUUM (ANALYZE) vacparted; VACUUM (FULL) vacparted; VACUUM (FREEZE) vacparted; +-- multiple tables specified +VACUUM FREEZE vaccluster, vactst; +VACUUM FREEZE vacparted, does_not_exist; +ERROR: relation "does_not_exist" does not exist +VACUUM (FREEZE) vacparted, vaccluster, vactst; +VACUUM (FREEZE) vaccluster, does_not_exist; +ERROR: relation "does_not_exist" does not exist +VACUUM (FREEZE) vactst (i); +VACUUM (FREEZE) vactst, vacparted (i); +ERROR: only one table may be specified when columns are specified +DROP TABLE vaccluster; +DROP TABLE vactst; DROP TABLE vacparted; diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql index 7c5fb04..e1bd0b7 100644 --- a/src/test/regress/sql/vacuum.sql +++ b/src/test/regress/sql/vacuum.sql @@ -62,9 +62,6 @@ VACUUM FULL vactst; VACUUM (DISABLE_PAGE_SKIPPING) vaccluster; -DROP TABLE vaccluster; -DROP TABLE vactst; - -- partitioned table CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a); CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1); @@ -73,4 +70,15 @@ UPDATE vacparted SET b = 'b'; VACUUM (ANALYZE) vacparted; VACUUM (FULL) vacparted; VACUUM (FREEZE) vacparted; + +-- multiple tables specified +VACUUM FREEZE vaccluster, vactst; +VACUUM FREEZE vacparted, does_not_exist; +VACUUM (FREEZE) vacparted, vaccluster, vactst; +VACUUM (FREEZE) vaccluster, does_not_exist; +VACUUM (FREEZE) vactst (i); +VACUUM (FREEZE) vactst, vacparted (i); + +DROP TABLE vaccluster; +DROP TABLE vactst; DROP TABLE vacparted;