Allow multiple tables to be specified in one VACUUM or ANALYZE command.
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 3 Oct 2017 22:53:44 +0000 (18:53 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 3 Oct 2017 22:53:44 +0000 (18:53 -0400)
Not much to say about this; does what it says on the tin.

However, formerly, if there was a column list then the ANALYZE action was
implied; now it must be specified, or you get an error.  This is because
it would otherwise be a bit unclear what the user meant if some tables
have column lists and some don't.

Nathan Bossart, reviewed by Michael Paquier and Masahiko Sawada, with some
editorialization by me

Discussion: https://postgr.es/m/E061A8E3-5E3D-494D-94F0-E8A9B312BBFC@amazon.com

14 files changed:
doc/src/sgml/ref/analyze.sgml
doc/src/sgml/ref/vacuum.sgml
src/backend/commands/vacuum.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/nodes/makefuncs.c
src/backend/parser/gram.y
src/backend/postmaster/autovacuum.c
src/include/commands/vacuum.h
src/include/nodes/makefuncs.h
src/include/nodes/nodes.h
src/include/nodes/parsenodes.h
src/test/regress/expected/vacuum.out
src/test/regress/sql/vacuum.sql

index 45dee101dfa3f46787098180e5500bfef71e0eae..ba429730226755276c57d714f5b03e0481844fae 100644 (file)
@@ -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>
 
@@ -38,9 +42,11 @@ ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [
   </para>
 
   <para>
-   With no parameter, <command>ANALYZE</command> examines every table in the
-   current database.  With a parameter, <command>ANALYZE</command> examines
-   only that table.  It is further possible to give a list of column names,
+   Without a <replaceable class="PARAMETER">table_and_columns</replaceable>
+   list, <command>ANALYZE</command> processes every table and materialized view
+   in the current database that the current user has permission to analyze.
+   With a list, <command>ANALYZE</command> processes only those table(s).
+   It is further possible to give a list of column names for a table,
    in which case only the statistics for those columns are collected.
   </para>
  </refsect1>
index 421c18d117c33d86f39361d987fd617c9d273d21..e712226c61cb5b98e4bd23bd199bd5a2d9707377 100644 (file)
@@ -21,9 +21,20 @@ 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 ] [ 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>
 
@@ -40,9 +51,10 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
   </para>
 
   <para>
-   With no parameter, <command>VACUUM</command> processes every table in the
-   current database that the current user has permission to vacuum.
-   With a parameter, <command>VACUUM</command> processes only that table.
+   Without a <replaceable class="PARAMETER">table_and_columns</replaceable>
+   list, <command>VACUUM</command> processes every table and materialized view
+   in the current database that the current user has permission to vacuum.
+   With a list, <command>VACUUM</command> processes only those table(s).
   </para>
 
   <para>
@@ -141,8 +153,8 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
       except when performing an aggressive vacuum, some pages may be skipped
       in order to avoid waiting for other sessions to finish using them.
       This option disables all page-skipping behavior, and is intended to
-      be used only the contents of the visibility map are thought to
-      be suspect, which should happen only if there is a hardware or software
+      be used only when the contents of the visibility map are
+      suspect, which should happen only if there is a hardware or software
       issue causing database corruption.
      </para>
     </listitem>
@@ -152,9 +164,8 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
     <term><replaceable class="PARAMETER">table_name</replaceable></term>
     <listitem>
      <para>
-      The name (optionally schema-qualified) of a specific table to
-      vacuum.  If omitted, all regular tables and materialized views in the
-      current database are vacuumed.  If the specified table is a partitioned
+      The name (optionally schema-qualified) of a specific table or
+      materialized view to vacuum.  If the specified table is a partitioned
       table, all of its leaf partitions are vacuumed.
      </para>
     </listitem>
@@ -165,7 +176,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>
index d533cef6a6c19c5efc154e1f1d616efe3a81013a..f439b55ea5e23b81a01580b2f778fc726b7f0aec 100644 (file)
@@ -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"
@@ -67,7 +68,8 @@ static BufferAccessStrategy vac_strategy;
 
 
 /* non-export function prototypes */
-static List *get_rel_oids(Oid relid, const RangeVar *vacrel);
+static List *expand_vacuum_rel(VacuumRelation *vrel);
+static List *get_all_vacuum_rels(void);
 static void vac_truncate_clog(TransactionId frozenXID,
                  MultiXactId minMulti,
                  TransactionId lastSaneFrozenXid,
@@ -90,9 +92,26 @@ 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));
 
+   /*
+    * Make sure VACOPT_ANALYZE is specified if any column lists are present.
+    */
+   if (!(vacstmt->options & VACOPT_ANALYZE))
+   {
+       ListCell   *lc;
+
+       foreach(lc, vacstmt->rels)
+       {
+           VacuumRelation *vrel = lfirst_node(VacuumRelation, lc);
+
+           if (vrel->va_cols != NIL)
+               ereport(ERROR,
+                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                        errmsg("ANALYZE option must be specified when a column list is provided")));
+       }
+   }
+
    /*
     * All freeze ages are zero if the FREEZE option is given; otherwise pass
     * them as -1 which means to use the default values.
@@ -119,26 +138,22 @@ 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);
+   vacuum(vacstmt->options, vacstmt->rels, &params, NULL, isTopLevel);
 }
 
 /*
- * Primary entry point for VACUUM and ANALYZE commands.
+ * Internal entry point for VACUUM and ANALYZE commands.
  *
  * options is a bitmask of VacuumOption flags, indicating what to do.
  *
- * relid, if not InvalidOid, indicates the relation to process; otherwise,
- * if a RangeVar is supplied, that's what to process; otherwise, we process
- * all relevant tables in the database.  (If both relid and a RangeVar are
- * supplied, the relid is what is processed, but we use the RangeVar's name
- * to report any open/lock failure.)
+ * relations, if not NIL, is a list of VacuumRelation to process; otherwise,
+ * we process all relevant tables in the database.  For each VacuumRelation,
+ * if a valid OID is supplied, the table with that OID is what to process;
+ * otherwise, the VacuumRelation's RangeVar indicates what to process.
  *
  * 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.
  *
@@ -148,14 +163,14 @@ 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)
 {
+   static bool in_vacuum = false;
+
    const char *stmttype;
    volatile bool in_outer_xact,
                use_own_xacts;
-   List       *relations;
-   static bool in_vacuum = false;
 
    Assert(params != NULL);
 
@@ -228,10 +243,29 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
    vac_strategy = bstrategy;
 
    /*
-    * Build list of relation OID(s) to process, putting it in vac_context for
-    * safekeeping.
+    * Build list of relation(s) to process, putting any new data in
+    * vac_context for safekeeping.
     */
