From 2325d0b66cfcbb15a0a9632ee2ad3c28ca3de98f Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Fri, 28 Jul 2017 15:10:04 +0900
Subject: [PATCH 2/2] Allow multiple target table of VACUUM

This patch allows VACUUM to take multiple tables.
---
 doc/src/sgml/ref/vacuum.sgml         |  6 +--
 src/backend/parser/gram.y            | 82 +++++++++++++++++++++++++++---------
 src/test/regress/expected/vacuum.out |  4 +-
 src/test/regress/sql/vacuum.sql      |  4 +-
 4 files changed, 67 insertions(+), 29 deletions(-)

diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 421c18d..39cf334 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -21,9 +21,9 @@ 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 [ ( { 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> [, ...] ) [, ...]] ]
 </synopsis>
  </refsynopsisdiv>
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 844c691..3161b9e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -187,6 +187,7 @@ static void processCASbits(int cas_bits, int location, const char *constrType,
 			   bool *deferrable, bool *initdeferred, bool *not_valid,
 			   bool *no_inherit, core_yyscan_t yyscanner);
 static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
+static bool implies_analyze(List *relcols);
 
 %}
 
@@ -306,6 +307,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <ival>	opt_lock lock_type cast_context
 %type <ival>	vacuum_option_list vacuum_option_elem
+%type <node>	analyze_target_item
 %type <boolean>	opt_or_replace
 				opt_grant_grant_option opt_grant_admin_option
 				opt_nowait opt_if_exists opt_with_data
@@ -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 analyze_target_list
 
 %type <list>	group_by_list
 %type <node>	group_by_item empty_grouping_set rollup_clause cube_clause
@@ -3849,6 +3851,11 @@ CreateStatsStmt:
 				}
 			;
 
+opt_name_list:
+			'(' name_list ')'						{ $$ = $2; }
+			| /*EMPTY*/								{ $$ = NIL; }
+		;
+
 /*****************************************************************************
  *
  *		QUERY :
@@ -10131,10 +10138,9 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
 					n->relcols = NIL;
 					$$ = (Node *)n;
 				}
-			| VACUUM opt_full opt_freeze opt_verbose qualified_name
+			| VACUUM opt_full opt_freeze opt_verbose analyze_target_list
 				{
 					VacuumStmt *n = makeNode(VacuumStmt);
-					VacRelCols *relcol = makeNode(VacRelCols);
 					n->options = VACOPT_VACUUM;
 					if ($2)
 						n->options |= VACOPT_FULL;
@@ -10142,9 +10148,9 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
 						n->options |= VACOPT_FREEZE;
 					if ($4)
 						n->options |= VACOPT_VERBOSE;
-					relcol->relation = $5;
-					relcol->va_cols = NIL;
-					n->relcols = list_make1(relcol);
+					if (implies_analyze($5))
+						n->options |= VACOPT_ANALYZE;
+					n->relcols = $5;
 					$$ = (Node *)n;
 				}
 			| VACUUM opt_full opt_freeze opt_verbose AnalyzeStmt
@@ -10166,16 +10172,13 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
 					n->relcols = NIL;
 					$$ = (Node *) n;
 				}
-			| VACUUM '(' vacuum_option_list ')' qualified_name opt_name_list
+			| VACUUM '(' vacuum_option_list ')' analyze_target_list
 				{
 					VacuumStmt *n = makeNode(VacuumStmt);
-					VacRelCols *relcol = makeNode(VacRelCols);
 					n->options = VACOPT_VACUUM | $3;
-					relcol->relation = $5;
-					relcol->va_cols = $6;
-					if (relcol->va_cols != NIL)	/* implies analyze */
+					if (implies_analyze($5))
 						n->options |= VACOPT_ANALYZE;
-					n->relcols = list_make1(relcol);
+					n->relcols = $5;
 					$$ = (Node *) n;
 				}
 		;
@@ -10212,16 +10215,13 @@ AnalyzeStmt:
 					n->relcols = NIL;
 					$$ = (Node *)n;
 				}
-			| analyze_keyword opt_verbose qualified_name opt_name_list
+			| analyze_keyword opt_verbose analyze_target_list
 				{
 					VacuumStmt *n = makeNode(VacuumStmt);
-					VacRelCols *relcol = makeNode(VacRelCols);
 					n->options = VACOPT_ANALYZE;
 					if ($2)
 						n->options |= VACOPT_VERBOSE;
-					relcol->relation = $3;
-					relcol->va_cols = $4;
-					n->relcols = list_make1(relcol);
+					n->relcols = $3;
 					$$ = (Node *)n;
 				}
 		;
@@ -10244,11 +10244,33 @@ opt_freeze: FREEZE									{ $$ = TRUE; }
 			| /*EMPTY*/								{ $$ = FALSE; }
 		;
 
-opt_name_list:
-			'(' name_list ')'						{ $$ = $2; }
-			| /*EMPTY*/								{ $$ = NIL; }
+analyze_target_list:
+			analyze_target_item
+				{
+					$$ = list_make1($1);
+				}
+			| analyze_target_list ',' analyze_target_item
+				{
+					$$ = lappend($1, $3);
+				}
 		;
 
+analyze_target_item:
+			qualified_name
+				{
+					VacRelCols *n = makeNode(VacRelCols);
+					n->relation = $1;
+					n->va_cols = NIL;
+					$$ = (Node *)n;
+				}
+			| qualified_name '(' name_list ')'
+				{
+					VacRelCols *n = makeNode(VacRelCols);
+					n->relation = $1;
+					n->va_cols = $3;
+					$$ = (Node *)n;
+				}
+		;
 
 /*****************************************************************************
  *
@@ -15906,6 +15928,26 @@ makeRecursiveViewSelect(char *relname, List *aliases, Node *query)
 	return (Node *) s;
 }
 
+/*
+ * Retuns true if relcols implies VACOPT_ANALYZE
+ */
+static bool
+implies_analyze(List *relcols)
+{
+	ListCell *lc;
+
+	foreach (lc, relcols)
+	{
+		VacRelCols *t = (VacRelCols *) lfirst(lc);
+		Assert(IsA(t, VacRelCols));
+
+		if (t->va_cols != NIL)	/* implies analyze */
+			return true;
+	}
+
+	return false;
+}
+
 /* parser_init()
  * Initialize to parse one query string
  */
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index 6f68663..d7c8a1e 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -71,9 +71,7 @@ ANALYZE vaccluster;
 ERROR:  ANALYZE cannot be executed from VACUUM or ANALYZE
 CONTEXT:  SQL function "do_analyze" statement 1
 SQL function "wrap_do_analyze" statement 1
-VACUUM FULL pg_am;
-VACUUM FULL pg_class;
-VACUUM FULL pg_database;
+VACUUM FULL pg_am, pg_class, pg_database;
 VACUUM FULL vaccluster;
 ERROR:  ANALYZE cannot be executed from VACUUM or ANALYZE
 CONTEXT:  SQL function "do_analyze" statement 1
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 7c5fb04..c67d019 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -54,9 +54,7 @@ CREATE INDEX ON vaccluster(wrap_do_analyze(i));
 INSERT INTO vaccluster VALUES (1), (2);
 ANALYZE vaccluster;
 
-VACUUM FULL pg_am;
-VACUUM FULL pg_class;
-VACUUM FULL pg_database;
+VACUUM FULL pg_am, pg_class, pg_database;
 VACUUM FULL vaccluster;
 VACUUM FULL vactst;
 
-- 
2.9.2

