0002-vacuum_multiple_tables_v19.patch

application/octet-stream

Filename: 0002-vacuum_multiple_tables_v19.patch
Type: application/octet-stream
Part: 0
Message: Re: [Proposal] Allow users to specify multiple tables in VACUUM commands

Patch

Format: unified
Series: patch v19-0002
File+
doc/src/sgml/ref/analyze.sgml 5 1
doc/src/sgml/ref/vacuum.sgml 17 4
src/backend/commands/analyze.c 14 0
src/backend/commands/vacuum.c 110 31
src/backend/nodes/copyfuncs.c 14 0
src/backend/nodes/equalfuncs.c 12 0
src/backend/nodes/makefuncs.c 15 0
src/backend/parser/gram.y 24 18
src/backend/postmaster/autovacuum.c 13 9
src/include/commands/vacuum.h 1 2
src/include/nodes/makefuncs.h 2 0
src/include/nodes/nodes.h 1 0
src/include/nodes/parsenodes.h 15 4
src/test/regress/expected/vacuum.out 35 2
src/test/regress/sql/vacuum.sql 25 3
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 45dee10..c69a349 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -21,7 +21,11 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+
+<phrase>where <replaceable class="PARAMETER">table_and_columns</replaceable> is:</phrase>
+
+    <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
 </synopsis>
  </refsynopsisdiv>
 
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 421c18d..3a0afa3 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -21,9 +21,21 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-VACUUM [ ( { FULL | FREEZE | VERBOSE | ANALYZE | DISABLE_PAGE_SKIPPING } [, ...] ) ] [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
-VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> ]
-VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+VACUUM [ ( <replaceable class="PARAMETER">option</replaceable> [, ...] ) ] [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [, ...] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+
+<phrase>where <replaceable class="PARAMETER">option</replaceable> can be one of:</phrase>
+
+    FULL
+    FREEZE
+    VERBOSE
+    ANALYZE
+    DISABLE_PAGE_SKIPPING
+
+<phrase>and <replaceable class="PARAMETER">table_and_columns</replaceable> is:</phrase>
+
+    <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -165,7 +177,8 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
     <listitem>
      <para>
       The name of a specific column to analyze. Defaults to all columns.
-      If a column list is specified, <literal>ANALYZE</> is implied.
+      If a column list is specified, <literal>ANALYZE</> must also be
+      specified.
      </para>
     </listitem>
    </varlistentry>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index a46ef0e..1e5a107 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!
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index faa1812..7dc0648 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -37,6 +37,7 @@
 #include "commands/cluster.h"
 #include "commands/vacuum.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
 #include "storage/bufmgr.h"
@@ -46,6 +47,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 +69,7 @@ static BufferAccessStrategy vac_strategy;
 
 
 /* non-export function prototypes */
-static List *get_rel_oids(Oid relid, const RangeVar *vacrel);
+static List *get_rel_oids(VacuumRelation *vacrel);
 static void vac_truncate_clog(TransactionId frozenXID,
 				  MultiXactId minMulti,
 				  TransactionId lastSaneFrozenXid,
@@ -90,7 +92,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 +120,15 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
 	params.log_min_duration = -1;
 
 	/* Now go through the common routine */
-	vacuum(vacstmt->options, vacstmt->relation, InvalidOid, &params,
-		   vacstmt->va_cols, NULL, isTopLevel);
+	if (vacstmt->rels == NIL)
+		vacuum(vacstmt->options, NULL, &params, NULL, isTopLevel);
+	else
+	{
+		ListCell *lc;
+		foreach(lc, vacstmt->rels)
+			vacuum(vacstmt->options, lfirst_node(VacuumRelation, lc),
+				   &params, NULL, isTopLevel);
+	}
 }
 
 /*
@@ -128,15 +136,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.)
+ * relation is the VacuumRelation to process.  If it is NULL, 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,8 +151,8 @@ 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, VacuumRelation *relation, VacuumParams *params,
+	   BufferAccessStrategy bstrategy, bool isTopLevel)
 {
 	const char *stmttype;
 	volatile bool in_outer_xact,
@@ -196,6 +201,25 @@ 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 a column list is present.
+	 */
+	if (relation != NULL && 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 +250,13 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
 	vac_strategy = bstrategy;
 
 	/*
-	 * Build list of relations to process, unless caller gave us one. (If we
-	 * build one, we put it in vac_context for safekeeping.)
+	 * Create a list of relations with their corresponding OIDs.
+	 *
+	 * If the relation is a partitioned table, an extra entry will be added
+	 * to the list for each partition.  The list is kept in vac_context for
+	 * safekeeping.
 	 */
-	relations = get_rel_oids(relid, relation);
+	relations = get_rel_oids(relation);
 
 	/*
 	 * Decide whether we need to start/commit our own transactions.
@@ -297,11 +324,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 +345,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,22 +400,23 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
 }
 
 /*
- * Build a list of Oids for each relation to be processed
+ * Create a list of relations with their corresponding OIDs.
  *
- * The list is built in vac_context so that it will survive across our
- * per-relation transactions.
+ * If the given relation is a partitioned table, an extra entry will be added to
+ * the list for each partition.  The list is kept in vac_context so that it will
+ * survive across our per-relation transactions.
  */
 static List *
-get_rel_oids(Oid relid, const RangeVar *vacrel)
+get_rel_oids(VacuumRelation *vacrel)
 {
-	List	   *oid_list = NIL;
+	List *vacrels = NIL;
 	MemoryContext oldcontext;
 
 	/* OID supplied by VACUUM's caller? */
-	if (OidIsValid(relid))
+	if (vacrel != NULL && OidIsValid(vacrel->oid))
 	{
 		oldcontext = MemoryContextSwitchTo(vac_context);
-		oid_list = lappend_oid(oid_list, relid);
+		vacrels = lappend(vacrels, vacrel);
 		MemoryContextSwitchTo(oldcontext);
 	}
 	else if (vacrel)
@@ -408,7 +436,7 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
 		 * going to commit this transaction and begin a new one between now
 		 * and then.
 		 */
-		relid = RangeVarGetRelid(vacrel, NoLock, false);
+		relid = RangeVarGetRelid(vacrel->relation, NoLock, false);
 
 		/*
 		 * To check whether the relation is a partitioned table, fetch its
@@ -428,11 +456,39 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
 		 * 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));
+		{
+			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, vacrel->va_cols, part_oid);
+
+				vacrels = lappend(vacrels, tmp);
+			}
+		}
 		else
-			oid_list = lappend_oid(oid_list, relid);
+		{
+			vacrel->oid = relid;
+			vacrels = lappend(vacrels, vacrel);
+		}
+
 		MemoryContextSwitchTo(oldcontext);
 	}
 	else
@@ -451,7 +507,12 @@ 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;
+			VacuumRelation *relinfo;
 
 			/*
 			 * We include partitioned tables here; depending on which
@@ -465,7 +526,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 = lappend(vacrels, relinfo);
+
 			MemoryContextSwitchTo(oldcontext);
 		}
 
@@ -473,7 +541,7 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
 		heap_close(pgclass, AccessShareLock);
 	}
 
-	return oid_list;
+	return vacrels;
 }
 
 /*
@@ -1232,6 +1300,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 +1378,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 f1bed14..d32c4e6 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 8b56b91..1d0d779 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 0755039..60d6add 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 c303818..b3fe50e 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 <ival>	import_qualification_type
 %type <importqual> import_qualification
 
+%type <node>	vacuum_relation
+
 %type <list>	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 <list>	group_by_list
 %type <node>	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 b745d89..246e484 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,23 @@ relation_needs_vacanalyze(Oid relid,
 static void
 autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
 {
-	RangeVar	rangevar;
+	RangeVar	*rangevar;
+	VacuumRelation	*rel;
+	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 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);
+	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, &tab->at_params, bstrategy, true);
 }
 
 /*
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a903511..441ec4d 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, VacuumRelation *relation, 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 46a79b1..12d78ff 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 27bd4f3..ffeeb49 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 f3e4c69..9fdba9e 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;	/* table to process (required for error messaging) */
+	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/vacuum.out b/src/test/regress/expected/vacuum.out
index 99a7007..9726a86 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,6 +88,41 @@ 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);
+ERROR:  column lists cannot have duplicate entries
+HINT:  the column list specified for relation "public.vactst" contains duplicates
+VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
+VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
+VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (ANALYZE) vactst (i), vacparted (does_not_exist);
+ERROR:  column "does_not_exist" of relation "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 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 vactst (i), vacparted (does_not_exist);
+ERROR:  column "does_not_exist" of relation "vacparted" does not exist
+ANALYZE vactst, vactst, vactst;
+ANALYZE vacparted (a), vacparted (b), vacparted;
+DROP TABLE vaccluster;
+DROP TABLE vactst;
 DROP TABLE vacparted;
 -- analyze with duplicate columns
 CREATE TABLE analyze_test (a int, b char, c int, d char, e int, f char);
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 3d4e308..d4e59cf 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,6 +70,31 @@ 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 (ANALYZE) vactst (i), vacparted (does_not_exist);
+VACUUM FREEZE vactst, vactst, vactst;
+VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
+VACUUM vactst (i);
+VACUUM vactst, vacparted, vaccluster (i);
+VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE 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;
 
 -- analyze with duplicate columns