Commit 11d8d72c authored by Tom Lane's avatar Tom Lane

Allow multiple tables to be specified in one VACUUM or ANALYZE command.

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
parent 45f9d086
...@@ -21,7 +21,11 @@ PostgreSQL documentation ...@@ -21,7 +21,11 @@ PostgreSQL documentation
<refsynopsisdiv> <refsynopsisdiv>
<synopsis> <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> </synopsis>
</refsynopsisdiv> </refsynopsisdiv>
...@@ -38,9 +42,11 @@ ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [ ...@@ -38,9 +42,11 @@ ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [
</para> </para>
<para> <para>
With no parameter, <command>ANALYZE</command> examines every table in the Without a <replaceable class="PARAMETER">table_and_columns</replaceable>
current database. With a parameter, <command>ANALYZE</command> examines list, <command>ANALYZE</command> processes every table and materialized view
only that table. It is further possible to give a list of column names, 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. in which case only the statistics for those columns are collected.
</para> </para>
</refsect1> </refsect1>
......
...@@ -21,9 +21,20 @@ PostgreSQL documentation ...@@ -21,9 +21,20 @@ PostgreSQL documentation
<refsynopsisdiv> <refsynopsisdiv>
<synopsis> <synopsis>
VACUUM [ ( { FULL | FREEZE | VERBOSE | ANALYZE | DISABLE_PAGE_SKIPPING } [, ...] ) ] [ <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> [, ...] ]
VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</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> </synopsis>
</refsynopsisdiv> </refsynopsisdiv>
...@@ -40,9 +51,10 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER"> ...@@ -40,9 +51,10 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
</para> </para>
<para> <para>
With no parameter, <command>VACUUM</command> processes every table in the Without a <replaceable class="PARAMETER">table_and_columns</replaceable>
current database that the current user has permission to vacuum. list, <command>VACUUM</command> processes every table and materialized view
With a parameter, <command>VACUUM</command> processes only that table. 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>
<para> <para>
...@@ -141,8 +153,8 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER"> ...@@ -141,8 +153,8 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
except when performing an aggressive vacuum, some pages may be skipped except when performing an aggressive vacuum, some pages may be skipped
in order to avoid waiting for other sessions to finish using them. in order to avoid waiting for other sessions to finish using them.
This option disables all page-skipping behavior, and is intended to This option disables all page-skipping behavior, and is intended to
be used only the contents of the visibility map are thought to be used only when the contents of the visibility map are
be suspect, which should happen only if there is a hardware or software suspect, which should happen only if there is a hardware or software
issue causing database corruption. issue causing database corruption.
</para> </para>
</listitem> </listitem>
...@@ -152,9 +164,8 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER"> ...@@ -152,9 +164,8 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
<term><replaceable class="PARAMETER">table_name</replaceable></term> <term><replaceable class="PARAMETER">table_name</replaceable></term>
<listitem> <listitem>
<para> <para>
The name (optionally schema-qualified) of a specific table to The name (optionally schema-qualified) of a specific table or
vacuum. If omitted, all regular tables and materialized views in the materialized view to vacuum. If the specified table is a partitioned
current database are vacuumed. If the specified table is a partitioned
table, all of its leaf partitions are vacuumed. table, all of its leaf partitions are vacuumed.
</para> </para>
</listitem> </listitem>
...@@ -165,7 +176,8 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER"> ...@@ -165,7 +176,8 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
<listitem> <listitem>
<para> <para>
The name of a specific column to analyze. Defaults to all columns. 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> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
......
...@@ -37,6 +37,7 @@ ...@@ -37,6 +37,7 @@
#include "commands/cluster.h" #include "commands/cluster.h"
#include "commands/vacuum.h" #include "commands/vacuum.h"
#include "miscadmin.h" #include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h" #include "pgstat.h"
#include "postmaster/autovacuum.h" #include "postmaster/autovacuum.h"
#include "storage/bufmgr.h" #include "storage/bufmgr.h"
...@@ -67,7 +68,8 @@ static BufferAccessStrategy vac_strategy; ...@@ -67,7 +68,8 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */ /* 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, static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti, MultiXactId minMulti,
TransactionId lastSaneFrozenXid, TransactionId lastSaneFrozenXid,
...@@ -90,9 +92,26 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel) ...@@ -90,9 +92,26 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
Assert(vacstmt->options & (VACOPT_VACUUM | VACOPT_ANALYZE)); Assert(vacstmt->options & (VACOPT_VACUUM | VACOPT_ANALYZE));
Assert((vacstmt->options & VACOPT_VACUUM) || Assert((vacstmt->options & VACOPT_VACUUM) ||
!(vacstmt->options & (VACOPT_FULL | VACOPT_FREEZE))); !(vacstmt->options & (VACOPT_FULL | VACOPT_FREEZE)));
Assert((vacstmt->options & VACOPT_ANALYZE) || vacstmt->va_cols == NIL);
Assert(!(vacstmt->options & VACOPT_SKIPTOAST)); 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 * All freeze ages are zero if the FREEZE option is given; otherwise pass
* them as -1 which means to use the default values. * them as -1 which means to use the default values.
...@@ -119,26 +138,22 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel) ...@@ -119,26 +138,22 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
params.log_min_duration = -1; params.log_min_duration = -1;
/* Now go through the common routine */ /* Now go through the common routine */
vacuum(vacstmt->options, vacstmt->relation, InvalidOid, &params, vacuum(vacstmt->options, vacstmt->rels, &params, NULL, isTopLevel);
vacstmt->va_cols, 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. * options is a bitmask of VacuumOption flags, indicating what to do.
* *
* relid, if not InvalidOid, indicates the relation to process; otherwise, * relations, if not NIL, is a list of VacuumRelation to process; otherwise,
* if a RangeVar is supplied, that's what to process; otherwise, we process * we process all relevant tables in the database. For each VacuumRelation,
* all relevant tables in the database. (If both relid and a RangeVar are * if a valid OID is supplied, the table with that OID is what to process;
* supplied, the relid is what is processed, but we use the RangeVar's name * otherwise, the VacuumRelation's RangeVar indicates what to process.
* to report any open/lock failure.)
* *
* params contains a set of parameters that can be used to customize the * params contains a set of parameters that can be used to customize the
* behavior. * 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 * 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. * in to use the same buffer strategy object across multiple vacuum() calls.
* *
...@@ -148,14 +163,14 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel) ...@@ -148,14 +163,14 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
* memory context that will not disappear at transaction commit. * memory context that will not disappear at transaction commit.
*/ */
void void
vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params, vacuum(int options, List *relations, VacuumParams *params,
List *va_cols, BufferAccessStrategy bstrategy, bool isTopLevel) BufferAccessStrategy bstrategy, bool isTopLevel)
{ {
static bool in_vacuum = false;
const char *stmttype; const char *stmttype;
volatile bool in_outer_xact, volatile bool in_outer_xact,
use_own_xacts; use_own_xacts;
List *relations;
static bool in_vacuum = false;
Assert(params != NULL); Assert(params != NULL);
...@@ -228,10 +243,29 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params, ...@@ -228,10 +243,29 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
vac_strategy = bstrategy; vac_strategy = bstrategy;
/* /*
* Build list of relation OID(s) to process, putting it in vac_context for * Build list of relation(s) to process, putting any new data in
* safekeeping. * 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. * Decide whether we need to start/commit our own transactions.
...@@ -282,7 +316,7 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params, ...@@ -282,7 +316,7 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
CommitTransactionCommand(); CommitTransactionCommand();
} }
/* Turn vacuum cost accounting on or off */ /* Turn vacuum cost accounting on or off, and set/clear in_vacuum */
PG_TRY(); PG_TRY();
{ {
ListCell *cur; ListCell *cur;
...@@ -299,11 +333,11 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params, ...@@ -299,11 +333,11 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
*/ */
foreach(cur, relations) foreach(cur, relations)
{ {
Oid relid = lfirst_oid(cur); VacuumRelation *vrel = lfirst_node(VacuumRelation, cur);
if (options & VACOPT_VACUUM) if (options & VACOPT_VACUUM)
{ {
if (!vacuum_rel(relid, relation, options, params)) if (!vacuum_rel(vrel->oid, vrel->relation, options, params))
continue; continue;
} }
...@@ -320,8 +354,8 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params, ...@@ -320,8 +354,8 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
PushActiveSnapshot(GetTransactionSnapshot()); PushActiveSnapshot(GetTransactionSnapshot());
} }
analyze_rel(relid, relation, options, params, analyze_rel(vrel->oid, vrel->relation, options, params,
va_cols, in_outer_xact, vac_strategy); vrel->va_cols, in_outer_xact, vac_strategy);
if (use_own_xacts) if (use_own_xacts)
{ {
...@@ -375,25 +409,33 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params, ...@@ -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 * We take care not to modify the input data structure, but instead build
* per-relation transactions. * 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 * static List *
get_rel_oids(Oid relid, const RangeVar *vacrel) expand_vacuum_rel(VacuumRelation *vrel)
{ {
List *oid_list = NIL; List *vacrels = NIL;
MemoryContext oldcontext; MemoryContext oldcontext;
/* OID supplied by VACUUM's caller? */ /* If caller supplied OID, there's nothing we need do here. */
if (OidIsValid(relid)) if (OidIsValid(vrel->oid))
{ {
oldcontext = MemoryContextSwitchTo(vac_context); oldcontext = MemoryContextSwitchTo(vac_context);
oid_list = lappend_oid(oid_list, relid); vacrels = lappend(vacrels, vrel);
MemoryContextSwitchTo(oldcontext); MemoryContextSwitchTo(oldcontext);
} }
else if (vacrel) else
{ {
/* Process a specific relation, and possibly partitions thereof */ /* Process a specific relation, and possibly partitions thereof */
Oid relid; Oid relid;
...@@ -406,7 +448,16 @@ get_rel_oids(Oid relid, const RangeVar *vacrel) ...@@ -406,7 +448,16 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
* below, as well as find_all_inheritors's expectation that the caller * below, as well as find_all_inheritors's expectation that the caller
* holds some lock on the starting relation. * 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 * To check whether the relation is a partitioned table, fetch its
...@@ -420,19 +471,36 @@ get_rel_oids(Oid relid, const RangeVar *vacrel) ...@@ -420,19 +471,36 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
ReleaseSysCache(tuple); ReleaseSysCache(tuple);
/* /*
* Make relation list entries for this rel and its partitions, if any. * If it is, make relation list entries for its partitions. Note that
* Note that the list returned by find_all_inheritors() includes the * the list returned by find_all_inheritors() includes the passed-in
* passed-in OID at its head. There's no point in taking locks on the * OID, so we have to skip that. There's no point in taking locks on
* individual partitions yet, and doing so would just add unnecessary * the individual partitions yet, and doing so would just add
* deadlock risk. * unnecessary deadlock risk.
*/ */
oldcontext = MemoryContextSwitchTo(vac_context);
if (include_parts) if (include_parts)
oid_list = list_concat(oid_list, {
find_all_inheritors(relid, NoLock, NULL)); List *part_oids = find_all_inheritors(relid, NoLock, NULL);
else ListCell *part_lc;
oid_list = lappend_oid(oid_list, relid);
MemoryContextSwitchTo(oldcontext); 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 * 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) ...@@ -447,45 +515,57 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
*/ */
UnlockRelationOid(relid, AccessShareLock); 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) pgclass = heap_open(RelationRelationId, AccessShareLock);
{
Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple); scan = heap_beginscan_catalog(pgclass, 0, NULL);
/* while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
* We include partitioned tables here; depending on which {
* operation is to be performed, caller will decide whether to Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
* process or ignore them. MemoryContext oldcontext;
*/
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);
}
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;
} }
/* /*
......
...@@ -3766,7 +3766,18 @@ _copyVacuumStmt(const VacuumStmt *from) ...@@ -3766,7 +3766,18 @@ _copyVacuumStmt(const VacuumStmt *from)
VacuumStmt *newnode = makeNode(VacuumStmt); VacuumStmt *newnode = makeNode(VacuumStmt);
COPY_SCALAR_FIELD(options); 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(relation);
COPY_SCALAR_FIELD(oid);
COPY_NODE_FIELD(va_cols); COPY_NODE_FIELD(va_cols);
return newnode; return newnode;
...@@ -5215,6 +5226,9 @@ copyObjectImpl(const void *from) ...@@ -5215,6 +5226,9 @@ copyObjectImpl(const void *from)
case T_VacuumStmt: case T_VacuumStmt:
retval = _copyVacuumStmt(from); retval = _copyVacuumStmt(from);
break; break;
case T_VacuumRelation:
retval = _copyVacuumRelation(from);
break;
case T_ExplainStmt: case T_ExplainStmt:
retval = _copyExplainStmt(from); retval = _copyExplainStmt(from);
break; break;
......
...@@ -1663,7 +1663,16 @@ static bool ...@@ -1663,7 +1663,16 @@ static bool
_equalVacuumStmt(const VacuumStmt *a, const VacuumStmt *b) _equalVacuumStmt(const VacuumStmt *a, const VacuumStmt *b)
{ {
COMPARE_SCALAR_FIELD(options); 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(relation);
COMPARE_SCALAR_FIELD(oid);
COMPARE_NODE_FIELD(va_cols); COMPARE_NODE_FIELD(va_cols);
return true; return true;
...@@ -3361,6 +3370,9 @@ equal(const void *a, const void *b) ...@@ -3361,6 +3370,9 @@ equal(const void *a, const void *b)
case T_VacuumStmt: case T_VacuumStmt:
retval = _equalVacuumStmt(a, b); retval = _equalVacuumStmt(a, b);
break; break;
case T_VacuumRelation:
retval = _equalVacuumRelation(a, b);
break;
case T_ExplainStmt: case T_ExplainStmt:
retval = _equalExplainStmt(a, b); retval = _equalExplainStmt(a, b);
break; break;
......
...@@ -611,3 +611,18 @@ makeGroupingSet(GroupingSetKind kind, List *content, int location) ...@@ -611,3 +611,18 @@ makeGroupingSet(GroupingSetKind kind, List *content, int location)
n->location = location; n->location = location;
return n; 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;
}
...@@ -365,6 +365,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); ...@@ -365,6 +365,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <list> DefACLOptionList %type <list> DefACLOptionList
%type <ival> import_qualification_type %type <ival> import_qualification_type
%type <importqual> import_qualification %type <importqual> import_qualification
%type <node> vacuum_relation
%type <list> stmtblock stmtmulti %type <list> stmtblock stmtmulti
OptTableElementList TableElementList OptInherit definition OptTableElementList TableElementList OptInherit definition
...@@ -396,6 +397,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); ...@@ -396,6 +397,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list transform_element_list transform_type_list
TriggerTransitions TriggerReferencing TriggerTransitions TriggerReferencing
publication_name_list publication_name_list
vacuum_relation_list opt_vacuum_relation_list
%type <list> group_by_list %type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause %type <node> group_by_item empty_grouping_set rollup_clause cube_clause
...@@ -10147,7 +10149,7 @@ cluster_index_specification: ...@@ -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); VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM; n->options = VACOPT_VACUUM;
...@@ -10157,22 +10159,7 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose ...@@ -10157,22 +10159,7 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE; n->options |= VACOPT_FREEZE;
if ($4) if ($4)
n->options |= VACOPT_VERBOSE; n->options |= VACOPT_VERBOSE;
n->relation = NULL; n->rels = $5;
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;
$$ = (Node *)n; $$ = (Node *)n;
} }
| VACUUM opt_full opt_freeze opt_verbose AnalyzeStmt | VACUUM opt_full opt_freeze opt_verbose AnalyzeStmt
...@@ -10187,22 +10174,11 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose ...@@ -10187,22 +10174,11 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_VERBOSE; n->options |= VACOPT_VERBOSE;
$$ = (Node *)n; $$ = (Node *)n;
} }
| VACUUM '(' vacuum_option_list ')' | VACUUM '(' vacuum_option_list ')' opt_vacuum_relation_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
{ {
VacuumStmt *n = makeNode(VacuumStmt); VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3; n->options = VACOPT_VACUUM | $3;
n->relation = $5; n->rels = $5;
n->va_cols = $6;
if (n->va_cols != NIL) /* implies analyze */
n->options |= VACOPT_ANALYZE;
$$ = (Node *) n; $$ = (Node *) n;
} }
; ;
...@@ -10229,25 +10205,13 @@ vacuum_option_elem: ...@@ -10229,25 +10205,13 @@ vacuum_option_elem:
} }
; ;
AnalyzeStmt: AnalyzeStmt: analyze_keyword opt_verbose opt_vacuum_relation_list
analyze_keyword opt_verbose
{ {
VacuumStmt *n = makeNode(VacuumStmt); VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_ANALYZE; n->options = VACOPT_ANALYZE;
if ($2) if ($2)
n->options |= VACOPT_VERBOSE; n->options |= VACOPT_VERBOSE;
n->relation = NULL; n->rels = $3;
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;
$$ = (Node *)n; $$ = (Node *)n;
} }
; ;
...@@ -10275,6 +10239,25 @@ opt_name_list: ...@@ -10275,6 +10239,25 @@ opt_name_list:
| /*EMPTY*/ { $$ = NIL; } | /*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; }
;
/***************************************************************************** /*****************************************************************************
* *
......
...@@ -79,6 +79,7 @@ ...@@ -79,6 +79,7 @@
#include "lib/ilist.h" #include "lib/ilist.h"
#include "libpq/pqsignal.h" #include "libpq/pqsignal.h"
#include "miscadmin.h" #include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h" #include "pgstat.h"
#include "postmaster/autovacuum.h" #include "postmaster/autovacuum.h"
#include "postmaster/fork_process.h" #include "postmaster/fork_process.h"
...@@ -3081,20 +3082,19 @@ relation_needs_vacanalyze(Oid relid, ...@@ -3081,20 +3082,19 @@ relation_needs_vacanalyze(Oid relid,
static void static void
autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy) autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
{ {
RangeVar rangevar; RangeVar *rangevar;
VacuumRelation *rel;
/* Set up command parameters --- use local variables instead of palloc */ List *rel_list;
MemSet(&rangevar, 0, sizeof(rangevar));
rangevar.schemaname = tab->at_nspname;
rangevar.relname = tab->at_relname;
rangevar.location = -1;
/* Let pgstat know what we're doing */ /* Let pgstat know what we're doing */
autovac_report_activity(tab); autovac_report_activity(tab);
vacuum(tab->at_vacoptions, &rangevar, tab->at_relid, &tab->at_params, NIL, /* Set up one VacuumRelation target, identified by OID, for vacuum() */
bstrategy, true); 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);
} }
/* /*
......
...@@ -157,8 +157,7 @@ extern int vacuum_multixact_freeze_table_age; ...@@ -157,8 +157,7 @@ extern int vacuum_multixact_freeze_table_age;
/* in commands/vacuum.c */ /* in commands/vacuum.c */
extern void ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel); extern void ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel);
extern void vacuum(int options, RangeVar *relation, Oid relid, extern void vacuum(int options, List *relations, VacuumParams *params,
VacuumParams *params, List *va_cols,
BufferAccessStrategy bstrategy, bool isTopLevel); BufferAccessStrategy bstrategy, bool isTopLevel);
extern void vac_open_indexes(Relation relation, LOCKMODE lockmode, extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
int *nindexes, Relation **Irel); int *nindexes, Relation **Irel);
......
...@@ -86,4 +86,6 @@ extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg, ...@@ -86,4 +86,6 @@ extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location); extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location);
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
#endif /* MAKEFUNC_H */ #endif /* MAKEFUNC_H */
...@@ -468,6 +468,7 @@ typedef enum NodeTag ...@@ -468,6 +468,7 @@ typedef enum NodeTag
T_PartitionBoundSpec, T_PartitionBoundSpec,
T_PartitionRangeDatum, T_PartitionRangeDatum,
T_PartitionCmd, T_PartitionCmd,
T_VacuumRelation,
/* /*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h) * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
......
...@@ -1778,8 +1778,8 @@ typedef struct AlterTableCmd /* one subcommand of an ALTER TABLE */ ...@@ -1778,8 +1778,8 @@ typedef struct AlterTableCmd /* one subcommand of an ALTER TABLE */
AlterTableType subtype; /* Type of table alteration to apply */ AlterTableType subtype; /* Type of table alteration to apply */
char *name; /* column, constraint, or trigger to act on, char *name; /* column, constraint, or trigger to act on,
* or tablespace */ * or tablespace */
int16 num; /* attribute number for columns referenced int16 num; /* attribute number for columns referenced by
* by number */ * number */
RoleSpec *newowner; RoleSpec *newowner;
Node *def; /* definition of new column, index, Node *def; /* definition of new column, index,
* constraint, or parent table */ * constraint, or parent table */
...@@ -3098,12 +3098,26 @@ typedef enum VacuumOption ...@@ -3098,12 +3098,26 @@ typedef enum VacuumOption
VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7 /* don't skip any pages */ VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7 /* don't skip any pages */
} VacuumOption; } 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 typedef struct VacuumStmt
{ {
NodeTag type; NodeTag type;
int options; /* OR of VacuumOption flags */ int options; /* OR of VacuumOption flags */
RangeVar *relation; /* single table to process, or NULL */ List *rels; /* list of VacuumRelation, or NIL for all */
List *va_cols; /* list of column names, or NIL for all */
} VacuumStmt; } VacuumStmt;
/* ---------------------- /* ----------------------
......
...@@ -80,8 +80,6 @@ CONTEXT: SQL function "do_analyze" statement 1 ...@@ -80,8 +80,6 @@ CONTEXT: SQL function "do_analyze" statement 1
SQL function "wrap_do_analyze" statement 1 SQL function "wrap_do_analyze" statement 1
VACUUM FULL vactst; VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster; VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
DROP TABLE vaccluster;
DROP TABLE vactst;
-- partitioned table -- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a); CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1); CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
...@@ -95,4 +93,25 @@ VACUUM ANALYZE vacparted(a,b,a); ...@@ -95,4 +93,25 @@ VACUUM ANALYZE vacparted(a,b,a);
ERROR: column "a" of relation "vacparted" appears more than once ERROR: column "a" of relation "vacparted" appears more than once
ANALYZE vacparted(a,b,b); ANALYZE vacparted(a,b,b);
ERROR: column "b" of relation "vacparted" appears more than once 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; DROP TABLE vacparted;
...@@ -62,9 +62,6 @@ VACUUM FULL vactst; ...@@ -62,9 +62,6 @@ VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster; VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
DROP TABLE vaccluster;
DROP TABLE vactst;
-- partitioned table -- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a); CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1); CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
...@@ -78,4 +75,20 @@ VACUUM (FREEZE) vacparted; ...@@ -78,4 +75,20 @@ VACUUM (FREEZE) vacparted;
VACUUM ANALYZE vacparted(a,b,a); VACUUM ANALYZE vacparted(a,b,a);
ANALYZE vacparted(a,b,b); 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; DROP TABLE vacparted;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment