diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 5736f12b8f..d7e79794cd 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -30,11 +30,13 @@ #include "access/multixact.h" #include "access/transam.h" #include "access/xact.h" +#include "catalog/catalog.h" #include "catalog/namespace.h" #include "catalog/pg_database.h" #include "catalog/pg_inherits.h" #include "catalog/pg_namespace.h" #include "commands/cluster.h" +#include "commands/tablecmds.h" #include "commands/vacuum.h" #include "miscadmin.h" #include "nodes/makefuncs.h" @@ -47,6 +49,7 @@ #include "utils/acl.h" #include "utils/fmgroids.h" #include "utils/guc.h" +#include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/syscache.h" @@ -68,6 +71,7 @@ static BufferAccessStrategy vac_strategy; /* non-export function prototypes */ +static bool vacuum_skip_rel(Oid relid, Form_pg_class reltuple, int elevel); static List *expand_vacuum_rel(VacuumRelation *vrel); static List *get_all_vacuum_rels(void); static void vac_truncate_clog(TransactionId frozenXID, @@ -408,6 +412,60 @@ vacuum(int options, List *relations, VacuumParams *params, vac_context = NULL; } +/* + * Check if a given relation can be safely vacuumed or not. If the + * relation needs to be skipped, issue a log message and return true to + * let the caller know what to do with this relation. + */ +static bool +vacuum_skip_rel(Oid relid, Form_pg_class reltuple, int elevel) +{ + /* + * Check permissions. + * + * We allow the user to vacuum a table if he is superuser, the table + * owner, or the database owner (but in the latter case, only if it's not + * a shared relation). pg_class_ownercheck includes the superuser case. + * + * Note we choose to treat permissions failure as a WARNING and keep + * trying to vacuum the rest of the DB --- is this appropriate? + */ + if (!(pg_class_ownercheck(relid, GetUserId()) || + (pg_database_ownercheck(MyDatabaseId, GetUserId()) && !reltuple->relisshared))) + { + if (reltuple->relisshared) + ereport(elevel, + (errmsg("skipping \"%s\" --- only superuser can vacuum it", + NameStr(reltuple->relname)))); + else if (reltuple->relnamespace == PG_CATALOG_NAMESPACE) + ereport(elevel, + (errmsg("skipping \"%s\" --- only superuser or database owner can vacuum it", + NameStr(reltuple->relname)))); + else + ereport(elevel, + (errmsg("skipping \"%s\" --- only table or database owner can vacuum it", + NameStr(reltuple->relname)))); + return true; + } + + /* + * Check that it's of a vacuumable relkind. + */ + if (reltuple->relkind != RELKIND_RELATION && + reltuple->relkind != RELKIND_MATVIEW && + reltuple->relkind != RELKIND_TOASTVALUE && + reltuple->relkind != RELKIND_PARTITIONED_TABLE) + { + ereport(elevel, + (errmsg("skipping \"%s\" --- cannot vacuum non-tables or special system tables", + NameStr(reltuple->relname)))); + return true; + } + + return false; +} + + /* * Given a VacuumRelation, fill in the table OID if it wasn't specified, * and optionally add VacuumRelations for partitions of the table. @@ -456,15 +514,6 @@ expand_vacuum_rel(VacuumRelation *vrel) */ relid = RangeVarGetRelid(vrel->relation, AccessShareLock, false); - /* - * Make a returnable VacuumRelation for this rel. - */ - oldcontext = MemoryContextSwitchTo(vac_context); - vacrels = lappend(vacrels, makeVacuumRelation(vrel->relation, - relid, - vrel->va_cols)); - MemoryContextSwitchTo(oldcontext); - /* * To check whether the relation is a partitioned table, fetch its * syscache entry. @@ -473,9 +522,27 @@ expand_vacuum_rel(VacuumRelation *vrel) if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup failed for relation %u", relid); classForm = (Form_pg_class) GETSTRUCT(tuple); + + /* check permissions of relation */ + if (vacuum_skip_rel(relid, classForm, WARNING)) + { + ReleaseSysCache(tuple); + UnlockRelationOid(relid, AccessShareLock); + return vacrels; + } + include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE); ReleaseSysCache(tuple); + /* + * Make a returnable VacuumRelation for this rel. + */ + oldcontext = MemoryContextSwitchTo(vac_context); + vacrels = lappend(vacrels, makeVacuumRelation(vrel->relation, + relid, + vrel->va_cols)); + MemoryContextSwitchTo(oldcontext); + /* * If it is, make relation list entries for its partitions. Note that * the list returned by find_all_inheritors() includes the passed-in @@ -545,15 +612,10 @@ get_all_vacuum_rels(void) { Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple); MemoryContext oldcontext; + Oid relid = HeapTupleGetOid(tuple); - /* - * We include partitioned tables here; depending on which operation is - * to be performed, caller will decide whether to process or ignore - * them. - */ - if (classForm->relkind != RELKIND_RELATION && - classForm->relkind != RELKIND_MATVIEW && - classForm->relkind != RELKIND_PARTITIONED_TABLE) + /* check permissions of relation */ + if (vacuum_skip_rel(relid, classForm, DEBUG1)) continue; /* @@ -563,7 +625,7 @@ get_all_vacuum_rels(void) */ oldcontext = MemoryContextSwitchTo(vac_context); vacrels = lappend(vacrels, makeVacuumRelation(NULL, - HeapTupleGetOid(tuple), + relid, NIL)); MemoryContextSwitchTo(oldcontext); } @@ -1436,47 +1498,13 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params) } /* - * Check permissions. - * - * We allow the user to vacuum a table if he is superuser, the table - * owner, or the database owner (but in the latter case, only if it's not - * a shared relation). pg_class_ownercheck includes the superuser case. - * - * Note we choose to treat permissions failure as a WARNING and keep - * trying to vacuum the rest of the DB --- is this appropriate? - */ - if (!(pg_class_ownercheck(RelationGetRelid(onerel), GetUserId()) || - (pg_database_ownercheck(MyDatabaseId, GetUserId()) && !onerel->rd_rel->relisshared))) - { - if (onerel->rd_rel->relisshared) - ereport(WARNING, - (errmsg("skipping \"%s\" --- only superuser can vacuum it", - RelationGetRelationName(onerel)))); - else if (onerel->rd_rel->relnamespace == PG_CATALOG_NAMESPACE) - ereport(WARNING, - (errmsg("skipping \"%s\" --- only superuser or database owner can vacuum it", - RelationGetRelationName(onerel)))); - else - ereport(WARNING, - (errmsg("skipping \"%s\" --- only table or database owner can vacuum it", - RelationGetRelationName(onerel)))); - relation_close(onerel, lmode); - PopActiveSnapshot(); - CommitTransactionCommand(); - return false; - } - - /* - * Check that it's of a vacuumable relkind. + * Check if relation needs to be skipped. This check happens as well + * when building the relation list to VACUUM for a manual operation, + * and needs to be done here as well as this could be spawned across + * multiple transactions. */ - if (onerel->rd_rel->relkind != RELKIND_RELATION && - onerel->rd_rel->relkind != RELKIND_MATVIEW && - onerel->rd_rel->relkind != RELKIND_TOASTVALUE && - onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) + if (vacuum_skip_rel(RelationGetRelid(onerel), onerel->rd_rel, WARNING)) { - ereport(WARNING, - (errmsg("skipping \"%s\" --- cannot vacuum non-tables or special system tables", - RelationGetRelationName(onerel)))); relation_close(onerel, lmode); PopActiveSnapshot(); CommitTransactionCommand();