diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 45dee101df..c69a3490bf 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -21,7 +21,11 @@ PostgreSQL documentation
-ANALYZE [ VERBOSE ] [ table_name [ ( column_name [, ...] ) ] ]
+ANALYZE [ VERBOSE ] [ table_and_columns [, ...] ]
+
+where table_and_columns is:
+
+ table_name [ ( column_name [, ...] ) ]
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 421c18d117..3a0afa3b74 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -21,9 +21,21 @@ 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 [ ( option [, ...] ) ] [ table_and_columns [, ...] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ table_name [, ...] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ table_and_columns [, ...] ]
+
+where option can be one of:
+
+ FULL
+ FREEZE
+ VERBOSE
+ ANALYZE
+ DISABLE_PAGE_SKIPPING
+
+and table_and_columns is:
+
+ table_name [ ( column_name [, ...] ) ]
@@ -165,7 +177,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, ANALYZE> must also be
+ specified.
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 08fc18e96b..e9cfd903d2 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -117,6 +117,8 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
AcquireSampleRowsFunc acquirefunc = NULL;
BlockNumber relpages = 0;
+ Assert(relation != NULL);
+
/* Select logging level */
if (options & VACOPT_VERBOSE)
elevel = INFO;
@@ -152,7 +154,19 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
relation->relname)));
}
if (!onerel)
+ {
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually analyzed.
+ */
+ if (!IsAutoVacuumWorkerProcess())
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
return;
+ }
/*
* Check permissions --- this should match vacuum's check!
@@ -383,12 +397,22 @@ do_analyze_rel(Relation onerel, int options, VacuumParams *params,
{
char *col = strVal(lfirst(le));
+ /*
+ * We have already checked the column list in vacuum(...),
+ * but the columns may have disappeared since then. If
+ * this happens, emit a nice warning message and skip the
+ * undefined column.
+ */
i = attnameAttNum(onerel, col, false);
if (i == InvalidAttrNumber)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column \"%s\" of relation \"%s\" does not exist",
+ errmsg("skipping column \"%s\" of relation \"%s\" because the column no longer exists",
col, RelationGetRelationName(onerel))));
+ continue;
+ }
+
vacattrstats[tcnt] = examine_attribute(onerel, i, NULL);
if (vacattrstats[tcnt] != NULL)
tcnt++;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index faa181207a..7fca813594 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -37,6 +37,8 @@
#include "commands/cluster.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "parser/parse_relation.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "storage/bufmgr.h"
@@ -46,6 +48,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"
@@ -67,7 +70,8 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */
-static List *get_rel_oids(Oid relid, const RangeVar *vacrel);
+static void get_rel_oids(List **vacrels);
+static void check_columns_exist(List *relations);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
@@ -90,7 +94,6 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
Assert(vacstmt->options & (VACOPT_VACUUM | VACOPT_ANALYZE));
Assert((vacstmt->options & VACOPT_VACUUM) ||
!(vacstmt->options & (VACOPT_FULL | VACOPT_FREEZE)));
- Assert((vacstmt->options & VACOPT_ANALYZE) || vacstmt->va_cols == NIL);
Assert(!(vacstmt->options & VACOPT_SKIPTOAST));
/*
@@ -119,8 +122,7 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
params.log_min_duration = -1;
/* Now go through the common routine */
- vacuum(vacstmt->options, vacstmt->relation, InvalidOid, ¶ms,
- vacstmt->va_cols, NULL, isTopLevel);
+ vacuum(vacstmt->options, vacstmt->rels, ¶ms, NULL, isTopLevel);
}
/*
@@ -128,15 +130,12 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
*
* options is a bitmask of VacuumOption flags, indicating what to do.
*
- * relid, if not InvalidOid, indicate the relation to process; otherwise,
- * the RangeVar is used. (The latter must always be passed, because it's
- * used for error messages.)
+ * relations is a list of VacuumRelation to process. If it is NIL, all
+ * relations in the database are processed.
*
* params contains a set of parameters that can be used to customize the
* behavior.
*
- * va_cols is a list of columns to analyze, or NIL to process them all.
- *
* bstrategy is normally given as NULL, but in autovacuum it can be passed
* in to use the same buffer strategy object across multiple vacuum() calls.
*
@@ -146,14 +145,15 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
* memory context that will not disappear at transaction commit.
*/
void
-vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
- List *va_cols, BufferAccessStrategy bstrategy, bool isTopLevel)
+vacuum(int options, List *relations, VacuumParams *params,
+ BufferAccessStrategy bstrategy, bool isTopLevel)
{
- const char *stmttype;
- volatile bool in_outer_xact,
- use_own_xacts;
- List *relations;
- static bool in_vacuum = false;
+ const char *stmttype;
+ volatile bool in_outer_xact,
+ use_own_xacts;
+ static bool in_vacuum = false;
+ MemoryContext oldcontext;
+ ListCell *lc;
Assert(params != NULL);
@@ -196,6 +196,29 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
errmsg("VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL")));
/*
+ * Make sure VACOPT_ANALYZE is specified if any column lists are present.
+ */
+ foreach(lc, relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, lc);
+ if (relation->va_cols != NIL && (options & VACOPT_ANALYZE) == 0)
+ {
+ char *rel_name;
+ RangeVar *rangeVar = relation->relation;
+
+ if (rangeVar->schemaname != NULL)
+ rel_name = psprintf("%s.%s", rangeVar->schemaname, rangeVar->relname);
+ else
+ rel_name = rangeVar->relname;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ANALYZE option must be specified when a column list is provided"),
+ errhint("A column list was specified for relation \"%s\".", rel_name)));
+ }
+ }
+
+ /*
* Send info about dead objects to the statistics collector, unless we are
* in autovacuum --- autovacuum.c does this for itself.
*/
@@ -226,10 +249,25 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
vac_strategy = bstrategy;
/*
+ * Move our relation list to our special memory context so that we do
+ * not lose it among our per-relation transactions.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ relations = copyObject(relations);
+ MemoryContextSwitchTo(oldcontext);
+
+ /*
* 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);
+ get_rel_oids(&relations);
+
+ /*
+ * Check that all specified columns exist so that we can fast-fail
+ * commands with multiple tables. If the column disappears before we
+ * actually process it, we will emit a WARNING and skip it later on.
+ */
+ check_columns_exist(relations);
/*
* Decide whether we need to start/commit our own transactions.
@@ -297,11 +335,11 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
*/
foreach(cur, relations)
{
- Oid relid = lfirst_oid(cur);
+ VacuumRelation *relinfo = lfirst_node(VacuumRelation, cur);
if (options & VACOPT_VACUUM)
{
- if (!vacuum_rel(relid, relation, options, params))
+ if (!vacuum_rel(relinfo->oid, relinfo->relation, options, params))
continue;
}
@@ -318,8 +356,8 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
PushActiveSnapshot(GetTransactionSnapshot());
}
- analyze_rel(relid, relation, options, params,
- va_cols, in_outer_xact, vac_strategy);
+ analyze_rel(relinfo->oid, relinfo->relation, options, params,
+ relinfo->va_cols, in_outer_xact, vac_strategy);
if (use_own_xacts)
{
@@ -373,67 +411,105 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
}
/*
- * Build a list of Oids for each relation to be processed
+ * Determine the OID for each relation to be processed
*
* The list is built in vac_context so that it will survive across our
* per-relation transactions.
*/
-static List *
-get_rel_oids(Oid relid, const RangeVar *vacrel)
+static void
+get_rel_oids(List **vacrels)
{
- List *oid_list = NIL;
+ List *vacrels_tmp = NIL;
+ VacuumRelation *relinfo;
MemoryContext oldcontext;
- /* OID supplied by VACUUM's caller? */
- if (OidIsValid(relid))
- {
- oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
- }
- else if (vacrel)
+ 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);
+ relinfo = lfirst_node(VacuumRelation, lc);
- /*
- * 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);
+ /* OIDs supplied by VACUUM's caller? */
+ if (OidIsValid(relinfo->oid))
+ {
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+ MemoryContextSwitchTo(oldcontext);
+ continue;
+ }
+
+ /*
+ * 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(relinfo->relation, NoLock, false);
+
+ /*
+ * 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)
+ {
+ List *partition_oids = find_all_inheritors(relid, NoLock, NULL);
+ ListCell *part_lc;
+ foreach(part_lc, partition_oids)
+ {
+ RangeVar *rangevar;
+ VacuumRelation *tmp;
+ char *schemaname = NULL;
+ char *relname = NULL;
+ Oid part_oid;
+ Oid namespace_oid;
+
+ part_oid = lfirst_oid(part_lc);
+
+ namespace_oid = get_rel_namespace(part_oid);
+ if (OidIsValid(namespace_oid))
+ schemaname = get_namespace_name(namespace_oid);
+ relname = get_rel_name(part_oid);
+ rangevar = makeRangeVar(schemaname, relname, -1);
+
+ tmp = makeVacuumRelation(rangevar, relinfo->va_cols, part_oid);
+
+ vacrels_tmp = lappend(vacrels_tmp, tmp);
+ }
+ }
+ else
+ {
+ relinfo->oid = relid;
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+ }
}
else
{
@@ -451,7 +527,11 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
+ Oid rel_oid;
Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
+ char *schemaname;
+ char *relname;
+ RangeVar *rangevar;
/*
* We include partitioned tables here; depending on which
@@ -465,7 +545,14 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
/* Make a relation list entry for this guy */
oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, HeapTupleGetOid(tuple));
+
+ rel_oid = HeapTupleGetOid(tuple);
+ schemaname = get_namespace_name(classForm->relnamespace);
+ relname = NameStr(classForm->relname);
+ rangevar = makeRangeVar(schemaname, relname, -1);
+ relinfo = makeVacuumRelation(rangevar, NIL, rel_oid);
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+
MemoryContextSwitchTo(oldcontext);
}
@@ -473,7 +560,77 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
heap_close(pgclass, AccessShareLock);
}
- return oid_list;
+ *vacrels = vacrels_tmp;
+}
+
+/*
+ * Check that all specified columns for the relations exist.
+ *
+ * This function emits an ERROR if the relation or the columns specified for
+ * a relation cannot be found. This is used to pre-validate any column lists
+ * specified in VACUUM/ANALYZE commands. If any columns disappear between
+ * calling this function and when we actually get to processing them, we will
+ * emit a WARNING and skip it at that time.
+ */
+static void
+check_columns_exist(List *relations)
+{
+ ListCell *relation_lc;
+ foreach(relation_lc, relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, relation_lc);
+ Relation rel;
+ ListCell *column_lc;
+ char *schemaname;
+
+ if (relation->va_cols == NIL) /* nothing to check */
+ continue;
+
+ /*
+ * Since we are only checking the existence of columns,
+ * we do not take a lock yet. If a relation or column
+ * disappears before we get to it, we will emit a
+ * WARNING and skip it at that time.
+ */
+ rel = try_relation_open(relation->oid, NoLock);
+ if (!rel)
+ {
+ if (relation->relation->schemaname)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s.%s\" does not exist",
+ relation->relation->schemaname,
+ relation->relation->relname)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" does not exist",
+ relation->relation->relname)));
+ }
+
+ schemaname = get_namespace_name(RelationGetNamespace(rel));
+
+ foreach(column_lc, relation->va_cols)
+ {
+ char *col = strVal(lfirst(column_lc));
+ int i = attnameAttNum(rel, col, false);
+
+ if (i != InvalidAttrNumber)
+ continue;
+
+ if (schemaname)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s.%s\" does not exist",
+ col, schemaname, RelationGetRelationName(rel))));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \%s\" of relation \"%s\" does not exist",
+ col, RelationGetRelationName(rel))));
+ }
+ relation_close(rel, NoLock);
+ }
}
/*
@@ -1232,6 +1389,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
int save_nestlevel;
Assert(params != NULL);
+ Assert(relation != NULL);
/* Begin a transaction for vacuuming this relation */
StartTransactionCommand();
@@ -1309,6 +1467,16 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
if (!onerel)
{
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually vacuumed.
+ */
+ if (!IsAutoVacuumWorkerProcess())
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
PopActiveSnapshot();
CommitTransactionCommand();
return false;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index f1bed14e2b..d32c4e688b 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3767,8 +3767,19 @@ _copyVacuumStmt(const VacuumStmt *from)
VacuumStmt *newnode = makeNode(VacuumStmt);
COPY_SCALAR_FIELD(options);
+ COPY_NODE_FIELD(rels);
+
+ return newnode;
+}
+
+static VacuumRelation *
+_copyVacuumRelation(const VacuumRelation *from)
+{
+ VacuumRelation *newnode = makeNode(VacuumRelation);
+
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(va_cols);
+ COPY_SCALAR_FIELD(oid);
return newnode;
}
@@ -5216,6 +5227,9 @@ copyObjectImpl(const void *from)
case T_VacuumStmt:
retval = _copyVacuumStmt(from);
break;
+ case T_VacuumRelation:
+ retval = _copyVacuumRelation(from);
+ break;
case T_ExplainStmt:
retval = _copyExplainStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8b56b9146a..1d0d779609 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1664,8 +1664,17 @@ static bool
_equalVacuumStmt(const VacuumStmt *a, const VacuumStmt *b)
{
COMPARE_SCALAR_FIELD(options);
+ COMPARE_NODE_FIELD(rels);
+
+ return true;
+}
+
+static bool
+_equalVacuumRelation(const VacuumRelation *a, const VacuumRelation *b)
+{
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(va_cols);
+ COMPARE_SCALAR_FIELD(oid);
return true;
}
@@ -3362,6 +3371,9 @@ equal(const void *a, const void *b)
case T_VacuumStmt:
retval = _equalVacuumStmt(a, b);
break;
+ case T_VacuumRelation:
+ retval = _equalVacuumRelation(a, b);
+ break;
case T_ExplainStmt:
retval = _equalExplainStmt(a, b);
break;
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 0755039da9..60d6addf0f 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -611,3 +611,18 @@ makeGroupingSet(GroupingSetKind kind, List *content, int location)
n->location = location;
return n;
}
+
+/*
+ * makeVacuumRelation -
+ * create a VacuumRelation node
+ */
+VacuumRelation *
+makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid)
+{
+ VacuumRelation *v = makeNode(VacuumRelation);
+
+ v->relation = relation;
+ v->va_cols = va_cols;
+ v->oid = oid;
+ return v;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c303818c9b..b3fe50efb6 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -366,6 +366,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type import_qualification_type
%type import_qualification
+%type vacuum_relation
+
%type stmtblock stmtmulti
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
@@ -395,7 +397,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
relation_expr_list dostmt_opt_list
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
- publication_name_list
+ publication_name_list vacuum_relation_list
%type group_by_list
%type group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10157,11 +10159,10 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *)n;
}
- | VACUUM opt_full opt_freeze opt_verbose qualified_name
+ | VACUUM opt_full opt_freeze opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM;
@@ -10171,8 +10172,7 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = $5;
- n->va_cols = NIL;
+ n->rels = $5;
$$ = (Node *)n;
}
| VACUUM opt_full opt_freeze opt_verbose AnalyzeStmt
@@ -10191,18 +10191,14 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *) n;
}
- | VACUUM '(' vacuum_option_list ')' qualified_name opt_name_list
+ | VACUUM '(' vacuum_option_list ')' vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = $5;
- n->va_cols = $6;
- if (n->va_cols != NIL) /* implies analyze */
- n->options |= VACOPT_ANALYZE;
+ n->rels = $5;
$$ = (Node *) n;
}
;
@@ -10236,18 +10232,16 @@ AnalyzeStmt:
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *)n;
}
- | analyze_keyword opt_verbose qualified_name opt_name_list
+ | analyze_keyword opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = $3;
- n->va_cols = $4;
+ n->rels = $3;
$$ = (Node *)n;
}
;
@@ -10275,6 +10269,18 @@ opt_name_list:
| /*EMPTY*/ { $$ = NIL; }
;
+vacuum_relation:
+ qualified_name opt_name_list
+ {
+ $$ = (Node *) makeVacuumRelation($1, $2, InvalidOid);
+ }
+ ;
+
+vacuum_relation_list:
+ vacuum_relation { $$ = list_make1($1); }
+ | vacuum_relation_list ',' vacuum_relation { $$ = lappend($1, $3); }
+ ;
+
/*****************************************************************************
*
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 776b1c0a9d..b171049e9b 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -79,6 +79,7 @@
#include "lib/ilist.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "postmaster/fork_process.h"
@@ -3069,20 +3070,25 @@ relation_needs_vacanalyze(Oid relid,
static void
autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
{
- RangeVar rangevar;
+ RangeVar *rangevar;
+ VacuumRelation *rel;
+ List *rel_list;
+ MemoryContext old_cxt;
- /* Set up command parameters --- use local variables instead of palloc */
- MemSet(&rangevar, 0, sizeof(rangevar));
-
- rangevar.schemaname = tab->at_nspname;
- rangevar.relname = tab->at_relname;
- rangevar.location = -1;
+ /*
+ * Create the relation list in a long-lived memory context so that it
+ * survives transaction boundaries.
+ */
+ old_cxt = MemoryContextSwitchTo(AutovacMemCxt);
+ rangevar = makeRangeVar(tab->at_nspname, tab->at_relname, -1);
+ rel = makeVacuumRelation(rangevar, NIL, tab->at_relid);
+ rel_list = list_make1(rel);
+ MemoryContextSwitchTo(old_cxt);
/* 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, rel_list, &tab->at_params, bstrategy, true);
}
/*
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a9035112e9..7a7b793ddf 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -157,8 +157,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,
- VacuumParams *params, List *va_cols,
+extern void vacuum(int options, List *relations, VacuumParams *params,
BufferAccessStrategy bstrategy, bool isTopLevel);
extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
int *nindexes, Relation **Irel);
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 46a79b1817..12d78ff923 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -86,4 +86,6 @@ extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location);
+extern VacuumRelation *makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 27bd4f3363..ffeeb4919b 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -468,6 +468,7 @@ typedef enum NodeTag
T_PartitionBoundSpec,
T_PartitionRangeDatum,
T_PartitionCmd,
+ T_VacuumRelation,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index f3e4c69753..2adbd54dd8 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3098,12 +3098,23 @@ typedef enum VacuumOption
VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7 /* don't skip any pages */
} VacuumOption;
+/*
+ * This is used to keep track of a relation and an optional list of
+ * column names, as may be specified in VACUUM and ANALYZE.
+ */
+typedef struct VacuumRelation
+{
+ NodeTag type;
+ RangeVar *relation; /* single table to process */
+ List *va_cols; /* list of column names, or NIL for all */
+ Oid oid; /* corresponding OID (filled in by [auto]vacuum.c) */
+} VacuumRelation;
+
typedef struct VacuumStmt
{
- NodeTag type;
- int options; /* OR of VacuumOption flags */
- RangeVar *relation; /* single table to process, or NULL */
- List *va_cols; /* list of column names, or NIL for all */
+ NodeTag type;
+ int options; /* OR of VacuumOption flags */
+ List *rels; /* list of tables/columns to process, or NIL for all */
} VacuumStmt;
/* ----------------------
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 0478a8ac60..506ff98f22 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -1282,13 +1282,13 @@ ERROR: "myview" is not a table, composite type, or foreign table
drop view myview;
-- test some commands to make sure they fail on the dropped column
analyze atacc1(a);
-ERROR: column "a" of relation "atacc1" does not exist
+ERROR: column "a" of relation "public.atacc1" does not exist
analyze atacc1("........pg.dropped.1........");
-ERROR: column "........pg.dropped.1........" of relation "atacc1" does not exist
+ERROR: column "........pg.dropped.1........" of relation "public.atacc1" does not exist
vacuum analyze atacc1(a);
-ERROR: column "a" of relation "atacc1" does not exist
+ERROR: column "a" of relation "public.atacc1" does not exist
vacuum analyze atacc1("........pg.dropped.1........");
-ERROR: column "........pg.dropped.1........" of relation "atacc1" does not exist
+ERROR: column "........pg.dropped.1........" of relation "public.atacc1" does not exist
comment on column atacc1.a is 'testing';
ERROR: column "a" of relation "atacc1" does not exist
comment on column atacc1."........pg.dropped.1........" is 'testing';
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index 6f68663087..62c1a03685 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,37 @@ 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, ANALYZE) vactst (i);
+VACUUM (FREEZE, ANALYZE) vactst (i, i, i);
+VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
+VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
+VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (VERBOSE, ANALYZE) vactst (i), vacparted (does_not_exist);
+ERROR: column "does_not_exist" of relation "public.vacparted" does not exist
+VACUUM FREEZE vactst, vactst, vactst;
+VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
+VACUUM vactst (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vactst".
+VACUUM (VERBOSE) vactst, vacparted, vaccluster (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vaccluster".
+VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vactst".
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
+ERROR: column "does_not_exist" of relation "public.vacparted" does not exist
+ANALYZE vactst, vactst, vactst;
+ANALYZE vacparted (a), vacparted (b), vacparted;
+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 7c5fb04917..10e0de0113 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,29 @@ 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, ANALYZE) vactst (i);
+VACUUM (FREEZE, ANALYZE) vactst (i, i, i);
+VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
+VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
+VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (VERBOSE, ANALYZE) vactst (i), vacparted (does_not_exist);
+VACUUM FREEZE vactst, vactst, vactst;
+VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
+VACUUM vactst (i);
+VACUUM (VERBOSE) vactst, vacparted, vaccluster (i);
+VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
+ANALYZE vactst, vactst, vactst;
+ANALYZE vacparted (a), vacparted (b), vacparted;
+
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;