-   relations = get_rel_oids(relid, relation);
+   if (relations != NIL)
+   {
+       List       *newrels = NIL;
+       ListCell   *lc;
+
+       foreach(lc, relations)
+       {
+           VacuumRelation *vrel = lfirst_node(VacuumRelation, lc);
+           List       *sublist;
+           MemoryContext old_context;
+
+           sublist = expand_vacuum_rel(vrel);
+           old_context = MemoryContextSwitchTo(vac_context);
+           newrels = list_concat(newrels, sublist);
+           MemoryContextSwitchTo(old_context);
+       }
+       relations = newrels;
+   }
+   else
+       relations = get_all_vacuum_rels();
 
    /*
     * Decide whether we need to start/commit our own transactions.
@@ -282,7 +316,7 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
        CommitTransactionCommand();
    }
 
-   /* Turn vacuum cost accounting on or off */
+   /* Turn vacuum cost accounting on or off, and set/clear in_vacuum */
    PG_TRY();
    {
        ListCell   *cur;
@@ -299,11 +333,11 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
         */
        foreach(cur, relations)
        {
-           Oid         relid = lfirst_oid(cur);
+           VacuumRelation *vrel = lfirst_node(VacuumRelation, cur);
 
            if (options & VACOPT_VACUUM)
            {
-               if (!vacuum_rel(relid, relation, options, params))
+               if (!vacuum_rel(vrel->oid, vrel->relation, options, params))
                    continue;
            }
 
@@ -320,8 +354,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(vrel->oid, vrel->relation, options, params,
+                           vrel->va_cols, in_outer_xact, vac_strategy);
 
                if (use_own_xacts)
                {
@@ -375,25 +409,33 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
 }
 
 /*
- * Build a list of Oids for each relation to be processed
+ * Given a VacuumRelation, fill in the table OID if it wasn't specified,
+ * and optionally add VacuumRelations for partitions of the table.
+ *
+ * If a VacuumRelation does not have an OID supplied and is a partitioned
+ * table, an extra entry will be added to the output for each partition.
+ * Presently, only autovacuum supplies OIDs when calling vacuum(), and
+ * it does not want us to expand partitioned tables.
  *
- * The list is built in vac_context so that it will survive across our
- * per-relation transactions.
+ * We take care not to modify the input data structure, but instead build
+ * new VacuumRelation(s) to return.  (But note that they will reference
+ * unmodified parts of the input, eg column lists.)  New data structures
+ * are made in vac_context.
  */
 static List *
-get_rel_oids(Oid relid, const RangeVar *vacrel)
+expand_vacuum_rel(VacuumRelation *vrel)
 {
-   List       *oid_list = NIL;
+   List       *vacrels = NIL;
    MemoryContext oldcontext;
 
-   /* OID supplied by VACUUM's caller? */
-   if (OidIsValid(relid))
+   /* If caller supplied OID, there's nothing we need do here. */
+   if (OidIsValid(vrel->oid))
    {
        oldcontext = MemoryContextSwitchTo(vac_context);
-       oid_list = lappend_oid(oid_list, relid);
+       vacrels = lappend(vacrels, vrel);
        MemoryContextSwitchTo(oldcontext);
    }
-   else if (vacrel)
+   else
    {
        /* Process a specific relation, and possibly partitions thereof */
        Oid         relid;
@@ -406,7 +448,16 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
         * below, as well as find_all_inheritors's expectation that the caller
         * holds some lock on the starting relation.
         */
-       relid = RangeVarGetRelid(vacrel, AccessShareLock, false);
+       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
@@ -420,19 +471,36 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
        ReleaseSysCache(tuple);
 
        /*
-        * Make relation list entries for this rel and its partitions, if any.
-        * Note that the list returned by find_all_inheritors() includes the
-        * passed-in OID at its head.  There's no point in taking locks on the
-        * individual partitions yet, and doing so would just add unnecessary
-        * deadlock risk.
+        * If it is, make relation list entries for its partitions.  Note that
+        * the list returned by find_all_inheritors() includes the passed-in
+        * OID, so we have to skip that.  There's no point in taking locks on
+        * the individual partitions yet, and doing so would just add
+        * unnecessary deadlock risk.
         */
-       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);
+       {
+           List       *part_oids = find_all_inheritors(relid, NoLock, NULL);
+           ListCell   *part_lc;
+
+           foreach(part_lc, part_oids)
+           {
+               Oid         part_oid = lfirst_oid(part_lc);
+
+               if (part_oid == relid)
+                   continue;   /* ignore original table */
+
+               /*
+                * We omit a RangeVar since it wouldn't be appropriate to
+                * complain about failure to open one of these relations
+                * later.
+                */
+               oldcontext = MemoryContextSwitchTo(vac_context);
+               vacrels = lappend(vacrels, makeVacuumRelation(NULL,
+                                                             part_oid,
+                                                             vrel->va_cols));
+               MemoryContextSwitchTo(oldcontext);
+           }
+       }
 
        /*
         * Release lock again.  This means that by the time we actually try to
@@ -447,45 +515,57 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
         */
        UnlockRelationOid(relid, AccessShareLock);
    }
-   else
-   {
-       /*
-        * Process all plain relations and materialized views listed in
-        * pg_class
-        */
-       Relation    pgclass;
-       HeapScanDesc scan;
-       HeapTuple   tuple;
 
-       pgclass = heap_open(RelationRelationId, AccessShareLock);
+   return vacrels;
+}
 
-       scan = heap_beginscan_catalog(pgclass, 0, NULL);
+/*
+ * Construct a list of VacuumRelations for all vacuumable rels in
+ * the current database.  The list is built in vac_context.
+ */
+static List *
+get_all_vacuum_rels(void)
+{
+   List       *vacrels = NIL;
+   Relation    pgclass;
+   HeapScanDesc scan;
+   HeapTuple   tuple;
 
-       while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
-       {
-           Form_pg_class classForm = (Form_pg_class) GETSTRUCT(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)
-               continue;
-
-           /* Make a relation list entry for this rel */
-           oldcontext = MemoryContextSwitchTo(vac_context);
-           oid_list = lappend_oid(oid_list, HeapTupleGetOid(tuple));
-           MemoryContextSwitchTo(oldcontext);
-       }
+   pgclass = heap_open(RelationRelationId, AccessShareLock);
+
+   scan = heap_beginscan_catalog(pgclass, 0, NULL);
+
+   while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+   {
+       Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
+       MemoryContext oldcontext;
 
-       heap_endscan(scan);
-       heap_close(pgclass, AccessShareLock);
+       /*
+        * 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)
+           continue;
+
+       /*
+        * Build VacuumRelation(s) specifying the table OIDs to be processed.
+        * We omit a RangeVar since it wouldn't be appropriate to complain
+        * about failure to open one of these relations later.
+        */
+       oldcontext = MemoryContextSwitchTo(vac_context);
+       vacrels = lappend(vacrels, makeVacuumRelation(NULL,
+                                                     HeapTupleGetOid(tuple),
+                                                     NIL));
+       MemoryContextSwitchTo(oldcontext);
    }
 
-   return oid_list;
+   heap_endscan(scan);
+   heap_close(pgclass, AccessShareLock);
+
+   return vacrels;
 }
 
 /*
index b274af26a4278e02cb7a2fb2ec88732efc6c6391..c1a83ca9098f9375c60c75e19f9dac62d133fb4d 100644 (file)
@@ -3766,7 +3766,18 @@ _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_SCALAR_FIELD(oid);
    COPY_NODE_FIELD(va_cols);
 
    return newnode;
@@ -5215,6 +5226,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;
index 5c839f4c31a6ace93292c0565656d86d5df44553..7a700018e72acea1900ac65968c8a069f2f005df 100644 (file)
@@ -1663,7 +1663,16 @@ 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_SCALAR_FIELD(oid);
    COMPARE_NODE_FIELD(va_cols);
 
    return true;
@@ -3361,6 +3370,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;
index 0755039da9fb97c5150c090cb288c5bc8b8e7995..b58eb0f815e615b4301890efe2ea6ee52a8c9cde 100644 (file)
@@ -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, Oid oid, List *va_cols)
+{
+   VacuumRelation *v = makeNode(VacuumRelation);
+
+   v->relation = relation;
+   v->oid = oid;
+   v->va_cols = va_cols;
+   return v;
+}
index c303818c9b0cf5d2a7e7be188722de7aca7593dc..4c83a63f7d9094912b56215a1774a2e59fcfba28 100644 (file)
@@ -365,6 +365,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <list>   DefACLOptionList
 %type <ival>   import_qualification_type
 %type <importqual> import_qualification
+%type <node>   vacuum_relation
 
 %type <list>   stmtblock stmtmulti
                OptTableElementList TableElementList OptInherit definition
@@ -396,6 +397,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
                transform_element_list transform_type_list
                TriggerTransitions TriggerReferencing
                publication_name_list
+               vacuum_relation_list opt_vacuum_relation_list
 
 %type <list>   group_by_list
 %type <node>   group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10147,7 +10149,7 @@ cluster_index_specification:
  *
  *****************************************************************************/
 
-VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
+VacuumStmt: VACUUM opt_full opt_freeze opt_verbose opt_vacuum_relation_list
                {
                    VacuumStmt *n = makeNode(VacuumStmt);
                    n->options = VACOPT_VACUUM;
@@ -10157,22 +10159,7 @@ 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;
-                   $$ = (Node *)n;
-               }
-           | VACUUM opt_full opt_freeze opt_verbose qualified_name
-               {
-                   VacuumStmt *n = makeNode(VacuumStmt);
-                   n->options = VACOPT_VACUUM;
-                   if ($2)
-                       n->options |= VACOPT_FULL;
-                   if ($3)
-                       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
@@ -10187,22 +10174,11 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
                        n->options |= VACOPT_VERBOSE;
                    $$ = (Node *)n;
                }
-           | VACUUM '(' vacuum_option_list ')'
-               {
-                   VacuumStmt *n = makeNode(VacuumStmt);
-                   n->options = VACOPT_VACUUM | $3;
-                   n->relation = NULL;
-                   n->va_cols = NIL;
-                   $$ = (Node *) n;
-               }
-           | VACUUM '(' vacuum_option_list ')' qualified_name opt_name_list
+           | VACUUM '(' vacuum_option_list ')' opt_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;
                }
        ;
@@ -10229,25 +10205,13 @@ vacuum_option_elem:
                }
        ;
 
