Hello,

I sometimes feel annoyed when trying to VACUUM multiple specific
tables.

postgres=# vacuum a, b;
ERROR:  syntax error at or near ","
LINE 1: vacuum a, b;

This patch just allows multiple targets for VACUUM command.

regards,

-- 
Kyotaro Horiguchi
NTT Open Source Software Center
>From 481690f0d84a21db755a986a7f785e8bbbe0769e Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyot...@lab.ntt.co.jp>
Date: Fri, 28 Jul 2017 13:30:59 +0900
Subject: [PATCH 1/2] Make VaccumStmt capable to have multiple table parameters

---
 src/backend/commands/vacuum.c  | 18 +++++++++++++++---
 src/backend/nodes/copyfuncs.c  | 13 +++++++++++++
 src/backend/nodes/equalfuncs.c | 11 +++++++++++
 src/backend/parser/gram.y      | 29 ++++++++++++++++-------------
 src/include/nodes/nodes.h      |  1 +
 src/include/nodes/parsenodes.h | 10 ++++++++--
 6 files changed, 64 insertions(+), 18 deletions(-)

diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index faa1812..d6cd352 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -85,12 +85,12 @@ void
 ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
 {
 	VacuumParams params;
+	ListCell    *lc;
 
 	/* sanity checks on options */
 	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 +119,20 @@ 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 (list_length(vacstmt->relcols) == 0)
+		vacuum(vacstmt->options, NULL, InvalidOid, &params,
+			   NIL, NULL, isTopLevel);
+	else
+	{
+		foreach (lc, vacstmt->relcols)
+		{
+			VacRelCols *relcol = (VacRelCols *) lfirst(lc);
+			Assert((vacstmt->options & VACOPT_ANALYZE) ||
+				   relcol->va_cols == NIL);
+			vacuum(vacstmt->options, relcol->relation, InvalidOid, &params,
+				   relcol->va_cols, NULL, isTopLevel);
+		}
+	}
 }
 
 /*
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 7204169..761f758 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3764,6 +3764,16 @@ _copyVacuumStmt(const VacuumStmt *from)
 	VacuumStmt *newnode = makeNode(VacuumStmt);
 
 	COPY_SCALAR_FIELD(options);
+	COPY_NODE_FIELD(relcols);
+
+	return newnode;
+}
+
+static VacRelCols *
+_copyVacRelCols(const VacRelCols *from)
+{
+	VacRelCols *newnode = makeNode(VacRelCols);
+
 	COPY_NODE_FIELD(relation);
 	COPY_NODE_FIELD(va_cols);
 
@@ -5527,6 +5537,9 @@ copyObjectImpl(const void *from)
 		case T_PartitionCmd:
 			retval = _copyPartitionCmd(from);
 			break;
+		case T_VacRelCols:
+			retval = _copyVacRelCols(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8d92c03..aea7168 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1663,6 +1663,14 @@ static bool
 _equalVacuumStmt(const VacuumStmt *a, const VacuumStmt *b)
 {
 	COMPARE_SCALAR_FIELD(options);
+	COMPARE_NODE_FIELD(relcols);
+
+	return true;
+}
+
+static bool
+_equalVacRelCols(const VacRelCols *a, const VacRelCols *b)
+{
 	COMPARE_NODE_FIELD(relation);
 	COMPARE_NODE_FIELD(va_cols);
 
@@ -3675,6 +3683,9 @@ equal(const void *a, const void *b)
 		case T_PartitionCmd:
 			retval = _equalPartitionCmd(a, b);
 			break;
+		case T_VacRelCols:
+			retval = _equalVacRelCols(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7d0de99..844c691 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10128,13 +10128,13 @@ 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->relcols = NIL;
 					$$ = (Node *)n;
 				}
 			| VACUUM opt_full opt_freeze opt_verbose qualified_name
 				{
 					VacuumStmt *n = makeNode(VacuumStmt);
+					VacRelCols *relcol = makeNode(VacRelCols);
 					n->options = VACOPT_VACUUM;
 					if ($2)
 						n->options |= VACOPT_FULL;
@@ -10142,8 +10142,9 @@ 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;
+					relcol->relation = $5;
+					relcol->va_cols = NIL;
+					n->relcols = list_make1(relcol);
 					$$ = (Node *)n;
 				}
 			| VACUUM opt_full opt_freeze opt_verbose AnalyzeStmt
@@ -10162,18 +10163,19 @@ 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->relcols = NIL;
 					$$ = (Node *) n;
 				}
 			| VACUUM '(' vacuum_option_list ')' qualified_name opt_name_list
 				{
 					VacuumStmt *n = makeNode(VacuumStmt);
+					VacRelCols *relcol = makeNode(VacRelCols);
 					n->options = VACOPT_VACUUM | $3;
-					n->relation = $5;
-					n->va_cols = $6;
-					if (n->va_cols != NIL)	/* implies analyze */
+					relcol->relation = $5;
+					relcol->va_cols = $6;
+					if (relcol->va_cols != NIL)	/* implies analyze */
 						n->options |= VACOPT_ANALYZE;
+					n->relcols = list_make1(relcol);
 					$$ = (Node *) n;
 				}
 		;
@@ -10207,18 +10209,19 @@ AnalyzeStmt:
 					n->options = VACOPT_ANALYZE;
 					if ($2)
 						n->options |= VACOPT_VERBOSE;
-					n->relation = NULL;
-					n->va_cols = NIL;
+					n->relcols = NIL;
 					$$ = (Node *)n;
 				}
 			| analyze_keyword opt_verbose qualified_name opt_name_list
 				{
 					VacuumStmt *n = makeNode(VacuumStmt);
+					VacRelCols *relcol = makeNode(VacRelCols);
 					n->options = VACOPT_ANALYZE;
 					if ($2)
 						n->options |= VACOPT_VERBOSE;
-					n->relation = $3;
-					n->va_cols = $4;
+					relcol->relation = $3;
+					relcol->va_cols = $4;
+					n->relcols = list_make1(relcol);
 					$$ = (Node *)n;
 				}
 		;
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 27bd4f3..1c2e3a6 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_VacRelCols,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 5f2a4a7..3f800f1 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3090,12 +3090,18 @@ typedef enum VacuumOption
 	VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7	/* don't skip any pages */
 } VacuumOption;
 
-typedef struct VacuumStmt
+typedef struct VacRelCols
 {
 	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 */
+} VacRelCols;
+
+typedef struct VacuumStmt
+{
+	NodeTag		type;
+	int			options;		/* OR of VacuumOption flags */
+	List	   *relcols;		/* List of VacRelCols  */
 } VacuumStmt;
 
 /* ----------------------
-- 
2.9.2

>From 2325d0b66cfcbb15a0a9632ee2ad3c28ca3de98f Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyot...@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

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to