-AnalyzeStmt:
-           analyze_keyword opt_verbose
+AnalyzeStmt: analyze_keyword opt_verbose opt_vacuum_relation_list
                {
                    VacuumStmt *n = makeNode(VacuumStmt);
                    n->options = VACOPT_ANALYZE;
                    if ($2)
                        n->options |= VACOPT_VERBOSE;
-                   n->relation = NULL;
-                   n->va_cols = NIL;
-                   $$ = (Node *)n;
-               }
-           | analyze_keyword opt_verbose qualified_name opt_name_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 +10239,25 @@ opt_name_list:
            | /*EMPTY*/                             { $$ = NIL; }
        ;
 
+vacuum_relation:
+           qualified_name opt_name_list
+               {
+                   $$ = (Node *) makeVacuumRelation($1, InvalidOid, $2);
+               }
+       ;
+
+vacuum_relation_list:
+           vacuum_relation
+                   { $$ = list_make1($1); }
+           | vacuum_relation_list ',' vacuum_relation
+                   { $$ = lappend($1, $3); }
+       ;
+
+opt_vacuum_relation_list:
+           vacuum_relation_list                    { $$ = $1; }
+           | /*EMPTY*/                             { $$ = NIL; }
+       ;
+
 
 /*****************************************************************************
  *
index db6d91ffdfcc5821b9da6c500900c411086a0cec..c04c0b548d4c8b16ac69d99c626925beda37898e 100644 (file)
@@ -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"
@@ -3081,20 +3082,19 @@ relation_needs_vacanalyze(Oid relid,
 static void
 autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
 {
-   RangeVar    rangevar;
-
-   /* 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;
+   RangeVar   *rangevar;
+   VacuumRelation *rel;
+   List       *rel_list;
 
    /* 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);
+   /* Set up one VacuumRelation target, identified by OID, for vacuum() */
+   rangevar = makeRangeVar(tab->at_nspname, tab->at_relname, -1);
+   rel = makeVacuumRelation(rangevar, tab->at_relid, NIL);
+   rel_list = list_make1(rel);
+
+   vacuum(tab->at_vacoptions, rel_list, &tab->at_params, bstrategy, true);
 }
 
 /*
index a9035112e96683703a314e04edf252c95c0bbc35..7a7b793ddfe75547acb86c2228f0ce39db94fb08 100644 (file)
@@ -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);
index 46a79b18175cfeccd951e84ee95c5bdc68a69cbf..dd0d2ea07d3f23299b52bafca7a053ee1bbe5a99 100644 (file)
@@ -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, Oid oid, List *va_cols);
+
 #endif                         /* MAKEFUNC_H */
index 27bd4f3363e2467bdb006660e1e89daeb7d9e379..ffeeb4919b2cf1757fabde9239f717419053f75c 100644 (file)
@@ -468,6 +468,7 @@ typedef enum NodeTag
    T_PartitionBoundSpec,
    T_PartitionRangeDatum,
    T_PartitionCmd,
+   T_VacuumRelation,
 
    /*
     * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
index f3e4c6975395fba22705454d0eacf73b73ca24ad..50eec730b3da4ca68e2e91f015d47b3303090719 100644 (file)
@@ -1778,8 +1778,8 @@ typedef struct AlterTableCmd  /* one subcommand of an ALTER TABLE */
    AlterTableType subtype;     /* Type of table alteration to apply */
    char       *name;           /* column, constraint, or trigger to act on,
                                 * or tablespace */
-   int16       num;            /* attribute number for columns referenced
-                                * by number */
+   int16       num;            /* attribute number for columns referenced by
+                                * number */
    RoleSpec   *newowner;
    Node       *def;            /* definition of new column, index,
                                 * constraint, or parent table */
@@ -3098,12 +3098,26 @@ typedef enum VacuumOption
    VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7   /* don't skip any pages */
 } VacuumOption;
 
+/*
+ * Info about a single target table of VACUUM/ANALYZE.
+ *
+ * If the OID field is set, it always identifies the table to process.
+ * Then the relation field can be NULL; if it isn't, it's used only to report
+ * failure to open/lock the relation.
+ */
+typedef struct VacuumRelation
+{
+   NodeTag     type;
+   RangeVar   *relation;       /* table name to process, or NULL */
+   Oid         oid;            /* table's OID; InvalidOid if not looked up */
+   List       *va_cols;        /* list of column names, or NIL for all */
+} 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 */
+   List       *rels;           /* list of VacuumRelation, or NIL for all */
 } VacuumStmt;
 
 /* ----------------------
index ced53ca9aaf5c18c92ba8da7cc7ef78f13e8835d..c440c7ea58f36741d96cd0963cd40511e38f6e71 100644 (file)
@@ -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);
@@ -95,4 +93,25 @@ VACUUM ANALYZE vacparted(a,b,a);
 ERROR:  column "a" of relation "vacparted" appears more than once
 ANALYZE vacparted(a,b,b);
 ERROR:  column "b" of relation "vacparted" appears more than once
+-- multiple tables specified
+VACUUM vaccluster, vactst;
+VACUUM vacparted, does_not_exist;
+ERROR:  relation "does_not_exist" does not exist
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) does_not_exist, vaccluster;
+ERROR:  relation "does_not_exist" does not exist
+VACUUM ANALYZE vactst, vacparted (a);
+VACUUM ANALYZE vactst (does_not_exist), vacparted (b);
+ERROR:  column "does_not_exist" of relation "vactst" does not exist
+VACUUM FULL vacparted, vactst;
+VACUUM FULL vactst, vacparted (a, b), vaccluster (i);
+ERROR:  ANALYZE option must be specified when a column list is provided
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE vactst, does_not_exist, vacparted;
+ERROR:  relation "does_not_exist" does not exist
+ANALYZE vactst (i), vacparted (does_not_exist);
+ERROR:  column "does_not_exist" of relation "vacparted" does not exist
+DROP TABLE vaccluster;
+DROP TABLE vactst;
 DROP TABLE vacparted;
index 96a848ca954b0a0a11f1b323ca75b6aed79e1a77..92eaca2a93b06dba575f08da544de721c16aa293 100644 (file)
@@ -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);
@@ -78,4 +75,20 @@ VACUUM (FREEZE) vacparted;
 VACUUM ANALYZE vacparted(a,b,a);
 ANALYZE vacparted(a,b,b);
 
+-- multiple tables specified
+VACUUM vaccluster, vactst;
+VACUUM vacparted, does_not_exist;
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) does_not_exist, vaccluster;
+VACUUM ANALYZE vactst, vacparted (a);
+VACUUM ANALYZE vactst (does_not_exist), vacparted (b);
+VACUUM FULL vacparted, vactst;
+VACUUM FULL vactst, vacparted (a, b), vaccluster (i);
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE vactst, does_not_exist, vacparted;
+ANALYZE vactst (i), vacparted (does_not_exist);
+
+DROP TABLE vaccluster;
+DROP TABLE vactst;
 DROP TABLE vacparted;